From 37a6ebd4317fb36e0b9ef1ae2bc944e3cb868bfd Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 17 Jun 2026 19:01:33 +0530 Subject: [PATCH 01/10] refactor(postgres): port POS Profile doctype queries to the query builder Co-Authored-By: Claude Opus 4.8 (1M context) --- .../doctype/pos_profile/pos_profile.py | 85 ++++++++++--------- 1 file changed, 43 insertions(+), 42 deletions(-) diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.py b/erpnext/accounts/doctype/pos_profile/pos_profile.py index 3b7801bf11e..e9676ac917e 100644 --- a/erpnext/accounts/doctype/pos_profile/pos_profile.py +++ b/erpnext/accounts/doctype/pos_profile/pos_profile.py @@ -118,14 +118,21 @@ class POSProfile(Document): def validate_default_profile(self): for row in self.applicable_for_users: - res = frappe.db.sql( - """select pf.name - from - `tabPOS Profile User` pfu, `tabPOS Profile` pf - where - pf.name = pfu.parent and pfu.user = %s and pf.name != %s and pf.company = %s - and pfu.default=1 and pf.disabled = 0""", - (row.user, self.name, self.company), + pfu = frappe.qb.DocType("POS Profile User") + pf = frappe.qb.DocType("POS Profile") + res = ( + frappe.qb.from_(pfu) + .inner_join(pf) + .on(pf.name == pfu.parent) + .select(pf.name) + .where( + (pfu.user == row.user) + & (pf.name != self.name) + & (pf.company == self.company) + & (pfu.default == 1) + & (pf.disabled == 0) + ) + .run() ) if row.default and res: @@ -265,10 +272,11 @@ def get_permitted_nodes(group_type): def get_child_nodes(group_type, root): lft, rgt = frappe.db.get_value(group_type, root, ["lft", "rgt"]) - return frappe.db.sql( - f""" Select name, lft, rgt from `tab{group_type}` where - lft >= {lft} and rgt <= {rgt} order by lft""", - as_dict=1, + return frappe.get_all( + group_type, + filters={"lft": [">=", lft], "rgt": ["<=", rgt]}, + fields=["name", "lft", "rgt"], + order_by="lft", ) @@ -278,40 +286,33 @@ def pos_profile_query(doctype: str, txt: str, searchfield: str, start: int, page user = frappe.session["user"] company = filters.get("company") or frappe.defaults.get_user_default("company") - args = { - "user": user, - "start": start, - "company": company, - "page_len": page_len, - "txt": "%%%s%%" % txt, - } + pf = frappe.qb.DocType("POS Profile") + pfu = frappe.qb.DocType("POS Profile User") - pos_profile = frappe.db.sql( - """select pf.name - from - `tabPOS Profile` pf, `tabPOS Profile User` pfu - where - pfu.parent = pf.name and pfu.user = %(user)s and pf.company = %(company)s - and (pf.name like %(txt)s) - and pf.disabled = 0 limit %(page_len)s offset %(start)s""", - args, + pos_profile = ( + frappe.qb.from_(pf) + .inner_join(pfu) + .on(pfu.parent == pf.name) + .select(pf.name) + .where((pfu.user == user) & (pf.company == company) & pf.name.like(f"%{txt}%") & (pf.disabled == 0)) + .limit(page_len) + .offset(start) + .run() ) if not pos_profile: - del args["user"] - - pos_profile = frappe.db.sql( - """select pf.name - from - `tabPOS Profile` pf left join `tabPOS Profile User` pfu - on - pf.name = pfu.parent - where - ifnull(pfu.user, '') = '' - and pf.company = %(company)s - and pf.name like %(txt)s - and pf.disabled = 0""", - args, + pos_profile = ( + frappe.qb.from_(pf) + .left_join(pfu) + .on(pf.name == pfu.parent) + .select(pf.name) + .where( + (pfu.user.isnull() | (pfu.user == "")) + & (pf.company == company) + & pf.name.like(f"%{txt}%") + & (pf.disabled == 0) + ) + .run() ) return pos_profile From 42c61915c4cd05c56bc00d17305e6c8776c917ee Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 17 Jun 2026 19:01:33 +0530 Subject: [PATCH 02/10] refactor(postgres): port POS Invoice doctype queries to the query builder Co-Authored-By: Claude Opus 4.8 (1M context) --- .../doctype/pos_invoice/pos_invoice.py | 33 +++++++------------ 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index b10f9686789..a51b27f4b71 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -505,19 +505,16 @@ class POSInvoice(SalesInvoice): if d.get("serial_no"): serial_nos = get_serial_nos(d.serial_no) for sr in serial_nos: - serial_no_exists = frappe.db.sql( - """ - SELECT name - FROM `tabPOS Invoice Item` - WHERE - parent = %s - and (serial_no = %s - or serial_no like %s - or serial_no like %s - or serial_no like %s - ) - """, - (self.return_against, sr, sr + "\n%", "%\n" + sr, "%\n" + sr + "\n%"), + serial_no_exists = frappe.get_all( + "POS Invoice Item", + filters={"parent": self.return_against}, + or_filters=[ + ["serial_no", "=", sr], + ["serial_no", "like", f"{sr}\n%"], + ["serial_no", "like", f"%\n{sr}"], + ["serial_no", "like", f"%\n{sr}\n%"], + ], + limit=1, ) if not serial_no_exists: @@ -963,15 +960,9 @@ def get_bundle_availability(bundle_item_code, warehouse): def get_bin_qty(item_code, warehouse): - bin_qty = frappe.db.sql( - """select actual_qty from `tabBin` - where item_code = %s and warehouse = %s - limit 1""", - (item_code, warehouse), - as_dict=1, - ) + actual_qty = frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, "actual_qty") - return bin_qty[0].actual_qty or 0 if bin_qty else 0 + return actual_qty or 0 def get_pos_reserved_qty(item_code, warehouse): From a09e8751095eb247ea689367992012c5b755e04f Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 17 Jun 2026 19:01:34 +0530 Subject: [PATCH 03/10] refactor(postgres): port Loyalty Point Entry doctype queries to the query builder Co-Authored-By: Claude Opus 4.8 (1M context) --- .../loyalty_point_entry.py | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.py b/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.py index 5b4c0d2df3f..d1c21973f5e 100644 --- a/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.py +++ b/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.py @@ -39,28 +39,32 @@ def get_loyalty_point_entries(customer, loyalty_program, company, expiry_date=No if not expiry_date: expiry_date = today() - return frappe.db.sql( - """ - select name, loyalty_points, expiry_date, loyalty_program_tier, invoice_type, invoice - from `tabLoyalty Point Entry` - where customer=%s and loyalty_program=%s - and expiry_date>=%s and loyalty_points>0 and company=%s - order by expiry_date - """, - (customer, loyalty_program, expiry_date, company), - as_dict=1, + return frappe.get_all( + "Loyalty Point Entry", + filters={ + "customer": customer, + "loyalty_program": loyalty_program, + "expiry_date": [">=", expiry_date], + "loyalty_points": [">", 0], + "company": company, + }, + fields=["name", "loyalty_points", "expiry_date", "loyalty_program_tier", "invoice_type", "invoice"], + order_by="expiry_date", ) def get_redemption_details(customer, loyalty_program, company): return frappe._dict( - frappe.db.sql( - """ - select redeem_against, sum(loyalty_points) - from `tabLoyalty Point Entry` - where customer=%s and loyalty_program=%s and loyalty_points<0 and company=%s - group by redeem_against - """, - (customer, loyalty_program, company), + frappe.get_all( + "Loyalty Point Entry", + filters={ + "customer": customer, + "loyalty_program": loyalty_program, + "loyalty_points": ["<", 0], + "company": company, + }, + fields=["redeem_against", {"SUM": "loyalty_points", "as": "loyalty_points"}], + group_by="redeem_against", + as_list=True, ) ) From 8e9680afce007afc14e4427547ec563b51f965ab Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 17 Jun 2026 19:01:35 +0530 Subject: [PATCH 04/10] refactor(postgres): port Pricing Rule utils queries to the query builder Co-Authored-By: Claude Opus 4.8 (1M context) --- .../accounts/doctype/pricing_rule/utils.py | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index 82f269d6da6..d41038027a7 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -114,7 +114,7 @@ def _get_pricing_rules(apply_on, args, values): if apply_on_field == "item_code": if args.get("uom", None): item_conditions += ( - " and ({child_doc}.uom={item_uom} or IFNULL({child_doc}.uom, '')='')".format( + " and ({child_doc}.uom={item_uom} or COALESCE({child_doc}.uom, '')='')".format( child_doc=child_doc, item_uom=frappe.db.escape(args.get("uom")) ) ) @@ -127,7 +127,7 @@ def _get_pricing_rules(apply_on, args, values): elif apply_on_field == "item_group": item_conditions = _get_tree_conditions(args, "Item Group", child_doc, False) if args.get("uom", None): - item_conditions += " and ({child_doc}.uom={item_uom} or IFNULL({child_doc}.uom, '')='')".format( + item_conditions += " and ({child_doc}.uom={item_uom} or COALESCE({child_doc}.uom, '')='')".format( child_doc=child_doc, item_uom=frappe.db.escape(args.get("uom")) ) @@ -139,7 +139,7 @@ def _get_pricing_rules(apply_on, args, values): if not args.price_list: args.price_list = None - conditions += " and ifnull(`tabPricing Rule`.for_price_list, '') in (%(price_list)s, '')" + conditions += " and coalesce(`tabPricing Rule`.for_price_list, '') in (%(price_list)s, '')" values["price_list"] = args.get("price_list") pricing_rules = ( @@ -195,10 +195,8 @@ def _get_tree_conditions(args, parenttype, table, allow_blank=True): except TypeError: frappe.throw(_("Invalid {0}").format(args.get(field))) - parent_groups = frappe.db.sql_list( - """select name from `tab{}` - where lft<={} and rgt>={}""".format(parenttype, "%s", "%s"), - (lft, rgt), + parent_groups = frappe.get_all( + parenttype, filters={"lft": ["<=", lft], "rgt": [">=", rgt]}, pluck="name" ) if parenttype in ["Customer Group", "Item Group", "Territory"]: @@ -217,14 +215,14 @@ def _get_tree_conditions(args, parenttype, table, allow_blank=True): if parent_groups: if allow_blank: parent_groups.append("") - condition = "ifnull({table}.{field}, '') in ({parent_groups})".format( + condition = "coalesce({table}.{field}, '') in ({parent_groups})".format( table=table, field=field, parent_groups=", ".join(frappe.db.escape(d) for d in parent_groups) ) frappe.flags.tree_conditions[key] = condition elif allow_blank: - condition = f"ifnull({table}.{field}, '') = ''" + condition = f"coalesce({table}.{field}, '') = ''" return condition @@ -232,10 +230,10 @@ def _get_tree_conditions(args, parenttype, table, allow_blank=True): def get_other_conditions(conditions, values, args): for field in ["company", "customer", "supplier", "campaign", "sales_partner"]: if args.get(field): - conditions += f" and ifnull(`tabPricing Rule`.{field}, '') in (%({field})s, '')" + conditions += f" and coalesce(`tabPricing Rule`.{field}, '') in (%({field})s, '')" values[field] = args.get(field) else: - conditions += f" and ifnull(`tabPricing Rule`.{field}, '') = ''" + conditions += f" and coalesce(`tabPricing Rule`.{field}, '') = ''" for parenttype in ["Customer Group", "Territory", "Supplier Group"]: group_condition = _get_tree_conditions(args, parenttype, "`tabPricing Rule`") @@ -248,8 +246,8 @@ def get_other_conditions(conditions, values, args): or frappe.get_value(args.get("doctype"), args.get("name"), "posting_date", ignore=True) ) if date: - conditions += """ and %(transaction_date)s between ifnull(`tabPricing Rule`.valid_from, '2000-01-01') - and ifnull(`tabPricing Rule`.valid_upto, '2500-12-31')""" + conditions += """ and %(transaction_date)s between coalesce(`tabPricing Rule`.valid_from, '2000-01-01') + and coalesce(`tabPricing Rule`.valid_upto, '2500-12-31')""" values["transaction_date"] = date if args.get("doctype") in [ @@ -264,9 +262,9 @@ def get_other_conditions(conditions, values, args): "POS Invoice", "POS Invoice Item", ]: - conditions += """ and ifnull(`tabPricing Rule`.selling, 0) = 1""" + conditions += """ and coalesce(`tabPricing Rule`.selling, 0) = 1""" else: - conditions += """ and ifnull(`tabPricing Rule`.buying, 0) = 1""" + conditions += """ and coalesce(`tabPricing Rule`.buying, 0) = 1""" return conditions From 8ce63dac65137ea436c6402803858bf8a2560cef Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 17 Jun 2026 19:01:35 +0530 Subject: [PATCH 05/10] refactor(postgres): port Sales Taxes and Charges Template queries to the query builder Co-Authored-By: Claude Opus 4.8 (1M context) --- .../sales_taxes_and_charges_template.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py index 907b4cc6b19..bd39170cd82 100644 --- a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py +++ b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py @@ -56,11 +56,14 @@ def valdiate_taxes_and_charges_template(doc): # doc.is_default = 1 if doc.is_default == 1: - frappe.db.sql( - f"""update `tab{doc.doctype}` set is_default = 0 - where is_default = 1 and name != %s and company = %s""", - (doc.name, doc.company), - ) + template = frappe.qb.DocType(doc.doctype) + ( + frappe.qb.update(template) + .set(template.is_default, 0) + .where( + (template.is_default == 1) & (template.name != doc.name) & (template.company == doc.company) + ) + ).run() validate_disabled(doc) From 813b42d706104828a6e2b630289faee365208b1a Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 17 Jun 2026 19:01:36 +0530 Subject: [PATCH 06/10] refactor(postgres): port Cost Center doctype queries to the query builder Co-Authored-By: Claude Opus 4.8 (1M context) --- erpnext/accounts/doctype/cost_center/cost_center.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/cost_center/cost_center.py b/erpnext/accounts/doctype/cost_center/cost_center.py index 761369c80cf..3711f8d5f0c 100644 --- a/erpnext/accounts/doctype/cost_center/cost_center.py +++ b/erpnext/accounts/doctype/cost_center/cost_center.py @@ -84,10 +84,10 @@ class CostCenter(NestedSet): return frappe.db.get_value("GL Entry", {"cost_center": self.name}) def check_if_child_exists(self): - return frappe.db.sql( - "select name from `tabCost Center` where \ - parent_cost_center = %s and docstatus != 2", - self.name, + return frappe.get_all( + "Cost Center", + filters={"parent_cost_center": self.name, "docstatus": ["!=", 2]}, + pluck="name", ) def if_allocation_exists_against_cost_center(self): From 08abf960472671da6cbb4a4d7495f136dee14786 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 17 Jun 2026 19:01:37 +0530 Subject: [PATCH 07/10] refactor(postgres): port Fiscal Year doctype queries to the query builder Co-Authored-By: Claude Opus 4.8 (1M context) --- erpnext/accounts/doctype/fiscal_year/fiscal_year.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/fiscal_year/fiscal_year.py b/erpnext/accounts/doctype/fiscal_year/fiscal_year.py index 52fe3cd148c..553f8d8d9d5 100644 --- a/erpnext/accounts/doctype/fiscal_year/fiscal_year.py +++ b/erpnext/accounts/doctype/fiscal_year/fiscal_year.py @@ -72,10 +72,8 @@ class FiscalYear(Document): if existing_fiscal_years: for existing in existing_fiscal_years: - company_for_existing = frappe.db.sql_list( - """select company from `tabFiscal Year Company` - where parent=%s""", - existing.name, + company_for_existing = frappe.get_all( + "Fiscal Year Company", filters={"parent": existing.name}, pluck="company" ) overlap = False From 8e0ba50c4d4788547d628843e560793b8a039c1c Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 17 Jun 2026 19:01:37 +0530 Subject: [PATCH 08/10] refactor(postgres): port Purchase Invoice doctype queries to the query builder Co-Authored-By: Claude Opus 4.8 (1M context) --- .../purchase_invoice/purchase_invoice.py | 30 +++++++------------ 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 0ce3b42c56e..74ee04e89ec 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -524,16 +524,11 @@ class PurchaseInvoice(BuyingController): def check_prev_docstatus(self): for d in self.get("items"): if d.purchase_order: - submitted = frappe.db.sql( - "select name from `tabPurchase Order` where docstatus = 1 and name = %s", d.purchase_order - ) + submitted = frappe.db.exists("Purchase Order", {"docstatus": 1, "name": d.purchase_order}) if not submitted: frappe.throw(_("Purchase Order {0} is not submitted").format(d.purchase_order)) if d.purchase_receipt: - submitted = frappe.db.sql( - "select name from `tabPurchase Receipt` where docstatus = 1 and name = %s", - d.purchase_receipt, - ) + submitted = frappe.db.exists("Purchase Receipt", {"docstatus": 1, "name": d.purchase_receipt}) if not submitted: frappe.throw(_("Purchase Receipt {0} is not submitted").format(d.purchase_receipt)) @@ -801,25 +796,20 @@ class PurchaseInvoice(BuyingController): if cint(frappe.get_single_value("Accounts Settings", "check_supplier_invoice_uniqueness")): fiscal_year = get_fiscal_year(self.posting_date, company=self.company, as_dict=True) - pi = frappe.db.sql( - """select name from `tabPurchase Invoice` - where - bill_no = %(bill_no)s - and supplier = %(supplier)s - and name != %(name)s - and docstatus < 2 - and posting_date between %(year_start_date)s and %(year_end_date)s""", - { + pi = frappe.get_all( + "Purchase Invoice", + filters={ "bill_no": self.bill_no, "supplier": self.supplier, - "name": self.name, - "year_start_date": fiscal_year.year_start_date, - "year_end_date": fiscal_year.year_end_date, + "name": ["!=", self.name], + "docstatus": ["<", 2], + "posting_date": ["between", [fiscal_year.year_start_date, fiscal_year.year_end_date]], }, + pluck="name", ) if pi: - pi = pi[0][0] + pi = pi[0] frappe.throw( _("Supplier Invoice No exists in Purchase Invoice {0}").format( From d23677636dfacb284c57a056454936fb8591566e Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 17 Jun 2026 19:01:38 +0530 Subject: [PATCH 09/10] refactor(postgres): port Purchase Invoice GL composer queries to the query builder Co-Authored-By: Claude Opus 4.8 (1M context) --- .../doctype/purchase_invoice/services/gl_composer.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/services/gl_composer.py b/erpnext/accounts/doctype/purchase_invoice/services/gl_composer.py index 1c4ea5c9e7f..60f49535cb0 100644 --- a/erpnext/accounts/doctype/purchase_invoice/services/gl_composer.py +++ b/erpnext/accounts/doctype/purchase_invoice/services/gl_composer.py @@ -395,10 +395,14 @@ class PurchaseInvoiceGLComposer(BaseGLComposer): ): # Post reverse entry for Stock-Received-But-Not-Billed if booked in Purchase Receipt if item.purchase_receipt and valuation_tax_accounts: - negative_expense_booked_in_pr = frappe.db.sql( - """select name from `tabGL Entry` - where voucher_type='Purchase Receipt' and voucher_no=%s and account in %s""", - (item.purchase_receipt, valuation_tax_accounts), + negative_expense_booked_in_pr = frappe.get_all( + "GL Entry", + filters={ + "voucher_type": "Purchase Receipt", + "voucher_no": item.purchase_receipt, + "account": ["in", valuation_tax_accounts], + }, + pluck="name", ) ( From 88cb132fd130cb34d1a269dc348d1fc491b8131c Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 17 Jun 2026 19:01:39 +0530 Subject: [PATCH 10/10] refactor(postgres): port Purchase Invoice expense-account queries to the query builder Co-Authored-By: Claude Opus 4.8 (1M context) --- .../purchase_invoice/services/expense_account.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/services/expense_account.py b/erpnext/accounts/doctype/purchase_invoice/services/expense_account.py index df03a9a838f..3a84382b585 100644 --- a/erpnext/accounts/doctype/purchase_invoice/services/expense_account.py +++ b/erpnext/accounts/doctype/purchase_invoice/services/expense_account.py @@ -55,10 +55,13 @@ class ExpenseAccountService: else: # check if 'Stock Received But Not Billed' account is credited in Purchase receipt or not if item.purchase_receipt: - negative_expense_booked_in_pr = frappe.db.sql( - """select name from `tabGL Entry` - where voucher_type='Purchase Receipt' and voucher_no=%s and account = %s""", - (item.purchase_receipt, stock_not_billed_account), + negative_expense_booked_in_pr = frappe.db.exists( + "GL Entry", + { + "voucher_type": "Purchase Receipt", + "voucher_no": item.purchase_receipt, + "account": stock_not_billed_account, + }, ) if negative_expense_booked_in_pr: