Merge pull request #23675 from rohitwaghchaure/feat-added-balance-serial-no-column-develop

 feat: balance serial nos in stock ledger report
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index a094e6c..3b62c38 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -1188,8 +1188,7 @@
 
 	if item_code:
 		item_cache = ItemVariantsCacheManager(item_code)
-		item_cache.clear_cache()
-
+		item_cache.rebuild_cache()
 
 def check_stock_uom_with_bin(item, stock_uom):
 	if stock_uom == frappe.db.get_value("Item", item, "stock_uom"):
diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py
index fe8ad71..86af5e0 100644
--- a/erpnext/stock/report/stock_ledger/stock_ledger.py
+++ b/erpnext/stock/report/stock_ledger/stock_ledger.py
@@ -7,6 +7,7 @@
 from frappe.utils import cint, flt
 from erpnext.stock.utils import update_included_uom_in_report
 from frappe import _
+from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
 
 def execute(filters=None):
 	include_uom = filters.get("include_uom")
@@ -24,6 +25,7 @@
 
 	actual_qty = stock_value = 0
 
+	available_serial_nos = {}
 	for sle in sl_entries:
 		item_detail = item_details[sle.item_code]
 
@@ -47,6 +49,9 @@
 			"out_qty": min(sle.actual_qty, 0)
 		})
 
+		if sle.serial_no:
+			update_available_serial_nos(available_serial_nos, sle)
+
 		data.append(sle)
 
 		if include_uom:
@@ -55,6 +60,26 @@
 	update_included_uom_in_report(columns, data, include_uom, conversion_factors)
 	return columns, data
 
+def update_available_serial_nos(available_serial_nos, sle):
+	serial_nos = get_serial_nos(sle.serial_no)
+	key = (sle.item_code, sle.warehouse)
+	if key not in available_serial_nos:
+		available_serial_nos.setdefault(key, [])
+
+	existing_serial_no = available_serial_nos[key]
+	for sn in serial_nos:
+		if sle.actual_qty > 0:
+			if sn in existing_serial_no:
+				existing_serial_no.remove(sn)
+			else:
+				existing_serial_no.append(sn)
+		else:
+			if sn in existing_serial_no:
+				existing_serial_no.remove(sn)
+			else:
+				existing_serial_no.append(sn)
+
+	sle.balance_serial_no = '\n'.join(existing_serial_no)
 
 def get_columns():
 	columns = [
@@ -76,7 +101,8 @@
 		{"label": _("Voucher Type"), "fieldname": "voucher_type", "width": 110},
 		{"label": _("Voucher #"), "fieldname": "voucher_no", "fieldtype": "Dynamic Link", "options": "voucher_type", "width": 100},
 		{"label": _("Batch"), "fieldname": "batch_no", "fieldtype": "Link", "options": "Batch", "width": 100},
-		{"label": _("Serial #"), "fieldname": "serial_no", "fieldtype": "Link", "options": "Serial No", "width": 100},
+		{"label": _("Serial No"), "fieldname": "serial_no", "fieldtype": "Link", "options": "Serial No", "width": 100},
+		{"label": _("Balance Serial No"), "fieldname": "balance_serial_no", "width": 100},
 		{"label": _("Project"), "fieldname": "project", "fieldtype": "Link", "options": "Project", "width": 100},
 		{"label": _("Company"), "fieldname": "company", "fieldtype": "Link", "options": "Company", "width": 110}
 	]