From 13d06e77b49d6ffc7adfeef45d4bc7aeba1ce0c3 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Thu, 18 Jun 2026 21:00:55 +0530 Subject: [PATCH] refactor(postgres): port GL & ledger-core queries to the query builder Pure MariaDB-identical conversion (raw frappe.db.sql -> frappe.qb / portable functions) for Postgres compatibility. Split out of #56082. general_ledger, gl_entry, gl_validator, period_closing_voucher, deferred_revenue, process_payment_reconciliation + their tests. No behaviour change on MariaDB; verified equivalent and the suites pass on both engines. Co-Authored-By: Claude Opus 4.8 (1M context) --- erpnext/accounts/deferred_revenue.py | 189 +++++++++++------- erpnext/accounts/doctype/gl_entry/gl_entry.py | 73 ++++--- .../doctype/gl_entry/test_gl_entry.py | 26 ++- .../period_closing_voucher.py | 72 ++++--- .../test_period_closing_voucher.py | 69 ++++--- .../process_payment_reconciliation.py | 4 +- erpnext/accounts/general_ledger.py | 30 +-- .../general_ledger/test_general_ledger.py | 18 +- erpnext/accounts/services/gl_validator.py | 47 ++--- 9 files changed, 306 insertions(+), 222 deletions(-) diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py index c3518077410..83ab5badb50 100644 --- a/erpnext/accounts/deferred_revenue.py +++ b/erpnext/accounts/deferred_revenue.py @@ -1,6 +1,7 @@ import frappe from frappe import _ from frappe.email import sendmail_to_system_managers +from frappe.query_builder.functions import IfNull, Sum from frappe.utils import ( add_days, add_months, @@ -53,20 +54,24 @@ def validate_service_stop_date(doc): def build_conditions(process_type, account, company): - conditions = "" - deferred_account = ( - "item.deferred_revenue_account" if process_type == "Income" else "item.deferred_expense_account" - ) + if process_type == "Income": + item = frappe.qb.DocType("Sales Invoice Item") + parent = frappe.qb.DocType("Sales Invoice") + deferred_account = item.deferred_revenue_account + else: + item = frappe.qb.DocType("Purchase Invoice Item") + parent = frappe.qb.DocType("Purchase Invoice") + deferred_account = item.deferred_expense_account if account: - conditions += f"AND {deferred_account}={frappe.db.escape(account)}" + return deferred_account == account elif company: - conditions += f"AND p.company = {frappe.db.escape(company)}" + return parent.company == company - return conditions + return None -def convert_deferred_expense_to_expense(deferred_process, start_date=None, end_date=None, conditions=""): +def convert_deferred_expense_to_expense(deferred_process, start_date=None, end_date=None, conditions=None): # book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM if not start_date: @@ -75,17 +80,25 @@ def convert_deferred_expense_to_expense(deferred_process, start_date=None, end_d end_date = add_days(today(), -1) # check for the purchase invoice for which GL entries has to be done - invoices = frappe.db.sql_list( - f""" - select distinct item.parent - from `tabPurchase Invoice Item` item, `tabPurchase Invoice` p - where item.service_start_date<=%s and item.service_end_date>=%s - and item.enable_deferred_expense = 1 and item.parent=p.name - and item.docstatus = 1 and ifnull(item.amount, 0) > 0 - {conditions} - """, - (end_date, start_date), - ) # nosec + item = frappe.qb.DocType("Purchase Invoice Item") + parent = frappe.qb.DocType("Purchase Invoice") + query = ( + frappe.qb.from_(item) + .inner_join(parent) + .on(item.parent == parent.name) + .select(item.parent) + .distinct() + .where( + (item.service_start_date <= end_date) + & (item.service_end_date >= start_date) + & (item.enable_deferred_expense == 1) + & (item.docstatus == 1) + & (IfNull(item.amount, 0) > 0) + ) + ) + if conditions is not None: + query = query.where(conditions) + invoices = query.run(pluck=True) # For each invoice, book deferred expense for invoice in invoices: @@ -96,7 +109,7 @@ def convert_deferred_expense_to_expense(deferred_process, start_date=None, end_d send_mail(deferred_process) -def convert_deferred_revenue_to_income(deferred_process, start_date=None, end_date=None, conditions=""): +def convert_deferred_revenue_to_income(deferred_process, start_date=None, end_date=None, conditions=None): # book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM if not start_date: @@ -105,17 +118,25 @@ def convert_deferred_revenue_to_income(deferred_process, start_date=None, end_da end_date = add_days(today(), -1) # check for the sales invoice for which GL entries has to be done - invoices = frappe.db.sql_list( - f""" - select distinct item.parent - from `tabSales Invoice Item` item, `tabSales Invoice` p - where item.service_start_date<=%s and item.service_end_date>=%s - and item.enable_deferred_revenue = 1 and item.parent=p.name - and item.docstatus = 1 and ifnull(item.amount, 0) > 0 - {conditions} - """, - (end_date, start_date), - ) # nosec + item = frappe.qb.DocType("Sales Invoice Item") + parent = frappe.qb.DocType("Sales Invoice") + query = ( + frappe.qb.from_(item) + .inner_join(parent) + .on(item.parent == parent.name) + .select(item.parent) + .distinct() + .where( + (item.service_start_date <= end_date) + & (item.service_end_date >= start_date) + & (item.enable_deferred_revenue == 1) + & (item.docstatus == 1) + & (IfNull(item.amount, 0) > 0) + ) + ) + if conditions is not None: + query = query.where(conditions) + invoices = query.run(pluck=True) for invoice in invoices: doc = frappe.get_doc("Sales Invoice", invoice) @@ -136,26 +157,39 @@ def get_booking_dates(doc, item, posting_date=None, prev_posting_date=None): ) if not prev_posting_date: - prev_gl_entry = frappe.db.sql( - """ - select name, posting_date from `tabGL Entry` where company=%s and account=%s and - voucher_type=%s and voucher_no=%s and voucher_detail_no=%s - and is_cancelled = 0 - order by posting_date desc limit 1 - """, - (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), - as_dict=True, + prev_gl_entry = frappe.get_all( + "GL Entry", + filters={ + "company": doc.company, + "account": item.get(deferred_account), + "voucher_type": doc.doctype, + "voucher_no": doc.name, + "voucher_detail_no": item.name, + "is_cancelled": 0, + }, + fields=["name", "posting_date"], + order_by="posting_date desc", + limit=1, ) - prev_gl_via_je = frappe.db.sql( - """ - SELECT p.name, p.posting_date FROM `tabJournal Entry` p, `tabJournal Entry Account` c - WHERE p.name = c.parent and p.company=%s and c.account=%s - and c.reference_type=%s and c.reference_name=%s - and c.reference_detail_no=%s and c.docstatus < 2 order by posting_date desc limit 1 - """, - (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), - as_dict=True, + je = frappe.qb.DocType("Journal Entry") + jea = frappe.qb.DocType("Journal Entry Account") + prev_gl_via_je = ( + frappe.qb.from_(je) + .inner_join(jea) + .on(je.name == jea.parent) + .select(je.name, je.posting_date) + .where( + (je.company == doc.company) + & (jea.account == item.get(deferred_account)) + & (jea.reference_type == doc.doctype) + & (jea.reference_name == doc.name) + & (jea.reference_detail_no == item.name) + & (jea.docstatus < 2) + ) + .orderby(je.posting_date, order=frappe.qb.desc) + .limit(1) + .run(as_dict=True) ) if prev_gl_via_je: @@ -277,26 +311,47 @@ def get_already_booked_amount(doc, item): total_credit_debit, total_credit_debit_currency = "credit", "credit_in_account_currency" deferred_account = "deferred_expense_account" - gl_entries_details = frappe.db.sql( - """ - select sum({}) as total_credit, sum({}) as total_credit_in_account_currency, voucher_detail_no - from `tabGL Entry` where company=%s and account=%s and voucher_type=%s and voucher_no=%s and voucher_detail_no=%s - and is_cancelled = 0 - group by voucher_detail_no - """.format(total_credit_debit, total_credit_debit_currency), - (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), - as_dict=True, + gle = frappe.qb.DocType("GL Entry") + gl_entries_details = ( + frappe.qb.from_(gle) + .select( + Sum(gle[total_credit_debit]).as_("total_credit"), + Sum(gle[total_credit_debit_currency]).as_("total_credit_in_account_currency"), + gle.voucher_detail_no, + ) + .where( + (gle.company == doc.company) + & (gle.account == item.get(deferred_account)) + & (gle.voucher_type == doc.doctype) + & (gle.voucher_no == doc.name) + & (gle.voucher_detail_no == item.name) + & (gle.is_cancelled == 0) + ) + .groupby(gle.voucher_detail_no) + .run(as_dict=True) ) - journal_entry_details = frappe.db.sql( - """ - SELECT sum(c.{}) as total_credit, sum(c.{}) as total_credit_in_account_currency, reference_detail_no - FROM `tabJournal Entry` p , `tabJournal Entry Account` c WHERE p.name = c.parent and - p.company = %s and c.account=%s and c.reference_type=%s and c.reference_name=%s and c.reference_detail_no=%s - and p.docstatus < 2 group by reference_detail_no - """.format(total_credit_debit, total_credit_debit_currency), - (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), - as_dict=True, + je = frappe.qb.DocType("Journal Entry") + jea = frappe.qb.DocType("Journal Entry Account") + journal_entry_details = ( + frappe.qb.from_(je) + .inner_join(jea) + .on(je.name == jea.parent) + .select( + Sum(jea[total_credit_debit]).as_("total_credit"), + Sum(jea[total_credit_debit_currency]).as_("total_credit_in_account_currency"), + jea.reference_detail_no, + ) + .where( + (je.company == doc.company) + & (jea.account == item.get(deferred_account)) + & (jea.reference_type == doc.doctype) + & (jea.reference_name == doc.name) + & (jea.reference_detail_no == item.name) + & (je.docstatus < 2) + ) + .groupby(jea.reference_detail_no) + .run(as_dict=True) ) already_booked_amount = gl_entries_details[0].total_credit if gl_entries_details else 0 diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index 452164c728c..a838e1647d2 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -7,6 +7,7 @@ from frappe import _ from frappe.model.document import Document from frappe.model.meta import get_field_precision from frappe.model.naming import set_name_from_naming_options +from frappe.query_builder.functions import Sum from frappe.utils import create_batch, flt, fmt_money, now import erpnext @@ -331,10 +332,12 @@ def validate_balance_type(account, adv_adj=False): if not adv_adj and account: balance_must_be = frappe.get_cached_value("Account", account, "balance_must_be") if balance_must_be: - balance = frappe.db.sql( - """select sum(debit) - sum(credit) - from `tabGL Entry` where is_cancelled = 0 and account = %s""", - account, + gle = frappe.qb.DocType("GL Entry") + balance = ( + frappe.qb.from_(gle) + .select(Sum(gle.debit) - Sum(gle.credit)) + .where((gle.is_cancelled == 0) & (gle.account == account)) + .run() )[0][0] if (balance_must_be == "Debit" and flt(balance) < 0) or ( @@ -348,44 +351,48 @@ def validate_balance_type(account, adv_adj=False): def update_outstanding_amt( account, party_type, party, against_voucher_type, against_voucher, on_cancel=False ): + gle = frappe.qb.DocType("GL Entry") + + conditions = ( + (gle.against_voucher_type == against_voucher_type) + & (gle.against_voucher == against_voucher) + & (gle.voucher_type != "Invoice Discounting") + ) if party_type and party: - party_condition = " and party_type={} and party={}".format( - frappe.db.escape(party_type), frappe.db.escape(party) - ) - else: - party_condition = "" + conditions &= (gle.party_type == party_type) & (gle.party == party) if against_voucher_type == "Sales Invoice": party_account = frappe.get_cached_value(against_voucher_type, against_voucher, "debit_to") - account_condition = f"and account in ({frappe.db.escape(account)}, {frappe.db.escape(party_account)})" + conditions &= gle.account.isin([account, party_account]) else: - account_condition = f" and account = {frappe.db.escape(account)}" + conditions &= gle.account == account # get final outstanding amt bal = flt( - frappe.db.sql( - f""" - select sum(debit_in_account_currency) - sum(credit_in_account_currency) - from `tabGL Entry` - where against_voucher_type=%s and against_voucher=%s - and voucher_type != 'Invoice Discounting' - {party_condition} {account_condition}""", - (against_voucher_type, against_voucher), - )[0][0] + frappe.qb.from_(gle) + .select(Sum(gle.debit_in_account_currency) - Sum(gle.credit_in_account_currency)) + .where(conditions) + .run()[0][0] or 0.0 ) if against_voucher_type == "Purchase Invoice": bal = -bal elif against_voucher_type == "Journal Entry": + je_conditions = ( + (gle.voucher_type == "Journal Entry") + & (gle.voucher_no == against_voucher) + & (gle.account == account) + & (gle.against_voucher.isnull() | (gle.against_voucher == "")) + ) + if party_type and party: + je_conditions &= (gle.party_type == party_type) & (gle.party == party) + against_voucher_amount = flt( - frappe.db.sql( - f""" - select sum(debit_in_account_currency) - sum(credit_in_account_currency) - from `tabGL Entry` where voucher_type = 'Journal Entry' and voucher_no = %s - and account = %s and (against_voucher is null or against_voucher='') {party_condition}""", - (against_voucher, account), - )[0][0] + frappe.qb.from_(gle) + .select(Sum(gle.debit_in_account_currency) - Sum(gle.credit_in_account_currency)) + .where(je_conditions) + .run()[0][0] ) if not against_voucher_amount: @@ -480,10 +487,14 @@ def rename_temporarily_named_docs(doctype): oldname = doc.name set_name_from_naming_options(autoname, doc) newname = doc.name - frappe.db.sql( - f"UPDATE `tab{doctype}` SET name = %s, to_rename = 0, modified = %s where name = %s", - (newname, now(), oldname), - ) + dt = frappe.qb.DocType(doctype) + ( + frappe.qb.update(dt) + .set(dt.name, newname) + .set(dt.to_rename, 0) + .set(dt.modified, now()) + .where(dt.name == oldname) + ).run() for hook_type in ("on_gle_rename", "on_sle_rename"): for hook in frappe.get_hooks(hook_type): diff --git a/erpnext/accounts/doctype/gl_entry/test_gl_entry.py b/erpnext/accounts/doctype/gl_entry/test_gl_entry.py index 6caec70dc00..2ac56db3cd5 100644 --- a/erpnext/accounts/doctype/gl_entry/test_gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/test_gl_entry.py @@ -26,12 +26,17 @@ class TestGLEntry(ERPNextTestSuite): jv.flags.ignore_validate = True jv.submit() - round_off_entry = frappe.db.sql( - """select name from `tabGL Entry` - where voucher_type='Journal Entry' and voucher_no = %s - and account='_Test Write Off - _TC' and cost_center='_Test Cost Center - _TC' - and debit = 0 and credit = '.01'""", - jv.name, + round_off_entry = frappe.get_all( + "GL Entry", + filters={ + "voucher_type": "Journal Entry", + "voucher_no": jv.name, + "account": "_Test Write Off - _TC", + "cost_center": "_Test Cost Center - _TC", + "debit": 0, + "credit": 0.01, + }, + pluck="name", ) self.assertTrue(round_off_entry) @@ -55,8 +60,9 @@ class TestGLEntry(ERPNextTestSuite): ) self.assertTrue(all(entry.to_rename == 1 for entry in gl_entries)) - old_naming_series_current_value = frappe.db.sql( - "SELECT current from tabSeries where name = %s", naming_series + series = frappe.qb.DocType("Series") + old_naming_series_current_value = ( + frappe.qb.from_(series).select(series["current"]).where(series.name == naming_series).run() )[0][0] rename_gle_sle_docs() @@ -73,8 +79,8 @@ class TestGLEntry(ERPNextTestSuite): all(new.name != old.name for new, old in zip(gl_entries, new_gl_entries, strict=False)) ) - new_naming_series_current_value = frappe.db.sql( - "SELECT current from tabSeries where name = %s", naming_series + new_naming_series_current_value = ( + frappe.qb.from_(series).select(series["current"]).where(series.name == naming_series).run() )[0][0] self.assertEqual(old_naming_series_current_value + 2, new_naming_series_current_value) diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py index 760f7523be0..0f16abb9a05 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py @@ -73,7 +73,10 @@ class PeriodClosingVoucher(AccountsController): if not previous_fiscal_year: return - previous_fiscal_year_start_date = previous_fiscal_year[0][1] + # get_fiscal_year() returns a single (name, start_date, end_date) tuple, so the start date + # is [1]; the old [0][1] read the 2nd char of the name ('T'), which MariaDB silently + # coerced to NULL but postgres rejects as an invalid date. + previous_fiscal_year_start_date = previous_fiscal_year[1] previous_fiscal_year_closed = frappe.db.exists( "Period Closing Voucher", { @@ -287,41 +290,44 @@ class PeriodClosingVoucher(AccountsController): self.accounting_dimension_fields = default_dimensions + get_accounting_dimensions() def get_gl_entries_for_current_period(self, report_type, only_opening_entries=False, as_iterator=False): - date_condition = "" - if only_opening_entries: - date_condition = "is_opening = 'Yes'" - else: - date_condition = f"posting_date BETWEEN '{self.period_start_date}' AND '{self.period_end_date}' and is_opening = 'No'" + gle = frappe.qb.DocType("GL Entry") + account = frappe.qb.DocType("Account") - # nosemgrep - return frappe.db.sql( - """ - SELECT - name, - posting_date, - account, - account_currency, - debit_in_account_currency, - credit_in_account_currency, - debit, - credit, - {} - FROM `tabGL Entry` - WHERE - {} - AND company = %s - AND voucher_type != 'Period Closing Voucher' - AND EXISTS(SELECT name FROM `tabAccount` WHERE name = account AND report_type = %s) - AND is_cancelled = 0 - """.format( - ", ".join(self.accounting_dimension_fields), - date_condition, - ), - (self.company, report_type), - as_dict=1, - as_iterator=as_iterator, + fields = [ + gle.name, + gle.posting_date, + gle.account, + gle.account_currency, + gle.debit_in_account_currency, + gle.credit_in_account_currency, + gle.debit, + gle.credit, + ] + fields += [gle[dimension] for dimension in self.accounting_dimension_fields] + + query = ( + frappe.qb.from_(gle) + .select(*fields) + .where( + (gle.company == self.company) + & (gle.voucher_type != "Period Closing Voucher") + & (gle.is_cancelled == 0) + & gle.account.isin( + frappe.qb.from_(account).select(account.name).where(account.report_type == report_type) + ) + ) ) + if only_opening_entries: + query = query.where(gle.is_opening == "Yes") + else: + query = query.where( + gle.posting_date.between(self.period_start_date, self.period_end_date) + & (gle.is_opening == "No") + ) + + return query.run(as_dict=1, as_iterator=as_iterator) + def set_account_balance_dict(self, gle, acc_bal_dict): key = self.get_key(gle) diff --git a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py index 1a16bcd124e..f19996dcf80 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py @@ -55,15 +55,19 @@ class TestPeriodClosingVoucher(ERPNextTestSuite): ("Sales - TPC", 400.0, 0.0), ) - pcv_gle = frappe.db.sql( - """ - select account, debit, credit from `tabGL Entry` where voucher_no=%s order by account - """, - (pcv.name), - ) + pcv_gle = [ + tuple(row) + for row in frappe.get_all( + "GL Entry", + filters={"voucher_no": pcv.name}, + fields=["account", "debit", "credit"], + order_by="account", + as_list=True, + ) + ] pcv.reload() self.assertEqual(pcv.gle_processing_status, "Completed") - self.assertEqual(pcv_gle, expected_gle) + self.assertEqual(tuple(pcv_gle), expected_gle) def test_cost_center_wise_posting(self): surplus_account = create_account() @@ -106,14 +110,16 @@ class TestPeriodClosingVoucher(ERPNextTestSuite): ("Sales - TPC", 200.0, 0.0, cost_center2), ) - pcv_gle = frappe.db.sql( - """ - select account, debit, credit, cost_center - from `tabGL Entry` where voucher_no=%s - order by account, cost_center - """, - (pcv.name), - ) + pcv_gle = [ + tuple(row) + for row in frappe.get_all( + "GL Entry", + filters={"voucher_no": pcv.name}, + fields=["account", "debit", "credit", "cost_center"], + order_by="account, cost_center", + as_list=True, + ) + ] self.assertSequenceEqual(pcv_gle, expected_gle) @@ -166,16 +172,19 @@ class TestPeriodClosingVoucher(ERPNextTestSuite): ("Sales - TPC", 400.0, 0.0, jv.finance_book), ) - pcv_gle = frappe.db.sql( - """ - select account, debit, credit, finance_book - from `tabGL Entry` where voucher_no=%s - order by account, finance_book - """, - (pcv.name), - ) + pcv_gle = [ + tuple(row) + for row in frappe.get_all( + "GL Entry", + filters={"voucher_no": pcv.name}, + fields=["account", "debit", "credit", "finance_book"], + order_by="account, finance_book", + as_list=True, + ) + ] - self.assertSequenceEqual(pcv_gle, expected_gle) + # compare order-independently: postgres and MariaDB order NULL finance_book differently + self.assertSequenceEqual(sorted(pcv_gle, key=str), sorted(expected_gle, key=str)) def test_gl_entries_restrictions(self): cost_center = create_cost_center("Test Cost Center 1") @@ -358,14 +367,10 @@ class TestPeriodClosingVoucher(ERPNextTestSuite): posting_date="2022-01-01", ) - totals_after_cancel = frappe.db.sql( - """ - select sum(debit) as total_debit, sum(credit) as total_credit - from `tabGL Entry` - where voucher_type=%s and voucher_no=%s and is_cancelled=0 - """, - ("Journal Entry", jv.name), - as_dict=True, + totals_after_cancel = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Journal Entry", "voucher_no": jv.name, "is_cancelled": 0}, + fields=[{"SUM": "debit", "as": "total_debit"}, {"SUM": "credit", "as": "total_credit"}], )[0] self.assertEqual(totals_after_cancel.total_debit, totals_after_cancel.total_credit) diff --git a/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py index d110f9df8a2..316e691c89c 100644 --- a/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py +++ b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py @@ -431,7 +431,9 @@ def reconcile(doc: None | str = None) -> None: # Update reconciled flag allocation_names = [x.name for x in allocations] ppa = qb.DocType("Process Payment Reconciliation Log Allocations") - qb.update(ppa).set(ppa.reconciled, True).where(ppa.name.isin(allocation_names)).run() + qb.update(ppa).set(ppa.reconciled, 1).where( + ppa.name.isin(allocation_names) + ).run() # smallint, not bool # Update reconciled count reconciled_count = frappe.db.count( diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 0c535ff6b4b..1e54eb0370c 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -672,7 +672,7 @@ def make_reverse_gl_entries( ) if not immutable_ledger_enabled: - query = query.set(gle.is_cancelled, True) + query = query.set(gle.is_cancelled, 1) # smallint column; postgres rejects boolean true query.run() else: @@ -683,12 +683,14 @@ def make_reverse_gl_entries( if not all(gle_names): set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"]) else: - frappe.db.sql( - """UPDATE `tabGL Entry` SET is_cancelled = 1, - modified=%s, modified_by=%s - where name in %s and is_cancelled = 0""", - (now(), frappe.session.user, tuple(gle_names)), - ) + gle = frappe.qb.DocType("GL Entry") + ( + frappe.qb.update(gle) + .set(gle.is_cancelled, 1) + .set(gle.modified, now()) + .set(gle.modified_by, frappe.session.user) + .where(gle.name.isin(gle_names) & (gle.is_cancelled == 0)) + ).run() for entry in gl_entries: new_gle = copy.deepcopy(entry) @@ -725,9 +727,11 @@ def set_as_cancel(voucher_type, voucher_no): """ Set is_cancelled=1 in all original gl entries for the voucher """ - frappe.db.sql( - """UPDATE `tabGL Entry` SET is_cancelled = 1, - modified=%s, modified_by=%s - where voucher_type=%s and voucher_no=%s and is_cancelled = 0""", - (now(), frappe.session.user, voucher_type, voucher_no), - ) + gle = frappe.qb.DocType("GL Entry") + ( + frappe.qb.update(gle) + .set(gle.is_cancelled, 1) + .set(gle.modified, now()) + .set(gle.modified_by, frappe.session.user) + .where((gle.voucher_type == voucher_type) & (gle.voucher_no == voucher_no) & (gle.is_cancelled == 0)) + ).run() diff --git a/erpnext/accounts/report/general_ledger/test_general_ledger.py b/erpnext/accounts/report/general_ledger/test_general_ledger.py index e88f4fa8560..8c3a14a7804 100644 --- a/erpnext/accounts/report/general_ledger/test_general_ledger.py +++ b/erpnext/accounts/report/general_ledger/test_general_ledger.py @@ -134,17 +134,17 @@ class TestGeneralLedger(ERPNextTestSuite): revaluation_jv.submit() # check the balance of the account - balance = frappe.db.sql( - """ - select sum(debit_in_account_currency) - sum(credit_in_account_currency) - from `tabGL Entry` - where account = %s - group by account - """, - account.name, + balance = frappe.get_all( + "GL Entry", + filters={"account": account.name}, + fields=[ + {"SUM": "debit_in_account_currency", "as": "debit"}, + {"SUM": "credit_in_account_currency", "as": "credit"}, + ], + group_by="account", ) - self.assertEqual(balance[0][0], 100) + self.assertEqual(flt(balance[0].debit) - flt(balance[0].credit), 100) # check if general ledger shows correct balance columns, data = execute( diff --git a/erpnext/accounts/services/gl_validator.py b/erpnext/accounts/services/gl_validator.py index fd597fb38a8..a493b1fd774 100644 --- a/erpnext/accounts/services/gl_validator.py +++ b/erpnext/accounts/services/gl_validator.py @@ -37,25 +37,22 @@ def validate_disabled_accounts(gl_map): def validate_accounting_period(gl_map): - accounting_periods = frappe.db.sql( - """ SELECT - ap.name as name, ap.exempted_role as exempted_role - FROM - `tabAccounting Period` ap, `tabClosed Document` cd - WHERE - ap.name = cd.parent - AND ap.company = %(company)s - AND ap.disabled = 0 - AND cd.closed = 1 - AND cd.document_type = %(voucher_type)s - AND %(date)s between ap.start_date and ap.end_date - """, - { - "date": gl_map[0].posting_date, - "company": gl_map[0].company, - "voucher_type": gl_map[0].voucher_type, - }, - as_dict=1, + ap = frappe.qb.DocType("Accounting Period") + cd = frappe.qb.DocType("Closed Document") + accounting_periods = ( + frappe.qb.from_(ap) + .inner_join(cd) + .on(ap.name == cd.parent) + .select(ap.name.as_("name"), ap.exempted_role.as_("exempted_role")) + .where( + (ap.company == gl_map[0].company) + & (ap.disabled == 0) + & (cd.closed == 1) + & (cd.document_type == gl_map[0].voucher_type) + & (ap.start_date <= gl_map[0].posting_date) + & (ap.end_date >= gl_map[0].posting_date) + ) + .run(as_dict=1) ) if accounting_periods: @@ -81,13 +78,11 @@ def validate_cwip_accounts(gl_map): for ac in frappe.db.get_all("Asset Category", "enable_cwip_accounting") ) if cwip_enabled: - cwip_accounts = [ - d[0] - for d in frappe.db.sql( - """select name from tabAccount - where account_type = 'Capital Work in Progress' and is_group=0""" - ) - ] + cwip_accounts = frappe.get_all( + "Account", + filters={"account_type": "Capital Work in Progress", "is_group": 0}, + pluck="name", + ) for entry in gl_map: if entry.account in cwip_accounts: