From 46a6290ce9971e18f383f6a62b44b78ea8f16a71 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Sun, 6 Jul 2025 13:14:40 +0530 Subject: [PATCH 1/3] perf: use cached value for account validations in gl and ple --- erpnext/accounts/doctype/gl_entry/gl_entry.py | 15 ++++------ .../payment_ledger_entry.py | 28 +++++++++---------- 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index 89b184e89d0..98816a4a2d4 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -224,26 +224,23 @@ class GLEntry(Document): def validate_account_details(self, adv_adj): """Account must be ledger, active and not freezed""" - ret = frappe.db.sql( - """select is_group, docstatus, company - from tabAccount where name=%s""", - self.account, - as_dict=1, - )[0] + account = frappe.get_cached_value( + "Account", self.account, fieldname=["is_group", "docstatus", "company"], as_dict=True + ) - if ret.is_group == 1: + if account.is_group == 1: frappe.throw( _( """{0} {1}: Account {2} is a Group Account and group accounts cannot be used in transactions""" ).format(self.voucher_type, self.voucher_no, self.account) ) - if ret.docstatus == 2: + if account.docstatus == 2: frappe.throw( _("{0} {1}: Account {2} is inactive").format(self.voucher_type, self.voucher_no, self.account) ) - if ret.company != self.company: + if account.company != self.company: frappe.throw( _("{0} {1}: Account {2} does not belong to Company {3}").format( self.voucher_type, self.voucher_no, self.account, self.company diff --git a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py index 2bc44893c20..f48ee9ee6d1 100644 --- a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py +++ b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py @@ -51,38 +51,36 @@ class PaymentLedgerEntry(Document): # end: auto-generated types def validate_account(self): - valid_account = frappe.db.get_list( - "Account", - "name", - filters={"name": self.account, "account_type": self.account_type, "company": self.company}, - ignore_permissions=True, + account = frappe.get_cached_value( + "Account", self.account, fieldname=["account_type", "company"], as_dict=True ) - if not valid_account: + + if account.company != self.company: + frappe.throw(_("{0} account is not of company {1}").format(self.account, self.company)) + + if account.account_type != self.account_type: frappe.throw(_("{0} account is not of type {1}").format(self.account, self.account_type)) def validate_account_details(self): """Account must be ledger, active and not freezed""" - ret = frappe.db.sql( - """select is_group, docstatus, company - from tabAccount where name=%s""", - self.account, - as_dict=1, - )[0] + account = frappe.get_cached_value( + "Account", self.account, fieldname=["is_group", "docstatus", "company"], as_dict=True + ) - if ret.is_group == 1: + if account.is_group == 1: frappe.throw( _( """{0} {1}: Account {2} is a Group Account and group accounts cannot be used in transactions""" ).format(self.voucher_type, self.voucher_no, self.account) ) - if ret.docstatus == 2: + if account.docstatus == 2: frappe.throw( _("{0} {1}: Account {2} is inactive").format(self.voucher_type, self.voucher_no, self.account) ) - if ret.company != self.company: + if account.company != self.company: frappe.throw( _("{0} {1}: Account {2} does not belong to Company {3}").format( self.voucher_type, self.voucher_no, self.account, self.company From fc622631c05d40404ab83c468d67dc0d9012c5cc Mon Sep 17 00:00:00 2001 From: ljain112 Date: Sun, 6 Jul 2025 13:59:07 +0530 Subject: [PATCH 2/3] fix: do not query outstanding for Journal Entry --- erpnext/accounts/doctype/gl_entry/gl_entry.py | 4 ++-- .../doctype/payment_ledger_entry/payment_ledger_entry.py | 4 ++-- erpnext/accounts/utils.py | 8 ++------ 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index 98816a4a2d4..1c2e561e992 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -18,7 +18,7 @@ from erpnext.accounts.party import ( validate_party_frozen_disabled, validate_party_gle_currency, ) -from erpnext.accounts.utils import get_account_currency, get_fiscal_year +from erpnext.accounts.utils import OUTSTANDING_DOCTYPES, get_account_currency, get_fiscal_year from erpnext.exceptions import InvalidAccountCurrency exclude_from_linked_with = True @@ -382,7 +382,7 @@ def update_outstanding_amt( ) ) - if against_voucher_type in ["Sales Invoice", "Purchase Invoice", "Fees"]: + if against_voucher_type in OUTSTANDING_DOCTYPES: ref_doc = frappe.get_doc(against_voucher_type, against_voucher) # Didn't use db_set for optimization purpose diff --git a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py index f48ee9ee6d1..30e885adacc 100644 --- a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py +++ b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py @@ -16,7 +16,7 @@ from erpnext.accounts.doctype.gl_entry.gl_entry import ( validate_balance_type, validate_frozen_account, ) -from erpnext.accounts.utils import update_voucher_outstanding +from erpnext.accounts.utils import OUTSTANDING_DOCTYPES, update_voucher_outstanding from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError @@ -168,7 +168,7 @@ class PaymentLedgerEntry(Document): # update outstanding amount if ( - self.against_voucher_type in ["Journal Entry", "Sales Invoice", "Purchase Invoice", "Fees"] + self.against_voucher_type in OUTSTANDING_DOCTYPES and self.flags.update_outstanding == "Yes" and not frappe.flags.is_reverse_depr_entry ): diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 9d73681cf95..3a395d837f7 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -49,6 +49,7 @@ class PaymentEntryUnlinkError(frappe.ValidationError): GL_REPOSTING_CHUNK = 100 +OUTSTANDING_DOCTYPES = frozenset(["Sales Invoice", "Purchase Invoice", "Fees"]) @frappe.whitelist() @@ -1884,12 +1885,7 @@ def update_voucher_outstanding(voucher_type, voucher_no, account, party_type, pa # on cancellation outstanding can be an empty list voucher_outstanding = ple_query.get_voucher_outstandings(vouchers, common_filter=common_filter) - if ( - voucher_type in ["Sales Invoice", "Purchase Invoice", "Fees"] - and party_type - and party - and voucher_outstanding - ): + if voucher_type in OUTSTANDING_DOCTYPES and party_type and party and voucher_outstanding: outstanding = voucher_outstanding[0] ref_doc = frappe.get_lazy_doc(voucher_type, voucher_no) outstanding_amount = flt( From df0994c0d306654a15f86bd75d0c6caebdee9d41 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Sun, 6 Jul 2025 14:09:05 +0530 Subject: [PATCH 3/3] fix: query voucher outstanding only when all the conditions are true. --- erpnext/accounts/utils.py | 46 ++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 3a395d837f7..007fcf03ffb 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -1869,40 +1869,42 @@ def create_payment_ledger_entry( def update_voucher_outstanding(voucher_type, voucher_no, account, party_type, party): + if not (voucher_type in OUTSTANDING_DOCTYPES and party_type and party): + return + ple = frappe.qb.DocType("Payment Ledger Entry") vouchers = [frappe._dict({"voucher_type": voucher_type, "voucher_no": voucher_no})] common_filter = [] + common_filter.append(ple.party_type == party_type) + common_filter.append(ple.party == party) + if account: common_filter.append(ple.account == account) - if party_type: - common_filter.append(ple.party_type == party_type) - - if party: - common_filter.append(ple.party == party) - ple_query = QueryPaymentLedger() # on cancellation outstanding can be an empty list voucher_outstanding = ple_query.get_voucher_outstandings(vouchers, common_filter=common_filter) - if voucher_type in OUTSTANDING_DOCTYPES and party_type and party and voucher_outstanding: - outstanding = voucher_outstanding[0] - ref_doc = frappe.get_lazy_doc(voucher_type, voucher_no) - outstanding_amount = flt( - outstanding["outstanding_in_account_currency"], ref_doc.precision("outstanding_amount") - ) + if not voucher_outstanding: + return - # Didn't use db_set for optimisation purpose - ref_doc.outstanding_amount = outstanding_amount - frappe.db.set_value( - voucher_type, - voucher_no, - "outstanding_amount", - outstanding_amount, - ) + outstanding = voucher_outstanding[0] + ref_doc = frappe.get_lazy_doc(voucher_type, voucher_no) + outstanding_amount = flt( + outstanding["outstanding_in_account_currency"], ref_doc.precision("outstanding_amount") + ) - ref_doc.set_status(update=True) - ref_doc.notify_update() + # Didn't use db_set for optimisation purpose + ref_doc.outstanding_amount = outstanding_amount + frappe.db.set_value( + voucher_type, + voucher_no, + "outstanding_amount", + outstanding_amount, + ) + + ref_doc.set_status(update=True) + ref_doc.notify_update() def delink_original_entry(pl_entry, partial_cancel=False):