mirror of
https://github.com/frappe/erpnext.git
synced 2026-09-19 19:37:56 +00:00
perf(stock): skip the negative batch scan when nothing is negative (#59061)
This commit is contained in:
@@ -288,6 +288,7 @@ class FIFOSlots:
|
||||
self.transferred_item_details = {}
|
||||
self.serial_no_details = {}
|
||||
self.batch_no_details = {}
|
||||
self.batches_with_negative_slots = set()
|
||||
self.batchwise_valuation_by_batch = {}
|
||||
self.valuation_method_by_item = {}
|
||||
self.filters = filters
|
||||
@@ -659,6 +660,11 @@ class FIFOSlots:
|
||||
if not qty:
|
||||
return qty, stock_value_difference
|
||||
|
||||
if (batch_no, row.warehouse) not in self.batches_with_negative_slots:
|
||||
return qty, stock_value_difference
|
||||
|
||||
negative_slot_may_remain = False
|
||||
|
||||
for slot in list(fifo_queue):
|
||||
if not self._is_matching_negative_batch_slot(slot, batch_no, use_batchwise_valuation):
|
||||
continue
|
||||
@@ -679,10 +685,16 @@ class FIFOSlots:
|
||||
|
||||
if not flt(slot[BATCH_SLOT_QTY_INDEX]) and not flt(slot[BATCH_SLOT_VALUE_INDEX]):
|
||||
fifo_queue.remove(slot)
|
||||
elif flt(slot[BATCH_SLOT_QTY_INDEX]) < 0:
|
||||
negative_slot_may_remain = True
|
||||
|
||||
if not qty:
|
||||
negative_slot_may_remain = True
|
||||
break
|
||||
|
||||
if not negative_slot_may_remain:
|
||||
self.batches_with_negative_slots.discard((batch_no, row.warehouse))
|
||||
|
||||
return qty, stock_value_difference
|
||||
|
||||
def _is_matching_negative_batch_slot(
|
||||
@@ -795,9 +807,13 @@ class FIFOSlots:
|
||||
qty: float,
|
||||
stock_value_difference: float,
|
||||
) -> None:
|
||||
"""The only place a batch slot goes negative, so it is also where the warehouse
|
||||
is recorded as owing stock on that batch. The record is discarded again by a walk
|
||||
that reaches the end of the queue and leaves nothing negative behind."""
|
||||
fifo_queue.append(
|
||||
[batch_no, use_batchwise_valuation, -(qty), row.posting_date, -(stock_value_difference)]
|
||||
)
|
||||
self.batches_with_negative_slots.add((batch_no, row.warehouse))
|
||||
self.transferred_item_details[transfer_key].append([qty, row.posting_date, stock_value_difference])
|
||||
|
||||
def _consume_fifo_slots(
|
||||
|
||||
@@ -1884,6 +1884,57 @@ class TestStockAgeing(ERPNextTestSuite):
|
||||
[[5.0, "2021-09-01", 50.0]],
|
||||
)
|
||||
|
||||
def test_second_negative_batch_slot_survives_a_partial_refill(self):
|
||||
"""Ledger (same wh, batch B): two issues against no stock, then two receipts.
|
||||
The first receipt clears only the first negative slot, so the second receipt
|
||||
must still find and clear the one left behind."""
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
|
||||
item_code = make_item(
|
||||
"Test Stock Ageing Twice Negative Batch",
|
||||
{"is_stock_item": 1, "has_batch_no": 1, "valuation_method": "FIFO"},
|
||||
).name
|
||||
|
||||
batch_no = "SA-TWICE-NEGATIVE-BATCH"
|
||||
frappe.get_doc({"doctype": "Batch", "batch_id": batch_no, "item": item_code}).insert(
|
||||
ignore_permissions=True, ignore_if_duplicate=True
|
||||
)
|
||||
frappe.db.set_value("Batch", batch_no, "use_batchwise_valuation", 1)
|
||||
|
||||
qty_after = 0
|
||||
|
||||
def make_sle(posting_date, voucher_no, actual_qty):
|
||||
nonlocal qty_after
|
||||
|
||||
qty_after += actual_qty
|
||||
return frappe._dict(
|
||||
name=item_code,
|
||||
actual_qty=actual_qty,
|
||||
qty_after_transaction=qty_after,
|
||||
stock_value_difference=actual_qty * 10,
|
||||
valuation_rate=10,
|
||||
warehouse="WH 1",
|
||||
posting_date=posting_date,
|
||||
voucher_type="Stock Entry",
|
||||
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", "001", -10),
|
||||
make_sle("2021-12-02", "002", -5),
|
||||
make_sle("2021-12-03", "003", 10),
|
||||
make_sle("2021-12-04", "004", 5),
|
||||
]
|
||||
|
||||
result = FIFOSlots(self.filters, sle).generate()[item_code]
|
||||
|
||||
self.assertEqual(result["fifo_queue"], [])
|
||||
self.assertEqual(result["total_qty"], 0)
|
||||
|
||||
def test_batchwise_valuation_negative_stock_same_voucher(self):
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
|
||||
|
||||
Reference in New Issue
Block a user