refactor(controllers): make StatusUpdater Postgres-valid (ifnull→coalesce, raw SQL→qb)

- Replace MySQL-only `ifnull(...)` with `coalesce(...)` in the two
  source/second-source percentage subqueries that remain raw (they
  interpolate dynamic table/field names).
- zero_amount_refdocs: raw `sql_list` → `frappe.get_all(pluck="name")`.
- update_billing_status: two raw `ifnull(sum(qty), 0)` selects → frappe.qb
  `Sum`; an empty result yields None and flt(None) == 0, matching the old
  ifnull behaviour.

Behaviour is unchanged on MariaDB. The percentage path (coalesce subquery)
is exercised by test_selling_controller's Sales Order -> Delivery Note
per_delivered test 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 05:10:19 +05:30
parent f793027800
commit 083858d450

View File

@@ -5,6 +5,7 @@
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.query_builder.functions import Sum
from frappe.utils import comma_or, flt, get_link_to_form, getdate, now, nowdate, safe_div
@@ -554,7 +555,7 @@ class StatusUpdater(Document):
args["second_source_extra_cond"] = ""
args["second_source_condition"] = frappe.db.sql(
""" select ifnull((select sum({second_source_field})
""" select coalesce((select sum({second_source_field})
from `tab{second_source_dt}`
where `{second_join_field}`=%(detail_id)s
and (`tab{second_source_dt}`.docstatus=1)
@@ -569,7 +570,7 @@ class StatusUpdater(Document):
args["source_dt_value"] = (
frappe.db.sql(
"""
(select ifnull(sum({source_field}), 0)
(select coalesce(sum({source_field}), 0)
from `tab{source_dt}` where `{join_field}`=%(detail_id)s
and (docstatus=1 {cond}) {extra_cond})
""".format(**args),
@@ -684,18 +685,10 @@ class StatusUpdater(Document):
if not ref_docs:
return
zero_amount_refdocs = frappe.db.sql_list(
f"""
SELECT
name
from
`tab{ref_dt}`
where
docstatus = 1
and base_net_total = 0
and name in %(ref_docs)s
""",
{"ref_docs": ref_docs},
zero_amount_refdocs = frappe.get_all(
ref_dt,
filters={"docstatus": 1, "base_net_total": 0, "name": ["in", ref_docs]},
pluck="name",
)
if zero_amount_refdocs:
@@ -703,20 +696,20 @@ class StatusUpdater(Document):
def update_billing_status(self, zero_amount_refdoc, ref_dt, ref_fieldname):
for ref_dn in zero_amount_refdoc:
ref_item = frappe.qb.DocType(f"{ref_dt} Item")
ref_doc_qty = flt(
frappe.db.sql(
"""select ifnull(sum(qty), 0) from `tab{} Item`
where parent={}""".format(ref_dt, "%s"),
(ref_dn),
)[0][0]
frappe.qb.from_(ref_item)
.select(Sum(ref_item.qty))
.where(ref_item.parent == ref_dn)
.run()[0][0]
)
doc_item = frappe.qb.DocType(f"{self.doctype} Item")
billed_qty = flt(
frappe.db.sql(
"""select ifnull(sum(qty), 0)
from `tab{} Item` where {}={} and docstatus=1""".format(self.doctype, ref_fieldname, "%s"),
(ref_dn),
)[0][0]
frappe.qb.from_(doc_item)
.select(Sum(doc_item.qty))
.where((doc_item[ref_fieldname] == ref_dn) & (doc_item.docstatus == 1))
.run()[0][0]
)
per_billed = safe_div(min(ref_doc_qty, billed_qty), ref_doc_qty) * 100