feat: Splitting group assets

This commit is contained in:
Deepesh Garg 2022-01-19 10:00:05 +05:30
parent 388293dbea
commit d37feb8208
4 changed files with 303 additions and 129 deletions

View File

@ -108,6 +108,10 @@ frappe.ui.form.on('Asset', {
frm.trigger("create_asset_repair"); frm.trigger("create_asset_repair");
}, __("Manage")); }, __("Manage"));
frm.add_custom_button(__("Split Asset"), function() {
frm.trigger("split_asset");
}, __("Manage"));
if (frm.doc.status != 'Fully Depreciated') { if (frm.doc.status != 'Fully Depreciated') {
frm.add_custom_button(__("Adjust Asset Value"), function() { frm.add_custom_button(__("Adjust Asset Value"), function() {
frm.trigger("create_asset_value_adjustment"); 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) { create_asset_value_adjustment: function(frm) {
frappe.call({ frappe.call({
args: { args: {

View File

@ -3,7 +3,7 @@
"allow_import": 1, "allow_import": 1,
"allow_rename": 1, "allow_rename": 1,
"autoname": "naming_series:", "autoname": "naming_series:",
"creation": "2016-03-01 17:01:27.920130", "creation": "2022-01-18 02:26:55.975005",
"doctype": "DocType", "doctype": "DocType",
"document_type": "Document", "document_type": "Document",
"engine": "InnoDB", "engine": "InnoDB",
@ -23,6 +23,8 @@
"asset_name", "asset_name",
"asset_category", "asset_category",
"location", "location",
"split_from",
"qty",
"custodian", "custodian",
"department", "department",
"disposal_date", "disposal_date",
@ -480,6 +482,18 @@
"fieldname": "section_break_36", "fieldname": "section_break_36",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Finance Books" "label": "Finance Books"
},
{
"fieldname": "qty",
"fieldtype": "Int",
"label": "Qty"
},
{
"fieldname": "split_from",
"fieldtype": "Link",
"label": "Split From",
"options": "Asset",
"read_only": 1
} }
], ],
"idx": 72, "idx": 72,
@ -502,10 +516,11 @@
"link_fieldname": "asset" "link_fieldname": "asset"
} }
], ],
"modified": "2021-06-24 14:58:51.097908", "modified": "2022-01-18 09:15:34.238601",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Assets", "module": "Assets",
"name": "Asset", "name": "Asset",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
@ -542,6 +557,7 @@
"show_name_in_global_search": 1, "show_name_in_global_search": 1,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"states": [],
"title_field": "asset_name", "title_field": "asset_name",
"track_changes": 1 "track_changes": 1
} }

View File

@ -37,6 +37,7 @@ class Asset(AccountsController):
self.validate_asset_and_reference() self.validate_asset_and_reference()
self.validate_item() self.validate_item()
self.set_missing_values() self.set_missing_values()
if not self.split_from:
self.prepare_depreciation_data() self.prepare_depreciation_data()
self.validate_gross_and_purchase_amount() self.validate_gross_and_purchase_amount()
if self.get("schedules"): if self.get("schedules"):
@ -188,22 +189,18 @@ class Asset(AccountsController):
start = self.clear_depreciation_schedule() start = self.clear_depreciation_schedule()
for finance_book in self.get('finance_books'): for finance_book in self.get('finance_books'):
self._make_depreciation_schedule(finance_book, start, date_of_sale)
def _make_depreciation_schedule(self, finance_book, start, date_of_sale):
self.validate_asset_finance_books(finance_book) self.validate_asset_finance_books(finance_book)
# value_after_depreciation - current Asset value value_after_depreciation = self._get_value_after_depreciation(finance_book)
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))
finance_book.value_after_depreciation = value_after_depreciation finance_book.value_after_depreciation = value_after_depreciation
number_of_pending_depreciations = cint(finance_book.total_number_of_depreciations) - \ number_of_pending_depreciations = cint(finance_book.total_number_of_depreciations) - \
cint(self.number_of_depreciations_booked) 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: if has_pro_rata:
number_of_pending_depreciations += 1 number_of_pending_depreciations += 1
@ -230,13 +227,8 @@ class Asset(AccountsController):
from_date, date_of_sale) from_date, date_of_sale)
if depreciation_amount > 0: if depreciation_amount > 0:
self.append("schedules", { self._add_depreciation_row(date_of_sale, depreciation_amount, finance_book.depreciation_method,
"schedule_date": date_of_sale, finance_book.finance_book, finance_book.idx)
"depreciation_amount": depreciation_amount,
"depreciation_method": finance_book.depreciation_method,
"finance_book": finance_book.finance_book,
"finance_book_id": finance_book.idx
})
break break
@ -309,22 +301,31 @@ class Asset(AccountsController):
date = add_months(monthly_schedule_date, r) date = add_months(monthly_schedule_date, r)
amount = depreciation_amount / month_range amount = depreciation_amount / month_range
self.append("schedules", { self._add_depreciation_row(date, amount, finance_book.depreciation_method,
"schedule_date": date, finance_book.finance_book, finance_book.idx)
"depreciation_amount": amount,
"depreciation_method": finance_book.depreciation_method,
"finance_book": finance_book.finance_book,
"finance_book_id": finance_book.idx
})
else: 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", { self.append("schedules", {
"schedule_date": schedule_date, "schedule_date": schedule_date,
"depreciation_amount": depreciation_amount, "depreciation_amount": depreciation_amount,
"depreciation_method": finance_book.depreciation_method, "depreciation_method": depreciation_method,
"finance_book": finance_book.finance_book, "finance_book": finance_book,
"finance_book_id": finance_book.idx "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 # depreciation schedules need to be cleared before modification due to increase in asset life/asset sales
# JE: Journal Entry, FB: Finance Book # JE: Journal Entry, FB: Finance Book
def clear_depreciation_schedule(self): def clear_depreciation_schedule(self):
@ -333,7 +334,6 @@ class Asset(AccountsController):
depr_schedule = [] depr_schedule = []
for schedule in self.get('schedules'): for schedule in self.get('schedules'):
# to update start when there are JEs linked with all the schedule rows corresponding to an FB # 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): if len(start) == (int(schedule.finance_book_id) - 2):
start.append(num_of_depreciations_completed) 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)) depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100))
return depreciation_amount 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()

View File

@ -7,7 +7,7 @@ import frappe
from frappe.utils import add_days, add_months, cstr, flt, get_last_day, getdate, nowdate 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.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 ( from erpnext.assets.doctype.asset.depreciation import (
post_depreciation_entries, post_depreciation_entries,
restore_asset, 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") make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location")
frappe.db.sql("delete from `tabTax Rule`") frappe.db.sql("delete from `tabTax Rule`")
@classmethod # @classmethod
def tearDownClass(cls): # def tearDownClass(cls):
frappe.db.rollback() # frappe.db.rollback()
class TestAsset(AssetSetup): class TestAsset(AssetSetup):
def test_asset_category_is_fetched(self): def test_asset_category_is_fetched(self):
@ -222,6 +222,22 @@ class TestAsset(AssetSetup):
si.cancel() si.cancel()
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated") 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): def test_expense_head(self):
pr = make_purchase_receipt(item_code="Macbook Pro", pr = make_purchase_receipt(item_code="Macbook Pro",
qty=2, rate=200000.0, location="Test Location") 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", "available_for_use_date": args.available_for_use_date or "2020-06-06",
"location": args.location or "Test Location", "location": args.location or "Test Location",
"asset_owner": args.asset_owner or "Company", "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: if asset.calculate_depreciation: