From 7021402a45c5e7dcc20ba7421150ce3172d1fdc1 Mon Sep 17 00:00:00 2001 From: "hello@openetech.com" Date: Wed, 16 Oct 2019 23:38:51 +0530 Subject: [PATCH 01/20] [feat] #19334 --- .../v12_0/set_permission_einvoicing.py | 12 + .../import_supplier_invoice/__init__.py | 0 .../import_supplier_invoice.js | 21 + .../import_supplier_invoice.json | 81 ++++ .../import_supplier_invoice.py | 383 ++++++++++++++++++ .../test_import_supplier_invoice.py | 10 + erpnext/regional/italy/setup.py | 25 ++ erpnext/regional/italy/utils.py | 2 + .../purchase_einvoice/__init__.py | 0 .../purchase_einvoice/purchase_einvoice.json | 23 ++ 10 files changed, 557 insertions(+) create mode 100644 erpnext/patches/v12_0/set_permission_einvoicing.py create mode 100644 erpnext/regional/doctype/import_supplier_invoice/__init__.py create mode 100644 erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js create mode 100644 erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.json create mode 100644 erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py create mode 100644 erpnext/regional/doctype/import_supplier_invoice/test_import_supplier_invoice.py create mode 100644 erpnext/regional/print_format/purchase_einvoice/__init__.py create mode 100644 erpnext/regional/print_format/purchase_einvoice/purchase_einvoice.json diff --git a/erpnext/patches/v12_0/set_permission_einvoicing.py b/erpnext/patches/v12_0/set_permission_einvoicing.py new file mode 100644 index 0000000000..c01f34a70d --- /dev/null +++ b/erpnext/patches/v12_0/set_permission_einvoicing.py @@ -0,0 +1,12 @@ +import frappe +from frappe.permissions import add_permission, update_permission_property + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'Italy'}) + + if not company: + return + + add_permission('Import Supplier Invoice', 'Accounts Manager', 0) + update_permission_property('Import Supplier Invoice', 'Accounts Manager', 0, 'write', 1) + update_permission_property('Import Supplier Invoice', 'Accounts Manager', 0, 'create', 1) \ No newline at end of file diff --git a/erpnext/regional/doctype/import_supplier_invoice/__init__.py b/erpnext/regional/doctype/import_supplier_invoice/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js new file mode 100644 index 0000000000..efd55db4de --- /dev/null +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js @@ -0,0 +1,21 @@ +// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +// License: GNU General Public License v3. See license.txt + +frappe.ui.form.on('Import Supplier Invoice', { + onload: function(frm) { + frappe.realtime.on("import_invoice_update", function (data) { + frm.dashboard.show_progress(data.title, (data.count / data.total) * 100, data.message); + if (data.count == data.total) { + window.setTimeout(title => frm.dashboard.hide_progress(title), 1500, data.title); + } + }); + + frm.set_query("tax_account", function() { + return { + filters: { + account_type: 'Tax' + } + } + }); + } +}); \ No newline at end of file diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.json b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.json new file mode 100644 index 0000000000..6d28eac341 --- /dev/null +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.json @@ -0,0 +1,81 @@ +{ + "creation": "2019-10-15 12:33:21.845329", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "company", + "item_code", + "supplier_group", + "tax_account", + "column_break_5", + "zip_file", + "import_invoices", + "status" + ], + "fields": [ + { + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "reqd": 1 + }, + { + "fieldname": "item_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Code", + "options": "Item", + "reqd": 1 + }, + { + "fieldname": "supplier_group", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Supplier Group", + "options": "Supplier Group", + "reqd": 1 + }, + { + "fieldname": "tax_account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Tax Account", + "options": "Account", + "reqd": 1 + }, + { + "fieldname": "column_break_5", + "fieldtype": "Column Break" + }, + { + "fieldname": "zip_file", + "fieldtype": "Attach", + "label": "Zip File" + }, + { + "description": "Click on Import Invoices button once the zip file has been attached to the document. Any errors related to processing will be shown in the Error Log.", + "fieldname": "import_invoices", + "fieldtype": "Button", + "label": "Import Invoices", + "options": "process_file_data" + }, + { + "fieldname": "status", + "fieldtype": "Data", + "label": "Status", + "read_only": 1 + } + ], + "modified": "2019-10-16 02:00:00.538268", + "modified_by": "Administrator", + "module": "Regional", + "name": "Import Supplier Invoice", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py new file mode 100644 index 0000000000..3b43fe7771 --- /dev/null +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py @@ -0,0 +1,383 @@ +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals + +from decimal import Decimal +import json +import re +import traceback +import zipfile +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.custom.doctype.custom_field.custom_field import create_custom_field +from frappe.utils.data import format_datetime +from bs4 import BeautifulSoup as bs +from frappe.utils import cint, flt, today, nowdate, add_days, get_files_path +import dateutil +from frappe.utils.file_manager import save_file + +class ImportSupplierInvoice(Document): + + def autoname(self): + if not self.name: + self.name = "Import Invoice on " + format_datetime(self.creation) + + def import_xml_data(self): + import_file = frappe.get_doc("File", {"file_url": self.zip_file}) + self.publish("File Import", _("Processing XML Files"), 1, 3) + pi_count = 0 + with zipfile.ZipFile(get_full_path(self.zip_file)) as zf: + file_count = 0 + for file_name in zf.namelist(): + items = [] + taxes = [] + terms = [] + encoded_content = zf.read(file_name) + try: + content = encoded_content.decode("utf-8-sig") + except UnicodeDecodeError: + content = encoded_content.decode("utf-16") + file_content = bs(content, "xml") + + for line in file_content.find_all("DatiTrasmissione"): + destination_code = line.CodiceDestinatario.text + + for line in file_content.find_all("DatiGeneraliDocumento"): + document_type = line.TipoDocumento.text + bill_date = dateutil.parser.parse(line.Data.text).strftime("%Y-%m-%d") + invoice_no = line.Numero.text + if len(invoice_no) != 0: + for line in file_content.find_all("CedentePrestatore"): + tax_id = line.DatiAnagrafici.IdPaese.text + line.DatiAnagrafici.IdCodice.text + if line.find("CodiceFiscale"): + fiscal_code = line.DatiAnagrafici.CodiceFiscale.text + else: + fiscal_code = "" + if line.find("RegimeFiscale"): + fiscal_regime = line.DatiAnagrafici.RegimeFiscale.text + else: + fiscal_regime = "" + if line.find("Denominazione"): + supplier = line.DatiAnagrafici.Anagrafica.Denominazione.text + if line.find("Nome"): + supplier = line.DatiAnagrafici.Anagrafica.Nome.text + " " + line.DatiAnagrafici.Anagrafica.Cognome.text + address_line1 = line.Sede.Indirizzo.text + city = line.Sede.Comune.text + if line.find("Provincia"): + province = line.Sede.Provincia.text + else: + province = "" + pin_code = line.Sede.CAP.text + country = get_country(line.Sede.Nazione.text) + + for line in file_content.find_all("DettaglioLinee"): + if line.find("PrezzoUnitario") and line.find("PrezzoTotale"): + unit_rate = float(line.PrezzoUnitario.text) or float(0) + line_total = float(line.PrezzoTotale.text) or float(0) + + if (unit_rate == 0.0): + qty = float(1) + uom = "nos" + rate = tax_rate = 0 + else: + if (line_total / unit_rate) == 1.0: + qty = float(1) + uom = "nos" + else: + if line.find("Quantita"): + qty = float(line.Quantita.text) or float(0) + if line.find("UnitaMisura"): + uom = create_uom(line.UnitaMisura.text) + else: + uom = "nos" + + if (unit_rate < 0 and line_total < 0): + qty *= -1 + return_invoice = 1 + else: + return_invoice = 0 + + rate = unit_rate + if line.find("AliquotaIVA"): + tax_rate = float(line.AliquotaIVA.text) + + line_str = re.sub('[^A-Za-z0-9]+', '-', line.Descrizione.text) + item_name = line_str[0:140] + items.append({ + "item_code": self.item_code, + "item_name": item_name, + "description": line_str, + "qty": float(qty), + "uom": uom, + "rate": rate, + "conversion_factor": float(1), + "tax_rate": tax_rate + }) + + for line in file_content.find_all("DatiRiepilogo"): + if line.find("AliquotaIVA"): + if line.find("EsigibilitaIVA"): + descr = line.EsigibilitaIVA.text + else: + descr = "None" + taxes.append({ + "charge_type": "Actual", + "account_head": self.tax_account, + "tax_rate": float(line.AliquotaIVA.text) or float(0), + "description": descr, + "tax_amount": float(line.Imposta.text) if len(line.find("Imposta"))!=0 else float(0) + }) + + mop_dict = {'MP01':"MP01-Contanti", 'MP02':"MP02-Assegno", 'MP03':"MP03-Assegno circolare", 'MP04':"MP04-Contanti presso Tesoreria", + 'MP05':"MP05-Bonifico", 'MP06': "MP06-Vaglia cambiario", 'MP07': "MP07-Bollettino bancario", 'MP08': "MP08-Carta di pagamento", + 'MP09':"MP09-RID", 'MP10': "MP10-RID utenze", 'MP11': "MP11-RID veloce", 'MP12':"MP12-RIBA", 'MP13':"MP13-MAV", + 'MP14':"MP14-Quietanza erario", 'MP15':"MP15-Giroconto su conti di contabilità speciale", 'MP16':"MP16-Domiciliazione bancaria", + 'MP17': "MP17-Domiciliazione postale", 'MP18': "MP18-Bollettino di c/c postale", 'MP19': "MP19-SEPA Direct Debit", + 'MP20': "MP20-SEPA Direct Debit CORE", 'MP21': "MP21-SEPA Direct Debit B2B", 'MP22':"MP22-Trattenuta su somme già riscosse"} + + for line in file_content.find_all("DettaglioPagamento"): + mop_code = mop_dict.get(line.ModalitaPagamento.text) + + if line.find("DataScadenzaPagamento"): + due_date = dateutil.parser.parse(line.DataScadenzaPagamento.text).strftime("%Y-%m-%d") + else: + due_date = today() + terms.append({ + "mode_of_payment_code": mop_code, + "bank_account_iban": line.IBAN.text if line.find("IBAN") else "", + "due_date": due_date, + "payment_amount": line.ImportoPagamento.text + }) + + supplier_name = create_supplier(supplier = supplier, supplier_group = self.supplier_group, + tax_id = tax_id, fiscal_code = fiscal_code, + fiscal_regime = fiscal_regime) + + address = create_address(supplier_name = supplier_name, address_line1 = address_line1, + city = city, province = province, + pin_code = pin_code, country = country) + + pi_name = create_purchase_invoice(company = self.company, + supplier_name = supplier_name, + bill_no = invoice_no, + document_type = document_type, + bill_date = bill_date, + is_return = return_invoice, + destination_code = destination_code, + items = items, + taxes = taxes, + terms = terms, + file_name = file_name) + file_count += 1 + if pi_name: + pi_count += 1 + file_save = save_file(file_name, encoded_content, "Purchase Invoice", pi_name, folder=None, decode=False, is_private=0, df=None) + + if pi_count == file_count: + self.status = "File Import Completed" + self.publish("File Import", _("XML Files Processed"), 2, 3) + else: + self.status = "Partially Completed - Check Error Log" + self.publish("File Import", _("XML Files Processed"), 2, 3) + + self.save() + self.publish("File Import", _("XML Files Processed"), 3, 3) + + def process_file_data(self): + self.status = "Processing File Data" + self.save() + frappe.enqueue_doc(self.doctype, self.name, "import_xml_data", queue="long", timeout=3600) + + def publish(self, title, message, count, total): + frappe.publish_realtime("import_invoice_update", {"title": title, "message": message, "count": count, "total": total}) + +def create_supplier(**args): + + args = frappe._dict(args) + existing_supplier_name = frappe.db.get_value("Supplier", + filters={"tax_id": args.tax_id}, fieldname="name") + if existing_supplier_name: + pass + else: + existing_supplier_name = frappe.db.get_value("Supplier", + filters={"name": args.supplier}, fieldname="name") + + if existing_supplier_name: + filters = [ + ["Dynamic Link", "link_doctype", "=", "Supplier"], + ["Dynamic Link", "link_name", "=", args.existing_supplier_name], + ["Dynamic Link", "parenttype", "=", "Contact"] + ] + + existing_contacts = frappe.get_list("Contact", filters) + + if existing_contacts: + pass + else: + new_contact = frappe.new_doc("Contact") + new_contact.first_name = args.supplier + new_contact.append('links', { + "link_doctype": "Supplier", + "link_name": existing_supplier_name + }) + new_contact.insert() + + return existing_supplier_name + else: + + new_supplier = frappe.new_doc("Supplier") + new_supplier.supplier_name = args.supplier + new_supplier.supplier_group = args.supplier_group + new_supplier.tax_id = args.tax_id + new_supplier.fiscal_code = args.fiscal_code + new_supplier.fiscal_regime = args.fiscal_regime + new_supplier.save() + + new_contact = frappe.new_doc("Contact") + new_contact.first_name = args.supplier + new_contact.append('links', { + "link_doctype": "Supplier", + "link_name": new_supplier.name + }) + + new_contact.insert() + + return new_supplier.name + +def create_address(**args): + + args = frappe._dict(args) + filters = [ + ["Dynamic Link", "link_doctype", "=", "Supplier"], + ["Dynamic Link", "link_name", "=", args.supplier_name], + ["Dynamic Link", "parenttype", "=", "Address"] + ] + + existing_address = frappe.get_list("Address", filters) + + if args.address_line1: + make_address = frappe.new_doc("Address") + make_address.address_line1 = args.address_line1 + + if args.city: + make_address.city = args.city + else: + make_address.city = "Not Provided" + + if args.province: + make_address.state_code = args.province + + if args.pincode: + make_address.pincode = args.pincode + + if args.country: + make_address.country = args.country + + for address in existing_address: + address_doc = frappe.get_doc("Address", address["name"]) + if (address_doc.address_line1 == make_address.address_line1 and + address_doc.pincode == make_address.pincode): + return address + + make_address.append("links", { + "link_doctype": "Supplier", + "link_name": args.supplier_name + }) + make_address.address_type = "Billing" + make_address.insert() + return make_address.name + else: + return None + +def create_purchase_invoice(**args): + + args = frappe._dict(args) + pi = frappe.get_doc({ + "doctype": "Purchase Invoice", + "company": args.company, + "naming_series": "PINV-", + "supplier": args.supplier_name, + "is_return": args.is_return, + "posting_date": today(), + "bill_no": args.bill_no, + "bill_date": args.bill_date, + "destination_code": args.destination_code, + "document_type": args.document_type, + "items": args["items"], + "taxes": args["taxes"] + }) + + try: + pi.insert(ignore_permissions=True) + calc_total = 0 + adj = 0 + for term in args.terms: + calc_total += float(term["payment_amount"]) + if float(calc_total - float(pi.grand_total)) != 0: + adj = calc_total - float(pi.grand_total) + pi.payment_schedule = [] + for term in args.terms: + pi.append('payment_schedule',{"mode_of_payment_code": term["mode_of_payment_code"], + "bank_account_iban": term["bank_account_iban"], + "due_date": term["due_date"], + "payment_amount": float(term["payment_amount"]) - adj }) + adj = 0 + pi.imported_grand_total = calc_total + pi.save() + return pi.name + except Exception as e: + frappe.log_error(message=e, title="Create Purchase Invoice: " + args.bill_no + "File Name: " + args.file_name) + return None + +def get_country(code): + + existing_country_name = frappe.db.get_value("Country", + filters={"code": code}, fieldname="name") + if existing_country_name: + return existing_country_name + else: + frappe.throw(_("Country Code in File does not match with country code set up in the system")) + +def create_uom(uom): + + existing_uom = frappe.db.get_value("UOM", + filters={"uom_name": uom}, fieldname="uom_name") + if existing_uom: + return existing_uom + else: + new_uom = frappe.new_doc("UOM") + new_uom.uom_name = uom + new_uom.save() + return new_uom.uom_name + +def check_bill_no(invoice_no): + + existing_bill_no = frappe.db.get_value("Purchase Invoice", + filters={"bill_no": invoice_no, "docstatus": 1}, fieldname="name") + if existing_bill_no: + return existing_bill_no + else: + return None + +def get_full_path(file_name): + """Returns file path from given file name""" + file_path = file_name + + if "/" not in file_path: + file_path = "/files/" + file_path + + if file_path.startswith("/private/files/"): + file_path = get_files_path(*file_path.split("/private/files/", 1)[1].split("/"), is_private=1) + + elif file_path.startswith("/files/"): + file_path = get_files_path(*file_path.split("/files/", 1)[1].split("/")) + + elif file_path.startswith("http"): + pass + + elif not self.file_url: + frappe.throw(_("There is some problem with the file url: {0}").format(file_path)) + + return file_path \ No newline at end of file diff --git a/erpnext/regional/doctype/import_supplier_invoice/test_import_supplier_invoice.py b/erpnext/regional/doctype/import_supplier_invoice/test_import_supplier_invoice.py new file mode 100644 index 0000000000..d1caf77fc2 --- /dev/null +++ b/erpnext/regional/doctype/import_supplier_invoice/test_import_supplier_invoice.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestImportSupplierInvoice(unittest.TestCase): + pass diff --git a/erpnext/regional/italy/setup.py b/erpnext/regional/italy/setup.py index 1526d6f62f..2d0ad66b0a 100644 --- a/erpnext/regional/italy/setup.py +++ b/erpnext/regional/italy/setup.py @@ -155,6 +155,31 @@ def make_custom_fields(update=True): fetch_from="country.code"), dict(fieldname='state_code', label='State Code', fieldtype='Data', insert_after='state', print_hide=1) + ], + 'Purchase Invoice': [ + dict(fieldname='document_type', label='Document Type', + fieldtype='Data', insert_after='company', print_hide=1, read_only=1 + ), + dict(fieldname='destination_code', label='Destination Code', + fieldtype='Data', insert_after='company', print_hide=1, read_only=1 + ), + dict(fieldname='imported_grand_total', label='Imported Grand Total', + fieldtype='Data', insert_after='update_auto_repeat_reference', print_hide=1, read_only=1 + ) + ], + 'Purchase Taxes and Charges': [ + dict(fieldname='tax_rate', label='Tax Rate', + fieldtype='Data', insert_after='parenttype', print_hide=1, read_only=0 + ) + ], + 'Supplier': [ + dict(fieldname='fiscal_code', label='Fiscal Code', + fieldtype='Data', insert_after='tax_id', print_hide=1, read_only=1 + ), + dict(fieldname='fiscal_regime', label='Fiscal Regime', + fieldtype='Select', insert_after='fiscal_code', print_hide=1, read_only=1, + options= "\nRF01\nRF02\nRF04\nRF05\nRF06\nRF07\nRF08\nRF09\nRF10\nRF11\nRF12\nRF13\nRF14\nRF15\nRF16\nRF17\nRF18\nRF19" + ) ] } diff --git a/erpnext/regional/italy/utils.py b/erpnext/regional/italy/utils.py index bc8d00d8b8..2af72f8e27 100644 --- a/erpnext/regional/italy/utils.py +++ b/erpnext/regional/italy/utils.py @@ -13,6 +13,8 @@ from erpnext.regional.italy import state_codes def update_itemised_tax_data(doc): if not doc.taxes: return + if doc.doctype == "Purchase Invoice": return + itemised_tax = get_itemised_tax(doc.taxes) for row in doc.items: diff --git a/erpnext/regional/print_format/purchase_einvoice/__init__.py b/erpnext/regional/print_format/purchase_einvoice/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/regional/print_format/purchase_einvoice/purchase_einvoice.json b/erpnext/regional/print_format/purchase_einvoice/purchase_einvoice.json new file mode 100644 index 0000000000..88f31dd130 --- /dev/null +++ b/erpnext/regional/print_format/purchase_einvoice/purchase_einvoice.json @@ -0,0 +1,23 @@ +{ + "align_labels_right": 0, + "creation": "2019-10-16 00:47:08.877767", + "custom_format": 0, + "disabled": 1, + "doc_type": "Purchase Invoice", + "docstatus": 0, + "doctype": "Print Format", + "font": "Default", + "format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"Custom HTML\", \"options\": \"
\\t\\t\\t\\t

Purchase Invoice
{{ doc.name }}\\t\\t\\t\\t

\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
\\nCedente/prestatore (fornitore)\\n\\nCessionario/committente (cliente)\\n
\\n

Identificstivo fiscale ai fini IVA: {{frappe.db.get_value(\\\"Supplier\\\", doc.supplier, \\\"tax_id\\\")}}

\\n

Codice fiscale: {{frappe.db.get_value(\\\"Supplier\\\", doc.supplier, \\\"fiscal_code\\\")}}

\\n

Denominazione: {{frappe.db.get_value(\\\"Supplier\\\", doc.supplier, \\\"supplier_name\\\")}}

\\n

Regime fiscale: {{frappe.db.get_value(\\\"Supplier\\\", doc.supplier, \\\"fiscal_regime\\\")}}

\\n

Indrizo: {{frappe.db.get_value(\\\"Address\\\", doc.supplier_address, \\\"address_line1\\\")}}

\\n

Commune: {{frappe.db.get_value(\\\"Address\\\", doc.supplier_address, \\\"city\\\")}} Provincia: {{frappe.db.get_value(\\\"Address\\\", doc.supplier_address, \\\"state_code\\\")}}

\\n

Cap: {{(frappe.db.get_value(\\\"Address\\\", doc.supplier_address, \\\"pincode\\\")) or \\\" \\\"}} Nazione: {{frappe.db.get_value(\\\"Address\\\", doc.supplier_address, \\\"country\\\")}}

\\n
\\n

Identificstivo fiscale ai fini IVA: {{frappe.db.get_value(\\\"Company\\\", doc.company, \\\"tax_id\\\")}}

\\n

Codice fiscale: {{frappe.db.get_value(\\\"Company\\\", doc.company, \\\"fiscal_code\\\")}}

\\n

Denominazione: {{doc.company}}

\\n

Indrizo: {{frappe.db.get_value(\\\"Address\\\", doc.shipping_address, \\\"address_line1\\\")}}

\\n

Commune: {{frappe.db.get_value(\\\"Address\\\", doc.shipping_address, \\\"city\\\")}} Provincia: {{frappe.db.get_value(\\\"Address\\\", doc.shipping_address, \\\"state_code\\\")}}

\\n

Cap: {{frappe.db.get_value(\\\"Address\\\", doc.shipping_address, \\\"pincode\\\")}} Nazione: {{frappe.db.get_value(\\\"Address\\\", doc.shipping_address, \\\"country\\\")}}

\\n
\\n
\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
\\nTipologla\\n\\nArt. 73\\n\\nNumero documento\\n\\nData documento\\n\\nCodice destinatario\\n
\\n{{doc.document_type or \\\" \\\"}}\\n\\n{{\\\" \\\"}}\\n\\n{{doc.bill_no or \\\" \\\"}}\\n\\n{{doc.get_formatted(\\\"bill_date\\\") or \\\" \\\"}}\\n\\n{{doc.destination_code or \\\" \\\"}}\\n
\\n
\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n{%- for row in doc.items -%}\\n\\n\\n\\n\\n\\n\\n\\n{%- endfor -%}\\n\\n
\\nDescrizione\\n\\nQuantita\\n\\nPrezzo unitario\\n\\nUM\\n\\n%IVA\\n\\nPrezzo totale\\n
\\n{{row.description or \\\" \\\"}}\\n\\n{{row.get_formatted(\\\"qty\\\", doc)}}\\n\\n{{row.get_formatted(\\\"rate\\\", doc)}}\\n\\n{{row.get_formatted(\\\"uom\\\", doc)}}\\n\\n{{row.get_formatted(\\\"tax_rate\\\", doc)}}\\n\\n{{row.get_formatted(\\\"amount\\\", doc)}}\\n
\\n
\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n{%- for row in doc.taxes -%}\\n\\n\\n\\n\\n\\n\\n\\n{%- endfor -%}\\n\\n
\\nesigibilita immediata / riferimenti normativi\\n\\n%IVA\\n\\nSpese accessorie\\n\\nArr.\\n\\nTotale imponibile\\n\\nTotale Imposta\\n
\\n{% if 'None' in row.description %}\\n {{ \\\" \\\" }}\\n{% else %}\\n{{row.description}}\\n{% endif %}\\n\\n{{row.get_formatted(\\\"tax_rate\\\", doc)}}\\n\\n{{\\\"0,00\\\"}}\\n\\n{{\\\" \\\"}}\\n\\n{{doc.get_formatted(\\\"base_net_total\\\")}}\\n\\n{{row.get_formatted(\\\"tax_amount\\\", doc)}}\\n
\\n
\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
\\nImporto bolio\\n\\nSconto/Magglorazione\\n\\nArr.\\n\\nTotale documento\\n
\\n{{\\\" \\\"}}\\n\\n{{\\\" \\\"}}\\n\\n{{\\\" \\\"}}\\n\\n{{doc.get_formatted(\\\"base_grand_total\\\")}}\\n
\\n
\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n{%- for row in doc.payment_schedule -%}\\n\\n\\n\\n\\n\\n\\n{%- endfor -%}\\n\\n
\\nModalita pagamento\\n\\nIBAN\\n\\nInstituto\\n\\nData scadenza\\n\\nImporto\\n
\\n{{row.get_formatted(\\\"mode_of_payment_code\\\",doc)}}\\n\\n{{row.get_formatted(\\\"bank_account_iban\\\",doc)}}\\n\\n{{\\\" \\\"}}\\n\\n{{row.get_formatted(\\\"due_date\\\",doc)}}\\n\\n{{row.get_formatted(\\\"payment_amount\\\",doc)}}\\n
\"}]", + "idx": 0, + "line_breaks": 0, + "modified": "2019-10-16 23:32:37.709344", + "modified_by": "Administrator", + "module": "Regional", + "name": "Purchase eInvoice", + "owner": "Administrator", + "print_format_builder": 1, + "print_format_type": "Jinja", + "raw_printing": 0, + "show_section_headings": 0, + "standard": "Yes" +} \ No newline at end of file From 9964ad787002b4f9b0794280bc6294afa89b6fae Mon Sep 17 00:00:00 2001 From: "hello@openetech.com" Date: Thu, 17 Oct 2019 07:38:50 +0530 Subject: [PATCH 02/20] python 2.7 fixes --- .../import_supplier_invoice.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py index 3b43fe7771..8b0c894bcf 100644 --- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py @@ -27,7 +27,12 @@ class ImportSupplierInvoice(Document): def import_xml_data(self): import_file = frappe.get_doc("File", {"file_url": self.zip_file}) self.publish("File Import", _("Processing XML Files"), 1, 3) + pi_count = 0 + mop_options = frappe.get_meta('Mode of Payment').fields['4'].options + mop_str = re.sub('\n', ',', mop_options) + mop_dict = dict(item.split("-") for item in mop_str.split(",")) + with zipfile.ZipFile(get_full_path(self.zip_file)) as zf: file_count = 0 for file_name in zf.namelist(): @@ -129,16 +134,9 @@ class ImportSupplierInvoice(Document): "description": descr, "tax_amount": float(line.Imposta.text) if len(line.find("Imposta"))!=0 else float(0) }) - - mop_dict = {'MP01':"MP01-Contanti", 'MP02':"MP02-Assegno", 'MP03':"MP03-Assegno circolare", 'MP04':"MP04-Contanti presso Tesoreria", - 'MP05':"MP05-Bonifico", 'MP06': "MP06-Vaglia cambiario", 'MP07': "MP07-Bollettino bancario", 'MP08': "MP08-Carta di pagamento", - 'MP09':"MP09-RID", 'MP10': "MP10-RID utenze", 'MP11': "MP11-RID veloce", 'MP12':"MP12-RIBA", 'MP13':"MP13-MAV", - 'MP14':"MP14-Quietanza erario", 'MP15':"MP15-Giroconto su conti di contabilità speciale", 'MP16':"MP16-Domiciliazione bancaria", - 'MP17': "MP17-Domiciliazione postale", 'MP18': "MP18-Bollettino di c/c postale", 'MP19': "MP19-SEPA Direct Debit", - 'MP20': "MP20-SEPA Direct Debit CORE", 'MP21': "MP21-SEPA Direct Debit B2B", 'MP22':"MP22-Trattenuta su somme già riscosse"} for line in file_content.find_all("DettaglioPagamento"): - mop_code = mop_dict.get(line.ModalitaPagamento.text) + mop_code = line.ModalitaPagamento.text + '-' + mop_dict.get(line.ModalitaPagamento.text) if line.find("DataScadenzaPagamento"): due_date = dateutil.parser.parse(line.DataScadenzaPagamento.text).strftime("%Y-%m-%d") From fa8159efa3efef6ac16120c7caa3441d9d196358 Mon Sep 17 00:00:00 2001 From: "hello@openetech.com" Date: Thu, 17 Oct 2019 07:45:23 +0530 Subject: [PATCH 03/20] mop options fix --- .../doctype/import_supplier_invoice/import_supplier_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py index 8b0c894bcf..5144be1ae7 100644 --- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py @@ -29,7 +29,7 @@ class ImportSupplierInvoice(Document): self.publish("File Import", _("Processing XML Files"), 1, 3) pi_count = 0 - mop_options = frappe.get_meta('Mode of Payment').fields['4'].options + mop_options = frappe.get_meta('Mode of Payment').fields[4].options mop_str = re.sub('\n', ',', mop_options) mop_dict = dict(item.split("-") for item in mop_str.split(",")) From 5fadd355dfab01cc8883d5b0b179cf5d10f79a3d Mon Sep 17 00:00:00 2001 From: "hello@openetech.com" Date: Sat, 19 Oct 2019 00:19:21 +0530 Subject: [PATCH 04/20] add naming series option --- .../import_supplier_invoice.json | 10 +++++++++- .../import_supplier_invoice/import_supplier_invoice.py | 3 ++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.json b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.json index 6d28eac341..aabf9de3cd 100644 --- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.json +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.json @@ -4,6 +4,7 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ + "invoice_series", "company", "item_code", "supplier_group", @@ -67,9 +68,16 @@ "fieldtype": "Data", "label": "Status", "read_only": 1 + }, + { + "fieldname": "invoice_series", + "fieldtype": "Select", + "label": "Invoice Series", + "options": "ACC-PINV-.YYYY.-", + "reqd": 1 } ], - "modified": "2019-10-16 02:00:00.538268", + "modified": "2019-10-19 00:15:11.404733", "modified_by": "Administrator", "module": "Regional", "name": "Import Supplier Invoice", diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py index 5144be1ae7..dc580ad24b 100644 --- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py @@ -158,6 +158,7 @@ class ImportSupplierInvoice(Document): pin_code = pin_code, country = country) pi_name = create_purchase_invoice(company = self.company, + naming_series = self.invoice_series, supplier_name = supplier_name, bill_no = invoice_no, document_type = document_type, @@ -295,7 +296,7 @@ def create_purchase_invoice(**args): pi = frappe.get_doc({ "doctype": "Purchase Invoice", "company": args.company, - "naming_series": "PINV-", + "naming_series": args.naming_series, "supplier": args.supplier_name, "is_return": args.is_return, "posting_date": today(), From 309913bbdf61f6bbab4da2194c77a9aee434735a Mon Sep 17 00:00:00 2001 From: "hello@openetech.com" Date: Sat, 19 Oct 2019 07:23:22 +0530 Subject: [PATCH 05/20] fix codacy issues --- .../import_supplier_invoice/import_supplier_invoice.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js index efd55db4de..f347cd7bd8 100644 --- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js @@ -11,11 +11,11 @@ frappe.ui.form.on('Import Supplier Invoice', { }); frm.set_query("tax_account", function() { - return { - filters: { - account_type: 'Tax' + return { + filters: { + account_type: 'Tax' } - } - }); + }; + }); } }); \ No newline at end of file From c8e64235c0cb21fe66313ebf0fb6ccd82c81901b Mon Sep 17 00:00:00 2001 From: "hello@openetech.com" Date: Sat, 19 Oct 2019 08:05:05 +0530 Subject: [PATCH 06/20] codacy fixes --- .../import_supplier_invoice/import_supplier_invoice.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js index f347cd7bd8..3669b6f483 100644 --- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js @@ -12,9 +12,7 @@ frappe.ui.form.on('Import Supplier Invoice', { frm.set_query("tax_account", function() { return { - filters: { - account_type: 'Tax' - } + filters: { account_type: 'Tax' } }; }); } From 90f170c376e4e193ebbed3cb4fbe7a67c4fc38b7 Mon Sep 17 00:00:00 2001 From: "hello@openetech.com" Date: Sat, 19 Oct 2019 12:00:25 +0530 Subject: [PATCH 07/20] code improvement --- .../doctype/import_supplier_invoice/import_supplier_invoice.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js index 3669b6f483..bbc0f92cc7 100644 --- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js @@ -9,7 +9,8 @@ frappe.ui.form.on('Import Supplier Invoice', { window.setTimeout(title => frm.dashboard.hide_progress(title), 1500, data.title); } }); - + }, + setup: function(frm) { frm.set_query("tax_account", function() { return { filters: { account_type: 'Tax' } From 5b33765a8c155fa050f4c0406b56a3b72f16b479 Mon Sep 17 00:00:00 2001 From: "hello@openetech.com" Date: Mon, 21 Oct 2019 18:54:08 +0530 Subject: [PATCH 08/20] discount handling --- .../import_supplier_invoice.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py index dc580ad24b..293d6c908e 100644 --- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py @@ -77,6 +77,8 @@ class ImportSupplierInvoice(Document): pin_code = line.Sede.CAP.text country = get_country(line.Sede.Nazione.text) + total_discount = 0 + for line in file_content.find_all("DettaglioLinee"): if line.find("PrezzoUnitario") and line.find("PrezzoTotale"): unit_rate = float(line.PrezzoUnitario.text) or float(0) @@ -121,6 +123,11 @@ class ImportSupplierInvoice(Document): "tax_rate": tax_rate }) + for disc_line in line.find_all("ScontoMaggiorazione"): + if disc_line.find("Percentuale"): + discount = float(disc_line.Percentuale.text) or float(0) + total_discount += float((discount / 100) * (rate * qty)) + for line in file_content.find_all("DatiRiepilogo"): if line.find("AliquotaIVA"): if line.find("EsigibilitaIVA"): @@ -165,6 +172,7 @@ class ImportSupplierInvoice(Document): bill_date = bill_date, is_return = return_invoice, destination_code = destination_code, + total_discount = total_discount, items = items, taxes = taxes, terms = terms, @@ -310,6 +318,12 @@ def create_purchase_invoice(**args): try: pi.insert(ignore_permissions=True) + + if args.total_discount > 0: + pi.apply_discount_on = "Grand Total" + pi.discount_amount = args.total_discount + pi.save() + calc_total = 0 adj = 0 for term in args.terms: From 9e0762e8c2c3643268c98c62253fa5a7210af71b Mon Sep 17 00:00:00 2001 From: "hello@openetech.com" Date: Wed, 23 Oct 2019 19:14:14 +0530 Subject: [PATCH 09/20] code review changes --- .../import_supplier_invoice.py | 29 +++++-------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py index 293d6c908e..33884787a2 100644 --- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py @@ -164,19 +164,13 @@ class ImportSupplierInvoice(Document): city = city, province = province, pin_code = pin_code, country = country) - pi_name = create_purchase_invoice(company = self.company, - naming_series = self.invoice_series, - supplier_name = supplier_name, - bill_no = invoice_no, - document_type = document_type, - bill_date = bill_date, - is_return = return_invoice, - destination_code = destination_code, - total_discount = total_discount, - items = items, - taxes = taxes, - terms = terms, - file_name = file_name) + pi_name = create_purchase_invoice(company = self.company, naming_series = self.invoice_series, + supplier_name = supplier_name, bill_no = invoice_no, + document_type = document_type, bill_date = bill_date, + is_return = return_invoice, destination_code = destination_code, + total_discount = total_discount, items = items, + taxes = taxes, terms = terms,file_name = file_name) + file_count += 1 if pi_name: pi_count += 1 @@ -365,15 +359,6 @@ def create_uom(uom): new_uom.save() return new_uom.uom_name -def check_bill_no(invoice_no): - - existing_bill_no = frappe.db.get_value("Purchase Invoice", - filters={"bill_no": invoice_no, "docstatus": 1}, fieldname="name") - if existing_bill_no: - return existing_bill_no - else: - return None - def get_full_path(file_name): """Returns file path from given file name""" file_path = file_name From eb87566fa627e5794aaf335caa30faa09c2f7143 Mon Sep 17 00:00:00 2001 From: "hello@openetech.com" Date: Sat, 9 Nov 2019 16:28:54 +0530 Subject: [PATCH 10/20] log encoding exception --- .../import_supplier_invoice/import_supplier_invoice.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py index 33884787a2..e28d305e86 100644 --- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py @@ -40,10 +40,15 @@ class ImportSupplierInvoice(Document): taxes = [] terms = [] encoded_content = zf.read(file_name) + try: content = encoded_content.decode("utf-8-sig") except UnicodeDecodeError: - content = encoded_content.decode("utf-16") + try: + content = encoded_content.decode("utf-16") + except UnicodeDecodeError as e: + frappe.log_error(message=e, title="UTF-16 encoding error for File Name: " + file_name) + file_content = bs(content, "xml") for line in file_content.find_all("DatiTrasmissione"): From e9377e25131891a79f959a9dc90299fa4a4a2799 Mon Sep 17 00:00:00 2001 From: "hello@openetech.com" Date: Mon, 11 Nov 2019 16:14:28 +0530 Subject: [PATCH 11/20] review changes --- .../import_supplier_invoice.py | 101 ++++++++++-------- 1 file changed, 54 insertions(+), 47 deletions(-) diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py index e28d305e86..f65c7f169a 100644 --- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py @@ -33,6 +33,11 @@ class ImportSupplierInvoice(Document): mop_str = re.sub('\n', ',', mop_options) mop_dict = dict(item.split("-") for item in mop_str.split(",")) + default_uom = frappe.db.get_value("Stock Settings", fieldname="stock_uom") + + if not default_uom: + frappe.throw(_("Please set default UOM in Stock Settings")) + with zipfile.ZipFile(get_full_path(self.zip_file)) as zf: file_count = 0 for file_name in zf.namelist(): @@ -54,6 +59,7 @@ class ImportSupplierInvoice(Document): for line in file_content.find_all("DatiTrasmissione"): destination_code = line.CodiceDestinatario.text + #read address information from file for line in file_content.find_all("DatiGeneraliDocumento"): document_type = line.TipoDocumento.text bill_date = dateutil.parser.parse(line.Data.text).strftime("%Y-%m-%d") @@ -84,26 +90,27 @@ class ImportSupplierInvoice(Document): total_discount = 0 + #read item information from file for line in file_content.find_all("DettaglioLinee"): if line.find("PrezzoUnitario") and line.find("PrezzoTotale"): - unit_rate = float(line.PrezzoUnitario.text) or float(0) - line_total = float(line.PrezzoTotale.text) or float(0) + unit_rate = flt(line.PrezzoUnitario.text) or 0 + line_total = flt(line.PrezzoTotale.text) or 0 if (unit_rate == 0.0): - qty = float(1) - uom = "nos" + qty = 1.0 + uom = default_uom rate = tax_rate = 0 else: if (line_total / unit_rate) == 1.0: - qty = float(1) - uom = "nos" + qty = 1.0 + uom = default_uom else: if line.find("Quantita"): - qty = float(line.Quantita.text) or float(0) + qty = flt(line.Quantita.text) or 0 if line.find("UnitaMisura"): uom = create_uom(line.UnitaMisura.text) else: - uom = "nos" + uom = default_uom if (unit_rate < 0 and line_total < 0): qty *= -1 @@ -113,7 +120,7 @@ class ImportSupplierInvoice(Document): rate = unit_rate if line.find("AliquotaIVA"): - tax_rate = float(line.AliquotaIVA.text) + tax_rate = flt(line.AliquotaIVA.text) line_str = re.sub('[^A-Za-z0-9]+', '-', line.Descrizione.text) item_name = line_str[0:140] @@ -121,18 +128,19 @@ class ImportSupplierInvoice(Document): "item_code": self.item_code, "item_name": item_name, "description": line_str, - "qty": float(qty), + "qty": qty, "uom": uom, "rate": rate, - "conversion_factor": float(1), + "conversion_factor": 1.0, "tax_rate": tax_rate }) for disc_line in line.find_all("ScontoMaggiorazione"): if disc_line.find("Percentuale"): - discount = float(disc_line.Percentuale.text) or float(0) - total_discount += float((discount / 100) * (rate * qty)) + discount = flt(disc_line.Percentuale.text) or 0 + total_discount += flt((discount / 100) * (rate * qty)) + #read taxes from file for line in file_content.find_all("DatiRiepilogo"): if line.find("AliquotaIVA"): if line.find("EsigibilitaIVA"): @@ -142,11 +150,12 @@ class ImportSupplierInvoice(Document): taxes.append({ "charge_type": "Actual", "account_head": self.tax_account, - "tax_rate": float(line.AliquotaIVA.text) or float(0), + "tax_rate": flt(line.AliquotaIVA.text) or 0, "description": descr, - "tax_amount": float(line.Imposta.text) if len(line.find("Imposta"))!=0 else float(0) + "tax_amount": flt(line.Imposta.text) if len(line.find("Imposta"))!=0 else 0 }) - + + #read payment data from file for line in file_content.find_all("DettaglioPagamento"): mop_code = line.ModalitaPagamento.text + '-' + mop_dict.get(line.ModalitaPagamento.text) @@ -162,19 +171,18 @@ class ImportSupplierInvoice(Document): }) supplier_name = create_supplier(supplier = supplier, supplier_group = self.supplier_group, - tax_id = tax_id, fiscal_code = fiscal_code, - fiscal_regime = fiscal_regime) + tax_id = tax_id, fiscal_code = fiscal_code, + fiscal_regime = fiscal_regime) address = create_address(supplier_name = supplier_name, address_line1 = address_line1, - city = city, province = province, - pin_code = pin_code, country = country) + city = city, province = province, + pin_code = pin_code, country = country) pi_name = create_purchase_invoice(company = self.company, naming_series = self.invoice_series, - supplier_name = supplier_name, bill_no = invoice_no, - document_type = document_type, bill_date = bill_date, - is_return = return_invoice, destination_code = destination_code, - total_discount = total_discount, items = items, - taxes = taxes, terms = terms,file_name = file_name) + supplier_name = supplier_name, bill_no = invoice_no,document_type = document_type, + bill_date = bill_date,is_return = return_invoice, destination_code = destination_code, + total_discount = total_discount, items = items,taxes = taxes, terms = terms, + file_name = file_name) file_count += 1 if pi_name: @@ -264,36 +272,31 @@ def create_address(**args): existing_address = frappe.get_list("Address", filters) if args.address_line1: - make_address = frappe.new_doc("Address") - make_address.address_line1 = args.address_line1 + new_address_doc = frappe.new_doc("Address") + new_address_doc.address_line1 = args.address_line1 if args.city: - make_address.city = args.city + new_address_doc.city = args.city else: - make_address.city = "Not Provided" + new_address_doc.city = "Not Provided" - if args.province: - make_address.state_code = args.province - - if args.pincode: - make_address.pincode = args.pincode - - if args.country: - make_address.country = args.country + for field in ["province", "pincode", "country"]: + if args.get(field): + new_address_doc.set(field, args.get(field)) for address in existing_address: address_doc = frappe.get_doc("Address", address["name"]) - if (address_doc.address_line1 == make_address.address_line1 and - address_doc.pincode == make_address.pincode): + if (address_doc.address_line1 == new_address_doc.address_line1 and + address_doc.pincode == new_address_doc.pincode): return address - make_address.append("links", { + new_address_doc.append("links", { "link_doctype": "Supplier", "link_name": args.supplier_name }) - make_address.address_type = "Billing" - make_address.insert() - return make_address.name + new_address_doc.address_type = "Billing" + new_address_doc.insert() + return new_address_doc.name else: return None @@ -316,28 +319,32 @@ def create_purchase_invoice(**args): }) try: + pi.set_missing_values() pi.insert(ignore_permissions=True) + #if discount exists in file, apply any discount on grand total if args.total_discount > 0: pi.apply_discount_on = "Grand Total" pi.discount_amount = args.total_discount pi.save() + #adjust payment amount to match with grand total calculated calc_total = 0 adj = 0 for term in args.terms: - calc_total += float(term["payment_amount"]) - if float(calc_total - float(pi.grand_total)) != 0: - adj = calc_total - float(pi.grand_total) + calc_total += flt(term["payment_amount"]) + if flt(calc_total - flt(pi.grand_total)) != 0: + adj = calc_total - flt(pi.grand_total) pi.payment_schedule = [] for term in args.terms: pi.append('payment_schedule',{"mode_of_payment_code": term["mode_of_payment_code"], "bank_account_iban": term["bank_account_iban"], "due_date": term["due_date"], - "payment_amount": float(term["payment_amount"]) - adj }) + "payment_amount": flt(term["payment_amount"]) - adj }) adj = 0 pi.imported_grand_total = calc_total pi.save() + return pi.name except Exception as e: frappe.log_error(message=e, title="Create Purchase Invoice: " + args.bill_no + "File Name: " + args.file_name) From 39b54cb8893f0ef06a012335ea3c412126e4d37e Mon Sep 17 00:00:00 2001 From: "hello@openetech.com" Date: Sat, 23 Nov 2019 06:50:51 +0530 Subject: [PATCH 12/20] validation change --- .../import_supplier_invoice.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py index f65c7f169a..43c6454e4d 100644 --- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py @@ -19,6 +19,9 @@ import dateutil from frappe.utils.file_manager import save_file class ImportSupplierInvoice(Document): + def validate(self): + if not frappe.db.get_value("Stock Settings", fieldname="stock_uom"): + frappe.throw(_("Please set default UOM in Stock Settings")) def autoname(self): if not self.name: @@ -35,9 +38,6 @@ class ImportSupplierInvoice(Document): default_uom = frappe.db.get_value("Stock Settings", fieldname="stock_uom") - if not default_uom: - frappe.throw(_("Please set default UOM in Stock Settings")) - with zipfile.ZipFile(get_full_path(self.zip_file)) as zf: file_count = 0 for file_name in zf.namelist(): @@ -158,7 +158,6 @@ class ImportSupplierInvoice(Document): #read payment data from file for line in file_content.find_all("DettaglioPagamento"): mop_code = line.ModalitaPagamento.text + '-' + mop_dict.get(line.ModalitaPagamento.text) - if line.find("DataScadenzaPagamento"): due_date = dateutil.parser.parse(line.DataScadenzaPagamento.text).strftime("%Y-%m-%d") else: @@ -208,7 +207,6 @@ class ImportSupplierInvoice(Document): frappe.publish_realtime("import_invoice_update", {"title": title, "message": message, "count": count, "total": total}) def create_supplier(**args): - args = frappe._dict(args) existing_supplier_name = frappe.db.get_value("Supplier", filters={"tax_id": args.tax_id}, fieldname="name") @@ -261,7 +259,6 @@ def create_supplier(**args): return new_supplier.name def create_address(**args): - args = frappe._dict(args) filters = [ ["Dynamic Link", "link_doctype", "=", "Supplier"], @@ -301,7 +298,6 @@ def create_address(**args): return None def create_purchase_invoice(**args): - args = frappe._dict(args) pi = frappe.get_doc({ "doctype": "Purchase Invoice", @@ -321,13 +317,11 @@ def create_purchase_invoice(**args): try: pi.set_missing_values() pi.insert(ignore_permissions=True) - #if discount exists in file, apply any discount on grand total if args.total_discount > 0: pi.apply_discount_on = "Grand Total" pi.discount_amount = args.total_discount pi.save() - #adjust payment amount to match with grand total calculated calc_total = 0 adj = 0 @@ -344,14 +338,12 @@ def create_purchase_invoice(**args): adj = 0 pi.imported_grand_total = calc_total pi.save() - return pi.name except Exception as e: frappe.log_error(message=e, title="Create Purchase Invoice: " + args.bill_no + "File Name: " + args.file_name) return None def get_country(code): - existing_country_name = frappe.db.get_value("Country", filters={"code": code}, fieldname="name") if existing_country_name: @@ -360,7 +352,6 @@ def get_country(code): frappe.throw(_("Country Code in File does not match with country code set up in the system")) def create_uom(uom): - existing_uom = frappe.db.get_value("UOM", filters={"uom_name": uom}, fieldname="uom_name") if existing_uom: From c3bb04ccb8283356b6b5ebd2a9915c65f937ebd6 Mon Sep 17 00:00:00 2001 From: Pawan Mehta Date: Mon, 2 Dec 2019 15:27:19 +0530 Subject: [PATCH 13/20] code improvements --- .../import_supplier_invoice.py | 271 ++++++++++-------- 1 file changed, 153 insertions(+), 118 deletions(-) diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py index 43c6454e4d..62a5329dbb 100644 --- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py @@ -28,16 +28,10 @@ class ImportSupplierInvoice(Document): self.name = "Import Invoice on " + format_datetime(self.creation) def import_xml_data(self): + pi_count = 0 import_file = frappe.get_doc("File", {"file_url": self.zip_file}) self.publish("File Import", _("Processing XML Files"), 1, 3) - pi_count = 0 - mop_options = frappe.get_meta('Mode of Payment').fields[4].options - mop_str = re.sub('\n', ',', mop_options) - mop_dict = dict(item.split("-") for item in mop_str.split(",")) - - default_uom = frappe.db.get_value("Stock Settings", fieldname="stock_uom") - with zipfile.ZipFile(get_full_path(self.zip_file)) as zf: file_count = 0 for file_name in zf.namelist(): @@ -56,126 +50,26 @@ class ImportSupplierInvoice(Document): file_content = bs(content, "xml") - for line in file_content.find_all("DatiTrasmissione"): - destination_code = line.CodiceDestinatario.text - - #read address information from file for line in file_content.find_all("DatiGeneraliDocumento"): document_type = line.TipoDocumento.text bill_date = dateutil.parser.parse(line.Data.text).strftime("%Y-%m-%d") invoice_no = line.Numero.text if len(invoice_no) != 0: - for line in file_content.find_all("CedentePrestatore"): - tax_id = line.DatiAnagrafici.IdPaese.text + line.DatiAnagrafici.IdCodice.text - if line.find("CodiceFiscale"): - fiscal_code = line.DatiAnagrafici.CodiceFiscale.text - else: - fiscal_code = "" - if line.find("RegimeFiscale"): - fiscal_regime = line.DatiAnagrafici.RegimeFiscale.text - else: - fiscal_regime = "" - if line.find("Denominazione"): - supplier = line.DatiAnagrafici.Anagrafica.Denominazione.text - if line.find("Nome"): - supplier = line.DatiAnagrafici.Anagrafica.Nome.text + " " + line.DatiAnagrafici.Anagrafica.Cognome.text - address_line1 = line.Sede.Indirizzo.text - city = line.Sede.Comune.text - if line.find("Provincia"): - province = line.Sede.Provincia.text - else: - province = "" - pin_code = line.Sede.CAP.text - country = get_country(line.Sede.Nazione.text) - total_discount = 0 - #read item information from file - for line in file_content.find_all("DettaglioLinee"): - if line.find("PrezzoUnitario") and line.find("PrezzoTotale"): - unit_rate = flt(line.PrezzoUnitario.text) or 0 - line_total = flt(line.PrezzoTotale.text) or 0 + supp_dict = get_supplier_info_from_file(file_content) + destination_code = get_destination_code_from_file(file_content) + items, return_invoice, total_discount = get_item_from_file(file_content, self.item_code) + taxes = get_taxes_from_file(file_content, self.tax_account) + terms = get_payment_terms_from_file(file_content) - if (unit_rate == 0.0): - qty = 1.0 - uom = default_uom - rate = tax_rate = 0 - else: - if (line_total / unit_rate) == 1.0: - qty = 1.0 - uom = default_uom - else: - if line.find("Quantita"): - qty = flt(line.Quantita.text) or 0 - if line.find("UnitaMisura"): - uom = create_uom(line.UnitaMisura.text) - else: - uom = default_uom + supplier_name = create_supplier(supplier = supp_dict.get('supplier'), supplier_group = self.supplier_group, + tax_id = supp_dict.get('tax_id'), fiscal_code = supp_dict.get('fiscal_code'), + fiscal_regime = supp_dict.get('fiscal_regime')) - if (unit_rate < 0 and line_total < 0): - qty *= -1 - return_invoice = 1 - else: - return_invoice = 0 - - rate = unit_rate - if line.find("AliquotaIVA"): - tax_rate = flt(line.AliquotaIVA.text) - - line_str = re.sub('[^A-Za-z0-9]+', '-', line.Descrizione.text) - item_name = line_str[0:140] - items.append({ - "item_code": self.item_code, - "item_name": item_name, - "description": line_str, - "qty": qty, - "uom": uom, - "rate": rate, - "conversion_factor": 1.0, - "tax_rate": tax_rate - }) - - for disc_line in line.find_all("ScontoMaggiorazione"): - if disc_line.find("Percentuale"): - discount = flt(disc_line.Percentuale.text) or 0 - total_discount += flt((discount / 100) * (rate * qty)) - - #read taxes from file - for line in file_content.find_all("DatiRiepilogo"): - if line.find("AliquotaIVA"): - if line.find("EsigibilitaIVA"): - descr = line.EsigibilitaIVA.text - else: - descr = "None" - taxes.append({ - "charge_type": "Actual", - "account_head": self.tax_account, - "tax_rate": flt(line.AliquotaIVA.text) or 0, - "description": descr, - "tax_amount": flt(line.Imposta.text) if len(line.find("Imposta"))!=0 else 0 - }) - - #read payment data from file - for line in file_content.find_all("DettaglioPagamento"): - mop_code = line.ModalitaPagamento.text + '-' + mop_dict.get(line.ModalitaPagamento.text) - if line.find("DataScadenzaPagamento"): - due_date = dateutil.parser.parse(line.DataScadenzaPagamento.text).strftime("%Y-%m-%d") - else: - due_date = today() - terms.append({ - "mode_of_payment_code": mop_code, - "bank_account_iban": line.IBAN.text if line.find("IBAN") else "", - "due_date": due_date, - "payment_amount": line.ImportoPagamento.text - }) - - supplier_name = create_supplier(supplier = supplier, supplier_group = self.supplier_group, - tax_id = tax_id, fiscal_code = fiscal_code, - fiscal_regime = fiscal_regime) - - address = create_address(supplier_name = supplier_name, address_line1 = address_line1, - city = city, province = province, - pin_code = pin_code, country = country) + address = create_address(supplier_name = supplier_name, address_line1 = supp_dict.get('address_line1'), + city = supp_dict.get('city'), province = supp_dict.get('province'), + pin_code = supp_dict.get('pin_code'), country = supp_dict.get('country')) pi_name = create_purchase_invoice(company = self.company, naming_series = self.invoice_series, supplier_name = supplier_name, bill_no = invoice_no,document_type = document_type, @@ -206,6 +100,147 @@ class ImportSupplierInvoice(Document): def publish(self, title, message, count, total): frappe.publish_realtime("import_invoice_update", {"title": title, "message": message, "count": count, "total": total}) +def get_supplier_info_from_file(file_content): + supplier_info = {} + for line in file_content.find_all("CedentePrestatore"): + tax_id = line.DatiAnagrafici.IdPaese.text + line.DatiAnagrafici.IdCodice.text + if line.find("CodiceFiscale"): + fiscal_code = line.DatiAnagrafici.CodiceFiscale.text + else: + fiscal_code = "" + if line.find("RegimeFiscale"): + fiscal_regime = line.DatiAnagrafici.RegimeFiscale.text + else: + fiscal_regime = "" + if line.find("Denominazione"): + supplier = line.DatiAnagrafici.Anagrafica.Denominazione.text + if line.find("Nome"): + supplier = line.DatiAnagrafici.Anagrafica.Nome.text + " " + line.DatiAnagrafici.Anagrafica.Cognome.text + address_line1 = line.Sede.Indirizzo.text + city = line.Sede.Comune.text + if line.find("Provincia"): + province = line.Sede.Provincia.text + else: + province = "" + pin_code = line.Sede.CAP.text + country = get_country(line.Sede.Nazione.text) + #set the dict values + supplier_info['tax_id'] = tax_id + supplier_info['fiscal_code'] = fiscal_code + supplier_info['fiscal_regime'] = fiscal_regime + supplier_info['supplier'] = supplier + supplier_info['address_line1'] = address_line1 + supplier_info['city'] = city + supplier_info['province'] = province + supplier_info['pin_code'] = pin_code + supplier_info['country'] = country + + return supplier_info + +def get_item_from_file(file_content, item_code): + items = [] + total_discount = 0 + default_uom = frappe.db.get_value("Stock Settings", fieldname="stock_uom") + #read file for item information + for line in file_content.find_all("DettaglioLinee"): + if line.find("PrezzoUnitario") and line.find("PrezzoTotale"): + unit_rate = flt(line.PrezzoUnitario.text) or 0 + line_total = flt(line.PrezzoTotale.text) or 0 + + if (unit_rate == 0.0): + qty = 1.0 + uom = default_uom + rate = tax_rate = 0 + else: + if (line_total / unit_rate) == 1.0: + qty = 1.0 + uom = default_uom + else: + if line.find("Quantita"): + qty = flt(line.Quantita.text) or 0 + if line.find("UnitaMisura"): + uom = create_uom(line.UnitaMisura.text) + else: + uom = default_uom + + if (unit_rate < 0 and line_total < 0): + qty *= -1 + return_invoice = 1 + unit_rate *= -1 + else: + return_invoice = 0 + + rate = unit_rate + if line.find("AliquotaIVA"): + tax_rate = flt(line.AliquotaIVA.text) + + line_str = re.sub('[^A-Za-z0-9]+', '-', line.Descrizione.text) + item_name = line_str[0:140] + items.append({ + "item_code": item_code, + "item_name": item_name, + "description": line_str, + "qty": qty, + "uom": uom, + "rate": rate, + "conversion_factor": 1.0, + "tax_rate": tax_rate + }) + + for disc_line in line.find_all("ScontoMaggiorazione"): + if disc_line.find("Percentuale"): + discount = flt(disc_line.Percentuale.text) or 0 + total_discount += flt((discount / 100) * (rate * qty)) + + return items, return_invoice, total_discount + +def get_taxes_from_file(file_content, tax_account): + taxes = [] + #read file for taxes information + for line in file_content.find_all("DatiRiepilogo"): + if line.find("AliquotaIVA"): + if line.find("EsigibilitaIVA"): + descr = line.EsigibilitaIVA.text + else: + descr = "None" + taxes.append({ + "charge_type": "Actual", + "account_head": tax_account, + "tax_rate": flt(line.AliquotaIVA.text) or 0, + "description": descr, + "tax_amount": flt(line.Imposta.text) if len(line.find("Imposta"))!=0 else 0 + }) + + return taxes + +def get_payment_terms_from_file(file_content): + terms = [] + #Get mode of payment dict from setup + mop_options = frappe.get_meta('Mode of Payment').fields[4].options + mop_str = re.sub('\n', ',', mop_options) + mop_dict = dict(item.split("-") for item in mop_str.split(",")) + #read file for payment information + for line in file_content.find_all("DettaglioPagamento"): + mop_code = line.ModalitaPagamento.text + '-' + mop_dict.get(line.ModalitaPagamento.text) + if line.find("DataScadenzaPagamento"): + due_date = dateutil.parser.parse(line.DataScadenzaPagamento.text).strftime("%Y-%m-%d") + else: + due_date = today() + terms.append({ + "mode_of_payment_code": mop_code, + "bank_account_iban": line.IBAN.text if line.find("IBAN") else "", + "due_date": due_date, + "payment_amount": line.ImportoPagamento.text + }) + + return terms + +def get_destination_code_from_file(file_content): + for line in file_content.find_all("DatiTrasmissione"): + destination_code = line.CodiceDestinatario.text + + return destination_code + def create_supplier(**args): args = frappe._dict(args) existing_supplier_name = frappe.db.get_value("Supplier", From bc40161d08ad683f71dafd925f50152f9da4768c Mon Sep 17 00:00:00 2001 From: Pawan Mehta Date: Wed, 4 Dec 2019 17:28:48 +0530 Subject: [PATCH 14/20] add ignore mandatory flag --- .../import_supplier_invoice/import_supplier_invoice.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py index 62a5329dbb..15e6344fa7 100644 --- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py @@ -269,7 +269,7 @@ def create_supplier(**args): "link_doctype": "Supplier", "link_name": existing_supplier_name }) - new_contact.insert() + new_contact.insert(ignore_mandatory=True) return existing_supplier_name else: @@ -289,7 +289,7 @@ def create_supplier(**args): "link_name": new_supplier.name }) - new_contact.insert() + new_contact.insert(ignore_mandatory=True) return new_supplier.name @@ -327,7 +327,7 @@ def create_address(**args): "link_name": args.supplier_name }) new_address_doc.address_type = "Billing" - new_address_doc.insert() + new_address_doc.insert(ignore_mandatory=True) return new_address_doc.name else: return None @@ -351,7 +351,7 @@ def create_purchase_invoice(**args): try: pi.set_missing_values() - pi.insert(ignore_permissions=True) + pi.insert(ignore_mandatory=True) #if discount exists in file, apply any discount on grand total if args.total_discount > 0: pi.apply_discount_on = "Grand Total" From 2c13a0533b652e0b020c0557501a4a730f42c3fd Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 10 Dec 2019 17:07:03 +0530 Subject: [PATCH 15/20] code cleanup --- erpnext/patches.txt | 1 + .../v12_0/set_permission_einvoicing.py | 3 + .../import_supplier_invoice.js | 15 +- .../import_supplier_invoice.json | 20 +- .../import_supplier_invoice.py | 307 ++++++++---------- 5 files changed, 179 insertions(+), 167 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 89be499f6f..6d15d5432d 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -653,3 +653,4 @@ erpnext.patches.v12_0.set_employee_preferred_emails erpnext.patches.v12_0.set_against_blanket_order_in_sales_and_purchase_order erpnext.patches.v12_0.set_cost_center_in_child_table_of_expense_claim erpnext.patches.v12_0.set_lead_title_field +erpnext.patches.v12_0.set_permission_einvoicing diff --git a/erpnext/patches/v12_0/set_permission_einvoicing.py b/erpnext/patches/v12_0/set_permission_einvoicing.py index c01f34a70d..1095c8c43e 100644 --- a/erpnext/patches/v12_0/set_permission_einvoicing.py +++ b/erpnext/patches/v12_0/set_permission_einvoicing.py @@ -1,4 +1,5 @@ import frappe +from erpnext.regional.italy.setup import make_custom_fields from frappe.permissions import add_permission, update_permission_property def execute(): @@ -7,6 +8,8 @@ def execute(): if not company: return + make_custom_fields() + add_permission('Import Supplier Invoice', 'Accounts Manager', 0) update_permission_property('Import Supplier Invoice', 'Accounts Manager', 0, 'write', 1) update_permission_property('Import Supplier Invoice', 'Accounts Manager', 0, 'create', 1) \ No newline at end of file diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js index bbc0f92cc7..9f1a092d17 100644 --- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js @@ -11,9 +11,20 @@ frappe.ui.form.on('Import Supplier Invoice', { }); }, setup: function(frm) { - frm.set_query("tax_account", function() { + frm.set_query("tax_account", function(doc) { return { - filters: { account_type: 'Tax' } + filters: { + account_type: 'Tax', + company: doc.company + } + }; + }); + + frm.set_query("default_buying_price_list", function(doc) { + return { + filters: { + currency: frappe.get_doc(":Company", doc.company).default_currency + } }; }); } diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.json b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.json index aabf9de3cd..59e955c23f 100644 --- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.json +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.json @@ -1,4 +1,5 @@ { + "actions": [], "creation": "2019-10-15 12:33:21.845329", "doctype": "DocType", "editable_grid": 1, @@ -7,9 +8,11 @@ "invoice_series", "company", "item_code", + "column_break_5", "supplier_group", "tax_account", - "column_break_5", + "default_buying_price_list", + "upload_xml_invoices_section", "zip_file", "import_invoices", "status" @@ -75,9 +78,22 @@ "label": "Invoice Series", "options": "ACC-PINV-.YYYY.-", "reqd": 1 + }, + { + "fieldname": "default_buying_price_list", + "fieldtype": "Link", + "label": "Default Buying Price List", + "options": "Price List", + "reqd": 1 + }, + { + "fieldname": "upload_xml_invoices_section", + "fieldtype": "Section Break", + "label": "Upload XML Invoices" } ], - "modified": "2019-10-19 00:15:11.404733", + "links": [], + "modified": "2019-12-10 16:37:26.793398", "modified_by": "Administrator", "module": "Regional", "name": "Import Supplier Invoice", diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py index 15e6344fa7..1d21d39798 100644 --- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py @@ -8,13 +8,13 @@ import json import re import traceback import zipfile -import frappe +import frappe, erpnext from frappe import _ from frappe.model.document import Document from frappe.custom.doctype.custom_field.custom_field import create_custom_field from frappe.utils.data import format_datetime from bs4 import BeautifulSoup as bs -from frappe.utils import cint, flt, today, nowdate, add_days, get_files_path +from frappe.utils import cint, flt, today, nowdate, add_days, get_files_path, get_datetime_str import dateutil from frappe.utils.file_manager import save_file @@ -28,61 +28,20 @@ class ImportSupplierInvoice(Document): self.name = "Import Invoice on " + format_datetime(self.creation) def import_xml_data(self): - pi_count = 0 import_file = frappe.get_doc("File", {"file_url": self.zip_file}) self.publish("File Import", _("Processing XML Files"), 1, 3) + self.file_count = 0 + self.purchase_invoices_count = 0 + self.default_uom = frappe.db.get_value("Stock Settings", fieldname="stock_uom") + with zipfile.ZipFile(get_full_path(self.zip_file)) as zf: - file_count = 0 for file_name in zf.namelist(): - items = [] - taxes = [] - terms = [] - encoded_content = zf.read(file_name) - - try: - content = encoded_content.decode("utf-8-sig") - except UnicodeDecodeError: - try: - content = encoded_content.decode("utf-16") - except UnicodeDecodeError as e: - frappe.log_error(message=e, title="UTF-16 encoding error for File Name: " + file_name) - + content = get_file_content(file_name, zf) file_content = bs(content, "xml") + self.prepare_data_for_import(file_content, file_name, content) - for line in file_content.find_all("DatiGeneraliDocumento"): - document_type = line.TipoDocumento.text - bill_date = dateutil.parser.parse(line.Data.text).strftime("%Y-%m-%d") - invoice_no = line.Numero.text - if len(invoice_no) != 0: - total_discount = 0 - - supp_dict = get_supplier_info_from_file(file_content) - destination_code = get_destination_code_from_file(file_content) - items, return_invoice, total_discount = get_item_from_file(file_content, self.item_code) - taxes = get_taxes_from_file(file_content, self.tax_account) - terms = get_payment_terms_from_file(file_content) - - supplier_name = create_supplier(supplier = supp_dict.get('supplier'), supplier_group = self.supplier_group, - tax_id = supp_dict.get('tax_id'), fiscal_code = supp_dict.get('fiscal_code'), - fiscal_regime = supp_dict.get('fiscal_regime')) - - address = create_address(supplier_name = supplier_name, address_line1 = supp_dict.get('address_line1'), - city = supp_dict.get('city'), province = supp_dict.get('province'), - pin_code = supp_dict.get('pin_code'), country = supp_dict.get('country')) - - pi_name = create_purchase_invoice(company = self.company, naming_series = self.invoice_series, - supplier_name = supplier_name, bill_no = invoice_no,document_type = document_type, - bill_date = bill_date,is_return = return_invoice, destination_code = destination_code, - total_discount = total_discount, items = items,taxes = taxes, terms = terms, - file_name = file_name) - - file_count += 1 - if pi_name: - pi_count += 1 - file_save = save_file(file_name, encoded_content, "Purchase Invoice", pi_name, folder=None, decode=False, is_private=0, df=None) - - if pi_count == file_count: + if self.purchase_invoices_count == self.file_count: self.status = "File Import Completed" self.publish("File Import", _("XML Files Processed"), 2, 3) else: @@ -92,6 +51,78 @@ class ImportSupplierInvoice(Document): self.save() self.publish("File Import", _("XML Files Processed"), 3, 3) + def prepare_data_for_import(self, file_content, file_name, encoded_content): + for line in file_content.find_all("DatiGeneraliDocumento"): + invoices_args = { + "company": self.company, + "naming_series": self.invoice_series, + "document_type": line.TipoDocumento.text, + "bill_date": get_datetime_str(line.Data.text), + "invoice_no": line.Numero.text, + "total_discount": 0, + "items": [], + "buying_price_list": self.default_buying_price_list + } + + if not invoices_args.get("invoice_no", ''): return + + supp_dict = get_supplier_details(file_content) + invoices_args["destination_code"] = get_destination_code_from_file(file_content) + invoices_args["taxes"] = get_taxes_from_file(file_content, self.tax_account) + invoices_args["terms"] = get_payment_terms_from_file(file_content) + self.prepare_items_for_invoice(file_content, invoices_args) + + supplier_name = create_supplier(self.supplier_group, supp_dict) + address = create_address(supplier_name, supp_dict) + pi_name = create_purchase_invoice(supplier_name, file_name, invoices_args) + + self.file_count += 1 + if pi_name: + self.purchase_invoices_count += 1 + file_save = save_file(file_name, encoded_content, "Purchase Invoice", + pi_name, folder=None, decode=False, is_private=0, df=None) + + def prepare_items_for_invoice(self, file_content, invoices_args): + qty = 1 + rate, tax_rate = [0 ,0] + uom = self.default_uom + + #read file for item information + for line in file_content.find_all("DettaglioLinee"): + if line.find("PrezzoUnitario") and line.find("PrezzoTotale"): + rate = flt(line.PrezzoUnitario.text) or 0 + line_total = flt(line.PrezzoTotale.text) or 0 + + if rate and flt(line_total) / rate != 1.0 and line.find("Quantita"): + qty = flt(line.Quantita.text) or 0 + if line.find("UnitaMisura"): + uom = create_uom(line.UnitaMisura.text) + + if (rate < 0 and line_total < 0): + qty *= -1 + invoices_args["return_invoice"] = 1 + + if line.find("AliquotaIVA"): + tax_rate = flt(line.AliquotaIVA.text) + + line_str = re.sub('[^A-Za-z0-9]+', '-', line.Descrizione.text) + item_name = line_str[0:140] + + invoices_args['items'].append({ + "item_code": self.item_code, + "item_name": item_name, + "description": line_str, + "qty": qty, + "uom": uom, + "rate": abs(rate), + "conversion_factor": 1.0, + "tax_rate": tax_rate + }) + + for disc_line in line.find_all("ScontoMaggiorazione"): + if disc_line.find("Percentuale"): + invoices_args["total_discount"] += flt((flt(disc_line.Percentuale.text) / 100) * (rate * qty)) + def process_file_data(self): self.status = "Processing File Data" self.save() @@ -100,100 +131,47 @@ class ImportSupplierInvoice(Document): def publish(self, title, message, count, total): frappe.publish_realtime("import_invoice_update", {"title": title, "message": message, "count": count, "total": total}) -def get_supplier_info_from_file(file_content): +def get_file_content(file_name, zip_file_object): + content = '' + encoded_content = zip_file_object.read(file_name) + + try: + content = encoded_content.decode("utf-8-sig") + except UnicodeDecodeError: + try: + content = encoded_content.decode("utf-16") + except UnicodeDecodeError as e: + frappe.log_error(message=e, title="UTF-16 encoding error for File Name: " + file_name) + + return content + +def get_supplier_details(file_content): supplier_info = {} for line in file_content.find_all("CedentePrestatore"): - tax_id = line.DatiAnagrafici.IdPaese.text + line.DatiAnagrafici.IdCodice.text + supplier_info['tax_id'] = line.DatiAnagrafici.IdPaese.text + line.DatiAnagrafici.IdCodice.text if line.find("CodiceFiscale"): - fiscal_code = line.DatiAnagrafici.CodiceFiscale.text - else: - fiscal_code = "" + supplier_info['fiscal_code'] = line.DatiAnagrafici.CodiceFiscale.text + if line.find("RegimeFiscale"): - fiscal_regime = line.DatiAnagrafici.RegimeFiscale.text - else: - fiscal_regime = "" + supplier_info['fiscal_regime'] = line.DatiAnagrafici.RegimeFiscale.text + if line.find("Denominazione"): - supplier = line.DatiAnagrafici.Anagrafica.Denominazione.text + supplier_info['supplier'] = line.DatiAnagrafici.Anagrafica.Denominazione.text + if line.find("Nome"): - supplier = line.DatiAnagrafici.Anagrafica.Nome.text + " " + line.DatiAnagrafici.Anagrafica.Cognome.text - address_line1 = line.Sede.Indirizzo.text - city = line.Sede.Comune.text + supplier_info['supplier'] = (line.DatiAnagrafici.Anagrafica.Nome.text + + " " + line.DatiAnagrafici.Anagrafica.Cognome.text) + + supplier_info['address_line1'] = line.Sede.Indirizzo.text + supplier_info['city'] = line.Sede.Comune.text if line.find("Provincia"): - province = line.Sede.Provincia.text - else: - province = "" - pin_code = line.Sede.CAP.text - country = get_country(line.Sede.Nazione.text) - #set the dict values - supplier_info['tax_id'] = tax_id - supplier_info['fiscal_code'] = fiscal_code - supplier_info['fiscal_regime'] = fiscal_regime - supplier_info['supplier'] = supplier - supplier_info['address_line1'] = address_line1 - supplier_info['city'] = city - supplier_info['province'] = province - supplier_info['pin_code'] = pin_code - supplier_info['country'] = country + supplier_info['province'] = line.Sede.Provincia.text + + supplier_info['pin_code'] = line.Sede.CAP.text + supplier_info['country'] = get_country(line.Sede.Nazione.text) return supplier_info -def get_item_from_file(file_content, item_code): - items = [] - total_discount = 0 - default_uom = frappe.db.get_value("Stock Settings", fieldname="stock_uom") - #read file for item information - for line in file_content.find_all("DettaglioLinee"): - if line.find("PrezzoUnitario") and line.find("PrezzoTotale"): - unit_rate = flt(line.PrezzoUnitario.text) or 0 - line_total = flt(line.PrezzoTotale.text) or 0 - - if (unit_rate == 0.0): - qty = 1.0 - uom = default_uom - rate = tax_rate = 0 - else: - if (line_total / unit_rate) == 1.0: - qty = 1.0 - uom = default_uom - else: - if line.find("Quantita"): - qty = flt(line.Quantita.text) or 0 - if line.find("UnitaMisura"): - uom = create_uom(line.UnitaMisura.text) - else: - uom = default_uom - - if (unit_rate < 0 and line_total < 0): - qty *= -1 - return_invoice = 1 - unit_rate *= -1 - else: - return_invoice = 0 - - rate = unit_rate - if line.find("AliquotaIVA"): - tax_rate = flt(line.AliquotaIVA.text) - - line_str = re.sub('[^A-Za-z0-9]+', '-', line.Descrizione.text) - item_name = line_str[0:140] - items.append({ - "item_code": item_code, - "item_name": item_name, - "description": line_str, - "qty": qty, - "uom": uom, - "rate": rate, - "conversion_factor": 1.0, - "tax_rate": tax_rate - }) - - for disc_line in line.find_all("ScontoMaggiorazione"): - if disc_line.find("Percentuale"): - discount = flt(disc_line.Percentuale.text) or 0 - total_discount += flt((discount / 100) * (rate * qty)) - - return items, return_invoice, total_discount - def get_taxes_from_file(file_content, tax_account): taxes = [] #read file for taxes information @@ -227,22 +205,24 @@ def get_payment_terms_from_file(file_content): else: due_date = today() terms.append({ - "mode_of_payment_code": mop_code, - "bank_account_iban": line.IBAN.text if line.find("IBAN") else "", - "due_date": due_date, - "payment_amount": line.ImportoPagamento.text + "mode_of_payment_code": mop_code, + "bank_account_iban": line.IBAN.text if line.find("IBAN") else "", + "due_date": due_date, + "payment_amount": line.ImportoPagamento.text }) return terms def get_destination_code_from_file(file_content): + destination_code = '' for line in file_content.find_all("DatiTrasmissione"): destination_code = line.CodiceDestinatario.text return destination_code -def create_supplier(**args): +def create_supplier(supplier_group, args): args = frappe._dict(args) + existing_supplier_name = frappe.db.get_value("Supplier", filters={"tax_id": args.tax_id}, fieldname="name") if existing_supplier_name: @@ -258,11 +238,7 @@ def create_supplier(**args): ["Dynamic Link", "parenttype", "=", "Contact"] ] - existing_contacts = frappe.get_list("Contact", filters) - - if existing_contacts: - pass - else: + if not frappe.get_list("Contact", filters): new_contact = frappe.new_doc("Contact") new_contact.first_name = args.supplier new_contact.append('links', { @@ -276,7 +252,7 @@ def create_supplier(**args): new_supplier = frappe.new_doc("Supplier") new_supplier.supplier_name = args.supplier - new_supplier.supplier_group = args.supplier_group + new_supplier.supplier_group = supplier_group new_supplier.tax_id = args.tax_id new_supplier.fiscal_code = args.fiscal_code new_supplier.fiscal_regime = args.fiscal_regime @@ -293,11 +269,12 @@ def create_supplier(**args): return new_supplier.name -def create_address(**args): +def create_address(supplier_name, args): args = frappe._dict(args) + filters = [ ["Dynamic Link", "link_doctype", "=", "Supplier"], - ["Dynamic Link", "link_name", "=", args.supplier_name], + ["Dynamic Link", "link_name", "=", supplier_name], ["Dynamic Link", "parenttype", "=", "Address"] ] @@ -324,7 +301,7 @@ def create_address(**args): new_address_doc.append("links", { "link_doctype": "Supplier", - "link_name": args.supplier_name + "link_name": supplier_name }) new_address_doc.address_type = "Billing" new_address_doc.insert(ignore_mandatory=True) @@ -332,26 +309,29 @@ def create_address(**args): else: return None -def create_purchase_invoice(**args): +def create_purchase_invoice(supplier_name, file_name, args): args = frappe._dict(args) pi = frappe.get_doc({ - "doctype": "Purchase Invoice", - "company": args.company, - "naming_series": args.naming_series, - "supplier": args.supplier_name, - "is_return": args.is_return, - "posting_date": today(), - "bill_no": args.bill_no, - "bill_date": args.bill_date, - "destination_code": args.destination_code, - "document_type": args.document_type, - "items": args["items"], - "taxes": args["taxes"] - }) + "doctype": "Purchase Invoice", + "company": args.company, + "currency": erpnext.get_company_currency(args.company), + "naming_series": args.naming_series, + "supplier": supplier_name, + "is_return": args.is_return, + "posting_date": today(), + "bill_no": args.bill_no, + "buying_price_list": args.buying_price_list, + "bill_date": args.bill_date, + "destination_code": args.destination_code, + "document_type": args.document_type, + "items": args["items"], + "taxes": args["taxes"] + }) try: pi.set_missing_values() pi.insert(ignore_mandatory=True) + #if discount exists in file, apply any discount on grand total if args.total_discount > 0: pi.apply_discount_on = "Grand Total" @@ -375,7 +355,8 @@ def create_purchase_invoice(**args): pi.save() return pi.name except Exception as e: - frappe.log_error(message=e, title="Create Purchase Invoice: " + args.bill_no + "File Name: " + args.file_name) + frappe.log_error(message=e, + title="Create Purchase Invoice: " + args.bill_no + "File Name: " + file_name) return None def get_country(code): From 6e1ba43c3c3ad5491fe692a1029a6714a7e663a3 Mon Sep 17 00:00:00 2001 From: Pawan Mehta Date: Tue, 10 Dec 2019 23:30:33 +0530 Subject: [PATCH 16/20] code fixes --- .../doctype/import_supplier_invoice/import_supplier_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py index 1d21d39798..9478ce2b19 100644 --- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py @@ -68,9 +68,9 @@ class ImportSupplierInvoice(Document): supp_dict = get_supplier_details(file_content) invoices_args["destination_code"] = get_destination_code_from_file(file_content) + self.prepare_items_for_invoice(file_content, invoices_args) invoices_args["taxes"] = get_taxes_from_file(file_content, self.tax_account) invoices_args["terms"] = get_payment_terms_from_file(file_content) - self.prepare_items_for_invoice(file_content, invoices_args) supplier_name = create_supplier(self.supplier_group, supp_dict) address = create_address(supplier_name, supp_dict) From 2469fb9e351b23ebdd205fb512424d7d37b323d1 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 26 Dec 2019 13:26:39 +0530 Subject: [PATCH 17/20] fix: rounded total issue --- .../import_supplier_invoice.js | 15 +++++++++++++++ .../import_supplier_invoice.py | 8 +++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js index 9f1a092d17..c2d6edfc77 100644 --- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js @@ -27,5 +27,20 @@ frappe.ui.form.on('Import Supplier Invoice', { } }; }); + }, + + refresh: function(frm) { + frm.trigger("toggle_read_only_fields"); + }, + + toggle_read_only_fields: function(frm) { + if (in_list(["File Import Completed", "Processing File Data"], frm.doc.status)) { + cur_frm.set_read_only(); + cur_frm.refresh_fields(); + frm.set_df_property("import_invoices", "hidden", 1); + } else { + frm.set_df_property("import_invoices", "hidden", 0); + } } + }); \ No newline at end of file diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py index 9478ce2b19..72fe17fb37 100644 --- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py @@ -74,7 +74,7 @@ class ImportSupplierInvoice(Document): supplier_name = create_supplier(self.supplier_group, supp_dict) address = create_address(supplier_name, supp_dict) - pi_name = create_purchase_invoice(supplier_name, file_name, invoices_args) + pi_name = create_purchase_invoice(supplier_name, file_name, invoices_args, self.name) self.file_count += 1 if pi_name: @@ -309,7 +309,7 @@ def create_address(supplier_name, args): else: return None -def create_purchase_invoice(supplier_name, file_name, args): +def create_purchase_invoice(supplier_name, file_name, args, name): args = frappe._dict(args) pi = frappe.get_doc({ "doctype": "Purchase Invoice", @@ -324,6 +324,7 @@ def create_purchase_invoice(supplier_name, file_name, args): "bill_date": args.bill_date, "destination_code": args.destination_code, "document_type": args.document_type, + "disable_rounded_total": 1, "items": args["items"], "taxes": args["taxes"] }) @@ -355,8 +356,9 @@ def create_purchase_invoice(supplier_name, file_name, args): pi.save() return pi.name except Exception as e: + frappe.db.set_value("Import Supplier Invoice", name, "status", "Error") frappe.log_error(message=e, - title="Create Purchase Invoice: " + args.bill_no + "File Name: " + file_name) + title="Create Purchase Invoice: " + args.get("bill_no") + "File Name: " + file_name) return None def get_country(code): From a99f926a4b23ce4d1c2fb65cff833eef5177902a Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 27 Dec 2019 12:21:15 +0530 Subject: [PATCH 18/20] fix: incorrect validation for scrap items (#20109) --- erpnext/stock/doctype/stock_entry/stock_entry.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 47f6cf64f5..18af062147 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -585,12 +585,18 @@ class StockEntry(StockController): def validate_finished_goods(self): """validation: finished good quantity should be same as manufacturing quantity""" + if not self.work_order: return + items_with_target_warehouse = [] allowance_percentage = flt(frappe.db.get_single_value("Manufacturing Settings", "overproduction_percentage_for_work_order")) + production_item, wo_qty = frappe.db.get_value("Work Order", + self.work_order, ["production_item", "qty"]) + for d in self.get('items'): - if self.purpose != "Send to Subcontractor" and d.bom_no and flt(d.transfer_qty) > flt(self.fg_completed_qty) and (d.t_warehouse != getattr(self, "pro_doc", frappe._dict()).scrap_warehouse): + if (self.purpose != "Send to Subcontractor" and d.bom_no + and flt(d.transfer_qty) > flt(self.fg_completed_qty) and d.item_code == production_item): frappe.throw(_("Quantity in row {0} ({1}) must be same as manufactured quantity {2}"). \ format(d.idx, d.transfer_qty, self.fg_completed_qty)) @@ -598,9 +604,6 @@ class StockEntry(StockController): items_with_target_warehouse.append(d.item_code) if self.work_order and self.purpose == "Manufacture": - production_item, wo_qty = frappe.db.get_value("Work Order", - self.work_order, ["production_item", "qty"]) - allowed_qty = wo_qty + (allowance_percentage/100 * wo_qty) if self.fg_completed_qty > allowed_qty: frappe.throw(_("For quantity {0} should not be grater than work order quantity {1}") From e944f7ac05afa2baad38c20567226d23dadba985 Mon Sep 17 00:00:00 2001 From: Rohan Date: Fri, 27 Dec 2019 12:34:36 +0530 Subject: [PATCH 19/20] fix: Add Serial No button prompt (#20099) --- erpnext/public/js/utils/serial_no_batch_selector.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index 61a693933f..e64d5458b3 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -139,7 +139,7 @@ erpnext.SerialNoBatchSelector = Class.extend({ this.dialog.set_value('serial_no', d.serial_no); } - if (d.batch_no) { + if (d.has_batch_no && d.batch_no) { this.frm.doc.items.forEach(data => { if(data.item_code == d.item_code) { this.dialog.fields_dict.batches.df.data.push({ From 616431d7c3e64ef4057c2a97dfeb5585aef6caa5 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 27 Dec 2019 12:35:58 +0530 Subject: [PATCH 20/20] fix: incorrect reorder level in stock balance report (#20111) --- erpnext/stock/report/stock_balance/stock_balance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index a74253e872..ccba8b0e23 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -55,7 +55,7 @@ def execute(filters=None): 'item_code': item, 'warehouse': warehouse, 'company': company, - 'reorder_level': item_reorder_qty, + 'reorder_level': item_reorder_level, 'reorder_qty': item_reorder_qty, } report_data.update(item_map[item])