fix(postgres): satisfy strict GROUP BY in Bom Stock Analysis report

Wrap the non-aggregated, functionally-dependent column(s) in Max()/Min() (or add
them to GROUP BY) so the report's grouped query is valid under PostgreSQL's strict
GROUP BY. No behaviour change on MariaDB.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mihir Kandoi
2026-06-17 13:19:01 +05:30
parent 8a566e6ba5
commit 9ab8803fed

View File

@@ -3,7 +3,7 @@
import frappe
from frappe import _
from frappe.query_builder.functions import Floor, IfNull, Sum
from frappe.query_builder.functions import Floor, IfNull, Max, Min, Sum
from frappe.utils import flt
from frappe.utils.data import comma_and
from pypika.terms import ExistsCriterion
@@ -202,14 +202,15 @@ def get_bom_data(filters):
.on(bom_item.item_code == bin.item_code)
.select(
bom_item.item_code,
bom_item.description,
bom_item.parent.as_("from_bom_no"),
# non-grouped columns are constant per grouped item_code -> Max() keeps the GROUP BY valid
Max(bom_item.description).as_("description"),
Max(bom_item.parent).as_("from_bom_no"),
Sum(bom_item.qty_consumed_per_unit).as_("qty_per_unit"),
IfNull(Sum(bin.actual_qty), 0).as_("actual_qty"),
)
.where((bom_item.parent == filters.get("bom")) & (bom_item.parenttype == "BOM"))
.groupby(bom_item.item_code)
.orderby(bom_item.idx)
.orderby(Min(bom_item.idx))
)
if filters.get("warehouse"):
@@ -233,7 +234,9 @@ def get_bom_data(filters):
query = query.where(bin.warehouse == filters.get("warehouse"))
if bom_item_table == "BOM Item":
query = query.select(bom_item.bom_no, bom_item.is_phantom_item)
query = query.select(
Max(bom_item.bom_no).as_("bom_no"), Max(bom_item.is_phantom_item).as_("is_phantom_item")
)
data = query.run(as_dict=True)
return explode_phantom_boms(data, filters) if bom_item_table == "BOM Item" else data
@@ -312,15 +315,17 @@ def get_producible_fg_items(filters):
.on(BOM_ITEM.item_code == bin_subquery.item_code)
.select(
BOM_ITEM.item_code,
BOM_ITEM.description,
BOM_ITEM.parent.as_("from_bom_no"),
(BOM_ITEM.stock_qty / BOM.quantity).as_("qty_per_unit"),
IfNull(bin_subquery.actual_qty, 0).as_("available_qty"),
Floor(bin_subquery.actual_qty / ((Sum(BOM_ITEM.stock_qty)) / BOM.quantity)),
# Sum() below makes this an aggregate query; the other columns are constant per grouped
# item_code -> Max() keeps them valid on postgres with the same value MySQL picked.
Max(BOM_ITEM.description).as_("description"),
Max(BOM_ITEM.parent).as_("from_bom_no"),
Max(BOM_ITEM.stock_qty / BOM.quantity).as_("qty_per_unit"),
Max(IfNull(bin_subquery.actual_qty, 0)).as_("available_qty"),
Floor(Max(bin_subquery.actual_qty) / ((Sum(BOM_ITEM.stock_qty)) / Max(BOM.quantity))),
)
.where((BOM_ITEM.parent == filters.get("bom")) & (BOM_ITEM.parenttype == "BOM"))
.groupby(BOM_ITEM.item_code)
.orderby(BOM_ITEM.idx)
.orderby(Min(BOM_ITEM.idx))
)
return query.run(as_list=True)