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.
This commit is contained in:
Mihir Kandoi
2026-06-23 07:41:39 +05:30
parent ff737df55f
commit 90ef4f4776
2 changed files with 42 additions and 5 deletions

View File

@@ -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))
)

View File

@@ -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"):