fix(manufacturing): make Work Order Stock report GROUP BY Postgres-valid

get_item_list() computes build_qty as IfNull(bin.actual_qty * bom.quantity
/ bom_item.stock_qty, 0) while grouping only by bom_item.item_code. The
three operand columns are neither grouped nor aggregated, so MariaDB
arbitrary-picks them but Postgres rejects the query with "column ... must
appear in the GROUP BY clause".

Add bom.quantity, bom_item.stock_qty and bin.actual_qty to the GROUP BY.
The WHERE pins bom/item and the join pins warehouse to single rows (Bin is
unique per item+warehouse), so the result stays one row per item and
MariaDB behaviour is unchanged.

Adds a test (no test file existed) that runs the report against a Work
Order and asserts it is listed, exercising the query 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 04:44:02 +05:30
parent 4255059846
commit e9c391608c
2 changed files with 28 additions and 1 deletions

View File

@@ -0,0 +1,24 @@
# Copyright (c) 2017, Velometro Mobility Inc and contributors
# For license information, please see license.txt
import frappe
from erpnext.tests.utils import ERPNextTestSuite
class TestWorkOrderStockReport(ERPNextTestSuite):
def test_report_executes_and_lists_work_order(self):
# get_item_list computes build_qty by multiplying bin/bom/bom_item columns that are not
# functionally dependent on the grouped item_code; they must be in the GROUP BY for the
# report to run on Postgres. This exercises that query on both engines.
from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
from erpnext.manufacturing.report.work_order_stock_report.work_order_stock_report import execute
wo = make_wo_order_test_record(
production_item="_Test FG Item", qty=1, source_warehouse="_Test Warehouse - _TC"
)
columns, data = execute(frappe._dict(warehouse="_Test Warehouse - _TC"))
self.assertTrue(columns)
self.assertIn(wo.name, {row["work_order"] for row in data})

View File

@@ -47,7 +47,10 @@ def get_item_list(wo_list, filters):
& (bom_item.item_code == wo_item_details.item_code)
& (bom.name == wo_details.bom_no)
)
.groupby(bom_item.item_code)
# build_qty multiplies columns from bin/bom/bom_item that aren't functionally
# dependent on the grouped item_code, so postgres requires them in the GROUP BY.
# The WHERE pins bom, item and warehouse to single rows, so this stays one row.
.groupby(bom_item.item_code, bom.quantity, bom_item.stock_qty, bin.actual_qty)
).run(as_dict=1)
stock_qty = 0