mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-01 11:19:09 +00:00
Merge pull request #47399 from frappe/mergify/bp/version-15-hotfix/pr-47145
refactor: make AR / AP report more memory efficient (backport #47145)
This commit is contained in:
@@ -77,9 +77,12 @@
|
|||||||
"reports_tab",
|
"reports_tab",
|
||||||
"remarks_section",
|
"remarks_section",
|
||||||
"general_ledger_remarks_length",
|
"general_ledger_remarks_length",
|
||||||
"ignore_is_opening_check_for_reporting",
|
|
||||||
"column_break_lvjk",
|
"column_break_lvjk",
|
||||||
"receivable_payable_remarks_length",
|
"receivable_payable_remarks_length",
|
||||||
|
"accounts_receivable_payable_tuning_section",
|
||||||
|
"receivable_payable_fetch_method",
|
||||||
|
"legacy_section",
|
||||||
|
"ignore_is_opening_check_for_reporting",
|
||||||
"payment_request_settings",
|
"payment_request_settings",
|
||||||
"create_pr_in_draft_status"
|
"create_pr_in_draft_status"
|
||||||
],
|
],
|
||||||
@@ -532,6 +535,34 @@
|
|||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Posting Date Inheritance for Exchange Gain / Loss",
|
"label": "Posting Date Inheritance for Exchange Gain / Loss",
|
||||||
"options": "Invoice\nPayment\nReconciliation Date"
|
"options": "Invoice\nPayment\nReconciliation Date"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_xrnd",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"description": "If enabled, Sales Invoice will be generated instead of POS Invoice in POS Transactions for real-time update of G/L and Stock Ledger.",
|
||||||
|
"fieldname": "use_sales_invoice_in_pos",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Use Sales Invoice"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "Buffered Cursor",
|
||||||
|
"fieldname": "receivable_payable_fetch_method",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Data Fetch Method",
|
||||||
|
"options": "Buffered Cursor\nUnBuffered Cursor"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "accounts_receivable_payable_tuning_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Accounts Receivable / Payable Tuning"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "legacy_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Legacy Fields"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "icon-cog",
|
"icon": "icon-cog",
|
||||||
@@ -539,7 +570,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-01-23 13:15:44.077853",
|
"modified": "2025-05-05 12:29:38.302027",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Accounts Settings",
|
"name": "Accounts Settings",
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ class AccountsSettings(Document):
|
|||||||
merge_similar_account_heads: DF.Check
|
merge_similar_account_heads: DF.Check
|
||||||
over_billing_allowance: DF.Currency
|
over_billing_allowance: DF.Currency
|
||||||
post_change_gl_entries: DF.Check
|
post_change_gl_entries: DF.Check
|
||||||
|
receivable_payable_fetch_method: DF.Literal["Buffered Cursor", "UnBuffered Cursor"]
|
||||||
receivable_payable_remarks_length: DF.Int
|
receivable_payable_remarks_length: DF.Int
|
||||||
reconciliation_queue_size: DF.Int
|
reconciliation_queue_size: DF.Int
|
||||||
role_allowed_to_over_bill: DF.Link | None
|
role_allowed_to_over_bill: DF.Link | None
|
||||||
|
|||||||
@@ -54,6 +54,10 @@ class ReceivablePayableReport:
|
|||||||
self.filters.range = "30, 60, 90, 120"
|
self.filters.range = "30, 60, 90, 120"
|
||||||
self.ranges = [num.strip() for num in self.filters.range.split(",") if num.strip().isdigit()]
|
self.ranges = [num.strip() for num in self.filters.range.split(",") if num.strip().isdigit()]
|
||||||
self.range_numbers = [num for num in range(1, len(self.ranges) + 2)]
|
self.range_numbers = [num for num in range(1, len(self.ranges) + 2)]
|
||||||
|
self.ple_fetch_method = (
|
||||||
|
frappe.db.get_single_value("Accounts Settings", "receivable_payable_fetch_method")
|
||||||
|
or "Buffered Cursor"
|
||||||
|
) # Fail Safe
|
||||||
|
|
||||||
def run(self, args):
|
def run(self, args):
|
||||||
self.filters.update(args)
|
self.filters.update(args)
|
||||||
@@ -90,10 +94,7 @@ class ReceivablePayableReport:
|
|||||||
self.skip_total_row = 1
|
self.skip_total_row = 1
|
||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
self.get_ple_entries()
|
|
||||||
self.get_sales_invoices_or_customers_based_on_sales_person()
|
self.get_sales_invoices_or_customers_based_on_sales_person()
|
||||||
self.voucher_balance = OrderedDict()
|
|
||||||
self.init_voucher_balance() # invoiced, paid, credit_note, outstanding
|
|
||||||
|
|
||||||
# Build delivery note map against all sales invoices
|
# Build delivery note map against all sales invoices
|
||||||
self.build_delivery_note_map()
|
self.build_delivery_note_map()
|
||||||
@@ -110,12 +111,40 @@ class ReceivablePayableReport:
|
|||||||
# Get Exchange Rate Revaluations
|
# Get Exchange Rate Revaluations
|
||||||
self.get_exchange_rate_revaluations()
|
self.get_exchange_rate_revaluations()
|
||||||
|
|
||||||
|
self.prepare_ple_query()
|
||||||
self.data = []
|
self.data = []
|
||||||
|
self.voucher_balance = OrderedDict()
|
||||||
|
|
||||||
|
if self.ple_fetch_method == "Buffered Cursor":
|
||||||
|
self.fetch_ple_in_buffered_cursor()
|
||||||
|
elif self.ple_fetch_method == "UnBuffered Cursor":
|
||||||
|
self.fetch_ple_in_unbuffered_cursor()
|
||||||
|
|
||||||
|
self.build_data()
|
||||||
|
|
||||||
|
def fetch_ple_in_buffered_cursor(self):
|
||||||
|
self.ple_entries = frappe.db.sql(self.ple_query.get_sql(), as_dict=True)
|
||||||
|
|
||||||
|
for ple in self.ple_entries:
|
||||||
|
self.init_voucher_balance(ple) # invoiced, paid, credit_note, outstanding
|
||||||
|
|
||||||
|
# This is unavoidable. Initialization and allocation cannot happen in same loop
|
||||||
for ple in self.ple_entries:
|
for ple in self.ple_entries:
|
||||||
self.update_voucher_balance(ple)
|
self.update_voucher_balance(ple)
|
||||||
|
|
||||||
self.build_data()
|
delattr(self, "ple_entries")
|
||||||
|
|
||||||
|
def fetch_ple_in_unbuffered_cursor(self):
|
||||||
|
self.ple_entries = []
|
||||||
|
with frappe.db.unbuffered_cursor():
|
||||||
|
for ple in frappe.db.sql(self.ple_query.get_sql(), as_dict=True, as_iterator=True):
|
||||||
|
self.init_voucher_balance(ple) # invoiced, paid, credit_note, outstanding
|
||||||
|
self.ple_entries.append(ple)
|
||||||
|
|
||||||
|
# This is unavoidable. Initialization and allocation cannot happen in same loop
|
||||||
|
for ple in self.ple_entries:
|
||||||
|
self.update_voucher_balance(ple)
|
||||||
|
delattr(self, "ple_entries")
|
||||||
|
|
||||||
def build_voucher_dict(self, ple):
|
def build_voucher_dict(self, ple):
|
||||||
return frappe._dict(
|
return frappe._dict(
|
||||||
@@ -136,26 +165,22 @@ class ReceivablePayableReport:
|
|||||||
outstanding_in_account_currency=0.0,
|
outstanding_in_account_currency=0.0,
|
||||||
)
|
)
|
||||||
|
|
||||||
def init_voucher_balance(self):
|
def init_voucher_balance(self, ple):
|
||||||
# build all keys, since we want to exclude vouchers beyond the report date
|
if self.filters.get("ignore_accounts"):
|
||||||
for ple in self.ple_entries:
|
key = (ple.voucher_type, ple.voucher_no, ple.party)
|
||||||
# get the balance object for voucher_type
|
else:
|
||||||
|
key = (ple.account, ple.voucher_type, ple.voucher_no, ple.party)
|
||||||
|
|
||||||
if self.filters.get("ignore_accounts"):
|
if key not in self.voucher_balance:
|
||||||
key = (ple.voucher_type, ple.voucher_no, ple.party)
|
self.voucher_balance[key] = self.build_voucher_dict(ple)
|
||||||
else:
|
|
||||||
key = (ple.account, ple.voucher_type, ple.voucher_no, ple.party)
|
|
||||||
|
|
||||||
if key not in self.voucher_balance:
|
if ple.voucher_type == ple.against_voucher_type and ple.voucher_no == ple.against_voucher_no:
|
||||||
self.voucher_balance[key] = self.build_voucher_dict(ple)
|
self.voucher_balance[key].cost_center = ple.cost_center
|
||||||
|
|
||||||
if ple.voucher_type == ple.against_voucher_type and ple.voucher_no == ple.against_voucher_no:
|
self.get_invoices(ple)
|
||||||
self.voucher_balance[key].cost_center = ple.cost_center
|
|
||||||
|
|
||||||
self.get_invoices(ple)
|
if self.filters.get("group_by_party"):
|
||||||
|
self.init_subtotal_row(ple.party)
|
||||||
if self.filters.get("group_by_party"):
|
|
||||||
self.init_subtotal_row(ple.party)
|
|
||||||
|
|
||||||
if self.filters.get("group_by_party") and not self.filters.get("in_party_currency"):
|
if self.filters.get("group_by_party") and not self.filters.get("in_party_currency"):
|
||||||
self.init_subtotal_row("Total")
|
self.init_subtotal_row("Total")
|
||||||
@@ -778,7 +803,7 @@ class ReceivablePayableReport:
|
|||||||
)
|
)
|
||||||
row["range" + str(index + 1)] = row.outstanding
|
row["range" + str(index + 1)] = row.outstanding
|
||||||
|
|
||||||
def get_ple_entries(self):
|
def prepare_ple_query(self):
|
||||||
# get all the GL entries filtered by the given filters
|
# get all the GL entries filtered by the given filters
|
||||||
|
|
||||||
self.prepare_conditions()
|
self.prepare_conditions()
|
||||||
@@ -831,7 +856,7 @@ class ReceivablePayableReport:
|
|||||||
else:
|
else:
|
||||||
query = query.orderby(self.ple.posting_date, self.ple.party)
|
query = query.orderby(self.ple.posting_date, self.ple.party)
|
||||||
|
|
||||||
self.ple_entries = query.run(as_dict=True)
|
self.ple_query = query
|
||||||
|
|
||||||
def get_sales_invoices_or_customers_based_on_sales_person(self):
|
def get_sales_invoices_or_customers_based_on_sales_person(self):
|
||||||
if self.filters.get("sales_person"):
|
if self.filters.get("sales_person"):
|
||||||
|
|||||||
@@ -403,3 +403,4 @@ erpnext.patches.v15_0.rename_sla_fields #2025-03-12
|
|||||||
erpnext.patches.v15_0.set_purchase_receipt_row_item_to_capitalization_stock_item
|
erpnext.patches.v15_0.set_purchase_receipt_row_item_to_capitalization_stock_item
|
||||||
erpnext.patches.v15_0.update_payment_schedule_fields_in_invoices
|
erpnext.patches.v15_0.update_payment_schedule_fields_in_invoices
|
||||||
erpnext.patches.v15_0.rename_group_by_to_categorize_by
|
erpnext.patches.v15_0.rename_group_by_to_categorize_by
|
||||||
|
execute:frappe.db.set_single_value("Accounts Settings", "receivable_payable_fetch_method", "Buffered Cursor")
|
||||||
|
|||||||
Reference in New Issue
Block a user