mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-05 22:48:27 +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 datetime
|
||||||
|
import json
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
@@ -226,6 +227,9 @@ class DeprecatedBatchNoValuation:
|
|||||||
)
|
)
|
||||||
def set_balance_value_for_non_batchwise_valuation_batches(self):
|
def set_balance_value_for_non_batchwise_valuation_batches(self):
|
||||||
self.last_sle = self.get_last_sle_for_non_batch()
|
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_sl_entries()
|
||||||
self.set_balance_value_from_bundle()
|
self.set_balance_value_from_bundle()
|
||||||
|
|
||||||
@@ -305,6 +309,7 @@ class DeprecatedBatchNoValuation:
|
|||||||
.select(
|
.select(
|
||||||
sle.stock_value,
|
sle.stock_value,
|
||||||
sle.qty_after_transaction,
|
sle.qty_after_transaction,
|
||||||
|
sle.stock_queue,
|
||||||
)
|
)
|
||||||
.where(
|
.where(
|
||||||
(sle.item_code == self.sle.item_code)
|
(sle.item_code == self.sle.item_code)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
import collections
|
import collections
|
||||||
import csv
|
import csv
|
||||||
|
import json
|
||||||
from collections import Counter, defaultdict
|
from collections import Counter, defaultdict
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
@@ -29,6 +30,7 @@ from erpnext.stock.serial_batch_bundle import (
|
|||||||
get_batches_from_bundle,
|
get_batches_from_bundle,
|
||||||
)
|
)
|
||||||
from erpnext.stock.serial_batch_bundle import get_serial_nos as get_serial_nos_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):
|
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):
|
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()
|
sle = self.get_sle_for_outward_transaction()
|
||||||
|
|
||||||
if self.has_serial_no:
|
if self.has_serial_no:
|
||||||
@@ -483,13 +487,40 @@ class SerialandBatchBundle(Document):
|
|||||||
warehouse=self.warehouse,
|
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:
|
for d in self.entries:
|
||||||
available_qty = 0
|
available_qty = 0
|
||||||
|
|
||||||
if self.has_serial_no:
|
if self.has_serial_no:
|
||||||
d.incoming_rate = abs(sn_obj.serial_no_incoming_rate.get(d.serial_no, 0.0))
|
d.incoming_rate = abs(sn_obj.serial_no_incoming_rate.get(d.serial_no, 0.0))
|
||||||
else:
|
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"))
|
available_qty = flt(sn_obj.available_qty.get(d.batch_no), d.precision("qty"))
|
||||||
if self.docstatus == 1:
|
if self.docstatus == 1:
|
||||||
|
|||||||
@@ -203,7 +203,10 @@ class TestSerialandBatchBundle(IntegrationTestCase):
|
|||||||
batch_item_code,
|
batch_item_code,
|
||||||
{
|
{
|
||||||
"has_batch_no": 1,
|
"has_batch_no": 1,
|
||||||
|
"batch_number_series": "TEST-OLD-BAT-VAL-.#####",
|
||||||
|
"create_new_batch": 1,
|
||||||
"is_stock_item": 1,
|
"is_stock_item": 1,
|
||||||
|
"valuation_method": "FIFO",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -256,57 +259,63 @@ class TestSerialandBatchBundle(IntegrationTestCase):
|
|||||||
doc.submit()
|
doc.submit()
|
||||||
doc.reload()
|
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.ignore_serial_batch_bundle_validation = False
|
||||||
frappe.flags.use_serial_and_batch_fields = 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):
|
def test_old_serial_no_valuation(self):
|
||||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
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():
|
for key, value in kwargs.items():
|
||||||
setattr(self, key, value)
|
setattr(self, key, value)
|
||||||
|
|
||||||
|
self.stock_queue = []
|
||||||
self.batch_nos = self.get_batch_nos()
|
self.batch_nos = self.get_batch_nos()
|
||||||
self.prepare_batches()
|
self.prepare_batches()
|
||||||
self.calculate_avg_rate()
|
self.calculate_avg_rate()
|
||||||
@@ -804,15 +805,12 @@ class BatchNoValuation(DeprecatedBatchNoValuation):
|
|||||||
self.non_batchwise_valuation_batches = self.batches
|
self.non_batchwise_valuation_batches = self.batches
|
||||||
return
|
return
|
||||||
|
|
||||||
if get_valuation_method(self.sle.item_code) == "FIFO":
|
batches = frappe.get_all(
|
||||||
self.batchwise_valuation_batches = self.batches
|
"Batch", filters={"name": ("in", self.batches), "use_batchwise_valuation": 1}, fields=["name"]
|
||||||
else:
|
)
|
||||||
batches = frappe.get_all(
|
|
||||||
"Batch", filters={"name": ("in", self.batches), "use_batchwise_valuation": 1}, fields=["name"]
|
|
||||||
)
|
|
||||||
|
|
||||||
for batch in batches:
|
for batch in batches:
|
||||||
self.batchwise_valuation_batches.append(batch.name)
|
self.batchwise_valuation_batches.append(batch.name)
|
||||||
|
|
||||||
self.non_batchwise_valuation_batches = list(set(self.batches) - set(self.batchwise_valuation_batches))
|
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.set_incoming_rate(save=True, allow_negative_stock=self.allow_negative_stock)
|
||||||
doc.calculate_qty_and_amount(save=True)
|
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.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)
|
self.wh_data.qty_after_transaction += flt(doc.total_qty, self.flt_precision)
|
||||||
if flt(self.wh_data.qty_after_transaction, 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):
|
def get_valuation_method(item_code):
|
||||||
"""get valuation method from item or default"""
|
"""get valuation method from item or default"""
|
||||||
val_method = frappe.get_cached_value("Item", item_code, "valuation_method")
|
val_method = frappe.get_cached_value("Item", item_code, "valuation_method")
|
||||||
|
|||||||
Reference in New Issue
Block a user