From 2c13a0533b652e0b020c0557501a4a730f42c3fd Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 10 Dec 2019 17:07:03 +0530 Subject: [PATCH] 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 89be499f6f3..6d15d5432d9 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 c01f34a70d2..1095c8c43e7 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 bbc0f92cc7b..9f1a092d175 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 aabf9de3cdf..59e955c23f4 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 15e6344fa70..1d21d397984 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):