From ae0cd164f3cddaa0afe077f533ed432e481c67c9 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 27 Jul 2026 17:22:46 +0000 Subject: [PATCH] fix: Incorrect creation time at the time cancelling an entry causing an issue especially same posting datetime (backport #57380) (#57396) * fix: Incorrect creation time at the time cancelling an entry causing an issue especially same posting datetime (#57380) * fix: shift same-timestamp sibling SLEs when cancelling an entry update_qty_in_future_sle compared against the reversal SLE's own creation and skipped same-posting_datetime siblings on cancel, leaving their qty_after_transaction stale and causing false negative stock errors. * fix: revert update_qty_in_future_sle cancel tie-break, it double-counted (cherry picked from commit 8c0ec3c179538eb15cbae26384a54de216f52a20) # Conflicts: # erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py * chore: fix conflicts Fix test cases related to stock ledger entry cancellations and ensure correct handling of same timestamp entries. * test: use named item in test_cancel_shifts_same_timestamp_delivery_notes * chore: fix typo --------- Co-authored-by: rohitwaghchaure --- .../test_stock_ledger_entry.py | 75 +++++++++++++++++++ erpnext/stock/stock_ledger.py | 9 +++ 2 files changed, 84 insertions(+) diff --git a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py index 6aea0ef5337..d917d5914bf 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py @@ -1357,6 +1357,81 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin): # 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_cancel_shifts_same_timestamp_delivery_notes(self): + item = make_item("Test Shifts Same Timestamp Multiple DNs").name + warehouse = "_Test Warehouse - _TC" + posting_date = today() + posting_time = "10:00:00" + + make_stock_entry( + item_code=item, + to_warehouse=warehouse, + qty=100, + rate=10, + posting_date=posting_date, + posting_time="09:00:00", + ) + + dns = [] + for i in range(5): + dns.append( + create_delivery_note( + item_code=item, + warehouse=warehouse, + qty=20, + rate=10 * i, + posting_date=posting_date, + posting_time=posting_time, + ) + ) + time.sleep(1) + + dn = dns[2] + dn.cancel() + + expected_qty_after_transaction_of_dns3 = 40 + qty_after_transaction_of_dns3 = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_no": dns[3].name, "is_cancelled": 0}, + "qty_after_transaction", + ) + + self.assertEqual(expected_qty_after_transaction_of_dns3, qty_after_transaction_of_dns3) + + def test_cancel_updates_bin_stock_value_when_no_sle_shares_timestamp(self): + item = make_item("Test Cancel Bin Stock Value Lone Timestamp").name + warehouse = "_Test Warehouse - _TC" + posting_date = today() + + make_stock_entry( + item_code=item, + to_warehouse=warehouse, + qty=100, + rate=10, + posting_date=posting_date, + posting_time="09:00:00", + ) + + dn = create_delivery_note( + item_code=item, + warehouse=warehouse, + qty=20, + rate=10, + posting_date=posting_date, + posting_time="10:00:00", + ) + + dn.cancel() + + # Nothing else sits on the delivery note's posting datetime, so the cancellation leaves no + # live SLE to reprocess. The bin must still fall back to the receipt's stock value. + bin_qty, bin_stock_value = frappe.db.get_value( + "Bin", {"item_code": item, "warehouse": warehouse}, ["actual_qty", "stock_value"] + ) + + self.assertEqual(bin_qty, 100) + self.assertEqual(bin_stock_value, 1000) + 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 diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index ddaaf702c86..a2cce420e3a 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -799,9 +799,18 @@ class update_entries_after: def process_sle_against_current_timestamp(self): sl_entries = get_sle_against_current_voucher(self.args) + if self.args.get("cancelled") and sl_entries: + self.seed_previous_sle_for_cancellation(sl_entries[0]) for sle in sl_entries: self.process_sle(sle) + def seed_previous_sle_for_cancellation(self, anchor_sle): + args = frappe._dict(anchor_sle) + args["sle_id"] = args.name + prev_sle = get_previous_sle_of_current_voucher(args) + if prev_sle: + self.prev_sle_dict[(anchor_sle.item_code, anchor_sle.warehouse)] = prev_sle + def get_future_entries_to_fix(self): # includes current entry! args = self.data[self.args.warehouse].previous_sle or frappe._dict(