Merge branch 'version-12-hotfix' into version-12
This commit is contained in:
		
						commit
						9335cdd536
					
				| @ -5,7 +5,7 @@ import frappe | |||||||
| from erpnext.hooks import regional_overrides | from erpnext.hooks import regional_overrides | ||||||
| from frappe.utils import getdate | from frappe.utils import getdate | ||||||
| 
 | 
 | ||||||
| __version__ = '12.0.4' | __version__ = '12.0.5' | ||||||
| 
 | 
 | ||||||
| def get_default_company(user=None): | def get_default_company(user=None): | ||||||
| 	'''Get default company for user''' | 	'''Get default company for user''' | ||||||
|  | |||||||
| @ -6,16 +6,10 @@ from frappe import _ | |||||||
| def get_data(): | def get_data(): | ||||||
| 	return { | 	return { | ||||||
| 		'fieldname': 'bank', | 		'fieldname': 'bank', | ||||||
| 		'non_standard_fieldnames': { |  | ||||||
| 			'Paymnet Order': 'company_bank' |  | ||||||
| 		}, |  | ||||||
| 		'transactions': [ | 		'transactions': [ | ||||||
| 			{ | 			{ | ||||||
| 				'label': _('Bank Deatils'), | 				'label': _('Bank Deatils'), | ||||||
| 				'items': ['Bank Account', 'Bank Guarantee'] | 				'items': ['Bank Account', 'Bank Guarantee'] | ||||||
| 			}, |  | ||||||
| 			{ |  | ||||||
| 				'items': ['Payment Order'] |  | ||||||
| 			} | 			} | ||||||
| 		] | 		] | ||||||
| 	} | 	} | ||||||
|  | |||||||
| @ -648,13 +648,18 @@ def get_orders_to_be_billed(posting_date, party_type, party, | |||||||
| 
 | 
 | ||||||
| 	orders = [] | 	orders = [] | ||||||
| 	if voucher_type: | 	if voucher_type: | ||||||
| 		ref_field = "base_grand_total" if party_account_currency == company_currency else "grand_total" | 		if party_account_currency == company_currency: | ||||||
|  | 			grand_total_field = "base_grand_total" | ||||||
|  | 			rounded_total_field = "base_rounded_total" | ||||||
|  | 		else: | ||||||
|  | 			grand_total_field = "grand_total" | ||||||
|  | 			rounded_total_field = "rounded_total" | ||||||
| 
 | 
 | ||||||
| 		orders = frappe.db.sql(""" | 		orders = frappe.db.sql(""" | ||||||
| 			select | 			select | ||||||
| 				name as voucher_no, | 				name as voucher_no, | ||||||
| 				{ref_field} as invoice_amount, | 				if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) as invoice_amount, | ||||||
| 				({ref_field} - advance_paid) as outstanding_amount, | 				(if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) - advance_paid) as outstanding_amount, | ||||||
| 				transaction_date as posting_date | 				transaction_date as posting_date | ||||||
| 			from | 			from | ||||||
| 				`tab{voucher_type}` | 				`tab{voucher_type}` | ||||||
| @ -663,13 +668,14 @@ def get_orders_to_be_billed(posting_date, party_type, party, | |||||||
| 				and docstatus = 1 | 				and docstatus = 1 | ||||||
| 				and company = %s | 				and company = %s | ||||||
| 				and ifnull(status, "") != "Closed" | 				and ifnull(status, "") != "Closed" | ||||||
| 				and {ref_field} > advance_paid | 				and if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) > advance_paid | ||||||
| 				and abs(100 - per_billed) > 0.01 | 				and abs(100 - per_billed) > 0.01 | ||||||
| 				{condition} | 				{condition} | ||||||
| 			order by | 			order by | ||||||
| 				transaction_date, name | 				transaction_date, name | ||||||
| 		""".format(**{ | 		""".format(**{ | ||||||
| 			"ref_field": ref_field, | 			"rounded_total_field": rounded_total_field, | ||||||
|  | 			"grand_total_field": grand_total_field, | ||||||
| 			"voucher_type": voucher_type, | 			"voucher_type": voucher_type, | ||||||
| 			"party_type": scrub(party_type), | 			"party_type": scrub(party_type), | ||||||
| 			"condition": condition | 			"condition": condition | ||||||
|  | |||||||
| @ -382,7 +382,7 @@ def get_qty_amount_data_for_cumulative(pr_doc, doc, items=[]): | |||||||
| 			`tab{child_doc}`.amount | 			`tab{child_doc}`.amount | ||||||
| 		FROM `tab{child_doc}`, `tab{parent_doc}` | 		FROM `tab{child_doc}`, `tab{parent_doc}` | ||||||
| 		WHERE | 		WHERE | ||||||
| 			`tab{child_doc}`.parent = `tab{parent_doc}`.name and {date_field} | 			`tab{child_doc}`.parent = `tab{parent_doc}`.name and `tab{parent_doc}`.{date_field} | ||||||
| 			between %s and %s and `tab{parent_doc}`.docstatus = 1 | 			between %s and %s and `tab{parent_doc}`.docstatus = 1 | ||||||
| 			{condition} group by `tab{child_doc}`.name | 			{condition} group by `tab{child_doc}`.name | ||||||
| 	""".format(parent_doc = doctype, | 	""".format(parent_doc = doctype, | ||||||
|  | |||||||
| @ -93,6 +93,7 @@ def check_if_in_list(gle, gl_map, dimensions=None): | |||||||
| def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False): | def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False): | ||||||
| 	if not from_repost: | 	if not from_repost: | ||||||
| 		validate_account_for_perpetual_inventory(gl_map) | 		validate_account_for_perpetual_inventory(gl_map) | ||||||
|  | 		validate_cwip_accounts(gl_map) | ||||||
| 
 | 
 | ||||||
| 	round_off_debit_credit(gl_map) | 	round_off_debit_credit(gl_map) | ||||||
| 
 | 
 | ||||||
| @ -123,6 +124,16 @@ def validate_account_for_perpetual_inventory(gl_map): | |||||||
| 					frappe.throw(_("Account: {0} can only be updated via Stock Transactions") | 					frappe.throw(_("Account: {0} can only be updated via Stock Transactions") | ||||||
| 						.format(entry.account), StockAccountInvalidTransaction) | 						.format(entry.account), StockAccountInvalidTransaction) | ||||||
| 
 | 
 | ||||||
|  | def validate_cwip_accounts(gl_map): | ||||||
|  | 	if not cint(frappe.db.get_value("Asset Settings", None, "disable_cwip_accounting")) \ | ||||||
|  | 		and gl_map[0].voucher_type == "Journal Entry": | ||||||
|  | 			cwip_accounts = [d[0] for d in frappe.db.sql("""select name from tabAccount | ||||||
|  | 				where account_type = 'Capital Work in Progress' and is_group=0""")] | ||||||
|  | 
 | ||||||
|  | 			for entry in gl_map: | ||||||
|  | 				if entry.account in cwip_accounts: | ||||||
|  | 					frappe.throw(_("Account: <b>{0}</b> is capital Work in progress and can not be updated by Journal Entry").format(entry.account)) | ||||||
|  | 
 | ||||||
| def round_off_debit_credit(gl_map): | def round_off_debit_credit(gl_map): | ||||||
| 	precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), | 	precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), | ||||||
| 		currency=frappe.get_cached_value('Company',  gl_map[0].company,  "default_currency")) | 		currency=frappe.get_cached_value('Company',  gl_map[0].company,  "default_currency")) | ||||||
|  | |||||||
| @ -30,7 +30,9 @@ def update_last_purchase_rate(doc, is_submit): | |||||||
| 			# for it to be considered for latest purchase rate | 			# for it to be considered for latest purchase rate | ||||||
| 			if flt(d.conversion_factor): | 			if flt(d.conversion_factor): | ||||||
| 				last_purchase_rate = flt(d.base_rate) / flt(d.conversion_factor) | 				last_purchase_rate = flt(d.base_rate) / flt(d.conversion_factor) | ||||||
| 			else: | 			# Check if item code is present | ||||||
|  | 			# Conversion factor should not be mandatory for non itemized items | ||||||
|  | 			elif d.item_code: | ||||||
| 				frappe.throw(_("UOM Conversion factor is required in row {0}").format(d.idx)) | 				frappe.throw(_("UOM Conversion factor is required in row {0}").format(d.idx)) | ||||||
| 
 | 
 | ||||||
| 		# update last purchsae rate | 		# update last purchsae rate | ||||||
| @ -84,13 +86,13 @@ def get_linked_material_requests(items): | |||||||
| 	items = json.loads(items) | 	items = json.loads(items) | ||||||
| 	mr_list = [] | 	mr_list = [] | ||||||
| 	for item in items: | 	for item in items: | ||||||
| 		material_request = frappe.db.sql("""SELECT distinct mr.name AS mr_name,  | 		material_request = frappe.db.sql("""SELECT distinct mr.name AS mr_name, | ||||||
| 				(mr_item.qty - mr_item.ordered_qty) AS qty,  | 				(mr_item.qty - mr_item.ordered_qty) AS qty, | ||||||
| 				mr_item.item_code AS item_code, | 				mr_item.item_code AS item_code, | ||||||
| 				mr_item.name AS mr_item  | 				mr_item.name AS mr_item | ||||||
| 			FROM `tabMaterial Request` mr, `tabMaterial Request Item` mr_item | 			FROM `tabMaterial Request` mr, `tabMaterial Request Item` mr_item | ||||||
| 			WHERE mr.name = mr_item.parent | 			WHERE mr.name = mr_item.parent | ||||||
| 				AND mr_item.item_code = %(item)s  | 				AND mr_item.item_code = %(item)s | ||||||
| 				AND mr.material_request_type = 'Purchase' | 				AND mr.material_request_type = 'Purchase' | ||||||
| 				AND mr.per_ordered < 99.99 | 				AND mr.per_ordered < 99.99 | ||||||
| 				AND mr.docstatus = 1 | 				AND mr.docstatus = 1 | ||||||
| @ -98,6 +100,6 @@ def get_linked_material_requests(items): | |||||||
|                         ORDER BY mr_item.item_code ASC""",{"item": item}, as_dict=1) |                         ORDER BY mr_item.item_code ASC""",{"item": item}, as_dict=1) | ||||||
| 		if material_request: | 		if material_request: | ||||||
| 			mr_list.append(material_request) | 			mr_list.append(material_request) | ||||||
| 	 | 
 | ||||||
| 	return mr_list | 	return mr_list | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -94,6 +94,13 @@ def get_data(): | |||||||
| 					"name": "BOM Update Tool", | 					"name": "BOM Update Tool", | ||||||
| 					"description": _("Replace BOM and update latest price in all BOMs"), | 					"description": _("Replace BOM and update latest price in all BOMs"), | ||||||
| 				}, | 				}, | ||||||
|  | 				{ | ||||||
|  | 					"type": "page", | ||||||
|  | 					"label": _("BOM Comparison Tool"), | ||||||
|  | 					"name": "bom-comparison-tool", | ||||||
|  | 					"description": _("Compare BOMs for changes in Raw Materials and Operations"), | ||||||
|  | 					"data_doctype": "BOM" | ||||||
|  | 				}, | ||||||
| 			] | 			] | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
|  | |||||||
| @ -1192,6 +1192,10 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil | |||||||
| 						 .format(child_item.idx, child_item.item_code)) | 						 .format(child_item.idx, child_item.item_code)) | ||||||
| 		else: | 		else: | ||||||
| 			child_item.rate = flt(d.get("rate")) | 			child_item.rate = flt(d.get("rate")) | ||||||
|  | 		if child_item.price_list_rate: | ||||||
|  | 			child_item.discount_percentage = flt((1 - flt(child_item.rate) / flt(child_item.price_list_rate)) * 100.0, \ | ||||||
|  | 				child_item.precision("discount_percentage")) | ||||||
|  | 
 | ||||||
| 		child_item.flags.ignore_validate_update_after_submit = True | 		child_item.flags.ignore_validate_update_after_submit = True | ||||||
| 		if new_child_flag: | 		if new_child_flag: | ||||||
| 			child_item.idx = len(parent.items) + 1 | 			child_item.idx = len(parent.items) + 1 | ||||||
|  | |||||||
| @ -395,7 +395,9 @@ class BuyingController(StockController): | |||||||
| 	def set_qty_as_per_stock_uom(self): | 	def set_qty_as_per_stock_uom(self): | ||||||
| 		for d in self.get("items"): | 		for d in self.get("items"): | ||||||
| 			if d.meta.get_field("stock_qty"): | 			if d.meta.get_field("stock_qty"): | ||||||
| 				if not d.conversion_factor: | 				# Check if item code is present | ||||||
|  | 				# Conversion factor should not be mandatory for non itemized items | ||||||
|  | 				if not d.conversion_factor and d.item_code: | ||||||
| 					frappe.throw(_("Row {0}: Conversion Factor is mandatory").format(d.idx)) | 					frappe.throw(_("Row {0}: Conversion Factor is mandatory").format(d.idx)) | ||||||
| 				d.stock_qty = flt(d.qty) * flt(d.conversion_factor) | 				d.stock_qty = flt(d.qty) * flt(d.conversion_factor) | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -82,7 +82,7 @@ class calculate_taxes_and_totals(object): | |||||||
| 
 | 
 | ||||||
| 				item.net_rate = item.rate | 				item.net_rate = item.rate | ||||||
| 
 | 
 | ||||||
| 				if not item.qty and self.doc.is_return: | 				if not item.qty and self.doc.get("is_return"): | ||||||
| 					item.amount = flt(-1 * item.rate, item.precision("amount")) | 					item.amount = flt(-1 * item.rate, item.precision("amount")) | ||||||
| 				else: | 				else: | ||||||
| 					item.amount = flt(item.rate * item.qty,	item.precision("amount")) | 					item.amount = flt(item.rate * item.qty,	item.precision("amount")) | ||||||
|  | |||||||
| @ -77,7 +77,7 @@ def get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_len | |||||||
| 
 | 
 | ||||||
| 	if or_filters: | 	if or_filters: | ||||||
| 		for r in frappe.get_list(doctype, fields=fields,filters=filters, or_filters=or_filters, | 		for r in frappe.get_list(doctype, fields=fields,filters=filters, or_filters=or_filters, | ||||||
| 			limit_start=limit_start, limit_page_length=limit_page_length,  | 			limit_start=limit_start, limit_page_length=limit_page_length, | ||||||
| 			ignore_permissions=ignore_permissions, order_by=order_by): | 			ignore_permissions=ignore_permissions, order_by=order_by): | ||||||
| 			data.append(r) | 			data.append(r) | ||||||
| 
 | 
 | ||||||
| @ -130,38 +130,56 @@ def get_customers_suppliers(doctype, user): | |||||||
| 	suppliers = [] | 	suppliers = [] | ||||||
| 	meta = frappe.get_meta(doctype) | 	meta = frappe.get_meta(doctype) | ||||||
| 
 | 
 | ||||||
|  | 	customer_field_name = get_customer_field_name(doctype) | ||||||
|  | 
 | ||||||
|  | 	has_customer_field = meta.has_field(customer_field_name) | ||||||
|  | 	has_supplier_field = meta.has_field('supplier') | ||||||
|  | 
 | ||||||
| 	if has_common(["Supplier", "Customer"], frappe.get_roles(user)): | 	if has_common(["Supplier", "Customer"], frappe.get_roles(user)): | ||||||
| 		contacts = frappe.db.sql(""" | 		contacts = frappe.db.sql(""" | ||||||
| 			select  | 			select | ||||||
| 				`tabContact`.email_id, | 				`tabContact`.email_id, | ||||||
| 				`tabDynamic Link`.link_doctype, | 				`tabDynamic Link`.link_doctype, | ||||||
| 				`tabDynamic Link`.link_name | 				`tabDynamic Link`.link_name | ||||||
| 			from  | 			from | ||||||
| 				`tabContact`, `tabDynamic Link` | 				`tabContact`, `tabDynamic Link` | ||||||
| 			where | 			where | ||||||
| 				`tabContact`.name=`tabDynamic Link`.parent and `tabContact`.email_id =%s | 				`tabContact`.name=`tabDynamic Link`.parent and `tabContact`.email_id =%s | ||||||
| 			""", user, as_dict=1) | 			""", user, as_dict=1) | ||||||
| 		customers = [c.link_name for c in contacts if c.link_doctype == 'Customer'] \ | 		customers = [c.link_name for c in contacts if c.link_doctype == 'Customer'] | ||||||
| 			if meta.get_field("customer") else None | 		suppliers = [c.link_name for c in contacts if c.link_doctype == 'Supplier'] | ||||||
| 		suppliers = [c.link_name for c in contacts if c.link_doctype == 'Supplier'] \ |  | ||||||
| 			if meta.get_field("supplier") else None |  | ||||||
| 	elif frappe.has_permission(doctype, 'read', user=user): | 	elif frappe.has_permission(doctype, 'read', user=user): | ||||||
| 		customers = [customer.name for customer in frappe.get_list("Customer")] \ | 		customer_list = frappe.get_list("Customer") | ||||||
| 			if meta.get_field("customer") else None | 		customers = suppliers = [customer.name for customer in customer_list] | ||||||
| 		suppliers = [supplier.name for supplier in frappe.get_list("Customer")] \ |  | ||||||
| 			if meta.get_field("supplier") else None |  | ||||||
| 
 | 
 | ||||||
| 	return customers, suppliers | 	return customers if has_customer_field else None, \ | ||||||
|  | 		suppliers if has_supplier_field else None | ||||||
| 
 | 
 | ||||||
| def has_website_permission(doc, ptype, user, verbose=False): | def has_website_permission(doc, ptype, user, verbose=False): | ||||||
| 	doctype = doc.doctype | 	doctype = doc.doctype | ||||||
| 	customers, suppliers = get_customers_suppliers(doctype, user) | 	customers, suppliers = get_customers_suppliers(doctype, user) | ||||||
| 	if customers: | 	if customers: | ||||||
| 		return frappe.get_all(doctype, filters=[(doctype, "customer", "in", customers), | 		return frappe.db.exists(doctype, filters=get_customer_filter(doc, customers)) | ||||||
| 			(doctype, "name", "=", doc.name)]) and True or False |  | ||||||
| 	elif suppliers: | 	elif suppliers: | ||||||
| 		fieldname = 'suppliers' if doctype == 'Request for Quotation' else 'supplier' | 		fieldname = 'suppliers' if doctype == 'Request for Quotation' else 'supplier' | ||||||
| 		return frappe.get_all(doctype, filters=[(doctype, fieldname, "in", suppliers), | 		return frappe.db.exists(doctype, filters={ | ||||||
| 			(doctype, "name", "=", doc.name)]) and True or False | 			'name': doc.name, | ||||||
|  | 			fieldname: ["in", suppliers] | ||||||
|  | 		}) | ||||||
| 	else: | 	else: | ||||||
| 		return False | 		return False | ||||||
|  | 
 | ||||||
|  | def get_customer_filter(doc, customers): | ||||||
|  | 	doctype = doc.doctype | ||||||
|  | 	filters = frappe._dict() | ||||||
|  | 	filters.name = doc.name | ||||||
|  | 	filters[get_customer_field_name(doctype)] = ['in', customers] | ||||||
|  | 	if doctype == 'Quotation': | ||||||
|  | 		filters.party_type = 'Customer' | ||||||
|  | 	return filters | ||||||
|  | 
 | ||||||
|  | def get_customer_field_name(doctype): | ||||||
|  | 	if doctype == 'Quotation': | ||||||
|  | 		return 'party_name' | ||||||
|  | 	else: | ||||||
|  | 		return 'customer' | ||||||
| @ -64,13 +64,20 @@ class EmployeeAdvance(Document): | |||||||
| 
 | 
 | ||||||
| 	def update_claimed_amount(self): | 	def update_claimed_amount(self): | ||||||
| 		claimed_amount = frappe.db.sql(""" | 		claimed_amount = frappe.db.sql(""" | ||||||
| 			select sum(ifnull(allocated_amount, 0)) | 			SELECT sum(ifnull(allocated_amount, 0)) | ||||||
| 			from `tabExpense Claim Advance` | 			FROM `tabExpense Claim Advance` eca, `tabExpense Claim` ec | ||||||
| 			where employee_advance = %s and docstatus=1 and allocated_amount > 0 | 			WHERE | ||||||
|  | 				eca.employee_advance = %s | ||||||
|  | 				AND ec.approval_status="Approved" | ||||||
|  | 				AND ec.name = eca.parent | ||||||
|  | 				AND ec.docstatus=1 | ||||||
|  | 				AND eca.allocated_amount > 0 | ||||||
| 		""", self.name)[0][0] or 0 | 		""", self.name)[0][0] or 0 | ||||||
| 
 | 
 | ||||||
| 		if claimed_amount: | 		frappe.db.set_value("Employee Advance", self.name, "claimed_amount", flt(claimed_amount)) | ||||||
| 			frappe.db.set_value("Employee Advance", self.name, "claimed_amount", flt(claimed_amount)) | 		self.reload() | ||||||
|  | 		self.set_status() | ||||||
|  | 		frappe.db.set_value("Employee Advance", self.name, "status", self.status) | ||||||
| 
 | 
 | ||||||
| @frappe.whitelist() | @frappe.whitelist() | ||||||
| def get_due_advance_amount(employee, posting_date): | def get_due_advance_amount(employee, posting_date): | ||||||
|  | |||||||
| @ -183,7 +183,7 @@ frappe.ui.form.on("Expense Claim", { | |||||||
| 	refresh: function(frm) { | 	refresh: function(frm) { | ||||||
| 		frm.trigger("toggle_fields"); | 		frm.trigger("toggle_fields"); | ||||||
| 
 | 
 | ||||||
| 		if(frm.doc.docstatus == 1) { | 		if(frm.doc.docstatus === 1 && frm.doc.approval_status !== "Rejected") { | ||||||
| 			frm.add_custom_button(__('Accounting Ledger'), function() { | 			frm.add_custom_button(__('Accounting Ledger'), function() { | ||||||
| 				frappe.route_options = { | 				frappe.route_options = { | ||||||
| 					voucher_no: frm.doc.name, | 					voucher_no: frm.doc.name, | ||||||
| @ -194,7 +194,7 @@ frappe.ui.form.on("Expense Claim", { | |||||||
| 			}, __("View")); | 			}, __("View")); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if (frm.doc.docstatus===1 | 		if (frm.doc.docstatus===1 && !cint(frm.doc.is_paid) && cint(frm.doc.grand_total) > 0 | ||||||
| 				&& (cint(frm.doc.total_amount_reimbursed) < cint(frm.doc.total_sanctioned_amount)) | 				&& (cint(frm.doc.total_amount_reimbursed) < cint(frm.doc.total_sanctioned_amount)) | ||||||
| 				&& frappe.model.can_create("Payment Entry")) { | 				&& frappe.model.can_create("Payment Entry")) { | ||||||
| 			frm.add_custom_button(__('Payment'), | 			frm.add_custom_button(__('Payment'), | ||||||
|  | |||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -9,6 +9,7 @@ from erpnext.setup.utils import get_exchange_rate | |||||||
| from frappe.website.website_generator import WebsiteGenerator | from frappe.website.website_generator import WebsiteGenerator | ||||||
| from erpnext.stock.get_item_details import get_conversion_factor | from erpnext.stock.get_item_details import get_conversion_factor | ||||||
| from erpnext.stock.get_item_details import get_price_list_rate | from erpnext.stock.get_item_details import get_price_list_rate | ||||||
|  | from frappe.core.doctype.version.version import get_diff | ||||||
| 
 | 
 | ||||||
| import functools | import functools | ||||||
| 
 | 
 | ||||||
| @ -763,3 +764,52 @@ def add_additional_cost(stock_entry, work_order): | |||||||
| 			'description': name[0], | 			'description': name[0], | ||||||
| 			'amount': items.get(name[0]) | 			'amount': items.get(name[0]) | ||||||
| 		}) | 		}) | ||||||
|  | 
 | ||||||
|  | @frappe.whitelist() | ||||||
|  | def get_bom_diff(bom1, bom2): | ||||||
|  | 	from frappe.model import table_fields | ||||||
|  | 
 | ||||||
|  | 	doc1 = frappe.get_doc('BOM', bom1) | ||||||
|  | 	doc2 = frappe.get_doc('BOM', bom2) | ||||||
|  | 
 | ||||||
|  | 	out = get_diff(doc1, doc2) | ||||||
|  | 	out.row_changed = [] | ||||||
|  | 	out.added = [] | ||||||
|  | 	out.removed = [] | ||||||
|  | 
 | ||||||
|  | 	meta = doc1.meta | ||||||
|  | 
 | ||||||
|  | 	identifiers = { | ||||||
|  | 		'operations': 'operation', | ||||||
|  | 		'items': 'item_code', | ||||||
|  | 		'scrap_items': 'item_code', | ||||||
|  | 		'exploded_items': 'item_code' | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for df in meta.fields: | ||||||
|  | 		old_value, new_value = doc1.get(df.fieldname), doc2.get(df.fieldname) | ||||||
|  | 
 | ||||||
|  | 		if df.fieldtype in table_fields: | ||||||
|  | 			identifier = identifiers[df.fieldname] | ||||||
|  | 			# make maps | ||||||
|  | 			old_row_by_identifier, new_row_by_identifier = {}, {} | ||||||
|  | 			for d in old_value: | ||||||
|  | 				old_row_by_identifier[d.get(identifier)] = d | ||||||
|  | 			for d in new_value: | ||||||
|  | 				new_row_by_identifier[d.get(identifier)] = d | ||||||
|  | 
 | ||||||
|  | 			# check rows for additions, changes | ||||||
|  | 			for i, d in enumerate(new_value): | ||||||
|  | 				if d.get(identifier) in old_row_by_identifier: | ||||||
|  | 					diff = get_diff(old_row_by_identifier[d.get(identifier)], d, for_child=True) | ||||||
|  | 					if diff and diff.changed: | ||||||
|  | 						out.row_changed.append((df.fieldname, i, d.get(identifier), diff.changed)) | ||||||
|  | 				else: | ||||||
|  | 					out.added.append([df.fieldname, d.as_dict()]) | ||||||
|  | 
 | ||||||
|  | 			# check for deletions | ||||||
|  | 			for d in old_value: | ||||||
|  | 				if not d.get(identifier) in new_row_by_identifier: | ||||||
|  | 					out.removed.append([df.fieldname, d.as_dict()]) | ||||||
|  | 
 | ||||||
|  | 	return out | ||||||
|  | |||||||
| @ -320,7 +320,8 @@ class ProductionPlan(Document): | |||||||
| 				'qty': data.get("stock_qty") * item.get("qty"), | 				'qty': data.get("stock_qty") * item.get("qty"), | ||||||
| 				'production_plan': self.name, | 				'production_plan': self.name, | ||||||
| 				'company': self.company, | 				'company': self.company, | ||||||
| 				'fg_warehouse': item.get("fg_warehouse") | 				'fg_warehouse': item.get("fg_warehouse"), | ||||||
|  | 				'update_consumed_material_cost_in_project': 0 | ||||||
| 			}) | 			}) | ||||||
| 
 | 
 | ||||||
| 			work_order = self.create_work_order(data) | 			work_order = self.create_work_order(data) | ||||||
| @ -430,7 +431,7 @@ def download_raw_materials(production_plan): | |||||||
| 					continue | 					continue | ||||||
| 
 | 
 | ||||||
| 				item_list.append(['', '', '', '', bin_dict.get('warehouse'), | 				item_list.append(['', '', '', '', bin_dict.get('warehouse'), | ||||||
| 					bin_dict.get('projected_qty'), bin_dict.get('actual_qty')]) | 					bin_dict.get('projected_qty', 0), bin_dict.get('actual_qty', 0)]) | ||||||
| 
 | 
 | ||||||
| 	build_csv_response(item_list, doc.name) | 	build_csv_response(item_list, doc.name) | ||||||
| 
 | 
 | ||||||
| @ -507,8 +508,8 @@ def get_material_request_items(row, sales_order, | |||||||
| 	required_qty = 0 | 	required_qty = 0 | ||||||
| 	if ignore_existing_ordered_qty or bin_dict.get("projected_qty", 0) < 0: | 	if ignore_existing_ordered_qty or bin_dict.get("projected_qty", 0) < 0: | ||||||
| 		required_qty = total_qty | 		required_qty = total_qty | ||||||
| 	elif total_qty > bin_dict.get("projected_qty"): | 	elif total_qty > bin_dict.get("projected_qty", 0): | ||||||
| 		required_qty = total_qty - bin_dict.get("projected_qty") | 		required_qty = total_qty - bin_dict.get("projected_qty", 0) | ||||||
| 	if required_qty > 0 and required_qty < row['min_order_qty']: | 	if required_qty > 0 and required_qty < row['min_order_qty']: | ||||||
| 		required_qty = row['min_order_qty'] | 		required_qty = row['min_order_qty'] | ||||||
| 	item_group_defaults = get_item_group_defaults(row.item_code, company) | 	item_group_defaults = get_item_group_defaults(row.item_code, company) | ||||||
|  | |||||||
| @ -1,484 +1,504 @@ | |||||||
| { | { | ||||||
|    "allow_import": 1, |  "allow_import": 1, | ||||||
|    "autoname": "naming_series:", |  "autoname": "naming_series:", | ||||||
|    "creation": "2013-01-10 16:34:16", |  "creation": "2013-01-10 16:34:16", | ||||||
|    "doctype": "DocType", |  "doctype": "DocType", | ||||||
|    "document_type": "Setup", |  "document_type": "Setup", | ||||||
|    "field_order": [ |  "engine": "InnoDB", | ||||||
|     "item", |  "field_order": [ | ||||||
|     "naming_series", |   "item", | ||||||
|     "status", |   "naming_series", | ||||||
|     "production_item", |   "status", | ||||||
|     "item_name", |   "production_item", | ||||||
|     "image", |   "item_name", | ||||||
|     "bom_no", |   "image", | ||||||
|     "allow_alternative_item", |   "bom_no", | ||||||
|     "use_multi_level_bom", |   "column_break1", | ||||||
|     "skip_transfer", |   "company", | ||||||
|     "column_break1", |   "qty", | ||||||
|     "company", |   "material_transferred_for_manufacturing", | ||||||
|     "qty", |   "produced_qty", | ||||||
|     "material_transferred_for_manufacturing", |   "sales_order", | ||||||
|     "produced_qty", |   "project", | ||||||
|     "sales_order", |   "settings_section", | ||||||
|     "project", |   "allow_alternative_item", | ||||||
|     "from_wip_warehouse", |   "use_multi_level_bom", | ||||||
|     "warehouses", |   "column_break_18", | ||||||
|     "wip_warehouse", |   "skip_transfer", | ||||||
|     "fg_warehouse", |   "from_wip_warehouse", | ||||||
|     "column_break_12", |   "update_consumed_material_cost_in_project", | ||||||
|     "scrap_warehouse", |   "warehouses", | ||||||
|     "required_items_section", |   "wip_warehouse", | ||||||
|     "required_items", |   "fg_warehouse", | ||||||
|     "time", |   "column_break_12", | ||||||
|     "planned_start_date", |   "scrap_warehouse", | ||||||
|     "actual_start_date", |   "required_items_section", | ||||||
|     "column_break_13", |   "required_items", | ||||||
|     "planned_end_date", |   "time", | ||||||
|     "actual_end_date", |   "planned_start_date", | ||||||
|     "expected_delivery_date", |   "actual_start_date", | ||||||
|     "operations_section", |   "column_break_13", | ||||||
|     "transfer_material_against", |   "planned_end_date", | ||||||
|     "operations", |   "actual_end_date", | ||||||
|     "section_break_22", |   "expected_delivery_date", | ||||||
|     "planned_operating_cost", |   "operations_section", | ||||||
|     "actual_operating_cost", |   "transfer_material_against", | ||||||
|     "additional_operating_cost", |   "operations", | ||||||
|     "column_break_24", |   "section_break_22", | ||||||
|     "total_operating_cost", |   "planned_operating_cost", | ||||||
|     "more_info", |   "actual_operating_cost", | ||||||
|     "description", |   "additional_operating_cost", | ||||||
|     "stock_uom", |   "column_break_24", | ||||||
|     "column_break2", |   "total_operating_cost", | ||||||
|     "material_request", |   "more_info", | ||||||
|     "material_request_item", |   "description", | ||||||
|     "sales_order_item", |   "stock_uom", | ||||||
|     "production_plan", |   "column_break2", | ||||||
|     "production_plan_item", |   "material_request", | ||||||
|     "product_bundle_item", |   "material_request_item", | ||||||
|     "amended_from" |   "sales_order_item", | ||||||
|    ], |   "production_plan", | ||||||
|    "fields": [ |   "production_plan_item", | ||||||
|     { |   "product_bundle_item", | ||||||
|      "fieldname": "item", |   "amended_from" | ||||||
|      "fieldtype": "Section Break", |  ], | ||||||
|      "options": "fa fa-gift" |  "fields": [ | ||||||
|     }, |   { | ||||||
|     { |    "fieldname": "item", | ||||||
|      "fieldname": "naming_series", |    "fieldtype": "Section Break", | ||||||
|      "fieldtype": "Select", |    "options": "fa fa-gift" | ||||||
|      "label": "Series", |   }, | ||||||
|      "options": "MFG-WO-.YYYY.-", |   { | ||||||
|      "print_hide": 1, |    "fieldname": "naming_series", | ||||||
|      "reqd": 1, |    "fieldtype": "Select", | ||||||
|      "set_only_once": 1 |    "label": "Series", | ||||||
|     }, |    "options": "MFG-WO-.YYYY.-", | ||||||
|     { |    "print_hide": 1, | ||||||
|      "default": "Draft", |    "reqd": 1, | ||||||
|      "depends_on": "eval:!doc.__islocal", |    "set_only_once": 1 | ||||||
|      "fieldname": "status", |   }, | ||||||
|      "fieldtype": "Select", |   { | ||||||
|      "label": "Status", |    "default": "Draft", | ||||||
|      "no_copy": 1, |    "depends_on": "eval:!doc.__islocal", | ||||||
|      "oldfieldname": "status", |    "fieldname": "status", | ||||||
|      "oldfieldtype": "Select", |    "fieldtype": "Select", | ||||||
|      "options": "\nDraft\nSubmitted\nNot Started\nIn Process\nCompleted\nStopped\nCancelled", |    "label": "Status", | ||||||
|      "read_only": 1, |    "no_copy": 1, | ||||||
|      "reqd": 1, |    "oldfieldname": "status", | ||||||
|      "search_index": 1 |    "oldfieldtype": "Select", | ||||||
|     }, |    "options": "\nDraft\nSubmitted\nNot Started\nIn Process\nCompleted\nStopped\nCancelled", | ||||||
|     { |    "read_only": 1, | ||||||
|      "fieldname": "production_item", |    "reqd": 1, | ||||||
|      "fieldtype": "Link", |    "search_index": 1 | ||||||
|      "in_global_search": 1, |   }, | ||||||
|      "in_list_view": 1, |   { | ||||||
|      "in_standard_filter": 1, |    "fieldname": "production_item", | ||||||
|      "label": "Item To Manufacture", |    "fieldtype": "Link", | ||||||
|      "oldfieldname": "production_item", |    "in_global_search": 1, | ||||||
|      "oldfieldtype": "Link", |    "in_list_view": 1, | ||||||
|      "options": "Item", |    "in_standard_filter": 1, | ||||||
|      "reqd": 1 |    "label": "Item To Manufacture", | ||||||
|     }, |    "oldfieldname": "production_item", | ||||||
|     { |    "oldfieldtype": "Link", | ||||||
|      "depends_on": "eval:doc.production_item", |    "options": "Item", | ||||||
|      "fieldname": "item_name", |    "reqd": 1 | ||||||
|      "fieldtype": "Data", |   }, | ||||||
|      "label": "Item Name", |   { | ||||||
|      "read_only": 1 |    "depends_on": "eval:doc.production_item", | ||||||
|     }, |    "fieldname": "item_name", | ||||||
|     { |    "fieldtype": "Data", | ||||||
|      "fetch_from": "production_item.image", |    "label": "Item Name", | ||||||
|      "fieldname": "image", |    "read_only": 1 | ||||||
|      "fieldtype": "Attach Image", |   }, | ||||||
|      "hidden": 1, |   { | ||||||
|      "label": "Image", |    "fetch_from": "production_item.image", | ||||||
|      "options": "image", |    "fieldname": "image", | ||||||
|      "print_hide": 1, |    "fieldtype": "Attach Image", | ||||||
|      "read_only": 1 |    "hidden": 1, | ||||||
|     }, |    "label": "Image", | ||||||
|     { |    "options": "image", | ||||||
|      "fieldname": "bom_no", |    "print_hide": 1, | ||||||
|      "fieldtype": "Link", |    "read_only": 1 | ||||||
|      "label": "BOM No", |   }, | ||||||
|      "oldfieldname": "bom_no", |   { | ||||||
|      "oldfieldtype": "Link", |    "fieldname": "bom_no", | ||||||
|      "options": "BOM", |    "fieldtype": "Link", | ||||||
|      "reqd": 1 |    "label": "BOM No", | ||||||
|     }, |    "oldfieldname": "bom_no", | ||||||
|     { |    "oldfieldtype": "Link", | ||||||
|      "default": "0", |    "options": "BOM", | ||||||
|      "fieldname": "allow_alternative_item", |    "reqd": 1 | ||||||
|      "fieldtype": "Check", |   }, | ||||||
|      "label": "Allow Alternative Item" |   { | ||||||
|     }, |    "default": "0", | ||||||
|     { |    "fieldname": "allow_alternative_item", | ||||||
|      "default": "1", |    "fieldtype": "Check", | ||||||
|      "description": "Plan material for sub-assemblies", |    "label": "Allow Alternative Item" | ||||||
|      "fieldname": "use_multi_level_bom", |   }, | ||||||
|      "fieldtype": "Check", |   { | ||||||
|      "label": "Use Multi-Level BOM", |    "default": "1", | ||||||
|      "print_hide": 1 |    "description": "Plan material for sub-assemblies", | ||||||
|     }, |    "fieldname": "use_multi_level_bom", | ||||||
|     { |    "fieldtype": "Check", | ||||||
|      "default": "0", |    "label": "Use Multi-Level BOM", | ||||||
|      "description": "Check if material transfer entry is not required", |    "print_hide": 1 | ||||||
|      "fieldname": "skip_transfer", |   }, | ||||||
|      "fieldtype": "Check", |   { | ||||||
|      "label": "Skip Material Transfer to WIP Warehouse" |    "default": "0", | ||||||
|     }, |    "description": "Check if material transfer entry is not required", | ||||||
|     { |    "fieldname": "skip_transfer", | ||||||
|      "fieldname": "column_break1", |    "fieldtype": "Check", | ||||||
|      "fieldtype": "Column Break", |    "label": "Skip Material Transfer to WIP Warehouse" | ||||||
|      "oldfieldtype": "Column Break", |   }, | ||||||
|      "width": "50%" |   { | ||||||
|     }, |    "fieldname": "column_break1", | ||||||
|     { |    "fieldtype": "Column Break", | ||||||
|      "fieldname": "company", |    "oldfieldtype": "Column Break", | ||||||
|      "fieldtype": "Link", |    "width": "50%" | ||||||
|      "label": "Company", |   }, | ||||||
|      "oldfieldname": "company", |   { | ||||||
|      "oldfieldtype": "Link", |    "fieldname": "company", | ||||||
|      "options": "Company", |    "fieldtype": "Link", | ||||||
|      "remember_last_selected_value": 1, |    "label": "Company", | ||||||
|      "reqd": 1 |    "oldfieldname": "company", | ||||||
|     }, |    "oldfieldtype": "Link", | ||||||
|     { |    "options": "Company", | ||||||
|      "fieldname": "qty", |    "remember_last_selected_value": 1, | ||||||
|      "fieldtype": "Float", |    "reqd": 1 | ||||||
|      "label": "Qty To Manufacture", |   }, | ||||||
|      "oldfieldname": "qty", |   { | ||||||
|      "oldfieldtype": "Currency", |    "fieldname": "qty", | ||||||
|      "reqd": 1 |    "fieldtype": "Float", | ||||||
|     }, |    "label": "Qty To Manufacture", | ||||||
|     { |    "oldfieldname": "qty", | ||||||
|      "default": "0", |    "oldfieldtype": "Currency", | ||||||
|      "depends_on": "eval:doc.docstatus==1 && doc.skip_transfer==0", |    "reqd": 1 | ||||||
|      "fieldname": "material_transferred_for_manufacturing", |   }, | ||||||
|      "fieldtype": "Float", |   { | ||||||
|      "label": "Material Transferred for Manufacturing", |    "default": "0", | ||||||
|      "no_copy": 1, |    "depends_on": "eval:doc.docstatus==1 && doc.skip_transfer==0", | ||||||
|      "read_only": 1 |    "fieldname": "material_transferred_for_manufacturing", | ||||||
|     }, |    "fieldtype": "Float", | ||||||
|     { |    "label": "Material Transferred for Manufacturing", | ||||||
|      "default": "0", |    "no_copy": 1, | ||||||
|      "depends_on": "eval:doc.docstatus==1", |    "read_only": 1 | ||||||
|      "fieldname": "produced_qty", |   }, | ||||||
|      "fieldtype": "Float", |   { | ||||||
|      "label": "Manufactured Qty", |    "default": "0", | ||||||
|      "no_copy": 1, |    "depends_on": "eval:doc.docstatus==1", | ||||||
|      "oldfieldname": "produced_qty", |    "fieldname": "produced_qty", | ||||||
|      "oldfieldtype": "Currency", |    "fieldtype": "Float", | ||||||
|      "read_only": 1 |    "label": "Manufactured Qty", | ||||||
|     }, |    "no_copy": 1, | ||||||
|     { |    "oldfieldname": "produced_qty", | ||||||
|      "allow_on_submit": 1, |    "oldfieldtype": "Currency", | ||||||
|      "fieldname": "sales_order", |    "read_only": 1 | ||||||
|      "fieldtype": "Link", |   }, | ||||||
|      "in_global_search": 1, |   { | ||||||
|      "label": "Sales Order", |    "allow_on_submit": 1, | ||||||
|      "options": "Sales Order" |    "fieldname": "sales_order", | ||||||
|     }, |    "fieldtype": "Link", | ||||||
|     { |    "in_global_search": 1, | ||||||
|      "fieldname": "project", |    "label": "Sales Order", | ||||||
|      "fieldtype": "Link", |    "options": "Sales Order" | ||||||
|      "label": "Project", |   }, | ||||||
|      "oldfieldname": "project", |   { | ||||||
|      "oldfieldtype": "Link", |    "fieldname": "project", | ||||||
|      "options": "Project" |    "fieldtype": "Link", | ||||||
|     }, |    "label": "Project", | ||||||
|     { |    "oldfieldname": "project", | ||||||
|      "default": "0", |    "oldfieldtype": "Link", | ||||||
|      "depends_on": "skip_transfer", |    "options": "Project" | ||||||
|      "fieldname": "from_wip_warehouse", |   }, | ||||||
|      "fieldtype": "Check", |   { | ||||||
|      "label": "Backflush Raw Materials From Work-in-Progress Warehouse" |    "default": "0", | ||||||
|     }, |    "depends_on": "skip_transfer", | ||||||
|     { |    "fieldname": "from_wip_warehouse", | ||||||
|      "fieldname": "warehouses", |    "fieldtype": "Check", | ||||||
|      "fieldtype": "Section Break", |    "label": "Backflush Raw Materials From Work-in-Progress Warehouse" | ||||||
|      "label": "Warehouses", |   }, | ||||||
|      "options": "fa fa-building" |   { | ||||||
|     }, |    "fieldname": "warehouses", | ||||||
|     { |    "fieldtype": "Section Break", | ||||||
|      "fieldname": "wip_warehouse", |    "label": "Warehouses", | ||||||
|      "fieldtype": "Link", |    "options": "fa fa-building" | ||||||
|      "label": "Work-in-Progress Warehouse", |   }, | ||||||
|      "options": "Warehouse" |   { | ||||||
|     }, |    "fieldname": "wip_warehouse", | ||||||
|     { |    "fieldtype": "Link", | ||||||
|      "fieldname": "fg_warehouse", |    "label": "Work-in-Progress Warehouse", | ||||||
|      "fieldtype": "Link", |    "options": "Warehouse" | ||||||
|      "label": "Target Warehouse", |   }, | ||||||
|      "options": "Warehouse" |   { | ||||||
|     }, |    "fieldname": "fg_warehouse", | ||||||
|     { |    "fieldtype": "Link", | ||||||
|      "fieldname": "column_break_12", |    "label": "Target Warehouse", | ||||||
|      "fieldtype": "Column Break" |    "options": "Warehouse" | ||||||
|     }, |   }, | ||||||
|     { |   { | ||||||
|      "fieldname": "scrap_warehouse", |    "fieldname": "column_break_12", | ||||||
|      "fieldtype": "Link", |    "fieldtype": "Column Break" | ||||||
|      "label": "Scrap Warehouse", |   }, | ||||||
|      "options": "Warehouse" |   { | ||||||
|     }, |    "fieldname": "scrap_warehouse", | ||||||
|     { |    "fieldtype": "Link", | ||||||
|      "fieldname": "required_items_section", |    "label": "Scrap Warehouse", | ||||||
|      "fieldtype": "Section Break", |    "options": "Warehouse" | ||||||
|      "label": "Required Items" |   }, | ||||||
|     }, |   { | ||||||
|     { |    "fieldname": "required_items_section", | ||||||
|      "fieldname": "required_items", |    "fieldtype": "Section Break", | ||||||
|      "fieldtype": "Table", |    "label": "Required Items" | ||||||
|      "label": "Required Items", |   }, | ||||||
|      "no_copy": 1, |   { | ||||||
|      "options": "Work Order Item", |    "fieldname": "required_items", | ||||||
|      "print_hide": 1 |    "fieldtype": "Table", | ||||||
|     }, |    "label": "Required Items", | ||||||
|     { |    "no_copy": 1, | ||||||
|      "fieldname": "time", |    "options": "Work Order Item", | ||||||
|      "fieldtype": "Section Break", |    "print_hide": 1 | ||||||
|      "label": "Time", |   }, | ||||||
|      "options": "fa fa-time" |   { | ||||||
|     }, |    "fieldname": "time", | ||||||
|     { |    "fieldtype": "Section Break", | ||||||
|      "allow_on_submit": 1, |    "label": "Time", | ||||||
|      "default": "now", |    "options": "fa fa-time" | ||||||
|      "fieldname": "planned_start_date", |   }, | ||||||
|      "fieldtype": "Datetime", |   { | ||||||
|      "label": "Planned Start Date", |    "allow_on_submit": 1, | ||||||
|      "reqd": 1 |    "default": "now", | ||||||
|     }, |    "fieldname": "planned_start_date", | ||||||
|     { |    "fieldtype": "Datetime", | ||||||
|      "fieldname": "actual_start_date", |    "label": "Planned Start Date", | ||||||
|      "fieldtype": "Datetime", |    "reqd": 1 | ||||||
|      "label": "Actual Start Date", |   }, | ||||||
|      "read_only": 1 |   { | ||||||
|     }, |    "fieldname": "actual_start_date", | ||||||
|     { |    "fieldtype": "Datetime", | ||||||
|      "fieldname": "column_break_13", |    "label": "Actual Start Date", | ||||||
|      "fieldtype": "Column Break" |    "read_only": 1 | ||||||
|     }, |   }, | ||||||
|     { |   { | ||||||
|      "fieldname": "planned_end_date", |    "fieldname": "column_break_13", | ||||||
|      "fieldtype": "Datetime", |    "fieldtype": "Column Break" | ||||||
|      "label": "Planned End Date", |   }, | ||||||
|      "no_copy": 1, |   { | ||||||
|      "read_only": 1 |    "fieldname": "planned_end_date", | ||||||
|     }, |    "fieldtype": "Datetime", | ||||||
|     { |    "label": "Planned End Date", | ||||||
|      "fieldname": "actual_end_date", |    "no_copy": 1, | ||||||
|      "fieldtype": "Datetime", |    "read_only": 1 | ||||||
|      "label": "Actual End Date", |   }, | ||||||
|      "read_only": 1 |   { | ||||||
|     }, |    "fieldname": "actual_end_date", | ||||||
|     { |    "fieldtype": "Datetime", | ||||||
|      "allow_on_submit": 1, |    "label": "Actual End Date", | ||||||
|      "fieldname": "expected_delivery_date", |    "read_only": 1 | ||||||
|      "fieldtype": "Date", |   }, | ||||||
|      "label": "Expected Delivery Date" |   { | ||||||
|     }, |    "allow_on_submit": 1, | ||||||
|     { |    "fieldname": "expected_delivery_date", | ||||||
|      "fieldname": "operations_section", |    "fieldtype": "Date", | ||||||
|      "fieldtype": "Section Break", |    "label": "Expected Delivery Date" | ||||||
|      "label": "Operations", |   }, | ||||||
|      "options": "fa fa-wrench" |   { | ||||||
|     }, |    "fieldname": "operations_section", | ||||||
|     { |    "fieldtype": "Section Break", | ||||||
|      "default": "Work Order", |    "label": "Operations", | ||||||
|      "depends_on": "operations", |    "options": "fa fa-wrench" | ||||||
|      "fieldname": "transfer_material_against", |   }, | ||||||
|      "fieldtype": "Select", |   { | ||||||
|      "label": "Transfer Material Against", |    "default": "Work Order", | ||||||
|      "options": "\nWork Order\nJob Card" |    "depends_on": "operations", | ||||||
|     }, |    "fieldname": "transfer_material_against", | ||||||
|     { |    "fieldtype": "Select", | ||||||
|      "fieldname": "operations", |    "label": "Transfer Material Against", | ||||||
|      "fieldtype": "Table", |    "options": "\nWork Order\nJob Card" | ||||||
|      "label": "Operations", |   }, | ||||||
|      "options": "Work Order Operation", |   { | ||||||
|      "read_only": 1 |    "fieldname": "operations", | ||||||
|     }, |    "fieldtype": "Table", | ||||||
|     { |    "label": "Operations", | ||||||
|      "depends_on": "operations", |    "options": "Work Order Operation", | ||||||
|      "fieldname": "section_break_22", |    "read_only": 1 | ||||||
|      "fieldtype": "Section Break", |   }, | ||||||
|      "label": "Operation Cost" |   { | ||||||
|     }, |    "depends_on": "operations", | ||||||
|     { |    "fieldname": "section_break_22", | ||||||
|      "fieldname": "planned_operating_cost", |    "fieldtype": "Section Break", | ||||||
|      "fieldtype": "Currency", |    "label": "Operation Cost" | ||||||
|      "label": "Planned Operating Cost", |   }, | ||||||
|      "options": "Company:company:default_currency", |   { | ||||||
|      "read_only": 1 |    "fieldname": "planned_operating_cost", | ||||||
|     }, |    "fieldtype": "Currency", | ||||||
|     { |    "label": "Planned Operating Cost", | ||||||
|      "fieldname": "actual_operating_cost", |    "options": "Company:company:default_currency", | ||||||
|      "fieldtype": "Currency", |    "read_only": 1 | ||||||
|      "label": "Actual Operating Cost", |   }, | ||||||
|      "no_copy": 1, |   { | ||||||
|      "options": "Company:company:default_currency", |    "fieldname": "actual_operating_cost", | ||||||
|      "read_only": 1 |    "fieldtype": "Currency", | ||||||
|     }, |    "label": "Actual Operating Cost", | ||||||
|     { |    "no_copy": 1, | ||||||
|      "fieldname": "additional_operating_cost", |    "options": "Company:company:default_currency", | ||||||
|      "fieldtype": "Currency", |    "read_only": 1 | ||||||
|      "label": "Additional Operating Cost", |   }, | ||||||
|      "no_copy": 1, |   { | ||||||
|      "options": "Company:company:default_currency" |    "fieldname": "additional_operating_cost", | ||||||
|     }, |    "fieldtype": "Currency", | ||||||
|     { |    "label": "Additional Operating Cost", | ||||||
|      "fieldname": "column_break_24", |    "no_copy": 1, | ||||||
|      "fieldtype": "Column Break" |    "options": "Company:company:default_currency" | ||||||
|     }, |   }, | ||||||
|     { |   { | ||||||
|      "fieldname": "total_operating_cost", |    "fieldname": "column_break_24", | ||||||
|      "fieldtype": "Currency", |    "fieldtype": "Column Break" | ||||||
|      "label": "Total Operating Cost", |   }, | ||||||
|      "no_copy": 1, |   { | ||||||
|      "options": "Company:company:default_currency", |    "fieldname": "total_operating_cost", | ||||||
|      "read_only": 1 |    "fieldtype": "Currency", | ||||||
|     }, |    "label": "Total Operating Cost", | ||||||
|     { |    "no_copy": 1, | ||||||
|      "collapsible": 1, |    "options": "Company:company:default_currency", | ||||||
|      "fieldname": "more_info", |    "read_only": 1 | ||||||
|      "fieldtype": "Section Break", |   }, | ||||||
|      "label": "More Information", |   { | ||||||
|      "options": "fa fa-file-text" |    "collapsible": 1, | ||||||
|     }, |    "fieldname": "more_info", | ||||||
|     { |    "fieldtype": "Section Break", | ||||||
|      "fieldname": "description", |    "label": "More Information", | ||||||
|      "fieldtype": "Small Text", |    "options": "fa fa-file-text" | ||||||
|      "label": "Item Description", |   }, | ||||||
|      "read_only": 1 |   { | ||||||
|     }, |    "fieldname": "description", | ||||||
|     { |    "fieldtype": "Small Text", | ||||||
|      "fieldname": "stock_uom", |    "label": "Item Description", | ||||||
|      "fieldtype": "Link", |    "read_only": 1 | ||||||
|      "label": "Stock UOM", |   }, | ||||||
|      "oldfieldname": "stock_uom", |   { | ||||||
|      "oldfieldtype": "Data", |    "fieldname": "stock_uom", | ||||||
|      "options": "UOM", |    "fieldtype": "Link", | ||||||
|      "read_only": 1 |    "label": "Stock UOM", | ||||||
|     }, |    "oldfieldname": "stock_uom", | ||||||
|     { |    "oldfieldtype": "Data", | ||||||
|      "fieldname": "column_break2", |    "options": "UOM", | ||||||
|      "fieldtype": "Column Break", |    "read_only": 1 | ||||||
|      "width": "50%" |   }, | ||||||
|     }, |   { | ||||||
|     { |    "fieldname": "column_break2", | ||||||
|      "description": "Manufacture against Material Request", |    "fieldtype": "Column Break", | ||||||
|      "fieldname": "material_request", |    "width": "50%" | ||||||
|      "fieldtype": "Link", |   }, | ||||||
|      "label": "Material Request", |   { | ||||||
|      "options": "Material Request" |    "description": "Manufacture against Material Request", | ||||||
|     }, |    "fieldname": "material_request", | ||||||
|     { |    "fieldtype": "Link", | ||||||
|      "fieldname": "material_request_item", |    "label": "Material Request", | ||||||
|      "fieldtype": "Data", |    "options": "Material Request" | ||||||
|      "hidden": 1, |   }, | ||||||
|      "label": "Material Request Item", |   { | ||||||
|      "read_only": 1 |    "fieldname": "material_request_item", | ||||||
|     }, |    "fieldtype": "Data", | ||||||
|     { |    "hidden": 1, | ||||||
|      "fieldname": "sales_order_item", |    "label": "Material Request Item", | ||||||
|      "fieldtype": "Data", |    "read_only": 1 | ||||||
|      "hidden": 1, |   }, | ||||||
|      "label": "Sales Order Item", |   { | ||||||
|      "read_only": 1 |    "fieldname": "sales_order_item", | ||||||
|     }, |    "fieldtype": "Data", | ||||||
|     { |    "hidden": 1, | ||||||
|      "fieldname": "production_plan", |    "label": "Sales Order Item", | ||||||
|      "fieldtype": "Link", |    "read_only": 1 | ||||||
|      "label": "Production Plan", |   }, | ||||||
|      "no_copy": 1, |   { | ||||||
|      "options": "Production Plan", |    "fieldname": "production_plan", | ||||||
|      "print_hide": 1, |    "fieldtype": "Link", | ||||||
|      "read_only": 1 |    "label": "Production Plan", | ||||||
|     }, |    "no_copy": 1, | ||||||
|     { |    "options": "Production Plan", | ||||||
|      "fieldname": "production_plan_item", |    "print_hide": 1, | ||||||
|      "fieldtype": "Data", |    "read_only": 1 | ||||||
|      "label": "Production Plan Item", |   }, | ||||||
|      "no_copy": 1, |   { | ||||||
|      "print_hide": 1, |    "fieldname": "production_plan_item", | ||||||
|      "read_only": 1 |    "fieldtype": "Data", | ||||||
|     }, |    "label": "Production Plan Item", | ||||||
|     { |    "no_copy": 1, | ||||||
|      "fieldname": "product_bundle_item", |    "print_hide": 1, | ||||||
|      "fieldtype": "Link", |    "read_only": 1 | ||||||
|      "label": "Product Bundle Item", |   }, | ||||||
|      "no_copy": 1, |   { | ||||||
|      "options": "Item", |    "fieldname": "product_bundle_item", | ||||||
|      "print_hide": 1, |    "fieldtype": "Link", | ||||||
|      "read_only": 1 |    "label": "Product Bundle Item", | ||||||
|     }, |    "no_copy": 1, | ||||||
|     { |    "options": "Item", | ||||||
|      "fieldname": "amended_from", |    "print_hide": 1, | ||||||
|      "fieldtype": "Link", |    "read_only": 1 | ||||||
|      "ignore_user_permissions": 1, |   }, | ||||||
|      "label": "Amended From", |   { | ||||||
|      "no_copy": 1, |    "fieldname": "amended_from", | ||||||
|      "oldfieldname": "amended_from", |    "fieldtype": "Link", | ||||||
|      "oldfieldtype": "Data", |    "ignore_user_permissions": 1, | ||||||
|      "options": "Work Order", |    "label": "Amended From", | ||||||
|      "read_only": 1 |    "no_copy": 1, | ||||||
|     } |    "oldfieldname": "amended_from", | ||||||
|    ], |    "oldfieldtype": "Data", | ||||||
|    "icon": "fa fa-cogs", |    "options": "Work Order", | ||||||
|    "idx": 1, |    "read_only": 1 | ||||||
|    "image_field": "image", |   }, | ||||||
|    "is_submittable": 1, |   { | ||||||
|    "modified": "2019-05-27 09:36:16.707719", |    "fieldname": "settings_section", | ||||||
|    "modified_by": "Administrator", |    "fieldtype": "Section Break", | ||||||
|    "module": "Manufacturing", |    "label": "Settings" | ||||||
|    "name": "Work Order", |   }, | ||||||
|    "owner": "Administrator", |   { | ||||||
|    "permissions": [ |    "fieldname": "column_break_18", | ||||||
|     { |    "fieldtype": "Column Break" | ||||||
|      "amend": 1, |   }, | ||||||
|      "cancel": 1, |   { | ||||||
|      "create": 1, |    "default": "1", | ||||||
|      "delete": 1, |    "fieldname": "update_consumed_material_cost_in_project", | ||||||
|      "email": 1, |    "fieldtype": "Check", | ||||||
|      "export": 1, |    "label": "Update Consumed Material Cost In Project" | ||||||
|      "import": 1, |   } | ||||||
|      "print": 1, |  ], | ||||||
|      "read": 1, |  "icon": "fa fa-cogs", | ||||||
|      "report": 1, |  "idx": 1, | ||||||
|      "role": "Manufacturing User", |  "image_field": "image", | ||||||
|      "set_user_permissions": 1, |  "is_submittable": 1, | ||||||
|      "share": 1, |  "modified": "2019-07-31 00:13:38.218277", | ||||||
|      "submit": 1, |  "modified_by": "Administrator", | ||||||
|      "write": 1 |  "module": "Manufacturing", | ||||||
|     }, |  "name": "Work Order", | ||||||
|     { |  "owner": "Administrator", | ||||||
|      "read": 1, |  "permissions": [ | ||||||
|      "report": 1, |   { | ||||||
|      "role": "Stock User" |    "amend": 1, | ||||||
|     } |    "cancel": 1, | ||||||
|    ], |    "create": 1, | ||||||
|    "sort_order": "ASC", |    "delete": 1, | ||||||
|    "title_field": "production_item", |    "email": 1, | ||||||
|    "track_changes": 1, |    "export": 1, | ||||||
|    "track_seen": 1 |    "import": 1, | ||||||
|   } |    "print": 1, | ||||||
|  |    "read": 1, | ||||||
|  |    "report": 1, | ||||||
|  |    "role": "Manufacturing User", | ||||||
|  |    "set_user_permissions": 1, | ||||||
|  |    "share": 1, | ||||||
|  |    "submit": 1, | ||||||
|  |    "write": 1 | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |    "read": 1, | ||||||
|  |    "report": 1, | ||||||
|  |    "role": "Stock User" | ||||||
|  |   } | ||||||
|  |  ], | ||||||
|  |  "sort_field": "modified", | ||||||
|  |  "sort_order": "ASC", | ||||||
|  |  "title_field": "production_item", | ||||||
|  |  "track_changes": 1, | ||||||
|  |  "track_seen": 1 | ||||||
|  | } | ||||||
| @ -0,0 +1,213 @@ | |||||||
|  | frappe.pages['bom-comparison-tool'].on_page_load = function(wrapper) { | ||||||
|  | 	var page = frappe.ui.make_app_page({ | ||||||
|  | 		parent: wrapper, | ||||||
|  | 		title: __('BOM Comparison Tool'), | ||||||
|  | 		single_column: true | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	new erpnext.BOMComparisonTool(page); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | erpnext.BOMComparisonTool = class BOMComparisonTool { | ||||||
|  | 	constructor(page) { | ||||||
|  | 		this.page = page; | ||||||
|  | 		this.make_form(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	make_form() { | ||||||
|  | 		this.form = new frappe.ui.FieldGroup({ | ||||||
|  | 			fields: [ | ||||||
|  | 				{ | ||||||
|  | 					label: __('BOM 1'), | ||||||
|  | 					fieldname: 'name1', | ||||||
|  | 					fieldtype: 'Link', | ||||||
|  | 					options: 'BOM', | ||||||
|  | 					change: () => this.fetch_and_render() | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					fieldtype: 'Column Break' | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					label: __('BOM 2'), | ||||||
|  | 					fieldname: 'name2', | ||||||
|  | 					fieldtype: 'Link', | ||||||
|  | 					options: 'BOM', | ||||||
|  | 					change: () => this.fetch_and_render() | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					fieldtype: 'Section Break' | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					fieldtype: 'HTML', | ||||||
|  | 					fieldname: 'preview' | ||||||
|  | 				} | ||||||
|  | 			], | ||||||
|  | 			body: this.page.body | ||||||
|  | 		}); | ||||||
|  | 		this.form.make(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	fetch_and_render() { | ||||||
|  | 		let { name1, name2 } = this.form.get_values(); | ||||||
|  | 		if (!(name1 && name2)) { | ||||||
|  | 			this.form.get_field('preview').html(''); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// set working state
 | ||||||
|  | 		this.form.get_field('preview').html(` | ||||||
|  | 			<div class="text-muted margin-top"> | ||||||
|  | 				${__("Fetching...")} | ||||||
|  | 			</div> | ||||||
|  | 		`);
 | ||||||
|  | 
 | ||||||
|  | 		frappe.call('erpnext.manufacturing.doctype.bom.bom.get_bom_diff', { | ||||||
|  | 			bom1: name1, | ||||||
|  | 			bom2: name2 | ||||||
|  | 		}).then(r => { | ||||||
|  | 			let diff = r.message; | ||||||
|  | 			frappe.model.with_doctype('BOM', () => { | ||||||
|  | 				this.render('BOM', name1, name2, diff); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	render(doctype, name1, name2, diff) { | ||||||
|  | 
 | ||||||
|  | 		let change_html = (title, doctype, changed) => { | ||||||
|  | 			let values_changed = this.get_changed_values(doctype, changed) | ||||||
|  | 				.map(change => { | ||||||
|  | 					let [fieldname, value1, value2] = change; | ||||||
|  | 					return ` | ||||||
|  | 						<tr> | ||||||
|  | 							<td>${frappe.meta.get_label(doctype, fieldname)}</td> | ||||||
|  | 							<td>${value1}</td> | ||||||
|  | 							<td>${value2}</td> | ||||||
|  | 						</tr> | ||||||
|  | 					`;
 | ||||||
|  | 				}) | ||||||
|  | 				.join(''); | ||||||
|  | 
 | ||||||
|  | 			return ` | ||||||
|  | 				<h4 class="margin-top">${title}</h4> | ||||||
|  | 				<div> | ||||||
|  | 					<table class="table table-bordered"> | ||||||
|  | 						<tr> | ||||||
|  | 							<th width="33%">${__('Field')}</th> | ||||||
|  | 							<th width="33%">${name1}</th> | ||||||
|  | 							<th width="33%">${name2}</th> | ||||||
|  | 						</tr> | ||||||
|  | 						${values_changed} | ||||||
|  | 					</table> | ||||||
|  | 				</div> | ||||||
|  | 			`;
 | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		let value_changes = change_html(__('Values Changed'), doctype, diff.changed); | ||||||
|  | 
 | ||||||
|  | 		let row_changes_by_fieldname = group_items(diff.row_changed, change => change[0]); | ||||||
|  | 
 | ||||||
|  | 		let table_changes = Object.keys(row_changes_by_fieldname).map(fieldname => { | ||||||
|  | 			let changes = row_changes_by_fieldname[fieldname]; | ||||||
|  | 			let df = frappe.meta.get_docfield(doctype, fieldname); | ||||||
|  | 
 | ||||||
|  | 			let html = changes.map(change => { | ||||||
|  | 				let [fieldname,, item_code, changes] = change; | ||||||
|  | 				let df = frappe.meta.get_docfield(doctype, fieldname); | ||||||
|  | 				let child_doctype = df.options; | ||||||
|  | 				let values_changed = this.get_changed_values(child_doctype, changes); | ||||||
|  | 
 | ||||||
|  | 				return values_changed.map((change, i) => { | ||||||
|  | 					let [fieldname, value1, value2] = change; | ||||||
|  | 					let th = i === 0 | ||||||
|  | 						? `<th rowspan="${values_changed.length}">${item_code}</th>` | ||||||
|  | 						: ''; | ||||||
|  | 					return ` | ||||||
|  | 						<tr> | ||||||
|  | 							${th} | ||||||
|  | 							<td>${frappe.meta.get_label(child_doctype, fieldname)}</td> | ||||||
|  | 							<td>${value1}</td> | ||||||
|  | 							<td>${value2}</td> | ||||||
|  | 						</tr> | ||||||
|  | 					`;
 | ||||||
|  | 				}).join(''); | ||||||
|  | 			}).join(''); | ||||||
|  | 
 | ||||||
|  | 			return ` | ||||||
|  | 				<h4 class="margin-top">${__('Changes in {0}', [df.label])}</h4> | ||||||
|  | 				<table class="table table-bordered"> | ||||||
|  | 					<tr> | ||||||
|  | 						<th width="25%">${__('Item Code')}</th> | ||||||
|  | 						<th width="25%">${__('Field')}</th> | ||||||
|  | 						<th width="25%">${name1}</th> | ||||||
|  | 						<th width="25%">${name2}</th> | ||||||
|  | 					</tr> | ||||||
|  | 					${html} | ||||||
|  | 				</table> | ||||||
|  | 			`;
 | ||||||
|  | 		}).join(''); | ||||||
|  | 
 | ||||||
|  | 		let get_added_removed_html = (title, grouped_items) => { | ||||||
|  | 			return Object.keys(grouped_items).map(fieldname => { | ||||||
|  | 				let rows = grouped_items[fieldname]; | ||||||
|  | 				let df = frappe.meta.get_docfield(doctype, fieldname); | ||||||
|  | 				let fields = frappe.meta.get_docfields(df.options) | ||||||
|  | 					.filter(df => df.in_list_view); | ||||||
|  | 
 | ||||||
|  | 				let html = rows.map(row => { | ||||||
|  | 					let [, doc] = row; | ||||||
|  | 					let cells = fields | ||||||
|  | 						.map(df => `<td>${doc[df.fieldname]}</td>`) | ||||||
|  | 						.join(''); | ||||||
|  | 					return `<tr>${cells}</tr>`; | ||||||
|  | 				}).join(''); | ||||||
|  | 
 | ||||||
|  | 				let header = fields.map(df => `<th>${df.label}</th>`).join(''); | ||||||
|  | 				return ` | ||||||
|  | 					<h4 class="margin-top">${$.format(title, [df.label])}</h4> | ||||||
|  | 					<table class="table table-bordered"> | ||||||
|  | 						<tr>${header}</tr> | ||||||
|  | 						${html} | ||||||
|  | 					</table> | ||||||
|  | 				`;
 | ||||||
|  | 			}).join(''); | ||||||
|  | 		}; | ||||||
|  | 
 | ||||||
|  | 		let added_by_fieldname = group_items(diff.added, change => change[0]); | ||||||
|  | 		let removed_by_fieldname = group_items(diff.removed, change => change[0]); | ||||||
|  | 
 | ||||||
|  | 		let added_html = get_added_removed_html(__('Rows Added in {0}'), added_by_fieldname); | ||||||
|  | 		let removed_html = get_added_removed_html(__('Rows Removed in {0}'), removed_by_fieldname); | ||||||
|  | 
 | ||||||
|  | 		let html = ` | ||||||
|  | 			${value_changes} | ||||||
|  | 			${table_changes} | ||||||
|  | 			${added_html} | ||||||
|  | 			${removed_html} | ||||||
|  | 		`;
 | ||||||
|  | 
 | ||||||
|  | 		this.form.get_field('preview').html(html); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	get_changed_values(doctype, changed) { | ||||||
|  | 		return changed.filter(change => { | ||||||
|  | 			let [fieldname, value1, value2] = change; | ||||||
|  | 			if (!value1) value1 = ''; | ||||||
|  | 			if (!value2) value2 = ''; | ||||||
|  | 			if (value1 === value2) return false; | ||||||
|  | 			let df = frappe.meta.get_docfield(doctype, fieldname); | ||||||
|  | 			if (!df) return false; | ||||||
|  | 			if (df.hidden) return false; | ||||||
|  | 			return true; | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | function group_items(array, fn) { | ||||||
|  | 	return array.reduce((acc, item) => { | ||||||
|  | 		let key = fn(item); | ||||||
|  | 		acc[key] = acc[key] || []; | ||||||
|  | 		acc[key].push(item); | ||||||
|  | 		return acc; | ||||||
|  | 	}, {}); | ||||||
|  | } | ||||||
| @ -0,0 +1,30 @@ | |||||||
|  | { | ||||||
|  |  "content": null, | ||||||
|  |  "creation": "2019-07-29 13:24:38.201981", | ||||||
|  |  "docstatus": 0, | ||||||
|  |  "doctype": "Page", | ||||||
|  |  "idx": 0, | ||||||
|  |  "modified": "2019-07-29 13:24:38.201981", | ||||||
|  |  "modified_by": "Administrator", | ||||||
|  |  "module": "Manufacturing", | ||||||
|  |  "name": "bom-comparison-tool", | ||||||
|  |  "owner": "Administrator", | ||||||
|  |  "page_name": "BOM Comparison Tool", | ||||||
|  |  "restrict_to_domain": "Manufacturing", | ||||||
|  |  "roles": [ | ||||||
|  |   { | ||||||
|  |    "role": "System Manager" | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |    "role": "Manufacturing User" | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |    "role": "Manufacturing Manager" | ||||||
|  |   } | ||||||
|  |  ], | ||||||
|  |  "script": null, | ||||||
|  |  "standard": "Yes", | ||||||
|  |  "style": null, | ||||||
|  |  "system_page": 0, | ||||||
|  |  "title": "BOM Comparison Tool" | ||||||
|  | } | ||||||
| @ -60,7 +60,15 @@ $.extend(erpnext, { | |||||||
| 
 | 
 | ||||||
| 		var me = this; | 		var me = this; | ||||||
| 		$btn.on("click", function() { | 		$btn.on("click", function() { | ||||||
| 			me.show_serial_batch_selector(grid_row.frm, grid_row.doc); | 			let callback = ''; | ||||||
|  | 			let on_close = ''; | ||||||
|  | 
 | ||||||
|  | 			if (grid_row.doc.serial_no) { | ||||||
|  | 				grid_row.doc.has_serial_no = true; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			me.show_serial_batch_selector(grid_row.frm, grid_row.doc, | ||||||
|  | 				callback, on_close, true); | ||||||
| 		}); | 		}); | ||||||
| 	}, | 	}, | ||||||
| }); | }); | ||||||
|  | |||||||
| @ -255,27 +255,44 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( | |||||||
| 					}); | 					}); | ||||||
| 					return; | 					return; | ||||||
| 				} else { | 				} else { | ||||||
| 					var fields = [ | 					const fields = [{ | ||||||
| 						{fieldtype:'Table', fieldname: 'items', | 						label: 'Items', | ||||||
| 							description: __('Select BOM and Qty for Production'), | 						fieldtype: 'Table', | ||||||
| 							fields: [ | 						fieldname: 'items', | ||||||
| 								{fieldtype:'Read Only', fieldname:'item_code', | 						description: __('Select BOM and Qty for Production'), | ||||||
| 									label: __('Item Code'), in_list_view:1}, | 						fields: [{ | ||||||
| 								{fieldtype:'Link', fieldname:'bom', options: 'BOM', reqd: 1, | 							fieldtype: 'Read Only', | ||||||
| 									label: __('Select BOM'), in_list_view:1, get_query: function(doc) { | 							fieldname: 'item_code', | ||||||
| 										return {filters: {item: doc.item_code}}; | 							label: __('Item Code'), | ||||||
| 									}}, | 							in_list_view: 1 | ||||||
| 								{fieldtype:'Float', fieldname:'pending_qty', reqd: 1, | 						}, { | ||||||
| 									label: __('Qty'), in_list_view:1}, | 							fieldtype: 'Link', | ||||||
| 								{fieldtype:'Data', fieldname:'sales_order_item', reqd: 1, | 							fieldname: 'bom', | ||||||
| 									label: __('Sales Order Item'), hidden:1} | 							options: 'BOM', | ||||||
| 							], | 							reqd: 1, | ||||||
| 							data: r.message, | 							label: __('Select BOM'), | ||||||
| 							get_data: function() { | 							in_list_view: 1, | ||||||
| 								return r.message | 							get_query: function (doc) { | ||||||
|  | 								return { filters: { item: doc.item_code } }; | ||||||
| 							} | 							} | ||||||
|  | 						}, { | ||||||
|  | 							fieldtype: 'Float', | ||||||
|  | 							fieldname: 'pending_qty', | ||||||
|  | 							reqd: 1, | ||||||
|  | 							label: __('Qty'), | ||||||
|  | 							in_list_view: 1 | ||||||
|  | 						}, { | ||||||
|  | 							fieldtype: 'Data', | ||||||
|  | 							fieldname: 'sales_order_item', | ||||||
|  | 							reqd: 1, | ||||||
|  | 							label: __('Sales Order Item'), | ||||||
|  | 							hidden: 1 | ||||||
|  | 						}], | ||||||
|  | 						data: r.message, | ||||||
|  | 						get_data: () => { | ||||||
|  | 							return r.message | ||||||
| 						} | 						} | ||||||
| 					] | 					}] | ||||||
| 					var d = new frappe.ui.Dialog({ | 					var d = new frappe.ui.Dialog({ | ||||||
| 						title: __('Select Items to Manufacture'), | 						title: __('Select Items to Manufacture'), | ||||||
| 						fields: fields, | 						fields: fields, | ||||||
|  | |||||||
| @ -251,13 +251,12 @@ def _get_cart_quotation(party=None): | |||||||
| 	if quotation: | 	if quotation: | ||||||
| 		qdoc = frappe.get_doc("Quotation", quotation[0].name) | 		qdoc = frappe.get_doc("Quotation", quotation[0].name) | ||||||
| 	else: | 	else: | ||||||
| 		[company, price_list] = frappe.db.get_value("Shopping Cart Settings", None, ["company", "price_list"]) | 		company = frappe.db.get_value("Shopping Cart Settings", None, ["company"]) | ||||||
| 		qdoc = frappe.get_doc({ | 		qdoc = frappe.get_doc({ | ||||||
| 			"doctype": "Quotation", | 			"doctype": "Quotation", | ||||||
| 			"naming_series": get_shopping_cart_settings().quotation_series or "QTN-CART-", | 			"naming_series": get_shopping_cart_settings().quotation_series or "QTN-CART-", | ||||||
| 			"quotation_to": party.doctype, | 			"quotation_to": party.doctype, | ||||||
| 			"company": company, | 			"company": company, | ||||||
| 			"selling_price_list": price_list, |  | ||||||
| 			"order_type": "Shopping Cart", | 			"order_type": "Shopping Cart", | ||||||
| 			"status": "Draft", | 			"status": "Draft", | ||||||
| 			"docstatus": 0, | 			"docstatus": 0, | ||||||
|  | |||||||
| @ -145,6 +145,10 @@ class StockEntry(StockController): | |||||||
| 				self.precision("transfer_qty", item)) | 				self.precision("transfer_qty", item)) | ||||||
| 
 | 
 | ||||||
| 	def update_cost_in_project(self): | 	def update_cost_in_project(self): | ||||||
|  | 		if (self.work_order and not frappe.db.get_value("Work Order", | ||||||
|  | 			self.work_order, "update_consumed_material_cost_in_project")): | ||||||
|  | 			return | ||||||
|  | 
 | ||||||
| 		if self.project: | 		if self.project: | ||||||
| 			amount = frappe.db.sql(""" select ifnull(sum(sed.amount), 0) | 			amount = frappe.db.sql(""" select ifnull(sum(sed.amount), 0) | ||||||
| 				from | 				from | ||||||
|  | |||||||
							
								
								
									
										3
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -1,3 +0,0 @@ | |||||||
| { |  | ||||||
|   "lockfileVersion": 1 |  | ||||||
| } |  | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user