diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 3524283db1c..a5b8ec870f3 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -962,6 +962,7 @@ class ProductionPlan(Document): "Fetch sub assembly items and optionally combine them." self.sub_assembly_items = [] sub_assembly_items_store = [] # temporary store to process all subassembly items + bin_details = frappe._dict() for row in self.po_items: if self.skip_available_sub_assembly_item and not self.sub_assembly_warehouse: @@ -977,6 +978,7 @@ class ProductionPlan(Document): get_sub_assembly_items( [item.production_item for item in sub_assembly_items_store], + bin_details, row.bom_no, bom_data, row.planned_qty, @@ -1777,6 +1779,7 @@ def get_item_data(item_code): def get_sub_assembly_items( sub_assembly_items, + bin_details, bom_no, bom_data, to_produce_qty, @@ -1791,25 +1794,27 @@ def get_sub_assembly_items( parent_item_code = frappe.get_cached_value("BOM", bom_no, "item") stock_qty = (d.stock_qty / d.parent_bom_qty) * flt(to_produce_qty) - bin_details = frappe._dict() if skip_available_sub_assembly_item and d.item_code not in sub_assembly_items: - bin_details = get_bin_details(d, company, for_warehouse=warehouse) + bin_details.setdefault(d.item_code, get_bin_details(d, company, for_warehouse=warehouse)) - for _bin_dict in bin_details: + for _bin_dict in bin_details[d.item_code]: if _bin_dict.projected_qty > 0: - if _bin_dict.projected_qty > stock_qty: + if _bin_dict.projected_qty >= stock_qty: + _bin_dict.projected_qty -= stock_qty stock_qty = 0 continue else: stock_qty = stock_qty - _bin_dict.projected_qty elif warehouse: - bin_details = get_bin_details(d, company, for_warehouse=warehouse) + bin_details.setdefault(d.item_code, get_bin_details(d, company, for_warehouse=warehouse)) if stock_qty > 0: bom_data.append( frappe._dict( { - "actual_qty": bin_details[0].get("actual_qty", 0) if bin_details else 0, + "actual_qty": bin_details[d.item_code][0].get("actual_qty", 0) + if bin_details + else 0, "parent_item_code": parent_item_code, "description": d.description, "production_item": d.item_code, @@ -1828,6 +1833,7 @@ def get_sub_assembly_items( if d.value: get_sub_assembly_items( sub_assembly_items, + bin_details, d.value, bom_data, stock_qty, diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index eefd577e9e6..7c1dba8be91 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -1848,6 +1848,64 @@ class TestProductionPlan(IntegrationTestCase): self.assertEqual(row.production_item, sf_item) self.assertEqual(row.qty, 5.0) + def test_calculation_of_sub_assembly_items(self): + make_item("Sub Assembly Item ", properties={"is_stock_item": 1}) + make_item("RM Item 1", properties={"is_stock_item": 1}) + make_item("RM Item 2", properties={"is_stock_item": 1}) + make_bom(item="Sub Assembly Item", raw_materials=["RM Item 1", "RM Item 2"]) + make_bom(item="_Test FG Item", raw_materials=["Sub Assembly Item", "RM Item 1"]) + make_bom(item="_Test FG Item 2", raw_materials=["Sub Assembly Item"]) + + from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry + + make_stock_entry( + item_code="Sub Assembly Item", + qty=80, + purpose="Material Receipt", + to_warehouse="_Test Warehouse - _TC", + ) + make_stock_entry( + item_code="RM Item 1", qty=90, purpose="Material Receipt", to_warehouse="_Test Warehouse - _TC" + ) + + plan = create_production_plan( + skip_available_sub_assembly_item=1, + sub_assembly_warehouse="_Test Warehouse - _TC", + warehouse="_Test Warehouse - _TC", + item_code="_Test FG Item", + skip_getting_mr_items=1, + planned_qty=100, + do_not_save=1, + ) + plan.get_items_from = "" + plan.append( + "po_items", + { + "use_multi_level_bom": 1, + "item_code": "_Test FG Item 2", + "bom_no": frappe.db.get_value("Item", "_Test FG Item 2", "default_bom"), + "planned_qty": 50, + "planned_start_date": now_datetime(), + "stock_uom": "Nos", + "warehouse": "_Test Warehouse - _TC", + }, + ) + plan.save() + + plan.get_sub_assembly_items() + + self.assertEqual(plan.sub_assembly_items[0].qty, 20) + self.assertEqual(plan.sub_assembly_items[1].qty, 50) + + from erpnext.manufacturing.doctype.production_plan.production_plan import ( + get_items_for_material_requests, + ) + + mr_items = get_items_for_material_requests(plan.as_dict()) + + self.assertEqual(mr_items[0].get("quantity"), 80) + self.assertEqual(mr_items[1].get("quantity"), 70) + def create_production_plan(**args): """