From 85a2776e9170c5f51c2d41759ffe243756b8d0d1 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 20 Apr 2016 17:57:04 +0530 Subject: [PATCH] Depreciation Schedule considering already booked depreciations for existing asset and testcases --- erpnext/accounts/doctype/asset/asset.js | 3 +- erpnext/accounts/doctype/asset/asset.json | 33 +++++++++- erpnext/accounts/doctype/asset/asset.py | 65 ++++++++++++------- erpnext/accounts/doctype/asset/test_asset.py | 45 +++++++++++-- .../asset_category/asset_category.json | 7 +- .../doctype/asset_category/asset_category.py | 2 +- .../asset_category/test_asset_category.py | 2 +- .../depreciation_schedule.json | 6 +- .../purchase_invoice/purchase_invoice.py | 1 - .../asset_depreciations_and_balances.py | 17 ++--- 10 files changed, 132 insertions(+), 49 deletions(-) diff --git a/erpnext/accounts/doctype/asset/asset.js b/erpnext/accounts/doctype/asset/asset.js index 548de08dfc..0dfdb219ff 100644 --- a/erpnext/accounts/doctype/asset/asset.js +++ b/erpnext/accounts/doctype/asset/asset.js @@ -60,7 +60,8 @@ erpnext.asset.make_purchase_invoice = function(frm) { "asset": frm.doc.name, "item_code": frm.doc.item_code, "gross_purchase_amount": frm.doc.gross_purchase_amount, - "company": frm.doc.company + "company": frm.doc.company, + "posting_date": frm.doc.purchase_date }, method: "erpnext.accounts.doctype.asset.asset.make_purchase_invoice", callback: function(r) { diff --git a/erpnext/accounts/doctype/asset/asset.json b/erpnext/accounts/doctype/asset/asset.json index cf03bf6922..826f70ef53 100644 --- a/erpnext/accounts/doctype/asset/asset.json +++ b/erpnext/accounts/doctype/asset/asset.json @@ -398,14 +398,14 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "fieldname": "number_of_depreciations", + "fieldname": "total_number_of_depreciations", "fieldtype": "Int", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "label": "Number of Depreciations", + "label": "Total Number of Depreciations", "length": 0, "no_copy": 0, "permlevel": 0, @@ -419,6 +419,32 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "depends_on": "eval:(doc.is_existing_asset && doc.opening_accumulated_depreciation)", + "fieldname": "number_of_depreciations_booked", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Number of Depreciations Booked", + "length": 0, + "no_copy": 1, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -686,7 +712,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2016-04-08 18:42:52.810809", + "modified": "2016-04-20 15:46:49.479769", "modified_by": "Administrator", "module": "Accounts", "name": "Asset", @@ -714,6 +740,7 @@ "write": 1 } ], + "quick_entry": 0, "read_only": 0, "read_only_onload": 1, "sort_field": "modified", diff --git a/erpnext/accounts/doctype/asset/asset.py b/erpnext/accounts/doctype/asset/asset.py index 1807d70acd..fff493ee16 100644 --- a/erpnext/accounts/doctype/asset/asset.py +++ b/erpnext/accounts/doctype/asset/asset.py @@ -8,7 +8,8 @@ from frappe import _ from frappe.utils import flt, add_months, cint, nowdate, getdate from frappe.model.document import Document from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import get_fixed_asset_account -from erpnext.accounts.doctype.asset.depreciation import get_disposal_account_and_cost_center +from erpnext.accounts.doctype.asset.depreciation \ + import get_disposal_account_and_cost_center, get_depreciation_accounts class Asset(Document): def validate(self): @@ -17,7 +18,8 @@ class Asset(Document): self.validate_asset_values() self.set_depreciation_settings() self.make_depreciation_schedule() - self.validate_depreciation_settings_in_company() + # Validate depreciation related accounts + get_depreciation_accounts(self) def on_submit(self): self.set_status() @@ -43,6 +45,7 @@ class Asset(Document): if not self.is_existing_asset: self.opening_accumulated_depreciation = 0 + self.number_of_depreciations_booked = 0 if not self.next_depreciation_date: frappe.throw(_("Next Depreciation Date is mandatory for new asset")) else: @@ -51,14 +54,28 @@ class Asset(Document): frappe.throw(_("Opening Accumulated Depreciation must be less than equal to {0}") .format(depreciable_amount)) + if self.opening_accumulated_depreciation: + if not self.number_of_depreciations_booked: + frappe.throw(_("Please set Number of Depreciations Booked")) + else: + self.number_of_depreciations_booked = 0 + + if cint(self.number_of_depreciations_booked) > cint(self.total_number_of_depreciations): + frappe.throw(_("Number of Depreciations Booked cannot be greater than Total Number of Depreciations")) + if self.next_depreciation_date and getdate(self.next_depreciation_date) < getdate(nowdate()): frappe.throw(_("Next Depreciation Date must be on or after today")) + + if (flt(self.value_after_depreciation) > flt(self.expected_value_after_useful_life) + and not self.next_depreciation_date): + frappe.throw(_("Please set Next Depreciation Date")) + def set_depreciation_settings(self): asset_category = frappe.get_doc("Asset Category", self.asset_category) - for field in ("depreciation_method", "number_of_depreciations", "frequency_of_depreciation"): + for field in ("depreciation_method", "total_number_of_depreciations", "frequency_of_depreciation"): if not self.get(field): self.set(field, asset_category.get(field)) @@ -67,27 +84,32 @@ class Asset(Document): if not self.get("schedules") and self.next_depreciation_date: accumulated_depreciation = flt(self.opening_accumulated_depreciation) value_after_depreciation = flt(self.value_after_depreciation) - for n in xrange(self.number_of_depreciations): - schedule_date = add_months(self.next_depreciation_date, - n * cint(self.frequency_of_depreciation)) + + 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)) - depreciation_amount = self.get_depreciation_amount(value_after_depreciation) + depreciation_amount = self.get_depreciation_amount(value_after_depreciation) - accumulated_depreciation += flt(depreciation_amount) - value_after_depreciation -= flt(depreciation_amount) + accumulated_depreciation += flt(depreciation_amount) + value_after_depreciation -= flt(depreciation_amount) - self.append("schedules", { - "schedule_date": schedule_date, - "depreciation_amount": depreciation_amount, - "accumulated_depreciation_amount": accumulated_depreciation - }) + self.append("schedules", { + "schedule_date": schedule_date, + "depreciation_amount": depreciation_amount, + "accumulated_depreciation_amount": accumulated_depreciation + }) def get_depreciation_amount(self, depreciable_value): if self.depreciation_method == "Straight Line": depreciation_amount = (flt(self.value_after_depreciation) - - flt(self.expected_value_after_useful_life)) / cint(self.number_of_depreciations) + flt(self.expected_value_after_useful_life)) / (cint(self.total_number_of_depreciations) - + cint(self.number_of_depreciations_booked)) else: - factor = 200.0 / cint(self.number_of_depreciations) + factor = 200.0 / self.total_number_of_depreciations depreciation_amount = flt(depreciable_value * factor / 100, 0) value_after_depreciation = flt(depreciable_value) - depreciation_amount @@ -112,14 +134,6 @@ class Asset(Document): self.db_set("value_after_depreciation", (flt(self.gross_purchase_amount) - flt(self.opening_accumulated_depreciation))) - def validate_depreciation_settings_in_company(self): - company = frappe.get_doc("Company", self.company) - for field in ("accumulated_depreciation_account", "depreciation_expense_account", - "disposal_account", "depreciation_cost_center"): - if not company.get(field): - frappe.throw(_("Please set {0} in Company {1}") - .format(company.meta.get_label(field), self.company)) - def set_status(self, status=None): '''Get and update status''' if not status: @@ -144,10 +158,11 @@ class Asset(Document): return status @frappe.whitelist() -def make_purchase_invoice(asset, item_code, gross_purchase_amount, company): +def make_purchase_invoice(asset, item_code, gross_purchase_amount, company, posting_date): pi = frappe.new_doc("Purchase Invoice") pi.company = company pi.currency = frappe.db.get_value("Company", company, "default_currency") + pi.posting_date = posting_date pi.append("items", { "item_code": item_code, "is_fixed_asset": 1, diff --git a/erpnext/accounts/doctype/asset/test_asset.py b/erpnext/accounts/doctype/asset/test_asset.py index ea1d6a2308..2753e15481 100644 --- a/erpnext/accounts/doctype/asset/test_asset.py +++ b/erpnext/accounts/doctype/asset/test_asset.py @@ -18,14 +18,15 @@ class TestAsset(unittest.TestCase): 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) + 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(getdate(asset.purchase_date), getdate(nowdate())) + self.assertEqual(asset.purchase_date, getdate("2015-01-01")) self.assertEqual(asset.purchase_invoice, pi.name) expected_gle = ( @@ -43,7 +44,6 @@ class TestAsset(unittest.TestCase): asset.load_from_db() self.assertEqual(asset.supplier, None) - self.assertEqual(asset.purchase_date, None) self.assertEqual(asset.purchase_invoice, None) self.assertFalse(frappe.db.get_value("GL Entry", @@ -65,6 +65,25 @@ 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 = [ + ["2020-12-31", 25000, 65000], + ["2021-03-31", 25000, 90000] + ] + + schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in asset.get("schedules")] + + self.assertEqual(schedules, expected_schedules) def test_schedule_for_double_declining_method(self): @@ -82,6 +101,24 @@ 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" + asset.is_existing_asset = 1 + asset.number_of_depreciations_booked = 1 + asset.opening_accumulated_depreciation = 50000 + asset.save() + + expected_schedules = [ + ["2020-12-31", 33333, 83333], + ["2021-03-31", 6667, 90000] + ] + + schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in asset.get("schedules")] + + self.assertEqual(schedules, expected_schedules) def test_depreciation(self): asset = frappe.get_doc("Asset", "Macbook Pro 1") @@ -204,7 +241,7 @@ def create_asset(): def create_asset_category(): asset_category = frappe.new_doc("Asset Category") asset_category.asset_category_name = "Computers" - asset_category.number_of_depreciations = 3 + asset_category.total_number_of_depreciations = 3 asset_category.frequency_of_depreciation = 3 asset_category.append("accounts", { "company_name": "_Test Company", diff --git a/erpnext/accounts/doctype/asset_category/asset_category.json b/erpnext/accounts/doctype/asset_category/asset_category.json index ccdb675e7a..20dd24705b 100644 --- a/erpnext/accounts/doctype/asset_category/asset_category.json +++ b/erpnext/accounts/doctype/asset_category/asset_category.json @@ -89,14 +89,14 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "fieldname": "number_of_depreciations", + "fieldname": "total_number_of_depreciations", "fieldtype": "Int", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "label": "Number of Depreciations", + "label": "Total Number of Depreciations", "length": 0, "no_copy": 0, "permlevel": 0, @@ -196,7 +196,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2016-04-07 18:16:57.343260", + "modified": "2016-04-20 13:23:09.890324", "modified_by": "Administrator", "module": "Accounts", "name": "Asset Category", @@ -244,6 +244,7 @@ "write": 1 } ], + "quick_entry": 0, "read_only": 0, "read_only_onload": 0, "sort_field": "modified", diff --git a/erpnext/accounts/doctype/asset_category/asset_category.py b/erpnext/accounts/doctype/asset_category/asset_category.py index f46dc84d82..5279c37ea8 100644 --- a/erpnext/accounts/doctype/asset_category/asset_category.py +++ b/erpnext/accounts/doctype/asset_category/asset_category.py @@ -10,6 +10,6 @@ from frappe.model.document import Document class AssetCategory(Document): def validate(self): - for field in ("number_of_depreciations", "frequency_of_depreciation"): + 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 diff --git a/erpnext/accounts/doctype/asset_category/test_asset_category.py b/erpnext/accounts/doctype/asset_category/test_asset_category.py index 624e019b35..b32f9b5020 100644 --- a/erpnext/accounts/doctype/asset_category/test_asset_category.py +++ b/erpnext/accounts/doctype/asset_category/test_asset_category.py @@ -13,7 +13,7 @@ class TestAssetCategory(unittest.TestCase): self.assertRaises(frappe.MandatoryError, asset_category.insert) - asset_category.number_of_depreciations = 3 + asset_category.total_number_of_depreciations = 3 asset_category.frequency_of_depreciation = 3 asset_category.append("accounts", { "company_name": "_Test Company", diff --git a/erpnext/accounts/doctype/depreciation_schedule/depreciation_schedule.json b/erpnext/accounts/doctype/depreciation_schedule/depreciation_schedule.json index 50fba8878d..dc854b219e 100644 --- a/erpnext/accounts/doctype/depreciation_schedule/depreciation_schedule.json +++ b/erpnext/accounts/doctype/depreciation_schedule/depreciation_schedule.json @@ -146,15 +146,17 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2016-03-09 12:21:27.938215", + "modified": "2016-04-20 16:43:21.407123", "modified_by": "Administrator", "module": "Accounts", "name": "Depreciation Schedule", "name_case": "", "owner": "Administrator", "permissions": [], + "quick_entry": 1, "read_only": 0, "read_only_onload": 0, "sort_field": "modified", - "sort_order": "DESC" + "sort_order": "DESC", + "track_seen": 0 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 65c8c029ea..b31c1aba6f 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -322,7 +322,6 @@ class PurchaseInvoice(BuyingController): asset.supplier = self.supplier else: asset.purchase_invoice = None - asset.purchase_date = None asset.supplier = None asset.flags.ignore_validate_update_after_submit = True diff --git a/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py index d65189201a..890833bf86 100644 --- a/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py +++ b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py @@ -29,13 +29,13 @@ def get_data(filters): row.update(asset_depreciations.get(asset_category)) row.accumulated_depreciation_as_on_to_date = (flt(row.accumulated_depreciation_as_on_from_date) + - flt(row.depreciation_amount_during_the_period)) - + flt(row.depreciation_amount_during_the_period) - flt(row.depreciation_eliminated)) + row.net_asset_value_as_on_from_date = (flt(row.cost_as_on_from_date) - flt(row.accumulated_depreciation_as_on_from_date)) - + row.net_asset_value_as_on_to_date = (flt(row.cost_as_on_to_date) - - flt(row.accumulated_depreciation_as_on_to_date) - flt(row.depreciation_eliminated)) + flt(row.accumulated_depreciation_as_on_to_date)) data.append(row) @@ -89,19 +89,20 @@ def get_accumulated_depreciations(assets, filters): asset_depreciations.setdefault(d.asset_category, frappe._dict({ "accumulated_depreciation_as_on_from_date": asset.opening_accumulated_depreciation, "depreciation_amount_during_the_period": 0, - "depreciation_eliminated": 0 + "depreciation_eliminated_during_the_period": 0 })) depr = asset_depreciations[d.asset_category] for schedule in asset.get("schedules"): if getdate(schedule.schedule_date) < getdate(filters.from_date): - depr.accumulated_depreciation_as_on_from_date += flt(schedule.depreciation_amount) + if not asset.disposal_date and getdate(asset.disposal_date) >= getdate(filters.from_date): + depr.accumulated_depreciation_as_on_from_date += flt(schedule.depreciation_amount) elif getdate(schedule.schedule_date) <= getdate(filters.to_date): depr.depreciation_amount_during_the_period += flt(schedule.depreciation_amount) if asset.disposal_date and getdate(schedule.schedule_date) > getdate(asset.disposal_date): - depr.depreciation_eliminated += flt(schedule.depreciation_amount) + depr.depreciation_eliminated_during_the_period += flt(schedule.depreciation_amount) return asset_depreciations @@ -158,7 +159,7 @@ def get_columns(filters): }, { "label": _("Depreciation Eliminated due to disposal of assets"), - "fieldname": "depreciation_eliminated", + "fieldname": "depreciation_eliminated_during_the_period", "fieldtype": "Currency", "width": 300 },