Merge pull request #17191 from rohitwaghchaure/asset_depreciation_rate_hotfix

fix: Asset depreciation formula for WDV method, user was not able to edit schedule table for Manula method
diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js
index ed02d87..18ba8f9 100644
--- a/erpnext/assets/doctype/asset/asset.js
+++ b/erpnext/assets/doctype/asset/asset.js
@@ -31,7 +31,7 @@
 				}
 			};
 		});
-		
+
 		frm.set_query("cost_center", function() {
 			return {
 				"filters": {
@@ -206,12 +206,10 @@
 		erpnext.asset.set_accululated_depreciation(frm);
 	},
 
-	depreciation_method: function(frm) {
-		frm.events.make_schedules_editable(frm);
-	},
-
 	make_schedules_editable: function(frm) {
-		var is_editable = frm.doc.depreciation_method==="Manual" ? true : false;
+		var is_editable = frm.doc.finance_books.filter(d => d.depreciation_method == "Manual").length > 0
+			? true : false;
+
 		frm.toggle_enable("schedules", is_editable);
 		frm.fields_dict["schedules"].grid.toggle_enable("schedule_date", is_editable);
 		frm.fields_dict["schedules"].grid.toggle_enable("depreciation_amount", is_editable);
@@ -296,6 +294,44 @@
 		})
 
 		frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation);
+	},
+
+	set_depreciation_rate: function(frm, row) {
+		if (row.total_number_of_depreciations && row.frequency_of_depreciation) {
+			frappe.call({
+				method: "get_depreciation_rate",
+				doc: frm.doc,
+				args: row,
+				callback: function(r) {
+					if (r.message) {
+						frappe.model.set_value(row.doctype, row.name, "rate_of_depreciation", r.message);
+					}
+				}
+			});
+		}
+	}
+});
+
+frappe.ui.form.on('Asset Finance Book', {
+	depreciation_method: function(frm, cdt, cdn) {
+		const row = locals[cdt][cdn];
+		frm.events.set_depreciation_rate(frm, row);
+		frm.events.make_schedules_editable(frm);
+	},
+
+	expected_value_after_useful_life: function(frm, cdt, cdn) {
+		const row = locals[cdt][cdn];
+		frm.events.set_depreciation_rate(frm, row);
+	},
+
+	frequency_of_depreciation: function(frm, cdt, cdn) {
+		const row = locals[cdt][cdn];
+		frm.events.set_depreciation_rate(frm, row);
+	},
+
+	total_number_of_depreciations: function(frm, cdt, cdn) {
+		const row = locals[cdt][cdn];
+		frm.events.set_depreciation_rate(frm, row);
 	}
 });
 
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index cfacb5a..8011038 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -3,8 +3,9 @@
 # For license information, please see license.txt
 
 from __future__ import unicode_literals
-import frappe, erpnext
+import frappe, erpnext, math, json
 from frappe import _
+from six import string_types
 from frappe.utils import flt, add_months, cint, nowdate, getdate, today, date_diff
 from frappe.model.document import Document
 from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
@@ -20,6 +21,7 @@
 		self.validate_item()
 		self.set_missing_values()
 		if self.calculate_depreciation:
+			self.set_depreciation_rate()
 			self.make_depreciation_schedule()
 			self.set_accumulated_depreciation()
 		else:
@@ -89,17 +91,22 @@
 		if self.is_existing_asset:
 			return
 
-		date =  nowdate()
 		docname = self.purchase_receipt or self.purchase_invoice
 		if docname:
 			doctype = 'Purchase Receipt' if self.purchase_receipt else 'Purchase Invoice'
 			date = frappe.db.get_value(doctype, docname, 'posting_date')
 
-		if self.available_for_use_date and getdate(self.available_for_use_date) < getdate(date):
+		if self.available_for_use_date and getdate(self.available_for_use_date) < getdate(self.purchase_date):
 			frappe.throw(_("Available-for-use Date should be after purchase date"))
 
+	def set_depreciation_rate(self):
+		for d in self.get("finance_books"):
+			d.rate_of_depreciation = self.get_depreciation_rate(d)
+
 	def make_depreciation_schedule(self):
-		if self.depreciation_method != 'Manual':
+		depreciation_method = [d.depreciation_method for d in self.finance_books]
+
+		if 'Manual' not in depreciation_method:
 			self.schedules = []
 
 		if not self.get("schedules") and self.available_for_use_date:
@@ -254,14 +261,16 @@
 		return flt(self.get('finance_books')[cint(idx)-1].value_after_depreciation)
 
 	def get_depreciation_amount(self, depreciable_value, total_number_of_depreciations, row):
-		percentage_value = 100.0 if row.depreciation_method == 'Written Down Value' else 200.0
+		if row.depreciation_method in ["Straight Line", "Manual"]:
+			amt = (flt(self.gross_purchase_amount) - flt(row.expected_value_after_useful_life) -
+				flt(self.opening_accumulated_depreciation))
 
-		factor = percentage_value /  cint(total_number_of_depreciations)
-		depreciation_amount = flt(depreciable_value * factor / 100, 0)
-
-		value_after_depreciation = flt(depreciable_value) - depreciation_amount
-		if value_after_depreciation < flt(row.expected_value_after_useful_life):
-			depreciation_amount = flt(depreciable_value) - flt(row.expected_value_after_useful_life)
+			depreciation_amount = amt * row.rate_of_depreciation
+		else:
+			depreciation_amount = flt(depreciable_value) * (flt(row.rate_of_depreciation) / 100)
+			value_after_depreciation = flt(depreciable_value) - depreciation_amount
+			if value_after_depreciation < flt(row.expected_value_after_useful_life):
+				depreciation_amount = flt(depreciable_value) - flt(row.expected_value_after_useful_life)
 
 		return depreciation_amount
 
@@ -394,6 +403,32 @@
 			make_gl_entries(gl_entries)
 			self.db_set('booked_fixed_asset', 1)
 
+	def get_depreciation_rate(self, args):
+		if isinstance(args, string_types):
+			args = json.loads(args)
+
+		number_of_depreciations_booked = 0
+		if self.is_existing_asset:
+			number_of_depreciations_booked = self.number_of_depreciations_booked
+
+		float_precision = cint(frappe.db.get_default("float_precision")) or 2
+		tot_no_of_depreciation = flt(args.get("total_number_of_depreciations")) - flt(number_of_depreciations_booked)
+
+		if args.get("depreciation_method") in ["Straight Line", "Manual"]:
+			return 1.0 / tot_no_of_depreciation
+
+		if args.get("depreciation_method") == 'Double Declining Balance':
+			return 200.0 / args.get("total_number_of_depreciations")
+
+		if args.get("depreciation_method") == "Written Down Value" and not args.get("rate_of_depreciation"):
+			no_of_years = flt(args.get("total_number_of_depreciations") * flt(args.get("frequency_of_depreciation"))) / 12
+			value = flt(args.get("expected_value_after_useful_life")) / flt(self.gross_purchase_amount)
+
+			# square root of flt(salvage_value) / flt(asset_cost)
+			depreciation_rate = math.pow(value, 1.0/flt(no_of_years, 2))
+
+			return 100 * (1 - flt(depreciation_rate, float_precision))
+
 def update_maintenance_status():
 	assets = frappe.get_all('Asset', filters = {'docstatus': 1, 'maintenance_required': 1})
 
@@ -480,7 +515,6 @@
 
 @frappe.whitelist()
 def transfer_asset(args):
-	import json
 	args = json.loads(args)
 
 	if args.get('serial_no'):
@@ -557,4 +591,4 @@
 	return je
 
 def is_cwip_accounting_disabled():
-	return cint(frappe.db.get_single_value("Asset Settings", "disable_cwip_accounting"))
\ No newline at end of file
+	return cint(frappe.db.get_single_value("Asset Settings", "disable_cwip_accounting"))
diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py
index a12348e..985097b 100644
--- a/erpnext/assets/doctype/asset/test_asset.py
+++ b/erpnext/assets/doctype/asset/test_asset.py
@@ -160,9 +160,9 @@
 		asset.save()
 
 		expected_schedules = [
-			["2020-06-06", 66667.0, 66667.0],
-			["2021-04-06", 22222.0, 88889.0],
-			["2022-02-06", 1111.0, 90000.0]
+			["2020-06-06", 66666.67, 66666.67],
+			["2021-04-06", 22222.22, 88888.89],
+			["2022-02-06", 1111.11, 90000.0]
 		]
 
 		schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
@@ -192,8 +192,8 @@
 		asset.save()
 
 		expected_schedules = [
-			["2020-06-06", 33333.0, 83333.0],
-			["2021-04-06", 6667.0, 90000.0]
+			["2020-06-06", 33333.33, 83333.33],
+			["2021-04-06", 6666.67, 90000.0]
 		]
 
 		schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
@@ -209,7 +209,7 @@
 		asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
 		asset = frappe.get_doc('Asset', asset_name)
 		asset.calculate_depreciation = 1
-		asset.purchase_date = '2020-06-06'
+		asset.purchase_date = '2020-01-30'
 		asset.is_existing_asset = 0
 		asset.available_for_use_date = "2020-01-30"
 		asset.append("finance_books", {
@@ -244,7 +244,7 @@
 		asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
 		asset = frappe.get_doc('Asset', asset_name)
 		asset.calculate_depreciation = 1
-		asset.purchase_date = '2020-06-06'
+		asset.purchase_date = '2020-01-30'
 		asset.available_for_use_date = "2020-01-30"
 		asset.append("finance_books", {
 			"expected_value_after_useful_life": 10000,
@@ -277,6 +277,37 @@
 		self.assertEqual(gle, expected_gle)
 		self.assertEqual(asset.get("value_after_depreciation"), 0)
 
+	def test_depreciation_entry_for_wdv(self):
+		pr = make_purchase_receipt(item_code="Macbook Pro",
+			qty=1, rate=8000.0, location="Test Location")
+
+		asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
+		asset = frappe.get_doc('Asset', asset_name)
+		asset.calculate_depreciation = 1
+		asset.available_for_use_date = '2030-06-06'
+		asset.purchase_date = '2030-06-06'
+		asset.append("finance_books", {
+			"expected_value_after_useful_life": 1000,
+			"depreciation_method": "Written Down Value",
+			"total_number_of_depreciations": 3,
+			"frequency_of_depreciation": 12,
+			"depreciation_start_date": "2030-12-31"
+		})
+		asset.save(ignore_permissions=True)
+
+		self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0)
+
+		expected_schedules = [
+			["2030-12-31", 4000.0, 4000.0],
+			["2031-12-31", 2000.0, 6000.0],
+			["2032-12-31", 1000.0, 7000.0],
+		]
+
+		schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)]
+			for d in asset.get("schedules")]
+
+		self.assertEqual(schedules, expected_schedules)
+
 	def test_depreciation_entry_cancellation(self):
 		pr = make_purchase_receipt(item_code="Macbook Pro",
 			qty=1, rate=100000.0, location="Test Location")
diff --git a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json
index f75c851..c80f95e 100644
--- a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json
+++ b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json
@@ -1,5 +1,6 @@
 {
  "allow_copy": 0, 
+ "allow_events_in_timeline": 0, 
  "allow_guest_to_view": 0, 
  "allow_import": 0, 
  "allow_rename": 0, 
@@ -14,11 +15,13 @@
  "fields": [
   {
    "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
    "depends_on": "", 
+   "fetch_if_empty": 0, 
    "fieldname": "finance_book", 
    "fieldtype": "Link", 
    "hidden": 0, 
@@ -42,14 +45,17 @@
    "reqd": 0, 
    "search_index": 0, 
    "set_only_once": 0, 
+   "translatable": 0, 
    "unique": 0
   }, 
   {
    "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "fetch_if_empty": 0, 
    "fieldname": "depreciation_method", 
    "fieldtype": "Select", 
    "hidden": 0, 
@@ -73,14 +79,17 @@
    "reqd": 1, 
    "search_index": 0, 
    "set_only_once": 0, 
+   "translatable": 0, 
    "unique": 0
   }, 
   {
    "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "fetch_if_empty": 0, 
    "fieldname": "total_number_of_depreciations", 
    "fieldtype": "Int", 
    "hidden": 0, 
@@ -103,14 +112,17 @@
    "reqd": 1, 
    "search_index": 0, 
    "set_only_once": 0, 
+   "translatable": 0, 
    "unique": 0
   }, 
   {
    "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "fetch_if_empty": 0, 
    "fieldname": "column_break_5", 
    "fieldtype": "Column Break", 
    "hidden": 0, 
@@ -133,14 +145,17 @@
    "reqd": 0, 
    "search_index": 0, 
    "set_only_once": 0, 
+   "translatable": 0, 
    "unique": 0
   }, 
   {
    "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "fetch_if_empty": 0, 
    "fieldname": "frequency_of_depreciation", 
    "fieldtype": "Int", 
    "hidden": 0, 
@@ -163,15 +178,18 @@
    "reqd": 1, 
    "search_index": 0, 
    "set_only_once": 0, 
+   "translatable": 0, 
    "unique": 0
   }, 
   {
    "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
    "depends_on": "eval:parent.doctype == 'Asset'", 
+   "fetch_if_empty": 0, 
    "fieldname": "depreciation_start_date", 
    "fieldtype": "Date", 
    "hidden": 0, 
@@ -194,16 +212,19 @@
    "reqd": 0, 
    "search_index": 0, 
    "set_only_once": 0, 
+   "translatable": 0, 
    "unique": 0
   }, 
   {
    "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
    "default": "0", 
    "depends_on": "eval:parent.doctype == 'Asset'", 
+   "fetch_if_empty": 0, 
    "fieldname": "expected_value_after_useful_life", 
    "fieldtype": "Currency", 
    "hidden": 0, 
@@ -227,14 +248,17 @@
    "reqd": 0, 
    "search_index": 0, 
    "set_only_once": 0, 
+   "translatable": 0, 
    "unique": 0
   }, 
   {
    "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "fetch_if_empty": 0, 
    "fieldname": "value_after_depreciation", 
    "fieldtype": "Currency", 
    "hidden": 1, 
@@ -258,20 +282,54 @@
    "reqd": 0, 
    "search_index": 0, 
    "set_only_once": 0, 
+   "translatable": 0, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "depends_on": "eval:doc.depreciation_method == 'Written Down Value'", 
+   "description": "In Percentage", 
+   "fetch_if_empty": 0, 
+   "fieldname": "rate_of_depreciation", 
+   "fieldtype": "Percent", 
+   "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": "Rate of Depreciation", 
+   "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, 
+   "translatable": 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": 0, 
  "istable": 1, 
  "max_attachments": 0, 
- "modified": "2018-05-12 14:56:44.800046", 
+ "modified": "2019-04-09 19:45:14.523488", 
  "modified_by": "Administrator", 
  "module": "Assets", 
  "name": "Asset Finance Book", 
@@ -280,10 +338,10 @@
  "permissions": [], 
  "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
+ "track_seen": 0, 
+ "track_views": 0
 }
\ No newline at end of file