From c9487e0427ce728b833458a2d7cf2fac66ad256a Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 11 Dec 2024 10:44:13 +0530 Subject: [PATCH 01/40] fix: Removed conflict --- erpnext/assets/doctype/asset/asset.py | 283 ++++--- erpnext/assets/doctype/asset/test_asset.py | 2 + .../asset_depreciation_schedule.json | 10 +- .../asset_depreciation_schedule.py | 738 +++++------------- .../asset_depreciation_schedule/utils.py | 351 +++++++++ .../asset_finance_book.json | 3 +- .../asset_shift_allocation.py | 2 +- 7 files changed, 721 insertions(+), 668 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 4fd135d359a..cb0e160eca1 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -34,7 +34,6 @@ from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_sched 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 @@ -131,30 +130,55 @@ class Asset(AccountsController): 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() @@ -178,17 +202,17 @@ class Asset(AccountsController): 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( { @@ -250,16 +274,6 @@ class Asset(AccountsController): 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 @@ -344,6 +358,22 @@ class Asset(AccountsController): 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): if self.gross_purchase_amount: self.gross_purchase_amount = flt( @@ -456,61 +486,65 @@ class Asset(AccountsController): 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) - 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 row.depreciation_start_date and getdate(row.depreciation_start_date) < getdate(self.purchase_date): + 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( - _("Depreciation Row {0}: Next Depreciation Date cannot be before Purchase Date").format( - row.idx + _("Row #{0}: Opening Accumulated Depreciation must be less than or equal to {1}").format( + row.idx, depreciable_amount ) ) - if row.depreciation_start_date and getdate(row.depreciation_start_date) < getdate( - self.available_for_use_date - ): + 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( _( - "Depreciation Row {0}: Next Depreciation Date cannot be before Available-for-use Date" - ).format(row.idx) + "Row #{0}: Total Number of Depreciations cannot be less than or equal to Opening Number of Booked Depreciations" + ).format(row.idx), + title=_("Invalid Schedule"), + ) + + 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}: 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 + ) + ) + else: + frappe.throw( + _("Row #{0}: Depreciation Start Date is required").format(row.idx), + title=_("Invalid Schedule"), ) def set_total_booked_depreciations(self): @@ -530,15 +564,11 @@ class Asset(AccountsController): 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"), @@ -820,53 +850,62 @@ class Asset(AccountsController): 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, + ) + + 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 args.get("rate_of_depreciation") and not flt(args.get("expected_value_after_useful_life")): + return args.get("rate_of_depreciation") + + if self.flags.increase_in_asset_value_due_to_repair: + value = flt(args.get("expected_value_after_useful_life")) / flt( + args.get("value_after_depreciation") + ) + else: + value = flt(args.get("expected_value_after_useful_life")) / ( + flt(self.gross_purchase_amount) - flt(self.opening_accumulated_depreciation) ) - 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") - - if args.get("rate_of_depreciation") and not flt(args.get("expected_value_after_useful_life")): - return args.get("rate_of_depreciation") - - if self.flags.increase_in_asset_value_due_to_repair: - value = flt(args.get("expected_value_after_useful_life")) / flt( - args.get("value_after_depreciation") - ) - 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 3f984d9326e..be8ce0ff5fc 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -34,6 +34,8 @@ from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_sched _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 76565cb4e38..fb075df4acc 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 5bddea56183..55122d48607 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py @@ -21,6 +21,9 @@ from frappe.utils import ( import erpnext from erpnext.accounts.utils import get_fiscal_year +from erpnext.assets.doctype.asset_depreciation_schedule.utils import ( + get_depreciation_amount, +) class AssetDepreciationSchedule(Document): @@ -32,9 +35,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 @@ -51,12 +52,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): @@ -145,50 +147,9 @@ class AssetDepreciationSchedule(Document): 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 @@ -214,11 +175,11 @@ class AssetDepreciationSchedule(Document): 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() @@ -227,6 +188,9 @@ class AssetDepreciationSchedule(Document): ) 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 = [] @@ -254,7 +218,7 @@ class AssetDepreciationSchedule(Document): 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) @@ -263,13 +227,7 @@ class AssetDepreciationSchedule(Document): 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 ( @@ -285,7 +243,7 @@ class AssetDepreciationSchedule(Document): 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) @@ -295,16 +253,12 @@ class AssetDepreciationSchedule(Document): 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, @@ -317,146 +271,120 @@ class AssetDepreciationSchedule(Document): 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), + self.get_depreciation_amount_for_disposal( + asset_doc, row, n, schedule_date, date_of_disposal, depreciation_amount ) - 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, - ) - 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, 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, + 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 ) - # 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 - ) - - schedule_date = add_days(schedule_date, days - 1) - if not depreciation_amount: - continue - depreciation_amount = flt(depreciation_amount, asset_doc.precision("gross_purchase_amount")) + break + + value_after_depreciation = flt( - value_after_depreciation - flt(depreciation_amount), + 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 - 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 - ) - depreciation_amount = flt(depreciation_amount, asset_doc.precision("gross_purchase_amount")) - 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, + ) + + 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_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 @@ -468,8 +396,84 @@ class AssetDepreciationSchedule(Document): 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) + 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: @@ -576,7 +580,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: @@ -595,11 +599,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: @@ -641,359 +646,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 00000000000..2617493751c --- /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 60e8778e0e6..c97ae1a8545 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'" @@ -100,7 +101,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", 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 323cb73fb8e..0c0da3bd8cd 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 frappe.utils import ( 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): From b433b125ff780f71640556cde04a4444e8c1f1f3 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 11 Dec 2024 10:47:08 +0530 Subject: [PATCH 02/40] fix: removed commented code --- erpnext/assets/doctype/asset/asset.py | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index cb0e160eca1..9b566e08372 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -202,17 +202,6 @@ class Asset(AccountsController): 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) - # ) if ( not frappe.db.exists( { @@ -358,22 +347,6 @@ class Asset(AccountsController): 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): if self.gross_purchase_amount: self.gross_purchase_amount = flt( From 9f3a1faa5eba0b015ad8a565012a5aae95ba86e1 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 11 Dec 2024 10:48:50 +0530 Subject: [PATCH 03/40] fix: add asset activity after insert --- erpnext/assets/doctype/asset/asset.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 9b566e08372..de3fb10324d 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -202,6 +202,7 @@ class Asset(AccountsController): self.db_set("booked_fixed_asset", 0) add_asset_activity(self.name, _("Asset cancelled")) + def after_insert(self): if ( not frappe.db.exists( { From 2a89bac11dbc51f2e39db7a691df6dd341ac703f Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 18 Dec 2024 12:20:03 +0530 Subject: [PATCH 04/40] fix: missing args --- .../asset_depreciation_schedule.py | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) 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 55122d48607..c30a4ef49eb 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py @@ -402,24 +402,28 @@ class AssetDepreciationSchedule(Document): """ 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 - 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 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) + self.validate_depreciation_amount_for_low_value_assets(asset_doc, row, depreciation_amount) return depreciation_amount @@ -451,7 +455,13 @@ class AssetDepreciationSchedule(Document): 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 + self, + row, + depreciation_amount, + value_after_depreciation, + final_number_of_depreciations, + row_no, + skip_row, ): if ( row_no == cint(final_number_of_depreciations) - 1 From f99d02b71ec632897ac77facf11a665af9495e10 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 18 Dec 2024 12:47:35 +0530 Subject: [PATCH 05/40] fix: remaining days for last row --- .../asset_depreciation_schedule.py | 62 ++++++++++--------- 1 file changed, 34 insertions(+), 28 deletions(-) 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 c30a4ef49eb..98cdb5c8b20 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py @@ -218,42 +218,25 @@ class AssetDepreciationSchedule(Document): update_asset_finance_book_row, value_after_depreciation, ): - # 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) - row.value_after_depreciation = value_after_depreciation - - if update_asset_finance_book_row: - row.db_update() - + 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 = 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) - - skip_row = False + 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 - - number_of_pending_depreciations = final_number_of_depreciations - start - yearly_opening_wdv = value_after_depreciation - self.current_fiscal_year_end_date = None prev_per_day_depr = True + self.current_fiscal_year_end_date = None + yearly_opening_wdv = value_after_depreciation + number_of_pending_depreciations = final_number_of_depreciations - start + for n in range(start, final_number_of_depreciations): # If depreciation is already completed (for double declining balance) if skip_row: continue - schedule_date = get_last_day( - add_months(row.depreciation_start_date, n * cint(row.frequency_of_depreciation)) - ) - if self.has_fiscal_year_changed(row, n): yearly_opening_wdv = value_after_depreciation @@ -273,7 +256,7 @@ class AssetDepreciationSchedule(Document): ) schedule_date = self.get_next_schedule_date( - row, n, schedule_date, has_pro_rata, should_get_last_day, final_number_of_depreciations + row, n, has_pro_rata, should_get_last_day, final_number_of_depreciations ) # if asset is being sold or scrapped @@ -310,6 +293,18 @@ class AssetDepreciationSchedule(Document): 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 @@ -321,6 +316,16 @@ class AssetDepreciationSchedule(Document): return final_number_of_depreciations, has_pro_rata + 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 has_fiscal_year_changed(self, row, row_no): fiscal_year_changed = False @@ -345,7 +350,7 @@ class AssetDepreciationSchedule(Document): 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 + self, row, n, 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 @@ -450,6 +455,7 @@ class AssetDepreciationSchedule(Document): 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 + days = date_diff(asset_doc.to_date, schedule_date) + 1 schedule_date = add_days(schedule_date, days - 1) return depreciation_amount, schedule_date From 0eb83a4474ce248ed528f4ca992dcf8cc14b3ebd Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 23 Dec 2024 12:47:38 +0530 Subject: [PATCH 06/40] chore: reorganised asset repair fields --- .../doctype/asset_repair/asset_repair.json | 143 +++++++++--------- 1 file changed, 74 insertions(+), 69 deletions(-) diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.json b/erpnext/assets/doctype/asset_repair/asset_repair.json index 8195c56b431..b74292a093e 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.json +++ b/erpnext/assets/doctype/asset_repair/asset_repair.json @@ -7,40 +7,42 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ + "naming_series", "asset", + "asset_name", "company", "column_break_2", - "asset_name", - "naming_series", - "section_break_5", - "failure_date", "repair_status", - "column_break_6", + "failure_date", "completion_date", - "accounting_dimensions_section", - "cost_center", - "column_break_14", - "project", - "accounting_details", - "invoices", - "section_break_y7cc", - "capitalize_repair_cost", - "stock_consumption", - "column_break_8", - "repair_cost", - "stock_consumption_details_section", - "stock_items", - "total_repair_cost", - "asset_depreciation_details_section", - "increase_in_asset_life", + "downtime", + "amended_from", "section_break_9", "description", "column_break_9", "actions_performed", - "section_break_23", - "downtime", - "column_break_19", - "amended_from" + "accounting_details", + "invoices", + "section_break_muyc", + "column_break_ajbh", + "column_break_hkem", + "repair_cost", + "asset_depreciation_details_section", + "cost_center", + "column_break_14", + "project", + "stock_consumption_details_section", + "stock_items", + "section_break_ltbb", + "column_break_ewor", + "column_break_ceuc", + "consumed_items_cost", + "capitalizations_section", + "column_break_spre", + "capitalize_repair_cost", + "increase_in_asset_life", + "column_break_xebe", + "total_repair_cost" ], "fields": [ { @@ -54,11 +56,6 @@ "fieldname": "column_break_2", "fieldtype": "Column Break" }, - { - "fieldname": "section_break_5", - "fieldtype": "Section Break", - "label": "Repair Details" - }, { "columns": 1, "fieldname": "failure_date", @@ -67,11 +64,7 @@ "reqd": 1 }, { - "fieldname": "column_break_6", - "fieldtype": "Column Break" - }, - { - "depends_on": "eval:!doc.__islocal", + "depends_on": "eval:doc.repair_status==\"Completed\"", "fieldname": "completion_date", "fieldtype": "Datetime", "label": "Completion Date", @@ -79,7 +72,6 @@ }, { "default": "Pending", - "depends_on": "eval:!doc.__islocal", "fieldname": "repair_status", "fieldtype": "Select", "label": "Repair Status", @@ -113,10 +105,6 @@ "label": "Downtime", "read_only": 1 }, - { - "fieldname": "column_break_19", - "fieldtype": "Column Break" - }, { "default": "0", "fieldname": "repair_cost", @@ -148,10 +136,6 @@ "fieldtype": "Read Only", "label": "Asset Name" }, - { - "fieldname": "column_break_8", - "fieldtype": "Column Break" - }, { "default": "0", "depends_on": "eval:!doc.__islocal", @@ -162,7 +146,8 @@ { "fieldname": "accounting_details", "fieldtype": "Section Break", - "label": "Accounting Details" + "hide_border": 1, + "label": "Repair Purchase Invoices" }, { "fieldname": "stock_items", @@ -171,16 +156,6 @@ "mandatory_depends_on": "stock_consumption", "options": "Asset Repair Consumed Item" }, - { - "fieldname": "section_break_23", - "fieldtype": "Section Break" - }, - { - "collapsible": 1, - "fieldname": "accounting_dimensions_section", - "fieldtype": "Section Break", - "label": "Accounting Dimensions" - }, { "fieldname": "cost_center", "fieldtype": "Link", @@ -198,21 +173,13 @@ "fieldtype": "Column Break" }, { - "default": "0", - "depends_on": "eval:!doc.__islocal", - "fieldname": "stock_consumption", - "fieldtype": "Check", - "label": "Stock Consumed During Repair" - }, - { - "depends_on": "stock_consumption", + "collapsible_depends_on": "stock_items", "fieldname": "stock_consumption_details_section", "fieldtype": "Section Break", - "label": "Stock Consumption Details" + "hide_border": 1, + "label": "Consumed Stock Items" }, { - "depends_on": "eval: doc.stock_consumption && doc.total_repair_cost > 0", - "description": "Sum of Repair Cost and Value of Consumed Stock Items.", "fieldname": "total_repair_cost", "fieldtype": "Currency", "label": "Total Repair Cost", @@ -225,6 +192,7 @@ "label": "Asset Depreciation Details" }, { + "depends_on": "capitalize_repair_cost", "fieldname": "increase_in_asset_life", "fieldtype": "Int", "label": "Increase In Asset Life(Months)", @@ -240,20 +208,57 @@ { "fieldname": "invoices", "fieldtype": "Table", - "label": "Asset Repair Purchase Invoices", "mandatory_depends_on": "eval: doc.repair_status == 'Completed' && doc.repair_cost > 0;", "no_copy": 1, "options": "Asset Repair Purchase Invoice" }, { - "fieldname": "section_break_y7cc", + "fieldname": "section_break_muyc", "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_hkem", + "fieldtype": "Column Break" + }, + { + "fieldname": "capitalizations_section", + "fieldtype": "Section Break", + "label": "Totals" + }, + { + "fieldname": "column_break_spre", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_ajbh", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_ltbb", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_ewor", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_ceuc", + "fieldtype": "Column Break" + }, + { + "fieldname": "consumed_items_cost", + "fieldtype": "Currency", + "label": "Consumed Items Cost" + }, + { + "fieldname": "column_break_xebe", + "fieldtype": "Column Break" } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2024-09-30 13:02:06.931188", + "modified": "2024-12-20 13:10:31.540666", "modified_by": "Administrator", "module": "Assets", "name": "Asset Repair", From 0e4706b074ad34fc27bd90359d468ebb6292995b Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Mon, 5 May 2025 12:04:05 +0530 Subject: [PATCH 07/40] fix: conflict in asset repair --- .../doctype/asset_repair/asset_repair.js | 21 +- .../doctype/asset_repair/asset_repair.py | 341 +++++++----------- 2 files changed, 141 insertions(+), 221 deletions(-) diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.js b/erpnext/assets/doctype/asset_repair/asset_repair.js index 167bef414f3..3ce1d5390db 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.js +++ b/erpnext/assets/doctype/asset_repair/asset_repair.js @@ -34,7 +34,16 @@ frappe.ui.form.on("Asset Repair", { query: "erpnext.assets.doctype.asset_repair.asset_repair.get_purchase_invoice", filters: { company: frm.doc.company, - docstatus: 1, + }, + }; + }); + + frm.set_query("expense_account", "invoices", function (doc, cdt, cdn) { + let row = locals[cdt][cdn]; + return { + query: "erpnext.assets.doctype.asset_repair.asset_repair.get_expense_accounts", + filters: { + purchase_invoice: row.purchase_invoice, }, }; }); @@ -59,16 +68,6 @@ frappe.ui.form.on("Asset Repair", { }, }; }); - - frm.set_query("expense_account", "invoices", function () { - return { - filters: { - company: frm.doc.company, - is_group: ["=", 0], - report_type: ["=", "Profit and Loss"], - }, - }; - }); }, refresh: function (frm) { diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py index c6ae3c5d8e4..4b4ff1a5383 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/asset_repair.py @@ -40,6 +40,7 @@ class AssetRepair(AccountsController): capitalize_repair_cost: DF.Check company: DF.Link | None completion_date: DF.Datetime | None + consumed_items_cost: DF.Currency cost_center: DF.Link | None description: DF.LongText | None downtime: DF.Data | None @@ -50,7 +51,6 @@ class AssetRepair(AccountsController): project: DF.Link | None repair_cost: DF.Currency repair_status: DF.Literal["Pending", "Completed", "Cancelled"] - stock_consumption: DF.Check stock_items: DF.Table[AssetRepairConsumedItem] total_repair_cost: DF.Currency # end: auto-generated types @@ -58,16 +58,12 @@ class AssetRepair(AccountsController): def validate(self): self.asset_doc = frappe.get_doc("Asset", self.asset) self.validate_dates() - self.validate_purchase_invoice() - self.validate_purchase_invoice_repair_cost() - self.validate_purchase_invoice_expense_account() + self.validate_purchase_invoices() self.update_status() - - if self.get("stock_items"): - self.set_stock_items_cost() - + self.calculate_consumed_items_cost() self.calculate_repair_cost() self.calculate_total_repair_cost() + self.check_repair_status() def validate_dates(self): if self.completion_date and (self.failure_date > self.completion_date): @@ -75,36 +71,58 @@ class AssetRepair(AccountsController): _("Completion Date can not be before Failure Date. Please adjust the dates accordingly.") ) - def validate_purchase_invoice(self): - query = expense_item_pi_query(self.company) - purchase_invoice_list = [item[0] for item in query.run()] - for pi in self.invoices: - if pi.purchase_invoice not in purchase_invoice_list: - frappe.throw(_("Expense item not present in Purchase Invoice")) + def validate_purchase_invoices(self): + for d in self.invoices: + invoice_items = self.get_invoice_items(d.purchase_invoice) + self.validate_service_purchase_invoice(d.purchase_invoice, invoice_items) + self.validate_expense_account(d, invoice_items) + self.validate_purchase_invoice_repair_cost(d, invoice_items) - def validate_purchase_invoice_repair_cost(self): - for pi in self.invoices: - if flt(pi.repair_cost) > frappe.db.get_value( - "Purchase Invoice", pi.purchase_invoice, "base_net_total" - ): - frappe.throw(_("Repair cost cannot be greater than purchase invoice base net total")) + def get_invoice_items(self, pi): + invoice_items = frappe.get_all( + "Purchase Invoice Item", + filters={"parent": pi}, + fields=["item_code", "expense_account", "base_net_amount"], + ) - def validate_purchase_invoice_expense_account(self): - for pi in self.invoices: - if pi.expense_account not in frappe.db.get_all( - "Purchase Invoice Item", {"parent": pi.purchase_invoice}, pluck="expense_account" - ): - frappe.throw( - _("Expense account not present in Purchase Invoice {0}").format( - get_link_to_form("Purchase Invoice", pi.purchase_invoice) - ) + return invoice_items + + def validate_service_purchase_invoice(self, purchase_invoice, invoice_items): + service_item_exists = False + for item in invoice_items: + if frappe.db.get_value("Item", item.item_code, "is_stock_item") == 0: + service_item_exists = True + break + + if not service_item_exists: + frappe.throw( + _("Service item not present in Purchase Invoice {0}").format( + get_link_to_form("Purchase Invoice", purchase_invoice) ) + ) + + def validate_expense_account(self, row, invoice_items): + pi_expense_accounts = set([item.expense_account for item in invoice_items]) + if row.expense_account not in pi_expense_accounts: + frappe.throw( + _("Expense account {0} not present in Purchase Invoice {1}").format( + row.expense_account, get_link_to_form("Purchase Invoice", row.purchase_invoice) + ) + ) + + def validate_purchase_invoice_repair_cost(self, row, invoice_items): + pi_net_total = sum([flt(item.base_net_amount) for item in invoice_items]) + if flt(row.repair_cost) > pi_net_total: + frappe.throw( + _("Repair cost cannot be greater than purchase invoice base net total {0}").format( + pi_net_total + ) + ) def update_status(self): if self.repair_status == "Pending" and self.asset_doc.status != "Out of Order": frappe.db.set_value("Asset", self.asset, "status", "Out of Order") - add_asset_activity( - self.asset, + self.add_asset_activity( _("Asset out of order due to Asset Repair {0}").format( get_link_to_form("Asset Repair", self.name) ), @@ -112,147 +130,87 @@ class AssetRepair(AccountsController): else: self.asset_doc.set_status() - def set_stock_items_cost(self): + def calculate_consumed_items_cost(self): + consumed_items_cost = 0.0 for item in self.get("stock_items"): item.total_value = flt(item.valuation_rate) * flt(item.consumed_quantity) + consumed_items_cost += item.total_value + self.consumed_items_cost = consumed_items_cost def calculate_repair_cost(self): self.repair_cost = sum(flt(pi.repair_cost) for pi in self.invoices) def calculate_total_repair_cost(self): - self.total_repair_cost = flt(self.repair_cost) - - total_value_of_stock_consumed = self.get_total_value_of_stock_consumed() - self.total_repair_cost += total_value_of_stock_consumed - - def before_submit(self): - self.check_repair_status() + self.total_repair_cost = flt(self.repair_cost) + flt(self.consumed_items_cost) + def on_submit(self): self.asset_doc.flags.increase_in_asset_value_due_to_repair = False + self.decrease_stock_quantity() - if self.get("stock_consumption") or self.get("capitalize_repair_cost"): - self.asset_doc.flags.increase_in_asset_value_due_to_repair = True + if self.get("capitalize_repair_cost"): + self.asset_doc.flags.ignore_validate_update_after_submit = True + self.update_asset_value() + self.make_gl_entries() + self.set_increase_in_asset_life() - self.increase_asset_value() + depreciation_note = self.get_depreciation_note() + make_new_active_asset_depr_schedules_and_cancel_current_ones( + self.asset_doc, depreciation_note, ignore_booked_entry=True + ) + self.add_asset_activity() - total_repair_cost = self.get_total_value_of_stock_consumed() - if self.capitalize_repair_cost: - total_repair_cost += self.repair_cost - self.asset_doc.total_asset_cost += total_repair_cost - self.asset_doc.additional_asset_cost += total_repair_cost - - if self.get("stock_consumption"): - self.check_for_stock_items_and_warehouse() - self.decrease_stock_quantity() - if self.get("capitalize_repair_cost"): - self.make_gl_entries() - if self.asset_doc.calculate_depreciation and self.increase_in_asset_life: - self.modify_depreciation_schedule() - - notes = _( - "This schedule was created when Asset {0} was repaired through Asset Repair {1}." - ).format( - get_link_to_form(self.asset_doc.doctype, self.asset_doc.name), - get_link_to_form(self.doctype, self.name), - ) - self.asset_doc.flags.ignore_validate_update_after_submit = True - make_new_active_asset_depr_schedules_and_cancel_current_ones( - self.asset_doc, notes, ignore_booked_entry=True - ) - self.asset_doc.save() - - add_asset_activity( - self.asset, - _("Asset updated after completion of Asset Repair {0}").format( - get_link_to_form("Asset Repair", self.name) - ), - ) - - def before_cancel(self): + def on_cancel(self): self.asset_doc = frappe.get_doc("Asset", self.asset) - self.asset_doc.flags.increase_in_asset_value_due_to_repair = False - - if self.get("stock_consumption") or self.get("capitalize_repair_cost"): + if self.get("capitalize_repair_cost"): self.asset_doc.flags.increase_in_asset_value_due_to_repair = True + self.asset_doc.flags.ignore_validate_update_after_submit = True - self.decrease_asset_value() + self.update_asset_value() + self.make_gl_entries(cancel=True) + self.set_increase_in_asset_life() - total_repair_cost = self.get_total_value_of_stock_consumed() - if self.capitalize_repair_cost: - total_repair_cost += self.repair_cost - self.asset_doc.total_asset_cost -= total_repair_cost - self.asset_doc.additional_asset_cost -= total_repair_cost - - if self.get("capitalize_repair_cost"): - self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry") - self.make_gl_entries(cancel=True) - if self.asset_doc.calculate_depreciation and self.increase_in_asset_life: - self.revert_depreciation_schedule_on_cancellation() - - notes = _( - "This schedule was created when Asset {0}'s Asset Repair {1} was cancelled." - ).format( - get_link_to_form(self.asset_doc.doctype, self.asset_doc.name), - get_link_to_form(self.doctype, self.name), - ) - self.asset_doc.flags.ignore_validate_update_after_submit = True - make_new_active_asset_depr_schedules_and_cancel_current_ones( - self.asset_doc, notes, ignore_booked_entry=True - ) - self.asset_doc.save() - - add_asset_activity( - self.asset, - _("Asset updated after cancellation of Asset Repair {0}").format( - get_link_to_form("Asset Repair", self.name) - ), - ) + depreciation_note = self.get_depreciation_note() + make_new_active_asset_depr_schedules_and_cancel_current_ones( + self.asset_doc, depreciation_note, ignore_booked_entry=True + ) + self.add_asset_activity() def after_delete(self): frappe.get_doc("Asset", self.asset).set_status() def check_repair_status(self): - if self.repair_status == "Pending": + if self.repair_status == "Pending" and self.docstatus == 1: frappe.throw(_("Please update Repair Status.")) - def check_for_stock_items_and_warehouse(self): - if not self.get("stock_items"): - frappe.throw(_("Please enter Stock Items consumed during the Repair."), title=_("Missing Items")) + def update_asset_value(self): + if self.docstaus == 2: + self.total_repair_cost *= -1 - def increase_asset_value(self): - total_value_of_stock_consumed = self.get_total_value_of_stock_consumed() + self.asset_doc.total_asset_cost += flt(self.total_repair_cost) + self.asset_doc.additional_asset_cost += flt(self.total_repair_cost) if self.asset_doc.calculate_depreciation: for row in self.asset_doc.finance_books: - row.value_after_depreciation += total_value_of_stock_consumed + row.value_after_depreciation += flt(self.total_repair_cost) - if self.capitalize_repair_cost: - row.value_after_depreciation += self.repair_cost - - def decrease_asset_value(self): - total_value_of_stock_consumed = self.get_total_value_of_stock_consumed() - - if self.asset_doc.calculate_depreciation: - for row in self.asset_doc.finance_books: - row.value_after_depreciation -= total_value_of_stock_consumed - - if self.capitalize_repair_cost: - row.value_after_depreciation -= self.repair_cost + self.asset_doc.save() def get_total_value_of_stock_consumed(self): - total_value_of_stock_consumed = 0 - if self.get("stock_consumption"): - for item in self.get("stock_items"): - total_value_of_stock_consumed += item.total_value - - return total_value_of_stock_consumed + return sum([flt(item.total_value) for item in self.get("stock_items")]) def decrease_stock_quantity(self): + if not self.get("stock_items"): + return + stock_entry = frappe.get_doc( - {"doctype": "Stock Entry", "stock_entry_type": "Material Issue", "company": self.company} + { + "doctype": "Stock Entry", + "stock_entry_type": "Material Issue", + "company": self.company, + "asset_repair": self.name, + } ) - stock_entry.asset_repair = self.name for stock_item in self.get("stock_items"): self.validate_serial_no(stock_item) @@ -278,7 +236,7 @@ class AssetRepair(AccountsController): "Item", stock_item.item_code, "has_serial_no" ): msg = f"Serial No Bundle is mandatory for Item {stock_item.item_code}" - frappe.throw(msg, title=_("Missing Serial No Bundle")) + frappe.throw(_(msg), title=_("Missing Serial No Bundle")) if stock_item.serial_and_batch_bundle: values_to_update = { @@ -291,6 +249,9 @@ class AssetRepair(AccountsController): ) def make_gl_entries(self, cancel=False): + if cancel: + self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry") + if flt(self.total_repair_cost) > 0: gl_entries = self.get_gl_entries() make_gl_entries(gl_entries, cancel) @@ -348,7 +309,7 @@ class AssetRepair(AccountsController): ) def get_gl_entries_for_consumed_items(self, gl_entries, fixed_asset_account): - if not (self.get("stock_consumption") and self.get("stock_items")): + if not self.get("stock_items"): return # creating GL Entries for each row in Stock Items based on the Stock Entry created for it @@ -400,72 +361,28 @@ class AssetRepair(AccountsController): ) ) - def modify_depreciation_schedule(self): - for row in self.asset_doc.finance_books: - row.total_number_of_depreciations += self.increase_in_asset_life / row.frequency_of_depreciation + def set_increase_in_asset_life(self): + if self.asset_doc.calculate_depreciation and cint(self.increase_in_asset_life) > 0: + for row in self.asset_doc.finance_books: + row.increase_in_asset_life = row.increase_in_asset_life + ( + cint(self.increase_in_asset_life) * (1 if self.docstatus == 1 else -1) + ) - self.asset_doc.flags.increase_in_asset_life = False - extra_months = self.increase_in_asset_life % row.frequency_of_depreciation - if extra_months != 0: - self.calculate_last_schedule_date(self.asset_doc, row, extra_months) - - # to help modify depreciation schedule when increase_in_asset_life is not a multiple of frequency_of_depreciation - def calculate_last_schedule_date(self, asset, row, extra_months): - asset.flags.increase_in_asset_life = True - number_of_pending_depreciations = cint(row.total_number_of_depreciations) - cint( - asset.opening_number_of_booked_depreciations + def get_depreciation_note(self): + return _("This schedule was created when Asset {0} was repaired through Asset Repair {1}.").format( + get_link_to_form(self.asset_doc.doctype, self.asset_doc.name), + get_link_to_form(self.doctype, self.name), ) - depr_schedule = get_depr_schedule(asset.name, "Active", row.finance_book) + def add_asset_activity(self, subject=None): + if not subject: + subject = _("Asset updated due to Asset Repair {0} {1}.").format( + get_link_to_form( + self.doctype, self.name, "submission" if self.docstatus == 1 else "cancellation" + ), + ) - # the Schedule Date in the final row of the old Depreciation Schedule - last_schedule_date = depr_schedule[len(depr_schedule) - 1].schedule_date - - # the Schedule Date in the final row of the new Depreciation Schedule - asset.to_date = add_months(last_schedule_date, extra_months) - - # the latest possible date at which the depreciation can occur, without increasing the Total Number of Depreciations - # if depreciations happen yearly and the Depreciation Posting Date is 01-01-2020, this could be 01-01-2021, 01-01-2022... - schedule_date = add_months( - row.depreciation_start_date, - number_of_pending_depreciations * cint(row.frequency_of_depreciation), - ) - - if asset.to_date > schedule_date: - row.total_number_of_depreciations += 1 - - def revert_depreciation_schedule_on_cancellation(self): - for row in self.asset_doc.finance_books: - row.total_number_of_depreciations -= self.increase_in_asset_life / row.frequency_of_depreciation - - self.asset_doc.flags.increase_in_asset_life = False - extra_months = self.increase_in_asset_life % row.frequency_of_depreciation - if extra_months != 0: - self.calculate_last_schedule_date_before_modification(self.asset_doc, row, extra_months) - - def calculate_last_schedule_date_before_modification(self, asset, row, extra_months): - asset.flags.increase_in_asset_life = True - number_of_pending_depreciations = cint(row.total_number_of_depreciations) - cint( - asset.opening_number_of_booked_depreciations - ) - - depr_schedule = get_depr_schedule(asset.name, "Active", row.finance_book) - - # the Schedule Date in the final row of the modified Depreciation Schedule - last_schedule_date = depr_schedule[len(depr_schedule) - 1].schedule_date - - # the Schedule Date in the final row of the original Depreciation Schedule - asset.to_date = add_months(last_schedule_date, -extra_months) - - # the latest possible date at which the depreciation can occur, without decreasing the Total Number of Depreciations - # if depreciations happen yearly and the Depreciation Posting Date is 01-01-2020, this could be 01-01-2021, 01-01-2022... - schedule_date = add_months( - row.depreciation_start_date, - (number_of_pending_depreciations - 1) * cint(row.frequency_of_depreciation), - ) - - if asset.to_date < schedule_date: - row.total_number_of_depreciations -= 1 + add_asset_activity(self.asset, subject) @frappe.whitelist() @@ -476,16 +393,11 @@ def get_downtime(failure_date, completion_date): @frappe.whitelist() def get_purchase_invoice(doctype, txt, searchfield, start, page_len, filters): - query = expense_item_pi_query(filters.get("company")) - return query.run(as_list=1) - - -def expense_item_pi_query(company): PurchaseInvoice = DocType("Purchase Invoice") PurchaseInvoiceItem = DocType("Purchase Invoice Item") Item = DocType("Item") - query = ( + return ( frappe.qb.from_(PurchaseInvoice) .join(PurchaseInvoiceItem) .on(PurchaseInvoiceItem.parent == PurchaseInvoice.name) @@ -495,8 +407,17 @@ def expense_item_pi_query(company): .where( (Item.is_stock_item == 0) & (Item.is_fixed_asset == 0) - & (PurchaseInvoice.company == company) + & (PurchaseInvoice.company == filters.get("company")) & (PurchaseInvoice.docstatus == 1) ) - ) - return query + ).run(as_list=1) + + +@frappe.whitelist() +def get_expense_accounts(doctype, txt, searchfield, start, page_len, filters): + PurchaseInvoiceItem = DocType("Purchase Invoice Item") + return ( + frappe.qb.from_(PurchaseInvoiceItem) + .select(PurchaseInvoiceItem.expense_account) + .where(PurchaseInvoiceItem.parent == filters.get("purchase_invoice")) + ).run(as_list=1) From c567a0847088f062bf3126b2bc6651ac8d173724 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 23 Dec 2024 12:53:57 +0530 Subject: [PATCH 08/40] refactor: Straight line depreciation after asset repair --- .../asset_depreciation_schedule.py | 79 +++++++--- .../asset_depreciation_schedule/utils.py | 136 ++++++++---------- .../asset_finance_book.json | 14 +- .../asset_finance_book/asset_finance_book.py | 1 + .../doctype/asset_repair/asset_repair.py | 1 + .../asset_value_adjustment.js | 3 + .../asset_value_adjustment.json | 17 ++- 7 files changed, 141 insertions(+), 110 deletions(-) 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 98cdb5c8b20..bf2f3fad049 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py @@ -175,12 +175,6 @@ class AssetDepreciationSchedule(Document): update_asset_finance_book_row=True, value_after_depreciation=None, ): - # if not self.get("depreciation_schedule"): - # self.depreciation_schedule = [] - - # if not asset_doc.available_for_use_date: - # return - start = self.clear_depr_schedule() self._make_depr_schedule( @@ -230,7 +224,7 @@ class AssetDepreciationSchedule(Document): prev_per_day_depr = True self.current_fiscal_year_end_date = None yearly_opening_wdv = value_after_depreciation - number_of_pending_depreciations = final_number_of_depreciations - start + 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) @@ -251,7 +245,7 @@ class AssetDepreciationSchedule(Document): n, prev_depreciation_amount, has_wdv_or_dd_non_yearly_pro_rata, - number_of_pending_depreciations, + pending_months, prev_per_day_depr, ) @@ -314,8 +308,38 @@ class AssetDepreciationSchedule(Document): 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 ( @@ -326,6 +350,22 @@ class AssetDepreciationSchedule(Document): return has_wdv_or_dd_non_yearly_pro_rata + def get_number_of_pending_months(self, asset_doc, row, start): + print(row.total_number_of_depreciations) + total_months = cint(row.total_number_of_depreciations) * cint(row.frequency_of_depreciation) + cint( + row.increase_in_asset_life + ) + depr_booked_for_months = 0 + 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) + print(total_months, depr_booked_for_months) + return total_months - depr_booked_for_months + def has_fiscal_year_changed(self, row, row_no): fiscal_year_changed = False @@ -352,13 +392,9 @@ class AssetDepreciationSchedule(Document): def get_next_schedule_date( self, row, n, 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) + 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 @@ -435,27 +471,28 @@ class AssetDepreciationSchedule(Document): 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: + 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 - asset_doc.to_date = add_months( + 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)): - asset_doc.to_date = get_last_day(asset_doc.to_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, - asset_doc.to_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 - depreciation_amount -= self.get("depreciation_schedule")[0].depreciation_amount - days = date_diff(asset_doc.to_date, schedule_date) + 1 + 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 diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/utils.py b/erpnext/assets/doctype/asset_depreciation_schedule/utils.py index 2617493751c..c13ad5ebdcb 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/utils.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/utils.py @@ -4,6 +4,7 @@ from frappe.utils import ( add_months, add_years, cint, + cstr, date_diff, flt, get_last_day, @@ -16,7 +17,7 @@ import erpnext def get_depreciation_amount( asset_depr_schedule, asset, - depreciable_value, + value_after_depreciation, yearly_opening_wdv, fb_row, schedule_idx=0, @@ -27,13 +28,18 @@ def get_depreciation_amount( ): 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 + 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, - depreciable_value, + value_after_depreciation, yearly_opening_wdv, schedule_idx, prev_depreciation_amount, @@ -44,83 +50,59 @@ def get_depreciation_amount( def get_straight_line_or_manual_depr_amount( - asset_depr_schedule, asset, row, schedule_idx, number_of_pending_depreciations + asset_depr_schedule, + asset, + fb_row, + schedule_idx, + value_after_depreciation, + number_of_pending_depreciations, ): - if row.shift_based: - return get_shift_depr_amount(asset_depr_schedule, asset, row, schedule_idx) + if fb_row.shift_based: + return get_shift_depr_amount(asset_depr_schedule, asset, fb_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 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 ) - # 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 + 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, row, schedule_idx, number_of_pending_depreciations, amount + asset, fb_row, schedule_idx, number_of_pending_depreciations, amount ): - daily_depr_amount = get_daily_depr_amount(asset, row, schedule_idx, amount) + daily_depr_amount = get_daily_depr_amount(asset, fb_row, schedule_idx, amount) from_date, total_depreciable_days = _get_total_days( - row.depreciation_start_date, schedule_idx, row.frequency_of_depreciation + 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, row, schedule_idx, amount): +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( - row.depreciation_start_date, + fb_row.depreciation_start_date, flt( - row.total_number_of_depreciations + fb_row.total_number_of_depreciations - asset.opening_number_of_booked_depreciations - 1 ) - * row.frequency_of_depreciation, + * fb_row.frequency_of_depreciation, ) ), add_days( get_last_day( add_months( - row.depreciation_start_date, + fb_row.depreciation_start_date, ( - row.frequency_of_depreciation + fb_row.frequency_of_depreciation * (asset.opening_number_of_booked_depreciations + 1) ) * -1, @@ -136,8 +118,8 @@ def get_daily_depr_amount(asset, row, schedule_idx, amount): else: total_years = ( flt( - (row.total_number_of_depreciations - row.total_number_of_booked_depreciations) - * row.frequency_of_depreciation + (fb_row.total_number_of_depreciations - fb_row.total_number_of_booked_depreciations) + * fb_row.frequency_of_depreciation ) / 12 ) @@ -145,24 +127,24 @@ def get_daily_depr_amount(asset, row, schedule_idx, amount): 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 + 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, ((row.frequency_of_depreciation * schedule_idx) // 12) + 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, row, schedule_idx): +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(row.expected_value_after_useful_life) - ) / flt(row.total_number_of_depreciations - asset.opening_number_of_booked_depreciations) + - 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 = ( @@ -181,7 +163,7 @@ def get_shift_depr_amount(asset_depr_schedule, asset, row, schedule_idx): ( flt(asset.gross_purchase_amount) - flt(asset.opening_accumulated_depreciation) - - flt(row.expected_value_after_useful_life) + - flt(fb_row.expected_value_after_useful_life) ) / flt(shift_factors_sum) ) * shift_factor @@ -195,7 +177,7 @@ def get_asset_shift_factors_map(): def get_wdv_or_dd_depr_amount( asset, fb_row, - depreciable_value, + value_after_depreciation, yearly_opening_wdv, schedule_idx, prev_depreciation_amount, @@ -206,7 +188,7 @@ def get_wdv_or_dd_depr_amount( return get_default_wdv_or_dd_depr_amount( asset, fb_row, - depreciable_value, + value_after_depreciation, schedule_idx, prev_depreciation_amount, has_wdv_or_dd_non_yearly_pro_rata, @@ -218,7 +200,7 @@ def get_wdv_or_dd_depr_amount( def get_default_wdv_or_dd_depr_amount( asset, fb_row, - depreciable_value, + value_after_depreciation, schedule_idx, prev_depreciation_amount, has_wdv_or_dd_non_yearly_pro_rata, @@ -229,7 +211,7 @@ def get_default_wdv_or_dd_depr_amount( return _get_default_wdv_or_dd_depr_amount( asset, fb_row, - depreciable_value, + value_after_depreciation, schedule_idx, prev_depreciation_amount, has_wdv_or_dd_non_yearly_pro_rata, @@ -239,7 +221,7 @@ def get_default_wdv_or_dd_depr_amount( return _get_daily_prorata_based_default_wdv_or_dd_depr_amount( asset, fb_row, - depreciable_value, + value_after_depreciation, schedule_idx, prev_depreciation_amount, has_wdv_or_dd_non_yearly_pro_rata, @@ -251,21 +233,21 @@ def get_default_wdv_or_dd_depr_amount( def _get_default_wdv_or_dd_depr_amount( asset, fb_row, - depreciable_value, + 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(depreciable_value) * (flt(fb_row.rate_of_depreciation) / 100) + 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(depreciable_value) * (flt(fb_row.rate_of_depreciation) / 100) + 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(depreciable_value) + flt(value_after_depreciation) * flt(fb_row.frequency_of_depreciation) * (flt(fb_row.rate_of_depreciation) / 1200) ) @@ -274,7 +256,7 @@ def _get_default_wdv_or_dd_depr_amount( else: if schedule_idx % (12 / cint(fb_row.frequency_of_depreciation)) == 0: return ( - flt(depreciable_value) + flt(value_after_depreciation) * flt(fb_row.frequency_of_depreciation) * (flt(fb_row.rate_of_depreciation) / 1200) ) @@ -285,7 +267,7 @@ def _get_default_wdv_or_dd_depr_amount( def _get_daily_prorata_based_default_wdv_or_dd_depr_amount( asset, fb_row, - depreciable_value, + value_after_depreciation, schedule_idx, prev_depreciation_amount, has_wdv_or_dd_non_yearly_pro_rata, @@ -294,20 +276,20 @@ def _get_daily_prorata_based_default_wdv_or_dd_depr_amount( ): 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 + 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, depreciable_value) + 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, depreciable_value) + 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, depreciable_value): +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 @@ -316,7 +298,7 @@ def get_monthly_depr_amount(fb_row, schedule_idx, depreciable_value): 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) + per_day_depr = get_per_day_depr(fb_row, value_after_depreciation, from_date) return (per_day_depr * days_in_month), per_day_depr @@ -333,12 +315,12 @@ def get_monthly_depr_amount_based_on_prev_per_day_depr(fb_row, schedule_idx, pre def get_per_day_depr( fb_row, - depreciable_value, + 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(depreciable_value) * (flt(fb_row.rate_of_depreciation) / 100)) / total_days + per_day_depr = (flt(value_after_depreciation) * (flt(fb_row.rate_of_depreciation) / 100)) / total_days return per_day_depr 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 c97ae1a8545..be035f7ea7a 100644 --- a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json +++ b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json @@ -9,6 +9,7 @@ "depreciation_method", "frequency_of_depreciation", "total_number_of_depreciations", + "increase_in_asset_life", "depreciation_start_date", "column_break_5", "salvage_value_percentage", @@ -61,7 +62,6 @@ { "fieldname": "depreciation_start_date", "fieldtype": "Date", - "hidden": 1, "in_list_view": 1, "label": "Depreciation Posting Date", "mandatory_depends_on": "eval:parent.doctype == 'Asset'" @@ -101,7 +101,7 @@ "default": "0", "fieldname": "daily_prorata_based", "fieldtype": "Check", - "label": "Depreciate based on days per period" + "label": "Depreciate based on daily pro-rata" }, { "default": "0", @@ -125,12 +125,20 @@ { "fieldname": "column_break_sigk", "fieldtype": "Column Break" + }, + { + "description": "via Asset Repair", + "fieldname": "increase_in_asset_life", + "fieldtype": "Int", + "label": "Increase In Asset Life (Months)", + "no_copy": 1, + "read_only": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2024-12-13 12:11:03.743209", + "modified": "2024-12-19 17:50:24.012434", "modified_by": "Administrator", "module": "Assets", "name": "Asset Finance Book", diff --git a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.py b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.py index d06d6355ec3..d629aefd967 100644 --- a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.py +++ b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.py @@ -22,6 +22,7 @@ class AssetFinanceBook(Document): expected_value_after_useful_life: DF.Currency finance_book: DF.Link | None frequency_of_depreciation: DF.Int + increase_in_asset_life: DF.Int parent: DF.Data parentfield: DF.Data parenttype: DF.Data diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py index 4b4ff1a5383..8a179ea5b68 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/asset_repair.py @@ -419,5 +419,6 @@ def get_expense_accounts(doctype, txt, searchfield, start, page_len, filters): return ( frappe.qb.from_(PurchaseInvoiceItem) .select(PurchaseInvoiceItem.expense_account) + .distinct() .where(PurchaseInvoiceItem.parent == filters.get("purchase_invoice")) ).run(as_list=1) diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js index 4435b2b1845..135fb76684e 100644 --- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js +++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js @@ -45,6 +45,9 @@ frappe.ui.form.on("Asset Value Adjustment", { asset: function (frm) { frm.trigger("set_acc_dimension"); + if (frm.doc.asset) { + frm.trigger("set_current_asset_value"); + } }, finance_book: function (frm) { diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.json b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.json index e584c3d8202..e60d401055d 100644 --- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.json +++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.json @@ -54,8 +54,8 @@ "fieldname": "journal_entry", "fieldtype": "Link", "label": "Journal Entry", - "options": "Journal Entry", "no_copy": 1, + "options": "Journal Entry", "read_only": 1 }, { @@ -125,18 +125,18 @@ "fieldtype": "Column Break" }, { - "fieldname": "difference_account", - "fieldtype": "Link", - "label": "Difference Account", - "no_copy": 1, - "options": "Account", - "reqd": 1 + "fieldname": "difference_account", + "fieldtype": "Link", + "label": "Difference Account", + "no_copy": 1, + "options": "Account", + "reqd": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2024-08-13 16:21:18.639208", + "modified": "2024-12-18 15:04:18.726505", "modified_by": "Administrator", "module": "Assets", "name": "Asset Value Adjustment", @@ -188,7 +188,6 @@ "write": 1 } ], - "quick_entry": 1, "sort_field": "creation", "sort_order": "DESC", "states": [], From 44e45b55d4b0ee3d072e925d088c699136315853 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 23 Dec 2024 18:24:10 +0530 Subject: [PATCH 09/40] refactor: asset value adjustment --- .../doctype/journal_entry/journal_entry.py | 23 +-- erpnext/assets/doctype/asset/asset.py | 4 +- .../asset_depreciation_schedule.py | 45 ++--- .../doctype/asset_repair/asset_repair.json | 16 +- .../doctype/asset_repair/asset_repair.py | 25 +-- .../asset_value_adjustment.py | 163 +++++++++--------- 6 files changed, 119 insertions(+), 157 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index a3047c9339d..ea7583e6218 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -196,7 +196,6 @@ class JournalEntry(AccountsController): self.update_asset_value() self.update_inter_company_jv() self.update_invoice_discounting() - self.update_booked_depreciation() def on_update_after_submit(self): # Flag will be set on Reconciliation @@ -232,7 +231,6 @@ class JournalEntry(AccountsController): self.unlink_inter_company_jv() self.unlink_asset_adjustment_entry() self.update_invoice_discounting() - self.update_booked_depreciation(1) def get_title(self): return self.pay_to_recd_from or self.accounts[0].account @@ -405,6 +403,7 @@ class JournalEntry(AccountsController): asset.db_set("value_after_depreciation", asset.value_after_depreciation - d.debit) asset.set_status() + asset.set_total_booked_depreciations() def update_inter_company_jv(self): if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference: @@ -459,25 +458,6 @@ class JournalEntry(AccountsController): if status: inv_disc_doc.set_status(status=status) - def update_booked_depreciation(self, cancel=0): - for d in self.get("accounts"): - if ( - self.voucher_type == "Depreciation Entry" - and d.reference_type == "Asset" - and d.reference_name - and frappe.get_cached_value("Account", d.account, "root_type") == "Expense" - and d.debit - ): - asset = frappe.get_doc("Asset", d.reference_name) - for fb_row in asset.get("finance_books"): - if fb_row.finance_book == self.finance_book: - if cancel: - fb_row.total_number_of_booked_depreciations -= 1 - else: - fb_row.total_number_of_booked_depreciations += 1 - fb_row.db_update() - break - def unlink_advance_entry_reference(self): for d in self.get("accounts"): if d.is_advance == "Yes" and d.reference_type in ("Sales Invoice", "Purchase Invoice"): @@ -530,6 +510,7 @@ class JournalEntry(AccountsController): else: asset.db_set("value_after_depreciation", asset.value_after_depreciation + d.debit) asset.set_status() + asset.set_total_booked_depreciations() elif self.voucher_type == "Journal Entry" and d.reference_type == "Asset" and d.reference_name: journal_entry_for_scrap = frappe.db.get_value( "Asset", d.reference_name, "journal_entry_for_scrap" diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index de3fb10324d..a65246ee820 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -130,8 +130,6 @@ class Asset(AccountsController): self.set_missing_values() self.validate_gross_and_purchase_amount() self.validate_finance_books() - 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() @@ -178,6 +176,8 @@ class Asset(AccountsController): def on_update(self): self.create_asset_depreciation_schedule() + self.validate_expected_value_after_useful_life() + self.set_total_booked_depreciations() def on_submit(self): self.validate_in_use_date() 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 bf2f3fad049..4566f52b20a 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py @@ -351,11 +351,11 @@ class AssetDepreciationSchedule(Document): return has_wdv_or_dd_non_yearly_pro_rata def get_number_of_pending_months(self, asset_doc, row, start): - print(row.total_number_of_depreciations) 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: @@ -363,7 +363,7 @@ class AssetDepreciationSchedule(Document): if last_depr_date: depr_booked_for_months = date_diff(last_depr_date, asset_doc.available_for_use_date) / (365 / 12) - print(total_months, depr_booked_for_months) + return total_months - depr_booked_for_months def has_fiscal_year_changed(self, row, row_no): @@ -516,7 +516,7 @@ class AssetDepreciationSchedule(Document): 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. """ @@ -553,42 +553,23 @@ class AssetDepreciationSchedule(Document): row, date_of_disposal=None, date_of_return=None, - ignore_booked_entry=False, ): - straight_line_idx = [ - d.idx - for d in self.get("depreciation_schedule") - if self.depreciation_method == "Straight Line" or self.depreciation_method == "Manual" - ] - - accumulated_depreciation = 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 ignore_booked_entry and d.journal_entry: + if d.journal_entry: + accumulated_depreciation = d.accumulated_depreciation_amount continue - if not accumulated_depreciation: - if i > 0 and ( - asset_doc.flags.decrease_in_asset_value_due_to_value_adjustment - or asset_doc.flags.increase_in_asset_value_due_to_repair - ): - accumulated_depreciation = self.get("depreciation_schedule")[ - i - 1 - ].accumulated_depreciation_amount - else: - accumulated_depreciation = flt( - self.opening_accumulated_depreciation, - asset_doc.precision("opening_accumulated_depreciation"), - ) - - value_after_depreciation -= flt(d.depreciation_amount) - value_after_depreciation = flt(value_after_depreciation, d.precision("depreciation_amount")) + 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 ( - straight_line_idx - and i == max(straight_line_idx) - 1 + 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 @@ -757,7 +738,6 @@ def make_new_active_asset_depr_schedules_and_cancel_current_ones( date_of_disposal=None, date_of_return=None, value_after_depreciation=None, - ignore_booked_entry=False, difference_amount=None, ): for row in asset_doc.get("finance_books"): @@ -773,6 +753,7 @@ def make_new_active_asset_depr_schedules_and_cancel_current_ones( ) 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 @@ -790,7 +771,7 @@ def make_new_active_asset_depr_schedules_and_cancel_current_ones( 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, ignore_booked_entry + asset_doc, row, date_of_disposal, date_of_return ) new_asset_depr_schedule_doc.notes = notes diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.json b/erpnext/assets/doctype/asset_repair/asset_repair.json index b74292a093e..54a067063b2 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.json +++ b/erpnext/assets/doctype/asset_repair/asset_repair.json @@ -27,7 +27,7 @@ "column_break_ajbh", "column_break_hkem", "repair_cost", - "asset_depreciation_details_section", + "accounting_dimensions_section", "cost_center", "column_break_14", "project", @@ -185,12 +185,6 @@ "label": "Total Repair Cost", "read_only": 1 }, - { - "depends_on": "capitalize_repair_cost", - "fieldname": "asset_depreciation_details_section", - "fieldtype": "Section Break", - "label": "Asset Depreciation Details" - }, { "depends_on": "capitalize_repair_cost", "fieldname": "increase_in_asset_life", @@ -253,12 +247,18 @@ { "fieldname": "column_break_xebe", "fieldtype": "Column Break" + }, + { + "depends_on": "capitalize_repair_cost", + "fieldname": "accounting_dimensions_section", + "fieldtype": "Section Break", + "label": "Accounting Dimensions" } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2024-12-20 13:10:31.540666", + "modified": "2024-12-23 18:08:35.159964", "modified_by": "Administrator", "module": "Assets", "name": "Asset Repair", diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py index 8a179ea5b68..8b541163179 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/asset_repair.py @@ -148,15 +148,12 @@ class AssetRepair(AccountsController): self.decrease_stock_quantity() if self.get("capitalize_repair_cost"): - self.asset_doc.flags.ignore_validate_update_after_submit = True self.update_asset_value() self.make_gl_entries() self.set_increase_in_asset_life() depreciation_note = self.get_depreciation_note() - make_new_active_asset_depr_schedules_and_cancel_current_ones( - self.asset_doc, depreciation_note, ignore_booked_entry=True - ) + make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, depreciation_note) self.add_asset_activity() def on_cancel(self): @@ -164,16 +161,13 @@ class AssetRepair(AccountsController): if self.get("capitalize_repair_cost"): self.asset_doc.flags.increase_in_asset_value_due_to_repair = True - self.asset_doc.flags.ignore_validate_update_after_submit = True self.update_asset_value() self.make_gl_entries(cancel=True) self.set_increase_in_asset_life() depreciation_note = self.get_depreciation_note() - make_new_active_asset_depr_schedules_and_cancel_current_ones( - self.asset_doc, depreciation_note, ignore_booked_entry=True - ) + make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, depreciation_note) self.add_asset_activity() def after_delete(self): @@ -184,16 +178,16 @@ class AssetRepair(AccountsController): frappe.throw(_("Please update Repair Status.")) def update_asset_value(self): - if self.docstaus == 2: - self.total_repair_cost *= -1 + total_repair_cost = self.total_repair_cost if self.docstatus == 1 else -1 * self.total_repair_cost - self.asset_doc.total_asset_cost += flt(self.total_repair_cost) - self.asset_doc.additional_asset_cost += flt(self.total_repair_cost) + self.asset_doc.total_asset_cost += flt(total_repair_cost) + self.asset_doc.additional_asset_cost += flt(total_repair_cost) if self.asset_doc.calculate_depreciation: for row in self.asset_doc.finance_books: - row.value_after_depreciation += flt(self.total_repair_cost) + row.value_after_depreciation += flt(total_repair_cost) + self.asset_doc.flags.ignore_validate_update_after_submit = True self.asset_doc.save() def get_total_value_of_stock_consumed(self): @@ -377,9 +371,8 @@ class AssetRepair(AccountsController): def add_asset_activity(self, subject=None): if not subject: subject = _("Asset updated due to Asset Repair {0} {1}.").format( - get_link_to_form( - self.doctype, self.name, "submission" if self.docstatus == 1 else "cancellation" - ), + get_link_to_form(self.doctype, self.name), + "submission" if self.docstatus == 1 else "cancellation", ) add_asset_activity(self.asset, subject) diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py index 6766b827f7f..07817d8e6ad 100644 --- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py +++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py @@ -5,7 +5,7 @@ import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import flt, formatdate, get_link_to_form, getdate +from frappe.utils import cstr, flt, formatdate, get_link_to_form, getdate from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( get_checks_for_pl_and_bs_accounts, @@ -46,10 +46,26 @@ class AssetValueAdjustment(Document): self.set_current_asset_value() self.set_difference_amount() + def validate_date(self): + asset_purchase_date = frappe.db.get_value("Asset", self.asset, "purchase_date") + if getdate(self.date) < getdate(asset_purchase_date): + frappe.throw( + _("Asset Value Adjustment cannot be posted before Asset's purchase date {0}.").format( + formatdate(asset_purchase_date) + ), + title=_("Incorrect Date"), + ) + + def set_difference_amount(self): + self.difference_amount = flt(self.new_asset_value - self.current_asset_value) + + def set_current_asset_value(self): + if not self.current_asset_value and self.asset: + self.current_asset_value = get_asset_value_after_depreciation(self.asset, self.finance_book) + def on_submit(self): self.make_depreciation_entry() - self.set_value_after_depreciation() - self.update_asset(self.new_asset_value) + self.update_asset() add_asset_activity( self.asset, _("Asset's value adjusted after submission of Asset Value Adjustment {0}").format( @@ -67,26 +83,6 @@ class AssetValueAdjustment(Document): ), ) - def validate_date(self): - asset_purchase_date = frappe.db.get_value("Asset", self.asset, "purchase_date") - if getdate(self.date) < getdate(asset_purchase_date): - frappe.throw( - _("Asset Value Adjustment cannot be posted before Asset's purchase date {0}.").format( - formatdate(asset_purchase_date) - ), - title=_("Incorrect Date"), - ) - - def set_difference_amount(self): - self.difference_amount = flt(self.new_asset_value - self.current_asset_value) - - def set_value_after_depreciation(self): - frappe.db.set_value("Asset", self.asset, "value_after_depreciation", self.new_asset_value) - - def set_current_asset_value(self): - if not self.current_asset_value and self.asset: - self.current_asset_value = get_asset_value_after_depreciation(self.asset, self.finance_book) - def make_depreciation_entry(self): asset = frappe.get_doc("Asset", self.asset) ( @@ -114,46 +110,15 @@ class AssetValueAdjustment(Document): } if self.difference_amount < 0: - credit_entry = { - "account": fixed_asset_account, - "credit_in_account_currency": -self.difference_amount, - **entry_template, - } - debit_entry = { - "account": self.difference_account, - "debit_in_account_currency": -self.difference_amount, - **entry_template, - } + credit_entry, debit_entry = self.get_entry_for_asset_value_decrease( + fixed_asset_account, entry_template + ) elif self.difference_amount > 0: - credit_entry = { - "account": self.difference_account, - "credit_in_account_currency": self.difference_amount, - **entry_template, - } - debit_entry = { - "account": fixed_asset_account, - "debit_in_account_currency": self.difference_amount, - **entry_template, - } + credit_entry, debit_entry = self.get_entry_for_asset_value_increase( + fixed_asset_account, entry_template + ) - accounting_dimensions = get_checks_for_pl_and_bs_accounts() - - for dimension in accounting_dimensions: - if dimension.get("mandatory_for_bs"): - credit_entry.update( - { - dimension["fieldname"]: self.get(dimension["fieldname"]) - or dimension.get("default_dimension") - } - ) - - if dimension.get("mandatory_for_pl"): - debit_entry.update( - { - dimension["fieldname"]: self.get(dimension["fieldname"]) - or dimension.get("default_dimension") - } - ) + self.update_accounting_dimensions(credit_entry, debit_entry) je.append("accounts", credit_entry) je.append("accounts", debit_entry) @@ -163,40 +128,82 @@ class AssetValueAdjustment(Document): self.db_set("journal_entry", je.name) - def update_asset(self, asset_value=None): + def get_entry_for_asset_value_decrease(self, fixed_asset_account, entry_template): + credit_entry = { + "account": fixed_asset_account, + "credit_in_account_currency": -self.difference_amount, + **entry_template, + } + debit_entry = { + "account": self.difference_account, + "debit_in_account_currency": -self.difference_amount, + **entry_template, + } + + return credit_entry, debit_entry + + def get_entry_for_asset_value_increase(self, fixed_asset_account, entry_template): + credit_entry = { + "account": self.difference_account, + "credit_in_account_currency": self.difference_amount, + **entry_template, + } + debit_entry = { + "account": fixed_asset_account, + "debit_in_account_currency": self.difference_amount, + **entry_template, + } + + return credit_entry, debit_entry + + def update_accounting_dimensions(self, credit_entry, debit_entry): + accounting_dimensions = get_checks_for_pl_and_bs_accounts() + + for dimension in accounting_dimensions: + dimension_value = self.get(dimension["fieldname"]) or dimension.get("default_dimension") + if dimension.get("mandatory_for_bs"): + credit_entry.update({dimension["fieldname"]: dimension_value}) + + if dimension.get("mandatory_for_pl"): + debit_entry.update({dimension["fieldname"]: dimension_value}) + + def update_asset(self): + asset = self.update_asset_value_after_depreciation() + note = self.get_adjustment_note() + make_new_active_asset_depr_schedules_and_cancel_current_ones(asset, note) + + def update_asset_value_after_depreciation(self): + difference_amount = self.difference_amount if self.docstatus == 1 else -1 * self.difference_amount + asset = frappe.get_doc("Asset", self.asset) - if not asset.calculate_depreciation: - asset.value_after_depreciation = asset_value - asset.save() - return + asset.value_after_depreciation += flt(difference_amount) + asset.db_update() + else: + for row in asset.finance_books: + if cstr(row.finance_book) == cstr(self.finance_book): + row.value_after_depreciation += flt(difference_amount) + row.db_update() - asset.flags.decrease_in_asset_value_due_to_value_adjustment = True + return asset + def get_adjustment_note(self): if self.docstatus == 1: notes = _( "This schedule was created when Asset {0} was adjusted through Asset Value Adjustment {1}." ).format( - get_link_to_form("Asset", asset.name), + get_link_to_form("Asset", self.asset), get_link_to_form(self.get("doctype"), self.get("name")), ) elif self.docstatus == 2: notes = _( "This schedule was created when Asset {0}'s Asset Value Adjustment {1} was cancelled." ).format( - get_link_to_form("Asset", asset.name), + get_link_to_form("Asset", self.asset), get_link_to_form(self.get("doctype"), self.get("name")), ) - make_new_active_asset_depr_schedules_and_cancel_current_ones( - asset, - notes, - value_after_depreciation=asset_value, - ignore_booked_entry=True, - difference_amount=self.difference_amount, - ) - asset.flags.ignore_validate_update_after_submit = True - asset.save() + return notes @frappe.whitelist() From 5f21d7ea1ddec7e13a41f49a13e2abd69a0a144a Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Tue, 14 Jan 2025 01:46:08 +0530 Subject: [PATCH 10/40] chore: rebase from develop --- .../doctype/journal_entry/journal_entry.json | 4 +- .../doctype/journal_entry/journal_entry.py | 64 +- .../doctype/sales_invoice/sales_invoice.py | 223 +++--- erpnext/assets/doctype/asset/asset.js | 36 +- erpnext/assets/doctype/asset/asset.py | 61 +- erpnext/assets/doctype/asset/depreciation.py | 294 +++---- erpnext/assets/doctype/asset/test_asset.py | 2 +- .../asset_capitalization.py | 6 +- .../asset_depreciation_schedule.py | 720 ++---------------- .../deppreciation_schedule_controller.py | 449 +++++++++++ .../depreciation_methods.py | 119 +++ .../asset_depreciation_schedule/utils.py | 333 -------- .../asset_finance_book.json | 6 +- .../doctype/asset_repair/asset_repair.json | 3 +- .../doctype/asset_repair/asset_repair.py | 16 +- .../asset_shift_allocation.py | 4 +- .../asset_value_adjustment.py | 8 +- erpnext/patches.txt | 1 + ...sset_depreciation_schedules_from_assets.py | 2 +- .../v15_0/update_journal_entry_type.py | 18 + 20 files changed, 1072 insertions(+), 1297 deletions(-) create mode 100644 erpnext/assets/doctype/asset_depreciation_schedule/deppreciation_schedule_controller.py create mode 100644 erpnext/assets/doctype/asset_depreciation_schedule/depreciation_methods.py delete mode 100644 erpnext/assets/doctype/asset_depreciation_schedule/utils.py create mode 100644 erpnext/patches/v15_0/update_journal_entry_type.py diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.json b/erpnext/accounts/doctype/journal_entry/journal_entry.json index 39c914c0c6a..c7430dbf00c 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.json +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.json @@ -89,7 +89,7 @@ "label": "Entry Type", "oldfieldname": "voucher_type", "oldfieldtype": "Select", - "options": "Journal Entry\nInter Company Journal Entry\nBank Entry\nCash Entry\nCredit Card Entry\nDebit Note\nCredit Note\nContra Entry\nExcise Entry\nWrite Off Entry\nOpening Entry\nDepreciation Entry\nExchange Rate Revaluation\nExchange Gain Or Loss\nDeferred Revenue\nDeferred Expense", + "options": "Journal Entry\nInter Company Journal Entry\nBank Entry\nCash Entry\nCredit Card Entry\nDebit Note\nCredit Note\nContra Entry\nExcise Entry\nWrite Off Entry\nOpening Entry\nDepreciation Entry\nAsset Disposal\nExchange Rate Revaluation\nExchange Gain Or Loss\nDeferred Revenue\nDeferred Expense", "reqd": 1, "search_index": 1 }, @@ -557,7 +557,7 @@ "table_fieldname": "payment_entries" } ], - "modified": "2024-07-18 15:32:29.413598", + "modified": "2024-12-26 15:32:20.730666", "modified_by": "Administrator", "module": "Accounts", "name": "Journal Entry", diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index ea7583e6218..4a79b6ceb2c 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -100,6 +100,7 @@ class JournalEntry(AccountsController): "Write Off Entry", "Opening Entry", "Depreciation Entry", + "Asset Disposal", "Exchange Rate Revaluation", "Exchange Gain Or Loss", "Deferred Revenue", @@ -377,7 +378,11 @@ class JournalEntry(AccountsController): self.remove(d) def update_asset_value(self): - if self.flags.planned_depr_entry or self.voucher_type != "Depreciation Entry": + self.update_asset_on_depreciation() + self.update_asset_on_disposal() + + def update_asset_on_depreciation(self): + if self.voucher_type != "Depreciation Entry": return for d in self.get("accounts"): @@ -387,24 +392,61 @@ class JournalEntry(AccountsController): and d.account_type == "Depreciation" and d.debit ): - asset = frappe.get_doc("Asset", d.reference_name) + asset = frappe.get_cached_doc("Asset", d.reference_name) if asset.calculate_depreciation: - fb_idx = 1 - if self.finance_book: - for fb_row in asset.get("finance_books"): - if fb_row.finance_book == self.finance_book: - fb_idx = fb_row.idx - break - fb_row = asset.get("finance_books")[fb_idx - 1] - fb_row.value_after_depreciation -= d.debit - fb_row.db_update() + self.update_journal_entry_link_on_depr_schedule(asset, d) + self.update_value_after_depreciation(asset, d.debit) else: asset.db_set("value_after_depreciation", asset.value_after_depreciation - d.debit) asset.set_status() asset.set_total_booked_depreciations() + def update_value_after_depreciation(self, asset, depr_amount): + fb_idx = 1 + if self.finance_book: + for fb_row in asset.get("finance_books"): + if fb_row.finance_book == self.finance_book: + fb_idx = fb_row.idx + break + fb_row = asset.get("finance_books")[fb_idx - 1] + fb_row.value_after_depreciation -= depr_amount + frappe.db.set_value( + "Asset Finance Book", fb_row.name, "value_after_depreciation", fb_row.value_after_depreciation + ) + + def update_journal_entry_link_on_depr_schedule(self, asset, je_row): + depr_schedule = get_depr_schedule(asset.name, "Active", self.finance_book) + for d in depr_schedule or []: + if ( + d.schedule_date == self.posting_date + and not d.journal_entry + and d.depreciation_amount == flt(je_row.debit) + ): + frappe.db.set_value("Depreciation Schedule", d.name, "journal_entry", self.name) + + def update_asset_on_disposal(self): + if self.voucher_type == "Asset Disposal": + disposed_assets = [] + for d in self.get("accounts"): + if ( + d.reference_type == "Asset" + and d.reference_name + and d.reference_name not in disposed_assets + ): + frappe.db.set_value( + "Asset", + d.reference_name, + { + "disposal_date": self.posting_date, + "journal_entry_for_scrap": self.name, + }, + ) + asset_doc = frappe.get_doc("Asset", d.reference_name) + asset_doc.set_status() + disposed_assets.append(d.reference_name) + def update_inter_company_jv(self): if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference: frappe.db.set_value( diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 8eacada5e4a..62bb9c65dc2 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -39,7 +39,7 @@ from erpnext.assets.doctype.asset.depreciation import ( get_gl_entries_on_asset_disposal, get_gl_entries_on_asset_regain, reset_depreciation_schedule, - reverse_depreciation_entry_made_after_disposal, + reverse_depreciation_entry_made_on_disposal, ) from erpnext.assets.doctype.asset_activity.asset_activity import add_asset_activity from erpnext.controllers.accounts_controller import validate_account_head @@ -368,21 +368,34 @@ class SalesInvoice(SellingController): validate_docs_for_deferred_accounting([self.name], []) def validate_fixed_asset(self): - for d in self.get("items"): - if d.is_fixed_asset and d.meta.get_field("asset") and d.asset: - asset = frappe.get_doc("Asset", d.asset) - if self.doctype == "Sales Invoice" and self.docstatus == 1: - if self.update_stock: - frappe.throw(_("'Update Stock' cannot be checked for fixed asset sale")) + if self.doctype != "Sales Invoice": + return - elif asset.status in ("Scrapped", "Cancelled", "Capitalized") or ( - asset.status == "Sold" and not self.is_return - ): - frappe.throw( - _("Row #{0}: Asset {1} cannot be submitted, it is already {2}").format( - d.idx, d.asset, asset.status + for d in self.get("items"): + if d.is_fixed_asset: + if d.asset: + if not self.is_return: + asset_status = frappe.db.get_value("Asset", d.asset, "status") + if self.update_stock: + frappe.throw(_("'Update Stock' cannot be checked for fixed asset sale")) + + elif asset_status in ("Scrapped", "Cancelled", "Capitalized"): + frappe.throw( + _("Row #{0}: Asset {1} cannot be sold, it is already {2}").format( + d.idx, d.asset, asset_status + ) ) + elif asset_status == "Sold" and not self.is_return: + frappe.throw(_("Row #{0}: Asset {1} is already sold").format(d.idx, d.asset)) + elif not self.return_against: + frappe.throw( + _("Row #{0}: Return Against is required for returning asset").format(d.idx) ) + else: + frappe.throw( + _("Row #{0}: You must select an Asset for Item {1}.").format(d.idx, d.item_code), + title=_("Missing Asset"), + ) def validate_item_cost_centers(self): for item in self.items: @@ -464,6 +477,8 @@ class SalesInvoice(SellingController): self.update_stock_reservation_entries() self.update_stock_ledger() + self.process_asset_depreciation() + # this sequence because outstanding may get -ve self.make_gl_entries() @@ -583,6 +598,8 @@ class SalesInvoice(SellingController): if self.update_stock == 1: self.update_stock_ledger() + self.process_asset_depreciation() + self.make_gl_entries_on_cancel() if self.update_stock == 1: @@ -1253,6 +1270,90 @@ class SalesInvoice(SellingController): ): throw(_("Delivery Note {0} is not submitted").format(d.delivery_note)) + def process_asset_depreciation(self): + if (self.is_return and self.docstatus == 2) or (not self.is_return and self.docstatus == 1): + self.depreciate_asset_on_sale() + else: + self.restore_asset() + + self.update_asset() + + def depreciate_asset_on_sale(self): + """ + Depreciate asset on sale or cancellation of return sales invoice + """ + disposal_date = self.get_disposal_date() + for d in self.get("items"): + if d.asset: + asset = frappe.get_doc("Asset", d.asset) + if asset.calculate_depreciation and asset.status != "Fully Depreciated": + depreciate_asset(asset, disposal_date, self.get_note_for_asset_sale(asset)) + + def get_note_for_asset_sale(self, asset): + return _("This schedule was created when Asset {0} was {1} through Sales Invoice {2}.").format( + get_link_to_form(asset.doctype, asset.name), + _("returned") if self.is_return else _("sold"), + get_link_to_form(self.doctype, self.get("name")), + ) + + def restore_asset(self): + """ + Restore asset on return or cancellation of original sales invoice + """ + + for d in self.get("items"): + if d.asset: + asset = frappe.get_cached_doc("Asset", d.asset) + if asset.calculate_depreciation: + reverse_depreciation_entry_made_on_disposal(asset) + + note = self.get_note_for_asset_return(asset) + reset_depreciation_schedule(asset, note) + + def get_note_for_asset_return(self, asset): + asset_link = get_link_to_form(asset.doctype, asset.name) + invoice_link = get_link_to_form(self.doctype, self.get("name")) + if self.is_return: + return _( + "This schedule was created when Asset {0} was returned through Sales Invoice {1}." + ).format(asset_link, invoice_link) + else: + return _( + "This schedule was created when Asset {0} was restored due to Sales Invoice {1} cancellation." + ).format(asset_link, invoice_link) + + def update_asset(self): + """ + Update asset status, disposal date and asset activity on sale or return sales invoice + """ + + def _update_asset(asset, disposal_date, note, asset_status=None): + frappe.db.set_value("Asset", d.asset, "disposal_date", disposal_date) + add_asset_activity(asset.name, note) + asset.set_status(asset_status) + + disposal_date = self.get_disposal_date() + for d in self.get("items"): + if d.asset: + asset = frappe.get_cached_doc("Asset", d.asset) + + if (self.is_return and self.docstatus == 1) or (not self.is_return and self.docstatus == 2): + note = _("Asset returned") if self.is_return else _("Asset sold") + asset_status, disposal_date = None, None + else: + note = _("Asset sold") if not self.is_return else _("Return invoice of asset cancelled") + asset_status = "Sold" + + _update_asset(asset, disposal_date, note, asset_status) + + def get_disposal_date(self): + if self.is_return: + disposal_date = frappe.db.get_value("Sales Invoice", self.return_against, "posting_date") + else: + disposal_date = self.posting_date + + return disposal_date + def make_gl_entries(self, gl_entries=None, from_repost=False): from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries @@ -1426,64 +1527,8 @@ class SalesInvoice(SellingController): if self.is_internal_transfer(): continue - if item.is_fixed_asset: - asset = self.get_asset(item) - - if self.is_return: - fixed_asset_gl_entries = get_gl_entries_on_asset_regain( - asset, - item.base_net_amount, - item.finance_book, - self.get("doctype"), - self.get("name"), - self.get("posting_date"), - ) - asset.db_set("disposal_date", None) - add_asset_activity(asset.name, _("Asset returned")) - - if asset.calculate_depreciation: - posting_date = frappe.db.get_value( - "Sales Invoice", self.return_against, "posting_date" - ) - reverse_depreciation_entry_made_after_disposal(asset, posting_date) - notes = _( - "This schedule was created when Asset {0} was returned through Sales Invoice {1}." - ).format( - get_link_to_form(asset.doctype, asset.name), - get_link_to_form(self.doctype, self.get("name")), - ) - reset_depreciation_schedule(asset, self.posting_date, notes) - asset.reload() - - else: - if asset.calculate_depreciation: - if not asset.status == "Fully Depreciated": - notes = _( - "This schedule was created when Asset {0} was sold through Sales Invoice {1}." - ).format( - get_link_to_form(asset.doctype, asset.name), - get_link_to_form(self.doctype, self.get("name")), - ) - depreciate_asset(asset, self.posting_date, notes) - asset.reload() - - fixed_asset_gl_entries = get_gl_entries_on_asset_disposal( - asset, - item.base_net_amount, - item.finance_book, - self.get("doctype"), - self.get("name"), - self.get("posting_date"), - ) - asset.db_set("disposal_date", self.posting_date) - add_asset_activity(asset.name, _("Asset sold")) - - for gle in fixed_asset_gl_entries: - gle["against"] = self.customer - gl_entries.append(self.get_gl_dict(gle, item=item)) - - self.set_asset_status(asset) - + if item.is_fixed_asset and item.asset: + self.get_gl_entries_for_fixed_asset(item, gl_entries) else: income_account = ( item.income_account @@ -1518,17 +1563,31 @@ class SalesInvoice(SellingController): if cint(self.update_stock) and erpnext.is_perpetual_inventory_enabled(self.company): gl_entries += super().get_gl_entries() - def get_asset(self, item): - if item.get("asset"): - asset = frappe.get_doc("Asset", item.asset) + def get_gl_entries_for_fixed_asset(self, item, gl_entries): + asset = frappe.get_cached_doc("Asset", item.asset) + + if self.is_return: + fixed_asset_gl_entries = get_gl_entries_on_asset_regain( + asset, + item.base_net_amount, + item.finance_book, + self.get("doctype"), + self.get("name"), + self.get("posting_date"), + ) else: - frappe.throw( - _("Row #{0}: You must select an Asset for Item {1}.").format(item.idx, item.item_name), - title=_("Missing Asset"), + fixed_asset_gl_entries = get_gl_entries_on_asset_disposal( + asset, + item.base_net_amount, + item.finance_book, + self.get("doctype"), + self.get("name"), + self.get("posting_date"), ) - self.check_finance_books(item, asset) - return asset + for gle in fixed_asset_gl_entries: + gle["against"] = self.customer + gl_entries.append(self.get_gl_dict(gle, item=item)) @property def enable_discount_accounting(self): @@ -1539,12 +1598,6 @@ class SalesInvoice(SellingController): return self._enable_discount_accounting - def set_asset_status(self, asset): - if self.is_return: - asset.set_status() - else: - asset.set_status("Sold" if self.docstatus == 1 else None) - def make_loyalty_point_redemption_gle(self, gl_entries): if cint(self.redeem_loyalty_points and self.loyalty_points and not self.is_consolidated): gl_entries.append( diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index 93c0fea0c4a..2e7f8868dcb 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -103,14 +103,26 @@ frappe.ui.form.on("Asset", { }, __("Manage") ); - } else if (frm.doc.status == "Scrapped") { + frm.add_custom_button( - __("Restore Asset"), + __("Repair Asset"), function () { - erpnext.asset.restore_asset(frm); + frm.trigger("create_asset_repair"); }, __("Manage") ); + + frm.add_custom_button( + __("Split Asset"), + function () { + frm.trigger("split_asset"); + }, + __("Manage") + ); + } else if (frm.doc.status == "Scrapped") { + frm.add_custom_button(__("Restore Asset"), function () { + erpnext.asset.restore_asset(frm); + }).addClass("btn-primary"); } if (frm.doc.maintenance_required && !frm.doc.maintenance_schedule) { @@ -123,23 +135,7 @@ frappe.ui.form.on("Asset", { ); } - frm.add_custom_button( - __("Repair Asset"), - function () { - frm.trigger("create_asset_repair"); - }, - __("Manage") - ); - - frm.add_custom_button( - __("Split Asset"), - function () { - frm.trigger("split_asset"); - }, - __("Manage") - ); - - if (frm.doc.status != "Fully Depreciated") { + if (in_list(["Submitted", "Partially Depreciated"], frm.doc.status)) { frm.add_custom_button( __("Adjust Asset Value"), function () { diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index a65246ee820..a5fd70402c4 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -33,8 +33,6 @@ from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_sched convert_draft_asset_depr_schedules_into_active, get_asset_depr_schedule_doc, get_depr_schedule, - make_draft_asset_depr_schedules, - update_draft_asset_depr_schedules, ) from erpnext.controllers.accounts_controller import AccountsController @@ -148,22 +146,23 @@ class Asset(AccountsController): schedule_doc = get_asset_depr_schedule_doc(self.name, "Draft", row.finance_book) if not schedule_doc: schedule_doc = frappe.new_doc("Asset Depreciation Schedule") - - schedule_doc.prepare_draft_asset_depr_schedule_data(self, row) + schedule_doc.asset = self.name + schedule_doc.create_depreciation_schedule(row) schedule_doc.save() schedules.append(schedule_doc.name) self.show_schedule_creation_message(schedules) def set_depr_rate_and_value_after_depreciation(self): + self.value_after_depreciation = flt(self.gross_purchase_amount) - flt( + self.opening_accumulated_depreciation + ) if self.calculate_depreciation: - self.value_after_depreciation = 0 self.set_depreciation_rate() + for d in self.finance_books: + d.value_after_depreciation = self.value_after_depreciation else: self.finance_books = [] - self.value_after_depreciation = flt(self.gross_purchase_amount) - flt( - self.opening_accumulated_depreciation - ) def show_schedule_creation_message(self, schedules): if schedules: @@ -845,41 +844,31 @@ class Asset(AccountsController): ) def get_written_down_value_rate(self, args, rate_field_precision, on_validate): - if ( - args.get("rate_of_depreciation") - and on_validate - and not self.flags.increase_in_asset_value_due_to_repair - ): + if args.get("rate_of_depreciation") and on_validate: return args.get("rate_of_depreciation") if args.get("rate_of_depreciation") and not flt(args.get("expected_value_after_useful_life")): return args.get("rate_of_depreciation") - if self.flags.increase_in_asset_value_due_to_repair: - value = flt(args.get("expected_value_after_useful_life")) / flt( - args.get("value_after_depreciation") - ) + if flt(args.get("value_after_depreciation")): + current_asset_value = flt(args.get("value_after_depreciation")) else: - value = flt(args.get("expected_value_after_useful_life")) / ( - flt(self.gross_purchase_amount) - flt(self.opening_accumulated_depreciation) - ) + current_asset_value = flt(self.gross_purchase_amount) - flt(self.opening_accumulated_depreciation) - depreciation_rate = math.pow( - value, - 1.0 - / ( - ( - ( - flt(args.get("total_number_of_depreciations"), 2) - - flt(self.opening_number_of_booked_depreciations) - ) - * flt(args.get("frequency_of_depreciation")) - ) - / 12 - ), + value = flt(args.get("expected_value_after_useful_life")) / current_asset_value + + pending_number_of_depreciations = ( + flt(args.get("total_number_of_depreciations"), 2) + - flt(self.opening_number_of_booked_depreciations) + - flt(args.get("total_number_of_booked_depreciations")) ) + pending_years = ( + pending_number_of_depreciations * flt(args.get("frequency_of_depreciation")) + + cint(args.get("increase_in_asset_life")) + ) / 12 - return flt((100 * (1 - depreciation_rate)), rate_field_precision) + depreciation_rate = 100 * (1 - math.pow(value, 1.0 / pending_years)) + return flt(depreciation_rate, rate_field_precision) def has_gl_entries(doctype, docname, target_account): @@ -1253,7 +1242,7 @@ def update_existing_asset(asset, remaining_qty, new_asset_name): current_asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active", row.finance_book) new_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc) - new_asset_depr_schedule_doc.set_draft_asset_depr_schedule_details(asset, row) + new_asset_depr_schedule_doc.fetch_asset_details(asset, row) accumulated_depreciation = 0 @@ -1310,7 +1299,7 @@ def create_new_asset_after_split(asset, split_qty): continue new_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc) - new_asset_depr_schedule_doc.set_draft_asset_depr_schedule_details(new_asset, row) + new_asset_depr_schedule_doc.fetch_asset_details(new_asset, row) accumulated_depreciation = 0 diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index 979c49a93eb..de7d14cb8a1 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -29,7 +29,7 @@ from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_sched get_asset_depr_schedule_doc, get_asset_depr_schedule_name, get_temp_asset_depr_schedule_doc, - make_new_active_asset_depr_schedules_and_cancel_current_ones, + reschedule_depreciation, ) @@ -136,10 +136,10 @@ def get_depreciable_asset_depr_schedules_data(date): return res -def make_depreciation_entry_for_all_asset_depr_schedules(asset_doc, date=None): +def make_depreciation_entry_on_disposal(asset_doc, disposal_date=None): for row in asset_doc.get("finance_books"): asset_depr_schedule_name = get_asset_depr_schedule_name(asset_doc.name, "Active", row.finance_book) - make_depreciation_entry(asset_depr_schedule_name, date) + make_depreciation_entry(asset_depr_schedule_name, disposal_date) def get_acc_frozen_upto(): @@ -244,10 +244,12 @@ def make_depreciation_entry( except Exception as e: depreciation_posting_error = e + asset.reload() asset.set_status() if not depreciation_posting_error: asset.db_set("depr_entry_posting_status", "Successful") + asset_depr_schedule_doc.reload() return asset_depr_schedule_doc raise depreciation_posting_error @@ -316,18 +318,10 @@ def _make_journal_entry_for_depreciation( je.append("accounts", debit_entry) je.flags.ignore_permissions = True - je.flags.planned_depr_entry = True je.save() - depr_schedule.db_set("journal_entry", je.name) - if not je.meta.get_workflow(): je.submit() - asset.reload() - idx = cint(asset_depr_schedule_doc.finance_book_id) - row = asset.get("finance_books")[idx - 1] - row.value_after_depreciation -= depr_schedule.depreciation_amount - row.db_update() def get_depreciation_accounts(asset_category, company): @@ -433,194 +427,162 @@ def get_comma_separated_links(names, doctype): @frappe.whitelist() def scrap_asset(asset_name, scrap_date=None): asset = frappe.get_doc("Asset", asset_name) + scrap_date = getdate(scrap_date) or getdate(today()) + asset.db_set("disposal_date", scrap_date) + validate_asset_for_scrap(asset, scrap_date) + depreciate_asset(asset, scrap_date, get_note_for_scrap(asset)) + asset.reload() + + create_journal_entry_for_scrap(asset, scrap_date) + + +def validate_asset_for_scrap(asset, scrap_date): if asset.docstatus != 1: frappe.throw(_("Asset {0} must be submitted").format(asset.name)) elif asset.status in ("Cancelled", "Sold", "Scrapped", "Capitalized"): frappe.throw(_("Asset {0} cannot be scrapped, as it is already {1}").format(asset.name, asset.status)) - today_date = getdate(today()) - date = getdate(scrap_date) or today_date - purchase_date = getdate(asset.purchase_date) + validate_scrap_date(asset, scrap_date) - validate_scrap_date(date, today_date, purchase_date, asset.calculate_depreciation, asset_name) - notes = _("This schedule was created when Asset {0} was scrapped.").format( +def validate_scrap_date(asset, scrap_date): + if scrap_date > getdate(): + frappe.throw(_("Future date is not allowed")) + elif scrap_date < getdate(asset.purchase_date): + frappe.throw(_("Scrap date cannot be before purchase date")) + + if asset.calculate_depreciation: + last_booked_depreciation_date = get_last_depreciation_date(asset.name) + if ( + last_booked_depreciation_date + and scrap_date < last_booked_depreciation_date + and scrap_date > getdate(asset.purchase_date) + ): + frappe.throw(_("Asset cannot be scrapped before the last depreciation entry.")) + + +def get_last_depreciation_date(asset_name): + depreciation = frappe.qb.DocType("Asset Depreciation Schedule") + depreciation_schedule = frappe.qb.DocType("Depreciation Schedule") + + last_depreciation_date = ( + frappe.qb.from_(depreciation) + .join(depreciation_schedule) + .on(depreciation.name == depreciation_schedule.parent) + .select(depreciation_schedule.schedule_date) + .where(depreciation.asset == asset_name) + .where(depreciation.docstatus == 1) + .where(depreciation_schedule.journal_entry != "") + .orderby(depreciation_schedule.schedule_date, order=Order.desc) + .limit(1) + .run() + ) + + return last_depreciation_date[0][0] if last_depreciation_date else None + + +def get_note_for_scrap(asset): + return _("This schedule was created when Asset {0} was scrapped.").format( get_link_to_form(asset.doctype, asset.name) ) - if asset.status != "Fully Depreciated": - depreciate_asset(asset, date, notes) - asset.reload() + +def create_journal_entry_for_scrap(asset, scrap_date): depreciation_series = frappe.get_cached_value("Company", asset.company, "series_for_depreciation_entry") je = frappe.new_doc("Journal Entry") - je.voucher_type = "Journal Entry" + je.voucher_type = "Asset Disposal" je.naming_series = depreciation_series - je.posting_date = date + je.posting_date = scrap_date je.company = asset.company - je.remark = f"Scrap Entry for asset {asset_name}" + je.remark = f"Scrap Entry for asset {asset.name}" - for entry in get_gl_entries_on_asset_disposal(asset, date): - entry.update({"reference_type": "Asset", "reference_name": asset_name}) + for entry in get_gl_entries_on_asset_disposal(asset, scrap_date): + entry.update({"reference_type": "Asset", "reference_name": asset.name}) je.append("accounts", entry) je.flags.ignore_permissions = True - je.submit() + je.save() + if not je.meta.get_workflow(): + je.submit() - frappe.db.set_value("Asset", asset_name, "disposal_date", date) - frappe.db.set_value("Asset", asset_name, "journal_entry_for_scrap", je.name) - asset.set_status("Scrapped") - - add_asset_activity(asset_name, _("Asset scrapped")) - - frappe.msgprint(_("Asset scrapped via Journal Entry {0}").format(je.name)) - - -def validate_scrap_date(scrap_date, today_date, purchase_date, calculate_depreciation, asset_name): - if scrap_date > today_date: - frappe.throw(_("Future date is not allowed")) - elif scrap_date < purchase_date: - frappe.throw(_("Scrap date cannot be before purchase date")) - - if calculate_depreciation: - asset_depreciation_schedules = frappe.db.get_all( - "Asset Depreciation Schedule", filters={"asset": asset_name, "docstatus": 1}, fields=["name"] - ) - - for depreciation_schedule in asset_depreciation_schedules: - last_booked_depreciation_date = frappe.db.get_value( - "Depreciation Schedule", - { - "parent": depreciation_schedule["name"], - "docstatus": 1, - "journal_entry": ["!=", ""], - }, - "schedule_date", - order_by="schedule_date desc", - ) - if ( - last_booked_depreciation_date - and scrap_date < last_booked_depreciation_date - and scrap_date > purchase_date - ): - frappe.throw(_("Asset cannot be scrapped before the last depreciation entry.")) + add_asset_activity(asset.name, _("Asset scrapped")) + frappe.msgprint( + _("Asset scrapped via Journal Entry {0}").format(get_link_to_form("Journal Entry", je.name)) + ) @frappe.whitelist() def restore_asset(asset_name): asset = frappe.get_doc("Asset", asset_name) + reverse_depreciation_entry_made_on_disposal(asset) + reset_depreciation_schedule(asset, get_note_for_restore(asset)) + cancel_journal_entry_for_scrap(asset) + asset.set_status() + add_asset_activity(asset_name, _("Asset restored")) - reverse_depreciation_entry_made_after_disposal(asset, asset.disposal_date) - je = asset.journal_entry_for_scrap - - notes = _("This schedule was created when Asset {0} was restored.").format( +def get_note_for_restore(asset): + return _("This schedule was created when Asset {0} was restored.").format( get_link_to_form(asset.doctype, asset.name) ) - reset_depreciation_schedule(asset, asset.disposal_date, notes) - asset.db_set("disposal_date", None) - asset.db_set("journal_entry_for_scrap", None) - - frappe.get_doc("Journal Entry", je).cancel() - - asset.set_status() - - add_asset_activity(asset_name, _("Asset restored")) +def cancel_journal_entry_for_scrap(asset): + if asset.journal_entry_for_scrap: + je = asset.journal_entry_for_scrap + asset.db_set("disposal_date", None) + asset.db_set("journal_entry_for_scrap", None) + frappe.get_doc("Journal Entry", je).cancel() def depreciate_asset(asset_doc, date, notes): if not asset_doc.calculate_depreciation: return - asset_doc.flags.ignore_validate_update_after_submit = True - - make_new_active_asset_depr_schedules_and_cancel_current_ones(asset_doc, notes, date_of_disposal=date) - - asset_doc.save() - - make_depreciation_entry_for_all_asset_depr_schedules(asset_doc, date) + reschedule_depreciation(asset_doc, notes, disposal_date=date) + make_depreciation_entry_on_disposal(asset_doc, date) + # As per Income Tax Act (India), the asset should not be depreciated + # in the financial year in which it is sold/scraped asset_doc.reload() - cancel_depreciation_entries(asset_doc, date) + # cancel_depreciation_entries(asset_doc, date) @erpnext.allow_regional def cancel_depreciation_entries(asset_doc, date): + # Cancel all depreciation entries for the current financial year + # if the asset is sold/scraped in the current financial year + # Overwritten via India Compliance app pass -def reset_depreciation_schedule(asset_doc, date, notes): - if not asset_doc.calculate_depreciation: - return - - asset_doc.flags.ignore_validate_update_after_submit = True - - make_new_active_asset_depr_schedules_and_cancel_current_ones(asset_doc, notes, date_of_return=date) - - modify_depreciation_schedule_for_asset_repairs(asset_doc, notes) - - asset_doc.save() +def reset_depreciation_schedule(asset_doc, notes): + if asset_doc.calculate_depreciation: + reschedule_depreciation(asset_doc, notes) -def modify_depreciation_schedule_for_asset_repairs(asset, notes): - asset_repairs = frappe.get_all( - "Asset Repair", filters={"asset": asset.name}, fields=["name", "increase_in_asset_life"] - ) - - for repair in asset_repairs: - if repair.increase_in_asset_life: - asset_repair = frappe.get_doc("Asset Repair", repair.name) - asset_repair.modify_depreciation_schedule() - make_new_active_asset_depr_schedules_and_cancel_current_ones(asset, notes) - - -def reverse_depreciation_entry_made_after_disposal(asset, date): +def reverse_depreciation_entry_made_on_disposal(asset): for row in asset.get("finance_books"): - asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active", row.finance_book) - if not asset_depr_schedule_doc or not asset_depr_schedule_doc.get("depreciation_schedule"): + schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active", row.finance_book) + if not schedule_doc or not schedule_doc.get("depreciation_schedule"): continue - for schedule_idx, schedule in enumerate(asset_depr_schedule_doc.get("depreciation_schedule")): - if schedule.schedule_date == date and schedule.journal_entry: + for schedule_idx, schedule in enumerate(schedule_doc.get("depreciation_schedule")): + if schedule.schedule_date == asset.disposal_date and schedule.journal_entry: if not disposal_was_made_on_original_schedule_date( - schedule_idx, row, date - ) or disposal_happens_in_the_future(date): - reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry) - reverse_journal_entry.posting_date = nowdate() - - for account in reverse_journal_entry.accounts: - account.update( - { - "reference_type": "Asset", - "reference_name": asset.name, - } - ) - - frappe.flags.is_reverse_depr_entry = True - reverse_journal_entry.submit() - - frappe.flags.is_reverse_depr_entry = False - asset_depr_schedule_doc.flags.ignore_validate_update_after_submit = True - asset.flags.ignore_validate_update_after_submit = True - schedule.journal_entry = None - depreciation_amount = get_depreciation_amount_in_je(reverse_journal_entry) - row.value_after_depreciation += depreciation_amount - asset_depr_schedule_doc.save() - asset.save() + schedule_idx, row, asset.disposal_date + ) or disposal_happens_in_the_future(asset.disposal_date): + je = create_reverse_depreciation_entry(asset.name, schedule.journal_entry) + update_value_after_depreciation_on_asset_restore(schedule, row, je) -def get_depreciation_amount_in_je(journal_entry): - if journal_entry.accounts[0].debit_in_account_currency: - return journal_entry.accounts[0].debit_in_account_currency - else: - return journal_entry.accounts[0].credit_in_account_currency - - -# if the invoice had been posted on the date the depreciation was initially supposed to happen, the depreciation shouldn't be undone -def disposal_was_made_on_original_schedule_date(schedule_idx, row, posting_date_of_disposal): +def disposal_was_made_on_original_schedule_date(schedule_idx, row, disposal_date): + """ + If asset is scrapped or sold on original schedule date, + then the depreciation entry should not be reversed. + """ orginal_schedule_date = add_months( row.depreciation_start_date, schedule_idx * cint(row.frequency_of_depreciation) ) @@ -628,19 +590,57 @@ def disposal_was_made_on_original_schedule_date(schedule_idx, row, posting_date_ if is_last_day_of_the_month(row.depreciation_start_date): orginal_schedule_date = get_last_day(orginal_schedule_date) - if orginal_schedule_date == posting_date_of_disposal: + if orginal_schedule_date == disposal_date: return True return False -def disposal_happens_in_the_future(posting_date_of_disposal): - if posting_date_of_disposal > getdate(): +def disposal_happens_in_the_future(disposal_date): + if disposal_date > getdate(): return True return False +def create_reverse_depreciation_entry(asset_name, journal_entry): + reverse_journal_entry = make_reverse_journal_entry(journal_entry) + reverse_journal_entry.posting_date = nowdate() + + for account in reverse_journal_entry.accounts: + account.update( + { + "reference_type": "Asset", + "reference_name": asset_name, + } + ) + + frappe.flags.is_reverse_depr_entry = True + if not reverse_journal_entry.meta.get_workflow(): + reverse_journal_entry.submit() + return reverse_journal_entry + else: + frappe.throw( + _("Please disable workflow temporarily for Journal Entry {0}").format(reverse_journal_entry.name) + ) + + +def update_value_after_depreciation_on_asset_restore(schedule, row, journal_entry): + frappe.db.set_value("Depreciation Schedule", schedule.name, "journal_entry", None, update_modified=False) + depreciation_amount = get_depreciation_amount_in_je(journal_entry) + value_after_depreciation = flt( + row.value_after_depreciation + depreciation_amount, row.precision("value_after_depreciation") + ) + row.db_set("value_after_depreciation", value_after_depreciation) + + +def get_depreciation_amount_in_je(journal_entry): + if journal_entry.accounts[0].debit_in_account_currency: + return journal_entry.accounts[0].debit_in_account_currency + else: + return journal_entry.accounts[0].credit_in_account_currency + + def get_gl_entries_on_asset_regain( asset, selling_amount=0, finance_book=None, voucher_type=None, voucher_no=None, date=None ): diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index be8ce0ff5fc..7162e8f5a1e 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -35,7 +35,7 @@ from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_sched get_asset_depr_schedule_doc, get_depr_schedule, ) -from erpnext.assets.doctype.asset_depreciation_schedule.utils import ( +from erpnext.erpnext.assets.doctype.asset_depreciation_schedule.deppreciation_schedule_controller import ( get_depreciation_amount, ) from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py index e01c722b0d4..449fffc0684 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -16,7 +16,7 @@ from erpnext.assets.doctype.asset.depreciation import ( get_gl_entries_on_asset_disposal, get_value_after_depreciation_on_disposal_date, reset_depreciation_schedule, - reverse_depreciation_entry_made_after_disposal, + reverse_depreciation_entry_made_on_disposal, ) from erpnext.assets.doctype.asset_activity.asset_activity import add_asset_activity from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account @@ -623,13 +623,13 @@ class AssetCapitalization(StockController): self.set_consumed_asset_status(asset) if asset.calculate_depreciation: - reverse_depreciation_entry_made_after_disposal(asset, self.posting_date) + reverse_depreciation_entry_made_on_disposal(asset, self.posting_date) notes = _( "This schedule was created when Asset {0} was restored on Asset Capitalization {1}'s cancellation." ).format( get_link_to_form(asset.doctype, asset.name), get_link_to_form(self.doctype, self.name) ) - reset_depreciation_schedule(asset, self.posting_date, notes) + reset_depreciation_schedule(asset, notes) def set_consumed_asset_status(self, asset): if self.docstatus == 1: 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 4566f52b20a..c75e08bbb87 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py @@ -3,7 +3,6 @@ import frappe from frappe import _ -from frappe.model.document import Document from frappe.utils import ( add_days, add_months, @@ -20,13 +19,12 @@ from frappe.utils import ( ) import erpnext -from erpnext.accounts.utils import get_fiscal_year -from erpnext.assets.doctype.asset_depreciation_schedule.utils import ( - get_depreciation_amount, +from erpnext.assets.doctype.asset_depreciation_schedule.deppreciation_schedule_controller import ( + DepreciationScheduleController, ) -class AssetDepreciationSchedule(Document): +class AssetDepreciationSchedule(DepreciationScheduleController): # begin: auto-generated types # This code is auto-generated. Do not modify anything in this block. @@ -61,15 +59,11 @@ class AssetDepreciationSchedule(Document): value_after_depreciation: DF.Currency # end: auto-generated types - def before_save(self): - if not self.finance_book_id: - self.prepare_draft_asset_depr_schedule_data_from_asset_name_and_fb_name( - self.asset, self.finance_book - ) - self.update_shift_depr_schedule() - def validate(self): self.validate_another_asset_depr_schedule_does_not_exist() + if not self.finance_book_id: + self.create_depreciation_schedule() + self.update_shift_depr_schedule() def validate_another_asset_depr_schedule_does_not_exist(self): finance_book_filter = ["finance_book", "is", "not set"] @@ -102,7 +96,8 @@ class AssetDepreciationSchedule(Document): def on_submit(self): self.db_set("status", "Active") - def before_cancel(self): + def on_cancel(self): + self.db_set("status", "Cancelled") if not self.flags.should_not_cancel_depreciation_entries: self.cancel_depreciation_entries() @@ -111,9 +106,6 @@ class AssetDepreciationSchedule(Document): if d.journal_entry: frappe.get_doc("Journal Entry", d.journal_entry).cancel() - def on_cancel(self): - self.db_set("status", "Cancelled") - def update_shift_depr_schedule(self): if not self.shift_based or self.docstatus != 0: return @@ -124,594 +116,50 @@ class AssetDepreciationSchedule(Document): self.make_depr_schedule(asset_doc, fb_row) self.set_accumulated_depreciation(asset_doc, fb_row) - def prepare_draft_asset_depr_schedule_data_from_asset_name_and_fb_name(self, asset_name, fb_name): - asset_doc = frappe.get_doc("Asset", asset_name) + def get_finance_book_row(self, fb_row=None): + if fb_row: + self.fb_row = fb_row + return finance_book_filter = ["finance_book", "is", "not set"] - if fb_name: - finance_book_filter = ["finance_book", "=", fb_name] + if self.finance_book: + finance_book_filter = ["finance_book", "=", self.finance_book] asset_finance_book_name = frappe.db.get_value( doctype="Asset Finance Book", - filters=[["parent", "=", asset_name], finance_book_filter], + filters=[["parent", "=", self.asset], finance_book_filter], ) - asset_finance_book_doc = frappe.get_doc("Asset Finance Book", asset_finance_book_name) + self.fb_row = frappe.get_doc("Asset Finance Book", asset_finance_book_name) - self.prepare_draft_asset_depr_schedule_data(asset_doc, asset_finance_book_doc) - - def prepare_draft_asset_depr_schedule_data( - self, - asset_doc, - row, - date_of_disposal=None, - date_of_return=None, - update_asset_finance_book_row=True, - ): - self.set_draft_asset_depr_schedule_details(asset_doc, row) - self.make_depr_schedule(asset_doc, row, date_of_disposal, update_asset_finance_book_row) - self.set_accumulated_depreciation(asset_doc, row, date_of_disposal, date_of_return) - - def set_draft_asset_depr_schedule_details(self, asset_doc, row): - self.asset = asset_doc.name - self.finance_book = row.finance_book - self.finance_book_id = row.idx - self.opening_accumulated_depreciation = asset_doc.opening_accumulated_depreciation or 0 - self.opening_number_of_booked_depreciations = asset_doc.opening_number_of_booked_depreciations or 0 - self.gross_purchase_amount = asset_doc.gross_purchase_amount - self.depreciation_method = row.depreciation_method - self.total_number_of_depreciations = row.total_number_of_depreciations - self.frequency_of_depreciation = row.frequency_of_depreciation - self.rate_of_depreciation = row.rate_of_depreciation - self.expected_value_after_useful_life = row.expected_value_after_useful_life - self.daily_prorata_based = row.daily_prorata_based - self.shift_based = row.shift_based + def fetch_asset_details(self): + self.asset = self.asset_doc.name + self.finance_book = self.fb_row.get("finance_book") + self.finance_book_id = self.fb_row.idx + self.opening_accumulated_depreciation = self.asset_doc.opening_accumulated_depreciation or 0 + self.opening_number_of_booked_depreciations = ( + self.asset_doc.opening_number_of_booked_depreciations or 0 + ) + self.gross_purchase_amount = self.asset_doc.gross_purchase_amount + self.depreciation_method = self.fb_row.depreciation_method + self.total_number_of_depreciations = self.fb_row.total_number_of_depreciations + self.frequency_of_depreciation = self.fb_row.frequency_of_depreciation + self.rate_of_depreciation = self.fb_row.get("rate_of_depreciation") + self.expected_value_after_useful_life = self.fb_row.get("expected_value_after_useful_life") + self.daily_prorata_based = self.fb_row.get("daily_prorata_based") + self.shift_based = self.fb_row.get("shift_based") self.status = "Draft" - def make_depr_schedule( - self, - asset_doc, - row, - date_of_disposal=None, - update_asset_finance_book_row=True, - value_after_depreciation=None, - ): - start = self.clear_depr_schedule() - - self._make_depr_schedule( - asset_doc, row, start, date_of_disposal, update_asset_finance_book_row, value_after_depreciation - ) - - def clear_depr_schedule(self): - """ - Clears the depreciation schedule preserving the depreciation entries that have been booked. - """ - start = 0 - num_of_depreciations_completed = 0 - depr_schedule = [] - - self.schedules_before_clearing = self.get("depreciation_schedule") - - for schedule in self.get("depreciation_schedule"): - if schedule.journal_entry: - num_of_depreciations_completed += 1 - depr_schedule.append(schedule) - else: - start = num_of_depreciations_completed - break - - self.depreciation_schedule = depr_schedule - - return start - - def _make_depr_schedule( - self, - asset_doc, - row, - start, - date_of_disposal, - update_asset_finance_book_row, - value_after_depreciation, - ): - row, value_after_depreciation = self.get_value_after_depreciation( - asset_doc, row, value_after_depreciation, update_asset_finance_book_row - ) - final_number_of_depreciations, has_pro_rata = self.get_final_number_of_depreciations(asset_doc, row) - has_wdv_or_dd_non_yearly_pro_rata = self.is_wdv_or_dd_non_yearly_pro_rata(asset_doc, row) - should_get_last_day = is_last_day_of_the_month(row.depreciation_start_date) - - skip_row = False - depreciation_amount = 0 - prev_per_day_depr = True - self.current_fiscal_year_end_date = None - yearly_opening_wdv = value_after_depreciation - pending_months = self.get_number_of_pending_months(asset_doc, row, start) - - for n in range(start, final_number_of_depreciations): - # If depreciation is already completed (for double declining balance) - if skip_row: - continue - - if self.has_fiscal_year_changed(row, n): - yearly_opening_wdv = value_after_depreciation - - prev_depreciation_amount = self.get_prev_depreciation_amount(n) - - depreciation_amount, prev_per_day_depr = get_depreciation_amount( - self, - asset_doc, - value_after_depreciation, - yearly_opening_wdv, - row, - n, - prev_depreciation_amount, - has_wdv_or_dd_non_yearly_pro_rata, - pending_months, - prev_per_day_depr, - ) - - schedule_date = self.get_next_schedule_date( - row, n, has_pro_rata, should_get_last_day, final_number_of_depreciations - ) - - # if asset is being sold or scrapped - if date_of_disposal and getdate(schedule_date) >= getdate(date_of_disposal): - self.get_depreciation_amount_for_disposal( - asset_doc, row, n, schedule_date, date_of_disposal, depreciation_amount - ) - break - - if n == 0: - # Get pro rata amount for first row if available for use date is mid of the month - depreciation_amount = self.get_depreciation_amount_for_first_row( - asset_doc, row, n, depreciation_amount, has_pro_rata, has_wdv_or_dd_non_yearly_pro_rata - ) - elif has_pro_rata and n == cint(final_number_of_depreciations) - 1: # for the last row - depreciation_amount, schedule_date = self.get_depreciation_amount_for_last_row( - asset_doc, row, n, depreciation_amount, schedule_date, has_wdv_or_dd_non_yearly_pro_rata - ) - - if not depreciation_amount: - break - - - value_after_depreciation = flt( - value_after_depreciation - flt(depreciation_amount, asset_doc.precision("gross_purchase_amount")), - asset_doc.precision("gross_purchase_amount"), - ) - - # Adjust depreciation amount in the last period based on the expected value after useful life - depreciation_amount, skip_row = self.adjust_depr_amount_for_salvage_value( - row, depreciation_amount, value_after_depreciation, final_number_of_depreciations, n, skip_row - ) - - if flt(depreciation_amount, asset_doc.precision("gross_purchase_amount")) > 0: - self.add_depr_schedule_row(schedule_date, depreciation_amount, n) - - def get_value_after_depreciation( - self, asset_doc, row, value_after_depreciation, update_asset_finance_book_row - ): - if not value_after_depreciation: - value_after_depreciation = _get_value_after_depreciation_for_making_schedule(asset_doc, row) - row.value_after_depreciation = value_after_depreciation - - if update_asset_finance_book_row: - row.db_update() - - return row, value_after_depreciation - - def get_final_number_of_depreciations(self, asset_doc, row): - final_number_of_depreciations = cint(row.total_number_of_depreciations) - cint( - self.opening_number_of_booked_depreciations - ) - - has_pro_rata = _check_is_pro_rata(asset_doc, row) - if has_pro_rata: - final_number_of_depreciations += 1 - - if row.increase_in_asset_life: - final_number_of_depreciations = ( - self.get_final_number_of_depreciations_considering_increase_in_asset_life( - asset_doc, row, final_number_of_depreciations - ) - ) - - return final_number_of_depreciations, has_pro_rata - - def get_final_number_of_depreciations_considering_increase_in_asset_life( - self, asset_doc, row, final_number_of_depreciations - ): - # final schedule date after increasing asset life - self.final_schedule_date = add_months( - asset_doc.available_for_use_date, - (row.total_number_of_depreciations * cint(row.frequency_of_depreciation)) - + row.increase_in_asset_life, - ) - - number_of_pending_depreciations = cint(row.total_number_of_depreciations) - cint( - asset_doc.opening_number_of_booked_depreciations - ) - schedule_date = add_months( - row.depreciation_start_date, - number_of_pending_depreciations * cint(row.frequency_of_depreciation), - ) - - if self.final_schedule_date > schedule_date: - final_number_of_depreciations += 1 - - return final_number_of_depreciations - - def is_wdv_or_dd_non_yearly_pro_rata(self, asset_doc, row): - has_wdv_or_dd_non_yearly_pro_rata = False - if ( - row.depreciation_method in ("Written Down Value", "Double Declining Balance") - and cint(row.frequency_of_depreciation) != 12 - ): - has_wdv_or_dd_non_yearly_pro_rata = _check_is_pro_rata(asset_doc, row, wdv_or_dd_non_yearly=True) - - return has_wdv_or_dd_non_yearly_pro_rata - - def get_number_of_pending_months(self, asset_doc, row, start): - total_months = cint(row.total_number_of_depreciations) * cint(row.frequency_of_depreciation) + cint( - row.increase_in_asset_life - ) - depr_booked_for_months = 0 - last_depr_date = None - if start > 0: - last_depr_date = self.depreciation_schedule[start - 1].schedule_date - elif asset_doc.opening_number_of_booked_depreciations > 0: - last_depr_date = add_months(row.depreciation_start_date, -1 * row.frequency_of_depreciation) - - if last_depr_date: - depr_booked_for_months = date_diff(last_depr_date, asset_doc.available_for_use_date) / (365 / 12) - - return total_months - depr_booked_for_months - - def has_fiscal_year_changed(self, row, row_no): - fiscal_year_changed = False - - schedule_date = get_last_day( - add_months(row.depreciation_start_date, row_no * cint(row.frequency_of_depreciation)) - ) - - if not self.current_fiscal_year_end_date: - self.current_fiscal_year_end_date = get_fiscal_year(row.depreciation_start_date)[2] - fiscal_year_changed = True - elif getdate(schedule_date) > getdate(self.current_fiscal_year_end_date): - self.current_fiscal_year_end_date = add_years(self.current_fiscal_year_end_date, 1) - fiscal_year_changed = True - - return fiscal_year_changed - - def get_prev_depreciation_amount(self, n): - prev_depreciation_amount = 0 - if n > 0 and len(self.get("depreciation_schedule")) > n - 1: - prev_depreciation_amount = self.get("depreciation_schedule")[n - 1].depreciation_amount - - return prev_depreciation_amount - - def get_next_schedule_date( - self, row, n, has_pro_rata, should_get_last_day, final_number_of_depreciations=None - ): - schedule_date = add_months(row.depreciation_start_date, n * cint(row.frequency_of_depreciation)) - if should_get_last_day: - schedule_date = get_last_day(schedule_date) - - return schedule_date - - def get_depreciation_amount_for_disposal( - self, asset_doc, row, row_no, schedule_date, date_of_disposal, depreciation_amount - ): - if self.depreciation_schedule: # if there are already booked depreciations - from_date = add_days(self.depreciation_schedule[-1].schedule_date, 1) - else: - from_date = _get_modified_available_for_use_date_for_existing_assets(asset_doc, row) - if is_last_day_of_the_month(getdate(asset_doc.available_for_use_date)): - from_date = get_last_day(from_date) - - depreciation_amount, days, months = _get_pro_rata_amt( - row, - depreciation_amount, - from_date, - date_of_disposal, - original_schedule_date=schedule_date, - ) - - depreciation_amount = flt(depreciation_amount, asset_doc.precision("gross_purchase_amount")) - if depreciation_amount > 0: - self.add_depr_schedule_row(date_of_disposal, depreciation_amount, row_no) - - def get_adjusted_depreciation_amount( - self, depreciation_amount_without_pro_rata, depreciation_amount_for_last_row - ): - # to ensure that final accumulated depreciation amount is accurate - if not self.opening_accumulated_depreciation: - depreciation_amount_for_first_row = self.get("depreciation_schedule")[0].depreciation_amount - - if ( - depreciation_amount_for_first_row + depreciation_amount_for_last_row - != depreciation_amount_without_pro_rata - ): - depreciation_amount_for_last_row = ( - depreciation_amount_without_pro_rata - depreciation_amount_for_first_row - ) - - return depreciation_amount_for_last_row - - def get_depreciation_amount_for_first_row( - self, asset_doc, row, n, depreciation_amount, has_pro_rata, has_wdv_or_dd_non_yearly_pro_rata - ): - """ - For the first row, if available for use date is mid of the month, then pro rata amount is needed - """ - pro_rata_amount_applicable = False - if ( - (has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata) - and not self.opening_accumulated_depreciation - and not self.flags.wdv_it_act_applied - ): # if not existing asset - from_date = asset_doc.available_for_use_date - pro_rata_amount_applicable = True - elif has_wdv_or_dd_non_yearly_pro_rata and self.opening_accumulated_depreciation: # if existing asset - from_date = _get_modified_available_for_use_date_for_existing_assets(asset_doc, row) - pro_rata_amount_applicable = True - - if pro_rata_amount_applicable: - depreciation_amount, days, months = _get_pro_rata_amt( - row, - depreciation_amount, - from_date, - row.depreciation_start_date, - has_wdv_or_dd_non_yearly_pro_rata, - ) - - self.validate_depreciation_amount_for_low_value_assets(asset_doc, row, depreciation_amount) - - return depreciation_amount - - def get_depreciation_amount_for_last_row( - self, asset_doc, row, n, depreciation_amount, schedule_date, has_wdv_or_dd_non_yearly_pro_rata - ): - if not row.increase_in_asset_life: - # In case of increase_in_asset_life, the asset.to_date is already set on asset_repair submission - self.final_schedule_date = add_months( - asset_doc.available_for_use_date, - (n + self.opening_number_of_booked_depreciations) * cint(row.frequency_of_depreciation), - ) - if is_last_day_of_the_month(getdate(asset_doc.available_for_use_date)): - self.final_schedule_date = get_last_day(self.final_schedule_date) - - if self.opening_accumulated_depreciation: - depreciation_amount, days, months = _get_pro_rata_amt( - row, - depreciation_amount, - schedule_date, - self.final_schedule_date, - has_wdv_or_dd_non_yearly_pro_rata, - ) - else: - # if not existing asset, remaining amount of first row is depreciated in the last row - if not row.increase_in_asset_life: - depreciation_amount -= self.get("depreciation_schedule")[0].depreciation_amount - days = date_diff(self.final_schedule_date, schedule_date) + 1 - - schedule_date = add_days(schedule_date, days - 1) - return depreciation_amount, schedule_date - - def adjust_depr_amount_for_salvage_value( - self, - row, - depreciation_amount, - value_after_depreciation, - final_number_of_depreciations, - row_no, - skip_row, - ): - if ( - row_no == cint(final_number_of_depreciations) - 1 - and flt(value_after_depreciation) != flt(row.expected_value_after_useful_life) - ) or flt(value_after_depreciation) < flt(row.expected_value_after_useful_life): - depreciation_amount += flt(value_after_depreciation) - flt(row.expected_value_after_useful_life) - depreciation_amount = flt(depreciation_amount, row.precision("depreciation_amount")) - skip_row = True - return depreciation_amount, skip_row - - def validate_depreciation_amount_for_low_value_assets(self, asset_doc, row, depreciation_amount): - """ - If gross purchase amount is too low, then depreciation amount - can come zero sometimes based on the frequency and number of depreciations. - """ - if flt(depreciation_amount, asset_doc.precision("gross_purchase_amount")) <= 0: - frappe.throw( - _("Gross Purchase Amount {0} cannot be depreciated over {1} cycles.").format( - frappe.bold(asset_doc.gross_purchase_amount), - frappe.bold(row.total_number_of_depreciations), - ) - ) - - def add_depr_schedule_row(self, schedule_date, depreciation_amount, schedule_idx): - if self.shift_based: - shift = ( - self.schedules_before_clearing[schedule_idx].shift - if self.schedules_before_clearing and len(self.schedules_before_clearing) > schedule_idx - else frappe.get_cached_value("Asset Shift Factor", {"default": 1}, "shift_name") - ) - else: - shift = None - - self.append( - "depreciation_schedule", - { - "schedule_date": schedule_date, - "depreciation_amount": depreciation_amount, - "shift": shift, - }, - ) - - def set_accumulated_depreciation( - self, - asset_doc, - row, - date_of_disposal=None, - date_of_return=None, - ): - accumulated_depreciation = flt(self.opening_accumulated_depreciation) - value_after_depreciation = flt(row.value_after_depreciation) - - for i, d in enumerate(self.get("depreciation_schedule")): - if d.journal_entry: - accumulated_depreciation = d.accumulated_depreciation_amount - continue - - value_after_depreciation = flt( - value_after_depreciation - flt(d.depreciation_amount), d.precision("depreciation_amount") - ) - - # for the last row, if depreciation method = Straight Line - if ( - self.depreciation_method in ("Straight Line", "Manual") - and i == len(self.get("depreciation_schedule")) - 1 - and not date_of_disposal - and not date_of_return - and not row.shift_based - ): - d.depreciation_amount += flt( - value_after_depreciation - flt(row.expected_value_after_useful_life), - d.precision("depreciation_amount"), - ) - - accumulated_depreciation += d.depreciation_amount - d.accumulated_depreciation_amount = flt( - accumulated_depreciation, d.precision("accumulated_depreciation_amount") - ) - - -def _get_value_after_depreciation_for_making_schedule(asset_doc, fb_row): - if asset_doc.docstatus == 1 and fb_row.value_after_depreciation: - value_after_depreciation = flt(fb_row.value_after_depreciation) - else: - value_after_depreciation = flt(asset_doc.gross_purchase_amount) - flt( - asset_doc.opening_accumulated_depreciation - ) - - return value_after_depreciation - - -# if it returns True, depreciation_amount will not be equal for the first and last rows -def _check_is_pro_rata(asset_doc, row, wdv_or_dd_non_yearly=False): - has_pro_rata = False - - # if not existing asset, from_date = available_for_use_date - # otherwise, if opening_number_of_booked_depreciations = 2, available_for_use_date = 01/01/2020 and frequency_of_depreciation = 12 - # from_date = 01/01/2022 - if row.depreciation_method in ("Straight Line", "Manual"): - prev_depreciation_start_date = get_last_day( - add_months( - row.depreciation_start_date, - (row.frequency_of_depreciation * -1) * asset_doc.opening_number_of_booked_depreciations, - ) - ) - from_date = asset_doc.available_for_use_date - days = date_diff(prev_depreciation_start_date, from_date) + 1 - total_days = get_total_days(prev_depreciation_start_date, row.frequency_of_depreciation) - else: - from_date = _get_modified_available_for_use_date_for_existing_assets(asset_doc, row) - days = date_diff(row.depreciation_start_date, from_date) + 1 - total_days = get_total_days(row.depreciation_start_date, row.frequency_of_depreciation) - if days <= 0: - frappe.throw( - _( - """Error: This asset already has {0} depreciation periods booked. - The `depreciation start` date must be at least {1} periods after the `available for use` date. - Please correct the dates accordingly.""" - ).format( - asset_doc.opening_number_of_booked_depreciations, - asset_doc.opening_number_of_booked_depreciations, - ) - ) - if days < total_days: - has_pro_rata = True - return has_pro_rata - - -def _get_modified_available_for_use_date_for_existing_assets(asset_doc, row): - """ - if Asset has opening booked depreciations = 3, - frequency of depreciation = 3, - available for use date = 17-07-2023, - depreciation start date = 30-06-2024 - then from date should be 01-04-2024 - """ - if asset_doc.opening_number_of_booked_depreciations > 0: - from_date = add_months( - asset_doc.available_for_use_date, - (asset_doc.opening_number_of_booked_depreciations * row.frequency_of_depreciation) - 1, - ) - if is_last_day_of_the_month(row.depreciation_start_date): - return add_days(get_last_day(from_date), 1) - - # get from date when depreciation start date is not last day of the month - months_difference = month_diff(row.depreciation_start_date, from_date) - 1 - return add_days(add_months(row.depreciation_start_date, -1 * months_difference), 1) - else: - return asset_doc.available_for_use_date - - -def _get_pro_rata_amt( - row, - depreciation_amount, - from_date, - to_date, - has_wdv_or_dd_non_yearly_pro_rata=False, - original_schedule_date=None, -): - days = date_diff(to_date, from_date) + 1 - months = month_diff(to_date, from_date) - if has_wdv_or_dd_non_yearly_pro_rata: - total_days = get_total_days(original_schedule_date or to_date, 12) - else: - total_days = get_total_days(original_schedule_date or to_date, row.frequency_of_depreciation) - return (depreciation_amount * flt(days)) / flt(total_days), days, months - - -def get_total_days(date, frequency): - period_start_date = add_months(date, cint(frequency) * -1) - if is_last_day_of_the_month(date): - period_start_date = get_last_day(period_start_date) - return date_diff(date, period_start_date) - - -def make_draft_asset_depr_schedules(asset_doc): - asset_depr_schedules_names = [] - - for row in asset_doc.get("finance_books"): - name = make_draft_asset_depr_schedule(asset_doc, row) - asset_depr_schedules_names.append(name) - - return asset_depr_schedules_names - def make_draft_asset_depr_schedule(asset_doc, row): asset_depr_schedule_doc = frappe.new_doc("Asset Depreciation Schedule") - asset_depr_schedule_doc.prepare_draft_asset_depr_schedule_data(asset_doc, row) + asset_depr_schedule_doc.create_depreciation_schedule(asset_doc, row) asset_depr_schedule_doc.insert() return asset_depr_schedule_doc.name -def update_draft_asset_depr_schedules(asset_doc): - for row in asset_doc.get("finance_books"): - asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset_doc.name, "Draft", row.finance_book) - - if not asset_depr_schedule_doc: - continue - - asset_depr_schedule_doc.prepare_draft_asset_depr_schedule_data(asset_doc, row) - - asset_depr_schedule_doc.save() - - def convert_draft_asset_depr_schedules_into_active(asset_doc): for row in asset_doc.get("finance_books"): asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset_doc.name, "Draft", row.finance_book) @@ -732,74 +180,63 @@ def cancel_asset_depr_schedules(asset_doc): asset_depr_schedule_doc.cancel() -def make_new_active_asset_depr_schedules_and_cancel_current_ones( - asset_doc, - notes, - date_of_disposal=None, - date_of_return=None, - value_after_depreciation=None, - difference_amount=None, -): +def reschedule_depreciation(asset_doc, notes, disposal_date=None): for row in asset_doc.get("finance_books"): - current_asset_depr_schedule_doc = get_asset_depr_schedule_doc( - asset_doc.name, "Active", row.finance_book + current_schedule = get_asset_depr_schedule_doc(asset_doc.name, None, row.finance_book) + + if current_schedule: + if current_schedule.docstatus == 1: + new_schedule = frappe.copy_doc(current_schedule) + elif current_schedule.docstatus == 0: + new_schedule = current_schedule + else: + new_schedule = frappe.new_doc("Asset Depreciation Schedule") + new_schedule.asset = asset_doc.name + + set_modified_depreciation_rate(asset_doc, row, new_schedule) + + new_schedule.create_depreciation_schedule(row, disposal_date) + new_schedule.notes = notes + + if current_schedule and current_schedule.docstatus == 1: + current_schedule.flags.should_not_cancel_depreciation_entries = True + current_schedule.cancel() + + new_schedule.submit() + + +def set_modified_depreciation_rate(asset_doc, row, new_schedule): + if row.depreciation_method in ( + "Written Down Value", + "Double Declining Balance", + ): + new_rate_of_depreciation = flt( + asset_doc.get_depreciation_rate(row), row.precision("rate_of_depreciation") ) - if not current_asset_depr_schedule_doc: - frappe.throw( - _("Asset Depreciation Schedule not found for Asset {0} and Finance Book {1}").format( - get_link_to_form("Asset", asset_doc.name), row.finance_book - ) - ) + row.db_set("rate_of_depreciation", new_rate_of_depreciation) + new_schedule.rate_of_depreciation = new_rate_of_depreciation - new_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc) - - if asset_doc.flags.decrease_in_asset_value_due_to_value_adjustment and not value_after_depreciation: - value_after_depreciation = row.value_after_depreciation - difference_amount - - if asset_doc.flags.increase_in_asset_value_due_to_repair and row.depreciation_method in ( - "Written Down Value", - "Double Declining Balance", - ): - new_rate_of_depreciation = flt( - asset_doc.get_depreciation_rate(row), row.precision("rate_of_depreciation") - ) - row.rate_of_depreciation = new_rate_of_depreciation - new_asset_depr_schedule_doc.rate_of_depreciation = new_rate_of_depreciation - - new_asset_depr_schedule_doc.make_depr_schedule( - asset_doc, row, date_of_disposal, value_after_depreciation=value_after_depreciation - ) - new_asset_depr_schedule_doc.set_accumulated_depreciation( - asset_doc, row, date_of_disposal, date_of_return - ) - - new_asset_depr_schedule_doc.notes = notes - - current_asset_depr_schedule_doc.flags.should_not_cancel_depreciation_entries = True - current_asset_depr_schedule_doc.cancel() - - new_asset_depr_schedule_doc.submit() def get_temp_asset_depr_schedule_doc( asset_doc, row, - date_of_disposal=None, + disposal_date=None, date_of_return=None, update_asset_finance_book_row=False, new_depr_schedule=None, ): - current_asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset_doc.name, "Active", row.finance_book) + current_schedule = get_asset_depr_schedule_doc(asset_doc.name, "Active", row.finance_book) - if not current_asset_depr_schedule_doc: + if not current_schedule: frappe.throw( _("Asset Depreciation Schedule not found for Asset {0} and Finance Book {1}").format( get_link_to_form("Asset", asset_doc.name), row.finance_book ) ) - temp_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc) + temp_asset_depr_schedule_doc = frappe.copy_doc(current_schedule) if new_depr_schedule: temp_asset_depr_schedule_doc.depreciation_schedule = [] @@ -816,10 +253,10 @@ def get_temp_asset_depr_schedule_doc( }, ) - temp_asset_depr_schedule_doc.prepare_draft_asset_depr_schedule_data( + temp_asset_depr_schedule_doc.create_depreciation_schedule( asset_doc, row, - date_of_disposal, + disposal_date, date_of_return, update_asset_finance_book_row, ) @@ -838,7 +275,7 @@ def get_depr_schedule(asset_name, status, finance_book=None): @frappe.whitelist() -def get_asset_depr_schedule_doc(asset_name, status, finance_book=None): +def get_asset_depr_schedule_doc(asset_name, status=None, finance_book=None): asset_depr_schedule = get_asset_depr_schedule_name(asset_name, status, finance_book) if not asset_depr_schedule: @@ -849,16 +286,17 @@ def get_asset_depr_schedule_doc(asset_name, status, finance_book=None): return asset_depr_schedule_doc -def get_asset_depr_schedule_name(asset_name, status, finance_book=None): - if isinstance(status, str): - status = [status] - +def get_asset_depr_schedule_name(asset_name, status=None, finance_book=None): filters = [ ["asset", "=", asset_name], - ["status", "in", status], ["docstatus", "<", 2], ] + if status: + if isinstance(status, str): + status = [status] + filters.append(["status", "in", status]) + if finance_book: filters.append(["finance_book", "=", finance_book]) else: diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/deppreciation_schedule_controller.py b/erpnext/assets/doctype/asset_depreciation_schedule/deppreciation_schedule_controller.py new file mode 100644 index 00000000000..0f72f80e907 --- /dev/null +++ b/erpnext/assets/doctype/asset_depreciation_schedule/deppreciation_schedule_controller.py @@ -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 diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/depreciation_methods.py b/erpnext/assets/doctype/asset_depreciation_schedule/depreciation_methods.py new file mode 100644 index 00000000000..9a573ea05e1 --- /dev/null +++ b/erpnext/assets/doctype/asset_depreciation_schedule/depreciation_methods.py @@ -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 diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/utils.py b/erpnext/assets/doctype/asset_depreciation_schedule/utils.py deleted file mode 100644 index c13ad5ebdcb..00000000000 --- a/erpnext/assets/doctype/asset_depreciation_schedule/utils.py +++ /dev/null @@ -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 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 be035f7ea7a..eb726a1a1f7 100644 --- a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json +++ b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json @@ -90,7 +90,8 @@ "fieldname": "rate_of_depreciation", "fieldtype": "Percent", "label": "Rate of Depreciation (%)", - "mandatory_depends_on": "eval:doc.depreciation_method == 'Written Down Value'" + "mandatory_depends_on": "eval:doc.depreciation_method == 'Written Down Value'", + "no_copy": 1 }, { "fieldname": "salvage_value_percentage", @@ -116,6 +117,7 @@ "fieldname": "total_number_of_booked_depreciations", "fieldtype": "Int", "label": "Total Number of Booked Depreciations ", + "no_copy": 1, "read_only": 1 }, { @@ -138,7 +140,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2024-12-19 17:50:24.012434", + "modified": "2025-01-06 17:14:51.836803", "modified_by": "Administrator", "module": "Assets", "name": "Asset Finance Book", diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.json b/erpnext/assets/doctype/asset_repair/asset_repair.json index 54a067063b2..f49de50838a 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.json +++ b/erpnext/assets/doctype/asset_repair/asset_repair.json @@ -157,6 +157,7 @@ "options": "Asset Repair Consumed Item" }, { + "fetch_from": "company.cost_center", "fieldname": "cost_center", "fieldtype": "Link", "label": "Cost Center", @@ -258,7 +259,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2024-12-23 18:08:35.159964", + "modified": "2024-12-27 18:11:40.548727", "modified_by": "Administrator", "module": "Assets", "name": "Asset Repair", diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py index 8b541163179..27569eb6cce 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/asset_repair.py @@ -12,7 +12,7 @@ from erpnext.assets.doctype.asset.asset import get_asset_account from erpnext.assets.doctype.asset_activity.asset_activity import add_asset_activity from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import ( get_depr_schedule, - make_new_active_asset_depr_schedules_and_cancel_current_ones, + reschedule_depreciation, ) from erpnext.controllers.accounts_controller import AccountsController @@ -144,30 +144,27 @@ class AssetRepair(AccountsController): self.total_repair_cost = flt(self.repair_cost) + flt(self.consumed_items_cost) def on_submit(self): - self.asset_doc.flags.increase_in_asset_value_due_to_repair = False self.decrease_stock_quantity() if self.get("capitalize_repair_cost"): self.update_asset_value() - self.make_gl_entries() self.set_increase_in_asset_life() depreciation_note = self.get_depreciation_note() - make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, depreciation_note) + reschedule_depreciation(self.asset_doc, depreciation_note) self.add_asset_activity() + self.make_gl_entries() + def on_cancel(self): self.asset_doc = frappe.get_doc("Asset", self.asset) - if self.get("capitalize_repair_cost"): - self.asset_doc.flags.increase_in_asset_value_due_to_repair = True - self.update_asset_value() self.make_gl_entries(cancel=True) self.set_increase_in_asset_life() depreciation_note = self.get_depreciation_note() - make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, depreciation_note) + reschedule_depreciation(self.asset_doc, depreciation_note) self.add_asset_activity() def after_delete(self): @@ -358,9 +355,10 @@ class AssetRepair(AccountsController): def set_increase_in_asset_life(self): if self.asset_doc.calculate_depreciation and cint(self.increase_in_asset_life) > 0: for row in self.asset_doc.finance_books: - row.increase_in_asset_life = row.increase_in_asset_life + ( + row.increase_in_asset_life = cint(row.increase_in_asset_life) + ( cint(self.increase_in_asset_life) * (1 if self.docstatus == 1 else -1) ) + row.db_update() def get_depreciation_note(self): return _("This schedule was created when Asset {0} was repaired through Asset Repair {1}.").format( 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 0c0da3bd8cd..4c5253b338d 100644 --- a/erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.py +++ b/erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.py @@ -18,7 +18,9 @@ from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_sched get_asset_depr_schedule_doc, get_temp_asset_depr_schedule_doc, ) -from erpnext.assets.doctype.asset_depreciation_schedule.utils import get_asset_shift_factors_map +from erpnext.erpnext.assets.doctype.asset_depreciation_schedule.deppreciation_schedule_controller import ( + get_asset_shift_factors_map, +) class AssetShiftAllocation(Document): diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py index 07817d8e6ad..f27ffb0d945 100644 --- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py +++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py @@ -14,7 +14,7 @@ from erpnext.assets.doctype.asset.asset import get_asset_value_after_depreciatio from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts from erpnext.assets.doctype.asset_activity.asset_activity import add_asset_activity from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import ( - make_new_active_asset_depr_schedules_and_cancel_current_ones, + reschedule_depreciation, ) @@ -64,7 +64,7 @@ class AssetValueAdjustment(Document): self.current_asset_value = get_asset_value_after_depreciation(self.asset, self.finance_book) def on_submit(self): - self.make_depreciation_entry() + self.make_asset_revaluation_entry() self.update_asset() add_asset_activity( self.asset, @@ -83,7 +83,7 @@ class AssetValueAdjustment(Document): ), ) - def make_depreciation_entry(self): + def make_asset_revaluation_entry(self): asset = frappe.get_doc("Asset", self.asset) ( fixed_asset_account, @@ -170,7 +170,7 @@ class AssetValueAdjustment(Document): def update_asset(self): asset = self.update_asset_value_after_depreciation() note = self.get_adjustment_note() - make_new_active_asset_depr_schedules_and_cancel_current_ones(asset, note) + reschedule_depreciation(asset, note) def update_asset_value_after_depreciation(self): difference_amount = self.difference_amount if self.docstatus == 1 else -1 * self.difference_amount diff --git a/erpnext/patches.txt b/erpnext/patches.txt index ec3ae09117b..0df74cf36d5 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -411,3 +411,4 @@ erpnext.patches.v15_0.update_payment_schedule_fields_in_invoices erpnext.patches.v15_0.rename_group_by_to_categorize_by execute:frappe.db.set_single_value("Accounts Settings", "receivable_payable_fetch_method", "Buffered Cursor") erpnext.patches.v14_0.set_update_price_list_based_on +erpnext.patches.v15_0.update_journal_entry_type diff --git a/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py b/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py index d4350d8f9a1..ba923b4e701 100644 --- a/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py +++ b/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py @@ -22,7 +22,7 @@ def execute(): asset_depr_schedule_doc.name, {"docstatus": 1, "status": "Active"}, ) - + update_depreciation_schedules(depreciation_schedules, asset_depr_schedule_doc.name) diff --git a/erpnext/patches/v15_0/update_journal_entry_type.py b/erpnext/patches/v15_0/update_journal_entry_type.py new file mode 100644 index 00000000000..32a499d01af --- /dev/null +++ b/erpnext/patches/v15_0/update_journal_entry_type.py @@ -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") From 813164c25b935c0a657cb0b08e0a865c090e208a Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Tue, 14 Jan 2025 01:49:40 +0530 Subject: [PATCH 11/40] fix: remove duplicate patch --- erpnext/assets/doctype/asset/asset.py | 2 +- erpnext/assets/doctype/asset/depreciation.py | 6 +- .../asset_depreciation_schedule.py | 68 ++-- .../deppreciation_schedule_controller.py | 23 +- .../depreciation_methods.py | 38 ++- .../asset_shift_allocation.js | 9 + .../asset_shift_allocation.json | 11 +- .../asset_shift_allocation.py | 297 ++++++++---------- .../v15_0/update_journal_entry_type.py | 1 + 9 files changed, 209 insertions(+), 246 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index a5fd70402c4..6a215040679 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -160,7 +160,7 @@ class Asset(AccountsController): if self.calculate_depreciation: self.set_depreciation_rate() for d in self.finance_books: - d.value_after_depreciation = self.value_after_depreciation + d.db_set("value_after_depreciation", self.value_after_depreciation) else: self.finance_books = [] diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index de7d14cb8a1..868441d60c3 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -28,7 +28,7 @@ from erpnext.assets.doctype.asset_activity.asset_activity import add_asset_activ from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import ( get_asset_depr_schedule_doc, get_asset_depr_schedule_name, - get_temp_asset_depr_schedule_doc, + get_temp_depr_schedule_doc, reschedule_depreciation, ) @@ -874,9 +874,7 @@ def get_value_after_depreciation_on_disposal_date(asset, disposal_date, finance_ row = asset_doc.finance_books[idx - 1] - temp_asset_depreciation_schedule = get_temp_asset_depr_schedule_doc( - asset_doc, row, getdate(disposal_date) - ) + temp_asset_depreciation_schedule = get_temp_depr_schedule_doc(asset_doc, row, getdate(disposal_date)) accumulated_depr_amount = temp_asset_depreciation_schedule.get("depreciation_schedule")[ -1 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 c75e08bbb87..e0e60d7cdfd 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py @@ -107,14 +107,9 @@ class AssetDepreciationSchedule(DepreciationScheduleController): frappe.get_doc("Journal Entry", d.journal_entry).cancel() def update_shift_depr_schedule(self): - if not self.shift_based or self.docstatus != 0: + if not self.shift_based or self.docstatus != 0 or self.get("__islocal"): return - - asset_doc = frappe.get_doc("Asset", self.asset) - fb_row = asset_doc.finance_books[self.finance_book_id - 1] - - self.make_depr_schedule(asset_doc, fb_row) - self.set_accumulated_depreciation(asset_doc, fb_row) + self.create_depreciation_schedule() def get_finance_book_row(self, fb_row=None): if fb_row: @@ -144,6 +139,7 @@ class AssetDepreciationSchedule(DepreciationScheduleController): self.total_number_of_depreciations = self.fb_row.total_number_of_depreciations self.frequency_of_depreciation = self.fb_row.frequency_of_depreciation self.rate_of_depreciation = self.fb_row.get("rate_of_depreciation") + self.value_after_depreciation = self.fb_row.value_after_depreciation self.expected_value_after_useful_life = self.fb_row.get("expected_value_after_useful_life") self.daily_prorata_based = self.fb_row.get("daily_prorata_based") self.shift_based = self.fb_row.get("shift_based") @@ -218,50 +214,46 @@ def set_modified_depreciation_rate(asset_doc, row, new_schedule): new_schedule.rate_of_depreciation = new_rate_of_depreciation +def get_temp_depr_schedule_doc(asset_doc, fb_row, disposal_date=None, updated_depr_schedule=None): + current_schedule = get_current_asset_depr(asset_doc, fb_row) + temp_schedule_doc = frappe.copy_doc(current_schedule) -def get_temp_asset_depr_schedule_doc( - asset_doc, - row, - disposal_date=None, - date_of_return=None, - update_asset_finance_book_row=False, - new_depr_schedule=None, -): + if updated_depr_schedule: + modify_depreciation_dchedule(temp_schedule_doc, updated_depr_schedule) + + temp_schedule_doc.create_depreciation_schedule(fb_row, disposal_date) + return temp_schedule_doc + + +def get_current_asset_depr(asset_doc, row): current_schedule = get_asset_depr_schedule_doc(asset_doc.name, "Active", row.finance_book) - if not current_schedule: frappe.throw( _("Asset Depreciation Schedule not found for Asset {0} and Finance Book {1}").format( get_link_to_form("Asset", asset_doc.name), row.finance_book ) ) + return current_schedule - temp_asset_depr_schedule_doc = frappe.copy_doc(current_schedule) - if new_depr_schedule: - temp_asset_depr_schedule_doc.depreciation_schedule = [] +def modify_depreciation_dchedule(temp_schedule_doc, updated_depr_schedule): + temp_schedule_doc.depreciation_schedule = [] - for schedule in new_depr_schedule: - temp_asset_depr_schedule_doc.append( - "depreciation_schedule", - { - "schedule_date": schedule.schedule_date, - "depreciation_amount": schedule.depreciation_amount, - "accumulated_depreciation_amount": schedule.accumulated_depreciation_amount, - "journal_entry": schedule.journal_entry, - "shift": schedule.shift, - }, - ) + for schedule in updated_depr_schedule: + temp_schedule_doc.append( + "depreciation_schedule", + { + "schedule_date": schedule.schedule_date, + "depreciation_amount": schedule.depreciation_amount, + "accumulated_depreciation_amount": schedule.accumulated_depreciation_amount, + "journal_entry": schedule.journal_entry, + "shift": schedule.shift, + }, + ) - temp_asset_depr_schedule_doc.create_depreciation_schedule( - asset_doc, - row, - disposal_date, - date_of_return, - update_asset_finance_book_row, - ) - return temp_asset_depr_schedule_doc +def get_asset_shift_factors_map(): + return dict(frappe.db.get_all("Asset Shift Factor", ["shift_name", "shift_factor"], as_list=True)) @frappe.whitelist() diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/deppreciation_schedule_controller.py b/erpnext/assets/doctype/asset_depreciation_schedule/deppreciation_schedule_controller.py index 0f72f80e907..277b12cfc5c 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/deppreciation_schedule_controller.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/deppreciation_schedule_controller.py @@ -42,7 +42,6 @@ class DepreciationScheduleController(StraightLineMethod, WDVMethod): 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 @@ -68,7 +67,6 @@ class DepreciationScheduleController(StraightLineMethod, WDVMethod): 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): @@ -228,12 +226,8 @@ class DepreciationScheduleController(StraightLineMethod, WDVMethod): 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 - ) + depr_booked_for_months = self.get_booked_depr_for_months_count(last_depr_date) self.pending_months = total_months - depr_booked_for_months @@ -245,9 +239,22 @@ class DepreciationScheduleController(StraightLineMethod, WDVMethod): last_depr_date = add_months( self.fb_row.depreciation_start_date, -1 * self.fb_row.frequency_of_depreciation ) - return last_depr_date + def get_booked_depr_for_months_count(self, last_depr_date): + depr_booked_for_months = 0 + if last_depr_date: + asset_used_for_months = self.fb_row.frequency_of_depreciation * ( + 1 + self.asset_doc.opening_number_of_booked_depreciations + ) + computed_available_for_use_date = add_days( + add_months(self.fb_row.depreciation_start_date, -1 * asset_used_for_months), 1 + ) + if getdate(computed_available_for_use_date) < getdate(self.asset_doc.available_for_use_date): + computed_available_for_use_date = self.asset_doc.available_for_use_date + depr_booked_for_months = date_diff(last_depr_date, computed_available_for_use_date) / (365 / 12) + return depr_booked_for_months + 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() diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/depreciation_methods.py b/erpnext/assets/doctype/asset_depreciation_schedule/depreciation_methods.py index 9a573ea05e1..da7962db63e 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/depreciation_methods.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/depreciation_methods.py @@ -25,7 +25,7 @@ class StraightLineMethod(Document): ) if self.fb_row.shift_based: - self.get_shift_depr_amount(row_idx) + return self.get_shift_depr_amount(row_idx) if self.fb_row.daily_prorata_based: return self.get_daily_prorata_based_depr_amount(row_idx) @@ -51,31 +51,29 @@ class StraightLineMethod(Document): 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 + if not self.schedules_before_clearing: + pending_periods = flt(self.pending_months) / flt(self.fb_row.frequency_of_depreciation) + return self.depreciable_value / pending_periods 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 - ) + + if self.schedules_before_clearing: + 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] + [ + flt(asset_shift_factors_map.get(d.shift)) + for d in self.schedules_before_clearing + if not d.journal_entry + ] ) - return (depreciable_value / shift_factors_sum) * shift_factor + return (self.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)) diff --git a/erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.js b/erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.js index c22feb05458..98903048f79 100644 --- a/erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.js +++ b/erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.js @@ -2,6 +2,15 @@ // For license information, please see license.txt frappe.ui.form.on("Asset Shift Allocation", { onload: function (frm) { + frm.set_query("asset", function () { + return { + filters: { + company: frm.doc.company, + docstatus: 1, + }, + }; + }); + frm.events.make_schedules_editable(frm); }, diff --git a/erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.json b/erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.json index 7693037042d..22c274df970 100644 --- a/erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.json +++ b/erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.json @@ -12,6 +12,7 @@ "finance_book", "amended_from", "depreciation_schedule_section", + "column_break_jomc", "depreciation_schedule" ], "fields": [ @@ -57,7 +58,9 @@ "fieldname": "depreciation_schedule", "fieldtype": "Table", "label": "Depreciation Schedule", - "options": "Depreciation Schedule" + "no_copy": 1, + "options": "Depreciation Schedule", + "read_only": 1 }, { "fieldname": "naming_series", @@ -65,12 +68,16 @@ "label": "Naming Series", "options": "ACC-ASA-.YYYY.-", "reqd": 1 + }, + { + "fieldname": "column_break_jomc", + "fieldtype": "Column Break" } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2024-03-27 13:06:35.732191", + "modified": "2025-01-10 16:25:31.397325", "modified_by": "Administrator", "module": "Assets", "name": "Asset Shift Allocation", 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 4c5253b338d..e273b997a3d 100644 --- a/erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.py +++ b/erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.py @@ -16,10 +16,8 @@ from frappe.utils import ( 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_temp_asset_depr_schedule_doc, -) -from erpnext.erpnext.assets.doctype.asset_depreciation_schedule.deppreciation_schedule_controller import ( get_asset_shift_factors_map, + get_temp_depr_schedule_doc, ) @@ -32,9 +30,7 @@ class AssetShiftAllocation(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 @@ -43,32 +39,138 @@ class AssetShiftAllocation(Document): naming_series: DF.Literal["ACC-ASA-.YYYY.-"] # end: auto-generated types - def after_insert(self): - self.fetch_and_set_depr_schedule() - def validate(self): self.asset_depr_schedule_doc = get_asset_depr_schedule_doc(self.asset, "Active", self.finance_book) + if self.get("depreciation_schedule") and self.docstatus == 0: + self.validate_invalid_shift_change() + self.update_depr_schedule() - self.validate_invalid_shift_change() - self.update_depr_schedule() + def after_insert(self): + self.fetch_and_set_depr_schedule() def on_submit(self): self.create_new_asset_depr_schedule() + def validate_invalid_shift_change(self): + for i, sch in enumerate(self.depreciation_schedule): + if sch.journal_entry and self.asset_depr_schedule_doc.depreciation_schedule[i].shift != sch.shift: + frappe.throw( + _( + "Row {0}: Shift cannot be changed since the depreciation has already been processed" + ).format(i) + ) + + def update_depr_schedule(self): + self.adjust_depr_shifts() + + asset_doc = frappe.get_doc("Asset", self.asset) + fb_row = self.get_finance_book_row(asset_doc) + + temp_depr_schedule_doc = get_temp_depr_schedule_doc( + asset_doc, fb_row, updated_depr_schedule=self.depreciation_schedule + ) + + # Update the depreciation schedule with the new shifts + self.depreciation_schedule = [] + self.modify_depr_schedule(temp_depr_schedule_doc.get("depreciation_schedule")) + + def adjust_depr_shifts(self): + """ + Adjust the shifts in the depreciation schedule based on the new shifts + """ + shift_factors_map = get_asset_shift_factors_map() + reverse_shift_factors_map = {v: k for k, v in shift_factors_map.items()} + factor_diff = self.calculate_shift_factor_diff(shift_factors_map) + + # Case 1: Reduce shifts if there is an excess factor + if factor_diff > 0: + self.reduce_depr_shifts(factor_diff, shift_factors_map, reverse_shift_factors_map) + + # Case 2: Add shifts if there is a missing factor + elif factor_diff < 0: + self.add_depr_shifts(factor_diff, shift_factors_map, reverse_shift_factors_map) + + def calculate_shift_factor_diff(self, shift_factors_map): + original_shift_sum = sum( + shift_factors_map.get(schedule.shift, 0) + for schedule in self.asset_depr_schedule_doc.depreciation_schedule + ) + new_shift_sum = sum( + shift_factors_map.get(schedule.shift, 0) for schedule in self.depreciation_schedule + ) + return new_shift_sum - original_shift_sum + + def reduce_depr_shifts(self, factor_diff, shift_factors_map, reverse_shift_factors_map): + for i, schedule in reversed(list(enumerate(self.depreciation_schedule))): + if factor_diff <= 0: + break + + current_factor = shift_factors_map.get(schedule.shift, 0) + if current_factor <= factor_diff: + self.depreciation_schedule.pop(i) + factor_diff -= current_factor + else: + new_factor = current_factor - factor_diff + self.depreciation_schedule[i].shift = reverse_shift_factors_map.get(new_factor) + factor_diff = 0 + + def add_depr_shifts(self, factor_diff, shift_factors_map, reverse_shift_factors_map): + factor_diff = abs(factor_diff) + shift_factors = sorted(shift_factors_map.values(), reverse=True) + + while factor_diff > 0: + for factor in shift_factors: + if factor <= factor_diff: + self.add_schedule_row(factor, reverse_shift_factors_map) + factor_diff -= factor + break + else: + frappe.throw( + _("Could not find a suitable shift to match the difference: {0}").format(factor_diff) + ) + + def add_schedule_row(self, factor, reverse_shift_factors_map): + schedule_date = add_months( + self.depreciation_schedule[-1].schedule_date, + cint(self.asset_depr_schedule_doc.frequency_of_depreciation), + ) + if is_last_day_of_the_month(self.depreciation_schedule[-1].schedule_date): + schedule_date = get_last_day(schedule_date) + + self.append( + "depreciation_schedule", + { + "schedule_date": schedule_date, + "shift": reverse_shift_factors_map.get(factor), + }, + ) + + def get_finance_book_row(self, asset_doc): + idx = 0 + for d in asset_doc.get("finance_books"): + if d.finance_book == self.finance_book: + idx = d.idx + break + + return asset_doc.get("finance_books")[idx - 1] + + def modify_depr_schedule(self, temp_depr_schedule): + for schedule in temp_depr_schedule: + self.append( + "depreciation_schedule", + { + "schedule_date": schedule.schedule_date, + "depreciation_amount": schedule.depreciation_amount, + "accumulated_depreciation_amount": schedule.accumulated_depreciation_amount, + "journal_entry": schedule.journal_entry, + "shift": schedule.shift, + }, + ) + def fetch_and_set_depr_schedule(self): if self.asset_depr_schedule_doc: if self.asset_depr_schedule_doc.shift_based: - for schedule in self.asset_depr_schedule_doc.get("depreciation_schedule"): - self.append( - "depreciation_schedule", - { - "schedule_date": schedule.schedule_date, - "depreciation_amount": schedule.depreciation_amount, - "accumulated_depreciation_amount": schedule.accumulated_depreciation_amount, - "journal_entry": schedule.journal_entry, - "shift": schedule.shift, - }, - ) + self.modify_depr_schedule(self.asset_depr_schedule_doc.depreciation_schedule) self.flags.ignore_validate = True self.save() @@ -85,143 +187,6 @@ class AssetShiftAllocation(Document): ) ) - def validate_invalid_shift_change(self): - if not self.get("depreciation_schedule") or self.docstatus == 1: - return - - for i, sch in enumerate(self.depreciation_schedule): - if sch.journal_entry and self.asset_depr_schedule_doc.depreciation_schedule[i].shift != sch.shift: - frappe.throw( - _( - "Row {0}: Shift cannot be changed since the depreciation has already been processed" - ).format(i) - ) - - def update_depr_schedule(self): - if not self.get("depreciation_schedule") or self.docstatus == 1: - return - - self.allocate_shift_diff_in_depr_schedule() - - asset_doc = frappe.get_doc("Asset", self.asset) - fb_row = asset_doc.finance_books[self.asset_depr_schedule_doc.finance_book_id - 1] - - asset_doc.flags.shift_allocation = True - - temp_depr_schedule = get_temp_asset_depr_schedule_doc( - asset_doc, fb_row, new_depr_schedule=self.depreciation_schedule - ).get("depreciation_schedule") - - self.depreciation_schedule = [] - - for schedule in temp_depr_schedule: - self.append( - "depreciation_schedule", - { - "schedule_date": schedule.schedule_date, - "depreciation_amount": schedule.depreciation_amount, - "accumulated_depreciation_amount": schedule.accumulated_depreciation_amount, - "journal_entry": schedule.journal_entry, - "shift": schedule.shift, - }, - ) - - def allocate_shift_diff_in_depr_schedule(self): - asset_shift_factors_map = get_asset_shift_factors_map() - reverse_asset_shift_factors_map = {asset_shift_factors_map[k]: k for k in asset_shift_factors_map} - - original_shift_factors_sum = sum( - flt(asset_shift_factors_map.get(schedule.shift)) - for schedule in self.asset_depr_schedule_doc.depreciation_schedule - ) - - new_shift_factors_sum = sum( - flt(asset_shift_factors_map.get(schedule.shift)) for schedule in self.depreciation_schedule - ) - - diff = new_shift_factors_sum - original_shift_factors_sum - - if diff > 0: - for i, schedule in reversed(list(enumerate(self.depreciation_schedule))): - if diff <= 0: - break - - shift_factor = flt(asset_shift_factors_map.get(schedule.shift)) - - if shift_factor <= diff: - self.depreciation_schedule.pop() - diff -= shift_factor - else: - try: - self.depreciation_schedule[i].shift = reverse_asset_shift_factors_map.get( - shift_factor - diff - ) - diff = 0 - except Exception: - frappe.throw( - _("Could not auto update shifts. Shift with shift factor {0} needed.") - ).format(shift_factor - diff) - elif diff < 0: - shift_factors = list(asset_shift_factors_map.values()) - desc_shift_factors = sorted(shift_factors, reverse=True) - depr_schedule_len_diff = self.asset_depr_schedule_doc.total_number_of_depreciations - len( - self.depreciation_schedule - ) - subsets_result = [] - - if depr_schedule_len_diff > 0: - num_rows_to_add = depr_schedule_len_diff - - while not subsets_result and num_rows_to_add > 0: - find_subsets_with_sum(shift_factors, num_rows_to_add, abs(diff), [], subsets_result) - if subsets_result: - break - num_rows_to_add -= 1 - - if subsets_result: - for i in range(num_rows_to_add): - schedule_date = add_months( - self.depreciation_schedule[-1].schedule_date, - cint(self.asset_depr_schedule_doc.frequency_of_depreciation), - ) - - if is_last_day_of_the_month(self.depreciation_schedule[-1].schedule_date): - schedule_date = get_last_day(schedule_date) - - self.append( - "depreciation_schedule", - { - "schedule_date": schedule_date, - "shift": reverse_asset_shift_factors_map.get(subsets_result[0][i]), - }, - ) - - if depr_schedule_len_diff <= 0 or not subsets_result: - for i, schedule in reversed(list(enumerate(self.depreciation_schedule))): - diff = abs(diff) - - if diff <= 0: - break - - shift_factor = flt(asset_shift_factors_map.get(schedule.shift)) - - if shift_factor <= diff: - for sf in desc_shift_factors: - if sf - shift_factor <= diff: - self.depreciation_schedule[i].shift = reverse_asset_shift_factors_map.get(sf) - diff -= sf - shift_factor - break - else: - try: - self.depreciation_schedule[i].shift = reverse_asset_shift_factors_map.get( - shift_factor + diff - ) - diff = 0 - except Exception: - frappe.throw( - _("Could not auto update shifts. Shift with shift factor {0} needed.") - ).format(shift_factor + diff) - def create_new_asset_depr_schedule(self): new_asset_depr_schedule_doc = frappe.copy_doc(self.asset_depr_schedule_doc) @@ -259,17 +224,3 @@ class AssetShiftAllocation(Document): get_link_to_form(self.doctype, self.name) ), ) - - -def find_subsets_with_sum(numbers, k, target_sum, current_subset, result): - if k == 0 and target_sum == 0: - result.append(current_subset.copy()) - return - if k <= 0 or target_sum <= 0 or not numbers: - return - - # Include the current number in the subset - find_subsets_with_sum(numbers, k - 1, target_sum - numbers[0], [*current_subset, numbers[0]], result) - - # Exclude the current number from the subset - find_subsets_with_sum(numbers[1:], k, target_sum, current_subset, result) diff --git a/erpnext/patches/v15_0/update_journal_entry_type.py b/erpnext/patches/v15_0/update_journal_entry_type.py index 32a499d01af..0d243bac2f7 100644 --- a/erpnext/patches/v15_0/update_journal_entry_type.py +++ b/erpnext/patches/v15_0/update_journal_entry_type.py @@ -6,6 +6,7 @@ def execute(): "Property Setter", {"doc_type": "Journal Entry", "field_name": "voucher_type", "property": "options"}, ["name", "value"], + as_dict=True, ) if custom_je_type: custom_je_type.value += "\nAsset Disposal" From fe52e802ce260837ba7a3506f0aabe99f889bfee Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Tue, 21 Jan 2025 11:38:57 +0530 Subject: [PATCH 12/40] refactor: depreciation booking code --- erpnext/assets/doctype/asset/depreciation.py | 266 ++++++++---------- erpnext/assets/doctype/asset/test_asset.py | 46 ++- .../asset_depreciation_schedule.js | 2 +- .../asset_depreciation_schedule.py | 8 - .../depreciation_methods.py | 5 - .../doctype/asset_repair/asset_repair.py | 2 +- 6 files changed, 149 insertions(+), 180 deletions(-) diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index 868441d60c3..3ef116ffa0a 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -40,72 +40,43 @@ def post_depreciation_entries(date=None): ): return - if not date: - date = today() + date = date or today() + book_depreciation_entries(date) - failed_asset_names = [] - error_log_names = [] - depreciable_asset_depr_schedules_data = get_depreciable_asset_depr_schedules_data(date) - - credit_and_debit_accounts_for_asset_category_and_company = {} - depreciation_cost_center_and_depreciation_series_for_company = ( - get_depreciation_cost_center_and_depreciation_series_for_company() - ) +def book_depreciation_entries(date): + # Process depreciation entries for all depreciable assets + failed_assets, error_logs = [], [] + depreciable_assets_data = get_depreciable_assets_data(date) accounting_dimensions = get_checks_for_pl_and_bs_accounts() - for asset_depr_schedule_data in depreciable_asset_depr_schedules_data: - ( - asset_depr_schedule_name, - asset_name, - asset_category, - asset_company, - sch_start_idx, - sch_end_idx, - ) = asset_depr_schedule_data - - if ( - asset_category, - asset_company, - ) not in credit_and_debit_accounts_for_asset_category_and_company: - credit_and_debit_accounts_for_asset_category_and_company.update( - { - ( - asset_category, - asset_company, - ): get_credit_and_debit_accounts_for_asset_category_and_company( - asset_category, asset_company - ), - } - ) + for data in depreciable_assets_data: + (depr_schedule_name, asset_name, sch_start_idx, sch_end_idx) = data try: make_depreciation_entry( - asset_depr_schedule_name, + depr_schedule_name, date, sch_start_idx, sch_end_idx, - credit_and_debit_accounts_for_asset_category_and_company[(asset_category, asset_company)], - depreciation_cost_center_and_depreciation_series_for_company[asset_company], accounting_dimensions, ) frappe.db.commit() except Exception as e: frappe.db.rollback() - failed_asset_names.append(asset_name) + failed_assets.append(asset_name) error_log = frappe.log_error(e) - error_log_names.append(error_log.name) - - if failed_asset_names: - set_depr_entry_posting_status_for_failed_assets(failed_asset_names) - notify_depr_entry_posting_error(failed_asset_names, error_log_names) + error_logs.append(error_log.name) + if failed_assets: + set_depr_entry_posting_status_for_failed_assets(failed_assets) + notify_depr_entry_posting_error(failed_assets, error_logs) frappe.db.commit() -def get_depreciable_asset_depr_schedules_data(date): +def get_depreciable_assets_data(date): a = frappe.qb.DocType("Asset") ads = frappe.qb.DocType("Asset Depreciation Schedule") ds = frappe.qb.DocType("Depreciation Schedule") @@ -116,7 +87,7 @@ def get_depreciable_asset_depr_schedules_data(date): .on(ads.asset == a.name) .join(ds) .on(ads.name == ds.parent) - .select(ads.name, a.name, a.asset_category, a.company, Min(ds.idx) - 1, Max(ds.idx)) + .select(ads.name, a.name, Min(ds.idx) - 1, Max(ds.idx)) .where(a.calculate_depreciation == 1) .where(a.docstatus == 1) .where(ads.docstatus == 1) @@ -138,8 +109,8 @@ def get_depreciable_asset_depr_schedules_data(date): def make_depreciation_entry_on_disposal(asset_doc, disposal_date=None): for row in asset_doc.get("finance_books"): - asset_depr_schedule_name = get_asset_depr_schedule_name(asset_doc.name, "Active", row.finance_book) - make_depreciation_entry(asset_depr_schedule_name, disposal_date) + depr_schedule_name = get_asset_depr_schedule_name(asset_doc.name, "Active", row.finance_book) + make_depreciation_entry(depr_schedule_name, disposal_date) def get_acc_frozen_upto(): @@ -156,21 +127,26 @@ def get_acc_frozen_upto(): return -def get_credit_and_debit_accounts_for_asset_category_and_company(asset_category, company): - ( - _, - accumulated_depreciation_account, - depreciation_expense_account, - ) = get_depreciation_accounts(asset_category, company) +def get_credit_debit_accounts_for_asset(asset_category, company): + # Returns credit and debit accounts for the given asset category and company. + (_, accumulated_depr_account, depr_expense_account) = get_depreciation_accounts(asset_category, company) credit_account, debit_account = get_credit_and_debit_accounts( - accumulated_depreciation_account, depreciation_expense_account + accumulated_depr_account, depr_expense_account ) return (credit_account, debit_account) -def get_depreciation_cost_center_and_depreciation_series_for_company(): +def get_depreciation_cost_center_and_series(asset): + depreciation_cost_center, depreciation_series = frappe.get_cached_value( + "Company", asset.company, ["depreciation_cost_center", "series_for_depreciation_entry"] + ) + depreciation_cost_center = asset.cost_center or depreciation_cost_center + return depreciation_cost_center, depreciation_series + + +def get_depr_cost_center_and_series(): company_names = frappe.db.get_all("Company", pluck="name") res = {} @@ -179,91 +155,70 @@ def get_depreciation_cost_center_and_depreciation_series_for_company(): depreciation_cost_center, depreciation_series = frappe.get_cached_value( "Company", company_name, ["depreciation_cost_center", "series_for_depreciation_entry"] ) - res.update({company_name: (depreciation_cost_center, depreciation_series)}) + res.setdefault(company_name, (depreciation_cost_center, depreciation_series)) return res @frappe.whitelist() def make_depreciation_entry( - asset_depr_schedule_name, + depr_schedule_name, date=None, sch_start_idx=None, sch_end_idx=None, - credit_and_debit_accounts=None, - depreciation_cost_center_and_depreciation_series=None, accounting_dimensions=None, ): frappe.has_permission("Journal Entry", throw=True) + date = date or today() - if not date: - date = today() + depr_schedule_doc = frappe.get_doc("Asset Depreciation Schedule", depr_schedule_name) + asset = frappe.get_doc("Asset", depr_schedule_doc.asset) - asset_depr_schedule_doc = frappe.get_doc("Asset Depreciation Schedule", asset_depr_schedule_name) + credit_account, debit_account = get_credit_debit_accounts_for_asset(asset.asset_category, asset.company) + depr_cost_center, depr_series = get_depreciation_cost_center_and_series(asset) + accounting_dimensions = accounting_dimensions or get_checks_for_pl_and_bs_accounts() + depr_posting_error = None - asset = frappe.get_doc("Asset", asset_depr_schedule_doc.asset) - - if credit_and_debit_accounts: - credit_account, debit_account = credit_and_debit_accounts - else: - credit_account, debit_account = get_credit_and_debit_accounts_for_asset_category_and_company( - asset.asset_category, asset.company - ) - - if depreciation_cost_center_and_depreciation_series: - depreciation_cost_center, depreciation_series = depreciation_cost_center_and_depreciation_series - else: - depreciation_cost_center, depreciation_series = frappe.get_cached_value( - "Company", asset.company, ["depreciation_cost_center", "series_for_depreciation_entry"] - ) - - depreciation_cost_center = asset.cost_center or depreciation_cost_center - - if not accounting_dimensions: - accounting_dimensions = get_checks_for_pl_and_bs_accounts() - - depreciation_posting_error = None - - for d in asset_depr_schedule_doc.get("depreciation_schedule")[ - sch_start_idx or 0 : sch_end_idx or len(asset_depr_schedule_doc.get("depreciation_schedule")) + for d in depr_schedule_doc.get("depreciation_schedule")[ + (sch_start_idx or 0) : (sch_end_idx or len(depr_schedule_doc.get("depreciation_schedule"))) ]: try: _make_journal_entry_for_depreciation( - asset_depr_schedule_doc, + depr_schedule_doc, asset, date, d, sch_start_idx, sch_end_idx, - depreciation_cost_center, - depreciation_series, + depr_cost_center, + depr_series, credit_account, debit_account, accounting_dimensions, ) except Exception as e: - depreciation_posting_error = e + depr_posting_error = e asset.reload() asset.set_status() - if not depreciation_posting_error: + if not depr_posting_error: asset.db_set("depr_entry_posting_status", "Successful") - asset_depr_schedule_doc.reload() - return asset_depr_schedule_doc + depr_schedule_doc.reload() + return depr_schedule_doc - raise depreciation_posting_error + raise depr_posting_error def _make_journal_entry_for_depreciation( - asset_depr_schedule_doc, + depr_schedule_doc, asset, date, depr_schedule, sch_start_idx, sch_end_idx, - depreciation_cost_center, - depreciation_series, + depr_cost_center, + depr_series, credit_account, debit_account, accounting_dimensions, @@ -274,45 +229,11 @@ def _make_journal_entry_for_depreciation( return je = frappe.new_doc("Journal Entry") - je.voucher_type = "Depreciation Entry" - je.naming_series = depreciation_series - je.posting_date = depr_schedule.schedule_date - je.company = asset.company - je.finance_book = asset_depr_schedule_doc.finance_book - je.remark = f"Depreciation Entry against {asset.name} worth {depr_schedule.depreciation_amount}" + setup_journal_entry_metadata(je, depr_schedule_doc, depr_series, depr_schedule, asset) - credit_entry = { - "account": credit_account, - "credit_in_account_currency": depr_schedule.depreciation_amount, - "reference_type": "Asset", - "reference_name": asset.name, - "cost_center": depreciation_cost_center, - } - - debit_entry = { - "account": debit_account, - "debit_in_account_currency": depr_schedule.depreciation_amount, - "reference_type": "Asset", - "reference_name": asset.name, - "cost_center": depreciation_cost_center, - } - - for dimension in accounting_dimensions: - if asset.get(dimension["fieldname"]) or dimension.get("mandatory_for_bs"): - credit_entry.update( - { - dimension["fieldname"]: asset.get(dimension["fieldname"]) - or dimension.get("default_dimension") - } - ) - - if asset.get(dimension["fieldname"]) or dimension.get("mandatory_for_pl"): - debit_entry.update( - { - dimension["fieldname"]: asset.get(dimension["fieldname"]) - or dimension.get("default_dimension") - } - ) + credit_entry, debit_entry = get_credit_and_debit_entry( + credit_account, depr_schedule, asset, depr_cost_center, debit_account, accounting_dimensions + ) je.append("accounts", credit_entry) je.append("accounts", debit_entry) @@ -324,6 +245,47 @@ def _make_journal_entry_for_depreciation( je.submit() +def setup_journal_entry_metadata(je, depr_schedule_doc, depr_series, depr_schedule, asset): + je.voucher_type = "Depreciation Entry" + je.naming_series = depr_series + je.posting_date = depr_schedule.schedule_date + je.company = asset.company + je.finance_book = depr_schedule_doc.finance_book + je.remark = f"Depreciation Entry against {asset.name} worth {depr_schedule.depreciation_amount}" + + +def get_credit_and_debit_entry( + credit_account, depr_schedule, asset, depr_cost_center, debit_account, dimensions +): + credit_entry = { + "account": credit_account, + "credit_in_account_currency": depr_schedule.depreciation_amount, + "reference_type": "Asset", + "reference_name": asset.name, + "cost_center": depr_cost_center, + } + + debit_entry = { + "account": debit_account, + "debit_in_account_currency": depr_schedule.depreciation_amount, + "reference_type": "Asset", + "reference_name": asset.name, + "cost_center": depr_cost_center, + } + + for dimension in dimensions: + if asset.get(dimension["fieldname"]) or dimension.get("mandatory_for_bs"): + credit_entry[dimension["fieldname"]] = asset.get(dimension["fieldname"]) or dimension.get( + "default_dimension" + ) + + if asset.get(dimension["fieldname"]) or dimension.get("mandatory_for_pl"): + debit_entry[dimension["fieldname"]] = asset.get(dimension["fieldname"]) or dimension.get( + "default_dimension" + ) + return credit_entry, debit_entry + + def get_depreciation_accounts(asset_category, company): fixed_asset_account = accumulated_depreciation_account = depreciation_expense_account = None @@ -394,7 +356,24 @@ def notify_depr_entry_posting_error(failed_asset_names, error_log_names): asset_links = get_comma_separated_links(failed_asset_names, "Asset") error_log_links = get_comma_separated_links(error_log_names, "Error Log") - message = ( + message = get_message_for_depr_entry_posting_error(asset_links, error_log_links) + + frappe.sendmail(recipients=recipients, subject=subject, message=message) + + +def get_comma_separated_links(names, doctype): + links = [] + + for name in names: + links.append(get_link_to_form(doctype, name)) + + links = ", ".join(links) + + return links + + +def get_message_for_depr_entry_posting_error(asset_links, error_log_links): + return ( _("Hello,") + "

" + _("The following assets have failed to automatically post depreciation entries: {0}").format( @@ -410,19 +389,6 @@ def notify_depr_entry_posting_error(failed_asset_names, error_log_names): + _("Please share this email with your support team so that they can find and fix the issue.") ) - frappe.sendmail(recipients=recipients, subject=subject, message=message) - - -def get_comma_separated_links(names, doctype): - links = [] - - for name in names: - links.append(get_link_to_form(doctype, name)) - - links = ", ".join(links) - - return links - @frappe.whitelist() def scrap_asset(asset_name, scrap_date=None): diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 7162e8f5a1e..3aaaf6e696f 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -30,14 +30,9 @@ from erpnext.assets.doctype.asset.depreciation import ( scrap_asset, ) from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import ( - _check_is_pro_rata, - _get_pro_rata_amt, get_asset_depr_schedule_doc, get_depr_schedule, ) -from erpnext.erpnext.assets.doctype.asset_depreciation_schedule.deppreciation_schedule_controller import ( - get_depreciation_amount, -) from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( make_purchase_invoice as make_invoice, ) @@ -260,7 +255,13 @@ class TestAsset(AssetSetup): asset.gross_purchase_amount - asset.finance_books[0].value_after_depreciation, asset.precision("gross_purchase_amount"), ) - pro_rata_amount, _, _ = _get_pro_rata_amt( + + second_asset_depr_schedule.depreciation_amount = 9000 + second_asset_depr_schedule.asset_doc = asset + second_asset_depr_schedule.get_finance_book_row() + second_asset_depr_schedule.fetch_asset_details() + + pro_rata_amount, _, _ = second_asset_depr_schedule._get_pro_rata_amt( asset.finance_books[0], 9000, add_days(get_last_day(add_months(purchase_date, 1)), 1), @@ -346,9 +347,12 @@ class TestAsset(AssetSetup): self.assertEqual(second_asset_depr_schedule.status, "Active") self.assertEqual(first_asset_depr_schedule.status, "Cancelled") - pro_rata_amount, _, _ = _get_pro_rata_amt( - asset.finance_books[0], - 9000, + second_asset_depr_schedule.depreciation_amount = 9000 + second_asset_depr_schedule.asset_doc = asset + second_asset_depr_schedule.get_finance_book_row() + second_asset_depr_schedule.fetch_asset_details() + + pro_rata_amount, _, _ = second_asset_depr_schedule._get_pro_rata_amt( add_days(get_last_day(add_months(purchase_date, 1)), 1), date, original_schedule_date=get_last_day(nowdate()), @@ -1029,7 +1033,7 @@ class TestDepreciationBasics(AssetSetup): asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active") - depreciation_amount, prev_per_day_depr = get_depreciation_amount( + depreciation_amount = asset_depr_schedule_doc.get_depreciation_amount( asset_depr_schedule_doc, asset, 100000, 100000, asset.finance_books[0] ) self.assertEqual(depreciation_amount, 30000) @@ -1076,7 +1080,7 @@ class TestDepreciationBasics(AssetSetup): def test_check_is_pro_rata(self): """Tests if check_is_pro_rata() returns the right value(i.e. checks if has_pro_rata is accurate).""" - asset = create_asset(item_code="Macbook Pro", available_for_use_date="2019-12-31", do_not_save=1) + asset = create_asset(item_code="Macbook Pro", available_for_use_date="2019-12-31") asset.calculate_depreciation = 1 asset.append( @@ -1089,9 +1093,15 @@ class TestDepreciationBasics(AssetSetup): "depreciation_start_date": "2020-12-31", }, ) + asset.save() - has_pro_rata = _check_is_pro_rata(asset, asset.finance_books[0]) - self.assertFalse(has_pro_rata) + depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Draft") + depr_schedule_doc.asset_doc = asset + depr_schedule_doc.get_finance_book_row() + depr_schedule_doc.fetch_asset_details() + depr_schedule_doc._check_is_pro_rata() + + self.assertFalse(depr_schedule_doc.has_pro_rata) asset.finance_books = [] asset.append( @@ -1104,9 +1114,15 @@ class TestDepreciationBasics(AssetSetup): "depreciation_start_date": "2020-07-01", }, ) + asset.save() - has_pro_rata = _check_is_pro_rata(asset, asset.finance_books[0]) - self.assertTrue(has_pro_rata) + depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Draft") + depr_schedule_doc.asset_doc = asset + depr_schedule_doc.get_finance_book_row() + depr_schedule_doc.fetch_asset_details() + depr_schedule_doc._check_is_pro_rata() + + self.assertTrue(depr_schedule_doc.has_pro_rata) def test_expected_value_after_useful_life_greater_than_purchase_amount(self): """Tests if an error is raised when expected_value_after_useful_life(110,000) > gross_purchase_amount(100,000).""" diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.js b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.js index 3f7a2e7c7d8..f76f1d11720 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.js +++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.js @@ -31,7 +31,7 @@ frappe.ui.form.on("Depreciation Schedule", { frappe.call({ method: "erpnext.assets.doctype.asset.depreciation.make_depreciation_entry", args: { - asset_depr_schedule_name: frm.doc.name, + depr_schedule_name: frm.doc.name, date: row.schedule_date, }, debounce: 1000, 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 e0e60d7cdfd..1750a2a782f 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py @@ -4,18 +4,10 @@ import frappe from frappe import _ from frappe.utils import ( - add_days, - add_months, - add_years, - cint, - date_diff, flt, get_first_day, - get_last_day, get_link_to_form, getdate, - is_last_day_of_the_month, - month_diff, ) import erpnext diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/depreciation_methods.py b/erpnext/assets/doctype/asset_depreciation_schedule/depreciation_methods.py index da7962db63e..961f7c70db1 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/depreciation_methods.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/depreciation_methods.py @@ -1,13 +1,8 @@ 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 diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py index 27569eb6cce..8a4349233e5 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/asset_repair.py @@ -4,7 +4,7 @@ import frappe from frappe import _ from frappe.query_builder import DocType -from frappe.utils import add_months, cint, flt, get_link_to_form, getdate, time_diff_in_hours +from frappe.utils import cint, flt, get_link_to_form, getdate, time_diff_in_hours import erpnext from erpnext.accounts.general_ledger import make_gl_entries From 48311ee5c5c24da18323d1f3da6f677d86af4ae9 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Thu, 23 Jan 2025 12:59:04 +0530 Subject: [PATCH 13/40] fix: set depreciation rate before return --- erpnext/assets/doctype/asset/asset.json | 1 - erpnext/assets/doctype/asset/asset.py | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index b18f122721b..8ea5e87af3c 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -405,7 +405,6 @@ "read_only": 1 }, { - "collapsible": 1, "collapsible_depends_on": "is_existing_asset", "fieldname": "purchase_details_section", "fieldtype": "Section Break", diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 6a215040679..491577e6e2a 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -132,11 +132,11 @@ class Asset(AccountsController): self.status = self.get_status() def create_asset_depreciation_schedule(self): + self.set_depr_rate_and_value_after_depreciation() + 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) @@ -1242,7 +1242,7 @@ def update_existing_asset(asset, remaining_qty, new_asset_name): current_asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active", row.finance_book) new_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc) - new_asset_depr_schedule_doc.fetch_asset_details(asset, row) + new_asset_depr_schedule_doc.fetch_asset_details() accumulated_depreciation = 0 @@ -1299,7 +1299,7 @@ def create_new_asset_after_split(asset, split_qty): continue new_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc) - new_asset_depr_schedule_doc.fetch_asset_details(new_asset, row) + new_asset_depr_schedule_doc.fetch_asset_details() accumulated_depreciation = 0 From 223e8e1bdbafd2c3b054597e6864b5db02f7b4b6 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Thu, 23 Jan 2025 13:04:28 +0530 Subject: [PATCH 14/40] fix(test): set root type to Expense --- erpnext/assets/doctype/asset/test_asset.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 3aaaf6e696f..8478c9b6e89 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -1634,6 +1634,10 @@ class TestDepreciationBasics(AssetSetup): frequency_of_depreciation=1, submit=1, ) + depr_expense_account = frappe.get_doc("Account", "_Test Depreciations - _TC") + depr_expense_account.root_type = "Expense" + depr_expense_account.parent_account = "Expenses - _TC" + depr_expense_account.save() self.assertEqual(asset.status, "Submitted") self.assertEqual(asset.get_value_after_depreciation(), 100000) From c7cc7d61d13b0bdd635a9b87db35d588c0df536f Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Wed, 29 Jan 2025 15:42:09 +0530 Subject: [PATCH 15/40] fix(test): value after depreciation for asset doc --- erpnext/accounts/doctype/journal_entry/journal_entry.py | 6 ++---- erpnext/assets/doctype/asset/asset.json | 1 + erpnext/assets/doctype/asset/test_asset.py | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 4a79b6ceb2c..456fccd406c 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -397,9 +397,8 @@ class JournalEntry(AccountsController): if asset.calculate_depreciation: self.update_journal_entry_link_on_depr_schedule(asset, d) self.update_value_after_depreciation(asset, d.debit) - else: - asset.db_set("value_after_depreciation", asset.value_after_depreciation - d.debit) + asset.db_set("value_after_depreciation", asset.value_after_depreciation - d.debit) asset.set_status() asset.set_total_booked_depreciations() @@ -549,8 +548,7 @@ class JournalEntry(AccountsController): fb_row = asset.get("finance_books")[fb_idx - 1] fb_row.value_after_depreciation += d.debit fb_row.db_update() - 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_total_booked_depreciations() elif self.voucher_type == "Journal Entry" and d.reference_type == "Asset" and d.reference_name: diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index 8ea5e87af3c..72f674edfa0 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -428,6 +428,7 @@ "fieldname": "split_from", "fieldtype": "Link", "label": "Split From", + "no_copy": 1, "options": "Asset", "read_only": 1 }, diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 8478c9b6e89..382b6840ac0 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -1537,7 +1537,7 @@ class TestDepreciationBasics(AssetSetup): ) self.assertSequenceEqual(gle, expected_gle) - self.assertEqual(asset.get("value_after_depreciation"), 0) + self.assertEqual(asset.get("value_after_depreciation"), 70000) def test_expected_value_change(self): """ From 8f1044a065442e873f7fce231e62ec872acf7b26 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Tue, 4 Feb 2025 16:37:40 +0530 Subject: [PATCH 16/40] refactor: cleanup Assset Splitting code --- erpnext/assets/doctype/asset/asset.json | 1 + erpnext/assets/doctype/asset/asset.py | 307 +++++++++++++----------- 2 files changed, 170 insertions(+), 138 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index 72f674edfa0..dfc7949c544 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -483,6 +483,7 @@ "read_only": 1 }, { + "default": "0", "depends_on": "eval:doc.docstatus > 0", "fieldname": "additional_asset_cost", "fieldtype": "Currency", diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 491577e6e2a..db020eee8c2 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -128,7 +128,7 @@ class Asset(AccountsController): self.set_missing_values() self.validate_gross_and_purchase_amount() self.validate_finance_books() - self.total_asset_cost = self.gross_purchase_amount + self.total_asset_cost = self.gross_purchase_amount + self.additional_asset_cost self.status = self.get_status() def create_asset_depreciation_schedule(self): @@ -154,8 +154,13 @@ class Asset(AccountsController): self.show_schedule_creation_message(schedules) def set_depr_rate_and_value_after_depreciation(self): - self.value_after_depreciation = flt(self.gross_purchase_amount) - flt( - self.opening_accumulated_depreciation + if self.split_from: + return + + self.value_after_depreciation = ( + flt(self.gross_purchase_amount) + - flt(self.opening_accumulated_depreciation) + + flt(self.additional_asset_cost) ) if self.calculate_depreciation: self.set_depreciation_rate() @@ -1187,166 +1192,203 @@ def get_values_from_purchase_doc(purchase_doc_name, item_code, doctype): @frappe.whitelist() def split_asset(asset_name, split_qty): - asset = frappe.get_doc("Asset", asset_name) + """Split an asset into two based on the given quantity.""" + existing_asset = frappe.get_doc("Asset", asset_name) split_qty = cint(split_qty) - if split_qty >= asset.asset_quantity: - frappe.throw(_("Split qty cannot be grater than or equal to asset qty")) + validate_split_quantity(existing_asset, split_qty) + remaining_qty = existing_asset.asset_quantity - split_qty - remaining_qty = asset.asset_quantity - split_qty + # Create new asset and update existing one + splitted_asset = create_new_asset_from_split(existing_asset, split_qty) + update_existing_asset_after_split(existing_asset, remaining_qty, splitted_asset) - new_asset = create_new_asset_after_split(asset, split_qty) - update_existing_asset(asset, remaining_qty, new_asset.name) + return splitted_asset + +def validate_split_quantity(existing_asset, split_qty): + if split_qty >= existing_asset.asset_quantity: + frappe.throw(_("Split Quantity must be less than Asset Quantity")) + + +def create_new_asset_from_split(existing_asset, split_qty): + """Create a new asset from the split quantity.""" + return process_asset_split(existing_asset, split_qty, is_new_asset=True) + + +def update_existing_asset_after_split(existing_asset, remaining_qty, splitted_asset): + """Update the existing asset with the remaining quantity.""" + process_asset_split(existing_asset, remaining_qty, splitted_asset=splitted_asset) + + +def process_asset_split(existing_asset, split_qty, splitted_asset=None, is_new_asset=False): + """Handle asset creation or update during the split.""" + scaling_factor = flt(split_qty) / flt(existing_asset.asset_quantity) + new_asset = frappe.copy_doc(existing_asset) if is_new_asset else splitted_asset + asset_doc = new_asset if is_new_asset else existing_asset + + set_split_asset_values(asset_doc, scaling_factor, split_qty, existing_asset, is_new_asset) + log_asset_activity(existing_asset, asset_doc, splitted_asset, is_new_asset) + + # Update finance books and depreciation schedules + update_finance_books(asset_doc, existing_asset, new_asset, scaling_factor, is_new_asset) return new_asset -def update_existing_asset(asset, remaining_qty, new_asset_name): - remaining_gross_purchase_amount = flt( - (asset.gross_purchase_amount * remaining_qty) / asset.asset_quantity - ) - opening_accumulated_depreciation = flt( - (asset.opening_accumulated_depreciation * remaining_qty) / asset.asset_quantity +def set_split_asset_values(asset_doc, scaling_factor, split_qty, existing_asset, is_new_asset): + asset_doc.gross_purchase_amount = existing_asset.gross_purchase_amount * scaling_factor + asset_doc.purchase_amount = existing_asset.gross_purchase_amount + asset_doc.additional_asset_cost = existing_asset.additional_asset_cost * scaling_factor + asset_doc.total_asset_cost = asset_doc.gross_purchase_amount + asset_doc.additional_asset_cost + asset_doc.opening_accumulated_depreciation = ( + existing_asset.opening_accumulated_depreciation * scaling_factor ) + asset_doc.value_after_depreciation = existing_asset.value_after_depreciation * scaling_factor + asset_doc.asset_quantity = split_qty + asset_doc.split_from = existing_asset.name if is_new_asset else None - frappe.db.set_value( - "Asset", - asset.name, - { - "opening_accumulated_depreciation": opening_accumulated_depreciation, - "gross_purchase_amount": remaining_gross_purchase_amount, - "asset_quantity": remaining_qty, - }, - ) + for row in asset_doc.get("finance_books"): + row.value_after_depreciation = row.value_after_depreciation * scaling_factor + row.expected_value_after_useful_life = row.expected_value_after_useful_life * scaling_factor - add_asset_activity( - asset.name, - _("Asset updated after being split into Asset {0}").format(get_link_to_form("Asset", new_asset_name)), - ) + if not is_new_asset: + asset_doc.flags.ignore_validate_update_after_submit = True + asset_doc.save() - for row in asset.get("finance_books"): - value_after_depreciation = flt((row.value_after_depreciation * remaining_qty) / asset.asset_quantity) - expected_value_after_useful_life = flt( - (row.expected_value_after_useful_life * remaining_qty) / asset.asset_quantity + +def log_asset_activity(existing_asset, asset_doc, splitted_asset, is_new_asset): + if is_new_asset: + asset_doc.insert() + add_asset_activity( + asset_doc.name, + _("Asset created after being split from Asset {0}").format( + get_link_to_form("Asset", existing_asset.name) + ), ) - frappe.db.set_value( - "Asset Finance Book", row.name, "value_after_depreciation", value_after_depreciation - ) - frappe.db.set_value( - "Asset Finance Book", - row.name, - "expected_value_after_useful_life", - expected_value_after_useful_life, + asset_doc.submit() + asset_doc.set_status() + else: + add_asset_activity( + existing_asset.name, + _("Asset updated after being split into Asset {0}").format( + get_link_to_form("Asset", splitted_asset.name) + ), ) - current_asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active", row.finance_book) - new_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc) - new_asset_depr_schedule_doc.fetch_asset_details() +def update_finance_books(asset_doc, existing_asset, new_asset, scaling_factor, is_new_asset): + """Update finance books and depreciation schedules for the asset.""" + for fb_row in asset_doc.get("finance_books"): + reschedule_depr_for_updated_asset(existing_asset, new_asset, fb_row, scaling_factor, is_new_asset) - accumulated_depreciation = 0 - - for term in new_asset_depr_schedule_doc.get("depreciation_schedule"): - depreciation_amount = flt((term.depreciation_amount * remaining_qty) / asset.asset_quantity) - term.depreciation_amount = depreciation_amount - accumulated_depreciation += depreciation_amount - term.accumulated_depreciation_amount = accumulated_depreciation - - notes = _( - "This schedule was created when Asset {0} was updated after being split into new Asset {1}." - ).format(get_link_to_form(asset.doctype, asset.name), get_link_to_form(asset.doctype, new_asset_name)) - new_asset_depr_schedule_doc.notes = notes - - current_asset_depr_schedule_doc.flags.should_not_cancel_depreciation_entries = True - current_asset_depr_schedule_doc.cancel() - - new_asset_depr_schedule_doc.submit() + # Add references in journal entries for new asset + if is_new_asset: + for row in new_asset.get("finance_books"): + depr_schedule_doc = get_depr_schedule(new_asset.name, "Active", row.finance_book) + for schedule in depr_schedule_doc: + if schedule.journal_entry: + add_reference_in_jv_on_split( + schedule.journal_entry, + new_asset.name, + existing_asset.name, + schedule.depreciation_amount, + ) -def create_new_asset_after_split(asset, split_qty): - new_asset = frappe.copy_doc(asset) - new_gross_purchase_amount = flt((asset.gross_purchase_amount * split_qty) / asset.asset_quantity) - opening_accumulated_depreciation = flt( - (asset.opening_accumulated_depreciation * split_qty) / asset.asset_quantity +def reschedule_depr_for_updated_asset(existing_asset, new_asset, fb_row, scaling_factor, is_new_asset): + """Reschedule depreciation for an asset after a split.""" + current_depr_schedule_doc = get_asset_depr_schedule_doc( + existing_asset.name, "Active", fb_row.finance_book + ) + if not current_depr_schedule_doc: + return + + # Create a new depreciation schedule based on the current one + new_depr_schedule_doc = create_new_depr_schedule( + current_depr_schedule_doc, existing_asset, new_asset, is_new_asset, fb_row ) - new_asset.gross_purchase_amount = new_gross_purchase_amount - if asset.purchase_amount: - new_asset.purchase_amount = new_gross_purchase_amount - new_asset.opening_accumulated_depreciation = opening_accumulated_depreciation - new_asset.asset_quantity = split_qty - new_asset.split_from = asset.name + update_depreciation_terms(new_depr_schedule_doc, scaling_factor) + add_depr_schedule_notes(new_depr_schedule_doc, existing_asset, new_asset, is_new_asset) - for row in new_asset.get("finance_books"): - row.value_after_depreciation = flt((row.value_after_depreciation * split_qty) / asset.asset_quantity) - row.expected_value_after_useful_life = flt( - (row.expected_value_after_useful_life * split_qty) / asset.asset_quantity + if not is_new_asset: + current_depr_schedule_doc.flags.should_not_cancel_depreciation_entries = True + current_depr_schedule_doc.cancel() + + new_depr_schedule_doc.submit() + + +def create_new_depr_schedule(current_depr_schedule_doc, existing_asset, new_asset, is_new_asset, fb_row): + """Create a new depreciation schedule based on the current one.""" + new_depr_schedule_doc = frappe.copy_doc(current_depr_schedule_doc) + new_depr_schedule_doc.asset_doc = new_asset if is_new_asset else existing_asset + new_depr_schedule_doc.fb_row = fb_row + new_depr_schedule_doc.fetch_asset_details() + return new_depr_schedule_doc + + +def update_depreciation_terms(new_depr_schedule_doc, scaling_factor): + """Update depreciation terms with scaled amounts.""" + accumulated_depreciation = 0 + for term in new_depr_schedule_doc.get("depreciation_schedule"): + depreciation_amount = flt( + term.depreciation_amount * scaling_factor, term.precision("depreciation_amount") ) + term.depreciation_amount = depreciation_amount + accumulated_depreciation = flt( + accumulated_depreciation + depreciation_amount, term.precision("depreciation_amount") + ) + term.accumulated_depreciation_amount = accumulated_depreciation - new_asset.insert() - add_asset_activity( - new_asset.name, - _("Asset created after being split from Asset {0}").format(get_link_to_form("Asset", asset.name)), +def add_depr_schedule_notes(new_depr_schedule_doc, existing_asset, new_asset, is_new_asset): + notes = _("This schedule was created when Asset {0} was {1} into new Asset {2}.").format( + get_link_to_form(existing_asset.doctype, existing_asset.name), + "split" if is_new_asset else "updated after being split", + get_link_to_form(new_asset.doctype, new_asset.name), ) - - new_asset.submit() - new_asset.set_status() - - for row in new_asset.get("finance_books"): - current_asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active", row.finance_book) - if not current_asset_depr_schedule_doc: - continue - new_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc) - - new_asset_depr_schedule_doc.fetch_asset_details() - - accumulated_depreciation = 0 - - for term in new_asset_depr_schedule_doc.get("depreciation_schedule"): - depreciation_amount = flt((term.depreciation_amount * split_qty) / asset.asset_quantity) - term.depreciation_amount = depreciation_amount - accumulated_depreciation += depreciation_amount - term.accumulated_depreciation_amount = accumulated_depreciation - - notes = _("This schedule was created when new Asset {0} was split from Asset {1}.").format( - get_link_to_form(new_asset.doctype, new_asset.name), get_link_to_form(asset.doctype, asset.name) - ) - new_asset_depr_schedule_doc.notes = notes - - new_asset_depr_schedule_doc.submit() - - for row in new_asset.get("finance_books"): - depr_schedule = get_depr_schedule(new_asset.name, "Active", row.finance_book) - for term in depr_schedule: - # Update references in JV - if term.journal_entry: - add_reference_in_jv_on_split( - term.journal_entry, new_asset.name, asset.name, term.depreciation_amount - ) - - return new_asset + new_depr_schedule_doc.notes = notes def add_reference_in_jv_on_split(entry_name, new_asset_name, old_asset_name, depreciation_amount): + """Add a reference to a new asset in a journal entry after a split.""" journal_entry = frappe.get_doc("Journal Entry", entry_name) entries_to_add = [] - idx = len(journal_entry.get("accounts")) + 1 + adjust_existing_accounts(journal_entry, old_asset_name, depreciation_amount, entries_to_add) + add_new_entries(journal_entry, entries_to_add, new_asset_name, depreciation_amount) + + # Save and repost the journal entry + journal_entry.flags.ignore_validate_update_after_submit = True + journal_entry.save() + + journal_entry.docstatus = 2 + journal_entry.make_gl_entries(1) + journal_entry.docstatus = 1 + journal_entry.make_gl_entries() + + +def adjust_existing_accounts(journal_entry, old_asset_name, depreciation_amount, entries_to_add): + """Adjust existing accounts and prepare new entries for the new asset.""" for account in journal_entry.get("accounts"): if account.reference_name == old_asset_name: entries_to_add.append(frappe.copy_doc(account).as_dict()) - if account.credit: - account.credit = account.credit - depreciation_amount - account.credit_in_account_currency = ( - account.credit_in_account_currency - account.exchange_rate * depreciation_amount - ) - elif account.debit: - account.debit = account.debit - depreciation_amount - account.debit_in_account_currency = ( - account.debit_in_account_currency - account.exchange_rate * depreciation_amount - ) + adjust_account_balance(account, depreciation_amount) + +def adjust_account_balance(account, depreciation_amount): + """Adjust the balance of an account based on the depreciation amount.""" + if account.credit: + account.credit -= depreciation_amount + account.credit_in_account_currency -= account.exchange_rate * depreciation_amount + elif account.debit: + account.debit -= depreciation_amount + account.debit_in_account_currency -= account.exchange_rate * depreciation_amount + + +def add_new_entries(journal_entry, entries_to_add, new_asset_name, depreciation_amount): + """Add new entries for the new asset to the journal entry.""" + idx = len(journal_entry.get("accounts")) + 1 for entry in entries_to_add: entry.reference_name = new_asset_name if entry.credit: @@ -1355,17 +1397,6 @@ def add_reference_in_jv_on_split(entry_name, new_asset_name, old_asset_name, dep elif entry.debit: entry.debit = depreciation_amount entry.debit_in_account_currency = entry.exchange_rate * depreciation_amount - entry.idx = idx idx += 1 - journal_entry.append("accounts", entry) - - journal_entry.flags.ignore_validate_update_after_submit = True - journal_entry.save() - - # Repost GL Entries - journal_entry.docstatus = 2 - journal_entry.make_gl_entries(1) - journal_entry.docstatus = 1 - journal_entry.make_gl_entries() From 3855536ef1aa6a0888df2be37c58bf970e8bee10 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Thu, 6 Feb 2025 15:36:48 +0530 Subject: [PATCH 17/40] fix: update status for composite asset --- erpnext/assets/doctype/asset/asset.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index db020eee8c2..4dd7d46d73e 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -615,7 +615,10 @@ class Asset(AccountsController): def get_status(self): """Returns status based on whether it is draft, submitted, scrapped or depreciated""" if self.docstatus == 0: - status = "Draft" + if self.is_composite_asset: + status = "Work In Progress" + else: + status = "Draft" elif self.docstatus == 1: status = "Submitted" From a121c30b56dc9649ddac225389496470a0709a31 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Fri, 7 Feb 2025 12:57:35 +0530 Subject: [PATCH 18/40] fix: update values of target asset on cancel of capitalization --- .../asset_capitalization/asset_capitalization.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py index 449fffc0684..eb2b79ba5e1 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -140,6 +140,7 @@ class AssetCapitalization(StockController): self.make_gl_entries() self.repost_future_sle_and_gle() self.restore_consumed_asset_items() + self.update_target_asset() def set_title(self): self.title = self.target_asset_name or self.target_item_name or self.target_item_code @@ -602,10 +603,14 @@ class AssetCapitalization(StockController): return total_target_asset_value = flt(self.total_value, self.precision("total_value")) - asset_doc = frappe.get_doc("Asset", self.target_asset) - asset_doc.gross_purchase_amount += total_target_asset_value - asset_doc.purchase_amount += total_target_asset_value + if self.docstatus == 2: + asset_doc.gross_purchase_amount -= total_target_asset_value + asset_doc.purchase_amount -= total_target_asset_value + else: + asset_doc.gross_purchase_amount += total_target_asset_value + asset_doc.purchase_amount += total_target_asset_value + asset_doc.set_status("Work In Progress") asset_doc.flags.ignore_validate = True asset_doc.save() @@ -623,7 +628,7 @@ class AssetCapitalization(StockController): self.set_consumed_asset_status(asset) if asset.calculate_depreciation: - reverse_depreciation_entry_made_on_disposal(asset, self.posting_date) + reverse_depreciation_entry_made_on_disposal(asset) notes = _( "This schedule was created when Asset {0} was restored on Asset Capitalization {1}'s cancellation." ).format( From 2391c859b2cf6fec944488e428459ef0484bf904 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Fri, 14 Feb 2025 12:52:41 +0530 Subject: [PATCH 19/40] fix: reset consumed asset's depreciation on capitalization cancel --- erpnext/assets/doctype/asset/asset.py | 14 ++++++------- .../asset_capitalization.py | 21 ++++++++++--------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 4dd7d46d73e..f118b3bf52a 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -635,13 +635,13 @@ class Asset(AccountsController): ].expected_value_after_useful_life value_after_depreciation = self.finance_books[idx].value_after_depreciation - if ( - flt(value_after_depreciation) <= expected_value_after_useful_life - or self.is_fully_depreciated - ): - status = "Fully Depreciated" - elif flt(value_after_depreciation) < flt(self.gross_purchase_amount): - status = "Partially Depreciated" + if ( + flt(value_after_depreciation) <= expected_value_after_useful_life + or self.is_fully_depreciated + ): + status = "Fully Depreciated" + elif flt(value_after_depreciation) < flt(self.gross_purchase_amount): + status = "Partially Depreciated" elif self.docstatus == 2: status = "Cancelled" return status diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py index eb2b79ba5e1..b8b285281e0 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -604,16 +604,17 @@ class AssetCapitalization(StockController): total_target_asset_value = flt(self.total_value, self.precision("total_value")) asset_doc = frappe.get_doc("Asset", self.target_asset) - if self.docstatus == 2: - asset_doc.gross_purchase_amount -= total_target_asset_value - asset_doc.purchase_amount -= total_target_asset_value - else: - asset_doc.gross_purchase_amount += total_target_asset_value - asset_doc.purchase_amount += total_target_asset_value - asset_doc.set_status("Work In Progress") - asset_doc.flags.ignore_validate = True - asset_doc.save() + if self.docstatus == 2: + gross_purchase_amount = asset_doc.gross_purchase_amount - total_target_asset_value + purchase_amount = asset_doc.purchase_amount - total_target_asset_value + asset_doc.db_set("total_asset_cost", asset_doc.total_asset_cost - total_target_asset_value) + else: + gross_purchase_amount = asset_doc.gross_purchase_amount + total_target_asset_value + purchase_amount = asset_doc.purchase_amount + total_target_asset_value + + asset_doc.db_set("gross_purchase_amount", gross_purchase_amount) + asset_doc.db_set("purchase_amount", purchase_amount) frappe.msgprint( _("Asset {0} has been updated. Please set the depreciation details if any and submit it.").format( @@ -624,7 +625,6 @@ class AssetCapitalization(StockController): def restore_consumed_asset_items(self): for item in self.asset_items: asset = frappe.get_doc("Asset", item.asset) - asset.db_set("disposal_date", None) self.set_consumed_asset_status(asset) if asset.calculate_depreciation: @@ -635,6 +635,7 @@ class AssetCapitalization(StockController): get_link_to_form(asset.doctype, asset.name), get_link_to_form(self.doctype, self.name) ) reset_depreciation_schedule(asset, notes) + asset.db_set("disposal_date", None) def set_consumed_asset_status(self, asset): if self.docstatus == 1: From 229d3634b13bd845332ea29e0d451bf1a362a78f Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Fri, 7 Mar 2025 17:05:38 +0530 Subject: [PATCH 20/40] fix(test): calculate total pending days of depreciation correctly --- .../deppreciation_schedule_controller.py | 6 +++++- .../test_asset_depreciation_schedule.py | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/deppreciation_schedule_controller.py b/erpnext/assets/doctype/asset_depreciation_schedule/deppreciation_schedule_controller.py index 277b12cfc5c..c66dee29e2a 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/deppreciation_schedule_controller.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/deppreciation_schedule_controller.py @@ -258,7 +258,11 @@ class DepreciationScheduleController(StraightLineMethod, WDVMethod): 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 + if last_depr_date: + self.total_pending_days = date_diff(self.final_schedule_date, last_depr_date) + 1 + self.total_pending_days = date_diff( + self.final_schedule_date, self.asset_doc.available_for_use_date + ) else: self.total_pending_years = self.pending_months / 12 diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py index 61e3761719e..a4f761bfb75 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py @@ -41,6 +41,7 @@ class TestAssetDepreciationSchedule(IntegrationTestCase): self.assertRaises(frappe.ValidationError, second_asset_depr_schedule.insert) def test_daily_prorata_based_depr_on_sl_method(self): + frappe.db.set_single_value("Accounts Settings", "calculate_depr_using_total_days", 0) asset = create_asset( calculate_depreciation=1, depreciation_method="Straight Line", From 6ac68ed1e73d62b231892a8e202f6f204546aa52 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Tue, 11 Mar 2025 00:19:51 +0530 Subject: [PATCH 21/40] fix(test): failing test and bug fixes for basic depreciation process --- .../deppreciation_schedule_controller.py | 36 +++--- .../depreciation_methods.py | 6 +- .../test_asset_depreciation_schedule.py | 110 ++++++------------ 3 files changed, 59 insertions(+), 93 deletions(-) diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/deppreciation_schedule_controller.py b/erpnext/assets/doctype/asset_depreciation_schedule/deppreciation_schedule_controller.py index c66dee29e2a..fd1d45f98cd 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/deppreciation_schedule_controller.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/deppreciation_schedule_controller.py @@ -102,6 +102,7 @@ class DepreciationScheduleController(StraightLineMethod, WDVMethod): self.skip_row = False self.depreciation_amount = 0 self.prev_per_day_depr = True + self.prev_depreciation_amount = 0 self.current_fiscal_year_end_date = None self.yearly_opening_wdv = self.pending_depreciation_amount self.get_number_of_pending_months() @@ -193,20 +194,11 @@ class DepreciationScheduleController(StraightLineMethod, WDVMethod): 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, + from_date = add_days( + add_months(self.fb_row.depreciation_start_date, (self.fb_row.frequency_of_depreciation * -1)), + 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) + return from_date else: return self.asset_doc.available_for_use_date @@ -259,10 +251,11 @@ class DepreciationScheduleController(StraightLineMethod, WDVMethod): if cint(frappe.db.get_single_value("Accounts Settings", "calculate_depr_using_total_days")): last_depr_date = self.get_last_booked_depreciation_date() if last_depr_date: - self.total_pending_days = date_diff(self.final_schedule_date, last_depr_date) + 1 - self.total_pending_days = date_diff( - self.final_schedule_date, self.asset_doc.available_for_use_date - ) + self.total_pending_days = date_diff(self.final_schedule_date, last_depr_date) - 1 + else: + self.total_pending_days = date_diff( + self.final_schedule_date, self.asset_doc.available_for_use_date + ) else: self.total_pending_years = self.pending_months / 12 @@ -283,9 +276,12 @@ class DepreciationScheduleController(StraightLineMethod, WDVMethod): 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 + if row_idx > 1: + self.prev_depreciation_amount = 0 + if 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( diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/depreciation_methods.py b/erpnext/assets/doctype/asset_depreciation_schedule/depreciation_methods.py index 961f7c70db1..4bea70e4049 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/depreciation_methods.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/depreciation_methods.py @@ -86,7 +86,11 @@ class WDVMethod(Document): 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 + + depreciation_amount = (yearly_amount * self.fb_row.frequency_of_depreciation) / 12 + self.prev_depreciation_amount = depreciation_amount + + return depreciation_amount else: return self.prev_depreciation_amount diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py index a4f761bfb75..44cb0a105f8 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py @@ -141,22 +141,22 @@ class TestAssetDepreciationSchedule(IntegrationTestCase): ) expected_schedules = [ - ["2024-07-31", 30.46, 295.76], - ["2024-08-31", 30.46, 326.22], - ["2024-09-30", 30.46, 356.68], - ["2024-10-31", 30.46, 387.14], - ["2024-11-30", 30.46, 417.6], - ["2024-12-31", 30.46, 448.06], - ["2025-01-31", 30.46, 478.52], - ["2025-02-28", 30.46, 508.98], - ["2025-03-31", 30.46, 539.44], - ["2025-04-30", 30.46, 569.9], - ["2025-05-31", 30.46, 600.36], - ["2025-06-30", 30.46, 630.82], - ["2025-07-31", 30.46, 661.28], - ["2025-08-31", 30.46, 691.74], - ["2025-09-30", 30.46, 722.2], - ["2025-10-10", 8.8, 731.0], + ["2024-07-31", 30.4, 295.7], + ["2024-08-31", 30.4, 326.1], + ["2024-09-30", 30.4, 356.5], + ["2024-10-31", 30.4, 386.9], + ["2024-11-30", 30.4, 417.3], + ["2024-12-31", 30.4, 447.7], + ["2025-01-31", 30.4, 478.1], + ["2025-02-28", 30.4, 508.5], + ["2025-03-31", 30.4, 538.9], + ["2025-04-30", 30.4, 569.3], + ["2025-05-31", 30.4, 599.7], + ["2025-06-30", 30.4, 630.1], + ["2025-07-31", 30.4, 660.5], + ["2025-08-31", 30.4, 690.9], + ["2025-09-30", 30.4, 721.3], + ["2025-10-10", 9.7, 731.0], ] schedules = [ [cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount] @@ -179,15 +179,15 @@ class TestAssetDepreciationSchedule(IntegrationTestCase): ) expected_schedules = [ - ["2024-12-31", 60.92, 284.07], - ["2025-03-31", 60.92, 344.99], - ["2025-06-30", 60.92, 405.91], - ["2025-09-30", 60.92, 466.83], - ["2025-12-31", 60.92, 527.75], - ["2026-03-31", 60.92, 588.67], - ["2026-06-30", 60.92, 649.59], - ["2026-09-30", 60.92, 710.51], - ["2026-11-01", 20.49, 731.0], + ["2024-12-31", 60.9, 284.05], + ["2025-03-31", 60.9, 344.95], + ["2025-06-30", 60.9, 405.85], + ["2025-09-30", 60.9, 466.75], + ["2025-12-31", 60.9, 527.65], + ["2026-03-31", 60.9, 588.55], + ["2026-06-30", 60.9, 649.45], + ["2026-09-30", 60.9, 710.35], + ["2026-11-01", 20.65, 731.0], ] schedules = [ [cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount] @@ -274,12 +274,12 @@ class TestAssetDepreciationSchedule(IntegrationTestCase): expected_schedules = [ ["2021-03-31", 4383.56, 4383.56], - ["2021-06-30", 9535.45, 13919.01], - ["2021-09-30", 9640.23, 23559.24], - ["2021-12-31", 9640.23, 33199.47], - ["2022-03-31", 9430.66, 42630.13], - ["2022-06-30", 5721.27, 48351.4], - ["2022-08-20", 51648.6, 100000.0], + ["2021-06-30", 9972.6, 14356.16], + ["2021-09-30", 10082.19, 24438.35], + ["2021-12-31", 10082.19, 34520.54], + ["2022-03-31", 6458.25, 40978.79], + ["2022-06-30", 6530.01, 47508.8], + ["2022-08-20", 52491.2, 100000.0], ] schedules = [ @@ -303,13 +303,13 @@ class TestAssetDepreciationSchedule(IntegrationTestCase): ) expected_schedules = [ - ["2020-02-29", 1092.90, 1092.90], - ["2020-08-31", 19944.01, 21036.91], - ["2021-02-28", 19618.83, 40655.74], - ["2021-08-31", 11966.4, 52622.14], - ["2022-02-28", 11771.3, 64393.44], - ["2022-08-31", 7179.84, 71573.28], - ["2023-02-20", 28426.72, 100000.0], + ["2020-02-29", 1092.9, 1092.9], + ["2020-08-31", 20109.29, 21202.19], + ["2021-02-28", 15630.03, 36832.22], + ["2021-08-31", 15889.09, 52721.31], + ["2022-02-28", 9378.02, 62099.33], + ["2022-08-31", 9533.46, 71632.79], + ["2023-02-20", 28367.21, 100000.0], ] schedules = [ @@ -377,37 +377,3 @@ class TestAssetDepreciationSchedule(IntegrationTestCase): asset.reload() self.assertEqual(asset.finance_books[0].total_number_of_booked_depreciations, 14) - - def test_schedule_for_wdv_method_for_existing_asset(self): - asset = create_asset( - calculate_depreciation=1, - depreciation_method="Written Down Value", - available_for_use_date="2020-07-17", - is_existing_asset=1, - opening_number_of_booked_depreciations=2, - opening_accumulated_depreciation=11666.67, - depreciation_start_date="2021-04-30", - total_number_of_depreciations=12, - frequency_of_depreciation=3, - gross_purchase_amount=50000, - rate_of_depreciation=40, - ) - - self.assertEqual(asset.status, "Draft") - expected_schedules = [ - ["2021-04-30", 3833.33, 15500.0], - ["2021-07-31", 3833.33, 19333.33], - ["2021-10-31", 3833.33, 23166.66], - ["2022-01-31", 3833.33, 26999.99], - ["2022-04-30", 2300.0, 29299.99], - ["2022-07-31", 2300.0, 31599.99], - ["2022-10-31", 2300.0, 33899.99], - ["2023-01-31", 2300.0, 36199.99], - ["2023-04-30", 1380.0, 37579.99], - ["2023-07-31", 12420.01, 50000.0], - ] - schedules = [ - [cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount] - for d in get_depr_schedule(asset.name, "Draft") - ] - self.assertEqual(schedules, expected_schedules) From b3ffdb6517443cbf71f79010df2ec93a7a4fb06e Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Tue, 11 Mar 2025 02:54:29 +0530 Subject: [PATCH 22/40] fix(test): increase in asset life after asset repair --- erpnext/assets/doctype/asset_repair/test_asset_repair.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset_repair/test_asset_repair.py b/erpnext/assets/doctype/asset_repair/test_asset_repair.py index 3ce3bbcb9c4..6c9b6820d45 100644 --- a/erpnext/assets/doctype/asset_repair/test_asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/test_asset_repair.py @@ -282,7 +282,9 @@ class TestAssetRepair(IntegrationTestCase): def num_of_depreciations(asset): - return asset.finance_books[0].total_number_of_depreciations + return asset.finance_books[0].total_number_of_depreciations + ( + asset.finance_books[0].increase_in_asset_life / 12 + ) def create_asset_repair(**args): From 0e7ae25f655c72a3a945b7f61786e83ca1dac975 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Tue, 11 Mar 2025 15:04:47 +0530 Subject: [PATCH 23/40] fix(test): difference amount after asset revaluation --- .../asset_value_adjustment/asset_value_adjustment.py | 7 +++---- .../test_asset_value_adjustment.py | 11 ++++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py index f27ffb0d945..2ad6146ffa6 100644 --- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py +++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py @@ -176,15 +176,14 @@ class AssetValueAdjustment(Document): difference_amount = self.difference_amount if self.docstatus == 1 else -1 * self.difference_amount asset = frappe.get_doc("Asset", self.asset) - if not asset.calculate_depreciation: - asset.value_after_depreciation += flt(difference_amount) - asset.db_update() - else: + if asset.calculate_depreciation: for row in asset.finance_books: if cstr(row.finance_book) == cstr(self.finance_book): row.value_after_depreciation += flt(difference_amount) row.db_update() + asset.value_after_depreciation += flt(difference_amount) + asset.db_update() return asset def get_adjustment_note(self): diff --git a/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py index bfb34023089..b484f08d7e0 100644 --- a/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py +++ b/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py @@ -262,7 +262,7 @@ class TestAssetValueAdjustment(IntegrationTestCase): self.assertEqual(schedules, expected_schedules) def test_difference_amount(self): - pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=120000.0, location="Test Location") + pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location") asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, "name") asset_doc = frappe.get_doc("Asset", asset_name) @@ -282,17 +282,18 @@ class TestAssetValueAdjustment(IntegrationTestCase): ) asset_doc.submit() + current_asset_value = get_asset_value_after_depreciation(asset_doc.name) adj_doc = make_asset_value_adjustment( asset=asset_doc.name, - current_asset_value=54000, - new_asset_value=50000.0, + current_asset_value=current_asset_value, + new_asset_value=40000, date="2023-08-21", ) adj_doc.submit() difference_amount = adj_doc.new_asset_value - adj_doc.current_asset_value - self.assertEqual(difference_amount, -4000) + self.assertEqual(difference_amount, -60000) asset_doc.load_from_db() - self.assertEqual(asset_doc.value_after_depreciation, 50000.0) + self.assertEqual(asset_doc.finance_books[0].value_after_depreciation, 40000.0) def make_asset_value_adjustment(**args): From 4b0279329f66a085d7a157faa92dc93881df0ced Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Tue, 11 Mar 2025 20:38:31 +0530 Subject: [PATCH 24/40] test: added tests for rescheduled depreciation after asset repair cancellation --- .../test_asset_depreciation_schedule.py | 368 +++++++++++++++++- .../doctype/asset_repair/test_asset_repair.py | 24 +- .../test_asset_value_adjustment.py | 6 +- 3 files changed, 391 insertions(+), 7 deletions(-) diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py index 44cb0a105f8..d68eed660a3 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py @@ -3,7 +3,7 @@ import frappe from frappe.tests import IntegrationTestCase, UnitTestCase -from frappe.utils import cstr, flt +from frappe.utils import cstr, date_diff, flt from erpnext.assets.doctype.asset.depreciation import ( post_depreciation_entries, @@ -13,6 +13,7 @@ from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_sched get_asset_depr_schedule_doc, get_depr_schedule, ) +from erpnext.assets.doctype.asset_repair.test_asset_repair import create_asset_repair class UnitTestAssetDepreciationSchedule(UnitTestCase): @@ -377,3 +378,368 @@ class TestAssetDepreciationSchedule(IntegrationTestCase): asset.reload() self.assertEqual(asset.finance_books[0].total_number_of_booked_depreciations, 14) + + def test_depreciation_schedule_after_cancelling_asset_repair(self): + asset = create_asset( + item_code="Macbook Pro", + gross_purchase_amount=500, + calculate_depreciation=1, + depreciation_method="Straight Line", + available_for_use_date="2023-01-01", + depreciation_start_date="2023-01-31", + frequency_of_depreciation=1, + total_number_of_depreciations=12, + submit=1, + ) + + expected_depreciation_before_repair = [ + ["2023-01-31", 41.67, 41.67], + ["2023-02-28", 41.67, 83.34], + ["2023-03-31", 41.67, 125.01], + ["2023-04-30", 41.67, 166.68], + ["2023-05-31", 41.67, 208.35], + ["2023-06-30", 41.67, 250.02], + ["2023-07-31", 41.67, 291.69], + ["2023-08-31", 41.67, 333.36], + ["2023-09-30", 41.67, 375.03], + ["2023-10-31", 41.67, 416.7], + ["2023-11-30", 41.67, 458.37], + ["2023-12-31", 41.63, 500.0], + ] + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Active") + ] + self.assertEqual(schedules, expected_depreciation_before_repair) + self.assertEqual(asset.finance_books[0].value_after_depreciation, 500) + + asset_repair = create_asset_repair( + asset=asset, + capitalize_repair_cost=1, + item="_Test Non Stock Item", + failure_date="2023-04-01", + pi_repair_cost1=60, + pi_repair_cost2=40, + increase_in_asset_life=0, + submit=1, + ) + self.assertEqual(asset_repair.total_repair_cost, 100) + + expected_depreciation_after_repair = [ + ["2023-01-31", 50.0, 50.0], + ["2023-02-28", 50.0, 100.0], + ["2023-03-31", 50.0, 150.0], + ["2023-04-30", 50.0, 200.0], + ["2023-05-31", 50.0, 250.0], + ["2023-06-30", 50.0, 300.0], + ["2023-07-31", 50.0, 350.0], + ["2023-08-31", 50.0, 400.0], + ["2023-09-30", 50.0, 450.0], + ["2023-10-31", 50.0, 500.0], + ["2023-11-30", 50.0, 550.0], + ["2023-12-31", 50.0, 600.0], + ] + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Active") + ] + self.assertEqual(schedules, expected_depreciation_after_repair) + asset.reload() + self.assertEqual(asset.finance_books[0].value_after_depreciation, 600) + + asset_repair.cancel() + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Active") + ] + self.assertEqual(schedules, expected_depreciation_before_repair) + asset.reload() + self.assertEqual(asset.finance_books[0].value_after_depreciation, 500) + + def test_depreciation_schedule_after_cancelling_asset_repair_for_6_months_frequency(self): + asset = create_asset( + item_code="Macbook Pro", + gross_purchase_amount=500, + calculate_depreciation=1, + depreciation_method="Straight Line", + available_for_use_date="2023-01-01", + depreciation_start_date="2023-06-30", + frequency_of_depreciation=6, + total_number_of_depreciations=4, + submit=1, + ) + + expected_depreciation_before_repair = [ + ["2023-06-30", 125.0, 125.0], + ["2023-12-31", 125.0, 250.0], + ["2024-06-30", 125.0, 375.0], + ["2024-12-31", 125.0, 500.0], + ] + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Active") + ] + + self.assertEqual(schedules, expected_depreciation_before_repair) + + asset_repair = create_asset_repair( + asset=asset, + capitalize_repair_cost=1, + item="_Test Non Stock Item", + failure_date="2023-04-01", + pi_repair_cost1=60, + pi_repair_cost2=40, + increase_in_asset_life=0, + submit=1, + ) + self.assertEqual(asset_repair.total_repair_cost, 100) + + expected_depreciation_after_repair = [ + ["2023-06-30", 150.0, 150.0], + ["2023-12-31", 150.0, 300.0], + ["2024-06-30", 150.0, 450.0], + ["2024-12-31", 150.0, 600.0], + ] + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Active") + ] + + self.assertEqual(schedules, expected_depreciation_after_repair) + asset.reload() + self.assertEqual(asset.finance_books[0].value_after_depreciation, 600) + + asset_repair.cancel() + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Active") + ] + self.assertEqual(schedules, expected_depreciation_before_repair) + asset.reload() + self.assertEqual(asset.finance_books[0].value_after_depreciation, 500) + + def test_depreciation_schedule_after_cancelling_asset_repair_for_existing_asset(self): + asset = create_asset( + item_code="Macbook Pro", + gross_purchase_amount=500, + calculate_depreciation=1, + depreciation_method="Straight Line", + available_for_use_date="2023-01-15", + depreciation_start_date="2023-03-31", + frequency_of_depreciation=1, + total_number_of_depreciations=12, + is_existing_asset=1, + opening_accumulated_depreciation=64.52, + opening_number_of_booked_depreciations=2, + submit=1, + ) + + expected_depreciation_before_repair = [ + ["2023-03-31", 41.26, 105.78], + ["2023-04-30", 41.26, 147.04], + ["2023-05-31", 41.26, 188.3], + ["2023-06-30", 41.26, 229.56], + ["2023-07-31", 41.26, 270.82], + ["2023-08-31", 41.26, 312.08], + ["2023-09-30", 41.26, 353.34], + ["2023-10-31", 41.26, 394.6], + ["2023-11-30", 41.26, 435.86], + ["2023-12-31", 41.26, 477.12], + ["2024-01-15", 22.88, 500.0], + ] + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Active") + ] + self.assertEqual(schedules, expected_depreciation_before_repair) + + asset_repair = create_asset_repair( + asset=asset, + capitalize_repair_cost=1, + item="_Test Non Stock Item", + failure_date="2023-04-01", + pi_repair_cost1=60, + pi_repair_cost2=40, + increase_in_asset_life=0, + submit=1, + ) + self.assertEqual(asset_repair.total_repair_cost, 100) + + expected_depreciation_after_repair = [ + ["2023-03-31", 50.74, 115.26], + ["2023-04-30", 50.74, 166.0], + ["2023-05-31", 50.74, 216.74], + ["2023-06-30", 50.74, 267.48], + ["2023-07-31", 50.74, 318.22], + ["2023-08-31", 50.74, 368.96], + ["2023-09-30", 50.74, 419.7], + ["2023-10-31", 50.74, 470.44], + ["2023-11-30", 50.74, 521.18], + ["2023-12-31", 50.74, 571.92], + ["2024-01-15", 28.08, 600.0], + ] + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Active") + ] + self.assertEqual(schedules, expected_depreciation_after_repair) + + asset_repair.cancel() + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Active") + ] + + self.assertEqual(schedules, expected_depreciation_before_repair) + asset.reload() + self.assertEqual(asset.finance_books[0].value_after_depreciation, 435.48) + + def test_wdv_depreciation_schedule_after_cancelling_asset_repair(self): + asset = create_asset( + item_code="Macbook Pro", + gross_purchase_amount=500, + calculate_depreciation=1, + depreciation_method="Written Down Value", + available_for_use_date="2023-04-01", + depreciation_start_date="2023-12-31", + frequency_of_depreciation=12, + total_number_of_depreciations=4, + rate_of_depreciation=40, + submit=1, + ) + + expected_depreciation_before_repair = [ + ["2023-12-31", 150.68, 150.68], + ["2024-12-31", 139.73, 290.41], + ["2025-12-31", 83.84, 374.25], + ["2026-12-31", 50.3, 424.55], + ["2027-04-01", 75.45, 500.0], + ] + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Active") + ] + self.assertEqual(schedules, expected_depreciation_before_repair) + + asset_repair = create_asset_repair( + asset=asset, + capitalize_repair_cost=1, + item="_Test Non Stock Item", + failure_date="2024-01-01", + pi_repair_cost1=60, + pi_repair_cost2=40, + increase_in_asset_life=0, + submit=1, + ) + + expected_depreciation_after_repair = [ + ["2023-12-31", 180.82, 180.82], + ["2024-12-31", 167.67, 348.49], + ["2025-12-31", 100.6, 449.09], + ["2026-12-31", 60.36, 509.45], + ["2027-04-01", 90.55, 600.0], + ] + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Active") + ] + self.assertEqual(schedules, expected_depreciation_after_repair) + + asset_repair.cancel() + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Active") + ] + + self.assertEqual(schedules, expected_depreciation_before_repair) + + def test_daily_prorata_based_depreciation_schedule_after_cancelling_asset_repair_for(self): + asset = create_asset( + item_code="Macbook Pro", + gross_purchase_amount=500, + calculate_depreciation=1, + depreciation_method="Straight Line", + available_for_use_date="2023-01-01", + depreciation_start_date="2023-01-31", + daily_prorata_based=1, + frequency_of_depreciation=1, + total_number_of_depreciations=12, + submit=1, + ) + + expected_depreciation_before_repair = [ + ["2023-01-31", 42.47, 42.47], + ["2023-02-28", 38.36, 80.83], + ["2023-03-31", 42.47, 123.3], + ["2023-04-30", 41.1, 164.4], + ["2023-05-31", 42.47, 206.87], + ["2023-06-30", 41.1, 247.97], + ["2023-07-31", 42.47, 290.44], + ["2023-08-31", 42.47, 332.91], + ["2023-09-30", 41.1, 374.01], + ["2023-10-31", 42.47, 416.48], + ["2023-11-30", 41.1, 457.58], + ["2023-12-31", 42.42, 500.0], + ] + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Active") + ] + self.assertEqual(schedules, expected_depreciation_before_repair) + + asset_repair = create_asset_repair( + asset=asset, + capitalize_repair_cost=1, + item="_Test Non Stock Item", + failure_date="2023-04-01", + pi_repair_cost1=60, + pi_repair_cost2=40, + increase_in_asset_life=0, + submit=1, + ) + self.assertEqual(asset_repair.total_repair_cost, 100) + + expected_depreciation_after_repair = [ + ["2023-01-31", 50.96, 50.96], + ["2023-02-28", 46.03, 96.99], + ["2023-03-31", 50.96, 147.95], + ["2023-04-30", 49.32, 197.27], + ["2023-05-31", 50.96, 248.23], + ["2023-06-30", 49.32, 297.55], + ["2023-07-31", 50.96, 348.51], + ["2023-08-31", 50.96, 399.47], + ["2023-09-30", 49.32, 448.79], + ["2023-10-31", 50.96, 499.75], + ["2023-11-30", 49.32, 549.07], + ["2023-12-31", 50.93, 600.0], + ] + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Active") + ] + self.assertEqual(schedules, expected_depreciation_after_repair) + asset.reload() + self.assertEqual(asset.finance_books[0].value_after_depreciation, 600) + + asset_repair.cancel() + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Active") + ] + self.assertEqual(schedules, expected_depreciation_before_repair) + asset.reload() + self.assertEqual(asset.finance_books[0].value_after_depreciation, 500) diff --git a/erpnext/assets/doctype/asset_repair/test_asset_repair.py b/erpnext/assets/doctype/asset_repair/test_asset_repair.py index 6c9b6820d45..f0b4f4fb6b2 100644 --- a/erpnext/assets/doctype/asset_repair/test_asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/test_asset_repair.py @@ -128,7 +128,11 @@ class TestAssetRepair(IntegrationTestCase): asset = create_asset(calculate_depreciation=1, submit=1) initial_asset_value = get_asset_value_after_depreciation(asset.name) asset_repair = create_asset_repair( - asset=asset, capitalize_repair_cost=1, item="_Test Non Stock Item", submit=1 + asset=asset, + capitalize_repair_cost=1, + item="_Test Non Stock Item", + submit=1, + increase_in_asset_value=1, ) asset.reload() @@ -136,7 +140,9 @@ class TestAssetRepair(IntegrationTestCase): self.assertEqual(asset_repair.repair_cost, increase_in_asset_value) def test_purchase_invoice(self): - asset_repair = create_asset_repair(capitalize_repair_cost=1, item="_Test Non Stock Item", submit=1) + asset_repair = create_asset_repair( + capitalize_repair_cost=1, item="_Test Non Stock Item", submit=1, increase_in_asset_value=1 + ) self.assertTrue(asset_repair.invoices) def test_gl_entries_with_perpetual_inventory(self): @@ -163,6 +169,7 @@ class TestAssetRepair(IntegrationTestCase): pi_expense_account1="Administrative Expenses - TCP1", pi_expense_account2="Legal Expenses - TCP1", item="_Test Non Stock Item", + increase_in_asset_life=1, submit=1, ) @@ -210,6 +217,7 @@ class TestAssetRepair(IntegrationTestCase): asset_repair = create_asset_repair( capitalize_repair_cost=1, stock_consumption=1, + increase_in_asset_life=1, item="_Test Non Stock Item", submit=1, ) @@ -259,7 +267,13 @@ class TestAssetRepair(IntegrationTestCase): self.assertEqual(first_asset_depr_schedule.status, "Active") initial_num_of_depreciations = num_of_depreciations(asset) - create_asset_repair(asset=asset, capitalize_repair_cost=1, item="_Test Non Stock Item", submit=1) + create_asset_repair( + asset=asset, + capitalize_repair_cost=1, + item="_Test Non Stock Item", + submit=1, + increase_in_asset_life=1, + ) asset.reload() first_asset_depr_schedule.load_from_db() @@ -302,7 +316,7 @@ def create_asset_repair(**args): { "asset": asset.name, "asset_name": asset.asset_name, - "failure_date": nowdate(), + "failure_date": args.failure_date or nowdate(), "description": "Test Description", "company": asset.company, } @@ -366,7 +380,7 @@ def create_asset_repair(**args): if args.capitalize_repair_cost: asset_repair.capitalize_repair_cost = 1 - if asset.calculate_depreciation: + if asset.calculate_depreciation and args.increase_in_asset_life: asset_repair.increase_in_asset_life = 12 pi1 = make_purchase_invoice( company=asset.company, diff --git a/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py index b484f08d7e0..bf3a1e8bda1 100644 --- a/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py +++ b/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py @@ -154,7 +154,11 @@ class TestAssetValueAdjustment(IntegrationTestCase): # create asset repair asset_repair = create_asset_repair( - asset=asset_doc, capitalize_repair_cost=1, item="_Test Non Stock Item", submit=1 + asset=asset_doc, + capitalize_repair_cost=1, + item="_Test Non Stock Item", + submit=1, + increase_in_asset_life=1, ) first_asset_depr_schedule = get_asset_depr_schedule_doc(asset_doc.name, "Active") From 881e5e34178884a08ed4c06fb5d317b4d4328fe8 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Wed, 12 Mar 2025 00:35:10 +0530 Subject: [PATCH 25/40] fix(test): asset value adjustment test cases --- .../test_asset_value_adjustment.py | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py index bf3a1e8bda1..46721773cf6 100644 --- a/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py +++ b/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py @@ -114,12 +114,12 @@ class TestAssetValueAdjustment(IntegrationTestCase): ["2023-05-31", 9983.33, 45408.05], ["2023-06-30", 9983.33, 55391.38], ["2023-07-31", 9983.33, 65374.71], - ["2023-08-31", 8300.0, 73674.71], - ["2023-09-30", 8300.0, 81974.71], - ["2023-10-31", 8300.0, 90274.71], - ["2023-11-30", 8300.0, 98574.71], - ["2023-12-31", 8300.0, 106874.71], - ["2024-01-15", 8300.0, 115174.71], + ["2023-08-31", 9016.37, 74391.08], + ["2023-09-30", 9016.37, 83407.45], + ["2023-10-31", 9016.37, 92423.82], + ["2023-11-30", 9016.37, 101440.19], + ["2023-12-31", 9016.37, 110456.56], + ["2024-01-15", 4718.15, 115174.71], ] schedules = [ @@ -205,24 +205,24 @@ class TestAssetValueAdjustment(IntegrationTestCase): ["2023-05-31", 9983.33, 45408.05], ["2023-06-30", 9983.33, 55391.38], ["2023-07-31", 9983.33, 65374.71], - ["2023-08-31", 2766.67, 68141.38], - ["2023-09-30", 2766.67, 70908.05], - ["2023-10-31", 2766.67, 73674.72], - ["2023-11-30", 2766.67, 76441.39], - ["2023-12-31", 2766.67, 79208.06], - ["2024-01-31", 2766.67, 81974.73], - ["2024-02-29", 2766.67, 84741.4], - ["2024-03-31", 2766.67, 87508.07], - ["2024-04-30", 2766.67, 90274.74], - ["2024-05-31", 2766.67, 93041.41], - ["2024-06-30", 2766.67, 95808.08], - ["2024-07-31", 2766.67, 98574.75], - ["2024-08-31", 2766.67, 101341.42], - ["2024-09-30", 2766.67, 104108.09], - ["2024-10-31", 2766.67, 106874.76], - ["2024-11-30", 2766.67, 109641.43], - ["2024-12-31", 2766.67, 112408.1], - ["2025-01-15", 2766.61, 115174.71], + ["2023-08-31", 2841.93, 68216.64], + ["2023-09-30", 2841.93, 71058.57], + ["2023-10-31", 2841.93, 73900.5], + ["2023-11-30", 2841.93, 76742.43], + ["2023-12-31", 2841.93, 79584.36], + ["2024-01-31", 2841.93, 82426.29], + ["2024-02-29", 2841.93, 85268.22], + ["2024-03-31", 2841.93, 88110.15], + ["2024-04-30", 2841.93, 90952.08], + ["2024-05-31", 2841.93, 93794.01], + ["2024-06-30", 2841.93, 96635.94], + ["2024-07-31", 2841.93, 99477.87], + ["2024-08-31", 2841.93, 102319.8], + ["2024-09-30", 2841.93, 105161.73], + ["2024-10-31", 2841.93, 108003.66], + ["2024-11-30", 2841.93, 110845.59], + ["2024-12-31", 2841.93, 113687.52], + ["2025-01-31", 1487.19, 115174.71], ] schedules = [ @@ -250,12 +250,12 @@ class TestAssetValueAdjustment(IntegrationTestCase): ["2023-05-31", 9983.33, 45408.05], ["2023-06-30", 9983.33, 55391.38], ["2023-07-31", 9983.33, 65374.71], - ["2023-08-31", 8208.33, 73583.04], - ["2023-09-30", 8208.33, 81791.37], - ["2023-10-31", 8208.33, 89999.7], - ["2023-11-30", 8208.33, 98208.03], - ["2023-12-31", 8208.33, 106416.36], - ["2024-01-15", 8208.35, 114624.71], + ["2023-08-31", 8916.79, 74291.5], + ["2023-09-30", 8916.79, 83208.29], + ["2023-10-31", 8916.79, 92125.08], + ["2023-11-30", 8916.79, 101041.87], + ["2023-12-31", 8916.79, 109958.66], + ["2024-01-15", 4666.05, 114624.71], ] schedules = [ From cf1af451b87b42e3d085d1dd1251408c28d99b6d Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Thu, 13 Mar 2025 15:34:21 +0530 Subject: [PATCH 26/40] fix(test): more fixes for failig test cases --- .../sales_invoice/test_sales_invoice.py | 2 +- erpnext/assets/doctype/asset/test_asset.py | 27 +++--- .../deppreciation_schedule_controller.py | 4 +- .../test_asset_depreciation_schedule.py | 94 +++++++++---------- .../test_asset_value_adjustment.py | 60 ++++++------ 5 files changed, 94 insertions(+), 93 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 367f3f82ccd..1ee6f55801b 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2981,7 +2981,7 @@ class TestSalesInvoice(ERPNextTestSuite): expected_values = [ ["2020-06-30", 1366.12, 1366.12], ["2021-06-30", 20000.0, 21366.12], - ["2021-09-30", 5041.1, 26407.22], + ["2021-09-30", 5041.34, 26407.46], ] for i, schedule in enumerate(get_depr_schedule(asset.name, "Active")): diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 382b6840ac0..77621d0874c 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -256,14 +256,12 @@ class TestAsset(AssetSetup): asset.precision("gross_purchase_amount"), ) - second_asset_depr_schedule.depreciation_amount = 9000 + second_asset_depr_schedule.depreciation_amount = 8932.70 second_asset_depr_schedule.asset_doc = asset second_asset_depr_schedule.get_finance_book_row() second_asset_depr_schedule.fetch_asset_details() pro_rata_amount, _, _ = second_asset_depr_schedule._get_pro_rata_amt( - asset.finance_books[0], - 9000, add_days(get_last_day(add_months(purchase_date, 1)), 1), date, original_schedule_date=get_last_day(nowdate()), @@ -347,7 +345,7 @@ class TestAsset(AssetSetup): self.assertEqual(second_asset_depr_schedule.status, "Active") self.assertEqual(first_asset_depr_schedule.status, "Cancelled") - second_asset_depr_schedule.depreciation_amount = 9000 + second_asset_depr_schedule.depreciation_amount = 8932.70 second_asset_depr_schedule.asset_doc = asset second_asset_depr_schedule.get_finance_book_row() second_asset_depr_schedule.fetch_asset_details() @@ -950,12 +948,12 @@ class TestDepreciationMethods(AssetSetup): ) expected_schedules = [ - ["2022-02-28", 310.89, 310.89], - ["2022-03-31", 654.45, 965.34], - ["2022-04-30", 654.45, 1619.79], - ["2022-05-31", 654.45, 2274.24], - ["2022-06-30", 654.45, 2928.69], - ["2022-07-15", 2071.31, 5000.0], + ["2022-02-28", 337.72, 337.72], + ["2022-03-31", 675.45, 1013.17], + ["2022-04-30", 675.45, 1688.62], + ["2022-05-31", 675.45, 2364.07], + ["2022-06-30", 675.45, 3039.52], + ["2022-07-15", 1960.48, 5000.0], ] schedules = [ @@ -1030,6 +1028,7 @@ class TestDepreciationBasics(AssetSetup): "depreciation_start_date": "2020-12-31", }, ) + asset.submit() asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active") @@ -1361,7 +1360,7 @@ class TestDepreciationBasics(AssetSetup): asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active") - asset_depr_schedule_doc.clear_depr_schedule() + asset_depr_schedule_doc.clear() self.assertEqual(len(asset_depr_schedule_doc.get("depreciation_schedule")), 1) @@ -1408,15 +1407,15 @@ class TestDepreciationBasics(AssetSetup): asset.load_from_db() asset_depr_schedule_doc_1 = get_asset_depr_schedule_doc(asset.name, "Active", "Test Finance Book 1") - asset_depr_schedule_doc_1.clear_depr_schedule() + asset_depr_schedule_doc_1.clear() self.assertEqual(len(asset_depr_schedule_doc_1.get("depreciation_schedule")), 3) asset_depr_schedule_doc_2 = get_asset_depr_schedule_doc(asset.name, "Active", "Test Finance Book 2") - asset_depr_schedule_doc_2.clear_depr_schedule() + asset_depr_schedule_doc_2.clear() self.assertEqual(len(asset_depr_schedule_doc_2.get("depreciation_schedule")), 3) asset_depr_schedule_doc_3 = get_asset_depr_schedule_doc(asset.name, "Active", "Test Finance Book 3") - asset_depr_schedule_doc_3.clear_depr_schedule() + asset_depr_schedule_doc_3.clear() self.assertEqual(len(asset_depr_schedule_doc_3.get("depreciation_schedule")), 0) def test_depreciation_schedules_are_set_up_for_multiple_finance_books(self): diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/deppreciation_schedule_controller.py b/erpnext/assets/doctype/asset_depreciation_schedule/deppreciation_schedule_controller.py index fd1d45f98cd..42f0d98fa4e 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/deppreciation_schedule_controller.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/deppreciation_schedule_controller.py @@ -244,7 +244,9 @@ class DepreciationScheduleController(StraightLineMethod, WDVMethod): ) if getdate(computed_available_for_use_date) < getdate(self.asset_doc.available_for_use_date): computed_available_for_use_date = self.asset_doc.available_for_use_date - depr_booked_for_months = date_diff(last_depr_date, computed_available_for_use_date) / (365 / 12) + depr_booked_for_months = (date_diff(last_depr_date, computed_available_for_use_date) + 1) / ( + 365 / 12 + ) return depr_booked_for_months def get_total_pending_days_or_years(self): diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py index d68eed660a3..947ce82e45d 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py @@ -142,22 +142,22 @@ class TestAssetDepreciationSchedule(IntegrationTestCase): ) expected_schedules = [ - ["2024-07-31", 30.4, 295.7], - ["2024-08-31", 30.4, 326.1], - ["2024-09-30", 30.4, 356.5], - ["2024-10-31", 30.4, 386.9], - ["2024-11-30", 30.4, 417.3], - ["2024-12-31", 30.4, 447.7], - ["2025-01-31", 30.4, 478.1], - ["2025-02-28", 30.4, 508.5], - ["2025-03-31", 30.4, 538.9], - ["2025-04-30", 30.4, 569.3], - ["2025-05-31", 30.4, 599.7], - ["2025-06-30", 30.4, 630.1], - ["2025-07-31", 30.4, 660.5], - ["2025-08-31", 30.4, 690.9], - ["2025-09-30", 30.4, 721.3], - ["2025-10-10", 9.7, 731.0], + ["2024-07-31", 30.46, 295.76], + ["2024-08-31", 30.46, 326.22], + ["2024-09-30", 30.46, 356.68], + ["2024-10-31", 30.46, 387.14], + ["2024-11-30", 30.46, 417.6], + ["2024-12-31", 30.46, 448.06], + ["2025-01-31", 30.46, 478.52], + ["2025-02-28", 30.46, 508.98], + ["2025-03-31", 30.46, 539.44], + ["2025-04-30", 30.46, 569.9], + ["2025-05-31", 30.46, 600.36], + ["2025-06-30", 30.46, 630.82], + ["2025-07-31", 30.46, 661.28], + ["2025-08-31", 30.46, 691.74], + ["2025-09-30", 30.46, 722.2], + ["2025-10-10", 8.8, 731.0], ] schedules = [ [cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount] @@ -180,15 +180,15 @@ class TestAssetDepreciationSchedule(IntegrationTestCase): ) expected_schedules = [ - ["2024-12-31", 60.9, 284.05], - ["2025-03-31", 60.9, 344.95], - ["2025-06-30", 60.9, 405.85], - ["2025-09-30", 60.9, 466.75], - ["2025-12-31", 60.9, 527.65], - ["2026-03-31", 60.9, 588.55], - ["2026-06-30", 60.9, 649.45], - ["2026-09-30", 60.9, 710.35], - ["2026-11-01", 20.65, 731.0], + ["2024-12-31", 60.98, 284.13], + ["2025-03-31", 60.98, 345.11], + ["2025-06-30", 60.98, 406.09], + ["2025-09-30", 60.98, 467.07], + ["2025-12-31", 60.98, 528.05], + ["2026-03-31", 60.98, 589.03], + ["2026-06-30", 60.98, 650.01], + ["2026-09-30", 60.98, 710.99], + ["2026-11-01", 20.01, 731.0], ] schedules = [ [cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount] @@ -541,17 +541,17 @@ class TestAssetDepreciationSchedule(IntegrationTestCase): ) expected_depreciation_before_repair = [ - ["2023-03-31", 41.26, 105.78], - ["2023-04-30", 41.26, 147.04], - ["2023-05-31", 41.26, 188.3], - ["2023-06-30", 41.26, 229.56], - ["2023-07-31", 41.26, 270.82], - ["2023-08-31", 41.26, 312.08], - ["2023-09-30", 41.26, 353.34], - ["2023-10-31", 41.26, 394.6], - ["2023-11-30", 41.26, 435.86], - ["2023-12-31", 41.26, 477.12], - ["2024-01-15", 22.88, 500.0], + ["2023-03-31", 41.39, 105.91], + ["2023-04-30", 41.39, 147.3], + ["2023-05-31", 41.39, 188.69], + ["2023-06-30", 41.39, 230.08], + ["2023-07-31", 41.39, 271.47], + ["2023-08-31", 41.39, 312.86], + ["2023-09-30", 41.39, 354.25], + ["2023-10-31", 41.39, 395.64], + ["2023-11-30", 41.39, 437.03], + ["2023-12-31", 41.39, 478.42], + ["2024-01-15", 21.58, 500.0], ] schedules = [ @@ -573,17 +573,17 @@ class TestAssetDepreciationSchedule(IntegrationTestCase): self.assertEqual(asset_repair.total_repair_cost, 100) expected_depreciation_after_repair = [ - ["2023-03-31", 50.74, 115.26], - ["2023-04-30", 50.74, 166.0], - ["2023-05-31", 50.74, 216.74], - ["2023-06-30", 50.74, 267.48], - ["2023-07-31", 50.74, 318.22], - ["2023-08-31", 50.74, 368.96], - ["2023-09-30", 50.74, 419.7], - ["2023-10-31", 50.74, 470.44], - ["2023-11-30", 50.74, 521.18], - ["2023-12-31", 50.74, 571.92], - ["2024-01-15", 28.08, 600.0], + ["2023-03-31", 50.9, 115.42], + ["2023-04-30", 50.9, 166.32], + ["2023-05-31", 50.9, 217.22], + ["2023-06-30", 50.9, 268.12], + ["2023-07-31", 50.9, 319.02], + ["2023-08-31", 50.9, 369.92], + ["2023-09-30", 50.9, 420.82], + ["2023-10-31", 50.9, 471.72], + ["2023-11-30", 50.9, 522.62], + ["2023-12-31", 50.9, 573.52], + ["2024-01-15", 26.48, 600.0], ] schedules = [ diff --git a/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py index 46721773cf6..e1d3b7b084f 100644 --- a/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py +++ b/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py @@ -114,12 +114,12 @@ class TestAssetValueAdjustment(IntegrationTestCase): ["2023-05-31", 9983.33, 45408.05], ["2023-06-30", 9983.33, 55391.38], ["2023-07-31", 9983.33, 65374.71], - ["2023-08-31", 9016.37, 74391.08], - ["2023-09-30", 9016.37, 83407.45], - ["2023-10-31", 9016.37, 92423.82], - ["2023-11-30", 9016.37, 101440.19], - ["2023-12-31", 9016.37, 110456.56], - ["2024-01-15", 4718.15, 115174.71], + ["2023-08-31", 9070.36, 74445.07], + ["2023-09-30", 9070.36, 83515.43], + ["2023-10-31", 9070.36, 92585.79], + ["2023-11-30", 9070.36, 101656.15], + ["2023-12-31", 9070.36, 110726.51], + ["2024-01-15", 4448.2, 115174.71], ] schedules = [ @@ -205,24 +205,24 @@ class TestAssetValueAdjustment(IntegrationTestCase): ["2023-05-31", 9983.33, 45408.05], ["2023-06-30", 9983.33, 55391.38], ["2023-07-31", 9983.33, 65374.71], - ["2023-08-31", 2841.93, 68216.64], - ["2023-09-30", 2841.93, 71058.57], - ["2023-10-31", 2841.93, 73900.5], - ["2023-11-30", 2841.93, 76742.43], - ["2023-12-31", 2841.93, 79584.36], - ["2024-01-31", 2841.93, 82426.29], - ["2024-02-29", 2841.93, 85268.22], - ["2024-03-31", 2841.93, 88110.15], - ["2024-04-30", 2841.93, 90952.08], - ["2024-05-31", 2841.93, 93794.01], - ["2024-06-30", 2841.93, 96635.94], - ["2024-07-31", 2841.93, 99477.87], - ["2024-08-31", 2841.93, 102319.8], - ["2024-09-30", 2841.93, 105161.73], - ["2024-10-31", 2841.93, 108003.66], - ["2024-11-30", 2841.93, 110845.59], - ["2024-12-31", 2841.93, 113687.52], - ["2025-01-31", 1487.19, 115174.71], + ["2023-08-31", 2847.27, 68221.98], + ["2023-09-30", 2847.27, 71069.25], + ["2023-10-31", 2847.27, 73916.52], + ["2023-11-30", 2847.27, 76763.79], + ["2023-12-31", 2847.27, 79611.06], + ["2024-01-31", 2847.27, 82458.33], + ["2024-02-29", 2847.27, 85305.6], + ["2024-03-31", 2847.27, 88152.87], + ["2024-04-30", 2847.27, 91000.14], + ["2024-05-31", 2847.27, 93847.41], + ["2024-06-30", 2847.27, 96694.68], + ["2024-07-31", 2847.27, 99541.95], + ["2024-08-31", 2847.27, 102389.22], + ["2024-09-30", 2847.27, 105236.49], + ["2024-10-31", 2847.27, 108083.76], + ["2024-11-30", 2847.27, 110931.03], + ["2024-12-31", 2847.27, 113778.3], + ["2025-01-31", 1396.41, 115174.71], ] schedules = [ @@ -250,12 +250,12 @@ class TestAssetValueAdjustment(IntegrationTestCase): ["2023-05-31", 9983.33, 45408.05], ["2023-06-30", 9983.33, 55391.38], ["2023-07-31", 9983.33, 65374.71], - ["2023-08-31", 8916.79, 74291.5], - ["2023-09-30", 8916.79, 83208.29], - ["2023-10-31", 8916.79, 92125.08], - ["2023-11-30", 8916.79, 101041.87], - ["2023-12-31", 8916.79, 109958.66], - ["2024-01-15", 4666.05, 114624.71], + ["2023-08-31", 8970.18, 74344.89], + ["2023-09-30", 8970.18, 83315.07], + ["2023-10-31", 8970.18, 92285.25], + ["2023-11-30", 8970.18, 101255.43], + ["2023-12-31", 8970.18, 110225.61], + ["2024-01-15", 4399.1, 114624.71], ] schedules = [ From f5fa757bc68a0c37a73cc4dee873e10b5af42bd7 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Sun, 16 Mar 2025 23:26:25 +0530 Subject: [PATCH 27/40] fix(test): failing test cases --- erpnext/assets/doctype/asset/test_asset.py | 15 ++++++++++++--- .../assets/doctype/asset_repair/asset_repair.py | 1 + 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 77621d0874c..9ca5e5ee1f0 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -1031,10 +1031,14 @@ class TestDepreciationBasics(AssetSetup): asset.submit() asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active") + asset_depr_schedule_doc.asset_doc = asset + asset_depr_schedule_doc.get_finance_book_row() + asset_depr_schedule_doc.fetch_asset_details() + asset_depr_schedule_doc.clear() + asset_depr_schedule_doc._check_is_pro_rata() + asset_depr_schedule_doc.initialize_variables() - depreciation_amount = asset_depr_schedule_doc.get_depreciation_amount( - asset_depr_schedule_doc, asset, 100000, 100000, asset.finance_books[0] - ) + depreciation_amount = asset_depr_schedule_doc.get_depreciation_amount(0) self.assertEqual(depreciation_amount, 30000) def test_make_depr_schedule(self): @@ -1465,6 +1469,11 @@ class TestDepreciationBasics(AssetSetup): submit=1, ) + depr_expense_account = frappe.get_doc("Account", "_Test Depreciations - _TC") + depr_expense_account.root_type = "Expense" + depr_expense_account.parent_account = "Expenses - _TC" + depr_expense_account.save() + post_depreciation_entries(date="2021-01-01") asset.load_from_db() diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py index 8a4349233e5..8bfbb2c7d21 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/asset_repair.py @@ -242,6 +242,7 @@ class AssetRepair(AccountsController): def make_gl_entries(self, cancel=False): if cancel: self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry") + self.save() if flt(self.total_repair_cost) > 0: gl_entries = self.get_gl_entries() From 42ccce16873200ea8c9a603e44bcdfffb71c1ae3 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Mon, 17 Mar 2025 01:05:22 +0530 Subject: [PATCH 28/40] test: added tests for reschedule depreciation after asset revaluation cancellation --- .../test_asset_depreciation_schedule.py | 239 ++++++++++++++++++ 1 file changed, 239 insertions(+) diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py index 947ce82e45d..8b4fe96fba3 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py @@ -14,6 +14,9 @@ from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_sched get_depr_schedule, ) from erpnext.assets.doctype.asset_repair.test_asset_repair import create_asset_repair +from erpnext.assets.doctype.asset_value_adjustment.test_asset_value_adjustment import ( + make_asset_value_adjustment, +) class UnitTestAssetDepreciationSchedule(UnitTestCase): @@ -743,3 +746,239 @@ class TestAssetDepreciationSchedule(IntegrationTestCase): self.assertEqual(schedules, expected_depreciation_before_repair) asset.reload() self.assertEqual(asset.finance_books[0].value_after_depreciation, 500) + + def test_depreciation_schedule_after_cancelling_asset_value_adjustent(self): + asset = create_asset( + item_code="Macbook Pro", + gross_purchase_amount=1000, + calculate_depreciation=1, + depreciation_method="Straight Line", + available_for_use_date="2023-01-01", + depreciation_start_date="2023-01-31", + frequency_of_depreciation=1, + total_number_of_depreciations=12, + submit=1, + ) + + expected_depreciation_before_adjustment = [ + ["2023-01-31", 83.33, 83.33], + ["2023-02-28", 83.33, 166.66], + ["2023-03-31", 83.33, 249.99], + ["2023-04-30", 83.33, 333.32], + ["2023-05-31", 83.33, 416.65], + ["2023-06-30", 83.33, 499.98], + ["2023-07-31", 83.33, 583.31], + ["2023-08-31", 83.33, 666.64], + ["2023-09-30", 83.33, 749.97], + ["2023-10-31", 83.33, 833.3], + ["2023-11-30", 83.33, 916.63], + ["2023-12-31", 83.37, 1000.0], + ] + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Active") + ] + self.assertEqual(schedules, expected_depreciation_before_adjustment) + + current_asset_value = asset.finance_books[0].value_after_depreciation + asset_value_adjustment = make_asset_value_adjustment( + asset=asset, + date="2023-04-01", + current_asset_value=current_asset_value, + new_asset_value=1200, + ) + asset_value_adjustment.submit() + + expected_depreciation_after_adjustment = [ + ["2023-01-31", 100.0, 100.0], + ["2023-02-28", 100.0, 200.0], + ["2023-03-31", 100.0, 300.0], + ["2023-04-30", 100.0, 400.0], + ["2023-05-31", 100.0, 500.0], + ["2023-06-30", 100.0, 600.0], + ["2023-07-31", 100.0, 700.0], + ["2023-08-31", 100.0, 800.0], + ["2023-09-30", 100.0, 900.0], + ["2023-10-31", 100.0, 1000.0], + ["2023-11-30", 100.0, 1100.0], + ["2023-12-31", 100.0, 1200.0], + ] + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Active") + ] + self.assertEqual(schedules, expected_depreciation_after_adjustment) + + asset_value_adjustment.cancel() + asset.reload() + self.assertEqual(asset.finance_books[0].value_after_depreciation, 1000) + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Active") + ] + self.assertEqual(schedules, expected_depreciation_before_adjustment) + + def test_depreciation_schedule_after_cancelling_asset_value_adjustent_for_existing_asset(self): + asset = create_asset( + item_code="Macbook Pro", + gross_purchase_amount=500, + calculate_depreciation=1, + depreciation_method="Straight Line", + available_for_use_date="2023-01-15", + depreciation_start_date="2023-03-31", + frequency_of_depreciation=1, + total_number_of_depreciations=12, + is_existing_asset=1, + opening_accumulated_depreciation=64.52, + opening_number_of_booked_depreciations=2, + submit=1, + ) + + expected_depreciation_before_adjustment = [ + ["2023-03-31", 41.39, 105.91], + ["2023-04-30", 41.39, 147.3], + ["2023-05-31", 41.39, 188.69], + ["2023-06-30", 41.39, 230.08], + ["2023-07-31", 41.39, 271.47], + ["2023-08-31", 41.39, 312.86], + ["2023-09-30", 41.39, 354.25], + ["2023-10-31", 41.39, 395.64], + ["2023-11-30", 41.39, 437.03], + ["2023-12-31", 41.39, 478.42], + ["2024-01-15", 21.58, 500.0], + ] + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Active") + ] + self.assertEqual(schedules, expected_depreciation_before_adjustment) + + current_asset_value = asset.finance_books[0].value_after_depreciation + asset_value_adjustment = make_asset_value_adjustment( + asset=asset, + date="2023-04-01", + current_asset_value=current_asset_value, + new_asset_value=600, + ) + asset_value_adjustment.submit() + + expected_depreciation_after_adjustment = [ + ["2023-03-31", 57.03, 121.55], + ["2023-04-30", 57.03, 178.58], + ["2023-05-31", 57.03, 235.61], + ["2023-06-30", 57.03, 292.64], + ["2023-07-31", 57.03, 349.67], + ["2023-08-31", 57.03, 406.7], + ["2023-09-30", 57.03, 463.73], + ["2023-10-31", 57.03, 520.76], + ["2023-11-30", 57.03, 577.79], + ["2023-12-31", 57.03, 634.82], + ["2024-01-15", 29.7, 664.52], + ] + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Active") + ] + self.assertEqual(schedules, expected_depreciation_after_adjustment) + + asset_value_adjustment.cancel() + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Active") + ] + + self.assertEqual(schedules, expected_depreciation_before_adjustment) + + def test_depreciation_schedule_for_parallel_adjustment_and_repair(self): + asset = create_asset( + item_code="Macbook Pro", + gross_purchase_amount=600, + calculate_depreciation=1, + depreciation_method="Straight Line", + available_for_use_date="2021-01-01", + depreciation_start_date="2021-12-31", + frequency_of_depreciation=12, + total_number_of_depreciations=3, + is_existing_asset=1, + submit=1, + ) + post_depreciation_entries(date="2021-12-31") + asset.reload() + + expected_depreciation_before_adjustment = [ + ["2021-12-31", 200, 200], + ["2022-12-31", 200, 400], + ["2023-12-31", 200, 600], + ] + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Active") + ] + self.assertEqual(schedules, expected_depreciation_before_adjustment) + + current_asset_value = asset.finance_books[0].value_after_depreciation + asset_value_adjustment = make_asset_value_adjustment( + asset=asset, + date="2022-01-15", + current_asset_value=current_asset_value, + new_asset_value=500, + ) + asset_value_adjustment.submit() + + expected_depreciation_after_adjustment = [ + ["2021-12-31", 200, 200], + ["2022-12-31", 250, 450], + ["2023-12-31", 250, 700], + ] + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Active") + ] + self.assertEqual(schedules, expected_depreciation_after_adjustment) + + asset_repair = create_asset_repair( + asset=asset, + capitalize_repair_cost=1, + item="_Test Non Stock Item", + failure_date="2022-01-20", + pi_repair_cost1=60, + pi_repair_cost2=40, + increase_in_asset_life=0, + submit=1, + ) + self.assertEqual(asset_repair.total_repair_cost, 100) + + expected_depreciation_after_repair = [ + ["2021-12-31", 200, 200], + ["2022-12-31", 300, 500], + ["2023-12-31", 300, 800], + ] + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Active") + ] + self.assertEqual(schedules, expected_depreciation_after_repair) + asset.reload() + + asset_value_adjustment.cancel() + + expected_depreciation_after_cancelling_adjustment = [ + ["2021-12-31", 200, 200], + ["2022-12-31", 250, 450], + ["2023-12-31", 250, 700], + ] + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Active") + ] + + self.assertEqual(schedules, expected_depreciation_after_cancelling_adjustment) From 5d16936b9ef9943cee24c03e8bc969d0fcdf9656 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Mon, 17 Mar 2025 12:17:10 +0530 Subject: [PATCH 29/40] fix(test): sales invoice test cases --- .../doctype/sales_invoice/test_sales_invoice.py | 10 +++++----- erpnext/assets/doctype/asset/depreciation.py | 1 + erpnext/assets/doctype/asset_repair/asset_repair.py | 1 - 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 1ee6f55801b..d2eed63df73 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -3013,7 +3013,7 @@ class TestSalesInvoice(ERPNextTestSuite): ) asset.load_from_db() - expected_values = [["2020-12-31", 30000, 30000], ["2021-12-31", 30000, 60000]] + expected_values = [["2020-12-31", 30000, 30000], ["2021-12-31", 30041.15, 60041.15]] for i, schedule in enumerate(get_depr_schedule(asset.name, "Active")): self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date) @@ -3038,10 +3038,10 @@ class TestSalesInvoice(ERPNextTestSuite): expected_values = [ ["2020-06-30", 1366.12, 1366.12, True], ["2021-06-30", 20000.0, 21366.12, True], - ["2022-06-30", 20000.0, 41366.12, False], - ["2023-06-30", 20000.0, 61366.12, False], - ["2024-06-30", 20000.0, 81366.12, False], - ["2025-06-06", 18633.88, 100000.0, False], + ["2022-06-30", 20000.95, 41367.07, False], + ["2023-06-30", 20000.95, 61368.02, False], + ["2024-06-30", 20000.95, 81368.97, False], + ["2025-06-06", 18631.03, 100000.0, False], ] for i, schedule in enumerate(get_depr_schedule(asset.name, "Active")): diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index 3ef116ffa0a..f6ec7bd6c33 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -527,6 +527,7 @@ def cancel_depreciation_entries(asset_doc, date): def reset_depreciation_schedule(asset_doc, notes): if asset_doc.calculate_depreciation: reschedule_depreciation(asset_doc, notes) + asset_doc.set_total_booked_depreciations() def reverse_depreciation_entry_made_on_disposal(asset): diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py index 8bfbb2c7d21..8a4349233e5 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/asset_repair.py @@ -242,7 +242,6 @@ class AssetRepair(AccountsController): def make_gl_entries(self, cancel=False): if cancel: self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry") - self.save() if flt(self.total_repair_cost) > 0: gl_entries = self.get_gl_entries() From 3e32c30298db13c4f5bb5f554e5174e56ea33f25 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Mon, 17 Mar 2025 16:18:04 +0530 Subject: [PATCH 30/40] test: added tests on sale of an asset --- .../test_asset_depreciation_schedule.py | 161 +++++++++++++++++- ...sset_depreciation_schedules_from_assets.py | 2 +- 2 files changed, 161 insertions(+), 2 deletions(-) diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py index 8b4fe96fba3..c3553675420 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py @@ -3,8 +3,9 @@ import frappe from frappe.tests import IntegrationTestCase, UnitTestCase -from frappe.utils import cstr, date_diff, flt +from frappe.utils import cstr, date_diff, flt, getdate +from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.assets.doctype.asset.depreciation import ( post_depreciation_entries, ) @@ -982,3 +983,161 @@ class TestAssetDepreciationSchedule(IntegrationTestCase): ] self.assertEqual(schedules, expected_depreciation_after_cancelling_adjustment) + + def test_depreciation_schedule_after_sale_of_asset(self): + asset = create_asset( + item_code="Macbook Pro", + gross_purchase_amount=600, + calculate_depreciation=1, + depreciation_method="Straight Line", + available_for_use_date="2021-01-01", + depreciation_start_date="2021-12-31", + frequency_of_depreciation=12, + total_number_of_depreciations=3, + is_existing_asset=1, + submit=1, + ) + post_depreciation_entries(date="2021-12-31") + asset.reload() + + expected_depreciation_before_adjustment = [ + ["2021-12-31", 200, 200], + ["2022-12-31", 200, 400], + ["2023-12-31", 200, 600], + ] + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Active") + ] + self.assertEqual(schedules, expected_depreciation_before_adjustment) + + current_asset_value = asset.finance_books[0].value_after_depreciation + asset_value_adjustment = make_asset_value_adjustment( + asset=asset, + date="2022-01-15", + current_asset_value=current_asset_value, + new_asset_value=500, + ) + asset_value_adjustment.submit() + + expected_depreciation_after_adjustment = [ + ["2021-12-31", 200, 200], + ["2022-12-31", 250, 450], + ["2023-12-31", 250, 700], + ] + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Active") + ] + self.assertEqual(schedules, expected_depreciation_after_adjustment) + + si = create_sales_invoice( + item_code="Macbook Pro", asset=asset.name, qty=1, rate=300, posting_date=getdate("2022-04-01") + ) + asset.load_from_db() + + self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold") + + expected_depreciation_after_sale = [ + ["2021-12-31", 200.0, 200.0], + ["2022-04-01", 62.33, 262.33], + ] + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Active") + ] + self.assertEqual(schedules, expected_depreciation_after_sale) + + si.cancel() + asset.reload() + + self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated") + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Active") + ] + self.assertEqual(schedules, expected_depreciation_after_adjustment) + + def test_depreciation_schedule_after_sale_of_asset_wdv_method(self): + asset = create_asset( + item_code="Macbook Pro", + gross_purchase_amount=500, + calculate_depreciation=1, + depreciation_method="Written Down Value", + available_for_use_date="2021-01-01", + depreciation_start_date="2021-12-31", + rate_of_depreciation=50, + frequency_of_depreciation=12, + total_number_of_depreciations=3, + is_existing_asset=1, + submit=1, + ) + post_depreciation_entries(date="2021-12-31") + asset.reload() + + expected_depreciation_before_repair = [ + ["2021-12-31", 250.0, 250.0], + ["2022-12-31", 125.0, 375.0], + ["2023-12-31", 125.0, 500.0], + ] + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Active") + ] + self.assertEqual(schedules, expected_depreciation_before_repair) + + create_asset_repair( + asset=asset, + capitalize_repair_cost=1, + item="_Test Non Stock Item", + failure_date="2022-03-01", + pi_repair_cost1=60, + pi_repair_cost2=40, + increase_in_asset_life=0, + submit=1, + ) + + expected_depreciation_after_repair = [ + ["2021-12-31", 250.0, 250.0], + ["2022-12-31", 175.0, 425.0], + ["2023-12-31", 175.0, 600.0], + ] + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Active") + ] + self.assertEqual(schedules, expected_depreciation_after_repair) + + si = create_sales_invoice( + item_code="Macbook Pro", asset=asset.name, qty=1, rate=300, posting_date=getdate("2022-04-01") + ) + asset.load_from_db() + + self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold") + + expected_depreciation_after_sale = [ + ["2021-12-31", 250.0, 250.0], + ["2022-04-01", 43.63, 293.63], + ] + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Active") + ] + self.assertEqual(schedules, expected_depreciation_after_sale) + + si.cancel() + asset.reload() + self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated") + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Active") + ] + self.assertEqual(schedules, expected_depreciation_after_repair) diff --git a/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py b/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py index ba923b4e701..d4350d8f9a1 100644 --- a/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py +++ b/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py @@ -22,7 +22,7 @@ def execute(): asset_depr_schedule_doc.name, {"docstatus": 1, "status": "Active"}, ) - + update_depreciation_schedules(depreciation_schedules, asset_depr_schedule_doc.name) From 99e8c984ad58ee7ebeae520cd977e713356aa536 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Fri, 21 Mar 2025 00:56:26 +0530 Subject: [PATCH 31/40] fix(test): sales invoice test cases fixes --- .../sales_invoice/test_sales_invoice.py | 29 ------------------- .../test_asset_depreciation_schedule.py | 29 +++++++++++++++++++ 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index d2eed63df73..da0d95828e3 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -3021,35 +3021,6 @@ class TestSalesInvoice(ERPNextTestSuite): self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount) self.assertTrue(schedule.journal_entry) - def test_depreciation_on_return_of_sold_asset(self): - from erpnext.controllers.sales_and_purchase_return import make_return_doc - - create_asset_data() - asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, submit=1) - post_depreciation_entries(getdate("2021-09-30")) - - si = create_sales_invoice( - item_code="Macbook Pro", asset=asset.name, qty=1, rate=90000, posting_date=getdate("2021-09-30") - ) - return_si = make_return_doc("Sales Invoice", si.name) - return_si.submit() - asset.load_from_db() - - expected_values = [ - ["2020-06-30", 1366.12, 1366.12, True], - ["2021-06-30", 20000.0, 21366.12, True], - ["2022-06-30", 20000.95, 41367.07, False], - ["2023-06-30", 20000.95, 61368.02, False], - ["2024-06-30", 20000.95, 81368.97, False], - ["2025-06-06", 18631.03, 100000.0, False], - ] - - for i, schedule in enumerate(get_depr_schedule(asset.name, "Active")): - self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date) - self.assertEqual(expected_values[i][1], schedule.depreciation_amount) - self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount) - self.assertEqual(schedule.journal_entry, schedule.journal_entry) - def test_sales_invoice_against_supplier(self): from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import ( make_customer, diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py index c3553675420..e36042bd6e7 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py @@ -821,6 +821,35 @@ class TestAssetDepreciationSchedule(IntegrationTestCase): ] self.assertEqual(schedules, expected_depreciation_before_adjustment) + def test_depreciation_on_return_of_sold_asset(self): + from erpnext.controllers.sales_and_purchase_return import make_return_doc + + create_asset_data() + asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, submit=1) + post_depreciation_entries(getdate("2021-09-30")) + + si = create_sales_invoice( + item_code="Macbook Pro", asset=asset.name, qty=1, rate=90000, posting_date=getdate("2021-09-30") + ) + return_si = make_return_doc("Sales Invoice", si.name) + return_si.submit() + asset.load_from_db() + + expected_values = [ + ["2020-06-30", 1366.12, 1366.12, True], + ["2021-06-30", 20000.0, 21366.12, True], + ["2022-06-30", 20000.95, 41367.07, False], + ["2023-06-30", 20000.95, 61368.02, False], + ["2024-06-30", 20000.95, 81368.97, False], + ["2025-06-06", 18631.03, 100000.0, False], + ] + + for i, schedule in enumerate(get_depr_schedule(asset.name, "Active")): + self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date) + self.assertEqual(expected_values[i][1], schedule.depreciation_amount) + self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount) + self.assertEqual(schedule.journal_entry, schedule.journal_entry) + def test_depreciation_schedule_after_cancelling_asset_value_adjustent_for_existing_asset(self): asset = create_asset( item_code="Macbook Pro", From 6fa316177b2e96eb0f497b40ded5e5265b57efe3 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Fri, 21 Mar 2025 18:18:40 +0530 Subject: [PATCH 32/40] fix(test): updated test for journal entry --- .../doctype/journal_entry/journal_entry.py | 18 ++++++---- erpnext/assets/doctype/asset/test_asset.py | 34 +++++++------------ 2 files changed, 24 insertions(+), 28 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 456fccd406c..d5070db1735 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -141,7 +141,7 @@ class JournalEntry(AccountsController): self.validate_credit_debit_note() self.validate_empty_accounts_table() self.validate_inter_company_accounts() - self.validate_depr_entry_voucher_type() + self.validate_depr_account_and_depr_entry_voucher_type() self.validate_company_in_accounting_dimension() self.validate_advance_accounts() @@ -268,12 +268,16 @@ class JournalEntry(AccountsController): ): frappe.throw(_("Total Credit/ Debit Amount should be same as linked Journal Entry")) - def validate_depr_entry_voucher_type(self): - if ( - any(d.account_type == "Depreciation" for d in self.get("accounts")) - and self.voucher_type != "Depreciation Entry" - ): - frappe.throw(_("Journal Entry type should be set as Depreciation Entry for asset depreciation")) + def validate_depr_account_and_depr_entry_voucher_type(self): + for d in self.get("accounts"): + if d.account_type == "Depreciation": + if self.voucher_type != "Depreciation Entry": + frappe.throw( + _("Journal Entry type should be set as Depreciation Entry for asset depreciation") + ) + + if frappe.get_cached_value("Account", d.account, "root_type") != "Expense": + frappe.throw(_("Account {0} should be of type Expense").format(d.account)) def validate_stock_accounts(self): stock_accounts = get_stock_accounts(self.company, accounts=self.accounts) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 9ca5e5ee1f0..6f18d38856b 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -1306,8 +1306,6 @@ class TestDepreciationBasics(AssetSetup): self.assertFalse(entry["debit"]) def test_depr_entry_posting_when_depr_expense_account_is_an_income_account(self): - """Tests if the Depreciation Expense Account gets credited and the Accumulated Depreciation Account gets debited when the former's an Income Account.""" - depr_expense_account = frappe.get_doc("Account", "_Test Depreciations - _TC") depr_expense_account.root_type = "Income" depr_expense_account.parent_account = "Income - _TC" @@ -1324,26 +1322,20 @@ class TestDepreciationBasics(AssetSetup): submit=1, ) - post_depreciation_entries(date="2021-06-01") - asset.load_from_db() + jv = make_journal_entry( + "_Test Depreciations - _TC", + "_Test Accumulated Depreciations - _TC", + 100, + posting_date="2020-01-15", + save=False, + ) + for d in jv.accounts: + d.reference_type = "Asset" + d.reference_name = asset.name + jv.voucher_type = "Depreciation Entry" - je = frappe.get_doc("Journal Entry", get_depr_schedule(asset.name, "Active")[0].journal_entry) - accounting_entries = [ - {"account": entry.account, "debit": entry.debit, "credit": entry.credit} for entry in je.accounts - ] - - for entry in accounting_entries: - if entry["account"] == "_Test Depreciations - _TC": - self.assertTrue(entry["credit"]) - self.assertFalse(entry["debit"]) - else: - self.assertTrue(entry["debit"]) - self.assertFalse(entry["credit"]) - - # resetting - depr_expense_account.root_type = "Expense" - depr_expense_account.parent_account = "Expenses - _TC" - depr_expense_account.save() + with self.assertRaises(frappe.ValidationError): + jv.insert() def test_clear_depr_schedule(self): """Tests if clear_depr_schedule() works as expected.""" From e2cfd01bb2b3f87d5983d0bfe3fc8eff479da61b Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Sun, 23 Mar 2025 22:49:03 +0530 Subject: [PATCH 33/40] test: asset splitting test --- erpnext/assets/doctype/asset/test_asset.py | 42 +++++++++++----------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 6f18d38856b..46dcc35134d 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -480,26 +480,26 @@ class TestAsset(AssetSetup): asset = create_asset( calculate_depreciation=1, asset_quantity=10, - available_for_use_date="2020-01-01", - purchase_date="2020-01-01", + available_for_use_date="2023-01-01", + purchase_date="2023-01-01", expected_value_after_useful_life=0, total_number_of_depreciations=6, opening_number_of_booked_depreciations=1, - frequency_of_depreciation=10, - depreciation_start_date="2021-01-01", - opening_accumulated_depreciation=20000, - gross_purchase_amount=120000, + frequency_of_depreciation=12, + depreciation_start_date="2024-03-31", + opening_accumulated_depreciation=493.15, + gross_purchase_amount=12000, submit=1, ) first_asset_depr_schedule = get_asset_depr_schedule_doc(asset.name, "Active") self.assertEqual(first_asset_depr_schedule.status, "Active") - post_depreciation_entries(date="2021-01-01") + post_depreciation_entries(date="2024-03-31") self.assertEqual(asset.asset_quantity, 10) - self.assertEqual(asset.gross_purchase_amount, 120000) - self.assertEqual(asset.opening_accumulated_depreciation, 20000) + self.assertEqual(asset.gross_purchase_amount, 12000) + self.assertEqual(asset.opening_accumulated_depreciation, 493.15) new_asset = split_asset(asset.name, 2) asset.load_from_db() @@ -515,25 +515,25 @@ class TestAsset(AssetSetup): depr_schedule_of_new_asset = first_asset_depr_schedule_of_new_asset.get("depreciation_schedule") self.assertEqual(new_asset.asset_quantity, 2) - self.assertEqual(new_asset.gross_purchase_amount, 24000) - self.assertEqual(new_asset.opening_accumulated_depreciation, 4000) + self.assertEqual(new_asset.gross_purchase_amount, 2400) + self.assertEqual(new_asset.opening_accumulated_depreciation, 98.63) self.assertEqual(new_asset.split_from, asset.name) - self.assertEqual(depr_schedule_of_new_asset[0].depreciation_amount, 4000) - self.assertEqual(depr_schedule_of_new_asset[1].depreciation_amount, 4000) + self.assertEqual(depr_schedule_of_new_asset[0].depreciation_amount, 400) + self.assertEqual(depr_schedule_of_new_asset[1].depreciation_amount, 400) self.assertEqual(asset.asset_quantity, 8) - self.assertEqual(asset.gross_purchase_amount, 96000) - self.assertEqual(asset.opening_accumulated_depreciation, 16000) - self.assertEqual(depr_schedule_of_asset[0].depreciation_amount, 16000) - self.assertEqual(depr_schedule_of_asset[1].depreciation_amount, 16000) + self.assertEqual(asset.gross_purchase_amount, 9600) + self.assertEqual(asset.opening_accumulated_depreciation, 394.52) + self.assertEqual(depr_schedule_of_asset[0].depreciation_amount, 1600) + self.assertEqual(depr_schedule_of_asset[1].depreciation_amount, 1600) journal_entry = depr_schedule_of_asset[0].journal_entry jv = frappe.get_doc("Journal Entry", journal_entry) - self.assertEqual(jv.accounts[0].credit_in_account_currency, 16000) - self.assertEqual(jv.accounts[1].debit_in_account_currency, 16000) - self.assertEqual(jv.accounts[2].credit_in_account_currency, 4000) - self.assertEqual(jv.accounts[3].debit_in_account_currency, 4000) + self.assertEqual(jv.accounts[0].credit_in_account_currency, 1600) + self.assertEqual(jv.accounts[1].debit_in_account_currency, 1600) + self.assertEqual(jv.accounts[2].credit_in_account_currency, 400) + self.assertEqual(jv.accounts[3].debit_in_account_currency, 400) self.assertEqual(jv.accounts[0].reference_name, asset.name) self.assertEqual(jv.accounts[1].reference_name, asset.name) From 782d16c1a337d5d650499c477f63c480387c0fee Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Tue, 25 Mar 2025 14:27:33 +0530 Subject: [PATCH 34/40] fix: linters related fix --- erpnext/assets/doctype/asset/asset.js | 2 +- erpnext/assets/doctype/asset_repair/asset_repair.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index 2e7f8868dcb..94b45bc08e6 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -135,7 +135,7 @@ frappe.ui.form.on("Asset", { ); } - if (in_list(["Submitted", "Partially Depreciated"], frm.doc.status)) { + if (["Submitted", "Partially Depreciated"].includes(frm.doc.status)) { frm.add_custom_button( __("Adjust Asset Value"), function () { diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py index 8a4349233e5..1d7d1791784 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/asset_repair.py @@ -186,6 +186,7 @@ class AssetRepair(AccountsController): self.asset_doc.flags.ignore_validate_update_after_submit = True self.asset_doc.save() + self.asset_doc.db_update() def get_total_value_of_stock_consumed(self): return sum([flt(item.total_value) for item in self.get("stock_items")]) @@ -241,7 +242,7 @@ class AssetRepair(AccountsController): def make_gl_entries(self, cancel=False): if cancel: - self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry") + self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry") # nosemgrep if flt(self.total_repair_cost) > 0: gl_entries = self.get_gl_entries() From 89d10ad4e61cf7a981d3100165f78f2c0909330f Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Tue, 25 Mar 2025 19:54:15 +0530 Subject: [PATCH 35/40] fix: fiscal year check in depreciation logic --- .../deppreciation_schedule_controller.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/deppreciation_schedule_controller.py b/erpnext/assets/doctype/asset_depreciation_schedule/deppreciation_schedule_controller.py index 42f0d98fa4e..c228dfbb8c7 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/deppreciation_schedule_controller.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/deppreciation_schedule_controller.py @@ -59,7 +59,8 @@ class DepreciationScheduleController(StraightLineMethod, WDVMethod): if self.skip_row: continue - if self.has_fiscal_year_changed(row_idx): + self.has_fiscal_year_changed(row_idx) + if self.fiscal_year_changed: self.yearly_opening_wdv = self.pending_depreciation_amount self.get_prev_depreciation_amount(row_idx) From 0e93c573a62d746210b2cd4c47d666371f43d83c Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Wed, 2 Apr 2025 00:28:18 +0530 Subject: [PATCH 36/40] chore: uncomment previously disabled code --- erpnext/assets/doctype/asset/depreciation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index f6ec7bd6c33..6eded8c9a4e 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -513,7 +513,7 @@ def depreciate_asset(asset_doc, date, notes): # As per Income Tax Act (India), the asset should not be depreciated # in the financial year in which it is sold/scraped asset_doc.reload() - # cancel_depreciation_entries(asset_doc, date) + cancel_depreciation_entries(asset_doc, date) @erpnext.allow_regional From c9980c59e7870f200a8051617565c15a2f092781 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Wed, 2 Apr 2025 01:02:07 +0530 Subject: [PATCH 37/40] chore: adjust regional depreciation logic after refactor --- .../asset_depreciation_schedule/depreciation_methods.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/depreciation_methods.py b/erpnext/assets/doctype/asset_depreciation_schedule/depreciation_methods.py index 4bea70e4049..daa3f0b0918 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/depreciation_methods.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/depreciation_methods.py @@ -75,7 +75,12 @@ class StraightLineMethod(Document): class WDVMethod(Document): + @erpnext.allow_regional def get_wdv_or_dd_depr_amount(self, row_idx): + return self.calculate_wdv_or_dd_based_depreciation_amount(row_idx) + + @staticmethod + def calculate_wdv_or_dd_based_depreciation_amount(self, row_idx): if self.fb_row.daily_prorata_based: return self.get_daily_prorata_based_wdv_depr_amount(row_idx) else: From dc0611642560e009bf7a2f49fcda1a62580d70d7 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Wed, 2 Apr 2025 01:19:28 +0530 Subject: [PATCH 38/40] fix: pass instance explicitly in static method --- .../doctype/asset_depreciation_schedule/depreciation_methods.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/depreciation_methods.py b/erpnext/assets/doctype/asset_depreciation_schedule/depreciation_methods.py index daa3f0b0918..1b235b1fa78 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/depreciation_methods.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/depreciation_methods.py @@ -77,7 +77,7 @@ class StraightLineMethod(Document): class WDVMethod(Document): @erpnext.allow_regional def get_wdv_or_dd_depr_amount(self, row_idx): - return self.calculate_wdv_or_dd_based_depreciation_amount(row_idx) + return WDVMethod.calculate_wdv_or_dd_based_depreciation_amount(self, row_idx) @staticmethod def calculate_wdv_or_dd_based_depreciation_amount(self, row_idx): From 3465c21090e4009c074a89ffdf5fbd71d5be54f8 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Mon, 5 May 2025 17:45:35 +0530 Subject: [PATCH 39/40] fix: failing test --- erpnext/assets/doctype/asset/test_asset.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 46dcc35134d..738573ea08e 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -193,7 +193,7 @@ class TestAsset(AssetSetup): self.assertEqual(doc.items[0].is_fixed_asset, 1) def test_scrap_asset(self): - date = nowdate() + date = "2025-05-05" purchase_date = add_months(get_first_day(date), -2) asset = create_asset( @@ -256,7 +256,7 @@ class TestAsset(AssetSetup): asset.precision("gross_purchase_amount"), ) - second_asset_depr_schedule.depreciation_amount = 8932.70 + second_asset_depr_schedule.depreciation_amount = 9006.17 second_asset_depr_schedule.asset_doc = asset second_asset_depr_schedule.get_finance_book_row() second_asset_depr_schedule.fetch_asset_details() @@ -264,7 +264,7 @@ class TestAsset(AssetSetup): pro_rata_amount, _, _ = second_asset_depr_schedule._get_pro_rata_amt( add_days(get_last_day(add_months(purchase_date, 1)), 1), date, - original_schedule_date=get_last_day(nowdate()), + original_schedule_date=get_last_day(date), ) pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount")) self.assertEqual( @@ -312,7 +312,7 @@ class TestAsset(AssetSetup): self.assertEqual(accumulated_depr_amount, 18000.0 + this_month_depr_amount) def test_gle_made_by_asset_sale(self): - date = nowdate() + date = "2025-05-05" purchase_date = add_months(get_first_day(date), -2) asset = create_asset( @@ -332,7 +332,7 @@ class TestAsset(AssetSetup): si = make_sales_invoice(asset=asset.name, item_code="Macbook Pro", company="_Test Company") si.customer = "_Test Customer" - si.due_date = nowdate() + si.due_date = date si.get("items")[0].rate = 25000 si.insert() si.submit() @@ -345,7 +345,7 @@ class TestAsset(AssetSetup): self.assertEqual(second_asset_depr_schedule.status, "Active") self.assertEqual(first_asset_depr_schedule.status, "Cancelled") - second_asset_depr_schedule.depreciation_amount = 8932.70 + second_asset_depr_schedule.depreciation_amount = 9006.17 second_asset_depr_schedule.asset_doc = asset second_asset_depr_schedule.get_finance_book_row() second_asset_depr_schedule.fetch_asset_details() @@ -353,7 +353,7 @@ class TestAsset(AssetSetup): pro_rata_amount, _, _ = second_asset_depr_schedule._get_pro_rata_amt( add_days(get_last_day(add_months(purchase_date, 1)), 1), date, - original_schedule_date=get_last_day(nowdate()), + original_schedule_date=get_last_day(date), ) pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount")) From 04e46ea787bf4de77720c1d223c58c0fb30753d1 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Thu, 8 May 2025 15:13:13 +0530 Subject: [PATCH 40/40] fix: more changes in the test --- erpnext/assets/doctype/asset/asset.py | 2 +- erpnext/assets/doctype/asset/test_asset.py | 21 +++++++------------ .../doctype/asset_repair/asset_repair.py | 3 +-- 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index f118b3bf52a..9f57e4165fb 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -933,7 +933,7 @@ def get_asset_naming_series(): @frappe.whitelist() -def make_sales_invoice(asset, item_code, company, serial_no=None): +def make_sales_invoice(asset, item_code, company, serial_no=None, posting_date=None): asset_doc = frappe.get_doc("Asset", asset) si = frappe.new_doc("Sales Invoice") si.company = company diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 738573ea08e..9e2207367ba 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -243,7 +243,7 @@ class TestAsset(AssetSetup): frappe.ValidationError, scrap_asset, asset.name, scrap_date=before_last_booked_depreciation_date ) - scrap_asset(asset.name) + scrap_asset(asset.name, date) asset.load_from_db() first_asset_depr_schedule.load_from_db() @@ -312,7 +312,7 @@ class TestAsset(AssetSetup): self.assertEqual(accumulated_depr_amount, 18000.0 + this_month_depr_amount) def test_gle_made_by_asset_sale(self): - date = "2025-05-05" + date = nowdate() purchase_date = add_months(get_first_day(date), -2) asset = create_asset( @@ -345,22 +345,17 @@ class TestAsset(AssetSetup): self.assertEqual(second_asset_depr_schedule.status, "Active") self.assertEqual(first_asset_depr_schedule.status, "Cancelled") - second_asset_depr_schedule.depreciation_amount = 9006.17 - second_asset_depr_schedule.asset_doc = asset - second_asset_depr_schedule.get_finance_book_row() - second_asset_depr_schedule.fetch_asset_details() - - pro_rata_amount, _, _ = second_asset_depr_schedule._get_pro_rata_amt( - add_days(get_last_day(add_months(purchase_date, 1)), 1), - date, - original_schedule_date=get_last_day(date), + asset.load_from_db() + accumulated_depr_amount = flt( + asset.gross_purchase_amount - asset.finance_books[0].value_after_depreciation, + asset.precision("gross_purchase_amount"), ) - pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount")) + pro_rata_amount = flt(accumulated_depr_amount - 18000) expected_gle = ( ( "_Test Accumulated Depreciations - _TC", - flt(18000.0 + pro_rata_amount, asset.precision("gross_purchase_amount")), + flt(accumulated_depr_amount, asset.precision("gross_purchase_amount")), 0.0, ), ("_Test Fixed Asset - _TC", 0.0, 100000.0), diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py index 1d7d1791784..8a4349233e5 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/asset_repair.py @@ -186,7 +186,6 @@ class AssetRepair(AccountsController): self.asset_doc.flags.ignore_validate_update_after_submit = True self.asset_doc.save() - self.asset_doc.db_update() def get_total_value_of_stock_consumed(self): return sum([flt(item.total_value) for item in self.get("stock_items")]) @@ -242,7 +241,7 @@ class AssetRepair(AccountsController): def make_gl_entries(self, cancel=False): if cancel: - self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry") # nosemgrep + self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry") if flt(self.total_repair_cost) > 0: gl_entries = self.get_gl_entries()