mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-12 03:15:07 +00:00
fix: valuation rate for no Use Batch wise Valuation batches
(cherry picked from commit 4befa15198)
This commit is contained in:
committed by
Mergify
parent
a5d1afe304
commit
ca6872c768
@@ -211,6 +211,7 @@ class DeprecatedBatchNoValuation:
|
|||||||
.select(
|
.select(
|
||||||
sle.batch_no,
|
sle.batch_no,
|
||||||
Sum(sle.actual_qty).as_("batch_qty"),
|
Sum(sle.actual_qty).as_("batch_qty"),
|
||||||
|
Sum(sle.stock_value_difference).as_("batch_value"),
|
||||||
)
|
)
|
||||||
.where(
|
.where(
|
||||||
(sle.item_code == self.sle.item_code)
|
(sle.item_code == self.sle.item_code)
|
||||||
@@ -227,9 +228,24 @@ class DeprecatedBatchNoValuation:
|
|||||||
if self.sle.name:
|
if self.sle.name:
|
||||||
query = query.where(sle.name != self.sle.name)
|
query = query.where(sle.name != self.sle.name)
|
||||||
|
|
||||||
|
# Moving Average items with no Use Batch wise Valuation but want to use batch wise valuation
|
||||||
|
moving_avg_item_non_batch_value = False
|
||||||
|
if valuation_method := self.get_valuation_method(self.sle.item_code):
|
||||||
|
if valuation_method == "Moving Average" and not frappe.db.get_single_value(
|
||||||
|
"Stock Settings", "do_not_use_batchwise_valuation"
|
||||||
|
):
|
||||||
|
query = query.where(batch.use_batchwise_valuation == 0)
|
||||||
|
moving_avg_item_non_batch_value = True
|
||||||
|
|
||||||
batch_data = query.run(as_dict=True)
|
batch_data = query.run(as_dict=True)
|
||||||
for d in batch_data:
|
for d in batch_data:
|
||||||
self.available_qty[d.batch_no] += flt(d.batch_qty)
|
self.available_qty[d.batch_no] += flt(d.batch_qty)
|
||||||
|
if moving_avg_item_non_batch_value:
|
||||||
|
self.non_batchwise_balance_qty[d.batch_no] += flt(d.batch_qty)
|
||||||
|
self.non_batchwise_balance_value[d.batch_no] += flt(d.batch_value)
|
||||||
|
|
||||||
|
if moving_avg_item_non_batch_value:
|
||||||
|
return
|
||||||
|
|
||||||
for d in batch_data:
|
for d in batch_data:
|
||||||
if self.available_qty.get(d.batch_no):
|
if self.available_qty.get(d.batch_no):
|
||||||
@@ -327,9 +343,24 @@ class DeprecatedBatchNoValuation:
|
|||||||
|
|
||||||
query = query.where(bundle.voucher_type != "Pick List")
|
query = query.where(bundle.voucher_type != "Pick List")
|
||||||
|
|
||||||
|
# Moving Average items with no Use Batch wise Valuation but want to use batch wise valuation
|
||||||
|
moving_avg_item_non_batch_value = False
|
||||||
|
if valuation_method := self.get_valuation_method(self.sle.item_code):
|
||||||
|
if valuation_method == "Moving Average" and not frappe.db.get_single_value(
|
||||||
|
"Stock Settings", "do_not_use_batchwise_valuation"
|
||||||
|
):
|
||||||
|
query = query.where(batch.use_batchwise_valuation == 0)
|
||||||
|
moving_avg_item_non_batch_value = True
|
||||||
|
|
||||||
batch_data = query.run(as_dict=True)
|
batch_data = query.run(as_dict=True)
|
||||||
for d in batch_data:
|
for d in batch_data:
|
||||||
self.available_qty[d.batch_no] += flt(d.batch_qty)
|
self.available_qty[d.batch_no] += flt(d.batch_qty)
|
||||||
|
if moving_avg_item_non_batch_value:
|
||||||
|
self.non_batchwise_balance_qty[d.batch_no] += flt(d.batch_qty)
|
||||||
|
self.non_batchwise_balance_value[d.batch_no] += flt(d.batch_value)
|
||||||
|
|
||||||
|
if moving_avg_item_non_batch_value:
|
||||||
|
return
|
||||||
|
|
||||||
if not self.last_sle:
|
if not self.last_sle:
|
||||||
return
|
return
|
||||||
@@ -337,3 +368,8 @@ class DeprecatedBatchNoValuation:
|
|||||||
for batch_no in self.available_qty:
|
for batch_no in self.available_qty:
|
||||||
self.non_batchwise_balance_value[batch_no] = flt(self.last_sle.stock_value)
|
self.non_batchwise_balance_value[batch_no] = flt(self.last_sle.stock_value)
|
||||||
self.non_batchwise_balance_qty[batch_no] = flt(self.last_sle.qty_after_transaction)
|
self.non_batchwise_balance_qty[batch_no] = flt(self.last_sle.qty_after_transaction)
|
||||||
|
|
||||||
|
def get_valuation_method(self, item_code):
|
||||||
|
from erpnext.stock.utils import get_valuation_method
|
||||||
|
|
||||||
|
return get_valuation_method(item_code, self.sle.company)
|
||||||
|
|||||||
@@ -358,6 +358,136 @@ class TestSerialandBatchBundle(FrappeTestCase):
|
|||||||
self.assertFalse(json.loads(sle.stock_queue or "[]"))
|
self.assertFalse(json.loads(sle.stock_queue or "[]"))
|
||||||
self.assertEqual(flt(sle.stock_value), 0.0)
|
self.assertEqual(flt(sle.stock_value), 0.0)
|
||||||
|
|
||||||
|
def test_old_moving_avg_item_with_without_batchwise_valuation(self):
|
||||||
|
frappe.flags.ignore_serial_batch_bundle_validation = True
|
||||||
|
frappe.flags.use_serial_and_batch_fields = True
|
||||||
|
batch_item_code = "Old Batch Item Valuation 2"
|
||||||
|
make_item(
|
||||||
|
batch_item_code,
|
||||||
|
{
|
||||||
|
"has_batch_no": 1,
|
||||||
|
"batch_number_series": "TEST-OLD2-BAT-VAL-.#####",
|
||||||
|
"create_new_batch": 1,
|
||||||
|
"is_stock_item": 1,
|
||||||
|
"valuation_method": "Moving Average",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
non_batchwise_val_batches = [
|
||||||
|
"TEST-OLD2-BAT-VAL-00001",
|
||||||
|
"TEST-OLD2-BAT-VAL-00002",
|
||||||
|
"TEST-OLD2-BAT-VAL-00003",
|
||||||
|
"TEST-OLD2-BAT-VAL-00004",
|
||||||
|
]
|
||||||
|
|
||||||
|
for batch_id in non_batchwise_val_batches:
|
||||||
|
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)
|
||||||
|
|
||||||
|
self.assertTrue(batch_doc.use_batchwise_valuation)
|
||||||
|
batch_doc.db_set(
|
||||||
|
{
|
||||||
|
"use_batchwise_valuation": 0,
|
||||||
|
"batch_qty": 20,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
qty_after_transaction = 0
|
||||||
|
balance_value = 0
|
||||||
|
i = 0
|
||||||
|
for batch_id in non_batchwise_val_batches:
|
||||||
|
i += 1
|
||||||
|
qty = 20
|
||||||
|
valuation = 100 * i
|
||||||
|
qty_after_transaction += qty
|
||||||
|
balance_value += qty * valuation
|
||||||
|
|
||||||
|
doc = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Stock Ledger Entry",
|
||||||
|
"posting_date": today(),
|
||||||
|
"posting_time": nowtime(),
|
||||||
|
"batch_no": batch_id,
|
||||||
|
"incoming_rate": valuation,
|
||||||
|
"qty_after_transaction": qty_after_transaction,
|
||||||
|
"stock_value_difference": valuation * qty,
|
||||||
|
"stock_value": balance_value,
|
||||||
|
"balance_value": balance_value,
|
||||||
|
"valuation_rate": balance_value / qty_after_transaction,
|
||||||
|
"actual_qty": qty,
|
||||||
|
"item_code": batch_item_code,
|
||||||
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
doc.set_posting_datetime()
|
||||||
|
doc.flags.ignore_permissions = True
|
||||||
|
doc.flags.ignore_mandatory = True
|
||||||
|
doc.flags.ignore_links = True
|
||||||
|
doc.flags.ignore_validate = True
|
||||||
|
doc.submit()
|
||||||
|
doc.reload()
|
||||||
|
|
||||||
|
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,
|
||||||
|
target="_Test Warehouse - _TC",
|
||||||
|
qty=30,
|
||||||
|
rate=355,
|
||||||
|
use_serial_batch_fields=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
se = make_stock_entry(
|
||||||
|
item_code=batch_item_code,
|
||||||
|
source="_Test Warehouse - _TC",
|
||||||
|
qty=70,
|
||||||
|
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},
|
||||||
|
["qty_after_transaction", "stock_value"],
|
||||||
|
as_dict=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(flt(sle.stock_value), 14000.0)
|
||||||
|
self.assertEqual(flt(sle.qty_after_transaction), 40.0)
|
||||||
|
|
||||||
|
se = make_stock_entry(
|
||||||
|
item_code=batch_item_code,
|
||||||
|
target="_Test Warehouse - _TC",
|
||||||
|
qty=10,
|
||||||
|
rate=200,
|
||||||
|
use_serial_batch_fields=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
se = make_stock_entry(
|
||||||
|
item_code=batch_item_code,
|
||||||
|
source="_Test Warehouse - _TC",
|
||||||
|
qty=50,
|
||||||
|
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},
|
||||||
|
["qty_after_transaction", "stock_value"],
|
||||||
|
as_dict=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(flt(sle.stock_value), 0.0)
|
||||||
|
self.assertEqual(flt(sle.qty_after_transaction), 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
|
||||||
|
|
||||||
|
|||||||
@@ -110,6 +110,22 @@ class StockSettings(Document):
|
|||||||
self.validate_auto_insert_price_list_rate_if_missing()
|
self.validate_auto_insert_price_list_rate_if_missing()
|
||||||
self.change_precision_for_for_sales()
|
self.change_precision_for_for_sales()
|
||||||
self.change_precision_for_purchase()
|
self.change_precision_for_purchase()
|
||||||
|
self.validate_do_not_use_batchwise_valuation()
|
||||||
|
|
||||||
|
def validate_do_not_use_batchwise_valuation(self):
|
||||||
|
doc_before_save = self.get_doc_before_save()
|
||||||
|
if not doc_before_save:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not frappe.get_all("Serial and Batch Bundle", filters={"docstatus": 1}, limit=1, pluck="name"):
|
||||||
|
return
|
||||||
|
|
||||||
|
if doc_before_save.do_not_use_batchwise_valuation and not self.do_not_use_batchwise_valuation:
|
||||||
|
frappe.throw(
|
||||||
|
_("Cannot disable {0} as it may lead to incorrect stock valuation.").format(
|
||||||
|
frappe.bold(_("Do Not Use Batchwise Valuation"))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def validate_warehouses(self):
|
def validate_warehouses(self):
|
||||||
warehouse_fields = ["default_warehouse", "sample_retention_warehouse"]
|
warehouse_fields = ["default_warehouse", "sample_retention_warehouse"]
|
||||||
|
|||||||
Reference in New Issue
Block a user