Merge branch 'develop' into publish-item
This commit is contained in:
		
						commit
						cca6fb2794
					
				| @ -653,4 +653,5 @@ 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 | ||||
| erpnext.patches.v12_0.set_published_in_hub_tracked_item | ||||
							
								
								
									
										15
									
								
								erpnext/patches/v12_0/set_permission_einvoicing.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								erpnext/patches/v12_0/set_permission_einvoicing.py
									
									
									
									
									
										Normal file
									
								
							| @ -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) | ||||
| @ -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({ | ||||
|  | ||||
| @ -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); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| }); | ||||
| @ -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 | ||||
| } | ||||
| @ -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 | ||||
| @ -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 | ||||
| @ -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" | ||||
| 				) | ||||
| 		] | ||||
| 	} | ||||
| 
 | ||||
|  | ||||
| @ -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: | ||||
|  | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @ -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}") | ||||
|  | ||||
| @ -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]) | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user