From 0f66c4181923ae8d69e3f639b86455eec0901537 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Thu, 17 Sep 2026 13:56:34 +0530 Subject: [PATCH] fix(buying): take the requested-item UOM pair off one line (#59132) --- .../requested_items_to_order_and_receive.py | 11 ++--- ...st_requested_items_to_order_and_receive.py | 48 +++++++++++++++++++ 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py b/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py index 945fb82b57e..1832fa0b115 100644 --- a/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py +++ b/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py @@ -51,7 +51,6 @@ def get_data(filters): mr_item.item_code.as_("item_code"), Sum(Coalesce(mr_item.qty, 0)).as_("qty"), Sum(Coalesce(mr_item.stock_qty, 0)).as_("stock_qty"), - Max(Coalesce(mr_item.stock_uom, "")).as_("stock_uom"), Sum(Coalesce(mr_item.ordered_qty, 0)).as_("ordered_qty"), Sum(Coalesce(mr_item.received_qty, 0)).as_("received_qty"), (Sum(Coalesce(mr_item.stock_qty, 0)) - Sum(Coalesce(mr_item.received_qty, 0))).as_( @@ -78,19 +77,14 @@ def get_data(filters): def apply_representative_lines(rows): - """Fill item_name/description/uom from one real Material Request Item line per group. - - All three are editable per line, so a request listing the same item twice holds several values - per group. Aggregating them sorts text, and MariaDB folds case while PostgreSQL orders by byte - value, so the engines pick differently. Take the first line by idx. - """ + """Fill the line-level columns from one real Material Request Item line: the first by idx.""" material_requests = list({row.material_request for row in rows}) representative = {} if material_requests: for line in frappe.get_all( "Material Request Item", filters={"parent": ("in", material_requests), "docstatus": 1}, - fields=["parent", "item_code", "item_name", "description", "uom"], + fields=["parent", "item_code", "item_name", "description", "uom", "stock_uom"], order_by="idx", ): representative.setdefault((line.parent, line.item_code), line) @@ -100,6 +94,7 @@ def apply_representative_lines(rows): row.item_name = line.item_name if line else None row.description = line.description if line else None row.uom = line.uom if line else "" + row.stock_uom = line.stock_uom if line else "" def get_conditions(filters, query, mr, mr_item): diff --git a/erpnext/buying/report/requested_items_to_order_and_receive/test_requested_items_to_order_and_receive.py b/erpnext/buying/report/requested_items_to_order_and_receive/test_requested_items_to_order_and_receive.py index efb83e41d0a..f1ca3541139 100644 --- a/erpnext/buying/report/requested_items_to_order_and_receive/test_requested_items_to_order_and_receive.py +++ b/erpnext/buying/report/requested_items_to_order_and_receive/test_requested_items_to_order_and_receive.py @@ -74,6 +74,54 @@ class TestRequestedItemsToOrderAndReceive(ERPNextTestSuite): self.assertEqual(len(data), 1) self.assertEqual(getdate(data[0].required_date), getdate(add_days(today(), 1))) + def test_uom_pair_comes_from_one_line(self): + """uom and stock_uom describe a line, so the reported pair must be one that was posted. + + A request can list the same item twice in different units. Sourcing each column separately + can report one line's uom beside another's stock_uom -- a pair belonging to neither. + """ + create_item("Test MR Report Uom Item") + mr = frappe.copy_doc(self.globalTestRecords["Material Request"][0]) + mr.transaction_date = today() + mr.schedule_date = add_days(today(), 5) + mr.set("items", mr.items[:1]) + row = mr.items[0] + row.item_code = "Test MR Report Uom Item" + row.item_name = "Test MR Report Uom Item" + row.description = "Test MR Report Uom Item" + row.uom = "Nos" + row.schedule_date = mr.schedule_date + mr.append( + "items", + { + "item_code": "Test MR Report Uom Item", + "item_name": "Test MR Report Uom Item", + "description": "Test MR Report Uom Item", + "uom": "Nos", + "qty": row.qty, + "warehouse": row.warehouse, + "schedule_date": mr.schedule_date, + }, + ) + mr.submit() + + # cross the two picks: the line holding the higher uom holds the lower stock_uom, so an + # independently aggregated pair cannot belong to either line + for line, uom, stock_uom in ((mr.items[0], "Nos", "Box"), (mr.items[1], "Box", "Nos")): + frappe.db.set_value( + "Material Request Item", + line.name, + {"uom": uom, "stock_uom": stock_uom}, + update_modified=False, + ) + + posted = {("Nos", "Box"), ("Box", "Nos")} + data = get_data(self.filters.update({"item_code": "Test MR Report Uom Item"})) + + self.assertEqual(len(data), 1) + self.assertIn((data[0].uom, data[0].stock_uom), posted) + self.assertEqual((data[0].uom, data[0].stock_uom), ("Nos", "Box"), "must be the first line by idx") + def setup_material_request(self, order=False, receive=False, days=0): po = None mr = frappe.copy_doc(self.globalTestRecords["Material Request"][0])