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

fix: incorrect schedule in asset value adjustment #36725

Merged
merged 7 commits into from
Aug 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 75 additions & 19 deletions erpnext/assets/doctype/asset/asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,18 +81,27 @@ def validate_asset_and_reference(self):
_("Purchase Invoice cannot be made against an existing asset {0}").format(self.name)
)

def prepare_depreciation_data(self, date_of_disposal=None, date_of_return=None):
def prepare_depreciation_data(
self,
date_of_disposal=None,
date_of_return=None,
value_after_depreciation=None,
ignore_booked_entry=False,
):
if self.calculate_depreciation:
self.value_after_depreciation = 0
self.set_depreciation_rate()
if self.should_prepare_depreciation_schedule():
self.make_depreciation_schedule(date_of_disposal)
self.set_accumulated_depreciation(date_of_disposal, date_of_return)
self.make_depreciation_schedule(date_of_disposal, value_after_depreciation)
self.set_accumulated_depreciation(date_of_disposal, date_of_return, ignore_booked_entry)
else:
self.finance_books = []
self.value_after_depreciation = flt(self.gross_purchase_amount) - flt(
self.opening_accumulated_depreciation
)
if value_after_depreciation:
self.value_after_depreciation = value_after_depreciation
else:
self.value_after_depreciation = flt(self.gross_purchase_amount) - flt(
self.opening_accumulated_depreciation
)

def should_prepare_depreciation_schedule(self):
if not self.get("schedules"):
Expand Down Expand Up @@ -285,7 +294,7 @@ def set_depreciation_rate(self):
self.get_depreciation_rate(d, on_validate=True), d.precision("rate_of_depreciation")
)

def make_depreciation_schedule(self, date_of_disposal):
def make_depreciation_schedule(self, date_of_disposal, value_after_depreciation=None):
if not self.get("schedules"):
self.schedules = []

Expand All @@ -295,24 +304,30 @@ def make_depreciation_schedule(self, date_of_disposal):
start = self.clear_depreciation_schedule()

for finance_book in self.get("finance_books"):
self._make_depreciation_schedule(finance_book, start, date_of_disposal)
self._make_depreciation_schedule(
finance_book, start, date_of_disposal, value_after_depreciation
)

if len(self.get("finance_books")) > 1 and any(start):
self.sort_depreciation_schedule()

def _make_depreciation_schedule(self, finance_book, start, date_of_disposal):
def _make_depreciation_schedule(
self, finance_book, start, date_of_disposal, value_after_depreciation=None
):
self.validate_asset_finance_books(finance_book)

value_after_depreciation = self._get_value_after_depreciation_for_making_schedule(finance_book)
if not value_after_depreciation:
value_after_depreciation = self._get_value_after_depreciation_for_making_schedule(finance_book)

finance_book.value_after_depreciation = value_after_depreciation

number_of_pending_depreciations = cint(finance_book.total_number_of_depreciations) - cint(
final_number_of_depreciations = cint(finance_book.total_number_of_depreciations) - cint(
self.number_of_depreciations_booked
)

has_pro_rata = self.check_is_pro_rata(finance_book)
if has_pro_rata:
number_of_pending_depreciations += 1
final_number_of_depreciations += 1

has_wdv_or_dd_non_yearly_pro_rata = False
if (
Expand All @@ -328,7 +343,9 @@ def _make_depreciation_schedule(self, finance_book, start, date_of_disposal):

depreciation_amount = 0

for n in range(start[finance_book.idx - 1], number_of_pending_depreciations):
number_of_pending_depreciations = final_number_of_depreciations - start[finance_book.idx - 1]

for n in range(start[finance_book.idx - 1], final_number_of_depreciations):
# If depreciation is already completed (for double declining balance)
if skip_row:
continue
Expand All @@ -345,10 +362,11 @@ def _make_depreciation_schedule(self, finance_book, start, date_of_disposal):
n,
prev_depreciation_amount,
has_wdv_or_dd_non_yearly_pro_rata,
number_of_pending_depreciations,
)

if not has_pro_rata or (
n < (cint(number_of_pending_depreciations) - 1) or number_of_pending_depreciations == 2
n < (cint(final_number_of_depreciations) - 1) or final_number_of_depreciations == 2
):
schedule_date = add_months(
finance_book.depreciation_start_date, n * cint(finance_book.frequency_of_depreciation)
Expand Down Expand Up @@ -416,7 +434,7 @@ def _make_depreciation_schedule(self, finance_book, start, date_of_disposal):
)

# For last row
elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1:
elif has_pro_rata and n == cint(final_number_of_depreciations) - 1:
if not self.flags.increase_in_asset_life:
# In case of increase_in_asset_life, the self.to_date is already set on asset_repair submission
self.to_date = add_months(
Expand Down Expand Up @@ -447,7 +465,7 @@ def _make_depreciation_schedule(self, finance_book, start, date_of_disposal):
# Adjust depreciation amount in the last period based on the expected value after useful life
if finance_book.expected_value_after_useful_life and (
(
n == cint(number_of_pending_depreciations) - 1
n == cint(final_number_of_depreciations) - 1
and value_after_depreciation != finance_book.expected_value_after_useful_life
)
or value_after_depreciation < finance_book.expected_value_after_useful_life
Expand Down Expand Up @@ -690,7 +708,10 @@ def set_accumulated_depreciation(
if s.finance_book_id == d.finance_book_id
and (s.depreciation_method == "Straight Line" or s.depreciation_method == "Manual")
]
accumulated_depreciation = flt(self.opening_accumulated_depreciation)
if i > 0 and self.flags.decrease_in_asset_value_due_to_value_adjustment:
accumulated_depreciation = self.get("schedules")[i - 1].accumulated_depreciation_amount
else:
accumulated_depreciation = flt(self.opening_accumulated_depreciation)
value_after_depreciation = flt(
self.get("finance_books")[cint(d.finance_book_id) - 1].value_after_depreciation
)
Expand Down Expand Up @@ -1296,11 +1317,14 @@ def get_depreciation_amount(
schedule_idx=0,
prev_depreciation_amount=0,
has_wdv_or_dd_non_yearly_pro_rata=False,
number_of_pending_depreciations=0,
):
frappe.flags.company = asset.company

if fb_row.depreciation_method in ("Straight Line", "Manual"):
return get_straight_line_or_manual_depr_amount(asset, fb_row, schedule_idx)
return get_straight_line_or_manual_depr_amount(
asset, fb_row, schedule_idx, number_of_pending_depreciations
)
else:
rate_of_depreciation = get_updated_rate_of_depreciation_for_wdv_and_dd(
asset, depreciable_value, fb_row
Expand All @@ -1320,7 +1344,9 @@ def get_updated_rate_of_depreciation_for_wdv_and_dd(asset, depreciable_value, fb
return fb_row.rate_of_depreciation


def get_straight_line_or_manual_depr_amount(asset, row, schedule_idx):
def get_straight_line_or_manual_depr_amount(
asset, row, schedule_idx, number_of_pending_depreciations
):
# 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 All @@ -1331,6 +1357,36 @@ def get_straight_line_or_manual_depr_amount(asset, row, schedule_idx):
return (flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)) / flt(
row.total_number_of_depreciations
)
# if the Depreciation Schedule is being modified after Asset Value Adjustment due to decrease in asset value
elif asset.flags.decrease_in_asset_value_due_to_value_adjustment:
if row.daily_depreciation:
daily_depr_amount = (
flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)
) / date_diff(
add_months(
row.depreciation_start_date,
flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked)
* row.frequency_of_depreciation,
),
add_months(
row.depreciation_start_date,
flt(
row.total_number_of_depreciations
- asset.number_of_depreciations_booked
- number_of_pending_depreciations
)
* row.frequency_of_depreciation,
),
)
to_date = add_months(row.depreciation_start_date, schedule_idx * row.frequency_of_depreciation)
from_date = add_months(
row.depreciation_start_date, (schedule_idx - 1) * row.frequency_of_depreciation
)
return daily_depr_amount * date_diff(to_date, from_date)
else:
return (
flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)
) / number_of_pending_depreciations
# if the Depreciation Schedule is being prepared for the first time
else:
if row.daily_depreciation:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,12 @@
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.utils import cint, date_diff, flt, formatdate, getdate
from frappe.utils import flt, formatdate, getdate

from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_checks_for_pl_and_bs_accounts,
)
from erpnext.assets.doctype.asset.asset import (
get_asset_value_after_depreciation,
get_depreciation_amount,
)
from erpnext.assets.doctype.asset.asset import get_asset_value_after_depreciation
from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts


Expand All @@ -25,10 +22,10 @@ def validate(self):

def on_submit(self):
self.make_depreciation_entry()
self.reschedule_depreciations(self.new_asset_value)
self.update_asset(self.new_asset_value)

def on_cancel(self):
self.reschedule_depreciations(self.current_asset_value)
self.update_asset(self.current_asset_value)

def validate_date(self):
asset_purchase_date = frappe.db.get_value("Asset", self.asset, "purchase_date")
Expand Down Expand Up @@ -71,12 +68,16 @@ def make_depreciation_entry(self):
"account": accumulated_depreciation_account,
"credit_in_account_currency": self.difference_amount,
"cost_center": depreciation_cost_center or self.cost_center,
"reference_type": "Asset",
"reference_name": asset.name,
}

debit_entry = {
"account": depreciation_expense_account,
"debit_in_account_currency": self.difference_amount,
"cost_center": depreciation_cost_center or self.cost_center,
"reference_type": "Asset",
"reference_name": asset.name,
}

accounting_dimensions = get_checks_for_pl_and_bs_accounts()
Expand Down Expand Up @@ -106,44 +107,11 @@ def make_depreciation_entry(self):

self.db_set("journal_entry", je.name)

def reschedule_depreciations(self, asset_value):
def update_asset(self, asset_value):
asset = frappe.get_doc("Asset", self.asset)
country = frappe.get_value("Company", self.company, "country")

for d in asset.finance_books:
d.value_after_depreciation = asset_value

if d.depreciation_method in ("Straight Line", "Manual"):
end_date = max(s.schedule_date for s in asset.schedules if cint(s.finance_book_id) == d.idx)
total_days = date_diff(end_date, self.date)
rate_per_day = flt(d.value_after_depreciation - d.expected_value_after_useful_life) / flt(
total_days
)
from_date = self.date
else:
no_of_depreciations = len(
[
s.name for s in asset.schedules if (cint(s.finance_book_id) == d.idx and not s.journal_entry)
]
)
asset.flags.decrease_in_asset_value_due_to_value_adjustment = True

value_after_depreciation = d.value_after_depreciation
for data in asset.schedules:
if cint(data.finance_book_id) == d.idx and not data.journal_entry:
if d.depreciation_method in ("Straight Line", "Manual"):
days = date_diff(data.schedule_date, from_date)
depreciation_amount = days * rate_per_day
from_date = data.schedule_date
else:
depreciation_amount = get_depreciation_amount(asset, value_after_depreciation, d)

if depreciation_amount:
value_after_depreciation -= flt(depreciation_amount)
data.depreciation_amount = depreciation_amount

d.db_update()

asset.set_accumulated_depreciation(ignore_booked_entry=True)
for asset_data in asset.schedules:
if not asset_data.journal_entry:
asset_data.db_update()
asset.prepare_depreciation_data(value_after_depreciation=asset_value, ignore_booked_entry=True)
asset.flags.ignore_validate_update_after_submit = True
asset.save()
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
import unittest

import frappe
from frappe.utils import add_days, get_last_day, nowdate
from frappe.utils import add_days, cstr, get_last_day, getdate, nowdate

from erpnext.assets.doctype.asset.asset import get_asset_value_after_depreciation
from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries
from erpnext.assets.doctype.asset.test_asset import create_asset_data
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt

Expand Down Expand Up @@ -46,40 +47,44 @@ def test_current_asset_value(self):

def test_asset_depreciation_value_adjustment(self):
pr = make_purchase_receipt(
item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location"
item_code="Macbook Pro", qty=1, rate=120000.0, location="Test Location"
)

asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, "name")
asset_doc = frappe.get_doc("Asset", asset_name)
asset_doc.calculate_depreciation = 1

month_end_date = get_last_day(nowdate())
purchase_date = nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15)

asset_doc.available_for_use_date = purchase_date
asset_doc.purchase_date = purchase_date
asset_doc.available_for_use_date = "2023-01-15"
asset_doc.purchase_date = "2023-01-15"
asset_doc.calculate_depreciation = 1
asset_doc.append(
"finance_books",
{
"expected_value_after_useful_life": 200,
"depreciation_method": "Straight Line",
"total_number_of_depreciations": 3,
"frequency_of_depreciation": 10,
"depreciation_start_date": month_end_date,
"total_number_of_depreciations": 12,
"frequency_of_depreciation": 1,
"depreciation_start_date": "2023-01-31",
},
)
asset_doc.submit()

post_depreciation_entries(getdate("2023-08-21"))

current_value = get_asset_value_after_depreciation(asset_doc.name)
adj_doc = make_asset_value_adjustment(
asset=asset_doc.name, current_asset_value=current_value, new_asset_value=50000.0
asset=asset_doc.name,
current_asset_value=current_value,
new_asset_value=50000.0,
date="2023-08-21",
)
adj_doc.submit()

asset_doc.reload()

expected_gle = (
("_Test Accumulated Depreciations - _TC", 0.0, 50000.0),
("_Test Depreciations - _TC", 50000.0, 0.0),
("_Test Accumulated Depreciations - _TC", 0.0, 4625.29),
("_Test Depreciations - _TC", 4625.29, 0.0),
)

gle = frappe.db.sql(
Expand All @@ -91,6 +96,29 @@ def test_asset_depreciation_value_adjustment(self):

self.assertSequenceEqual(gle, expected_gle)

expected_schedules = [
["2023-01-31", 5474.73, 5474.73],
["2023-02-28", 9983.33, 15458.06],
["2023-03-31", 9983.33, 25441.39],
["2023-04-30", 9983.33, 35424.72],
["2023-05-31", 9983.33, 45408.05],
["2023-06-30", 9983.33, 55391.38],
["2023-07-31", 9983.33, 65374.71],
["2023-08-31", 8300.0, 73674.71],
["2023-09-30", 8300.0, 81974.71],
["2023-10-31", 8300.0, 90274.71],
["2023-11-30", 8300.0, 98574.71],
["2023-12-31", 8300.0, 106874.71],
["2024-01-15", 8300.0, 115174.71],
]

schedules = [
[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
for d in asset_doc.get("schedules")
]

self.assertEqual(schedules, expected_schedules)


def make_asset_value_adjustment(**args):
args = frappe._dict(args)
Expand Down