diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 56dcc0ada1a..e16f86e238d 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -7,7 +7,7 @@ from collections import OrderedDict import frappe from frappe import _, qb, query_builder, scrub from frappe.query_builder import Criterion -from frappe.query_builder.functions import Date, Substring, Sum +from frappe.query_builder.functions import Date, Max, Substring, Sum from frappe.utils import cint, cstr, flt, getdate, nowdate from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( @@ -427,32 +427,21 @@ class ReceivablePayableReport: self.delivery_notes = frappe._dict() # delivery note link inside sales invoice - # nosemgrep - si_against_dn = frappe.db.sql( - """ - select parent, delivery_note - from `tabSales Invoice Item` - where docstatus=1 and parent in (%s) - """ - % (",".join(["%s"] * len(self.invoices))), - tuple(self.invoices), - as_dict=1, + si_against_dn = frappe.get_all( + "Sales Invoice Item", + filters={"docstatus": 1, "parent": ["in", list(self.invoices)]}, + fields=["parent", "delivery_note"], ) for d in si_against_dn: if d.delivery_note: self.delivery_notes.setdefault(d.parent, set()).add(d.delivery_note) - # nosemgrep - dn_against_si = frappe.db.sql( - """ - select distinct parent, against_sales_invoice - from `tabDelivery Note Item` - where against_sales_invoice in (%s) - """ - % (",".join(["%s"] * len(self.invoices))), - tuple(self.invoices), - as_dict=1, + dn_against_si = frappe.get_all( + "Delivery Note Item", + filters={"against_sales_invoice": ["in", list(self.invoices)]}, + fields=["parent", "against_sales_invoice"], + distinct=True, ) for d in dn_against_si: @@ -476,14 +465,10 @@ class ReceivablePayableReport: # Get Sales Team if self.filters.show_sales_person: - # nosemgrep - sales_team = frappe.db.sql( - """ - select parent, sales_person - from `tabSales Team` - where parenttype = 'Sales Invoice' - """, - as_dict=1, + sales_team = frappe.get_all( + "Sales Team", + filters={"parenttype": "Sales Invoice"}, + fields=["parent", "sales_person"], ) for d in sales_team: self.invoice_details.setdefault(d.parent, {}).setdefault("sales_team", []).append( @@ -548,22 +533,31 @@ class ReceivablePayableReport: def get_payment_terms(self, row): # build payment_terms for row - # nosemgrep - payment_terms_details = frappe.db.sql( - f""" - select - si.name, si.party_account_currency, si.currency, si.conversion_rate, - si.total_advance, ps.due_date, ps.payment_term, ps.payment_amount, ps.base_payment_amount, - ps.description, ps.paid_amount, ps.base_paid_amount, ps.discounted_amount - from `tab{row.voucher_type}` si, `tabPayment Schedule` ps - where - si.name = ps.parent and ps.parenttype = '{row.voucher_type}' and - si.name = %s and - si.is_return = 0 - order by ps.paid_amount desc, due_date - """, - row.voucher_no, - as_dict=1, + si = frappe.qb.DocType(row.voucher_type) + ps = frappe.qb.DocType("Payment Schedule") + payment_terms_details = ( + frappe.qb.from_(si) + .inner_join(ps) + .on(si.name == ps.parent) + .select( + si.name, + si.party_account_currency, + si.currency, + si.conversion_rate, + si.total_advance, + ps.due_date, + ps.payment_term, + ps.payment_amount, + ps.base_payment_amount, + ps.description, + ps.paid_amount, + ps.base_paid_amount, + ps.discounted_amount, + ) + .where((ps.parenttype == row.voucher_type) & (si.name == row.voucher_no) & (si.is_return == 0)) + .orderby(ps.paid_amount, order=frappe.qb.desc) + .orderby(ps.due_date) + .run(as_dict=1) ) original_row = frappe._dict(row) @@ -661,7 +655,6 @@ class ReceivablePayableReport: def get_future_payments_from_payment_entry(self): pe = frappe.qb.DocType("Payment Entry") pe_ref = frappe.qb.DocType("Payment Entry Reference") - ifelse = query_builder.CustomFunction("IF", ["condition", "then", "else"]) return ( frappe.qb.from_(pe) @@ -674,11 +667,14 @@ class ReceivablePayableReport: (pe.posting_date).as_("future_date"), (pe_ref.allocated_amount).as_("future_amount"), (pe.reference_no).as_("future_ref"), - ifelse( + # CASE is portable; MySQL's IF() does not exist on postgres + query_builder.Case() + .when( pe.payment_type == "Receive", pe.source_exchange_rate * pe_ref.allocated_amount, - pe.target_exchange_rate * pe_ref.allocated_amount, - ).as_("future_amount_in_base_currency"), + ) + .else_(pe.target_exchange_rate * pe_ref.allocated_amount) + .as_("future_amount_in_base_currency"), ) .where( (pe.docstatus < 2) @@ -695,11 +691,13 @@ class ReceivablePayableReport: .inner_join(jea) .on(jea.parent == je.name) .select( - jea.reference_name.as_("invoice_no"), - jea.party, - jea.party_type, - je.posting_date.as_("future_date"), - je.cheque_no.as_("future_ref"), + # Sum() below makes this an implicit aggregate (no GROUP BY); the non-aggregated columns + # are arbitrary per the single group on MySQL -> Max() keeps it valid on postgres. + Max(jea.reference_name).as_("invoice_no"), + Max(jea.party).as_("party"), + Max(jea.party_type).as_("party_type"), + Max(je.posting_date).as_("future_date"), + Max(je.cheque_no).as_("future_ref"), ) .where( (je.docstatus < 2) @@ -712,30 +710,25 @@ class ReceivablePayableReport: if self.filters.get("party"): if self.account_type == "Payable": - query = query.select( - Sum(jea.debit_in_account_currency - jea.credit_in_account_currency).as_("future_amount") - ) - query = query.select(Sum(jea.debit - jea.credit).as_("future_amount_in_base_currency")) + future_amount = Sum(jea.debit_in_account_currency - jea.credit_in_account_currency) + future_amount_in_base_currency = Sum(jea.debit - jea.credit) else: - query = query.select( - Sum(jea.credit_in_account_currency - jea.debit_in_account_currency).as_("future_amount") - ) - query = query.select(Sum(jea.credit - jea.debit).as_("future_amount_in_base_currency")) + future_amount = Sum(jea.credit_in_account_currency - jea.debit_in_account_currency) + future_amount_in_base_currency = Sum(jea.credit - jea.debit) else: - query = query.select( - Sum(jea.debit if self.account_type == "Payable" else jea.credit).as_( - "future_amount_in_base_currency" - ) - ) - query = query.select( - Sum( - jea.debit_in_account_currency - if self.account_type == "Payable" - else jea.credit_in_account_currency - ).as_("future_amount") + future_amount_in_base_currency = Sum(jea.debit if self.account_type == "Payable" else jea.credit) + future_amount = Sum( + jea.debit_in_account_currency + if self.account_type == "Payable" + else jea.credit_in_account_currency ) - query = query.having(qb.Field("future_amount") > 0) + query = query.select( + future_amount.as_("future_amount"), + future_amount_in_base_currency.as_("future_amount_in_base_currency"), + ) + # use the aggregate expression in HAVING; postgres can't reference a SELECT alias there + query = query.having(future_amount > 0) return query.run(as_dict=True) def allocate_future_payments(self, row): @@ -891,16 +884,19 @@ class ReceivablePayableReport: if self.filters.get("sales_person"): lft, rgt = frappe.db.get_value("Sales Person", self.filters.get("sales_person"), ["lft", "rgt"]) - # nosemgrep - records = frappe.db.sql( - """ - select distinct parent, parenttype - from `tabSales Team` steam - where parenttype in ('Customer', 'Sales Invoice') - and exists(select name from `tabSales Person` where lft >= %s and rgt <= %s and name = steam.sales_person) - """, - (lft, rgt), - as_dict=1, + steam = frappe.qb.DocType("Sales Team") + sp = frappe.qb.DocType("Sales Person") + records = ( + frappe.qb.from_(steam) + .select(steam.parent, steam.parenttype) + .distinct() + .where( + steam.parenttype.isin(["Customer", "Sales Invoice"]) + & steam.sales_person.isin( + frappe.qb.from_(sp).select(sp.name).where((sp.lft >= lft) & (sp.rgt <= rgt)) + ) + ) + .run(as_dict=1) ) self.sales_person_records = frappe._dict()