fix: persist main item code for MR plan items (#55623)

This commit is contained in:
Mihir Kandoi
2026-06-04 21:38:35 +05:30
committed by GitHub
parent ddaf75a60d
commit e8e0514a30
6 changed files with 119 additions and 2 deletions

View File

@@ -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
}
}

View File

@@ -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"
]

View File

@@ -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"),
}
)

View File

@@ -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

View File

@@ -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

View File

@@ -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}
)