Skip to content

Commit

Permalink
feat: shift depreciation for assets (backport #38327) (#38417)
Browse files Browse the repository at this point in the history
feat: shift depreciation for assets (#38327)

* feat: shift depreciation for assets

* chore: create new asset depr schedule on shift change

* refactor: move create_depr_schedule to after_insert

* fix: args in get_depreciation_amount test

* refactor: rename shift adjustment to shift allocation

* chore: asset shift factor doctype and auto allocate shift diff

* chore: use check instead of depr type, and add tests

* chore: make linter happy

* chore: give permissions to accounts users

(cherry picked from commit fe5fc5b)

Co-authored-by: Anand Baburajan <[email protected]>
  • Loading branch information
mergify[bot] and anandbaburajan authored Nov 29, 2023
1 parent 80afeca commit 12ad1ec
Show file tree
Hide file tree
Showing 19 changed files with 776 additions and 39 deletions.
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

0 comments on commit 12ad1ec

Please sign in to comment.