Merge branch 'develop' into asset_depreciation_schedule
diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json
index f0505ff..9af03ca 100644
--- a/erpnext/assets/doctype/asset/asset.json
+++ b/erpnext/assets/doctype/asset/asset.json
@@ -508,9 +508,14 @@
"group": "Value",
"link_doctype": "Asset Value Adjustment",
"link_fieldname": "asset"
+ },
+ {
+ "group": "Depreciation",
+ "link_doctype": "Asset Depreciation Schedule",
+ "link_fieldname": "asset"
}
],
- "modified": "2022-07-20 10:15:12.887372",
+ "modified": "2022-11-01 15:25:27.669803",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset",
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index ca6be9b..b8f368a 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -15,6 +15,7 @@
flt,
get_datetime,
get_last_day,
+ is_last_day_of_the_month,
getdate,
month_diff,
nowdate,
@@ -27,6 +28,12 @@
get_depreciation_accounts,
get_disposal_account_and_cost_center,
)
+from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
+ make_draft_asset_depreciation_schedules,
+ get_asset_depreciation_schedule_name,
+ convert_draft_asset_depreciation_schedules_into_active,
+ update_draft_asset_depreciation_schedules,
+)
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
from erpnext.controllers.accounts_controller import AccountsController
@@ -40,9 +47,9 @@
self.set_missing_values()
if not self.split_from:
self.prepare_depreciation_data()
+ update_draft_asset_depreciation_schedules(self)
self.validate_gross_and_purchase_amount()
- if self.get("schedules"):
- self.validate_expected_value_after_useful_life()
+ self.validate_expected_value_after_useful_life()
self.status = self.get_status()
@@ -52,6 +59,7 @@
self.make_asset_movement()
if not self.booked_fixed_asset and self.validate_make_gl_entry():
self.make_gl_entries()
+ convert_draft_asset_depreciation_schedules_into_active(self)
def on_cancel(self):
self.validate_cancellation()
@@ -62,6 +70,10 @@
make_reverse_gl_entries(voucher_type="Asset", voucher_no=self.name)
self.db_set("booked_fixed_asset", 0)
+ def after_insert(self):
+ if not self.split_from:
+ make_draft_asset_depreciation_schedules(self)
+
def validate_asset_and_reference(self):
if self.purchase_invoice or self.purchase_receipt:
reference_doc = "Purchase Invoice" if self.purchase_invoice else "Purchase Receipt"
@@ -79,12 +91,10 @@
_("Purchase Invoice cannot be made against an existing asset {0}").format(self.name)
)
- def prepare_depreciation_data(self, date_of_disposal=None, date_of_return=None):
+ def prepare_depreciation_data(self):
if self.calculate_depreciation:
self.value_after_depreciation = 0
self.set_depreciation_rate()
- self.make_depreciation_schedule(date_of_disposal)
- self.set_accumulated_depreciation(date_of_disposal, date_of_return)
else:
self.finance_books = []
self.value_after_depreciation = flt(self.gross_purchase_amount) - flt(
@@ -223,148 +233,6 @@
self.get_depreciation_rate(d, on_validate=True), d.precision("rate_of_depreciation")
)
- def make_depreciation_schedule(self, date_of_disposal):
- if "Manual" not in [d.depreciation_method for d in self.finance_books] and not self.get(
- "schedules"
- ):
- self.schedules = []
-
- if not self.available_for_use_date:
- return
-
- start = self.clear_depreciation_schedule()
-
- for finance_book in self.get("finance_books"):
- self._make_depreciation_schedule(finance_book, start, date_of_disposal)
-
- def _make_depreciation_schedule(self, finance_book, start, date_of_disposal):
- self.validate_asset_finance_books(finance_book)
-
- value_after_depreciation = self._get_value_after_depreciation(finance_book)
- finance_book.value_after_depreciation = value_after_depreciation
-
- number_of_pending_depreciations = cint(finance_book.total_number_of_depreciations) - cint(
- self.number_of_depreciations_booked
- )
-
- has_pro_rata = self.check_is_pro_rata(finance_book)
- if has_pro_rata:
- number_of_pending_depreciations += 1
-
- skip_row = False
- should_get_last_day = is_last_day_of_the_month(finance_book.depreciation_start_date)
-
- for n in range(start[finance_book.idx - 1], number_of_pending_depreciations):
- # If depreciation is already completed (for double declining balance)
- if skip_row:
- continue
-
- depreciation_amount = get_depreciation_amount(self, value_after_depreciation, finance_book)
-
- if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1:
- schedule_date = add_months(
- finance_book.depreciation_start_date, n * cint(finance_book.frequency_of_depreciation)
- )
-
- if should_get_last_day:
- schedule_date = get_last_day(schedule_date)
-
- # schedule date will be a year later from start date
- # so monthly schedule date is calculated by removing 11 months from it
- monthly_schedule_date = add_months(schedule_date, -finance_book.frequency_of_depreciation + 1)
-
- # if asset is being sold
- if date_of_disposal:
- from_date = self.get_from_date(finance_book.finance_book)
- depreciation_amount, days, months = self.get_pro_rata_amt(
- finance_book, depreciation_amount, from_date, date_of_disposal
- )
-
- if depreciation_amount > 0:
- self._add_depreciation_row(
- date_of_disposal,
- depreciation_amount,
- finance_book.depreciation_method,
- finance_book.finance_book,
- finance_book.idx,
- )
-
- break
-
- # For first row
- if has_pro_rata and not self.opening_accumulated_depreciation and n == 0:
- from_date = add_days(
- self.available_for_use_date, -1
- ) # needed to calc depr amount for available_for_use_date too
- depreciation_amount, days, months = self.get_pro_rata_amt(
- finance_book, depreciation_amount, from_date, finance_book.depreciation_start_date
- )
-
- # For first depr schedule date will be the start date
- # so monthly schedule date is calculated by removing month difference between use date and start date
- monthly_schedule_date = add_months(finance_book.depreciation_start_date, -months + 1)
-
- # For last row
- elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1:
- if not self.flags.increase_in_asset_life:
- # In case of increase_in_asset_life, the self.to_date is already set on asset_repair submission
- self.to_date = add_months(
- self.available_for_use_date,
- (n + self.number_of_depreciations_booked) * cint(finance_book.frequency_of_depreciation),
- )
-
- depreciation_amount_without_pro_rata = depreciation_amount
-
- depreciation_amount, days, months = self.get_pro_rata_amt(
- finance_book, depreciation_amount, schedule_date, self.to_date
- )
-
- depreciation_amount = self.get_adjusted_depreciation_amount(
- depreciation_amount_without_pro_rata, depreciation_amount, finance_book.finance_book
- )
-
- monthly_schedule_date = add_months(schedule_date, 1)
- schedule_date = add_days(schedule_date, days)
- last_schedule_date = schedule_date
-
- if not depreciation_amount:
- continue
- value_after_depreciation -= flt(depreciation_amount, self.precision("gross_purchase_amount"))
-
- # Adjust depreciation amount in the last period based on the expected value after useful life
- if finance_book.expected_value_after_useful_life and (
- (
- n == cint(number_of_pending_depreciations) - 1
- and value_after_depreciation != finance_book.expected_value_after_useful_life
- )
- or value_after_depreciation < finance_book.expected_value_after_useful_life
- ):
- depreciation_amount += value_after_depreciation - finance_book.expected_value_after_useful_life
- skip_row = True
-
- if depreciation_amount > 0:
- self._add_depreciation_row(
- schedule_date,
- depreciation_amount,
- finance_book.depreciation_method,
- finance_book.finance_book,
- finance_book.idx,
- )
-
- def _add_depreciation_row(
- self, schedule_date, depreciation_amount, depreciation_method, finance_book, finance_book_id
- ):
- self.append(
- "schedules",
- {
- "schedule_date": schedule_date,
- "depreciation_amount": depreciation_amount,
- "depreciation_method": depreciation_method,
- "finance_book": finance_book,
- "finance_book_id": finance_book_id,
- },
- )
-
def _get_value_after_depreciation(self, finance_book):
# value_after_depreciation - current Asset value
if self.docstatus == 1 and finance_book.value_after_depreciation:
@@ -376,57 +244,17 @@
return value_after_depreciation
- # depreciation schedules need to be cleared before modification due to increase in asset life/asset sales
- # JE: Journal Entry, FB: Finance Book
- def clear_depreciation_schedule(self):
- start = []
- num_of_depreciations_completed = 0
- depr_schedule = []
-
- for schedule in self.get("schedules"):
- # to update start when there are JEs linked with all the schedule rows corresponding to an FB
- if len(start) == (int(schedule.finance_book_id) - 2):
- start.append(num_of_depreciations_completed)
- num_of_depreciations_completed = 0
-
- # to ensure that start will only be updated once for each FB
- if len(start) == (int(schedule.finance_book_id) - 1):
- if schedule.journal_entry:
- num_of_depreciations_completed += 1
- depr_schedule.append(schedule)
- else:
- start.append(num_of_depreciations_completed)
- num_of_depreciations_completed = 0
-
- # to update start when all the schedule rows corresponding to the last FB are linked with JEs
- if len(start) == (len(self.finance_books) - 1):
- start.append(num_of_depreciations_completed)
-
- # when the Depreciation Schedule is being created for the first time
- if start == []:
- start = [0] * len(self.finance_books)
- else:
- self.schedules = depr_schedule
-
- return start
-
def get_from_date(self, finance_book):
- if not self.get("schedules"):
+ asset_depr_schedule_name = get_asset_depreciation_schedule_name(
+ self.name, finance_book
+ )
+
+ if not asset_depr_schedule_name:
return self.available_for_use_date
- if len(self.finance_books) == 1:
- return self.schedules[-1].schedule_date
+ asset_depr_schedule = frappe.get_doc("Asset Depreciation Schedule", asset_depr_schedule_name)
- from_date = ""
- for schedule in self.get("schedules"):
- if schedule.finance_book == finance_book:
- from_date = schedule.schedule_date
-
- if from_date:
- return from_date
-
- # since depr for available_for_use_date is not yet booked
- return add_days(self.available_for_use_date, -1)
+ return asset_depr_schedule.get("depreciation_schedule")[-1].schedule_date
# if it returns True, depreciation_amount will not be equal for the first and last rows
def check_is_pro_rata(self, row):
@@ -530,65 +358,29 @@
return depreciation_amount_for_last_row
def get_depreciation_amount_for_first_row(self, finance_book):
- if self.has_only_one_finance_book():
- return self.schedules[0].depreciation_amount
- else:
- for schedule in self.schedules:
- if schedule.finance_book == finance_book:
- return schedule.depreciation_amount
+ asset_depr_schedule_name = get_asset_depreciation_schedule_name(self.name, finance_book)
- def has_only_one_finance_book(self):
- if len(self.finance_books) == 1:
- return True
+ asset_depr_schedule = frappe.get_doc("Asset Depreciation Schedule", asset_depr_schedule_name)
- def set_accumulated_depreciation(
- self, date_of_sale=None, date_of_return=None, ignore_booked_entry=False
- ):
- straight_line_idx = [
- d.idx for d in self.get("schedules") if d.depreciation_method == "Straight Line"
- ]
- finance_books = []
-
- for i, d in enumerate(self.get("schedules")):
- if ignore_booked_entry and d.journal_entry:
- continue
-
- if int(d.finance_book_id) not in finance_books:
- accumulated_depreciation = flt(self.opening_accumulated_depreciation)
- value_after_depreciation = flt(self.get_value_after_depreciation(d.finance_book_id))
- finance_books.append(int(d.finance_book_id))
-
- depreciation_amount = flt(d.depreciation_amount, d.precision("depreciation_amount"))
- value_after_depreciation -= flt(depreciation_amount)
-
- # for the last row, if depreciation method = Straight Line
- if (
- straight_line_idx
- and i == max(straight_line_idx) - 1
- and not date_of_sale
- and not date_of_return
- ):
- book = self.get("finance_books")[cint(d.finance_book_id) - 1]
- depreciation_amount += flt(
- value_after_depreciation - flt(book.expected_value_after_useful_life),
- d.precision("depreciation_amount"),
- )
-
- d.depreciation_amount = depreciation_amount
- accumulated_depreciation += d.depreciation_amount
- d.accumulated_depreciation_amount = flt(
- accumulated_depreciation, d.precision("accumulated_depreciation_amount")
- )
+ return asset_depr_schedule.get("depreciation_schedule")[0].depreciation_amount
def get_value_after_depreciation(self, idx):
return flt(self.get("finance_books")[cint(idx) - 1].value_after_depreciation)
def validate_expected_value_after_useful_life(self):
for row in self.get("finance_books"):
+ asset_depr_schedule_name = get_asset_depreciation_schedule_name(
+ self.name, row.finance_book
+ )
+
+ if not asset_depr_schedule_name:
+ return
+
+ asset_depr_schedule = frappe.get_doc("Asset Depreciation Schedule", asset_depr_schedule_name)
+
accumulated_depreciation_after_full_schedule = [
d.accumulated_depreciation_amount
- for d in self.get("schedules")
- if cint(d.finance_book_id) == row.idx
+ for d in asset_depr_schedule.get("depreciation_schedule")
]
if accumulated_depreciation_after_full_schedule:
@@ -637,10 +429,20 @@
movement.cancel()
def delete_depreciation_entries(self):
- for d in self.get("schedules"):
- if d.journal_entry:
- frappe.get_doc("Journal Entry", d.journal_entry).cancel()
- d.db_set("journal_entry", None)
+ for row in self.get("finance_books"):
+ asset_depr_schedule_name = get_asset_depreciation_schedule_name(
+ self.name, row.finance_book
+ )
+
+ if not asset_depr_schedule_name:
+ return
+
+ asset_depr_schedule = frappe.get_doc("Asset Depreciation Schedule", asset_depr_schedule_name)
+
+ for d in asset_depr_schedule.get("depreciation_schedule"):
+ if d.journal_entry:
+ frappe.get_doc("Journal Entry", d.journal_entry).cancel()
+ d.db_set("journal_entry", None)
self.db_set(
"value_after_depreciation",
@@ -1072,32 +874,6 @@
return date_diff(date, period_start_date)
-def is_last_day_of_the_month(date):
- last_day_of_the_month = get_last_day(date)
-
- return getdate(last_day_of_the_month) == getdate(date)
-
-
-@erpnext.allow_regional
-def get_depreciation_amount(asset, depreciable_value, row):
- if row.depreciation_method in ("Straight Line", "Manual"):
- # if the Depreciation Schedule is being prepared for the first time
- if not asset.flags.increase_in_asset_life:
- depreciation_amount = (
- flt(asset.gross_purchase_amount) - flt(row.expected_value_after_useful_life)
- ) / flt(row.total_number_of_depreciations)
-
- # if the Depreciation Schedule is being modified after Asset Repair
- else:
- depreciation_amount = (
- flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)
- ) / (date_diff(asset.to_date, asset.available_for_use_date) / 365)
- else:
- depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100))
-
- return depreciation_amount
-
-
@frappe.whitelist()
def split_asset(asset_name, split_qty):
asset = frappe.get_doc("Asset", asset_name)
diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py
index 19c913a..e25d890 100644
--- a/erpnext/assets/doctype/asset/test_asset.py
+++ b/erpnext/assets/doctype/asset/test_asset.py
@@ -881,7 +881,9 @@
def test_get_depreciation_amount(self):
"""Tests if get_depreciation_amount() returns the right value."""
- from erpnext.assets.doctype.asset.asset import get_depreciation_amount
+ from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
+ get_depreciation_amount
+ )
asset = create_asset(item_code="Macbook Pro", available_for_use_date="2019-12-31")
diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/__init__.py b/erpnext/assets/doctype/asset_depreciation_schedule/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/__init__.py
diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.js b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.js
new file mode 100644
index 0000000..9d41de3
--- /dev/null
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Asset Depreciation Schedule', {
+ // refresh: function(frm) {
+
+ // }
+});
diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json
new file mode 100644
index 0000000..83ed089
--- /dev/null
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json
@@ -0,0 +1,174 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "autoname": "naming_series:",
+ "creation": "2022-10-31 15:03:35.424877",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "asset",
+ "naming_series",
+ "column_break_2",
+ "finance_book",
+ "depreciation_details_section",
+ "depreciation_method",
+ "total_number_of_depreciations",
+ "rate_of_depreciation",
+ "column_break_8",
+ "frequency_of_depreciation",
+ "expected_value_after_useful_life",
+ "depreciation_schedule_section",
+ "depreciation_schedule",
+ "details_section",
+ "notes",
+ "column_break_15",
+ "status",
+ "amended_from"
+ ],
+ "fields": [
+ {
+ "fieldname": "asset",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Asset",
+ "options": "Asset",
+ "reqd": 1
+ },
+ {
+ "fieldname": "naming_series",
+ "fieldtype": "Select",
+ "label": "Naming Series",
+ "options": "ACC-ADS-.YYYY.-"
+ },
+ {
+ "fieldname": "amended_from",
+ "fieldtype": "Link",
+ "label": "Amended From",
+ "no_copy": 1,
+ "options": "Asset Depreciation Schedule",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_2",
+ "fieldtype": "Column Break"
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "depreciation_details_section",
+ "fieldtype": "Section Break",
+ "label": "Depreciation Details"
+ },
+ {
+ "fieldname": "finance_book",
+ "fieldtype": "Link",
+ "label": "Finance Book",
+ "options": "Finance Book",
+ "read_only": 1
+ },
+ {
+ "fieldname": "depreciation_method",
+ "fieldtype": "Select",
+ "label": "Depreciation Method",
+ "options": "\nStraight Line\nDouble Declining Balance\nWritten Down Value\nManual",
+ "read_only": 1
+ },
+ {
+ "depends_on": "eval:doc.depreciation_method == 'Written Down Value'",
+ "description": "In Percentage",
+ "fieldname": "rate_of_depreciation",
+ "fieldtype": "Percent",
+ "label": "Rate of Depreciation",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_8",
+ "fieldtype": "Column Break"
+ },
+ {
+ "depends_on": "total_number_of_depreciations",
+ "fieldname": "total_number_of_depreciations",
+ "fieldtype": "Int",
+ "label": "Total Number of Depreciations",
+ "read_only": 1
+ },
+ {
+ "fieldname": "depreciation_schedule_section",
+ "fieldtype": "Section Break",
+ "label": "Depreciation Schedule"
+ },
+ {
+ "fieldname": "depreciation_schedule",
+ "fieldtype": "Table",
+ "label": "Depreciation Schedule",
+ "options": "Depreciation Schedule"
+ },
+ {
+ "collapsible": 1,
+ "collapsible_depends_on": "notes",
+ "fieldname": "details_section",
+ "fieldtype": "Section Break",
+ "label": "Details"
+ },
+ {
+ "fieldname": "notes",
+ "fieldtype": "Small Text",
+ "label": "Notes"
+ },
+ {
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "hidden": 1,
+ "label": "Status",
+ "options": "Draft\nActive\nCancelled",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_15",
+ "fieldtype": "Column Break"
+ },
+ {
+ "depends_on": "frequency_of_depreciation",
+ "fieldname": "frequency_of_depreciation",
+ "fieldtype": "Int",
+ "label": "Frequency of Depreciation (Months)",
+ "read_only": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "expected_value_after_useful_life",
+ "fieldtype": "Currency",
+ "label": "Expected Value After Useful Life",
+ "options": "Company:company:default_currency",
+ "read_only": 1
+ }
+ ],
+ "in_create": 1,
+ "index_web_pages_for_search": 1,
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2022-11-14 14:33:53.360643",
+ "modified_by": "Administrator",
+ "module": "Assets",
+ "name": "Asset Depreciation Schedule",
+ "naming_rule": "By \"Naming Series\" field",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
new file mode 100644
index 0000000..ae3707b
--- /dev/null
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
@@ -0,0 +1,355 @@
+# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe.utils import (
+ add_days,
+ add_months,
+ cint,
+ date_diff,
+ flt,
+ get_last_day,
+ is_last_day_of_the_month
+)
+from frappe.model.document import Document
+
+import erpnext
+
+
+class AssetDepreciationSchedule(Document):
+ pass
+
+
+def make_draft_asset_depreciation_schedules(asset):
+ for row in asset.get("finance_books"):
+ asset_depr_schedule = frappe.new_doc("Asset Depreciation Schedule")
+
+ prepare_draft_asset_depreciation_schedule_data(asset_depr_schedule, asset, row)
+
+
+def update_draft_asset_depreciation_schedules(asset):
+ for row in asset.get("finance_books"):
+ asset_depr_schedule_name = get_asset_depreciation_schedule_name(
+ asset.name, row.finance_book
+ )
+
+ if not asset_depr_schedule_name:
+ return
+
+ asset_depr_schedule = frappe.get_doc("Asset Depreciation Schedule", asset_depr_schedule_name)
+
+ prepare_draft_asset_depreciation_schedule_data(asset_depr_schedule, asset, row)
+
+
+def prepare_draft_asset_depreciation_schedule_data(asset_depr_schedule, asset, row):
+ set_draft_asset_depreciation_schedule_details(asset_depr_schedule, asset.name, row)
+ make_depreciation_schedule(asset_depr_schedule, asset, row)
+ set_accumulated_depreciation(asset_depr_schedule, asset, row)
+
+ asset_depr_schedule.save()
+
+
+def set_draft_asset_depreciation_schedule_details(asset_depr_schedule, asset_name, row):
+ asset_depr_schedule.asset = asset_name
+ asset_depr_schedule.finance_book = row.finance_book
+ asset_depr_schedule.depreciation_method = row.depreciation_method
+ asset_depr_schedule.total_number_of_depreciations = row.total_number_of_depreciations
+ asset_depr_schedule.frequency_of_depreciation = row.frequency_of_depreciation
+ asset_depr_schedule.rate_of_depreciation = row.rate_of_depreciation
+ asset_depr_schedule.expected_value_after_useful_life = row.expected_value_after_useful_life
+ asset_depr_schedule.status = "Draft"
+
+
+def convert_draft_asset_depreciation_schedules_into_active(asset):
+ for row in asset.get("finance_books"):
+ asset_depr_schedule_name = get_asset_depreciation_schedule_name(
+ asset.name, row.finance_book
+ )
+
+ if not asset_depr_schedule_name:
+ return
+
+ asset_depr_schedule = frappe.get_doc("Asset Depreciation Schedule", asset_depr_schedule_name)
+
+ asset_depr_schedule.status = "Active"
+
+ asset_depr_schedule.submit()
+
+
+def make_new_active_asset_depreciation_schedules_from_existing(
+ asset, date_of_disposal=None, date_of_return=None
+):
+ for row in asset.get("finance_books"):
+ old_asset_depr_schedule_name = get_asset_depreciation_schedule_name(
+ asset.name, row.finance_book
+ )
+
+ if not old_asset_depr_schedule_name:
+ return
+
+ old_asset_depr_schedule = frappe.get_doc(
+ "Asset Depreciation Schedule",
+ old_asset_depr_schedule_name
+ )
+
+ asset_depr_schedule = frappe.copy_doc(old_asset_depr_schedule, ignore_no_copy=False)
+
+ make_depreciation_schedule(asset_depr_schedule, asset, row, date_of_disposal)
+ set_accumulated_depreciation(asset_depr_schedule, asset, row, date_of_disposal, date_of_return)
+
+ asset_depr_schedule.save()
+
+
+def make_depreciation_schedule(asset_depr_schedule, asset, row, date_of_disposal=None):
+ if row.depreciation_method != "Manual" and not asset_depr_schedule.get("depreciation_schedule"):
+ asset_depr_schedule.depreciation_schedule = []
+
+ if not asset.available_for_use_date:
+ return
+
+ start = clear_depreciation_schedule(asset_depr_schedule)
+
+ _make_depreciation_schedule(asset_depr_schedule, asset, row, start, date_of_disposal)
+
+
+def get_asset_depreciation_schedule_name(asset_name, finance_book):
+ return frappe.db.get_value(
+ doctype="Asset Depreciation Schedule",
+ filters=[
+ ["asset", "=", asset_name],
+ ["finance_book", "=", finance_book],
+ ["docstatus", "<", 2],
+ ]
+ )
+
+
+def clear_depreciation_schedule(asset_depr_schedule):
+ start = []
+ num_of_depreciations_completed = 0
+ depr_schedule = []
+
+ for schedule in asset_depr_schedule.get("depreciation_schedule"):
+ if len(start) != 0:
+ break
+
+ if schedule.journal_entry:
+ num_of_depreciations_completed += 1
+ depr_schedule.append(schedule)
+ else:
+ start.append(num_of_depreciations_completed)
+ num_of_depreciations_completed = 0
+
+ # to update start when all the schedule rows corresponding to the FB are linked with JEs
+ if len(start) == 0:
+ start.append(num_of_depreciations_completed)
+
+ # when the Depreciation Schedule is being created for the first time
+ if start == []:
+ start = [0]
+ else:
+ asset_depr_schedule.depreciation_schedule = depr_schedule
+
+ return start
+
+
+def _make_depreciation_schedule(asset_depr_schedule, asset, row, start, date_of_disposal):
+ asset.validate_asset_finance_books(row)
+
+ value_after_depreciation = asset._get_value_after_depreciation(row)
+ row.value_after_depreciation = value_after_depreciation
+
+ number_of_pending_depreciations = cint(row.total_number_of_depreciations) - cint(
+ asset.number_of_depreciations_booked
+ )
+
+ has_pro_rata = asset.check_is_pro_rata(row)
+ if has_pro_rata:
+ number_of_pending_depreciations += 1
+
+ skip_row = False
+ should_get_last_day = is_last_day_of_the_month(row.depreciation_start_date)
+
+ for n in range(start[row.idx - 1], number_of_pending_depreciations):
+ # If depreciation is already completed (for double declining balance)
+ if skip_row:
+ continue
+
+ depreciation_amount = get_depreciation_amount(asset, value_after_depreciation, row)
+
+ if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1:
+ 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 will be a year later from start date
+ # so monthly schedule date is calculated by removing 11 months from it
+ monthly_schedule_date = add_months(schedule_date, -row.frequency_of_depreciation + 1)
+
+ # if asset is being sold or scrapped
+ if date_of_disposal:
+ from_date = asset.get_from_date(row.finance_book)
+ depreciation_amount, days, months = asset.get_pro_rata_amt(
+ row, depreciation_amount, from_date, date_of_disposal
+ )
+
+ if depreciation_amount > 0:
+ add_depr_schedule_row(
+ asset_depr_schedule,
+ date_of_disposal,
+ depreciation_amount,
+ row.depreciation_method,
+ row.finance_book,
+ row.idx,
+ )
+
+ break
+
+ # For first row
+ if has_pro_rata and not asset.opening_accumulated_depreciation and n == 0:
+ from_date = add_days(
+ asset.available_for_use_date, -1
+ ) # needed to calc depr amount for available_for_use_date too
+ depreciation_amount, days, months = asset.get_pro_rata_amt(
+ row, depreciation_amount, from_date, row.depreciation_start_date
+ )
+
+ # For first depr schedule date will be the start date
+ # so monthly schedule date is calculated by removing
+ # month difference between use date and start date
+ monthly_schedule_date = add_months(row.depreciation_start_date, -months + 1)
+
+ # For last row
+ elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1:
+ if not asset.flags.increase_in_asset_life:
+ # In case of increase_in_asset_life, the asset.to_date is already set on asset_repair submission
+ asset.to_date = add_months(
+ asset.available_for_use_date,
+ (n + asset.number_of_depreciations_booked) * cint(row.frequency_of_depreciation),
+ )
+
+ depreciation_amount_without_pro_rata = depreciation_amount
+
+ depreciation_amount, days, months = asset.get_pro_rata_amt(
+ row, depreciation_amount, schedule_date, asset.to_date
+ )
+
+ depreciation_amount = asset.get_adjusted_depreciation_amount(
+ depreciation_amount_without_pro_rata, depreciation_amount, row.finance_book
+ )
+
+ monthly_schedule_date = add_months(schedule_date, 1)
+ schedule_date = add_days(schedule_date, days)
+ last_schedule_date = schedule_date
+
+ if not depreciation_amount:
+ continue
+ value_after_depreciation -= flt(depreciation_amount, asset.precision("gross_purchase_amount"))
+
+ # Adjust depreciation amount in the last period based on the expected value after useful life
+ if row.expected_value_after_useful_life and (
+ (
+ n == cint(number_of_pending_depreciations) - 1
+ and value_after_depreciation != row.expected_value_after_useful_life
+ )
+ or value_after_depreciation < row.expected_value_after_useful_life
+ ):
+ depreciation_amount += value_after_depreciation - row.expected_value_after_useful_life
+ skip_row = True
+
+ if depreciation_amount > 0:
+ add_depr_schedule_row(
+ asset_depr_schedule,
+ schedule_date,
+ depreciation_amount,
+ row.depreciation_method,
+ row.finance_book,
+ row.idx,
+ )
+
+
+@erpnext.allow_regional
+def get_depreciation_amount(asset, depreciable_value, row):
+ if row.depreciation_method in ("Straight Line", "Manual"):
+ # if the Depreciation Schedule is being prepared for the first time
+ if not asset.flags.increase_in_asset_life:
+ depreciation_amount = (
+ flt(asset.gross_purchase_amount) - flt(row.expected_value_after_useful_life)
+ ) / flt(row.total_number_of_depreciations)
+
+ # if the Depreciation Schedule is being modified after Asset Repair
+ else:
+ depreciation_amount = (
+ flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)
+ ) / (date_diff(asset.to_date, asset.available_for_use_date) / 365)
+ else:
+ depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100))
+
+ return depreciation_amount
+
+
+def add_depr_schedule_row(
+ asset_depr_schedule,
+ schedule_date,
+ depreciation_amount,
+ depreciation_method,
+ finance_book,
+ finance_book_id
+):
+ asset_depr_schedule.append(
+ "depreciation_schedule",
+ {
+ "schedule_date": schedule_date,
+ "depreciation_amount": depreciation_amount,
+ "depreciation_method": depreciation_method,
+ "finance_book": finance_book,
+ "finance_book_id": finance_book_id,
+ },
+ )
+
+
+def set_accumulated_depreciation(
+ asset_depr_schedule,
+ asset,
+ row,
+ date_of_disposal=None,
+ date_of_return=None,
+ ignore_booked_entry=False
+):
+ straight_line_idx = [
+ d.idx for d in asset_depr_schedule.get("depreciation_schedule")
+ if d.depreciation_method == "Straight Line"
+ ]
+ finance_books = []
+
+ for i, d in enumerate(asset_depr_schedule.get("depreciation_schedule")):
+ if ignore_booked_entry and d.journal_entry:
+ continue
+
+ if int(d.finance_book_id) not in finance_books:
+ accumulated_depreciation = flt(asset.opening_accumulated_depreciation)
+ value_after_depreciation = flt(asset.get_value_after_depreciation(d.finance_book_id))
+ finance_books.append(int(d.finance_book_id))
+
+ depreciation_amount = flt(d.depreciation_amount, d.precision("depreciation_amount"))
+ value_after_depreciation -= flt(depreciation_amount)
+
+ # for the last row, if depreciation method = Straight Line
+ if (
+ straight_line_idx
+ and i == max(straight_line_idx) - 1
+ and not date_of_disposal
+ and not date_of_return
+ ):
+ depreciation_amount += flt(
+ value_after_depreciation - flt(row.expected_value_after_useful_life),
+ d.precision("depreciation_amount"),
+ )
+
+ d.depreciation_amount = depreciation_amount
+ accumulated_depreciation += d.depreciation_amount
+ d.accumulated_depreciation_amount = flt(
+ accumulated_depreciation, d.precision("accumulated_depreciation_amount")
+ )
diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py
new file mode 100644
index 0000000..21de8cd
--- /dev/null
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+# import frappe
+from frappe.tests.utils import FrappeTestCase
+
+
+class TestAssetDepreciationSchedule(FrappeTestCase):
+ pass
diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
index 84aa8fa..ae6e2b4 100644
--- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
+++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
@@ -10,7 +10,9 @@
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_checks_for_pl_and_bs_accounts,
)
-from erpnext.assets.doctype.asset.asset import get_depreciation_amount
+from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
+ get_depreciation_amount
+)
from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts