From 8ea9e4576fc9c56c9a58f7ac45749402c7854f74 Mon Sep 17 00:00:00 2001 From: anandbaburajan Date: Fri, 3 Feb 2023 21:43:18 +0530 Subject: [PATCH 1/2] fix: asset value for manual depr entries --- .../doctype/journal_entry/journal_entry.py | 75 +++++++++++++++--- erpnext/assets/doctype/asset/asset.js | 63 ++++++++------- erpnext/assets/doctype/asset/asset.json | 8 +- erpnext/assets/doctype/asset/asset.py | 75 ++++++++++++------ erpnext/assets/doctype/asset/depreciation.py | 12 +-- erpnext/assets/doctype/asset/test_asset.py | 31 ++++++++ .../asset_capitalization.py | 10 +-- .../asset_depreciation_schedule.py | 44 +++++++---- .../doctype/asset_repair/test_asset_repair.py | 17 ++-- .../asset_value_adjustment.js | 2 +- .../asset_value_adjustment.py | 12 +-- .../test_asset_value_adjustment.py | 8 +- .../fixed_asset_register.py | 78 ++++++++++++++----- erpnext/patches.txt | 1 + ...ate_asset_value_for_manual_depr_entries.py | 38 +++++++++ 15 files changed, 338 insertions(+), 136 deletions(-) create mode 100644 erpnext/patches/v15_0/update_asset_value_for_manual_depr_entries.py diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index ea8b7d831b..5b0322af2d 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -81,6 +81,7 @@ class JournalEntry(AccountsController): self.check_credit_limit() self.make_gl_entries() self.update_advance_paid() + self.update_asset_value() self.update_inter_company_jv() self.update_invoice_discounting() @@ -225,6 +226,34 @@ class JournalEntry(AccountsController): for d in to_remove: self.remove(d) + def update_asset_value(self): + if self.voucher_type != "Depreciation Entry": + return + + processed_assets = [] + + for d in self.get("accounts"): + if ( + d.reference_type == "Asset" and d.reference_name and d.reference_name not in processed_assets + ): + processed_assets.append(d.reference_name) + + asset = frappe.db.get_value( + "Asset", d.reference_name, ["calculate_depreciation", "value_after_depreciation"], as_dict=1 + ) + + if asset.calculate_depreciation: + continue + + depr_value = d.debit or d.credit + + frappe.db.set_value( + "Asset", + d.reference_name, + "value_after_depreciation", + asset.value_after_depreciation - depr_value, + ) + def update_inter_company_jv(self): if ( self.voucher_type == "Inter Company Journal Entry" @@ -283,20 +312,48 @@ class JournalEntry(AccountsController): d.db_update() def unlink_asset_reference(self): + if self.voucher_type != "Depreciation Entry": + return + + processed_assets = [] + for d in self.get("accounts"): - if d.reference_type == "Asset" and d.reference_name: + if ( + d.reference_type == "Asset" and d.reference_name and d.reference_name not in processed_assets + ): + processed_assets.append(d.reference_name) + asset = frappe.get_doc("Asset", d.reference_name) - for row in asset.get("finance_books"): - depr_schedule = get_depr_schedule(asset.name, "Active", row.finance_book) - for s in depr_schedule or []: - if s.journal_entry == self.name: - s.db_set("journal_entry", None) + if asset.calculate_depreciation: + je_found = False - row.value_after_depreciation += s.depreciation_amount - row.db_update() + for row in asset.get("finance_books"): + if je_found: + break - asset.set_status() + depr_schedule = get_depr_schedule(asset.name, "Active", row.finance_book) + + for s in depr_schedule or []: + if s.journal_entry == self.name: + s.db_set("journal_entry", None) + + row.value_after_depreciation += s.depreciation_amount + row.db_update() + + asset.set_status() + + je_found = True + break + else: + depr_value = d.debit or d.credit + + frappe.db.set_value( + "Asset", + d.reference_name, + "value_after_depreciation", + asset.value_after_depreciation + depr_value, + ) def unlink_inter_company_jv(self): if ( diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index 8f5b85d1b2..4ed99f7e49 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -221,34 +221,45 @@ frappe.ui.form.on('Asset', { asset_values.push(flt(frm.doc.gross_purchase_amount) - flt(frm.doc.opening_accumulated_depreciation)); } + if(frm.doc.calculate_depreciation) { + if (frm.doc.finance_books.length == 1) { + let depr_schedule = (await frappe.call( + "erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule.get_depr_schedule", + { + asset_name: frm.doc.name, + status: frm.doc.docstatus ? "Active" : "Draft", + finance_book: frm.doc.finance_books[0].finance_book || null + } + )).message; - let depr_schedule = []; - - if (frm.doc.finance_books.length == 1) { - depr_schedule = (await frappe.call( - "erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule.get_depr_schedule", - { - asset_name: frm.doc.name, - status: frm.doc.docstatus ? "Active" : "Draft", - finance_book: frm.doc.finance_books[0].finance_book || null - } - )).message; - } - - $.each(depr_schedule || [], function(i, v) { - x_intervals.push(v.schedule_date); - var asset_value = flt(frm.doc.gross_purchase_amount) - flt(v.accumulated_depreciation_amount); - if(v.journal_entry) { - last_depreciation_date = v.schedule_date; - asset_values.push(asset_value); - } else { - if (in_list(["Scrapped", "Sold"], frm.doc.status)) { - asset_values.push(null); - } else { - asset_values.push(asset_value) - } + $.each(depr_schedule || [], function(i, v) { + x_intervals.push(v.schedule_date); + var asset_value = flt(frm.doc.gross_purchase_amount) - flt(v.accumulated_depreciation_amount); + if(v.journal_entry) { + last_depreciation_date = v.schedule_date; + asset_values.push(asset_value); + } else { + if (in_list(["Scrapped", "Sold"], frm.doc.status)) { + asset_values.push(null); + } else { + asset_values.push(asset_value) + } + } + }); } - }); + } else { + let depr_entries = (await frappe.call({ + method: "get_manual_depreciation_entries", + doc: frm.doc, + })).message; + + $.each(depr_entries || [], function(i, v) { + x_intervals.push(v.posting_date); + last_depreciation_date = v.posting_date; + let last_asset_value = asset_values[asset_values.length - 1] + asset_values.push(last_asset_value - v.value); + }); + } if(in_list(["Scrapped", "Sold"], frm.doc.status)) { x_intervals.push(frm.doc.disposal_date); diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index 8a64a95317..ea575fd71f 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -509,9 +509,15 @@ "group": "Depreciation", "link_doctype": "Asset Depreciation Schedule", "link_fieldname": "asset" + }, + { + "group": "Journal Entry", + "link_doctype": "Journal Entry", + "link_fieldname": "reference_name", + "table_fieldname": "accounts" } ], - "modified": "2023-01-17 00:25:30.387242", + "modified": "2023-02-02 00:03:11.706427", "modified_by": "Administrator", "module": "Assets", "name": "Asset", diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index df05d5e632..e24c41d24b 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -240,17 +240,6 @@ class Asset(AccountsController): self.get_depreciation_rate(d, on_validate=True), d.precision("rate_of_depreciation") ) - def _get_value_after_depreciation(self, finance_book): - # value_after_depreciation - current Asset value - if self.docstatus == 1 and finance_book.value_after_depreciation: - value_after_depreciation = flt(finance_book.value_after_depreciation) - else: - value_after_depreciation = flt(self.gross_purchase_amount) - flt( - self.opening_accumulated_depreciation - ) - - return value_after_depreciation - # if it returns True, depreciation_amount will not be equal for the first and last rows def check_is_pro_rata(self, row): has_pro_rata = False @@ -392,18 +381,23 @@ class Asset(AccountsController): movement.cancel() def delete_depreciation_entries(self): - for row in self.get("finance_books"): - depr_schedule = get_depr_schedule(self.name, "Active", row.finance_book) + if self.calculate_depreciation: + for row in self.get("finance_books"): + depr_schedule = get_depr_schedule(self.name, "Active", row.finance_book) - for d in depr_schedule or []: - if d.journal_entry: - frappe.get_doc("Journal Entry", d.journal_entry).cancel() - d.db_set("journal_entry", None) + for d in depr_schedule or []: + if d.journal_entry: + frappe.get_doc("Journal Entry", d.journal_entry).cancel() + else: + depr_entries = self.get_manual_depreciation_entries() - self.db_set( - "value_after_depreciation", - (flt(self.gross_purchase_amount) - flt(self.opening_accumulated_depreciation)), - ) + for depr_entry in depr_entries or []: + frappe.get_doc("Journal Entry", depr_entry.name).cancel() + + self.db_set( + "value_after_depreciation", + (flt(self.gross_purchase_amount) - flt(self.opening_accumulated_depreciation)), + ) def set_status(self, status=None): """Get and update status""" @@ -434,6 +428,17 @@ class Asset(AccountsController): status = "Cancelled" return status + def get_value_after_depreciation(self, finance_book=None): + if not self.calculate_depreciation: + return self.value_after_depreciation + + if not finance_book: + return self.get("finance_books")[0].value_after_depreciation + + for row in self.get("finance_books"): + if finance_book == row.finance_book: + return row.value_after_depreciation + def get_default_finance_book_idx(self): if not self.get("default_finance_book") and self.company: self.default_finance_book = erpnext.get_default_finance_book(self.company) @@ -443,6 +448,24 @@ class Asset(AccountsController): if d.finance_book == self.default_finance_book: return cint(d.idx) - 1 + @frappe.whitelist() + def get_manual_depreciation_entries(self): + (_, _, depreciation_expense_account) = get_depreciation_accounts(self) + + gle = frappe.qb.DocType("GL Entry") + + records = ( + frappe.qb.from_(gle) + .select(gle.voucher_no.as_("name"), gle.debit.as_("value"), gle.posting_date) + .where(gle.against_voucher == self.name) + .where(gle.account == depreciation_expense_account) + .where(gle.debit != 0) + .where(gle.is_cancelled == 0) + .orderby(gle.posting_date) + ).run(as_dict=True) + + return records + def validate_make_gl_entry(self): purchase_document = self.get_purchase_document() if not purchase_document: @@ -603,7 +626,6 @@ def update_maintenance_status(): def make_post_gl_entry(): - asset_categories = frappe.db.get_all("Asset Category", fields=["name", "enable_cwip_accounting"]) for asset_category in asset_categories: @@ -756,7 +778,7 @@ def make_journal_entry(asset_name): depreciation_expense_account, ) = get_depreciation_accounts(asset) - depreciation_cost_center, depreciation_series = frappe.db.get_value( + depreciation_cost_center, depreciation_series = frappe.get_cached_value( "Company", asset.company, ["depreciation_cost_center", "series_for_depreciation_entry"] ) depreciation_cost_center = asset.cost_center or depreciation_cost_center @@ -821,6 +843,13 @@ def is_cwip_accounting_enabled(asset_category): return cint(frappe.db.get_value("Asset Category", asset_category, "enable_cwip_accounting")) +@frappe.whitelist() +def get_asset_value_after_depreciation(asset_name, finance_book=None): + asset = frappe.get_doc("Asset", asset_name) + + return asset.get_value_after_depreciation(finance_book) + + def get_total_days(date, frequency): period_start_date = add_months(date, cint(frequency) * -1) diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index 17d40784be..e7a25321b8 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -533,18 +533,8 @@ def get_asset_details(asset, finance_book=None): disposal_account, depreciation_cost_center = get_disposal_account_and_cost_center(asset.company) depreciation_cost_center = asset.cost_center or depreciation_cost_center - idx = 1 - if finance_book: - for d in asset.finance_books: - if d.finance_book == finance_book: - idx = d.idx - break + value_after_depreciation = asset.get_value_after_depreciation(finance_book) - value_after_depreciation = ( - asset.finance_books[idx - 1].value_after_depreciation - if asset.calculate_depreciation - else asset.value_after_depreciation - ) accumulated_depr_amount = flt(asset.gross_purchase_amount) - flt(value_after_depreciation) return ( diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 51a2b52897..a9af670618 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -16,6 +16,7 @@ from frappe.utils import ( nowdate, ) +from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice from erpnext.assets.doctype.asset.asset import ( make_sales_invoice, @@ -1503,6 +1504,36 @@ class TestDepreciationBasics(AssetSetup): for i, schedule in enumerate(get_depr_schedule(asset.name, "Active")): self.assertEqual(getdate(expected_dates[i]), getdate(schedule.schedule_date)) + def test_manual_depreciation_for_existing_asset(self): + asset = create_asset( + item_code="Macbook Pro", + is_existing_asset=1, + purchase_date="2020-01-30", + available_for_use_date="2020-01-30", + submit=1, + ) + + self.assertEqual(asset.status, "Submitted") + self.assertEqual(asset.get("value_after_depreciation"), 100000) + + jv = make_journal_entry( + "_Test Depreciations - _TC", "_Test Accumulated Depreciations - _TC", 100, save=False + ) + for d in jv.accounts: + d.reference_type = "Asset" + d.reference_name = asset.name + jv.voucher_type = "Depreciation Entry" + jv.insert() + jv.submit() + + asset.reload() + self.assertEqual(asset.get("value_after_depreciation"), 99900) + + jv.cancel() + + asset.reload() + self.assertEqual(asset.get("value_after_depreciation"), 100000) + def create_asset_data(): if not frappe.db.exists("Asset Category", "Computers"): diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py index 821accf96a..5b910dbb2e 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -10,6 +10,7 @@ from frappe import _ from frappe.utils import cint, flt, get_link_to_form import erpnext +from erpnext.assets.doctype.asset.asset import get_asset_value_after_depreciation from erpnext.assets.doctype.asset.depreciation import ( depreciate_asset, get_gl_entries_on_asset_disposal, @@ -21,9 +22,6 @@ from erpnext.assets.doctype.asset_category.asset_category import get_asset_categ from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import ( make_new_active_asset_depr_schedules_and_cancel_current_ones, ) -from erpnext.assets.doctype.asset_value_adjustment.asset_value_adjustment import ( - get_current_asset_value, -) from erpnext.controllers.stock_controller import StockController from erpnext.setup.doctype.brand.brand import get_brand_defaults from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults @@ -261,7 +259,9 @@ class AssetCapitalization(StockController): for d in self.get("asset_items"): if d.asset: finance_book = d.get("finance_book") or self.get("finance_book") - d.current_asset_value = flt(get_current_asset_value(d.asset, finance_book=finance_book)) + d.current_asset_value = flt( + get_asset_value_after_depreciation(d.asset, finance_book=finance_book) + ) d.asset_value = get_value_after_depreciation_on_disposal_date( d.asset, self.posting_date, finance_book=finance_book ) @@ -713,7 +713,7 @@ def get_consumed_asset_details(args): if args.asset: out.current_asset_value = flt( - get_current_asset_value(args.asset, finance_book=args.finance_book) + get_asset_value_after_depreciation(args.asset, finance_book=args.finance_book) ) out.asset_value = get_value_after_depreciation_on_disposal_date( args.asset, args.posting_date, finance_book=args.finance_book 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 1446a6e7a2..02e508a7fa 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py @@ -220,21 +220,6 @@ def get_temp_asset_depr_schedule_doc( return asset_depr_schedule_doc -def get_asset_depr_schedule_name(asset_name, status, finance_book=None): - finance_book_filter = ["finance_book", "is", "not set"] - if finance_book: - finance_book_filter = ["finance_book", "=", finance_book] - - return frappe.db.get_value( - doctype="Asset Depreciation Schedule", - filters=[ - ["asset", "=", asset_name], - finance_book_filter, - ["status", "=", status], - ], - ) - - @frappe.whitelist() def get_depr_schedule(asset_name, status, finance_book=None): asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset_name, status, finance_book) @@ -256,6 +241,21 @@ def get_asset_depr_schedule_doc(asset_name, status, finance_book=None): return asset_depr_schedule_doc +def get_asset_depr_schedule_name(asset_name, status, finance_book=None): + finance_book_filter = ["finance_book", "is", "not set"] + if finance_book: + finance_book_filter = ["finance_book", "=", finance_book] + + return frappe.db.get_value( + doctype="Asset Depreciation Schedule", + filters=[ + ["asset", "=", asset_name], + finance_book_filter, + ["status", "=", status], + ], + ) + + def make_depr_schedule( asset_depr_schedule_doc, asset_doc, row, date_of_disposal, update_asset_finance_book_row=True ): @@ -297,7 +297,7 @@ def _make_depr_schedule( ): asset_doc.validate_asset_finance_books(row) - value_after_depreciation = asset_doc._get_value_after_depreciation(row) + value_after_depreciation = _get_value_after_depreciation_for_making_schedule(asset_doc, row) row.value_after_depreciation = value_after_depreciation if update_asset_finance_book_row: @@ -414,6 +414,18 @@ def _make_depr_schedule( ) +def _get_value_after_depreciation_for_making_schedule(asset_doc, row): + # value_after_depreciation - current Asset value + if asset_doc.docstatus == 1 and row.value_after_depreciation: + value_after_depreciation = flt(row.value_after_depreciation) + else: + value_after_depreciation = flt(asset_doc.gross_purchase_amount) - flt( + asset_doc.opening_accumulated_depreciation + ) + + return value_after_depreciation + + # to ensure that final accumulated depreciation amount is accurate def get_adjusted_depreciation_amount( asset_depr_schedule_doc, depreciation_amount_without_pro_rata, depreciation_amount_for_last_row diff --git a/erpnext/assets/doctype/asset_repair/test_asset_repair.py b/erpnext/assets/doctype/asset_repair/test_asset_repair.py index ff72aa94b9..a9d0b25755 100644 --- a/erpnext/assets/doctype/asset_repair/test_asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/test_asset_repair.py @@ -6,7 +6,10 @@ import unittest import frappe from frappe.utils import flt, nowdate -from erpnext.assets.doctype.asset.asset import get_asset_account +from erpnext.assets.doctype.asset.asset import ( + get_asset_account, + get_asset_value_after_depreciation, +) from erpnext.assets.doctype.asset.test_asset import ( create_asset, create_asset_data, @@ -109,20 +112,20 @@ class TestAssetRepair(unittest.TestCase): def test_increase_in_asset_value_due_to_stock_consumption(self): asset = create_asset(calculate_depreciation=1, submit=1) - initial_asset_value = get_asset_value(asset) + initial_asset_value = get_asset_value_after_depreciation(asset.name) asset_repair = create_asset_repair(asset=asset, stock_consumption=1, submit=1) asset.reload() - increase_in_asset_value = get_asset_value(asset) - initial_asset_value + increase_in_asset_value = get_asset_value_after_depreciation(asset.name) - initial_asset_value self.assertEqual(asset_repair.stock_items[0].total_value, increase_in_asset_value) def test_increase_in_asset_value_due_to_repair_cost_capitalisation(self): asset = create_asset(calculate_depreciation=1, submit=1) - initial_asset_value = get_asset_value(asset) + initial_asset_value = get_asset_value_after_depreciation(asset.name) asset_repair = create_asset_repair(asset=asset, capitalize_repair_cost=1, submit=1) asset.reload() - increase_in_asset_value = get_asset_value(asset) - initial_asset_value + increase_in_asset_value = get_asset_value_after_depreciation(asset.name) - initial_asset_value self.assertEqual(asset_repair.repair_cost, increase_in_asset_value) def test_purchase_invoice(self): @@ -256,10 +259,6 @@ class TestAssetRepair(unittest.TestCase): ) -def get_asset_value(asset): - return asset.finance_books[0].value_after_depreciation - - def num_of_depreciations(asset): return asset.finance_books[0].total_number_of_depreciations diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js index 36f510b18e..ae0e1bda02 100644 --- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js +++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js @@ -47,7 +47,7 @@ frappe.ui.form.on('Asset Value Adjustment', { set_current_asset_value: function(frm) { if (frm.doc.asset) { frm.call({ - method: "erpnext.assets.doctype.asset_value_adjustment.asset_value_adjustment.get_current_asset_value", + method: "erpnext.assets.doctype.asset.asset.get_asset_value_after_depreciation", args: { asset: frm.doc.asset, finance_book: frm.doc.finance_book 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 6cfbe53cf6..539cdec74b 100644 --- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py +++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py @@ -10,6 +10,7 @@ from frappe.utils import date_diff, flt, formatdate, get_link_to_form, getdate from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( get_checks_for_pl_and_bs_accounts, ) +from erpnext.assets.doctype.asset.asset import get_asset_value_after_depreciation from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import ( get_asset_depr_schedule_doc, @@ -46,7 +47,7 @@ class AssetValueAdjustment(Document): def set_current_asset_value(self): if not self.current_asset_value and self.asset: - self.current_asset_value = get_current_asset_value(self.asset, self.finance_book) + self.current_asset_value = get_asset_value_after_depreciation(self.asset, self.finance_book) def make_depreciation_entry(self): asset = frappe.get_doc("Asset", self.asset) @@ -177,12 +178,3 @@ class AssetValueAdjustment(Document): asset_data.db_update() new_asset_depr_schedule_doc.submit() - - -@frappe.whitelist() -def get_current_asset_value(asset, finance_book=None): - cond = {"parent": asset, "parenttype": "Asset"} - if finance_book: - cond.update({"finance_book": finance_book}) - - return frappe.db.get_value("Asset Finance Book", cond, "value_after_depreciation") diff --git a/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py index 03dcea96c5..0b3dcba024 100644 --- a/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py +++ b/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py @@ -6,13 +6,11 @@ import unittest import frappe from frappe.utils import add_days, get_last_day, nowdate +from erpnext.assets.doctype.asset.asset import get_asset_value_after_depreciation from erpnext.assets.doctype.asset.test_asset import create_asset_data from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import ( get_asset_depr_schedule_doc, ) -from erpnext.assets.doctype.asset_value_adjustment.asset_value_adjustment import ( - get_current_asset_value, -) from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt @@ -46,7 +44,7 @@ class TestAssetValueAdjustment(unittest.TestCase): ) asset_doc.submit() - current_value = get_current_asset_value(asset_doc.name) + current_value = get_asset_value_after_depreciation(asset_doc.name) self.assertEqual(current_value, 100000.0) def test_asset_depreciation_value_adjustment(self): @@ -79,7 +77,7 @@ class TestAssetValueAdjustment(unittest.TestCase): first_asset_depr_schedule = get_asset_depr_schedule_doc(asset_doc.name, "Active") self.assertEquals(first_asset_depr_schedule.status, "Active") - current_value = get_current_asset_value(asset_doc.name) + current_value = get_asset_value_after_depreciation(asset_doc.name) adj_doc = make_asset_value_adjustment( asset=asset_doc.name, current_asset_value=current_value, new_asset_value=50000.0 ) diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py index d41069c1c9..cead72eae5 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py @@ -4,13 +4,16 @@ import frappe from frappe import _ -from frappe.utils import cstr, flt, formatdate, getdate +from frappe.query_builder.functions import Sum +from frappe.utils import cstr, formatdate, getdate from erpnext.accounts.report.financial_statements import ( get_fiscal_year_data, get_period_list, validate_fiscal_year, ) +from erpnext.assets.doctype.asset.asset import get_asset_value_after_depreciation +from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts def execute(filters=None): @@ -85,6 +88,7 @@ def get_data(filters): "asset_name", "status", "department", + "company", "cost_center", "calculate_depreciation", "purchase_receipt", @@ -98,8 +102,25 @@ def get_data(filters): ] assets_record = frappe.db.get_all("Asset", filters=conditions, fields=fields) + finance_book_filter = ("is", "not set") + if filters.finance_book: + finance_book_filter = ("=", filters.finance_book) + + assets_linked_to_fb = frappe.db.get_all( + doctype="Asset Finance Book", + filters={"finance_book": finance_book_filter}, + pluck="parent", + ) + for asset in assets_record: - asset_value = get_asset_value(asset, filters.finance_book) + if filters.finance_book: + if asset.asset_id not in assets_linked_to_fb: + continue + else: + if asset.calculate_depreciation and asset.asset_id not in assets_linked_to_fb: + continue + + asset_value = get_asset_value_after_depreciation(asset.asset_id, filters.finance_book) row = { "asset_id": asset.asset_id, "asset_name": asset.asset_name, @@ -110,7 +131,7 @@ def get_data(filters): or pi_supplier_map.get(asset.purchase_invoice), "gross_purchase_amount": asset.gross_purchase_amount, "opening_accumulated_depreciation": asset.opening_accumulated_depreciation, - "depreciated_amount": depreciation_amount_map.get(asset.asset_id) or 0.0, + "depreciated_amount": get_depreciation_amount_of_asset(asset, depreciation_amount_map, filters), "available_for_use_date": asset.available_for_use_date, "location": asset.location, "asset_category": asset.asset_category, @@ -122,23 +143,6 @@ def get_data(filters): return data -def get_asset_value(asset, finance_book=None): - if not asset.calculate_depreciation: - return flt(asset.gross_purchase_amount) - flt(asset.opening_accumulated_depreciation) - - result = frappe.get_all( - doctype="Asset Finance Book", - filters={ - "parent": asset.asset_id, - "finance_book": finance_book or ("is", "not set"), - }, - pluck="value_after_depreciation", - limit=1, - ) - - return result[0] if result else 0.0 - - def prepare_chart_data(data, filters): labels_values_map = {} date_field = frappe.scrub(filters.date_based_on) @@ -184,6 +188,15 @@ def prepare_chart_data(data, filters): } +def get_depreciation_amount_of_asset(asset, depreciation_amount_map, filters): + if asset.calculate_depreciation: + depr_amount = depreciation_amount_map.get(asset.asset_id) or 0.0 + else: + depr_amount = get_manual_depreciation_amount_of_asset(asset, filters) + + return depr_amount + + def get_finance_book_value_map(filters): date = filters.to_date if filters.filter_based_on == "Date Range" else filters.year_end_date @@ -205,6 +218,31 @@ def get_finance_book_value_map(filters): ) +def get_manual_depreciation_amount_of_asset(asset, filters): + date = filters.to_date if filters.filter_based_on == "Date Range" else filters.year_end_date + + (_, _, depreciation_expense_account) = get_depreciation_accounts(asset) + + gle = frappe.qb.DocType("GL Entry") + + result = ( + frappe.qb.from_(gle) + .select(Sum(gle.debit)) + .where(gle.against_voucher == asset.asset_id) + .where(gle.account == depreciation_expense_account) + .where(gle.debit != 0) + .where(gle.is_cancelled == 0) + .where(gle.posting_date <= date) + ).run() + + if result and result[0] and result[0][0]: + depr_amount = result[0][0] + else: + depr_amount = 0 + + return depr_amount + + def get_purchase_receipt_supplier_map(): return frappe._dict( frappe.db.sql( diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 698ffac77f..211f07445a 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -326,3 +326,4 @@ erpnext.patches.v14_0.create_accounting_dimensions_for_payment_request erpnext.patches.v14_0.update_entry_type_for_journal_entry erpnext.patches.v14_0.change_autoname_for_tax_withheld_vouchers erpnext.patches.v14_0.set_pick_list_status +erpnext.patches.v15_0.update_asset_value_for_manual_depr_entries diff --git a/erpnext/patches/v15_0/update_asset_value_for_manual_depr_entries.py b/erpnext/patches/v15_0/update_asset_value_for_manual_depr_entries.py new file mode 100644 index 0000000000..5d7b5cf19c --- /dev/null +++ b/erpnext/patches/v15_0/update_asset_value_for_manual_depr_entries.py @@ -0,0 +1,38 @@ +import frappe +from frappe.query_builder.functions import IfNull, Sum + + +def execute(): + asset = frappe.qb.DocType("Asset") + gle = frappe.qb.DocType("GL Entry") + aca = frappe.qb.DocType("Asset Category Account") + company = frappe.qb.DocType("Company") + + asset_total_depr_value_map = ( + frappe.qb.from_(gle) + .join(asset) + .on(gle.against_voucher == asset.name) + .join(aca) + .on((aca.parent == asset.asset_category) & (aca.company_name == asset.company)) + .join(company) + .on(company.name == asset.company) + .select(Sum(gle.debit).as_("value"), asset.name.as_("asset_name")) + .where( + gle.account == IfNull(aca.depreciation_expense_account, company.depreciation_expense_account) + ) + .where(gle.debit != 0) + .where(gle.is_cancelled == 0) + .where(asset.docstatus == 1) + .where(asset.calculate_depreciation == 0) + .groupby(asset.name) + ) + + frappe.qb.update(asset).join(asset_total_depr_value_map).on( + asset_total_depr_value_map.asset_name == asset.name + ).set( + asset.value_after_depreciation, asset.value_after_depreciation - asset_total_depr_value_map.value + ).where( + asset.docstatus == 1 + ).where( + asset.calculate_depreciation == 0 + ).run() From e5da0d7a633954ec06cdeebd146e4d579330193e Mon Sep 17 00:00:00 2001 From: anandbaburajan Date: Fri, 3 Feb 2023 22:23:17 +0530 Subject: [PATCH 2/2] chore: refactor AssetDepreciationSchedule --- erpnext/assets/doctype/asset/asset.py | 35 +- erpnext/assets/doctype/asset/test_asset.py | 16 +- .../asset_depreciation_schedule.py | 576 ++++++++---------- .../asset_value_adjustment.py | 6 +- ...sset_depreciation_schedules_from_assets.py | 6 +- 5 files changed, 296 insertions(+), 343 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index e24c41d24b..4f1cacaad5 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -36,7 +36,6 @@ from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_sched get_depr_schedule, make_draft_asset_depr_schedules, make_draft_asset_depr_schedules_if_not_present, - set_draft_asset_depr_schedule_details, update_draft_asset_depr_schedules, ) from erpnext.controllers.accounts_controller import AccountsController @@ -439,6 +438,17 @@ class Asset(AccountsController): if finance_book == row.finance_book: return row.value_after_depreciation + def _get_value_after_depreciation_for_making_schedule(self, fb_row): + # value_after_depreciation - current Asset value + if self.docstatus == 1 and fb_row.value_after_depreciation: + value_after_depreciation = flt(fb_row.value_after_depreciation) + else: + value_after_depreciation = flt(self.gross_purchase_amount) - flt( + self.opening_accumulated_depreciation + ) + + return value_after_depreciation + def get_default_finance_book_idx(self): if not self.get("default_finance_book") and self.company: self.default_finance_book = erpnext.get_default_finance_book(self.company) @@ -466,6 +476,25 @@ class Asset(AccountsController): return records + @erpnext.allow_regional + def get_depreciation_amount(self, depreciable_value, fb_row): + if fb_row.depreciation_method in ("Straight Line", "Manual"): + # if the Depreciation Schedule is being prepared for the first time + if not self.flags.increase_in_asset_life: + depreciation_amount = ( + flt(self.gross_purchase_amount) - flt(fb_row.expected_value_after_useful_life) + ) / flt(fb_row.total_number_of_depreciations) + + # if the Depreciation Schedule is being modified after Asset Repair + else: + depreciation_amount = ( + flt(fb_row.value_after_depreciation) - flt(fb_row.expected_value_after_useful_life) + ) / (date_diff(self.to_date, self.available_for_use_date) / 365) + else: + depreciation_amount = flt(depreciable_value * (flt(fb_row.rate_of_depreciation) / 100)) + + return depreciation_amount + def validate_make_gl_entry(self): purchase_document = self.get_purchase_document() if not purchase_document: @@ -915,7 +944,7 @@ def update_existing_asset(asset, remaining_qty, new_asset_name): ) new_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc) - set_draft_asset_depr_schedule_details(new_asset_depr_schedule_doc, asset, row) + new_asset_depr_schedule_doc.set_draft_asset_depr_schedule_details(asset, row) accumulated_depreciation = 0 @@ -967,7 +996,7 @@ def create_new_asset_after_split(asset, split_qty): ) new_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc) - set_draft_asset_depr_schedule_details(new_asset_depr_schedule_doc, new_asset, row) + new_asset_depr_schedule_doc.set_draft_asset_depr_schedule_details(new_asset, row) accumulated_depreciation = 0 diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index a9af670618..9a152638f9 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -29,7 +29,6 @@ from erpnext.assets.doctype.asset.depreciation import ( scrap_asset, ) from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import ( - clear_depr_schedule, get_asset_depr_schedule_doc, get_depr_schedule, ) @@ -925,11 +924,6 @@ class TestDepreciationBasics(AssetSetup): def test_get_depreciation_amount(self): """Tests if get_depreciation_amount() returns the right value.""" - - 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") asset.calculate_depreciation = 1 @@ -944,7 +938,7 @@ class TestDepreciationBasics(AssetSetup): }, ) - depreciation_amount = get_depreciation_amount(asset, 100000, asset.finance_books[0]) + depreciation_amount = asset.get_depreciation_amount(100000, asset.finance_books[0]) self.assertEqual(depreciation_amount, 30000) def test_make_depr_schedule(self): @@ -1260,7 +1254,7 @@ class TestDepreciationBasics(AssetSetup): asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active") - clear_depr_schedule(asset_depr_schedule_doc) + asset_depr_schedule_doc.clear_depr_schedule() self.assertEqual(len(asset_depr_schedule_doc.get("depreciation_schedule")), 1) @@ -1309,19 +1303,19 @@ class TestDepreciationBasics(AssetSetup): asset_depr_schedule_doc_1 = get_asset_depr_schedule_doc( asset.name, "Active", "Test Finance Book 1" ) - clear_depr_schedule(asset_depr_schedule_doc_1) + asset_depr_schedule_doc_1.clear_depr_schedule() self.assertEqual(len(asset_depr_schedule_doc_1.get("depreciation_schedule")), 3) asset_depr_schedule_doc_2 = get_asset_depr_schedule_doc( asset.name, "Active", "Test Finance Book 2" ) - clear_depr_schedule(asset_depr_schedule_doc_2) + asset_depr_schedule_doc_2.clear_depr_schedule() self.assertEqual(len(asset_depr_schedule_doc_2.get("depreciation_schedule")), 3) asset_depr_schedule_doc_3 = get_asset_depr_schedule_doc( asset.name, "Active", "Test Finance Book 3" ) - clear_depr_schedule(asset_depr_schedule_doc_3) + asset_depr_schedule_doc_3.clear_depr_schedule() self.assertEqual(len(asset_depr_schedule_doc_3.get("depreciation_schedule")), 0) def test_depreciation_schedules_are_set_up_for_multiple_finance_books(self): 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 02e508a7fa..7615fbc86f 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py @@ -4,17 +4,7 @@ import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import ( - add_days, - add_months, - cint, - date_diff, - flt, - get_last_day, - is_last_day_of_the_month, -) - -import erpnext +from frappe.utils import add_days, add_months, cint, flt, get_last_day, is_last_day_of_the_month class AssetDepreciationSchedule(Document): @@ -83,7 +73,256 @@ class AssetDepreciationSchedule(Document): ) asset_finance_book_doc = frappe.get_doc("Asset Finance Book", asset_finance_book_name) - prepare_draft_asset_depr_schedule_data(self, asset_doc, asset_finance_book_doc) + self.prepare_draft_asset_depr_schedule_data(asset_doc, asset_finance_book_doc) + + def prepare_draft_asset_depr_schedule_data( + self, + asset_doc, + row, + date_of_disposal=None, + date_of_return=None, + update_asset_finance_book_row=True, + ): + self.set_draft_asset_depr_schedule_details(asset_doc, row) + self.make_depr_schedule(asset_doc, row, date_of_disposal, update_asset_finance_book_row) + self.set_accumulated_depreciation(row, date_of_disposal, date_of_return) + + def set_draft_asset_depr_schedule_details(self, asset_doc, row): + self.asset = asset_doc.name + self.finance_book = row.finance_book + self.finance_book_id = row.idx + self.opening_accumulated_depreciation = asset_doc.opening_accumulated_depreciation + self.depreciation_method = row.depreciation_method + self.total_number_of_depreciations = row.total_number_of_depreciations + self.frequency_of_depreciation = row.frequency_of_depreciation + self.rate_of_depreciation = row.rate_of_depreciation + self.expected_value_after_useful_life = row.expected_value_after_useful_life + self.status = "Draft" + + def make_depr_schedule( + self, asset_doc, row, date_of_disposal, update_asset_finance_book_row=True + ): + if row.depreciation_method != "Manual" and not self.get("depreciation_schedule"): + self.depreciation_schedule = [] + + if not asset_doc.available_for_use_date: + return + + start = self.clear_depr_schedule() + + self._make_depr_schedule(asset_doc, row, start, date_of_disposal, update_asset_finance_book_row) + + def clear_depr_schedule(self): + start = 0 + num_of_depreciations_completed = 0 + depr_schedule = [] + + for schedule in self.get("depreciation_schedule"): + if schedule.journal_entry: + num_of_depreciations_completed += 1 + depr_schedule.append(schedule) + else: + start = num_of_depreciations_completed + break + + self.depreciation_schedule = depr_schedule + + return start + + def _make_depr_schedule( + self, asset_doc, row, start, date_of_disposal, update_asset_finance_book_row + ): + asset_doc.validate_asset_finance_books(row) + + value_after_depreciation = asset_doc._get_value_after_depreciation_for_making_schedule(row) + row.value_after_depreciation = value_after_depreciation + + if update_asset_finance_book_row: + row.db_update() + + number_of_pending_depreciations = cint(row.total_number_of_depreciations) - cint( + asset_doc.number_of_depreciations_booked + ) + + has_pro_rata = asset_doc.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, number_of_pending_depreciations): + # If depreciation is already completed (for double declining balance) + if skip_row: + continue + + depreciation_amount = asset_doc.get_depreciation_amount(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_doc.available_for_use_date + if self.depreciation_schedule: + from_date = self.depreciation_schedule[-1].schedule_date + + depreciation_amount, days, months = asset_doc.get_pro_rata_amt( + row, depreciation_amount, from_date, date_of_disposal + ) + + if depreciation_amount > 0: + self.add_depr_schedule_row( + date_of_disposal, + depreciation_amount, + row.depreciation_method, + ) + + break + + # For first row + if has_pro_rata and not asset_doc.opening_accumulated_depreciation and n == 0: + from_date = add_days( + asset_doc.available_for_use_date, -1 + ) # needed to calc depr amount for available_for_use_date too + depreciation_amount, days, months = asset_doc.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_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 + asset_doc.number_of_depreciations_booked) * cint(row.frequency_of_depreciation), + ) + + depreciation_amount_without_pro_rata = depreciation_amount + + depreciation_amount, days, months = asset_doc.get_pro_rata_amt( + row, depreciation_amount, schedule_date, asset_doc.to_date + ) + + depreciation_amount = self.get_adjusted_depreciation_amount( + depreciation_amount_without_pro_rata, depreciation_amount + ) + + 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_doc.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: + self.add_depr_schedule_row( + schedule_date, + depreciation_amount, + row.depreciation_method, + ) + + # to ensure that final accumulated depreciation amount is accurate + def get_adjusted_depreciation_amount( + self, depreciation_amount_without_pro_rata, depreciation_amount_for_last_row + ): + if not self.opening_accumulated_depreciation: + depreciation_amount_for_first_row = self.get_depreciation_amount_for_first_row() + + if ( + depreciation_amount_for_first_row + depreciation_amount_for_last_row + != depreciation_amount_without_pro_rata + ): + depreciation_amount_for_last_row = ( + depreciation_amount_without_pro_rata - depreciation_amount_for_first_row + ) + + return depreciation_amount_for_last_row + + def get_depreciation_amount_for_first_row(self): + return self.get("depreciation_schedule")[0].depreciation_amount + + def add_depr_schedule_row( + self, + schedule_date, + depreciation_amount, + depreciation_method, + ): + self.append( + "depreciation_schedule", + { + "schedule_date": schedule_date, + "depreciation_amount": depreciation_amount, + "depreciation_method": depreciation_method, + }, + ) + + def set_accumulated_depreciation( + self, + row, + date_of_disposal=None, + date_of_return=None, + ignore_booked_entry=False, + ): + straight_line_idx = [ + d.idx for d in self.get("depreciation_schedule") if d.depreciation_method == "Straight Line" + ] + + accumulated_depreciation = flt(self.opening_accumulated_depreciation) + value_after_depreciation = flt(row.value_after_depreciation) + + for i, d in enumerate(self.get("depreciation_schedule")): + if ignore_booked_entry and d.journal_entry: + continue + + 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") + ) def make_draft_asset_depr_schedules_if_not_present(asset_doc): @@ -108,7 +347,7 @@ def make_draft_asset_depr_schedules(asset_doc): def make_draft_asset_depr_schedule(asset_doc, row): asset_depr_schedule_doc = frappe.new_doc("Asset Depreciation Schedule") - prepare_draft_asset_depr_schedule_data(asset_depr_schedule_doc, asset_doc, row) + asset_depr_schedule_doc.prepare_draft_asset_depr_schedule_data(asset_doc, row) asset_depr_schedule_doc.insert() @@ -120,41 +359,11 @@ def update_draft_asset_depr_schedules(asset_doc): if not asset_depr_schedule_doc: continue - prepare_draft_asset_depr_schedule_data(asset_depr_schedule_doc, asset_doc, row) + asset_depr_schedule_doc.prepare_draft_asset_depr_schedule_data(asset_doc, row) asset_depr_schedule_doc.save() -def prepare_draft_asset_depr_schedule_data( - asset_depr_schedule_doc, - asset_doc, - row, - date_of_disposal=None, - date_of_return=None, - update_asset_finance_book_row=True, -): - set_draft_asset_depr_schedule_details(asset_depr_schedule_doc, asset_doc, row) - make_depr_schedule( - asset_depr_schedule_doc, asset_doc, row, date_of_disposal, update_asset_finance_book_row - ) - set_accumulated_depreciation(asset_depr_schedule_doc, row, date_of_disposal, date_of_return) - - -def set_draft_asset_depr_schedule_details(asset_depr_schedule_doc, asset_doc, row): - asset_depr_schedule_doc.asset = asset_doc.name - asset_depr_schedule_doc.finance_book = row.finance_book - asset_depr_schedule_doc.finance_book_id = row.idx - asset_depr_schedule_doc.opening_accumulated_depreciation = ( - asset_doc.opening_accumulated_depreciation - ) - asset_depr_schedule_doc.depreciation_method = row.depreciation_method - asset_depr_schedule_doc.total_number_of_depreciations = row.total_number_of_depreciations - asset_depr_schedule_doc.frequency_of_depreciation = row.frequency_of_depreciation - asset_depr_schedule_doc.rate_of_depreciation = row.rate_of_depreciation - asset_depr_schedule_doc.expected_value_after_useful_life = row.expected_value_after_useful_life - asset_depr_schedule_doc.status = "Draft" - - def convert_draft_asset_depr_schedules_into_active(asset_doc): for row in asset_doc.get("finance_books"): asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset_doc.name, "Draft", row.finance_book) @@ -192,8 +401,8 @@ def make_new_active_asset_depr_schedules_and_cancel_current_ones( new_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc) - make_depr_schedule(new_asset_depr_schedule_doc, asset_doc, row, date_of_disposal) - set_accumulated_depreciation(new_asset_depr_schedule_doc, row, date_of_disposal, date_of_return) + new_asset_depr_schedule_doc.make_depr_schedule(asset_doc, row, date_of_disposal) + new_asset_depr_schedule_doc.set_accumulated_depreciation(row, date_of_disposal, date_of_return) new_asset_depr_schedule_doc.notes = notes @@ -208,8 +417,7 @@ def get_temp_asset_depr_schedule_doc( ): asset_depr_schedule_doc = frappe.new_doc("Asset Depreciation Schedule") - prepare_draft_asset_depr_schedule_data( - asset_depr_schedule_doc, + asset_depr_schedule_doc.prepare_draft_asset_depr_schedule_data( asset_doc, row, date_of_disposal, @@ -254,275 +462,3 @@ def get_asset_depr_schedule_name(asset_name, status, finance_book=None): ["status", "=", status], ], ) - - -def make_depr_schedule( - asset_depr_schedule_doc, asset_doc, row, date_of_disposal, update_asset_finance_book_row=True -): - if row.depreciation_method != "Manual" and not asset_depr_schedule_doc.get( - "depreciation_schedule" - ): - asset_depr_schedule_doc.depreciation_schedule = [] - - if not asset_doc.available_for_use_date: - return - - start = clear_depr_schedule(asset_depr_schedule_doc) - - _make_depr_schedule( - asset_depr_schedule_doc, asset_doc, row, start, date_of_disposal, update_asset_finance_book_row - ) - - -def clear_depr_schedule(asset_depr_schedule_doc): - start = 0 - num_of_depreciations_completed = 0 - depr_schedule = [] - - for schedule in asset_depr_schedule_doc.get("depreciation_schedule"): - if schedule.journal_entry: - num_of_depreciations_completed += 1 - depr_schedule.append(schedule) - else: - start = num_of_depreciations_completed - break - - asset_depr_schedule_doc.depreciation_schedule = depr_schedule - - return start - - -def _make_depr_schedule( - asset_depr_schedule_doc, asset_doc, row, start, date_of_disposal, update_asset_finance_book_row -): - asset_doc.validate_asset_finance_books(row) - - value_after_depreciation = _get_value_after_depreciation_for_making_schedule(asset_doc, row) - row.value_after_depreciation = value_after_depreciation - - if update_asset_finance_book_row: - row.db_update() - - number_of_pending_depreciations = cint(row.total_number_of_depreciations) - cint( - asset_doc.number_of_depreciations_booked - ) - - has_pro_rata = asset_doc.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, number_of_pending_depreciations): - # If depreciation is already completed (for double declining balance) - if skip_row: - continue - - depreciation_amount = get_depreciation_amount(asset_doc, 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_doc.available_for_use_date - if asset_depr_schedule_doc.depreciation_schedule: - from_date = asset_depr_schedule_doc.depreciation_schedule[-1].schedule_date - - depreciation_amount, days, months = asset_doc.get_pro_rata_amt( - row, depreciation_amount, from_date, date_of_disposal - ) - - if depreciation_amount > 0: - add_depr_schedule_row( - asset_depr_schedule_doc, - date_of_disposal, - depreciation_amount, - row.depreciation_method, - ) - - break - - # For first row - if has_pro_rata and not asset_doc.opening_accumulated_depreciation and n == 0: - from_date = add_days( - asset_doc.available_for_use_date, -1 - ) # needed to calc depr amount for available_for_use_date too - depreciation_amount, days, months = asset_doc.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_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 + asset_doc.number_of_depreciations_booked) * cint(row.frequency_of_depreciation), - ) - - depreciation_amount_without_pro_rata = depreciation_amount - - depreciation_amount, days, months = asset_doc.get_pro_rata_amt( - row, depreciation_amount, schedule_date, asset_doc.to_date - ) - - depreciation_amount = get_adjusted_depreciation_amount( - asset_depr_schedule_doc, depreciation_amount_without_pro_rata, depreciation_amount - ) - - 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_doc.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_doc, - schedule_date, - depreciation_amount, - row.depreciation_method, - ) - - -def _get_value_after_depreciation_for_making_schedule(asset_doc, row): - # value_after_depreciation - current Asset value - if asset_doc.docstatus == 1 and row.value_after_depreciation: - value_after_depreciation = flt(row.value_after_depreciation) - else: - value_after_depreciation = flt(asset_doc.gross_purchase_amount) - flt( - asset_doc.opening_accumulated_depreciation - ) - - return value_after_depreciation - - -# to ensure that final accumulated depreciation amount is accurate -def get_adjusted_depreciation_amount( - asset_depr_schedule_doc, depreciation_amount_without_pro_rata, depreciation_amount_for_last_row -): - if not asset_depr_schedule_doc.opening_accumulated_depreciation: - depreciation_amount_for_first_row = get_depreciation_amount_for_first_row( - asset_depr_schedule_doc - ) - - if ( - depreciation_amount_for_first_row + depreciation_amount_for_last_row - != depreciation_amount_without_pro_rata - ): - depreciation_amount_for_last_row = ( - depreciation_amount_without_pro_rata - depreciation_amount_for_first_row - ) - - return depreciation_amount_for_last_row - - -def get_depreciation_amount_for_first_row(asset_depr_schedule_doc): - return asset_depr_schedule_doc.get("depreciation_schedule")[0].depreciation_amount - - -@erpnext.allow_regional -def get_depreciation_amount(asset_doc, 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_doc.flags.increase_in_asset_life: - depreciation_amount = ( - flt(asset_doc.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_doc.to_date, asset_doc.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_doc, - schedule_date, - depreciation_amount, - depreciation_method, -): - asset_depr_schedule_doc.append( - "depreciation_schedule", - { - "schedule_date": schedule_date, - "depreciation_amount": depreciation_amount, - "depreciation_method": depreciation_method, - }, - ) - - -def set_accumulated_depreciation( - asset_depr_schedule_doc, - row, - date_of_disposal=None, - date_of_return=None, - ignore_booked_entry=False, -): - straight_line_idx = [ - d.idx - for d in asset_depr_schedule_doc.get("depreciation_schedule") - if d.depreciation_method == "Straight Line" - ] - - accumulated_depreciation = flt(asset_depr_schedule_doc.opening_accumulated_depreciation) - value_after_depreciation = flt(row.value_after_depreciation) - - for i, d in enumerate(asset_depr_schedule_doc.get("depreciation_schedule")): - if ignore_booked_entry and d.journal_entry: - continue - - 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_value_adjustment/asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py index 539cdec74b..31d6ffab5f 100644 --- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py +++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py @@ -14,8 +14,6 @@ from erpnext.assets.doctype.asset.asset import get_asset_value_after_depreciatio from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import ( get_asset_depr_schedule_doc, - get_depreciation_amount, - set_accumulated_depreciation, ) @@ -164,7 +162,7 @@ class AssetValueAdjustment(Document): depreciation_amount = days * rate_per_day from_date = data.schedule_date else: - depreciation_amount = get_depreciation_amount(asset, value_after_depreciation, d) + depreciation_amount = asset.get_depreciation_amount(value_after_depreciation, d) if depreciation_amount: value_after_depreciation -= flt(depreciation_amount) @@ -172,7 +170,7 @@ class AssetValueAdjustment(Document): d.db_update() - set_accumulated_depreciation(new_asset_depr_schedule_doc, d, ignore_booked_entry=True) + new_asset_depr_schedule_doc.set_accumulated_depreciation(d, ignore_booked_entry=True) for asset_data in depr_schedule: if not asset_data.journal_entry: asset_data.db_update() diff --git a/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py b/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py index 5dc3cdde6f..371ecbc8c1 100644 --- a/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py +++ b/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py @@ -1,9 +1,5 @@ import frappe -from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import ( - set_draft_asset_depr_schedule_details, -) - def execute(): frappe.reload_doc("assets", "doctype", "Asset Depreciation Schedule") @@ -16,7 +12,7 @@ def execute(): for fb_row in finance_book_rows: asset_depr_schedule_doc = frappe.new_doc("Asset Depreciation Schedule") - set_draft_asset_depr_schedule_details(asset_depr_schedule_doc, asset, fb_row) + asset_depr_schedule_doc.set_draft_asset_depr_schedule_details(asset, fb_row) asset_depr_schedule_doc.insert()