mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-13 14:41:53 +00:00
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:
@@ -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={
|
||||
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user