fix(stock): value legacy sales returns at the outgoing rate

A return against a delivery without a Serial and Batch Bundle gets the
rate of each serial no / batch from the original stock ledger entry.
This used the incoming rate of the entry. An outgoing entry has no
incoming rate, so the return had a stock value of zero.

Now the rate of an outgoing entry is its stock value difference divided
by its qty. This is the same rate that get_rate_for_return uses for
sales returns.
This commit is contained in:
barredterra
2026-10-28 17:08:25 +01:00
parent 6688c3ca96
commit ac3c0bdc07
2 changed files with 24 additions and 7 deletions
@@ -3339,7 +3339,8 @@ class TestDeliveryNote(FrappeTestCase):
if packed_item.item_code not in serial_items:
continue
serial_nos = get_serial_nos_from_bundle(packed_item.serial_and_batch_bundle)
bundle = packed_item.serial_and_batch_bundle
serial_nos = get_serial_nos_from_bundle(bundle)
delivered_serial_nos[packed_item.item_code] = sorted(serial_nos)
legacy_values = {"serial_no": "\n".join(serial_nos), "serial_and_batch_bundle": None}
packed_item.db_set(legacy_values)
@@ -3348,6 +3349,8 @@ class TestDeliveryNote(FrappeTestCase):
{"voucher_no": dn.name, "item_code": packed_item.item_code},
legacy_values,
)
frappe.db.delete("Serial and Batch Entry", {"parent": bundle})
frappe.db.delete("Serial and Batch Bundle", bundle)
return_dn = make_sales_return(dn.name)
return_dn.submit()
@@ -3360,6 +3363,15 @@ class TestDeliveryNote(FrappeTestCase):
}
self.assertEqual(returned_serial_nos, delivered_serial_nos)
# The return gets the stock value of the delivery: 2 units at 100
for item_code in serial_items:
stock_value_difference = frappe.db.get_value(
"Stock Ledger Entry",
{"voucher_no": return_dn.name, "item_code": item_code, "is_cancelled": 0},
"stock_value_difference",
)
self.assertEqual(stock_value_difference, 200)
@change_settings("Stock Settings", {"use_serial_batch_fields": 1})
def test_partial_sales_return_for_product_bundle_with_serial_fields_and_bundle(self):
bundle_item = make_item("_Packed Serial Fields Bundle Item", {"is_stock_item": 0}).name
@@ -620,7 +620,7 @@ class SerialandBatchBundle(Document):
def get_legacy_valuation_rate_for_return_entry(
self, return_against, return_against_voucher_detail_no, return_warehouse=None
):
"""Return the original line's incoming rate per serial no / batch from the SLE, for legacy receipts with no bundle."""
"""Return the original line's rate per serial no / batch from the SLE, for legacy entries with no bundle."""
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
if not (self.has_serial_no or self.has_batch_no):
@@ -629,7 +629,9 @@ class SerialandBatchBundle(Document):
sle = frappe.qb.DocType("Stock Ledger Entry")
query = (
frappe.qb.from_(sle)
.select(sle.serial_no, sle.batch_no, sle.incoming_rate)
.select(
sle.serial_no, sle.batch_no, sle.incoming_rate, sle.actual_qty, sle.stock_value_difference
)
.where(
(sle.voucher_no == return_against)
& (sle.voucher_detail_no == return_against_voucher_detail_no)
@@ -644,18 +646,21 @@ class SerialandBatchBundle(Document):
data = []
for d in query.run(as_dict=True):
incoming_rate = d.incoming_rate
if d.actual_qty < 0:
# An outgoing entry (e.g. a delivery) has no incoming rate, use its outgoing value
incoming_rate = abs(d.stock_value_difference / d.actual_qty)
if d.serial_no:
for serial_no in get_serial_nos(d.serial_no):
data.append(
frappe._dict(
{"serial_no": serial_no, "batch_no": d.batch_no, "incoming_rate": d.incoming_rate}
{"serial_no": serial_no, "batch_no": d.batch_no, "incoming_rate": incoming_rate}
)
)
elif d.batch_no:
data.append(
frappe._dict(
{"serial_no": None, "batch_no": d.batch_no, "incoming_rate": d.incoming_rate}
)
frappe._dict({"serial_no": None, "batch_no": d.batch_no, "incoming_rate": incoming_rate})
)
return data