Merge branch 'develop' into publish-item
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 742fc6b..f4a371c 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -653,4 +653,5 @@
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
erpnext.patches.v12_0.set_published_in_hub_tracked_item
\ No newline at end of file
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 0000000..1095c8c
--- /dev/null
+++ b/erpnext/patches/v12_0/set_permission_einvoicing.py
@@ -0,0 +1,15 @@
+import frappe
+from erpnext.regional.italy.setup import make_custom_fields
+from frappe.permissions import add_permission, update_permission_property
+
+def execute():
+ company = frappe.get_all('Company', filters = {'country': 'Italy'})
+
+ 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/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js
index 61a6939..e64d545 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 @@
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({
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 0000000..e69de29
--- /dev/null
+++ b/erpnext/regional/doctype/import_supplier_invoice/__init__.py
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 0000000..c2d6edf
--- /dev/null
+++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js
@@ -0,0 +1,46 @@
+// 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);
+ }
+ });
+ },
+ setup: function(frm) {
+ frm.set_query("tax_account", function(doc) {
+ return {
+ 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
+ }
+ };
+ });
+ },
+
+ 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.json b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.json
new file mode 100644
index 0000000..59e955c
--- /dev/null
+++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.json
@@ -0,0 +1,105 @@
+{
+ "actions": [],
+ "creation": "2019-10-15 12:33:21.845329",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "invoice_series",
+ "company",
+ "item_code",
+ "column_break_5",
+ "supplier_group",
+ "tax_account",
+ "default_buying_price_list",
+ "upload_xml_invoices_section",
+ "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
+ },
+ {
+ "fieldname": "invoice_series",
+ "fieldtype": "Select",
+ "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"
+ }
+ ],
+ "links": [],
+ "modified": "2019-12-10 16:37:26.793398",
+ "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 0000000..72fe17f
--- /dev/null
+++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py
@@ -0,0 +1,402 @@
+# 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, 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, get_datetime_str
+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:
+ 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)
+
+ 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:
+ for file_name in zf.namelist():
+ content = get_file_content(file_name, zf)
+ file_content = bs(content, "xml")
+ self.prepare_data_for_import(file_content, file_name, content)
+
+ if self.purchase_invoices_count == self.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 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)
+ 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)
+
+ 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.name)
+
+ 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()
+ 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 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"):
+ supplier_info['tax_id'] = line.DatiAnagrafici.IdPaese.text + line.DatiAnagrafici.IdCodice.text
+ if line.find("CodiceFiscale"):
+ supplier_info['fiscal_code'] = line.DatiAnagrafici.CodiceFiscale.text
+
+ if line.find("RegimeFiscale"):
+ supplier_info['fiscal_regime'] = line.DatiAnagrafici.RegimeFiscale.text
+
+ if line.find("Denominazione"):
+ supplier_info['supplier'] = line.DatiAnagrafici.Anagrafica.Denominazione.text
+
+ if line.find("Nome"):
+ 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"):
+ 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_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):
+ destination_code = ''
+ for line in file_content.find_all("DatiTrasmissione"):
+ destination_code = line.CodiceDestinatario.text
+
+ return destination_code
+
+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:
+ 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"]
+ ]
+
+ if not frappe.get_list("Contact", filters):
+ 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(ignore_mandatory=True)
+
+ return existing_supplier_name
+ else:
+
+ new_supplier = frappe.new_doc("Supplier")
+ new_supplier.supplier_name = args.supplier
+ 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
+ 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(ignore_mandatory=True)
+
+ return new_supplier.name
+
+def create_address(supplier_name, args):
+ args = frappe._dict(args)
+
+ filters = [
+ ["Dynamic Link", "link_doctype", "=", "Supplier"],
+ ["Dynamic Link", "link_name", "=", supplier_name],
+ ["Dynamic Link", "parenttype", "=", "Address"]
+ ]
+
+ existing_address = frappe.get_list("Address", filters)
+
+ if args.address_line1:
+ new_address_doc = frappe.new_doc("Address")
+ new_address_doc.address_line1 = args.address_line1
+
+ if args.city:
+ new_address_doc.city = args.city
+ else:
+ new_address_doc.city = "Not Provided"
+
+ 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 == new_address_doc.address_line1 and
+ address_doc.pincode == new_address_doc.pincode):
+ return address
+
+ new_address_doc.append("links", {
+ "link_doctype": "Supplier",
+ "link_name": supplier_name
+ })
+ new_address_doc.address_type = "Billing"
+ new_address_doc.insert(ignore_mandatory=True)
+ return new_address_doc.name
+ else:
+ return None
+
+def create_purchase_invoice(supplier_name, file_name, args, name):
+ args = frappe._dict(args)
+ pi = frappe.get_doc({
+ "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,
+ "disable_rounded_total": 1,
+ "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"
+ 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 += 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": flt(term["payment_amount"]) - adj })
+ adj = 0
+ pi.imported_grand_total = calc_total
+ 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.get("bill_no") + "File Name: " + 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 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 0000000..d1caf77
--- /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 1526d6f..2d0ad66 100644
--- a/erpnext/regional/italy/setup.py
+++ b/erpnext/regional/italy/setup.py
@@ -155,6 +155,31 @@
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 bc8d00d..2af72f8 100644
--- a/erpnext/regional/italy/utils.py
+++ b/erpnext/regional/italy/utils.py
@@ -13,6 +13,8 @@
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 0000000..e69de29
--- /dev/null
+++ b/erpnext/regional/print_format/purchase_einvoice/__init__.py
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 0000000..88f31dd
--- /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\": \"<div class=\\\"print-heading\\\">\\t\\t\\t\\t<h2>Purchase Invoice<br><small>{{ doc.name }}</small>\\t\\t\\t\\t</h2></div>\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"<table border=\\\"1\\\" width=\\\"100%\\\">\\n<head>\\n<th>\\nCedente/prestatore (fornitore)\\n</th>\\n<th>\\nCessionario/committente (cliente)\\n</th>\\n</head>\\n<body>\\n<tr>\\n<td style=\\\"width: 50%;white-space:nowrap;\\\">\\n<p>Identificstivo fiscale ai fini IVA: {{frappe.db.get_value(\\\"Supplier\\\", doc.supplier, \\\"tax_id\\\")}}</p>\\n<p>Codice fiscale: {{frappe.db.get_value(\\\"Supplier\\\", doc.supplier, \\\"fiscal_code\\\")}}</p>\\n<p>Denominazione: {{frappe.db.get_value(\\\"Supplier\\\", doc.supplier, \\\"supplier_name\\\")}}</p>\\n<p>Regime fiscale: {{frappe.db.get_value(\\\"Supplier\\\", doc.supplier, \\\"fiscal_regime\\\")}}</p>\\n<p>Indrizo: {{frappe.db.get_value(\\\"Address\\\", doc.supplier_address, \\\"address_line1\\\")}}</p>\\n<p>Commune: {{frappe.db.get_value(\\\"Address\\\", doc.supplier_address, \\\"city\\\")}} Provincia: {{frappe.db.get_value(\\\"Address\\\", doc.supplier_address, \\\"state_code\\\")}}</p>\\n<p>Cap: {{(frappe.db.get_value(\\\"Address\\\", doc.supplier_address, \\\"pincode\\\")) or \\\" \\\"}} Nazione: {{frappe.db.get_value(\\\"Address\\\", doc.supplier_address, \\\"country\\\")}}</p>\\n</td>\\n<td style=\\\"width: 50%;white-space:nowrap;\\\">\\n<p>Identificstivo fiscale ai fini IVA: {{frappe.db.get_value(\\\"Company\\\", doc.company, \\\"tax_id\\\")}}</p>\\n<p>Codice fiscale: {{frappe.db.get_value(\\\"Company\\\", doc.company, \\\"fiscal_code\\\")}}</p>\\n<p>Denominazione: {{doc.company}}</p>\\n<p>Indrizo: {{frappe.db.get_value(\\\"Address\\\", doc.shipping_address, \\\"address_line1\\\")}}</p>\\n<p>Commune: {{frappe.db.get_value(\\\"Address\\\", doc.shipping_address, \\\"city\\\")}} Provincia: {{frappe.db.get_value(\\\"Address\\\", doc.shipping_address, \\\"state_code\\\")}}</p>\\n<p>Cap: {{frappe.db.get_value(\\\"Address\\\", doc.shipping_address, \\\"pincode\\\")}} Nazione: {{frappe.db.get_value(\\\"Address\\\", doc.shipping_address, \\\"country\\\")}}</p>\\n</td>\\n</td>\\n</tr>\\n</body>\\n</table>\\n<br>\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"<table border=\\\"1\\\" width=\\\"100%\\\">\\n<head>\\n<th>\\nTipologla\\n</th>\\n<th>\\nArt. 73\\n</th>\\n<th>\\nNumero documento\\n</th>\\n<th>\\nData documento\\n</th>\\n<th>\\nCodice destinatario\\n</th>\\n</head>\\n<body>\\n<tr>\\n<td style=\\\"width: 20%;white-space:nowrap;\\\">\\n{{doc.document_type or \\\" \\\"}}\\n</td>\\n<td style=\\\"width: 10%;white-space:nowrap;\\\">\\n{{\\\" \\\"}}\\n</td>\\n<td style=\\\"width: 20%;white-space:nowrap;\\\">\\n{{doc.bill_no or \\\" \\\"}}\\n</td>\\n<td style=\\\"width: 20%;white-space:nowrap;\\\">\\n{{doc.get_formatted(\\\"bill_date\\\") or \\\" \\\"}}\\n</td>\\n<td style=\\\"width: 30%;white-space:nowrap;\\\">\\n{{doc.destination_code or \\\" \\\"}}\\n</td>\\n</tr>\\n</body>\\n</table>\\n<br>\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"<table border=\\\"1\\\" width=\\\"100%\\\">\\n<head>\\n<th>\\nDescrizione\\n</th>\\n<th>\\nQuantita\\n</th>\\n<th>\\nPrezzo unitario\\n</th>\\n<th>\\nUM\\n</th>\\n<th>\\n%IVA\\n</th>\\n<th>\\nPrezzo totale\\n</th>\\n</head>\\n\\n<body>\\n{%- for row in doc.items -%}\\n<tr>\\n<td style=\\\"width: 30%;\\\">\\n{{row.description or \\\" \\\"}}\\n</td>\\n<td style=\\\"width: 15%;white-space:nowrap;\\\">\\n{{row.get_formatted(\\\"qty\\\", doc)}}\\n</td>\\n<td style=\\\"width: 15%;white-space:nowrap;\\\">\\n{{row.get_formatted(\\\"rate\\\", doc)}}\\n</td>\\n<td style=\\\"width: 5%;white-space:nowrap;\\\">\\n{{row.get_formatted(\\\"uom\\\", doc)}}\\n</td>\\n<td style=\\\"width: 15%;white-space:nowrap;\\\">\\n{{row.get_formatted(\\\"tax_rate\\\", doc)}}\\n</td>\\n<td style=\\\"width: 20%;white-space:nowrap;\\\">\\n{{row.get_formatted(\\\"amount\\\", doc)}}\\n</td>\\n{%- endfor -%}\\n</body>\\n</table>\\n<br>\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"<table border=\\\"1\\\" width=\\\"100%\\\">\\n<head>\\n<th>\\nesigibilita immediata / riferimenti normativi\\n</th>\\n<th>\\n%IVA\\n</th>\\n<th>\\nSpese accessorie\\n</th>\\n<th>\\nArr.\\n</th>\\n<th>\\nTotale imponibile\\n</th>\\n<th>\\nTotale Imposta\\n</th>\\n</head>\\n\\n<body>\\n{%- for row in doc.taxes -%}\\n<tr>\\n<td style=\\\"width: 30%;white-space:nowrap;\\\">\\n{% if 'None' in row.description %}\\n {{ \\\" \\\" }}\\n{% else %}\\n{{row.description}}\\n{% endif %}\\n</td>\\n<td style=\\\"width: 10%;white-space:nowrap;\\\">\\n{{row.get_formatted(\\\"tax_rate\\\", doc)}}\\n</td>\\n<td style=\\\"width: 10%;white-space:nowrap;\\\">\\n{{\\\"0,00\\\"}}\\n</td>\\n<td style=\\\"width: 10%;white-space:nowrap;\\\">\\n{{\\\" \\\"}}\\n</td>\\n<td style=\\\"width: 20%;white-space:nowrap;\\\">\\n{{doc.get_formatted(\\\"base_net_total\\\")}}\\n</td>\\n<td style=\\\"width: 20%;white-space:nowrap;\\\">\\n{{row.get_formatted(\\\"tax_amount\\\", doc)}}\\n</td>\\n{%- endfor -%}\\n</body>\\n</table>\\n<br>\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"<table border=\\\"1\\\" width=\\\"100%\\\">\\n<head>\\n<th>\\nImporto bolio\\n</th>\\n<th>\\nSconto/Magglorazione\\n</th>\\n<th>\\nArr.\\n</th>\\n<th>\\nTotale documento\\n</th>\\n</head>\\n\\n<body>\\n<tr>\\n<td style=\\\"width: 20%;white-space:nowrap;\\\">\\n{{\\\" \\\"}}\\n</td>\\n<td style=\\\"width: 30%;white-space:nowrap;\\\">\\n{{\\\" \\\"}}\\n</td>\\n<td style=\\\"width: 10%;white-space:nowrap;\\\">\\n{{\\\" \\\"}}\\n</td>\\n<td style=\\\"width: 40%;white-space:nowrap;\\\">\\n{{doc.get_formatted(\\\"base_grand_total\\\")}}\\n</td>\\n</body>\\n</table>\\n<br>\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"<table border=\\\"1\\\" width=\\\"100%\\\">\\n<head>\\n<th>\\nModalita pagamento\\n</th>\\n<th>\\nIBAN\\n</th>\\n<th>\\nInstituto\\n</th>\\n<th>\\nData scadenza\\n</th>\\n<th>\\nImporto\\n</th>\\n</head>\\n\\n<body>\\n{%- for row in doc.payment_schedule -%}\\n<tr>\\n<td style=\\\"width: 20%;white-space:nowrap;\\\">\\n{{row.get_formatted(\\\"mode_of_payment_code\\\",doc)}}\\n</td>\\n<td style=\\\"width: 20%;white-space:nowrap;\\\">\\n{{row.get_formatted(\\\"bank_account_iban\\\",doc)}}\\n</td>\\n<td style=\\\"width: 20%;white-space:nowrap;\\\">\\n{{\\\" \\\"}}\\n</td>\\n<td style=\\\"width: 20%;white-space:nowrap;\\\">\\n{{row.get_formatted(\\\"due_date\\\",doc)}}\\n</td>\\n<td style=\\\"width: 20%;white-space:nowrap;\\\">\\n{{row.get_formatted(\\\"payment_amount\\\",doc)}}\\n</td>\\n{%- endfor -%}\\n</body>\\n</table>\"}]",
+ "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
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 47f6cf6..18af062 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -585,12 +585,18 @@
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 @@
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}")
diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py
index a74253e..ccba8b0 100644
--- a/erpnext/stock/report/stock_balance/stock_balance.py
+++ b/erpnext/stock/report/stock_balance/stock_balance.py
@@ -55,7 +55,7 @@
'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])