mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-13 14:41:53 +00:00
fix: purchase return of batchwise valuation batch valued at original receipt rate instead of batch avg rate (version-15-hotfix) (#57837)
* fix: use current batch avg rate for outward returns of batchwise valuation batches * fix: honor zero batch average and avoid duplicate batch classification query
This commit is contained in:
@@ -4996,6 +4996,66 @@ class TestPurchaseReceipt(FrappeTestCase):
|
|||||||
|
|
||||||
self.assertEqual(frappe.parse_json(stock_queue), [[20, 0.0]])
|
self.assertEqual(frappe.parse_json(stock_queue), [[20, 0.0]])
|
||||||
|
|
||||||
|
def test_purchase_return_valuation_for_batchwise_valuation_batch(self):
|
||||||
|
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||||
|
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||||
|
|
||||||
|
item_code = make_item(
|
||||||
|
"Test Purchase Return Batchwise Valn Item",
|
||||||
|
{
|
||||||
|
"is_stock_item": 1,
|
||||||
|
"has_batch_no": 1,
|
||||||
|
"batch_number_series": "BN-TPRBWV-.#####",
|
||||||
|
},
|
||||||
|
).name
|
||||||
|
|
||||||
|
batch_no = "BN-TPRBWV-00001"
|
||||||
|
batch = frappe.new_doc("Batch").update({"batch_id": batch_no, "item": item_code}).insert()
|
||||||
|
self.assertEqual(batch.use_batchwise_valuation, 1)
|
||||||
|
|
||||||
|
warehouse = "_Test Warehouse - _TC"
|
||||||
|
pr = make_purchase_receipt(
|
||||||
|
item_code=item_code,
|
||||||
|
qty=100,
|
||||||
|
rate=1000,
|
||||||
|
warehouse=warehouse,
|
||||||
|
batch_no=batch_no,
|
||||||
|
use_serial_batch_fields=1,
|
||||||
|
)
|
||||||
|
make_purchase_receipt(
|
||||||
|
item_code=item_code,
|
||||||
|
qty=100,
|
||||||
|
rate=400,
|
||||||
|
warehouse=warehouse,
|
||||||
|
batch_no=batch_no,
|
||||||
|
use_serial_batch_fields=1,
|
||||||
|
)
|
||||||
|
create_delivery_note(
|
||||||
|
item_code=item_code,
|
||||||
|
qty=100,
|
||||||
|
warehouse=warehouse,
|
||||||
|
batch_no=batch_no,
|
||||||
|
use_serial_batch_fields=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
return_pr = make_return_doc("Purchase Receipt", pr.name)
|
||||||
|
return_pr.submit()
|
||||||
|
|
||||||
|
sle = frappe.db.get_value(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
{"voucher_no": return_pr.name, "is_cancelled": 0},
|
||||||
|
["stock_value_difference", "qty_after_transaction", "stock_value", "serial_and_batch_bundle"],
|
||||||
|
as_dict=True,
|
||||||
|
)
|
||||||
|
self.assertEqual(flt(sle.qty_after_transaction), 0.0)
|
||||||
|
self.assertEqual(flt(sle.stock_value_difference, 2), -70000.0)
|
||||||
|
self.assertEqual(flt(sle.stock_value, 2), 0.0)
|
||||||
|
|
||||||
|
rate = frappe.db.get_value(
|
||||||
|
"Serial and Batch Entry", {"parent": sle.serial_and_batch_bundle}, "incoming_rate"
|
||||||
|
)
|
||||||
|
self.assertEqual(flt(rate, 2), 700.0)
|
||||||
|
|
||||||
def test_negative_stock_error_for_purchase_return(self):
|
def test_negative_stock_error_for_purchase_return(self):
|
||||||
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||||
|
|||||||
@@ -402,6 +402,13 @@ class SerialandBatchBundle(Document):
|
|||||||
|
|
||||||
valuation_method = get_valuation_method(self.item_code)
|
valuation_method = get_valuation_method(self.item_code)
|
||||||
|
|
||||||
|
# An outward return must go out at the batch's current average rate for a
|
||||||
|
# batchwise valuation batch. The original receipt rate is only correct while
|
||||||
|
# the batch still holds stock at that rate; once other receipts have changed
|
||||||
|
# the average, removing at the original rate strands a residue in the batch
|
||||||
|
# value (negative when returning the costlier receipt).
|
||||||
|
batchwise_avg_rates = self.get_batchwise_return_avg_rates()
|
||||||
|
|
||||||
stock_queue = []
|
stock_queue = []
|
||||||
non_batchwise_batches = []
|
non_batchwise_batches = []
|
||||||
if not self.has_serial_no and valuation_method == "FIFO":
|
if not self.has_serial_no and valuation_method == "FIFO":
|
||||||
@@ -435,6 +442,12 @@ class SerialandBatchBundle(Document):
|
|||||||
batches = sorted(list(valuation_details["batches"].keys()))
|
batches = sorted(list(valuation_details["batches"].keys()))
|
||||||
valuation_rate = valuation_details["batches"].get(batches[cint(row.idx) - 1])
|
valuation_rate = valuation_details["batches"].get(batches[cint(row.idx) - 1])
|
||||||
|
|
||||||
|
# a batch with an available balance goes out at its current average rate (a
|
||||||
|
# valid 0.0 included); the original receipt rate applies only when there is
|
||||||
|
# no balance to average
|
||||||
|
if not row.serial_no and row.batch_no in batchwise_avg_rates:
|
||||||
|
valuation_rate = batchwise_avg_rates[row.batch_no]
|
||||||
|
|
||||||
row.incoming_rate = flt(valuation_rate)
|
row.incoming_rate = flt(valuation_rate)
|
||||||
row.stock_value_difference = flt(row.qty) * flt(row.incoming_rate)
|
row.stock_value_difference = flt(row.qty) * flt(row.incoming_rate)
|
||||||
|
|
||||||
@@ -463,6 +476,41 @@ class SerialandBatchBundle(Document):
|
|||||||
elif self.type_of_transaction == "Inward":
|
elif self.type_of_transaction == "Inward":
|
||||||
self.set_incoming_rate_for_inward_transaction(row, save, prev_sle=prev_sle)
|
self.set_incoming_rate_for_inward_transaction(row, save, prev_sle=prev_sle)
|
||||||
|
|
||||||
|
def get_batchwise_return_avg_rates(self):
|
||||||
|
from erpnext.stock.utils import get_valuation_method
|
||||||
|
|
||||||
|
if self.type_of_transaction != "Outward" or self.has_serial_no:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
batch_nos = [d.batch_no for d in self.entries if d.batch_no]
|
||||||
|
if not batch_nos:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
if get_valuation_method(self.item_code) == "Moving Average" and frappe.db.get_single_value(
|
||||||
|
"Stock Settings", "do_not_use_batchwise_valuation"
|
||||||
|
):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
batchwise_batches = frappe.get_all(
|
||||||
|
"Batch",
|
||||||
|
filters={"name": ("in", batch_nos), "use_batchwise_valuation": 1},
|
||||||
|
pluck="name",
|
||||||
|
)
|
||||||
|
if not batchwise_batches:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# scoped to batchwise batches only, so BatchNoValuation's non-batchwise
|
||||||
|
# machinery never runs for them
|
||||||
|
sle = self.get_sle_for_outward_transaction()
|
||||||
|
sle.batch_nos = {batch_no: sle.batch_nos[batch_no] for batch_no in batchwise_batches}
|
||||||
|
sle.batchwise_valuation_batches = batchwise_batches
|
||||||
|
sn_obj = BatchNoValuation(sle=sle, item_code=self.item_code, warehouse=self.warehouse)
|
||||||
|
return {
|
||||||
|
batch_no: abs(flt(sn_obj.batch_avg_rate.get(batch_no)))
|
||||||
|
for batch_no in batchwise_batches
|
||||||
|
if flt(sn_obj.available_qty.get(batch_no))
|
||||||
|
}
|
||||||
|
|
||||||
def validate_returned_serial_batch_no(self, return_against, row, original_inv_details):
|
def validate_returned_serial_batch_no(self, return_against, row, original_inv_details):
|
||||||
if frappe.flags.through_repost_item_valuation:
|
if frappe.flags.through_repost_item_valuation:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -910,6 +910,11 @@ class BatchNoValuation(DeprecatedBatchNoValuation):
|
|||||||
self.batchwise_valuation_batches = []
|
self.batchwise_valuation_batches = []
|
||||||
self.non_batchwise_valuation_batches = []
|
self.non_batchwise_valuation_batches = []
|
||||||
|
|
||||||
|
if batchwise_batches := self.sle.get("batchwise_valuation_batches"):
|
||||||
|
self.batchwise_valuation_batches = list(batchwise_batches)
|
||||||
|
self.non_batchwise_valuation_batches = list(set(self.batches) - set(batchwise_batches))
|
||||||
|
return
|
||||||
|
|
||||||
if get_valuation_method(self.sle.item_code) == "Moving Average" and frappe.db.get_single_value(
|
if get_valuation_method(self.sle.item_code) == "Moving Average" and frappe.db.get_single_value(
|
||||||
"Stock Settings", "do_not_use_batchwise_valuation"
|
"Stock Settings", "do_not_use_batchwise_valuation"
|
||||||
):
|
):
|
||||||
|
|||||||
Reference in New Issue
Block a user