diff --git a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json index 06c1b497551..41b03cc7793 100644 --- a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json +++ b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json @@ -21,6 +21,7 @@ "min_order_qty", "section_break_8", "sales_order", + "main_item_code", "bin_qty_section", "actual_qty", "requested_qty", @@ -114,6 +115,14 @@ "options": "Sales Order", "read_only": 1 }, + { + "fieldname": "main_item_code", + "fieldtype": "Data", + "hidden": 1, + "label": "Main Item Code", + "no_copy": 1, + "read_only": 1 + }, { "fieldname": "requested_qty", "fieldtype": "Float", @@ -213,4 +222,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.py b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.py index aa1c72294d3..02bb50617f5 100644 --- a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.py +++ b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.py @@ -20,6 +20,7 @@ class MaterialRequestPlanItem(Document): from_warehouse: DF.Link | None item_code: DF.Link item_name: DF.Data | None + main_item_code: DF.Data | None material_request_type: DF.Literal[ "", "Purchase", "Material Transfer", "Material Issue", "Manufacture", "Customer Provided" ] diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 1d63f91caaf..67323d42d40 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -1397,7 +1397,7 @@ def get_material_request_items( "sales_order": sales_order, "description": row.get("description"), "uom": row.get("purchase_uom") or row.get("stock_uom"), - "main_bom_item": row.get("main_bom_item"), + "main_item_code": row.get("main_bom_item"), } @@ -1557,6 +1557,8 @@ def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_d "item_code": sa_row.production_item, "required_qty": sa_row.qty, "include_exploded_items": 0, + "sales_order": sa_row.sales_order, + "main_bom_item": sa_row.parent_item_code, } ) ) @@ -1660,6 +1662,7 @@ def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_d "stock_uom": item_master.stock_uom, "conversion_factor": conversion_factor, "safety_stock": item_master.safety_stock, + "main_bom_item": data.get("main_bom_item"), } ) diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 62aa4f6ea11..5af1fdb36b5 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -1254,8 +1254,10 @@ class TestProductionPlan(FrappeTestCase): plan.get_sub_assembly_items() mr_items = [] + expected_main_item_by_mr_item = {"ChildPart1 For MR": "SubAssembly1-1 For MR"} for row in plan.sub_assembly_items: mr_items.append(row.production_item) + expected_main_item_by_mr_item[row.production_item] = row.parent_item_code row.type_of_manufacturing = "Material Request" plan.save() @@ -1265,6 +1267,10 @@ class TestProductionPlan(FrappeTestCase): for item_code in mr_items: self.assertTrue(item_code in validate_mr_items) + main_item_by_mr_item = {item.get("item_code"): item.get("main_item_code") for item in items} + for item_code, main_item_code in expected_main_item_by_mr_item.items(): + self.assertEqual(main_item_by_mr_item[item_code], main_item_code) + def test_resered_qty_for_production_plan_for_material_requests(self): from erpnext.stock.utils import get_or_make_bin diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 3e467b37b0f..239066f7c98 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -435,3 +435,4 @@ erpnext.patches.v16_0.update_order_qty_and_requested_qty_based_on_mr_and_po erpnext.patches.v16_0.depends_on_inv_dimensions erpnext.patches.v16_0.clear_procedures_from_receivable_report erpnext.patches.v16_0.migrate_address_contact_custom_fields +erpnext.patches.v15_0.set_main_item_code_in_material_request_plan_item diff --git a/erpnext/patches/v15_0/set_main_item_code_in_material_request_plan_item.py b/erpnext/patches/v15_0/set_main_item_code_in_material_request_plan_item.py new file mode 100644 index 00000000000..74d024361b3 --- /dev/null +++ b/erpnext/patches/v15_0/set_main_item_code_in_material_request_plan_item.py @@ -0,0 +1,97 @@ +import frappe + + +def execute(): + frappe.reload_doc("manufacturing", "doctype", "material_request_plan_item") + + if not frappe.db.has_column("Material Request Plan Item", "main_item_code"): + return + + for row in get_material_request_plan_items(): + if row.main_item_code: + continue + + main_item_code = get_main_item_code(row) + if main_item_code: + frappe.db.set_value( + "Material Request Plan Item", + row.name, + "main_item_code", + main_item_code, + update_modified=False, + ) + + +def get_material_request_plan_items(): + return frappe.get_all( + "Material Request Plan Item", + fields=["name", "parent", "item_code", "sales_order", "main_item_code"], + ) + + +def get_main_item_code(row): + return ( + get_main_item_code_from_sub_assembly(row) + or get_main_item_code_from_sub_assembly_bom(row) + or get_main_item_code_from_production_plan_bom(row) + ) + + +def get_main_item_code_from_sub_assembly(row): + sub_assembly = frappe.db.get_value( + "Production Plan Sub Assembly Item", + get_filters( + row, + { + "parent": row.parent, + "production_item": row.item_code, + }, + ), + "parent_item_code", + ) + + return sub_assembly + + +def get_main_item_code_from_sub_assembly_bom(row): + for sub_assembly in get_sub_assembly_items(row): + if item_exists_in_bom(row.item_code, sub_assembly.bom_no): + return frappe.db.get_value("BOM", sub_assembly.bom_no, "item") + + +def get_main_item_code_from_production_plan_bom(row): + for production_plan_item in get_production_plan_items(row): + if item_exists_in_bom(row.item_code, production_plan_item.bom_no): + return frappe.db.get_value("BOM", production_plan_item.bom_no, "item") + + +def get_sub_assembly_items(row): + return frappe.get_all( + "Production Plan Sub Assembly Item", + filters=get_filters(row, {"parent": row.parent}), + fields=["bom_no"], + ) + + +def get_production_plan_items(row): + return frappe.get_all( + "Production Plan Item", + filters=get_filters(row, {"parent": row.parent}), + fields=["bom_no"], + ) + + +def get_filters(row, filters): + if row.sales_order: + filters["sales_order"] = row.sales_order + + return filters + + +def item_exists_in_bom(item_code, bom_no): + if not bom_no: + return False + + return frappe.db.exists("BOM Item", {"parent": bom_no, "item_code": item_code}) or frappe.db.exists( + "BOM Explosion Item", {"parent": bom_no, "item_code": item_code} + )