mirror of
https://github.com/frappe/erpnext.git
synced 2026-07-31 08:14:36 +00:00
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 8c0ec3c179)
# 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 <rohitw1991@gmail.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user