diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 3c35e6cc5e..9248ac0fe8 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -205,7 +205,12 @@ var get_bom_material_detail= function(doc, cdt, cdn, scrap_items) { 'item_code': d.item_code, 'bom_no': d.bom_no != null ? d.bom_no: '', "scrap_items": scrap_items, - 'qty': d.qty + 'qty': d.qty, + "stock_qty": d.stock_qty, + "include_item_in_manufacturing": d.include_item_in_manufacturing, + "uom": d.uom, + "stock_uom": d.stock_uom, + "conversion_factor": d.conversion_factor }, callback: function(r) { d = locals[cdt][cdn]; diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 1063340b89..5c7375818f 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -172,13 +172,14 @@ class BOM(WebsiteGenerator): #Customer Provided parts will have zero rate if not frappe.db.get_value('Item', arg["item_code"], 'is_customer_provided_item'): if arg.get('bom_no') and self.set_rate_of_sub_assembly_item_based_on_bom: - rate = self.get_bom_unitcost(arg['bom_no']) + rate = self.get_bom_unitcost(arg['bom_no']) * (arg.get("conversion_factor") or 1) else: if self.rm_cost_as_per == 'Valuation Rate': - rate = self.get_valuation_rate(arg) + rate = self.get_valuation_rate(arg) * (arg.get("conversion_factor") or 1) elif self.rm_cost_as_per == 'Last Purchase Rate': - rate = arg.get('last_purchase_rate') \ - or frappe.db.get_value("Item", arg['item_code'], "last_purchase_rate") + rate = (arg.get('last_purchase_rate') \ + or frappe.db.get_value("Item", arg['item_code'], "last_purchase_rate")) \ + * (arg.get("conversion_factor") or 1) elif self.rm_cost_as_per == "Price List": if not self.buying_price_list: frappe.throw(_("Please select Price List")) @@ -191,7 +192,7 @@ class BOM(WebsiteGenerator): "transaction_type": "buying", "company": self.company, "currency": self.currency, - "conversion_rate": self.conversion_rate or 1, + "conversion_rate": 1, # Passed conversion rate as 1 purposefully, as conversion rate is applied at the end of the function "conversion_factor": arg.get("conversion_factor") or 1, "plc_conversion_rate": 1, "ignore_party": True @@ -203,13 +204,13 @@ class BOM(WebsiteGenerator): if not rate: if self.rm_cost_as_per == "Price List": - frappe.msgprint(_("Price not found for item {0} and price list {1}") + frappe.msgprint(_("Price not found for item {0} in price list {1}") .format(arg["item_code"], self.buying_price_list), alert=True) else: frappe.msgprint(_("{0} not found for item {1}") .format(self.rm_cost_as_per, arg["item_code"]), alert=True) - return flt(rate) + return flt(rate) / (self.conversion_rate or 1) def update_cost(self, update_parent=True, from_child_bom=False, save=True): if self.docstatus == 2: diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index 6d85ef35f9..e230e59848 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -9,6 +9,7 @@ from frappe.utils import cstr from frappe.test_runner import make_test_records from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation from erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool import update_cost +from six import string_types test_records = frappe.get_test_records('BOM') @@ -63,16 +64,8 @@ class TestBOM(unittest.TestCase): and item_code='_Test Item 2' and docstatus=1 and parenttype='BOM'""") rm_rate = rm_rate[0][0] if rm_rate else 0 - # update valuation rate of item '_Test Item 2' - warehouse_list = frappe.db.sql_list("""select warehouse from `tabBin` - where item_code='_Test Item 2' and actual_qty > 0""") - - if not warehouse_list: - warehouse_list.append("_Test Warehouse - _TC") - - for warehouse in warehouse_list: - create_stock_reconciliation(item_code="_Test Item 2", warehouse=warehouse, - qty=200, rate=rm_rate + 10) + # Reset item valuation rate + reset_item_valuation_rate(item_code='_Test Item 2', qty=200, rate=rm_rate + 10) # update cost of all BOMs based on latest valuation rate update_cost() @@ -96,7 +89,7 @@ class TestBOM(unittest.TestCase): self.assertEqual(bom.base_raw_material_cost, 480000) self.assertEqual(bom.base_total_cost, 486000) - def test_bom_cost_multi_uom_multi_currency(self): + def test_bom_cost_multi_uom_multi_currency_based_on_price_list(self): frappe.db.set_value("Price List", "_Test Price List", "price_not_uom_dependant", 1) for item_code, rate in (("_Test Item", 3600), ("_Test Item Home Desktop Manufactured", 3000)): frappe.db.sql("delete from `tabItem Price` where price_list='_Test Price List' and item_code=%s", @@ -131,5 +124,35 @@ class TestBOM(unittest.TestCase): self.assertEqual(bom.base_raw_material_cost, 27000) self.assertEqual(bom.base_total_cost, 33000) + def test_bom_cost_multi_uom_based_on_valuation_rate(self): + bom = frappe.copy_doc(test_records[2]) + bom.set_rate_of_sub_assembly_item_based_on_bom = 0 + bom.rm_cost_as_per = "Valuation Rate" + bom.items[0].uom = "_Test UOM 1" + bom.items[0].conversion_factor = 6 + bom.insert() + + reset_item_valuation_rate(item_code='_Test Item', qty=200, rate=200) + + bom.update_cost() + + self.assertEqual(bom.items[0].rate, 20) + def get_default_bom(item_code="_Test FG Item 2"): - return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1}) \ No newline at end of file + return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1}) + +def reset_item_valuation_rate(item_code, warehouse_list=None, qty=None, rate=None): + if warehouse_list and isinstance(warehouse_list, string_types): + warehouse_list = [warehouse_list] + + if not warehouse_list: + warehouse_list = frappe.db.sql_list(""" + select warehouse from `tabBin` + where item_code=%s and actual_qty > 0 + """, item_code) + + if not warehouse_list: + warehouse_list.append("_Test Warehouse - _TC") + + for warehouse in warehouse_list: + create_stock_reconciliation(item_code=item_code, warehouse=warehouse, qty=qty, rate=rate) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 30a45dd457..1147b48d78 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -438,7 +438,7 @@ def get_price_list_rate(args, item_doc, out): pl_details = get_price_list_currency_and_exchange_rate(args) args.update(pl_details) validate_price_list(args) - if meta.get_field("currency") and args.price_list: + if meta.get_field("currency"): validate_conversion_rate(args, meta) price_list_rate = get_price_list_rate_for(args, item_doc.name) or 0 @@ -615,21 +615,22 @@ def validate_conversion_rate(args, meta): get_field_precision(meta.get_field("conversion_rate"), frappe._dict({"fields": args}))) - if (not args.plc_conversion_rate - and args.price_list_currency==frappe.db.get_value("Price List", args.price_list, "currency", cache=True)): - args.plc_conversion_rate = 1.0 + if args.price_list: + if (not args.plc_conversion_rate + and args.price_list_currency==frappe.db.get_value("Price List", args.price_list, "currency", cache=True)): + args.plc_conversion_rate = 1.0 - # validate price list currency conversion rate - if not args.get("price_list_currency"): - throw(_("Price List Currency not selected")) - else: - validate_conversion_rate(args.price_list_currency, args.plc_conversion_rate, - meta.get_label("plc_conversion_rate"), args.company) + # validate price list currency conversion rate + if not args.get("price_list_currency"): + throw(_("Price List Currency not selected")) + else: + validate_conversion_rate(args.price_list_currency, args.plc_conversion_rate, + meta.get_label("plc_conversion_rate"), args.company) - if meta.get_field("plc_conversion_rate"): - args.plc_conversion_rate = flt(args.plc_conversion_rate, - get_field_precision(meta.get_field("plc_conversion_rate"), - frappe._dict({"fields": args}))) + if meta.get_field("plc_conversion_rate"): + args.plc_conversion_rate = flt(args.plc_conversion_rate, + get_field_precision(meta.get_field("plc_conversion_rate"), + frappe._dict({"fields": args}))) def get_party_item_code(args, item_doc, out): if args.transaction_type=="selling" and args.customer: