mirror of
https://github.com/frappe/erpnext.git
synced 2026-07-31 16:24:37 +00:00
fix(manufacturing): fall back to UOM Conversion Factor in Production Plan (backport #57553) (#57554)
fix(manufacturing): fall back to UOM Conversion Factor in Production Plan Production Plan read the conversion factor straight off the item's own UOM child table, so an item with a purchase UOM but no matching row threw "UOM Conversion factor not found" while Stock Entry silently resolved it from the item's variant template or the UOM Conversion Factor doctype. Resolve it the same way, and keep returning None when nothing is configured anywhere so the missing-setup error still fires.
This commit is contained in:
@@ -32,6 +32,7 @@ from erpnext.manufacturing.doctype.bom.bom import get_children as get_bom_childr
|
||||
from erpnext.manufacturing.doctype.bom.bom import validate_bom_no
|
||||
from erpnext.manufacturing.doctype.work_order.work_order import get_item_details
|
||||
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
|
||||
from erpnext.stock.doctype.item.item import get_uom_conv_factor
|
||||
from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import StockReservation
|
||||
from erpnext.stock.get_item_details import get_conversion_factor
|
||||
from erpnext.stock.utils import get_or_make_bin
|
||||
@@ -1333,9 +1334,16 @@ def get_exploded_items(item_details, company, bom_no, include_non_stock_items, p
|
||||
|
||||
|
||||
def get_uom_conversion_factor(item_code, uom):
|
||||
return frappe.db.get_value(
|
||||
item = frappe.get_cached_value("Item", item_code, ["variant_of", "stock_uom"], as_dict=True)
|
||||
conversion_factor = frappe.db.get_value(
|
||||
"UOM Conversion Detail", {"parent": item_code, "uom": uom}, "conversion_factor"
|
||||
)
|
||||
if not conversion_factor and item.variant_of:
|
||||
conversion_factor = frappe.db.get_value(
|
||||
"UOM Conversion Detail", {"parent": item.variant_of, "uom": uom}, "conversion_factor"
|
||||
)
|
||||
|
||||
return conversion_factor or get_uom_conv_factor(uom, item.stock_uom)
|
||||
|
||||
|
||||
def get_subitems(
|
||||
|
||||
@@ -1730,6 +1730,118 @@ class TestProductionPlan(ERPNextTestSuite):
|
||||
self.assertTrue(row.warehouse == mrp_warhouse)
|
||||
self.assertEqual(row.quantity, 12.0)
|
||||
|
||||
def test_purchase_uom_falls_back_to_uom_conversion_factor(self):
|
||||
from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
|
||||
|
||||
if not frappe.db.exists("UOM Conversion Factor", {"from_uom": "Kg", "to_uom": "Gram"}):
|
||||
frappe.get_doc(
|
||||
doctype="UOM Conversion Factor",
|
||||
category="Mass",
|
||||
from_uom="Kg",
|
||||
to_uom="Gram",
|
||||
value=1000,
|
||||
).insert()
|
||||
|
||||
rm = make_item("Test RM Item Global CF", {"is_stock_item": 1, "stock_uom": "Gram"})
|
||||
rm.purchase_uom = "Kg"
|
||||
rm.save()
|
||||
self.assertFalse([row for row in rm.uoms if row.uom == "Kg"])
|
||||
|
||||
bom_tree = {"Test FG Item Global CF": {rm.name: {}}}
|
||||
parent_bom = create_nested_bom(bom_tree, prefix="")
|
||||
|
||||
plan = create_production_plan(
|
||||
item_code=parent_bom.item,
|
||||
planned_qty=2000,
|
||||
ignore_existing_ordered_qty=1,
|
||||
skip_getting_mr_items=1,
|
||||
do_not_submit=1,
|
||||
warehouse="_Test Warehouse - _TC",
|
||||
)
|
||||
plan.for_warehouse = "_Test Warehouse - _TC"
|
||||
|
||||
items = get_items_for_material_requests(
|
||||
plan.as_dict(), warehouses=[{"warehouse": "_Test Warehouse - _TC"}]
|
||||
)
|
||||
|
||||
row = frappe._dict(next(item for item in items if item["item_code"] == rm.name))
|
||||
self.assertEqual(row.uom, "Kg")
|
||||
self.assertEqual(row.conversion_factor, 1000)
|
||||
self.assertEqual(row.quantity, 2)
|
||||
|
||||
def test_variant_inherits_purchase_uom_conversion_factor_of_template(self):
|
||||
from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
|
||||
|
||||
template = make_item(
|
||||
"TRMVCF",
|
||||
{
|
||||
"is_stock_item": 1,
|
||||
"stock_uom": "Nos",
|
||||
"has_variants": 1,
|
||||
"attributes": [{"attribute": "Colour"}],
|
||||
},
|
||||
)
|
||||
if not [row for row in template.uoms if row.uom == "Box"]:
|
||||
template.purchase_uom = "Box"
|
||||
template.append("uoms", {"uom": "Box", "conversion_factor": 12})
|
||||
template.save()
|
||||
|
||||
if not frappe.db.exists("Item", "TRMVCF-RED"):
|
||||
create_variant("TRMVCF", {"Colour": "Red"}).insert()
|
||||
|
||||
variant = frappe.get_doc("Item", "TRMVCF-RED")
|
||||
variant.uoms = [row for row in variant.uoms if row.uom != "Box"]
|
||||
variant.purchase_uom = "Box"
|
||||
variant.save()
|
||||
|
||||
bom_tree = {"Test FG Item Variant CF": {variant.name: {}}}
|
||||
parent_bom = create_nested_bom(bom_tree, prefix="")
|
||||
|
||||
plan = create_production_plan(
|
||||
item_code=parent_bom.item,
|
||||
planned_qty=24,
|
||||
ignore_existing_ordered_qty=1,
|
||||
skip_getting_mr_items=1,
|
||||
do_not_submit=1,
|
||||
warehouse="_Test Warehouse - _TC",
|
||||
)
|
||||
plan.for_warehouse = "_Test Warehouse - _TC"
|
||||
|
||||
items = get_items_for_material_requests(
|
||||
plan.as_dict(), warehouses=[{"warehouse": "_Test Warehouse - _TC"}]
|
||||
)
|
||||
|
||||
row = frappe._dict(next(item for item in items if item["item_code"] == variant.name))
|
||||
self.assertEqual(row.conversion_factor, 12)
|
||||
self.assertEqual(row.quantity, 2)
|
||||
|
||||
def test_missing_purchase_uom_conversion_factor_throws(self):
|
||||
from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
|
||||
|
||||
rm = make_item("Test RM Item Missing CF", {"is_stock_item": 1, "stock_uom": "Nos"})
|
||||
rm.purchase_uom = "Box"
|
||||
rm.save()
|
||||
|
||||
bom_tree = {"Test FG Item Missing CF": {rm.name: {}}}
|
||||
parent_bom = create_nested_bom(bom_tree, prefix="")
|
||||
|
||||
plan = create_production_plan(
|
||||
item_code=parent_bom.item,
|
||||
planned_qty=10,
|
||||
ignore_existing_ordered_qty=1,
|
||||
skip_getting_mr_items=1,
|
||||
do_not_submit=1,
|
||||
warehouse="_Test Warehouse - _TC",
|
||||
)
|
||||
plan.for_warehouse = "_Test Warehouse - _TC"
|
||||
|
||||
with self.assertRaises(frappe.ValidationError) as error:
|
||||
get_items_for_material_requests(
|
||||
plan.as_dict(), warehouses=[{"warehouse": "_Test Warehouse - _TC"}]
|
||||
)
|
||||
|
||||
self.assertIn("UOM Conversion factor", str(error.exception))
|
||||
|
||||
def test_mr_qty_for_complex_bom(self):
|
||||
from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
|
||||
Reference in New Issue
Block a user