From 8696ba2f5d9e99c799d4aef577f72f2fae5678e7 Mon Sep 17 00:00:00 2001 From: diptanilsaha Date: Fri, 15 Aug 2025 00:11:18 +0530 Subject: [PATCH 1/9] fix: use query builder instead of raw SQL in get_loyalty_details --- .../loyalty_program/loyalty_program.py | 43 +++++++++++-------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/erpnext/accounts/doctype/loyalty_program/loyalty_program.py b/erpnext/accounts/doctype/loyalty_program/loyalty_program.py index 17b18c17f23..9237d94053a 100644 --- a/erpnext/accounts/doctype/loyalty_program/loyalty_program.py +++ b/erpnext/accounts/doctype/loyalty_program/loyalty_program.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, today @@ -55,26 +56,34 @@ def get_loyalty_details( if not expiry_date: expiry_date = today() - condition = "" - if company: - condition = " and company=%s " % frappe.db.escape(company) - if not include_expired_entry: - condition += " and expiry_date>='%s' " % expiry_date + LoyaltyPointEntry = frappe.qb.DocType("Loyalty Point Entry") - loyalty_point_details = frappe.db.sql( - f"""select sum(loyalty_points) as loyalty_points, - sum(purchase_amount) as total_spent from `tabLoyalty Point Entry` - where customer=%s and loyalty_program=%s and posting_date <= %s - {condition} - group by customer""", - (customer, loyalty_program, expiry_date), - as_dict=1, + query = ( + frappe.qb.from_(LoyaltyPointEntry) + .select( + Sum(LoyaltyPointEntry.loyalty_points).as_("loyalty_points"), + Sum(LoyaltyPointEntry.purchase_amount).as_("total_spent"), + ) + .where( + (LoyaltyPointEntry.customer == customer) + & (LoyaltyPointEntry.loyalty_program == loyalty_program) + & (LoyaltyPointEntry.posting_date <= expiry_date) + ) + .groupby(LoyaltyPointEntry.customer) ) - if loyalty_point_details: - return loyalty_point_details[0] - else: - return {"loyalty_points": 0, "total_spent": 0} + if company: + query = query.where(LoyaltyPointEntry.company == company) + + if not include_expired_entry: + query = query.where(LoyaltyPointEntry.expiry_date >= expiry_date) + + loyalty_point_details = query.run(as_dict=True) + + return { + "loyalty_points": flt(loyalty_point_details[0].loyalty_points), + "total_spent": flt(loyalty_point_details[0].total_spent), + } @frappe.whitelist() From de919568b4f7a86c8d418c0c3fd88e1f3101696c Mon Sep 17 00:00:00 2001 From: diptanilsaha Date: Fri, 15 Aug 2025 19:04:31 +0530 Subject: [PATCH 2/9] fix: use query builder instead of raw SQL in get_material_requests_based_on_supplier --- .../material_request/material_request.py | 56 ++++++++++--------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index a67d44b3860..81680af6806 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -11,6 +11,7 @@ import frappe import frappe.defaults from frappe import _, msgprint from frappe.model.mapper import get_mapped_doc +from frappe.query_builder import Order from frappe.query_builder.functions import Sum from frappe.utils import cint, cstr, flt, get_link_to_form, getdate, new_line_sep, nowdate @@ -624,39 +625,44 @@ def get_items_based_on_default_supplier(supplier): @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def get_material_requests_based_on_supplier(doctype, txt, searchfield, start, page_len, filters): - conditions = "" - if txt: - conditions += "and mr.name like '%%" + txt + "%%' " - - if filters.get("transaction_date"): - date = filters.get("transaction_date")[1] - conditions += f"and mr.transaction_date between '{date[0]}' and '{date[1]}' " - supplier = filters.get("supplier") supplier_items = get_items_based_on_default_supplier(supplier) if not supplier_items: frappe.throw(_("{0} is not the default supplier for any items.").format(supplier)) - material_requests = frappe.db.sql( - """select distinct mr.name, transaction_date,company - from `tabMaterial Request` mr, `tabMaterial Request Item` mr_item - where mr.name = mr_item.parent - and mr_item.item_code in ({}) - and mr.material_request_type = 'Purchase' - and mr.per_ordered < 99.99 - and mr.docstatus = 1 - and mr.status != 'Stopped' - and mr.company = %s - {} - order by mr_item.item_code ASC - limit {} offset {} """.format( - ", ".join(["%s"] * len(supplier_items)), conditions, cint(page_len), cint(start) - ), - (*tuple(supplier_items), filters.get("company")), - as_dict=1, + mr = frappe.qb.DocType("Material Request") + mr_item = frappe.qb.DocType("Material Request Item") + + query = ( + frappe.qb.from_(mr) + .from_(mr_item) + .select(mr.name) + .distinct() + .select(mr.transaction_date, mr.company) + .where( + (mr.name == mr_item.parent) + & (mr_item.item_code.isin(supplier_items)) + & (mr.material_request_type == "Purchase") + & (mr.per_ordered < 99.99) + & (mr.docstatus == 1) + & (mr.status != "Stopped") + & (mr.company == filters.get("company")) + ) + .orderby(mr_item.item_code, order=Order.asc) + .limit(cint(page_len)) + .offset(cint(start)) ) + if txt: + query = query.where(mr.name.like(f"%%{txt}%%")) + + if filters.get("transaction_date"): + date = filters.get("transaction_date")[1] + query = query.where(mr.transaction_date[date[0] : date[1]]) + + material_requests = query.run(as_dict=True) + return material_requests From 6320f7290f93a5278ffdfaa790af70427c20a1c8 Mon Sep 17 00:00:00 2001 From: diptanilsaha Date: Fri, 15 Aug 2025 22:22:55 +0530 Subject: [PATCH 3/9] fix: formatted string for disabled filter in get_income_account --- erpnext/controllers/queries.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index f1d8582bf47..e1cf13140d5 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -620,7 +620,7 @@ def get_income_account(doctype, txt, searchfield, start, page_len, filters): if filters.get("company"): condition += "and tabAccount.company = %(company)s" - condition += f"and tabAccount.disabled = {filters.get('disabled', 0)}" + condition += " and tabAccount.disabled = %(disabled)s" return frappe.db.sql( f"""select tabAccount.name from `tabAccount` @@ -630,7 +630,11 @@ def get_income_account(doctype, txt, searchfield, start, page_len, filters): and tabAccount.`{searchfield}` LIKE %(txt)s {condition} {get_match_cond(doctype)} order by idx desc, name""", - {"txt": "%" + txt + "%", "company": filters.get("company", "")}, + { + "txt": "%" + txt + "%", + "company": filters.get("company", ""), + "disabled": cint(filters.get("disabled", 0)), + }, ) From 1db135262d9474411ef54e3367d24bb169d2503e Mon Sep 17 00:00:00 2001 From: diptanilsaha Date: Fri, 15 Aug 2025 22:56:45 +0530 Subject: [PATCH 4/9] fix: use query builder instead of raw SQL in get_blanket_orders --- erpnext/controllers/queries.py | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index e1cf13140d5..a77e338ff32 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -588,21 +588,27 @@ def get_account_list(doctype, txt, searchfield, start, page_len, filters): @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def get_blanket_orders(doctype, txt, searchfield, start, page_len, filters): - return frappe.db.sql( - """select distinct bo.name, bo.blanket_order_type, bo.to_date - from `tabBlanket Order` bo, `tabBlanket Order Item` boi - where - boi.parent = bo.name - and boi.item_code = {item_code} - and bo.blanket_order_type = '{blanket_order_type}' - and bo.company = {company} - and bo.docstatus = 1""".format( - item_code=frappe.db.escape(filters.get("item")), - blanket_order_type=filters.get("blanket_order_type"), - company=frappe.db.escape(filters.get("company")), + bo = frappe.qb.DocType("Blanket Order") + bo_item = frappe.qb.DocType("Blanket Order Item") + + blanket_orders = ( + frappe.qb.from_(bo) + .from_(bo_item) + .select(bo.name) + .distinct() + .select(bo.blanket_order_type, bo.to_date) + .where( + (bo_item.parent == bo.name) + & (bo_item.item_code == filters.get("item")) + & (bo.blanket_order_type == filters.get("blanket_order_type")) + & (bo.company == filters.get("company")) + & (bo.docstatus == 1) ) + .run() ) + return blanket_orders + @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs From eb22794f14351c2ff5731548c48bef0b91765c86 Mon Sep 17 00:00:00 2001 From: diptanilsaha Date: Sat, 16 Aug 2025 20:45:28 +0530 Subject: [PATCH 5/9] fix: sanitize column name for inventory_dimensions in get_stock_balance --- erpnext/stock/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index fa081db21b9..78fd3ca2a75 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -126,8 +126,9 @@ def get_stock_balance( extra_cond = "" if inventory_dimensions_dict: for field, value in inventory_dimensions_dict.items(): + column = frappe.utils.sanitize_column(field) args[field] = value - extra_cond += f" and {field} = %({field})s" + extra_cond += f" and {column} = %({field})s" last_entry = get_previous_sle(args, extra_cond=extra_cond) From 7fa4ed6139dfb737995fe297e40f4f5440c748c3 Mon Sep 17 00:00:00 2001 From: diptanilsaha Date: Sat, 16 Aug 2025 22:15:55 +0530 Subject: [PATCH 6/9] fix: use query builder instead of raw SQL in unset_existing_data --- .../chart_of_accounts_importer/chart_of_accounts_importer.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py index e7dab34d04a..9c4f2f8fd49 100644 --- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py +++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py @@ -462,9 +462,8 @@ def unset_existing_data(company): "Sales Taxes and Charges Template", "Purchase Taxes and Charges Template", ]: - frappe.db.sql( - f'''delete from `tab{doctype}` where `company`="%s"''' % (company) # nosec - ) + dt = frappe.qb.DocType(doctype) + frappe.qb.from_(dt).where(dt.company == company).delete().run() def set_default_accounts(company): From 7f2a52ff71a1fd5d4a9034cf217094c0be9f341a Mon Sep 17 00:00:00 2001 From: diptanilsaha Date: Sat, 16 Aug 2025 22:37:42 +0530 Subject: [PATCH 7/9] fix: use query builder instead of raw SQL in get_rfq_containing_supplier --- .../request_for_quotation.py | 54 +++++++++---------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py index d9d9f8709e0..504bb94b93d 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -9,6 +9,7 @@ from frappe import _ from frappe.core.doctype.communication.email import make from frappe.desk.form.load import get_attachments from frappe.model.mapper import get_mapped_doc +from frappe.query_builder import Order from frappe.utils import get_url from frappe.utils.print_format import download_pdf from frappe.utils.user import get_user_fullname @@ -582,35 +583,32 @@ def get_supplier_tag(): @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def get_rfq_containing_supplier(doctype, txt, searchfield, start, page_len, filters): - conditions = "" - if txt: - conditions += "and rfq.name like '%%" + txt + "%%' " + rfq = frappe.qb.DocType("Request for Quotation") + rfq_supplier = frappe.qb.DocType("Request for Quotation Supplier") - if filters.get("transaction_date"): - conditions += "and rfq.transaction_date = '{}'".format(filters.get("transaction_date")) - - rfq_data = frappe.db.sql( - f""" - select - distinct rfq.name, rfq.transaction_date, - rfq.company - from - `tabRequest for Quotation` rfq, `tabRequest for Quotation Supplier` rfq_supplier - where - rfq.name = rfq_supplier.parent - and rfq_supplier.supplier = %(supplier)s - and rfq.docstatus = 1 - and rfq.company = %(company)s - {conditions} - order by rfq.transaction_date ASC - limit %(page_len)s offset %(start)s """, - { - "page_len": page_len, - "start": start, - "company": filters.get("company"), - "supplier": filters.get("supplier"), - }, - as_dict=1, + query = ( + frappe.qb.from_(rfq) + .from_(rfq_supplier) + .select(rfq.name) + .distinct() + .select(rfq.transaction_date, rfq.company) + .where( + (rfq.name == rfq_supplier.parent) + & (rfq_supplier.supplier == filters.get("supplier")) + & (rfq.docstatus == 1) + & (rfq.company == filters.get("company")) + ) + .orderby(rfq.transaction_date, order=Order.asc) + .limit(page_len) + .offset(start) ) + if txt: + query = query.where(rfq.name.like(f"%%{txt}%%")) + + if filters.get("transaction_date"): + query = query.where(rfq.transaction_date == filters.get("transaction_date")) + + rfq_data = query.run(as_dict=1) + return rfq_data From e563ed0c75fd20135a6ad288e957e75eac7d3b8d Mon Sep 17 00:00:00 2001 From: diptanilsaha Date: Sat, 16 Aug 2025 22:48:45 +0530 Subject: [PATCH 8/9] fix: use query builder instead of raw SQL in get_timesheet_detail_rate --- erpnext/projects/doctype/timesheet/timesheet.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index a43763177db..2119d214a61 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -329,12 +329,16 @@ def get_projectwise_timesheet_data(project=None, parent=None, from_time=None, to @frappe.whitelist() def get_timesheet_detail_rate(timelog, currency): - timelog_detail = frappe.db.sql( - f"""SELECT tsd.billing_amount as billing_amount, - ts.currency as currency FROM `tabTimesheet Detail` tsd - INNER JOIN `tabTimesheet` ts ON ts.name=tsd.parent - WHERE tsd.name = '{timelog}'""", - as_dict=1, + ts = frappe.qb.DocType("Timesheet") + ts_detail = frappe.qb.DocType("Timesheet Detail") + + timelog_detail = ( + frappe.qb.from_(ts_detail) + .inner_join(ts) + .on(ts.name == ts_detail.parent) + .select(ts_detail.billing_amount.as_("billing_amount"), ts.currency.as_("currency")) + .where(ts_detail.name == timelog) + .run(as_dict=1) )[0] if timelog_detail.currency: From 1231ca17c91f6c42c07e016b11a51bea090e91b4 Mon Sep 17 00:00:00 2001 From: diptanilsaha Date: Sat, 16 Aug 2025 23:48:10 +0530 Subject: [PATCH 9/9] fix: handle empty loyalty point details --- .../accounts/doctype/loyalty_program/loyalty_program.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/loyalty_program/loyalty_program.py b/erpnext/accounts/doctype/loyalty_program/loyalty_program.py index 9237d94053a..701558f2c0b 100644 --- a/erpnext/accounts/doctype/loyalty_program/loyalty_program.py +++ b/erpnext/accounts/doctype/loyalty_program/loyalty_program.py @@ -80,10 +80,10 @@ def get_loyalty_details( loyalty_point_details = query.run(as_dict=True) - return { - "loyalty_points": flt(loyalty_point_details[0].loyalty_points), - "total_spent": flt(loyalty_point_details[0].total_spent), - } + if loyalty_point_details: + return loyalty_point_details[0] + else: + return {"loyalty_points": 0, "total_spent": 0} @frappe.whitelist()