Skip to content

Commit b5f6926

Browse files
rohitwaghchauremergify[bot]
authored andcommitted
fix: validate components and their qty as per BOM in the stock entry
(cherry picked from commit b1de82d)
1 parent c615df5 commit b5f6926

File tree

3 files changed

+74
-13
lines changed

3 files changed

+74
-13
lines changed

erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -242,14 +242,14 @@
242242
"depends_on": "eval:doc.backflush_raw_materials_based_on == \"BOM\"",
243243
"fieldname": "validate_components_quantities_per_bom",
244244
"fieldtype": "Check",
245-
"label": "Validate Components Quantities Per BOM"
245+
"label": "Validate Components and Quantities Per BOM"
246246
}
247247
],
248248
"icon": "icon-wrench",
249249
"index_web_pages_for_search": 1,
250250
"issingle": 1,
251251
"links": [],
252-
"modified": "2024-09-02 12:12:03.132567",
252+
"modified": "2025-01-02 12:46:33.520853",
253253
"modified_by": "Administrator",
254254
"module": "Manufacturing",
255255
"name": "Manufacturing Settings",
@@ -267,4 +267,4 @@
267267
"sort_order": "DESC",
268268
"states": [],
269269
"track_changes": 1
270-
}
270+
}

erpnext/manufacturing/doctype/work_order/test_work_order.py

+50
Original file line numberDiff line numberDiff line change
@@ -2401,6 +2401,56 @@ def test_components_qty_for_bom_based_manufacture_entry(self):
24012401

24022402
frappe.db.set_single_value("Manufacturing Settings", "validate_components_quantities_per_bom", 0)
24032403

2404+
def test_components_as_per_bom_for_manufacture_entry(self):
2405+
frappe.db.set_single_value("Manufacturing Settings", "backflush_raw_materials_based_on", "BOM")
2406+
frappe.db.set_single_value("Manufacturing Settings", "validate_components_quantities_per_bom", 1)
2407+
2408+
fg_item = "Test FG Item For Component Validation 1"
2409+
source_warehouse = "Stores - _TC"
2410+
raw_materials = ["Test Component Validation RM Item 11", "Test Component Validation RM Item 12"]
2411+
2412+
make_item(fg_item, {"is_stock_item": 1})
2413+
for item in raw_materials:
2414+
make_item(item, {"is_stock_item": 1})
2415+
test_stock_entry.make_stock_entry(
2416+
item_code=item,
2417+
target=source_warehouse,
2418+
qty=10,
2419+
basic_rate=100,
2420+
)
2421+
2422+
make_bom(item=fg_item, source_warehouse=source_warehouse, raw_materials=raw_materials)
2423+
2424+
wo = make_wo_order_test_record(
2425+
item=fg_item,
2426+
qty=10,
2427+
source_warehouse=source_warehouse,
2428+
)
2429+
2430+
transfer_entry = frappe.get_doc(make_stock_entry(wo.name, "Material Transfer for Manufacture", 10))
2431+
transfer_entry.save()
2432+
transfer_entry.remove(transfer_entry.items[0])
2433+
2434+
self.assertRaises(frappe.ValidationError, transfer_entry.save)
2435+
2436+
transfer_entry = frappe.get_doc(make_stock_entry(wo.name, "Material Transfer for Manufacture", 10))
2437+
transfer_entry.save()
2438+
transfer_entry.submit()
2439+
2440+
manufacture_entry = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 10))
2441+
manufacture_entry.save()
2442+
2443+
manufacture_entry.remove(manufacture_entry.items[0])
2444+
2445+
self.assertRaises(frappe.ValidationError, manufacture_entry.save)
2446+
manufacture_entry.delete()
2447+
2448+
manufacture_entry = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 10))
2449+
manufacture_entry.save()
2450+
manufacture_entry.submit()
2451+
2452+
frappe.db.set_single_value("Manufacturing Settings", "validate_components_quantities_per_bom", 0)
2453+
24042454

24052455
def make_operation(**kwargs):
24062456
kwargs = frappe._dict(kwargs)

erpnext/stock/doctype/stock_entry/stock_entry.py

+21-10
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ def validate(self):
231231
self.validate_serialized_batch()
232232
self.calculate_rate_and_amount()
233233
self.validate_putaway_capacity()
234-
self.validate_component_quantities()
234+
self.validate_component_and_quantities()
235235

236236
if not self.get("purpose") == "Manufacture":
237237
# ignore scrap item wh difference and empty source/target wh
@@ -713,7 +713,7 @@ def set_actual_qty(self):
713713
title=_("Insufficient Stock"),
714714
)
715715

716-
def validate_component_quantities(self):
716+
def validate_component_and_quantities(self):
717717
if self.purpose not in ["Manufacture", "Material Transfer for Manufacture"]:
718718
return
719719

@@ -726,20 +726,31 @@ def validate_component_quantities(self):
726726
raw_materials = self.get_bom_raw_materials(self.fg_completed_qty)
727727

728728
precision = frappe.get_precision("Stock Entry Detail", "qty")
729-
for row in self.items:
730-
if not row.s_warehouse:
731-
continue
732-
733-
if details := raw_materials.get(row.item_code):
734-
if flt(details.get("qty"), precision) != flt(row.qty, precision):
729+
for item_code, details in raw_materials.items():
730+
if matched_item := self.get_matched_items(item_code):
731+
if flt(details.get("qty"), precision) != flt(matched_item.qty, precision):
735732
frappe.throw(
736733
_("For the item {0}, the quantity should be {1} according to the BOM {2}.").format(
737-
frappe.bold(row.item_code),
738-
flt(details.get("qty"), precision),
734+
frappe.bold(item_code),
735+
flt(details.get("qty")),
739736
get_link_to_form("BOM", self.bom_no),
740737
),
741738
title=_("Incorrect Component Quantity"),
742739
)
740+
else:
741+
frappe.throw(
742+
_("According to the BOM {0}, the Item '{1}' is missing in the stock entry.").format(
743+
get_link_to_form("BOM", self.bom_no), frappe.bold(item_code)
744+
),
745+
title=_("Missing Item"),
746+
)
747+
748+
def get_matched_items(self, item_code):
749+
for row in self.items:
750+
if row.item_code == item_code:
751+
return row
752+
753+
return {}
743754

744755
@frappe.whitelist()
745756
def get_stock_and_rate(self):

0 commit comments

Comments
 (0)