Merge pull request #32933 from AnandBaburajan/asset_depreciation_schedule

feat: separating depreciation schedule from assets into a new doc
This commit is contained in:
Deepesh Garg 2023-01-09 20:47:21 +05:30 committed by GitHub
commit 57a6c37833
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 1484 additions and 890 deletions

View File

@ -6,7 +6,7 @@ import json
import frappe import frappe
from frappe import _, msgprint, scrub from frappe import _, msgprint, scrub
from frappe.utils import cint, cstr, flt, fmt_money, formatdate, get_link_to_form, nowdate from frappe.utils import cstr, flt, fmt_money, formatdate, get_link_to_form, nowdate
import erpnext import erpnext
from erpnext.accounts.deferred_revenue import get_deferred_booking_accounts from erpnext.accounts.deferred_revenue import get_deferred_booking_accounts
@ -23,6 +23,9 @@ from erpnext.accounts.utils import (
get_stock_accounts, get_stock_accounts,
get_stock_and_account_balance, get_stock_and_account_balance,
) )
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
get_depr_schedule,
)
from erpnext.controllers.accounts_controller import AccountsController from erpnext.controllers.accounts_controller import AccountsController
@ -283,16 +286,17 @@ class JournalEntry(AccountsController):
for d in self.get("accounts"): for d in self.get("accounts"):
if d.reference_type == "Asset" and d.reference_name: if d.reference_type == "Asset" and d.reference_name:
asset = frappe.get_doc("Asset", d.reference_name) asset = frappe.get_doc("Asset", d.reference_name)
for s in asset.get("schedules"): for row in asset.get("finance_books"):
if s.journal_entry == self.name: depr_schedule = get_depr_schedule(asset.name, "Active", row.finance_book)
s.db_set("journal_entry", None)
idx = cint(s.finance_book_id) or 1 for s in depr_schedule or []:
finance_books = asset.get("finance_books")[idx - 1] if s.journal_entry == self.name:
finance_books.value_after_depreciation += s.depreciation_amount s.db_set("journal_entry", None)
finance_books.db_update()
asset.set_status() row.value_after_depreciation += s.depreciation_amount
row.db_update()
asset.set_status()
def unlink_inter_company_jv(self): def unlink_inter_company_jv(self):
if ( if (

View File

@ -1185,11 +1185,24 @@ class SalesInvoice(SellingController):
if asset.calculate_depreciation: if asset.calculate_depreciation:
posting_date = frappe.db.get_value("Sales Invoice", self.return_against, "posting_date") posting_date = frappe.db.get_value("Sales Invoice", self.return_against, "posting_date")
reverse_depreciation_entry_made_after_disposal(asset, posting_date) reverse_depreciation_entry_made_after_disposal(asset, posting_date)
reset_depreciation_schedule(asset, self.posting_date) notes = _(
"This schedule was created when Asset {0} was returned after being sold through Sales Invoice {1}."
).format(
get_link_to_form(asset.doctype, asset.name),
get_link_to_form(self.doctype, self.get("name")),
)
reset_depreciation_schedule(asset, self.posting_date, notes)
asset.reload()
else: else:
if asset.calculate_depreciation: if asset.calculate_depreciation:
depreciate_asset(asset, self.posting_date) notes = _(
"This schedule was created when Asset {0} was sold through Sales Invoice {1}."
).format(
get_link_to_form(asset.doctype, asset.name),
get_link_to_form(self.doctype, self.get("name")),
)
depreciate_asset(asset, self.posting_date, notes)
asset.reload() asset.reload()
fixed_asset_gl_entries = get_gl_entries_on_asset_disposal( fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(

View File

@ -21,6 +21,9 @@ from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_comp
from erpnext.accounts.utils import PaymentEntryUnlinkError from erpnext.accounts.utils import PaymentEntryUnlinkError
from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries
from erpnext.assets.doctype.asset.test_asset import create_asset, create_asset_data from erpnext.assets.doctype.asset.test_asset import create_asset, create_asset_data
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
get_depr_schedule,
)
from erpnext.controllers.accounts_controller import update_invoice_status from erpnext.controllers.accounts_controller import update_invoice_status
from erpnext.controllers.taxes_and_totals import get_itemised_tax_breakup_data from erpnext.controllers.taxes_and_totals import get_itemised_tax_breakup_data
from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency
@ -2774,7 +2777,7 @@ class TestSalesInvoice(unittest.TestCase):
["2021-09-30", 5041.1, 26407.22], ["2021-09-30", 5041.1, 26407.22],
] ]
for i, schedule in enumerate(asset.schedules): for i, schedule in enumerate(get_depr_schedule(asset.name, "Active")):
self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date) self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date)
self.assertEqual(expected_values[i][1], schedule.depreciation_amount) self.assertEqual(expected_values[i][1], schedule.depreciation_amount)
self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount) self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount)
@ -2805,7 +2808,7 @@ class TestSalesInvoice(unittest.TestCase):
expected_values = [["2020-12-31", 30000, 30000], ["2021-12-31", 30000, 60000]] expected_values = [["2020-12-31", 30000, 30000], ["2021-12-31", 30000, 60000]]
for i, schedule in enumerate(asset.schedules): for i, schedule in enumerate(get_depr_schedule(asset.name, "Active")):
self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date) self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date)
self.assertEqual(expected_values[i][1], schedule.depreciation_amount) self.assertEqual(expected_values[i][1], schedule.depreciation_amount)
self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount) self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount)
@ -2834,7 +2837,7 @@ class TestSalesInvoice(unittest.TestCase):
["2025-06-06", 18633.88, 100000.0, False], ["2025-06-06", 18633.88, 100000.0, False],
] ]
for i, schedule in enumerate(asset.schedules): for i, schedule in enumerate(get_depr_schedule(asset.name, "Active")):
self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date) self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date)
self.assertEqual(expected_values[i][1], schedule.depreciation_amount) self.assertEqual(expected_values[i][1], schedule.depreciation_amount)
self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount) self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount)

View File

@ -131,8 +131,8 @@ def get_assets(filters):
else else
0 0
end), 0) as depreciation_amount_during_the_period end), 0) as depreciation_amount_during_the_period
from `tabAsset` a, `tabDepreciation Schedule` ds from `tabAsset` a, `tabAsset Depreciation Schedule` ads, `tabDepreciation Schedule` ds
where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s and a.name = ds.parent and ifnull(ds.journal_entry, '') != '' where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s and ads.asset = a.name and ads.docstatus=1 and ads.name = ds.parent and ifnull(ds.journal_entry, '') != ''
group by a.asset_category group by a.asset_category
union union
SELECT a.asset_category, SELECT a.asset_category,

View File

@ -76,7 +76,6 @@ frappe.ui.form.on('Asset', {
refresh: function(frm) { refresh: function(frm) {
frappe.ui.form.trigger("Asset", "is_existing_asset"); frappe.ui.form.trigger("Asset", "is_existing_asset");
frm.toggle_display("next_depreciation_date", frm.doc.docstatus < 1); frm.toggle_display("next_depreciation_date", frm.doc.docstatus < 1);
frm.events.make_schedules_editable(frm);
if (frm.doc.docstatus==1) { if (frm.doc.docstatus==1) {
if (in_list(["Submitted", "Partially Depreciated", "Fully Depreciated"], frm.doc.status)) { if (in_list(["Submitted", "Partially Depreciated", "Fully Depreciated"], frm.doc.status)) {
@ -188,7 +187,11 @@ frappe.ui.form.on('Asset', {
}) })
}, },
setup_chart: function(frm) { setup_chart: async function(frm) {
if(frm.doc.finance_books.length > 1) {
return
}
var x_intervals = [frm.doc.purchase_date]; var x_intervals = [frm.doc.purchase_date];
var asset_values = [frm.doc.gross_purchase_amount]; var asset_values = [frm.doc.gross_purchase_amount];
var last_depreciation_date = frm.doc.purchase_date; var last_depreciation_date = frm.doc.purchase_date;
@ -202,7 +205,20 @@ frappe.ui.form.on('Asset', {
flt(frm.doc.opening_accumulated_depreciation)); flt(frm.doc.opening_accumulated_depreciation));
} }
$.each(frm.doc.schedules || [], function(i, v) { let depr_schedule = [];
if (frm.doc.finance_books.length == 1) {
depr_schedule = (await frappe.call(
"erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule.get_depr_schedule",
{
asset_name: frm.doc.name,
status: frm.doc.docstatus ? "Active" : "Draft",
finance_book: frm.doc.finance_books[0].finance_book || null
}
)).message;
}
$.each(depr_schedule || [], function(i, v) {
x_intervals.push(v.schedule_date); x_intervals.push(v.schedule_date);
var asset_value = flt(frm.doc.gross_purchase_amount) - flt(v.accumulated_depreciation_amount); var asset_value = flt(frm.doc.gross_purchase_amount) - flt(v.accumulated_depreciation_amount);
if(v.journal_entry) { if(v.journal_entry) {
@ -266,21 +282,6 @@ frappe.ui.form.on('Asset', {
// frm.toggle_reqd("next_depreciation_date", (!frm.doc.is_existing_asset && frm.doc.calculate_depreciation)); // frm.toggle_reqd("next_depreciation_date", (!frm.doc.is_existing_asset && frm.doc.calculate_depreciation));
}, },
opening_accumulated_depreciation: function(frm) {
erpnext.asset.set_accumulated_depreciation(frm);
},
make_schedules_editable: function(frm) {
if (frm.doc.finance_books) {
var is_editable = frm.doc.finance_books.filter(d => d.depreciation_method == "Manual").length > 0
? true : false;
frm.toggle_enable("schedules", is_editable);
frm.fields_dict["schedules"].grid.toggle_enable("schedule_date", is_editable);
frm.fields_dict["schedules"].grid.toggle_enable("depreciation_amount", is_editable);
}
},
make_sales_invoice: function(frm) { make_sales_invoice: function(frm) {
frappe.call({ frappe.call({
args: { args: {
@ -476,7 +477,6 @@ frappe.ui.form.on('Asset Finance Book', {
depreciation_method: function(frm, cdt, cdn) { depreciation_method: function(frm, cdt, cdn) {
const row = locals[cdt][cdn]; const row = locals[cdt][cdn];
frm.events.set_depreciation_rate(frm, row); frm.events.set_depreciation_rate(frm, row);
frm.events.make_schedules_editable(frm);
}, },
expected_value_after_useful_life: function(frm, cdt, cdn) { expected_value_after_useful_life: function(frm, cdt, cdn) {
@ -512,41 +512,6 @@ frappe.ui.form.on('Asset Finance Book', {
} }
}); });
frappe.ui.form.on('Depreciation Schedule', {
make_depreciation_entry: function(frm, cdt, cdn) {
var row = locals[cdt][cdn];
if (!row.journal_entry) {
frappe.call({
method: "erpnext.assets.doctype.asset.depreciation.make_depreciation_entry",
args: {
"asset_name": frm.doc.name,
"date": row.schedule_date
},
callback: function(r) {
frappe.model.sync(r.message);
frm.refresh();
}
})
}
},
depreciation_amount: function(frm, cdt, cdn) {
erpnext.asset.set_accumulated_depreciation(frm);
}
})
erpnext.asset.set_accumulated_depreciation = function(frm) {
if(frm.doc.depreciation_method != "Manual") return;
var accumulated_depreciation = flt(frm.doc.opening_accumulated_depreciation);
$.each(frm.doc.schedules || [], function(i, row) {
accumulated_depreciation += flt(row.depreciation_amount);
frappe.model.set_value(row.doctype, row.name,
"accumulated_depreciation_amount", accumulated_depreciation);
})
};
erpnext.asset.scrap_asset = function(frm) { erpnext.asset.scrap_asset = function(frm) {
frappe.confirm(__("Do you really want to scrap this asset?"), function () { frappe.confirm(__("Do you really want to scrap this asset?"), function () {
frappe.call({ frappe.call({

View File

@ -52,8 +52,6 @@
"column_break_24", "column_break_24",
"frequency_of_depreciation", "frequency_of_depreciation",
"next_depreciation_date", "next_depreciation_date",
"section_break_14",
"schedules",
"insurance_details", "insurance_details",
"policy_number", "policy_number",
"insurer", "insurer",
@ -307,19 +305,6 @@
"label": "Next Depreciation Date", "label": "Next Depreciation Date",
"no_copy": 1 "no_copy": 1
}, },
{
"depends_on": "calculate_depreciation",
"fieldname": "section_break_14",
"fieldtype": "Section Break",
"label": "Depreciation Schedule"
},
{
"fieldname": "schedules",
"fieldtype": "Table",
"label": "Depreciation Schedule",
"no_copy": 1,
"options": "Depreciation Schedule"
},
{ {
"collapsible": 1, "collapsible": 1,
"fieldname": "insurance_details", "fieldname": "insurance_details",
@ -508,9 +493,14 @@
"group": "Value", "group": "Value",
"link_doctype": "Asset Value Adjustment", "link_doctype": "Asset Value Adjustment",
"link_fieldname": "asset" "link_fieldname": "asset"
},
{
"group": "Depreciation",
"link_doctype": "Asset Depreciation Schedule",
"link_fieldname": "asset"
} }
], ],
"modified": "2022-07-20 10:15:12.887372", "modified": "2022-11-25 12:47:19.689702",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Assets", "module": "Assets",
"name": "Asset", "name": "Asset",

View File

@ -8,14 +8,15 @@ import math
import frappe import frappe
from frappe import _ from frappe import _
from frappe.utils import ( from frappe.utils import (
add_days,
add_months, add_months,
cint, cint,
date_diff, date_diff,
flt, flt,
get_datetime, get_datetime,
get_last_day, get_last_day,
get_link_to_form,
getdate, getdate,
is_last_day_of_the_month,
month_diff, month_diff,
nowdate, nowdate,
today, today,
@ -28,6 +29,16 @@ from erpnext.assets.doctype.asset.depreciation import (
get_disposal_account_and_cost_center, get_disposal_account_and_cost_center,
) )
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
cancel_asset_depr_schedules,
convert_draft_asset_depr_schedules_into_active,
get_asset_depr_schedule_doc,
get_depr_schedule,
make_draft_asset_depr_schedules,
make_draft_asset_depr_schedules_if_not_present,
set_draft_asset_depr_schedule_details,
update_draft_asset_depr_schedules,
)
from erpnext.controllers.accounts_controller import AccountsController from erpnext.controllers.accounts_controller import AccountsController
@ -40,9 +51,9 @@ class Asset(AccountsController):
self.set_missing_values() self.set_missing_values()
if not self.split_from: if not self.split_from:
self.prepare_depreciation_data() self.prepare_depreciation_data()
update_draft_asset_depr_schedules(self)
self.validate_gross_and_purchase_amount() self.validate_gross_and_purchase_amount()
if self.get("schedules"): self.validate_expected_value_after_useful_life()
self.validate_expected_value_after_useful_life()
self.status = self.get_status() self.status = self.get_status()
@ -52,16 +63,24 @@ class Asset(AccountsController):
self.make_asset_movement() self.make_asset_movement()
if not self.booked_fixed_asset and self.validate_make_gl_entry(): if not self.booked_fixed_asset and self.validate_make_gl_entry():
self.make_gl_entries() self.make_gl_entries()
if not self.split_from:
make_draft_asset_depr_schedules_if_not_present(self)
convert_draft_asset_depr_schedules_into_active(self)
def on_cancel(self): def on_cancel(self):
self.validate_cancellation() self.validate_cancellation()
self.cancel_movement_entries() self.cancel_movement_entries()
self.delete_depreciation_entries() self.delete_depreciation_entries()
cancel_asset_depr_schedules(self)
self.set_status() self.set_status()
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry") self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
make_reverse_gl_entries(voucher_type="Asset", voucher_no=self.name) make_reverse_gl_entries(voucher_type="Asset", voucher_no=self.name)
self.db_set("booked_fixed_asset", 0) self.db_set("booked_fixed_asset", 0)
def after_insert(self):
if not self.split_from:
make_draft_asset_depr_schedules(self)
def validate_asset_and_reference(self): def validate_asset_and_reference(self):
if self.purchase_invoice or self.purchase_receipt: if self.purchase_invoice or self.purchase_receipt:
reference_doc = "Purchase Invoice" if self.purchase_invoice else "Purchase Receipt" reference_doc = "Purchase Invoice" if self.purchase_invoice else "Purchase Receipt"
@ -79,12 +98,10 @@ 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):
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()
self.make_depreciation_schedule(date_of_disposal)
self.set_accumulated_depreciation(date_of_disposal, date_of_return)
else: else:
self.finance_books = [] self.finance_books = []
self.value_after_depreciation = flt(self.gross_purchase_amount) - flt( self.value_after_depreciation = flt(self.gross_purchase_amount) - flt(
@ -223,148 +240,6 @@ 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):
if "Manual" not in [d.depreciation_method for d in self.finance_books] and not self.get(
"schedules"
):
self.schedules = []
if not self.available_for_use_date:
return
start = self.clear_depreciation_schedule()
for finance_book in self.get("finance_books"):
self._make_depreciation_schedule(finance_book, start, date_of_disposal)
def _make_depreciation_schedule(self, finance_book, start, date_of_disposal):
self.validate_asset_finance_books(finance_book)
value_after_depreciation = self._get_value_after_depreciation(finance_book)
finance_book.value_after_depreciation = value_after_depreciation
number_of_pending_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
skip_row = False
should_get_last_day = is_last_day_of_the_month(finance_book.depreciation_start_date)
for n in range(start[finance_book.idx - 1], number_of_pending_depreciations):
# If depreciation is already completed (for double declining balance)
if skip_row:
continue
depreciation_amount = get_depreciation_amount(self, value_after_depreciation, finance_book)
if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1:
schedule_date = add_months(
finance_book.depreciation_start_date, n * cint(finance_book.frequency_of_depreciation)
)
if should_get_last_day:
schedule_date = get_last_day(schedule_date)
# schedule date will be a year later from start date
# so monthly schedule date is calculated by removing 11 months from it
monthly_schedule_date = add_months(schedule_date, -finance_book.frequency_of_depreciation + 1)
# if asset is being sold
if date_of_disposal:
from_date = self.get_from_date(finance_book.finance_book)
depreciation_amount, days, months = self.get_pro_rata_amt(
finance_book, depreciation_amount, from_date, date_of_disposal
)
if depreciation_amount > 0:
self._add_depreciation_row(
date_of_disposal,
depreciation_amount,
finance_book.depreciation_method,
finance_book.finance_book,
finance_book.idx,
)
break
# For first row
if has_pro_rata and not self.opening_accumulated_depreciation and n == 0:
from_date = add_days(
self.available_for_use_date, -1
) # needed to calc depr amount for available_for_use_date too
depreciation_amount, days, months = self.get_pro_rata_amt(
finance_book, depreciation_amount, from_date, finance_book.depreciation_start_date
)
# For first depr schedule date will be the start date
# so monthly schedule date is calculated by removing month difference between use date and start date
monthly_schedule_date = add_months(finance_book.depreciation_start_date, -months + 1)
# For last row
elif has_pro_rata and n == cint(number_of_pending_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(
self.available_for_use_date,
(n + self.number_of_depreciations_booked) * cint(finance_book.frequency_of_depreciation),
)
depreciation_amount_without_pro_rata = depreciation_amount
depreciation_amount, days, months = self.get_pro_rata_amt(
finance_book, depreciation_amount, schedule_date, self.to_date
)
depreciation_amount = self.get_adjusted_depreciation_amount(
depreciation_amount_without_pro_rata, depreciation_amount, finance_book.finance_book
)
monthly_schedule_date = add_months(schedule_date, 1)
schedule_date = add_days(schedule_date, days)
last_schedule_date = schedule_date
if not depreciation_amount:
continue
value_after_depreciation -= flt(depreciation_amount, self.precision("gross_purchase_amount"))
# 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
and value_after_depreciation != finance_book.expected_value_after_useful_life
)
or value_after_depreciation < finance_book.expected_value_after_useful_life
):
depreciation_amount += value_after_depreciation - finance_book.expected_value_after_useful_life
skip_row = True
if depreciation_amount > 0:
self._add_depreciation_row(
schedule_date,
depreciation_amount,
finance_book.depreciation_method,
finance_book.finance_book,
finance_book.idx,
)
def _add_depreciation_row(
self, schedule_date, depreciation_amount, depreciation_method, finance_book, finance_book_id
):
self.append(
"schedules",
{
"schedule_date": schedule_date,
"depreciation_amount": depreciation_amount,
"depreciation_method": depreciation_method,
"finance_book": finance_book,
"finance_book_id": finance_book_id,
},
)
def _get_value_after_depreciation(self, finance_book): def _get_value_after_depreciation(self, finance_book):
# value_after_depreciation - current Asset value # value_after_depreciation - current Asset value
if self.docstatus == 1 and finance_book.value_after_depreciation: if self.docstatus == 1 and finance_book.value_after_depreciation:
@ -376,58 +251,6 @@ class Asset(AccountsController):
return value_after_depreciation return value_after_depreciation
# depreciation schedules need to be cleared before modification due to increase in asset life/asset sales
# JE: Journal Entry, FB: Finance Book
def clear_depreciation_schedule(self):
start = []
num_of_depreciations_completed = 0
depr_schedule = []
for schedule in self.get("schedules"):
# to update start when there are JEs linked with all the schedule rows corresponding to an FB
if len(start) == (int(schedule.finance_book_id) - 2):
start.append(num_of_depreciations_completed)
num_of_depreciations_completed = 0
# to ensure that start will only be updated once for each FB
if len(start) == (int(schedule.finance_book_id) - 1):
if schedule.journal_entry:
num_of_depreciations_completed += 1
depr_schedule.append(schedule)
else:
start.append(num_of_depreciations_completed)
num_of_depreciations_completed = 0
# to update start when all the schedule rows corresponding to the last FB are linked with JEs
if len(start) == (len(self.finance_books) - 1):
start.append(num_of_depreciations_completed)
# when the Depreciation Schedule is being created for the first time
if start == []:
start = [0] * len(self.finance_books)
else:
self.schedules = depr_schedule
return start
def get_from_date(self, finance_book):
if not self.get("schedules"):
return self.available_for_use_date
if len(self.finance_books) == 1:
return self.schedules[-1].schedule_date
from_date = ""
for schedule in self.get("schedules"):
if schedule.finance_book == finance_book:
from_date = schedule.schedule_date
if from_date:
return from_date
# since depr for available_for_use_date is not yet booked
return add_days(self.available_for_use_date, -1)
# if it returns True, depreciation_amount will not be equal for the first and last rows # if it returns True, depreciation_amount will not be equal for the first and last rows
def check_is_pro_rata(self, row): def check_is_pro_rata(self, row):
has_pro_rata = False has_pro_rata = False
@ -512,83 +335,15 @@ class Asset(AccountsController):
).format(row.idx) ).format(row.idx)
) )
# to ensure that final accumulated depreciation amount is accurate
def get_adjusted_depreciation_amount(
self, depreciation_amount_without_pro_rata, depreciation_amount_for_last_row, finance_book
):
if not self.opening_accumulated_depreciation:
depreciation_amount_for_first_row = self.get_depreciation_amount_for_first_row(finance_book)
if (
depreciation_amount_for_first_row + depreciation_amount_for_last_row
!= depreciation_amount_without_pro_rata
):
depreciation_amount_for_last_row = (
depreciation_amount_without_pro_rata - depreciation_amount_for_first_row
)
return depreciation_amount_for_last_row
def get_depreciation_amount_for_first_row(self, finance_book):
if self.has_only_one_finance_book():
return self.schedules[0].depreciation_amount
else:
for schedule in self.schedules:
if schedule.finance_book == finance_book:
return schedule.depreciation_amount
def has_only_one_finance_book(self):
if len(self.finance_books) == 1:
return True
def set_accumulated_depreciation(
self, date_of_sale=None, date_of_return=None, ignore_booked_entry=False
):
straight_line_idx = [
d.idx for d in self.get("schedules") if d.depreciation_method == "Straight Line"
]
finance_books = []
for i, d in enumerate(self.get("schedules")):
if ignore_booked_entry and d.journal_entry:
continue
if int(d.finance_book_id) not in finance_books:
accumulated_depreciation = flt(self.opening_accumulated_depreciation)
value_after_depreciation = flt(self.get_value_after_depreciation(d.finance_book_id))
finance_books.append(int(d.finance_book_id))
depreciation_amount = flt(d.depreciation_amount, d.precision("depreciation_amount"))
value_after_depreciation -= flt(depreciation_amount)
# for the last row, if depreciation method = Straight Line
if (
straight_line_idx
and i == max(straight_line_idx) - 1
and not date_of_sale
and not date_of_return
):
book = self.get("finance_books")[cint(d.finance_book_id) - 1]
depreciation_amount += flt(
value_after_depreciation - flt(book.expected_value_after_useful_life),
d.precision("depreciation_amount"),
)
d.depreciation_amount = depreciation_amount
accumulated_depreciation += d.depreciation_amount
d.accumulated_depreciation_amount = flt(
accumulated_depreciation, d.precision("accumulated_depreciation_amount")
)
def get_value_after_depreciation(self, idx):
return flt(self.get("finance_books")[cint(idx) - 1].value_after_depreciation)
def validate_expected_value_after_useful_life(self): def validate_expected_value_after_useful_life(self):
for row in self.get("finance_books"): for row in self.get("finance_books"):
depr_schedule = get_depr_schedule(self.name, "Draft", row.finance_book)
if not depr_schedule:
continue
accumulated_depreciation_after_full_schedule = [ accumulated_depreciation_after_full_schedule = [
d.accumulated_depreciation_amount d.accumulated_depreciation_amount for d in depr_schedule
for d in self.get("schedules")
if cint(d.finance_book_id) == row.idx
] ]
if accumulated_depreciation_after_full_schedule: if accumulated_depreciation_after_full_schedule:
@ -637,10 +392,13 @@ class Asset(AccountsController):
movement.cancel() movement.cancel()
def delete_depreciation_entries(self): def delete_depreciation_entries(self):
for d in self.get("schedules"): for row in self.get("finance_books"):
if d.journal_entry: depr_schedule = get_depr_schedule(self.name, "Active", row.finance_book)
frappe.get_doc("Journal Entry", d.journal_entry).cancel()
d.db_set("journal_entry", None) for d in depr_schedule or []:
if d.journal_entry:
frappe.get_doc("Journal Entry", d.journal_entry).cancel()
d.db_set("journal_entry", None)
self.db_set( self.db_set(
"value_after_depreciation", "value_after_depreciation",
@ -1072,32 +830,6 @@ def get_total_days(date, frequency):
return date_diff(date, period_start_date) return date_diff(date, period_start_date)
def is_last_day_of_the_month(date):
last_day_of_the_month = get_last_day(date)
return getdate(last_day_of_the_month) == getdate(date)
@erpnext.allow_regional
def get_depreciation_amount(asset, depreciable_value, row):
if row.depreciation_method in ("Straight Line", "Manual"):
# if the Depreciation Schedule is being prepared for the first time
if not asset.flags.increase_in_asset_life:
depreciation_amount = (
flt(asset.gross_purchase_amount) - flt(row.expected_value_after_useful_life)
) / flt(row.total_number_of_depreciations)
# if the Depreciation Schedule is being modified after Asset Repair
else:
depreciation_amount = (
flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)
) / (date_diff(asset.to_date, asset.available_for_use_date) / 365)
else:
depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100))
return depreciation_amount
@frappe.whitelist() @frappe.whitelist()
def split_asset(asset_name, split_qty): def split_asset(asset_name, split_qty):
asset = frappe.get_doc("Asset", asset_name) asset = frappe.get_doc("Asset", asset_name)
@ -1109,12 +841,12 @@ def split_asset(asset_name, split_qty):
remaining_qty = asset.asset_quantity - split_qty remaining_qty = asset.asset_quantity - split_qty
new_asset = create_new_asset_after_split(asset, split_qty) new_asset = create_new_asset_after_split(asset, split_qty)
update_existing_asset(asset, remaining_qty) update_existing_asset(asset, remaining_qty, new_asset.name)
return new_asset return new_asset
def update_existing_asset(asset, remaining_qty): def update_existing_asset(asset, remaining_qty, new_asset_name):
remaining_gross_purchase_amount = flt( remaining_gross_purchase_amount = flt(
(asset.gross_purchase_amount * remaining_qty) / asset.asset_quantity (asset.gross_purchase_amount * remaining_qty) / asset.asset_quantity
) )
@ -1132,34 +864,49 @@ def update_existing_asset(asset, remaining_qty):
}, },
) )
for finance_book in asset.get("finance_books"): for row in asset.get("finance_books"):
value_after_depreciation = flt( value_after_depreciation = flt(
(finance_book.value_after_depreciation * remaining_qty) / asset.asset_quantity (row.value_after_depreciation * remaining_qty) / asset.asset_quantity
) )
expected_value_after_useful_life = flt( expected_value_after_useful_life = flt(
(finance_book.expected_value_after_useful_life * remaining_qty) / asset.asset_quantity (row.expected_value_after_useful_life * remaining_qty) / asset.asset_quantity
) )
frappe.db.set_value( frappe.db.set_value(
"Asset Finance Book", finance_book.name, "value_after_depreciation", value_after_depreciation "Asset Finance Book", row.name, "value_after_depreciation", value_after_depreciation
) )
frappe.db.set_value( frappe.db.set_value(
"Asset Finance Book", "Asset Finance Book",
finance_book.name, row.name,
"expected_value_after_useful_life", "expected_value_after_useful_life",
expected_value_after_useful_life, expected_value_after_useful_life,
) )
accumulated_depreciation = 0 current_asset_depr_schedule_doc = get_asset_depr_schedule_doc(
asset.name, "Active", row.finance_book
)
new_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc)
for term in asset.get("schedules"): set_draft_asset_depr_schedule_details(new_asset_depr_schedule_doc, asset, row)
depreciation_amount = flt((term.depreciation_amount * remaining_qty) / asset.asset_quantity)
frappe.db.set_value( accumulated_depreciation = 0
"Depreciation Schedule", term.name, "depreciation_amount", depreciation_amount
) for term in new_asset_depr_schedule_doc.get("depreciation_schedule"):
accumulated_depreciation += depreciation_amount depreciation_amount = flt((term.depreciation_amount * remaining_qty) / asset.asset_quantity)
frappe.db.set_value( term.depreciation_amount = depreciation_amount
"Depreciation Schedule", term.name, "accumulated_depreciation_amount", accumulated_depreciation accumulated_depreciation += depreciation_amount
term.accumulated_depreciation_amount = accumulated_depreciation
notes = _(
"This schedule was created when Asset {0} was updated after being split into new Asset {1}."
).format(
get_link_to_form(asset.doctype, asset.name), get_link_to_form(asset.doctype, new_asset_name)
) )
new_asset_depr_schedule_doc.notes = notes
current_asset_depr_schedule_doc.flags.should_not_cancel_depreciation_entries = True
current_asset_depr_schedule_doc.cancel()
new_asset_depr_schedule_doc.submit()
def create_new_asset_after_split(asset, split_qty): def create_new_asset_after_split(asset, split_qty):
@ -1173,31 +920,49 @@ def create_new_asset_after_split(asset, split_qty):
new_asset.opening_accumulated_depreciation = opening_accumulated_depreciation new_asset.opening_accumulated_depreciation = opening_accumulated_depreciation
new_asset.asset_quantity = split_qty new_asset.asset_quantity = split_qty
new_asset.split_from = asset.name new_asset.split_from = asset.name
accumulated_depreciation = 0
for finance_book in new_asset.get("finance_books"): for row in new_asset.get("finance_books"):
finance_book.value_after_depreciation = flt( row.value_after_depreciation = flt(
(finance_book.value_after_depreciation * split_qty) / asset.asset_quantity (row.value_after_depreciation * split_qty) / asset.asset_quantity
) )
finance_book.expected_value_after_useful_life = flt( row.expected_value_after_useful_life = flt(
(finance_book.expected_value_after_useful_life * split_qty) / asset.asset_quantity (row.expected_value_after_useful_life * split_qty) / asset.asset_quantity
) )
for term in new_asset.get("schedules"):
depreciation_amount = flt((term.depreciation_amount * split_qty) / asset.asset_quantity)
term.depreciation_amount = depreciation_amount
accumulated_depreciation += depreciation_amount
term.accumulated_depreciation_amount = accumulated_depreciation
new_asset.submit() new_asset.submit()
new_asset.set_status() new_asset.set_status()
for term in new_asset.get("schedules"): for row in new_asset.get("finance_books"):
# Update references in JV current_asset_depr_schedule_doc = get_asset_depr_schedule_doc(
if term.journal_entry: asset.name, "Active", row.finance_book
add_reference_in_jv_on_split( )
term.journal_entry, new_asset.name, asset.name, term.depreciation_amount new_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc)
)
set_draft_asset_depr_schedule_details(new_asset_depr_schedule_doc, new_asset, row)
accumulated_depreciation = 0
for term in new_asset_depr_schedule_doc.get("depreciation_schedule"):
depreciation_amount = flt((term.depreciation_amount * split_qty) / asset.asset_quantity)
term.depreciation_amount = depreciation_amount
accumulated_depreciation += depreciation_amount
term.accumulated_depreciation_amount = accumulated_depreciation
notes = _("This schedule was created when new Asset {0} was split from Asset {1}.").format(
get_link_to_form(new_asset.doctype, new_asset.name), get_link_to_form(asset.doctype, asset.name)
)
new_asset_depr_schedule_doc.notes = notes
new_asset_depr_schedule_doc.submit()
for row in new_asset.get("finance_books"):
depr_schedule = get_depr_schedule(new_asset.name, "Active", row.finance_book)
for term in depr_schedule:
# Update references in JV
if term.journal_entry:
add_reference_in_jv_on_split(
term.journal_entry, new_asset.name, asset.name, term.depreciation_amount
)
return new_asset return new_asset

View File

@ -4,12 +4,18 @@
import frappe import frappe
from frappe import _ from frappe import _
from frappe.utils import add_months, cint, flt, getdate, nowdate, today from frappe.utils import add_months, cint, flt, get_link_to_form, getdate, nowdate, today
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.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
get_asset_depr_schedule_doc,
get_asset_depr_schedule_name,
get_temp_asset_depr_schedule_doc,
make_new_active_asset_depr_schedules_and_cancel_current_ones,
)
def post_depreciation_entries(date=None, commit=True): def post_depreciation_entries(date=None, commit=True):
@ -21,8 +27,11 @@ def post_depreciation_entries(date=None, commit=True):
if not date: if not date:
date = today() date = today()
for asset in get_depreciable_assets(date): for asset_name in get_depreciable_assets(date):
make_depreciation_entry(asset, date) asset_doc = frappe.get_doc("Asset", asset_name)
make_depreciation_entry_for_all_asset_depr_schedules(asset_doc, date)
if commit: if commit:
frappe.db.commit() frappe.db.commit()
@ -30,21 +39,35 @@ def post_depreciation_entries(date=None, commit=True):
def get_depreciable_assets(date): def get_depreciable_assets(date):
return frappe.db.sql_list( return frappe.db.sql_list(
"""select distinct a.name """select distinct a.name
from tabAsset a, `tabDepreciation Schedule` ds from tabAsset a, `tabAsset Depreciation Schedule` ads, `tabDepreciation Schedule` ds
where a.name = ds.parent and a.docstatus=1 and ds.schedule_date<=%s and a.calculate_depreciation = 1 where a.name = ads.asset and ads.name = ds.parent and a.docstatus=1 and ads.docstatus=1
and a.status in ('Submitted', 'Partially Depreciated') and a.status in ('Submitted', 'Partially Depreciated')
and a.calculate_depreciation = 1
and ds.schedule_date<=%s
and ifnull(ds.journal_entry, '')=''""", and ifnull(ds.journal_entry, '')=''""",
date, date,
) )
def make_depreciation_entry_for_all_asset_depr_schedules(asset_doc, date=None):
for row in asset_doc.get("finance_books"):
asset_depr_schedule_name = get_asset_depr_schedule_name(
asset_doc.name, "Active", row.finance_book
)
make_depreciation_entry(asset_depr_schedule_name, date)
@frappe.whitelist() @frappe.whitelist()
def make_depreciation_entry(asset_name, date=None): def make_depreciation_entry(asset_depr_schedule_name, date=None):
frappe.has_permission("Journal Entry", throw=True) frappe.has_permission("Journal Entry", throw=True)
if not date: if not date:
date = today() date = today()
asset_depr_schedule_doc = frappe.get_doc("Asset Depreciation Schedule", asset_depr_schedule_name)
asset_name = asset_depr_schedule_doc.asset
asset = frappe.get_doc("Asset", asset_name) asset = frappe.get_doc("Asset", asset_name)
( (
fixed_asset_account, fixed_asset_account,
@ -60,14 +83,14 @@ def make_depreciation_entry(asset_name, date=None):
accounting_dimensions = get_checks_for_pl_and_bs_accounts() accounting_dimensions = get_checks_for_pl_and_bs_accounts()
for d in asset.get("schedules"): for d in asset_depr_schedule_doc.get("depreciation_schedule"):
if not d.journal_entry and getdate(d.schedule_date) <= getdate(date): if not d.journal_entry and getdate(d.schedule_date) <= getdate(date):
je = frappe.new_doc("Journal Entry") je = frappe.new_doc("Journal Entry")
je.voucher_type = "Depreciation Entry" je.voucher_type = "Depreciation Entry"
je.naming_series = depreciation_series je.naming_series = depreciation_series
je.posting_date = d.schedule_date je.posting_date = d.schedule_date
je.company = asset.company je.company = asset.company
je.finance_book = d.finance_book je.finance_book = asset_depr_schedule_doc.finance_book
je.remark = "Depreciation Entry against {0} worth {1}".format(asset_name, d.depreciation_amount) je.remark = "Depreciation Entry against {0} worth {1}".format(asset_name, d.depreciation_amount)
credit_account, debit_account = get_credit_and_debit_accounts( credit_account, debit_account = get_credit_and_debit_accounts(
@ -118,14 +141,14 @@ def make_depreciation_entry(asset_name, date=None):
d.db_set("journal_entry", je.name) d.db_set("journal_entry", je.name)
idx = cint(d.finance_book_id) idx = cint(asset_depr_schedule_doc.finance_book_id)
finance_books = asset.get("finance_books")[idx - 1] row = asset.get("finance_books")[idx - 1]
finance_books.value_after_depreciation -= d.depreciation_amount row.value_after_depreciation -= d.depreciation_amount
finance_books.db_update() row.db_update()
asset.set_status() asset.set_status()
return asset return asset_depr_schedule_doc
def get_depreciation_accounts(asset): def get_depreciation_accounts(asset):
@ -199,7 +222,11 @@ def scrap_asset(asset_name):
date = today() date = today()
depreciate_asset(asset, date) notes = _("This schedule was created when Asset {0} was scrapped.").format(
get_link_to_form(asset.doctype, asset.name)
)
depreciate_asset(asset, date, notes)
asset.reload() asset.reload()
depreciation_series = frappe.get_cached_value( depreciation_series = frappe.get_cached_value(
@ -232,10 +259,15 @@ def restore_asset(asset_name):
asset = frappe.get_doc("Asset", asset_name) asset = frappe.get_doc("Asset", asset_name)
reverse_depreciation_entry_made_after_disposal(asset, asset.disposal_date) reverse_depreciation_entry_made_after_disposal(asset, asset.disposal_date)
reset_depreciation_schedule(asset, asset.disposal_date)
je = asset.journal_entry_for_scrap je = asset.journal_entry_for_scrap
notes = _("This schedule was created when Asset {0} was restored.").format(
get_link_to_form(asset.doctype, asset.name)
)
reset_depreciation_schedule(asset, asset.disposal_date, notes)
asset.db_set("disposal_date", None) asset.db_set("disposal_date", None)
asset.db_set("journal_entry_for_scrap", None) asset.db_set("journal_entry_for_scrap", None)
@ -244,22 +276,28 @@ def restore_asset(asset_name):
asset.set_status() asset.set_status()
def depreciate_asset(asset, date): def depreciate_asset(asset_doc, date, notes):
asset.flags.ignore_validate_update_after_submit = True asset_doc.flags.ignore_validate_update_after_submit = True
asset.prepare_depreciation_data(date_of_disposal=date)
asset.save()
make_depreciation_entry(asset.name, date) make_new_active_asset_depr_schedules_and_cancel_current_ones(
asset_doc, notes, date_of_disposal=date
)
asset_doc.save()
make_depreciation_entry_for_all_asset_depr_schedules(asset_doc, date)
def reset_depreciation_schedule(asset, date): def reset_depreciation_schedule(asset_doc, date, notes):
asset.flags.ignore_validate_update_after_submit = True asset_doc.flags.ignore_validate_update_after_submit = True
# recreate original depreciation schedule of the asset make_new_active_asset_depr_schedules_and_cancel_current_ones(
asset.prepare_depreciation_data(date_of_return=date) asset_doc, notes, date_of_return=date
)
modify_depreciation_schedule_for_asset_repairs(asset) modify_depreciation_schedule_for_asset_repairs(asset_doc)
asset.save()
asset_doc.save()
def modify_depreciation_schedule_for_asset_repairs(asset): def modify_depreciation_schedule_for_asset_repairs(asset):
@ -271,35 +309,36 @@ def modify_depreciation_schedule_for_asset_repairs(asset):
if repair.increase_in_asset_life: if repair.increase_in_asset_life:
asset_repair = frappe.get_doc("Asset Repair", repair.name) asset_repair = frappe.get_doc("Asset Repair", repair.name)
asset_repair.modify_depreciation_schedule() asset_repair.modify_depreciation_schedule()
asset.prepare_depreciation_data() notes = _("This schedule was created when Asset {0} went through Asset Repair {1}.").format(
get_link_to_form(asset.doctype, asset.name),
get_link_to_form(asset_repair.doctype, asset_repair.name),
)
make_new_active_asset_depr_schedules_and_cancel_current_ones(asset, notes)
def reverse_depreciation_entry_made_after_disposal(asset, date): def reverse_depreciation_entry_made_after_disposal(asset, date):
row = -1 for row in asset.get("finance_books"):
finance_book = asset.get("schedules")[0].get("finance_book") asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active", row.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: for schedule_idx, schedule in enumerate(asset_depr_schedule_doc.get("depreciation_schedule")):
if not disposal_was_made_on_original_schedule_date( if schedule.schedule_date == date:
asset, schedule, row, date if not disposal_was_made_on_original_schedule_date(
) or disposal_happens_in_the_future(date): schedule_idx, row, date
) or disposal_happens_in_the_future(date):
reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry) reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry)
reverse_journal_entry.posting_date = nowdate() reverse_journal_entry.posting_date = nowdate()
frappe.flags.is_reverse_depr_entry = True frappe.flags.is_reverse_depr_entry = True
reverse_journal_entry.submit() reverse_journal_entry.submit()
frappe.flags.is_reverse_depr_entry = False frappe.flags.is_reverse_depr_entry = False
asset.flags.ignore_validate_update_after_submit = True asset_depr_schedule_doc.flags.ignore_validate_update_after_submit = True
schedule.journal_entry = None asset.flags.ignore_validate_update_after_submit = True
depreciation_amount = get_depreciation_amount_in_je(reverse_journal_entry) schedule.journal_entry = None
asset.finance_books[0].value_after_depreciation += depreciation_amount depreciation_amount = get_depreciation_amount_in_je(reverse_journal_entry)
asset.save() row.value_after_depreciation += depreciation_amount
asset_depr_schedule_doc.save()
asset.save()
def get_depreciation_amount_in_je(journal_entry): def get_depreciation_amount_in_je(journal_entry):
@ -310,15 +349,14 @@ def get_depreciation_amount_in_je(journal_entry):
# if the invoice had been posted on the date the depreciation was initially supposed to happen, the depreciation shouldn't be undone # 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): def disposal_was_made_on_original_schedule_date(schedule_idx, row, posting_date_of_disposal):
for finance_book in asset.get("finance_books"): orginal_schedule_date = add_months(
if schedule.finance_book == finance_book.finance_book: row.depreciation_start_date, schedule_idx * cint(row.frequency_of_depreciation)
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
if orginal_schedule_date == posting_date_of_disposal:
return True
return False return False
@ -499,24 +537,27 @@ def get_disposal_account_and_cost_center(company):
def get_value_after_depreciation_on_disposal_date(asset, disposal_date, finance_book=None): def get_value_after_depreciation_on_disposal_date(asset, disposal_date, finance_book=None):
asset_doc = frappe.get_doc("Asset", asset) asset_doc = frappe.get_doc("Asset", asset)
if asset_doc.calculate_depreciation: if not asset_doc.calculate_depreciation:
asset_doc.prepare_depreciation_data(getdate(disposal_date))
finance_book_id = 1
if finance_book:
for fb in asset_doc.finance_books:
if fb.finance_book == finance_book:
finance_book_id = fb.idx
break
asset_schedules = [
sch for sch in asset_doc.schedules if cint(sch.finance_book_id) == finance_book_id
]
accumulated_depr_amount = asset_schedules[-1].accumulated_depreciation_amount
return flt(
flt(asset_doc.gross_purchase_amount) - accumulated_depr_amount,
asset_doc.precision("gross_purchase_amount"),
)
else:
return flt(asset_doc.value_after_depreciation) return flt(asset_doc.value_after_depreciation)
idx = 1
if finance_book:
for d in asset.finance_books:
if d.finance_book == finance_book:
idx = d.idx
break
row = asset_doc.finance_books[idx - 1]
temp_asset_depreciation_schedule = get_temp_asset_depr_schedule_doc(
asset_doc, row, getdate(disposal_date)
)
accumulated_depr_amount = temp_asset_depreciation_schedule.get("depreciation_schedule")[
-1
].accumulated_depreciation_amount
return flt(
flt(asset_doc.gross_purchase_amount) - accumulated_depr_amount,
asset_doc.precision("gross_purchase_amount"),
)

View File

@ -27,6 +27,11 @@ from erpnext.assets.doctype.asset.depreciation import (
restore_asset, restore_asset,
scrap_asset, scrap_asset,
) )
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
clear_depr_schedule,
get_asset_depr_schedule_doc,
get_depr_schedule,
)
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
make_purchase_invoice as make_invoice, make_purchase_invoice as make_invoice,
) )
@ -205,6 +210,9 @@ class TestAsset(AssetSetup):
submit=1, submit=1,
) )
first_asset_depr_schedule = get_asset_depr_schedule_doc(asset.name, "Active")
self.assertEquals(first_asset_depr_schedule.status, "Active")
post_depreciation_entries(date=add_months(purchase_date, 2)) post_depreciation_entries(date=add_months(purchase_date, 2))
asset.load_from_db() asset.load_from_db()
@ -216,6 +224,11 @@ class TestAsset(AssetSetup):
scrap_asset(asset.name) scrap_asset(asset.name)
asset.load_from_db() asset.load_from_db()
first_asset_depr_schedule.load_from_db()
second_asset_depr_schedule = get_asset_depr_schedule_doc(asset.name, "Active")
self.assertEquals(second_asset_depr_schedule.status, "Active")
self.assertEquals(first_asset_depr_schedule.status, "Cancelled")
accumulated_depr_amount = flt( accumulated_depr_amount = flt(
asset.gross_purchase_amount - asset.finance_books[0].value_after_depreciation, asset.gross_purchase_amount - asset.finance_books[0].value_after_depreciation,
@ -256,6 +269,11 @@ class TestAsset(AssetSetup):
self.assertSequenceEqual(gle, expected_gle) self.assertSequenceEqual(gle, expected_gle)
restore_asset(asset.name) restore_asset(asset.name)
second_asset_depr_schedule.load_from_db()
third_asset_depr_schedule = get_asset_depr_schedule_doc(asset.name, "Active")
self.assertEquals(third_asset_depr_schedule.status, "Active")
self.assertEquals(second_asset_depr_schedule.status, "Cancelled")
asset.load_from_db() asset.load_from_db()
self.assertFalse(asset.journal_entry_for_scrap) self.assertFalse(asset.journal_entry_for_scrap)
@ -283,6 +301,9 @@ class TestAsset(AssetSetup):
submit=1, submit=1,
) )
first_asset_depr_schedule = get_asset_depr_schedule_doc(asset.name, "Active")
self.assertEquals(first_asset_depr_schedule.status, "Active")
post_depreciation_entries(date=add_months(purchase_date, 2)) post_depreciation_entries(date=add_months(purchase_date, 2))
si = make_sales_invoice(asset=asset.name, item_code="Macbook Pro", company="_Test Company") si = make_sales_invoice(asset=asset.name, item_code="Macbook Pro", company="_Test Company")
@ -294,6 +315,12 @@ class TestAsset(AssetSetup):
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold") self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
first_asset_depr_schedule.load_from_db()
second_asset_depr_schedule = get_asset_depr_schedule_doc(asset.name, "Active")
self.assertEquals(second_asset_depr_schedule.status, "Active")
self.assertEquals(first_asset_depr_schedule.status, "Cancelled")
pro_rata_amount, _, _ = asset.get_pro_rata_amt( pro_rata_amount, _, _ = asset.get_pro_rata_amt(
asset.finance_books[0], 9000, get_last_day(add_months(purchase_date, 1)), date asset.finance_books[0], 9000, get_last_day(add_months(purchase_date, 1)), date
) )
@ -370,6 +397,9 @@ class TestAsset(AssetSetup):
submit=1, submit=1,
) )
first_asset_depr_schedule = get_asset_depr_schedule_doc(asset.name, "Active")
self.assertEquals(first_asset_depr_schedule.status, "Active")
post_depreciation_entries(date="2021-01-01") post_depreciation_entries(date="2021-01-01")
self.assertEqual(asset.asset_quantity, 10) self.assertEqual(asset.asset_quantity, 10)
@ -378,21 +408,31 @@ class TestAsset(AssetSetup):
new_asset = split_asset(asset.name, 2) new_asset = split_asset(asset.name, 2)
asset.load_from_db() asset.load_from_db()
first_asset_depr_schedule.load_from_db()
second_asset_depr_schedule = get_asset_depr_schedule_doc(asset.name, "Active")
first_asset_depr_schedule_of_new_asset = get_asset_depr_schedule_doc(new_asset.name, "Active")
self.assertEquals(second_asset_depr_schedule.status, "Active")
self.assertEquals(first_asset_depr_schedule_of_new_asset.status, "Active")
self.assertEquals(first_asset_depr_schedule.status, "Cancelled")
depr_schedule_of_asset = second_asset_depr_schedule.get("depreciation_schedule")
depr_schedule_of_new_asset = first_asset_depr_schedule_of_new_asset.get("depreciation_schedule")
self.assertEqual(new_asset.asset_quantity, 2) self.assertEqual(new_asset.asset_quantity, 2)
self.assertEqual(new_asset.gross_purchase_amount, 24000) self.assertEqual(new_asset.gross_purchase_amount, 24000)
self.assertEqual(new_asset.opening_accumulated_depreciation, 4000) self.assertEqual(new_asset.opening_accumulated_depreciation, 4000)
self.assertEqual(new_asset.split_from, asset.name) self.assertEqual(new_asset.split_from, asset.name)
self.assertEqual(new_asset.schedules[0].depreciation_amount, 4000) self.assertEqual(depr_schedule_of_new_asset[0].depreciation_amount, 4000)
self.assertEqual(new_asset.schedules[1].depreciation_amount, 4000) self.assertEqual(depr_schedule_of_new_asset[1].depreciation_amount, 4000)
self.assertEqual(asset.asset_quantity, 8) self.assertEqual(asset.asset_quantity, 8)
self.assertEqual(asset.gross_purchase_amount, 96000) self.assertEqual(asset.gross_purchase_amount, 96000)
self.assertEqual(asset.opening_accumulated_depreciation, 16000) self.assertEqual(asset.opening_accumulated_depreciation, 16000)
self.assertEqual(asset.schedules[0].depreciation_amount, 16000) self.assertEqual(depr_schedule_of_asset[0].depreciation_amount, 16000)
self.assertEqual(asset.schedules[1].depreciation_amount, 16000) self.assertEqual(depr_schedule_of_asset[1].depreciation_amount, 16000)
journal_entry = asset.schedules[0].journal_entry journal_entry = depr_schedule_of_asset[0].journal_entry
jv = frappe.get_doc("Journal Entry", journal_entry) jv = frappe.get_doc("Journal Entry", journal_entry)
self.assertEqual(jv.accounts[0].credit_in_account_currency, 16000) self.assertEqual(jv.accounts[0].credit_in_account_currency, 16000)
@ -629,7 +669,7 @@ class TestDepreciationMethods(AssetSetup):
schedules = [ schedules = [
[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
for d in asset.get("schedules") for d in get_depr_schedule(asset.name, "Draft")
] ]
self.assertEqual(schedules, expected_schedules) self.assertEqual(schedules, expected_schedules)
@ -651,7 +691,7 @@ class TestDepreciationMethods(AssetSetup):
expected_schedules = [["2032-12-31", 30000.0, 77095.89], ["2033-06-06", 12904.11, 90000.0]] expected_schedules = [["2032-12-31", 30000.0, 77095.89], ["2033-06-06", 12904.11, 90000.0]]
schedules = [ schedules = [
[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount] [cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount]
for d in asset.get("schedules") for d in get_depr_schedule(asset.name, "Draft")
] ]
self.assertEqual(schedules, expected_schedules) self.assertEqual(schedules, expected_schedules)
@ -678,7 +718,7 @@ class TestDepreciationMethods(AssetSetup):
schedules = [ schedules = [
[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
for d in asset.get("schedules") for d in get_depr_schedule(asset.name, "Draft")
] ]
self.assertEqual(schedules, expected_schedules) self.assertEqual(schedules, expected_schedules)
@ -703,7 +743,7 @@ class TestDepreciationMethods(AssetSetup):
schedules = [ schedules = [
[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
for d in asset.get("schedules") for d in get_depr_schedule(asset.name, "Draft")
] ]
self.assertEqual(schedules, expected_schedules) self.assertEqual(schedules, expected_schedules)
@ -733,7 +773,7 @@ class TestDepreciationMethods(AssetSetup):
flt(d.depreciation_amount, 2), flt(d.depreciation_amount, 2),
flt(d.accumulated_depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2),
] ]
for d in asset.get("schedules") for d in get_depr_schedule(asset.name, "Draft")
] ]
self.assertEqual(schedules, expected_schedules) self.assertEqual(schedules, expected_schedules)
@ -765,7 +805,7 @@ class TestDepreciationMethods(AssetSetup):
flt(d.depreciation_amount, 2), flt(d.depreciation_amount, 2),
flt(d.accumulated_depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2),
] ]
for d in asset.get("schedules") for d in get_depr_schedule(asset.name, "Draft")
] ]
self.assertEqual(schedules, expected_schedules) self.assertEqual(schedules, expected_schedules)
@ -798,7 +838,7 @@ class TestDepreciationMethods(AssetSetup):
flt(d.depreciation_amount, 2), flt(d.depreciation_amount, 2),
flt(d.accumulated_depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2),
] ]
for d in asset.get("schedules") for d in get_depr_schedule(asset.name, "Draft")
] ]
self.assertEqual(schedules, expected_schedules) self.assertEqual(schedules, expected_schedules)
@ -831,7 +871,7 @@ class TestDepreciationMethods(AssetSetup):
flt(d.depreciation_amount, 2), flt(d.depreciation_amount, 2),
flt(d.accumulated_depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2),
] ]
for d in asset.get("schedules") for d in get_depr_schedule(asset.name, "Draft")
] ]
self.assertEqual(schedules, expected_schedules) self.assertEqual(schedules, expected_schedules)
@ -854,7 +894,7 @@ class TestDepreciationBasics(AssetSetup):
["2022-12-31", 30000, 90000], ["2022-12-31", 30000, 90000],
] ]
for i, schedule in enumerate(asset.schedules): for i, schedule in enumerate(get_depr_schedule(asset.name, "Active")):
self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date) self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date)
self.assertEqual(expected_values[i][1], schedule.depreciation_amount) self.assertEqual(expected_values[i][1], schedule.depreciation_amount)
self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount) self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount)
@ -877,7 +917,7 @@ class TestDepreciationBasics(AssetSetup):
["2023-01-01", 15000, 90000], ["2023-01-01", 15000, 90000],
] ]
for i, schedule in enumerate(asset.schedules): for i, schedule in enumerate(get_depr_schedule(asset.name, "Active")):
self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date) self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date)
self.assertEqual(expected_values[i][1], schedule.depreciation_amount) self.assertEqual(expected_values[i][1], schedule.depreciation_amount)
self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount) self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount)
@ -885,7 +925,9 @@ class TestDepreciationBasics(AssetSetup):
def test_get_depreciation_amount(self): def test_get_depreciation_amount(self):
"""Tests if get_depreciation_amount() returns the right value.""" """Tests if get_depreciation_amount() returns the right value."""
from erpnext.assets.doctype.asset.asset import get_depreciation_amount from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
get_depreciation_amount,
)
asset = create_asset(item_code="Macbook Pro", available_for_use_date="2019-12-31") asset = create_asset(item_code="Macbook Pro", available_for_use_date="2019-12-31")
@ -904,8 +946,8 @@ class TestDepreciationBasics(AssetSetup):
depreciation_amount = get_depreciation_amount(asset, 100000, asset.finance_books[0]) depreciation_amount = get_depreciation_amount(asset, 100000, asset.finance_books[0])
self.assertEqual(depreciation_amount, 30000) self.assertEqual(depreciation_amount, 30000)
def test_make_depreciation_schedule(self): def test_make_depr_schedule(self):
"""Tests if make_depreciation_schedule() returns the right values.""" """Tests if make_depr_schedule() returns the right values."""
asset = create_asset( asset = create_asset(
item_code="Macbook Pro", item_code="Macbook Pro",
@ -920,7 +962,7 @@ class TestDepreciationBasics(AssetSetup):
expected_values = [["2020-12-31", 30000.0], ["2021-12-31", 30000.0], ["2022-12-31", 30000.0]] expected_values = [["2020-12-31", 30000.0], ["2021-12-31", 30000.0], ["2022-12-31", 30000.0]]
for i, schedule in enumerate(asset.schedules): for i, schedule in enumerate(get_depr_schedule(asset.name, "Draft")):
self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date) self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date)
self.assertEqual(expected_values[i][1], schedule.depreciation_amount) self.assertEqual(expected_values[i][1], schedule.depreciation_amount)
@ -940,7 +982,7 @@ class TestDepreciationBasics(AssetSetup):
expected_values = [30000.0, 60000.0, 90000.0] expected_values = [30000.0, 60000.0, 90000.0]
for i, schedule in enumerate(asset.schedules): for i, schedule in enumerate(get_depr_schedule(asset.name, "Draft")):
self.assertEqual(expected_values[i], schedule.accumulated_depreciation_amount) self.assertEqual(expected_values[i], schedule.accumulated_depreciation_amount)
def test_check_is_pro_rata(self): def test_check_is_pro_rata(self):
@ -1120,9 +1162,11 @@ class TestDepreciationBasics(AssetSetup):
post_depreciation_entries(date="2021-06-01") post_depreciation_entries(date="2021-06-01")
asset.load_from_db() asset.load_from_db()
self.assertTrue(asset.schedules[0].journal_entry) depr_schedule = get_depr_schedule(asset.name, "Active")
self.assertFalse(asset.schedules[1].journal_entry)
self.assertFalse(asset.schedules[2].journal_entry) self.assertTrue(depr_schedule[0].journal_entry)
self.assertFalse(depr_schedule[1].journal_entry)
self.assertFalse(depr_schedule[2].journal_entry)
def test_depr_entry_posting_when_depr_expense_account_is_an_expense_account(self): def test_depr_entry_posting_when_depr_expense_account_is_an_expense_account(self):
"""Tests if the Depreciation Expense Account gets debited and the Accumulated Depreciation Account gets credited when the former's an Expense Account.""" """Tests if the Depreciation Expense Account gets debited and the Accumulated Depreciation Account gets credited when the former's an Expense Account."""
@ -1141,7 +1185,7 @@ class TestDepreciationBasics(AssetSetup):
post_depreciation_entries(date="2021-06-01") post_depreciation_entries(date="2021-06-01")
asset.load_from_db() asset.load_from_db()
je = frappe.get_doc("Journal Entry", asset.schedules[0].journal_entry) je = frappe.get_doc("Journal Entry", get_depr_schedule(asset.name, "Active")[0].journal_entry)
accounting_entries = [ accounting_entries = [
{"account": entry.account, "debit": entry.debit, "credit": entry.credit} {"account": entry.account, "debit": entry.debit, "credit": entry.credit}
for entry in je.accounts for entry in je.accounts
@ -1177,7 +1221,7 @@ class TestDepreciationBasics(AssetSetup):
post_depreciation_entries(date="2021-06-01") post_depreciation_entries(date="2021-06-01")
asset.load_from_db() asset.load_from_db()
je = frappe.get_doc("Journal Entry", asset.schedules[0].journal_entry) je = frappe.get_doc("Journal Entry", get_depr_schedule(asset.name, "Active")[0].journal_entry)
accounting_entries = [ accounting_entries = [
{"account": entry.account, "debit": entry.debit, "credit": entry.credit} {"account": entry.account, "debit": entry.debit, "credit": entry.credit}
for entry in je.accounts for entry in je.accounts
@ -1196,8 +1240,8 @@ class TestDepreciationBasics(AssetSetup):
depr_expense_account.parent_account = "Expenses - _TC" depr_expense_account.parent_account = "Expenses - _TC"
depr_expense_account.save() depr_expense_account.save()
def test_clear_depreciation_schedule(self): def test_clear_depr_schedule(self):
"""Tests if clear_depreciation_schedule() works as expected.""" """Tests if clear_depr_schedule() works as expected."""
asset = create_asset( asset = create_asset(
item_code="Macbook Pro", item_code="Macbook Pro",
@ -1213,17 +1257,20 @@ class TestDepreciationBasics(AssetSetup):
post_depreciation_entries(date="2021-06-01") post_depreciation_entries(date="2021-06-01")
asset.load_from_db() asset.load_from_db()
asset.clear_depreciation_schedule() asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active")
self.assertEqual(len(asset.schedules), 1) clear_depr_schedule(asset_depr_schedule_doc)
def test_clear_depreciation_schedule_for_multiple_finance_books(self): self.assertEqual(len(asset_depr_schedule_doc.get("depreciation_schedule")), 1)
def test_clear_depr_schedule_for_multiple_finance_books(self):
asset = create_asset(item_code="Macbook Pro", available_for_use_date="2019-12-31", do_not_save=1) asset = create_asset(item_code="Macbook Pro", available_for_use_date="2019-12-31", do_not_save=1)
asset.calculate_depreciation = 1 asset.calculate_depreciation = 1
asset.append( asset.append(
"finance_books", "finance_books",
{ {
"finance_book": "Test Finance Book 1",
"depreciation_method": "Straight Line", "depreciation_method": "Straight Line",
"frequency_of_depreciation": 1, "frequency_of_depreciation": 1,
"total_number_of_depreciations": 3, "total_number_of_depreciations": 3,
@ -1234,6 +1281,7 @@ class TestDepreciationBasics(AssetSetup):
asset.append( asset.append(
"finance_books", "finance_books",
{ {
"finance_book": "Test Finance Book 2",
"depreciation_method": "Straight Line", "depreciation_method": "Straight Line",
"frequency_of_depreciation": 1, "frequency_of_depreciation": 1,
"total_number_of_depreciations": 6, "total_number_of_depreciations": 6,
@ -1244,6 +1292,7 @@ class TestDepreciationBasics(AssetSetup):
asset.append( asset.append(
"finance_books", "finance_books",
{ {
"finance_book": "Test Finance Book 3",
"depreciation_method": "Straight Line", "depreciation_method": "Straight Line",
"frequency_of_depreciation": 12, "frequency_of_depreciation": 12,
"total_number_of_depreciations": 3, "total_number_of_depreciations": 3,
@ -1256,15 +1305,23 @@ class TestDepreciationBasics(AssetSetup):
post_depreciation_entries(date="2020-04-01") post_depreciation_entries(date="2020-04-01")
asset.load_from_db() asset.load_from_db()
asset.clear_depreciation_schedule() asset_depr_schedule_doc_1 = get_asset_depr_schedule_doc(
asset.name, "Active", "Test Finance Book 1"
)
clear_depr_schedule(asset_depr_schedule_doc_1)
self.assertEqual(len(asset_depr_schedule_doc_1.get("depreciation_schedule")), 3)
self.assertEqual(len(asset.schedules), 6) asset_depr_schedule_doc_2 = get_asset_depr_schedule_doc(
asset.name, "Active", "Test Finance Book 2"
)
clear_depr_schedule(asset_depr_schedule_doc_2)
self.assertEqual(len(asset_depr_schedule_doc_2.get("depreciation_schedule")), 3)
for schedule in asset.schedules: asset_depr_schedule_doc_3 = get_asset_depr_schedule_doc(
if schedule.idx <= 3: asset.name, "Active", "Test Finance Book 3"
self.assertEqual(schedule.finance_book_id, "1") )
else: clear_depr_schedule(asset_depr_schedule_doc_3)
self.assertEqual(schedule.finance_book_id, "2") self.assertEqual(len(asset_depr_schedule_doc_3.get("depreciation_schedule")), 0)
def test_depreciation_schedules_are_set_up_for_multiple_finance_books(self): def test_depreciation_schedules_are_set_up_for_multiple_finance_books(self):
asset = create_asset(item_code="Macbook Pro", available_for_use_date="2019-12-31", do_not_save=1) asset = create_asset(item_code="Macbook Pro", available_for_use_date="2019-12-31", do_not_save=1)
@ -1273,6 +1330,7 @@ class TestDepreciationBasics(AssetSetup):
asset.append( asset.append(
"finance_books", "finance_books",
{ {
"finance_book": "Test Finance Book 1",
"depreciation_method": "Straight Line", "depreciation_method": "Straight Line",
"frequency_of_depreciation": 12, "frequency_of_depreciation": 12,
"total_number_of_depreciations": 3, "total_number_of_depreciations": 3,
@ -1283,6 +1341,7 @@ class TestDepreciationBasics(AssetSetup):
asset.append( asset.append(
"finance_books", "finance_books",
{ {
"finance_book": "Test Finance Book 2",
"depreciation_method": "Straight Line", "depreciation_method": "Straight Line",
"frequency_of_depreciation": 12, "frequency_of_depreciation": 12,
"total_number_of_depreciations": 6, "total_number_of_depreciations": 6,
@ -1292,13 +1351,15 @@ class TestDepreciationBasics(AssetSetup):
) )
asset.save() asset.save()
self.assertEqual(len(asset.schedules), 9) asset_depr_schedule_doc_1 = get_asset_depr_schedule_doc(
asset.name, "Draft", "Test Finance Book 1"
)
self.assertEqual(len(asset_depr_schedule_doc_1.get("depreciation_schedule")), 3)
for schedule in asset.schedules: asset_depr_schedule_doc_2 = get_asset_depr_schedule_doc(
if schedule.idx <= 3: asset.name, "Draft", "Test Finance Book 2"
self.assertEqual(schedule.finance_book_id, 1) )
else: self.assertEqual(len(asset_depr_schedule_doc_2.get("depreciation_schedule")), 6)
self.assertEqual(schedule.finance_book_id, 2)
def test_depreciation_entry_cancellation(self): def test_depreciation_entry_cancellation(self):
asset = create_asset( asset = create_asset(
@ -1318,12 +1379,12 @@ class TestDepreciationBasics(AssetSetup):
asset.load_from_db() asset.load_from_db()
# cancel depreciation entry # cancel depreciation entry
depr_entry = asset.get("schedules")[0].journal_entry depr_entry = get_depr_schedule(asset.name, "Active")[0].journal_entry
self.assertTrue(depr_entry) self.assertTrue(depr_entry)
frappe.get_doc("Journal Entry", depr_entry).cancel() frappe.get_doc("Journal Entry", depr_entry).cancel()
asset.load_from_db() depr_entry = get_depr_schedule(asset.name, "Active")[0].journal_entry
depr_entry = asset.get("schedules")[0].journal_entry
self.assertFalse(depr_entry) self.assertFalse(depr_entry)
def test_asset_expected_value_after_useful_life(self): def test_asset_expected_value_after_useful_life(self):
@ -1338,7 +1399,7 @@ class TestDepreciationBasics(AssetSetup):
) )
accumulated_depreciation_after_full_schedule = max( accumulated_depreciation_after_full_schedule = max(
d.accumulated_depreciation_amount for d in asset.get("schedules") d.accumulated_depreciation_amount for d in get_depr_schedule(asset.name, "Draft")
) )
asset_value_after_full_schedule = flt(asset.gross_purchase_amount) - flt( asset_value_after_full_schedule = flt(asset.gross_purchase_amount) - flt(
@ -1369,7 +1430,7 @@ class TestDepreciationBasics(AssetSetup):
asset.load_from_db() asset.load_from_db()
# check depreciation entry series # check depreciation entry series
self.assertEqual(asset.get("schedules")[0].journal_entry[:4], "DEPR") self.assertEqual(get_depr_schedule(asset.name, "Active")[0].journal_entry[:4], "DEPR")
expected_gle = ( expected_gle = (
("_Test Accumulated Depreciations - _TC", 0.0, 30000.0), ("_Test Accumulated Depreciations - _TC", 0.0, 30000.0),
@ -1439,7 +1500,7 @@ class TestDepreciationBasics(AssetSetup):
"2020-07-15", "2020-07-15",
] ]
for i, schedule in enumerate(asset.schedules): for i, schedule in enumerate(get_depr_schedule(asset.name, "Active")):
self.assertEqual(getdate(expected_dates[i]), getdate(schedule.schedule_date)) self.assertEqual(getdate(expected_dates[i]), getdate(schedule.schedule_date))
@ -1453,6 +1514,15 @@ def create_asset_data():
if not frappe.db.exists("Location", "Test Location"): if not frappe.db.exists("Location", "Test Location"):
frappe.get_doc({"doctype": "Location", "location_name": "Test Location"}).insert() frappe.get_doc({"doctype": "Location", "location_name": "Test Location"}).insert()
if not frappe.db.exists("Finance Book", "Test Finance Book 1"):
frappe.get_doc({"doctype": "Finance Book", "finance_book_name": "Test Finance Book 1"}).insert()
if not frappe.db.exists("Finance Book", "Test Finance Book 2"):
frappe.get_doc({"doctype": "Finance Book", "finance_book_name": "Test Finance Book 2"}).insert()
if not frappe.db.exists("Finance Book", "Test Finance Book 3"):
frappe.get_doc({"doctype": "Finance Book", "finance_book_name": "Test Finance Book 3"}).insert()
def create_asset(**args): def create_asset(**args):
args = frappe._dict(args) args = frappe._dict(args)

View File

@ -7,7 +7,7 @@ import frappe
# import erpnext # import erpnext
from frappe import _ from frappe import _
from frappe.utils import cint, flt from frappe.utils import cint, flt, get_link_to_form
from six import string_types from six import string_types
import erpnext import erpnext
@ -19,6 +19,9 @@ from erpnext.assets.doctype.asset.depreciation import (
reverse_depreciation_entry_made_after_disposal, reverse_depreciation_entry_made_after_disposal,
) )
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
make_new_active_asset_depr_schedules_and_cancel_current_ones,
)
from erpnext.assets.doctype.asset_value_adjustment.asset_value_adjustment import ( from erpnext.assets.doctype.asset_value_adjustment.asset_value_adjustment import (
get_current_asset_value, get_current_asset_value,
) )
@ -427,7 +430,12 @@ class AssetCapitalization(StockController):
asset = self.get_asset(item) asset = self.get_asset(item)
if asset.calculate_depreciation: if asset.calculate_depreciation:
depreciate_asset(asset, self.posting_date) notes = _(
"This schedule was created when Asset {0} was consumed when Asset Capitalization {1} was submitted."
).format(
get_link_to_form(asset.doctype, asset.name), get_link_to_form(self.doctype, self.get("name"))
)
depreciate_asset(asset, self.posting_date, notes)
asset.reload() asset.reload()
fixed_asset_gl_entries = get_gl_entries_on_asset_disposal( fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(
@ -513,7 +521,12 @@ class AssetCapitalization(StockController):
asset_doc.purchase_date = self.posting_date asset_doc.purchase_date = self.posting_date
asset_doc.gross_purchase_amount = total_target_asset_value asset_doc.gross_purchase_amount = total_target_asset_value
asset_doc.purchase_receipt_amount = total_target_asset_value asset_doc.purchase_receipt_amount = total_target_asset_value
asset_doc.prepare_depreciation_data() notes = _(
"This schedule was created when target Asset {0} was updated when Asset Capitalization {1} was submitted."
).format(
get_link_to_form(asset_doc.doctype, asset_doc.name), get_link_to_form(self.doctype, self.name)
)
make_new_active_asset_depr_schedules_and_cancel_current_ones(asset_doc, notes)
asset_doc.flags.ignore_validate_update_after_submit = True asset_doc.flags.ignore_validate_update_after_submit = True
asset_doc.save() asset_doc.save()
elif self.docstatus == 2: elif self.docstatus == 2:
@ -524,7 +537,12 @@ class AssetCapitalization(StockController):
if asset.calculate_depreciation: if asset.calculate_depreciation:
reverse_depreciation_entry_made_after_disposal(asset, self.posting_date) reverse_depreciation_entry_made_after_disposal(asset, self.posting_date)
reset_depreciation_schedule(asset, self.posting_date) notes = _(
"This schedule was created when Asset {0} was restored when Asset Capitalization {1} was cancelled."
).format(
get_link_to_form(asset.doctype, asset.name), get_link_to_form(self.doctype, self.name)
)
reset_depreciation_schedule(asset, self.posting_date, notes)
def get_asset(self, item): def get_asset(self, item):
asset = frappe.get_doc("Asset", item.asset) asset = frappe.get_doc("Asset", item.asset)

View File

@ -12,6 +12,9 @@ from erpnext.assets.doctype.asset.test_asset import (
create_asset_data, create_asset_data,
set_depreciation_settings_in_company, set_depreciation_settings_in_company,
) )
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
get_asset_depr_schedule_doc,
)
from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.doctype.item.test_item import create_item
@ -253,6 +256,9 @@ class TestAssetCapitalization(unittest.TestCase):
submit=1, submit=1,
) )
first_asset_depr_schedule = get_asset_depr_schedule_doc(consumed_asset.name, "Active")
self.assertEquals(first_asset_depr_schedule.status, "Active")
# Create and submit Asset Captitalization # Create and submit Asset Captitalization
asset_capitalization = create_asset_capitalization( asset_capitalization = create_asset_capitalization(
entry_type="Decapitalization", entry_type="Decapitalization",
@ -282,8 +288,18 @@ class TestAssetCapitalization(unittest.TestCase):
consumed_asset.reload() consumed_asset.reload()
self.assertEqual(consumed_asset.status, "Decapitalized") self.assertEqual(consumed_asset.status, "Decapitalized")
first_asset_depr_schedule.load_from_db()
second_asset_depr_schedule = get_asset_depr_schedule_doc(consumed_asset.name, "Active")
self.assertEquals(second_asset_depr_schedule.status, "Active")
self.assertEquals(first_asset_depr_schedule.status, "Cancelled")
depr_schedule_of_consumed_asset = second_asset_depr_schedule.get("depreciation_schedule")
consumed_depreciation_schedule = [ consumed_depreciation_schedule = [
d for d in consumed_asset.schedules if getdate(d.schedule_date) == getdate(capitalization_date) d
for d in depr_schedule_of_consumed_asset
if getdate(d.schedule_date) == getdate(capitalization_date)
] ]
self.assertTrue( self.assertTrue(
consumed_depreciation_schedule and consumed_depreciation_schedule[0].journal_entry consumed_depreciation_schedule and consumed_depreciation_schedule[0].journal_entry

View File

@ -0,0 +1,51 @@
// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.provide("erpnext.asset");
frappe.ui.form.on('Asset Depreciation Schedule', {
onload: function(frm) {
frm.events.make_schedules_editable(frm);
},
make_schedules_editable: function(frm) {
var is_editable = frm.doc.depreciation_method == "Manual" ? 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);
}
});
frappe.ui.form.on('Depreciation Schedule', {
make_depreciation_entry: function(frm, cdt, cdn) {
var row = locals[cdt][cdn];
if (!row.journal_entry) {
frappe.call({
method: "erpnext.assets.doctype.asset.depreciation.make_depreciation_entry",
args: {
"asset_depr_schedule_name": frm.doc.name,
"date": row.schedule_date
},
callback: function(r) {
frappe.model.sync(r.message);
frm.refresh();
}
})
}
},
depreciation_amount: function(frm, cdt, cdn) {
erpnext.asset.set_accumulated_depreciation(frm);
}
});
erpnext.asset.set_accumulated_depreciation = function(frm) {
if(frm.doc.depreciation_method != "Manual") return;
var accumulated_depreciation = flt(frm.doc.opening_accumulated_depreciation);
$.each(frm.doc.schedules || [], function(i, row) {
accumulated_depreciation += flt(row.depreciation_amount);
frappe.model.set_value(row.doctype, row.name,
"accumulated_depreciation_amount", accumulated_depreciation);
})
};

View File

@ -0,0 +1,202 @@
{
"actions": [],
"allow_rename": 1,
"autoname": "naming_series:",
"creation": "2022-10-31 15:03:35.424877",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"asset",
"naming_series",
"column_break_2",
"opening_accumulated_depreciation",
"finance_book",
"finance_book_id",
"depreciation_details_section",
"depreciation_method",
"total_number_of_depreciations",
"rate_of_depreciation",
"column_break_8",
"frequency_of_depreciation",
"expected_value_after_useful_life",
"depreciation_schedule_section",
"depreciation_schedule",
"details_section",
"notes",
"status",
"amended_from"
],
"fields": [
{
"fieldname": "asset",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Asset",
"options": "Asset",
"reqd": 1
},
{
"fieldname": "naming_series",
"fieldtype": "Select",
"label": "Naming Series",
"options": "ACC-ADS-.YYYY.-"
},
{
"fieldname": "amended_from",
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
"options": "Asset Depreciation Schedule",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "column_break_2",
"fieldtype": "Column Break"
},
{
"collapsible": 1,
"fieldname": "depreciation_details_section",
"fieldtype": "Section Break",
"label": "Depreciation Details"
},
{
"fieldname": "finance_book",
"fieldtype": "Link",
"label": "Finance Book",
"options": "Finance Book"
},
{
"fieldname": "depreciation_method",
"fieldtype": "Select",
"label": "Depreciation Method",
"options": "\nStraight Line\nDouble Declining Balance\nWritten Down Value\nManual",
"read_only": 1
},
{
"depends_on": "eval:doc.depreciation_method == 'Written Down Value'",
"description": "In Percentage",
"fieldname": "rate_of_depreciation",
"fieldtype": "Percent",
"label": "Rate of Depreciation",
"read_only": 1
},
{
"fieldname": "column_break_8",
"fieldtype": "Column Break"
},
{
"depends_on": "total_number_of_depreciations",
"fieldname": "total_number_of_depreciations",
"fieldtype": "Int",
"label": "Total Number of Depreciations",
"read_only": 1
},
{
"fieldname": "depreciation_schedule_section",
"fieldtype": "Section Break",
"label": "Depreciation Schedule"
},
{
"fieldname": "depreciation_schedule",
"fieldtype": "Table",
"label": "Depreciation Schedule",
"options": "Depreciation Schedule"
},
{
"collapsible": 1,
"collapsible_depends_on": "notes",
"fieldname": "details_section",
"fieldtype": "Section Break",
"label": "Details"
},
{
"fieldname": "notes",
"fieldtype": "Small Text",
"label": "Notes",
"read_only": 1
},
{
"fieldname": "status",
"fieldtype": "Select",
"hidden": 1,
"label": "Status",
"options": "Draft\nActive\nCancelled",
"read_only": 1
},
{
"depends_on": "frequency_of_depreciation",
"fieldname": "frequency_of_depreciation",
"fieldtype": "Int",
"label": "Frequency of Depreciation (Months)",
"read_only": 1
},
{
"fieldname": "expected_value_after_useful_life",
"fieldtype": "Currency",
"label": "Expected Value After Useful Life",
"options": "Company:company:default_currency",
"read_only": 1
},
{
"fieldname": "finance_book_id",
"fieldtype": "Int",
"hidden": 1,
"label": "Finance Book Id",
"print_hide": 1,
"read_only": 1
},
{
"depends_on": "opening_accumulated_depreciation",
"fieldname": "opening_accumulated_depreciation",
"fieldtype": "Currency",
"label": "Opening Accumulated Depreciation",
"options": "Company:company:default_currency",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2023-01-02 15:38:30.766779",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Depreciation Schedule",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts User",
"share": 1,
"submit": 1,
"write": 1
},
{
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Quality Manager",
"share": 1,
"submit": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@ -0,0 +1,516 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.utils import (
add_days,
add_months,
cint,
date_diff,
flt,
get_last_day,
is_last_day_of_the_month,
)
import erpnext
class AssetDepreciationSchedule(Document):
def before_save(self):
if not self.finance_book_id:
self.prepare_draft_asset_depr_schedule_data_from_asset_name_and_fb_name(
self.asset, self.finance_book
)
def validate(self):
self.validate_another_asset_depr_schedule_does_not_exist()
def validate_another_asset_depr_schedule_does_not_exist(self):
finance_book_filter = ["finance_book", "is", "not set"]
if self.finance_book:
finance_book_filter = ["finance_book", "=", self.finance_book]
asset_depr_schedule = frappe.db.exists(
"Asset Depreciation Schedule",
[
["asset", "=", self.asset],
finance_book_filter,
["docstatus", "<", 2],
],
)
if asset_depr_schedule and asset_depr_schedule != self.name:
if self.finance_book:
frappe.throw(
_(
"Asset Depreciation Schedule {0} for Asset {1} and Finance Book {2} already exists."
).format(asset_depr_schedule, self.asset, self.finance_book)
)
else:
frappe.throw(
_("Asset Depreciation Schedule {0} for Asset {1} already exists.").format(
asset_depr_schedule, self.asset
)
)
def on_submit(self):
self.db_set("status", "Active")
def before_cancel(self):
if not self.flags.should_not_cancel_depreciation_entries:
self.cancel_depreciation_entries()
def cancel_depreciation_entries(self):
for d in self.get("depreciation_schedule"):
if d.journal_entry:
frappe.get_doc("Journal Entry", d.journal_entry).cancel()
def on_cancel(self):
self.db_set("status", "Cancelled")
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)
finance_book_filter = ["finance_book", "is", "not set"]
if fb_name:
finance_book_filter = ["finance_book", "=", fb_name]
asset_finance_book_name = frappe.db.get_value(
doctype="Asset Finance Book",
filters=[["parent", "=", asset_name], finance_book_filter],
)
asset_finance_book_doc = frappe.get_doc("Asset Finance Book", asset_finance_book_name)
prepare_draft_asset_depr_schedule_data(self, asset_doc, asset_finance_book_doc)
def make_draft_asset_depr_schedules_if_not_present(asset_doc):
for row in asset_doc.get("finance_books"):
draft_asset_depr_schedule_name = get_asset_depr_schedule_name(
asset_doc.name, "Draft", row.finance_book
)
active_asset_depr_schedule_name = get_asset_depr_schedule_name(
asset_doc.name, "Active", row.finance_book
)
if not draft_asset_depr_schedule_name and not active_asset_depr_schedule_name:
make_draft_asset_depr_schedule(asset_doc, row)
def make_draft_asset_depr_schedules(asset_doc):
for row in asset_doc.get("finance_books"):
make_draft_asset_depr_schedule(asset_doc, row)
def make_draft_asset_depr_schedule(asset_doc, row):
asset_depr_schedule_doc = frappe.new_doc("Asset Depreciation Schedule")
prepare_draft_asset_depr_schedule_data(asset_depr_schedule_doc, asset_doc, row)
asset_depr_schedule_doc.insert()
def update_draft_asset_depr_schedules(asset_doc):
for row in asset_doc.get("finance_books"):
asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset_doc.name, "Draft", row.finance_book)
if not asset_depr_schedule_doc:
continue
prepare_draft_asset_depr_schedule_data(asset_depr_schedule_doc, asset_doc, row)
asset_depr_schedule_doc.save()
def prepare_draft_asset_depr_schedule_data(
asset_depr_schedule_doc,
asset_doc,
row,
date_of_disposal=None,
date_of_return=None,
update_asset_finance_book_row=True,
):
set_draft_asset_depr_schedule_details(asset_depr_schedule_doc, asset_doc, row)
make_depr_schedule(
asset_depr_schedule_doc, asset_doc, row, date_of_disposal, update_asset_finance_book_row
)
set_accumulated_depreciation(asset_depr_schedule_doc, row, date_of_disposal, date_of_return)
def set_draft_asset_depr_schedule_details(asset_depr_schedule_doc, asset_doc, row):
asset_depr_schedule_doc.asset = asset_doc.name
asset_depr_schedule_doc.finance_book = row.finance_book
asset_depr_schedule_doc.finance_book_id = row.idx
asset_depr_schedule_doc.opening_accumulated_depreciation = (
asset_doc.opening_accumulated_depreciation
)
asset_depr_schedule_doc.depreciation_method = row.depreciation_method
asset_depr_schedule_doc.total_number_of_depreciations = row.total_number_of_depreciations
asset_depr_schedule_doc.frequency_of_depreciation = row.frequency_of_depreciation
asset_depr_schedule_doc.rate_of_depreciation = row.rate_of_depreciation
asset_depr_schedule_doc.expected_value_after_useful_life = row.expected_value_after_useful_life
asset_depr_schedule_doc.status = "Draft"
def convert_draft_asset_depr_schedules_into_active(asset_doc):
for row in asset_doc.get("finance_books"):
asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset_doc.name, "Draft", row.finance_book)
if not asset_depr_schedule_doc:
continue
asset_depr_schedule_doc.submit()
def cancel_asset_depr_schedules(asset_doc):
for row in asset_doc.get("finance_books"):
asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset_doc.name, "Active", row.finance_book)
if not asset_depr_schedule_doc:
continue
asset_depr_schedule_doc.cancel()
def make_new_active_asset_depr_schedules_and_cancel_current_ones(
asset_doc, notes, date_of_disposal=None, date_of_return=None
):
for row in asset_doc.get("finance_books"):
current_asset_depr_schedule_doc = get_asset_depr_schedule_doc(
asset_doc.name, "Active", row.finance_book
)
if not current_asset_depr_schedule_doc:
frappe.throw(
_("Asset Depreciation Schedule not found for Asset {0} and Finance Book {1}").format(
asset_doc.name, row.finance_book
)
)
new_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc)
make_depr_schedule(new_asset_depr_schedule_doc, asset_doc, row, date_of_disposal)
set_accumulated_depreciation(new_asset_depr_schedule_doc, row, date_of_disposal, date_of_return)
new_asset_depr_schedule_doc.notes = notes
current_asset_depr_schedule_doc.flags.should_not_cancel_depreciation_entries = True
current_asset_depr_schedule_doc.cancel()
new_asset_depr_schedule_doc.submit()
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_depr_schedule_doc = frappe.new_doc("Asset Depreciation Schedule")
prepare_draft_asset_depr_schedule_data(
asset_depr_schedule_doc,
asset_doc,
row,
date_of_disposal,
date_of_return,
update_asset_finance_book_row,
)
return asset_depr_schedule_doc
def get_asset_depr_schedule_name(asset_name, status, finance_book=None):
finance_book_filter = ["finance_book", "is", "not set"]
if finance_book:
finance_book_filter = ["finance_book", "=", finance_book]
return frappe.db.get_value(
doctype="Asset Depreciation Schedule",
filters=[
["asset", "=", asset_name],
finance_book_filter,
["status", "=", status],
],
)
@frappe.whitelist()
def get_depr_schedule(asset_name, status, finance_book=None):
asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset_name, status, finance_book)
if not asset_depr_schedule_doc:
return
return asset_depr_schedule_doc.get("depreciation_schedule")
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)
if not asset_depr_schedule_name:
return
asset_depr_schedule_doc = frappe.get_doc("Asset Depreciation Schedule", asset_depr_schedule_name)
return asset_depr_schedule_doc
def make_depr_schedule(
asset_depr_schedule_doc, asset_doc, row, date_of_disposal, update_asset_finance_book_row=True
):
if row.depreciation_method != "Manual" and not asset_depr_schedule_doc.get(
"depreciation_schedule"
):
asset_depr_schedule_doc.depreciation_schedule = []
if not asset_doc.available_for_use_date:
return
start = clear_depr_schedule(asset_depr_schedule_doc)
_make_depr_schedule(
asset_depr_schedule_doc, asset_doc, row, start, date_of_disposal, update_asset_finance_book_row
)
def clear_depr_schedule(asset_depr_schedule_doc):
start = 0
num_of_depreciations_completed = 0
depr_schedule = []
for schedule in asset_depr_schedule_doc.get("depreciation_schedule"):
if schedule.journal_entry:
num_of_depreciations_completed += 1
depr_schedule.append(schedule)
else:
start = num_of_depreciations_completed
break
asset_depr_schedule_doc.depreciation_schedule = depr_schedule
return start
def _make_depr_schedule(
asset_depr_schedule_doc, asset_doc, row, start, date_of_disposal, update_asset_finance_book_row
):
asset_doc.validate_asset_finance_books(row)
value_after_depreciation = asset_doc._get_value_after_depreciation(row)
row.value_after_depreciation = value_after_depreciation
if update_asset_finance_book_row:
row.db_update()
number_of_pending_depreciations = cint(row.total_number_of_depreciations) - cint(
asset_doc.number_of_depreciations_booked
)
has_pro_rata = asset_doc.check_is_pro_rata(row)
if has_pro_rata:
number_of_pending_depreciations += 1
skip_row = False
should_get_last_day = is_last_day_of_the_month(row.depreciation_start_date)
for n in range(start, number_of_pending_depreciations):
# If depreciation is already completed (for double declining balance)
if skip_row:
continue
depreciation_amount = get_depreciation_amount(asset_doc, value_after_depreciation, row)
if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1:
schedule_date = add_months(row.depreciation_start_date, n * cint(row.frequency_of_depreciation))
if should_get_last_day:
schedule_date = get_last_day(schedule_date)
# schedule date will be a year later from start date
# so monthly schedule date is calculated by removing 11 months from it
monthly_schedule_date = add_months(schedule_date, -row.frequency_of_depreciation + 1)
# if asset is being sold or scrapped
if date_of_disposal:
from_date = asset_doc.available_for_use_date
if asset_depr_schedule_doc.depreciation_schedule:
from_date = asset_depr_schedule_doc.depreciation_schedule[-1].schedule_date
depreciation_amount, days, months = asset_doc.get_pro_rata_amt(
row, depreciation_amount, from_date, date_of_disposal
)
if depreciation_amount > 0:
add_depr_schedule_row(
asset_depr_schedule_doc,
date_of_disposal,
depreciation_amount,
row.depreciation_method,
)
break
# For first row
if has_pro_rata and not asset_doc.opening_accumulated_depreciation and n == 0:
from_date = add_days(
asset_doc.available_for_use_date, -1
) # needed to calc depr amount for available_for_use_date too
depreciation_amount, days, months = asset_doc.get_pro_rata_amt(
row, depreciation_amount, from_date, row.depreciation_start_date
)
# For first depr schedule date will be the start date
# so monthly schedule date is calculated by removing
# month difference between use date and start date
monthly_schedule_date = add_months(row.depreciation_start_date, -months + 1)
# For last row
elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1:
if not asset_doc.flags.increase_in_asset_life:
# In case of increase_in_asset_life, the asset.to_date is already set on asset_repair submission
asset_doc.to_date = add_months(
asset_doc.available_for_use_date,
(n + asset_doc.number_of_depreciations_booked) * cint(row.frequency_of_depreciation),
)
depreciation_amount_without_pro_rata = depreciation_amount
depreciation_amount, days, months = asset_doc.get_pro_rata_amt(
row, depreciation_amount, schedule_date, asset_doc.to_date
)
depreciation_amount = get_adjusted_depreciation_amount(
asset_depr_schedule_doc, depreciation_amount_without_pro_rata, depreciation_amount
)
monthly_schedule_date = add_months(schedule_date, 1)
schedule_date = add_days(schedule_date, days)
last_schedule_date = schedule_date
if not depreciation_amount:
continue
value_after_depreciation -= flt(
depreciation_amount, asset_doc.precision("gross_purchase_amount")
)
# Adjust depreciation amount in the last period based on the expected value after useful life
if row.expected_value_after_useful_life and (
(
n == cint(number_of_pending_depreciations) - 1
and value_after_depreciation != row.expected_value_after_useful_life
)
or value_after_depreciation < row.expected_value_after_useful_life
):
depreciation_amount += value_after_depreciation - row.expected_value_after_useful_life
skip_row = True
if depreciation_amount > 0:
add_depr_schedule_row(
asset_depr_schedule_doc,
schedule_date,
depreciation_amount,
row.depreciation_method,
)
# to ensure that final accumulated depreciation amount is accurate
def get_adjusted_depreciation_amount(
asset_depr_schedule_doc, depreciation_amount_without_pro_rata, depreciation_amount_for_last_row
):
if not asset_depr_schedule_doc.opening_accumulated_depreciation:
depreciation_amount_for_first_row = get_depreciation_amount_for_first_row(
asset_depr_schedule_doc
)
if (
depreciation_amount_for_first_row + depreciation_amount_for_last_row
!= depreciation_amount_without_pro_rata
):
depreciation_amount_for_last_row = (
depreciation_amount_without_pro_rata - depreciation_amount_for_first_row
)
return depreciation_amount_for_last_row
def get_depreciation_amount_for_first_row(asset_depr_schedule_doc):
return asset_depr_schedule_doc.get("depreciation_schedule")[0].depreciation_amount
@erpnext.allow_regional
def get_depreciation_amount(asset_doc, depreciable_value, row):
if row.depreciation_method in ("Straight Line", "Manual"):
# if the Depreciation Schedule is being prepared for the first time
if not asset_doc.flags.increase_in_asset_life:
depreciation_amount = (
flt(asset_doc.gross_purchase_amount) - flt(row.expected_value_after_useful_life)
) / flt(row.total_number_of_depreciations)
# if the Depreciation Schedule is being modified after Asset Repair
else:
depreciation_amount = (
flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)
) / (date_diff(asset_doc.to_date, asset_doc.available_for_use_date) / 365)
else:
depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100))
return depreciation_amount
def add_depr_schedule_row(
asset_depr_schedule_doc,
schedule_date,
depreciation_amount,
depreciation_method,
):
asset_depr_schedule_doc.append(
"depreciation_schedule",
{
"schedule_date": schedule_date,
"depreciation_amount": depreciation_amount,
"depreciation_method": depreciation_method,
},
)
def set_accumulated_depreciation(
asset_depr_schedule_doc,
row,
date_of_disposal=None,
date_of_return=None,
ignore_booked_entry=False,
):
straight_line_idx = [
d.idx
for d in asset_depr_schedule_doc.get("depreciation_schedule")
if d.depreciation_method == "Straight Line"
]
accumulated_depreciation = flt(asset_depr_schedule_doc.opening_accumulated_depreciation)
value_after_depreciation = flt(row.value_after_depreciation)
for i, d in enumerate(asset_depr_schedule_doc.get("depreciation_schedule")):
if ignore_booked_entry and d.journal_entry:
continue
depreciation_amount = flt(d.depreciation_amount, d.precision("depreciation_amount"))
value_after_depreciation -= flt(depreciation_amount)
# for the last row, if depreciation method = Straight Line
if (
straight_line_idx
and i == max(straight_line_idx) - 1
and not date_of_disposal
and not date_of_return
):
depreciation_amount += flt(
value_after_depreciation - flt(row.expected_value_after_useful_life),
d.precision("depreciation_amount"),
)
d.depreciation_amount = depreciation_amount
accumulated_depreciation += d.depreciation_amount
d.accumulated_depreciation_amount = flt(
accumulated_depreciation, d.precision("accumulated_depreciation_amount")
)

View File

@ -0,0 +1,27 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import frappe
from frappe.tests.utils import FrappeTestCase
from erpnext.assets.doctype.asset.test_asset import create_asset, create_asset_data
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
get_asset_depr_schedule_doc,
)
class TestAssetDepreciationSchedule(FrappeTestCase):
def setUp(self):
create_asset_data()
def test_throw_error_if_another_asset_depr_schedule_exist(self):
asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, submit=1)
first_asset_depr_schedule = get_asset_depr_schedule_doc(asset.name, "Active")
self.assertEquals(first_asset_depr_schedule.status, "Active")
second_asset_depr_schedule = frappe.get_doc(
{"doctype": "Asset Depreciation Schedule", "asset": asset.name, "finance_book": None}
)
self.assertRaises(frappe.ValidationError, second_asset_depr_schedule.insert)

View File

@ -3,11 +3,15 @@
import frappe import frappe
from frappe import _ from frappe import _
from frappe.utils import add_months, cint, flt, getdate, time_diff_in_hours from frappe.utils import add_months, cint, flt, get_link_to_form, getdate, time_diff_in_hours
import erpnext import erpnext
from erpnext.accounts.general_ledger import make_gl_entries from erpnext.accounts.general_ledger import make_gl_entries
from erpnext.assets.doctype.asset.asset import get_asset_account from erpnext.assets.doctype.asset.asset import get_asset_account
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
get_depr_schedule,
make_new_active_asset_depr_schedules_and_cancel_current_ones,
)
from erpnext.controllers.accounts_controller import AccountsController from erpnext.controllers.accounts_controller import AccountsController
@ -52,8 +56,11 @@ class AssetRepair(AccountsController):
): ):
self.modify_depreciation_schedule() self.modify_depreciation_schedule()
notes = _("This schedule was created when Asset Repair {0} was submitted.").format(
get_link_to_form(self.doctype, self.name)
)
self.asset_doc.flags.ignore_validate_update_after_submit = True self.asset_doc.flags.ignore_validate_update_after_submit = True
self.asset_doc.prepare_depreciation_data() make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes)
self.asset_doc.save() self.asset_doc.save()
def before_cancel(self): def before_cancel(self):
@ -73,8 +80,11 @@ class AssetRepair(AccountsController):
): ):
self.revert_depreciation_schedule_on_cancellation() self.revert_depreciation_schedule_on_cancellation()
notes = _("This schedule was created when Asset Repair {0} was cancelled.").format(
get_link_to_form(self.doctype, self.name)
)
self.asset_doc.flags.ignore_validate_update_after_submit = True self.asset_doc.flags.ignore_validate_update_after_submit = True
self.asset_doc.prepare_depreciation_data() make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes)
self.asset_doc.save() self.asset_doc.save()
def check_repair_status(self): def check_repair_status(self):
@ -279,8 +289,10 @@ class AssetRepair(AccountsController):
asset.number_of_depreciations_booked asset.number_of_depreciations_booked
) )
depr_schedule = get_depr_schedule(asset.name, "Active", row.finance_book)
# the Schedule Date in the final row of the old Depreciation Schedule # the Schedule Date in the final row of the old Depreciation Schedule
last_schedule_date = asset.schedules[len(asset.schedules) - 1].schedule_date last_schedule_date = depr_schedule[len(depr_schedule) - 1].schedule_date
# the Schedule Date in the final row of the new Depreciation Schedule # the Schedule Date in the final row of the new Depreciation Schedule
asset.to_date = add_months(last_schedule_date, extra_months) asset.to_date = add_months(last_schedule_date, extra_months)
@ -310,8 +322,10 @@ class AssetRepair(AccountsController):
asset.number_of_depreciations_booked asset.number_of_depreciations_booked
) )
depr_schedule = get_depr_schedule(asset.name, "Active", row.finance_book)
# the Schedule Date in the final row of the modified Depreciation Schedule # the Schedule Date in the final row of the modified Depreciation Schedule
last_schedule_date = asset.schedules[len(asset.schedules) - 1].schedule_date last_schedule_date = depr_schedule[len(depr_schedule) - 1].schedule_date
# the Schedule Date in the final row of the original Depreciation Schedule # the Schedule Date in the final row of the original Depreciation Schedule
asset.to_date = add_months(last_schedule_date, -extra_months) asset.to_date = add_months(last_schedule_date, -extra_months)

View File

@ -12,6 +12,9 @@ from erpnext.assets.doctype.asset.test_asset import (
create_asset_data, create_asset_data,
set_depreciation_settings_in_company, set_depreciation_settings_in_company,
) )
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
get_asset_depr_schedule_doc,
)
from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.doctype.item.test_item import create_item
@ -232,13 +235,23 @@ class TestAssetRepair(unittest.TestCase):
def test_increase_in_asset_life(self): def test_increase_in_asset_life(self):
asset = create_asset(calculate_depreciation=1, submit=1) asset = create_asset(calculate_depreciation=1, submit=1)
first_asset_depr_schedule = get_asset_depr_schedule_doc(asset.name, "Active")
self.assertEquals(first_asset_depr_schedule.status, "Active")
initial_num_of_depreciations = num_of_depreciations(asset) initial_num_of_depreciations = num_of_depreciations(asset)
create_asset_repair(asset=asset, capitalize_repair_cost=1, submit=1) create_asset_repair(asset=asset, capitalize_repair_cost=1, submit=1)
asset.reload() asset.reload()
first_asset_depr_schedule.load_from_db()
second_asset_depr_schedule = get_asset_depr_schedule_doc(asset.name, "Active")
self.assertEquals(second_asset_depr_schedule.status, "Active")
self.assertEquals(first_asset_depr_schedule.status, "Cancelled")
self.assertEqual((initial_num_of_depreciations + 1), num_of_depreciations(asset)) self.assertEqual((initial_num_of_depreciations + 1), num_of_depreciations(asset))
self.assertEqual( self.assertEqual(
asset.schedules[-1].accumulated_depreciation_amount, second_asset_depr_schedule.get("depreciation_schedule")[-1].accumulated_depreciation_amount,
asset.finance_books[0].value_after_depreciation, asset.finance_books[0].value_after_depreciation,
) )

View File

@ -5,13 +5,17 @@
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 date_diff, flt, formatdate, get_link_to_form, 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 get_depreciation_amount
from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
get_asset_depr_schedule_doc,
get_depreciation_amount,
set_accumulated_depreciation,
)
class AssetValueAdjustment(Document): class AssetValueAdjustment(Document):
@ -112,21 +116,40 @@ class AssetValueAdjustment(Document):
for d in asset.finance_books: for d in asset.finance_books:
d.value_after_depreciation = asset_value d.value_after_depreciation = asset_value
current_asset_depr_schedule_doc = get_asset_depr_schedule_doc(
asset.name, "Active", d.finance_book
)
new_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc)
new_asset_depr_schedule_doc.status = "Draft"
new_asset_depr_schedule_doc.docstatus = 0
current_asset_depr_schedule_doc.flags.should_not_cancel_depreciation_entries = True
current_asset_depr_schedule_doc.cancel()
notes = _(
"This schedule was created when Asset {0} was adjusted through Asset Value Adjustment {1}."
).format(
get_link_to_form(asset.doctype, asset.name),
get_link_to_form(self.get("doctype"), self.get("name")),
)
new_asset_depr_schedule_doc.notes = notes
new_asset_depr_schedule_doc.insert()
depr_schedule = new_asset_depr_schedule_doc.get("depreciation_schedule")
if d.depreciation_method in ("Straight Line", "Manual"): 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) end_date = max(s.schedule_date for s in depr_schedule)
total_days = date_diff(end_date, self.date) total_days = date_diff(end_date, self.date)
rate_per_day = flt(d.value_after_depreciation) / flt(total_days) rate_per_day = flt(d.value_after_depreciation) / flt(total_days)
from_date = self.date from_date = self.date
else: else:
no_of_depreciations = len( no_of_depreciations = len([s.name for s in depr_schedule if not s.journal_entry])
[
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 value_after_depreciation = d.value_after_depreciation
for data in asset.schedules: for data in depr_schedule:
if cint(data.finance_book_id) == d.idx and not data.journal_entry: if not data.journal_entry:
if d.depreciation_method in ("Straight Line", "Manual"): if d.depreciation_method in ("Straight Line", "Manual"):
days = date_diff(data.schedule_date, from_date) days = date_diff(data.schedule_date, from_date)
depreciation_amount = days * rate_per_day depreciation_amount = days * rate_per_day
@ -140,10 +163,12 @@ class AssetValueAdjustment(Document):
d.db_update() d.db_update()
asset.set_accumulated_depreciation(ignore_booked_entry=True) set_accumulated_depreciation(new_asset_depr_schedule_doc, d, ignore_booked_entry=True)
for asset_data in asset.schedules: for asset_data in depr_schedule:
if not asset_data.journal_entry: if not asset_data.journal_entry:
asset_data.db_update() asset_data.db_update()
new_asset_depr_schedule_doc.submit()
@frappe.whitelist() @frappe.whitelist()

View File

@ -7,6 +7,9 @@ import frappe
from frappe.utils import add_days, get_last_day, nowdate from frappe.utils import add_days, get_last_day, nowdate
from erpnext.assets.doctype.asset.test_asset import create_asset_data from erpnext.assets.doctype.asset.test_asset import create_asset_data
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
get_asset_depr_schedule_doc,
)
from erpnext.assets.doctype.asset_value_adjustment.asset_value_adjustment import ( from erpnext.assets.doctype.asset_value_adjustment.asset_value_adjustment import (
get_current_asset_value, get_current_asset_value,
) )
@ -73,12 +76,21 @@ class TestAssetValueAdjustment(unittest.TestCase):
) )
asset_doc.submit() asset_doc.submit()
first_asset_depr_schedule = get_asset_depr_schedule_doc(asset_doc.name, "Active")
self.assertEquals(first_asset_depr_schedule.status, "Active")
current_value = get_current_asset_value(asset_doc.name) current_value = get_current_asset_value(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
) )
adj_doc.submit() adj_doc.submit()
first_asset_depr_schedule.load_from_db()
second_asset_depr_schedule = get_asset_depr_schedule_doc(asset_doc.name, "Active")
self.assertEquals(second_asset_depr_schedule.status, "Active")
self.assertEquals(first_asset_depr_schedule.status, "Cancelled")
expected_gle = ( expected_gle = (
("_Test Accumulated Depreciations - _TC", 0.0, 50000.0), ("_Test Accumulated Depreciations - _TC", 0.0, 50000.0),
("_Test Depreciations - _TC", 50000.0, 0.0), ("_Test Depreciations - _TC", 50000.0, 0.0),

View File

@ -1,318 +1,84 @@
{ {
"allow_copy": 0, "actions": [],
"allow_guest_to_view": 0, "allow_rename": 1,
"allow_import": 0, "creation": "2016-03-02 15:11:01.278862",
"allow_rename": 1, "doctype": "DocType",
"autoname": "", "document_type": "Document",
"beta": 0, "editable_grid": 1,
"creation": "2016-03-02 15:11:01.278862", "engine": "InnoDB",
"custom": 0, "field_order": [
"docstatus": 0, "schedule_date",
"doctype": "DocType", "depreciation_amount",
"document_type": "Document", "column_break_3",
"editable_grid": 1, "accumulated_depreciation_amount",
"engine": "InnoDB", "journal_entry",
"make_depreciation_entry",
"depreciation_method"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "fieldname": "schedule_date",
"allow_on_submit": 0, "fieldtype": "Date",
"bold": 0, "in_list_view": 1,
"collapsible": 0, "label": "Schedule Date",
"columns": 0, "reqd": 1
"fieldname": "finance_book", },
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Finance Book",
"length": 0,
"no_copy": 0,
"options": "Finance Book",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "depreciation_amount",
"allow_on_submit": 0, "fieldtype": "Currency",
"bold": 0, "in_list_view": 1,
"collapsible": 0, "label": "Depreciation Amount",
"columns": 0, "options": "Company:company:default_currency",
"fieldname": "schedule_date", "reqd": 1
"fieldtype": "Date", },
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Schedule Date",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "column_break_3",
"allow_on_submit": 0, "fieldtype": "Column Break"
"bold": 0, },
"collapsible": 0,
"columns": 0,
"fieldname": "depreciation_amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Depreciation Amount",
"length": 0,
"no_copy": 1,
"options": "Company:company:default_currency",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "accumulated_depreciation_amount",
"allow_on_submit": 0, "fieldtype": "Currency",
"bold": 0, "in_list_view": 1,
"collapsible": 0, "label": "Accumulated Depreciation Amount",
"columns": 0, "options": "Company:company:default_currency",
"fieldname": "column_break_3", "read_only": 1
"fieldtype": "Column Break", },
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "depends_on": "eval:doc.docstatus==1",
"allow_on_submit": 0, "fieldname": "journal_entry",
"bold": 0, "fieldtype": "Link",
"collapsible": 0, "in_list_view": 1,
"columns": 0, "label": "Journal Entry",
"fieldname": "accumulated_depreciation_amount", "options": "Journal Entry",
"fieldtype": "Currency", "read_only": 1
"hidden": 0, },
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Accumulated Depreciation Amount",
"length": 0,
"no_copy": 1,
"options": "Company:company:default_currency",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "allow_on_submit": 1,
"allow_on_submit": 0, "depends_on": "eval:(doc.docstatus==1 && !doc.journal_entry && doc.schedule_date <= get_today())",
"bold": 0, "fieldname": "make_depreciation_entry",
"collapsible": 0, "fieldtype": "Button",
"columns": 0, "label": "Make Depreciation Entry"
"depends_on": "eval:doc.docstatus==1", },
"fieldname": "journal_entry",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Journal Entry",
"length": 0,
"no_copy": 1,
"options": "Journal Entry",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "depreciation_method",
"allow_on_submit": 1, "fieldtype": "Select",
"bold": 0, "hidden": 1,
"collapsible": 0, "label": "Depreciation Method",
"columns": 0, "options": "\nStraight Line\nDouble Declining Balance\nWritten Down Value\nManual",
"depends_on": "eval:(doc.docstatus==1 && !doc.journal_entry && doc.schedule_date <= get_today())", "print_hide": 1,
"fieldname": "make_depreciation_entry", "read_only": 1
"fieldtype": "Button",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Make Depreciation Entry",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "finance_book_id",
"fieldtype": "Data",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Finance Book Id",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "depreciation_method",
"fieldtype": "Select",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Depreciation Method",
"length": 0,
"no_copy": 1,
"options": "\nStraight Line\nDouble Declining Balance\nWritten Down Value\nManual",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
} }
], ],
"has_web_view": 0, "istable": 1,
"hide_heading": 0, "links": [],
"hide_toolbar": 0, "modified": "2022-12-06 20:35:50.264281",
"idx": 0, "modified_by": "Administrator",
"image_view": 0, "module": "Assets",
"in_create": 0, "name": "Depreciation Schedule",
"is_submittable": 0, "owner": "Administrator",
"issingle": 0, "permissions": [],
"istable": 1, "quick_entry": 1,
"max_attachments": 0, "sort_field": "modified",
"modified": "2018-05-10 15:12:41.679436", "sort_order": "DESC",
"modified_by": "Administrator", "states": []
"module": "Assets",
"name": "Depreciation Schedule",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0
} }

View File

@ -176,15 +176,17 @@ def get_finance_book_value_map(filters):
return frappe._dict( return frappe._dict(
frappe.db.sql( frappe.db.sql(
""" Select """ Select
parent, SUM(depreciation_amount) ads.asset, SUM(depreciation_amount)
FROM `tabDepreciation Schedule` FROM `tabAsset Depreciation Schedule` ads, `tabDepreciation Schedule` ds
WHERE WHERE
parentfield='schedules' ds.parent = ads.name
AND schedule_date<=%s AND ifnull(ads.finance_book, '')=%s
AND journal_entry IS NOT NULL AND ads.docstatus=1
AND ifnull(finance_book, '')=%s AND ds.parentfield='depreciation_schedule'
GROUP BY parent""", AND ds.schedule_date<=%s
(date, cstr(filters.finance_book or "")), AND ds.journal_entry IS NOT NULL
GROUP BY ads.asset""",
(cstr(filters.finance_book or ""), date),
) )
) )

View File

@ -268,6 +268,7 @@ erpnext.patches.v13_0.show_hr_payroll_deprecation_warning
erpnext.patches.v13_0.reset_corrupt_defaults erpnext.patches.v13_0.reset_corrupt_defaults
erpnext.patches.v13_0.create_accounting_dimensions_for_asset_repair erpnext.patches.v13_0.create_accounting_dimensions_for_asset_repair
erpnext.patches.v15_0.delete_taxjar_doctypes erpnext.patches.v15_0.delete_taxjar_doctypes
erpnext.patches.v15_0.create_asset_depreciation_schedules_from_assets
[post_model_sync] [post_model_sync]
execute:frappe.delete_doc_if_exists('Workspace', 'ERPNext Integrations Settings') execute:frappe.delete_doc_if_exists('Workspace', 'ERPNext Integrations Settings')

View File

@ -0,0 +1,80 @@
import frappe
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
set_draft_asset_depr_schedule_details,
)
def execute():
frappe.reload_doc("assets", "doctype", "Asset Depreciation Schedule")
assets = get_details_of_draft_or_submitted_depreciable_assets()
for asset in assets:
finance_book_rows = get_details_of_asset_finance_books_rows(asset.name)
for fb_row in finance_book_rows:
asset_depr_schedule_doc = frappe.new_doc("Asset Depreciation Schedule")
set_draft_asset_depr_schedule_details(asset_depr_schedule_doc, asset, fb_row)
asset_depr_schedule_doc.insert()
if asset.docstatus == 1:
asset_depr_schedule_doc.submit()
update_depreciation_schedules(asset.name, asset_depr_schedule_doc.name, fb_row.idx)
def get_details_of_draft_or_submitted_depreciable_assets():
asset = frappe.qb.DocType("Asset")
records = (
frappe.qb.from_(asset)
.select(asset.name, asset.opening_accumulated_depreciation, asset.docstatus)
.where(asset.calculate_depreciation == 1)
.where(asset.docstatus < 2)
).run(as_dict=True)
return records
def get_details_of_asset_finance_books_rows(asset_name):
afb = frappe.qb.DocType("Asset Finance Book")
records = (
frappe.qb.from_(afb)
.select(
afb.finance_book,
afb.idx,
afb.depreciation_method,
afb.total_number_of_depreciations,
afb.frequency_of_depreciation,
afb.rate_of_depreciation,
afb.expected_value_after_useful_life,
)
.where(afb.parent == asset_name)
).run(as_dict=True)
return records
def update_depreciation_schedules(asset_name, asset_depr_schedule_name, fb_row_idx):
ds = frappe.qb.DocType("Depreciation Schedule")
depr_schedules = (
frappe.qb.from_(ds)
.select(ds.name)
.where((ds.parent == asset_name) & (ds.finance_book_id == str(fb_row_idx)))
.orderby(ds.idx)
).run(as_dict=True)
for idx, depr_schedule in enumerate(depr_schedules, start=1):
(
frappe.qb.update(ds)
.set(ds.idx, idx)
.set(ds.parent, asset_depr_schedule_name)
.set(ds.parentfield, "depreciation_schedule")
.set(ds.parenttype, "Asset Depreciation Schedule")
.where(ds.name == depr_schedule.name)
).run()