From 6f5815e44f0532667509e64bc0266769d794c9be Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 27 Feb 2024 16:46:47 +0530 Subject: [PATCH] fix: parent warehouse checks in the production plan for sub-assemblies (#40150) --- .../production_plan/production_plan.js | 6 ++ .../production_plan/production_plan.json | 4 +- .../production_plan/production_plan.py | 82 +++++++++++-------- .../production_plan/test_production_plan.py | 44 ++++++++++ .../production_plan_item.json | 8 +- .../production_plan_sub_assembly_item.json | 6 +- 6 files changed, 112 insertions(+), 38 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index c9c474db7f..667ece2077 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -518,6 +518,12 @@ frappe.ui.form.on("Production Plan Sales Order", { } }); +frappe.ui.form.on("Production Plan Sub Assembly Item", { + fg_warehouse(frm, cdt, cdn) { + erpnext.utils.copy_value_in_all_rows(frm.doc, cdt, cdn, "sub_assembly_items", "fg_warehouse"); + }, +}) + frappe.tour['Production Plan'] = [ { fieldname: "get_items_from", diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json index 54c3893928..84bbad58c3 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.json +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json @@ -421,9 +421,11 @@ "fieldtype": "Column Break" }, { + "description": "When a parent warehouse is chosen, the system conducts stock checks against the associated child warehouses", "fieldname": "sub_assembly_warehouse", "fieldtype": "Link", "label": "Sub Assembly Warehouse", + "mandatory_depends_on": "eval:doc.skip_available_sub_assembly_item === 1", "options": "Warehouse" }, { @@ -437,7 +439,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2024-02-11 15:42:47.642481", + "modified": "2024-02-27 13:34:20.692211", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan", diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 517b2b0f88..c852f845b1 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -894,8 +894,8 @@ class ProductionPlan(Document): sub_assembly_items_store = [] # temporary store to process all subassembly items for row in self.po_items: - if self.skip_available_sub_assembly_item and not row.warehouse: - frappe.throw(_("Row #{0}: Please select the FG Warehouse in Assembly Items").format(row.idx)) + if self.skip_available_sub_assembly_item and not self.sub_assembly_warehouse: + frappe.throw(_("Row #{0}: Please select the Sub Assembly Warehouse").format(row.idx)) if not row.item_code: frappe.throw(_("Row #{0}: Please select Item Code in Assembly Items").format(row.idx)) @@ -905,15 +905,24 @@ class ProductionPlan(Document): bom_data = [] - warehouse = ( - (self.sub_assembly_warehouse or row.warehouse) - if self.skip_available_sub_assembly_item - else None - ) + warehouse = (self.sub_assembly_warehouse) if self.skip_available_sub_assembly_item else None get_sub_assembly_items(row.bom_no, bom_data, row.planned_qty, self.company, warehouse=warehouse) self.set_sub_assembly_items_based_on_level(row, bom_data, manufacturing_type) sub_assembly_items_store.extend(bom_data) + if not sub_assembly_items_store and self.skip_available_sub_assembly_item: + message = ( + _( + "As there are sufficient Sub Assembly Items, Work Order is not required for Warehouse {0}." + ).format(self.sub_assembly_warehouse) + + "

" + ) + message += _( + "If you still want to proceed, please disable 'Skip Available Sub Assembly Items' checkbox." + ) + + frappe.msgprint(message, title=_("Note")) + if self.combine_sub_items: # Combine subassembly items sub_assembly_items_store = self.combine_subassembly_items(sub_assembly_items_store) @@ -926,15 +935,19 @@ class ProductionPlan(Document): def set_sub_assembly_items_based_on_level(self, row, bom_data, manufacturing_type=None): "Modify bom_data, set additional details." + is_group_warehouse = frappe.db.get_value("Warehouse", self.sub_assembly_warehouse, "is_group") + for data in bom_data: data.qty = data.stock_qty data.production_plan_item = row.name - data.fg_warehouse = self.sub_assembly_warehouse or row.warehouse data.schedule_date = row.planned_start_date data.type_of_manufacturing = manufacturing_type or ( "Subcontract" if data.is_sub_contracted_item else "In House" ) + if not is_group_warehouse: + data.fg_warehouse = self.sub_assembly_warehouse + def set_default_supplier_for_subcontracting_order(self): items = [ d.production_item for d in self.sub_assembly_items if d.type_of_manufacturing == "Subcontract" @@ -1478,7 +1491,7 @@ def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_d so_item_details = frappe._dict() sub_assembly_items = {} - if doc.get("skip_available_sub_assembly_item"): + if doc.get("skip_available_sub_assembly_item") and doc.get("sub_assembly_items"): for d in doc.get("sub_assembly_items"): sub_assembly_items.setdefault((d.get("production_item"), d.get("bom_no")), d.get("qty")) @@ -1690,34 +1703,37 @@ def get_sub_assembly_items(bom_no, bom_data, to_produce_qty, company, warehouse= stock_qty = (d.stock_qty / d.parent_bom_qty) * flt(to_produce_qty) if warehouse: - bin_dict = get_bin_details(d, company, for_warehouse=warehouse) + bin_details = get_bin_details(d, company, for_warehouse=warehouse) - if bin_dict and bin_dict[0].projected_qty > 0: - if bin_dict[0].projected_qty > stock_qty: - continue - else: - stock_qty = stock_qty - bin_dict[0].projected_qty + for _bin_dict in bin_details: + if _bin_dict.projected_qty > 0: + if _bin_dict.projected_qty > stock_qty: + stock_qty = 0 + continue + else: + stock_qty = stock_qty - _bin_dict.projected_qty - bom_data.append( - frappe._dict( - { - "parent_item_code": parent_item_code, - "description": d.description, - "production_item": d.item_code, - "item_name": d.item_name, - "stock_uom": d.stock_uom, - "uom": d.stock_uom, - "bom_no": d.value, - "is_sub_contracted_item": d.is_sub_contracted_item, - "bom_level": indent, - "indent": indent, - "stock_qty": stock_qty, - } + if stock_qty > 0: + bom_data.append( + frappe._dict( + { + "parent_item_code": parent_item_code, + "description": d.description, + "production_item": d.item_code, + "item_name": d.item_name, + "stock_uom": d.stock_uom, + "uom": d.stock_uom, + "bom_no": d.value, + "is_sub_contracted_item": d.is_sub_contracted_item, + "bom_level": indent, + "indent": indent, + "stock_qty": stock_qty, + } + ) ) - ) - if d.value: - get_sub_assembly_items(d.value, bom_data, stock_qty, company, warehouse, indent=indent + 1) + if d.value: + get_sub_assembly_items(d.value, bom_data, stock_qty, company, warehouse, indent=indent + 1) def set_default_warehouses(row, default_warehouses): diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 53537f9e1a..0bf370564f 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -1205,6 +1205,7 @@ class TestProductionPlan(FrappeTestCase): ignore_existing_ordered_qty=1, do_not_submit=1, skip_available_sub_assembly_item=1, + sub_assembly_warehouse="_Test Warehouse - _TC", warehouse="_Test Warehouse - _TC", ) @@ -1338,6 +1339,7 @@ class TestProductionPlan(FrappeTestCase): ignore_existing_ordered_qty=1, do_not_submit=1, skip_available_sub_assembly_item=1, + sub_assembly_warehouse="_Test Warehouse - _TC", warehouse="_Test Warehouse - _TC", ) @@ -1590,6 +1592,48 @@ class TestProductionPlan(FrappeTestCase): for row in work_orders: self.assertEqual(row.qty, wo_qty[row.name]) + def test_parent_warehouse_for_sub_assembly_items(self): + from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom + from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse + + parent_warehouse = "_Test Warehouse Group - _TC" + sub_warehouse = create_warehouse("Sub Warehouse", company="_Test Company") + + fg_item = make_item(properties={"is_stock_item": 1}).name + sf_item = make_item(properties={"is_stock_item": 1}).name + rm_item = make_item(properties={"is_stock_item": 1}).name + + bom_tree = {fg_item: {sf_item: {rm_item: {}}}} + create_nested_bom(bom_tree, prefix="") + + pln = create_production_plan( + item_code=fg_item, + planned_qty=10, + warehouse="_Test Warehouse - _TC", + sub_assembly_warehouse=parent_warehouse, + skip_available_sub_assembly_item=1, + do_not_submit=1, + skip_getting_mr_items=1, + ) + + pln.get_sub_assembly_items() + + for row in pln.sub_assembly_items: + self.assertFalse(row.fg_warehouse) + self.assertEqual(row.production_item, sf_item) + self.assertEqual(row.qty, 10.0) + + make_stock_entry(item_code=sf_item, qty=5, target=sub_warehouse, rate=100) + + pln.sub_assembly_items = [] + pln.get_sub_assembly_items() + + self.assertEqual(pln.sub_assembly_warehouse, parent_warehouse) + for row in pln.sub_assembly_items: + self.assertFalse(row.fg_warehouse) + self.assertEqual(row.production_item, sf_item) + self.assertEqual(row.qty, 5.0) + def create_production_plan(**args): """ diff --git a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json index 0688278e09..78a389760a 100644 --- a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json +++ b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json @@ -11,6 +11,7 @@ "bom_no", "column_break_6", "planned_qty", + "stock_uom", "warehouse", "planned_start_date", "section_break_9", @@ -18,7 +19,6 @@ "ordered_qty", "column_break_17", "description", - "stock_uom", "produced_qty", "reference_section", "sales_order", @@ -65,6 +65,7 @@ "width": "100px" }, { + "columns": 1, "fieldname": "planned_qty", "fieldtype": "Float", "in_list_view": 1, @@ -80,6 +81,7 @@ "fieldtype": "Column Break" }, { + "columns": 2, "fieldname": "warehouse", "fieldtype": "Link", "in_list_view": 1, @@ -141,8 +143,10 @@ "width": "200px" }, { + "columns": 1, "fieldname": "stock_uom", "fieldtype": "Link", + "in_list_view": 1, "label": "UOM", "oldfieldname": "stock_uom", "oldfieldtype": "Data", @@ -216,7 +220,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2022-11-25 14:15:40.061514", + "modified": "2024-02-27 13:24:43.571844", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan Item", diff --git a/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json index aff740b732..7965965d2b 100644 --- a/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json +++ b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json @@ -101,7 +101,6 @@ "columns": 1, "fieldname": "bom_level", "fieldtype": "Int", - "in_list_view": 1, "label": "Level (BOM)", "read_only": 1 }, @@ -149,8 +148,10 @@ "label": "Indent" }, { + "columns": 2, "fieldname": "fg_warehouse", "fieldtype": "Link", + "in_list_view": 1, "label": "Target Warehouse", "options": "Warehouse" }, @@ -170,6 +171,7 @@ "options": "Supplier" }, { + "columns": 1, "fieldname": "schedule_date", "fieldtype": "Datetime", "in_list_view": 1, @@ -207,7 +209,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-11-03 13:33:42.959387", + "modified": "2024-02-27 13:45:17.422435", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan Sub Assembly Item",