mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-29 09:54:47 +00:00
feat: company wise valuation method
This commit is contained in:
@@ -12,7 +12,6 @@ from frappe.utils import cint, flt, format_datetime, get_datetime
|
|||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
from erpnext.stock.serial_batch_bundle import get_batches_from_bundle
|
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
|
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
|
ref.rate
|
||||||
and flt(d.rate) > ref.rate
|
and flt(d.rate) > ref.rate
|
||||||
and doc.doctype in ("Delivery Note", "Sales Invoice")
|
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(
|
frappe.throw(
|
||||||
_("Row # {0}: Rate cannot be greater than the rate used in {1} {2}").format(
|
_("Row # {0}: Rate cannot be greater than the rate used in {1} {2}").format(
|
||||||
|
|||||||
@@ -524,7 +524,7 @@ class SellingController(StockController):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if not self.get("return_against") or (
|
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 self.get("is_return")
|
||||||
and not item_details.has_serial_no
|
and not item_details.has_serial_no
|
||||||
and not item_details.has_batch_no
|
and not item_details.has_batch_no
|
||||||
@@ -535,7 +535,10 @@ class SellingController(StockController):
|
|||||||
if (
|
if (
|
||||||
not d.incoming_rate
|
not d.incoming_rate
|
||||||
or self.is_internal_transfer()
|
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(
|
d.incoming_rate = get_incoming_rate(
|
||||||
{
|
{
|
||||||
@@ -560,7 +563,7 @@ class SellingController(StockController):
|
|||||||
not d.incoming_rate
|
not d.incoming_rate
|
||||||
and self.get("return_against")
|
and self.get("return_against")
|
||||||
and self.get("is_return")
|
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(
|
d.incoming_rate = get_rate_for_return(
|
||||||
self.doctype, self.name, d.item_code, self.return_against, item_row=d
|
self.doctype, self.name, d.item_code, self.return_against, item_row=d
|
||||||
|
|||||||
@@ -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.set_asset_status_if_not_already_set
|
||||||
erpnext.patches.v15_0.toggle_legacy_controller_for_period_closing
|
erpnext.patches.v15_0.toggle_legacy_controller_for_period_closing
|
||||||
erpnext.patches.v16_0.update_serial_batch_entries
|
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
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -117,6 +117,7 @@
|
|||||||
"enable_item_wise_inventory_account",
|
"enable_item_wise_inventory_account",
|
||||||
"enable_provisional_accounting_for_non_stock_items",
|
"enable_provisional_accounting_for_non_stock_items",
|
||||||
"default_inventory_account",
|
"default_inventory_account",
|
||||||
|
"valuation_method",
|
||||||
"column_break_32",
|
"column_break_32",
|
||||||
"stock_adjustment_account",
|
"stock_adjustment_account",
|
||||||
"stock_received_but_not_billed",
|
"stock_received_but_not_billed",
|
||||||
@@ -890,6 +891,14 @@
|
|||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Enable Item-wise Inventory Account"
|
"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",
|
"fieldname": "default_wip_warehouse",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ class Company(NestedSet):
|
|||||||
transactions_annual_history: DF.Code | None
|
transactions_annual_history: DF.Code | None
|
||||||
unrealized_exchange_gain_loss_account: DF.Link | None
|
unrealized_exchange_gain_loss_account: DF.Link | None
|
||||||
unrealized_profit_loss_account: DF.Link | None
|
unrealized_profit_loss_account: DF.Link | None
|
||||||
|
valuation_method: DF.Literal["FIFO", "Moving Average", "LIFO"]
|
||||||
website: DF.Data | None
|
website: DF.Data | None
|
||||||
write_off_account: DF.Link | None
|
write_off_account: DF.Link | None
|
||||||
# end: auto-generated types
|
# end: auto-generated types
|
||||||
@@ -166,6 +167,32 @@ class Company(NestedSet):
|
|||||||
self.validate_parent_company()
|
self.validate_parent_company()
|
||||||
self.set_reporting_currency()
|
self.set_reporting_currency()
|
||||||
self.validate_inventory_account_settings()
|
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):
|
def validate_inventory_account_settings(self):
|
||||||
doc_before_save = self.get_doc_before_save()
|
doc_before_save = self.get_doc_before_save()
|
||||||
|
|||||||
@@ -514,7 +514,7 @@ class SerialandBatchBundle(Document):
|
|||||||
if hasattr(sn_obj, "stock_queue") and sn_obj.stock_queue:
|
if hasattr(sn_obj, "stock_queue") and sn_obj.stock_queue:
|
||||||
stock_queue = parse_json(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:
|
for d in self.entries:
|
||||||
available_qty = 0
|
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):
|
def set_incoming_rate_for_inward_transaction(self, row=None, save=False, prev_sle=None):
|
||||||
from erpnext.stock.utils import get_valuation_method
|
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"
|
valuation_field = "valuation_rate"
|
||||||
if self.voucher_type in ["Sales Invoice", "Delivery Note", "Quotation"]:
|
if self.voucher_type in ["Sales Invoice", "Delivery Note", "Quotation"]:
|
||||||
|
|||||||
@@ -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)
|
range_values = get_range_age(filters, fifo_queue, to_date, item_dict)
|
||||||
|
|
||||||
check_and_replace_valuations_if_moving_average(
|
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]
|
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
|
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 (
|
if item_valuation_method == "Moving Average" or (
|
||||||
not item_valuation_method
|
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):
|
for i in range(0, len(range_values), 2):
|
||||||
range_values[i + 1] = range_values[i] * valuation_rate
|
range_values[i + 1] = range_values[i] * valuation_rate
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ def get_columns():
|
|||||||
def get_data(filters=None):
|
def get_data(filters=None):
|
||||||
filters = frappe._dict(filters or {})
|
filters = frappe._dict(filters or {})
|
||||||
item_warehouse_map = get_item_warehouse_combinations(filters)
|
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 = []
|
data = []
|
||||||
if item_warehouse_map:
|
if item_warehouse_map:
|
||||||
|
|||||||
@@ -861,9 +861,9 @@ class BatchNoValuation(DeprecatedBatchNoValuation):
|
|||||||
self.batchwise_valuation_batches = []
|
self.batchwise_valuation_batches = []
|
||||||
self.non_batchwise_valuation_batches = []
|
self.non_batchwise_valuation_batches = []
|
||||||
|
|
||||||
if get_valuation_method(self.sle.item_code) == "Moving Average" and frappe.get_single_value(
|
if get_valuation_method(
|
||||||
"Stock Settings", "do_not_use_batchwise_valuation"
|
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
|
self.non_batchwise_valuation_batches = self.batches
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -563,7 +563,7 @@ class update_entries_after:
|
|||||||
|
|
||||||
self.company = frappe.get_cached_value("Warehouse", self.args.warehouse, "company")
|
self.company = frappe.get_cached_value("Warehouse", self.args.warehouse, "company")
|
||||||
self.set_precision()
|
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.new_items_found = False
|
||||||
self.distinct_item_warehouses = args.get("distinct_item_warehouses", frappe._dict())
|
self.distinct_item_warehouses = args.get("distinct_item_warehouses", frappe._dict())
|
||||||
@@ -1087,7 +1087,7 @@ class update_entries_after:
|
|||||||
avg_rate = 0.0
|
avg_rate = 0.0
|
||||||
|
|
||||||
for d in sabb_data:
|
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)
|
amount = incoming_rate * flt(d.qty)
|
||||||
tot_amt += flt(amount)
|
tot_amt += flt(amount)
|
||||||
total_qty += flt(d.qty)
|
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:
|
if row.serial_no:
|
||||||
return abs(sn_obj.serial_no_incoming_rate.get(row.serial_no, 0.0))
|
return abs(sn_obj.serial_no_incoming_rate.get(row.serial_no, 0.0))
|
||||||
else:
|
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:
|
if hasattr(sn_obj, "stock_queue") and sn_obj.stock_queue:
|
||||||
stock_queue = parse_json(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
|
actual_qty = row.qty
|
||||||
if stock_queue and val_method == "FIFO" and row.batch_no in sn_obj.non_batchwise_valuation_batches:
|
if stock_queue and val_method == "FIFO" and row.batch_no in sn_obj.non_batchwise_valuation_batches:
|
||||||
|
|||||||
@@ -302,7 +302,7 @@ def get_incoming_rate(args, raise_error_if_no_rate=True):
|
|||||||
|
|
||||||
return batch_obj.get_incoming_rate()
|
return batch_obj.get_incoming_rate()
|
||||||
else:
|
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)
|
previous_sle = get_previous_sle(args)
|
||||||
if valuation_method in ("FIFO", "LIFO"):
|
if valuation_method in ("FIFO", "LIFO"):
|
||||||
if previous_sle:
|
if previous_sle:
|
||||||
@@ -374,11 +374,15 @@ def get_avg_purchase_rate(serial_nos):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.request_cache
|
@frappe.request_cache
|
||||||
def get_valuation_method(item_code):
|
def get_valuation_method(item_code, company=None):
|
||||||
"""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")
|
||||||
if not val_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
|
return val_method
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user