diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index 4fd135d359a..cb0e160eca1 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -34,7 +34,6 @@ from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_sched
get_asset_depr_schedule_doc,
get_depr_schedule,
make_draft_asset_depr_schedules,
- make_draft_asset_depr_schedules_if_not_present,
update_draft_asset_depr_schedules,
)
from erpnext.controllers.accounts_controller import AccountsController
@@ -131,30 +130,55 @@ class Asset(AccountsController):
self.set_missing_values()
self.validate_gross_and_purchase_amount()
self.validate_finance_books()
-
- if not self.split_from:
- self.prepare_depreciation_data()
-
- if self.calculate_depreciation:
- update_draft_asset_depr_schedules(self)
-
- if frappe.db.exists("Asset", self.name):
- asset_depr_schedules_names = make_draft_asset_depr_schedules_if_not_present(self)
-
- if asset_depr_schedules_names:
- asset_depr_schedules_links = get_comma_separated_links(
- asset_depr_schedules_names, "Asset Depreciation Schedule"
- )
- frappe.msgprint(
- _(
- "Asset Depreciation Schedules created:
{0}
Please check, edit if needed, and submit the Asset."
- ).format(asset_depr_schedules_links)
- )
self.validate_expected_value_after_useful_life()
self.set_total_booked_depreciations()
self.total_asset_cost = self.gross_purchase_amount
self.status = self.get_status()
+ def create_asset_depreciation_schedule(self):
+ if self.split_from or not self.calculate_depreciation:
+ return
+
+ self.set_depr_rate_and_value_after_depreciation()
+
+ schedules = []
+ for row in self.get("finance_books"):
+ self.validate_asset_finance_books(row)
+ if not row.rate_of_depreciation:
+ row.rate_of_depreciation = self.get_depreciation_rate(row, on_validate=True)
+
+ schedule_doc = get_asset_depr_schedule_doc(self.name, "Draft", row.finance_book)
+ if not schedule_doc:
+ schedule_doc = frappe.new_doc("Asset Depreciation Schedule")
+
+ schedule_doc.prepare_draft_asset_depr_schedule_data(self, row)
+ schedule_doc.save()
+ schedules.append(schedule_doc.name)
+
+ self.show_schedule_creation_message(schedules)
+
+ def set_depr_rate_and_value_after_depreciation(self):
+ if self.calculate_depreciation:
+ self.value_after_depreciation = 0
+ self.set_depreciation_rate()
+ else:
+ self.finance_books = []
+ self.value_after_depreciation = flt(self.gross_purchase_amount) - flt(
+ self.opening_accumulated_depreciation
+ )
+
+ def show_schedule_creation_message(self, schedules):
+ if schedules:
+ asset_depr_schedules_links = get_comma_separated_links(schedules, "Asset Depreciation Schedule")
+ frappe.msgprint(
+ _(
+ "Asset Depreciation Schedules created/updated:
{0}
Please check, edit if needed, and submit the Asset."
+ ).format(asset_depr_schedules_links)
+ )
+
+ def on_update(self):
+ self.create_asset_depreciation_schedule()
+
def on_submit(self):
self.validate_in_use_date()
self.make_asset_movement()
@@ -178,17 +202,17 @@ class Asset(AccountsController):
self.db_set("booked_fixed_asset", 0)
add_asset_activity(self.name, _("Asset cancelled"))
- def after_insert(self):
- if self.calculate_depreciation and not self.split_from:
- asset_depr_schedules_names = make_draft_asset_depr_schedules(self)
- asset_depr_schedules_links = get_comma_separated_links(
- asset_depr_schedules_names, "Asset Depreciation Schedule"
- )
- frappe.msgprint(
- _(
- "Asset Depreciation Schedules created:
{0}
Please check, edit if needed, and submit the Asset."
- ).format(asset_depr_schedules_links)
- )
+ # def after_insert(self):
+ # if self.calculate_depreciation and not self.split_from:
+ # asset_depr_schedules_names = make_draft_asset_depr_schedules(self)
+ # asset_depr_schedules_links = get_comma_separated_links(
+ # asset_depr_schedules_names, "Asset Depreciation Schedule"
+ # )
+ # frappe.msgprint(
+ # _(
+ # "Asset Depreciation Schedules created:
{0}
Please check, edit if needed, and submit the Asset."
+ # ).format(asset_depr_schedules_links)
+ # )
if (
not frappe.db.exists(
{
@@ -250,16 +274,6 @@ class Asset(AccountsController):
if self.is_existing_asset and self.purchase_invoice:
frappe.throw(_("Purchase Invoice cannot be made against an existing asset {0}").format(self.name))
- def prepare_depreciation_data(self):
- if self.calculate_depreciation:
- self.value_after_depreciation = 0
- self.set_depreciation_rate()
- else:
- self.finance_books = []
- self.value_after_depreciation = flt(self.gross_purchase_amount) - flt(
- self.opening_accumulated_depreciation
- )
-
def validate_item(self):
item = frappe.get_cached_value(
"Item", self.item_code, ["is_fixed_asset", "is_stock_item", "disabled"], as_dict=1
@@ -344,6 +358,22 @@ class Asset(AccountsController):
title=_("Missing Finance Book"),
)
+ # def set_depreciation_start_date(self):
+ # if not self.calculate_depreciation:
+ # return
+
+ # for d in self.get("finance_books"):
+ # if not d.depreciation_start_date:
+ # if self.is_existing_asset and self.opening_number_of_booked_depreciations:
+
+ # months = d.frequency_of_depreciation * self.opening_number_of_booked_depreciations
+ # else:
+ # months = d.frequency_of_depreciation
+
+ # d.depreciation_start_date = get_last_day(
+ # add_months(self.available_for_use_date, months)
+ # )
+
def validate_precision(self):
if self.gross_purchase_amount:
self.gross_purchase_amount = flt(
@@ -456,61 +486,65 @@ class Asset(AccountsController):
frappe.throw(
_("Row {0}: Expected Value After Useful Life must be less than Gross Purchase Amount").format(
row.idx
- ),
- title=_("Invalid Schedule"),
+ )
)
if not row.depreciation_start_date:
- if not self.available_for_use_date:
- frappe.throw(
- _("Row {0}: Depreciation Start Date is required").format(row.idx),
- title=_("Invalid Schedule"),
- )
row.depreciation_start_date = get_last_day(self.available_for_use_date)
+ self.validate_depreciation_start_date(row)
if not self.is_existing_asset:
self.opening_accumulated_depreciation = 0
self.opening_number_of_booked_depreciations = 0
else:
- depreciable_amount = flt(
- flt(self.gross_purchase_amount) - flt(row.expected_value_after_useful_life),
- self.precision("gross_purchase_amount"),
- )
- if flt(self.opening_accumulated_depreciation) > depreciable_amount:
- frappe.throw(
- _("Opening Accumulated Depreciation must be less than or equal to {0}").format(
- depreciable_amount
- )
- )
+ self.validate_opening_depreciation_values(row)
- if self.opening_accumulated_depreciation:
- if not self.opening_number_of_booked_depreciations:
- frappe.throw(_("Please set Opening Number of Booked Depreciations"))
- else:
- self.opening_number_of_booked_depreciations = 0
-
- if flt(row.total_number_of_depreciations) <= cint(self.opening_number_of_booked_depreciations):
- frappe.throw(
- _(
- "Row {0}: Total Number of Depreciations cannot be less than or equal to Opening Number of Booked Depreciations"
- ).format(row.idx),
- title=_("Invalid Schedule"),
- )
-
- if row.depreciation_start_date and getdate(row.depreciation_start_date) < getdate(self.purchase_date):
+ def validate_opening_depreciation_values(self, row):
+ row.expected_value_after_useful_life = flt(
+ row.expected_value_after_useful_life, self.precision("gross_purchase_amount")
+ )
+ depreciable_amount = flt(
+ flt(self.gross_purchase_amount) - flt(row.expected_value_after_useful_life),
+ self.precision("gross_purchase_amount"),
+ )
+ if flt(self.opening_accumulated_depreciation) > depreciable_amount:
frappe.throw(
- _("Depreciation Row {0}: Next Depreciation Date cannot be before Purchase Date").format(
- row.idx
+ _("Row #{0}: Opening Accumulated Depreciation must be less than or equal to {1}").format(
+ row.idx, depreciable_amount
)
)
- if row.depreciation_start_date and getdate(row.depreciation_start_date) < getdate(
- self.available_for_use_date
- ):
+ if self.opening_accumulated_depreciation:
+ if not self.opening_number_of_booked_depreciations:
+ frappe.throw(_("Please set opening number of booked depreciations"))
+ else:
+ self.opening_number_of_booked_depreciations = 0
+
+ if flt(row.total_number_of_depreciations) <= cint(self.opening_number_of_booked_depreciations):
frappe.throw(
_(
- "Depreciation Row {0}: Next Depreciation Date cannot be before Available-for-use Date"
- ).format(row.idx)
+ "Row #{0}: Total Number of Depreciations cannot be less than or equal to Opening Number of Booked Depreciations"
+ ).format(row.idx),
+ title=_("Invalid Schedule"),
+ )
+
+ def validate_depreciation_start_date(self, row):
+ if row.depreciation_start_date:
+ if getdate(row.depreciation_start_date) < getdate(self.purchase_date):
+ frappe.throw(
+ _("Row #{0}: Next Depreciation Date cannot be before Purchase Date").format(row.idx)
+ )
+
+ if getdate(row.depreciation_start_date) < getdate(self.available_for_use_date):
+ frappe.throw(
+ _("Row #{0}: Next Depreciation Date cannot be before Available-for-use Date").format(
+ row.idx
+ )
+ )
+ else:
+ frappe.throw(
+ _("Row #{0}: Depreciation Start Date is required").format(row.idx),
+ title=_("Invalid Schedule"),
)
def set_total_booked_depreciations(self):
@@ -530,15 +564,11 @@ class Asset(AccountsController):
if not depr_schedule:
continue
- accumulated_depreciation_after_full_schedule = [
- d.accumulated_depreciation_amount for d in depr_schedule
- ]
+ accumulated_depreciation_after_full_schedule = max(
+ [d.accumulated_depreciation_amount for d in depr_schedule]
+ )
if accumulated_depreciation_after_full_schedule:
- accumulated_depreciation_after_full_schedule = max(
- accumulated_depreciation_after_full_schedule
- )
-
asset_value_after_full_schedule = flt(
flt(self.gross_purchase_amount) - flt(accumulated_depreciation_after_full_schedule),
self.precision("gross_purchase_amount"),
@@ -820,53 +850,62 @@ class Asset(AccountsController):
if isinstance(args, str):
args = json.loads(args)
- float_precision = cint(frappe.db.get_default("float_precision")) or 2
+ rate_field_precision = frappe.get_precision(args.doctype, "rate_of_depreciation") or 2
if args.get("depreciation_method") == "Double Declining Balance":
- return 200.0 / (
+ return self.get_double_declining_balance_rate(args, rate_field_precision)
+ elif args.get("depreciation_method") == "Written Down Value":
+ return self.get_written_down_value_rate(args, rate_field_precision, on_validate)
+
+ def get_double_declining_balance_rate(self, args, rate_field_precision):
+ return flt(
+ 200.0
+ / (
(
flt(args.get("total_number_of_depreciations"), 2)
* flt(args.get("frequency_of_depreciation"))
)
/ 12
+ ),
+ rate_field_precision,
+ )
+
+ def get_written_down_value_rate(self, args, rate_field_precision, on_validate):
+ if (
+ args.get("rate_of_depreciation")
+ and on_validate
+ and not self.flags.increase_in_asset_value_due_to_repair
+ ):
+ return args.get("rate_of_depreciation")
+
+ if args.get("rate_of_depreciation") and not flt(args.get("expected_value_after_useful_life")):
+ return args.get("rate_of_depreciation")
+
+ if self.flags.increase_in_asset_value_due_to_repair:
+ value = flt(args.get("expected_value_after_useful_life")) / flt(
+ args.get("value_after_depreciation")
+ )
+ else:
+ value = flt(args.get("expected_value_after_useful_life")) / (
+ flt(self.gross_purchase_amount) - flt(self.opening_accumulated_depreciation)
)
- if args.get("depreciation_method") == "Written Down Value":
- if (
- args.get("rate_of_depreciation")
- and on_validate
- and not self.flags.increase_in_asset_value_due_to_repair
- ):
- return args.get("rate_of_depreciation")
-
- if args.get("rate_of_depreciation") and not flt(args.get("expected_value_after_useful_life")):
- return args.get("rate_of_depreciation")
-
- if self.flags.increase_in_asset_value_due_to_repair:
- value = flt(args.get("expected_value_after_useful_life")) / flt(
- args.get("value_after_depreciation")
- )
- else:
- value = flt(args.get("expected_value_after_useful_life")) / (
- flt(self.gross_purchase_amount) - flt(self.opening_accumulated_depreciation)
- )
-
- depreciation_rate = math.pow(
- value,
- 1.0
- / (
+ depreciation_rate = math.pow(
+ value,
+ 1.0
+ / (
+ (
(
- (
- flt(args.get("total_number_of_depreciations"), 2)
- - flt(self.opening_number_of_booked_depreciations)
- )
- * flt(args.get("frequency_of_depreciation"))
+ flt(args.get("total_number_of_depreciations"), 2)
+ - flt(self.opening_number_of_booked_depreciations)
)
- / 12
- ),
- )
+ * flt(args.get("frequency_of_depreciation"))
+ )
+ / 12
+ ),
+ )
- return flt((100 * (1 - depreciation_rate)), float_precision)
+ return flt((100 * (1 - depreciation_rate)), rate_field_precision)
def has_gl_entries(doctype, docname, target_account):
diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py
index 3f984d9326e..be8ce0ff5fc 100644
--- a/erpnext/assets/doctype/asset/test_asset.py
+++ b/erpnext/assets/doctype/asset/test_asset.py
@@ -34,6 +34,8 @@ from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_sched
_get_pro_rata_amt,
get_asset_depr_schedule_doc,
get_depr_schedule,
+)
+from erpnext.assets.doctype.asset_depreciation_schedule.utils import (
get_depreciation_amount,
)
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json
index 76565cb4e38..fb075df4acc 100644
--- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json
@@ -25,6 +25,7 @@
"column_break_8",
"frequency_of_depreciation",
"expected_value_after_useful_life",
+ "value_after_depreciation",
"depreciation_schedule_section",
"depreciation_schedule",
"details_section",
@@ -38,6 +39,7 @@
"fieldtype": "Link",
"in_list_view": 1,
"label": "Asset",
+ "link_filters": "[[\"Asset\",\"docstatus\",\"<\",\"2\"],[\"Asset\",\"company\",\"=\",\"eval:doc.company\"]]",
"options": "Asset",
"reqd": 1
},
@@ -202,12 +204,18 @@
"label": "Company",
"options": "Company",
"read_only": 1
+ },
+ {
+ "fieldname": "value_after_depreciation",
+ "fieldtype": "Currency",
+ "label": "Value After Depreciation",
+ "read_only": 1
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2024-03-27 13:06:34.135004",
+ "modified": "2024-12-02 17:54:20.635668",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Depreciation Schedule",
diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
index 5bddea56183..55122d48607 100644
--- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
@@ -21,6 +21,9 @@ from frappe.utils import (
import erpnext
from erpnext.accounts.utils import get_fiscal_year
+from erpnext.assets.doctype.asset_depreciation_schedule.utils import (
+ get_depreciation_amount,
+)
class AssetDepreciationSchedule(Document):
@@ -32,9 +35,7 @@ class AssetDepreciationSchedule(Document):
if TYPE_CHECKING:
from frappe.types import DF
- from erpnext.assets.doctype.depreciation_schedule.depreciation_schedule import (
- DepreciationSchedule,
- )
+ from erpnext.assets.doctype.depreciation_schedule.depreciation_schedule import DepreciationSchedule
amended_from: DF.Link | None
asset: DF.Link
@@ -51,12 +52,13 @@ class AssetDepreciationSchedule(Document):
gross_purchase_amount: DF.Currency
naming_series: DF.Literal["ACC-ADS-.YYYY.-"]
notes: DF.SmallText | None
- opening_number_of_booked_depreciations: DF.Int
opening_accumulated_depreciation: DF.Currency
+ opening_number_of_booked_depreciations: DF.Int
rate_of_depreciation: DF.Percent
shift_based: DF.Check
status: DF.Literal["Draft", "Active", "Cancelled"]
total_number_of_depreciations: DF.Int
+ value_after_depreciation: DF.Currency
# end: auto-generated types
def before_save(self):
@@ -145,50 +147,9 @@ class AssetDepreciationSchedule(Document):
date_of_return=None,
update_asset_finance_book_row=True,
):
- have_asset_details_been_modified = self.have_asset_details_been_modified(asset_doc)
- not_manual_depr_or_have_manual_depr_details_been_modified = (
- self.not_manual_depr_or_have_manual_depr_details_been_modified(row)
- )
-
self.set_draft_asset_depr_schedule_details(asset_doc, row)
-
- if self.should_prepare_depreciation_schedule(
- have_asset_details_been_modified, not_manual_depr_or_have_manual_depr_details_been_modified
- ):
- self.make_depr_schedule(asset_doc, row, date_of_disposal, update_asset_finance_book_row)
- self.set_accumulated_depreciation(asset_doc, row, date_of_disposal, date_of_return)
-
- def have_asset_details_been_modified(self, asset_doc):
- return (
- asset_doc.gross_purchase_amount != self.gross_purchase_amount
- or asset_doc.opening_accumulated_depreciation != self.opening_accumulated_depreciation
- or asset_doc.opening_number_of_booked_depreciations != self.opening_number_of_booked_depreciations
- )
-
- def not_manual_depr_or_have_manual_depr_details_been_modified(self, row):
- return (
- self.depreciation_method != "Manual"
- or row.total_number_of_depreciations != self.total_number_of_depreciations
- or row.frequency_of_depreciation != self.frequency_of_depreciation
- or getdate(row.depreciation_start_date) != self.get("depreciation_schedule")[0].schedule_date
- or row.expected_value_after_useful_life != self.expected_value_after_useful_life
- )
-
- def should_prepare_depreciation_schedule(
- self, have_asset_details_been_modified, not_manual_depr_or_have_manual_depr_details_been_modified
- ):
- if not self.get("depreciation_schedule"):
- return True
-
- old_asset_depr_schedule_doc = self.get_doc_before_save()
-
- if self.docstatus != 0 and not old_asset_depr_schedule_doc:
- return True
-
- if have_asset_details_been_modified or not_manual_depr_or_have_manual_depr_details_been_modified:
- return True
-
- return False
+ self.make_depr_schedule(asset_doc, row, date_of_disposal, update_asset_finance_book_row)
+ self.set_accumulated_depreciation(asset_doc, row, date_of_disposal, date_of_return)
def set_draft_asset_depr_schedule_details(self, asset_doc, row):
self.asset = asset_doc.name
@@ -214,11 +175,11 @@ class AssetDepreciationSchedule(Document):
update_asset_finance_book_row=True,
value_after_depreciation=None,
):
- if not self.get("depreciation_schedule"):
- self.depreciation_schedule = []
+ # if not self.get("depreciation_schedule"):
+ # self.depreciation_schedule = []
- if not asset_doc.available_for_use_date:
- return
+ # if not asset_doc.available_for_use_date:
+ # return
start = self.clear_depr_schedule()
@@ -227,6 +188,9 @@ class AssetDepreciationSchedule(Document):
)
def clear_depr_schedule(self):
+ """
+ Clears the depreciation schedule preserving the depreciation entries that have been booked.
+ """
start = 0
num_of_depreciations_completed = 0
depr_schedule = []
@@ -254,7 +218,7 @@ class AssetDepreciationSchedule(Document):
update_asset_finance_book_row,
value_after_depreciation,
):
- asset_doc.validate_asset_finance_books(row)
+ # asset_doc.validate_asset_finance_books(row)
if not value_after_depreciation:
value_after_depreciation = _get_value_after_depreciation_for_making_schedule(asset_doc, row)
@@ -263,13 +227,7 @@ class AssetDepreciationSchedule(Document):
if update_asset_finance_book_row:
row.db_update()
- final_number_of_depreciations = cint(row.total_number_of_depreciations) - cint(
- self.opening_number_of_booked_depreciations
- )
-
- has_pro_rata = _check_is_pro_rata(asset_doc, row)
- if has_pro_rata:
- final_number_of_depreciations += 1
+ final_number_of_depreciations, has_pro_rata = self.get_final_number_of_depreciations(asset_doc, row)
has_wdv_or_dd_non_yearly_pro_rata = False
if (
@@ -285,7 +243,7 @@ class AssetDepreciationSchedule(Document):
number_of_pending_depreciations = final_number_of_depreciations - start
yearly_opening_wdv = value_after_depreciation
- current_fiscal_year_end_date = None
+ self.current_fiscal_year_end_date = None
prev_per_day_depr = True
for n in range(start, final_number_of_depreciations):
# If depreciation is already completed (for double declining balance)
@@ -295,16 +253,12 @@ class AssetDepreciationSchedule(Document):
schedule_date = get_last_day(
add_months(row.depreciation_start_date, n * cint(row.frequency_of_depreciation))
)
- if not current_fiscal_year_end_date:
- current_fiscal_year_end_date = get_fiscal_year(row.depreciation_start_date)[2]
- elif getdate(schedule_date) > getdate(current_fiscal_year_end_date):
- current_fiscal_year_end_date = add_years(current_fiscal_year_end_date, 1)
+
+ if self.has_fiscal_year_changed(row, n):
yearly_opening_wdv = value_after_depreciation
- if n > 0 and len(self.get("depreciation_schedule")) > n - 1:
- prev_depreciation_amount = self.get("depreciation_schedule")[n - 1].depreciation_amount
- else:
- prev_depreciation_amount = 0
+ prev_depreciation_amount = self.get_prev_depreciation_amount(n)
+
depreciation_amount, prev_per_day_depr = get_depreciation_amount(
self,
asset_doc,
@@ -317,146 +271,120 @@ class AssetDepreciationSchedule(Document):
number_of_pending_depreciations,
prev_per_day_depr,
)
- if not has_pro_rata or (
- n < (cint(final_number_of_depreciations) - 1) or final_number_of_depreciations == 2
- ):
- schedule_date = add_months(
- row.depreciation_start_date, n * cint(row.frequency_of_depreciation)
- )
- if should_get_last_day:
- schedule_date = get_last_day(schedule_date)
+ schedule_date = self.get_next_schedule_date(
+ row, n, schedule_date, has_pro_rata, should_get_last_day, final_number_of_depreciations
+ )
# if asset is being sold or scrapped
if date_of_disposal and getdate(schedule_date) >= getdate(date_of_disposal):
- from_date = add_months(
- getdate(asset_doc.available_for_use_date),
- (asset_doc.opening_number_of_booked_depreciations * row.frequency_of_depreciation),
+ self.get_depreciation_amount_for_disposal(
+ asset_doc, row, n, schedule_date, date_of_disposal, depreciation_amount
)
- if is_last_day_of_the_month(getdate(asset_doc.available_for_use_date)):
- from_date = get_last_day(from_date)
- if self.depreciation_schedule:
- from_date = add_days(self.depreciation_schedule[-1].schedule_date, 1)
-
- depreciation_amount, days, months = _get_pro_rata_amt(
- row,
- depreciation_amount,
- from_date,
- date_of_disposal,
- original_schedule_date=schedule_date,
- )
- depreciation_amount = flt(depreciation_amount, asset_doc.precision("gross_purchase_amount"))
- if depreciation_amount > 0:
- self.add_depr_schedule_row(date_of_disposal, depreciation_amount, n)
-
break
- # For first row
- if (
- n == 0
- and (has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata)
- and not self.opening_accumulated_depreciation
- and not self.flags.wdv_it_act_applied
- ):
- from_date = asset_doc.available_for_use_date
- # needed to calc depr amount for available_for_use_date too
- depreciation_amount, days, months = _get_pro_rata_amt(
- row,
- depreciation_amount,
- from_date,
- row.depreciation_start_date,
- has_wdv_or_dd_non_yearly_pro_rata,
+ if n == 0:
+ # Get pro rata amount for first row if available for use date is mid of the month
+ depreciation_amount = self.get_depreciation_amount_for_first_row(
+ asset_doc, row, n, depreciation_amount, has_pro_rata, has_wdv_or_dd_non_yearly_pro_rata
)
- if flt(depreciation_amount, asset_doc.precision("gross_purchase_amount")) <= 0:
- frappe.throw(
- _(
- "Gross Purchase Amount Too Low: {0} cannot be depreciated over {1} cycles with a frequency of {2} depreciations."
- ).format(
- frappe.bold(asset_doc.gross_purchase_amount),
- frappe.bold(row.total_number_of_depreciations),
- frappe.bold(row.frequency_of_depreciation),
- )
- )
- elif n == 0 and has_wdv_or_dd_non_yearly_pro_rata and self.opening_accumulated_depreciation:
- if not is_first_day_of_the_month(getdate(asset_doc.available_for_use_date)):
- from_date = get_last_day(
- add_months(
- getdate(asset_doc.available_for_use_date),
- (
- (self.opening_number_of_booked_depreciations - 1)
- * row.frequency_of_depreciation
- ),
- )
- )
- else:
- from_date = add_months(
- getdate(add_days(asset_doc.available_for_use_date, -1)),
- (self.opening_number_of_booked_depreciations * row.frequency_of_depreciation),
- )
- depreciation_amount, days, months = _get_pro_rata_amt(
- row,
- depreciation_amount,
- from_date,
- row.depreciation_start_date,
- has_wdv_or_dd_non_yearly_pro_rata,
+ elif has_pro_rata and n == cint(final_number_of_depreciations) - 1: # for the last row
+ depreciation_amount, schedule_date = self.get_depreciation_amount_for_last_row(
+ asset_doc, row, n, depreciation_amount, schedule_date, has_wdv_or_dd_non_yearly_pro_rata
)
- # For last row
- elif has_pro_rata and n == cint(final_number_of_depreciations) - 1:
- if not asset_doc.flags.increase_in_asset_life:
- # In case of increase_in_asset_life, the asset.to_date is already set on asset_repair submission
- asset_doc.to_date = add_months(
- asset_doc.available_for_use_date,
- (n + self.opening_number_of_booked_depreciations)
- * cint(row.frequency_of_depreciation),
- )
- if is_last_day_of_the_month(getdate(asset_doc.available_for_use_date)):
- asset_doc.to_date = get_last_day(asset_doc.to_date)
-
- depreciation_amount_without_pro_rata = depreciation_amount
-
- depreciation_amount, days, months = _get_pro_rata_amt(
- row,
- depreciation_amount,
- schedule_date,
- asset_doc.to_date,
- has_wdv_or_dd_non_yearly_pro_rata,
- )
-
- depreciation_amount = self.get_adjusted_depreciation_amount(
- depreciation_amount_without_pro_rata, depreciation_amount
- )
-
- schedule_date = add_days(schedule_date, days - 1)
-
if not depreciation_amount:
- continue
- depreciation_amount = flt(depreciation_amount, asset_doc.precision("gross_purchase_amount"))
+ break
+
+
value_after_depreciation = flt(
- value_after_depreciation - flt(depreciation_amount),
+ value_after_depreciation - flt(depreciation_amount, asset_doc.precision("gross_purchase_amount")),
asset_doc.precision("gross_purchase_amount"),
)
# Adjust depreciation amount in the last period based on the expected value after useful life
- if (
- n == cint(final_number_of_depreciations) - 1
- and flt(value_after_depreciation) != flt(row.expected_value_after_useful_life)
- ) or flt(value_after_depreciation) < flt(row.expected_value_after_useful_life):
- depreciation_amount += flt(value_after_depreciation) - flt(
- row.expected_value_after_useful_life
- )
- depreciation_amount = flt(depreciation_amount, asset_doc.precision("gross_purchase_amount"))
- skip_row = True
+ depreciation_amount, skip_row = self.adjust_depr_amount_for_salvage_value(
+ row, depreciation_amount, value_after_depreciation, final_number_of_depreciations, n, skip_row
+ )
if flt(depreciation_amount, asset_doc.precision("gross_purchase_amount")) > 0:
self.add_depr_schedule_row(schedule_date, depreciation_amount, n)
- # to ensure that final accumulated depreciation amount is accurate
+ def get_final_number_of_depreciations(self, asset_doc, row):
+ final_number_of_depreciations = cint(row.total_number_of_depreciations) - cint(
+ self.opening_number_of_booked_depreciations
+ )
+
+ has_pro_rata = _check_is_pro_rata(asset_doc, row)
+ if has_pro_rata:
+ final_number_of_depreciations += 1
+
+ return final_number_of_depreciations, has_pro_rata
+
+ def has_fiscal_year_changed(self, row, row_no):
+ fiscal_year_changed = False
+
+ schedule_date = get_last_day(
+ add_months(row.depreciation_start_date, row_no * cint(row.frequency_of_depreciation))
+ )
+
+ if not self.current_fiscal_year_end_date:
+ self.current_fiscal_year_end_date = get_fiscal_year(row.depreciation_start_date)[2]
+ fiscal_year_changed = True
+ elif getdate(schedule_date) > getdate(self.current_fiscal_year_end_date):
+ self.current_fiscal_year_end_date = add_years(self.current_fiscal_year_end_date, 1)
+ fiscal_year_changed = True
+
+ return fiscal_year_changed
+
+ def get_prev_depreciation_amount(self, n):
+ prev_depreciation_amount = 0
+ if n > 0 and len(self.get("depreciation_schedule")) > n - 1:
+ prev_depreciation_amount = self.get("depreciation_schedule")[n - 1].depreciation_amount
+
+ return prev_depreciation_amount
+
+ def get_next_schedule_date(
+ self, row, n, schedule_date, has_pro_rata, should_get_last_day, final_number_of_depreciations=None
+ ):
+ if not has_pro_rata or (
+ n < (cint(final_number_of_depreciations) - 1) or final_number_of_depreciations == 2
+ ):
+ schedule_date = add_months(row.depreciation_start_date, n * cint(row.frequency_of_depreciation))
+
+ if should_get_last_day:
+ schedule_date = get_last_day(schedule_date)
+
+ return schedule_date
+
+ def get_depreciation_amount_for_disposal(
+ self, asset_doc, row, row_no, schedule_date, date_of_disposal, depreciation_amount
+ ):
+ if self.depreciation_schedule: # if there are already booked depreciations
+ from_date = add_days(self.depreciation_schedule[-1].schedule_date, 1)
+ else:
+ from_date = _get_modified_available_for_use_date_for_existing_assets(asset_doc, row)
+ if is_last_day_of_the_month(getdate(asset_doc.available_for_use_date)):
+ from_date = get_last_day(from_date)
+
+ depreciation_amount, days, months = _get_pro_rata_amt(
+ row,
+ depreciation_amount,
+ from_date,
+ date_of_disposal,
+ original_schedule_date=schedule_date,
+ )
+
+ depreciation_amount = flt(depreciation_amount, asset_doc.precision("gross_purchase_amount"))
+ if depreciation_amount > 0:
+ self.add_depr_schedule_row(date_of_disposal, depreciation_amount, row_no)
+
def get_adjusted_depreciation_amount(
self, depreciation_amount_without_pro_rata, depreciation_amount_for_last_row
):
+ # to ensure that final accumulated depreciation amount is accurate
if not self.opening_accumulated_depreciation:
- depreciation_amount_for_first_row = self.get_depreciation_amount_for_first_row()
+ depreciation_amount_for_first_row = self.get("depreciation_schedule")[0].depreciation_amount
if (
depreciation_amount_for_first_row + depreciation_amount_for_last_row
@@ -468,8 +396,84 @@ class AssetDepreciationSchedule(Document):
return depreciation_amount_for_last_row
- def get_depreciation_amount_for_first_row(self):
- return self.get("depreciation_schedule")[0].depreciation_amount
+ def get_depreciation_amount_for_first_row(
+ self, asset_doc, row, n, depreciation_amount, has_pro_rata, has_wdv_or_dd_non_yearly_pro_rata
+ ):
+ """
+ For the first row, if available for use date is mid of the month, then pro rata amount is needed
+ """
+ if (
+ (has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata)
+ and not self.opening_accumulated_depreciation
+ and not self.flags.wdv_it_act_applied
+ ): # if not existing asset
+ from_date = asset_doc.available_for_use_date
+ elif has_wdv_or_dd_non_yearly_pro_rata and self.opening_accumulated_depreciation: # if existing asset
+ from_date = _get_modified_available_for_use_date_for_existing_assets(asset_doc, row)
+
+ depreciation_amount, days, months = _get_pro_rata_amt(
+ row,
+ depreciation_amount,
+ from_date,
+ row.depreciation_start_date,
+ has_wdv_or_dd_non_yearly_pro_rata,
+ )
+
+ self.validate_depreciation_amount_for_low_value_assets(asset_doc, row, depreciation_amount)
+
+ return depreciation_amount
+
+ def get_depreciation_amount_for_last_row(
+ self, asset_doc, row, n, depreciation_amount, schedule_date, has_wdv_or_dd_non_yearly_pro_rata
+ ):
+ if not asset_doc.flags.increase_in_asset_life:
+ # In case of increase_in_asset_life, the asset.to_date is already set on asset_repair submission
+ asset_doc.to_date = add_months(
+ asset_doc.available_for_use_date,
+ (n + self.opening_number_of_booked_depreciations) * cint(row.frequency_of_depreciation),
+ )
+ if is_last_day_of_the_month(getdate(asset_doc.available_for_use_date)):
+ asset_doc.to_date = get_last_day(asset_doc.to_date)
+
+ if self.opening_accumulated_depreciation:
+ depreciation_amount, days, months = _get_pro_rata_amt(
+ row,
+ depreciation_amount,
+ schedule_date,
+ asset_doc.to_date,
+ has_wdv_or_dd_non_yearly_pro_rata,
+ )
+ else:
+ # if not existing asset, remaining amount of first row is depreciated in the last row
+ depreciation_amount -= self.get("depreciation_schedule")[0].depreciation_amount
+
+ schedule_date = add_days(schedule_date, days - 1)
+ return depreciation_amount, schedule_date
+
+ def adjust_depr_amount_for_salvage_value(
+ row, depreciation_amount, value_after_depreciation, final_number_of_depreciations, row_no, skip_row
+ ):
+ if (
+ row_no == cint(final_number_of_depreciations) - 1
+ and flt(value_after_depreciation) != flt(row.expected_value_after_useful_life)
+ ) or flt(value_after_depreciation) < flt(row.expected_value_after_useful_life):
+ depreciation_amount += flt(value_after_depreciation) - flt(row.expected_value_after_useful_life)
+ depreciation_amount = flt(depreciation_amount, row.precision("depreciation_amount"))
+ skip_row = True
+ return depreciation_amount, skip_row
+
+ def validate_depreciation_amount_for_low_value_assets(self, asset_doc, row, depreciation_amount):
+ """ "
+ If gross purchase amount is too low, then depreciation amount
+ can come zero sometimes based on the frequency and number of depreciations.
+ """
+ if flt(depreciation_amount, asset_doc.precision("gross_purchase_amount")) <= 0:
+ frappe.throw(
+ _("Gross Purchase Amount {0} cannot be depreciated over {1} cycles.").format(
+ frappe.bold(asset_doc.gross_purchase_amount),
+ frappe.bold(row.total_number_of_depreciations),
+ )
+ )
def add_depr_schedule_row(self, schedule_date, depreciation_amount, schedule_idx):
if self.shift_based:
@@ -576,7 +580,7 @@ def _check_is_pro_rata(asset_doc, row, wdv_or_dd_non_yearly=False):
days = date_diff(prev_depreciation_start_date, from_date) + 1
total_days = get_total_days(prev_depreciation_start_date, row.frequency_of_depreciation)
else:
- from_date = _get_modified_available_for_use_date(asset_doc, row, wdv_or_dd_non_yearly=False)
+ from_date = _get_modified_available_for_use_date_for_existing_assets(asset_doc, row)
days = date_diff(row.depreciation_start_date, from_date) + 1
total_days = get_total_days(row.depreciation_start_date, row.frequency_of_depreciation)
if days <= 0:
@@ -595,11 +599,12 @@ def _check_is_pro_rata(asset_doc, row, wdv_or_dd_non_yearly=False):
return has_pro_rata
-def _get_modified_available_for_use_date(asset_doc, row, wdv_or_dd_non_yearly=False):
+def _get_modified_available_for_use_date_for_existing_assets(asset_doc, row):
"""
- if Asset has opening booked depreciations = 9,
+ if Asset has opening booked depreciations = 3,
+ frequency of depreciation = 3,
available for use date = 17-07-2023,
- depreciation start date = 30-04-2024
+ depreciation start date = 30-06-2024
then from date should be 01-04-2024
"""
if asset_doc.opening_number_of_booked_depreciations > 0:
@@ -641,359 +646,6 @@ def get_total_days(date, frequency):
return date_diff(date, period_start_date)
-def get_depreciation_amount(
- asset_depr_schedule,
- asset,
- depreciable_value,
- yearly_opening_wdv,
- fb_row,
- schedule_idx=0,
- prev_depreciation_amount=0,
- has_wdv_or_dd_non_yearly_pro_rata=False,
- number_of_pending_depreciations=0,
- prev_per_day_depr=0,
-):
- if fb_row.depreciation_method in ("Straight Line", "Manual"):
- return get_straight_line_or_manual_depr_amount(
- asset_depr_schedule, asset, fb_row, schedule_idx, number_of_pending_depreciations
- ), None
- else:
- return get_wdv_or_dd_depr_amount(
- asset,
- fb_row,
- depreciable_value,
- yearly_opening_wdv,
- schedule_idx,
- prev_depreciation_amount,
- has_wdv_or_dd_non_yearly_pro_rata,
- asset_depr_schedule,
- prev_per_day_depr,
- )
-
-
-def get_straight_line_or_manual_depr_amount(
- asset_depr_schedule, asset, row, schedule_idx, number_of_pending_depreciations
-):
- if row.shift_based:
- return get_shift_depr_amount(asset_depr_schedule, asset, row, schedule_idx)
-
- # if the Depreciation Schedule is being modified after Asset Repair due to increase in asset life and value
- if asset.flags.increase_in_asset_life:
- return (flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)) / (
- date_diff(asset.to_date, asset.available_for_use_date) / 365
- )
- # if the Depreciation Schedule is being modified after Asset Repair due to increase in asset value
- elif asset.flags.increase_in_asset_value_due_to_repair:
- return (flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)) / flt(
- number_of_pending_depreciations
- )
- # if the Depreciation Schedule is being modified after Asset Value Adjustment due to decrease in asset value
- elif asset.flags.decrease_in_asset_value_due_to_value_adjustment:
- if row.daily_prorata_based:
- amount = flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)
-
- return get_daily_prorata_based_straight_line_depr(
- asset,
- row,
- schedule_idx,
- number_of_pending_depreciations,
- amount,
- )
- else:
- return (
- flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)
- ) / number_of_pending_depreciations
- # if the Depreciation Schedule is being prepared for the first time
- else:
- if row.daily_prorata_based:
- amount = flt(asset.gross_purchase_amount) - flt(row.expected_value_after_useful_life)
- return get_daily_prorata_based_straight_line_depr(
- asset, row, schedule_idx, number_of_pending_depreciations, amount
- )
- else:
- depreciation_amount = (
- flt(asset.gross_purchase_amount) - flt(row.expected_value_after_useful_life)
- ) / flt(row.total_number_of_depreciations)
- return depreciation_amount
-
-
-def get_daily_prorata_based_straight_line_depr(
- asset, row, schedule_idx, number_of_pending_depreciations, amount
-):
- daily_depr_amount = get_daily_depr_amount(asset, row, schedule_idx, amount)
-
- from_date, total_depreciable_days = _get_total_days(
- row.depreciation_start_date, schedule_idx, row.frequency_of_depreciation
- )
- return daily_depr_amount * total_depreciable_days
-
-
-def get_daily_depr_amount(asset, row, schedule_idx, amount):
- if cint(frappe.db.get_single_value("Accounts Settings", "calculate_depr_using_total_days")):
- total_days = (
- date_diff(
- get_last_day(
- add_months(
- row.depreciation_start_date,
- flt(
- row.total_number_of_depreciations
- - asset.opening_number_of_booked_depreciations
- - 1
- )
- * row.frequency_of_depreciation,
- )
- ),
- add_days(
- get_last_day(
- add_months(
- row.depreciation_start_date,
- (
- row.frequency_of_depreciation
- * (asset.opening_number_of_booked_depreciations + 1)
- )
- * -1,
- ),
- ),
- 1,
- ),
- )
- + 1
- )
-
- return amount / total_days
- else:
- total_years = (
- flt(
- (row.total_number_of_depreciations - row.total_number_of_booked_depreciations)
- * row.frequency_of_depreciation
- )
- / 12
- )
-
- every_year_depr = amount / total_years
-
- depr_period_start_date = add_days(
- get_last_day(add_months(row.depreciation_start_date, row.frequency_of_depreciation * -1)), 1
- )
-
- year_start_date = add_years(
- depr_period_start_date, ((row.frequency_of_depreciation * schedule_idx) // 12)
- )
- year_end_date = add_days(add_years(year_start_date, 1), -1)
-
- return every_year_depr / (date_diff(year_end_date, year_start_date) + 1)
-
-
-def get_shift_depr_amount(asset_depr_schedule, asset, row, schedule_idx):
- if asset_depr_schedule.get("__islocal") and not asset.flags.shift_allocation:
- return (
- flt(asset.gross_purchase_amount)
- - flt(asset.opening_accumulated_depreciation)
- - flt(row.expected_value_after_useful_life)
- ) / flt(row.total_number_of_depreciations - asset.opening_number_of_booked_depreciations)
-
- asset_shift_factors_map = get_asset_shift_factors_map()
- shift = (
- asset_depr_schedule.schedules_before_clearing[schedule_idx].shift
- if len(asset_depr_schedule.schedules_before_clearing) > schedule_idx
- else None
- )
- shift_factor = asset_shift_factors_map.get(shift) if shift else 0
-
- shift_factors_sum = sum(
- flt(asset_shift_factors_map.get(schedule.shift))
- for schedule in asset_depr_schedule.schedules_before_clearing
- )
-
- return (
- (
- flt(asset.gross_purchase_amount)
- - flt(asset.opening_accumulated_depreciation)
- - flt(row.expected_value_after_useful_life)
- )
- / flt(shift_factors_sum)
- ) * shift_factor
-
-
-def get_asset_shift_factors_map():
- return dict(frappe.db.get_all("Asset Shift Factor", ["shift_name", "shift_factor"], as_list=True))
-
-
-@erpnext.allow_regional
-def get_wdv_or_dd_depr_amount(
- asset,
- fb_row,
- depreciable_value,
- yearly_opening_wdv,
- schedule_idx,
- prev_depreciation_amount,
- has_wdv_or_dd_non_yearly_pro_rata,
- asset_depr_schedule,
- prev_per_day_depr,
-):
- return get_default_wdv_or_dd_depr_amount(
- asset,
- fb_row,
- depreciable_value,
- schedule_idx,
- prev_depreciation_amount,
- has_wdv_or_dd_non_yearly_pro_rata,
- asset_depr_schedule,
- prev_per_day_depr,
- )
-
-
-def get_default_wdv_or_dd_depr_amount(
- asset,
- fb_row,
- depreciable_value,
- schedule_idx,
- prev_depreciation_amount,
- has_wdv_or_dd_non_yearly_pro_rata,
- asset_depr_schedule,
- prev_per_day_depr,
-):
- if not fb_row.daily_prorata_based or cint(fb_row.frequency_of_depreciation) == 12:
- return _get_default_wdv_or_dd_depr_amount(
- asset,
- fb_row,
- depreciable_value,
- schedule_idx,
- prev_depreciation_amount,
- has_wdv_or_dd_non_yearly_pro_rata,
- asset_depr_schedule,
- ), None
- else:
- return _get_daily_prorata_based_default_wdv_or_dd_depr_amount(
- asset,
- fb_row,
- depreciable_value,
- schedule_idx,
- prev_depreciation_amount,
- has_wdv_or_dd_non_yearly_pro_rata,
- asset_depr_schedule,
- prev_per_day_depr,
- )
-
-
-def _get_default_wdv_or_dd_depr_amount(
- asset,
- fb_row,
- depreciable_value,
- schedule_idx,
- prev_depreciation_amount,
- has_wdv_or_dd_non_yearly_pro_rata,
- asset_depr_schedule,
-):
- if cint(fb_row.frequency_of_depreciation) == 12:
- return flt(depreciable_value) * (flt(fb_row.rate_of_depreciation) / 100)
- else:
- if has_wdv_or_dd_non_yearly_pro_rata:
- if schedule_idx == 0:
- return flt(depreciable_value) * (flt(fb_row.rate_of_depreciation) / 100)
- elif schedule_idx % (12 / cint(fb_row.frequency_of_depreciation)) == 1:
- return (
- flt(depreciable_value)
- * flt(fb_row.frequency_of_depreciation)
- * (flt(fb_row.rate_of_depreciation) / 1200)
- )
- else:
- return prev_depreciation_amount
- else:
- if schedule_idx % (12 / cint(fb_row.frequency_of_depreciation)) == 0:
- return (
- flt(depreciable_value)
- * flt(fb_row.frequency_of_depreciation)
- * (flt(fb_row.rate_of_depreciation) / 1200)
- )
- else:
- return prev_depreciation_amount
-
-
-def _get_daily_prorata_based_default_wdv_or_dd_depr_amount(
- asset,
- fb_row,
- depreciable_value,
- schedule_idx,
- prev_depreciation_amount,
- has_wdv_or_dd_non_yearly_pro_rata,
- asset_depr_schedule,
- prev_per_day_depr,
-):
- if has_wdv_or_dd_non_yearly_pro_rata: # If applicable days for ther first month is less than full month
- if schedule_idx == 0:
- return flt(depreciable_value) * (flt(fb_row.rate_of_depreciation) / 100), None
-
- elif schedule_idx % (12 / cint(fb_row.frequency_of_depreciation)) == 1: # Year changes
- return get_monthly_depr_amount(fb_row, schedule_idx, depreciable_value)
- else:
- return get_monthly_depr_amount_based_on_prev_per_day_depr(fb_row, schedule_idx, prev_per_day_depr)
- else:
- if schedule_idx % (12 / cint(fb_row.frequency_of_depreciation)) == 0: # year changes
- return get_monthly_depr_amount(fb_row, schedule_idx, depreciable_value)
- else:
- return get_monthly_depr_amount_based_on_prev_per_day_depr(fb_row, schedule_idx, prev_per_day_depr)
-
-
-def get_monthly_depr_amount(fb_row, schedule_idx, depreciable_value):
- """
- Returns monthly depreciation amount when year changes
- 1. Calculate per day depr based on new year
- 2. Calculate monthly amount based on new per day amount
- """
- from_date, days_in_month = _get_total_days(
- fb_row.depreciation_start_date, schedule_idx, cint(fb_row.frequency_of_depreciation)
- )
- per_day_depr = get_per_day_depr(fb_row, depreciable_value, from_date)
- return (per_day_depr * days_in_month), per_day_depr
-
-
-def get_monthly_depr_amount_based_on_prev_per_day_depr(fb_row, schedule_idx, prev_per_day_depr):
- """ "
- Returns monthly depreciation amount based on prev per day depr
- Calculate per day depr only for the first month
- """
- from_date, days_in_month = _get_total_days(
- fb_row.depreciation_start_date, schedule_idx, cint(fb_row.frequency_of_depreciation)
- )
- return (prev_per_day_depr * days_in_month), prev_per_day_depr
-
-
-def get_per_day_depr(
- fb_row,
- depreciable_value,
- from_date,
-):
- to_date = add_days(add_years(from_date, 1), -1)
- total_days = date_diff(to_date, from_date) + 1
- per_day_depr = (flt(depreciable_value) * (flt(fb_row.rate_of_depreciation) / 100)) / total_days
- return per_day_depr
-
-
-def _get_total_days(depreciation_start_date, schedule_idx, frequency_of_depreciation):
- from_date = add_months(depreciation_start_date, (schedule_idx - 1) * frequency_of_depreciation)
- to_date = add_months(from_date, frequency_of_depreciation)
- if is_last_day_of_the_month(depreciation_start_date):
- to_date = get_last_day(to_date)
- from_date = add_days(get_last_day(from_date), 1)
- return from_date, date_diff(to_date, from_date) + 1
-
-
-def make_draft_asset_depr_schedules_if_not_present(asset_doc):
- asset_depr_schedules_names = []
-
- for row in asset_doc.get("finance_books"):
- asset_depr_schedule = get_asset_depr_schedule_name(
- asset_doc.name, ["Draft", "Active"], row.finance_book
- )
-
- if not asset_depr_schedule:
- name = make_draft_asset_depr_schedule(asset_doc, row)
- asset_depr_schedules_names.append(name)
-
- return asset_depr_schedules_names
-
-
def make_draft_asset_depr_schedules(asset_doc):
asset_depr_schedules_names = []
diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/utils.py b/erpnext/assets/doctype/asset_depreciation_schedule/utils.py
new file mode 100644
index 00000000000..2617493751c
--- /dev/null
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/utils.py
@@ -0,0 +1,351 @@
+import frappe
+from frappe.utils import (
+ add_days,
+ add_months,
+ add_years,
+ cint,
+ date_diff,
+ flt,
+ get_last_day,
+ is_last_day_of_the_month,
+)
+
+import erpnext
+
+
+def get_depreciation_amount(
+ asset_depr_schedule,
+ asset,
+ depreciable_value,
+ yearly_opening_wdv,
+ fb_row,
+ schedule_idx=0,
+ prev_depreciation_amount=0,
+ has_wdv_or_dd_non_yearly_pro_rata=False,
+ number_of_pending_depreciations=0,
+ prev_per_day_depr=0,
+):
+ if fb_row.depreciation_method in ("Straight Line", "Manual"):
+ return get_straight_line_or_manual_depr_amount(
+ asset_depr_schedule, asset, fb_row, schedule_idx, number_of_pending_depreciations
+ ), None
+ else:
+ return get_wdv_or_dd_depr_amount(
+ asset,
+ fb_row,
+ depreciable_value,
+ yearly_opening_wdv,
+ schedule_idx,
+ prev_depreciation_amount,
+ has_wdv_or_dd_non_yearly_pro_rata,
+ asset_depr_schedule,
+ prev_per_day_depr,
+ )
+
+
+def get_straight_line_or_manual_depr_amount(
+ asset_depr_schedule, asset, row, schedule_idx, number_of_pending_depreciations
+):
+ if row.shift_based:
+ return get_shift_depr_amount(asset_depr_schedule, asset, row, schedule_idx)
+
+ # if the Depreciation Schedule is being modified after Asset Repair due to increase in asset life and value
+ if asset.flags.increase_in_asset_life:
+ return (flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)) / (
+ date_diff(asset.to_date, asset.available_for_use_date) / 365
+ )
+ # if the Depreciation Schedule is being modified after Asset Repair due to increase in asset value
+ elif asset.flags.increase_in_asset_value_due_to_repair:
+ return (flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)) / flt(
+ number_of_pending_depreciations
+ )
+ # if the Depreciation Schedule is being modified after Asset Value Adjustment due to decrease in asset value
+ elif asset.flags.decrease_in_asset_value_due_to_value_adjustment:
+ if row.daily_prorata_based:
+ amount = flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)
+
+ return get_daily_prorata_based_straight_line_depr(
+ asset,
+ row,
+ schedule_idx,
+ number_of_pending_depreciations,
+ amount,
+ )
+ else:
+ return (
+ flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)
+ ) / number_of_pending_depreciations
+ # if the Depreciation Schedule is being prepared for the first time
+ else:
+ if row.daily_prorata_based:
+ amount = flt(asset.gross_purchase_amount) - flt(row.expected_value_after_useful_life)
+ return get_daily_prorata_based_straight_line_depr(
+ asset, row, schedule_idx, number_of_pending_depreciations, amount
+ )
+ else:
+ depreciation_amount = (
+ flt(asset.gross_purchase_amount) - flt(row.expected_value_after_useful_life)
+ ) / flt(row.total_number_of_depreciations)
+ return depreciation_amount
+
+
+def get_daily_prorata_based_straight_line_depr(
+ asset, row, schedule_idx, number_of_pending_depreciations, amount
+):
+ daily_depr_amount = get_daily_depr_amount(asset, row, schedule_idx, amount)
+
+ from_date, total_depreciable_days = _get_total_days(
+ row.depreciation_start_date, schedule_idx, row.frequency_of_depreciation
+ )
+ return daily_depr_amount * total_depreciable_days
+
+
+def get_daily_depr_amount(asset, row, schedule_idx, amount):
+ if cint(frappe.db.get_single_value("Accounts Settings", "calculate_depr_using_total_days")):
+ total_days = (
+ date_diff(
+ get_last_day(
+ add_months(
+ row.depreciation_start_date,
+ flt(
+ row.total_number_of_depreciations
+ - asset.opening_number_of_booked_depreciations
+ - 1
+ )
+ * row.frequency_of_depreciation,
+ )
+ ),
+ add_days(
+ get_last_day(
+ add_months(
+ row.depreciation_start_date,
+ (
+ row.frequency_of_depreciation
+ * (asset.opening_number_of_booked_depreciations + 1)
+ )
+ * -1,
+ ),
+ ),
+ 1,
+ ),
+ )
+ + 1
+ )
+
+ return amount / total_days
+ else:
+ total_years = (
+ flt(
+ (row.total_number_of_depreciations - row.total_number_of_booked_depreciations)
+ * row.frequency_of_depreciation
+ )
+ / 12
+ )
+
+ every_year_depr = amount / total_years
+
+ depr_period_start_date = add_days(
+ get_last_day(add_months(row.depreciation_start_date, row.frequency_of_depreciation * -1)), 1
+ )
+
+ year_start_date = add_years(
+ depr_period_start_date, ((row.frequency_of_depreciation * schedule_idx) // 12)
+ )
+ year_end_date = add_days(add_years(year_start_date, 1), -1)
+
+ return every_year_depr / (date_diff(year_end_date, year_start_date) + 1)
+
+
+def get_shift_depr_amount(asset_depr_schedule, asset, row, schedule_idx):
+ if asset_depr_schedule.get("__islocal") and not asset.flags.shift_allocation:
+ return (
+ flt(asset.gross_purchase_amount)
+ - flt(asset.opening_accumulated_depreciation)
+ - flt(row.expected_value_after_useful_life)
+ ) / flt(row.total_number_of_depreciations - asset.opening_number_of_booked_depreciations)
+
+ asset_shift_factors_map = get_asset_shift_factors_map()
+ shift = (
+ asset_depr_schedule.schedules_before_clearing[schedule_idx].shift
+ if len(asset_depr_schedule.schedules_before_clearing) > schedule_idx
+ else None
+ )
+ shift_factor = asset_shift_factors_map.get(shift) if shift else 0
+
+ shift_factors_sum = sum(
+ flt(asset_shift_factors_map.get(schedule.shift))
+ for schedule in asset_depr_schedule.schedules_before_clearing
+ )
+
+ return (
+ (
+ flt(asset.gross_purchase_amount)
+ - flt(asset.opening_accumulated_depreciation)
+ - flt(row.expected_value_after_useful_life)
+ )
+ / flt(shift_factors_sum)
+ ) * shift_factor
+
+
+def get_asset_shift_factors_map():
+ return dict(frappe.db.get_all("Asset Shift Factor", ["shift_name", "shift_factor"], as_list=True))
+
+
+@erpnext.allow_regional
+def get_wdv_or_dd_depr_amount(
+ asset,
+ fb_row,
+ depreciable_value,
+ yearly_opening_wdv,
+ schedule_idx,
+ prev_depreciation_amount,
+ has_wdv_or_dd_non_yearly_pro_rata,
+ asset_depr_schedule,
+ prev_per_day_depr,
+):
+ return get_default_wdv_or_dd_depr_amount(
+ asset,
+ fb_row,
+ depreciable_value,
+ schedule_idx,
+ prev_depreciation_amount,
+ has_wdv_or_dd_non_yearly_pro_rata,
+ asset_depr_schedule,
+ prev_per_day_depr,
+ )
+
+
+def get_default_wdv_or_dd_depr_amount(
+ asset,
+ fb_row,
+ depreciable_value,
+ schedule_idx,
+ prev_depreciation_amount,
+ has_wdv_or_dd_non_yearly_pro_rata,
+ asset_depr_schedule,
+ prev_per_day_depr,
+):
+ if not fb_row.daily_prorata_based or cint(fb_row.frequency_of_depreciation) == 12:
+ return _get_default_wdv_or_dd_depr_amount(
+ asset,
+ fb_row,
+ depreciable_value,
+ schedule_idx,
+ prev_depreciation_amount,
+ has_wdv_or_dd_non_yearly_pro_rata,
+ asset_depr_schedule,
+ ), None
+ else:
+ return _get_daily_prorata_based_default_wdv_or_dd_depr_amount(
+ asset,
+ fb_row,
+ depreciable_value,
+ schedule_idx,
+ prev_depreciation_amount,
+ has_wdv_or_dd_non_yearly_pro_rata,
+ asset_depr_schedule,
+ prev_per_day_depr,
+ )
+
+
+def _get_default_wdv_or_dd_depr_amount(
+ asset,
+ fb_row,
+ depreciable_value,
+ schedule_idx,
+ prev_depreciation_amount,
+ has_wdv_or_dd_non_yearly_pro_rata,
+ asset_depr_schedule,
+):
+ if cint(fb_row.frequency_of_depreciation) == 12:
+ return flt(depreciable_value) * (flt(fb_row.rate_of_depreciation) / 100)
+ else:
+ if has_wdv_or_dd_non_yearly_pro_rata:
+ if schedule_idx == 0:
+ return flt(depreciable_value) * (flt(fb_row.rate_of_depreciation) / 100)
+ elif schedule_idx % (12 / cint(fb_row.frequency_of_depreciation)) == 1:
+ return (
+ flt(depreciable_value)
+ * flt(fb_row.frequency_of_depreciation)
+ * (flt(fb_row.rate_of_depreciation) / 1200)
+ )
+ else:
+ return prev_depreciation_amount
+ else:
+ if schedule_idx % (12 / cint(fb_row.frequency_of_depreciation)) == 0:
+ return (
+ flt(depreciable_value)
+ * flt(fb_row.frequency_of_depreciation)
+ * (flt(fb_row.rate_of_depreciation) / 1200)
+ )
+ else:
+ return prev_depreciation_amount
+
+
+def _get_daily_prorata_based_default_wdv_or_dd_depr_amount(
+ asset,
+ fb_row,
+ depreciable_value,
+ schedule_idx,
+ prev_depreciation_amount,
+ has_wdv_or_dd_non_yearly_pro_rata,
+ asset_depr_schedule,
+ prev_per_day_depr,
+):
+ if has_wdv_or_dd_non_yearly_pro_rata: # If applicable days for ther first month is less than full month
+ if schedule_idx == 0:
+ return flt(depreciable_value) * (flt(fb_row.rate_of_depreciation) / 100), None
+
+ elif schedule_idx % (12 / cint(fb_row.frequency_of_depreciation)) == 1: # Year changes
+ return get_monthly_depr_amount(fb_row, schedule_idx, depreciable_value)
+ else:
+ return get_monthly_depr_amount_based_on_prev_per_day_depr(fb_row, schedule_idx, prev_per_day_depr)
+ else:
+ if schedule_idx % (12 / cint(fb_row.frequency_of_depreciation)) == 0: # year changes
+ return get_monthly_depr_amount(fb_row, schedule_idx, depreciable_value)
+ else:
+ return get_monthly_depr_amount_based_on_prev_per_day_depr(fb_row, schedule_idx, prev_per_day_depr)
+
+
+def get_monthly_depr_amount(fb_row, schedule_idx, depreciable_value):
+ """
+ Returns monthly depreciation amount when year changes
+ 1. Calculate per day depr based on new year
+ 2. Calculate monthly amount based on new per day amount
+ """
+ from_date, days_in_month = _get_total_days(
+ fb_row.depreciation_start_date, schedule_idx, cint(fb_row.frequency_of_depreciation)
+ )
+ per_day_depr = get_per_day_depr(fb_row, depreciable_value, from_date)
+ return (per_day_depr * days_in_month), per_day_depr
+
+
+def get_monthly_depr_amount_based_on_prev_per_day_depr(fb_row, schedule_idx, prev_per_day_depr):
+ """ "
+ Returns monthly depreciation amount based on prev per day depr
+ Calculate per day depr only for the first month
+ """
+ from_date, days_in_month = _get_total_days(
+ fb_row.depreciation_start_date, schedule_idx, cint(fb_row.frequency_of_depreciation)
+ )
+ return (prev_per_day_depr * days_in_month), prev_per_day_depr
+
+
+def get_per_day_depr(
+ fb_row,
+ depreciable_value,
+ from_date,
+):
+ to_date = add_days(add_years(from_date, 1), -1)
+ total_days = date_diff(to_date, from_date) + 1
+ per_day_depr = (flt(depreciable_value) * (flt(fb_row.rate_of_depreciation) / 100)) / total_days
+ return per_day_depr
+
+
+def _get_total_days(depreciation_start_date, schedule_idx, frequency_of_depreciation):
+ from_date = add_months(depreciation_start_date, (schedule_idx - 1) * frequency_of_depreciation)
+ to_date = add_months(from_date, frequency_of_depreciation)
+ if is_last_day_of_the_month(depreciation_start_date):
+ to_date = get_last_day(to_date)
+ from_date = add_days(get_last_day(from_date), 1)
+ return from_date, date_diff(to_date, from_date) + 1
diff --git a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json
index 60e8778e0e6..c97ae1a8545 100644
--- a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json
+++ b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json
@@ -61,6 +61,7 @@
{
"fieldname": "depreciation_start_date",
"fieldtype": "Date",
+ "hidden": 1,
"in_list_view": 1,
"label": "Depreciation Posting Date",
"mandatory_depends_on": "eval:parent.doctype == 'Asset'"
@@ -100,7 +101,7 @@
"default": "0",
"fieldname": "daily_prorata_based",
"fieldtype": "Check",
- "label": "Depreciate based on daily pro-rata"
+ "label": "Depreciate based on days per period"
},
{
"default": "0",
diff --git a/erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.py b/erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.py
index 323cb73fb8e..0c0da3bd8cd 100644
--- a/erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.py
+++ b/erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.py
@@ -16,9 +16,9 @@ from frappe.utils import (
from erpnext.assets.doctype.asset_activity.asset_activity import add_asset_activity
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
get_asset_depr_schedule_doc,
- get_asset_shift_factors_map,
get_temp_asset_depr_schedule_doc,
)
+from erpnext.assets.doctype.asset_depreciation_schedule.utils import get_asset_shift_factors_map
class AssetShiftAllocation(Document):