mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-26 18:18:30 +00:00
fix: continuous raw material consumption with bom validation
(cherry picked from commit 017cc9d9f9)
# Conflicts:
# erpnext/stock/doctype/stock_entry/test_stock_entry.py
This commit is contained in:
@@ -731,7 +731,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 =
|
||||
|
||||
@@ -858,7 +858,9 @@ class StockEntry(StockController):
|
||||
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),
|
||||
@@ -923,12 +925,37 @@ class StockEntry(StockController):
|
||||
)
|
||||
|
||||
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):
|
||||
"""
|
||||
|
||||
@@ -2233,7 +2233,91 @@ class TestStockEntry(FrappeTestCase):
|
||||
se.submit()
|
||||
|
||||
|
||||
<<<<<<< HEAD
|
||||
def make_serialized_item(**args):
|
||||
=======
|
||||
warehouse = "_Test Warehouse - _TC"
|
||||
retain_sample_item = make_item(
|
||||
"Retain Sample Item",
|
||||
properties={
|
||||
"is_stock_item": 1,
|
||||
"retain_sample": 1,
|
||||
"sample_quantity": 2,
|
||||
"has_batch_no": 1,
|
||||
"has_serial_no": 1,
|
||||
"create_new_batch": 1,
|
||||
"batch_number_series": "SAMPLE-RET-.#####",
|
||||
"serial_no_series": "SAMPLE-RET-SN-.#####",
|
||||
},
|
||||
)
|
||||
material_receipt = make_stock_entry(
|
||||
item_code=retain_sample_item.item_code, target=warehouse, qty=10, purpose="Material Receipt"
|
||||
)
|
||||
|
||||
source_sabb = frappe.get_doc(
|
||||
"Serial and Batch Bundle", material_receipt.items[0].serial_and_batch_bundle
|
||||
)
|
||||
batch = source_sabb.entries[0].batch_no
|
||||
serial_nos = [entry.serial_no for entry in source_sabb.entries]
|
||||
|
||||
sample_entry = frappe.get_doc(
|
||||
move_sample_to_retention_warehouse(material_receipt.company, material_receipt.items)
|
||||
)
|
||||
sample_entry.submit()
|
||||
target_sabb = frappe.get_doc("Serial and Batch Bundle", sample_entry.items[0].serial_and_batch_bundle)
|
||||
|
||||
self.assertEqual(sample_entry.items[0].transfer_qty, 2)
|
||||
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": 0})
|
||||
def test_raw_material_missing_validation(self):
|
||||
stock_entry = make_stock_entry(
|
||||
item_code="_Test Item",
|
||||
qty=1,
|
||||
target="_Test Warehouse - _TC",
|
||||
do_not_save=True,
|
||||
)
|
||||
|
||||
stock_entry.purpose = "Manufacture"
|
||||
stock_entry.stock_entry_type = "Manufacture"
|
||||
stock_entry.items[0].is_finished_item = 1
|
||||
|
||||
self.assertRaises(
|
||||
frappe.ValidationError,
|
||||
stock_entry.save,
|
||||
)
|
||||
|
||||
@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):
|
||||
>>>>>>> 017cc9d9f9 (fix: continuous raw material consumption with bom validation)
|
||||
args = frappe._dict(args)
|
||||
se = frappe.copy_doc(test_records[0])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user