Merge pull request #50506 from mihir-kandoi/company-wise-valuation-method

feat: company wise valuation method
This commit is contained in:
rohitwaghchaure
2025-11-17 11:49:09 +05:30
committed by GitHub
12 changed files with 74 additions and 22 deletions

View File

@@ -12,7 +12,6 @@ from frappe.utils import cint, flt, format_datetime, get_datetime
import erpnext
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.utils import get_combine_datetime, get_incoming_rate, get_valuation_method
@@ -145,7 +144,7 @@ def validate_returned_items(doc):
ref.rate
and flt(d.rate) > ref.rate
and doc.doctype in ("Delivery Note", "Sales Invoice")
and get_valuation_method(ref.item_code) != "Moving Average"
and get_valuation_method(ref.item_code, doc.company) != "Moving Average"
):
frappe.throw(
_("Row # {0}: Rate cannot be greater than the rate used in {1} {2}").format(

View File

@@ -524,7 +524,7 @@ class SellingController(StockController):
)
if not self.get("return_against") or (
get_valuation_method(d.item_code) == "Moving Average"
get_valuation_method(d.item_code, self.company) == "Moving Average"
and self.get("is_return")
and not item_details.has_serial_no
and not item_details.has_batch_no
@@ -535,7 +535,10 @@ class SellingController(StockController):
if (
not d.incoming_rate
or self.is_internal_transfer()
or (get_valuation_method(d.item_code) == "Moving Average" and self.get("is_return"))
or (
get_valuation_method(d.item_code, self.company) == "Moving Average"
and self.get("is_return")
)
):
d.incoming_rate = get_incoming_rate(
{
@@ -560,7 +563,7 @@ class SellingController(StockController):
not d.incoming_rate
and self.get("return_against")
and self.get("is_return")
and get_valuation_method(d.item_code) == "Moving Average"
and get_valuation_method(d.item_code, self.company) == "Moving Average"
):
d.incoming_rate = get_rate_for_return(
self.doctype, self.name, d.item_code, self.return_against, item_row=d

View File

@@ -446,4 +446,5 @@ erpnext.patches.v16_0.add_new_stock_entry_types
erpnext.patches.v15_0.set_asset_status_if_not_already_set
erpnext.patches.v15_0.toggle_legacy_controller_for_period_closing
erpnext.patches.v16_0.update_serial_batch_entries
erpnext.patches.v16_0.set_company_wise_warehouses
erpnext.patches.v16_0.set_company_wise_warehouses
erpnext.patches.v16_0.set_valuation_method_on_companies

View File

@@ -0,0 +1,7 @@
import frappe
def execute():
valuation_method = frappe.get_single_value("Stock Settings", "valuation_method")
for company in frappe.get_all("Company", pluck="name"):
frappe.db.set_value("Company", company, "valuation_method", valuation_method)

View File

@@ -117,6 +117,7 @@
"enable_item_wise_inventory_account",
"enable_provisional_accounting_for_non_stock_items",
"default_inventory_account",
"valuation_method",
"column_break_32",
"stock_adjustment_account",
"stock_received_but_not_billed",
@@ -890,6 +891,14 @@
"fieldtype": "Check",
"label": "Enable Item-wise Inventory Account"
},
{
"default": "FIFO",
"fieldname": "valuation_method",
"fieldtype": "Select",
"label": "Default Stock Valuation Method",
"options": "FIFO\nMoving Average\nLIFO",
"reqd": 1
},
{
"fieldname": "default_wip_warehouse",
"fieldtype": "Link",

View File

@@ -116,6 +116,7 @@ class Company(NestedSet):
transactions_annual_history: DF.Code | None
unrealized_exchange_gain_loss_account: DF.Link | None
unrealized_profit_loss_account: DF.Link | None
valuation_method: DF.Literal["FIFO", "Moving Average", "LIFO"]
website: DF.Data | None
write_off_account: DF.Link | None
# end: auto-generated types
@@ -166,6 +167,32 @@ class Company(NestedSet):
self.validate_parent_company()
self.set_reporting_currency()
self.validate_inventory_account_settings()
self.cant_change_valuation_method()
def cant_change_valuation_method(self):
doc_before_save = self.get_doc_before_save()
if not doc_before_save:
return
previous_valuation_method = doc_before_save.get("valuation_method")
if previous_valuation_method and previous_valuation_method != self.valuation_method:
# check if there are any stock ledger entries against items
# which does not have it's own valuation method
sle = frappe.db.sql(
"""select name from `tabStock Ledger Entry` sle
where exists(select name from tabItem
where name=sle.item_code and (valuation_method is null or valuation_method='')) and sle.company=%s limit 1
""",
self.name,
)
if sle:
frappe.throw(
_(
"Can't change the valuation method, as there are transactions against some items which do not have its own valuation method"
)
)
def validate_inventory_account_settings(self):
doc_before_save = self.get_doc_before_save()

View File

@@ -514,7 +514,7 @@ class SerialandBatchBundle(Document):
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)
val_method = get_valuation_method(self.item_code, self.company)
for d in self.entries:
available_qty = 0
@@ -642,7 +642,7 @@ class SerialandBatchBundle(Document):
def set_incoming_rate_for_inward_transaction(self, row=None, save=False, prev_sle=None):
from erpnext.stock.utils import get_valuation_method
valuation_method = get_valuation_method(self.item_code)
valuation_method = get_valuation_method(self.item_code, self.company)
valuation_field = "valuation_rate"
if self.voucher_type in ["Sales Invoice", "Delivery Note", "Quotation"]:

View File

@@ -52,7 +52,7 @@ def format_report_data(filters: Filters, item_details: dict, to_date: str) -> li
range_values = get_range_age(filters, fifo_queue, to_date, item_dict)
check_and_replace_valuations_if_moving_average(
range_values, details.valuation_method, details.valuation_rate
range_values, details.valuation_method, details.valuation_rate, filters.get("company")
)
row = [details.name, details.item_name, details.description, details.item_group, details.brand]
@@ -76,10 +76,12 @@ def format_report_data(filters: Filters, item_details: dict, to_date: str) -> li
return data
def check_and_replace_valuations_if_moving_average(range_values, item_valuation_method, valuation_rate):
def check_and_replace_valuations_if_moving_average(
range_values, item_valuation_method, valuation_rate, company
):
if item_valuation_method == "Moving Average" or (
not item_valuation_method
and frappe.db.get_single_value("Stock Settings", "valuation_method") == "Moving Average"
and frappe.get_cached_value("Company", company, "valuation_method") == "Moving Average"
):
for i in range(0, len(range_values), 2):
range_values[i + 1] = range_values[i] * valuation_rate

View File

@@ -201,7 +201,7 @@ def get_columns():
def get_data(filters=None):
filters = frappe._dict(filters or {})
item_warehouse_map = get_item_warehouse_combinations(filters)
valuation_method = frappe.db.get_single_value("Stock Settings", "valuation_method")
valuation_method = frappe.get_cached_value("Company", filters.get("company"), "valuation_method")
data = []
if item_warehouse_map:

View File

@@ -861,9 +861,9 @@ class BatchNoValuation(DeprecatedBatchNoValuation):
self.batchwise_valuation_batches = []
self.non_batchwise_valuation_batches = []
if get_valuation_method(self.sle.item_code) == "Moving Average" and frappe.get_single_value(
"Stock Settings", "do_not_use_batchwise_valuation"
):
if get_valuation_method(
self.sle.item_code, self.sle.company
) == "Moving Average" and frappe.get_single_value("Stock Settings", "do_not_use_batchwise_valuation"):
self.non_batchwise_valuation_batches = self.batches
return

View File

@@ -563,7 +563,7 @@ class update_entries_after:
self.company = frappe.get_cached_value("Warehouse", self.args.warehouse, "company")
self.set_precision()
self.valuation_method = get_valuation_method(self.item_code)
self.valuation_method = get_valuation_method(self.item_code, self.company)
self.new_items_found = False
self.distinct_item_warehouses = args.get("distinct_item_warehouses", frappe._dict())
@@ -1087,7 +1087,7 @@ class update_entries_after:
avg_rate = 0.0
for d in sabb_data:
incoming_rate = get_incoming_rate_for_serial_and_batch(self.item_code, d, sn_obj)
incoming_rate = get_incoming_rate_for_serial_and_batch(self.item_code, d, sn_obj, self.company)
amount = incoming_rate * flt(d.qty)
tot_amt += flt(amount)
total_qty += flt(d.qty)
@@ -2398,7 +2398,7 @@ def get_serial_from_sabb(serial_and_batch_bundle):
)
def get_incoming_rate_for_serial_and_batch(item_code, row, sn_obj):
def get_incoming_rate_for_serial_and_batch(item_code, row, sn_obj, company):
if row.serial_no:
return abs(sn_obj.serial_no_incoming_rate.get(row.serial_no, 0.0))
else:
@@ -2406,7 +2406,7 @@ def get_incoming_rate_for_serial_and_batch(item_code, row, sn_obj):
if hasattr(sn_obj, "stock_queue") and sn_obj.stock_queue:
stock_queue = parse_json(sn_obj.stock_queue)
val_method = get_valuation_method(item_code)
val_method = get_valuation_method(item_code, company)
actual_qty = row.qty
if stock_queue and val_method == "FIFO" and row.batch_no in sn_obj.non_batchwise_valuation_batches:

View File

@@ -302,7 +302,7 @@ def get_incoming_rate(args, raise_error_if_no_rate=True):
return batch_obj.get_incoming_rate()
else:
valuation_method = get_valuation_method(args.get("item_code"))
valuation_method = get_valuation_method(args.get("item_code"), args.get("company"))
previous_sle = get_previous_sle(args)
if valuation_method in ("FIFO", "LIFO"):
if previous_sle:
@@ -374,11 +374,15 @@ def get_avg_purchase_rate(serial_nos):
@frappe.request_cache
def get_valuation_method(item_code):
def get_valuation_method(item_code, company=None):
"""get valuation method from item or default"""
val_method = frappe.get_cached_value("Item", item_code, "valuation_method")
if not val_method:
val_method = frappe.get_cached_doc("Stock Settings").valuation_method or "FIFO"
val_method = (
frappe.get_cached_value("Company", company, "valuation_method")
if company
else frappe.get_single_value("Stock Settings", "valuation_method") or "FIFO"
)
return val_method