mirror of
https://github.com/frappe/erpnext.git
synced 2026-02-17 08:35:00 +00:00
refactor: Asset module code for better readability
refactor: Asset module code for better readability
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
@@ -140,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()
|
||||
|
||||
@@ -196,7 +197,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 +232,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
|
||||
@@ -269,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)
|
||||
@@ -379,7 +382,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"):
|
||||
@@ -389,22 +396,59 @@ 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()
|
||||
else:
|
||||
asset.db_set("value_after_depreciation", asset.value_after_depreciation - d.debit)
|
||||
self.update_journal_entry_link_on_depr_schedule(asset, d)
|
||||
self.update_value_after_depreciation(asset, d.debit)
|
||||
|
||||
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:
|
||||
@@ -459,25 +503,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"):
|
||||
@@ -527,9 +552,9 @@ 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:
|
||||
journal_entry_for_scrap = frappe.db.get_value(
|
||||
"Asset", d.reference_name, "journal_entry_for_scrap"
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -2979,7 +2979,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")):
|
||||
@@ -3011,7 +3011,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)
|
||||
@@ -3019,35 +3019,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.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],
|
||||
]
|
||||
|
||||
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,
|
||||
|
||||
@@ -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 (["Submitted", "Partially Depreciated"].includes(frm.doc.status)) {
|
||||
frm.add_custom_button(
|
||||
__("Adjust Asset Value"),
|
||||
function () {
|
||||
|
||||
@@ -405,7 +405,6 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "is_existing_asset",
|
||||
"fieldname": "purchase_details_section",
|
||||
"fieldtype": "Section Break",
|
||||
@@ -429,6 +428,7 @@
|
||||
"fieldname": "split_from",
|
||||
"fieldtype": "Link",
|
||||
"label": "Split From",
|
||||
"no_copy": 1,
|
||||
"options": "Asset",
|
||||
"read_only": 1
|
||||
},
|
||||
@@ -483,6 +483,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.docstatus > 0",
|
||||
"fieldname": "additional_asset_cost",
|
||||
"fieldtype": "Currency",
|
||||
|
||||
@@ -33,9 +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,
|
||||
make_draft_asset_depr_schedules_if_not_present,
|
||||
update_draft_asset_depr_schedules,
|
||||
)
|
||||
from erpnext.controllers.accounts_controller import AccountsController
|
||||
|
||||
@@ -131,29 +128,60 @@ 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.additional_asset_cost
|
||||
self.status = self.get_status()
|
||||
|
||||
if not self.split_from:
|
||||
self.prepare_depreciation_data()
|
||||
def create_asset_depreciation_schedule(self):
|
||||
self.set_depr_rate_and_value_after_depreciation()
|
||||
|
||||
if self.calculate_depreciation:
|
||||
update_draft_asset_depr_schedules(self)
|
||||
if self.split_from or not self.calculate_depreciation:
|
||||
return
|
||||
|
||||
if frappe.db.exists("Asset", self.name):
|
||||
asset_depr_schedules_names = make_draft_asset_depr_schedules_if_not_present(self)
|
||||
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)
|
||||
|
||||
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:<br>{0}<br><br>Please check, edit if needed, and submit the Asset."
|
||||
).format(asset_depr_schedules_links)
|
||||
)
|
||||
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.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):
|
||||
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()
|
||||
for d in self.finance_books:
|
||||
d.db_set("value_after_depreciation", self.value_after_depreciation)
|
||||
else:
|
||||
self.finance_books = []
|
||||
|
||||
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:<br>{0}<br><br>Please check, edit if needed, and submit the Asset."
|
||||
).format(asset_depr_schedules_links)
|
||||
)
|
||||
|
||||
def on_update(self):
|
||||
self.create_asset_depreciation_schedule()
|
||||
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 on_submit(self):
|
||||
self.validate_in_use_date()
|
||||
@@ -179,16 +207,6 @@ class Asset(AccountsController):
|
||||
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:<br>{0}<br><br>Please check, edit if needed, and submit the Asset."
|
||||
).format(asset_depr_schedules_links)
|
||||
)
|
||||
if (
|
||||
not frappe.db.exists(
|
||||
{
|
||||
@@ -250,16 +268,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
|
||||
@@ -456,61 +464,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 +542,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"),
|
||||
@@ -607,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"
|
||||
|
||||
@@ -624,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
|
||||
@@ -820,53 +831,52 @@ 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,
|
||||
)
|
||||
|
||||
if args.get("depreciation_method") == "Written Down Value":
|
||||
if (
|
||||
args.get("rate_of_depreciation")
|
||||
and on_validate
|
||||
and not self.flags.increase_in_asset_value_due_to_repair
|
||||
):
|
||||
return args.get("rate_of_depreciation")
|
||||
def get_written_down_value_rate(self, args, rate_field_precision, on_validate):
|
||||
if args.get("rate_of_depreciation") and on_validate:
|
||||
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 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 flt(args.get("value_after_depreciation")):
|
||||
current_asset_value = flt(args.get("value_after_depreciation"))
|
||||
else:
|
||||
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
|
||||
|
||||
return flt((100 * (1 - depreciation_rate)), float_precision)
|
||||
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
|
||||
|
||||
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):
|
||||
@@ -923,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
|
||||
@@ -1185,166 +1195,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.set_draft_asset_depr_schedule_details(asset, row)
|
||||
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.set_draft_asset_depr_schedule_details(new_asset, row)
|
||||
|
||||
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:
|
||||
@@ -1353,17 +1400,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()
|
||||
|
||||
@@ -28,8 +28,8 @@ 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,
|
||||
make_new_active_asset_depr_schedules_and_cancel_current_ones,
|
||||
get_temp_depr_schedule_doc,
|
||||
reschedule_depreciation,
|
||||
)
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -136,10 +107,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)
|
||||
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,89 +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")
|
||||
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,
|
||||
@@ -272,19 +229,40 @@ def _make_journal_entry_for_depreciation(
|
||||
return
|
||||
|
||||
je = frappe.new_doc("Journal Entry")
|
||||
setup_journal_entry_metadata(je, depr_schedule_doc, depr_series, depr_schedule, asset)
|
||||
|
||||
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)
|
||||
|
||||
je.flags.ignore_permissions = True
|
||||
je.save()
|
||||
|
||||
if not je.meta.get_workflow():
|
||||
je.submit()
|
||||
|
||||
|
||||
def setup_journal_entry_metadata(je, depr_schedule_doc, depr_series, depr_schedule, asset):
|
||||
je.voucher_type = "Depreciation Entry"
|
||||
je.naming_series = depreciation_series
|
||||
je.naming_series = depr_series
|
||||
je.posting_date = depr_schedule.schedule_date
|
||||
je.company = asset.company
|
||||
je.finance_book = asset_depr_schedule_doc.finance_book
|
||||
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": depreciation_cost_center,
|
||||
"cost_center": depr_cost_center,
|
||||
}
|
||||
|
||||
debit_entry = {
|
||||
@@ -292,42 +270,20 @@ def _make_journal_entry_for_depreciation(
|
||||
"debit_in_account_currency": depr_schedule.depreciation_amount,
|
||||
"reference_type": "Asset",
|
||||
"reference_name": asset.name,
|
||||
"cost_center": depreciation_cost_center,
|
||||
"cost_center": depr_cost_center,
|
||||
}
|
||||
|
||||
for dimension in accounting_dimensions:
|
||||
for dimension in 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")
|
||||
}
|
||||
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.update(
|
||||
{
|
||||
dimension["fieldname"]: asset.get(dimension["fieldname"])
|
||||
or dimension.get("default_dimension")
|
||||
}
|
||||
debit_entry[dimension["fieldname"]] = asset.get(dimension["fieldname"]) or dimension.get(
|
||||
"default_dimension"
|
||||
)
|
||||
|
||||
je.append("accounts", credit_entry)
|
||||
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()
|
||||
return credit_entry, debit_entry
|
||||
|
||||
|
||||
def get_depreciation_accounts(asset_category, company):
|
||||
@@ -400,21 +356,7 @@ 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 = (
|
||||
_("Hello,")
|
||||
+ "<br><br>"
|
||||
+ _("The following assets have failed to automatically post depreciation entries: {0}").format(
|
||||
asset_links
|
||||
)
|
||||
+ "."
|
||||
+ "<br><br>"
|
||||
+ _("Here are the error logs for the aforementioned failed depreciation entries: {0}").format(
|
||||
error_log_links
|
||||
)
|
||||
+ "."
|
||||
+ "<br><br>"
|
||||
+ _("Please share this email with your support team so that they can find and fix the issue.")
|
||||
)
|
||||
message = get_message_for_depr_entry_posting_error(asset_links, error_log_links)
|
||||
|
||||
frappe.sendmail(recipients=recipients, subject=subject, message=message)
|
||||
|
||||
@@ -430,197 +372,184 @@ def get_comma_separated_links(names, doctype):
|
||||
return links
|
||||
|
||||
|
||||
def get_message_for_depr_entry_posting_error(asset_links, error_log_links):
|
||||
return (
|
||||
_("Hello,")
|
||||
+ "<br><br>"
|
||||
+ _("The following assets have failed to automatically post depreciation entries: {0}").format(
|
||||
asset_links
|
||||
)
|
||||
+ "."
|
||||
+ "<br><br>"
|
||||
+ _("Here are the error logs for the aforementioned failed depreciation entries: {0}").format(
|
||||
error_log_links
|
||||
)
|
||||
+ "."
|
||||
+ "<br><br>"
|
||||
+ _("Please share this email with your support team so that they can find and fix the issue.")
|
||||
)
|
||||
|
||||
|
||||
@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)
|
||||
|
||||
|
||||
@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)
|
||||
asset_doc.set_total_booked_depreciations()
|
||||
|
||||
|
||||
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 +557,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
|
||||
):
|
||||
@@ -874,9 +841,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
|
||||
|
||||
@@ -30,11 +30,8 @@ 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,
|
||||
get_depreciation_amount,
|
||||
)
|
||||
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
|
||||
make_purchase_invoice as make_invoice,
|
||||
@@ -196,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(
|
||||
@@ -246,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()
|
||||
|
||||
@@ -258,12 +255,16 @@ 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(
|
||||
asset.finance_books[0],
|
||||
9000,
|
||||
|
||||
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(nowdate()),
|
||||
original_schedule_date=get_last_day(date),
|
||||
)
|
||||
pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount"))
|
||||
self.assertEqual(
|
||||
@@ -331,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()
|
||||
@@ -344,19 +345,17 @@ 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,
|
||||
add_days(get_last_day(add_months(purchase_date, 1)), 1),
|
||||
date,
|
||||
original_schedule_date=get_last_day(nowdate()),
|
||||
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),
|
||||
@@ -476,26 +475,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()
|
||||
@@ -511,25 +510,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)
|
||||
@@ -944,12 +943,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 = [
|
||||
@@ -1024,12 +1023,17 @@ class TestDepreciationBasics(AssetSetup):
|
||||
"depreciation_start_date": "2020-12-31",
|
||||
},
|
||||
)
|
||||
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, prev_per_day_depr = 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):
|
||||
@@ -1074,7 +1078,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(
|
||||
@@ -1087,9 +1091,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(
|
||||
@@ -1102,9 +1112,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)."""
|
||||
@@ -1285,8 +1301,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"
|
||||
@@ -1303,26 +1317,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."""
|
||||
@@ -1343,7 +1351,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)
|
||||
|
||||
@@ -1390,15 +1398,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):
|
||||
@@ -1448,6 +1456,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()
|
||||
@@ -1519,7 +1532,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):
|
||||
"""
|
||||
@@ -1616,6 +1629,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)
|
||||
|
||||
@@ -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
|
||||
@@ -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,13 +603,18 @@ 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
|
||||
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(
|
||||
@@ -619,17 +625,17 @@ 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:
|
||||
reverse_depreciation_entry_made_after_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(
|
||||
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)
|
||||
asset.db_set("disposal_date", None)
|
||||
|
||||
def set_consumed_asset_status(self, asset):
|
||||
if self.docstatus == 1:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,459 @@
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
self.schedule_date = self.get_next_schedule_date(row_idx)
|
||||
|
||||
self.depreciation_amount = self.get_depreciation_amount(row_idx)
|
||||
|
||||
# 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.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()
|
||||
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_days(
|
||||
add_months(self.fb_row.depreciation_start_date, (self.fb_row.frequency_of_depreciation * -1)),
|
||||
1,
|
||||
)
|
||||
return from_date
|
||||
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)
|
||||
last_depr_date = self.get_last_booked_depreciation_date()
|
||||
depr_booked_for_months = self.get_booked_depr_for_months_count(last_depr_date)
|
||||
|
||||
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_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) + 1) / (
|
||||
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()
|
||||
if last_depr_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
|
||||
|
||||
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):
|
||||
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(
|
||||
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
|
||||
@@ -0,0 +1,121 @@
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import (
|
||||
cint,
|
||||
flt,
|
||||
)
|
||||
|
||||
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:
|
||||
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)
|
||||
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):
|
||||
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()
|
||||
|
||||
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
|
||||
if not d.journal_entry
|
||||
]
|
||||
)
|
||||
|
||||
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))
|
||||
|
||||
|
||||
class WDVMethod(Document):
|
||||
@erpnext.allow_regional
|
||||
def get_wdv_or_dd_depr_amount(self, 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):
|
||||
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
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
@@ -3,8 +3,9 @@
|
||||
|
||||
import frappe
|
||||
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||
from frappe.utils import cstr, 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,
|
||||
)
|
||||
@@ -13,6 +14,10 @@ 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
|
||||
from erpnext.assets.doctype.asset_value_adjustment.test_asset_value_adjustment import (
|
||||
make_asset_value_adjustment,
|
||||
)
|
||||
|
||||
|
||||
class UnitTestAssetDepreciationSchedule(UnitTestCase):
|
||||
@@ -41,6 +46,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",
|
||||
@@ -178,15 +184,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.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]
|
||||
@@ -273,12 +279,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 = [
|
||||
@@ -302,13 +308,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,36 +383,790 @@ class TestAssetDepreciationSchedule(IntegrationTestCase):
|
||||
|
||||
self.assertEqual(asset.finance_books[0].total_number_of_booked_depreciations, 14)
|
||||
|
||||
def test_schedule_for_wdv_method_for_existing_asset(self):
|
||||
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="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",
|
||||
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,
|
||||
frequency_of_depreciation=3,
|
||||
gross_purchase_amount=50000,
|
||||
rate_of_depreciation=40,
|
||||
submit=1,
|
||||
)
|
||||
|
||||
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],
|
||||
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.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_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.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 = [
|
||||
[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)
|
||||
|
||||
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), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount]
|
||||
for d in get_depr_schedule(asset.name, "Draft")
|
||||
[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
|
||||
for d in get_depr_schedule(asset.name, "Active")
|
||||
]
|
||||
self.assertEqual(schedules, expected_schedules)
|
||||
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_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",
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
@@ -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",
|
||||
@@ -89,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",
|
||||
@@ -115,6 +117,7 @@
|
||||
"fieldname": "total_number_of_booked_depreciations",
|
||||
"fieldtype": "Int",
|
||||
"label": "Total Number of Booked Depreciations ",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@@ -124,12 +127,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": "2025-01-06 17:14:51.836803",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Finance Book",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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",
|
||||
"accounting_dimensions_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",
|
||||
@@ -172,16 +157,7 @@
|
||||
"options": "Asset Repair Consumed Item"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_23",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "accounting_dimensions_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Accounting Dimensions"
|
||||
},
|
||||
{
|
||||
"fetch_from": "company.cost_center",
|
||||
"fieldname": "cost_center",
|
||||
"fieldtype": "Link",
|
||||
"label": "Cost Center",
|
||||
@@ -198,21 +174,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",
|
||||
@@ -220,11 +188,6 @@
|
||||
},
|
||||
{
|
||||
"depends_on": "capitalize_repair_cost",
|
||||
"fieldname": "asset_depreciation_details_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Asset Depreciation Details"
|
||||
},
|
||||
{
|
||||
"fieldname": "increase_in_asset_life",
|
||||
"fieldtype": "Int",
|
||||
"label": "Increase In Asset Life(Months)",
|
||||
@@ -240,20 +203,63 @@
|
||||
{
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"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-09-30 13:02:06.931188",
|
||||
"modified": "2024-12-27 18:11:40.548727",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Repair",
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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,78 @@ 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)
|
||||
self.total_repair_cost = flt(self.repair_cost) + flt(self.consumed_items_cost)
|
||||
|
||||
total_value_of_stock_consumed = self.get_total_value_of_stock_consumed()
|
||||
self.total_repair_cost += total_value_of_stock_consumed
|
||||
def on_submit(self):
|
||||
self.decrease_stock_quantity()
|
||||
|
||||
def before_submit(self):
|
||||
self.check_repair_status()
|
||||
if self.get("capitalize_repair_cost"):
|
||||
self.update_asset_value()
|
||||
self.set_increase_in_asset_life()
|
||||
|
||||
self.asset_doc.flags.increase_in_asset_value_due_to_repair = False
|
||||
depreciation_note = self.get_depreciation_note()
|
||||
reschedule_depreciation(self.asset_doc, depreciation_note)
|
||||
self.add_asset_activity()
|
||||
|
||||
if self.get("stock_consumption") or self.get("capitalize_repair_cost"):
|
||||
self.asset_doc.flags.increase_in_asset_value_due_to_repair = True
|
||||
self.make_gl_entries()
|
||||
|
||||
self.increase_asset_value()
|
||||
|
||||
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)
|
||||
if self.get("capitalize_repair_cost"):
|
||||
self.update_asset_value()
|
||||
self.make_gl_entries(cancel=True)
|
||||
self.set_increase_in_asset_life()
|
||||
|
||||
self.asset_doc.flags.increase_in_asset_value_due_to_repair = False
|
||||
|
||||
if self.get("stock_consumption") or self.get("capitalize_repair_cost"):
|
||||
self.asset_doc.flags.increase_in_asset_value_due_to_repair = True
|
||||
|
||||
self.decrease_asset_value()
|
||||
|
||||
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()
|
||||
reschedule_depreciation(self.asset_doc, depreciation_note)
|
||||
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):
|
||||
total_repair_cost = self.total_repair_cost if self.docstatus == 1 else -1 * self.total_repair_cost
|
||||
|
||||
def increase_asset_value(self):
|
||||
total_value_of_stock_consumed = self.get_total_value_of_stock_consumed()
|
||||
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 += total_value_of_stock_consumed
|
||||
row.value_after_depreciation += flt(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.flags.ignore_validate_update_after_submit = True
|
||||
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 +227,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 +240,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 +300,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 +352,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 = cint(row.increase_in_asset_life) + (
|
||||
cint(self.increase_in_asset_life) * (1 if self.docstatus == 1 else -1)
|
||||
)
|
||||
row.db_update()
|
||||
|
||||
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 +384,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 +398,18 @@ 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)
|
||||
.distinct()
|
||||
.where(PurchaseInvoiceItem.parent == filters.get("purchase_invoice"))
|
||||
).run(as_list=1)
|
||||
|
||||
@@ -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()
|
||||
@@ -282,7 +296,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):
|
||||
@@ -300,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,
|
||||
}
|
||||
@@ -364,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,
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -17,7 +17,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_shift_factors_map,
|
||||
get_temp_asset_depr_schedule_doc,
|
||||
get_temp_depr_schedule_doc,
|
||||
)
|
||||
|
||||
|
||||
@@ -30,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
|
||||
@@ -41,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()
|
||||
@@ -83,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)
|
||||
|
||||
@@ -257,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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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": [],
|
||||
|
||||
@@ -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,
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
@@ -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 <b>{0}</b>.").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.make_asset_revaluation_entry()
|
||||
self.update_asset()
|
||||
add_asset_activity(
|
||||
self.asset,
|
||||
_("Asset's value adjusted after submission of Asset Value Adjustment {0}").format(
|
||||
@@ -67,27 +83,7 @@ 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 <b>{0}</b>.").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):
|
||||
def make_asset_revaluation_entry(self):
|
||||
asset = frappe.get_doc("Asset", self.asset)
|
||||
(
|
||||
fixed_asset_account,
|
||||
@@ -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,81 @@ 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()
|
||||
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
|
||||
|
||||
asset = frappe.get_doc("Asset", self.asset)
|
||||
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()
|
||||
|
||||
if not asset.calculate_depreciation:
|
||||
asset.value_after_depreciation = asset_value
|
||||
asset.save()
|
||||
return
|
||||
|
||||
asset.flags.decrease_in_asset_value_due_to_value_adjustment = True
|
||||
asset.value_after_depreciation += flt(difference_amount)
|
||||
asset.db_update()
|
||||
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()
|
||||
|
||||
@@ -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", 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 = [
|
||||
@@ -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")
|
||||
@@ -201,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", 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 = [
|
||||
@@ -246,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", 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 = [
|
||||
@@ -262,7 +266,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 +286,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):
|
||||
|
||||
@@ -411,4 +411,5 @@ 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
|
||||
erpnext.patches.v15_0.set_grand_total_to_default_mop
|
||||
|
||||
19
erpnext/patches/v15_0/update_journal_entry_type.py
Normal file
19
erpnext/patches/v15_0/update_journal_entry_type.py
Normal file
@@ -0,0 +1,19 @@
|
||||
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"],
|
||||
as_dict=True,
|
||||
)
|
||||
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")
|
||||
Reference in New Issue
Block a user