chore: rebase from develop
This commit is contained in:
@@ -89,7 +89,7 @@
|
||||
"label": "Entry Type",
|
||||
"oldfieldname": "voucher_type",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "Journal Entry\nInter Company Journal Entry\nBank Entry\nCash Entry\nCredit Card Entry\nDebit Note\nCredit Note\nContra Entry\nExcise Entry\nWrite Off Entry\nOpening Entry\nDepreciation Entry\nExchange Rate Revaluation\nExchange Gain Or Loss\nDeferred Revenue\nDeferred Expense",
|
||||
"options": "Journal Entry\nInter Company Journal Entry\nBank Entry\nCash Entry\nCredit Card Entry\nDebit Note\nCredit Note\nContra Entry\nExcise Entry\nWrite Off Entry\nOpening Entry\nDepreciation Entry\nAsset Disposal\nExchange Rate Revaluation\nExchange Gain Or Loss\nDeferred Revenue\nDeferred Expense",
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
@@ -557,7 +557,7 @@
|
||||
"table_fieldname": "payment_entries"
|
||||
}
|
||||
],
|
||||
"modified": "2024-07-18 15:32:29.413598",
|
||||
"modified": "2024-12-26 15:32:20.730666",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Journal Entry",
|
||||
|
||||
@@ -100,6 +100,7 @@ class JournalEntry(AccountsController):
|
||||
"Write Off Entry",
|
||||
"Opening Entry",
|
||||
"Depreciation Entry",
|
||||
"Asset Disposal",
|
||||
"Exchange Rate Revaluation",
|
||||
"Exchange Gain Or Loss",
|
||||
"Deferred Revenue",
|
||||
@@ -377,7 +378,11 @@ class JournalEntry(AccountsController):
|
||||
self.remove(d)
|
||||
|
||||
def update_asset_value(self):
|
||||
if self.flags.planned_depr_entry or self.voucher_type != "Depreciation Entry":
|
||||
self.update_asset_on_depreciation()
|
||||
self.update_asset_on_disposal()
|
||||
|
||||
def update_asset_on_depreciation(self):
|
||||
if self.voucher_type != "Depreciation Entry":
|
||||
return
|
||||
|
||||
for d in self.get("accounts"):
|
||||
@@ -387,24 +392,61 @@ class JournalEntry(AccountsController):
|
||||
and d.account_type == "Depreciation"
|
||||
and d.debit
|
||||
):
|
||||
asset = frappe.get_doc("Asset", d.reference_name)
|
||||
asset = frappe.get_cached_doc("Asset", d.reference_name)
|
||||
|
||||
if asset.calculate_depreciation:
|
||||
fb_idx = 1
|
||||
if self.finance_book:
|
||||
for fb_row in asset.get("finance_books"):
|
||||
if fb_row.finance_book == self.finance_book:
|
||||
fb_idx = fb_row.idx
|
||||
break
|
||||
fb_row = asset.get("finance_books")[fb_idx - 1]
|
||||
fb_row.value_after_depreciation -= d.debit
|
||||
fb_row.db_update()
|
||||
self.update_journal_entry_link_on_depr_schedule(asset, d)
|
||||
self.update_value_after_depreciation(asset, d.debit)
|
||||
else:
|
||||
asset.db_set("value_after_depreciation", asset.value_after_depreciation - d.debit)
|
||||
|
||||
asset.set_status()
|
||||
asset.set_total_booked_depreciations()
|
||||
|
||||
def update_value_after_depreciation(self, asset, depr_amount):
|
||||
fb_idx = 1
|
||||
if self.finance_book:
|
||||
for fb_row in asset.get("finance_books"):
|
||||
if fb_row.finance_book == self.finance_book:
|
||||
fb_idx = fb_row.idx
|
||||
break
|
||||
fb_row = asset.get("finance_books")[fb_idx - 1]
|
||||
fb_row.value_after_depreciation -= depr_amount
|
||||
frappe.db.set_value(
|
||||
"Asset Finance Book", fb_row.name, "value_after_depreciation", fb_row.value_after_depreciation
|
||||
)
|
||||
|
||||
def update_journal_entry_link_on_depr_schedule(self, asset, je_row):
|
||||
depr_schedule = get_depr_schedule(asset.name, "Active", self.finance_book)
|
||||
for d in depr_schedule or []:
|
||||
if (
|
||||
d.schedule_date == self.posting_date
|
||||
and not d.journal_entry
|
||||
and d.depreciation_amount == flt(je_row.debit)
|
||||
):
|
||||
frappe.db.set_value("Depreciation Schedule", d.name, "journal_entry", self.name)
|
||||
|
||||
def update_asset_on_disposal(self):
|
||||
if self.voucher_type == "Asset Disposal":
|
||||
disposed_assets = []
|
||||
for d in self.get("accounts"):
|
||||
if (
|
||||
d.reference_type == "Asset"
|
||||
and d.reference_name
|
||||
and d.reference_name not in disposed_assets
|
||||
):
|
||||
frappe.db.set_value(
|
||||
"Asset",
|
||||
d.reference_name,
|
||||
{
|
||||
"disposal_date": self.posting_date,
|
||||
"journal_entry_for_scrap": self.name,
|
||||
},
|
||||
)
|
||||
asset_doc = frappe.get_doc("Asset", d.reference_name)
|
||||
asset_doc.set_status()
|
||||
disposed_assets.append(d.reference_name)
|
||||
|
||||
def update_inter_company_jv(self):
|
||||
if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:
|
||||
frappe.db.set_value(
|
||||
|
||||
@@ -39,7 +39,7 @@ from erpnext.assets.doctype.asset.depreciation import (
|
||||
get_gl_entries_on_asset_disposal,
|
||||
get_gl_entries_on_asset_regain,
|
||||
reset_depreciation_schedule,
|
||||
reverse_depreciation_entry_made_after_disposal,
|
||||
reverse_depreciation_entry_made_on_disposal,
|
||||
)
|
||||
from erpnext.assets.doctype.asset_activity.asset_activity import add_asset_activity
|
||||
from erpnext.controllers.accounts_controller import validate_account_head
|
||||
@@ -368,21 +368,34 @@ class SalesInvoice(SellingController):
|
||||
validate_docs_for_deferred_accounting([self.name], [])
|
||||
|
||||
def validate_fixed_asset(self):
|
||||
for d in self.get("items"):
|
||||
if d.is_fixed_asset and d.meta.get_field("asset") and d.asset:
|
||||
asset = frappe.get_doc("Asset", d.asset)
|
||||
if self.doctype == "Sales Invoice" and self.docstatus == 1:
|
||||
if self.update_stock:
|
||||
frappe.throw(_("'Update Stock' cannot be checked for fixed asset sale"))
|
||||
if self.doctype != "Sales Invoice":
|
||||
return
|
||||
|
||||
elif asset.status in ("Scrapped", "Cancelled", "Capitalized") or (
|
||||
asset.status == "Sold" and not self.is_return
|
||||
):
|
||||
frappe.throw(
|
||||
_("Row #{0}: Asset {1} cannot be submitted, it is already {2}").format(
|
||||
d.idx, d.asset, asset.status
|
||||
for d in self.get("items"):
|
||||
if d.is_fixed_asset:
|
||||
if d.asset:
|
||||
if not self.is_return:
|
||||
asset_status = frappe.db.get_value("Asset", d.asset, "status")
|
||||
if self.update_stock:
|
||||
frappe.throw(_("'Update Stock' cannot be checked for fixed asset sale"))
|
||||
|
||||
elif asset_status in ("Scrapped", "Cancelled", "Capitalized"):
|
||||
frappe.throw(
|
||||
_("Row #{0}: Asset {1} cannot be sold, it is already {2}").format(
|
||||
d.idx, d.asset, asset_status
|
||||
)
|
||||
)
|
||||
elif asset_status == "Sold" and not self.is_return:
|
||||
frappe.throw(_("Row #{0}: Asset {1} is already sold").format(d.idx, d.asset))
|
||||
elif not self.return_against:
|
||||
frappe.throw(
|
||||
_("Row #{0}: Return Against is required for returning asset").format(d.idx)
|
||||
)
|
||||
else:
|
||||
frappe.throw(
|
||||
_("Row #{0}: You must select an Asset for Item {1}.").format(d.idx, d.item_code),
|
||||
title=_("Missing Asset"),
|
||||
)
|
||||
|
||||
def validate_item_cost_centers(self):
|
||||
for item in self.items:
|
||||
@@ -464,6 +477,8 @@ class SalesInvoice(SellingController):
|
||||
self.update_stock_reservation_entries()
|
||||
self.update_stock_ledger()
|
||||
|
||||
self.process_asset_depreciation()
|
||||
|
||||
# this sequence because outstanding may get -ve
|
||||
self.make_gl_entries()
|
||||
|
||||
@@ -583,6 +598,8 @@ class SalesInvoice(SellingController):
|
||||
if self.update_stock == 1:
|
||||
self.update_stock_ledger()
|
||||
|
||||
self.process_asset_depreciation()
|
||||
|
||||
self.make_gl_entries_on_cancel()
|
||||
|
||||
if self.update_stock == 1:
|
||||
@@ -1253,6 +1270,90 @@ class SalesInvoice(SellingController):
|
||||
):
|
||||
throw(_("Delivery Note {0} is not submitted").format(d.delivery_note))
|
||||
|
||||
def process_asset_depreciation(self):
|
||||
if (self.is_return and self.docstatus == 2) or (not self.is_return and self.docstatus == 1):
|
||||
self.depreciate_asset_on_sale()
|
||||
else:
|
||||
self.restore_asset()
|
||||
|
||||
self.update_asset()
|
||||
|
||||
def depreciate_asset_on_sale(self):
|
||||
"""
|
||||
Depreciate asset on sale or cancellation of return sales invoice
|
||||
"""
|
||||
disposal_date = self.get_disposal_date()
|
||||
for d in self.get("items"):
|
||||
if d.asset:
|
||||
asset = frappe.get_doc("Asset", d.asset)
|
||||
if asset.calculate_depreciation and asset.status != "Fully Depreciated":
|
||||
depreciate_asset(asset, disposal_date, self.get_note_for_asset_sale(asset))
|
||||
|
||||
def get_note_for_asset_sale(self, asset):
|
||||
return _("This schedule was created when Asset {0} was {1} through Sales Invoice {2}.").format(
|
||||
get_link_to_form(asset.doctype, asset.name),
|
||||
_("returned") if self.is_return else _("sold"),
|
||||
get_link_to_form(self.doctype, self.get("name")),
|
||||
)
|
||||
|
||||
def restore_asset(self):
|
||||
"""
|
||||
Restore asset on return or cancellation of original sales invoice
|
||||
"""
|
||||
|
||||
for d in self.get("items"):
|
||||
if d.asset:
|
||||
asset = frappe.get_cached_doc("Asset", d.asset)
|
||||
if asset.calculate_depreciation:
|
||||
reverse_depreciation_entry_made_on_disposal(asset)
|
||||
|
||||
note = self.get_note_for_asset_return(asset)
|
||||
reset_depreciation_schedule(asset, note)
|
||||
|
||||
def get_note_for_asset_return(self, asset):
|
||||
asset_link = get_link_to_form(asset.doctype, asset.name)
|
||||
invoice_link = get_link_to_form(self.doctype, self.get("name"))
|
||||
if self.is_return:
|
||||
return _(
|
||||
"This schedule was created when Asset {0} was returned through Sales Invoice {1}."
|
||||
).format(asset_link, invoice_link)
|
||||
else:
|
||||
return _(
|
||||
"This schedule was created when Asset {0} was restored due to Sales Invoice {1} cancellation."
|
||||
).format(asset_link, invoice_link)
|
||||
|
||||
def update_asset(self):
|
||||
"""
|
||||
Update asset status, disposal date and asset activity on sale or return sales invoice
|
||||
"""
|
||||
|
||||
def _update_asset(asset, disposal_date, note, asset_status=None):
|
||||
frappe.db.set_value("Asset", d.asset, "disposal_date", disposal_date)
|
||||
add_asset_activity(asset.name, note)
|
||||
asset.set_status(asset_status)
|
||||
|
||||
disposal_date = self.get_disposal_date()
|
||||
for d in self.get("items"):
|
||||
if d.asset:
|
||||
asset = frappe.get_cached_doc("Asset", d.asset)
|
||||
|
||||
if (self.is_return and self.docstatus == 1) or (not self.is_return and self.docstatus == 2):
|
||||
note = _("Asset returned") if self.is_return else _("Asset sold")
|
||||
asset_status, disposal_date = None, None
|
||||
else:
|
||||
note = _("Asset sold") if not self.is_return else _("Return invoice of asset cancelled")
|
||||
asset_status = "Sold"
|
||||
|
||||
_update_asset(asset, disposal_date, note, asset_status)
|
||||
|
||||
def get_disposal_date(self):
|
||||
if self.is_return:
|
||||
disposal_date = frappe.db.get_value("Sales Invoice", self.return_against, "posting_date")
|
||||
else:
|
||||
disposal_date = self.posting_date
|
||||
|
||||
return disposal_date
|
||||
|
||||
def make_gl_entries(self, gl_entries=None, from_repost=False):
|
||||
from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries
|
||||
|
||||
@@ -1426,64 +1527,8 @@ class SalesInvoice(SellingController):
|
||||
if self.is_internal_transfer():
|
||||
continue
|
||||
|
||||
if item.is_fixed_asset:
|
||||
asset = self.get_asset(item)
|
||||
|
||||
if self.is_return:
|
||||
fixed_asset_gl_entries = get_gl_entries_on_asset_regain(
|
||||
asset,
|
||||
item.base_net_amount,
|
||||
item.finance_book,
|
||||
self.get("doctype"),
|
||||
self.get("name"),
|
||||
self.get("posting_date"),
|
||||
)
|
||||
asset.db_set("disposal_date", None)
|
||||
add_asset_activity(asset.name, _("Asset returned"))
|
||||
|
||||
if asset.calculate_depreciation:
|
||||
posting_date = frappe.db.get_value(
|
||||
"Sales Invoice", self.return_against, "posting_date"
|
||||
)
|
||||
reverse_depreciation_entry_made_after_disposal(asset, posting_date)
|
||||
notes = _(
|
||||
"This schedule was created when Asset {0} was returned 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:
|
||||
if asset.calculate_depreciation:
|
||||
if not asset.status == "Fully Depreciated":
|
||||
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()
|
||||
|
||||
fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(
|
||||
asset,
|
||||
item.base_net_amount,
|
||||
item.finance_book,
|
||||
self.get("doctype"),
|
||||
self.get("name"),
|
||||
self.get("posting_date"),
|
||||
)
|
||||
asset.db_set("disposal_date", self.posting_date)
|
||||
add_asset_activity(asset.name, _("Asset sold"))
|
||||
|
||||
for gle in fixed_asset_gl_entries:
|
||||
gle["against"] = self.customer
|
||||
gl_entries.append(self.get_gl_dict(gle, item=item))
|
||||
|
||||
self.set_asset_status(asset)
|
||||
|
||||
if item.is_fixed_asset and item.asset:
|
||||
self.get_gl_entries_for_fixed_asset(item, gl_entries)
|
||||
else:
|
||||
income_account = (
|
||||
item.income_account
|
||||
@@ -1518,17 +1563,31 @@ class SalesInvoice(SellingController):
|
||||
if cint(self.update_stock) and erpnext.is_perpetual_inventory_enabled(self.company):
|
||||
gl_entries += super().get_gl_entries()
|
||||
|
||||
def get_asset(self, item):
|
||||
if item.get("asset"):
|
||||
asset = frappe.get_doc("Asset", item.asset)
|
||||
def get_gl_entries_for_fixed_asset(self, item, gl_entries):
|
||||
asset = frappe.get_cached_doc("Asset", item.asset)
|
||||
|
||||
if self.is_return:
|
||||
fixed_asset_gl_entries = get_gl_entries_on_asset_regain(
|
||||
asset,
|
||||
item.base_net_amount,
|
||||
item.finance_book,
|
||||
self.get("doctype"),
|
||||
self.get("name"),
|
||||
self.get("posting_date"),
|
||||
)
|
||||
else:
|
||||
frappe.throw(
|
||||
_("Row #{0}: You must select an Asset for Item {1}.").format(item.idx, item.item_name),
|
||||
title=_("Missing Asset"),
|
||||
fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(
|
||||
asset,
|
||||
item.base_net_amount,
|
||||
item.finance_book,
|
||||
self.get("doctype"),
|
||||
self.get("name"),
|
||||
self.get("posting_date"),
|
||||
)
|
||||
|
||||
self.check_finance_books(item, asset)
|
||||
return asset
|
||||
for gle in fixed_asset_gl_entries:
|
||||
gle["against"] = self.customer
|
||||
gl_entries.append(self.get_gl_dict(gle, item=item))
|
||||
|
||||
@property
|
||||
def enable_discount_accounting(self):
|
||||
@@ -1539,12 +1598,6 @@ class SalesInvoice(SellingController):
|
||||
|
||||
return self._enable_discount_accounting
|
||||
|
||||
def set_asset_status(self, asset):
|
||||
if self.is_return:
|
||||
asset.set_status()
|
||||
else:
|
||||
asset.set_status("Sold" if self.docstatus == 1 else None)
|
||||
|
||||
def make_loyalty_point_redemption_gle(self, gl_entries):
|
||||
if cint(self.redeem_loyalty_points and self.loyalty_points and not self.is_consolidated):
|
||||
gl_entries.append(
|
||||
|
||||
@@ -103,14 +103,26 @@ frappe.ui.form.on("Asset", {
|
||||
},
|
||||
__("Manage")
|
||||
);
|
||||
} else if (frm.doc.status == "Scrapped") {
|
||||
|
||||
frm.add_custom_button(
|
||||
__("Restore Asset"),
|
||||
__("Repair Asset"),
|
||||
function () {
|
||||
erpnext.asset.restore_asset(frm);
|
||||
frm.trigger("create_asset_repair");
|
||||
},
|
||||
__("Manage")
|
||||
);
|
||||
|
||||
frm.add_custom_button(
|
||||
__("Split Asset"),
|
||||
function () {
|
||||
frm.trigger("split_asset");
|
||||
},
|
||||
__("Manage")
|
||||
);
|
||||
} else if (frm.doc.status == "Scrapped") {
|
||||
frm.add_custom_button(__("Restore Asset"), function () {
|
||||
erpnext.asset.restore_asset(frm);
|
||||
}).addClass("btn-primary");
|
||||
}
|
||||
|
||||
if (frm.doc.maintenance_required && !frm.doc.maintenance_schedule) {
|
||||
@@ -123,23 +135,7 @@ frappe.ui.form.on("Asset", {
|
||||
);
|
||||
}
|
||||
|
||||
frm.add_custom_button(
|
||||
__("Repair Asset"),
|
||||
function () {
|
||||
frm.trigger("create_asset_repair");
|
||||
},
|
||||
__("Manage")
|
||||
);
|
||||
|
||||
frm.add_custom_button(
|
||||
__("Split Asset"),
|
||||
function () {
|
||||
frm.trigger("split_asset");
|
||||
},
|
||||
__("Manage")
|
||||
);
|
||||
|
||||
if (frm.doc.status != "Fully Depreciated") {
|
||||
if (in_list(["Submitted", "Partially Depreciated"], frm.doc.status)) {
|
||||
frm.add_custom_button(
|
||||
__("Adjust Asset Value"),
|
||||
function () {
|
||||
|
||||
@@ -33,8 +33,6 @@ from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_sched
|
||||
convert_draft_asset_depr_schedules_into_active,
|
||||
get_asset_depr_schedule_doc,
|
||||
get_depr_schedule,
|
||||
make_draft_asset_depr_schedules,
|
||||
update_draft_asset_depr_schedules,
|
||||
)
|
||||
from erpnext.controllers.accounts_controller import AccountsController
|
||||
|
||||
@@ -148,22 +146,23 @@ class Asset(AccountsController):
|
||||
schedule_doc = get_asset_depr_schedule_doc(self.name, "Draft", row.finance_book)
|
||||
if not schedule_doc:
|
||||
schedule_doc = frappe.new_doc("Asset Depreciation Schedule")
|
||||
|
||||
schedule_doc.prepare_draft_asset_depr_schedule_data(self, row)
|
||||
schedule_doc.asset = self.name
|
||||
schedule_doc.create_depreciation_schedule(row)
|
||||
schedule_doc.save()
|
||||
schedules.append(schedule_doc.name)
|
||||
|
||||
self.show_schedule_creation_message(schedules)
|
||||
|
||||
def set_depr_rate_and_value_after_depreciation(self):
|
||||
self.value_after_depreciation = flt(self.gross_purchase_amount) - flt(
|
||||
self.opening_accumulated_depreciation
|
||||
)
|
||||
if self.calculate_depreciation:
|
||||
self.value_after_depreciation = 0
|
||||
self.set_depreciation_rate()
|
||||
for d in self.finance_books:
|
||||
d.value_after_depreciation = self.value_after_depreciation
|
||||
else:
|
||||
self.finance_books = []
|
||||
self.value_after_depreciation = flt(self.gross_purchase_amount) - flt(
|
||||
self.opening_accumulated_depreciation
|
||||
)
|
||||
|
||||
def show_schedule_creation_message(self, schedules):
|
||||
if schedules:
|
||||
@@ -845,41 +844,31 @@ class Asset(AccountsController):
|
||||
)
|
||||
|
||||
def get_written_down_value_rate(self, args, rate_field_precision, on_validate):
|
||||
if (
|
||||
args.get("rate_of_depreciation")
|
||||
and on_validate
|
||||
and not self.flags.increase_in_asset_value_due_to_repair
|
||||
):
|
||||
if args.get("rate_of_depreciation") and on_validate:
|
||||
return args.get("rate_of_depreciation")
|
||||
|
||||
if args.get("rate_of_depreciation") and not flt(args.get("expected_value_after_useful_life")):
|
||||
return args.get("rate_of_depreciation")
|
||||
|
||||
if self.flags.increase_in_asset_value_due_to_repair:
|
||||
value = flt(args.get("expected_value_after_useful_life")) / flt(
|
||||
args.get("value_after_depreciation")
|
||||
)
|
||||
if flt(args.get("value_after_depreciation")):
|
||||
current_asset_value = flt(args.get("value_after_depreciation"))
|
||||
else:
|
||||
value = flt(args.get("expected_value_after_useful_life")) / (
|
||||
flt(self.gross_purchase_amount) - flt(self.opening_accumulated_depreciation)
|
||||
)
|
||||
current_asset_value = flt(self.gross_purchase_amount) - flt(self.opening_accumulated_depreciation)
|
||||
|
||||
depreciation_rate = math.pow(
|
||||
value,
|
||||
1.0
|
||||
/ (
|
||||
(
|
||||
(
|
||||
flt(args.get("total_number_of_depreciations"), 2)
|
||||
- flt(self.opening_number_of_booked_depreciations)
|
||||
)
|
||||
* flt(args.get("frequency_of_depreciation"))
|
||||
)
|
||||
/ 12
|
||||
),
|
||||
value = flt(args.get("expected_value_after_useful_life")) / current_asset_value
|
||||
|
||||
pending_number_of_depreciations = (
|
||||
flt(args.get("total_number_of_depreciations"), 2)
|
||||
- flt(self.opening_number_of_booked_depreciations)
|
||||
- flt(args.get("total_number_of_booked_depreciations"))
|
||||
)
|
||||
pending_years = (
|
||||
pending_number_of_depreciations * flt(args.get("frequency_of_depreciation"))
|
||||
+ cint(args.get("increase_in_asset_life"))
|
||||
) / 12
|
||||
|
||||
return flt((100 * (1 - depreciation_rate)), rate_field_precision)
|
||||
depreciation_rate = 100 * (1 - math.pow(value, 1.0 / pending_years))
|
||||
return flt(depreciation_rate, rate_field_precision)
|
||||
|
||||
|
||||
def has_gl_entries(doctype, docname, target_account):
|
||||
@@ -1253,7 +1242,7 @@ def update_existing_asset(asset, remaining_qty, new_asset_name):
|
||||
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)
|
||||
|
||||
new_asset_depr_schedule_doc.set_draft_asset_depr_schedule_details(asset, row)
|
||||
new_asset_depr_schedule_doc.fetch_asset_details(asset, row)
|
||||
|
||||
accumulated_depreciation = 0
|
||||
|
||||
@@ -1310,7 +1299,7 @@ def create_new_asset_after_split(asset, split_qty):
|
||||
continue
|
||||
new_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc)
|
||||
|
||||
new_asset_depr_schedule_doc.set_draft_asset_depr_schedule_details(new_asset, row)
|
||||
new_asset_depr_schedule_doc.fetch_asset_details(new_asset, row)
|
||||
|
||||
accumulated_depreciation = 0
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_sched
|
||||
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,
|
||||
reschedule_depreciation,
|
||||
)
|
||||
|
||||
|
||||
@@ -136,10 +136,10 @@ def get_depreciable_asset_depr_schedules_data(date):
|
||||
return res
|
||||
|
||||
|
||||
def make_depreciation_entry_for_all_asset_depr_schedules(asset_doc, date=None):
|
||||
def make_depreciation_entry_on_disposal(asset_doc, disposal_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)
|
||||
make_depreciation_entry(asset_depr_schedule_name, disposal_date)
|
||||
|
||||
|
||||
def get_acc_frozen_upto():
|
||||
@@ -244,10 +244,12 @@ def make_depreciation_entry(
|
||||
except Exception as e:
|
||||
depreciation_posting_error = e
|
||||
|
||||
asset.reload()
|
||||
asset.set_status()
|
||||
|
||||
if not depreciation_posting_error:
|
||||
asset.db_set("depr_entry_posting_status", "Successful")
|
||||
asset_depr_schedule_doc.reload()
|
||||
return asset_depr_schedule_doc
|
||||
|
||||
raise depreciation_posting_error
|
||||
@@ -316,18 +318,10 @@ def _make_journal_entry_for_depreciation(
|
||||
je.append("accounts", debit_entry)
|
||||
|
||||
je.flags.ignore_permissions = True
|
||||
je.flags.planned_depr_entry = True
|
||||
je.save()
|
||||
|
||||
depr_schedule.db_set("journal_entry", je.name)
|
||||
|
||||
if not je.meta.get_workflow():
|
||||
je.submit()
|
||||
asset.reload()
|
||||
idx = cint(asset_depr_schedule_doc.finance_book_id)
|
||||
row = asset.get("finance_books")[idx - 1]
|
||||
row.value_after_depreciation -= depr_schedule.depreciation_amount
|
||||
row.db_update()
|
||||
|
||||
|
||||
def get_depreciation_accounts(asset_category, company):
|
||||
@@ -433,194 +427,162 @@ def get_comma_separated_links(names, doctype):
|
||||
@frappe.whitelist()
|
||||
def scrap_asset(asset_name, scrap_date=None):
|
||||
asset = frappe.get_doc("Asset", asset_name)
|
||||
scrap_date = getdate(scrap_date) or getdate(today())
|
||||
asset.db_set("disposal_date", scrap_date)
|
||||
validate_asset_for_scrap(asset, scrap_date)
|
||||
|
||||
depreciate_asset(asset, scrap_date, get_note_for_scrap(asset))
|
||||
asset.reload()
|
||||
|
||||
create_journal_entry_for_scrap(asset, scrap_date)
|
||||
|
||||
|
||||
def validate_asset_for_scrap(asset, scrap_date):
|
||||
if asset.docstatus != 1:
|
||||
frappe.throw(_("Asset {0} must be submitted").format(asset.name))
|
||||
elif asset.status in ("Cancelled", "Sold", "Scrapped", "Capitalized"):
|
||||
frappe.throw(_("Asset {0} cannot be scrapped, as it is already {1}").format(asset.name, asset.status))
|
||||
|
||||
today_date = getdate(today())
|
||||
date = getdate(scrap_date) or today_date
|
||||
purchase_date = getdate(asset.purchase_date)
|
||||
validate_scrap_date(asset, scrap_date)
|
||||
|
||||
validate_scrap_date(date, today_date, purchase_date, asset.calculate_depreciation, asset_name)
|
||||
|
||||
notes = _("This schedule was created when Asset {0} was scrapped.").format(
|
||||
def validate_scrap_date(asset, scrap_date):
|
||||
if scrap_date > getdate():
|
||||
frappe.throw(_("Future date is not allowed"))
|
||||
elif scrap_date < getdate(asset.purchase_date):
|
||||
frappe.throw(_("Scrap date cannot be before purchase date"))
|
||||
|
||||
if asset.calculate_depreciation:
|
||||
last_booked_depreciation_date = get_last_depreciation_date(asset.name)
|
||||
if (
|
||||
last_booked_depreciation_date
|
||||
and scrap_date < last_booked_depreciation_date
|
||||
and scrap_date > getdate(asset.purchase_date)
|
||||
):
|
||||
frappe.throw(_("Asset cannot be scrapped before the last depreciation entry."))
|
||||
|
||||
|
||||
def get_last_depreciation_date(asset_name):
|
||||
depreciation = frappe.qb.DocType("Asset Depreciation Schedule")
|
||||
depreciation_schedule = frappe.qb.DocType("Depreciation Schedule")
|
||||
|
||||
last_depreciation_date = (
|
||||
frappe.qb.from_(depreciation)
|
||||
.join(depreciation_schedule)
|
||||
.on(depreciation.name == depreciation_schedule.parent)
|
||||
.select(depreciation_schedule.schedule_date)
|
||||
.where(depreciation.asset == asset_name)
|
||||
.where(depreciation.docstatus == 1)
|
||||
.where(depreciation_schedule.journal_entry != "")
|
||||
.orderby(depreciation_schedule.schedule_date, order=Order.desc)
|
||||
.limit(1)
|
||||
.run()
|
||||
)
|
||||
|
||||
return last_depreciation_date[0][0] if last_depreciation_date else None
|
||||
|
||||
|
||||
def get_note_for_scrap(asset):
|
||||
return _("This schedule was created when Asset {0} was scrapped.").format(
|
||||
get_link_to_form(asset.doctype, asset.name)
|
||||
)
|
||||
if asset.status != "Fully Depreciated":
|
||||
depreciate_asset(asset, date, notes)
|
||||
asset.reload()
|
||||
|
||||
|
||||
def create_journal_entry_for_scrap(asset, scrap_date):
|
||||
depreciation_series = frappe.get_cached_value("Company", asset.company, "series_for_depreciation_entry")
|
||||
|
||||
je = frappe.new_doc("Journal Entry")
|
||||
je.voucher_type = "Journal Entry"
|
||||
je.voucher_type = "Asset Disposal"
|
||||
je.naming_series = depreciation_series
|
||||
je.posting_date = date
|
||||
je.posting_date = scrap_date
|
||||
je.company = asset.company
|
||||
je.remark = f"Scrap Entry for asset {asset_name}"
|
||||
je.remark = f"Scrap Entry for asset {asset.name}"
|
||||
|
||||
for entry in get_gl_entries_on_asset_disposal(asset, date):
|
||||
entry.update({"reference_type": "Asset", "reference_name": asset_name})
|
||||
for entry in get_gl_entries_on_asset_disposal(asset, scrap_date):
|
||||
entry.update({"reference_type": "Asset", "reference_name": asset.name})
|
||||
je.append("accounts", entry)
|
||||
|
||||
je.flags.ignore_permissions = True
|
||||
je.submit()
|
||||
je.save()
|
||||
if not je.meta.get_workflow():
|
||||
je.submit()
|
||||
|
||||
frappe.db.set_value("Asset", asset_name, "disposal_date", date)
|
||||
frappe.db.set_value("Asset", asset_name, "journal_entry_for_scrap", je.name)
|
||||
asset.set_status("Scrapped")
|
||||
|
||||
add_asset_activity(asset_name, _("Asset scrapped"))
|
||||
|
||||
frappe.msgprint(_("Asset scrapped via Journal Entry {0}").format(je.name))
|
||||
|
||||
|
||||
def validate_scrap_date(scrap_date, today_date, purchase_date, calculate_depreciation, asset_name):
|
||||
if scrap_date > today_date:
|
||||
frappe.throw(_("Future date is not allowed"))
|
||||
elif scrap_date < purchase_date:
|
||||
frappe.throw(_("Scrap date cannot be before purchase date"))
|
||||
|
||||
if calculate_depreciation:
|
||||
asset_depreciation_schedules = frappe.db.get_all(
|
||||
"Asset Depreciation Schedule", filters={"asset": asset_name, "docstatus": 1}, fields=["name"]
|
||||
)
|
||||
|
||||
for depreciation_schedule in asset_depreciation_schedules:
|
||||
last_booked_depreciation_date = frappe.db.get_value(
|
||||
"Depreciation Schedule",
|
||||
{
|
||||
"parent": depreciation_schedule["name"],
|
||||
"docstatus": 1,
|
||||
"journal_entry": ["!=", ""],
|
||||
},
|
||||
"schedule_date",
|
||||
order_by="schedule_date desc",
|
||||
)
|
||||
if (
|
||||
last_booked_depreciation_date
|
||||
and scrap_date < last_booked_depreciation_date
|
||||
and scrap_date > purchase_date
|
||||
):
|
||||
frappe.throw(_("Asset cannot be scrapped before the last depreciation entry."))
|
||||
add_asset_activity(asset.name, _("Asset scrapped"))
|
||||
frappe.msgprint(
|
||||
_("Asset scrapped via Journal Entry {0}").format(get_link_to_form("Journal Entry", je.name))
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def restore_asset(asset_name):
|
||||
asset = frappe.get_doc("Asset", asset_name)
|
||||
reverse_depreciation_entry_made_on_disposal(asset)
|
||||
reset_depreciation_schedule(asset, get_note_for_restore(asset))
|
||||
cancel_journal_entry_for_scrap(asset)
|
||||
asset.set_status()
|
||||
add_asset_activity(asset_name, _("Asset restored"))
|
||||
|
||||
reverse_depreciation_entry_made_after_disposal(asset, asset.disposal_date)
|
||||
|
||||
je = asset.journal_entry_for_scrap
|
||||
|
||||
notes = _("This schedule was created when Asset {0} was restored.").format(
|
||||
def get_note_for_restore(asset):
|
||||
return _("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("journal_entry_for_scrap", None)
|
||||
|
||||
frappe.get_doc("Journal Entry", je).cancel()
|
||||
|
||||
asset.set_status()
|
||||
|
||||
add_asset_activity(asset_name, _("Asset restored"))
|
||||
def cancel_journal_entry_for_scrap(asset):
|
||||
if asset.journal_entry_for_scrap:
|
||||
je = asset.journal_entry_for_scrap
|
||||
asset.db_set("disposal_date", None)
|
||||
asset.db_set("journal_entry_for_scrap", None)
|
||||
frappe.get_doc("Journal Entry", je).cancel()
|
||||
|
||||
|
||||
def depreciate_asset(asset_doc, date, notes):
|
||||
if not asset_doc.calculate_depreciation:
|
||||
return
|
||||
|
||||
asset_doc.flags.ignore_validate_update_after_submit = True
|
||||
|
||||
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)
|
||||
reschedule_depreciation(asset_doc, notes, disposal_date=date)
|
||||
make_depreciation_entry_on_disposal(asset_doc, date)
|
||||
|
||||
# As per Income Tax Act (India), the asset should not be depreciated
|
||||
# in the financial year in which it is sold/scraped
|
||||
asset_doc.reload()
|
||||
cancel_depreciation_entries(asset_doc, date)
|
||||
# cancel_depreciation_entries(asset_doc, date)
|
||||
|
||||
|
||||
@erpnext.allow_regional
|
||||
def cancel_depreciation_entries(asset_doc, date):
|
||||
# Cancel all depreciation entries for the current financial year
|
||||
# if the asset is sold/scraped in the current financial year
|
||||
# Overwritten via India Compliance app
|
||||
pass
|
||||
|
||||
|
||||
def reset_depreciation_schedule(asset_doc, date, notes):
|
||||
if not asset_doc.calculate_depreciation:
|
||||
return
|
||||
|
||||
asset_doc.flags.ignore_validate_update_after_submit = True
|
||||
|
||||
make_new_active_asset_depr_schedules_and_cancel_current_ones(asset_doc, notes, date_of_return=date)
|
||||
|
||||
modify_depreciation_schedule_for_asset_repairs(asset_doc, notes)
|
||||
|
||||
asset_doc.save()
|
||||
def reset_depreciation_schedule(asset_doc, notes):
|
||||
if asset_doc.calculate_depreciation:
|
||||
reschedule_depreciation(asset_doc, notes)
|
||||
|
||||
|
||||
def modify_depreciation_schedule_for_asset_repairs(asset, notes):
|
||||
asset_repairs = frappe.get_all(
|
||||
"Asset Repair", filters={"asset": asset.name}, fields=["name", "increase_in_asset_life"]
|
||||
)
|
||||
|
||||
for repair in asset_repairs:
|
||||
if repair.increase_in_asset_life:
|
||||
asset_repair = frappe.get_doc("Asset Repair", repair.name)
|
||||
asset_repair.modify_depreciation_schedule()
|
||||
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_on_disposal(asset):
|
||||
for row in asset.get("finance_books"):
|
||||
asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active", row.finance_book)
|
||||
if not asset_depr_schedule_doc or not asset_depr_schedule_doc.get("depreciation_schedule"):
|
||||
schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active", row.finance_book)
|
||||
if not schedule_doc or not schedule_doc.get("depreciation_schedule"):
|
||||
continue
|
||||
|
||||
for schedule_idx, schedule in enumerate(asset_depr_schedule_doc.get("depreciation_schedule")):
|
||||
if schedule.schedule_date == date and schedule.journal_entry:
|
||||
for schedule_idx, schedule in enumerate(schedule_doc.get("depreciation_schedule")):
|
||||
if schedule.schedule_date == asset.disposal_date and schedule.journal_entry:
|
||||
if not disposal_was_made_on_original_schedule_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.posting_date = nowdate()
|
||||
|
||||
for account in reverse_journal_entry.accounts:
|
||||
account.update(
|
||||
{
|
||||
"reference_type": "Asset",
|
||||
"reference_name": asset.name,
|
||||
}
|
||||
)
|
||||
|
||||
frappe.flags.is_reverse_depr_entry = True
|
||||
reverse_journal_entry.submit()
|
||||
|
||||
frappe.flags.is_reverse_depr_entry = False
|
||||
asset_depr_schedule_doc.flags.ignore_validate_update_after_submit = True
|
||||
asset.flags.ignore_validate_update_after_submit = True
|
||||
schedule.journal_entry = None
|
||||
depreciation_amount = get_depreciation_amount_in_je(reverse_journal_entry)
|
||||
row.value_after_depreciation += depreciation_amount
|
||||
asset_depr_schedule_doc.save()
|
||||
asset.save()
|
||||
schedule_idx, row, asset.disposal_date
|
||||
) or disposal_happens_in_the_future(asset.disposal_date):
|
||||
je = create_reverse_depreciation_entry(asset.name, schedule.journal_entry)
|
||||
update_value_after_depreciation_on_asset_restore(schedule, row, je)
|
||||
|
||||
|
||||
def get_depreciation_amount_in_je(journal_entry):
|
||||
if journal_entry.accounts[0].debit_in_account_currency:
|
||||
return journal_entry.accounts[0].debit_in_account_currency
|
||||
else:
|
||||
return journal_entry.accounts[0].credit_in_account_currency
|
||||
|
||||
|
||||
# if the invoice had been posted on the date the depreciation was initially supposed to happen, the depreciation shouldn't be undone
|
||||
def disposal_was_made_on_original_schedule_date(schedule_idx, row, posting_date_of_disposal):
|
||||
def disposal_was_made_on_original_schedule_date(schedule_idx, row, disposal_date):
|
||||
"""
|
||||
If asset is scrapped or sold on original schedule date,
|
||||
then the depreciation entry should not be reversed.
|
||||
"""
|
||||
orginal_schedule_date = add_months(
|
||||
row.depreciation_start_date, schedule_idx * cint(row.frequency_of_depreciation)
|
||||
)
|
||||
@@ -628,19 +590,57 @@ def disposal_was_made_on_original_schedule_date(schedule_idx, row, posting_date_
|
||||
if is_last_day_of_the_month(row.depreciation_start_date):
|
||||
orginal_schedule_date = get_last_day(orginal_schedule_date)
|
||||
|
||||
if orginal_schedule_date == posting_date_of_disposal:
|
||||
if orginal_schedule_date == disposal_date:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def disposal_happens_in_the_future(posting_date_of_disposal):
|
||||
if posting_date_of_disposal > getdate():
|
||||
def disposal_happens_in_the_future(disposal_date):
|
||||
if disposal_date > getdate():
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def create_reverse_depreciation_entry(asset_name, journal_entry):
|
||||
reverse_journal_entry = make_reverse_journal_entry(journal_entry)
|
||||
reverse_journal_entry.posting_date = nowdate()
|
||||
|
||||
for account in reverse_journal_entry.accounts:
|
||||
account.update(
|
||||
{
|
||||
"reference_type": "Asset",
|
||||
"reference_name": asset_name,
|
||||
}
|
||||
)
|
||||
|
||||
frappe.flags.is_reverse_depr_entry = True
|
||||
if not reverse_journal_entry.meta.get_workflow():
|
||||
reverse_journal_entry.submit()
|
||||
return reverse_journal_entry
|
||||
else:
|
||||
frappe.throw(
|
||||
_("Please disable workflow temporarily for Journal Entry {0}").format(reverse_journal_entry.name)
|
||||
)
|
||||
|
||||
|
||||
def update_value_after_depreciation_on_asset_restore(schedule, row, journal_entry):
|
||||
frappe.db.set_value("Depreciation Schedule", schedule.name, "journal_entry", None, update_modified=False)
|
||||
depreciation_amount = get_depreciation_amount_in_je(journal_entry)
|
||||
value_after_depreciation = flt(
|
||||
row.value_after_depreciation + depreciation_amount, row.precision("value_after_depreciation")
|
||||
)
|
||||
row.db_set("value_after_depreciation", value_after_depreciation)
|
||||
|
||||
|
||||
def get_depreciation_amount_in_je(journal_entry):
|
||||
if journal_entry.accounts[0].debit_in_account_currency:
|
||||
return journal_entry.accounts[0].debit_in_account_currency
|
||||
else:
|
||||
return journal_entry.accounts[0].credit_in_account_currency
|
||||
|
||||
|
||||
def get_gl_entries_on_asset_regain(
|
||||
asset, selling_amount=0, finance_book=None, voucher_type=None, voucher_no=None, date=None
|
||||
):
|
||||
|
||||
@@ -35,7 +35,7 @@ from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_sched
|
||||
get_asset_depr_schedule_doc,
|
||||
get_depr_schedule,
|
||||
)
|
||||
from erpnext.assets.doctype.asset_depreciation_schedule.utils import (
|
||||
from erpnext.erpnext.assets.doctype.asset_depreciation_schedule.deppreciation_schedule_controller import (
|
||||
get_depreciation_amount,
|
||||
)
|
||||
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
|
||||
|
||||
@@ -16,7 +16,7 @@ from erpnext.assets.doctype.asset.depreciation import (
|
||||
get_gl_entries_on_asset_disposal,
|
||||
get_value_after_depreciation_on_disposal_date,
|
||||
reset_depreciation_schedule,
|
||||
reverse_depreciation_entry_made_after_disposal,
|
||||
reverse_depreciation_entry_made_on_disposal,
|
||||
)
|
||||
from erpnext.assets.doctype.asset_activity.asset_activity import add_asset_activity
|
||||
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
|
||||
@@ -623,13 +623,13 @@ class AssetCapitalization(StockController):
|
||||
self.set_consumed_asset_status(asset)
|
||||
|
||||
if asset.calculate_depreciation:
|
||||
reverse_depreciation_entry_made_after_disposal(asset, self.posting_date)
|
||||
reverse_depreciation_entry_made_on_disposal(asset, self.posting_date)
|
||||
notes = _(
|
||||
"This schedule was created when Asset {0} was restored on Asset Capitalization {1}'s cancellation."
|
||||
).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)
|
||||
reset_depreciation_schedule(asset, notes)
|
||||
|
||||
def set_consumed_asset_status(self, asset):
|
||||
if self.docstatus == 1:
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import (
|
||||
add_days,
|
||||
add_months,
|
||||
@@ -20,13 +19,12 @@ from frappe.utils import (
|
||||
)
|
||||
|
||||
import erpnext
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
from erpnext.assets.doctype.asset_depreciation_schedule.utils import (
|
||||
get_depreciation_amount,
|
||||
from erpnext.assets.doctype.asset_depreciation_schedule.deppreciation_schedule_controller import (
|
||||
DepreciationScheduleController,
|
||||
)
|
||||
|
||||
|
||||
class AssetDepreciationSchedule(Document):
|
||||
class AssetDepreciationSchedule(DepreciationScheduleController):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
@@ -61,15 +59,11 @@ class AssetDepreciationSchedule(Document):
|
||||
value_after_depreciation: DF.Currency
|
||||
# end: auto-generated types
|
||||
|
||||
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
|
||||
)
|
||||
self.update_shift_depr_schedule()
|
||||
|
||||
def validate(self):
|
||||
self.validate_another_asset_depr_schedule_does_not_exist()
|
||||
if not self.finance_book_id:
|
||||
self.create_depreciation_schedule()
|
||||
self.update_shift_depr_schedule()
|
||||
|
||||
def validate_another_asset_depr_schedule_does_not_exist(self):
|
||||
finance_book_filter = ["finance_book", "is", "not set"]
|
||||
@@ -102,7 +96,8 @@ class AssetDepreciationSchedule(Document):
|
||||
def on_submit(self):
|
||||
self.db_set("status", "Active")
|
||||
|
||||
def before_cancel(self):
|
||||
def on_cancel(self):
|
||||
self.db_set("status", "Cancelled")
|
||||
if not self.flags.should_not_cancel_depreciation_entries:
|
||||
self.cancel_depreciation_entries()
|
||||
|
||||
@@ -111,9 +106,6 @@ class AssetDepreciationSchedule(Document):
|
||||
if d.journal_entry:
|
||||
frappe.get_doc("Journal Entry", d.journal_entry).cancel()
|
||||
|
||||
def on_cancel(self):
|
||||
self.db_set("status", "Cancelled")
|
||||
|
||||
def update_shift_depr_schedule(self):
|
||||
if not self.shift_based or self.docstatus != 0:
|
||||
return
|
||||
@@ -124,594 +116,50 @@ class AssetDepreciationSchedule(Document):
|
||||
self.make_depr_schedule(asset_doc, fb_row)
|
||||
self.set_accumulated_depreciation(asset_doc, fb_row)
|
||||
|
||||
def prepare_draft_asset_depr_schedule_data_from_asset_name_and_fb_name(self, asset_name, fb_name):
|
||||
asset_doc = frappe.get_doc("Asset", asset_name)
|
||||
def get_finance_book_row(self, fb_row=None):
|
||||
if fb_row:
|
||||
self.fb_row = fb_row
|
||||
return
|
||||
|
||||
finance_book_filter = ["finance_book", "is", "not set"]
|
||||
if fb_name:
|
||||
finance_book_filter = ["finance_book", "=", fb_name]
|
||||
if self.finance_book:
|
||||
finance_book_filter = ["finance_book", "=", self.finance_book]
|
||||
|
||||
asset_finance_book_name = frappe.db.get_value(
|
||||
doctype="Asset Finance Book",
|
||||
filters=[["parent", "=", asset_name], finance_book_filter],
|
||||
filters=[["parent", "=", self.asset], finance_book_filter],
|
||||
)
|
||||
asset_finance_book_doc = frappe.get_doc("Asset Finance Book", asset_finance_book_name)
|
||||
self.fb_row = frappe.get_doc("Asset Finance Book", asset_finance_book_name)
|
||||
|
||||
self.prepare_draft_asset_depr_schedule_data(asset_doc, asset_finance_book_doc)
|
||||
|
||||
def prepare_draft_asset_depr_schedule_data(
|
||||
self,
|
||||
asset_doc,
|
||||
row,
|
||||
date_of_disposal=None,
|
||||
date_of_return=None,
|
||||
update_asset_finance_book_row=True,
|
||||
):
|
||||
self.set_draft_asset_depr_schedule_details(asset_doc, row)
|
||||
self.make_depr_schedule(asset_doc, row, date_of_disposal, update_asset_finance_book_row)
|
||||
self.set_accumulated_depreciation(asset_doc, row, date_of_disposal, date_of_return)
|
||||
|
||||
def set_draft_asset_depr_schedule_details(self, asset_doc, row):
|
||||
self.asset = asset_doc.name
|
||||
self.finance_book = row.finance_book
|
||||
self.finance_book_id = row.idx
|
||||
self.opening_accumulated_depreciation = asset_doc.opening_accumulated_depreciation or 0
|
||||
self.opening_number_of_booked_depreciations = asset_doc.opening_number_of_booked_depreciations or 0
|
||||
self.gross_purchase_amount = asset_doc.gross_purchase_amount
|
||||
self.depreciation_method = row.depreciation_method
|
||||
self.total_number_of_depreciations = row.total_number_of_depreciations
|
||||
self.frequency_of_depreciation = row.frequency_of_depreciation
|
||||
self.rate_of_depreciation = row.rate_of_depreciation
|
||||
self.expected_value_after_useful_life = row.expected_value_after_useful_life
|
||||
self.daily_prorata_based = row.daily_prorata_based
|
||||
self.shift_based = row.shift_based
|
||||
def fetch_asset_details(self):
|
||||
self.asset = self.asset_doc.name
|
||||
self.finance_book = self.fb_row.get("finance_book")
|
||||
self.finance_book_id = self.fb_row.idx
|
||||
self.opening_accumulated_depreciation = self.asset_doc.opening_accumulated_depreciation or 0
|
||||
self.opening_number_of_booked_depreciations = (
|
||||
self.asset_doc.opening_number_of_booked_depreciations or 0
|
||||
)
|
||||
self.gross_purchase_amount = self.asset_doc.gross_purchase_amount
|
||||
self.depreciation_method = self.fb_row.depreciation_method
|
||||
self.total_number_of_depreciations = self.fb_row.total_number_of_depreciations
|
||||
self.frequency_of_depreciation = self.fb_row.frequency_of_depreciation
|
||||
self.rate_of_depreciation = self.fb_row.get("rate_of_depreciation")
|
||||
self.expected_value_after_useful_life = self.fb_row.get("expected_value_after_useful_life")
|
||||
self.daily_prorata_based = self.fb_row.get("daily_prorata_based")
|
||||
self.shift_based = self.fb_row.get("shift_based")
|
||||
self.status = "Draft"
|
||||
|
||||
def make_depr_schedule(
|
||||
self,
|
||||
asset_doc,
|
||||
row,
|
||||
date_of_disposal=None,
|
||||
update_asset_finance_book_row=True,
|
||||
value_after_depreciation=None,
|
||||
):
|
||||
start = self.clear_depr_schedule()
|
||||
|
||||
self._make_depr_schedule(
|
||||
asset_doc, row, start, date_of_disposal, update_asset_finance_book_row, value_after_depreciation
|
||||
)
|
||||
|
||||
def clear_depr_schedule(self):
|
||||
"""
|
||||
Clears the depreciation schedule preserving the depreciation entries that have been booked.
|
||||
"""
|
||||
start = 0
|
||||
num_of_depreciations_completed = 0
|
||||
depr_schedule = []
|
||||
|
||||
self.schedules_before_clearing = self.get("depreciation_schedule")
|
||||
|
||||
for schedule in self.get("depreciation_schedule"):
|
||||
if schedule.journal_entry:
|
||||
num_of_depreciations_completed += 1
|
||||
depr_schedule.append(schedule)
|
||||
else:
|
||||
start = num_of_depreciations_completed
|
||||
break
|
||||
|
||||
self.depreciation_schedule = depr_schedule
|
||||
|
||||
return start
|
||||
|
||||
def _make_depr_schedule(
|
||||
self,
|
||||
asset_doc,
|
||||
row,
|
||||
start,
|
||||
date_of_disposal,
|
||||
update_asset_finance_book_row,
|
||||
value_after_depreciation,
|
||||
):
|
||||
row, value_after_depreciation = self.get_value_after_depreciation(
|
||||
asset_doc, row, value_after_depreciation, update_asset_finance_book_row
|
||||
)
|
||||
final_number_of_depreciations, has_pro_rata = self.get_final_number_of_depreciations(asset_doc, row)
|
||||
has_wdv_or_dd_non_yearly_pro_rata = self.is_wdv_or_dd_non_yearly_pro_rata(asset_doc, row)
|
||||
should_get_last_day = is_last_day_of_the_month(row.depreciation_start_date)
|
||||
|
||||
skip_row = False
|
||||
depreciation_amount = 0
|
||||
prev_per_day_depr = True
|
||||
self.current_fiscal_year_end_date = None
|
||||
yearly_opening_wdv = value_after_depreciation
|
||||
pending_months = self.get_number_of_pending_months(asset_doc, row, start)
|
||||
|
||||
for n in range(start, final_number_of_depreciations):
|
||||
# If depreciation is already completed (for double declining balance)
|
||||
if skip_row:
|
||||
continue
|
||||
|
||||
if self.has_fiscal_year_changed(row, n):
|
||||
yearly_opening_wdv = value_after_depreciation
|
||||
|
||||
prev_depreciation_amount = self.get_prev_depreciation_amount(n)
|
||||
|
||||
depreciation_amount, prev_per_day_depr = get_depreciation_amount(
|
||||
self,
|
||||
asset_doc,
|
||||
value_after_depreciation,
|
||||
yearly_opening_wdv,
|
||||
row,
|
||||
n,
|
||||
prev_depreciation_amount,
|
||||
has_wdv_or_dd_non_yearly_pro_rata,
|
||||
pending_months,
|
||||
prev_per_day_depr,
|
||||
)
|
||||
|
||||
schedule_date = self.get_next_schedule_date(
|
||||
row, n, has_pro_rata, should_get_last_day, final_number_of_depreciations
|
||||
)
|
||||
|
||||
# if asset is being sold or scrapped
|
||||
if date_of_disposal and getdate(schedule_date) >= getdate(date_of_disposal):
|
||||
self.get_depreciation_amount_for_disposal(
|
||||
asset_doc, row, n, schedule_date, date_of_disposal, depreciation_amount
|
||||
)
|
||||
break
|
||||
|
||||
if n == 0:
|
||||
# Get pro rata amount for first row if available for use date is mid of the month
|
||||
depreciation_amount = self.get_depreciation_amount_for_first_row(
|
||||
asset_doc, row, n, depreciation_amount, has_pro_rata, has_wdv_or_dd_non_yearly_pro_rata
|
||||
)
|
||||
elif has_pro_rata and n == cint(final_number_of_depreciations) - 1: # for the last row
|
||||
depreciation_amount, schedule_date = self.get_depreciation_amount_for_last_row(
|
||||
asset_doc, row, n, depreciation_amount, schedule_date, has_wdv_or_dd_non_yearly_pro_rata
|
||||
)
|
||||
|
||||
if not depreciation_amount:
|
||||
break
|
||||
|
||||
|
||||
value_after_depreciation = flt(
|
||||
value_after_depreciation - flt(depreciation_amount, asset_doc.precision("gross_purchase_amount")),
|
||||
asset_doc.precision("gross_purchase_amount"),
|
||||
)
|
||||
|
||||
# Adjust depreciation amount in the last period based on the expected value after useful life
|
||||
depreciation_amount, skip_row = self.adjust_depr_amount_for_salvage_value(
|
||||
row, depreciation_amount, value_after_depreciation, final_number_of_depreciations, n, skip_row
|
||||
)
|
||||
|
||||
if flt(depreciation_amount, asset_doc.precision("gross_purchase_amount")) > 0:
|
||||
self.add_depr_schedule_row(schedule_date, depreciation_amount, n)
|
||||
|
||||
def get_value_after_depreciation(
|
||||
self, asset_doc, row, value_after_depreciation, update_asset_finance_book_row
|
||||
):
|
||||
if not value_after_depreciation:
|
||||
value_after_depreciation = _get_value_after_depreciation_for_making_schedule(asset_doc, row)
|
||||
row.value_after_depreciation = value_after_depreciation
|
||||
|
||||
if update_asset_finance_book_row:
|
||||
row.db_update()
|
||||
|
||||
return row, value_after_depreciation
|
||||
|
||||
def get_final_number_of_depreciations(self, asset_doc, row):
|
||||
final_number_of_depreciations = cint(row.total_number_of_depreciations) - cint(
|
||||
self.opening_number_of_booked_depreciations
|
||||
)
|
||||
|
||||
has_pro_rata = _check_is_pro_rata(asset_doc, row)
|
||||
if has_pro_rata:
|
||||
final_number_of_depreciations += 1
|
||||
|
||||
if row.increase_in_asset_life:
|
||||
final_number_of_depreciations = (
|
||||
self.get_final_number_of_depreciations_considering_increase_in_asset_life(
|
||||
asset_doc, row, final_number_of_depreciations
|
||||
)
|
||||
)
|
||||
|
||||
return final_number_of_depreciations, has_pro_rata
|
||||
|
||||
def get_final_number_of_depreciations_considering_increase_in_asset_life(
|
||||
self, asset_doc, row, final_number_of_depreciations
|
||||
):
|
||||
# final schedule date after increasing asset life
|
||||
self.final_schedule_date = add_months(
|
||||
asset_doc.available_for_use_date,
|
||||
(row.total_number_of_depreciations * cint(row.frequency_of_depreciation))
|
||||
+ row.increase_in_asset_life,
|
||||
)
|
||||
|
||||
number_of_pending_depreciations = cint(row.total_number_of_depreciations) - cint(
|
||||
asset_doc.opening_number_of_booked_depreciations
|
||||
)
|
||||
schedule_date = add_months(
|
||||
row.depreciation_start_date,
|
||||
number_of_pending_depreciations * cint(row.frequency_of_depreciation),
|
||||
)
|
||||
|
||||
if self.final_schedule_date > schedule_date:
|
||||
final_number_of_depreciations += 1
|
||||
|
||||
return final_number_of_depreciations
|
||||
|
||||
def is_wdv_or_dd_non_yearly_pro_rata(self, asset_doc, row):
|
||||
has_wdv_or_dd_non_yearly_pro_rata = False
|
||||
if (
|
||||
row.depreciation_method in ("Written Down Value", "Double Declining Balance")
|
||||
and cint(row.frequency_of_depreciation) != 12
|
||||
):
|
||||
has_wdv_or_dd_non_yearly_pro_rata = _check_is_pro_rata(asset_doc, row, wdv_or_dd_non_yearly=True)
|
||||
|
||||
return has_wdv_or_dd_non_yearly_pro_rata
|
||||
|
||||
def get_number_of_pending_months(self, asset_doc, row, start):
|
||||
total_months = cint(row.total_number_of_depreciations) * cint(row.frequency_of_depreciation) + cint(
|
||||
row.increase_in_asset_life
|
||||
)
|
||||
depr_booked_for_months = 0
|
||||
last_depr_date = None
|
||||
if start > 0:
|
||||
last_depr_date = self.depreciation_schedule[start - 1].schedule_date
|
||||
elif asset_doc.opening_number_of_booked_depreciations > 0:
|
||||
last_depr_date = add_months(row.depreciation_start_date, -1 * row.frequency_of_depreciation)
|
||||
|
||||
if last_depr_date:
|
||||
depr_booked_for_months = date_diff(last_depr_date, asset_doc.available_for_use_date) / (365 / 12)
|
||||
|
||||
return total_months - depr_booked_for_months
|
||||
|
||||
def has_fiscal_year_changed(self, row, row_no):
|
||||
fiscal_year_changed = False
|
||||
|
||||
schedule_date = get_last_day(
|
||||
add_months(row.depreciation_start_date, row_no * cint(row.frequency_of_depreciation))
|
||||
)
|
||||
|
||||
if not self.current_fiscal_year_end_date:
|
||||
self.current_fiscal_year_end_date = get_fiscal_year(row.depreciation_start_date)[2]
|
||||
fiscal_year_changed = True
|
||||
elif getdate(schedule_date) > getdate(self.current_fiscal_year_end_date):
|
||||
self.current_fiscal_year_end_date = add_years(self.current_fiscal_year_end_date, 1)
|
||||
fiscal_year_changed = True
|
||||
|
||||
return fiscal_year_changed
|
||||
|
||||
def get_prev_depreciation_amount(self, n):
|
||||
prev_depreciation_amount = 0
|
||||
if n > 0 and len(self.get("depreciation_schedule")) > n - 1:
|
||||
prev_depreciation_amount = self.get("depreciation_schedule")[n - 1].depreciation_amount
|
||||
|
||||
return prev_depreciation_amount
|
||||
|
||||
def get_next_schedule_date(
|
||||
self, row, n, has_pro_rata, should_get_last_day, final_number_of_depreciations=None
|
||||
):
|
||||
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)
|
||||
|
||||
return schedule_date
|
||||
|
||||
def get_depreciation_amount_for_disposal(
|
||||
self, asset_doc, row, row_no, schedule_date, date_of_disposal, depreciation_amount
|
||||
):
|
||||
if self.depreciation_schedule: # if there are already booked depreciations
|
||||
from_date = add_days(self.depreciation_schedule[-1].schedule_date, 1)
|
||||
else:
|
||||
from_date = _get_modified_available_for_use_date_for_existing_assets(asset_doc, row)
|
||||
if is_last_day_of_the_month(getdate(asset_doc.available_for_use_date)):
|
||||
from_date = get_last_day(from_date)
|
||||
|
||||
depreciation_amount, days, months = _get_pro_rata_amt(
|
||||
row,
|
||||
depreciation_amount,
|
||||
from_date,
|
||||
date_of_disposal,
|
||||
original_schedule_date=schedule_date,
|
||||
)
|
||||
|
||||
depreciation_amount = flt(depreciation_amount, asset_doc.precision("gross_purchase_amount"))
|
||||
if depreciation_amount > 0:
|
||||
self.add_depr_schedule_row(date_of_disposal, depreciation_amount, row_no)
|
||||
|
||||
def get_adjusted_depreciation_amount(
|
||||
self, depreciation_amount_without_pro_rata, depreciation_amount_for_last_row
|
||||
):
|
||||
# to ensure that final accumulated depreciation amount is accurate
|
||||
if not self.opening_accumulated_depreciation:
|
||||
depreciation_amount_for_first_row = self.get("depreciation_schedule")[0].depreciation_amount
|
||||
|
||||
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, asset_doc, row, n, depreciation_amount, has_pro_rata, has_wdv_or_dd_non_yearly_pro_rata
|
||||
):
|
||||
"""
|
||||
For the first row, if available for use date is mid of the month, then pro rata amount is needed
|
||||
"""
|
||||
pro_rata_amount_applicable = False
|
||||
if (
|
||||
(has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata)
|
||||
and not self.opening_accumulated_depreciation
|
||||
and not self.flags.wdv_it_act_applied
|
||||
): # if not existing asset
|
||||
from_date = asset_doc.available_for_use_date
|
||||
pro_rata_amount_applicable = True
|
||||
elif has_wdv_or_dd_non_yearly_pro_rata and self.opening_accumulated_depreciation: # if existing asset
|
||||
from_date = _get_modified_available_for_use_date_for_existing_assets(asset_doc, row)
|
||||
pro_rata_amount_applicable = True
|
||||
|
||||
if pro_rata_amount_applicable:
|
||||
depreciation_amount, days, months = _get_pro_rata_amt(
|
||||
row,
|
||||
depreciation_amount,
|
||||
from_date,
|
||||
row.depreciation_start_date,
|
||||
has_wdv_or_dd_non_yearly_pro_rata,
|
||||
)
|
||||
|
||||
self.validate_depreciation_amount_for_low_value_assets(asset_doc, row, depreciation_amount)
|
||||
|
||||
return depreciation_amount
|
||||
|
||||
def get_depreciation_amount_for_last_row(
|
||||
self, asset_doc, row, n, depreciation_amount, schedule_date, has_wdv_or_dd_non_yearly_pro_rata
|
||||
):
|
||||
if not row.increase_in_asset_life:
|
||||
# In case of increase_in_asset_life, the asset.to_date is already set on asset_repair submission
|
||||
self.final_schedule_date = add_months(
|
||||
asset_doc.available_for_use_date,
|
||||
(n + self.opening_number_of_booked_depreciations) * cint(row.frequency_of_depreciation),
|
||||
)
|
||||
if is_last_day_of_the_month(getdate(asset_doc.available_for_use_date)):
|
||||
self.final_schedule_date = get_last_day(self.final_schedule_date)
|
||||
|
||||
if self.opening_accumulated_depreciation:
|
||||
depreciation_amount, days, months = _get_pro_rata_amt(
|
||||
row,
|
||||
depreciation_amount,
|
||||
schedule_date,
|
||||
self.final_schedule_date,
|
||||
has_wdv_or_dd_non_yearly_pro_rata,
|
||||
)
|
||||
else:
|
||||
# if not existing asset, remaining amount of first row is depreciated in the last row
|
||||
if not row.increase_in_asset_life:
|
||||
depreciation_amount -= self.get("depreciation_schedule")[0].depreciation_amount
|
||||
days = date_diff(self.final_schedule_date, schedule_date) + 1
|
||||
|
||||
schedule_date = add_days(schedule_date, days - 1)
|
||||
return depreciation_amount, schedule_date
|
||||
|
||||
def adjust_depr_amount_for_salvage_value(
|
||||
self,
|
||||
row,
|
||||
depreciation_amount,
|
||||
value_after_depreciation,
|
||||
final_number_of_depreciations,
|
||||
row_no,
|
||||
skip_row,
|
||||
):
|
||||
if (
|
||||
row_no == cint(final_number_of_depreciations) - 1
|
||||
and flt(value_after_depreciation) != flt(row.expected_value_after_useful_life)
|
||||
) or flt(value_after_depreciation) < flt(row.expected_value_after_useful_life):
|
||||
depreciation_amount += flt(value_after_depreciation) - flt(row.expected_value_after_useful_life)
|
||||
depreciation_amount = flt(depreciation_amount, row.precision("depreciation_amount"))
|
||||
skip_row = True
|
||||
return depreciation_amount, skip_row
|
||||
|
||||
def validate_depreciation_amount_for_low_value_assets(self, asset_doc, row, depreciation_amount):
|
||||
"""
|
||||
If gross purchase amount is too low, then depreciation amount
|
||||
can come zero sometimes based on the frequency and number of depreciations.
|
||||
"""
|
||||
if flt(depreciation_amount, asset_doc.precision("gross_purchase_amount")) <= 0:
|
||||
frappe.throw(
|
||||
_("Gross Purchase Amount {0} cannot be depreciated over {1} cycles.").format(
|
||||
frappe.bold(asset_doc.gross_purchase_amount),
|
||||
frappe.bold(row.total_number_of_depreciations),
|
||||
)
|
||||
)
|
||||
|
||||
def add_depr_schedule_row(self, schedule_date, depreciation_amount, schedule_idx):
|
||||
if self.shift_based:
|
||||
shift = (
|
||||
self.schedules_before_clearing[schedule_idx].shift
|
||||
if self.schedules_before_clearing and len(self.schedules_before_clearing) > schedule_idx
|
||||
else frappe.get_cached_value("Asset Shift Factor", {"default": 1}, "shift_name")
|
||||
)
|
||||
else:
|
||||
shift = None
|
||||
|
||||
self.append(
|
||||
"depreciation_schedule",
|
||||
{
|
||||
"schedule_date": schedule_date,
|
||||
"depreciation_amount": depreciation_amount,
|
||||
"shift": shift,
|
||||
},
|
||||
)
|
||||
|
||||
def set_accumulated_depreciation(
|
||||
self,
|
||||
asset_doc,
|
||||
row,
|
||||
date_of_disposal=None,
|
||||
date_of_return=None,
|
||||
):
|
||||
accumulated_depreciation = flt(self.opening_accumulated_depreciation)
|
||||
value_after_depreciation = flt(row.value_after_depreciation)
|
||||
|
||||
for i, d in enumerate(self.get("depreciation_schedule")):
|
||||
if d.journal_entry:
|
||||
accumulated_depreciation = d.accumulated_depreciation_amount
|
||||
continue
|
||||
|
||||
value_after_depreciation = flt(
|
||||
value_after_depreciation - flt(d.depreciation_amount), d.precision("depreciation_amount")
|
||||
)
|
||||
|
||||
# for the last row, if depreciation method = Straight Line
|
||||
if (
|
||||
self.depreciation_method in ("Straight Line", "Manual")
|
||||
and i == len(self.get("depreciation_schedule")) - 1
|
||||
and not date_of_disposal
|
||||
and not date_of_return
|
||||
and not row.shift_based
|
||||
):
|
||||
d.depreciation_amount += flt(
|
||||
value_after_depreciation - flt(row.expected_value_after_useful_life),
|
||||
d.precision("depreciation_amount"),
|
||||
)
|
||||
|
||||
accumulated_depreciation += d.depreciation_amount
|
||||
d.accumulated_depreciation_amount = flt(
|
||||
accumulated_depreciation, d.precision("accumulated_depreciation_amount")
|
||||
)
|
||||
|
||||
|
||||
def _get_value_after_depreciation_for_making_schedule(asset_doc, fb_row):
|
||||
if asset_doc.docstatus == 1 and fb_row.value_after_depreciation:
|
||||
value_after_depreciation = flt(fb_row.value_after_depreciation)
|
||||
else:
|
||||
value_after_depreciation = flt(asset_doc.gross_purchase_amount) - flt(
|
||||
asset_doc.opening_accumulated_depreciation
|
||||
)
|
||||
|
||||
return value_after_depreciation
|
||||
|
||||
|
||||
# if it returns True, depreciation_amount will not be equal for the first and last rows
|
||||
def _check_is_pro_rata(asset_doc, row, wdv_or_dd_non_yearly=False):
|
||||
has_pro_rata = False
|
||||
|
||||
# if not existing asset, from_date = available_for_use_date
|
||||
# otherwise, if opening_number_of_booked_depreciations = 2, available_for_use_date = 01/01/2020 and frequency_of_depreciation = 12
|
||||
# from_date = 01/01/2022
|
||||
if row.depreciation_method in ("Straight Line", "Manual"):
|
||||
prev_depreciation_start_date = get_last_day(
|
||||
add_months(
|
||||
row.depreciation_start_date,
|
||||
(row.frequency_of_depreciation * -1) * asset_doc.opening_number_of_booked_depreciations,
|
||||
)
|
||||
)
|
||||
from_date = asset_doc.available_for_use_date
|
||||
days = date_diff(prev_depreciation_start_date, from_date) + 1
|
||||
total_days = get_total_days(prev_depreciation_start_date, row.frequency_of_depreciation)
|
||||
else:
|
||||
from_date = _get_modified_available_for_use_date_for_existing_assets(asset_doc, row)
|
||||
days = date_diff(row.depreciation_start_date, from_date) + 1
|
||||
total_days = get_total_days(row.depreciation_start_date, row.frequency_of_depreciation)
|
||||
if days <= 0:
|
||||
frappe.throw(
|
||||
_(
|
||||
"""Error: This asset already has {0} depreciation periods booked.
|
||||
The `depreciation start` date must be at least {1} periods after the `available for use` date.
|
||||
Please correct the dates accordingly."""
|
||||
).format(
|
||||
asset_doc.opening_number_of_booked_depreciations,
|
||||
asset_doc.opening_number_of_booked_depreciations,
|
||||
)
|
||||
)
|
||||
if days < total_days:
|
||||
has_pro_rata = True
|
||||
return has_pro_rata
|
||||
|
||||
|
||||
def _get_modified_available_for_use_date_for_existing_assets(asset_doc, row):
|
||||
"""
|
||||
if Asset has opening booked depreciations = 3,
|
||||
frequency of depreciation = 3,
|
||||
available for use date = 17-07-2023,
|
||||
depreciation start date = 30-06-2024
|
||||
then from date should be 01-04-2024
|
||||
"""
|
||||
if asset_doc.opening_number_of_booked_depreciations > 0:
|
||||
from_date = add_months(
|
||||
asset_doc.available_for_use_date,
|
||||
(asset_doc.opening_number_of_booked_depreciations * row.frequency_of_depreciation) - 1,
|
||||
)
|
||||
if is_last_day_of_the_month(row.depreciation_start_date):
|
||||
return add_days(get_last_day(from_date), 1)
|
||||
|
||||
# get from date when depreciation start date is not last day of the month
|
||||
months_difference = month_diff(row.depreciation_start_date, from_date) - 1
|
||||
return add_days(add_months(row.depreciation_start_date, -1 * months_difference), 1)
|
||||
else:
|
||||
return asset_doc.available_for_use_date
|
||||
|
||||
|
||||
def _get_pro_rata_amt(
|
||||
row,
|
||||
depreciation_amount,
|
||||
from_date,
|
||||
to_date,
|
||||
has_wdv_or_dd_non_yearly_pro_rata=False,
|
||||
original_schedule_date=None,
|
||||
):
|
||||
days = date_diff(to_date, from_date) + 1
|
||||
months = month_diff(to_date, from_date)
|
||||
if has_wdv_or_dd_non_yearly_pro_rata:
|
||||
total_days = get_total_days(original_schedule_date or to_date, 12)
|
||||
else:
|
||||
total_days = get_total_days(original_schedule_date or to_date, row.frequency_of_depreciation)
|
||||
return (depreciation_amount * flt(days)) / flt(total_days), days, months
|
||||
|
||||
|
||||
def get_total_days(date, frequency):
|
||||
period_start_date = add_months(date, cint(frequency) * -1)
|
||||
if is_last_day_of_the_month(date):
|
||||
period_start_date = get_last_day(period_start_date)
|
||||
return date_diff(date, period_start_date)
|
||||
|
||||
|
||||
def make_draft_asset_depr_schedules(asset_doc):
|
||||
asset_depr_schedules_names = []
|
||||
|
||||
for row in asset_doc.get("finance_books"):
|
||||
name = make_draft_asset_depr_schedule(asset_doc, row)
|
||||
asset_depr_schedules_names.append(name)
|
||||
|
||||
return asset_depr_schedules_names
|
||||
|
||||
|
||||
def make_draft_asset_depr_schedule(asset_doc, row):
|
||||
asset_depr_schedule_doc = frappe.new_doc("Asset Depreciation Schedule")
|
||||
|
||||
asset_depr_schedule_doc.prepare_draft_asset_depr_schedule_data(asset_doc, row)
|
||||
asset_depr_schedule_doc.create_depreciation_schedule(asset_doc, row)
|
||||
|
||||
asset_depr_schedule_doc.insert()
|
||||
|
||||
return asset_depr_schedule_doc.name
|
||||
|
||||
|
||||
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
|
||||
|
||||
asset_depr_schedule_doc.prepare_draft_asset_depr_schedule_data(asset_doc, row)
|
||||
|
||||
asset_depr_schedule_doc.save()
|
||||
|
||||
|
||||
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)
|
||||
@@ -732,74 +180,63 @@ def cancel_asset_depr_schedules(asset_doc):
|
||||
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,
|
||||
value_after_depreciation=None,
|
||||
difference_amount=None,
|
||||
):
|
||||
def reschedule_depreciation(asset_doc, notes, disposal_date=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
|
||||
current_schedule = get_asset_depr_schedule_doc(asset_doc.name, None, row.finance_book)
|
||||
|
||||
if current_schedule:
|
||||
if current_schedule.docstatus == 1:
|
||||
new_schedule = frappe.copy_doc(current_schedule)
|
||||
elif current_schedule.docstatus == 0:
|
||||
new_schedule = current_schedule
|
||||
else:
|
||||
new_schedule = frappe.new_doc("Asset Depreciation Schedule")
|
||||
new_schedule.asset = asset_doc.name
|
||||
|
||||
set_modified_depreciation_rate(asset_doc, row, new_schedule)
|
||||
|
||||
new_schedule.create_depreciation_schedule(row, disposal_date)
|
||||
new_schedule.notes = notes
|
||||
|
||||
if current_schedule and current_schedule.docstatus == 1:
|
||||
current_schedule.flags.should_not_cancel_depreciation_entries = True
|
||||
current_schedule.cancel()
|
||||
|
||||
new_schedule.submit()
|
||||
|
||||
|
||||
def set_modified_depreciation_rate(asset_doc, row, new_schedule):
|
||||
if row.depreciation_method in (
|
||||
"Written Down Value",
|
||||
"Double Declining Balance",
|
||||
):
|
||||
new_rate_of_depreciation = flt(
|
||||
asset_doc.get_depreciation_rate(row), row.precision("rate_of_depreciation")
|
||||
)
|
||||
|
||||
if not current_asset_depr_schedule_doc:
|
||||
frappe.throw(
|
||||
_("Asset Depreciation Schedule not found for Asset {0} and Finance Book {1}").format(
|
||||
get_link_to_form("Asset", asset_doc.name), row.finance_book
|
||||
)
|
||||
)
|
||||
row.db_set("rate_of_depreciation", new_rate_of_depreciation)
|
||||
new_schedule.rate_of_depreciation = new_rate_of_depreciation
|
||||
|
||||
new_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc)
|
||||
|
||||
if asset_doc.flags.decrease_in_asset_value_due_to_value_adjustment and not value_after_depreciation:
|
||||
value_after_depreciation = row.value_after_depreciation - difference_amount
|
||||
|
||||
if asset_doc.flags.increase_in_asset_value_due_to_repair and row.depreciation_method in (
|
||||
"Written Down Value",
|
||||
"Double Declining Balance",
|
||||
):
|
||||
new_rate_of_depreciation = flt(
|
||||
asset_doc.get_depreciation_rate(row), row.precision("rate_of_depreciation")
|
||||
)
|
||||
row.rate_of_depreciation = new_rate_of_depreciation
|
||||
new_asset_depr_schedule_doc.rate_of_depreciation = new_rate_of_depreciation
|
||||
|
||||
new_asset_depr_schedule_doc.make_depr_schedule(
|
||||
asset_doc, row, date_of_disposal, value_after_depreciation=value_after_depreciation
|
||||
)
|
||||
new_asset_depr_schedule_doc.set_accumulated_depreciation(
|
||||
asset_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,
|
||||
disposal_date=None,
|
||||
date_of_return=None,
|
||||
update_asset_finance_book_row=False,
|
||||
new_depr_schedule=None,
|
||||
):
|
||||
current_asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset_doc.name, "Active", row.finance_book)
|
||||
current_schedule = get_asset_depr_schedule_doc(asset_doc.name, "Active", row.finance_book)
|
||||
|
||||
if not current_asset_depr_schedule_doc:
|
||||
if not current_schedule:
|
||||
frappe.throw(
|
||||
_("Asset Depreciation Schedule not found for Asset {0} and Finance Book {1}").format(
|
||||
get_link_to_form("Asset", asset_doc.name), row.finance_book
|
||||
)
|
||||
)
|
||||
|
||||
temp_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc)
|
||||
temp_asset_depr_schedule_doc = frappe.copy_doc(current_schedule)
|
||||
|
||||
if new_depr_schedule:
|
||||
temp_asset_depr_schedule_doc.depreciation_schedule = []
|
||||
@@ -816,10 +253,10 @@ def get_temp_asset_depr_schedule_doc(
|
||||
},
|
||||
)
|
||||
|
||||
temp_asset_depr_schedule_doc.prepare_draft_asset_depr_schedule_data(
|
||||
temp_asset_depr_schedule_doc.create_depreciation_schedule(
|
||||
asset_doc,
|
||||
row,
|
||||
date_of_disposal,
|
||||
disposal_date,
|
||||
date_of_return,
|
||||
update_asset_finance_book_row,
|
||||
)
|
||||
@@ -838,7 +275,7 @@ def get_depr_schedule(asset_name, status, finance_book=None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_asset_depr_schedule_doc(asset_name, status, finance_book=None):
|
||||
def get_asset_depr_schedule_doc(asset_name, status=None, finance_book=None):
|
||||
asset_depr_schedule = get_asset_depr_schedule_name(asset_name, status, finance_book)
|
||||
|
||||
if not asset_depr_schedule:
|
||||
@@ -849,16 +286,17 @@ def get_asset_depr_schedule_doc(asset_name, status, finance_book=None):
|
||||
return asset_depr_schedule_doc
|
||||
|
||||
|
||||
def get_asset_depr_schedule_name(asset_name, status, finance_book=None):
|
||||
if isinstance(status, str):
|
||||
status = [status]
|
||||
|
||||
def get_asset_depr_schedule_name(asset_name, status=None, finance_book=None):
|
||||
filters = [
|
||||
["asset", "=", asset_name],
|
||||
["status", "in", status],
|
||||
["docstatus", "<", 2],
|
||||
]
|
||||
|
||||
if status:
|
||||
if isinstance(status, str):
|
||||
status = [status]
|
||||
filters.append(["status", "in", status])
|
||||
|
||||
if finance_book:
|
||||
filters.append(["finance_book", "=", finance_book])
|
||||
else:
|
||||
|
||||
@@ -0,0 +1,449 @@
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import (
|
||||
add_days,
|
||||
add_months,
|
||||
add_years,
|
||||
cint,
|
||||
date_diff,
|
||||
flt,
|
||||
get_last_day,
|
||||
getdate,
|
||||
is_last_day_of_the_month,
|
||||
month_diff,
|
||||
nowdate,
|
||||
)
|
||||
|
||||
import erpnext
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
from erpnext.assets.doctype.asset_depreciation_schedule.depreciation_methods import (
|
||||
StraightLineMethod,
|
||||
WDVMethod,
|
||||
)
|
||||
|
||||
|
||||
class DepreciationScheduleController(StraightLineMethod, WDVMethod):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def create_depreciation_schedule(self, fb_row=None, disposal_date=None):
|
||||
self.disposal_date = disposal_date
|
||||
self.asset_doc = frappe.get_doc("Asset", self.asset)
|
||||
|
||||
self.get_finance_book_row(fb_row)
|
||||
self.fetch_asset_details()
|
||||
self.clear()
|
||||
self.create()
|
||||
self.set_accumulated_depreciation()
|
||||
|
||||
def clear(self):
|
||||
self.first_non_depreciated_row_idx = 0
|
||||
num_of_depreciations_completed = 0
|
||||
depr_schedule = []
|
||||
|
||||
self.schedules_before_clearing = self.get("depreciation_schedule")
|
||||
|
||||
for schedule in self.get("depreciation_schedule"):
|
||||
if schedule.journal_entry:
|
||||
num_of_depreciations_completed += 1
|
||||
depr_schedule.append(schedule)
|
||||
else:
|
||||
self.first_non_depreciated_row_idx = num_of_depreciations_completed
|
||||
break
|
||||
|
||||
self.depreciation_schedule = depr_schedule
|
||||
|
||||
def create(self):
|
||||
self.initialize_variables()
|
||||
for row_idx in range(self.first_non_depreciated_row_idx, self.final_number_of_depreciations):
|
||||
# If depreciation is already completed (for double declining balance)
|
||||
if self.skip_row:
|
||||
continue
|
||||
|
||||
if self.has_fiscal_year_changed(row_idx):
|
||||
self.yearly_opening_wdv = self.pending_depreciation_amount
|
||||
|
||||
self.get_prev_depreciation_amount(row_idx)
|
||||
|
||||
self.schedule_date = self.get_next_schedule_date(row_idx)
|
||||
|
||||
self.depreciation_amount = self.get_depreciation_amount(row_idx)
|
||||
print(row_idx, self.schedule_date, self.depreciation_amount)
|
||||
|
||||
# if asset is being sold or scrapped
|
||||
if self.disposal_date and getdate(self.schedule_date) >= getdate(self.disposal_date):
|
||||
self.set_depreciation_amount_for_disposal(row_idx)
|
||||
break
|
||||
|
||||
if row_idx == 0:
|
||||
self.set_depreciation_amount_for_first_row(row_idx)
|
||||
elif (
|
||||
self.has_pro_rata and row_idx == cint(self.final_number_of_depreciations) - 1
|
||||
): # for the last row
|
||||
self.set_depreciation_amount_for_last_row(row_idx)
|
||||
|
||||
self.depreciation_amount = flt(
|
||||
self.depreciation_amount, self.asset_doc.precision("gross_purchase_amount")
|
||||
)
|
||||
if not self.depreciation_amount:
|
||||
break
|
||||
|
||||
self.pending_depreciation_amount = flt(
|
||||
self.pending_depreciation_amount - self.depreciation_amount,
|
||||
self.asset_doc.precision("gross_purchase_amount"),
|
||||
)
|
||||
|
||||
self.adjust_depr_amount_for_salvage_value(row_idx)
|
||||
|
||||
if flt(self.depreciation_amount, self.asset_doc.precision("gross_purchase_amount")) > 0:
|
||||
self.add_depr_schedule_row(row_idx)
|
||||
|
||||
def initialize_variables(self):
|
||||
self.pending_depreciation_amount = self.fb_row.value_after_depreciation
|
||||
self.should_get_last_day = is_last_day_of_the_month(self.fb_row.depreciation_start_date)
|
||||
self.skip_row = False
|
||||
self.depreciation_amount = 0
|
||||
self.prev_per_day_depr = True
|
||||
self.current_fiscal_year_end_date = None
|
||||
self.yearly_opening_wdv = self.pending_depreciation_amount
|
||||
self.get_number_of_pending_months()
|
||||
self.get_final_number_of_depreciations()
|
||||
self.is_wdv_or_dd_non_yearly_pro_rata()
|
||||
self.get_total_pending_days_or_years()
|
||||
|
||||
def get_final_number_of_depreciations(self):
|
||||
self.final_number_of_depreciations = cint(self.fb_row.total_number_of_depreciations) - cint(
|
||||
self.opening_number_of_booked_depreciations
|
||||
)
|
||||
|
||||
self._check_is_pro_rata()
|
||||
if self.has_pro_rata:
|
||||
self.final_number_of_depreciations += 1
|
||||
|
||||
self.set_final_number_of_depreciations_considering_increase_in_asset_life()
|
||||
|
||||
def set_final_number_of_depreciations_considering_increase_in_asset_life(self):
|
||||
# final schedule date after increasing asset life
|
||||
self.final_schedule_date = add_months(
|
||||
self.asset_doc.available_for_use_date,
|
||||
(self.fb_row.total_number_of_depreciations * cint(self.fb_row.frequency_of_depreciation))
|
||||
+ cint(self.fb_row.increase_in_asset_life),
|
||||
)
|
||||
|
||||
number_of_pending_depreciations = cint(self.fb_row.total_number_of_depreciations) - cint(
|
||||
self.asset_doc.opening_number_of_booked_depreciations
|
||||
)
|
||||
schedule_date = add_months(
|
||||
self.fb_row.depreciation_start_date,
|
||||
number_of_pending_depreciations * cint(self.fb_row.frequency_of_depreciation),
|
||||
)
|
||||
|
||||
if self.final_schedule_date > getdate(schedule_date):
|
||||
months = month_diff(self.final_schedule_date, schedule_date)
|
||||
self.final_number_of_depreciations += months // cint(self.fb_row.frequency_of_depreciation) + 1
|
||||
|
||||
def is_wdv_or_dd_non_yearly_pro_rata(self):
|
||||
if (
|
||||
self.fb_row.depreciation_method in ("Written Down Value", "Double Declining Balance")
|
||||
and cint(self.fb_row.frequency_of_depreciation) != 12
|
||||
):
|
||||
self._check_is_pro_rata()
|
||||
|
||||
def _check_is_pro_rata(self):
|
||||
self.has_pro_rata = False
|
||||
|
||||
# if not existing asset, from_date = available_for_use_date
|
||||
# otherwise, if opening_number_of_booked_depreciations = 2, available_for_use_date = 01/01/2020 and frequency_of_depreciation = 12
|
||||
# from_date = 01/01/2022
|
||||
if self.fb_row.depreciation_method in ("Straight Line", "Manual"):
|
||||
prev_depreciation_start_date = get_last_day(
|
||||
add_months(
|
||||
self.fb_row.depreciation_start_date,
|
||||
(self.fb_row.frequency_of_depreciation * -1)
|
||||
* self.asset_doc.opening_number_of_booked_depreciations,
|
||||
)
|
||||
)
|
||||
from_date = self.asset_doc.available_for_use_date
|
||||
days = date_diff(prev_depreciation_start_date, from_date) + 1
|
||||
total_days = self.get_total_days(prev_depreciation_start_date)
|
||||
else:
|
||||
from_date = self._get_modified_available_for_use_date_for_existing_assets()
|
||||
days = date_diff(self.fb_row.depreciation_start_date, from_date) + 1
|
||||
total_days = self.get_total_days(self.fb_row.depreciation_start_date)
|
||||
|
||||
if days <= 0:
|
||||
frappe.throw(
|
||||
_(
|
||||
"""Error: This asset already has {0} depreciation periods booked.
|
||||
The `depreciation start` date must be at least {1} periods after the `available for use` date.
|
||||
Please correct the dates accordingly."""
|
||||
).format(
|
||||
self.asset_doc.opening_number_of_booked_depreciations,
|
||||
self.asset_doc.opening_number_of_booked_depreciations,
|
||||
)
|
||||
)
|
||||
if days < total_days:
|
||||
self.has_pro_rata = True
|
||||
self.has_wdv_or_dd_non_yearly_pro_rata = True
|
||||
|
||||
def _get_modified_available_for_use_date_for_existing_assets(self):
|
||||
"""
|
||||
if Asset has opening booked depreciations = 3,
|
||||
frequency of depreciation = 3,
|
||||
available for use date = 17-07-2023,
|
||||
depreciation start date = 30-06-2024
|
||||
then from date should be 01-04-2024
|
||||
"""
|
||||
if self.asset_doc.opening_number_of_booked_depreciations > 0:
|
||||
from_date = add_months(
|
||||
self.asset_doc.available_for_use_date,
|
||||
(
|
||||
self.asset_doc.opening_number_of_booked_depreciations
|
||||
* self.fb_row.frequency_of_depreciation
|
||||
)
|
||||
- 1,
|
||||
)
|
||||
if is_last_day_of_the_month(self.fb_row.depreciation_start_date):
|
||||
return add_days(get_last_day(from_date), 1)
|
||||
|
||||
# get from date when depreciation start date is not last day of the month
|
||||
months_difference = month_diff(self.fb_row.depreciation_start_date, from_date) - 1
|
||||
return add_days(add_months(self.fb_row.depreciation_start_date, -1 * months_difference), 1)
|
||||
else:
|
||||
return self.asset_doc.available_for_use_date
|
||||
|
||||
def get_total_days(self, date):
|
||||
period_start_date = add_months(date, cint(self.fb_row.frequency_of_depreciation) * -1)
|
||||
if is_last_day_of_the_month(date):
|
||||
period_start_date = get_last_day(period_start_date)
|
||||
return date_diff(date, period_start_date)
|
||||
|
||||
def _get_pro_rata_amt(self, from_date, to_date, original_schedule_date=None):
|
||||
days = date_diff(to_date, from_date) + 1
|
||||
months = month_diff(to_date, from_date)
|
||||
total_days = self.get_total_days(original_schedule_date or to_date)
|
||||
return (self.depreciation_amount * flt(days)) / flt(total_days), days, months
|
||||
|
||||
def get_number_of_pending_months(self):
|
||||
total_months = cint(self.fb_row.total_number_of_depreciations) * cint(
|
||||
self.fb_row.frequency_of_depreciation
|
||||
) + cint(self.fb_row.increase_in_asset_life)
|
||||
depr_booked_for_months = 0
|
||||
last_depr_date = self.get_last_booked_depreciation_date()
|
||||
if last_depr_date:
|
||||
depr_booked_for_months = date_diff(last_depr_date, self.asset_doc.available_for_use_date) / (
|
||||
365 / 12
|
||||
)
|
||||
|
||||
self.pending_months = total_months - depr_booked_for_months
|
||||
|
||||
def get_last_booked_depreciation_date(self):
|
||||
last_depr_date = None
|
||||
if self.first_non_depreciated_row_idx > 0:
|
||||
last_depr_date = self.depreciation_schedule[self.first_non_depreciated_row_idx - 1].schedule_date
|
||||
elif self.asset_doc.opening_number_of_booked_depreciations > 0:
|
||||
last_depr_date = add_months(
|
||||
self.fb_row.depreciation_start_date, -1 * self.fb_row.frequency_of_depreciation
|
||||
)
|
||||
|
||||
return last_depr_date
|
||||
|
||||
def get_total_pending_days_or_years(self):
|
||||
if cint(frappe.db.get_single_value("Accounts Settings", "calculate_depr_using_total_days")):
|
||||
last_depr_date = self.get_last_booked_depreciation_date()
|
||||
self.total_pending_days = date_diff(self.final_schedule_date, last_depr_date) + 1
|
||||
else:
|
||||
self.total_pending_years = self.pending_months / 12
|
||||
|
||||
def has_fiscal_year_changed(self, row_idx):
|
||||
self.fiscal_year_changed = False
|
||||
|
||||
schedule_date = get_last_day(
|
||||
add_months(
|
||||
self.fb_row.depreciation_start_date, row_idx * cint(self.fb_row.frequency_of_depreciation)
|
||||
)
|
||||
)
|
||||
|
||||
if not self.current_fiscal_year_end_date:
|
||||
self.current_fiscal_year_end_date = get_fiscal_year(self.fb_row.depreciation_start_date)[2]
|
||||
self.fiscal_year_changed = True
|
||||
elif getdate(schedule_date) > getdate(self.current_fiscal_year_end_date):
|
||||
self.current_fiscal_year_end_date = add_years(self.current_fiscal_year_end_date, 1)
|
||||
self.fiscal_year_changed = True
|
||||
|
||||
def get_prev_depreciation_amount(self, row_idx):
|
||||
self.prev_depreciation_amount = 0
|
||||
if row_idx > 0 and len(self.get("depreciation_schedule")) > row_idx - 1:
|
||||
self.prev_depreciation_amount = self.get("depreciation_schedule")[row_idx - 1].depreciation_amount
|
||||
|
||||
def get_next_schedule_date(self, row_idx):
|
||||
schedule_date = add_months(
|
||||
self.fb_row.depreciation_start_date, row_idx * cint(self.fb_row.frequency_of_depreciation)
|
||||
)
|
||||
if self.should_get_last_day:
|
||||
schedule_date = get_last_day(schedule_date)
|
||||
|
||||
return schedule_date
|
||||
|
||||
def set_depreciation_amount_for_disposal(self, row_idx):
|
||||
if self.depreciation_schedule: # if there are already booked depreciations
|
||||
from_date = add_days(self.depreciation_schedule[-1].schedule_date, 1)
|
||||
else:
|
||||
from_date = self._get_modified_available_for_use_date_for_existing_assets()
|
||||
if is_last_day_of_the_month(getdate(self.asset_doc.available_for_use_date)):
|
||||
from_date = get_last_day(from_date)
|
||||
|
||||
self.depreciation_amount, days, months = self._get_pro_rata_amt(
|
||||
from_date,
|
||||
self.disposal_date,
|
||||
original_schedule_date=self.schedule_date,
|
||||
)
|
||||
|
||||
self.depreciation_amount = flt(
|
||||
self.depreciation_amount, self.asset_doc.precision("gross_purchase_amount")
|
||||
)
|
||||
if self.depreciation_amount > 0:
|
||||
self.schedule_date = self.disposal_date
|
||||
self.add_depr_schedule_row(row_idx)
|
||||
|
||||
def set_depreciation_amount_for_first_row(self, row_idx):
|
||||
"""
|
||||
For the first row, if available for use date is mid of the month, then pro rata amount is needed
|
||||
"""
|
||||
pro_rata_amount_applicable = False
|
||||
if (
|
||||
self.has_pro_rata
|
||||
and not self.opening_accumulated_depreciation
|
||||
and not self.flags.wdv_it_act_applied
|
||||
): # if not existing asset
|
||||
from_date = self.asset_doc.available_for_use_date
|
||||
pro_rata_amount_applicable = True
|
||||
elif self.has_pro_rata and self.opening_accumulated_depreciation: # if existing asset
|
||||
from_date = self._get_modified_available_for_use_date_for_existing_assets()
|
||||
pro_rata_amount_applicable = True
|
||||
|
||||
if pro_rata_amount_applicable:
|
||||
self.depreciation_amount, days, months = self._get_pro_rata_amt(
|
||||
from_date,
|
||||
self.fb_row.depreciation_start_date,
|
||||
)
|
||||
|
||||
self.validate_depreciation_amount_for_low_value_assets()
|
||||
|
||||
def set_depreciation_amount_for_last_row(self, row_idx):
|
||||
if not self.fb_row.increase_in_asset_life:
|
||||
self.final_schedule_date = add_months(
|
||||
self.asset_doc.available_for_use_date,
|
||||
(row_idx + self.opening_number_of_booked_depreciations)
|
||||
* cint(self.fb_row.frequency_of_depreciation),
|
||||
)
|
||||
if is_last_day_of_the_month(getdate(self.asset_doc.available_for_use_date)):
|
||||
self.final_schedule_date = get_last_day(self.final_schedule_date)
|
||||
|
||||
if self.opening_accumulated_depreciation:
|
||||
self.depreciation_amount, days, months = self._get_pro_rata_amt(
|
||||
self.schedule_date,
|
||||
self.final_schedule_date,
|
||||
)
|
||||
else:
|
||||
if not self.fb_row.increase_in_asset_life:
|
||||
self.depreciation_amount -= self.get("depreciation_schedule")[0].depreciation_amount
|
||||
days = date_diff(self.final_schedule_date, self.schedule_date) + 1
|
||||
|
||||
self.schedule_date = add_days(self.schedule_date, days - 1)
|
||||
|
||||
def adjust_depr_amount_for_salvage_value(self, row_idx):
|
||||
"""
|
||||
Adjust depreciation amount in the last period based on the expected value after useful life
|
||||
"""
|
||||
if (
|
||||
row_idx == cint(self.final_number_of_depreciations) - 1
|
||||
and flt(self.pending_depreciation_amount) != flt(self.fb_row.expected_value_after_useful_life)
|
||||
) or flt(self.pending_depreciation_amount) < flt(self.fb_row.expected_value_after_useful_life):
|
||||
self.depreciation_amount += flt(self.pending_depreciation_amount) - flt(
|
||||
self.fb_row.expected_value_after_useful_life
|
||||
)
|
||||
self.depreciation_amount = flt(
|
||||
self.depreciation_amount, self.precision("value_after_depreciation")
|
||||
)
|
||||
self.skip_row = True
|
||||
|
||||
def validate_depreciation_amount_for_low_value_assets(self):
|
||||
"""
|
||||
If gross purchase amount is too low, then depreciation amount
|
||||
can come zero sometimes based on the frequency and number of depreciations.
|
||||
"""
|
||||
if flt(self.depreciation_amount, self.asset_doc.precision("gross_purchase_amount")) <= 0:
|
||||
frappe.throw(
|
||||
_("Gross Purchase Amount {0} cannot be depreciated over {1} cycles.").format(
|
||||
frappe.bold(self.asset_doc.gross_purchase_amount),
|
||||
frappe.bold(self.fb_row.total_number_of_depreciations),
|
||||
)
|
||||
)
|
||||
|
||||
def add_depr_schedule_row(self, row_idx):
|
||||
shift = None
|
||||
if self.shift_based:
|
||||
shift = (
|
||||
self.schedules_before_clearing[row_idx].shift
|
||||
if (self.schedules_before_clearing and len(self.schedules_before_clearing) > row_idx)
|
||||
else frappe.get_cached_value("Asset Shift Factor", {"default": 1}, "shift_name")
|
||||
)
|
||||
|
||||
self.append(
|
||||
"depreciation_schedule",
|
||||
{
|
||||
"schedule_date": self.schedule_date,
|
||||
"depreciation_amount": self.depreciation_amount,
|
||||
"shift": shift,
|
||||
},
|
||||
)
|
||||
|
||||
def set_accumulated_depreciation(self):
|
||||
accumulated_depreciation = flt(self.opening_accumulated_depreciation)
|
||||
for d in self.get("depreciation_schedule"):
|
||||
if d.journal_entry:
|
||||
accumulated_depreciation = d.accumulated_depreciation_amount
|
||||
continue
|
||||
|
||||
accumulated_depreciation += d.depreciation_amount
|
||||
d.accumulated_depreciation_amount = flt(
|
||||
accumulated_depreciation, d.precision("accumulated_depreciation_amount")
|
||||
)
|
||||
|
||||
def get_depreciation_amount(self, row_idx):
|
||||
if self.fb_row.depreciation_method in ("Straight Line", "Manual"):
|
||||
return self.get_straight_line_depr_amount(row_idx)
|
||||
else:
|
||||
return self.get_wdv_or_dd_depr_amount(row_idx)
|
||||
|
||||
def _get_total_days(self, depreciation_start_date, row_idx):
|
||||
from_date = add_months(depreciation_start_date, (row_idx - 1) * self.frequency_of_depreciation)
|
||||
to_date = add_months(from_date, self.frequency_of_depreciation)
|
||||
if is_last_day_of_the_month(depreciation_start_date):
|
||||
to_date = get_last_day(to_date)
|
||||
from_date = add_days(get_last_day(from_date), 1)
|
||||
return from_date, date_diff(to_date, from_date) + 1
|
||||
|
||||
def get_total_days_in_current_depr_year(self):
|
||||
fy_start_date, fy_end_date = self.get_fiscal_year(self.schedule_date)
|
||||
return date_diff(fy_end_date, fy_start_date) + 1
|
||||
|
||||
def get_fiscal_year(self, date):
|
||||
fy = get_fiscal_year(date, as_dict=True, raise_on_missing=False)
|
||||
if fy:
|
||||
fy_start_date = fy.year_start_date
|
||||
fy_end_date = fy.year_end_date
|
||||
else:
|
||||
current_fy = get_fiscal_year(nowdate(), as_dict=True)
|
||||
# get fiscal year start date of the year in which the schedule date falls
|
||||
months = month_diff(date, current_fy.year_start_date)
|
||||
if months % 12:
|
||||
years = months // 12
|
||||
else:
|
||||
years = months // 12 - 1
|
||||
|
||||
fy_start_date = add_years(current_fy.year_start_date, years)
|
||||
fy_end_date = add_days(add_years(fy_start_date, 1), -1)
|
||||
|
||||
return fy_start_date, fy_end_date
|
||||
@@ -0,0 +1,119 @@
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import (
|
||||
add_days,
|
||||
add_years,
|
||||
cint,
|
||||
date_diff,
|
||||
flt,
|
||||
month_diff,
|
||||
nowdate,
|
||||
)
|
||||
|
||||
import erpnext
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
|
||||
# from erpnext.assets.doctype.asset_depreciation_schedule.deppreciation_schedule_controller import (
|
||||
# _get_total_days,
|
||||
# )
|
||||
|
||||
|
||||
class StraightLineMethod(Document):
|
||||
def get_straight_line_depr_amount(self, row_idx):
|
||||
self.depreciable_value = flt(self.fb_row.value_after_depreciation) - flt(
|
||||
self.fb_row.expected_value_after_useful_life
|
||||
)
|
||||
|
||||
if self.fb_row.shift_based:
|
||||
self.get_shift_depr_amount(row_idx)
|
||||
|
||||
if self.fb_row.daily_prorata_based:
|
||||
return self.get_daily_prorata_based_depr_amount(row_idx)
|
||||
else:
|
||||
return self.get_fixed_depr_amount()
|
||||
|
||||
def get_fixed_depr_amount(self):
|
||||
pending_periods = flt(self.pending_months) / flt(self.fb_row.frequency_of_depreciation)
|
||||
return self.depreciable_value / pending_periods
|
||||
|
||||
def get_daily_prorata_based_depr_amount(self, row_idx):
|
||||
daily_depr_amount = self.get_daily_depr_amount()
|
||||
|
||||
from_date, total_depreciable_days = self._get_total_days(self.fb_row.depreciation_start_date, row_idx)
|
||||
return daily_depr_amount * total_depreciable_days
|
||||
|
||||
def get_daily_depr_amount(self):
|
||||
if cint(frappe.db.get_single_value("Accounts Settings", "calculate_depr_using_total_days")):
|
||||
return self.depreciable_value / self.total_pending_days
|
||||
else:
|
||||
yearly_depr_amount = self.depreciable_value / self.total_pending_years
|
||||
total_days_in_current_depr_year = self.get_total_days_in_current_depr_year()
|
||||
return yearly_depr_amount / total_days_in_current_depr_year
|
||||
|
||||
def get_shift_depr_amount(self, row_idx):
|
||||
depreciable_value = (
|
||||
flt(self.asset_doc.gross_purchase_amount)
|
||||
- flt(self.asset_doc.opening_accumulated_depreciation)
|
||||
- flt(self.fb_row.expected_value_after_useful_life)
|
||||
)
|
||||
if self.get("__islocal") and not self.asset_doc.flags.shift_allocation:
|
||||
pending_depreciations = flt(
|
||||
self.fb_row.total_number_of_depreciations
|
||||
- self.asset_doc.opening_number_of_booked_depreciations
|
||||
)
|
||||
return depreciable_value / pending_depreciations
|
||||
|
||||
asset_shift_factors_map = self.get_asset_shift_factors_map()
|
||||
shift = (
|
||||
self.schedules_before_clearing[row_idx].shift
|
||||
if len(self.schedules_before_clearing) > row_idx
|
||||
else None
|
||||
)
|
||||
shift_factor = asset_shift_factors_map.get(shift, 0)
|
||||
|
||||
shift_factors_sum = sum(
|
||||
[flt(asset_shift_factors_map.get(d.shift)) for d in self.schedules_before_clearing]
|
||||
)
|
||||
|
||||
return (depreciable_value / shift_factors_sum) * shift_factor
|
||||
|
||||
def get_asset_shift_factors_map(self):
|
||||
return dict(frappe.db.get_all("Asset Shift Factor", ["shift_name", "shift_factor"], as_list=True))
|
||||
|
||||
|
||||
class WDVMethod(Document):
|
||||
def get_wdv_or_dd_depr_amount(self, row_idx):
|
||||
if self.fb_row.daily_prorata_based:
|
||||
return self.get_daily_prorata_based_wdv_depr_amount(row_idx)
|
||||
else:
|
||||
return self.get_wdv_depr_amount()
|
||||
|
||||
def get_wdv_depr_amount(self):
|
||||
if self.is_fiscal_year_changed():
|
||||
yearly_amount = (
|
||||
flt(self.pending_depreciation_amount) * flt(self.fb_row.rate_of_depreciation) / 100
|
||||
)
|
||||
return (yearly_amount * self.fb_row.frequency_of_depreciation) / 12
|
||||
else:
|
||||
return self.prev_depreciation_amount
|
||||
|
||||
def is_fiscal_year_changed(self):
|
||||
fy_start_date, fy_end_date = self.get_fiscal_year(self.schedule_date)
|
||||
if fy_start_date != self.get("prev_fy_start_date"):
|
||||
self.prev_fy_start_date = fy_start_date
|
||||
return True
|
||||
|
||||
def get_daily_prorata_based_wdv_depr_amount(self, row_idx):
|
||||
daily_depr_amount = self.get_daily_wdv_depr_amount()
|
||||
|
||||
from_date, total_depreciable_days = self._get_total_days(self.fb_row.depreciation_start_date, row_idx)
|
||||
return daily_depr_amount * total_depreciable_days
|
||||
|
||||
def get_daily_wdv_depr_amount(self):
|
||||
if self.is_fiscal_year_changed():
|
||||
self.yearly_wdv_depr_amount = (
|
||||
self.pending_depreciation_amount * self.fb_row.rate_of_depreciation / 100
|
||||
)
|
||||
|
||||
total_days_in_current_depr_year = self.get_total_days_in_current_depr_year()
|
||||
return self.yearly_wdv_depr_amount / total_days_in_current_depr_year
|
||||
@@ -1,333 +0,0 @@
|
||||
import frappe
|
||||
from frappe.utils import (
|
||||
add_days,
|
||||
add_months,
|
||||
add_years,
|
||||
cint,
|
||||
cstr,
|
||||
date_diff,
|
||||
flt,
|
||||
get_last_day,
|
||||
is_last_day_of_the_month,
|
||||
)
|
||||
|
||||
import erpnext
|
||||
|
||||
|
||||
def get_depreciation_amount(
|
||||
asset_depr_schedule,
|
||||
asset,
|
||||
value_after_depreciation,
|
||||
yearly_opening_wdv,
|
||||
fb_row,
|
||||
schedule_idx=0,
|
||||
prev_depreciation_amount=0,
|
||||
has_wdv_or_dd_non_yearly_pro_rata=False,
|
||||
number_of_pending_depreciations=0,
|
||||
prev_per_day_depr=0,
|
||||
):
|
||||
if fb_row.depreciation_method in ("Straight Line", "Manual"):
|
||||
return get_straight_line_or_manual_depr_amount(
|
||||
asset_depr_schedule,
|
||||
asset,
|
||||
fb_row,
|
||||
schedule_idx,
|
||||
value_after_depreciation,
|
||||
number_of_pending_depreciations,
|
||||
), None
|
||||
else:
|
||||
return get_wdv_or_dd_depr_amount(
|
||||
asset,
|
||||
fb_row,
|
||||
value_after_depreciation,
|
||||
yearly_opening_wdv,
|
||||
schedule_idx,
|
||||
prev_depreciation_amount,
|
||||
has_wdv_or_dd_non_yearly_pro_rata,
|
||||
asset_depr_schedule,
|
||||
prev_per_day_depr,
|
||||
)
|
||||
|
||||
|
||||
def get_straight_line_or_manual_depr_amount(
|
||||
asset_depr_schedule,
|
||||
asset,
|
||||
fb_row,
|
||||
schedule_idx,
|
||||
value_after_depreciation,
|
||||
number_of_pending_depreciations,
|
||||
):
|
||||
if fb_row.shift_based:
|
||||
return get_shift_depr_amount(asset_depr_schedule, asset, fb_row, schedule_idx)
|
||||
|
||||
if fb_row.daily_prorata_based:
|
||||
amount = flt(asset.gross_purchase_amount) - flt(fb_row.expected_value_after_useful_life)
|
||||
return get_daily_prorata_based_straight_line_depr(
|
||||
asset, fb_row, schedule_idx, number_of_pending_depreciations, amount
|
||||
)
|
||||
else:
|
||||
return (flt(fb_row.value_after_depreciation) - flt(fb_row.expected_value_after_useful_life)) / (
|
||||
flt(number_of_pending_depreciations) / flt(fb_row.frequency_of_depreciation)
|
||||
)
|
||||
|
||||
|
||||
def get_daily_prorata_based_straight_line_depr(
|
||||
asset, fb_row, schedule_idx, number_of_pending_depreciations, amount
|
||||
):
|
||||
daily_depr_amount = get_daily_depr_amount(asset, fb_row, schedule_idx, amount)
|
||||
|
||||
from_date, total_depreciable_days = _get_total_days(
|
||||
fb_row.depreciation_start_date, schedule_idx, fb_row.frequency_of_depreciation
|
||||
)
|
||||
return daily_depr_amount * total_depreciable_days
|
||||
|
||||
|
||||
def get_daily_depr_amount(asset, fb_row, schedule_idx, amount):
|
||||
if cint(frappe.db.get_single_value("Accounts Settings", "calculate_depr_using_total_days")):
|
||||
total_days = (
|
||||
date_diff(
|
||||
get_last_day(
|
||||
add_months(
|
||||
fb_row.depreciation_start_date,
|
||||
flt(
|
||||
fb_row.total_number_of_depreciations
|
||||
- asset.opening_number_of_booked_depreciations
|
||||
- 1
|
||||
)
|
||||
* fb_row.frequency_of_depreciation,
|
||||
)
|
||||
),
|
||||
add_days(
|
||||
get_last_day(
|
||||
add_months(
|
||||
fb_row.depreciation_start_date,
|
||||
(
|
||||
fb_row.frequency_of_depreciation
|
||||
* (asset.opening_number_of_booked_depreciations + 1)
|
||||
)
|
||||
* -1,
|
||||
),
|
||||
),
|
||||
1,
|
||||
),
|
||||
)
|
||||
+ 1
|
||||
)
|
||||
|
||||
return amount / total_days
|
||||
else:
|
||||
total_years = (
|
||||
flt(
|
||||
(fb_row.total_number_of_depreciations - fb_row.total_number_of_booked_depreciations)
|
||||
* fb_row.frequency_of_depreciation
|
||||
)
|
||||
/ 12
|
||||
)
|
||||
|
||||
every_year_depr = amount / total_years
|
||||
|
||||
depr_period_start_date = add_days(
|
||||
get_last_day(add_months(fb_row.depreciation_start_date, fb_row.frequency_of_depreciation * -1)), 1
|
||||
)
|
||||
|
||||
year_start_date = add_years(
|
||||
depr_period_start_date, ((fb_row.frequency_of_depreciation * schedule_idx) // 12)
|
||||
)
|
||||
year_end_date = add_days(add_years(year_start_date, 1), -1)
|
||||
|
||||
return every_year_depr / (date_diff(year_end_date, year_start_date) + 1)
|
||||
|
||||
|
||||
def get_shift_depr_amount(asset_depr_schedule, asset, fb_row, schedule_idx):
|
||||
if asset_depr_schedule.get("__islocal") and not asset.flags.shift_allocation:
|
||||
return (
|
||||
flt(asset.gross_purchase_amount)
|
||||
- flt(asset.opening_accumulated_depreciation)
|
||||
- flt(fb_row.expected_value_after_useful_life)
|
||||
) / flt(fb_row.total_number_of_depreciations - asset.opening_number_of_booked_depreciations)
|
||||
|
||||
asset_shift_factors_map = get_asset_shift_factors_map()
|
||||
shift = (
|
||||
asset_depr_schedule.schedules_before_clearing[schedule_idx].shift
|
||||
if len(asset_depr_schedule.schedules_before_clearing) > schedule_idx
|
||||
else None
|
||||
)
|
||||
shift_factor = asset_shift_factors_map.get(shift) if shift else 0
|
||||
|
||||
shift_factors_sum = sum(
|
||||
flt(asset_shift_factors_map.get(schedule.shift))
|
||||
for schedule in asset_depr_schedule.schedules_before_clearing
|
||||
)
|
||||
|
||||
return (
|
||||
(
|
||||
flt(asset.gross_purchase_amount)
|
||||
- flt(asset.opening_accumulated_depreciation)
|
||||
- flt(fb_row.expected_value_after_useful_life)
|
||||
)
|
||||
/ flt(shift_factors_sum)
|
||||
) * shift_factor
|
||||
|
||||
|
||||
def get_asset_shift_factors_map():
|
||||
return dict(frappe.db.get_all("Asset Shift Factor", ["shift_name", "shift_factor"], as_list=True))
|
||||
|
||||
|
||||
@erpnext.allow_regional
|
||||
def get_wdv_or_dd_depr_amount(
|
||||
asset,
|
||||
fb_row,
|
||||
value_after_depreciation,
|
||||
yearly_opening_wdv,
|
||||
schedule_idx,
|
||||
prev_depreciation_amount,
|
||||
has_wdv_or_dd_non_yearly_pro_rata,
|
||||
asset_depr_schedule,
|
||||
prev_per_day_depr,
|
||||
):
|
||||
return get_default_wdv_or_dd_depr_amount(
|
||||
asset,
|
||||
fb_row,
|
||||
value_after_depreciation,
|
||||
schedule_idx,
|
||||
prev_depreciation_amount,
|
||||
has_wdv_or_dd_non_yearly_pro_rata,
|
||||
asset_depr_schedule,
|
||||
prev_per_day_depr,
|
||||
)
|
||||
|
||||
|
||||
def get_default_wdv_or_dd_depr_amount(
|
||||
asset,
|
||||
fb_row,
|
||||
value_after_depreciation,
|
||||
schedule_idx,
|
||||
prev_depreciation_amount,
|
||||
has_wdv_or_dd_non_yearly_pro_rata,
|
||||
asset_depr_schedule,
|
||||
prev_per_day_depr,
|
||||
):
|
||||
if not fb_row.daily_prorata_based or cint(fb_row.frequency_of_depreciation) == 12:
|
||||
return _get_default_wdv_or_dd_depr_amount(
|
||||
asset,
|
||||
fb_row,
|
||||
value_after_depreciation,
|
||||
schedule_idx,
|
||||
prev_depreciation_amount,
|
||||
has_wdv_or_dd_non_yearly_pro_rata,
|
||||
asset_depr_schedule,
|
||||
), None
|
||||
else:
|
||||
return _get_daily_prorata_based_default_wdv_or_dd_depr_amount(
|
||||
asset,
|
||||
fb_row,
|
||||
value_after_depreciation,
|
||||
schedule_idx,
|
||||
prev_depreciation_amount,
|
||||
has_wdv_or_dd_non_yearly_pro_rata,
|
||||
asset_depr_schedule,
|
||||
prev_per_day_depr,
|
||||
)
|
||||
|
||||
|
||||
def _get_default_wdv_or_dd_depr_amount(
|
||||
asset,
|
||||
fb_row,
|
||||
value_after_depreciation,
|
||||
schedule_idx,
|
||||
prev_depreciation_amount,
|
||||
has_wdv_or_dd_non_yearly_pro_rata,
|
||||
asset_depr_schedule,
|
||||
):
|
||||
if cint(fb_row.frequency_of_depreciation) == 12:
|
||||
return flt(value_after_depreciation) * (flt(fb_row.rate_of_depreciation) / 100)
|
||||
else:
|
||||
if has_wdv_or_dd_non_yearly_pro_rata:
|
||||
if schedule_idx == 0:
|
||||
return flt(value_after_depreciation) * (flt(fb_row.rate_of_depreciation) / 100)
|
||||
elif schedule_idx % (12 / cint(fb_row.frequency_of_depreciation)) == 1:
|
||||
return (
|
||||
flt(value_after_depreciation)
|
||||
* flt(fb_row.frequency_of_depreciation)
|
||||
* (flt(fb_row.rate_of_depreciation) / 1200)
|
||||
)
|
||||
else:
|
||||
return prev_depreciation_amount
|
||||
else:
|
||||
if schedule_idx % (12 / cint(fb_row.frequency_of_depreciation)) == 0:
|
||||
return (
|
||||
flt(value_after_depreciation)
|
||||
* flt(fb_row.frequency_of_depreciation)
|
||||
* (flt(fb_row.rate_of_depreciation) / 1200)
|
||||
)
|
||||
else:
|
||||
return prev_depreciation_amount
|
||||
|
||||
|
||||
def _get_daily_prorata_based_default_wdv_or_dd_depr_amount(
|
||||
asset,
|
||||
fb_row,
|
||||
value_after_depreciation,
|
||||
schedule_idx,
|
||||
prev_depreciation_amount,
|
||||
has_wdv_or_dd_non_yearly_pro_rata,
|
||||
asset_depr_schedule,
|
||||
prev_per_day_depr,
|
||||
):
|
||||
if has_wdv_or_dd_non_yearly_pro_rata: # If applicable days for ther first month is less than full month
|
||||
if schedule_idx == 0:
|
||||
return flt(value_after_depreciation) * (flt(fb_row.rate_of_depreciation) / 100), None
|
||||
|
||||
elif schedule_idx % (12 / cint(fb_row.frequency_of_depreciation)) == 1: # Year changes
|
||||
return get_monthly_depr_amount(fb_row, schedule_idx, value_after_depreciation)
|
||||
else:
|
||||
return get_monthly_depr_amount_based_on_prev_per_day_depr(fb_row, schedule_idx, prev_per_day_depr)
|
||||
else:
|
||||
if schedule_idx % (12 / cint(fb_row.frequency_of_depreciation)) == 0: # year changes
|
||||
return get_monthly_depr_amount(fb_row, schedule_idx, value_after_depreciation)
|
||||
else:
|
||||
return get_monthly_depr_amount_based_on_prev_per_day_depr(fb_row, schedule_idx, prev_per_day_depr)
|
||||
|
||||
|
||||
def get_monthly_depr_amount(fb_row, schedule_idx, value_after_depreciation):
|
||||
"""
|
||||
Returns monthly depreciation amount when year changes
|
||||
1. Calculate per day depr based on new year
|
||||
2. Calculate monthly amount based on new per day amount
|
||||
"""
|
||||
from_date, days_in_month = _get_total_days(
|
||||
fb_row.depreciation_start_date, schedule_idx, cint(fb_row.frequency_of_depreciation)
|
||||
)
|
||||
per_day_depr = get_per_day_depr(fb_row, value_after_depreciation, from_date)
|
||||
return (per_day_depr * days_in_month), per_day_depr
|
||||
|
||||
|
||||
def get_monthly_depr_amount_based_on_prev_per_day_depr(fb_row, schedule_idx, prev_per_day_depr):
|
||||
""" "
|
||||
Returns monthly depreciation amount based on prev per day depr
|
||||
Calculate per day depr only for the first month
|
||||
"""
|
||||
from_date, days_in_month = _get_total_days(
|
||||
fb_row.depreciation_start_date, schedule_idx, cint(fb_row.frequency_of_depreciation)
|
||||
)
|
||||
return (prev_per_day_depr * days_in_month), prev_per_day_depr
|
||||
|
||||
|
||||
def get_per_day_depr(
|
||||
fb_row,
|
||||
value_after_depreciation,
|
||||
from_date,
|
||||
):
|
||||
to_date = add_days(add_years(from_date, 1), -1)
|
||||
total_days = date_diff(to_date, from_date) + 1
|
||||
per_day_depr = (flt(value_after_depreciation) * (flt(fb_row.rate_of_depreciation) / 100)) / total_days
|
||||
return per_day_depr
|
||||
|
||||
|
||||
def _get_total_days(depreciation_start_date, schedule_idx, frequency_of_depreciation):
|
||||
from_date = add_months(depreciation_start_date, (schedule_idx - 1) * frequency_of_depreciation)
|
||||
to_date = add_months(from_date, frequency_of_depreciation)
|
||||
if is_last_day_of_the_month(depreciation_start_date):
|
||||
to_date = get_last_day(to_date)
|
||||
from_date = add_days(get_last_day(from_date), 1)
|
||||
return from_date, date_diff(to_date, from_date) + 1
|
||||
@@ -90,7 +90,8 @@
|
||||
"fieldname": "rate_of_depreciation",
|
||||
"fieldtype": "Percent",
|
||||
"label": "Rate of Depreciation (%)",
|
||||
"mandatory_depends_on": "eval:doc.depreciation_method == 'Written Down Value'"
|
||||
"mandatory_depends_on": "eval:doc.depreciation_method == 'Written Down Value'",
|
||||
"no_copy": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "salvage_value_percentage",
|
||||
@@ -116,6 +117,7 @@
|
||||
"fieldname": "total_number_of_booked_depreciations",
|
||||
"fieldtype": "Int",
|
||||
"label": "Total Number of Booked Depreciations ",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@@ -138,7 +140,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-12-19 17:50:24.012434",
|
||||
"modified": "2025-01-06 17:14:51.836803",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Finance Book",
|
||||
|
||||
@@ -157,6 +157,7 @@
|
||||
"options": "Asset Repair Consumed Item"
|
||||
},
|
||||
{
|
||||
"fetch_from": "company.cost_center",
|
||||
"fieldname": "cost_center",
|
||||
"fieldtype": "Link",
|
||||
"label": "Cost Center",
|
||||
@@ -258,7 +259,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-12-23 18:08:35.159964",
|
||||
"modified": "2024-12-27 18:11:40.548727",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Repair",
|
||||
|
||||
@@ -12,7 +12,7 @@ from erpnext.assets.doctype.asset.asset import get_asset_account
|
||||
from erpnext.assets.doctype.asset_activity.asset_activity import add_asset_activity
|
||||
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
|
||||
get_depr_schedule,
|
||||
make_new_active_asset_depr_schedules_and_cancel_current_ones,
|
||||
reschedule_depreciation,
|
||||
)
|
||||
from erpnext.controllers.accounts_controller import AccountsController
|
||||
|
||||
@@ -144,30 +144,27 @@ class AssetRepair(AccountsController):
|
||||
self.total_repair_cost = flt(self.repair_cost) + flt(self.consumed_items_cost)
|
||||
|
||||
def on_submit(self):
|
||||
self.asset_doc.flags.increase_in_asset_value_due_to_repair = False
|
||||
self.decrease_stock_quantity()
|
||||
|
||||
if self.get("capitalize_repair_cost"):
|
||||
self.update_asset_value()
|
||||
self.make_gl_entries()
|
||||
self.set_increase_in_asset_life()
|
||||
|
||||
depreciation_note = self.get_depreciation_note()
|
||||
make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, depreciation_note)
|
||||
reschedule_depreciation(self.asset_doc, depreciation_note)
|
||||
self.add_asset_activity()
|
||||
|
||||
self.make_gl_entries()
|
||||
|
||||
def on_cancel(self):
|
||||
self.asset_doc = frappe.get_doc("Asset", self.asset)
|
||||
|
||||
if self.get("capitalize_repair_cost"):
|
||||
self.asset_doc.flags.increase_in_asset_value_due_to_repair = True
|
||||
|
||||
self.update_asset_value()
|
||||
self.make_gl_entries(cancel=True)
|
||||
self.set_increase_in_asset_life()
|
||||
|
||||
depreciation_note = self.get_depreciation_note()
|
||||
make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, depreciation_note)
|
||||
reschedule_depreciation(self.asset_doc, depreciation_note)
|
||||
self.add_asset_activity()
|
||||
|
||||
def after_delete(self):
|
||||
@@ -358,9 +355,10 @@ class AssetRepair(AccountsController):
|
||||
def set_increase_in_asset_life(self):
|
||||
if self.asset_doc.calculate_depreciation and cint(self.increase_in_asset_life) > 0:
|
||||
for row in self.asset_doc.finance_books:
|
||||
row.increase_in_asset_life = row.increase_in_asset_life + (
|
||||
row.increase_in_asset_life = cint(row.increase_in_asset_life) + (
|
||||
cint(self.increase_in_asset_life) * (1 if self.docstatus == 1 else -1)
|
||||
)
|
||||
row.db_update()
|
||||
|
||||
def get_depreciation_note(self):
|
||||
return _("This schedule was created when Asset {0} was repaired through Asset Repair {1}.").format(
|
||||
|
||||
@@ -18,7 +18,9 @@ from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_sched
|
||||
get_asset_depr_schedule_doc,
|
||||
get_temp_asset_depr_schedule_doc,
|
||||
)
|
||||
from erpnext.assets.doctype.asset_depreciation_schedule.utils import get_asset_shift_factors_map
|
||||
from erpnext.erpnext.assets.doctype.asset_depreciation_schedule.deppreciation_schedule_controller import (
|
||||
get_asset_shift_factors_map,
|
||||
)
|
||||
|
||||
|
||||
class AssetShiftAllocation(Document):
|
||||
|
||||
@@ -14,7 +14,7 @@ from erpnext.assets.doctype.asset.asset import get_asset_value_after_depreciatio
|
||||
from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts
|
||||
from erpnext.assets.doctype.asset_activity.asset_activity import add_asset_activity
|
||||
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
|
||||
make_new_active_asset_depr_schedules_and_cancel_current_ones,
|
||||
reschedule_depreciation,
|
||||
)
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ class AssetValueAdjustment(Document):
|
||||
self.current_asset_value = get_asset_value_after_depreciation(self.asset, self.finance_book)
|
||||
|
||||
def on_submit(self):
|
||||
self.make_depreciation_entry()
|
||||
self.make_asset_revaluation_entry()
|
||||
self.update_asset()
|
||||
add_asset_activity(
|
||||
self.asset,
|
||||
@@ -83,7 +83,7 @@ class AssetValueAdjustment(Document):
|
||||
),
|
||||
)
|
||||
|
||||
def make_depreciation_entry(self):
|
||||
def make_asset_revaluation_entry(self):
|
||||
asset = frappe.get_doc("Asset", self.asset)
|
||||
(
|
||||
fixed_asset_account,
|
||||
@@ -170,7 +170,7 @@ class AssetValueAdjustment(Document):
|
||||
def update_asset(self):
|
||||
asset = self.update_asset_value_after_depreciation()
|
||||
note = self.get_adjustment_note()
|
||||
make_new_active_asset_depr_schedules_and_cancel_current_ones(asset, note)
|
||||
reschedule_depreciation(asset, note)
|
||||
|
||||
def update_asset_value_after_depreciation(self):
|
||||
difference_amount = self.difference_amount if self.docstatus == 1 else -1 * self.difference_amount
|
||||
|
||||
@@ -411,3 +411,4 @@ erpnext.patches.v15_0.update_payment_schedule_fields_in_invoices
|
||||
erpnext.patches.v15_0.rename_group_by_to_categorize_by
|
||||
execute:frappe.db.set_single_value("Accounts Settings", "receivable_payable_fetch_method", "Buffered Cursor")
|
||||
erpnext.patches.v14_0.set_update_price_list_based_on
|
||||
erpnext.patches.v15_0.update_journal_entry_type
|
||||
|
||||
@@ -22,7 +22,7 @@ def execute():
|
||||
asset_depr_schedule_doc.name,
|
||||
{"docstatus": 1, "status": "Active"},
|
||||
)
|
||||
|
||||
|
||||
update_depreciation_schedules(depreciation_schedules, asset_depr_schedule_doc.name)
|
||||
|
||||
|
||||
|
||||
18
erpnext/patches/v15_0/update_journal_entry_type.py
Normal file
18
erpnext/patches/v15_0/update_journal_entry_type.py
Normal file
@@ -0,0 +1,18 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
custom_je_type = frappe.db.get_value(
|
||||
"Property Setter",
|
||||
{"doc_type": "Journal Entry", "field_name": "voucher_type", "property": "options"},
|
||||
["name", "value"],
|
||||
)
|
||||
if custom_je_type:
|
||||
custom_je_type.value += "\nAsset Disposal"
|
||||
frappe.db.set_value("Property Setter", custom_je_type.name, "value", custom_je_type.value)
|
||||
|
||||
scrapped_journal_entries = frappe.get_all(
|
||||
"Asset", filters={"journal_entry_for_scrap": ["is", "not set"]}, fields=["name"]
|
||||
)
|
||||
for je in scrapped_journal_entries:
|
||||
frappe.db.set_value("Journal Entry", je.name, "voucher_type", "Asset Disposal")
|
||||
Reference in New Issue
Block a user