test: Cover back to back recos from different warehouses
diff --git a/erpnext/stock/report/stock_ageing/stock_ageing_fifo_logic.md b/erpnext/stock/report/stock_ageing/stock_ageing_fifo_logic.md
index 5ffe97f..9e9bed4 100644
--- a/erpnext/stock/report/stock_ageing/stock_ageing_fifo_logic.md
+++ b/erpnext/stock/report/stock_ageing/stock_ageing_fifo_logic.md
@@ -15,6 +15,7 @@
50 qty is (today-the 1st) days old
20 qty is (today-the 2nd) days old
+> Note: We generate FIFO slots warehouse wise as stock reconciliations from different warehouses can cause incorrect values.
### Calculation of FIFO Slots
#### Case 1: Outward from sufficient balance qty
diff --git a/erpnext/stock/report/stock_ageing/test_stock_ageing.py b/erpnext/stock/report/stock_ageing/test_stock_ageing.py
index 949bb7c..66d2f6b 100644
--- a/erpnext/stock/report/stock_ageing/test_stock_ageing.py
+++ b/erpnext/stock/report/stock_ageing/test_stock_ageing.py
@@ -15,11 +15,12 @@
)
def test_normal_inward_outward_queue(self):
- "Reference: Case 1 in stock_ageing_fifo_logic.md"
+ "Reference: Case 1 in stock_ageing_fifo_logic.md (same wh)"
sle = [
frappe._dict(
name="Flask Item",
actual_qty=30, qty_after_transaction=30,
+ warehouse="WH 1",
posting_date="2021-12-01", voucher_type="Stock Entry",
voucher_no="001",
has_serial_no=False, serial_no=None
@@ -27,6 +28,7 @@
frappe._dict(
name="Flask Item",
actual_qty=20, qty_after_transaction=50,
+ warehouse="WH 1",
posting_date="2021-12-02", voucher_type="Stock Entry",
voucher_no="002",
has_serial_no=False, serial_no=None
@@ -34,6 +36,7 @@
frappe._dict(
name="Flask Item",
actual_qty=(-10), qty_after_transaction=40,
+ warehouse="WH 1",
posting_date="2021-12-03", voucher_type="Stock Entry",
voucher_no="003",
has_serial_no=False, serial_no=None
@@ -50,11 +53,12 @@
self.assertEqual(queue[0][0], 20.0)
def test_insufficient_balance(self):
- "Reference: Case 3 in stock_ageing_fifo_logic.md"
+ "Reference: Case 3 in stock_ageing_fifo_logic.md (same wh)"
sle = [
frappe._dict(
name="Flask Item",
actual_qty=(-30), qty_after_transaction=(-30),
+ warehouse="WH 1",
posting_date="2021-12-01", voucher_type="Stock Entry",
voucher_no="001",
has_serial_no=False, serial_no=None
@@ -62,6 +66,7 @@
frappe._dict(
name="Flask Item",
actual_qty=20, qty_after_transaction=(-10),
+ warehouse="WH 1",
posting_date="2021-12-02", voucher_type="Stock Entry",
voucher_no="002",
has_serial_no=False, serial_no=None
@@ -69,6 +74,7 @@
frappe._dict(
name="Flask Item",
actual_qty=20, qty_after_transaction=10,
+ warehouse="WH 1",
posting_date="2021-12-03", voucher_type="Stock Entry",
voucher_no="003",
has_serial_no=False, serial_no=None
@@ -76,6 +82,7 @@
frappe._dict(
name="Flask Item",
actual_qty=10, qty_after_transaction=20,
+ warehouse="WH 1",
posting_date="2021-12-03", voucher_type="Stock Entry",
voucher_no="004",
has_serial_no=False, serial_no=None
@@ -91,11 +98,16 @@
self.assertEqual(queue[0][0], 10.0)
self.assertEqual(queue[1][0], 10.0)
- def test_stock_reconciliation(self):
+ def test_basic_stock_reconciliation(self):
+ """
+ Ledger (same wh): [+30, reco reset >> 50, -10]
+ Bal: 40
+ """
sle = [
frappe._dict(
name="Flask Item",
actual_qty=30, qty_after_transaction=30,
+ warehouse="WH 1",
posting_date="2021-12-01", voucher_type="Stock Entry",
voucher_no="001",
has_serial_no=False, serial_no=None
@@ -103,6 +115,7 @@
frappe._dict(
name="Flask Item",
actual_qty=0, qty_after_transaction=50,
+ warehouse="WH 1",
posting_date="2021-12-02", voucher_type="Stock Reconciliation",
voucher_no="002",
has_serial_no=False, serial_no=None
@@ -110,6 +123,7 @@
frappe._dict(
name="Flask Item",
actual_qty=(-10), qty_after_transaction=40,
+ warehouse="WH 1",
posting_date="2021-12-03", voucher_type="Stock Entry",
voucher_no="003",
has_serial_no=False, serial_no=None
@@ -122,5 +136,112 @@
queue = result["fifo_queue"]
self.assertEqual(result["qty_after_transaction"], result["total_qty"])
+ self.assertEqual(result["total_qty"], 40.0)
self.assertEqual(queue[0][0], 20.0)
self.assertEqual(queue[1][0], 20.0)
+
+ def test_sequential_stock_reco_same_warehouse(self):
+ """
+ Test back to back stock recos (same warehouse).
+ Ledger: [reco opening >> +1000, reco reset >> 400, -10]
+ Bal: 390
+ """
+ sle = [
+ frappe._dict(
+ name="Flask Item",
+ actual_qty=0, qty_after_transaction=1000,
+ warehouse="WH 1",
+ posting_date="2021-12-01", voucher_type="Stock Reconciliation",
+ voucher_no="002",
+ has_serial_no=False, serial_no=None
+ ),
+ frappe._dict(
+ name="Flask Item",
+ actual_qty=0, qty_after_transaction=400,
+ warehouse="WH 1",
+ posting_date="2021-12-02", voucher_type="Stock Reconciliation",
+ voucher_no="003",
+ has_serial_no=False, serial_no=None
+ ),
+ frappe._dict(
+ name="Flask Item",
+ actual_qty=(-10), qty_after_transaction=390,
+ warehouse="WH 1",
+ posting_date="2021-12-03", voucher_type="Stock Entry",
+ voucher_no="003",
+ has_serial_no=False, serial_no=None
+ )
+ ]
+ slots = FIFOSlots(self.filters, sle).generate()
+
+ result = slots["Flask Item"]
+ queue = result["fifo_queue"]
+
+ self.assertEqual(result["qty_after_transaction"], result["total_qty"])
+ self.assertEqual(result["total_qty"], 390.0)
+ self.assertEqual(queue[0][0], 390.0)
+
+ def test_sequential_stock_reco_different_warehouse(self):
+ """
+ Ledger:
+ WH | Voucher | Qty
+ -------------------
+ WH1 | Reco | 1000
+ WH2 | Reco | 400
+ WH1 | SE | -10
+
+ Bal: WH1 bal + WH2 bal = 990 + 400 = 1390
+ """
+ sle = [
+ frappe._dict(
+ name="Flask Item",
+ actual_qty=0, qty_after_transaction=1000,
+ warehouse="WH 1",
+ posting_date="2021-12-01", voucher_type="Stock Reconciliation",
+ voucher_no="002",
+ has_serial_no=False, serial_no=None
+ ),
+ frappe._dict(
+ name="Flask Item",
+ actual_qty=0, qty_after_transaction=400,
+ warehouse="WH 2",
+ posting_date="2021-12-02", voucher_type="Stock Reconciliation",
+ voucher_no="003",
+ has_serial_no=False, serial_no=None
+ ),
+ frappe._dict(
+ name="Flask Item",
+ actual_qty=(-10), qty_after_transaction=990,
+ warehouse="WH 1",
+ posting_date="2021-12-03", voucher_type="Stock Entry",
+ voucher_no="004",
+ has_serial_no=False, serial_no=None
+ )
+ ]
+
+ item_wise_slots, item_wh_wise_slots = generate_item_and_item_wh_wise_slots(
+ filters=self.filters,sle=sle
+ )
+
+ # test without 'show_warehouse_wise_stock'
+ item_result = item_wise_slots["Flask Item"]
+ queue = item_result["fifo_queue"]
+
+ self.assertEqual(item_result["qty_after_transaction"], item_result["total_qty"])
+ self.assertEqual(item_result["total_qty"], 1390.0)
+ self.assertEqual(queue[0][0], 990.0)
+ self.assertEqual(queue[1][0], 400.0)
+
+ # test with 'show_warehouse_wise_stock' checked
+ item_wh_balances = [item_wh_wise_slots.get(i).get("qty_after_transaction") for i in item_wh_wise_slots]
+ self.assertEqual(sum(item_wh_balances), item_result["qty_after_transaction"])
+
+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()
+
+ filters.show_warehouse_wise_stock = True
+ item_wh_wise_slots = FIFOSlots(filters, sle).generate()
+ filters.show_warehouse_wise_stock = False
+
+ return item_wise_slots, item_wh_wise_slots
\ No newline at end of file