From ffa65b0c481bbacb1c7bd57a85f3aed3203698bf Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Sat, 8 Aug 2026 10:48:24 +0530 Subject: [PATCH] fix: repost read stale sibling SLE rate for moving average returns During repost, a return line with recalculate_rate resolved its moving average rate through get_incoming_rate -> get_previous_sle, which matches posting_datetime <= and orders by creation desc. For a multi-line return of the same item, every line shares one posting_datetime, so the query landed on a sibling line of the same voucher whose stored valuation_rate was still the previous repost run's output, not the rate before the voucher. Each repost run therefore re-seeded the voucher from its own prior output. The error gain per run is (qty returned at the stale rate) / (qty remaining after the return), so whenever a return removes most of the stock the loop diverges instead of converging, alternating sign and growing until stock_value overflows decimal(21,9) and the repost dies with 'Out of range value for column stock_value'. Use the in-memory running valuation rate that update_entries_after already tracks for the warehouse at this point in the repost. It is the authoritative pre-entry state, is immune to sibling rows, and makes the repost idempotent. The database lookup is kept only as a fallback for a zero in-memory rate, preserving the existing zero-rate fallback chain. --- erpnext/stock/stock_ledger.py | 36 ++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 60fcec3f7cb..f2b1544b212 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1301,23 +1301,25 @@ class update_entries_after: and not sle.get("batch_no") and not sle.get("serial_and_batch_bundle") ): - rate = get_incoming_rate( - { - "item_code": sle.item_code, - "warehouse": sle.warehouse, - "posting_date": sle.posting_date, - "posting_time": sle.posting_time, - "qty": sle.actual_qty, - "serial_no": sle.get("serial_no"), - "batch_no": sle.get("batch_no"), - "serial_and_batch_bundle": sle.get("serial_and_batch_bundle"), - "company": sle.company, - "voucher_type": sle.voucher_type, - "voucher_no": sle.voucher_no, - "allow_zero_valuation": self.allow_zero_rate, - "sle": sle.name, - } - ) + rate = flt(self.wh_data.valuation_rate) + if not rate: + rate = get_incoming_rate( + { + "item_code": sle.item_code, + "warehouse": sle.warehouse, + "posting_date": sle.posting_date, + "posting_time": sle.posting_time, + "qty": sle.actual_qty, + "serial_no": sle.get("serial_no"), + "batch_no": sle.get("batch_no"), + "serial_and_batch_bundle": sle.get("serial_and_batch_bundle"), + "company": sle.company, + "voucher_type": sle.voucher_type, + "voucher_no": sle.voucher_no, + "allow_zero_valuation": self.allow_zero_rate, + "sle": sle.name, + } + ) if not rate and sle.voucher_type in ["Delivery Note", "Sales Invoice"]: rate = get_rate_for_return(