diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index 8287401105..236c4f9f9b 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -198,6 +198,19 @@ frappe.ui.form.on('Asset', { frappe.set_route("Form", doclist[0].doctype, doclist[0].name); } }) + }, + + calculate_depreciation: function(frm) { + frappe.db.get_value("Asset Settings", {'name':"Asset Settings"}, 'schedule_based_on_fiscal_year', (data) => { + if (data.schedule_based_on_fiscal_year == 1) { + frm.set_df_property("depreciation_method", "options", "\nStraight Line\nManual"); + frm.toggle_reqd("available_for_use_date", true); + frm.toggle_display("frequency_of_depreciation", false); + frappe.db.get_value("Fiscal Year", {'name': frappe.sys_defaults.fiscal_year}, "year_end_date", (data) => { + frm.set_value("next_depreciation_date", data.year_end_date); + }) + } + }) } }); @@ -316,4 +329,4 @@ erpnext.asset.transfer_asset = function(frm) { }) }); dialog.show(); -}; \ No newline at end of file +}; diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index 45c9a65c37..49b574d3b0 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -507,6 +507,36 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "available_for_use_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Available-for-use Date", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -1221,7 +1251,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-12-19 12:58:44.137460", + "modified": "2018-01-05 09:53:05.945328", "modified_by": "Administrator", "module": "Assets", "name": "Asset", @@ -1277,4 +1307,4 @@ "sort_order": "DESC", "track_changes": 0, "track_seen": 0 -} \ No newline at end of file +} diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 8bba0b6936..bd33a1b211 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import flt, add_months, cint, nowdate, getdate, today +from frappe.utils import flt, add_months, cint, nowdate, getdate, today, date_diff from frappe.model.document import Document from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import get_fixed_asset_account from erpnext.assets.doctype.asset.depreciation \ @@ -90,11 +90,15 @@ class Asset(Document): if self.next_depreciation_date and getdate(self.next_depreciation_date) < getdate(self.purchase_date): frappe.throw(_("Next Depreciation Date cannot be before Purchase Date")) + if self.next_depreciation_date and getdate(self.next_depreciation_date) < getdate(self.available_for_use_date): + frappe.throw(_("Next Depreciation Date cannot be before Available-for-use Date")) + if (flt(self.value_after_depreciation) > flt(self.expected_value_after_useful_life) and not self.next_depreciation_date and self.calculate_depreciation): frappe.throw(_("Please set Next Depreciation Date")) def make_depreciation_schedule(self): + if self.depreciation_method != 'Manual': self.schedules = [] @@ -104,18 +108,51 @@ class Asset(Document): number_of_pending_depreciations = cint(self.total_number_of_depreciations) - \ cint(self.number_of_depreciations_booked) if number_of_pending_depreciations: - for n in xrange(number_of_pending_depreciations): - schedule_date = add_months(self.next_depreciation_date, - n * cint(self.frequency_of_depreciation)) + next_depr_date = getdate(add_months(self.available_for_use_date, + number_of_pending_depreciations * 12)) + if (cint(frappe.db.get_value("Asset Settings", None, "schedule_based_on_fiscal_year")) == 1 + and getdate(self.next_depreciation_date) < next_depr_date): - depreciation_amount = self.get_depreciation_amount(value_after_depreciation) - if depreciation_amount: - value_after_depreciation -= flt(depreciation_amount) + number_of_pending_depreciations += 1 + for n in xrange(number_of_pending_depreciations): + if n == xrange(number_of_pending_depreciations)[-1]: + schedule_date = add_months(self.available_for_use_date, n * 12) + previous_scheduled_date = add_months(self.next_depreciation_date, (n-1) * 12) + depreciation_amount = \ + self.get_depreciation_amount_prorata_temporis(value_after_depreciation, + previous_scheduled_date, schedule_date) - self.append("schedules", { - "schedule_date": schedule_date, - "depreciation_amount": depreciation_amount - }) + elif n == xrange(number_of_pending_depreciations)[0]: + schedule_date = self.next_depreciation_date + depreciation_amount = \ + self.get_depreciation_amount_prorata_temporis(value_after_depreciation, + self.available_for_use_date, schedule_date) + + else: + schedule_date = add_months(self.next_depreciation_date, n * 12) + depreciation_amount = \ + self.get_depreciation_amount_prorata_temporis(value_after_depreciation) + + if value_after_depreciation != 0: + value_after_depreciation -= flt(depreciation_amount) + + self.append("schedules", { + "schedule_date": schedule_date, + "depreciation_amount": depreciation_amount + }) + else: + for n in xrange(number_of_pending_depreciations): + schedule_date = add_months(self.next_depreciation_date, + n * cint(self.frequency_of_depreciation)) + + depreciation_amount = self.get_depreciation_amount(value_after_depreciation) + if depreciation_amount: + value_after_depreciation -= flt(depreciation_amount) + + self.append("schedules", { + "schedule_date": schedule_date, + "depreciation_amount": depreciation_amount + }) def set_accumulated_depreciation(self): accumulated_depreciation = flt(self.opening_accumulated_depreciation) @@ -125,12 +162,13 @@ class Asset(Document): value_after_depreciation -= flt(depreciation_amount) if i==len(self.get("schedules"))-1 and self.depreciation_method == "Straight Line": - depreciation_amount += flt(value_after_depreciation - flt(self.expected_value_after_useful_life), - d.precision("depreciation_amount")) + depreciation_amount += flt(value_after_depreciation - + flt(self.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")) + d.accumulated_depreciation_amount = flt(accumulated_depreciation, + d.precision("accumulated_depreciation_amount")) def get_depreciation_amount(self, depreciable_value): if self.depreciation_method in ("Straight Line", "Manual"): @@ -147,6 +185,21 @@ class Asset(Document): return depreciation_amount + def get_depreciation_amount_prorata_temporis(self, depreciable_value, start_date=None, end_date=None): + if start_date and end_date: + prorata_temporis = min(abs(flt(date_diff(str(end_date), str(start_date)))) / flt(frappe.db.get_value("Asset Settings", None, "number_of_days_in_fiscal_year")), 1) + else: + prorata_temporis = 1 + + if self.depreciation_method in ("Straight Line", "Manual"): + depreciation_amount = (flt(self.value_after_depreciation) - + flt(self.expected_value_after_useful_life)) / (cint(self.total_number_of_depreciations) - + cint(self.number_of_depreciations_booked)) * prorata_temporis + + return depreciation_amount + else: + self.get_depreciation_amount(depreciable_value) + def validate_expected_value_after_useful_life(self): accumulated_depreciation_after_full_schedule = \ max([d.accumulated_depreciation_amount for d in self.get("schedules")]) @@ -178,7 +231,7 @@ class Asset(Document): def set_status(self, status=None): '''Get and update status''' if not status: - status = self.get_status() + status = self.get_status() self.db_set("status", status) def get_status(self): diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 76c46cff7a..07ed0bacae 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -12,24 +12,25 @@ from erpnext.assets.doctype.asset.asset import make_sales_invoice, make_purchase class TestAsset(unittest.TestCase): def setUp(self): set_depreciation_settings_in_company() + remove_prorated_depreciation_schedule() create_asset() frappe.db.sql("delete from `tabTax Rule`") def test_purchase_asset(self): asset = frappe.get_doc("Asset", "Macbook Pro 1") asset.submit() - + pi = make_purchase_invoice(asset.name, asset.item_code, asset.gross_purchase_amount, asset.company, asset.purchase_date) pi.supplier = "_Test Supplier" pi.insert() pi.submit() - + asset.load_from_db() self.assertEqual(asset.supplier, "_Test Supplier") self.assertEqual(asset.purchase_date, getdate("2015-01-01")) self.assertEqual(asset.purchase_invoice, pi.name) - + expected_gle = ( ("_Test Fixed Asset - _TC", 100000.0, 0.0), ("Creditors - _TC", 0.0, 100000.0) @@ -46,10 +47,10 @@ class TestAsset(unittest.TestCase): asset.load_from_db() self.assertEqual(asset.supplier, None) self.assertEqual(asset.purchase_invoice, None) - + self.assertFalse(frappe.db.get_value("GL Entry", {"voucher_type": "Purchase Invoice", "voucher_no": pi.name})) - + def test_schedule_for_straight_line_method(self): asset = frappe.get_doc("Asset", "Macbook Pro 1") @@ -66,14 +67,14 @@ class TestAsset(unittest.TestCase): for d in asset.get("schedules")] self.assertEqual(schedules, expected_schedules) - + def test_schedule_for_straight_line_method_for_existing_asset(self): asset = frappe.get_doc("Asset", "Macbook Pro 1") asset.is_existing_asset = 1 asset.number_of_depreciations_booked = 1 asset.opening_accumulated_depreciation = 40000 asset.save() - + self.assertEqual(asset.status, "Draft") expected_schedules = [ @@ -102,7 +103,7 @@ class TestAsset(unittest.TestCase): for d in asset.get("schedules")] self.assertEqual(schedules, expected_schedules) - + def test_schedule_for_double_declining_method_for_existing_asset(self): asset = frappe.get_doc("Asset", "Macbook Pro 1") asset.depreciation_method = "Double Declining Balance" @@ -120,7 +121,7 @@ class TestAsset(unittest.TestCase): for d in asset.get("schedules")] self.assertEqual(schedules, expected_schedules) - + def test_schedule_for_manual_method(self): asset = frappe.get_doc("Asset", "Macbook Pro 1") asset.depreciation_method = "Manual" @@ -145,6 +146,29 @@ class TestAsset(unittest.TestCase): self.assertEqual(schedules, expected_schedules) + def test_schedule_for_prorated_straight_line_method(self): + set_prorated_depreciation_schedule() + asset = frappe.get_doc("Asset", "Macbook Pro 1") + asset.is_existing_asset = 0 + asset.available_for_use_date = "2020-01-30" + asset.next_depreciation_date = "2020-12-31" + asset.depreciation_method = "Straight Line" + asset.save() + + expected_schedules = [ + ["2020-12-31", 28000, 28000], + ["2021-12-31", 30000, 58000], + ["2022-12-31", 30000, 88000], + ["2023-01-30", 2000, 90000] + ] + + schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in asset.get("schedules")] + + self.assertEqual(schedules, expected_schedules) + + remove_prorated_depreciation_schedule() + def test_depreciation(self): asset = frappe.get_doc("Asset", "Macbook Pro 1") asset.submit() @@ -172,23 +196,23 @@ class TestAsset(unittest.TestCase): self.assertEqual(gle, expected_gle) self.assertEqual(asset.get("value_after_depreciation"), 70000) - + def test_depreciation_entry_cancellation(self): asset = frappe.get_doc("Asset", "Macbook Pro 1") asset.submit() post_depreciation_entries(date="2021-01-01") - + asset.load_from_db() - + # cancel depreciation entry depr_entry = asset.get("schedules")[0].journal_entry self.assertTrue(depr_entry) frappe.get_doc("Journal Entry", depr_entry).cancel() - + asset.load_from_db() depr_entry = asset.get("schedules")[0].journal_entry self.assertFalse(depr_entry) - + def test_scrap_asset(self): asset = frappe.get_doc("Asset", "Macbook Pro 1") @@ -283,7 +307,7 @@ def create_asset(): if not frappe.db.exists("Item", "Macbook Pro"): create_fixed_asset_item() - + asset = frappe.get_doc({ "doctype": "Asset", "asset_name": "Macbook Pro 1", @@ -341,6 +365,21 @@ def set_depreciation_settings_in_company(): company.disposal_account = "_Test Gain/Loss on Asset Disposal - _TC" company.depreciation_cost_center = "_Test Cost Center - _TC" company.save() - + # Enable booking asset depreciation entry automatically - frappe.db.set_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically", 1) \ No newline at end of file + frappe.db.set_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically", 1) + +def remove_prorated_depreciation_schedule(): + asset_settings = frappe.get_doc("Asset Settings", "Asset Settings") + asset_settings.schedule_based_on_fiscal_year = 0 + asset_settings.save() + + frappe.db.commit() + +def set_prorated_depreciation_schedule(): + asset_settings = frappe.get_doc("Asset Settings", "Asset Settings") + asset_settings.schedule_based_on_fiscal_year = 1 + asset_settings.number_of_days_in_fiscal_year = 360 + asset_settings.save() + + frappe.db.commit() diff --git a/erpnext/assets/doctype/asset_category/asset_category.py b/erpnext/assets/doctype/asset_category/asset_category.py index 5279c37ea8..542bd12861 100644 --- a/erpnext/assets/doctype/asset_category/asset_category.py +++ b/erpnext/assets/doctype/asset_category/asset_category.py @@ -12,4 +12,4 @@ class AssetCategory(Document): def validate(self): for field in ("total_number_of_depreciations", "frequency_of_depreciation"): if cint(self.get(field))<1: - frappe.throw(_("{0} must be greater than 0").format(self.meta.get_label(field)), frappe.MandatoryError) \ No newline at end of file + frappe.throw(_("{0} must be greater than 0").format(self.meta.get_label(field)), frappe.MandatoryError) diff --git a/erpnext/assets/doctype/asset_settings/__init__.py b/erpnext/assets/doctype/asset_settings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/assets/doctype/asset_settings/asset_settings.js b/erpnext/assets/doctype/asset_settings/asset_settings.js new file mode 100644 index 0000000000..3b421486c3 --- /dev/null +++ b/erpnext/assets/doctype/asset_settings/asset_settings.js @@ -0,0 +1,5 @@ +// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Asset Settings', { +}); diff --git a/erpnext/assets/doctype/asset_settings/asset_settings.json b/erpnext/assets/doctype/asset_settings/asset_settings.json new file mode 100644 index 0000000000..d6ddd33c30 --- /dev/null +++ b/erpnext/assets/doctype/asset_settings/asset_settings.json @@ -0,0 +1,175 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2018-01-03 10:30:32.983381", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "depreciation_options", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Depreciation Options", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "schedule_based_on_fiscal_year", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Calculate Prorated Depreciation Schedule Based on Fiscal Year", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "360", + "depends_on": "eval:doc.schedule_based_on_fiscal_year", + "description": "This value is used for pro-rata temporis calculation", + "fieldname": "number_of_days_in_fiscal_year", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Number of Days in Fiscal Year", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 1, + "istable": 0, + "max_attachments": 0, + "modified": "2018-01-05 10:10:39.803255", + "modified_by": "Administrator", + "module": "Assets", + "name": "Asset Settings", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 0, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 0, + "role": "Accounts Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_settings/asset_settings.py b/erpnext/assets/doctype/asset_settings/asset_settings.py new file mode 100644 index 0000000000..e303ebd23f --- /dev/null +++ b/erpnext/assets/doctype/asset_settings/asset_settings.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +from frappe.model.document import Document + +class AssetSettings(Document): + pass diff --git a/erpnext/assets/doctype/asset_settings/test_asset_settings.js b/erpnext/assets/doctype/asset_settings/test_asset_settings.js new file mode 100644 index 0000000000..eac2c928f3 --- /dev/null +++ b/erpnext/assets/doctype/asset_settings/test_asset_settings.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Asset Settings", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Asset Settings + () => frappe.tests.make('Asset Settings', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/assets/doctype/asset_settings/test_asset_settings.py b/erpnext/assets/doctype/asset_settings/test_asset_settings.py new file mode 100644 index 0000000000..75f146a27e --- /dev/null +++ b/erpnext/assets/doctype/asset_settings/test_asset_settings.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import unittest + +class TestAssetSettings(unittest.TestCase): + pass diff --git a/erpnext/config/assets.py b/erpnext/config/assets.py index 1001e948ec..73c1aee87a 100644 --- a/erpnext/config/assets.py +++ b/erpnext/config/assets.py @@ -13,6 +13,10 @@ def get_data(): { "type": "doctype", "name": "Asset Category", + }, + { + "type": "doctype", + "name": "Asset Settings", } ] }, @@ -74,4 +78,4 @@ def get_data(): }, ] } - ] \ No newline at end of file + ] diff --git a/erpnext/docs/assets/img/asset/asset_prorated_depreciation.png b/erpnext/docs/assets/img/asset/asset_prorated_depreciation.png new file mode 100644 index 0000000000..d81699beaf Binary files /dev/null and b/erpnext/docs/assets/img/asset/asset_prorated_depreciation.png differ diff --git a/erpnext/docs/user/manual/en/asset/assets.md b/erpnext/docs/user/manual/en/asset/assets.md index 6932c59dcc..1a5ce860c4 100644 --- a/erpnext/docs/user/manual/en/asset/assets.md +++ b/erpnext/docs/user/manual/en/asset/assets.md @@ -50,6 +50,13 @@ On the scheduled date, system creates depreciation entry by creating a Journal E In the depreciation entry, the "Accumulated Depreciation Account" is credited and "Depreciation Expense Account" is debited. The related accounts can be set in the Asset Category or Company. +If you are required to calculate the depreciation based on your Fiscal Year and prorated by the number of days left, select the corresponding option in "Account Settings". + +The system will automatically set the fiscal year end date as the next depreciation date and calculate the depreciation amount prorata temporis based on the Available-for-use Date (IFRS16) + +Asset + + For better visibility, net value of the asset on different depreciation dates are shown in a line graph. Asset