Skip to content

Commit

Permalink
Merge pull request #30953 from frappe/mergify/bp/version-13-hotfix/pr…
Browse files Browse the repository at this point in the history
…-30913

fix: double future qty updates (backport #30913)
  • Loading branch information
ankush authored May 10, 2022
2 parents b7e873b + 4d682fa commit f64ba80
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,42 @@ def _day(days):
# original amount
self.assertEqual(50, _get_stock_credit(final_consumption))

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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def setUpClass(cls):

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")
Expand Down Expand Up @@ -383,6 +384,7 @@ def test_backdated_stock_reco_qty_reposting(self):
-------------------------------------------
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)
Expand All @@ -392,6 +394,14 @@ def test_backdated_stock_reco_qty_reposting(self):
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)
)
Expand All @@ -401,62 +411,37 @@ def test_backdated_stock_reco_qty_reposting(self):
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"
)
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"
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)
)
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):
Expand Down Expand Up @@ -562,7 +547,6 @@ def test_intermediate_sr_bin_update(self):

# 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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,17 +105,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",
},
{
Expand Down
17 changes: 7 additions & 10 deletions erpnext/stock/stock_ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -1241,6 +1241,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)
Expand All @@ -1253,24 +1255,18 @@ 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
item_code = %(item_code)s
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,
)

Expand Down Expand Up @@ -1321,6 +1317,7 @@ def get_next_stock_reco(args):
and creation > %(creation)s
)
)
order by timestamp(posting_date, posting_time) asc, creation asc
limit 1
""",
args,
Expand Down

0 comments on commit f64ba80

Please sign in to comment.