Skip to content

Commit

Permalink
Merge pull request frappe#45043 from rohitwaghchaure/fixed-github-44909
Browse files Browse the repository at this point in the history
fix: validate components and their qty as per BOM in the stock entry
  • Loading branch information
rohitwaghchaure authored Jan 2, 2025
2 parents e609a6a + b1de82d commit bdece96
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -242,14 +242,14 @@
"depends_on": "eval:doc.backflush_raw_materials_based_on == \"BOM\"",
"fieldname": "validate_components_quantities_per_bom",
"fieldtype": "Check",
"label": "Validate Components Quantities Per BOM"
"label": "Validate Components and Quantities Per BOM"
}
],
"icon": "icon-wrench",
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2024-09-02 12:12:03.132567",
"modified": "2025-01-02 12:46:33.520853",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Manufacturing Settings",
Expand All @@ -267,4 +267,4 @@
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}
50 changes: 50 additions & 0 deletions erpnext/manufacturing/doctype/work_order/test_work_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -2417,6 +2417,56 @@ def test_components_qty_for_bom_based_manufacture_entry(self):

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

def test_components_as_per_bom_for_manufacture_entry(self):
frappe.db.set_single_value("Manufacturing Settings", "backflush_raw_materials_based_on", "BOM")
frappe.db.set_single_value("Manufacturing Settings", "validate_components_quantities_per_bom", 1)

fg_item = "Test FG Item For Component Validation 1"
source_warehouse = "Stores - _TC"
raw_materials = ["Test Component Validation RM Item 11", "Test Component Validation RM Item 12"]

make_item(fg_item, {"is_stock_item": 1})
for item in raw_materials:
make_item(item, {"is_stock_item": 1})
test_stock_entry.make_stock_entry(
item_code=item,
target=source_warehouse,
qty=10,
basic_rate=100,
)

make_bom(item=fg_item, source_warehouse=source_warehouse, raw_materials=raw_materials)

wo = make_wo_order_test_record(
item=fg_item,
qty=10,
source_warehouse=source_warehouse,
)

transfer_entry = frappe.get_doc(make_stock_entry(wo.name, "Material Transfer for Manufacture", 10))
transfer_entry.save()
transfer_entry.remove(transfer_entry.items[0])

self.assertRaises(frappe.ValidationError, transfer_entry.save)

transfer_entry = frappe.get_doc(make_stock_entry(wo.name, "Material Transfer for Manufacture", 10))
transfer_entry.save()
transfer_entry.submit()

manufacture_entry = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 10))
manufacture_entry.save()

manufacture_entry.remove(manufacture_entry.items[0])

self.assertRaises(frappe.ValidationError, manufacture_entry.save)
manufacture_entry.delete()

manufacture_entry = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 10))
manufacture_entry.save()
manufacture_entry.submit()

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


def make_operation(**kwargs):
kwargs = frappe._dict(kwargs)
Expand Down
31 changes: 21 additions & 10 deletions erpnext/stock/doctype/stock_entry/stock_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ def validate(self):
self.validate_serialized_batch()
self.calculate_rate_and_amount()
self.validate_putaway_capacity()
self.validate_component_quantities()
self.validate_component_and_quantities()

if self.get("purpose") != "Manufacture":
# ignore scrap item wh difference and empty source/target wh
Expand Down Expand Up @@ -731,7 +731,7 @@ def set_actual_qty(self):
title=_("Insufficient Stock"),
)

def validate_component_quantities(self):
def validate_component_and_quantities(self):
if self.purpose not in ["Manufacture", "Material Transfer for Manufacture"]:
return

Expand All @@ -744,20 +744,31 @@ def validate_component_quantities(self):
raw_materials = self.get_bom_raw_materials(self.fg_completed_qty)

precision = frappe.get_precision("Stock Entry Detail", "qty")
for row in self.items:
if not row.s_warehouse:
continue

if details := raw_materials.get(row.item_code):
if flt(details.get("qty"), precision) != flt(row.qty, precision):
for item_code, details in raw_materials.items():
if matched_item := self.get_matched_items(item_code):
if flt(details.get("qty"), precision) != flt(matched_item.qty, precision):
frappe.throw(
_("For the item {0}, the quantity should be {1} according to the BOM {2}.").format(
frappe.bold(row.item_code),
flt(details.get("qty"), precision),
frappe.bold(item_code),
flt(details.get("qty")),
get_link_to_form("BOM", self.bom_no),
),
title=_("Incorrect Component Quantity"),
)
else:
frappe.throw(
_("According to the BOM {0}, the Item '{1}' is missing in the stock entry.").format(
get_link_to_form("BOM", self.bom_no), frappe.bold(item_code)
),
title=_("Missing Item"),
)

def get_matched_items(self, item_code):
for row in self.items:
if row.item_code == item_code:
return row

return {}

@frappe.whitelist()
def get_stock_and_rate(self):
Expand Down

0 comments on commit bdece96

Please sign in to comment.