From 8b4845d27283bc44bf612166e3e511d960603957 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Sun, 21 Jun 2026 04:40:33 +0530 Subject: [PATCH 1/2] fix(buying): make Procurement Tracker GROUP BY Postgres-valid get_po_entries() grouped only by (Purchase Order, material_request_item) while selecting other Purchase Order Item columns. MariaDB allows this loose GROUP BY (arbitrary-picking the extra columns); Postgres rejects it with "column ... must appear in the GROUP BY clause". Add the Purchase Order Item PK (child.name) to the GROUP BY so the selected child columns are functionally determined by a grouped key. Behaviour note: this is not a MariaDB no-op. When one PO has multiple items sharing the same/blank material_request_item, MariaDB collapsed them into one arbitrary row; now there is one row per PO line. The downstream report already keys rows by purchase_order, so totals are unaffected and the per-line breakdown is more correct. Adds a test that runs the report and asserts the PO is listed, exercising the GROUP BY on both engines. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../procurement_tracker/procurement_tracker.py | 4 +++- .../test_procurement_tracker.py | 14 +++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/erpnext/buying/report/procurement_tracker/procurement_tracker.py b/erpnext/buying/report/procurement_tracker/procurement_tracker.py index 10169c554fb..fd30ddf6884 100644 --- a/erpnext/buying/report/procurement_tracker/procurement_tracker.py +++ b/erpnext/buying/report/procurement_tracker/procurement_tracker.py @@ -305,7 +305,9 @@ def get_po_entries(filters): & (parent.name == child.parent) & (parent.status.notin(("Closed", "Completed", "Cancelled"))) ) - .groupby(parent.name, child.material_request_item) + # This is one row per PO item; the selected child.* columns are only functionally dependent + # on the child PK, which postgres requires in the GROUP BY (MariaDB allows omitting it). + .groupby(parent.name, child.material_request_item, child.name) ) query = apply_filters_on_query(filters, parent, child, query) diff --git a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py index 1577bf2cbf6..f1781c15e05 100644 --- a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py +++ b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py @@ -6,4 +6,16 @@ from erpnext.tests.utils import ERPNextTestSuite class TestProcurementTracker(ERPNextTestSuite): - pass + def test_report_executes_and_lists_po(self): + # get_po_entries groups by (Purchase Order, material_request_item, Purchase Order Item) + # while selecting other child columns; this exercises that GROUP BY so the report stays + # valid on Postgres (which rejects selecting non-grouped columns). + from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order + from erpnext.buying.report.procurement_tracker.procurement_tracker import execute + + po = create_purchase_order(company="_Test Company") + + columns, data = execute({"company": "_Test Company"}) + + self.assertTrue(columns) + self.assertIn(po.name, {row.get("purchase_order") for row in data}) From 0b4e52e8d78eeeb974479777ebb02df3651ada00 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Sun, 21 Jun 2026 04:40:35 +0530 Subject: [PATCH 2/2] fix(buying): make Purchase Order Analysis GROUP BY Postgres-valid get_data() grouped only by Purchase Order Item while selecting Purchase Order parent columns. MariaDB allows this loose GROUP BY; Postgres rejects it with "column ... must appear in the GROUP BY clause". Add the Purchase Order PK (po.name) to the GROUP BY. po.name is 1:1 with the already-grouped po_item.name, so groups are unchanged and the result is identical on MariaDB. Adds a test (no test file existed) that runs the report and asserts the PO is listed, exercising the GROUP BY on both engines. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../purchase_order_analysis.py | 4 ++- .../test_purchase_order_analysis.py | 28 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 erpnext/buying/report/purchase_order_analysis/test_purchase_order_analysis.py diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py index b6bf1d9f8da..5522aac1044 100644 --- a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py +++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py @@ -71,7 +71,9 @@ def get_data(filters): po_item.name, ) .where((po_item.parent == po.name) & (po.status.notin(("Stopped", "On Hold"))) & (po.docstatus == 1)) - .groupby(po_item.name) + # the selected po.* columns need the Purchase Order PK grouped on postgres; po.name is 1:1 + # with the grouped po_item.name, so groups are unchanged. + .groupby(po_item.name, po.name) .orderby(po.transaction_date) ) diff --git a/erpnext/buying/report/purchase_order_analysis/test_purchase_order_analysis.py b/erpnext/buying/report/purchase_order_analysis/test_purchase_order_analysis.py new file mode 100644 index 00000000000..7b7eda56a05 --- /dev/null +++ b/erpnext/buying/report/purchase_order_analysis/test_purchase_order_analysis.py @@ -0,0 +1,28 @@ +# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from frappe.utils import add_days, nowdate + +from erpnext.tests.utils import ERPNextTestSuite + + +class TestPurchaseOrderAnalysis(ERPNextTestSuite): + def test_report_executes_and_lists_po(self): + # get_data groups by (Purchase Order Item, Purchase Order) while selecting other parent + # columns; this exercises that GROUP BY so the report stays valid on Postgres (which rejects + # selecting non-grouped columns). + from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order + from erpnext.buying.report.purchase_order_analysis.purchase_order_analysis import execute + + po = create_purchase_order(company="_Test Company") + + filters = { + "company": "_Test Company", + "from_date": add_days(nowdate(), -1), + "to_date": add_days(nowdate(), 1), + } + result = execute(filters) + columns, data = result[0], result[1] + + self.assertTrue(columns) + self.assertIn(po.name, {row.get("purchase_order") for row in data})