chore: rebase from develop

This commit is contained in:
Khushi Rawat
2025-01-14 01:46:08 +05:30
parent 44e45b55d4
commit 5f21d7ea1d
20 changed files with 1072 additions and 1297 deletions

View File

@@ -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",

View File

@@ -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(

View File

@@ -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(

View File

@@ -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 () {

View File

@@ -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

View File

@@ -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
): ):

View File

@@ -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 (

View File

@@ -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:

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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",

View File

@@ -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",

View File

@@ -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(

View File

@@ -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):

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View 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")