fix(manufacturing): make Production Planning report GROUP BY Postgres-valid

get_purchase_details grouped Purchase Order Item by (item_code, warehouse)
while selecting `qty` ungrouped/unaggregated. MariaDB arbitrary-picks one
row's qty; Postgres rejects the query ("must appear in the GROUP BY clause"),
so the report is broken on Postgres.

Sum the qty per item+warehouse ({"SUM": "qty"}). The column is the "Arrival
Qty" (quantity on order arriving) display figure; summing the open PO lines is
the meaningful planning number, and is deterministic vs MariaDB's arbitrary
single-line pick (which only differed when an item+warehouse had multiple open
PO lines).

Adds a test (no test file existed) that creates a Work Order plus two PO lines
for a BOM raw material and asserts the report runs and reports arrival_qty = 7
(3 + 4), on both engines.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mihir Kandoi
2026-06-21 09:35:56 +05:30
parent 59dd3fe84e
commit 469d58d1f4
2 changed files with 51 additions and 1 deletions

View File

@@ -233,7 +233,10 @@ class ProductionPlanReport:
fields=[
"item_code",
{"MIN": "schedule_date", "as": "arrival_date"},
"qty as arrival_qty",
# qty is not in the GROUP BY, so it must be aggregated to be valid on postgres; sum the
# on-order qty per item+warehouse (the meaningful "arriving" figure) instead of MariaDB's
# arbitrary single-row pick.
{"SUM": "qty", "as": "arrival_qty"},
"warehouse",
],
filters={

View File

@@ -0,0 +1,47 @@
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from erpnext.tests.utils import ERPNextTestSuite
class TestProductionPlanningReport(ERPNextTestSuite):
def test_report_runs_and_sums_on_order_qty(self):
# get_purchase_details groups Purchase Order Item by (item_code, warehouse) while summing qty;
# this exercises that GROUP BY on both engines (loose on MariaDB, must be aggregated on Postgres).
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
from erpnext.manufacturing.report.production_planning_report.production_planning_report import execute
wh = "_Test Warehouse - _TC"
wo = make_wo_order_test_record(production_item="_Test FG Item", qty=2, source_warehouse=wh)
self.addCleanup(self._cancel_and_delete, "Work Order", wo.name)
rm = wo.required_items[0].item_code
for qty in (3, 4):
po = create_purchase_order(item_code=rm, warehouse=wh, qty=qty, rate=10)
self.addCleanup(self._cancel_and_delete, "Purchase Order", po.name)
filters = {
"company": "_Test Company",
"based_on": "Work Order",
"docnames": [wo.name],
"raw_material_warehouse": wh,
}
columns, data = execute(filters)
self.assertTrue(columns)
rm_rows = [d for d in data if d.get("item_code") == rm and d.get("arrival_qty")]
self.assertTrue(rm_rows)
# both on-order PO lines (3 + 4) are summed, not arbitrary-picked
self.assertEqual(rm_rows[0]["arrival_qty"], 7)
@staticmethod
def _cancel_and_delete(doctype, name):
import frappe
if not frappe.db.exists(doctype, name):
return
doc = frappe.get_doc(doctype, name)
if doc.docstatus == 1:
doc.cancel()
frappe.delete_doc(doctype, name, force=1)