Merge pull request #32226 from AnandBaburajan/fix_asset_scrap_and_sale_dep

fix: calculate depreciation properly on asset sale entry and scrap entry
This commit is contained in:
Nabin Hait 2022-09-28 14:28:52 +05:30 committed by GitHub
commit dde8b2afff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 170 additions and 109 deletions

View File

@ -22,9 +22,12 @@ from erpnext.accounts.general_ledger import get_round_off_account_and_cost_cente
from erpnext.accounts.party import get_due_date, get_party_account, get_party_details
from erpnext.accounts.utils import get_account_currency
from erpnext.assets.doctype.asset.depreciation import (
depreciate_asset,
get_disposal_account_and_cost_center,
get_gl_entries_on_asset_disposal,
get_gl_entries_on_asset_regain,
reset_depreciation_schedule,
reverse_depreciation_entry_made_after_disposal,
)
from erpnext.controllers.accounts_controller import validate_account_head
from erpnext.controllers.selling_controller import SellingController
@ -1086,18 +1089,20 @@ class SalesInvoice(SellingController):
asset.db_set("disposal_date", None)
if asset.calculate_depreciation:
self.reverse_depreciation_entry_made_after_disposal(asset)
self.reset_depreciation_schedule(asset)
posting_date = frappe.db.get_value("Sales Invoice", self.return_against, "posting_date")
reverse_depreciation_entry_made_after_disposal(asset, posting_date)
reset_depreciation_schedule(asset, self.posting_date)
else:
if asset.calculate_depreciation:
depreciate_asset(asset, self.posting_date)
asset.reload()
fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(
asset, item.base_net_amount, item.finance_book, self.get("doctype"), self.get("name")
)
asset.db_set("disposal_date", self.posting_date)
if asset.calculate_depreciation:
self.depreciate_asset(asset)
for gle in fixed_asset_gl_entries:
gle["against"] = self.customer
gl_entries.append(self.get_gl_dict(gle, item=item))

View File

@ -4,11 +4,12 @@
import frappe
from frappe import _
from frappe.utils import cint, flt, getdate, today
from frappe.utils import add_months, cint, flt, getdate, nowdate, today
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_checks_for_pl_and_bs_accounts,
)
from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
def post_depreciation_entries(date=None, commit=True):
@ -196,6 +197,11 @@ def scrap_asset(asset_name):
_("Asset {0} cannot be scrapped, as it is already {1}").format(asset.name, asset.status)
)
date = today()
depreciate_asset(asset, date)
asset.reload()
depreciation_series = frappe.get_cached_value(
"Company", asset.company, "series_for_depreciation_entry"
)
@ -203,7 +209,7 @@ def scrap_asset(asset_name):
je = frappe.new_doc("Journal Entry")
je.voucher_type = "Journal Entry"
je.naming_series = depreciation_series
je.posting_date = today()
je.posting_date = date
je.company = asset.company
je.remark = "Scrap Entry for asset {0}".format(asset_name)
@ -214,7 +220,7 @@ def scrap_asset(asset_name):
je.flags.ignore_permissions = True
je.submit()
frappe.db.set_value("Asset", asset_name, "disposal_date", today())
frappe.db.set_value("Asset", asset_name, "disposal_date", date)
frappe.db.set_value("Asset", asset_name, "journal_entry_for_scrap", je.name)
asset.set_status("Scrapped")
@ -225,6 +231,9 @@ def scrap_asset(asset_name):
def restore_asset(asset_name):
asset = frappe.get_doc("Asset", asset_name)
reverse_depreciation_entry_made_after_disposal(asset, asset.disposal_date)
reset_depreciation_schedule(asset, asset.disposal_date)
je = asset.journal_entry_for_scrap
asset.db_set("disposal_date", None)
@ -235,6 +244,91 @@ def restore_asset(asset_name):
asset.set_status()
def depreciate_asset(asset, date):
asset.flags.ignore_validate_update_after_submit = True
asset.prepare_depreciation_data(date_of_disposal=date)
asset.save()
make_depreciation_entry(asset.name, date)
def reset_depreciation_schedule(asset, date):
asset.flags.ignore_validate_update_after_submit = True
# recreate original depreciation schedule of the asset
asset.prepare_depreciation_data(date_of_return=date)
modify_depreciation_schedule_for_asset_repairs(asset)
asset.save()
def modify_depreciation_schedule_for_asset_repairs(asset):
asset_repairs = frappe.get_all(
"Asset Repair", filters={"asset": asset.name}, fields=["name", "increase_in_asset_life"]
)
for repair in asset_repairs:
if repair.increase_in_asset_life:
asset_repair = frappe.get_doc("Asset Repair", repair.name)
asset_repair.modify_depreciation_schedule()
asset.prepare_depreciation_data()
def reverse_depreciation_entry_made_after_disposal(asset, date):
row = -1
finance_book = asset.get("schedules")[0].get("finance_book")
for schedule in asset.get("schedules"):
if schedule.finance_book != finance_book:
row = 0
finance_book = schedule.finance_book
else:
row += 1
if schedule.schedule_date == date:
if not disposal_was_made_on_original_schedule_date(
asset, schedule, row, date
) or disposal_happens_in_the_future(date):
reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry)
reverse_journal_entry.posting_date = nowdate()
frappe.flags.is_reverse_depr_entry = True
reverse_journal_entry.submit()
frappe.flags.is_reverse_depr_entry = False
asset.flags.ignore_validate_update_after_submit = True
schedule.journal_entry = None
depreciation_amount = get_depreciation_amount_in_je(reverse_journal_entry)
asset.finance_books[0].value_after_depreciation += depreciation_amount
asset.save()
def get_depreciation_amount_in_je(journal_entry):
if journal_entry.accounts[0].debit_in_account_currency:
return journal_entry.accounts[0].debit_in_account_currency
else:
return journal_entry.accounts[0].credit_in_account_currency
# if the invoice had been posted on the date the depreciation was initially supposed to happen, the depreciation shouldn't be undone
def disposal_was_made_on_original_schedule_date(asset, schedule, row, posting_date_of_disposal):
for finance_book in asset.get("finance_books"):
if schedule.finance_book == finance_book.finance_book:
orginal_schedule_date = add_months(
finance_book.depreciation_start_date, row * cint(finance_book.frequency_of_depreciation)
)
if orginal_schedule_date == posting_date_of_disposal:
return True
return False
def disposal_happens_in_the_future(posting_date_of_disposal):
if posting_date_of_disposal > getdate():
return True
return False
def get_gl_entries_on_asset_regain(
asset, selling_amount=0, finance_book=None, voucher_type=None, voucher_no=None
):

View File

@ -4,7 +4,16 @@
import unittest
import frappe
from frappe.utils import add_days, add_months, cstr, flt, get_last_day, getdate, nowdate
from frappe.utils import (
add_days,
add_months,
cstr,
flt,
get_first_day,
get_last_day,
getdate,
nowdate,
)
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.assets.doctype.asset.asset import make_sales_invoice, split_asset
@ -178,28 +187,48 @@ class TestAsset(AssetSetup):
self.assertEqual(doc.items[0].is_fixed_asset, 1)
def test_scrap_asset(self):
date = nowdate()
purchase_date = add_months(get_first_day(date), -2)
asset = create_asset(
calculate_depreciation=1,
available_for_use_date="2020-01-01",
purchase_date="2020-01-01",
available_for_use_date=purchase_date,
purchase_date=purchase_date,
expected_value_after_useful_life=10000,
total_number_of_depreciations=10,
frequency_of_depreciation=1,
submit=1,
)
post_depreciation_entries(date=add_months("2020-01-01", 4))
post_depreciation_entries(date=add_months(purchase_date, 2))
asset.load_from_db()
accumulated_depr_amount = flt(
asset.gross_purchase_amount - asset.finance_books[0].value_after_depreciation,
asset.precision("gross_purchase_amount"),
)
self.assertEquals(accumulated_depr_amount, 18000.0)
scrap_asset(asset.name)
asset.load_from_db()
accumulated_depr_amount = flt(
asset.gross_purchase_amount - asset.finance_books[0].value_after_depreciation,
asset.precision("gross_purchase_amount"),
)
pro_rata_amount, _, _ = asset.get_pro_rata_amt(
asset.finance_books[0], 9000, add_months(get_last_day(purchase_date), 1), date
)
pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount"))
self.assertEquals(accumulated_depr_amount, 18000.00 + pro_rata_amount)
self.assertEqual(asset.status, "Scrapped")
self.assertTrue(asset.journal_entry_for_scrap)
expected_gle = (
("_Test Accumulated Depreciations - _TC", 36000.0, 0.0),
("_Test Accumulated Depreciations - _TC", 18000.0 + pro_rata_amount, 0.0),
("_Test Fixed Asset - _TC", 0.0, 100000.0),
("_Test Gain/Loss on Asset Disposal - _TC", 64000.0, 0.0),
("_Test Gain/Loss on Asset Disposal - _TC", 82000.0 - pro_rata_amount, 0.0),
)
gle = frappe.db.sql(
@ -216,19 +245,27 @@ class TestAsset(AssetSetup):
self.assertFalse(asset.journal_entry_for_scrap)
self.assertEqual(asset.status, "Partially Depreciated")
accumulated_depr_amount = flt(
asset.gross_purchase_amount - asset.finance_books[0].value_after_depreciation,
asset.precision("gross_purchase_amount"),
)
self.assertEquals(accumulated_depr_amount, 18000.0)
def test_gle_made_by_asset_sale(self):
date = nowdate()
purchase_date = add_months(get_first_day(date), -2)
asset = create_asset(
calculate_depreciation=1,
available_for_use_date="2020-06-06",
purchase_date="2020-01-01",
available_for_use_date=purchase_date,
purchase_date=purchase_date,
expected_value_after_useful_life=10000,
total_number_of_depreciations=3,
frequency_of_depreciation=10,
depreciation_start_date="2020-12-31",
total_number_of_depreciations=10,
frequency_of_depreciation=1,
submit=1,
)
post_depreciation_entries(date="2021-01-01")
post_depreciation_entries(date=add_months(purchase_date, 2))
si = make_sales_invoice(asset=asset.name, item_code="Macbook Pro", company="_Test Company")
si.customer = "_Test Customer"
@ -239,10 +276,15 @@ class TestAsset(AssetSetup):
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
pro_rata_amount, _, _ = asset.get_pro_rata_amt(
asset.finance_books[0], 9000, add_months(get_last_day(purchase_date), 1), date
)
pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount"))
expected_gle = (
("_Test Accumulated Depreciations - _TC", 20490.2, 0.0),
("_Test Accumulated Depreciations - _TC", 18000.0 + pro_rata_amount, 0.0),
("_Test Fixed Asset - _TC", 0.0, 100000.0),
("_Test Gain/Loss on Asset Disposal - _TC", 54509.8, 0.0),
("_Test Gain/Loss on Asset Disposal - _TC", 57000.0 - pro_rata_amount, 0.0),
("Debtors - _TC", 25000.0, 0.0),
)

View File

@ -12,8 +12,11 @@ from six import string_types
import erpnext
from erpnext.assets.doctype.asset.depreciation import (
depreciate_asset,
get_gl_entries_on_asset_disposal,
get_value_after_depreciation_on_disposal_date,
reset_depreciation_schedule,
reverse_depreciation_entry_made_after_disposal,
)
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
from erpnext.assets.doctype.asset_value_adjustment.asset_value_adjustment import (
@ -424,7 +427,7 @@ class AssetCapitalization(StockController):
asset = self.get_asset(item)
if asset.calculate_depreciation:
self.depreciate_asset(asset)
depreciate_asset(asset, self.posting_date)
asset.reload()
fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(
@ -520,8 +523,8 @@ class AssetCapitalization(StockController):
self.set_consumed_asset_status(asset)
if asset.calculate_depreciation:
self.reverse_depreciation_entry_made_after_disposal(asset)
self.reset_depreciation_schedule(asset)
reverse_depreciation_entry_made_after_disposal(asset, self.posting_date)
reset_depreciation_schedule(asset, self.posting_date)
def get_asset(self, item):
asset = frappe.get_doc("Asset", item.asset)

View File

@ -38,7 +38,6 @@ from erpnext.accounts.party import (
validate_party_frozen_disabled,
)
from erpnext.accounts.utils import get_account_currency, get_fiscal_years, validate_fiscal_year
from erpnext.assets.doctype.asset.depreciation import make_depreciation_entry
from erpnext.buying.utils import update_last_purchase_rate
from erpnext.controllers.print_settings import (
set_print_templates_for_item_table,
@ -1891,88 +1890,6 @@ class AccountsController(TransactionBase):
_("Select finance book for the item {0} at row {1}").format(item.item_code, item.idx)
)
def depreciate_asset(self, asset):
asset.flags.ignore_validate_update_after_submit = True
asset.prepare_depreciation_data(date_of_disposal=self.posting_date)
asset.save()
make_depreciation_entry(asset.name, self.posting_date)
def reset_depreciation_schedule(self, asset):
asset.flags.ignore_validate_update_after_submit = True
# recreate original depreciation schedule of the asset
asset.prepare_depreciation_data(date_of_return=self.posting_date)
self.modify_depreciation_schedule_for_asset_repairs(asset)
asset.save()
def modify_depreciation_schedule_for_asset_repairs(self, asset):
asset_repairs = frappe.get_all(
"Asset Repair", filters={"asset": asset.name}, fields=["name", "increase_in_asset_life"]
)
for repair in asset_repairs:
if repair.increase_in_asset_life:
asset_repair = frappe.get_doc("Asset Repair", repair.name)
asset_repair.modify_depreciation_schedule()
asset.prepare_depreciation_data()
def reverse_depreciation_entry_made_after_disposal(self, asset):
from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
posting_date_of_original_disposal = self.get_posting_date_of_disposal_entry()
row = -1
finance_book = asset.get("schedules")[0].get("finance_book")
for schedule in asset.get("schedules"):
if schedule.finance_book != finance_book:
row = 0
finance_book = schedule.finance_book
else:
row += 1
if schedule.schedule_date == posting_date_of_original_disposal:
if not self.disposal_was_made_on_original_schedule_date(
asset, schedule, row, posting_date_of_original_disposal
) or self.disposal_happens_in_the_future(posting_date_of_original_disposal):
reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry)
reverse_journal_entry.posting_date = nowdate()
frappe.flags.is_reverse_depr_entry = True
reverse_journal_entry.submit()
frappe.flags.is_reverse_depr_entry = False
asset.flags.ignore_validate_update_after_submit = True
schedule.journal_entry = None
asset.save()
def get_posting_date_of_disposal_entry(self):
if self.doctype == "Sales Invoice" and self.return_against:
return frappe.db.get_value("Sales Invoice", self.return_against, "posting_date")
else:
return self.posting_date
# if the invoice had been posted on the date the depreciation was initially supposed to happen, the depreciation shouldn't be undone
def disposal_was_made_on_original_schedule_date(
self, asset, schedule, row, posting_date_of_disposal
):
for finance_book in asset.get("finance_books"):
if schedule.finance_book == finance_book.finance_book:
orginal_schedule_date = add_months(
finance_book.depreciation_start_date, row * cint(finance_book.frequency_of_depreciation)
)
if orginal_schedule_date == posting_date_of_disposal:
return True
return False
def disposal_happens_in_the_future(self, posting_date_of_disposal):
if posting_date_of_disposal > getdate():
return True
return False
@frappe.whitelist()
def get_tax_rate(account_head):