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.
This commit is contained in:
Mihir Kandoi
2026-08-08 10:48:24 +05:30
parent 0e26f9b1db
commit ffa65b0c48

View File

@@ -1301,23 +1301,25 @@ class update_entries_after:
and not sle.get("batch_no") and not sle.get("batch_no")
and not sle.get("serial_and_batch_bundle") and not sle.get("serial_and_batch_bundle")
): ):
rate = get_incoming_rate( rate = flt(self.wh_data.valuation_rate)
{ if not rate:
"item_code": sle.item_code, rate = get_incoming_rate(
"warehouse": sle.warehouse, {
"posting_date": sle.posting_date, "item_code": sle.item_code,
"posting_time": sle.posting_time, "warehouse": sle.warehouse,
"qty": sle.actual_qty, "posting_date": sle.posting_date,
"serial_no": sle.get("serial_no"), "posting_time": sle.posting_time,
"batch_no": sle.get("batch_no"), "qty": sle.actual_qty,
"serial_and_batch_bundle": sle.get("serial_and_batch_bundle"), "serial_no": sle.get("serial_no"),
"company": sle.company, "batch_no": sle.get("batch_no"),
"voucher_type": sle.voucher_type, "serial_and_batch_bundle": sle.get("serial_and_batch_bundle"),
"voucher_no": sle.voucher_no, "company": sle.company,
"allow_zero_valuation": self.allow_zero_rate, "voucher_type": sle.voucher_type,
"sle": sle.name, "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"]: if not rate and sle.voucher_type in ["Delivery Note", "Sales Invoice"]:
rate = get_rate_for_return( rate = get_rate_for_return(