fix: stock ledger balance qty for the batch (#40274)

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 d01dfef..dd59a5d 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
@@ -847,7 +847,7 @@
 
 		available_batches = get_available_batches_qty(available_batches)
 		for batch_no in batches:
-			if batch_no not in available_batches or available_batches[batch_no] < 0:
+			if batch_no in available_batches and available_batches[batch_no] < 0:
 				if flt(available_batches.get(batch_no)) < 0:
 					self.validate_negative_batch(batch_no, available_batches[batch_no])
 
diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.js b/erpnext/stock/report/stock_ledger/stock_ledger.js
index 2ec757b..baccd55 100644
--- a/erpnext/stock/report/stock_ledger/stock_ledger.js
+++ b/erpnext/stock/report/stock_ledger/stock_ledger.js
@@ -58,7 +58,15 @@
 			"fieldname":"batch_no",
 			"label": __("Batch No"),
 			"fieldtype": "Link",
-			"options": "Batch"
+			"options": "Batch",
+			on_change() {
+				const batch_no = frappe.query_report.get_filter_value('batch_no');
+				if (batch_no) {
+					frappe.query_report.set_filter_value('segregate_serial_batch_bundle', 1);
+				} else {
+					frappe.query_report.set_filter_value('segregate_serial_batch_bundle', 0);
+				}
+			}
 		},
 		{
 			"fieldname":"brand",
diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py
index d859f4e..2e4b08c 100644
--- a/erpnext/stock/report/stock_ledger/stock_ledger.py
+++ b/erpnext/stock/report/stock_ledger/stock_ledger.py
@@ -3,6 +3,7 @@
 
 
 import copy
+from collections import defaultdict
 
 import frappe
 from frappe import _
@@ -31,7 +32,7 @@
 	bundle_details = {}
 
 	if filters.get("segregate_serial_batch_bundle"):
-		bundle_details = get_serial_batch_bundle_details(sl_entries)
+		bundle_details = get_serial_batch_bundle_details(sl_entries, filters)
 
 	data = []
 	conversion_factors = []
@@ -47,12 +48,13 @@
 	available_serial_nos = {}
 	inventory_dimension_filters_applied = check_inventory_dimension_filters_applied(filters)
 
+	batch_balance_dict = defaultdict(float)
 	for sle in sl_entries:
 		item_detail = item_details[sle.item_code]
 
 		sle.update(item_detail)
 		if bundle_info := bundle_details.get(sle.serial_and_batch_bundle):
-			data.extend(get_segregated_bundle_entries(sle, bundle_info))
+			data.extend(get_segregated_bundle_entries(sle, bundle_info, batch_balance_dict))
 			continue
 
 		if filters.get("batch_no") or inventory_dimension_filters_applied:
@@ -85,7 +87,7 @@
 	return columns, data
 
 
-def get_segregated_bundle_entries(sle, bundle_details):
+def get_segregated_bundle_entries(sle, bundle_details, batch_balance_dict):
 	segregated_entries = []
 	qty_before_transaction = sle.qty_after_transaction - sle.actual_qty
 	stock_value_before_transaction = sle.stock_value - sle.stock_value_difference
@@ -93,7 +95,6 @@
 	for row in bundle_details:
 		new_sle = copy.deepcopy(sle)
 		new_sle.update(row)
-
 		new_sle.update(
 			{
 				"in_out_rate": flt(new_sle.stock_value_difference / row.qty) if row.qty else 0,
@@ -105,6 +106,10 @@
 			}
 		)
 
+		if row.batch_no:
+			batch_balance_dict[row.batch_no] += row.qty
+			new_sle.update({"qty_after_transaction": batch_balance_dict[row.batch_no]})
+
 		qty_before_transaction += row.qty
 		stock_value_before_transaction += new_sle.stock_value_difference
 
@@ -117,7 +122,7 @@
 	return segregated_entries
 
 
-def get_serial_batch_bundle_details(sl_entries):
+def get_serial_batch_bundle_details(sl_entries, filters=None):
 	bundle_details = []
 	for sle in sl_entries:
 		if sle.serial_and_batch_bundle:
@@ -126,10 +131,14 @@
 	if not bundle_details:
 		return frappe._dict({})
 
+	query_filers = {"parent": ("in", bundle_details)}
+	if filters.get("batch_no"):
+		query_filers["batch_no"] = filters.batch_no
+
 	_bundle_details = frappe._dict({})
 	batch_entries = frappe.get_all(
 		"Serial and Batch Entry",
-		filters={"parent": ("in", bundle_details)},
+		filters=query_filers,
 		fields=["parent", "qty", "incoming_rate", "stock_value_difference", "batch_no", "serial_no"],
 		order_by="parent, idx",
 	)
diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py
index 1fcc439..12df0fab 100644
--- a/erpnext/stock/serial_batch_bundle.py
+++ b/erpnext/stock/serial_batch_bundle.py
@@ -325,9 +325,7 @@
 			batches = frappe._dict({self.sle.batch_no: self.sle.actual_qty})
 
 		batches_qty = get_available_batches(
-			frappe._dict(
-				{"item_code": self.item_code, "warehouse": self.warehouse, "batch_no": list(batches.keys())}
-			)
+			frappe._dict({"item_code": self.item_code, "batch_no": list(batches.keys())})
 		)
 
 		for batch_no in batches: