From d37feb8208fac99c38c8417c01173c3dfd136c3f Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 19 Jan 2022 10:00:05 +0530 Subject: [PATCH 1/2] feat: Splitting group assets --- erpnext/assets/doctype/asset/asset.js | 41 +++ erpnext/assets/doctype/asset/asset.json | 20 +- erpnext/assets/doctype/asset/asset.py | 344 +++++++++++++-------- erpnext/assets/doctype/asset/test_asset.py | 27 +- 4 files changed, 303 insertions(+), 129 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index 153f5c537a..f0e59ee115 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -108,6 +108,10 @@ frappe.ui.form.on('Asset', { frm.trigger("create_asset_repair"); }, __("Manage")); + frm.add_custom_button(__("Split Asset"), function() { + frm.trigger("split_asset"); + }, __("Manage")); + if (frm.doc.status != 'Fully Depreciated') { frm.add_custom_button(__("Adjust Asset Value"), function() { frm.trigger("create_asset_value_adjustment"); @@ -322,6 +326,43 @@ frappe.ui.form.on('Asset', { }); }, + split_asset: function(frm) { + const title = __('Split Asset'); + + const fields = [ + { + fieldname: 'split_qty', + fieldtype:'Int', + label: __('Split Qty'), + reqd: 1 + } + ]; + + let dialog = new frappe.ui.Dialog({ + title: title, + fields: fields + }); + + dialog.set_primary_action(__('Split'), function() { + const dialog_data = dialog.get_values(); + frappe.call({ + args: { + "asset_name": frm.doc.name, + "split_qty": cint(dialog_data.split_qty) + }, + method: "erpnext.assets.doctype.asset.asset.split_asset", + callback: function(r) { + let doclist = frappe.model.sync(r.message); + frappe.set_route("Form", doclist[0].doctype, doclist[0].name); + } + }); + + dialog.hide(); + }); + + dialog.show(); + }, + create_asset_value_adjustment: function(frm) { frappe.call({ args: { diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index de060757e2..72f692c533 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -3,7 +3,7 @@ "allow_import": 1, "allow_rename": 1, "autoname": "naming_series:", - "creation": "2016-03-01 17:01:27.920130", + "creation": "2022-01-18 02:26:55.975005", "doctype": "DocType", "document_type": "Document", "engine": "InnoDB", @@ -23,6 +23,8 @@ "asset_name", "asset_category", "location", + "split_from", + "qty", "custodian", "department", "disposal_date", @@ -480,6 +482,18 @@ "fieldname": "section_break_36", "fieldtype": "Section Break", "label": "Finance Books" + }, + { + "fieldname": "qty", + "fieldtype": "Int", + "label": "Qty" + }, + { + "fieldname": "split_from", + "fieldtype": "Link", + "label": "Split From", + "options": "Asset", + "read_only": 1 } ], "idx": 72, @@ -502,10 +516,11 @@ "link_fieldname": "asset" } ], - "modified": "2021-06-24 14:58:51.097908", + "modified": "2022-01-18 09:15:34.238601", "modified_by": "Administrator", "module": "Assets", "name": "Asset", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { @@ -542,6 +557,7 @@ "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "title_field": "asset_name", "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index ee3ec8e63a..3d51af80da 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -37,7 +37,8 @@ class Asset(AccountsController): self.validate_asset_and_reference() self.validate_item() self.set_missing_values() - self.prepare_depreciation_data() + if not self.split_from: + self.prepare_depreciation_data() self.validate_gross_and_purchase_amount() if self.get("schedules"): self.validate_expected_value_after_useful_life() @@ -188,142 +189,142 @@ class Asset(AccountsController): start = self.clear_depreciation_schedule() for finance_book in self.get('finance_books'): - self.validate_asset_finance_books(finance_book) + self._make_depreciation_schedule(finance_book, start, date_of_sale) - # value_after_depreciation - current Asset value - if self.docstatus == 1 and finance_book.value_after_depreciation: - value_after_depreciation = flt(finance_book.value_after_depreciation) - else: - value_after_depreciation = (flt(self.gross_purchase_amount) - - flt(self.opening_accumulated_depreciation)) + def _make_depreciation_schedule(self, finance_book, start, date_of_sale): + self.validate_asset_finance_books(finance_book) - finance_book.value_after_depreciation = value_after_depreciation + value_after_depreciation = self._get_value_after_depreciation(finance_book) + finance_book.value_after_depreciation = value_after_depreciation - number_of_pending_depreciations = cint(finance_book.total_number_of_depreciations) - \ - cint(self.number_of_depreciations_booked) + number_of_pending_depreciations = cint(finance_book.total_number_of_depreciations) - \ + cint(self.number_of_depreciations_booked) - has_pro_rata = self.check_is_pro_rata(finance_book) + has_pro_rata = self.check_is_pro_rata(finance_book) + if has_pro_rata: + number_of_pending_depreciations += 1 - if has_pro_rata: - number_of_pending_depreciations += 1 + skip_row = False - skip_row = False + for n in range(start[finance_book.idx-1], number_of_pending_depreciations): + # If depreciation is already completed (for double declining balance) + if skip_row: continue - for n in range(start[finance_book.idx-1], number_of_pending_depreciations): - # If depreciation is already completed (for double declining balance) - if skip_row: continue + depreciation_amount = get_depreciation_amount(self, value_after_depreciation, finance_book) - depreciation_amount = get_depreciation_amount(self, value_after_depreciation, finance_book) + if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1: + schedule_date = add_months(finance_book.depreciation_start_date, + n * cint(finance_book.frequency_of_depreciation)) - if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1: - schedule_date = add_months(finance_book.depreciation_start_date, - n * cint(finance_book.frequency_of_depreciation)) + # schedule date will be a year later from start date + # so monthly schedule date is calculated by removing 11 months from it + monthly_schedule_date = add_months(schedule_date, - finance_book.frequency_of_depreciation + 1) - # schedule date will be a year later from start date - # so monthly schedule date is calculated by removing 11 months from it - monthly_schedule_date = add_months(schedule_date, - finance_book.frequency_of_depreciation + 1) - - # if asset is being sold - if date_of_sale: - from_date = self.get_from_date(finance_book.finance_book) - depreciation_amount, days, months = self.get_pro_rata_amt(finance_book, depreciation_amount, - from_date, date_of_sale) - - if depreciation_amount > 0: - self.append("schedules", { - "schedule_date": date_of_sale, - "depreciation_amount": depreciation_amount, - "depreciation_method": finance_book.depreciation_method, - "finance_book": finance_book.finance_book, - "finance_book_id": finance_book.idx - }) - - break - - # For first row - if has_pro_rata and not self.opening_accumulated_depreciation and n==0: - depreciation_amount, days, months = self.get_pro_rata_amt(finance_book, depreciation_amount, - self.available_for_use_date, finance_book.depreciation_start_date) - - # For first depr schedule date will be the start date - # so monthly schedule date is calculated by removing month difference between use date and start date - monthly_schedule_date = add_months(finance_book.depreciation_start_date, - months + 1) - - # For last row - elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1: - if not self.flags.increase_in_asset_life: - # In case of increase_in_asset_life, the self.to_date is already set on asset_repair submission - self.to_date = add_months(self.available_for_use_date, - (n + self.number_of_depreciations_booked) * cint(finance_book.frequency_of_depreciation)) - - depreciation_amount_without_pro_rata = depreciation_amount - - depreciation_amount, days, months = self.get_pro_rata_amt(finance_book, - depreciation_amount, schedule_date, self.to_date) - - depreciation_amount = self.get_adjusted_depreciation_amount(depreciation_amount_without_pro_rata, - depreciation_amount, finance_book.finance_book) - - monthly_schedule_date = add_months(schedule_date, 1) - schedule_date = add_days(schedule_date, days) - last_schedule_date = schedule_date - - if not depreciation_amount: continue - value_after_depreciation -= flt(depreciation_amount, - self.precision("gross_purchase_amount")) - - # Adjust depreciation amount in the last period based on the expected value after useful life - if finance_book.expected_value_after_useful_life and ((n == cint(number_of_pending_depreciations) - 1 - and value_after_depreciation != finance_book.expected_value_after_useful_life) - or value_after_depreciation < finance_book.expected_value_after_useful_life): - depreciation_amount += (value_after_depreciation - finance_book.expected_value_after_useful_life) - skip_row = True + # if asset is being sold + if date_of_sale: + from_date = self.get_from_date(finance_book.finance_book) + depreciation_amount, days, months = self.get_pro_rata_amt(finance_book, depreciation_amount, + from_date, date_of_sale) if depreciation_amount > 0: - # With monthly depreciation, each depreciation is divided by months remaining until next date - if self.allow_monthly_depreciation: - # month range is 1 to 12 - # In pro rata case, for first and last depreciation, month range would be different - month_range = months \ - if (has_pro_rata and n==0) or (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) \ - else finance_book.frequency_of_depreciation + self._add_depreciation_row(date_of_sale, depreciation_amount, finance_book.depreciation_method, + finance_book.finance_book, finance_book.idx) - for r in range(month_range): - if (has_pro_rata and n == 0): - # For first entry of monthly depr - if r == 0: - days_until_first_depr = date_diff(monthly_schedule_date, self.available_for_use_date) - per_day_amt = depreciation_amount / days - depreciation_amount_for_current_month = per_day_amt * days_until_first_depr - depreciation_amount -= depreciation_amount_for_current_month - date = monthly_schedule_date - amount = depreciation_amount_for_current_month - else: - date = add_months(monthly_schedule_date, r) - amount = depreciation_amount / (month_range - 1) - elif (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) and r == cint(month_range) - 1: - # For last entry of monthly depr - date = last_schedule_date - amount = depreciation_amount / month_range + break + + # For first row + if has_pro_rata and not self.opening_accumulated_depreciation and n==0: + depreciation_amount, days, months = self.get_pro_rata_amt(finance_book, depreciation_amount, + self.available_for_use_date, finance_book.depreciation_start_date) + + # For first depr schedule date will be the start date + # so monthly schedule date is calculated by removing month difference between use date and start date + monthly_schedule_date = add_months(finance_book.depreciation_start_date, - months + 1) + + # For last row + elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1: + if not self.flags.increase_in_asset_life: + # In case of increase_in_asset_life, the self.to_date is already set on asset_repair submission + self.to_date = add_months(self.available_for_use_date, + (n + self.number_of_depreciations_booked) * cint(finance_book.frequency_of_depreciation)) + + depreciation_amount_without_pro_rata = depreciation_amount + + depreciation_amount, days, months = self.get_pro_rata_amt(finance_book, + depreciation_amount, schedule_date, self.to_date) + + depreciation_amount = self.get_adjusted_depreciation_amount(depreciation_amount_without_pro_rata, + depreciation_amount, finance_book.finance_book) + + monthly_schedule_date = add_months(schedule_date, 1) + schedule_date = add_days(schedule_date, days) + last_schedule_date = schedule_date + + if not depreciation_amount: continue + value_after_depreciation -= flt(depreciation_amount, + self.precision("gross_purchase_amount")) + + # Adjust depreciation amount in the last period based on the expected value after useful life + if finance_book.expected_value_after_useful_life and ((n == cint(number_of_pending_depreciations) - 1 + and value_after_depreciation != finance_book.expected_value_after_useful_life) + or value_after_depreciation < finance_book.expected_value_after_useful_life): + depreciation_amount += (value_after_depreciation - finance_book.expected_value_after_useful_life) + skip_row = True + + if depreciation_amount > 0: + # With monthly depreciation, each depreciation is divided by months remaining until next date + if self.allow_monthly_depreciation: + # month range is 1 to 12 + # In pro rata case, for first and last depreciation, month range would be different + month_range = months \ + if (has_pro_rata and n==0) or (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) \ + else finance_book.frequency_of_depreciation + + for r in range(month_range): + if (has_pro_rata and n == 0): + # For first entry of monthly depr + if r == 0: + days_until_first_depr = date_diff(monthly_schedule_date, self.available_for_use_date) + per_day_amt = depreciation_amount / days + depreciation_amount_for_current_month = per_day_amt * days_until_first_depr + depreciation_amount -= depreciation_amount_for_current_month + date = monthly_schedule_date + amount = depreciation_amount_for_current_month else: date = add_months(monthly_schedule_date, r) - amount = depreciation_amount / month_range + amount = depreciation_amount / (month_range - 1) + elif (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) and r == cint(month_range) - 1: + # For last entry of monthly depr + date = last_schedule_date + amount = depreciation_amount / month_range + else: + date = add_months(monthly_schedule_date, r) + amount = depreciation_amount / month_range - self.append("schedules", { - "schedule_date": date, - "depreciation_amount": amount, - "depreciation_method": finance_book.depreciation_method, - "finance_book": finance_book.finance_book, - "finance_book_id": finance_book.idx - }) - else: - self.append("schedules", { - "schedule_date": schedule_date, - "depreciation_amount": depreciation_amount, - "depreciation_method": finance_book.depreciation_method, - "finance_book": finance_book.finance_book, - "finance_book_id": finance_book.idx - }) + self._add_depreciation_row(date, amount, finance_book.depreciation_method, + finance_book.finance_book, finance_book.idx) + else: + self._add_depreciation_row(schedule_date, depreciation_amount, finance_book.depreciation_method, + finance_book.finance_book, finance_book.idx) + + def _add_depreciation_row(self, schedule_date, depreciation_amount, depreciation_method, finance_book, finance_book_id): + self.append("schedules", { + "schedule_date": schedule_date, + "depreciation_amount": depreciation_amount, + "depreciation_method": depreciation_method, + "finance_book": finance_book, + "finance_book_id": finance_book_id + }) + + def _get_value_after_depreciation(self, finance_book): + # value_after_depreciation - current Asset value + if self.docstatus == 1 and finance_book.value_after_depreciation: + value_after_depreciation = flt(finance_book.value_after_depreciation) + else: + value_after_depreciation = (flt(self.gross_purchase_amount) - + flt(self.opening_accumulated_depreciation)) + + return value_after_depreciation # depreciation schedules need to be cleared before modification due to increase in asset life/asset sales # JE: Journal Entry, FB: Finance Book @@ -333,7 +334,6 @@ class Asset(AccountsController): depr_schedule = [] for schedule in self.get('schedules'): - # to update start when there are JEs linked with all the schedule rows corresponding to an FB if len(start) == (int(schedule.finance_book_id) - 2): start.append(num_of_depreciations_completed) @@ -907,3 +907,103 @@ def get_depreciation_amount(asset, depreciable_value, row): depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100)) return depreciation_amount + +@frappe.whitelist() +def split_asset(asset_name, split_qty): + asset = frappe.get_doc("Asset", asset_name) + split_qty = cint(split_qty) + + if split_qty >= asset.qty: + frappe.throw(_("Split qty cannot be grater than or equal to asset qty")) + + remaining_qty = asset.qty - split_qty + + # Update gross purchase amount + new_gross_purchase_amount = flt((asset.gross_purchase_amount * split_qty) / asset.qty) + remaining_gross_purchase_amount = flt((asset.gross_purchase_amount * split_qty) / asset.qty) + + new_asset = create_new_asset_after_split(asset, new_gross_purchase_amount, split_qty) + update_existing_asset(asset, remaining_gross_purchase_amount, remaining_qty) + + return new_asset + +def update_existing_asset(asset, remaingin_gross_purchase_amount, remaining_qty): + frappe.db.set_value("Asset", asset.name, { + 'gross_purchase_amount': remaingin_gross_purchase_amount, + 'qty': remaining_qty + }) + + for finance_book in asset.get('finance_books'): + value_after_depreciation = flt((finance_book.value_after_depreciation * remaining_qty)/asset.qty) + frappe.db.set_value('Asset Finance Book', finance_book.name, 'value_after_depreciation', value_after_depreciation) + + accumulated_depreciation = 0 + + for term in asset.get('schedules'): + depreciation_amount = flt((term.depreciation_amount * remaining_qty)/asset.qty) + frappe.db.set_value('Depreciation Schedule', term.name, 'depreciation_amount', depreciation_amount) + accumulated_depreciation += depreciation_amount + frappe.db.set_value('Depreciation Schedule', term.name, 'accumulated_depreciation_amount', depreciation_amount) + +def create_new_asset_after_split(asset, new_gross_purchase_amount, split_qty): + new_asset = frappe.copy_doc(asset) + + new_asset.gross_purchase_amount = new_gross_purchase_amount + new_asset.qty = split_qty + new_asset.split_from = asset.name + accumulated_depreciation = 0 + + for finance_book in new_asset.get('finance_books'): + finance_book.value_after_depreciation = flt((finance_book.value_after_depreciation * split_qty)/asset.qty) + + for term in new_asset.get('schedules'): + depreciation_amount = flt((term.depreciation_amount * split_qty)/asset.qty) + term.depreciation_amount = depreciation_amount + accumulated_depreciation += depreciation_amount + term.accumulated_depreciation_amount = accumulated_depreciation + + new_asset.submit() + new_asset.set_status() + + for term in new_asset.get('schedules'): + # 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 + +def add_reference_in_jv_on_split(entry_name, new_asset_name, old_asset_name, depreciation_amount): + journal_entry = frappe.get_doc('Journal Entry', entry_name) + entries_to_add = [] + + 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 + + for entry in entries_to_add: + entry.reference_name = new_asset_name + if entry.credit: + entry.credit = depreciation_amount + entry.credit_in_account_currency = entry.exchange_rate * depreciation_amount + elif entry.debit: + entry.debit = depreciation_amount + entry.debit_in_account_currency = entry.exchange_rate * depreciation_amount + + 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() \ No newline at end of file diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 44c4ce542d..283b2ea161 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -7,7 +7,7 @@ import frappe from frappe.utils import add_days, add_months, cstr, flt, get_last_day, getdate, nowdate from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice -from erpnext.assets.doctype.asset.asset import make_sales_invoice +from erpnext.assets.doctype.asset.asset import make_sales_invoice, split_asset from erpnext.assets.doctype.asset.depreciation import ( post_depreciation_entries, restore_asset, @@ -28,9 +28,9 @@ class AssetSetup(unittest.TestCase): make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location") frappe.db.sql("delete from `tabTax Rule`") - @classmethod - def tearDownClass(cls): - frappe.db.rollback() + # @classmethod + # def tearDownClass(cls): + # frappe.db.rollback() class TestAsset(AssetSetup): def test_asset_category_is_fetched(self): @@ -222,6 +222,22 @@ class TestAsset(AssetSetup): si.cancel() self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated") + def test_asset_splitting(self): + asset = create_asset( + calculate_depreciation = 1, + qty=10, + available_for_use_date = '2020-01-01', + purchase_date = '2020-01-01', + expected_value_after_useful_life = 0, + total_number_of_depreciations = 5, + frequency_of_depreciation = 10, + depreciation_start_date = '2021-01-01', + submit = 1 + ) + + post_depreciation_entries(date="2021-01-01") + split_asset(asset.name, 5) + def test_expense_head(self): pr = make_purchase_receipt(item_code="Macbook Pro", qty=2, rate=200000.0, location="Test Location") @@ -1164,7 +1180,8 @@ def create_asset(**args): "available_for_use_date": args.available_for_use_date or "2020-06-06", "location": args.location or "Test Location", "asset_owner": args.asset_owner or "Company", - "is_existing_asset": args.is_existing_asset or 1 + "is_existing_asset": args.is_existing_asset or 1, + "qty": args.get("qty") or 1 }) if asset.calculate_depreciation: From 4abc7da2c04d6aaac366b4c2505181c620152f13 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 19 Jan 2022 14:28:00 +0530 Subject: [PATCH 2/2] chore: Add test case --- erpnext/assets/doctype/asset/asset.js | 2 +- erpnext/assets/doctype/asset/asset.json | 14 +++---- erpnext/assets/doctype/asset/asset.py | 46 ++++++++++++-------- erpnext/assets/doctype/asset/test_asset.py | 49 ++++++++++++++++++---- 4 files changed, 78 insertions(+), 33 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index f0e59ee115..f414930d72 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -332,7 +332,7 @@ frappe.ui.form.on('Asset', { const fields = [ { fieldname: 'split_qty', - fieldtype:'Int', + fieldtype: 'Int', label: __('Split Qty'), reqd: 1 } diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index 72f692c533..9dba17121e 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -24,7 +24,6 @@ "asset_category", "location", "split_from", - "qty", "custodian", "department", "disposal_date", @@ -37,6 +36,7 @@ "available_for_use_date", "column_break_23", "gross_purchase_amount", + "asset_quantity", "purchase_date", "section_break_23", "calculate_depreciation", @@ -483,17 +483,17 @@ "fieldtype": "Section Break", "label": "Finance Books" }, - { - "fieldname": "qty", - "fieldtype": "Int", - "label": "Qty" - }, { "fieldname": "split_from", "fieldtype": "Link", "label": "Split From", "options": "Asset", "read_only": 1 + }, + { + "fieldname": "asset_quantity", + "fieldtype": "Int", + "label": "Asset Quantity" } ], "idx": 72, @@ -516,7 +516,7 @@ "link_fieldname": "asset" } ], - "modified": "2022-01-18 09:15:34.238601", + "modified": "2022-01-19 01:36:51.361485", "modified_by": "Administrator", "module": "Assets", "name": "Asset", diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 3d51af80da..01c1f71649 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -913,51 +913,57 @@ def split_asset(asset_name, split_qty): asset = frappe.get_doc("Asset", asset_name) split_qty = cint(split_qty) - if split_qty >= asset.qty: + if split_qty >= asset.asset_quantity: frappe.throw(_("Split qty cannot be grater than or equal to asset qty")) - remaining_qty = asset.qty - split_qty + remaining_qty = asset.asset_quantity - split_qty - # Update gross purchase amount - new_gross_purchase_amount = flt((asset.gross_purchase_amount * split_qty) / asset.qty) - remaining_gross_purchase_amount = flt((asset.gross_purchase_amount * split_qty) / asset.qty) - - new_asset = create_new_asset_after_split(asset, new_gross_purchase_amount, split_qty) - update_existing_asset(asset, remaining_gross_purchase_amount, remaining_qty) + new_asset = create_new_asset_after_split(asset, split_qty) + update_existing_asset(asset, remaining_qty) return new_asset -def update_existing_asset(asset, remaingin_gross_purchase_amount, remaining_qty): +def update_existing_asset(asset, remaining_qty): + 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) + frappe.db.set_value("Asset", asset.name, { - 'gross_purchase_amount': remaingin_gross_purchase_amount, - 'qty': remaining_qty + 'opening_accumulated_depreciation': opening_accumulated_depreciation, + 'gross_purchase_amount': remaining_gross_purchase_amount, + 'asset_quantity': remaining_qty }) for finance_book in asset.get('finance_books'): - value_after_depreciation = flt((finance_book.value_after_depreciation * remaining_qty)/asset.qty) + value_after_depreciation = flt((finance_book.value_after_depreciation * remaining_qty)/asset.asset_quantity) + expected_value_after_useful_life = flt((finance_book.expected_value_after_useful_life * remaining_qty)/asset.asset_quantity) frappe.db.set_value('Asset Finance Book', finance_book.name, 'value_after_depreciation', value_after_depreciation) + frappe.db.set_value('Asset Finance Book', finance_book.name, 'expected_value_after_useful_life', expected_value_after_useful_life) accumulated_depreciation = 0 for term in asset.get('schedules'): - depreciation_amount = flt((term.depreciation_amount * remaining_qty)/asset.qty) + depreciation_amount = flt((term.depreciation_amount * remaining_qty)/asset.asset_quantity) frappe.db.set_value('Depreciation Schedule', term.name, 'depreciation_amount', depreciation_amount) accumulated_depreciation += depreciation_amount - frappe.db.set_value('Depreciation Schedule', term.name, 'accumulated_depreciation_amount', depreciation_amount) + frappe.db.set_value('Depreciation Schedule', term.name, 'accumulated_depreciation_amount', accumulated_depreciation) -def create_new_asset_after_split(asset, new_gross_purchase_amount, split_qty): +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) new_asset.gross_purchase_amount = new_gross_purchase_amount - new_asset.qty = split_qty + new_asset.opening_accumulated_depreciation = opening_accumulated_depreciation + new_asset.asset_quantity = split_qty new_asset.split_from = asset.name accumulated_depreciation = 0 for finance_book in new_asset.get('finance_books'): - finance_book.value_after_depreciation = flt((finance_book.value_after_depreciation * split_qty)/asset.qty) + finance_book.value_after_depreciation = flt((finance_book.value_after_depreciation * split_qty)/asset.asset_quantity) + finance_book.expected_value_after_useful_life = flt((finance_book.expected_value_after_useful_life * split_qty)/asset.asset_quantity) for term in new_asset.get('schedules'): - depreciation_amount = flt((term.depreciation_amount * split_qty)/asset.qty) + 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 @@ -975,6 +981,7 @@ def create_new_asset_after_split(asset, new_gross_purchase_amount, split_qty): def add_reference_in_jv_on_split(entry_name, new_asset_name, old_asset_name, depreciation_amount): journal_entry = frappe.get_doc('Journal Entry', entry_name) entries_to_add = [] + idx = len(journal_entry.get('accounts')) + 1 for account in journal_entry.get('accounts'): if account.reference_name == old_asset_name: @@ -997,6 +1004,9 @@ def add_reference_in_jv_on_split(entry_name, new_asset_name, old_asset_name, dep 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 diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 283b2ea161..7fdcb0dd3b 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -28,9 +28,9 @@ class AssetSetup(unittest.TestCase): make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location") frappe.db.sql("delete from `tabTax Rule`") - # @classmethod - # def tearDownClass(cls): - # frappe.db.rollback() + @classmethod + def tearDownClass(cls): + frappe.db.rollback() class TestAsset(AssetSetup): def test_asset_category_is_fetched(self): @@ -225,18 +225,53 @@ class TestAsset(AssetSetup): def test_asset_splitting(self): asset = create_asset( calculate_depreciation = 1, - qty=10, + asset_quantity=10, available_for_use_date = '2020-01-01', purchase_date = '2020-01-01', expected_value_after_useful_life = 0, - total_number_of_depreciations = 5, + total_number_of_depreciations = 6, + number_of_depreciations_booked = 1, frequency_of_depreciation = 10, depreciation_start_date = '2021-01-01', + opening_accumulated_depreciation=20000, + gross_purchase_amount=120000, submit = 1 ) post_depreciation_entries(date="2021-01-01") - split_asset(asset.name, 5) + + self.assertEqual(asset.asset_quantity, 10) + self.assertEqual(asset.gross_purchase_amount, 120000) + self.assertEqual(asset.opening_accumulated_depreciation, 20000) + + new_asset = split_asset(asset.name, 2) + asset.load_from_db() + + 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.split_from, asset.name) + self.assertEqual(new_asset.schedules[0].depreciation_amount, 4000) + self.assertEqual(new_asset.schedules[1].depreciation_amount, 4000) + + self.assertEqual(asset.asset_quantity, 8) + self.assertEqual(asset.gross_purchase_amount, 96000) + self.assertEqual(asset.opening_accumulated_depreciation, 16000) + self.assertEqual(asset.schedules[0].depreciation_amount, 16000) + self.assertEqual(asset.schedules[1].depreciation_amount, 16000) + + journal_entry = asset.schedules[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].reference_name, asset.name) + self.assertEqual(jv.accounts[1].reference_name, asset.name) + self.assertEqual(jv.accounts[2].reference_name, new_asset.name) + self.assertEqual(jv.accounts[3].reference_name, new_asset.name) def test_expense_head(self): pr = make_purchase_receipt(item_code="Macbook Pro", @@ -1181,7 +1216,7 @@ def create_asset(**args): "location": args.location or "Test Location", "asset_owner": args.asset_owner or "Company", "is_existing_asset": args.is_existing_asset or 1, - "qty": args.get("qty") or 1 + "asset_quantity": args.get("asset_quantity") or 1 }) if asset.calculate_depreciation: