fix(controllers): make budget requested-amount aggregate Postgres-valid

The Material Request requested-amount query selects
`Sum(stock_qty - ordered_qty) * mri.rate` -- an implicit aggregate with no
GROUP BY, where mri.rate is neither grouped nor aggregated. MariaDB
arbitrary-picks the rate; Postgres rejects it ("must appear in the GROUP BY
clause"). Wrap the rate in Max(mri.rate) so the SELECT is a pure aggregate.

Behaviour note: for matched MR items with differing rates, Max() picks the
highest (vs MariaDB's arbitrary single rate). The underlying Sum(qty) * rate
is a pre-existing single-rate aggregation; this preserves it under the
accepted arbitrary-pick convention.

Covered by erpnext.accounts.doctype.budget.test_budget
.test_monthly_budget_crossed_for_mr, which now passes on Postgres (it errors
on develop) and is unchanged on MariaDB.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mihir Kandoi
2026-06-21 05:28:11 +05:30
parent 4255059846
commit f95e32a581

View File

@@ -3,7 +3,7 @@ from collections import OrderedDict
import frappe
from frappe import _, qb
from frappe.query_builder import Criterion
from frappe.query_builder.functions import IfNull, Sum
from frappe.query_builder.functions import IfNull, Max, Sum
from frappe.utils import fmt_money
from erpnext.accounts.doctype.budget.budget import BudgetError, get_accumulated_monthly_budget
@@ -260,7 +260,11 @@ class BudgetValidation:
qb.from_(mr)
.inner_join(mri)
.on(mr.name == mri.parent)
.select((Sum(IfNull(mri.stock_qty, 0) - IfNull(mri.ordered_qty, 0)) * mri.rate).as_("amount"))
# rate is outside the Sum (no GROUP BY -> implicit aggregate); Max() keeps it valid on
# postgres and matches MySQL's arbitrary single-rate choice for this aggregate.
.select(
(Sum(IfNull(mri.stock_qty, 0) - IfNull(mri.ordered_qty, 0)) * Max(mri.rate)).as_("amount")
)
.where(Criterion.all(conditions))
.run(as_dict=True)
):