From 85bf9366b0c819e04b47a16d067f93c1d8d6e49b Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 27 Oct 2025 13:16:01 +0530 Subject: [PATCH 1/3] fix: optimized the slow query to get the batchwise available qty (cherry picked from commit 9c215673090ea67374aa6312a59fa2f73a7c75f1) # Conflicts: # erpnext/stock/serial_batch_bundle.py --- erpnext/stock/serial_batch_bundle.py | 65 +++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py index fbd30075be3..37d539026e9 100644 --- a/erpnext/stock/serial_batch_bundle.py +++ b/erpnext/stock/serial_batch_bundle.py @@ -731,19 +731,24 @@ class BatchNoValuation(DeprecatedBatchNoValuation): for ledger in entries: self.stock_value_differece[ledger.batch_no] += flt(ledger.incoming_rate) self.available_qty[ledger.batch_no] += flt(ledger.qty) - self.total_qty[ledger.batch_no] += flt(ledger.total_qty) + + entries = self.get_batch_wise_total_available_qty() + for row in entries: + self.total_qty[row.batch_no] += flt(row.total_qty) self.calculate_avg_rate_from_deprecarated_ledgers() self.calculate_avg_rate_for_non_batchwise_valuation() self.set_stock_value_difference() - def get_batch_no_ledgers(self) -> list[dict]: + def get_batch_wise_total_available_qty(self) -> list[dict]: + # Get total qty of each batch no from Serial and Batch Bundle without checking time condition if not self.batchwise_valuation_batches: return [] parent = frappe.qb.DocType("Serial and Batch Bundle") child = frappe.qb.DocType("Serial and Batch Entry") +<<<<<<< HEAD timestamp_condition = "" if self.sle.posting_date: if self.sle.posting_time is None: @@ -759,16 +764,14 @@ class BatchNoValuation(DeprecatedBatchNoValuation): == CombineDatetime(self.sle.posting_date, self.sle.posting_time) ) & (parent.creation < self.sle.creation) +======= +>>>>>>> 9c21567309 (fix: optimized the slow query to get the batchwise available qty) query = ( frappe.qb.from_(parent) .inner_join(child) .on(parent.name == child.parent) .select( child.batch_no, - Sum(Case().when(timestamp_condition, child.stock_value_difference).else_(0)).as_( - "incoming_rate" - ), - Sum(Case().when(timestamp_condition, child.qty).else_(0)).as_("qty"), Sum(child.qty).as_("total_qty"), ) .where( @@ -793,6 +796,56 @@ class BatchNoValuation(DeprecatedBatchNoValuation): return query.run(as_dict=True) + def get_batch_no_ledgers(self) -> list[dict]: + # Get batch wise stock value difference from Serial and Batch Bundle considering time condition + if not self.batchwise_valuation_batches: + return [] + + parent = frappe.qb.DocType("Serial and Batch Bundle") + child = frappe.qb.DocType("Serial and Batch Entry") + + timestamp_condition = "" + if self.sle.posting_datetime: + timestamp_condition = parent.posting_datetime < self.sle.posting_datetime + + if self.sle.creation: + timestamp_condition |= (parent.posting_datetime == self.sle.posting_datetime) & ( + parent.creation < self.sle.creation + ) + + query = ( + frappe.qb.from_(parent) + .inner_join(child) + .on(parent.name == child.parent) + .select( + child.batch_no, + Sum(child.stock_value_difference).as_("incoming_rate"), + Sum(child.qty).as_("qty"), + ) + .where( + (parent.warehouse == self.sle.warehouse) + & (parent.item_code == self.sle.item_code) + & (child.batch_no.isin(self.batchwise_valuation_batches)) + & (parent.docstatus == 1) + & (parent.is_cancelled == 0) + & (parent.type_of_transaction.isin(["Inward", "Outward"])) + ) + .for_update() + .groupby(child.batch_no) + ) + + # Important to exclude the current voucher detail no / voucher no to calculate the correct stock value difference + if self.sle.voucher_detail_no: + query = query.where(parent.voucher_detail_no != self.sle.voucher_detail_no) + elif self.sle.voucher_no: + query = query.where(parent.voucher_no != self.sle.voucher_no) + + query = query.where(parent.voucher_type != "Pick List") + if timestamp_condition: + query = query.where(timestamp_condition) + + return query.run(as_dict=True) + def prepare_batches(self): from erpnext.stock.utils import get_valuation_method From 2d7fde024d859a90bb17868b06fde17c2890ab57 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 27 Oct 2025 16:57:00 +0530 Subject: [PATCH 2/3] chore: fix conflicts --- erpnext/stock/serial_batch_bundle.py | 35 ++++++++++------------------ 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py index 37d539026e9..7365d388fd0 100644 --- a/erpnext/stock/serial_batch_bundle.py +++ b/erpnext/stock/serial_batch_bundle.py @@ -748,24 +748,6 @@ class BatchNoValuation(DeprecatedBatchNoValuation): parent = frappe.qb.DocType("Serial and Batch Bundle") child = frappe.qb.DocType("Serial and Batch Entry") -<<<<<<< HEAD - timestamp_condition = "" - if self.sle.posting_date: - if self.sle.posting_time is None: - self.sle.posting_time = nowtime() - - timestamp_condition = CombineDatetime(parent.posting_date, parent.posting_time) < CombineDatetime( - self.sle.posting_date, self.sle.posting_time - ) - - if self.sle.creation: - timestamp_condition |= ( - CombineDatetime(parent.posting_date, parent.posting_time) - == CombineDatetime(self.sle.posting_date, self.sle.posting_time) - ) & (parent.creation < self.sle.creation) - -======= ->>>>>>> 9c21567309 (fix: optimized the slow query to get the batchwise available qty) query = ( frappe.qb.from_(parent) .inner_join(child) @@ -805,13 +787,20 @@ class BatchNoValuation(DeprecatedBatchNoValuation): child = frappe.qb.DocType("Serial and Batch Entry") timestamp_condition = "" - if self.sle.posting_datetime: - timestamp_condition = parent.posting_datetime < self.sle.posting_datetime + if self.sle.posting_date: + if self.sle.posting_time is None: + self.sle.posting_time = nowtime() + + timestamp_condition = CombineDatetime(parent.posting_date, parent.posting_time) < CombineDatetime( + self.sle.posting_date, self.sle.posting_time + ) if self.sle.creation: - timestamp_condition |= (parent.posting_datetime == self.sle.posting_datetime) & ( - parent.creation < self.sle.creation - ) + timestamp_condition |= ( + CombineDatetime(parent.posting_date, parent.posting_time) + == CombineDatetime(self.sle.posting_date, self.sle.posting_time) + ) & (parent.creation < self.sle.creation) + query = ( frappe.qb.from_(parent) From 2680f8543025c1e8b40d363dc8f3b7f345c2cd6b Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 27 Oct 2025 17:06:45 +0530 Subject: [PATCH 3/3] chore: fix linters issue --- erpnext/stock/serial_batch_bundle.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py index 7365d388fd0..890899c7343 100644 --- a/erpnext/stock/serial_batch_bundle.py +++ b/erpnext/stock/serial_batch_bundle.py @@ -801,7 +801,6 @@ class BatchNoValuation(DeprecatedBatchNoValuation): == CombineDatetime(self.sle.posting_date, self.sle.posting_time) ) & (parent.creation < self.sle.creation) - query = ( frappe.qb.from_(parent) .inner_join(child)