Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: shift depreciation for assets #38327

Merged
merged 9 commits into from
Nov 29, 2023
41 changes: 27 additions & 14 deletions erpnext/assets/doctype/asset/asset.js
Original file line number Diff line number Diff line change
Expand Up @@ -214,30 +214,43 @@ frappe.ui.form.on('Asset', {
})
},

render_depreciation_schedule_view: function(frm, depr_schedule) {
render_depreciation_schedule_view: function(frm, asset_depr_schedule_doc) {
let wrapper = $(frm.fields_dict["depreciation_schedule_view"].wrapper).empty();

let data = [];

depr_schedule.forEach((sch) => {
asset_depr_schedule_doc.depreciation_schedule.forEach((sch) => {
const row = [
sch['idx'],
frappe.format(sch['schedule_date'], { fieldtype: 'Date' }),
frappe.format(sch['depreciation_amount'], { fieldtype: 'Currency' }),
frappe.format(sch['accumulated_depreciation_amount'], { fieldtype: 'Currency' }),
sch['journal_entry'] || ''
sch['journal_entry'] || '',
];

if (asset_depr_schedule_doc.shift_based) {
row.push(sch['shift']);
}

data.push(row);
});

let columns = [
{name: __("No."), editable: false, resizable: false, format: value => value, width: 60},
{name: __("Schedule Date"), editable: false, resizable: false, width: 270},
{name: __("Depreciation Amount"), editable: false, resizable: false, width: 164},
{name: __("Accumulated Depreciation Amount"), editable: false, resizable: false, width: 164},
];

if (asset_depr_schedule_doc.shift_based) {
columns.push({name: __("Journal Entry"), editable: false, resizable: false, format: value => `<a href="/app/journal-entry/${value}">${value}</a>`, width: 245});
columns.push({name: __("Shift"), editable: false, resizable: false, width: 59});
} else {
columns.push({name: __("Journal Entry"), editable: false, resizable: false, format: value => `<a href="/app/journal-entry/${value}">${value}</a>`, width: 304});
}

let datatable = new frappe.DataTable(wrapper.get(0), {
columns: [
{name: __("No."), editable: false, resizable: false, format: value => value, width: 60},
{name: __("Schedule Date"), editable: false, resizable: false, width: 270},
{name: __("Depreciation Amount"), editable: false, resizable: false, width: 164},
{name: __("Accumulated Depreciation Amount"), editable: false, resizable: false, width: 164},
{name: __("Journal Entry"), editable: false, resizable: false, format: value => `<a href="/app/journal-entry/${value}">${value}</a>`, width: 304}
],
columns: columns,
data: data,
layout: "fluid",
serialNoColumn: false,
Expand Down Expand Up @@ -272,16 +285,16 @@ frappe.ui.form.on('Asset', {
asset_values.push(flt(frm.doc.gross_purchase_amount - frm.doc.opening_accumulated_depreciation, precision('gross_purchase_amount')));
}

let depr_schedule = (await frappe.call(
"erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule.get_depr_schedule",
let asset_depr_schedule_doc = (await frappe.call(
"erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule.get_asset_depr_schedule_doc",
{
asset_name: frm.doc.name,
status: "Active",
finance_book: frm.doc.finance_books[0].finance_book || null
}
)).message;

$.each(depr_schedule || [], function(i, v) {
$.each(asset_depr_schedule_doc.depreciation_schedule || [], function(i, v) {
x_intervals.push(frappe.format(v.schedule_date, { fieldtype: 'Date' }));
var asset_value = flt(frm.doc.gross_purchase_amount - v.accumulated_depreciation_amount, precision('gross_purchase_amount'));
if(v.journal_entry) {
Expand All @@ -296,7 +309,7 @@ frappe.ui.form.on('Asset', {
});

frm.toggle_display(["depreciation_schedule_view"], 1);
frm.events.render_depreciation_schedule_view(frm, depr_schedule);
frm.events.render_depreciation_schedule_view(frm, asset_depr_schedule_doc);
} else {
if(frm.doc.opening_accumulated_depreciation) {
x_intervals.push(frappe.format(frm.doc.creation.split(" ")[0], { fieldtype: 'Date' }));
Expand Down
1 change: 1 addition & 0 deletions erpnext/assets/doctype/asset/asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -825,6 +825,7 @@ def get_item_details(item_code, asset_category, gross_purchase_amount):
"total_number_of_depreciations": d.total_number_of_depreciations,
"frequency_of_depreciation": d.frequency_of_depreciation,
"daily_prorata_based": d.daily_prorata_based,
"shift_based": d.shift_based,
"salvage_value_percentage": d.salvage_value_percentage,
"expected_value_after_useful_life": flt(gross_purchase_amount)
* flt(d.salvage_value_percentage / 100),
Expand Down
7 changes: 6 additions & 1 deletion erpnext/assets/doctype/asset/test_asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -1000,7 +1000,11 @@ def test_get_depreciation_amount(self):
},
)

depreciation_amount = get_depreciation_amount(asset, 100000, asset.finance_books[0])
asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active")

depreciation_amount = get_depreciation_amount(
asset_depr_schedule_doc, asset, 100000, asset.finance_books[0]
)
self.assertEqual(depreciation_amount, 30000)

def test_make_depr_schedule(self):
Expand Down Expand Up @@ -1732,6 +1736,7 @@ def create_asset(**args):
"expected_value_after_useful_life": args.expected_value_after_useful_life or 0,
"depreciation_start_date": args.depreciation_start_date,
"daily_prorata_based": args.daily_prorata_based or 0,
"shift_based": args.shift_based or 0,
},
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ frappe.ui.form.on('Asset Depreciation Schedule', {
},

make_schedules_editable: function(frm) {
var is_editable = frm.doc.depreciation_method == "Manual" ? true : false;
var is_manual_hence_editable = frm.doc.depreciation_method === "Manual" ? true : false;
var is_shift_hence_editable = frm.doc.shift_based ? true : false;

frm.toggle_enable("depreciation_schedule", is_editable);
frm.fields_dict["depreciation_schedule"].grid.toggle_enable("schedule_date", is_editable);
frm.fields_dict["depreciation_schedule"].grid.toggle_enable("depreciation_amount", is_editable);
frm.toggle_enable("depreciation_schedule", is_manual_hence_editable || is_shift_hence_editable);
frm.fields_dict["depreciation_schedule"].grid.toggle_enable("schedule_date", is_manual_hence_editable);
frm.fields_dict["depreciation_schedule"].grid.toggle_enable("depreciation_amount", is_manual_hence_editable);
frm.fields_dict["depreciation_schedule"].grid.toggle_enable("shift", is_shift_hence_editable);
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"total_number_of_depreciations",
"rate_of_depreciation",
"daily_prorata_based",
"shift_based",
"column_break_8",
"frequency_of_depreciation",
"expected_value_after_useful_life",
Expand Down Expand Up @@ -184,12 +185,20 @@
"label": "Depreciate based on daily pro-rata",
"print_hide": 1,
"read_only": 1
},
{
"default": "0",
"depends_on": "eval:doc.depreciation_method == \"Straight Line\"",
"fieldname": "shift_based",
"fieldtype": "Check",
"label": "Depreciate based on shifts",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2023-11-03 21:32:15.021796",
"modified": "2023-11-29 00:57:00.461998",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Depreciation Schedule",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ def before_save(self):
self.prepare_draft_asset_depr_schedule_data_from_asset_name_and_fb_name(
self.asset, self.finance_book
)
self.update_shift_depr_schedule()

def validate(self):
self.validate_another_asset_depr_schedule_does_not_exist()
Expand Down Expand Up @@ -73,6 +74,16 @@ def cancel_depreciation_entries(self):
def on_cancel(self):
self.db_set("status", "Cancelled")

def update_shift_depr_schedule(self):
if not self.shift_based or self.docstatus != 0:
return

asset_doc = frappe.get_doc("Asset", self.asset)
fb_row = asset_doc.finance_books[self.finance_book_id - 1]

self.make_depr_schedule(asset_doc, fb_row)
self.set_accumulated_depreciation(asset_doc, fb_row)

def prepare_draft_asset_depr_schedule_data_from_asset_name_and_fb_name(self, asset_name, fb_name):
asset_doc = frappe.get_doc("Asset", asset_name)

Expand Down Expand Up @@ -154,13 +165,14 @@ def set_draft_asset_depr_schedule_details(self, asset_doc, row):
self.rate_of_depreciation = row.rate_of_depreciation
self.expected_value_after_useful_life = row.expected_value_after_useful_life
self.daily_prorata_based = row.daily_prorata_based
self.shift_based = row.shift_based
self.status = "Draft"

def make_depr_schedule(
self,
asset_doc,
row,
date_of_disposal,
date_of_disposal=None,
update_asset_finance_book_row=True,
value_after_depreciation=None,
):
Expand All @@ -181,6 +193,8 @@ def clear_depr_schedule(self):
num_of_depreciations_completed = 0
depr_schedule = []

self.schedules_before_clearing = self.get("depreciation_schedule")

for schedule in self.get("depreciation_schedule"):
if schedule.journal_entry:
num_of_depreciations_completed += 1
Expand Down Expand Up @@ -246,6 +260,7 @@ def _make_depr_schedule(
prev_depreciation_amount = 0

depreciation_amount = get_depreciation_amount(
self,
asset_doc,
value_after_depreciation,
row,
Expand Down Expand Up @@ -282,10 +297,7 @@ def _make_depr_schedule(
)

if depreciation_amount > 0:
self.add_depr_schedule_row(
date_of_disposal,
depreciation_amount,
)
self.add_depr_schedule_row(date_of_disposal, depreciation_amount, n)

break

Expand Down Expand Up @@ -369,10 +381,7 @@ def _make_depr_schedule(
skip_row = True

if flt(depreciation_amount, asset_doc.precision("gross_purchase_amount")) > 0:
self.add_depr_schedule_row(
schedule_date,
depreciation_amount,
)
self.add_depr_schedule_row(schedule_date, depreciation_amount, n)

# to ensure that final accumulated depreciation amount is accurate
def get_adjusted_depreciation_amount(
Expand All @@ -394,16 +403,22 @@ def get_adjusted_depreciation_amount(
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,
):
def add_depr_schedule_row(self, schedule_date, depreciation_amount, schedule_idx):
if self.shift_based:
shift = (
self.schedules_before_clearing[schedule_idx].shift
if self.schedules_before_clearing and len(self.schedules_before_clearing) > schedule_idx
else frappe.get_cached_value("Asset Shift Factor", {"default": 1}, "shift_name")
)
else:
shift = None

self.append(
"depreciation_schedule",
{
"schedule_date": schedule_date,
"depreciation_amount": depreciation_amount,
"shift": shift,
},
)

Expand Down Expand Up @@ -445,6 +460,7 @@ def set_accumulated_depreciation(
and i == max(straight_line_idx) - 1
and not date_of_disposal
and not date_of_return
and not row.shift_based
):
depreciation_amount += flt(
value_after_depreciation - flt(row.expected_value_after_useful_life),
Expand Down Expand Up @@ -527,6 +543,7 @@ def get_total_days(date, frequency):


def get_depreciation_amount(
asset_depr_schedule,
asset,
depreciable_value,
fb_row,
Expand All @@ -537,7 +554,7 @@ def get_depreciation_amount(
):
if fb_row.depreciation_method in ("Straight Line", "Manual"):
return get_straight_line_or_manual_depr_amount(
asset, fb_row, schedule_idx, number_of_pending_depreciations
asset_depr_schedule, asset, fb_row, schedule_idx, number_of_pending_depreciations
)
else:
rate_of_depreciation = get_updated_rate_of_depreciation_for_wdv_and_dd(
Expand All @@ -559,8 +576,11 @@ def get_updated_rate_of_depreciation_for_wdv_and_dd(asset, depreciable_value, fb


def get_straight_line_or_manual_depr_amount(
asset, row, schedule_idx, number_of_pending_depreciations
asset_depr_schedule, asset, row, schedule_idx, number_of_pending_depreciations
):
if row.shift_based:
return get_shift_depr_amount(asset_depr_schedule, asset, row, schedule_idx)

# if the Depreciation Schedule is being modified after Asset Repair due to increase in asset life and value
if asset.flags.increase_in_asset_life:
return (flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)) / (
Expand Down Expand Up @@ -655,6 +675,41 @@ def get_straight_line_or_manual_depr_amount(
) / flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked)


def get_shift_depr_amount(asset_depr_schedule, asset, row, schedule_idx):
if asset_depr_schedule.get("__islocal") and not asset.flags.shift_allocation:
return (
flt(asset.gross_purchase_amount)
- flt(asset.opening_accumulated_depreciation)
- flt(row.expected_value_after_useful_life)
) / flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked)

asset_shift_factors_map = get_asset_shift_factors_map()
shift = (
asset_depr_schedule.schedules_before_clearing[schedule_idx].shift
if len(asset_depr_schedule.schedules_before_clearing) > schedule_idx
else None
)
shift_factor = asset_shift_factors_map.get(shift) if shift else 0

shift_factors_sum = sum(
flt(asset_shift_factors_map.get(schedule.shift))
for schedule in asset_depr_schedule.schedules_before_clearing
)

return (
(
flt(asset.gross_purchase_amount)
- flt(asset.opening_accumulated_depreciation)
- flt(row.expected_value_after_useful_life)
)
/ flt(shift_factors_sum)
) * shift_factor


def get_asset_shift_factors_map():
return dict(frappe.db.get_all("Asset Shift Factor", ["shift_name", "shift_factor"], as_list=True))


def get_wdv_or_dd_depr_amount(
depreciable_value,
rate_of_depreciation,
Expand Down Expand Up @@ -803,7 +858,12 @@ def make_new_active_asset_depr_schedules_and_cancel_current_ones(


def get_temp_asset_depr_schedule_doc(
asset_doc, row, date_of_disposal=None, date_of_return=None, update_asset_finance_book_row=False
asset_doc,
row,
date_of_disposal=None,
date_of_return=None,
update_asset_finance_book_row=False,
new_depr_schedule=None,
):
current_asset_depr_schedule_doc = get_asset_depr_schedule_doc(
asset_doc.name, "Active", row.finance_book
Expand All @@ -818,6 +878,21 @@ def get_temp_asset_depr_schedule_doc(

temp_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc)

if new_depr_schedule:
temp_asset_depr_schedule_doc.depreciation_schedule = []

for schedule in new_depr_schedule:
temp_asset_depr_schedule_doc.append(
"depreciation_schedule",
{
"schedule_date": schedule.schedule_date,
"depreciation_amount": schedule.depreciation_amount,
"accumulated_depreciation_amount": schedule.accumulated_depreciation_amount,
"journal_entry": schedule.journal_entry,
"shift": schedule.shift,
},
)

temp_asset_depr_schedule_doc.prepare_draft_asset_depr_schedule_data(
asset_doc,
row,
Expand All @@ -839,6 +914,7 @@ def get_depr_schedule(asset_name, status, finance_book=None):
return asset_depr_schedule_doc.get("depreciation_schedule")


@frappe.whitelist()
def get_asset_depr_schedule_doc(asset_name, status, finance_book=None):
asset_depr_schedule_name = get_asset_depr_schedule_name(asset_name, status, finance_book)

Expand Down
Loading