From d16a6d42a5e371dbd8a8fa3a132d7f546622a6a3 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 11 Jun 2025 15:57:08 +0530 Subject: [PATCH] perf: Use lazy loaded documents (#48017) * perf: Use lazy docs for status updaters and similar use cases * perf: lazy load documents while reposting --- .../accounts/doctype/payment_entry/payment_entry.py | 8 ++++---- .../doctype/purchase_invoice/purchase_invoice.py | 12 ++++++------ .../accounts/doctype/sales_invoice/sales_invoice.py | 2 +- erpnext/accounts/utils.py | 8 ++++---- .../buying/doctype/purchase_order/purchase_order.py | 8 ++++---- erpnext/controllers/accounts_controller.py | 7 ++----- erpnext/controllers/budget_controller.py | 2 +- erpnext/controllers/buying_controller.py | 2 +- erpnext/controllers/selling_controller.py | 4 ++-- erpnext/controllers/status_updater.py | 4 ++-- erpnext/controllers/stock_controller.py | 4 ++-- erpnext/selling/doctype/quotation/quotation.py | 2 +- erpnext/selling/doctype/sales_order/sales_order.py | 6 +++--- erpnext/stock/doctype/delivery_note/delivery_note.py | 4 ++-- .../doctype/purchase_receipt/purchase_receipt.py | 8 ++++---- .../repost_item_valuation/repost_item_valuation.py | 2 +- erpnext/stock/stock_ledger.py | 8 ++++---- 17 files changed, 44 insertions(+), 47 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index e35a6e31139..a9e890c947c 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -498,7 +498,7 @@ class PaymentEntry(AccountsController): def delink_advance_entry_references(self): for reference in self.references: if reference.reference_doctype in ("Sales Invoice", "Purchase Invoice"): - doc = frappe.get_doc(reference.reference_doctype, reference.reference_name) + doc = frappe.get_lazy_doc(reference.reference_doctype, reference.reference_name) doc.delink_advance_entries(self.name) def set_missing_values(self): @@ -661,7 +661,7 @@ class PaymentEntry(AccountsController): if not frappe.db.exists(d.reference_doctype, d.reference_name): frappe.throw(_("{0} {1} does not exist").format(d.reference_doctype, d.reference_name)) - ref_doc = frappe.get_doc(d.reference_doctype, d.reference_name) + ref_doc = frappe.get_lazy_doc(d.reference_doctype, d.reference_name) if d.reference_doctype != "Journal Entry": if self.party != ref_doc.get(scrub(self.party_type)): @@ -1785,7 +1785,7 @@ class PaymentEntry(AccountsController): ) for d in self.get("references"): if d.allocated_amount and d.reference_doctype in advance_payment_doctypes: - frappe.get_doc( + frappe.get_lazy_doc( d.reference_doctype, d.reference_name, for_update=True ).set_total_advance_paid() @@ -2865,7 +2865,7 @@ def get_reference_details( ): total_amount = outstanding_amount = exchange_rate = account = None - ref_doc = frappe.get_doc(reference_doctype, reference_name) + ref_doc = frappe.get_lazy_doc(reference_doctype, reference_name) company_currency = ref_doc.get("company_currency") or erpnext.get_company_currency(ref_doc.company) # Only applies for Reverse Payment Entries diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index a9eb75b7260..d212e58e834 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -749,7 +749,7 @@ class PurchaseInvoice(BuyingController): self.update_status_updater_args() self.update_prevdoc_status() - frappe.get_doc("Authorization Control").validate_approving_authority( + frappe.get_cached_doc("Authorization Control").validate_approving_authority( self.doctype, self.company, self.base_grand_total ) @@ -1718,7 +1718,7 @@ class PurchaseInvoice(BuyingController): res = frappe.qb.from_(pj).select(pj.total_purchase_cost).where(pj.name == proj).for_update().run() current_purchase_cost = res and res[0][0] or 0 # frappe.db.set_value("Project", proj, "total_purchase_cost", current_purchase_cost + value) - project_doc = frappe.get_doc("Project", proj) + project_doc = frappe.get_lazy_doc("Project", proj) project_doc.total_purchase_cost = current_purchase_cost + value project_doc.calculate_gross_margin() project_doc.db_update() @@ -1790,7 +1790,7 @@ class PurchaseInvoice(BuyingController): for pr in set(updated_pr): from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_billing_percentage - pr_doc = frappe.get_doc("Purchase Receipt", pr) + pr_doc = frappe.get_lazy_doc("Purchase Receipt", pr) update_billing_percentage( pr_doc, update_modified=update_modified, adjust_incoming_rate=adjust_incoming_rate ) @@ -2046,21 +2046,21 @@ def make_stock_entry(source_name, target_doc=None): @frappe.whitelist() def change_release_date(name, release_date=None): if frappe.db.exists("Purchase Invoice", name): - pi = frappe.get_doc("Purchase Invoice", name) + pi = frappe.get_lazy_doc("Purchase Invoice", name) pi.db_set("release_date", release_date) @frappe.whitelist() def unblock_invoice(name): if frappe.db.exists("Purchase Invoice", name): - pi = frappe.get_doc("Purchase Invoice", name) + pi = frappe.get_lazy_doc("Purchase Invoice", name) pi.unblock_invoice() @frappe.whitelist() def block_invoice(name, release_date, hold_comment=None): if frappe.db.exists("Purchase Invoice", name): - pi = frappe.get_doc("Purchase Invoice", name) + pi = frappe.get_lazy_doc("Purchase Invoice", name) pi.block_invoice(hold_comment, release_date) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index b3b259cf825..bee314e0b82 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -448,7 +448,7 @@ class SalesInvoice(SellingController): self.validate_pos_paid_amount() if not self.auto_repeat: - frappe.get_doc("Authorization Control").validate_approving_authority( + frappe.get_cached_doc("Authorization Control").validate_approving_authority( self.doctype, self.company, self.base_grand_total, self ) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 9fe24857672..3d9b7903ae7 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -236,7 +236,7 @@ def get_balance_on( report_type = "" if cost_center and report_type == "Profit and Loss": - cc = frappe.get_doc("Cost Center", cost_center) + cc = frappe.get_lazy_doc("Cost Center", cost_center) if cc.is_group: cond.append( f""" exists ( @@ -555,7 +555,7 @@ def reconcile_against_document( # update advance paid in Advance Receivable/Payable doctypes if update_advance_paid: for t, n in update_advance_paid: - frappe.get_doc(t, n).set_total_advance_paid() + frappe.get_lazy_doc(t, n).set_total_advance_paid() frappe.flags.ignore_party_validation = False @@ -1467,7 +1467,7 @@ def repost_gle_for_stock_vouchers( for voucher_type, voucher_no in stock_vouchers_chunk: existing_gle = gle.get((voucher_type, voucher_no), []) - voucher_obj = frappe.get_doc(voucher_type, voucher_no) + voucher_obj = frappe.get_lazy_doc(voucher_type, voucher_no) # Some transactions post credit as negative debit, this is handled while posting GLE # but while comparing we need to make sure it's flipped so comparisons are accurate expected_gle = toggle_debit_credit_if_negative(voucher_obj.get_gl_entries(warehouse_account)) @@ -1891,7 +1891,7 @@ def update_voucher_outstanding(voucher_type, voucher_no, account, party_type, pa and voucher_outstanding ): outstanding = voucher_outstanding[0] - ref_doc = frappe.get_doc(voucher_type, voucher_no) + ref_doc = frappe.get_lazy_doc(voucher_type, voucher_no) outstanding_amount = flt( outstanding["outstanding_in_account_currency"], ref_doc.precision("outstanding_amount") ) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 367299fa634..d37b33f9540 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -498,7 +498,7 @@ class PurchaseOrder(BuyingController): self.validate_budget() self.update_reserved_qty_for_subcontract() - frappe.get_doc("Authorization Control").validate_approving_authority( + frappe.get_cached_doc("Authorization Control").validate_approving_authority( self.doctype, self.company, self.base_grand_total ) @@ -594,7 +594,7 @@ class PurchaseOrder(BuyingController): sales_orders_to_update.append(item.sales_order) for so_name in sales_orders_to_update: - so = frappe.get_doc("Sales Order", so_name) + so = frappe.get_lazy_doc("Sales Order", so_name) so.update_delivery_status() so.set_status(update=True) so.notify_update() @@ -725,7 +725,7 @@ def close_or_unclose_purchase_orders(names, status): names = json.loads(names) for name in names: - po = frappe.get_doc("Purchase Order", name) + po = frappe.get_lazy_doc("Purchase Order", name) if po.docstatus == 1: if status == "Closed": if po.status not in ("Cancelled", "Closed") and ( @@ -902,7 +902,7 @@ def get_list_context(context=None): @frappe.whitelist() def update_status(status, name): - po = frappe.get_doc("Purchase Order", name) + po = frappe.get_lazy_doc("Purchase Order", name) po.update_status(status) po.update_delivered_qty_in_sales_order() diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index cd1bcf5f489..90a2fe07ff0 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -150,10 +150,7 @@ class AccountsController(TransactionBase): supplier = None if supplier_name: - supplier = frappe.get_doc( - "Supplier", - supplier_name, - ) + supplier = frappe.get_lazy_doc("Supplier", supplier_name) if supplier and supplier.on_hold: if (is_buying_invoice and supplier.hold_type in ["All", "Invoices"]) or ( @@ -3918,7 +3915,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil if parent_doctype == "Sales Order": make_packing_list(parent) parent.set_gross_profit() - frappe.get_doc("Authorization Control").validate_approving_authority( + frappe.get_cached_doc("Authorization Control").validate_approving_authority( parent.doctype, parent.company, parent.base_grand_total ) diff --git a/erpnext/controllers/budget_controller.py b/erpnext/controllers/budget_controller.py index d7461afc4b0..6a9e6ae316d 100644 --- a/erpnext/controllers/budget_controller.py +++ b/erpnext/controllers/budget_controller.py @@ -154,7 +154,7 @@ class BudgetValidation: def get_dimensions(self): self.dimensions = [] for _x in frappe.db.get_all("Accounting Dimension"): - self.dimensions.append(frappe.get_doc("Accounting Dimension", _x.name)) + self.dimensions.append(frappe.get_lazy_doc("Accounting Dimension", _x.name)) self.dimensions.extend( [ {"fieldname": "cost_center", "document_type": "Cost Center"}, diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 830b6b32cff..4e7e27f30ac 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -793,7 +793,7 @@ class BuyingController(SubcontractingController): for po, po_item_rows in po_map.items(): if po and po_item_rows: - po_obj = frappe.get_doc("Purchase Order", po) + po_obj = frappe.get_lazy_doc("Purchase Order", po) if po_obj.status in ["Closed", "Cancelled"]: frappe.throw( diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 013953e9818..bca73736922 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -123,7 +123,7 @@ class SellingController(StockController): def remove_shipping_charge(self): if self.shipping_rule: - shipping_rule = frappe.get_doc("Shipping Rule", self.shipping_rule) + shipping_rule = frappe.get_last_doc("Shipping Rule", self.shipping_rule) existing_shipping_charge = self.get( "taxes", { @@ -463,7 +463,7 @@ class SellingController(StockController): for so, so_item_rows in so_map.items(): if so and so_item_rows: - sales_order = frappe.get_doc("Sales Order", so) + sales_order = frappe.get_lazy_doc("Sales Order", so) if (sales_order.status == "Closed" and not self.is_return) or sales_order.status in [ "Cancelled" diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index facfff7ded8..e9649e50d44 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -559,7 +559,7 @@ class StatusUpdater(Document): ) if update_data: - target = frappe.get_doc(args["target_parent_dt"], args["name"]) + target = frappe.get_lazy_doc(args["target_parent_dt"], args["name"]) target.update(update_data) # status calculus might depend on it status = target.get_status() if status.get("status"): @@ -619,7 +619,7 @@ class StatusUpdater(Document): per_billed = safe_div(min(ref_doc_qty, billed_qty), ref_doc_qty) * 100 - ref_doc = frappe.get_doc(ref_dt, ref_dn) + ref_doc = frappe.get_lazy_doc(ref_dt, ref_dn) ref_doc.db_set("per_billed", per_billed) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 0e542944b0e..ed8936ef639 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -1444,7 +1444,7 @@ class StockController(AccountsController): @frappe.whitelist() def show_accounting_ledger_preview(company, doctype, docname): filters = frappe._dict(company=company, include_dimensions=1) - doc = frappe.get_doc(doctype, docname) + doc = frappe.get_lazy_doc(doctype, docname) doc.run_method("before_gl_preview") gl_columns, gl_data = get_accounting_ledger_preview(doc, filters) @@ -1457,7 +1457,7 @@ def show_accounting_ledger_preview(company, doctype, docname): @frappe.whitelist() def show_stock_ledger_preview(company, doctype, docname): filters = frappe._dict(company=company) - doc = frappe.get_doc(doctype, docname) + doc = frappe.get_lazy_doc(doctype, docname) doc.run_method("before_sl_preview") sl_columns, sl_data = get_stock_ledger_preview(doc, filters) diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py index 9529cffb364..82a613e4744 100644 --- a/erpnext/selling/doctype/quotation/quotation.py +++ b/erpnext/selling/doctype/quotation/quotation.py @@ -284,7 +284,7 @@ class Quotation(SellingController): def on_submit(self): # Check for Approving Authority - frappe.get_doc("Authorization Control").validate_approving_authority( + frappe.get_cached_doc("Authorization Control").validate_approving_authority( self.doctype, self.company, self.base_grand_total, self ) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 10f888ce12e..f0e9d33f700 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -435,7 +435,7 @@ class SalesOrder(SellingController): self.check_credit_limit() self.update_reserved_qty() - frappe.get_doc("Authorization Control").validate_approving_authority( + frappe.get_cached_doc("Authorization Control").validate_approving_authority( self.doctype, self.company, self.base_grand_total, self ) self.update_project() @@ -487,7 +487,7 @@ class SalesOrder(SellingController): return if self.project: - project = frappe.get_doc("Project", self.project) + project = frappe.get_lazy_doc("Project", self.project) project.update_sales_amount() project.db_update() @@ -825,7 +825,7 @@ def close_or_unclose_sales_orders(names, status): names = json.loads(names) for name in names: - so = frappe.get_doc("Sales Order", name) + so = frappe.get_lazy_doc("Sales Order", name) if so.docstatus == 1: if status == "Closed": if so.status not in ("Cancelled", "Closed") and ( diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 0c390ce9085..e93bd8ce599 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -639,7 +639,7 @@ class DeliveryNote(SellingController): updated_delivery_notes += update_billed_amount_based_on_so(d.so_detail, update_modified) for dn in set(updated_delivery_notes): - dn_doc = self if (dn == self.name) else frappe.get_doc("Delivery Note", dn) + dn_doc = self if (dn == self.name) else frappe.get_lazy_doc("Delivery Note", dn) dn_doc.update_billing_percentage(update_modified=update_modified) self.load_from_db() @@ -1102,7 +1102,7 @@ def make_sales_return(source_name, target_doc=None): @frappe.whitelist() def update_delivery_note_status(docname, status): - dn = frappe.get_doc("Delivery Note", docname) + dn = frappe.get_lazy_doc("Delivery Note", docname) dn.update_status(status) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 2a564d862c0..62aa0460b9c 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -365,7 +365,7 @@ class PurchaseReceipt(BuyingController): super().on_submit() # Check for Approving Authority - frappe.get_doc("Authorization Control").validate_approving_authority( + frappe.get_cached_doc("Authorization Control").validate_approving_authority( self.doctype, self.company, self.base_grand_total ) @@ -942,7 +942,7 @@ class PurchaseReceipt(BuyingController): updated_pr += update_billed_amount_based_on_po(po_details, update_modified, self) for pr in set(updated_pr): - pr_doc = self if (pr == self.name) else frappe.get_doc("Purchase Receipt", pr) + pr_doc = self if (pr == self.name) else frappe.get_lazy_doc("Purchase Receipt", pr) update_billing_percentage(pr_doc, update_modified=update_modified) def reserve_stock(self): @@ -980,7 +980,7 @@ class PurchaseReceipt(BuyingController): ) for so, items_details in so_items_details_map.items(): - so_doc = frappe.get_doc("Sales Order", so) + so_doc = frappe.get_lazy_doc("Sales Order", so) so_doc.create_stock_reservation_entries( items_details=items_details, from_voucher_type="Purchase Receipt", @@ -1463,7 +1463,7 @@ def make_purchase_return(source_name, target_doc=None): @frappe.whitelist() def update_purchase_receipt_status(docname, status): - pr = frappe.get_doc("Purchase Receipt", docname) + pr = frappe.get_lazy_doc("Purchase Receipt", docname) pr.update_status(status) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 6e92d33131c..b8adbd08518 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -390,7 +390,7 @@ def _get_directly_dependent_vouchers(doc): warehouses = set() if doc.based_on == "Transaction": - ref_doc = frappe.get_doc(doc.voucher_type, doc.voucher_no) + ref_doc = frappe.get_lazy_doc(doc.voucher_type, doc.voucher_no) doc_items, doc_warehouses = ref_doc.get_items_and_warehouses() items.update(doc_items) warehouses.update(doc_warehouses) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 8d3c92c8736..15856a2449b 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1225,7 +1225,7 @@ class update_entries_after: return False def recalculate_amounts_in_stock_entry(self, voucher_no, voucher_detail_no): - stock_entry = frappe.get_doc("Stock Entry", voucher_no, for_update=True) + stock_entry = frappe.get_lazy_doc("Stock Entry", voucher_no, for_update=True) stock_entry.calculate_rate_and_amount(reset_outgoing_rate=False, raise_error_if_no_rate=False) stock_entry.db_update() for d in stock_entry.items: @@ -1268,7 +1268,7 @@ class update_entries_after: # Recalculate subcontracted item's rate in case of subcontracted purchase receipt/invoice if frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_subcontracted"): - doc = frappe.get_doc(sle.voucher_type, sle.voucher_no) + doc = frappe.get_lazy_doc(sle.voucher_type, sle.voucher_no) doc.update_valuation_rate(reset_outgoing_rate=False) for d in doc.items + doc.supplied_items: d.db_update() @@ -1283,7 +1283,7 @@ class update_entries_after: {"rate": outgoing_rate, "amount": abs(sle.actual_qty) * outgoing_rate}, ) - scr = frappe.get_doc("Subcontracting Receipt", sle.voucher_no, for_update=True) + scr = frappe.get_lazy_doc("Subcontracting Receipt", sle.voucher_no, for_update=True) scr.calculate_items_qty_and_amount() scr.db_update() for d in scr.items: @@ -1291,7 +1291,7 @@ class update_entries_after: def update_rate_on_stock_reconciliation(self, sle): if not sle.serial_no and not sle.batch_no: - sr = frappe.get_doc("Stock Reconciliation", sle.voucher_no, for_update=True) + sr = frappe.get_lazy_doc("Stock Reconciliation", sle.voucher_no, for_update=True) for item in sr.items: # Skip for Serial and Batch Items