feat: LIFOValuation class for handling LIFO
diff --git a/erpnext/stock/valuation.py b/erpnext/stock/valuation.py
index f056439..ee9477e 100644
--- a/erpnext/stock/valuation.py
+++ b/erpnext/stock/valuation.py
@@ -3,7 +3,7 @@
 
 from frappe.utils import flt
 
-StockBin = NewType("FifoBin", List[float])
+StockBin = NewType("StockBin", List[float])  # [[qty, rate], ...]
 
 # Indexes of values inside FIFO bin 2-tuple
 QTY = 0
@@ -164,11 +164,12 @@
 	Stack is implemented using "bins" of [qty, rate].
 
 	ref: https://en.wikipedia.org/wiki/FIFO_and_LIFO_accounting
+	Implementation detail: appends and pops both at end of list.
 	"""
 
 	# specifying the attributes to save resources
 	# ref: https://docs.python.org/3/reference/datamodel.html#slots
-	__slots__ = ["queue",]
+	__slots__ = ["stack",]
 
 	def __init__(self, state: Optional[List[StockBin]]):
 		self.stack: List[StockBin] = state if state is not None else []
@@ -183,8 +184,26 @@
 
 			args:
 				qty: new quantity to add
-				rate: incoming rate of new quantity"""
-		pass
+				rate: incoming rate of new quantity.
+
+			Behaviour of this is same as FIFO valuation.
+		"""
+		if not len(self.stack):
+			self.stack.append([0, 0])
+
+		# last row has the same rate, merge new bin.
+		if self.stack[-1][RATE] == rate:
+			self.stack[-1][QTY] += qty
+		else:
+			# Item has a positive balance qty, add new entry
+			if self.stack[-1][QTY] > 0:
+				self.stack.append([qty, rate])
+			else:  # negative balance qty
+				qty = self.stack[-1][QTY] + qty
+				if qty > 0:  # new balance qty is positive
+					self.stack[-1] = [qty, rate]
+				else:  # new balance qty is still negative, maintain same rate
+					self.stack[-1][QTY] = qty
 
 
 	def remove_stock(
@@ -194,10 +213,41 @@
 
 		args:
 			qty: quantity to remove
-			rate: outgoing rate
+			rate: outgoing rate - ignored. Kept for backwards compatibility.
 			rate_generator: function to be called if stack is not found and rate is required.
 		"""
-		pass
+		if not rate_generator:
+			rate_generator = lambda : 0.0  # noqa
+
+		consumed_bins = []
+		while qty:
+			if not len(self.stack):
+				# rely on rate generator.
+				self.stack.append([0, rate_generator()])
+
+			# start at the end.
+			index = -1
+
+			stock_bin = self.stack[index]
+			if qty >= stock_bin[QTY]:
+				# consume current bin
+				qty = _round_off_if_near_zero(qty - stock_bin[QTY])
+				to_consume = self.stack.pop(index)
+				consumed_bins.append(list(to_consume))
+
+				if not self.stack and qty:
+					# stock finished, qty still remains to be withdrawn
+					# negative stock, keep in as a negative bin
+					self.stack.append([-qty, outgoing_rate or stock_bin[RATE]])
+					consumed_bins.append([qty, outgoing_rate or stock_bin[RATE]])
+					break
+			else:
+				# qty found in current bin consume it and exit
+				stock_bin[QTY] = _round_off_if_near_zero(stock_bin[QTY] - qty)
+				consumed_bins.append([qty, stock_bin[RATE]])
+				qty = 0
+
+		return consumed_bins
 
 
 def _round_off_if_near_zero(number: float, precision: int = 7) -> float: