Merge pull request #54009 from frappe/mergify/bp/version-15-hotfix/pr-53994

fix(stock): update stock queue in SABE for return entries (backport #53994)
This commit is contained in:
rohitwaghchaure
2026-04-07 19:29:19 +05:30
committed by GitHub
2 changed files with 121 additions and 0 deletions

View File

@@ -400,6 +400,25 @@ class SerialandBatchBundle(Document):
def set_valuation_rate_for_return_entry(self, return_against, row, save=False, prev_sle=None):
if valuation_details := self.get_valuation_rate_for_return_entry(return_against):
from erpnext.stock.utils import get_valuation_method
valuation_method = get_valuation_method(self.item_code)
stock_queue = []
non_batchwise_batches = []
if not self.has_serial_no and valuation_method == "FIFO":
non_batchwise_batches = frappe.get_all(
"Batch",
filters={
"name": ("in", [d.batch_no for d in self.entries if d.batch_no]),
"use_batchwise_valuation": 0,
},
pluck="name",
)
if non_batchwise_batches and prev_sle and prev_sle.stock_queue:
stock_queue = parse_json(prev_sle.stock_queue)
for row in self.entries:
if valuation_details:
self.validate_returned_serial_batch_no(return_against, row, valuation_details)
@@ -421,11 +440,25 @@ class SerialandBatchBundle(Document):
row.incoming_rate = flt(valuation_rate)
row.stock_value_difference = flt(row.qty) * flt(row.incoming_rate)
if (
non_batchwise_batches
and row.batch_no in non_batchwise_batches
and row.incoming_rate is not None
):
if flt(row.qty) > 0:
stock_queue.append([row.qty, row.incoming_rate])
elif flt(row.qty) < 0:
stock_queue = FIFOValuation(stock_queue)
stock_queue.remove_stock(qty=abs(row.qty))
stock_queue = stock_queue.state
row.stock_queue = json.dumps(stock_queue)
if save:
row.db_set(
{
"incoming_rate": row.incoming_rate,
"stock_value_difference": row.stock_value_difference,
"stock_queue": row.get("stock_queue"),
}
)

View File

@@ -1071,6 +1071,94 @@ class TestSerialandBatchBundle(FrappeTestCase):
self.assertTrue(bundle_doc.docstatus == 0)
self.assertRaises(frappe.ValidationError, bundle_doc.submit)
def test_stock_queue_for_return_entry_with_non_batchwise_valuation(self):
from erpnext.controllers.sales_and_purchase_return import make_return_doc
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
batch_item_code = "Old Batch Return Queue Test"
make_item(
batch_item_code,
{
"has_batch_no": 1,
"batch_number_series": "TEST-RET-Q-.#####",
"create_new_batch": 1,
"is_stock_item": 1,
"valuation_method": "FIFO",
},
)
batch_id = "Old Batch Return Queue 1"
if not frappe.db.exists("Batch", batch_id):
batch_doc = frappe.get_doc(
{
"doctype": "Batch",
"batch_id": batch_id,
"item": batch_item_code,
"use_batchwise_valuation": 0,
}
).insert(ignore_permissions=True)
batch_doc.db_set(
{
"use_batchwise_valuation": 0,
"batch_qty": 0,
}
)
# Create initial stock with FIFO queue: [[10, 100], [20, 200]]
make_stock_entry(
item_code=batch_item_code,
target="_Test Warehouse - _TC",
qty=10,
rate=100,
batch_no=batch_id,
use_serial_batch_fields=True,
)
make_stock_entry(
item_code=batch_item_code,
target="_Test Warehouse - _TC",
qty=20,
rate=200,
batch_no=batch_id,
use_serial_batch_fields=True,
)
# Purchase Receipt: inward 5 @ 300
pr = make_purchase_receipt(
item_code=batch_item_code,
warehouse="_Test Warehouse - _TC",
qty=5,
rate=300,
batch_no=batch_id,
use_serial_batch_fields=True,
)
sle = frappe.db.get_value(
"Stock Ledger Entry",
{"item_code": batch_item_code, "is_cancelled": 0, "voucher_no": pr.name},
["stock_queue"],
as_dict=True,
)
# Stock queue should now be [[10, 100], [20, 200], [5, 300]]
self.assertEqual(json.loads(sle.stock_queue), [[10, 100], [20, 200], [5, 300]])
# Purchase Return: return 5 against the PR
return_pr = make_return_doc("Purchase Receipt", pr.name)
return_pr.submit()
return_sle = frappe.db.get_value(
"Stock Ledger Entry",
{"item_code": batch_item_code, "is_cancelled": 0, "voucher_no": return_pr.name},
["stock_queue"],
as_dict=True,
)
# Stock queue should have 5 removed via FIFO from [[10, 100], [20, 200], [5, 300]]
# FIFO removes from front: [10, 100] -> [5, 100], rest unchanged
self.assertEqual(json.loads(return_sle.stock_queue), [[5, 100], [20, 200], [5, 300]])
def test_reference_voucher_on_cancel(self):
"""
When a source document is cancelled, the reference voucher field