fix: Stock Ageing report issues for serialized items (#27228)

* fix: incorrect calculation in get_range_age

* fix: remove serial numbers not in stock from fifo_queue

* refactor: make serial_no condition explicit

* refactor: reduce code duplication

Co-authored-by: Ankush Menat <ankush@iwebnotes.com>
diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.py b/erpnext/stock/report/stock_ageing/stock_ageing.py
index 623dc2f..8a9f0a5 100644
--- a/erpnext/stock/report/stock_ageing/stock_ageing.py
+++ b/erpnext/stock/report/stock_ageing/stock_ageing.py
@@ -26,7 +26,7 @@
 		average_age = get_average_age(fifo_queue, to_date)
 		earliest_age = date_diff(to_date, fifo_queue[0][1])
 		latest_age = date_diff(to_date, fifo_queue[-1][1])
-		range1, range2, range3, above_range3 = get_range_age(filters, fifo_queue, to_date)
+		range1, range2, range3, above_range3 = get_range_age(filters, fifo_queue, to_date, item_dict)
 
 		row = [details.name, details.item_name,
 			details.description, details.item_group, details.brand]
@@ -58,19 +58,21 @@
 
 	return flt(age_qty / total_qty, 2) if total_qty else 0.0
 
-def get_range_age(filters, fifo_queue, to_date):
+def get_range_age(filters, fifo_queue, to_date, item_dict):
 	range1 = range2 = range3 = above_range3 = 0.0
+
 	for item in fifo_queue:
 		age = date_diff(to_date, item[1])
+		qty = flt(item[0]) if not item_dict["has_serial_no"] else 1.0
 
 		if age <= filters.range1:
-			range1 += flt(item[0])
+			range1 += qty
 		elif age <= filters.range2:
-			range2 += flt(item[0])
+			range2 += qty
 		elif age <= filters.range3:
-			range3 += flt(item[0])
+			range3 += qty
 		else:
-			above_range3 += flt(item[0])
+			above_range3 += qty
 
 	return range1, range2, range3, above_range3
 
@@ -197,9 +199,7 @@
 					fifo_queue.append([d.actual_qty, d.posting_date])
 		else:
 			if serial_no_list:
-				for serial_no in fifo_queue:
-					if serial_no[0] in serial_no_list:
-						fifo_queue.remove(serial_no)
+				fifo_queue[:] = [serial_no for serial_no in fifo_queue if serial_no[0] not in serial_no_list]
 			else:
 				qty_to_pop = abs(d.actual_qty)
 				while qty_to_pop:
@@ -222,14 +222,16 @@
 		else:
 			item_details[key]["total_qty"] += d.actual_qty
 
+		item_details[key]["has_serial_no"] = d.has_serial_no
+
 	return item_details
 
 def get_stock_ledger_entries(filters):
 	return frappe.db.sql("""select
-			item.name, item.item_name, item_group, brand, description, item.stock_uom,
+			item.name, item.item_name, item_group, brand, description, item.stock_uom, item.has_serial_no,
 			actual_qty, posting_date, voucher_type, voucher_no, serial_no, batch_no, qty_after_transaction, warehouse
 		from `tabStock Ledger Entry` sle,
-			(select name, item_name, description, stock_uom, brand, item_group
+			(select name, item_name, description, stock_uom, brand, item_group, has_serial_no
 				from `tabItem` {item_conditions}) item
 		where item_code = item.name and
 			company = %(company)s and