From c0149925ad93ee437265961617322d1d350240af Mon Sep 17 00:00:00 2001 From: krupalvora Date: Fri, 9 Jan 2026 03:24:30 +0000 Subject: [PATCH 1/5] refactor: Batch & Bundle get Stock ledger for snos --- .../serial_and_batch_bundle.py | 46 ++++++++++++------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index 37904c5e9a2..88e03d62080 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -12,7 +12,7 @@ import frappe.query_builder.functions from frappe import _, _dict, bold from frappe.model.document import Document from frappe.model.naming import make_autoname -from frappe.query_builder.functions import Sum +from frappe.query_builder.functions import Sum, Concat_ws, Locate from frappe.utils import ( cint, cstr, @@ -2985,33 +2985,33 @@ def get_ledgers_from_serial_batch_bundle(**kwargs) -> list[frappe._dict]: def get_stock_ledgers_for_serial_nos(kwargs): stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry") + serial_batch_entry = frappe.qb.DocType("Serial and Batch Entry") + + serial_nos = kwargs.get("serial_nos") or kwargs.get("serial_no") + if serial_nos and not isinstance(serial_nos, list): + serial_nos = [serial_nos] query = ( frappe.qb.from_(stock_ledger_entry) .select( - stock_ledger_entry.posting_datetime, stock_ledger_entry.actual_qty, stock_ledger_entry.serial_no, stock_ledger_entry.serial_and_batch_bundle, ) .where(stock_ledger_entry.is_cancelled == 0) - .orderby(stock_ledger_entry.posting_datetime) - .orderby(stock_ledger_entry.creation) ) - if kwargs.get("posting_datetime"): - timestamp_condition = stock_ledger_entry.posting_datetime <= kwargs.posting_datetime + if kwargs.get("posting_date"): + if kwargs.get("posting_time") is None: + kwargs.posting_time = nowtime() - if kwargs.get("creation"): - timestamp_condition = stock_ledger_entry.posting_datetime < kwargs.posting_datetime - - timestamp_condition |= (stock_ledger_entry.posting_datetime == kwargs.posting_datetime) & ( - stock_ledger_entry.creation < kwargs.creation - ) + timestamp_condition = CombineDatetime( + stock_ledger_entry.posting_date, stock_ledger_entry.posting_time + ) <= CombineDatetime(kwargs.posting_date, kwargs.posting_time) query = query.where(timestamp_condition) - for field in ["warehouse", "item_code", "serial_no"]: + for field in ["warehouse", "item_code"]: if not kwargs.get(field): continue @@ -3020,10 +3020,24 @@ def get_stock_ledgers_for_serial_nos(kwargs): else: query = query.where(stock_ledger_entry[field] == kwargs.get(field)) - if kwargs.ignore_voucher_detail_no: - query = query.where(stock_ledger_entry.voucher_detail_no != kwargs.ignore_voucher_detail_no) + if serial_nos: + query = ( + query.left_join(serial_batch_entry) + .on(stock_ledger_entry.serial_and_batch_bundle == serial_batch_entry.parent) + .distinct() + ) - elif kwargs.voucher_no: + bundle_match = serial_batch_entry.serial_no.isin(serial_nos) + + padded_serial_no = Concat_ws("", "\n", stock_ledger_entry.serial_no, "\n") + direct_match = None + for sn in serial_nos: + cond = Locate(f"\n{sn}\n", padded_serial_no) > 0 + direct_match = cond if direct_match is None else (direct_match | cond) + + query = query.where(bundle_match | direct_match) + + if kwargs.voucher_no: query = query.where(stock_ledger_entry.voucher_no != kwargs.voucher_no) return query.run(as_dict=True) From a074d8175458354afb2f0133eed4a417c64ddfc1 Mon Sep 17 00:00:00 2001 From: krupalvora Date: Sat, 10 Jan 2026 07:00:37 +0000 Subject: [PATCH 2/5] refactor: Batch & Bundle get Stock ledger for snos v2 --- .../serial_and_batch_bundle.py | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index 88e03d62080..fa8477e6db4 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -2987,10 +2987,6 @@ def get_stock_ledgers_for_serial_nos(kwargs): stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry") serial_batch_entry = frappe.qb.DocType("Serial and Batch Entry") - serial_nos = kwargs.get("serial_nos") or kwargs.get("serial_no") - if serial_nos and not isinstance(serial_nos, list): - serial_nos = [serial_nos] - query = ( frappe.qb.from_(stock_ledger_entry) .select( @@ -2999,15 +2995,19 @@ def get_stock_ledgers_for_serial_nos(kwargs): stock_ledger_entry.serial_and_batch_bundle, ) .where(stock_ledger_entry.is_cancelled == 0) + .orderby(stock_ledger_entry.posting_datetime) + .orderby(stock_ledger_entry.creation) ) - if kwargs.get("posting_date"): - if kwargs.get("posting_time") is None: - kwargs.posting_time = nowtime() + if kwargs.get("posting_datetime"): + timestamp_condition = stock_ledger_entry.posting_datetime <= kwargs.posting_datetime - timestamp_condition = CombineDatetime( - stock_ledger_entry.posting_date, stock_ledger_entry.posting_time - ) <= CombineDatetime(kwargs.posting_date, kwargs.posting_time) + if kwargs.get("creation"): + timestamp_condition = stock_ledger_entry.posting_datetime < kwargs.posting_datetime + + timestamp_condition |= (stock_ledger_entry.posting_datetime == kwargs.posting_datetime) & ( + stock_ledger_entry.creation < kwargs.creation + ) query = query.where(timestamp_condition) @@ -3020,6 +3020,10 @@ def get_stock_ledgers_for_serial_nos(kwargs): else: query = query.where(stock_ledger_entry[field] == kwargs.get(field)) + serial_nos = kwargs.get("serial_nos") or kwargs.get("serial_no") + if serial_nos and not isinstance(serial_nos, list): + serial_nos = [serial_nos] + if serial_nos: query = ( query.left_join(serial_batch_entry) @@ -3037,7 +3041,10 @@ def get_stock_ledgers_for_serial_nos(kwargs): query = query.where(bundle_match | direct_match) - if kwargs.voucher_no: + if kwargs.ignore_voucher_detail_no: + query = query.where(stock_ledger_entry.voucher_detail_no != kwargs.ignore_voucher_detail_no) + + elif kwargs.voucher_no: query = query.where(stock_ledger_entry.voucher_no != kwargs.voucher_no) return query.run(as_dict=True) From 1ccc7365a761ce438956f63a13bd2ab2fa18b64f Mon Sep 17 00:00:00 2001 From: krupalvora Date: Sat, 10 Jan 2026 07:03:56 +0000 Subject: [PATCH 3/5] refactor: Batch & Bundle get Stock ledger for snos - added posting date in select --- .../doctype/serial_and_batch_bundle/serial_and_batch_bundle.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index fa8477e6db4..9e11bbe7e8a 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -2990,6 +2990,7 @@ def get_stock_ledgers_for_serial_nos(kwargs): query = ( frappe.qb.from_(stock_ledger_entry) .select( + stock_ledger_entry.posting_datetime, stock_ledger_entry.actual_qty, stock_ledger_entry.serial_no, stock_ledger_entry.serial_and_batch_bundle, From 22dee50348ba8f6769e63987ad717ac5b02d5888 Mon Sep 17 00:00:00 2001 From: krupalvora Date: Sat, 10 Jan 2026 07:10:57 +0000 Subject: [PATCH 4/5] refactor: Batch & Bundle get sle for snos - Added docstring --- .../serial_and_batch_bundle/serial_and_batch_bundle.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index 9e11bbe7e8a..bff0761c148 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -2984,6 +2984,14 @@ def get_ledgers_from_serial_batch_bundle(**kwargs) -> list[frappe._dict]: def get_stock_ledgers_for_serial_nos(kwargs): + """ + Fetch stock ledger entries based on various filters. + + :param kwargs: etch stock ledger entries based on filters like posting_datetime, creation, warehouse, item_code, serial_nos, ignore_voucher_detail_no, voucher_no. Joining with Serial and Batch Entry table to filter based on serial numbers. + :return: List of stock ledger entries as dictionaries. + :rtype: Dictionary + """ + stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry") serial_batch_entry = frappe.qb.DocType("Serial and Batch Entry") From dfcbee9cc0a2ac64409784276695187caaf54198 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 19 Jan 2026 14:22:32 +0530 Subject: [PATCH 5/5] chore: fix semantic commit message --- .../serial_and_batch_bundle/serial_and_batch_bundle.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index bff0761c148..d6065dc0c3b 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -12,7 +12,7 @@ import frappe.query_builder.functions from frappe import _, _dict, bold from frappe.model.document import Document from frappe.model.naming import make_autoname -from frappe.query_builder.functions import Sum, Concat_ws, Locate +from frappe.query_builder.functions import Concat_ws, Locate, Sum from frappe.utils import ( cint, cstr, @@ -2986,10 +2986,9 @@ def get_ledgers_from_serial_batch_bundle(**kwargs) -> list[frappe._dict]: def get_stock_ledgers_for_serial_nos(kwargs): """ Fetch stock ledger entries based on various filters. - - :param kwargs: etch stock ledger entries based on filters like posting_datetime, creation, warehouse, item_code, serial_nos, ignore_voucher_detail_no, voucher_no. Joining with Serial and Batch Entry table to filter based on serial numbers. + :param kwargs: Filters including posting_datetime, creation, warehouse, item_code, serial_nos, ignore_voucher_detail_no, voucher_no. Joins with Serial and Batch Entry table to filter based on serial numbers. :return: List of stock ledger entries as dictionaries. - :rtype: Dictionary + :rtype: list[dict] """ stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry")