From 083858d450feb84e778508bb9cacc8adc4660058 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Sun, 21 Jun 2026 05:10:19 +0530 Subject: [PATCH] =?UTF-8?q?refactor(controllers):=20make=20StatusUpdater?= =?UTF-8?q?=20Postgres-valid=20(ifnull=E2=86=92coalesce,=20raw=20SQL?= =?UTF-8?q?=E2=86=92qb)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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) --- erpnext/controllers/status_updater.py | 41 +++++++++++---------------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index 1b0ee5cf6b7..ccff198fd89 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -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