fix: continuous raw material consumption with bom validation (backport #51914) (#51919)

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
This commit is contained in:
mergify[bot]
2026-01-20 12:56:27 +00:00
committed by GitHub
parent 8393c3c32d
commit c9d7c6cd42
3 changed files with 57 additions and 3 deletions

View File

@@ -829,7 +829,7 @@ erpnext.work_order = {
}
}
if (counter > 0) {
var consumption_btn = frm.add_custom_button(
frm.add_custom_button(
__("Material Consumption"),
function () {
const backflush_raw_materials_based_on =

View File

@@ -938,7 +938,9 @@ class StockEntry(StockController, SubcontractingInwardController):
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(
_(
"For the item {0}, the consumed quantity should be {1} according to the BOM {2}."
).format(
frappe.bold(item_code),
flt(details.get("qty")),
get_link_to_form("BOM", self.bom_no),
@@ -1003,12 +1005,37 @@ class StockEntry(StockController, SubcontractingInwardController):
)
def get_matched_items(self, item_code):
for row in self.items:
items = [item for item in self.items if item.s_warehouse]
for row in items or self.get_consumed_items():
if row.item_code == item_code or row.original_item == item_code:
return row
return {}
def get_consumed_items(self):
"""Get all raw materials consumed through consumption entries"""
parent = frappe.qb.DocType("Stock Entry")
child = frappe.qb.DocType("Stock Entry Detail")
query = (
frappe.qb.from_(parent)
.join(child)
.on(parent.name == child.parent)
.select(
child.item_code,
Sum(child.qty).as_("qty"),
child.original_item,
)
.where(
(parent.docstatus == 1)
& (parent.purpose == "Material Consumption for Manufacture")
& (parent.work_order == self.work_order)
)
.groupby(child.item_code, child.original_item)
)
return query.run(as_dict=True)
@frappe.whitelist()
def get_stock_and_rate(self):
"""

View File

@@ -2362,6 +2362,33 @@ class TestStockEntry(IntegrationTestCase):
self.assertEqual(target_sabb.entries[0].batch_no, batch)
self.assertEqual([entry.serial_no for entry in target_sabb.entries], serial_nos[:2])
@IntegrationTestCase.change_settings(
"Manufacturing Settings",
{
"material_consumption": 1,
"backflush_raw_materials_based_on": "BOM",
"validate_components_quantities_per_bom": 1,
},
)
def test_validation_as_per_bom_with_continuous_raw_material_consumption(self):
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
from erpnext.manufacturing.doctype.work_order.work_order import make_stock_entry as _make_stock_entry
from erpnext.manufacturing.doctype.work_order.work_order import make_work_order
fg_item = make_item("_Mobiles", properties={"is_stock_item": 1}).name
rm_item1 = make_item("_Battery", properties={"is_stock_item": 1}).name
warehouse = "Stores - WP"
bom_no = make_bom(item=fg_item, raw_materials=[rm_item1]).name
make_stock_entry(item_code=rm_item1, target=warehouse, qty=5, rate=10, purpose="Material Receipt")
work_order = make_work_order(bom_no, fg_item, 5)
work_order.skip_transfer = 1
work_order.fg_warehouse = warehouse
work_order.submit()
frappe.get_doc(_make_stock_entry(work_order.name, "Material Consumption for Manufacture", 5)).submit()
frappe.get_doc(_make_stock_entry(work_order.name, "Manufacture", 5)).submit()
def make_serialized_item(self, **args):
args = frappe._dict(args)