mirror of
https://github.com/frappe/erpnext.git
synced 2026-07-24 05:05:03 +00:00
fix: rescale batch FIFO slot values on stock reconciliation
Batch items take the batch-slot path, which mirrors the same value arithmetic: the reco's incoming entry dumps the revaluation remainder on one slot. Rescale each reconciled batch's slots at its post-reco rate (stock_value_difference / qty of the incoming bundle entry).
This commit is contained in:
@@ -370,7 +370,7 @@ class FIFOSlots:
|
||||
row, fifo_queue, transferred_item_key, serial_nos, batch_nos, from_end
|
||||
)
|
||||
|
||||
self._revalue_stock_reconciliation_slots(row, fifo_queue)
|
||||
self._revalue_stock_reconciliation_slots(row, fifo_queue, batch_nos)
|
||||
self._update_balances(row, key)
|
||||
self._trim_serial_fifo_queue(row, key, fifo_queue)
|
||||
|
||||
@@ -394,14 +394,29 @@ class FIFOSlots:
|
||||
# Stock reconciliation stores the final balance; FIFO needs the movement delta.
|
||||
row.actual_qty = flt(row.qty_after_transaction) - flt(prev_balance_qty)
|
||||
|
||||
def _revalue_stock_reconciliation_slots(self, row: dict, fifo_queue: list) -> None:
|
||||
if row.voucher_type != "Stock Reconciliation" or row.has_serial_no or row.has_batch_no:
|
||||
def _revalue_stock_reconciliation_slots(self, row: dict, fifo_queue: list, batch_nos: list) -> None:
|
||||
if row.voucher_type != "Stock Reconciliation" or row.has_serial_no:
|
||||
return
|
||||
|
||||
if row.has_batch_no:
|
||||
if flt(row.actual_qty) > 0:
|
||||
self._revalue_reconciled_batch_slots(fifo_queue, batch_nos)
|
||||
return
|
||||
|
||||
for slot in fifo_queue:
|
||||
if is_qty_slot(slot):
|
||||
slot[FIFO_VALUE_INDEX] = flt(slot[FIFO_QTY_INDEX] * flt(row.valuation_rate))
|
||||
|
||||
def _revalue_reconciled_batch_slots(self, fifo_queue: list, batch_nos: list) -> None:
|
||||
for batch_no, _use_batchwise_valuation, qty, stock_value_difference in batch_nos:
|
||||
if not flt(qty):
|
||||
continue
|
||||
|
||||
rate = flt(stock_value_difference) / flt(qty)
|
||||
for slot in fifo_queue:
|
||||
if is_batch_slot(slot) and slot[BATCH_SLOT_BATCH_INDEX] == batch_no:
|
||||
slot[BATCH_SLOT_VALUE_INDEX] = flt(slot[BATCH_SLOT_QTY_INDEX] * rate)
|
||||
|
||||
def _get_serial_and_batch_nos(
|
||||
self, row: dict, bundle_wise_serial_nos: dict, bundle_wise_batch_nos: dict
|
||||
) -> tuple[list, list]:
|
||||
|
||||
@@ -464,6 +464,56 @@ class TestStockAgeing(ERPNextTestSuite):
|
||||
|
||||
self.assertEqual(queue, [[10.0, "2021-12-01", 20.0], [2.0, "2021-12-02", 4.0]])
|
||||
|
||||
def test_batch_stock_reco_revaluation_rescales_slot_values(self):
|
||||
"Ledger (same wh, batch B): [+10 @ 100, reco out >> 0, reco in >> 12 @ 2]"
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
|
||||
item_code = make_item(
|
||||
"Test Stock Ageing Batch Reco Revaluation",
|
||||
{"is_stock_item": 1, "has_batch_no": 1, "valuation_method": "FIFO"},
|
||||
).name
|
||||
|
||||
batch_no = "SA-RECO-REVALUE-BATCH"
|
||||
if not frappe.db.exists("Batch", batch_no):
|
||||
frappe.get_doc({"doctype": "Batch", "batch_id": batch_no, "item": item_code}).insert(
|
||||
ignore_permissions=True
|
||||
)
|
||||
frappe.db.set_value("Batch", batch_no, "use_batchwise_valuation", 1)
|
||||
|
||||
def make_sle(posting_date, voucher_type, voucher_no, actual_qty, qty_after, stock_value_difference):
|
||||
return frappe._dict(
|
||||
name=item_code,
|
||||
actual_qty=actual_qty,
|
||||
qty_after_transaction=qty_after,
|
||||
stock_value_difference=stock_value_difference,
|
||||
valuation_rate=abs(stock_value_difference / actual_qty),
|
||||
warehouse="WH 1",
|
||||
posting_date=posting_date,
|
||||
voucher_type=voucher_type,
|
||||
voucher_no=voucher_no,
|
||||
has_serial_no=False,
|
||||
has_batch_no=True,
|
||||
serial_no=None,
|
||||
batch_no=batch_no,
|
||||
)
|
||||
|
||||
sle = [
|
||||
make_sle("2021-12-01", "Stock Entry", "001", 10, 10, 1000),
|
||||
make_sle("2021-12-02", "Stock Reconciliation", "002", -10, 0, -1000),
|
||||
make_sle("2021-12-02", "Stock Reconciliation", "002", 12, 12, 24),
|
||||
]
|
||||
|
||||
slots = FIFOSlots(self.filters, sle).generate()
|
||||
queue = slots[item_code]["fifo_queue"]
|
||||
|
||||
self.assertEqual(
|
||||
queue,
|
||||
[
|
||||
[batch_no, 1, 10.0, "2021-12-01", 20.0],
|
||||
[batch_no, 1, 2.0, "2021-12-02", 4.0],
|
||||
],
|
||||
)
|
||||
|
||||
def test_sequential_stock_reco_same_warehouse(self):
|
||||
"""
|
||||
Test back to back stock recos (same warehouse).
|
||||
|
||||
Reference in New Issue
Block a user