mirror of
https://github.com/frappe/erpnext.git
synced 2026-02-14 18:23:55 +00:00
Merge pull request #49453 from rohitwaghchaure/fixed-fifo-valuation-for-non-batchwise-valuation
fix: non batch-wise valuation for batch item
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import datetime
|
||||
import json
|
||||
from collections import defaultdict
|
||||
|
||||
import frappe
|
||||
@@ -226,6 +227,9 @@ class DeprecatedBatchNoValuation:
|
||||
)
|
||||
def set_balance_value_for_non_batchwise_valuation_batches(self):
|
||||
self.last_sle = self.get_last_sle_for_non_batch()
|
||||
if self.last_sle and self.last_sle.stock_queue:
|
||||
self.stock_queue = json.loads(self.last_sle.stock_queue or "[]") or []
|
||||
|
||||
self.set_balance_value_from_sl_entries()
|
||||
self.set_balance_value_from_bundle()
|
||||
|
||||
@@ -305,6 +309,7 @@ class DeprecatedBatchNoValuation:
|
||||
.select(
|
||||
sle.stock_value,
|
||||
sle.qty_after_transaction,
|
||||
sle.stock_queue,
|
||||
)
|
||||
.where(
|
||||
(sle.item_code == self.sle.item_code)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
import collections
|
||||
import csv
|
||||
import json
|
||||
from collections import Counter, defaultdict
|
||||
|
||||
import frappe
|
||||
@@ -29,6 +30,7 @@ from erpnext.stock.serial_batch_bundle import (
|
||||
get_batches_from_bundle,
|
||||
)
|
||||
from erpnext.stock.serial_batch_bundle import get_serial_nos as get_serial_nos_from_bundle
|
||||
from erpnext.stock.valuation import FIFOValuation
|
||||
|
||||
|
||||
class SerialNoExistsInFutureTransactionError(frappe.ValidationError):
|
||||
@@ -467,6 +469,8 @@ class SerialandBatchBundle(Document):
|
||||
)
|
||||
|
||||
def set_incoming_rate_for_outward_transaction(self, row=None, save=False, allow_negative_stock=False):
|
||||
from erpnext.stock.utils import get_valuation_method
|
||||
|
||||
sle = self.get_sle_for_outward_transaction()
|
||||
|
||||
if self.has_serial_no:
|
||||
@@ -483,13 +487,40 @@ class SerialandBatchBundle(Document):
|
||||
warehouse=self.warehouse,
|
||||
)
|
||||
|
||||
stock_queue = []
|
||||
if hasattr(sn_obj, "stock_queue") and sn_obj.stock_queue:
|
||||
stock_queue = parse_json(sn_obj.stock_queue)
|
||||
|
||||
val_method = get_valuation_method(self.item_code)
|
||||
|
||||
for d in self.entries:
|
||||
available_qty = 0
|
||||
|
||||
if self.has_serial_no:
|
||||
d.incoming_rate = abs(sn_obj.serial_no_incoming_rate.get(d.serial_no, 0.0))
|
||||
else:
|
||||
d.incoming_rate = abs(flt(sn_obj.batch_avg_rate.get(d.batch_no)))
|
||||
actual_qty = d.qty
|
||||
if (
|
||||
stock_queue
|
||||
and val_method == "FIFO"
|
||||
and d.batch_no in sn_obj.non_batchwise_valuation_batches
|
||||
):
|
||||
if actual_qty < 0:
|
||||
stock_queue = FIFOValuation(stock_queue)
|
||||
_prev_qty, prev_stock_value = stock_queue.get_total_stock_and_value()
|
||||
|
||||
stock_queue.remove_stock(qty=abs(actual_qty))
|
||||
_qty, stock_value = stock_queue.get_total_stock_and_value()
|
||||
|
||||
stock_value_difference = stock_value - prev_stock_value
|
||||
d.incoming_rate = abs(flt(stock_value_difference) / abs(flt(actual_qty)))
|
||||
stock_queue = stock_queue.state
|
||||
else:
|
||||
d.incoming_rate = abs(flt(sn_obj.batch_avg_rate.get(d.batch_no)))
|
||||
stock_queue.append([d.qty, d.incoming_rate])
|
||||
d.stock_queue = json.dumps(stock_queue)
|
||||
else:
|
||||
d.incoming_rate = abs(flt(sn_obj.batch_avg_rate.get(d.batch_no)))
|
||||
|
||||
available_qty = flt(sn_obj.available_qty.get(d.batch_no), d.precision("qty"))
|
||||
if self.docstatus == 1:
|
||||
|
||||
@@ -203,7 +203,10 @@ class TestSerialandBatchBundle(IntegrationTestCase):
|
||||
batch_item_code,
|
||||
{
|
||||
"has_batch_no": 1,
|
||||
"batch_number_series": "TEST-OLD-BAT-VAL-.#####",
|
||||
"create_new_batch": 1,
|
||||
"is_stock_item": 1,
|
||||
"valuation_method": "FIFO",
|
||||
},
|
||||
)
|
||||
|
||||
@@ -256,57 +259,63 @@ class TestSerialandBatchBundle(IntegrationTestCase):
|
||||
doc.submit()
|
||||
doc.reload()
|
||||
|
||||
bundle_doc = make_serial_batch_bundle(
|
||||
{
|
||||
"item_code": batch_item_code,
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"voucher_type": "Stock Entry",
|
||||
"posting_date": today(),
|
||||
"posting_time": nowtime(),
|
||||
"qty": -10,
|
||||
"batches": frappe._dict({batch_id: 10}),
|
||||
"type_of_transaction": "Outward",
|
||||
"do_not_submit": True,
|
||||
}
|
||||
)
|
||||
|
||||
bundle_doc.reload()
|
||||
for row in bundle_doc.entries:
|
||||
self.assertEqual(flt(row.stock_value_difference, 2), -1666.67)
|
||||
|
||||
bundle_doc.flags.ignore_permissions = True
|
||||
bundle_doc.flags.ignore_mandatory = True
|
||||
bundle_doc.flags.ignore_links = True
|
||||
bundle_doc.flags.ignore_validate = True
|
||||
bundle_doc.submit()
|
||||
|
||||
bundle_doc = make_serial_batch_bundle(
|
||||
{
|
||||
"item_code": batch_item_code,
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"voucher_type": "Stock Entry",
|
||||
"posting_date": today(),
|
||||
"posting_time": nowtime(),
|
||||
"qty": -20,
|
||||
"batches": frappe._dict({batch_id: 20}),
|
||||
"type_of_transaction": "Outward",
|
||||
"do_not_submit": True,
|
||||
}
|
||||
)
|
||||
|
||||
bundle_doc.reload()
|
||||
for row in bundle_doc.entries:
|
||||
self.assertEqual(flt(row.stock_value_difference, 2), -3333.33)
|
||||
|
||||
bundle_doc.flags.ignore_permissions = True
|
||||
bundle_doc.flags.ignore_mandatory = True
|
||||
bundle_doc.flags.ignore_links = True
|
||||
bundle_doc.flags.ignore_validate = True
|
||||
bundle_doc.submit()
|
||||
|
||||
frappe.flags.ignore_serial_batch_bundle_validation = False
|
||||
frappe.flags.use_serial_and_batch_fields = False
|
||||
|
||||
se = make_stock_entry(
|
||||
item_code=batch_item_code,
|
||||
source="_Test Warehouse - _TC",
|
||||
qty=10,
|
||||
use_serial_batch_fields=True,
|
||||
batch_no=batch_id,
|
||||
)
|
||||
|
||||
sle = frappe.db.get_value(
|
||||
"Stock Ledger Entry",
|
||||
{"item_code": batch_item_code, "is_cancelled": 0, "voucher_no": se.name},
|
||||
["stock_value_difference", "stock_queue"],
|
||||
as_dict=True,
|
||||
)
|
||||
|
||||
self.assertEqual(flt(sle.stock_value_difference), 1000.00 * -1)
|
||||
self.assertEqual(json.loads(sle.stock_queue), [[20, 200]])
|
||||
|
||||
se = make_stock_entry(
|
||||
item_code=batch_item_code,
|
||||
target="_Test Warehouse - _TC",
|
||||
qty=10,
|
||||
rate=100,
|
||||
use_serial_batch_fields=True,
|
||||
)
|
||||
|
||||
sle = frappe.db.get_value(
|
||||
"Stock Ledger Entry",
|
||||
{"item_code": batch_item_code, "is_cancelled": 0, "voucher_no": se.name},
|
||||
["stock_value_difference", "stock_queue"],
|
||||
as_dict=True,
|
||||
)
|
||||
|
||||
self.assertEqual(flt(sle.stock_value_difference), 1000.00)
|
||||
self.assertEqual(json.loads(sle.stock_queue), [[20, 200]])
|
||||
|
||||
se = make_stock_entry(
|
||||
item_code=batch_item_code,
|
||||
source="_Test Warehouse - _TC",
|
||||
qty=30,
|
||||
use_serial_batch_fields=False,
|
||||
)
|
||||
|
||||
sle = frappe.db.get_value(
|
||||
"Stock Ledger Entry",
|
||||
{"item_code": batch_item_code, "is_cancelled": 0, "voucher_no": se.name},
|
||||
["stock_value_difference", "stock_queue", "stock_value"],
|
||||
as_dict=True,
|
||||
)
|
||||
|
||||
self.assertEqual(flt(sle.stock_value_difference), 5000.00 * -1)
|
||||
self.assertFalse(json.loads(sle.stock_queue or "[]"))
|
||||
self.assertEqual(flt(sle.stock_value), 0.0)
|
||||
|
||||
def test_old_serial_no_valuation(self):
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||
|
||||
|
||||
@@ -708,6 +708,7 @@ class BatchNoValuation(DeprecatedBatchNoValuation):
|
||||
for key, value in kwargs.items():
|
||||
setattr(self, key, value)
|
||||
|
||||
self.stock_queue = []
|
||||
self.batch_nos = self.get_batch_nos()
|
||||
self.prepare_batches()
|
||||
self.calculate_avg_rate()
|
||||
@@ -804,15 +805,12 @@ class BatchNoValuation(DeprecatedBatchNoValuation):
|
||||
self.non_batchwise_valuation_batches = self.batches
|
||||
return
|
||||
|
||||
if get_valuation_method(self.sle.item_code) == "FIFO":
|
||||
self.batchwise_valuation_batches = self.batches
|
||||
else:
|
||||
batches = frappe.get_all(
|
||||
"Batch", filters={"name": ("in", self.batches), "use_batchwise_valuation": 1}, fields=["name"]
|
||||
)
|
||||
batches = frappe.get_all(
|
||||
"Batch", filters={"name": ("in", self.batches), "use_batchwise_valuation": 1}, fields=["name"]
|
||||
)
|
||||
|
||||
for batch in batches:
|
||||
self.batchwise_valuation_batches.append(batch.name)
|
||||
for batch in batches:
|
||||
self.batchwise_valuation_batches.append(batch.name)
|
||||
|
||||
self.non_batchwise_valuation_batches = list(set(self.batches) - set(self.batchwise_valuation_batches))
|
||||
|
||||
|
||||
@@ -1046,6 +1046,15 @@ class update_entries_after:
|
||||
doc.set_incoming_rate(save=True, allow_negative_stock=self.allow_negative_stock)
|
||||
doc.calculate_qty_and_amount(save=True)
|
||||
|
||||
if stock_queue := frappe.get_all(
|
||||
"Serial and Batch Entry",
|
||||
filters={"parent": sle.serial_and_batch_bundle, "stock_queue": ("is", "set")},
|
||||
pluck="stock_queue",
|
||||
order_by="idx desc",
|
||||
limit=1,
|
||||
):
|
||||
self.wh_data.stock_queue = json.loads(stock_queue[0]) if stock_queue else []
|
||||
|
||||
self.wh_data.stock_value = round_off_if_near_zero(self.wh_data.stock_value + doc.total_amount)
|
||||
self.wh_data.qty_after_transaction += flt(doc.total_qty, self.flt_precision)
|
||||
if flt(self.wh_data.qty_after_transaction, self.flt_precision):
|
||||
|
||||
@@ -373,6 +373,7 @@ def get_avg_purchase_rate(serial_nos):
|
||||
)
|
||||
|
||||
|
||||
@frappe.request_cache
|
||||
def get_valuation_method(item_code):
|
||||
"""get valuation method from item or default"""
|
||||
val_method = frappe.get_cached_value("Item", item_code, "valuation_method")
|
||||
|
||||
Reference in New Issue
Block a user