mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-14 15:11:52 +00:00
fix: seed cancelled voucher replay from before its posting datetime
On cancel, update_entries_after replays every live SLE at the voucher's
posting datetime, but get_previous_sle_of_current_voucher seeded the
replay with the reversal SLE's creation, which resolves to the bucket's
own closing row. The bucket's net qty got double-counted into every
same-datetime row, so later submissions passed negative stock validation
against inflated balances, and the queued repost then rewrote correct
values with allow_negative_stock forced on, silently creating negative
stock. Backports the missing guard from eca71dce54.
This commit is contained in:
@@ -2751,6 +2751,50 @@ class TestStockEntry(FrappeTestCase):
|
|||||||
|
|
||||||
self.assertEqual(se.process_loss_qty, 50)
|
self.assertEqual(se.process_loss_qty, 50)
|
||||||
|
|
||||||
|
@change_settings("Stock Settings", {"allow_negative_stock": 0})
|
||||||
|
def test_cancel_seeds_replay_from_before_posting_datetime_bucket(self):
|
||||||
|
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||||
|
|
||||||
|
item = make_item().name
|
||||||
|
warehouse = create_warehouse("Cancel Replay Warehouse", company="_Test Company")
|
||||||
|
|
||||||
|
def stock_entry(qty, posting_date, **kwargs):
|
||||||
|
return make_stock_entry(
|
||||||
|
item_code=item,
|
||||||
|
qty=qty,
|
||||||
|
company="_Test Company",
|
||||||
|
posting_date=posting_date,
|
||||||
|
posting_time="10:00:00",
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
stock_entry(25, add_days(today(), -3), to_warehouse=warehouse, rate=10)
|
||||||
|
|
||||||
|
bucket_date = add_days(today(), -2)
|
||||||
|
stock_entry(20, bucket_date, from_warehouse=warehouse)
|
||||||
|
receipt = stock_entry(75, bucket_date, to_warehouse=warehouse, rate=10)
|
||||||
|
duplicate_issue = stock_entry(20, bucket_date, from_warehouse=warehouse)
|
||||||
|
|
||||||
|
frappe.flags.dont_execute_stock_reposts = True
|
||||||
|
self.addCleanup(frappe.flags.pop, "dont_execute_stock_reposts")
|
||||||
|
|
||||||
|
duplicate_issue.reload()
|
||||||
|
duplicate_issue.cancel()
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
flt(
|
||||||
|
frappe.db.get_value(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
{"voucher_no": receipt.name, "is_cancelled": 0},
|
||||||
|
"qty_after_transaction",
|
||||||
|
)
|
||||||
|
),
|
||||||
|
80,
|
||||||
|
)
|
||||||
|
|
||||||
|
overdraw = stock_entry(100, add_days(today(), -1), from_warehouse=warehouse, do_not_submit=True)
|
||||||
|
self.assertRaises(NegativeStockError, overdraw.submit)
|
||||||
|
|
||||||
|
|
||||||
def make_serialized_item(**args):
|
def make_serialized_item(**args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
|||||||
@@ -1762,7 +1762,7 @@ def get_previous_sle_of_current_voucher(args, operator="<", exclude_current_vouc
|
|||||||
voucher_no = args.get("voucher_no")
|
voucher_no = args.get("voucher_no")
|
||||||
voucher_condition = f"and voucher_no != '{voucher_no}'"
|
voucher_condition = f"and voucher_no != '{voucher_no}'"
|
||||||
|
|
||||||
elif args.get("creation") and args.get("sle_id"):
|
elif args.get("creation") and args.get("sle_id") and not args.get("cancelled"):
|
||||||
creation = args.get("creation")
|
creation = args.get("creation")
|
||||||
operator = "<="
|
operator = "<="
|
||||||
voucher_condition = f"and creation < '{creation}'"
|
voucher_condition = f"and creation < '{creation}'"
|
||||||
|
|||||||
Reference in New Issue
Block a user