From 013011aa43dec308dccd95c111c66f294ae61dfb Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 4 Jun 2025 15:44:57 +0530 Subject: [PATCH 1/7] perf: cache existence of budgets while validating GL --- erpnext/accounts/doctype/budget/budget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/budget/budget.py b/erpnext/accounts/doctype/budget/budget.py index 6fe4e8edcf7..11fa72c3c2f 100644 --- a/erpnext/accounts/doctype/budget/budget.py +++ b/erpnext/accounts/doctype/budget/budget.py @@ -145,7 +145,7 @@ class Budget(Document): def validate_expense_against_budget(args, expense_amount=0): args = frappe._dict(args) - if not frappe.get_all("Budget", limit=1): + if not frappe.db.count("Budget", cache=True): return if args.get("company") and not args.fiscal_year: From 9870dd26f717dea89cacfa9bfbdb8fa760312867 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 4 Jun 2025 15:55:15 +0530 Subject: [PATCH 2/7] perf: avoid querying cost center allocation repeatedly --- .../doctype/cost_center_allocation/cost_center_allocation.py | 4 ++++ erpnext/accounts/general_ledger.py | 2 ++ 2 files changed, 6 insertions(+) diff --git a/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py index 34b2ec68743..52a92acd55e 100644 --- a/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py +++ b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py @@ -154,3 +154,7 @@ class CostCenterAllocation(Document): ).format(d.cost_center), InvalidChildCostCenter, ) + + def clear_cache(self): + frappe.clear_cache(doctype="Cost Center") + return super().clear_cache() diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index edf8c2e3581..c30560b998d 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -8,6 +8,7 @@ import frappe from frappe import _ from frappe.model.meta import get_field_precision from frappe.utils import cint, flt, formatdate, get_link_to_form, getdate, now +from frappe.utils.caching import request_cache from frappe.utils.dashboard import cache_source import erpnext @@ -222,6 +223,7 @@ def distribute_gl_based_on_cost_center_allocation(gl_map, precision=None, from_r return new_gl_map +@request_cache def get_cost_center_allocation_data(company, posting_date, cost_center): cost_center_allocation = frappe.db.get_value( "Cost Center Allocation", From c06a36134806ced0b6198f69cb1317f1042bc813 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 4 Jun 2025 15:55:53 +0530 Subject: [PATCH 3/7] fix: get_value doesn't support named plucking --- erpnext/accounts/general_ledger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index c30560b998d..786ddfc64c3 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -233,7 +233,7 @@ def get_cost_center_allocation_data(company, posting_date, cost_center): "valid_from": ("<=", posting_date), "main_cost_center": cost_center, }, - pluck="name", + pluck=True, order_by="valid_from desc", ) From 4dc2969fa6178eaaea56a9d11c759089ac55c099 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 4 Jun 2025 16:57:36 +0530 Subject: [PATCH 4/7] perf: avoid duplicate fetching of stock qty --- erpnext/controllers/selling_controller.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index dff14d91daa..9a70f6c3636 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -21,7 +21,11 @@ class SellingController(StockController): def onload(self): super().onload() - if self.doctype in ("Sales Order", "Delivery Note", "Sales Invoice", "Quotation"): + if ( + self.doctype in ("Sales Order", "Delivery Note", "Sales Invoice", "Quotation") + and self.docstatus.is_draft() + and not hasattr(self, "_action") + ): for item in self.get("items") + (self.get("packed_items") or []): company = self.company From a36daec9399a63e460e347285df712ec445f6674 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 4 Jun 2025 17:07:33 +0530 Subject: [PATCH 5/7] perf: cache pricing rule query --- erpnext/accounts/doctype/pricing_rule/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index e4a85b86b83..ff1ccfd352a 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -28,7 +28,7 @@ def get_pricing_rules(args, doc=None): pricing_rules = [] values = {} - if not frappe.db.exists("Pricing Rule", {"disable": 0, args.transaction_type: 1}): + if not frappe.db.count("Pricing Rule", cache=True): return for apply_on in ["Item Code", "Item Group", "Brand"]: From 8e17054b675bc17cc8064fb0ee273b6035a1e092 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 4 Jun 2025 17:13:41 +0530 Subject: [PATCH 6/7] fix: invalid logic for cache hit if there are no inventory dimensions then `[]` gets treated as a miss. --- .../inventory_dimension.py | 35 ++++++++----------- .../test_inventory_dimension.py | 5 +-- 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py index 3eca690538a..3480926f0e6 100644 --- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py +++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py @@ -364,28 +364,21 @@ def get_evaluated_inventory_dimension(doc, sl_dict, parent_doc=None): return filter_dimensions +@request_cache def get_document_wise_inventory_dimensions(doctype) -> dict: - if not hasattr(frappe.local, "document_wise_inventory_dimensions"): - frappe.local.document_wise_inventory_dimensions = {} - - if not frappe.local.document_wise_inventory_dimensions.get(doctype): - dimensions = frappe.get_all( - "Inventory Dimension", - fields=[ - "name", - "source_fieldname", - "condition", - "target_fieldname", - "type_of_transaction", - "fetch_from_parent", - ], - filters={"disabled": 0}, - or_filters={"document_type": doctype, "apply_to_all_doctypes": 1}, - ) - - frappe.local.document_wise_inventory_dimensions[doctype] = dimensions - - return frappe.local.document_wise_inventory_dimensions[doctype] + return frappe.get_all( + "Inventory Dimension", + fields=[ + "name", + "source_fieldname", + "condition", + "target_fieldname", + "type_of_transaction", + "fetch_from_parent", + ], + filters={"disabled": 0}, + or_filters={"document_type": doctype, "apply_to_all_doctypes": 1}, + ) @frappe.whitelist() diff --git a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py index 2272ea5e50e..ad3f3d59436 100644 --- a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py +++ b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py @@ -87,7 +87,7 @@ class TestInventoryDimension(IntegrationTestCase): self.assertFalse(custom_field) def test_inventory_dimension(self): - frappe.local.document_wise_inventory_dimensions = {} + frappe.clear_cache(doctype="Inventory Dimension") warehouse = "Shelf Warehouse - _TC" item_code = "_Test Item" @@ -159,7 +159,8 @@ class TestInventoryDimension(IntegrationTestCase): self.assertRaises(DoNotChangeError, inv_dim1.save) def test_inventory_dimension_for_purchase_receipt_and_delivery_note(self): - frappe.local.document_wise_inventory_dimensions = {} + frappe.clear_cache(doctype="Inventory Dimension") + frappe.clear_cache("Inventory Dimension") inv_dimension = create_inventory_dimension( reference_document="Rack", dimension_name="Rack", apply_to_all_doctypes=1 From 958cc6f8f854ca8ee70c219b80d60958fd393fde Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 4 Jun 2025 19:04:24 +0530 Subject: [PATCH 7/7] perf: use estimated count on item table --- erpnext/controllers/queries.py | 2 +- .../doctype/inventory_dimension/test_inventory_dimension.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 3f09c7dd5cc..c5d4b0b3418 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -236,7 +236,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals filters.pop("supplier", None) description_cond = "" - if frappe.db.count(doctype, cache=True) < 50000: + if frappe.db.estimate_count(doctype) < 50000: # scan description only if items are less than 50000 description_cond = "or tabItem.description LIKE %(txt)s" diff --git a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py index ad3f3d59436..f0aab97933d 100644 --- a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py +++ b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py @@ -160,7 +160,6 @@ class TestInventoryDimension(IntegrationTestCase): def test_inventory_dimension_for_purchase_receipt_and_delivery_note(self): frappe.clear_cache(doctype="Inventory Dimension") - frappe.clear_cache("Inventory Dimension") inv_dimension = create_inventory_dimension( reference_document="Rack", dimension_name="Rack", apply_to_all_doctypes=1