From adbba1b9c7f9f2c74e7baf29568852d2fbfc193d Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 11 Dec 2024 10:44:13 +0530 Subject: [PATCH] fix: Removed conflict --- erpnext/assets/doctype/asset/asset.py | 273 ++++--- erpnext/assets/doctype/asset/test_asset.py | 2 + .../asset_depreciation_schedule.json | 10 +- .../asset_depreciation_schedule.py | 731 +++++------------- .../asset_depreciation_schedule/utils.py | 351 +++++++++ .../asset_finance_book.json | 5 +- .../asset_shift_allocation.py | 2 +- 7 files changed, 713 insertions(+), 661 deletions(-) create mode 100644 erpnext/assets/doctype/asset_depreciation_schedule/utils.py diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 9b124826fcec..730a67d7271a 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -33,7 +33,6 @@ get_asset_depr_schedule_doc, get_depr_schedule, make_draft_asset_depr_schedules, - make_draft_asset_depr_schedules_if_not_present, update_draft_asset_depr_schedules, ) from erpnext.controllers.accounts_controller import AccountsController @@ -127,30 +126,55 @@ def validate(self): self.set_missing_values() self.validate_gross_and_purchase_amount() self.validate_finance_books() - - if not self.split_from: - self.prepare_depreciation_data() - - if self.calculate_depreciation: - update_draft_asset_depr_schedules(self) - - if frappe.db.exists("Asset", self.name): - asset_depr_schedules_names = make_draft_asset_depr_schedules_if_not_present(self) - - if asset_depr_schedules_names: - asset_depr_schedules_links = get_comma_separated_links( - asset_depr_schedules_names, "Asset Depreciation Schedule" - ) - frappe.msgprint( - _( - "Asset Depreciation Schedules created:
{0}

Please check, edit if needed, and submit the Asset." - ).format(asset_depr_schedules_links) - ) self.validate_expected_value_after_useful_life() self.set_total_booked_depreciations() self.total_asset_cost = self.gross_purchase_amount self.status = self.get_status() + def create_asset_depreciation_schedule(self): + if self.split_from or not self.calculate_depreciation: + return + + self.set_depr_rate_and_value_after_depreciation() + + schedules = [] + for row in self.get("finance_books"): + self.validate_asset_finance_books(row) + if not row.rate_of_depreciation: + row.rate_of_depreciation = self.get_depreciation_rate(row, on_validate=True) + + schedule_doc = get_asset_depr_schedule_doc(self.name, "Draft", row.finance_book) + if not schedule_doc: + schedule_doc = frappe.new_doc("Asset Depreciation Schedule") + + schedule_doc.prepare_draft_asset_depr_schedule_data(self, row) + schedule_doc.save() + schedules.append(schedule_doc.name) + + self.show_schedule_creation_message(schedules) + + def set_depr_rate_and_value_after_depreciation(self): + if self.calculate_depreciation: + self.value_after_depreciation = 0 + self.set_depreciation_rate() + else: + self.finance_books = [] + self.value_after_depreciation = flt(self.gross_purchase_amount) - flt( + self.opening_accumulated_depreciation + ) + + def show_schedule_creation_message(self, schedules): + if schedules: + asset_depr_schedules_links = get_comma_separated_links(schedules, "Asset Depreciation Schedule") + frappe.msgprint( + _( + "Asset Depreciation Schedules created/updated:
{0}

Please check, edit if needed, and submit the Asset." + ).format(asset_depr_schedules_links) + ) + + def on_update(self): + self.create_asset_depreciation_schedule() + def on_submit(self): self.validate_in_use_date() self.make_asset_movement() @@ -174,17 +198,17 @@ def on_cancel(self): self.db_set("booked_fixed_asset", 0) add_asset_activity(self.name, _("Asset cancelled")) - def after_insert(self): - if self.calculate_depreciation and not self.split_from: - asset_depr_schedules_names = make_draft_asset_depr_schedules(self) - asset_depr_schedules_links = get_comma_separated_links( - asset_depr_schedules_names, "Asset Depreciation Schedule" - ) - frappe.msgprint( - _( - "Asset Depreciation Schedules created:
{0}

Please check, edit if needed, and submit the Asset." - ).format(asset_depr_schedules_links) - ) + # def after_insert(self): + # if self.calculate_depreciation and not self.split_from: + # asset_depr_schedules_names = make_draft_asset_depr_schedules(self) + # asset_depr_schedules_links = get_comma_separated_links( + # asset_depr_schedules_names, "Asset Depreciation Schedule" + # ) + # frappe.msgprint( + # _( + # "Asset Depreciation Schedules created:
{0}

Please check, edit if needed, and submit the Asset." + # ).format(asset_depr_schedules_links) + # ) if ( not frappe.db.exists( { @@ -214,16 +238,6 @@ def validate_asset_and_reference(self): if self.is_existing_asset and self.purchase_invoice: frappe.throw(_("Purchase Invoice cannot be made against an existing asset {0}").format(self.name)) - def prepare_depreciation_data(self): - if self.calculate_depreciation: - self.value_after_depreciation = 0 - self.set_depreciation_rate() - else: - self.finance_books = [] - self.value_after_depreciation = flt(self.gross_purchase_amount) - flt( - self.opening_accumulated_depreciation - ) - def validate_item(self): item = frappe.get_cached_value( "Item", self.item_code, ["is_fixed_asset", "is_stock_item", "disabled"], as_dict=1 @@ -308,6 +322,22 @@ def validate_finance_books(self): title=_("Missing Finance Book"), ) + # def set_depreciation_start_date(self): + # if not self.calculate_depreciation: + # return + + # for d in self.get("finance_books"): + # if not d.depreciation_start_date: + # if self.is_existing_asset and self.opening_number_of_booked_depreciations: + + # months = d.frequency_of_depreciation * self.opening_number_of_booked_depreciations + # else: + # months = d.frequency_of_depreciation + + # d.depreciation_start_date = get_last_day( + # add_months(self.available_for_use_date, months) + # ) + def validate_precision(self): float_precision = cint(frappe.db.get_default("float_precision")) or 2 if self.gross_purchase_amount: @@ -418,61 +448,65 @@ def validate_asset_finance_books(self, row): frappe.throw( _("Row {0}: Expected Value After Useful Life must be less than Gross Purchase Amount").format( row.idx - ), - title=_("Invalid Schedule"), + ) ) if not row.depreciation_start_date: - if not self.available_for_use_date: - frappe.throw( - _("Row {0}: Depreciation Start Date is required").format(row.idx), - title=_("Invalid Schedule"), - ) row.depreciation_start_date = get_last_day(self.available_for_use_date) + self.validate_depreciation_start_date(row) if not self.is_existing_asset: self.opening_accumulated_depreciation = 0 self.opening_number_of_booked_depreciations = 0 else: - depreciable_amount = flt( - flt(self.gross_purchase_amount) - flt(row.expected_value_after_useful_life), - self.precision("gross_purchase_amount"), - ) - if flt(self.opening_accumulated_depreciation) > depreciable_amount: - frappe.throw( - _("Opening Accumulated Depreciation must be less than or equal to {0}").format( - depreciable_amount - ) + self.validate_opening_depreciation_values(row) + + def validate_opening_depreciation_values(self, row): + row.expected_value_after_useful_life = flt( + row.expected_value_after_useful_life, self.precision("gross_purchase_amount") + ) + depreciable_amount = flt( + flt(self.gross_purchase_amount) - flt(row.expected_value_after_useful_life), + self.precision("gross_purchase_amount"), + ) + if flt(self.opening_accumulated_depreciation) > depreciable_amount: + frappe.throw( + _("Row #{0}: Opening Accumulated Depreciation must be less than or equal to {1}").format( + row.idx, depreciable_amount ) + ) - if self.opening_accumulated_depreciation: - if not self.opening_number_of_booked_depreciations: - frappe.throw(_("Please set Opening Number of Booked Depreciations")) - else: - self.opening_number_of_booked_depreciations = 0 + if self.opening_accumulated_depreciation: + if not self.opening_number_of_booked_depreciations: + frappe.throw(_("Please set opening number of booked depreciations")) + else: + self.opening_number_of_booked_depreciations = 0 + + if flt(row.total_number_of_depreciations) <= cint(self.opening_number_of_booked_depreciations): + frappe.throw( + _( + "Row #{0}: Total Number of Depreciations cannot be less than or equal to Opening Number of Booked Depreciations" + ).format(row.idx), + title=_("Invalid Schedule"), + ) - if flt(row.total_number_of_depreciations) <= cint(self.opening_number_of_booked_depreciations): + def validate_depreciation_start_date(self, row): + if row.depreciation_start_date: + if getdate(row.depreciation_start_date) < getdate(self.purchase_date): frappe.throw( - _( - "Row {0}: Total Number of Depreciations cannot be less than or equal to Opening Number of Booked Depreciations" - ).format(row.idx), - title=_("Invalid Schedule"), + _("Row #{0}: Next Depreciation Date cannot be before Purchase Date").format(row.idx) ) - if row.depreciation_start_date and getdate(row.depreciation_start_date) < getdate(self.purchase_date): - frappe.throw( - _("Depreciation Row {0}: Next Depreciation Date cannot be before Purchase Date").format( - row.idx + if getdate(row.depreciation_start_date) < getdate(self.available_for_use_date): + frappe.throw( + _("Row #{0}: Next Depreciation Date cannot be before Available-for-use Date").format( + row.idx + ) ) - ) - - if row.depreciation_start_date and getdate(row.depreciation_start_date) < getdate( - self.available_for_use_date - ): + else: frappe.throw( - _( - "Depreciation Row {0}: Next Depreciation Date cannot be before Available-for-use Date" - ).format(row.idx) + _("Row #{0}: Depreciation Start Date is required").format(row.idx), + title=_("Invalid Schedule"), ) def set_total_booked_depreciations(self): @@ -496,15 +530,11 @@ def validate_expected_value_after_useful_life(self): if not depr_schedule: continue - accumulated_depreciation_after_full_schedule = [ - d.accumulated_depreciation_amount for d in depr_schedule - ] + accumulated_depreciation_after_full_schedule = max( + [d.accumulated_depreciation_amount for d in depr_schedule] + ) if accumulated_depreciation_after_full_schedule: - accumulated_depreciation_after_full_schedule = max( - accumulated_depreciation_after_full_schedule - ) - asset_value_after_full_schedule = flt( flt(self.gross_purchase_amount) - flt(accumulated_depreciation_after_full_schedule), self.precision("gross_purchase_amount"), @@ -786,50 +816,59 @@ def get_depreciation_rate(self, args, on_validate=False): if isinstance(args, str): args = json.loads(args) - float_precision = cint(frappe.db.get_default("float_precision")) or 2 + rate_field_precision = frappe.get_precision(args.doctype, "rate_of_depreciation") or 2 if args.get("depreciation_method") == "Double Declining Balance": - return 200.0 / ( + return self.get_double_declining_balance_rate(args, rate_field_precision) + elif args.get("depreciation_method") == "Written Down Value": + return self.get_written_down_value_rate(args, rate_field_precision, on_validate) + + def get_double_declining_balance_rate(self, args, rate_field_precision): + return flt( + 200.0 + / ( ( flt(args.get("total_number_of_depreciations"), 2) * flt(args.get("frequency_of_depreciation")) ) / 12 - ) + ), + rate_field_precision, + ) - if args.get("depreciation_method") == "Written Down Value": - if ( - 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") + def get_written_down_value_rate(self, args, rate_field_precision, on_validate): + if ( + args.get("rate_of_depreciation") + and on_validate + and not self.flags.increase_in_asset_value_due_to_repair + ): + return args.get("rate_of_depreciation") - if self.flags.increase_in_asset_value_due_to_repair: - value = flt(args.get("expected_value_after_useful_life")) / flt( - args.get("value_after_depreciation") - ) - else: - value = flt(args.get("expected_value_after_useful_life")) / ( - flt(self.gross_purchase_amount) - flt(self.opening_accumulated_depreciation) - ) + if self.flags.increase_in_asset_value_due_to_repair: + value = flt(args.get("expected_value_after_useful_life")) / flt( + args.get("value_after_depreciation") + ) + else: + value = flt(args.get("expected_value_after_useful_life")) / ( + flt(self.gross_purchase_amount) - flt(self.opening_accumulated_depreciation) + ) - depreciation_rate = math.pow( - value, - 1.0 - / ( + depreciation_rate = math.pow( + value, + 1.0 + / ( + ( ( - ( - flt(args.get("total_number_of_depreciations"), 2) - - flt(self.opening_number_of_booked_depreciations) - ) - * flt(args.get("frequency_of_depreciation")) + flt(args.get("total_number_of_depreciations"), 2) + - flt(self.opening_number_of_booked_depreciations) ) - / 12 - ), - ) + * flt(args.get("frequency_of_depreciation")) + ) + / 12 + ), + ) - return flt((100 * (1 - depreciation_rate)), float_precision) + return flt((100 * (1 - depreciation_rate)), rate_field_precision) def has_gl_entries(doctype, docname, target_account): diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index f475a785e4fb..f0042a84e89b 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -34,6 +34,8 @@ _get_pro_rata_amt, get_asset_depr_schedule_doc, get_depr_schedule, +) +from erpnext.assets.doctype.asset_depreciation_schedule.utils import ( get_depreciation_amount, ) from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json index 76565cb4e38b..fb075df4acca 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json +++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json @@ -25,6 +25,7 @@ "column_break_8", "frequency_of_depreciation", "expected_value_after_useful_life", + "value_after_depreciation", "depreciation_schedule_section", "depreciation_schedule", "details_section", @@ -38,6 +39,7 @@ "fieldtype": "Link", "in_list_view": 1, "label": "Asset", + "link_filters": "[[\"Asset\",\"docstatus\",\"<\",\"2\"],[\"Asset\",\"company\",\"=\",\"eval:doc.company\"]]", "options": "Asset", "reqd": 1 }, @@ -202,12 +204,18 @@ "label": "Company", "options": "Company", "read_only": 1 + }, + { + "fieldname": "value_after_depreciation", + "fieldtype": "Currency", + "label": "Value After Depreciation", + "read_only": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2024-03-27 13:06:34.135004", + "modified": "2024-12-02 17:54:20.635668", "modified_by": "Administrator", "module": "Assets", "name": "Asset Depreciation Schedule", diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py index 679fbfe2e58d..2a57acdcd374 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py @@ -20,6 +20,9 @@ import erpnext from erpnext.accounts.utils import get_fiscal_year +from erpnext.assets.doctype.asset_depreciation_schedule.utils import ( + get_depreciation_amount, +) class AssetDepreciationSchedule(Document): @@ -31,9 +34,7 @@ class AssetDepreciationSchedule(Document): if TYPE_CHECKING: from frappe.types import DF - from erpnext.assets.doctype.depreciation_schedule.depreciation_schedule import ( - DepreciationSchedule, - ) + from erpnext.assets.doctype.depreciation_schedule.depreciation_schedule import DepreciationSchedule amended_from: DF.Link | None asset: DF.Link @@ -50,12 +51,13 @@ class AssetDepreciationSchedule(Document): gross_purchase_amount: DF.Currency naming_series: DF.Literal["ACC-ADS-.YYYY.-"] notes: DF.SmallText | None - opening_number_of_booked_depreciations: DF.Int opening_accumulated_depreciation: DF.Currency + opening_number_of_booked_depreciations: DF.Int rate_of_depreciation: DF.Percent shift_based: DF.Check status: DF.Literal["Draft", "Active", "Cancelled"] total_number_of_depreciations: DF.Int + value_after_depreciation: DF.Currency # end: auto-generated types def before_save(self): @@ -144,50 +146,9 @@ def prepare_draft_asset_depr_schedule_data( date_of_return=None, update_asset_finance_book_row=True, ): - have_asset_details_been_modified = self.have_asset_details_been_modified(asset_doc) - not_manual_depr_or_have_manual_depr_details_been_modified = ( - self.not_manual_depr_or_have_manual_depr_details_been_modified(row) - ) - self.set_draft_asset_depr_schedule_details(asset_doc, row) - - if self.should_prepare_depreciation_schedule( - have_asset_details_been_modified, not_manual_depr_or_have_manual_depr_details_been_modified - ): - self.make_depr_schedule(asset_doc, row, date_of_disposal, update_asset_finance_book_row) - self.set_accumulated_depreciation(asset_doc, row, date_of_disposal, date_of_return) - - def have_asset_details_been_modified(self, asset_doc): - return ( - asset_doc.gross_purchase_amount != self.gross_purchase_amount - or asset_doc.opening_accumulated_depreciation != self.opening_accumulated_depreciation - or asset_doc.opening_number_of_booked_depreciations != self.opening_number_of_booked_depreciations - ) - - def not_manual_depr_or_have_manual_depr_details_been_modified(self, row): - return ( - self.depreciation_method != "Manual" - or row.total_number_of_depreciations != self.total_number_of_depreciations - or row.frequency_of_depreciation != self.frequency_of_depreciation - or getdate(row.depreciation_start_date) != self.get("depreciation_schedule")[0].schedule_date - or row.expected_value_after_useful_life != self.expected_value_after_useful_life - ) - - def should_prepare_depreciation_schedule( - self, have_asset_details_been_modified, not_manual_depr_or_have_manual_depr_details_been_modified - ): - if not self.get("depreciation_schedule"): - return True - - old_asset_depr_schedule_doc = self.get_doc_before_save() - - if self.docstatus != 0 and not old_asset_depr_schedule_doc: - return True - - if have_asset_details_been_modified or not_manual_depr_or_have_manual_depr_details_been_modified: - return True - - return False + self.make_depr_schedule(asset_doc, row, date_of_disposal, update_asset_finance_book_row) + self.set_accumulated_depreciation(asset_doc, row, date_of_disposal, date_of_return) def set_draft_asset_depr_schedule_details(self, asset_doc, row): self.asset = asset_doc.name @@ -213,11 +174,11 @@ def make_depr_schedule( update_asset_finance_book_row=True, value_after_depreciation=None, ): - if not self.get("depreciation_schedule"): - self.depreciation_schedule = [] + # if not self.get("depreciation_schedule"): + # self.depreciation_schedule = [] - if not asset_doc.available_for_use_date: - return + # if not asset_doc.available_for_use_date: + # return start = self.clear_depr_schedule() @@ -226,6 +187,9 @@ def make_depr_schedule( ) 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 = [] @@ -253,7 +217,7 @@ def _make_depr_schedule( update_asset_finance_book_row, value_after_depreciation, ): - asset_doc.validate_asset_finance_books(row) + # asset_doc.validate_asset_finance_books(row) if not value_after_depreciation: value_after_depreciation = _get_value_after_depreciation_for_making_schedule(asset_doc, row) @@ -262,13 +226,7 @@ def _make_depr_schedule( if update_asset_finance_book_row: row.db_update() - 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 + final_number_of_depreciations, has_pro_rata = self.get_final_number_of_depreciations(asset_doc, row) has_wdv_or_dd_non_yearly_pro_rata = False if ( @@ -284,7 +242,7 @@ def _make_depr_schedule( number_of_pending_depreciations = final_number_of_depreciations - start yearly_opening_wdv = value_after_depreciation - current_fiscal_year_end_date = None + self.current_fiscal_year_end_date = None prev_per_day_depr = True for n in range(start, final_number_of_depreciations): # If depreciation is already completed (for double declining balance) @@ -294,16 +252,12 @@ def _make_depr_schedule( schedule_date = get_last_day( add_months(row.depreciation_start_date, n * cint(row.frequency_of_depreciation)) ) - if not current_fiscal_year_end_date: - current_fiscal_year_end_date = get_fiscal_year(row.depreciation_start_date)[2] - elif getdate(schedule_date) > getdate(current_fiscal_year_end_date): - current_fiscal_year_end_date = add_years(current_fiscal_year_end_date, 1) + + if self.has_fiscal_year_changed(row, n): yearly_opening_wdv = value_after_depreciation - if n > 0 and len(self.get("depreciation_schedule")) > n - 1: - prev_depreciation_amount = self.get("depreciation_schedule")[n - 1].depreciation_amount - else: - prev_depreciation_amount = 0 + prev_depreciation_amount = self.get_prev_depreciation_amount(n) + depreciation_amount, prev_per_day_depr = get_depreciation_amount( self, asset_doc, @@ -316,144 +270,118 @@ def _make_depr_schedule( number_of_pending_depreciations, prev_per_day_depr, ) - if not has_pro_rata or ( - n < (cint(final_number_of_depreciations) - 1) or final_number_of_depreciations == 2 - ): - schedule_date = add_months( - row.depreciation_start_date, n * cint(row.frequency_of_depreciation) - ) - if should_get_last_day: - schedule_date = get_last_day(schedule_date) + schedule_date = self.get_next_schedule_date( + row, n, schedule_date, 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): - from_date = add_months( - getdate(asset_doc.available_for_use_date), - (asset_doc.opening_number_of_booked_depreciations * row.frequency_of_depreciation), - ) - if is_last_day_of_the_month(getdate(asset_doc.available_for_use_date)): - from_date = get_last_day(from_date) - if self.depreciation_schedule: - from_date = add_days(self.depreciation_schedule[-1].schedule_date, 1) - - depreciation_amount, days, months = _get_pro_rata_amt( - row, - depreciation_amount, - from_date, - date_of_disposal, - original_schedule_date=schedule_date, + self.get_depreciation_amount_for_disposal( + asset_doc, row, n, schedule_date, date_of_disposal, depreciation_amount ) - - if depreciation_amount > 0: - self.add_depr_schedule_row(date_of_disposal, depreciation_amount, n) - break - # For first row - if ( - n == 0 - and (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 - ): - from_date = asset_doc.available_for_use_date - # needed to calc depr amount for available_for_use_date too - 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, + 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 ) - if flt(depreciation_amount, asset_doc.precision("gross_purchase_amount")) <= 0: - frappe.throw( - _( - "Gross Purchase Amount Too Low: {0} cannot be depreciated over {1} cycles with a frequency of {2} depreciations." - ).format( - frappe.bold(asset_doc.gross_purchase_amount), - frappe.bold(row.total_number_of_depreciations), - frappe.bold(row.frequency_of_depreciation), - ) - ) - elif n == 0 and has_wdv_or_dd_non_yearly_pro_rata and self.opening_accumulated_depreciation: - if not is_first_day_of_the_month(getdate(asset_doc.available_for_use_date)): - from_date = get_last_day( - add_months( - getdate(asset_doc.available_for_use_date), - ( - (self.opening_number_of_booked_depreciations - 1) - * row.frequency_of_depreciation - ), - ) - ) - else: - from_date = add_months( - getdate(add_days(asset_doc.available_for_use_date, -1)), - (self.opening_number_of_booked_depreciations * row.frequency_of_depreciation), - ) - 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, - ) - - # For last row - elif has_pro_rata and n == cint(final_number_of_depreciations) - 1: - if not asset_doc.flags.increase_in_asset_life: - # In case of increase_in_asset_life, the asset.to_date is already set on asset_repair submission - asset_doc.to_date = add_months( - asset_doc.available_for_use_date, - (n + 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)): - asset_doc.to_date = get_last_day(asset_doc.to_date) - - depreciation_amount_without_pro_rata = depreciation_amount - - depreciation_amount, days, months = _get_pro_rata_amt( - row, - depreciation_amount, - schedule_date, - asset_doc.to_date, - has_wdv_or_dd_non_yearly_pro_rata, - ) - - depreciation_amount = self.get_adjusted_depreciation_amount( - depreciation_amount_without_pro_rata, depreciation_amount + 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 ) - schedule_date = add_days(schedule_date, days - 1) - if not depreciation_amount: - continue + break + value_after_depreciation = flt( value_after_depreciation - flt(depreciation_amount), asset_doc.precision("gross_purchase_amount"), ) # Adjust depreciation amount in the last period based on the expected value after useful life - if ( - n == 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 - ) - skip_row = True + 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) - # to ensure that final accumulated depreciation amount is accurate + 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 + + return final_number_of_depreciations, has_pro_rata + + 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, schedule_date, has_pro_rata, should_get_last_day, final_number_of_depreciations=None + ): + if not has_pro_rata or ( + n < (cint(final_number_of_depreciations) - 1) or final_number_of_depreciations == 2 + ): + 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, + ) + + 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_amount_for_first_row() + depreciation_amount_for_first_row = self.get("depreciation_schedule")[0].depreciation_amount if ( depreciation_amount_for_first_row + depreciation_amount_for_last_row @@ -465,8 +393,83 @@ def get_adjusted_depreciation_amount( return depreciation_amount_for_last_row - def get_depreciation_amount_for_first_row(self): - return self.get("depreciation_schedule")[0].depreciation_amount + 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 + """ + 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 + 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) + + 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 asset_doc.flags.increase_in_asset_life: + # In case of increase_in_asset_life, the asset.to_date is already set on asset_repair submission + asset_doc.to_date = add_months( + asset_doc.available_for_use_date, + (n + 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)): + asset_doc.to_date = get_last_day(asset_doc.to_date) + + if self.opening_accumulated_depreciation: + depreciation_amount, days, months = _get_pro_rata_amt( + row, + depreciation_amount, + schedule_date, + asset_doc.to_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 + depreciation_amount -= self.get("depreciation_schedule")[0].depreciation_amount + + schedule_date = add_days(schedule_date, days - 1) + return depreciation_amount, schedule_date + + def adjust_depr_amount_for_salvage_value( + 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) + 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: @@ -571,7 +574,7 @@ def _check_is_pro_rata(asset_doc, row, wdv_or_dd_non_yearly=False): 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(asset_doc, row, wdv_or_dd_non_yearly=False) + 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: @@ -590,11 +593,12 @@ def _check_is_pro_rata(asset_doc, row, wdv_or_dd_non_yearly=False): return has_pro_rata -def _get_modified_available_for_use_date(asset_doc, row, wdv_or_dd_non_yearly=False): +def _get_modified_available_for_use_date_for_existing_assets(asset_doc, row): """ - if Asset has opening booked depreciations = 9, + if Asset has opening booked depreciations = 3, + frequency of depreciation = 3, available for use date = 17-07-2023, - depreciation start date = 30-04-2024 + depreciation start date = 30-06-2024 then from date should be 01-04-2024 """ if asset_doc.opening_number_of_booked_depreciations > 0: @@ -636,359 +640,6 @@ def get_total_days(date, frequency): return date_diff(date, period_start_date) -def get_depreciation_amount( - asset_depr_schedule, - asset, - depreciable_value, - 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, number_of_pending_depreciations - ), None - else: - return get_wdv_or_dd_depr_amount( - asset, - fb_row, - depreciable_value, - 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, row, schedule_idx, number_of_pending_depreciations -): - if row.shift_based: - return get_shift_depr_amount(asset_depr_schedule, asset, row, schedule_idx) - - # if the Depreciation Schedule is being modified after Asset Repair due to increase in asset life and value - if asset.flags.increase_in_asset_life: - return (flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)) / ( - date_diff(asset.to_date, asset.available_for_use_date) / 365 - ) - # if the Depreciation Schedule is being modified after Asset Repair due to increase in asset value - elif asset.flags.increase_in_asset_value_due_to_repair: - return (flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)) / flt( - number_of_pending_depreciations - ) - # if the Depreciation Schedule is being modified after Asset Value Adjustment due to decrease in asset value - elif asset.flags.decrease_in_asset_value_due_to_value_adjustment: - if row.daily_prorata_based: - amount = flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life) - - return get_daily_prorata_based_straight_line_depr( - asset, - row, - schedule_idx, - number_of_pending_depreciations, - amount, - ) - else: - return ( - flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life) - ) / number_of_pending_depreciations - # if the Depreciation Schedule is being prepared for the first time - else: - if row.daily_prorata_based: - amount = flt(asset.gross_purchase_amount) - flt(row.expected_value_after_useful_life) - return get_daily_prorata_based_straight_line_depr( - asset, row, schedule_idx, number_of_pending_depreciations, amount - ) - else: - depreciation_amount = ( - flt(asset.gross_purchase_amount) - flt(row.expected_value_after_useful_life) - ) / flt(row.total_number_of_depreciations) - return depreciation_amount - - -def get_daily_prorata_based_straight_line_depr( - asset, row, schedule_idx, number_of_pending_depreciations, amount -): - daily_depr_amount = get_daily_depr_amount(asset, row, schedule_idx, amount) - - from_date, total_depreciable_days = _get_total_days( - row.depreciation_start_date, schedule_idx, row.frequency_of_depreciation - ) - return daily_depr_amount * total_depreciable_days - - -def get_daily_depr_amount(asset, 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( - row.depreciation_start_date, - flt( - row.total_number_of_depreciations - - asset.opening_number_of_booked_depreciations - - 1 - ) - * row.frequency_of_depreciation, - ) - ), - add_days( - get_last_day( - add_months( - row.depreciation_start_date, - ( - row.frequency_of_depreciation - * (asset.opening_number_of_booked_depreciations + 1) - ) - * -1, - ), - ), - 1, - ), - ) - + 1 - ) - - return amount / total_days - else: - total_years = ( - flt( - (row.total_number_of_depreciations - row.total_number_of_booked_depreciations) - * row.frequency_of_depreciation - ) - / 12 - ) - - every_year_depr = amount / total_years - - depr_period_start_date = add_days( - get_last_day(add_months(row.depreciation_start_date, row.frequency_of_depreciation * -1)), 1 - ) - - year_start_date = add_years( - depr_period_start_date, ((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, 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(row.expected_value_after_useful_life) - ) / flt(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(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, - depreciable_value, - 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, - depreciable_value, - 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, - depreciable_value, - 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, - depreciable_value, - 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, - depreciable_value, - 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, - depreciable_value, - 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(depreciable_value) * (flt(fb_row.rate_of_depreciation) / 100) - else: - if has_wdv_or_dd_non_yearly_pro_rata: - if schedule_idx == 0: - return flt(depreciable_value) * (flt(fb_row.rate_of_depreciation) / 100) - elif schedule_idx % (12 / cint(fb_row.frequency_of_depreciation)) == 1: - return ( - flt(depreciable_value) - * 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(depreciable_value) - * 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, - depreciable_value, - 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(depreciable_value) * (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, depreciable_value) - 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, depreciable_value) - 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, depreciable_value): - """ - 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, depreciable_value, 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, - depreciable_value, - 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(depreciable_value) * (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 - - -def make_draft_asset_depr_schedules_if_not_present(asset_doc): - asset_depr_schedules_names = [] - - for row in asset_doc.get("finance_books"): - asset_depr_schedule = get_asset_depr_schedule_name( - asset_doc.name, ["Draft", "Active"], row.finance_book - ) - - if not asset_depr_schedule: - 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_schedules(asset_doc): asset_depr_schedules_names = [] diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/utils.py b/erpnext/assets/doctype/asset_depreciation_schedule/utils.py new file mode 100644 index 000000000000..2617493751c6 --- /dev/null +++ b/erpnext/assets/doctype/asset_depreciation_schedule/utils.py @@ -0,0 +1,351 @@ +import frappe +from frappe.utils import ( + add_days, + add_months, + add_years, + cint, + date_diff, + flt, + get_last_day, + is_last_day_of_the_month, +) + +import erpnext + + +def get_depreciation_amount( + asset_depr_schedule, + asset, + depreciable_value, + 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, number_of_pending_depreciations + ), None + else: + return get_wdv_or_dd_depr_amount( + asset, + fb_row, + depreciable_value, + 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, row, schedule_idx, number_of_pending_depreciations +): + if row.shift_based: + return get_shift_depr_amount(asset_depr_schedule, asset, row, schedule_idx) + + # if the Depreciation Schedule is being modified after Asset Repair due to increase in asset life and value + if asset.flags.increase_in_asset_life: + return (flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)) / ( + date_diff(asset.to_date, asset.available_for_use_date) / 365 + ) + # if the Depreciation Schedule is being modified after Asset Repair due to increase in asset value + elif asset.flags.increase_in_asset_value_due_to_repair: + return (flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)) / flt( + number_of_pending_depreciations + ) + # if the Depreciation Schedule is being modified after Asset Value Adjustment due to decrease in asset value + elif asset.flags.decrease_in_asset_value_due_to_value_adjustment: + if row.daily_prorata_based: + amount = flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life) + + return get_daily_prorata_based_straight_line_depr( + asset, + row, + schedule_idx, + number_of_pending_depreciations, + amount, + ) + else: + return ( + flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life) + ) / number_of_pending_depreciations + # if the Depreciation Schedule is being prepared for the first time + else: + if row.daily_prorata_based: + amount = flt(asset.gross_purchase_amount) - flt(row.expected_value_after_useful_life) + return get_daily_prorata_based_straight_line_depr( + asset, row, schedule_idx, number_of_pending_depreciations, amount + ) + else: + depreciation_amount = ( + flt(asset.gross_purchase_amount) - flt(row.expected_value_after_useful_life) + ) / flt(row.total_number_of_depreciations) + return depreciation_amount + + +def get_daily_prorata_based_straight_line_depr( + asset, row, schedule_idx, number_of_pending_depreciations, amount +): + daily_depr_amount = get_daily_depr_amount(asset, row, schedule_idx, amount) + + from_date, total_depreciable_days = _get_total_days( + row.depreciation_start_date, schedule_idx, row.frequency_of_depreciation + ) + return daily_depr_amount * total_depreciable_days + + +def get_daily_depr_amount(asset, 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( + row.depreciation_start_date, + flt( + row.total_number_of_depreciations + - asset.opening_number_of_booked_depreciations + - 1 + ) + * row.frequency_of_depreciation, + ) + ), + add_days( + get_last_day( + add_months( + row.depreciation_start_date, + ( + row.frequency_of_depreciation + * (asset.opening_number_of_booked_depreciations + 1) + ) + * -1, + ), + ), + 1, + ), + ) + + 1 + ) + + return amount / total_days + else: + total_years = ( + flt( + (row.total_number_of_depreciations - row.total_number_of_booked_depreciations) + * row.frequency_of_depreciation + ) + / 12 + ) + + every_year_depr = amount / total_years + + depr_period_start_date = add_days( + get_last_day(add_months(row.depreciation_start_date, row.frequency_of_depreciation * -1)), 1 + ) + + year_start_date = add_years( + depr_period_start_date, ((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, 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(row.expected_value_after_useful_life) + ) / flt(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(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, + depreciable_value, + 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, + depreciable_value, + 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, + depreciable_value, + 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, + depreciable_value, + 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, + depreciable_value, + 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, + depreciable_value, + 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(depreciable_value) * (flt(fb_row.rate_of_depreciation) / 100) + else: + if has_wdv_or_dd_non_yearly_pro_rata: + if schedule_idx == 0: + return flt(depreciable_value) * (flt(fb_row.rate_of_depreciation) / 100) + elif schedule_idx % (12 / cint(fb_row.frequency_of_depreciation)) == 1: + return ( + flt(depreciable_value) + * 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(depreciable_value) + * 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, + depreciable_value, + 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(depreciable_value) * (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, depreciable_value) + 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, depreciable_value) + 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, depreciable_value): + """ + 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, depreciable_value, 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, + depreciable_value, + 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(depreciable_value) * (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 diff --git a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json index 8b00bc29c3cc..1e12c76ea7d0 100644 --- a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json +++ b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json @@ -61,6 +61,7 @@ { "fieldname": "depreciation_start_date", "fieldtype": "Date", + "hidden": 1, "in_list_view": 1, "label": "Depreciation Posting Date", "mandatory_depends_on": "eval:parent.doctype == 'Asset'" @@ -99,7 +100,7 @@ "default": "0", "fieldname": "daily_prorata_based", "fieldtype": "Check", - "label": "Depreciate based on daily pro-rata" + "label": "Depreciate based on days per period" }, { "default": "0", @@ -128,7 +129,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2024-11-29 14:36:54.399034", + "modified": "2024-12-02 14:37:00.371797", "modified_by": "Administrator", "module": "Assets", "name": "Asset Finance Book", diff --git a/erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.py b/erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.py index 323cb73fb8e9..0c0da3bd8cd0 100644 --- a/erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.py +++ b/erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.py @@ -16,9 +16,9 @@ from erpnext.assets.doctype.asset_activity.asset_activity import add_asset_activity from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import ( get_asset_depr_schedule_doc, - get_asset_shift_factors_map, get_temp_asset_depr_schedule_doc, ) +from erpnext.assets.doctype.asset_depreciation_schedule.utils import get_asset_shift_factors_map class AssetShiftAllocation(Document):