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:
rohitwaghchaure
2024-07-05 20:14:32 +05:30
committed by GitHub
parent 36403cde98
commit f06ba0cc36
12 changed files with 164 additions and 9 deletions

View File

@@ -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

View 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)

View File

@@ -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):

View File

@@ -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,
},
)

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -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-.#####",
},

View File

@@ -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
}
}

View File

@@ -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"]

View File

@@ -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

View File

@@ -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})