From 90ef4f47767fc83ecf560d7ff2edfc55f50a2436 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Tue, 23 Jun 2026 07:41:39 +0530 Subject: [PATCH] fix(stock): keep supplier-based Material Request picker valid on Postgres get_material_requests_based_on_supplier deduplicated requests with SELECT DISTINCT (name, transaction_date, company) while ordering by mr_item.item_code, which is not in the select list. MariaDB allows this; PostgreSQL rejects it: psycopg2.errors.InvalidColumnReference: for SELECT DISTINCT, ORDER BY expressions must appear in select list so the picker errored out there. Group by the three selected columns (equivalent to the DISTINCT, so the same set of requests is returned) and order by Min(item_code). The order key stays item_code but is now a well-defined aggregate, making the query valid - and the ordering deterministic and identical - on both engines. --- .../material_request/material_request.py | 9 ++--- .../material_request/test_material_request.py | 38 +++++++++++++++++++ 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index a806b5b6a31..4faa24941a0 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -12,7 +12,7 @@ import frappe.defaults from frappe import _, msgprint from frappe.model.document import Document from frappe.query_builder import Order -from frappe.query_builder.functions import Sum +from frappe.query_builder.functions import Min, Sum from frappe.utils import cint, flt, get_datetime, get_link_to_form, getdate, new_line_sep, nowdate from erpnext.buying.utils import check_on_hold_or_closed_status, validate_for_items @@ -483,9 +483,7 @@ def get_material_requests_based_on_supplier( query = ( frappe.qb.from_(mr) .from_(mr_item) - .select(mr.name) - .distinct() - .select(mr.transaction_date, mr.company) + .select(mr.name, mr.transaction_date, mr.company) .where( (mr.name == mr_item.parent) & (mr_item.item_code.isin(supplier_items)) @@ -495,7 +493,8 @@ def get_material_requests_based_on_supplier( & (mr.status != "Stopped") & (mr.company == filters.get("company")) ) - .orderby(mr_item.item_code, order=Order.asc) + .groupby(mr.name, mr.transaction_date, mr.company) + .orderby(Min(mr_item.item_code), order=Order.asc) .limit(cint(page_len)) .offset(cint(start)) ) diff --git a/erpnext/stock/doctype/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py index 0e1c42c0828..c6e63269c1f 100644 --- a/erpnext/stock/doctype/material_request/test_material_request.py +++ b/erpnext/stock/doctype/material_request/test_material_request.py @@ -1252,6 +1252,44 @@ class TestMaterialRequest(ERPNextTestSuite): over.items[0].qty = 4 over.validate_qty_against_so() + def test_get_material_requests_based_on_supplier(self): + """The supplier-based Material Request picker must run on every engine. + + It deduplicated requests with SELECT DISTINCT while ordering by an item + column that is not in the select list; PostgreSQL rejects that, so the + picker has to group and order by an aggregate instead. + """ + from erpnext.stock.doctype.material_request.material_request import ( + get_material_requests_based_on_supplier, + ) + + item = create_item("_Test MR Default Supplier Item") + item.set("item_defaults", []) + item.append( + "item_defaults", + { + "company": "_Test Company", + "default_warehouse": "_Test Warehouse - _TC", + "default_supplier": "_Test Supplier", + }, + ) + item.save() + + mr1 = make_material_request(item_code=item.name, qty=5) + mr2 = make_material_request(item_code=item.name, qty=7) + + result = get_material_requests_based_on_supplier( + doctype="Material Request", + txt="", + searchfield="name", + start=0, + page_len=20, + filters={"supplier": "_Test Supplier", "company": "_Test Company"}, + ) + returned = {row["name"] for row in result} + self.assertIn(mr1.name, returned) + self.assertIn(mr2.name, returned) + def get_in_transit_warehouse(company): if not frappe.db.exists("Warehouse Type", "Transit"):