fix: incorrect schedule in asset value adjustment (#36725)

* fix: incorrect schedule in asset value adjustment

* chore: remove unnecessary commented code

* test: check schedules in test

* test: improving the test

* chore: better function name

* chore: use None instead of 0 for default value after depr

* chore: typo
This commit is contained in:
Anand Baburajan
2023-08-22 01:40:42 +05:30
committed by GitHub
parent fe78076cde
commit a0575ed2b0
3 changed files with 129 additions and 77 deletions

View File

@@ -81,15 +81,24 @@ class Asset(AccountsController):
_("Purchase Invoice cannot be made against an existing asset {0}").format(self.name) _("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: if self.calculate_depreciation:
self.value_after_depreciation = 0 self.value_after_depreciation = 0
self.set_depreciation_rate() self.set_depreciation_rate()
if self.should_prepare_depreciation_schedule(): if self.should_prepare_depreciation_schedule():
self.make_depreciation_schedule(date_of_disposal) self.make_depreciation_schedule(date_of_disposal, value_after_depreciation)
self.set_accumulated_depreciation(date_of_disposal, date_of_return) self.set_accumulated_depreciation(date_of_disposal, date_of_return, ignore_booked_entry)
else: else:
self.finance_books = [] self.finance_books = []
if value_after_depreciation:
self.value_after_depreciation = value_after_depreciation
else:
self.value_after_depreciation = flt(self.gross_purchase_amount) - flt( self.value_after_depreciation = flt(self.gross_purchase_amount) - flt(
self.opening_accumulated_depreciation self.opening_accumulated_depreciation
) )
@@ -285,7 +294,7 @@ class Asset(AccountsController):
self.get_depreciation_rate(d, on_validate=True), d.precision("rate_of_depreciation") 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"): if not self.get("schedules"):
self.schedules = [] self.schedules = []
@@ -295,24 +304,30 @@ class Asset(AccountsController):
start = self.clear_depreciation_schedule() start = self.clear_depreciation_schedule()
for finance_book in self.get("finance_books"): 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): if len(self.get("finance_books")) > 1 and any(start):
self.sort_depreciation_schedule() 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) self.validate_asset_finance_books(finance_book)
if not value_after_depreciation:
value_after_depreciation = self._get_value_after_depreciation_for_making_schedule(finance_book) value_after_depreciation = self._get_value_after_depreciation_for_making_schedule(finance_book)
finance_book.value_after_depreciation = value_after_depreciation 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 self.number_of_depreciations_booked
) )
has_pro_rata = self.check_is_pro_rata(finance_book) has_pro_rata = self.check_is_pro_rata(finance_book)
if has_pro_rata: if has_pro_rata:
number_of_pending_depreciations += 1 final_number_of_depreciations += 1
has_wdv_or_dd_non_yearly_pro_rata = False has_wdv_or_dd_non_yearly_pro_rata = False
if ( if (
@@ -328,7 +343,9 @@ class Asset(AccountsController):
depreciation_amount = 0 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 depreciation is already completed (for double declining balance)
if skip_row: if skip_row:
continue continue
@@ -345,10 +362,11 @@ class Asset(AccountsController):
n, n,
prev_depreciation_amount, prev_depreciation_amount,
has_wdv_or_dd_non_yearly_pro_rata, has_wdv_or_dd_non_yearly_pro_rata,
number_of_pending_depreciations,
) )
if not has_pro_rata or ( 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( schedule_date = add_months(
finance_book.depreciation_start_date, n * cint(finance_book.frequency_of_depreciation) finance_book.depreciation_start_date, n * cint(finance_book.frequency_of_depreciation)
@@ -416,7 +434,7 @@ class Asset(AccountsController):
) )
# For last row # 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: 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 # In case of increase_in_asset_life, the self.to_date is already set on asset_repair submission
self.to_date = add_months( self.to_date = add_months(
@@ -447,7 +465,7 @@ class Asset(AccountsController):
# Adjust depreciation amount in the last period based on the expected value after useful life # Adjust depreciation amount in the last period based on the expected value after useful life
if finance_book.expected_value_after_useful_life and ( 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 and value_after_depreciation != finance_book.expected_value_after_useful_life
) )
or value_after_depreciation < finance_book.expected_value_after_useful_life or value_after_depreciation < finance_book.expected_value_after_useful_life
@@ -690,6 +708,9 @@ class Asset(AccountsController):
if s.finance_book_id == d.finance_book_id if s.finance_book_id == d.finance_book_id
and (s.depreciation_method == "Straight Line" or s.depreciation_method == "Manual") and (s.depreciation_method == "Straight Line" or s.depreciation_method == "Manual")
] ]
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) accumulated_depreciation = flt(self.opening_accumulated_depreciation)
value_after_depreciation = flt( value_after_depreciation = flt(
self.get("finance_books")[cint(d.finance_book_id) - 1].value_after_depreciation self.get("finance_books")[cint(d.finance_book_id) - 1].value_after_depreciation
@@ -1296,11 +1317,14 @@ def get_depreciation_amount(
schedule_idx=0, schedule_idx=0,
prev_depreciation_amount=0, prev_depreciation_amount=0,
has_wdv_or_dd_non_yearly_pro_rata=False, has_wdv_or_dd_non_yearly_pro_rata=False,
number_of_pending_depreciations=0,
): ):
frappe.flags.company = asset.company frappe.flags.company = asset.company
if fb_row.depreciation_method in ("Straight Line", "Manual"): 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: else:
rate_of_depreciation = get_updated_rate_of_depreciation_for_wdv_and_dd( rate_of_depreciation = get_updated_rate_of_depreciation_for_wdv_and_dd(
asset, depreciable_value, fb_row asset, depreciable_value, fb_row
@@ -1320,7 +1344,9 @@ def get_updated_rate_of_depreciation_for_wdv_and_dd(asset, depreciable_value, fb
return fb_row.rate_of_depreciation 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 the Depreciation Schedule is being modified after Asset Repair due to increase in asset life and value
if asset.flags.increase_in_asset_life: if asset.flags.increase_in_asset_life:
return (flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)) / ( return (flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)) / (
@@ -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( return (flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)) / flt(
row.total_number_of_depreciations 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 # if the Depreciation Schedule is being prepared for the first time
else: else:
if row.daily_depreciation: if row.daily_depreciation:

View File

@@ -5,15 +5,12 @@
import frappe import frappe
from frappe import _ from frappe import _
from frappe.model.document import Document 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 ( from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_checks_for_pl_and_bs_accounts, get_checks_for_pl_and_bs_accounts,
) )
from erpnext.assets.doctype.asset.asset import ( from erpnext.assets.doctype.asset.asset import get_asset_value_after_depreciation
get_asset_value_after_depreciation,
get_depreciation_amount,
)
from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts
@@ -25,10 +22,10 @@ class AssetValueAdjustment(Document):
def on_submit(self): def on_submit(self):
self.make_depreciation_entry() self.make_depreciation_entry()
self.reschedule_depreciations(self.new_asset_value) self.update_asset(self.new_asset_value)
def on_cancel(self): def on_cancel(self):
self.reschedule_depreciations(self.current_asset_value) self.update_asset(self.current_asset_value)
def validate_date(self): def validate_date(self):
asset_purchase_date = frappe.db.get_value("Asset", self.asset, "purchase_date") asset_purchase_date = frappe.db.get_value("Asset", self.asset, "purchase_date")
@@ -71,12 +68,16 @@ class AssetValueAdjustment(Document):
"account": accumulated_depreciation_account, "account": accumulated_depreciation_account,
"credit_in_account_currency": self.difference_amount, "credit_in_account_currency": self.difference_amount,
"cost_center": depreciation_cost_center or self.cost_center, "cost_center": depreciation_cost_center or self.cost_center,
"reference_type": "Asset",
"reference_name": asset.name,
} }
debit_entry = { debit_entry = {
"account": depreciation_expense_account, "account": depreciation_expense_account,
"debit_in_account_currency": self.difference_amount, "debit_in_account_currency": self.difference_amount,
"cost_center": depreciation_cost_center or self.cost_center, "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() accounting_dimensions = get_checks_for_pl_and_bs_accounts()
@@ -106,44 +107,11 @@ class AssetValueAdjustment(Document):
self.db_set("journal_entry", je.name) 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) asset = frappe.get_doc("Asset", self.asset)
country = frappe.get_value("Company", self.company, "country")
for d in asset.finance_books: asset.flags.decrease_in_asset_value_due_to_value_adjustment = True
d.value_after_depreciation = asset_value
if d.depreciation_method in ("Straight Line", "Manual"): asset.prepare_depreciation_data(value_after_depreciation=asset_value, ignore_booked_entry=True)
end_date = max(s.schedule_date for s in asset.schedules if cint(s.finance_book_id) == d.idx) asset.flags.ignore_validate_update_after_submit = True
total_days = date_diff(end_date, self.date) asset.save()
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)
]
)
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()

View File

@@ -4,9 +4,10 @@
import unittest import unittest
import frappe 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.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.assets.doctype.asset.test_asset import create_asset_data
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
@@ -46,40 +47,44 @@ class TestAssetValueAdjustment(unittest.TestCase):
def test_asset_depreciation_value_adjustment(self): def test_asset_depreciation_value_adjustment(self):
pr = make_purchase_receipt( 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_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, "name")
asset_doc = frappe.get_doc("Asset", asset_name) asset_doc = frappe.get_doc("Asset", asset_name)
asset_doc.calculate_depreciation = 1 asset_doc.calculate_depreciation = 1
month_end_date = get_last_day(nowdate()) asset_doc.available_for_use_date = "2023-01-15"
purchase_date = nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15) asset_doc.purchase_date = "2023-01-15"
asset_doc.available_for_use_date = purchase_date
asset_doc.purchase_date = purchase_date
asset_doc.calculate_depreciation = 1 asset_doc.calculate_depreciation = 1
asset_doc.append( asset_doc.append(
"finance_books", "finance_books",
{ {
"expected_value_after_useful_life": 200, "expected_value_after_useful_life": 200,
"depreciation_method": "Straight Line", "depreciation_method": "Straight Line",
"total_number_of_depreciations": 3, "total_number_of_depreciations": 12,
"frequency_of_depreciation": 10, "frequency_of_depreciation": 1,
"depreciation_start_date": month_end_date, "depreciation_start_date": "2023-01-31",
}, },
) )
asset_doc.submit() asset_doc.submit()
post_depreciation_entries(getdate("2023-08-21"))
current_value = get_asset_value_after_depreciation(asset_doc.name) current_value = get_asset_value_after_depreciation(asset_doc.name)
adj_doc = make_asset_value_adjustment( 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() adj_doc.submit()
asset_doc.reload()
expected_gle = ( expected_gle = (
("_Test Accumulated Depreciations - _TC", 0.0, 50000.0), ("_Test Accumulated Depreciations - _TC", 0.0, 4625.29),
("_Test Depreciations - _TC", 50000.0, 0.0), ("_Test Depreciations - _TC", 4625.29, 0.0),
) )
gle = frappe.db.sql( gle = frappe.db.sql(
@@ -91,6 +96,29 @@ class TestAssetValueAdjustment(unittest.TestCase):
self.assertSequenceEqual(gle, expected_gle) 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): def make_asset_value_adjustment(**args):
args = frappe._dict(args) args = frappe._dict(args)