From 53b61e5ac663a661fdfa3406bf210e73695d5191 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 19 Aug 2026 12:01:42 +0000 Subject: [PATCH] fix(stock): preserve item UOM conversion factor (backport #58299) (#58304) Co-authored-by: Mihir Kandoi --- erpnext/stock/doctype/item/item.js | 6 ++++++ erpnext/stock/doctype/item/item.py | 3 +++ erpnext/stock/doctype/item/test_item.py | 23 ++++++++++++++++++----- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 909da62a492..67a7c3564dc 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -7,6 +7,12 @@ const SALES_DOCTYPES = ["Quotation", "Sales Order", "Delivery Note", "Sales Invo const PURCHASE_DOCTYPES = ["Purchase Order", "Purchase Receipt", "Purchase Invoice"]; frappe.ui.form.on("Item", { + stock_uom(frm) { + // Each factor is relative to Stock UOM and becomes invalid when it changes. + frm.clear_table("uoms"); + frm.refresh_field("uoms"); + }, + valuation_method(frm) { if (!frm.is_new() && frm.doc.valuation_method === "Moving Average") { let stock_exists = frm.doc.__onload && frm.doc.__onload.stock_exists ? 1 : 0; diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index a11c9b1c6dc..03197ca4114 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -1023,6 +1023,9 @@ class Item(Document): def validate_uom_conversion_factor(self): if self.uoms: for d in self.uoms: + if d.conversion_factor: + continue + value = get_uom_conv_factor(d.uom, self.stock_uom) if value: d.conversion_factor = value diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 221c45e3591..72c5b1465f3 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -749,15 +749,25 @@ class TestItem(ERPNextTestSuite): "Test Item UOM", {"stock_uom": "Gram", "uoms": [dict(uom="Carat"), dict(uom="Kg")]} ) - for d in item_doc.uoms: - value = get_uom_conv_factor(d.uom, item_doc.stock_uom) - d.conversion_factor = value - self.assertEqual(item_doc.uoms[0].uom, "Carat") self.assertEqual(item_doc.uoms[0].conversion_factor, 0.2) self.assertEqual(item_doc.uoms[1].uom, "Kg") self.assertEqual(item_doc.uoms[1].conversion_factor, 1000) + def test_item_uom_conversion_factor_overrides_global_factor(self): + custom_factor = 10.76 + global_factor = get_uom_conv_factor("Square Meter", "Square Foot") + self.assertNotEqual(custom_factor, global_factor) + + item = make_item( + properties={"stock_uom": "Square Foot"}, + uoms=[{"uom": "Square Meter", "conversion_factor": custom_factor}], + ) + item.reload() + + conversion_factor = next(row.conversion_factor for row in item.uoms if row.uom == "Square Meter") + self.assertEqual(conversion_factor, custom_factor) + def test_uom_conv_intermediate(self): factor = get_uom_conv_factor("Pound", "Gram") self.assertAlmostEqual(factor, 453.592, 3) @@ -930,7 +940,10 @@ class TestItem(ERPNextTestSuite): item.reload() item.stock_uom = "Nos" item.save() - self.assertEqual(len(item.uoms), 1) + self.assertEqual( + [(row.uom, row.conversion_factor) for row in item.uoms], + [("Nos", 1)], + ) def test_validate_stock_item(self): self.assertRaises(frappe.ValidationError, validate_is_stock_item, "_Test Non Stock Item")