fix: validate components and their qty as per BOM in the stock entry
(cherry picked from commit b1de82ddad)
This commit is contained in:
committed by
Mergify
parent
c615df5ac4
commit
b5f6926140
@@ -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",
|
||||
@@ -267,4 +267,4 @@
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
@@ -2401,6 +2401,56 @@ class TestWorkOrder(FrappeTestCase):
|
||||
|
||||
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)
|
||||
|
||||
@@ -231,7 +231,7 @@ class StockEntry(StockController):
|
||||
self.validate_serialized_batch()
|
||||
self.calculate_rate_and_amount()
|
||||
self.validate_putaway_capacity()
|
||||
self.validate_component_quantities()
|
||||
self.validate_component_and_quantities()
|
||||
|
||||
if not self.get("purpose") == "Manufacture":
|
||||
# ignore scrap item wh difference and empty source/target wh
|
||||
@@ -713,7 +713,7 @@ class StockEntry(StockController):
|
||||
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
|
||||
|
||||
@@ -726,20 +726,31 @@ class StockEntry(StockController):
|
||||
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):
|
||||
|
||||
Reference in New Issue
Block a user