From b5d727fb81e668a82ad5c562c3c14cfe7273b907 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Tue, 11 Aug 2026 20:41:47 +0530 Subject: [PATCH 1/3] fix(selling): reset stale item details on item change (cherry picked from commit 009961edc73246d972b100b4e63e7c8e214d890d) --- erpnext/controllers/tests/test_reactivity.py | 36 ++++++++++++++++++++ erpnext/utilities/transaction_base.py | 11 ++++++ 2 files changed, 47 insertions(+) diff --git a/erpnext/controllers/tests/test_reactivity.py b/erpnext/controllers/tests/test_reactivity.py index 17f6f480589..a4f652722ae 100644 --- a/erpnext/controllers/tests/test_reactivity.py +++ b/erpnext/controllers/tests/test_reactivity.py @@ -46,3 +46,39 @@ class TestReactivity(ERPNextTestSuite): with self.subTest(field=field): self.assertIsNotNone(itm.get(field[0])) si.save().submit() + + def test_item_change_clears_stale_item_details(self): + from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order + from erpnext.stock.doctype.item.test_item import make_item + + old_item = make_item(properties={"is_stock_item": 0, "stock_uom": "Nos"}) + new_item = make_item( + properties={ + "is_stock_item": 0, + "stock_uom": "Kg", + "weight_per_unit": 2, + "weight_uom": "Kg", + } + ) + sales_order = make_sales_order(item_code=old_item.name, do_not_submit=True) + + item = sales_order.items[0] + self.assertEqual(item.uom, "Nos") + row_state = (item.qty, item.warehouse, item.delivery_date) + + sales_order.ignore_pricing_rule = 1 + item.weight_per_unit = 10 + item.weight_uom = "Nos" + item.barcode = "OLD-BARCODE" + item.pricing_rules = "OLD-PRICING-RULE" + item.item_code = new_item.name + sales_order.process_item_selection(item.idx) + + self.assertEqual(item.uom, "Kg") + self.assertEqual(item.stock_uom, "Kg") + self.assertEqual(item.conversion_factor, 1) + self.assertEqual(item.weight_per_unit, 2) + self.assertEqual(item.weight_uom, "Kg") + self.assertIsNone(item.barcode) + self.assertFalse(item.pricing_rules) + self.assertEqual((item.qty, item.warehouse, item.delivery_date), row_state) diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py index e6cad737a6b..9f1886da98e 100644 --- a/erpnext/utilities/transaction_base.py +++ b/erpnext/utilities/transaction_base.py @@ -357,6 +357,17 @@ class TransactionBase(StatusUpdater): if not item_obj.item_code: return + # Do not carry item-specific values from the previously selected item. + for fieldname in ( + "weight_per_unit", + "weight_uom", + "uom", + "conversion_factor", + "barcode", + "pricing_rules", + ): + item_obj.set(fieldname, None) + # 'item_details' has latest item related values item_details = self.fetch_item_details(item_obj) From 218397e78d276b2e0d55824df36df0ad76bad975 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Tue, 11 Aug 2026 20:54:04 +0530 Subject: [PATCH 2/3] fix(selling): preserve explicit UOM during item selection (cherry picked from commit e8c890a8443f4a8f995486ffba0e42902494453a) # Conflicts: # erpnext/utilities/transaction_base.py --- erpnext/controllers/tests/test_reactivity.py | 24 ++++++++++++++++++- erpnext/public/js/controllers/transaction.js | 1 + erpnext/utilities/transaction_base.py | 25 ++++++++++++-------- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/erpnext/controllers/tests/test_reactivity.py b/erpnext/controllers/tests/test_reactivity.py index a4f652722ae..448025ddddc 100644 --- a/erpnext/controllers/tests/test_reactivity.py +++ b/erpnext/controllers/tests/test_reactivity.py @@ -72,7 +72,7 @@ class TestReactivity(ERPNextTestSuite): item.barcode = "OLD-BARCODE" item.pricing_rules = "OLD-PRICING-RULE" item.item_code = new_item.name - sales_order.process_item_selection(item.idx) + sales_order.process_item_selection(item.idx, reset_item_details=True) self.assertEqual(item.uom, "Kg") self.assertEqual(item.stock_uom, "Kg") @@ -82,3 +82,25 @@ class TestReactivity(ERPNextTestSuite): self.assertIsNone(item.barcode) self.assertFalse(item.pricing_rules) self.assertEqual((item.qty, item.warehouse, item.delivery_date), row_state) + + def test_programmatic_item_selection_preserves_explicit_uom(self): + from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice + from erpnext.stock.doctype.item.test_item import make_item + + item = make_item( + properties={ + "is_stock_item": 0, + "stock_uom": "Kg", + "sales_uom": "Nos", + "weight_per_unit": 2, + "weight_uom": "Kg", + }, + uoms=[{"uom": "Nos", "conversion_factor": 10}], + ) + sales_invoice = create_sales_invoice(item_code=item.name, uom="Kg", do_not_save=True) + + sales_invoice.process_item_selection(sales_invoice.items[0].idx) + + self.assertEqual(sales_invoice.items[0].uom, "Kg") + self.assertEqual(sales_invoice.items[0].conversion_factor, 1) + self.assertEqual(sales_invoice.items[0].stock_qty, sales_invoice.items[0].qty) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 9d64ef09dd0..d6e3a308bf5 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -761,6 +761,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe method: "process_item_selection", args: { item_idx: item.idx, + reset_item_details: true, }, callback: function (r) { if (!r.exc) { diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py index 9f1886da98e..7efc43936e7 100644 --- a/erpnext/utilities/transaction_base.py +++ b/erpnext/utilities/transaction_base.py @@ -350,23 +350,28 @@ class TransactionBase(StatusUpdater): ) @frappe.whitelist() +<<<<<<< HEAD def process_item_selection(self, item_idx): +======= + def process_item_selection(self, item_idx: int, reset_item_details: bool = False): +>>>>>>> e8c890a844 (fix(selling): preserve explicit UOM during item selection) # Server side 'item' doc. Update this to reflect in UI item_obj = self.get("items", {"idx": item_idx})[0] if not item_obj.item_code: return - # Do not carry item-specific values from the previously selected item. - for fieldname in ( - "weight_per_unit", - "weight_uom", - "uom", - "conversion_factor", - "barcode", - "pricing_rules", - ): - item_obj.set(fieldname, None) + if cint(reset_item_details): + # Do not carry item-specific values from the previously selected item. + for fieldname in ( + "weight_per_unit", + "weight_uom", + "uom", + "conversion_factor", + "barcode", + "pricing_rules", + ): + item_obj.set(fieldname, None) # 'item_details' has latest item related values item_details = self.fetch_item_details(item_obj) From bc6cb75bf4be6cfe44f1e27a3d9b64a00a7f4ca4 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Tue, 11 Aug 2026 21:19:00 +0530 Subject: [PATCH 3/3] chore: resolve conflict --- erpnext/utilities/transaction_base.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py index 7efc43936e7..97dbcfaa4d6 100644 --- a/erpnext/utilities/transaction_base.py +++ b/erpnext/utilities/transaction_base.py @@ -350,11 +350,7 @@ class TransactionBase(StatusUpdater): ) @frappe.whitelist() -<<<<<<< HEAD - def process_item_selection(self, item_idx): -======= def process_item_selection(self, item_idx: int, reset_item_details: bool = False): ->>>>>>> e8c890a844 (fix(selling): preserve explicit UOM during item selection) # Server side 'item' doc. Update this to reflect in UI item_obj = self.get("items", {"idx": item_idx})[0]