mirror of
https://github.com/frappe/erpnext.git
synced 2026-02-16 16:15:02 +00:00
fix: provision to enable do not use batch-wise valuation (#42186)
fix: provision to enable do not use batchwise valuation
This commit is contained in:
@@ -373,3 +373,4 @@ erpnext.patches.v15_0.enable_old_serial_batch_fields
|
||||
erpnext.patches.v15_0.update_warehouse_field_in_asset_repair_consumed_item_doctype
|
||||
erpnext.patches.v15_0.update_asset_repair_field_in_stock_entry
|
||||
erpnext.patches.v15_0.update_total_number_of_booked_depreciations
|
||||
erpnext.patches.v15_0.do_not_use_batchwise_valuation
|
||||
|
||||
15
erpnext/patches/v15_0/do_not_use_batchwise_valuation.py
Normal file
15
erpnext/patches/v15_0/do_not_use_batchwise_valuation.py
Normal file
@@ -0,0 +1,15 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
valuation_method = frappe.db.get_single_value("Stock Settings", "valuation_method")
|
||||
if valuation_method in ["FIFO", "LIFO"]:
|
||||
return
|
||||
|
||||
if frappe.get_all("Batch", filters={"use_batchwise_valuation": 1}, limit=1):
|
||||
return
|
||||
|
||||
if frappe.get_all("Item", filters={"has_batch_no": 1, "valuation_method": "FIFO"}, limit=1):
|
||||
return
|
||||
|
||||
frappe.db.set_single_value("Stock Settings", "do_not_use_batchwise_valuation", 1)
|
||||
@@ -158,6 +158,10 @@ class Batch(Document):
|
||||
|
||||
def set_batchwise_valuation(self):
|
||||
if self.is_new():
|
||||
if frappe.db.get_single_value("Stock Settings", "do_not_use_batchwise_valuation"):
|
||||
self.use_batchwise_valuation = 0
|
||||
return
|
||||
|
||||
self.use_batchwise_valuation = 1
|
||||
|
||||
def before_save(self):
|
||||
|
||||
@@ -1681,7 +1681,7 @@ class TestDeliveryNote(FrappeTestCase):
|
||||
{
|
||||
"is_stock_item": 1,
|
||||
"has_batch_no": 1,
|
||||
"batch_no_series": "BATCH-TESTSERIAL-.#####",
|
||||
"batch_number_series": "BATCH-TESTSERIAL-.#####",
|
||||
"create_new_batch": 1,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -648,7 +648,7 @@ class TestPickList(FrappeTestCase):
|
||||
def test_picklist_for_batch_item(self):
|
||||
warehouse = "_Test Warehouse - _TC"
|
||||
item = make_item(
|
||||
properties={"is_stock_item": 1, "has_batch_no": 1, "batch_no_series": "PICKLT-.######"}
|
||||
properties={"is_stock_item": 1, "has_batch_no": 1, "batch_number_series": "PICKLT-.######"}
|
||||
).name
|
||||
|
||||
# create batch
|
||||
|
||||
@@ -3210,6 +3210,65 @@ class TestPurchaseReceipt(FrappeTestCase):
|
||||
lcv.save().submit()
|
||||
return lcv
|
||||
|
||||
def test_do_not_use_batchwise_valuation_rate(self):
|
||||
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||
|
||||
item_code = "Test Item for Do Not Use Batchwise Valuation"
|
||||
make_item(
|
||||
item_code,
|
||||
properties={
|
||||
"is_stock_item": 1,
|
||||
"has_batch_no": 1,
|
||||
"create_new_batch": 1,
|
||||
"batch_number_series": "TIDNBV-.#####",
|
||||
"valuation_method": "Moving Average",
|
||||
},
|
||||
)
|
||||
|
||||
# 1st pr for 100 rate
|
||||
pr = make_purchase_receipt(
|
||||
item_code=item_code,
|
||||
qty=1,
|
||||
rate=100,
|
||||
posting_date=add_days(today(), -2),
|
||||
)
|
||||
|
||||
make_purchase_receipt(
|
||||
item_code=item_code,
|
||||
qty=1,
|
||||
rate=200,
|
||||
posting_date=add_days(today(), -1),
|
||||
)
|
||||
|
||||
dn = create_delivery_note(
|
||||
item_code=item_code,
|
||||
qty=1,
|
||||
rate=300,
|
||||
posting_date=today(),
|
||||
use_serial_batch_fields=1,
|
||||
batch_no=get_batch_from_bundle(pr.items[0].serial_and_batch_bundle),
|
||||
)
|
||||
dn.reload()
|
||||
bundle = dn.items[0].serial_and_batch_bundle
|
||||
|
||||
valuation_rate = frappe.db.get_value("Serial and Batch Bundle", bundle, "avg_rate")
|
||||
self.assertEqual(valuation_rate, 100)
|
||||
|
||||
doc = frappe.get_doc("Stock Settings")
|
||||
doc.do_not_use_batchwise_valuation = 1
|
||||
doc.flags.ignore_validate = True
|
||||
doc.save()
|
||||
|
||||
pr.repost_future_sle_and_gle(force=True)
|
||||
|
||||
valuation_rate = frappe.db.get_value("Serial and Batch Bundle", bundle, "avg_rate")
|
||||
self.assertEqual(valuation_rate, 150)
|
||||
|
||||
doc = frappe.get_doc("Stock Settings")
|
||||
doc.do_not_use_batchwise_valuation = 0
|
||||
doc.flags.ignore_validate = True
|
||||
doc.save()
|
||||
|
||||
|
||||
def prepare_data_for_internal_transfer():
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
||||
|
||||
@@ -228,6 +228,16 @@ class SerialandBatchBundle(Document):
|
||||
def get_serial_nos(self):
|
||||
return [d.serial_no for d in self.entries if d.serial_no]
|
||||
|
||||
def update_valuation_rate(self, valuation_rate=None, save=False):
|
||||
for row in self.entries:
|
||||
row.incoming_rate = valuation_rate
|
||||
row.stock_value_difference = flt(row.qty) * flt(valuation_rate)
|
||||
|
||||
if save:
|
||||
row.db_set(
|
||||
{"incoming_rate": row.incoming_rate, "stock_value_difference": row.stock_value_difference}
|
||||
)
|
||||
|
||||
def set_incoming_rate_for_outward_transaction(self, row=None, save=False, allow_negative_stock=False):
|
||||
sle = self.get_sle_for_outward_transaction()
|
||||
|
||||
|
||||
@@ -653,7 +653,7 @@ class TestSerialandBatchBundle(FrappeTestCase):
|
||||
"is_stock_item": 1,
|
||||
"has_batch_no": 1,
|
||||
"create_new_batch": 1,
|
||||
"batch_no_series": "PSNBI-TSNVL-.#####",
|
||||
"batch_number_series": "PSNBI-TSNVL-.#####",
|
||||
"has_serial_no": 1,
|
||||
"serial_no_series": "SN-PSNBI-TSNVL-.#####",
|
||||
},
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
"auto_reserve_serial_and_batch",
|
||||
"serial_and_batch_item_settings_tab",
|
||||
"section_break_7",
|
||||
"do_not_use_batchwise_valuation",
|
||||
"auto_create_serial_and_batch_bundle_for_outward",
|
||||
"pick_serial_and_batch_based_on",
|
||||
"column_break_mhzc",
|
||||
@@ -437,6 +438,14 @@
|
||||
"fieldname": "do_not_update_serial_batch_on_creation_of_auto_bundle",
|
||||
"fieldtype": "Check",
|
||||
"label": "Do Not Update Serial / Batch on Creation of Auto Bundle"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.valuation_method === \"Moving Average\"",
|
||||
"description": "If enabled, the system will use the moving average valuation method to calculate the valuation rate for the batched items and will not consider the individual batch-wise incoming rate.",
|
||||
"fieldname": "do_not_use_batchwise_valuation",
|
||||
"fieldtype": "Check",
|
||||
"label": "Do Not Use Batch-wise Valuation"
|
||||
}
|
||||
],
|
||||
"icon": "icon-cog",
|
||||
@@ -444,7 +453,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:10:45.423987",
|
||||
"modified": "2024-07-04 12:45:09.811280",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Settings",
|
||||
@@ -469,4 +478,4 @@
|
||||
"sort_order": "ASC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,7 @@ class StockSettings(Document):
|
||||
default_warehouse: DF.Link | None
|
||||
disable_serial_no_and_batch_selector: DF.Check
|
||||
do_not_update_serial_batch_on_creation_of_auto_bundle: DF.Check
|
||||
do_not_use_batchwise_valuation: DF.Check
|
||||
enable_stock_reservation: DF.Check
|
||||
item_group: DF.Link | None
|
||||
item_naming_by: DF.Literal["Item Code", "Naming Series"]
|
||||
@@ -98,6 +99,22 @@ class StockSettings(Document):
|
||||
self.validate_stock_reservation()
|
||||
self.change_precision_for_for_sales()
|
||||
self.change_precision_for_purchase()
|
||||
self.validate_use_batch_wise_valuation()
|
||||
|
||||
def validate_use_batch_wise_valuation(self):
|
||||
if not self.do_not_use_batchwise_valuation:
|
||||
return
|
||||
|
||||
if self.valuation_method == "FIFO":
|
||||
frappe.throw(_("Cannot disable batch wise valuation for FIFO valuation method."))
|
||||
|
||||
if frappe.get_all(
|
||||
"Item", filters={"valuation_method": "FIFO", "is_stock_item": 1, "has_batch_no": 1}, limit=1
|
||||
):
|
||||
frappe.throw(_("Can't disable batch wise valuation for items with FIFO valuation method."))
|
||||
|
||||
if frappe.get_all("Batch", filters={"use_batchwise_valuation": 1}, limit=1):
|
||||
frappe.throw(_("Can't disable batch wise valuation for active batches."))
|
||||
|
||||
def validate_warehouses(self):
|
||||
warehouse_fields = ["default_warehouse", "sample_retention_warehouse"]
|
||||
|
||||
@@ -530,6 +530,10 @@ class update_entries_after:
|
||||
self.allow_zero_rate = allow_zero_rate
|
||||
self.via_landed_cost_voucher = via_landed_cost_voucher
|
||||
self.item_code = args.get("item_code")
|
||||
self.use_moving_avg_for_batch = frappe.db.get_single_value(
|
||||
"Stock Settings", "do_not_use_batchwise_valuation"
|
||||
)
|
||||
|
||||
self.allow_negative_stock = allow_negative_stock or is_negative_stock_allowed(
|
||||
item_code=self.item_code
|
||||
)
|
||||
@@ -745,7 +749,7 @@ class update_entries_after:
|
||||
if sle.get(dimension.get("fieldname")):
|
||||
has_dimensions = True
|
||||
|
||||
if sle.serial_and_batch_bundle:
|
||||
if sle.serial_and_batch_bundle and (not self.use_moving_avg_for_batch or sle.has_serial_no):
|
||||
self.calculate_valuation_for_serial_batch_bundle(sle)
|
||||
elif sle.serial_no and not self.args.get("sle_id"):
|
||||
# Only run in reposting
|
||||
@@ -765,7 +769,12 @@ class update_entries_after:
|
||||
# Only run in reposting
|
||||
self.update_batched_values(sle)
|
||||
else:
|
||||
if sle.voucher_type == "Stock Reconciliation" and not sle.batch_no and not has_dimensions:
|
||||
if (
|
||||
sle.voucher_type == "Stock Reconciliation"
|
||||
and not sle.batch_no
|
||||
and not sle.has_batch_no
|
||||
and not has_dimensions
|
||||
):
|
||||
# assert
|
||||
self.wh_data.valuation_rate = sle.valuation_rate
|
||||
self.wh_data.qty_after_transaction = sle.qty_after_transaction
|
||||
@@ -806,6 +815,15 @@ class update_entries_after:
|
||||
sle.doctype = "Stock Ledger Entry"
|
||||
frappe.get_doc(sle).db_update()
|
||||
|
||||
if (
|
||||
sle.serial_and_batch_bundle
|
||||
and self.valuation_method == "Moving Average"
|
||||
and self.use_moving_avg_for_batch
|
||||
and (sle.batch_no or sle.has_batch_no)
|
||||
):
|
||||
valuation_rate = flt(stock_value_difference) / flt(sle.actual_qty)
|
||||
self.update_valuation_rate_in_serial_and_batch_bundle(sle, valuation_rate)
|
||||
|
||||
if not self.args.get("sle_id") or (
|
||||
sle.serial_and_batch_bundle and sle.auto_created_serial_and_batch_bundle
|
||||
):
|
||||
@@ -916,6 +934,21 @@ class update_entries_after:
|
||||
self.wh_data.qty_after_transaction, precision
|
||||
)
|
||||
|
||||
def update_valuation_rate_in_serial_and_batch_bundle(self, sle, valuation_rate):
|
||||
# Only execute if the item has batch_no and the valuation method is moving average
|
||||
if not frappe.db.exists("Serial and Batch Bundle", sle.serial_and_batch_bundle):
|
||||
return
|
||||
|
||||
doc = frappe.get_cached_doc("Serial and Batch Bundle", sle.serial_and_batch_bundle)
|
||||
doc.update_valuation_rate(valuation_rate, save=True)
|
||||
doc.calculate_qty_and_amount(save=True)
|
||||
|
||||
def get_outgoing_rate_for_batched_item(self, sle):
|
||||
if self.wh_data.qty_after_transaction == 0:
|
||||
return 0
|
||||
|
||||
return flt(self.wh_data.stock_value) / flt(self.wh_data.qty_after_transaction)
|
||||
|
||||
def validate_negative_stock(self, sle):
|
||||
"""
|
||||
validate negative stock for entries current datetime onwards
|
||||
|
||||
@@ -244,6 +244,8 @@ def get_incoming_rate(args, raise_error_if_no_rate=True):
|
||||
"Item", args.get("item_code"), ["has_serial_no", "has_batch_no"], as_dict=1
|
||||
)
|
||||
|
||||
use_moving_avg_for_batch = frappe.db.get_single_value("Stock Settings", "do_not_use_batchwise_valuation")
|
||||
|
||||
if isinstance(args, dict):
|
||||
args = frappe._dict(args)
|
||||
|
||||
@@ -257,7 +259,12 @@ def get_incoming_rate(args, raise_error_if_no_rate=True):
|
||||
|
||||
return sn_obj.get_incoming_rate()
|
||||
|
||||
elif item_details and item_details.has_batch_no and args.get("serial_and_batch_bundle"):
|
||||
elif (
|
||||
item_details
|
||||
and item_details.has_batch_no
|
||||
and args.get("serial_and_batch_bundle")
|
||||
and not use_moving_avg_for_batch
|
||||
):
|
||||
args.actual_qty = args.qty
|
||||
batch_obj = BatchNoValuation(
|
||||
sle=args,
|
||||
@@ -274,7 +281,7 @@ def get_incoming_rate(args, raise_error_if_no_rate=True):
|
||||
sn_obj = SerialNoValuation(sle=args, warehouse=args.get("warehouse"), item_code=args.get("item_code"))
|
||||
|
||||
return sn_obj.get_incoming_rate()
|
||||
elif args.get("batch_no") and not args.get("serial_and_batch_bundle"):
|
||||
elif args.get("batch_no") and not args.get("serial_and_batch_bundle") and not use_moving_avg_for_batch:
|
||||
args.actual_qty = args.qty
|
||||
args.batch_nos = frappe._dict({args.batch_no: args})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user