Merge pull request #30913 from ankush/against_voucher_processing
fix: double future qty updates
This commit is contained in:
commit
d25841369d
@ -1183,6 +1183,42 @@ class TestStockLedgerEntry(FrappeTestCase):
|
||||
backdated.cancel()
|
||||
self.assertEqual([1], ordered_qty_after_transaction())
|
||||
|
||||
def test_timestamp_clash(self):
|
||||
|
||||
item = make_item().name
|
||||
warehouse = "_Test Warehouse - _TC"
|
||||
|
||||
reciept = make_stock_entry(
|
||||
item_code=item,
|
||||
to_warehouse=warehouse,
|
||||
qty=100,
|
||||
rate=10,
|
||||
posting_date="2021-01-01",
|
||||
posting_time="01:00:00",
|
||||
)
|
||||
|
||||
consumption = make_stock_entry(
|
||||
item_code=item,
|
||||
from_warehouse=warehouse,
|
||||
qty=50,
|
||||
posting_date="2021-01-01",
|
||||
posting_time="02:00:00.1234", # ms are possible when submitted without editing posting time
|
||||
)
|
||||
|
||||
backdated_receipt = make_stock_entry(
|
||||
item_code=item,
|
||||
to_warehouse=warehouse,
|
||||
qty=100,
|
||||
posting_date="2021-01-01",
|
||||
rate=10,
|
||||
posting_time="02:00:00", # same posting time as consumption but ms part stripped
|
||||
)
|
||||
|
||||
try:
|
||||
backdated_receipt.cancel()
|
||||
except Exception as e:
|
||||
self.fail("Double processing of qty for clashing timestamp.")
|
||||
|
||||
|
||||
def create_repack_entry(**args):
|
||||
args = frappe._dict(args)
|
||||
|
@ -31,6 +31,7 @@ class TestStockReconciliation(FrappeTestCase):
|
||||
|
||||
def tearDown(self):
|
||||
frappe.local.future_sle = {}
|
||||
frappe.flags.pop("dont_execute_stock_reposts", None)
|
||||
|
||||
def test_reco_for_fifo(self):
|
||||
self._test_reco_sle_gle("FIFO")
|
||||
@ -384,6 +385,7 @@ class TestStockReconciliation(FrappeTestCase):
|
||||
-------------------------------------------
|
||||
Var | Doc | Qty | Balance
|
||||
-------------------------------------------
|
||||
PR5 | PR | 10 | 10 (posting date: today-4) [backdated]
|
||||
SR5 | Reco | 0 | 8 (posting date: today-4) [backdated]
|
||||
PR1 | PR | 10 | 18 (posting date: today-3)
|
||||
PR2 | PR | 1 | 19 (posting date: today-2)
|
||||
@ -393,6 +395,14 @@ class TestStockReconciliation(FrappeTestCase):
|
||||
item_code = make_item().name
|
||||
warehouse = "_Test Warehouse - _TC"
|
||||
|
||||
frappe.flags.dont_execute_stock_reposts = True
|
||||
|
||||
def assertBalance(doc, qty_after_transaction):
|
||||
sle_balance = frappe.db.get_value(
|
||||
"Stock Ledger Entry", {"voucher_no": doc.name, "is_cancelled": 0}, "qty_after_transaction"
|
||||
)
|
||||
self.assertEqual(sle_balance, qty_after_transaction)
|
||||
|
||||
pr1 = make_purchase_receipt(
|
||||
item_code=item_code, warehouse=warehouse, qty=10, rate=100, posting_date=add_days(nowdate(), -3)
|
||||
)
|
||||
@ -402,62 +412,37 @@ class TestStockReconciliation(FrappeTestCase):
|
||||
pr3 = make_purchase_receipt(
|
||||
item_code=item_code, warehouse=warehouse, qty=1, rate=100, posting_date=nowdate()
|
||||
)
|
||||
|
||||
pr1_balance = frappe.db.get_value(
|
||||
"Stock Ledger Entry", {"voucher_no": pr1.name, "is_cancelled": 0}, "qty_after_transaction"
|
||||
)
|
||||
pr3_balance = frappe.db.get_value(
|
||||
"Stock Ledger Entry", {"voucher_no": pr3.name, "is_cancelled": 0}, "qty_after_transaction"
|
||||
)
|
||||
self.assertEqual(pr1_balance, 10)
|
||||
self.assertEqual(pr3_balance, 12)
|
||||
assertBalance(pr1, 10)
|
||||
assertBalance(pr3, 12)
|
||||
|
||||
# post backdated stock reco in between
|
||||
sr4 = create_stock_reconciliation(
|
||||
item_code=item_code, warehouse=warehouse, qty=6, rate=100, posting_date=add_days(nowdate(), -1)
|
||||
)
|
||||
pr3_balance = frappe.db.get_value(
|
||||
"Stock Ledger Entry", {"voucher_no": pr3.name, "is_cancelled": 0}, "qty_after_transaction"
|
||||
)
|
||||
self.assertEqual(pr3_balance, 7)
|
||||
assertBalance(pr3, 7)
|
||||
|
||||
# post backdated stock reco at the start
|
||||
sr5 = create_stock_reconciliation(
|
||||
item_code=item_code, warehouse=warehouse, qty=8, rate=100, posting_date=add_days(nowdate(), -4)
|
||||
)
|
||||
pr1_balance = frappe.db.get_value(
|
||||
"Stock Ledger Entry", {"voucher_no": pr1.name, "is_cancelled": 0}, "qty_after_transaction"
|
||||
assertBalance(pr1, 18)
|
||||
assertBalance(pr2, 19)
|
||||
assertBalance(sr4, 6) # check if future stock reco is unaffected
|
||||
|
||||
# Make a backdated receipt and check only entries till first SR are affected
|
||||
pr5 = make_purchase_receipt(
|
||||
item_code=item_code, warehouse=warehouse, qty=10, rate=100, posting_date=add_days(nowdate(), -5)
|
||||
)
|
||||
pr2_balance = frappe.db.get_value(
|
||||
"Stock Ledger Entry", {"voucher_no": pr2.name, "is_cancelled": 0}, "qty_after_transaction"
|
||||
)
|
||||
sr4_balance = frappe.db.get_value(
|
||||
"Stock Ledger Entry", {"voucher_no": sr4.name, "is_cancelled": 0}, "qty_after_transaction"
|
||||
)
|
||||
self.assertEqual(pr1_balance, 18)
|
||||
self.assertEqual(pr2_balance, 19)
|
||||
self.assertEqual(sr4_balance, 6) # check if future stock reco is unaffected
|
||||
assertBalance(pr5, 10)
|
||||
# check if future stock reco is unaffected
|
||||
assertBalance(sr4, 6)
|
||||
assertBalance(sr5, 8)
|
||||
|
||||
# cancel backdated stock reco and check future impact
|
||||
sr5.cancel()
|
||||
pr1_balance = frappe.db.get_value(
|
||||
"Stock Ledger Entry", {"voucher_no": pr1.name, "is_cancelled": 0}, "qty_after_transaction"
|
||||
)
|
||||
pr2_balance = frappe.db.get_value(
|
||||
"Stock Ledger Entry", {"voucher_no": pr2.name, "is_cancelled": 0}, "qty_after_transaction"
|
||||
)
|
||||
sr4_balance = frappe.db.get_value(
|
||||
"Stock Ledger Entry", {"voucher_no": sr4.name, "is_cancelled": 0}, "qty_after_transaction"
|
||||
)
|
||||
self.assertEqual(pr1_balance, 10)
|
||||
self.assertEqual(pr2_balance, 11)
|
||||
self.assertEqual(sr4_balance, 6) # check if future stock reco is unaffected
|
||||
|
||||
# teardown
|
||||
sr4.cancel()
|
||||
pr3.cancel()
|
||||
pr2.cancel()
|
||||
pr1.cancel()
|
||||
assertBalance(pr1, 10)
|
||||
assertBalance(pr2, 11)
|
||||
assertBalance(sr4, 6) # check if future stock reco is unaffected
|
||||
|
||||
@change_settings("Stock Settings", {"allow_negative_stock": 0})
|
||||
def test_backdated_stock_reco_future_negative_stock(self):
|
||||
@ -563,7 +548,6 @@ class TestStockReconciliation(FrappeTestCase):
|
||||
|
||||
# repost will make this test useless, qty should update in realtime without reposts
|
||||
frappe.flags.dont_execute_stock_reposts = True
|
||||
self.addCleanup(frappe.flags.pop, "dont_execute_stock_reposts")
|
||||
|
||||
item_code = make_item().name
|
||||
warehouse = "_Test Warehouse - _TC"
|
||||
|
@ -111,17 +111,17 @@ def get_columns():
|
||||
},
|
||||
{
|
||||
"fieldname": "posting_date",
|
||||
"fieldtype": "Date",
|
||||
"fieldtype": "Data",
|
||||
"label": _("Posting Date"),
|
||||
},
|
||||
{
|
||||
"fieldname": "posting_time",
|
||||
"fieldtype": "Time",
|
||||
"fieldtype": "Data",
|
||||
"label": _("Posting Time"),
|
||||
},
|
||||
{
|
||||
"fieldname": "creation",
|
||||
"fieldtype": "Datetime",
|
||||
"fieldtype": "Data",
|
||||
"label": _("Creation"),
|
||||
},
|
||||
{
|
||||
|
@ -1303,6 +1303,8 @@ def update_qty_in_future_sle(args, allow_negative_stock=False):
|
||||
datetime_limit_condition = ""
|
||||
qty_shift = args.actual_qty
|
||||
|
||||
args["time_format"] = "%H:%i:%s"
|
||||
|
||||
# find difference/shift in qty caused by stock reconciliation
|
||||
if args.voucher_type == "Stock Reconciliation":
|
||||
qty_shift = get_stock_reco_qty_shift(args)
|
||||
@ -1315,7 +1317,7 @@ def update_qty_in_future_sle(args, allow_negative_stock=False):
|
||||
datetime_limit_condition = get_datetime_limit_condition(detail)
|
||||
|
||||
frappe.db.sql(
|
||||
"""
|
||||
f"""
|
||||
update `tabStock Ledger Entry`
|
||||
set qty_after_transaction = qty_after_transaction + {qty_shift}
|
||||
where
|
||||
@ -1323,16 +1325,10 @@ def update_qty_in_future_sle(args, allow_negative_stock=False):
|
||||
and warehouse = %(warehouse)s
|
||||
and voucher_no != %(voucher_no)s
|
||||
and is_cancelled = 0
|
||||
and (timestamp(posting_date, posting_time) > timestamp(%(posting_date)s, %(posting_time)s)
|
||||
or (
|
||||
timestamp(posting_date, posting_time) = timestamp(%(posting_date)s, %(posting_time)s)
|
||||
and creation > %(creation)s
|
||||
)
|
||||
)
|
||||
and timestamp(posting_date, time_format(posting_time, %(time_format)s))
|
||||
> timestamp(%(posting_date)s, time_format(%(posting_time)s, %(time_format)s))
|
||||
{datetime_limit_condition}
|
||||
""".format(
|
||||
qty_shift=qty_shift, datetime_limit_condition=datetime_limit_condition
|
||||
),
|
||||
""",
|
||||
args,
|
||||
)
|
||||
|
||||
@ -1383,6 +1379,7 @@ def get_next_stock_reco(args):
|
||||
and creation > %(creation)s
|
||||
)
|
||||
)
|
||||
order by timestamp(posting_date, posting_time) asc, creation asc
|
||||
limit 1
|
||||
""",
|
||||
args,
|
||||
|
Loading…
x
Reference in New Issue
Block a user