mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-16 11:39:18 +00:00
chore: rebase from develop
This commit is contained in:
@@ -89,7 +89,7 @@
|
|||||||
"label": "Entry Type",
|
"label": "Entry Type",
|
||||||
"oldfieldname": "voucher_type",
|
"oldfieldname": "voucher_type",
|
||||||
"oldfieldtype": "Select",
|
"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,
|
"reqd": 1,
|
||||||
"search_index": 1
|
"search_index": 1
|
||||||
},
|
},
|
||||||
@@ -557,7 +557,7 @@
|
|||||||
"table_fieldname": "payment_entries"
|
"table_fieldname": "payment_entries"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2024-07-18 15:32:29.413598",
|
"modified": "2024-12-26 15:32:20.730666",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Journal Entry",
|
"name": "Journal Entry",
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ class JournalEntry(AccountsController):
|
|||||||
"Write Off Entry",
|
"Write Off Entry",
|
||||||
"Opening Entry",
|
"Opening Entry",
|
||||||
"Depreciation Entry",
|
"Depreciation Entry",
|
||||||
|
"Asset Disposal",
|
||||||
"Exchange Rate Revaluation",
|
"Exchange Rate Revaluation",
|
||||||
"Exchange Gain Or Loss",
|
"Exchange Gain Or Loss",
|
||||||
"Deferred Revenue",
|
"Deferred Revenue",
|
||||||
@@ -377,7 +378,11 @@ class JournalEntry(AccountsController):
|
|||||||
self.remove(d)
|
self.remove(d)
|
||||||
|
|
||||||
def update_asset_value(self):
|
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
|
return
|
||||||
|
|
||||||
for d in self.get("accounts"):
|
for d in self.get("accounts"):
|
||||||
@@ -387,24 +392,61 @@ class JournalEntry(AccountsController):
|
|||||||
and d.account_type == "Depreciation"
|
and d.account_type == "Depreciation"
|
||||||
and d.debit
|
and d.debit
|
||||||
):
|
):
|
||||||
asset = frappe.get_doc("Asset", d.reference_name)
|
asset = frappe.get_cached_doc("Asset", d.reference_name)
|
||||||
|
|
||||||
if asset.calculate_depreciation:
|
if asset.calculate_depreciation:
|
||||||
fb_idx = 1
|
self.update_journal_entry_link_on_depr_schedule(asset, d)
|
||||||
if self.finance_book:
|
self.update_value_after_depreciation(asset, d.debit)
|
||||||
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()
|
|
||||||
else:
|
else:
|
||||||
asset.db_set("value_after_depreciation", asset.value_after_depreciation - d.debit)
|
asset.db_set("value_after_depreciation", asset.value_after_depreciation - d.debit)
|
||||||
|
|
||||||
asset.set_status()
|
asset.set_status()
|
||||||
asset.set_total_booked_depreciations()
|
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):
|
def update_inter_company_jv(self):
|
||||||
if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:
|
if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:
|
||||||
frappe.db.set_value(
|
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_disposal,
|
||||||
get_gl_entries_on_asset_regain,
|
get_gl_entries_on_asset_regain,
|
||||||
reset_depreciation_schedule,
|
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_activity.asset_activity import add_asset_activity
|
||||||
from erpnext.controllers.accounts_controller import validate_account_head
|
from erpnext.controllers.accounts_controller import validate_account_head
|
||||||
@@ -368,21 +368,34 @@ class SalesInvoice(SellingController):
|
|||||||
validate_docs_for_deferred_accounting([self.name], [])
|
validate_docs_for_deferred_accounting([self.name], [])
|
||||||
|
|
||||||
def validate_fixed_asset(self):
|
def validate_fixed_asset(self):
|
||||||
for d in self.get("items"):
|
if self.doctype != "Sales Invoice":
|
||||||
if d.is_fixed_asset and d.meta.get_field("asset") and d.asset:
|
return
|
||||||
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"))
|
|
||||||
|
|
||||||
elif asset.status in ("Scrapped", "Cancelled", "Capitalized") or (
|
for d in self.get("items"):
|
||||||
asset.status == "Sold" and not self.is_return
|
if d.is_fixed_asset:
|
||||||
):
|
if d.asset:
|
||||||
frappe.throw(
|
if not self.is_return:
|
||||||
_("Row #{0}: Asset {1} cannot be submitted, it is already {2}").format(
|
asset_status = frappe.db.get_value("Asset", d.asset, "status")
|
||||||
d.idx, d.asset, 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):
|
def validate_item_cost_centers(self):
|
||||||
for item in self.items:
|
for item in self.items:
|
||||||
@@ -464,6 +477,8 @@ class SalesInvoice(SellingController):
|
|||||||
self.update_stock_reservation_entries()
|
self.update_stock_reservation_entries()
|
||||||
self.update_stock_ledger()
|
self.update_stock_ledger()
|
||||||
|
|
||||||
|
self.process_asset_depreciation()
|
||||||
|
|
||||||
# this sequence because outstanding may get -ve
|
# this sequence because outstanding may get -ve
|
||||||
self.make_gl_entries()
|
self.make_gl_entries()
|
||||||
|
|
||||||
@@ -583,6 +598,8 @@ class SalesInvoice(SellingController):
|
|||||||
if self.update_stock == 1:
|
if self.update_stock == 1:
|
||||||
self.update_stock_ledger()
|
self.update_stock_ledger()
|
||||||
|
|
||||||
|
self.process_asset_depreciation()
|
||||||
|
|
||||||
self.make_gl_entries_on_cancel()
|
self.make_gl_entries_on_cancel()
|
||||||
|
|
||||||
if self.update_stock == 1:
|
if self.update_stock == 1:
|
||||||
@@ -1253,6 +1270,90 @@ class SalesInvoice(SellingController):
|
|||||||
):
|
):
|
||||||
throw(_("Delivery Note {0} is not submitted").format(d.delivery_note))
|
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):
|
def make_gl_entries(self, gl_entries=None, from_repost=False):
|
||||||
from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries
|
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():
|
if self.is_internal_transfer():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if item.is_fixed_asset:
|
if item.is_fixed_asset and item.asset:
|
||||||
asset = self.get_asset(item)
|
self.get_gl_entries_for_fixed_asset(item, gl_entries)
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
income_account = (
|
income_account = (
|
||||||
item.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):
|
if cint(self.update_stock) and erpnext.is_perpetual_inventory_enabled(self.company):
|
||||||
gl_entries += super().get_gl_entries()
|
gl_entries += super().get_gl_entries()
|
||||||
|
|
||||||
def get_asset(self, item):
|
def get_gl_entries_for_fixed_asset(self, item, gl_entries):
|
||||||
if item.get("asset"):
|
asset = frappe.get_cached_doc("Asset", item.asset)
|
||||||
asset = frappe.get_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:
|
else:
|
||||||
frappe.throw(
|
fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(
|
||||||
_("Row #{0}: You must select an Asset for Item {1}.").format(item.idx, item.item_name),
|
asset,
|
||||||
title=_("Missing Asset"),
|
item.base_net_amount,
|
||||||
|
item.finance_book,
|
||||||
|
self.get("doctype"),
|
||||||
|
self.get("name"),
|
||||||
|
self.get("posting_date"),
|
||||||
)
|
)
|
||||||
|
|
||||||
self.check_finance_books(item, asset)
|
for gle in fixed_asset_gl_entries:
|
||||||
return asset
|
gle["against"] = self.customer
|
||||||
|
gl_entries.append(self.get_gl_dict(gle, item=item))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def enable_discount_accounting(self):
|
def enable_discount_accounting(self):
|
||||||
@@ -1539,12 +1598,6 @@ class SalesInvoice(SellingController):
|
|||||||
|
|
||||||
return self._enable_discount_accounting
|
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):
|
def make_loyalty_point_redemption_gle(self, gl_entries):
|
||||||
if cint(self.redeem_loyalty_points and self.loyalty_points and not self.is_consolidated):
|
if cint(self.redeem_loyalty_points and self.loyalty_points and not self.is_consolidated):
|
||||||
gl_entries.append(
|
gl_entries.append(
|
||||||
|
|||||||
@@ -103,14 +103,26 @@ frappe.ui.form.on("Asset", {
|
|||||||
},
|
},
|
||||||
__("Manage")
|
__("Manage")
|
||||||
);
|
);
|
||||||
} else if (frm.doc.status == "Scrapped") {
|
|
||||||
frm.add_custom_button(
|
frm.add_custom_button(
|
||||||
__("Restore Asset"),
|
__("Repair Asset"),
|
||||||
function () {
|
function () {
|
||||||
erpnext.asset.restore_asset(frm);
|
frm.trigger("create_asset_repair");
|
||||||
},
|
},
|
||||||
__("Manage")
|
__("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) {
|
if (frm.doc.maintenance_required && !frm.doc.maintenance_schedule) {
|
||||||
@@ -123,23 +135,7 @@ frappe.ui.form.on("Asset", {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
frm.add_custom_button(
|
if (in_list(["Submitted", "Partially Depreciated"], frm.doc.status)) {
|
||||||
__("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") {
|
|
||||||
frm.add_custom_button(
|
frm.add_custom_button(
|
||||||
__("Adjust Asset Value"),
|
__("Adjust Asset Value"),
|
||||||
function () {
|
function () {
|
||||||
|
|||||||
@@ -33,8 +33,6 @@ from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_sched
|
|||||||
convert_draft_asset_depr_schedules_into_active,
|
convert_draft_asset_depr_schedules_into_active,
|
||||||
get_asset_depr_schedule_doc,
|
get_asset_depr_schedule_doc,
|
||||||
get_depr_schedule,
|
get_depr_schedule,
|
||||||
make_draft_asset_depr_schedules,
|
|
||||||
update_draft_asset_depr_schedules,
|
|
||||||
)
|
)
|
||||||
from erpnext.controllers.accounts_controller import AccountsController
|
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)
|
schedule_doc = get_asset_depr_schedule_doc(self.name, "Draft", row.finance_book)
|
||||||
if not schedule_doc:
|
if not schedule_doc:
|
||||||
schedule_doc = frappe.new_doc("Asset Depreciation Schedule")
|
schedule_doc = frappe.new_doc("Asset Depreciation Schedule")
|
||||||
|
schedule_doc.asset = self.name
|
||||||
schedule_doc.prepare_draft_asset_depr_schedule_data(self, row)
|
schedule_doc.create_depreciation_schedule(row)
|
||||||
schedule_doc.save()
|
schedule_doc.save()
|
||||||
schedules.append(schedule_doc.name)
|
schedules.append(schedule_doc.name)
|
||||||
|
|
||||||
self.show_schedule_creation_message(schedules)
|
self.show_schedule_creation_message(schedules)
|
||||||
|
|
||||||
def set_depr_rate_and_value_after_depreciation(self):
|
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:
|
if self.calculate_depreciation:
|
||||||
self.value_after_depreciation = 0
|
|
||||||
self.set_depreciation_rate()
|
self.set_depreciation_rate()
|
||||||
|
for d in self.finance_books:
|
||||||
|
d.value_after_depreciation = self.value_after_depreciation
|
||||||
else:
|
else:
|
||||||
self.finance_books = []
|
self.finance_books = []
|
||||||
self.value_after_depreciation = flt(self.gross_purchase_amount) - flt(
|
|
||||||
self.opening_accumulated_depreciation
|
|
||||||
)
|
|
||||||
|
|
||||||
def show_schedule_creation_message(self, schedules):
|
def show_schedule_creation_message(self, schedules):
|
||||||
if schedules:
|
if schedules:
|
||||||
@@ -845,41 +844,31 @@ class Asset(AccountsController):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def get_written_down_value_rate(self, args, rate_field_precision, on_validate):
|
def get_written_down_value_rate(self, args, rate_field_precision, on_validate):
|
||||||
if (
|
if args.get("rate_of_depreciation") and on_validate:
|
||||||
args.get("rate_of_depreciation")
|
|
||||||
and on_validate
|
|
||||||
and not self.flags.increase_in_asset_value_due_to_repair
|
|
||||||
):
|
|
||||||
return args.get("rate_of_depreciation")
|
return args.get("rate_of_depreciation")
|
||||||
|
|
||||||
if args.get("rate_of_depreciation") and not flt(args.get("expected_value_after_useful_life")):
|
if args.get("rate_of_depreciation") and not flt(args.get("expected_value_after_useful_life")):
|
||||||
return args.get("rate_of_depreciation")
|
return args.get("rate_of_depreciation")
|
||||||
|
|
||||||
if self.flags.increase_in_asset_value_due_to_repair:
|
if flt(args.get("value_after_depreciation")):
|
||||||
value = flt(args.get("expected_value_after_useful_life")) / flt(
|
current_asset_value = flt(args.get("value_after_depreciation"))
|
||||||
args.get("value_after_depreciation")
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
value = flt(args.get("expected_value_after_useful_life")) / (
|
current_asset_value = flt(self.gross_purchase_amount) - flt(self.opening_accumulated_depreciation)
|
||||||
flt(self.gross_purchase_amount) - flt(self.opening_accumulated_depreciation)
|
|
||||||
)
|
|
||||||
|
|
||||||
depreciation_rate = math.pow(
|
value = flt(args.get("expected_value_after_useful_life")) / current_asset_value
|
||||||
value,
|
|
||||||
1.0
|
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"))
|
||||||
flt(args.get("total_number_of_depreciations"), 2)
|
|
||||||
- flt(self.opening_number_of_booked_depreciations)
|
|
||||||
)
|
|
||||||
* flt(args.get("frequency_of_depreciation"))
|
|
||||||
)
|
|
||||||
/ 12
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
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):
|
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)
|
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 = 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
|
accumulated_depreciation = 0
|
||||||
|
|
||||||
@@ -1310,7 +1299,7 @@ def create_new_asset_after_split(asset, split_qty):
|
|||||||
continue
|
continue
|
||||||
new_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc)
|
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
|
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_doc,
|
||||||
get_asset_depr_schedule_name,
|
get_asset_depr_schedule_name,
|
||||||
get_temp_asset_depr_schedule_doc,
|
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
|
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"):
|
for row in asset_doc.get("finance_books"):
|
||||||
asset_depr_schedule_name = get_asset_depr_schedule_name(asset_doc.name, "Active", row.finance_book)
|
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():
|
def get_acc_frozen_upto():
|
||||||
@@ -244,10 +244,12 @@ def make_depreciation_entry(
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
depreciation_posting_error = e
|
depreciation_posting_error = e
|
||||||
|
|
||||||
|
asset.reload()
|
||||||
asset.set_status()
|
asset.set_status()
|
||||||
|
|
||||||
if not depreciation_posting_error:
|
if not depreciation_posting_error:
|
||||||
asset.db_set("depr_entry_posting_status", "Successful")
|
asset.db_set("depr_entry_posting_status", "Successful")
|
||||||
|
asset_depr_schedule_doc.reload()
|
||||||
return asset_depr_schedule_doc
|
return asset_depr_schedule_doc
|
||||||
|
|
||||||
raise depreciation_posting_error
|
raise depreciation_posting_error
|
||||||
@@ -316,18 +318,10 @@ def _make_journal_entry_for_depreciation(
|
|||||||
je.append("accounts", debit_entry)
|
je.append("accounts", debit_entry)
|
||||||
|
|
||||||
je.flags.ignore_permissions = True
|
je.flags.ignore_permissions = True
|
||||||
je.flags.planned_depr_entry = True
|
|
||||||
je.save()
|
je.save()
|
||||||
|
|
||||||
depr_schedule.db_set("journal_entry", je.name)
|
|
||||||
|
|
||||||
if not je.meta.get_workflow():
|
if not je.meta.get_workflow():
|
||||||
je.submit()
|
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):
|
def get_depreciation_accounts(asset_category, company):
|
||||||
@@ -433,194 +427,162 @@ def get_comma_separated_links(names, doctype):
|
|||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def scrap_asset(asset_name, scrap_date=None):
|
def scrap_asset(asset_name, scrap_date=None):
|
||||||
asset = frappe.get_doc("Asset", asset_name)
|
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:
|
if asset.docstatus != 1:
|
||||||
frappe.throw(_("Asset {0} must be submitted").format(asset.name))
|
frappe.throw(_("Asset {0} must be submitted").format(asset.name))
|
||||||
elif asset.status in ("Cancelled", "Sold", "Scrapped", "Capitalized"):
|
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))
|
frappe.throw(_("Asset {0} cannot be scrapped, as it is already {1}").format(asset.name, asset.status))
|
||||||
|
|
||||||
today_date = getdate(today())
|
validate_scrap_date(asset, scrap_date)
|
||||||
date = getdate(scrap_date) or today_date
|
|
||||||
purchase_date = getdate(asset.purchase_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)
|
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")
|
depreciation_series = frappe.get_cached_value("Company", asset.company, "series_for_depreciation_entry")
|
||||||
|
|
||||||
je = frappe.new_doc("Journal Entry")
|
je = frappe.new_doc("Journal Entry")
|
||||||
je.voucher_type = "Journal Entry"
|
je.voucher_type = "Asset Disposal"
|
||||||
je.naming_series = depreciation_series
|
je.naming_series = depreciation_series
|
||||||
je.posting_date = date
|
je.posting_date = scrap_date
|
||||||
je.company = asset.company
|
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):
|
for entry in get_gl_entries_on_asset_disposal(asset, scrap_date):
|
||||||
entry.update({"reference_type": "Asset", "reference_name": asset_name})
|
entry.update({"reference_type": "Asset", "reference_name": asset.name})
|
||||||
je.append("accounts", entry)
|
je.append("accounts", entry)
|
||||||
|
|
||||||
je.flags.ignore_permissions = True
|
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)
|
add_asset_activity(asset.name, _("Asset scrapped"))
|
||||||
frappe.db.set_value("Asset", asset_name, "journal_entry_for_scrap", je.name)
|
frappe.msgprint(
|
||||||
asset.set_status("Scrapped")
|
_("Asset scrapped via Journal Entry {0}").format(get_link_to_form("Journal Entry", je.name))
|
||||||
|
)
|
||||||
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."))
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def restore_asset(asset_name):
|
def restore_asset(asset_name):
|
||||||
asset = frappe.get_doc("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
|
def get_note_for_restore(asset):
|
||||||
|
return _("This schedule was created when Asset {0} was restored.").format(
|
||||||
notes = _("This schedule was created when Asset {0} was restored.").format(
|
|
||||||
get_link_to_form(asset.doctype, asset.name)
|
get_link_to_form(asset.doctype, asset.name)
|
||||||
)
|
)
|
||||||
|
|
||||||
reset_depreciation_schedule(asset, asset.disposal_date, notes)
|
|
||||||
|
|
||||||
asset.db_set("disposal_date", None)
|
def cancel_journal_entry_for_scrap(asset):
|
||||||
asset.db_set("journal_entry_for_scrap", None)
|
if asset.journal_entry_for_scrap:
|
||||||
|
je = asset.journal_entry_for_scrap
|
||||||
frappe.get_doc("Journal Entry", je).cancel()
|
asset.db_set("disposal_date", None)
|
||||||
|
asset.db_set("journal_entry_for_scrap", None)
|
||||||
asset.set_status()
|
frappe.get_doc("Journal Entry", je).cancel()
|
||||||
|
|
||||||
add_asset_activity(asset_name, _("Asset restored"))
|
|
||||||
|
|
||||||
|
|
||||||
def depreciate_asset(asset_doc, date, notes):
|
def depreciate_asset(asset_doc, date, notes):
|
||||||
if not asset_doc.calculate_depreciation:
|
if not asset_doc.calculate_depreciation:
|
||||||
return
|
return
|
||||||
|
|
||||||
asset_doc.flags.ignore_validate_update_after_submit = True
|
reschedule_depreciation(asset_doc, notes, disposal_date=date)
|
||||||
|
make_depreciation_entry_on_disposal(asset_doc, date)
|
||||||
make_new_active_asset_depr_schedules_and_cancel_current_ones(asset_doc, notes, date_of_disposal=date)
|
|
||||||
|
|
||||||
asset_doc.save()
|
|
||||||
|
|
||||||
make_depreciation_entry_for_all_asset_depr_schedules(asset_doc, date)
|
|
||||||
|
|
||||||
|
# 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()
|
asset_doc.reload()
|
||||||
cancel_depreciation_entries(asset_doc, date)
|
# cancel_depreciation_entries(asset_doc, date)
|
||||||
|
|
||||||
|
|
||||||
@erpnext.allow_regional
|
@erpnext.allow_regional
|
||||||
def cancel_depreciation_entries(asset_doc, date):
|
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
|
pass
|
||||||
|
|
||||||
|
|
||||||
def reset_depreciation_schedule(asset_doc, date, notes):
|
def reset_depreciation_schedule(asset_doc, notes):
|
||||||
if not asset_doc.calculate_depreciation:
|
if asset_doc.calculate_depreciation:
|
||||||
return
|
reschedule_depreciation(asset_doc, notes)
|
||||||
|
|
||||||
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 modify_depreciation_schedule_for_asset_repairs(asset, notes):
|
def reverse_depreciation_entry_made_on_disposal(asset):
|
||||||
asset_repairs = frappe.get_all(
|
|
||||||
"Asset Repair", filters={"asset": asset.name}, fields=["name", "increase_in_asset_life"]
|
|
||||||
)
|
|
||||||
|
|
||||||
for repair in asset_repairs:
|
|
||||||
if repair.increase_in_asset_life:
|
|
||||||
asset_repair = frappe.get_doc("Asset Repair", repair.name)
|
|
||||||
asset_repair.modify_depreciation_schedule()
|
|
||||||
make_new_active_asset_depr_schedules_and_cancel_current_ones(asset, notes)
|
|
||||||
|
|
||||||
|
|
||||||
def reverse_depreciation_entry_made_after_disposal(asset, date):
|
|
||||||
for row in asset.get("finance_books"):
|
for row in asset.get("finance_books"):
|
||||||
asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active", row.finance_book)
|
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"):
|
if not schedule_doc or not schedule_doc.get("depreciation_schedule"):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for schedule_idx, schedule in enumerate(asset_depr_schedule_doc.get("depreciation_schedule")):
|
for schedule_idx, schedule in enumerate(schedule_doc.get("depreciation_schedule")):
|
||||||
if schedule.schedule_date == date and schedule.journal_entry:
|
if schedule.schedule_date == asset.disposal_date and schedule.journal_entry:
|
||||||
if not disposal_was_made_on_original_schedule_date(
|
if not disposal_was_made_on_original_schedule_date(
|
||||||
schedule_idx, row, date
|
schedule_idx, row, asset.disposal_date
|
||||||
) or disposal_happens_in_the_future(date):
|
) or disposal_happens_in_the_future(asset.disposal_date):
|
||||||
reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry)
|
je = create_reverse_depreciation_entry(asset.name, schedule.journal_entry)
|
||||||
reverse_journal_entry.posting_date = nowdate()
|
update_value_after_depreciation_on_asset_restore(schedule, row, je)
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
|
|
||||||
def get_depreciation_amount_in_je(journal_entry):
|
def disposal_was_made_on_original_schedule_date(schedule_idx, row, disposal_date):
|
||||||
if journal_entry.accounts[0].debit_in_account_currency:
|
"""
|
||||||
return journal_entry.accounts[0].debit_in_account_currency
|
If asset is scrapped or sold on original schedule date,
|
||||||
else:
|
then the depreciation entry should not be reversed.
|
||||||
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):
|
|
||||||
orginal_schedule_date = add_months(
|
orginal_schedule_date = add_months(
|
||||||
row.depreciation_start_date, schedule_idx * cint(row.frequency_of_depreciation)
|
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):
|
if is_last_day_of_the_month(row.depreciation_start_date):
|
||||||
orginal_schedule_date = get_last_day(orginal_schedule_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 True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def disposal_happens_in_the_future(posting_date_of_disposal):
|
def disposal_happens_in_the_future(disposal_date):
|
||||||
if posting_date_of_disposal > getdate():
|
if disposal_date > getdate():
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
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(
|
def get_gl_entries_on_asset_regain(
|
||||||
asset, selling_amount=0, finance_book=None, voucher_type=None, voucher_no=None, date=None
|
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_asset_depr_schedule_doc,
|
||||||
get_depr_schedule,
|
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,
|
get_depreciation_amount,
|
||||||
)
|
)
|
||||||
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
|
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_gl_entries_on_asset_disposal,
|
||||||
get_value_after_depreciation_on_disposal_date,
|
get_value_after_depreciation_on_disposal_date,
|
||||||
reset_depreciation_schedule,
|
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_activity.asset_activity import add_asset_activity
|
||||||
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
|
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
|
||||||
@@ -623,13 +623,13 @@ class AssetCapitalization(StockController):
|
|||||||
self.set_consumed_asset_status(asset)
|
self.set_consumed_asset_status(asset)
|
||||||
|
|
||||||
if asset.calculate_depreciation:
|
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 = _(
|
notes = _(
|
||||||
"This schedule was created when Asset {0} was restored on Asset Capitalization {1}'s cancellation."
|
"This schedule was created when Asset {0} was restored on Asset Capitalization {1}'s cancellation."
|
||||||
).format(
|
).format(
|
||||||
get_link_to_form(asset.doctype, asset.name), get_link_to_form(self.doctype, self.name)
|
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):
|
def set_consumed_asset_status(self, asset):
|
||||||
if self.docstatus == 1:
|
if self.docstatus == 1:
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
|
||||||
from frappe.utils import (
|
from frappe.utils import (
|
||||||
add_days,
|
add_days,
|
||||||
add_months,
|
add_months,
|
||||||
@@ -20,13 +19,12 @@ from frappe.utils import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
from erpnext.accounts.utils import get_fiscal_year
|
from erpnext.assets.doctype.asset_depreciation_schedule.deppreciation_schedule_controller import (
|
||||||
from erpnext.assets.doctype.asset_depreciation_schedule.utils import (
|
DepreciationScheduleController,
|
||||||
get_depreciation_amount,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class AssetDepreciationSchedule(Document):
|
class AssetDepreciationSchedule(DepreciationScheduleController):
|
||||||
# begin: auto-generated types
|
# begin: auto-generated types
|
||||||
# This code is auto-generated. Do not modify anything in this block.
|
# This code is auto-generated. Do not modify anything in this block.
|
||||||
|
|
||||||
@@ -61,15 +59,11 @@ class AssetDepreciationSchedule(Document):
|
|||||||
value_after_depreciation: DF.Currency
|
value_after_depreciation: DF.Currency
|
||||||
# end: auto-generated types
|
# 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):
|
def validate(self):
|
||||||
self.validate_another_asset_depr_schedule_does_not_exist()
|
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):
|
def validate_another_asset_depr_schedule_does_not_exist(self):
|
||||||
finance_book_filter = ["finance_book", "is", "not set"]
|
finance_book_filter = ["finance_book", "is", "not set"]
|
||||||
@@ -102,7 +96,8 @@ class AssetDepreciationSchedule(Document):
|
|||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.db_set("status", "Active")
|
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:
|
if not self.flags.should_not_cancel_depreciation_entries:
|
||||||
self.cancel_depreciation_entries()
|
self.cancel_depreciation_entries()
|
||||||
|
|
||||||
@@ -111,9 +106,6 @@ class AssetDepreciationSchedule(Document):
|
|||||||
if d.journal_entry:
|
if d.journal_entry:
|
||||||
frappe.get_doc("Journal Entry", d.journal_entry).cancel()
|
frappe.get_doc("Journal Entry", d.journal_entry).cancel()
|
||||||
|
|
||||||
def on_cancel(self):
|
|
||||||
self.db_set("status", "Cancelled")
|
|
||||||
|
|
||||||
def update_shift_depr_schedule(self):
|
def update_shift_depr_schedule(self):
|
||||||
if not self.shift_based or self.docstatus != 0:
|
if not self.shift_based or self.docstatus != 0:
|
||||||
return
|
return
|
||||||
@@ -124,594 +116,50 @@ class AssetDepreciationSchedule(Document):
|
|||||||
self.make_depr_schedule(asset_doc, fb_row)
|
self.make_depr_schedule(asset_doc, fb_row)
|
||||||
self.set_accumulated_depreciation(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):
|
def get_finance_book_row(self, fb_row=None):
|
||||||
asset_doc = frappe.get_doc("Asset", asset_name)
|
if fb_row:
|
||||||
|
self.fb_row = fb_row
|
||||||
|
return
|
||||||
|
|
||||||
finance_book_filter = ["finance_book", "is", "not set"]
|
finance_book_filter = ["finance_book", "is", "not set"]
|
||||||
if fb_name:
|
if self.finance_book:
|
||||||
finance_book_filter = ["finance_book", "=", fb_name]
|
finance_book_filter = ["finance_book", "=", self.finance_book]
|
||||||
|
|
||||||
asset_finance_book_name = frappe.db.get_value(
|
asset_finance_book_name = frappe.db.get_value(
|
||||||
doctype="Asset Finance Book",
|
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 fetch_asset_details(self):
|
||||||
|
self.asset = self.asset_doc.name
|
||||||
def prepare_draft_asset_depr_schedule_data(
|
self.finance_book = self.fb_row.get("finance_book")
|
||||||
self,
|
self.finance_book_id = self.fb_row.idx
|
||||||
asset_doc,
|
self.opening_accumulated_depreciation = self.asset_doc.opening_accumulated_depreciation or 0
|
||||||
row,
|
self.opening_number_of_booked_depreciations = (
|
||||||
date_of_disposal=None,
|
self.asset_doc.opening_number_of_booked_depreciations or 0
|
||||||
date_of_return=None,
|
)
|
||||||
update_asset_finance_book_row=True,
|
self.gross_purchase_amount = self.asset_doc.gross_purchase_amount
|
||||||
):
|
self.depreciation_method = self.fb_row.depreciation_method
|
||||||
self.set_draft_asset_depr_schedule_details(asset_doc, row)
|
self.total_number_of_depreciations = self.fb_row.total_number_of_depreciations
|
||||||
self.make_depr_schedule(asset_doc, row, date_of_disposal, update_asset_finance_book_row)
|
self.frequency_of_depreciation = self.fb_row.frequency_of_depreciation
|
||||||
self.set_accumulated_depreciation(asset_doc, row, date_of_disposal, date_of_return)
|
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")
|
||||||
def set_draft_asset_depr_schedule_details(self, asset_doc, row):
|
self.daily_prorata_based = self.fb_row.get("daily_prorata_based")
|
||||||
self.asset = asset_doc.name
|
self.shift_based = self.fb_row.get("shift_based")
|
||||||
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
|
|
||||||
self.status = "Draft"
|
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):
|
def make_draft_asset_depr_schedule(asset_doc, row):
|
||||||
asset_depr_schedule_doc = frappe.new_doc("Asset Depreciation Schedule")
|
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()
|
asset_depr_schedule_doc.insert()
|
||||||
|
|
||||||
return asset_depr_schedule_doc.name
|
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):
|
def convert_draft_asset_depr_schedules_into_active(asset_doc):
|
||||||
for row in asset_doc.get("finance_books"):
|
for row in asset_doc.get("finance_books"):
|
||||||
asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset_doc.name, "Draft", row.finance_book)
|
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()
|
asset_depr_schedule_doc.cancel()
|
||||||
|
|
||||||
|
|
||||||
def make_new_active_asset_depr_schedules_and_cancel_current_ones(
|
def reschedule_depreciation(asset_doc, notes, disposal_date=None):
|
||||||
asset_doc,
|
|
||||||
notes,
|
|
||||||
date_of_disposal=None,
|
|
||||||
date_of_return=None,
|
|
||||||
value_after_depreciation=None,
|
|
||||||
difference_amount=None,
|
|
||||||
):
|
|
||||||
for row in asset_doc.get("finance_books"):
|
for row in asset_doc.get("finance_books"):
|
||||||
current_asset_depr_schedule_doc = get_asset_depr_schedule_doc(
|
current_schedule = get_asset_depr_schedule_doc(asset_doc.name, None, row.finance_book)
|
||||||
asset_doc.name, "Active", 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:
|
row.db_set("rate_of_depreciation", new_rate_of_depreciation)
|
||||||
frappe.throw(
|
new_schedule.rate_of_depreciation = new_rate_of_depreciation
|
||||||
_("Asset Depreciation Schedule not found for Asset {0} and Finance Book {1}").format(
|
|
||||||
get_link_to_form("Asset", asset_doc.name), row.finance_book
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
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(
|
def get_temp_asset_depr_schedule_doc(
|
||||||
asset_doc,
|
asset_doc,
|
||||||
row,
|
row,
|
||||||
date_of_disposal=None,
|
disposal_date=None,
|
||||||
date_of_return=None,
|
date_of_return=None,
|
||||||
update_asset_finance_book_row=False,
|
update_asset_finance_book_row=False,
|
||||||
new_depr_schedule=None,
|
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(
|
frappe.throw(
|
||||||
_("Asset Depreciation Schedule not found for Asset {0} and Finance Book {1}").format(
|
_("Asset Depreciation Schedule not found for Asset {0} and Finance Book {1}").format(
|
||||||
get_link_to_form("Asset", asset_doc.name), row.finance_book
|
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:
|
if new_depr_schedule:
|
||||||
temp_asset_depr_schedule_doc.depreciation_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,
|
asset_doc,
|
||||||
row,
|
row,
|
||||||
date_of_disposal,
|
disposal_date,
|
||||||
date_of_return,
|
date_of_return,
|
||||||
update_asset_finance_book_row,
|
update_asset_finance_book_row,
|
||||||
)
|
)
|
||||||
@@ -838,7 +275,7 @@ def get_depr_schedule(asset_name, status, finance_book=None):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@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)
|
asset_depr_schedule = get_asset_depr_schedule_name(asset_name, status, finance_book)
|
||||||
|
|
||||||
if not asset_depr_schedule:
|
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
|
return asset_depr_schedule_doc
|
||||||
|
|
||||||
|
|
||||||
def get_asset_depr_schedule_name(asset_name, status, finance_book=None):
|
def get_asset_depr_schedule_name(asset_name, status=None, finance_book=None):
|
||||||
if isinstance(status, str):
|
|
||||||
status = [status]
|
|
||||||
|
|
||||||
filters = [
|
filters = [
|
||||||
["asset", "=", asset_name],
|
["asset", "=", asset_name],
|
||||||
["status", "in", status],
|
|
||||||
["docstatus", "<", 2],
|
["docstatus", "<", 2],
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if status:
|
||||||
|
if isinstance(status, str):
|
||||||
|
status = [status]
|
||||||
|
filters.append(["status", "in", status])
|
||||||
|
|
||||||
if finance_book:
|
if finance_book:
|
||||||
filters.append(["finance_book", "=", finance_book])
|
filters.append(["finance_book", "=", finance_book])
|
||||||
else:
|
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",
|
"fieldname": "rate_of_depreciation",
|
||||||
"fieldtype": "Percent",
|
"fieldtype": "Percent",
|
||||||
"label": "Rate of Depreciation (%)",
|
"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",
|
"fieldname": "salvage_value_percentage",
|
||||||
@@ -116,6 +117,7 @@
|
|||||||
"fieldname": "total_number_of_booked_depreciations",
|
"fieldname": "total_number_of_booked_depreciations",
|
||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
"label": "Total Number of Booked Depreciations ",
|
"label": "Total Number of Booked Depreciations ",
|
||||||
|
"no_copy": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -138,7 +140,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-12-19 17:50:24.012434",
|
"modified": "2025-01-06 17:14:51.836803",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Assets",
|
"module": "Assets",
|
||||||
"name": "Asset Finance Book",
|
"name": "Asset Finance Book",
|
||||||
|
|||||||
@@ -157,6 +157,7 @@
|
|||||||
"options": "Asset Repair Consumed Item"
|
"options": "Asset Repair Consumed Item"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"fetch_from": "company.cost_center",
|
||||||
"fieldname": "cost_center",
|
"fieldname": "cost_center",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Cost Center",
|
"label": "Cost Center",
|
||||||
@@ -258,7 +259,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-12-23 18:08:35.159964",
|
"modified": "2024-12-27 18:11:40.548727",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Assets",
|
"module": "Assets",
|
||||||
"name": "Asset Repair",
|
"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_activity.asset_activity import add_asset_activity
|
||||||
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
|
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
|
||||||
get_depr_schedule,
|
get_depr_schedule,
|
||||||
make_new_active_asset_depr_schedules_and_cancel_current_ones,
|
reschedule_depreciation,
|
||||||
)
|
)
|
||||||
from erpnext.controllers.accounts_controller import AccountsController
|
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)
|
self.total_repair_cost = flt(self.repair_cost) + flt(self.consumed_items_cost)
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.asset_doc.flags.increase_in_asset_value_due_to_repair = False
|
|
||||||
self.decrease_stock_quantity()
|
self.decrease_stock_quantity()
|
||||||
|
|
||||||
if self.get("capitalize_repair_cost"):
|
if self.get("capitalize_repair_cost"):
|
||||||
self.update_asset_value()
|
self.update_asset_value()
|
||||||
self.make_gl_entries()
|
|
||||||
self.set_increase_in_asset_life()
|
self.set_increase_in_asset_life()
|
||||||
|
|
||||||
depreciation_note = self.get_depreciation_note()
|
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.add_asset_activity()
|
||||||
|
|
||||||
|
self.make_gl_entries()
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.asset_doc = frappe.get_doc("Asset", self.asset)
|
self.asset_doc = frappe.get_doc("Asset", self.asset)
|
||||||
|
|
||||||
if self.get("capitalize_repair_cost"):
|
if self.get("capitalize_repair_cost"):
|
||||||
self.asset_doc.flags.increase_in_asset_value_due_to_repair = True
|
|
||||||
|
|
||||||
self.update_asset_value()
|
self.update_asset_value()
|
||||||
self.make_gl_entries(cancel=True)
|
self.make_gl_entries(cancel=True)
|
||||||
self.set_increase_in_asset_life()
|
self.set_increase_in_asset_life()
|
||||||
|
|
||||||
depreciation_note = self.get_depreciation_note()
|
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.add_asset_activity()
|
||||||
|
|
||||||
def after_delete(self):
|
def after_delete(self):
|
||||||
@@ -358,9 +355,10 @@ class AssetRepair(AccountsController):
|
|||||||
def set_increase_in_asset_life(self):
|
def set_increase_in_asset_life(self):
|
||||||
if self.asset_doc.calculate_depreciation and cint(self.increase_in_asset_life) > 0:
|
if self.asset_doc.calculate_depreciation and cint(self.increase_in_asset_life) > 0:
|
||||||
for row in self.asset_doc.finance_books:
|
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)
|
cint(self.increase_in_asset_life) * (1 if self.docstatus == 1 else -1)
|
||||||
)
|
)
|
||||||
|
row.db_update()
|
||||||
|
|
||||||
def get_depreciation_note(self):
|
def get_depreciation_note(self):
|
||||||
return _("This schedule was created when Asset {0} was repaired through Asset Repair {1}.").format(
|
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_asset_depr_schedule_doc,
|
||||||
get_temp_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):
|
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.depreciation import get_depreciation_accounts
|
||||||
from erpnext.assets.doctype.asset_activity.asset_activity import add_asset_activity
|
from erpnext.assets.doctype.asset_activity.asset_activity import add_asset_activity
|
||||||
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
|
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)
|
self.current_asset_value = get_asset_value_after_depreciation(self.asset, self.finance_book)
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.make_depreciation_entry()
|
self.make_asset_revaluation_entry()
|
||||||
self.update_asset()
|
self.update_asset()
|
||||||
add_asset_activity(
|
add_asset_activity(
|
||||||
self.asset,
|
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)
|
asset = frappe.get_doc("Asset", self.asset)
|
||||||
(
|
(
|
||||||
fixed_asset_account,
|
fixed_asset_account,
|
||||||
@@ -170,7 +170,7 @@ class AssetValueAdjustment(Document):
|
|||||||
def update_asset(self):
|
def update_asset(self):
|
||||||
asset = self.update_asset_value_after_depreciation()
|
asset = self.update_asset_value_after_depreciation()
|
||||||
note = self.get_adjustment_note()
|
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):
|
def update_asset_value_after_depreciation(self):
|
||||||
difference_amount = self.difference_amount if self.docstatus == 1 else -1 * self.difference_amount
|
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
|
erpnext.patches.v15_0.rename_group_by_to_categorize_by
|
||||||
execute:frappe.db.set_single_value("Accounts Settings", "receivable_payable_fetch_method", "Buffered Cursor")
|
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.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,
|
asset_depr_schedule_doc.name,
|
||||||
{"docstatus": 1, "status": "Active"},
|
{"docstatus": 1, "status": "Active"},
|
||||||
)
|
)
|
||||||
|
|
||||||
update_depreciation_schedules(depreciation_schedules, asset_depr_schedule_doc.name)
|
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