fix: update qty in future SLEs when cancelling documents (#56638)

This commit is contained in:
rohitwaghchaure
2026-06-30 14:27:46 +05:30
committed by GitHub
parent 5d8f653476
commit 01374db8da
2 changed files with 45 additions and 1 deletions

View File

@@ -1303,6 +1303,47 @@ class TestStockLedgerEntry(ERPNextTestSuite, StockTestMixin):
self.assertEqual(frappe.db.get_value("Stock Ledger Entry", sle2.name, "qty_after_transaction"), 35)
self.assertEqual(frappe.db.get_value("Stock Ledger Entry", sle1.name, "qty_after_transaction"), 10)
def test_cancel_first_of_two_same_timestamp_entries(self):
# Two receipts of the same item+warehouse at the exact same posting timestamp: balances 10 -> 20.
# Cancelling the first must leave the second standing alone on a zero base (qty 10), not
# double-decremented. The same-timestamp sibling is corrected by the cancellation reprocessing,
# so update_qty_in_future_sle must not shift it again.
item = make_item().name
warehouse = "_Test Warehouse - _TC"
receipt1 = make_purchase_receipt(
item_code=item,
warehouse=warehouse,
qty=10,
rate=10,
posting_date="2026-06-01",
posting_time="10:00:00",
)
time.sleep(1)
receipt2 = make_purchase_receipt(
item_code=item,
warehouse=warehouse,
qty=10,
rate=10,
posting_date="2026-06-01",
posting_time="10:00:00", # identical timestamp, later creation
)
def qty_after(voucher):
return frappe.db.get_value(
"Stock Ledger Entry",
{"voucher_no": voucher.name, "is_cancelled": 0},
"qty_after_transaction",
)
self.assertEqual(qty_after(receipt1), 10)
self.assertEqual(qty_after(receipt2), 20)
receipt1.cancel()
# receipt2 now sits on a zero base -> 10 (not 0 from a double shift, nor a negative-stock error).
self.assertEqual(qty_after(receipt2), 10)
def test_get_next_stock_reco_respects_creation_order(self):
# A stock reco sharing the exact posting timestamp of the current entry must only count as the
# "next" reco when it was created after that entry. A reco created before it actually precedes

View File

@@ -2048,8 +2048,11 @@ def update_qty_in_future_sle(args, allow_negative_stock=False):
# current entry in that tuple order: a later posting_datetime, or the same posting_datetime with a
# later creation. Comparing posting_datetime alone would skip same-timestamp entries created after
# this one (e.g. the same item repeated in a voucher, or another voucher posted in the same second).
# On cancellation `args` is a freshly inserted reversal entry, so its `creation` is the cancel time
# (not the original entry's position) and same-timestamp siblings are already recomputed by the
# cancelled path in update_entries_after; applying the tiebreaker here would double-shift them.
future_condition = sle.posting_datetime > posting_datetime
if args.get("creation"):
if args.get("creation") and not args.get("is_cancelled"):
future_condition = future_condition | (
(sle.posting_datetime == posting_datetime) & (sle.creation > args.get("creation"))
)