test: Negative Stock, over consumption & over production with split rows, balance precision
diff --git a/erpnext/stock/report/stock_ageing/test_stock_ageing.py b/erpnext/stock/report/stock_ageing/test_stock_ageing.py
index 3055332..3fc357e 100644
--- a/erpnext/stock/report/stock_ageing/test_stock_ageing.py
+++ b/erpnext/stock/report/stock_ageing/test_stock_ageing.py
@@ -3,7 +3,7 @@
 
 import frappe
 
-from erpnext.stock.report.stock_ageing.stock_ageing import FIFOSlots
+from erpnext.stock.report.stock_ageing.stock_ageing import FIFOSlots, format_report_data
 from erpnext.tests.utils import ERPNextTestCase
 
 
@@ -11,7 +11,8 @@
 	def setUp(self) -> None:
 		self.filters = frappe._dict(
 			company="_Test Company",
-			to_date="2021-12-10"
+			to_date="2021-12-10",
+			range1=30, range2=60, range3=90
 		)
 
 	def test_normal_inward_outward_queue(self):
@@ -289,7 +290,8 @@
 
 		self.assertEqual(item_result["total_qty"], 500.0)
 		self.assertEqual(queue[0][0], 400.0)
-		self.assertEqual(queue[1][0], 100.0)
+		self.assertEqual(queue[1][0], 50.0)
+		self.assertEqual(queue[2][0], 50.0)
 		# check if time buckets add up to balance qty
 		self.assertEqual(sum([i[0] for i in queue]), 500.0)
 
@@ -341,6 +343,63 @@
 		# check if time buckets add up to balance qty
 		self.assertEqual(sum([i[0] for i in queue]), 450.0)
 
+	def test_repack_entry_same_item_overconsume_with_split_rows(self):
+		"""
+		Over consume item and have less repacked item qty (same warehouse).
+		Ledger:
+		Item	| Qty  | Voucher
+		------------------------
+		Item 1  | 20   | 001
+		Item 1  | -50  | 002 (repack)
+		Item 1  | -50  | 002 (repack)
+		Item 1  | 50   | 002 (repack)
+		"""
+		sle = [
+			frappe._dict( # stock up item
+				name="Flask Item",
+				actual_qty=20, qty_after_transaction=20,
+				warehouse="WH 1",
+				posting_date="2021-12-03", voucher_type="Stock Entry",
+				voucher_no="001",
+				has_serial_no=False, serial_no=None
+			),
+			frappe._dict(
+				name="Flask Item",
+				actual_qty=(-50), qty_after_transaction=(-30),
+				warehouse="WH 1",
+				posting_date="2021-12-04", voucher_type="Stock Entry",
+				voucher_no="002",
+				has_serial_no=False, serial_no=None
+			),
+			frappe._dict(
+				name="Flask Item",
+				actual_qty=(-50), qty_after_transaction=(-80),
+				warehouse="WH 1",
+				posting_date="2021-12-04", voucher_type="Stock Entry",
+				voucher_no="002",
+				has_serial_no=False, serial_no=None
+			),
+			frappe._dict(
+				name="Flask Item",
+				actual_qty=50, qty_after_transaction=(-30),
+				warehouse="WH 1",
+				posting_date="2021-12-04", voucher_type="Stock Entry",
+				voucher_no="002",
+				has_serial_no=False, serial_no=None
+			),
+		]
+		fifo_slots = FIFOSlots(self.filters, sle)
+		slots = fifo_slots.generate()
+		item_result = slots["Flask Item"]
+		queue = item_result["fifo_queue"]
+
+		self.assertEqual(item_result["total_qty"], -30.0)
+		self.assertEqual(queue[0][0], -30.0)
+
+		# check transfer bucket
+		transfer_bucket = fifo_slots.transferred_item_details[('002', 'Flask Item', 'WH 1')]
+		self.assertEqual(transfer_bucket[0][0], 50)
+
 	def test_repack_entry_same_item_overproduce(self):
 		"""
 		Under consume item and have more repacked item qty (same warehouse).
@@ -385,10 +444,164 @@
 
 		self.assertEqual(item_result["total_qty"], 550.0)
 		self.assertEqual(queue[0][0], 450.0)
-		self.assertEqual(queue[1][0], 100.0)
+		self.assertEqual(queue[1][0], 50.0)
+		self.assertEqual(queue[2][0], 50.0)
 		# check if time buckets add up to balance qty
 		self.assertEqual(sum([i[0] for i in queue]), 550.0)
 
+	def test_repack_entry_same_item_overproduce_with_split_rows(self):
+		"""
+		Over consume item and have less repacked item qty (same warehouse).
+		Ledger:
+		Item	| Qty  | Voucher
+		------------------------
+		Item 1  | 20   | 001
+		Item 1  | -50  | 002 (repack)
+		Item 1  | 50  | 002 (repack)
+		Item 1  | 50   | 002 (repack)
+		"""
+		sle = [
+			frappe._dict( # stock up item
+				name="Flask Item",
+				actual_qty=20, qty_after_transaction=20,
+				warehouse="WH 1",
+				posting_date="2021-12-03", voucher_type="Stock Entry",
+				voucher_no="001",
+				has_serial_no=False, serial_no=None
+			),
+			frappe._dict(
+				name="Flask Item",
+				actual_qty=(-50), qty_after_transaction=(-30),
+				warehouse="WH 1",
+				posting_date="2021-12-04", voucher_type="Stock Entry",
+				voucher_no="002",
+				has_serial_no=False, serial_no=None
+			),
+			frappe._dict(
+				name="Flask Item",
+				actual_qty=50, qty_after_transaction=20,
+				warehouse="WH 1",
+				posting_date="2021-12-04", voucher_type="Stock Entry",
+				voucher_no="002",
+				has_serial_no=False, serial_no=None
+			),
+			frappe._dict(
+				name="Flask Item",
+				actual_qty=50, qty_after_transaction=70,
+				warehouse="WH 1",
+				posting_date="2021-12-04", voucher_type="Stock Entry",
+				voucher_no="002",
+				has_serial_no=False, serial_no=None
+			),
+		]
+		fifo_slots = FIFOSlots(self.filters, sle)
+		slots = fifo_slots.generate()
+		item_result = slots["Flask Item"]
+		queue = item_result["fifo_queue"]
+
+		self.assertEqual(item_result["total_qty"], 70.0)
+		self.assertEqual(queue[0][0], 20.0)
+		self.assertEqual(queue[1][0], 50.0)
+
+		# check transfer bucket
+		transfer_bucket = fifo_slots.transferred_item_details[('002', 'Flask Item', 'WH 1')]
+		self.assertFalse(transfer_bucket)
+
+	def test_negative_stock_same_voucher(self):
+		"""
+		Test negative stock scenario in transfer bucket via repack entry (same wh).
+		Ledger:
+		Item	| Qty  | Voucher
+		------------------------
+		Item 1  | -50  | 001
+		Item 1  | -50  | 001
+		Item 1  | 30   | 001
+		Item 1  | 80   | 001
+		"""
+		sle = [
+			frappe._dict( # stock up item
+				name="Flask Item",
+				actual_qty=(-50), qty_after_transaction=(-50),
+				warehouse="WH 1",
+				posting_date="2021-12-01", voucher_type="Stock Entry",
+				voucher_no="001",
+				has_serial_no=False, serial_no=None
+			),
+			frappe._dict( # stock up item
+				name="Flask Item",
+				actual_qty=(-50), qty_after_transaction=(-100),
+				warehouse="WH 1",
+				posting_date="2021-12-01", voucher_type="Stock Entry",
+				voucher_no="001",
+				has_serial_no=False, serial_no=None
+			),
+			frappe._dict( # stock up item
+				name="Flask Item",
+				actual_qty=30, qty_after_transaction=(-70),
+				warehouse="WH 1",
+				posting_date="2021-12-01", voucher_type="Stock Entry",
+				voucher_no="001",
+				has_serial_no=False, serial_no=None
+			),
+		]
+		fifo_slots = FIFOSlots(self.filters, sle)
+		slots = fifo_slots.generate()
+		item_result = slots["Flask Item"]
+
+		# check transfer bucket
+		transfer_bucket = fifo_slots.transferred_item_details[('001', 'Flask Item', 'WH 1')]
+		self.assertEqual(transfer_bucket[0][0], 20)
+		self.assertEqual(transfer_bucket[1][0], 50)
+		self.assertEqual(item_result["fifo_queue"][0][0], -70.0)
+
+		sle.append(frappe._dict(
+			name="Flask Item",
+			actual_qty=80, qty_after_transaction=10,
+			warehouse="WH 1",
+			posting_date="2021-12-01", voucher_type="Stock Entry",
+			voucher_no="001",
+			has_serial_no=False, serial_no=None
+		))
+
+		fifo_slots = FIFOSlots(self.filters, sle)
+		slots = fifo_slots.generate()
+		item_result = slots["Flask Item"]
+
+		transfer_bucket = fifo_slots.transferred_item_details[('001', 'Flask Item', 'WH 1')]
+		self.assertFalse(transfer_bucket)
+		self.assertEqual(item_result["fifo_queue"][0][0], 10.0)
+
+	def test_precision(self):
+		"Test if final balance qty is rounded off correctly."
+		sle = [
+			frappe._dict( # stock up item
+				name="Flask Item",
+				actual_qty=0.3, qty_after_transaction=0.3,
+				warehouse="WH 1",
+				posting_date="2021-12-01", voucher_type="Stock Entry",
+				voucher_no="001",
+				has_serial_no=False, serial_no=None
+			),
+			frappe._dict( # stock up item
+				name="Flask Item",
+				actual_qty=0.6, qty_after_transaction=0.9,
+				warehouse="WH 1",
+				posting_date="2021-12-01", voucher_type="Stock Entry",
+				voucher_no="001",
+				has_serial_no=False, serial_no=None
+			),
+		]
+
+		slots = FIFOSlots(self.filters, sle).generate()
+		report_data = format_report_data(self.filters, slots, self.filters["to_date"])
+		row = report_data[0] # first row in report
+		bal_qty = row[5]
+		range_qty_sum = sum([i for i in row[7:11]]) # get sum of range balance
+
+		# check if value of Available Qty column matches with range bucket post format
+		self.assertEqual(bal_qty, 0.9)
+		self.assertEqual(bal_qty, range_qty_sum)
+
 def generate_item_and_item_wh_wise_slots(filters, sle):
 	"Return results with and without 'show_warehouse_wise_stock'"
 	item_wise_slots = FIFOSlots(filters, sle).generate()