mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-12 22:21:50 +00:00
Merge pull request #55553 from aerele/fix/support-#69882
fix(manufacturing): fetch raw materials from work order
This commit is contained in:
@@ -3631,6 +3631,58 @@ class TestWorkOrder(FrappeTestCase):
|
||||
|
||||
self.assertEqual(bin1_at_completion.reserved_qty_for_production, 0)
|
||||
|
||||
@change_settings(
|
||||
"Manufacturing Settings",
|
||||
{"allow_editing_of_items_and_quantities_in_work_order": 1},
|
||||
)
|
||||
def test_manufacture_se_fetches_edited_qty_from_work_order(self):
|
||||
"""When a raw material qty is edited on the Work Order, the Manufacture Stock Entry
|
||||
must consume the edited quantity (scaled to fg_completed_qty) from the Work Order,
|
||||
not the original BOM quantity."""
|
||||
warehouse = "_Test Warehouse - _TC"
|
||||
wo_order = make_wo_order_test_record(
|
||||
item="_Test FG Item", qty=10, skip_transfer=1, source_warehouse=warehouse
|
||||
)
|
||||
|
||||
# edit a required item's qty
|
||||
wo_order.required_items[0].db_set("required_qty", flt(wo_order.required_items[0].required_qty) + 7)
|
||||
wo_order.reload()
|
||||
edited_row = wo_order.required_items[0]
|
||||
|
||||
fg_qty = 5
|
||||
se = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", fg_qty))
|
||||
se_qty = {row.item_code: row.qty for row in se.items if row.s_warehouse}
|
||||
|
||||
precision = frappe.get_precision("Stock Entry Detail", "qty")
|
||||
expected = flt(edited_row.required_qty / wo_order.qty * fg_qty, precision)
|
||||
self.assertEqual(flt(se_qty.get(edited_row.item_code)), expected)
|
||||
|
||||
@change_settings(
|
||||
"Manufacturing Settings",
|
||||
{"allow_editing_of_items_and_quantities_in_work_order": 1},
|
||||
)
|
||||
def test_manufacture_se_fetches_item_not_in_bom_from_work_order(self):
|
||||
"""A raw material that is present on the Work Order but not on the BOM must still be
|
||||
fetched into the Manufacture Stock Entry, proving items are sourced from the Work
|
||||
Order's required_items rather than re-derived from the BOM."""
|
||||
extra_item = make_item(
|
||||
"_Test WO Extra Raw Material", {"is_stock_item": 1, "valuation_rate": 100}
|
||||
).name
|
||||
warehouse = "_Test Warehouse - _TC"
|
||||
wo_order = make_wo_order_test_record(
|
||||
item="_Test FG Item", qty=10, skip_transfer=1, source_warehouse=warehouse
|
||||
)
|
||||
|
||||
original_item = wo_order.required_items[0].item_code
|
||||
wo_order.required_items[0].db_set("item_code", extra_item)
|
||||
wo_order.reload()
|
||||
|
||||
se = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 5))
|
||||
se_items = [row.item_code for row in se.items if row.s_warehouse]
|
||||
|
||||
self.assertIn(extra_item, se_items)
|
||||
self.assertNotIn(original_item, se_items)
|
||||
|
||||
|
||||
def make_stock_in_entries_and_get_batches(rm_item, source_warehouse, wip_warehouse):
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import (
|
||||
|
||||
@@ -2467,9 +2467,24 @@ class StockEntry(StockController):
|
||||
):
|
||||
self.get_unconsumed_raw_materials()
|
||||
|
||||
elif self.pro_doc and (
|
||||
self.purpose == "Manufacture" or self.purpose == "Material Consumption for Manufacture"
|
||||
):
|
||||
if not self.fg_completed_qty:
|
||||
frappe.throw(_("{0} is mandatory").format(_(self.meta.get_label("fg_completed_qty"))))
|
||||
|
||||
item_dict = self.get_work_order_raw_materials(self.fg_completed_qty)
|
||||
|
||||
for item in item_dict.values():
|
||||
if self.pro_doc.from_wip_warehouse:
|
||||
item["from_warehouse"] = self.pro_doc.wip_warehouse
|
||||
item["to_warehouse"] = ""
|
||||
|
||||
self.add_to_stock_entry_detail(item_dict)
|
||||
|
||||
else:
|
||||
if not self.fg_completed_qty:
|
||||
frappe.throw(_("Manufacturing Quantity is mandatory"))
|
||||
frappe.throw(_("{0} is mandatory").format(_(self.meta.get_label("fg_completed_qty"))))
|
||||
|
||||
item_dict = self.get_bom_raw_materials(self.fg_completed_qty)
|
||||
|
||||
@@ -2724,6 +2739,56 @@ class StockEntry(StockController):
|
||||
|
||||
return item_dict
|
||||
|
||||
def get_work_order_raw_materials(self, qty):
|
||||
item_dict = frappe._dict()
|
||||
|
||||
used_alternative_items = get_used_alternative_items(
|
||||
subcontract_order_field=self.subcontract_data.order_field, work_order=self.work_order
|
||||
)
|
||||
|
||||
for d in self.pro_doc.get("required_items"):
|
||||
item_qty = flt(
|
||||
(d.required_qty / self.pro_doc.qty) * qty, frappe.get_precision("Stock Entry Detail", "qty")
|
||||
)
|
||||
from_warehouse = (
|
||||
d.source_warehouse
|
||||
if self.pro_doc.skip_transfer and not self.pro_doc.from_wip_warehouse
|
||||
else self.from_warehouse or d.source_warehouse
|
||||
)
|
||||
|
||||
item_row = frappe._dict(
|
||||
{
|
||||
"item_code": d.item_code,
|
||||
"item_name": d.item_name,
|
||||
"description": d.description,
|
||||
"qty": item_qty,
|
||||
"stock_uom": d.stock_uom,
|
||||
"uom": d.stock_uom,
|
||||
"conversion_factor": 1,
|
||||
"from_warehouse": from_warehouse,
|
||||
"allow_alternative_item": d.allow_alternative_item
|
||||
and self.pro_doc.allow_alternative_item,
|
||||
}
|
||||
)
|
||||
|
||||
if d.item_code in used_alternative_items:
|
||||
alt = used_alternative_items.get(d.item_code)
|
||||
item_row.update(
|
||||
{
|
||||
"item_code": alt.item_code,
|
||||
"item_name": alt.item_name,
|
||||
"stock_uom": alt.stock_uom,
|
||||
"uom": alt.uom,
|
||||
"conversion_factor": alt.conversion_factor,
|
||||
"description": alt.description,
|
||||
"original_item": d.item_code,
|
||||
}
|
||||
)
|
||||
|
||||
item_dict[d.item_code] = item_row
|
||||
|
||||
return item_dict
|
||||
|
||||
def get_bom_scrap_material(self, qty):
|
||||
from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict
|
||||
|
||||
|
||||
Reference in New Issue
Block a user