From 09beed9cc380c3b65cdd3ca961c6020328701f49 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 17 Jun 2026 18:37:57 +0530 Subject: [PATCH 1/7] refactor(postgres): port Payment Order doctype queries to the query builder Co-Authored-By: Claude Opus 4.8 (1M context) --- .../doctype/payment_order/payment_order.py | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/doctype/payment_order/payment_order.py b/erpnext/accounts/doctype/payment_order/payment_order.py index 5f1651a9f7c..d75bce51a4f 100644 --- a/erpnext/accounts/doctype/payment_order/payment_order.py +++ b/erpnext/accounts/doctype/payment_order/payment_order.py @@ -60,23 +60,32 @@ class PaymentOrder(Document): @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def get_mop_query(doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict): - return frappe.db.sql( - """ select mode_of_payment from `tabPayment Order Reference` - where parent = %(parent)s and mode_of_payment like %(txt)s - limit %(page_len)s offset %(start)s""", - {"parent": filters.get("parent"), "start": start, "page_len": page_len, "txt": "%%%s%%" % txt}, + return frappe.get_all( + "Payment Order Reference", + filters={"parent": filters.get("parent"), "mode_of_payment": ["like", f"%{txt}%"]}, + fields=["mode_of_payment"], + limit_start=start, + limit_page_length=page_len, + order_by="", # match the original query (no ORDER BY); avoid get_all's default sort + as_list=True, ) @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def get_supplier_query(doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict): - return frappe.db.sql( - """ select supplier from `tabPayment Order Reference` - where parent = %(parent)s and supplier like %(txt)s and - (payment_reference is null or payment_reference='') - limit %(page_len)s offset %(start)s""", - {"parent": filters.get("parent"), "start": start, "page_len": page_len, "txt": "%%%s%%" % txt}, + return frappe.get_all( + "Payment Order Reference", + filters={ + "parent": filters.get("parent"), + "supplier": ["like", f"%{txt}%"], + "payment_reference": ["is", "not set"], + }, + fields=["supplier"], + limit_start=start, + limit_page_length=page_len, + order_by="", # match the original query (no ORDER BY); avoid get_all's default sort + as_list=True, ) From 4d03e915f765733dc236cd3bcd8b834a40a66332 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 17 Jun 2026 18:37:58 +0530 Subject: [PATCH 2/7] refactor(postgres): port Bank Transaction doctype queries to the query builder Co-Authored-By: Claude Opus 4.8 (1M context) --- .../bank_transaction/bank_transaction.py | 93 ++++++++++--------- 1 file changed, 47 insertions(+), 46 deletions(-) diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py index 0c36e7e5297..3b3fc16c0cb 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py @@ -5,6 +5,8 @@ import frappe from frappe import _ from frappe.model.docstatus import DocStatus from frappe.model.document import Document +from frappe.query_builder import Tuple +from frappe.query_builder.functions import Abs, Max, Sum from frappe.utils import flt, getdate @@ -478,30 +480,28 @@ def get_clearance_details(transaction, payment_entry, bt_allocations, gl_entries def get_related_bank_gl_entries(docs): - # nosemgrep: frappe-semgrep-rules.rules.frappe-using-db-sql if not docs: return {} - result = frappe.db.sql( - """ - SELECT - gle.voucher_type AS doctype, - gle.voucher_no AS docname, - gle.account AS gl_account, - SUM(ABS(gle.credit_in_account_currency - gle.debit_in_account_currency)) AS amount - FROM - `tabGL Entry` gle - LEFT JOIN - `tabAccount` ac ON ac.name = gle.account - WHERE - ac.account_type = 'Bank' - AND (gle.voucher_type, gle.voucher_no) IN %(docs)s - AND gle.is_cancelled = 0 - GROUP BY - gle.voucher_type, gle.voucher_no, gle.account - """, - {"docs": docs}, - as_dict=True, + gle = frappe.qb.DocType("GL Entry") + ac = frappe.qb.DocType("Account") + result = ( + frappe.qb.from_(gle) + .left_join(ac) + .on(ac.name == gle.account) + .select( + gle.voucher_type.as_("doctype"), + gle.voucher_no.as_("docname"), + gle.account.as_("gl_account"), + Sum(Abs(gle.credit_in_account_currency - gle.debit_in_account_currency)).as_("amount"), + ) + .where( + (ac.account_type == "Bank") + & Tuple(gle.voucher_type, gle.voucher_no).isin([Tuple(vt, vn) for vt, vn in docs]) + & (gle.is_cancelled == 0) + ) + .groupby(gle.voucher_type, gle.voucher_no, gle.account) + .run(as_dict=True) ) entries = {} @@ -523,31 +523,32 @@ def get_total_allocated_amount(docs): if not docs: return {} - # nosemgrep: frappe-semgrep-rules.rules.frappe-using-db-sql - result = frappe.db.sql( - """ - SELECT total, latest_date, gl_account, payment_document, payment_entry FROM ( - SELECT - ROW_NUMBER() OVER w AS rownum, - SUM(btp.allocated_amount) OVER(PARTITION BY ba.account, btp.payment_document, btp.payment_entry) AS total, - FIRST_VALUE(bt.date) OVER w AS latest_date, - ba.account AS gl_account, - btp.payment_document, - btp.payment_entry - FROM - `tabBank Transaction Payments` btp - LEFT JOIN `tabBank Transaction` bt ON bt.name=btp.parent - LEFT JOIN `tabBank Account` ba ON ba.name=bt.bank_account - WHERE - (btp.payment_document, btp.payment_entry) IN %(docs)s - AND bt.docstatus = 1 - WINDOW w AS (PARTITION BY ba.account, btp.payment_document, btp.payment_entry ORDER BY bt.date DESC) - ) temp - WHERE - rownum = 1 - """, - dict(docs=docs), - as_dict=True, + # The original window query (ROW_NUMBER/FIRST_VALUE + rownum = 1) just collapses to one + # row per (account, payment_document, payment_entry) with the partition's allocation total + # and most recent transaction date — i.e. a plain GROUP BY with SUM and MAX. + btp = frappe.qb.DocType("Bank Transaction Payments") + bt = frappe.qb.DocType("Bank Transaction") + ba = frappe.qb.DocType("Bank Account") + + result = ( + frappe.qb.from_(btp) + .left_join(bt) + .on(bt.name == btp.parent) + .left_join(ba) + .on(ba.name == bt.bank_account) + .select( + Sum(btp.allocated_amount).as_("total"), + Max(bt.date).as_("latest_date"), + ba.account.as_("gl_account"), + btp.payment_document, + btp.payment_entry, + ) + .where( + Tuple(btp.payment_document, btp.payment_entry).isin([Tuple(pd, pe) for pd, pe in docs]) + & (bt.docstatus == 1) + ) + .groupby(ba.account, btp.payment_document, btp.payment_entry) + .run(as_dict=True) ) payment_allocation_details = {} From c0d2bd7bcee02ee6d866f28e5b7ba11a519fdb9e Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 17 Jun 2026 18:37:59 +0530 Subject: [PATCH 3/7] refactor(postgres): port Invoice Discounting doctype queries to the query builder Co-Authored-By: Claude Opus 4.8 (1M context) --- .../invoice_discounting.py | 78 +++++++++---------- 1 file changed, 35 insertions(+), 43 deletions(-) diff --git a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py index 0eb90b139b9..3e2f18e1f41 100644 --- a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py +++ b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py @@ -319,56 +319,48 @@ class InvoiceDiscounting(AccountsController): @frappe.whitelist() def get_invoices(filters: str): filters = frappe._dict(json.loads(filters)) - cond = [] - if filters.customer: - cond.append("customer=%(customer)s") - if filters.from_date: - cond.append("posting_date >= %(from_date)s") - if filters.to_date: - cond.append("posting_date <= %(to_date)s") - if filters.min_amount: - cond.append("base_grand_total >= %(min_amount)s") - if filters.max_amount: - cond.append("base_grand_total <= %(max_amount)s") + si = frappe.qb.DocType("Sales Invoice") + di = frappe.qb.DocType("Discounted Invoice") - where_condition = "" - if cond: - where_condition += " and " + " and ".join(cond) + discounted = frappe.qb.from_(di).select(di.sales_invoice).where(di.docstatus == 1) - return frappe.db.sql( - """ - select - name as sales_invoice, - customer, - posting_date, - outstanding_amount, - debit_to - from `tabSales Invoice` si - where - docstatus = 1 - and outstanding_amount > 0 - %s - and not exists(select di.name from `tabDiscounted Invoice` di - where di.docstatus=1 and di.sales_invoice=si.name) - """ - % where_condition, - filters, - as_dict=1, + query = ( + frappe.qb.from_(si) + .select( + si.name.as_("sales_invoice"), + si.customer, + si.posting_date, + si.outstanding_amount, + si.debit_to, + ) + .where((si.docstatus == 1) & (si.outstanding_amount > 0) & si.name.notin(discounted)) ) + if filters.customer: + query = query.where(si.customer == filters.customer) + if filters.from_date: + query = query.where(si.posting_date >= filters.from_date) + if filters.to_date: + query = query.where(si.posting_date <= filters.to_date) + if filters.min_amount: + query = query.where(si.base_grand_total >= filters.min_amount) + if filters.max_amount: + query = query.where(si.base_grand_total <= filters.max_amount) + + return query.run(as_dict=1) + def get_party_account_based_on_invoice_discounting(sales_invoice): party_account = None - invoice_discounting = frappe.db.sql( - """ - select par.accounts_receivable_discounted, par.accounts_receivable_unpaid, par.status - from `tabInvoice Discounting` par, `tabDiscounted Invoice` ch - where par.name=ch.parent - and par.docstatus=1 - and ch.sales_invoice = %s - """, - (sales_invoice), - as_dict=1, + par = frappe.qb.DocType("Invoice Discounting") + ch = frappe.qb.DocType("Discounted Invoice") + invoice_discounting = ( + frappe.qb.from_(par) + .inner_join(ch) + .on(par.name == ch.parent) + .select(par.accounts_receivable_discounted, par.accounts_receivable_unpaid, par.status) + .where((par.docstatus == 1) & (ch.sales_invoice == sales_invoice)) + .run(as_dict=1) ) if invoice_discounting: if invoice_discounting[0].status == "Disbursed": From ac26c01e52f575b1fd3eae78b9ecc16f5e2411ef Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 17 Jun 2026 18:38:00 +0530 Subject: [PATCH 4/7] refactor(postgres): port Cashier Closing doctype queries to the query builder Co-Authored-By: Claude Opus 4.8 (1M context) --- .../cashier_closing/cashier_closing.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/cashier_closing/cashier_closing.py b/erpnext/accounts/doctype/cashier_closing/cashier_closing.py index 6ab94c5c51b..e89f2830223 100644 --- a/erpnext/accounts/doctype/cashier_closing/cashier_closing.py +++ b/erpnext/accounts/doctype/cashier_closing/cashier_closing.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 flt @@ -43,13 +44,17 @@ class CashierClosing(Document): self.make_calculations() def get_outstanding(self): - values = frappe.db.sql( - """ - select sum(outstanding_amount) - from `tabSales Invoice` - where posting_date=%s and posting_time>=%s and posting_time<=%s and owner=%s - """, - (self.date, self.from_time, self.time, self.user), + si = frappe.qb.DocType("Sales Invoice") + values = ( + frappe.qb.from_(si) + .select(Sum(si.outstanding_amount)) + .where( + (si.posting_date == self.date) + & (si.posting_time >= self.from_time) + & (si.posting_time <= self.time) + & (si.owner == self.user) + ) + .run() ) self.outstanding_amount = flt(values[0][0] if values else 0) From 588dfac4cd51f2b684462cc5cbaed0ce1bd44c2c Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 17 Jun 2026 18:38:00 +0530 Subject: [PATCH 5/7] refactor(postgres): port Mode of Payment doctype queries to the query builder Co-Authored-By: Claude Opus 4.8 (1M context) --- .../accounts/doctype/mode_of_payment/mode_of_payment.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.py b/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.py index 6fc9eba4c3f..388bccac844 100644 --- a/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.py +++ b/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.py @@ -52,12 +52,11 @@ class ModeofPayment(Document): def validate_pos_mode_of_payment(self): if not self.enabled: - pos_profiles = frappe.db.sql( - """SELECT sip.parent FROM `tabSales Invoice Payment` sip - WHERE sip.parenttype = 'POS Profile' and sip.mode_of_payment = %s""", - (self.name), + pos_profiles = frappe.get_all( + "Sales Invoice Payment", + filters={"parenttype": "POS Profile", "mode_of_payment": self.name}, + pluck="parent", ) - pos_profiles = list(map(lambda x: x[0], pos_profiles)) if pos_profiles: message = _( From d1e167815f729ec5a3c23d2eb7f9f96a02b2310d Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 17 Jun 2026 18:38:01 +0530 Subject: [PATCH 6/7] refactor(postgres): port Payment Entry doctype queries to the query builder 3-way merged onto develop, preserving develop's set_exchange_rate(ref_doc=doc) change. One portable raw query is intentionally kept (as on the source branch). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../doctype/payment_entry/payment_entry.py | 193 ++++++++---------- 1 file changed, 85 insertions(+), 108 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 2b5f40e149b..369a5143082 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -9,8 +9,8 @@ import frappe from frappe import ValidationError, _, qb, scrub, throw from frappe.model.document import Document from frappe.model.meta import get_field_precision -from frappe.query_builder import Tuple -from frappe.query_builder.functions import Count +from frappe.query_builder import Case, Tuple +from frappe.query_builder.functions import Abs, Count, Max from frappe.utils import cint, comma_or, flt, getdate, nowdate from frappe.utils.data import comma_and, fmt_money, get_link_to_form from pypika.functions import Coalesce, Sum @@ -766,13 +766,19 @@ class PaymentEntry(AccountsController): def validate_journal_entry(self): for d in self.get("references"): if d.allocated_amount and d.reference_doctype == "Journal Entry": - je_accounts = frappe.db.sql( - """select debit, credit from `tabJournal Entry Account` - where account = %s and party=%s and docstatus = 1 and parent = %s - and (reference_type is null or reference_type in ("", "Sales Order", "Purchase Order")) - """, - (self.party_account, self.party, d.reference_name), - as_dict=True, + je_accounts = frappe.get_all( + "Journal Entry Account", + filters={ + "account": self.party_account, + "party": self.party, + "docstatus": 1, + "parent": d.reference_name, + }, + or_filters=[ + ["reference_type", "is", "not set"], + ["reference_type", "in", ["Sales Order", "Purchase Order"]], + ], + fields=["debit", "credit"], ) if not je_accounts: @@ -857,27 +863,17 @@ class PaymentEntry(AccountsController): ) base_outstanding = flt(allocated_amount * conversion_rate, base_outstanding_precision) + ps = frappe.qb.DocType("Payment Schedule") if cancel: - frappe.db.sql( - """ - UPDATE `tabPayment Schedule` - SET - paid_amount = `paid_amount` - %s, - base_paid_amount = `base_paid_amount` - %s, - discounted_amount = `discounted_amount` - %s, - outstanding = `outstanding` + %s, - base_outstanding = `base_outstanding` - %s - WHERE parent = %s and payment_term = %s""", - ( - allocated_amount - discounted_amt, - base_paid_amount, - discounted_amt, - allocated_amount, - base_outstanding, - key[1], - key[0], - ), - ) + ( + frappe.qb.update(ps) + .set(ps.paid_amount, ps.paid_amount - (allocated_amount - discounted_amt)) + .set(ps.base_paid_amount, ps.base_paid_amount - base_paid_amount) + .set(ps.discounted_amount, ps.discounted_amount - discounted_amt) + .set(ps.outstanding, ps.outstanding + allocated_amount) + .set(ps.base_outstanding, ps.base_outstanding - base_outstanding) + .where((ps.parent == key[1]) & (ps.payment_term == key[0])) + ).run() else: if allocated_amount > outstanding: frappe.throw( @@ -887,26 +883,15 @@ class PaymentEntry(AccountsController): ) if allocated_amount and outstanding: - frappe.db.sql( - """ - UPDATE `tabPayment Schedule` - SET - paid_amount = `paid_amount` + %s, - base_paid_amount = `base_paid_amount` + %s, - discounted_amount = `discounted_amount` + %s, - outstanding = `outstanding` - %s, - base_outstanding = `base_outstanding` - %s - WHERE parent = %s and payment_term = %s""", - ( - allocated_amount - discounted_amt, - base_paid_amount, - discounted_amt, - allocated_amount, - base_outstanding, - key[1], - key[0], - ), - ) + ( + frappe.qb.update(ps) + .set(ps.paid_amount, ps.paid_amount + (allocated_amount - discounted_amt)) + .set(ps.base_paid_amount, ps.base_paid_amount + base_paid_amount) + .set(ps.discounted_amount, ps.discounted_amount + discounted_amt) + .set(ps.outstanding, ps.outstanding - allocated_amount) + .set(ps.base_outstanding, ps.base_outstanding - base_outstanding) + .where((ps.parent == key[1]) & (ps.payment_term == key[0])) + ).run() def get_allocated_amount_in_transaction_currency( self, allocated_amount, reference_doctype, reference_docname @@ -1216,11 +1201,7 @@ class PaymentEntry(AccountsController): # Clear the reference document which doesn't have allocated amount on validate so that form can be loaded fast def clear_unallocated_reference_document_rows(self): self.set("references", self.get("references", {"allocated_amount": ["not in", [0, None, ""]]})) - frappe.db.sql( - """delete from `tabPayment Entry Reference` - where parent = %s and allocated_amount = 0""", - self.name, - ) + frappe.db.delete("Payment Entry Reference", {"parent": self.name, "allocated_amount": 0}) def set_title(self): if frappe.flags.in_import and self.title: @@ -1876,7 +1857,7 @@ def get_matched_payment_request_of_references(references=None): PR.reference_doctype, PR.reference_name, PR.outstanding_amount.as_("allocated_amount"), - PR.name.as_("payment_request"), + Max(PR.name).as_("payment_request"), # count == 1 below ⇒ one row per group; postgres-safe Count("*").as_("count"), ) .where(Tuple(PR.reference_doctype, PR.reference_name, PR.outstanding_amount).isin(refs)) @@ -2315,12 +2296,7 @@ def get_orders_to_be_billed( if not voucher_type: return [] - # dynamic dimension filters - condition = "" active_dimensions = get_dimensions(True)[0] - for dim in active_dimensions: - if filters.get(dim.fieldname): - condition += f" and {dim.fieldname}={frappe.db.escape(filters.get(dim.fieldname))}" if party_account_currency == company_currency: grand_total_field = "base_grand_total" @@ -2329,38 +2305,38 @@ def get_orders_to_be_billed( grand_total_field = "grand_total" rounded_total_field = "rounded_total" - orders = frappe.db.sql( - """ - select - name as voucher_no, - if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) as invoice_amount, - (if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) - advance_paid) as outstanding_amount, - transaction_date as posting_date - from - `tab{voucher_type}` - where - {party_type} = %s - and docstatus = 1 - and company = %s - and status != "Closed" - and if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) > advance_paid - and abs(100 - per_billed) > 0.01 - {condition} - order by - transaction_date, name - """.format( - **{ - "rounded_total_field": rounded_total_field, - "grand_total_field": grand_total_field, - "voucher_type": voucher_type, - "party_type": scrub(party_type), - "condition": condition, - } - ), - (party, company), - as_dict=True, + voucher = frappe.qb.DocType(voucher_type) + invoice_amount = ( + Case() + .when(voucher[rounded_total_field] != 0, voucher[rounded_total_field]) + .else_(voucher[grand_total_field]) ) + query = ( + frappe.qb.from_(voucher) + .select( + voucher.name.as_("voucher_no"), + invoice_amount.as_("invoice_amount"), + (invoice_amount - voucher.advance_paid).as_("outstanding_amount"), + voucher.transaction_date.as_("posting_date"), + ) + .where( + (voucher[scrub(party_type)] == party) + & (voucher.docstatus == 1) + & (voucher.company == company) + & (voucher.status != "Closed") + & (invoice_amount > voucher.advance_paid) + & (Abs(100 - voucher.per_billed) > 0.01) + ) + ) + + # dynamic dimension filters + for dim in active_dimensions: + if filters.get(dim.fieldname): + query = query.where(voucher[dim.fieldname] == filters.get(dim.fieldname)) + + orders = query.orderby(voucher.transaction_date).orderby(voucher.name).run(as_dict=True) + order_list = [] for d in orders: if ( @@ -2409,8 +2385,8 @@ def get_negative_outstanding_invoices( return frappe.db.sql( """ select - "{voucher_type}" as voucher_type, name as voucher_no, {account} as account, - if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) as invoice_amount, + '{voucher_type}' as voucher_type, name as voucher_no, {account} as account, + coalesce(nullif({rounded_total_field}, 0), {grand_total_field}) as invoice_amount, outstanding_amount, posting_date, due_date, conversion_rate as exchange_rate from @@ -3272,27 +3248,28 @@ def get_reference_as_per_payment_terms( def get_paid_amount(dt, dn, party_type, party, account, due_date): + gle = frappe.qb.DocType("GL Entry") if party_type == "Customer": - dr_or_cr = "credit_in_account_currency - debit_in_account_currency" + dr_or_cr = gle.credit_in_account_currency - gle.debit_in_account_currency else: - dr_or_cr = "debit_in_account_currency - credit_in_account_currency" + dr_or_cr = gle.debit_in_account_currency - gle.credit_in_account_currency - paid_amount = frappe.db.sql( - f""" - select ifnull(sum({dr_or_cr}), 0) as paid_amount - from `tabGL Entry` - where against_voucher_type = %s - and against_voucher = %s - and party_type = %s - and party = %s - and account = %s - and due_date = %s - and {dr_or_cr} > 0 - """, - (dt, dn, party_type, party, account, due_date), + paid_amount = ( + frappe.qb.from_(gle) + .select(Sum(dr_or_cr)) + .where( + (gle.against_voucher_type == dt) + & (gle.against_voucher == dn) + & (gle.party_type == party_type) + & (gle.party == party) + & (gle.account == account) + & (gle.due_date == due_date) + & (dr_or_cr > 0) + ) + .run() ) - return paid_amount[0][0] if paid_amount else 0 + return (paid_amount[0][0] or 0) if paid_amount else 0 @frappe.whitelist() From acda04a4bd25eb794d72bc74133173accf89216c Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 17 Jun 2026 18:38:02 +0530 Subject: [PATCH 7/7] refactor(postgres): port Payment Request doctype queries to the query builder 3-way merged onto develop (preserving the get_party_bank_account import move). get_subscription_details passes order_by="" so get_all does not inject the doctype default sort the raw query never had. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../doctype/payment_request/payment_request.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index 69dada00561..93faa06a1a2 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -629,11 +629,9 @@ class PaymentRequest(Document): def check_if_payment_entry_exists(self): if self.status == "Paid": - if frappe.get_all( + if frappe.db.exists( "Payment Entry Reference", - filters={"reference_name": self.reference_name, "docstatus": ["<", 2]}, - fields=["parent"], - limit=1, + {"reference_name": self.reference_name, "docstatus": ["<", 2]}, ): frappe.throw(_("Payment Entry already exists"), title=_("Error")) @@ -1212,10 +1210,11 @@ def get_dummy_message(doc): @frappe.whitelist() def get_subscription_details(reference_doctype: str, reference_name: str): if reference_doctype == "Sales Invoice": - subscriptions = frappe.db.sql( - """SELECT parent as sub_name FROM `tabSubscription Invoice` WHERE invoice=%s""", - reference_name, - as_dict=1, + subscriptions = frappe.get_all( + "Subscription Invoice", + filters={"invoice": reference_name}, + fields=["parent as sub_name"], + order_by="", # match the original query (no ORDER BY); avoid get_all's default sort ) subscription_plans = [] for subscription in subscriptions: