From f1f66bdf2faa7ad72f715990513cca1ea9c0f64b Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 5 Jun 2026 12:12:36 +0530 Subject: [PATCH] perf(stock): cache is_serial_batch_item via Item document cache The @frappe.request_cache decorator keyed on `self`, which after the service extraction is a transient SerialBatchBundleService built per delegated call, so the request-wide dedup was lost and dead instances were pinned in request_cache. Use frappe.get_cached_value on the Item instead: caching is keyed by the item (request- local + redis), effective regardless of service-instance churn, and the redundant frappe.db.exists query is dropped. Verified: ledger snapshots + serial and batch bundle suite stay green. --- erpnext/stock/services/serial_batch_bundle.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/erpnext/stock/services/serial_batch_bundle.py b/erpnext/stock/services/serial_batch_bundle.py index cf98a0aea5c..d628b404aa2 100644 --- a/erpnext/stock/services/serial_batch_bundle.py +++ b/erpnext/stock/services/serial_batch_bundle.py @@ -376,17 +376,14 @@ class SerialBatchBundleService: return field, reference_ids - @frappe.request_cache def is_serial_batch_item(self, item_code) -> bool: - if not frappe.db.exists("Item", item_code): + item_details = frappe.get_cached_value( + "Item", item_code, ["has_serial_no", "has_batch_no"], as_dict=True + ) + if not item_details: frappe.throw(_("Item {0} does not exist.").format(bold(item_code))) - item_details = frappe.db.get_value("Item", item_code, ["has_serial_no", "has_batch_no"], as_dict=1) - - if item_details.has_serial_no or item_details.has_batch_no: - return True - - return False + return bool(item_details.has_serial_no or item_details.has_batch_no) def update_bundle_details(self, bundle_details, table_name, row, is_rejected=False, parent_details=None): from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos