feat: asset activity (#36391)

* feat: asset activity

* chore: add more actions to asset activity

* chore: fix failing test due to timestamp mismatch error

* chore: rewriting asset activity messages

* chore: add report and add it to workspace

* chore: show user in list view
This commit is contained in:
Anand Baburajan 2023-08-01 12:00:24 +05:30 committed by GitHub
parent 3f09f811bf
commit 2ab3d75274
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 329 additions and 8 deletions

View File

@ -32,6 +32,7 @@ from erpnext.assets.doctype.asset.depreciation import (
reset_depreciation_schedule,
reverse_depreciation_entry_made_after_disposal,
)
from erpnext.assets.doctype.asset_activity.asset_activity import add_asset_activity
from erpnext.controllers.accounts_controller import validate_account_head
from erpnext.controllers.selling_controller import SellingController
from erpnext.projects.doctype.timesheet.timesheet import get_projectwise_timesheet_data
@ -1176,12 +1177,13 @@ class SalesInvoice(SellingController):
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 after being sold through Sales Invoice {1}."
"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")),
@ -1209,6 +1211,7 @@ class SalesInvoice(SellingController):
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

View File

@ -533,6 +533,11 @@
"link_doctype": "Asset Depreciation Schedule",
"link_fieldname": "asset"
},
{
"group": "Activity",
"link_doctype": "Asset Activity",
"link_fieldname": "asset"
},
{
"group": "Journal Entry",
"link_doctype": "Journal Entry",
@ -540,7 +545,7 @@
"table_fieldname": "accounts"
}
],
"modified": "2023-07-28 15:47:01.137996",
"modified": "2023-07-28 20:12:44.819616",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset",

View File

@ -25,6 +25,7 @@ from erpnext.assets.doctype.asset.depreciation import (
get_depreciation_accounts,
get_disposal_account_and_cost_center,
)
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
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
cancel_asset_depr_schedules,
@ -59,7 +60,7 @@ class Asset(AccountsController):
self.make_asset_movement()
if not self.booked_fixed_asset and self.validate_make_gl_entry():
self.make_gl_entries()
if not self.split_from:
if self.calculate_depreciation and not self.split_from:
asset_depr_schedules_names = make_draft_asset_depr_schedules_if_not_present(self)
convert_draft_asset_depr_schedules_into_active(self)
if asset_depr_schedules_names:
@ -71,6 +72,7 @@ class Asset(AccountsController):
"Asset Depreciation Schedules created:<br>{0}<br><br>Please check, edit if needed, and submit the Asset."
).format(asset_depr_schedules_links)
)
add_asset_activity(self.name, _("Asset submitted"))
def on_cancel(self):
self.validate_cancellation()
@ -81,9 +83,10 @@ class Asset(AccountsController):
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
make_reverse_gl_entries(voucher_type="Asset", voucher_no=self.name)
self.db_set("booked_fixed_asset", 0)
add_asset_activity(self.name, _("Asset cancelled"))
def after_insert(self):
if not self.split_from:
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"
@ -93,6 +96,16 @@ class Asset(AccountsController):
"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(
{
"doctype": "Asset Activity",
"asset": self.name,
}
):
add_asset_activity(self.name, _("Asset created"))
def after_delete(self):
add_asset_activity(self.name, _("Asset deleted"))
def validate_asset_and_reference(self):
if self.purchase_invoice or self.purchase_receipt:
@ -903,6 +916,13 @@ def update_existing_asset(asset, remaining_qty, new_asset_name):
},
)
add_asset_activity(
asset.name,
_("Asset updated after being split into Asset {0}").format(
get_link_to_form("Asset", new_asset_name)
),
)
for row in asset.get("finance_books"):
value_after_depreciation = flt(
(row.value_after_depreciation * remaining_qty) / asset.asset_quantity
@ -970,6 +990,15 @@ def create_new_asset_after_split(asset, split_qty):
(row.expected_value_after_useful_life * split_qty) / asset.asset_quantity
)
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)
),
)
new_asset.submit()
new_asset.set_status()

View File

@ -21,6 +21,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_checks_for_pl_and_bs_accounts,
)
from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
from erpnext.assets.doctype.asset_activity.asset_activity import add_asset_activity
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
get_asset_depr_schedule_doc,
get_asset_depr_schedule_name,
@ -325,6 +326,8 @@ def scrap_asset(asset_name):
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))
@ -349,6 +352,8 @@ def restore_asset(asset_name):
asset.set_status()
add_asset_activity(asset_name, _("Asset restored"))
def depreciate_asset(asset_doc, date, notes):
asset_doc.flags.ignore_validate_update_after_submit = True

View File

@ -0,0 +1,8 @@
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
// frappe.ui.form.on("Asset Activity", {
// refresh(frm) {
// },
// });

View File

@ -0,0 +1,109 @@
{
"actions": [],
"creation": "2023-07-28 12:41:13.232505",
"default_view": "List",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"asset",
"column_break_vkdy",
"date",
"column_break_kkxv",
"user",
"section_break_romx",
"subject"
],
"fields": [
{
"fieldname": "asset",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Asset",
"options": "Asset",
"print_width": "165",
"read_only": 1,
"reqd": 1,
"width": "165"
},
{
"fieldname": "column_break_vkdy",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_romx",
"fieldtype": "Section Break"
},
{
"fieldname": "subject",
"fieldtype": "Small Text",
"in_list_view": 1,
"label": "Subject",
"print_width": "518",
"read_only": 1,
"reqd": 1,
"width": "518"
},
{
"default": "now",
"fieldname": "date",
"fieldtype": "Datetime",
"in_list_view": 1,
"label": "Date",
"print_width": "158",
"read_only": 1,
"reqd": 1,
"width": "158"
},
{
"fieldname": "user",
"fieldtype": "Link",
"in_list_view": 1,
"label": "User",
"options": "User",
"print_width": "150",
"read_only": 1,
"reqd": 1,
"width": "150"
},
{
"fieldname": "column_break_kkxv",
"fieldtype": "Column Break"
}
],
"in_create": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2023-08-01 11:09:52.584482",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Activity",
"owner": "Administrator",
"permissions": [
{
"email": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1
},
{
"email": 1,
"read": 1,
"report": 1,
"role": "Accounts User",
"share": 1
},
{
"email": 1,
"read": 1,
"report": 1,
"role": "Quality Manager",
"share": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@ -0,0 +1,20 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import frappe
from frappe.model.document import Document
class AssetActivity(Document):
pass
def add_asset_activity(asset, subject):
frappe.get_doc(
{
"doctype": "Asset Activity",
"asset": asset,
"subject": subject,
"user": frappe.session.user,
}
).insert(ignore_permissions=True, ignore_links=True)

View File

@ -0,0 +1,9 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
# import frappe
from frappe.tests.utils import FrappeTestCase
class TestAssetActivity(FrappeTestCase):
pass

View File

@ -18,6 +18,7 @@ from erpnext.assets.doctype.asset.depreciation import (
reset_depreciation_schedule,
reverse_depreciation_entry_made_after_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
from erpnext.controllers.stock_controller import StockController
from erpnext.setup.doctype.brand.brand import get_brand_defaults
@ -519,6 +520,13 @@ class AssetCapitalization(StockController):
"fixed_asset_account", item=self.target_item_code, company=asset_doc.company
)
add_asset_activity(
asset_doc.name,
_("Asset created after Asset Capitalization {0} was submitted").format(
get_link_to_form("Asset Capitalization", self.name)
),
)
frappe.msgprint(
_(
"Asset {0} has been created. Please set the depreciation details if any and submit it."
@ -542,9 +550,30 @@ class AssetCapitalization(StockController):
def set_consumed_asset_status(self, asset):
if self.docstatus == 1:
asset.set_status("Capitalized" if self.target_is_fixed_asset else "Decapitalized")
if self.target_is_fixed_asset:
asset.set_status("Capitalized")
add_asset_activity(
asset.name,
_("Asset capitalized after Asset Capitalization {0} was submitted").format(
get_link_to_form("Asset Capitalization", self.name)
),
)
else:
asset.set_status("Decapitalized")
add_asset_activity(
asset.name,
_("Asset decapitalized after Asset Capitalization {0} was submitted").format(
get_link_to_form("Asset Capitalization", self.name)
),
)
else:
asset.set_status()
add_asset_activity(
asset.name,
_("Asset restored after Asset Capitalization {0} was cancelled").format(
get_link_to_form("Asset Capitalization", self.name)
),
)
@frappe.whitelist()

View File

@ -5,6 +5,9 @@
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.utils import get_link_to_form
from erpnext.assets.doctype.asset_activity.asset_activity import add_asset_activity
class AssetMovement(Document):
@ -128,5 +131,24 @@ class AssetMovement(Document):
current_location = latest_movement_entry[0][0]
current_employee = latest_movement_entry[0][1]
frappe.db.set_value("Asset", d.asset, "location", current_location)
frappe.db.set_value("Asset", d.asset, "custodian", current_employee)
frappe.db.set_value("Asset", d.asset, "location", current_location, update_modified=False)
frappe.db.set_value("Asset", d.asset, "custodian", current_employee, update_modified=False)
if current_location and current_employee:
add_asset_activity(
d.asset,
_("Asset received at Location {0} and issued to Employee {1}").format(
get_link_to_form("Location", current_location),
get_link_to_form("Employee", current_employee),
),
)
elif current_location:
add_asset_activity(
d.asset,
_("Asset transferred to Location {0}").format(get_link_to_form("Location", current_location)),
)
elif current_employee:
add_asset_activity(
d.asset,
_("Asset issued to Employee {0}").format(get_link_to_form("Employee", current_employee)),
)

View File

@ -8,6 +8,7 @@ from frappe.utils import add_months, cint, flt, get_link_to_form, getdate, time_
import erpnext
from erpnext.accounts.general_ledger import make_gl_entries
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,
@ -25,8 +26,14 @@ class AssetRepair(AccountsController):
self.calculate_total_repair_cost()
def update_status(self):
if self.repair_status == "Pending":
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,
_("Asset out of order due to Asset Repair {0}").format(
get_link_to_form("Asset Repair", self.name)
),
)
else:
self.asset_doc.set_status()
@ -68,6 +75,13 @@ class AssetRepair(AccountsController):
make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes)
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):
self.asset_doc = frappe.get_doc("Asset", self.asset)
@ -95,6 +109,13 @@ class AssetRepair(AccountsController):
make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes)
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)
),
)
def after_delete(self):
frappe.get_doc("Asset", self.asset).set_status()

View File

@ -12,6 +12,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
)
from erpnext.assets.doctype.asset.asset import get_asset_value_after_depreciation
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 (
get_asset_depr_schedule_doc,
get_depreciation_amount,
@ -27,9 +28,21 @@ class AssetValueAdjustment(Document):
def on_submit(self):
self.make_depreciation_entry()
self.reschedule_depreciations(self.new_asset_value)
add_asset_activity(
self.asset,
_("Asset's value adjusted after submission of Asset Value Adjustment {0}").format(
get_link_to_form("Asset Value Adjustment", self.name)
),
)
def on_cancel(self):
self.reschedule_depreciations(self.current_asset_value)
add_asset_activity(
self.asset,
_("Asset's value adjusted after cancellation of Asset Value Adjustment {0}").format(
get_link_to_form("Asset Value Adjustment", self.name)
),
)
def validate_date(self):
asset_purchase_date = frappe.db.get_value("Asset", self.asset, "purchase_date")
@ -74,12 +87,16 @@ class AssetValueAdjustment(Document):
"account": accumulated_depreciation_account,
"credit_in_account_currency": self.difference_amount,
"cost_center": depreciation_cost_center or self.cost_center,
"reference_type": "Asset",
"reference_name": self.asset,
}
debit_entry = {
"account": depreciation_expense_account,
"debit_in_account_currency": self.difference_amount,
"cost_center": depreciation_cost_center or self.cost_center,
"reference_type": "Asset",
"reference_name": self.asset,
}
accounting_dimensions = get_checks_for_pl_and_bs_accounts()

View File

@ -0,0 +1,33 @@
{
"add_total_row": 0,
"columns": [],
"creation": "2023-08-01 11:14:46.581234",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"filters": [],
"idx": 0,
"is_standard": "Yes",
"json": "{}",
"letterhead": null,
"modified": "2023-08-01 11:14:46.581234",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Activity",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Asset Activity",
"report_name": "Asset Activity",
"report_type": "Report Builder",
"roles": [
{
"role": "System Manager"
},
{
"role": "Accounts User"
},
{
"role": "Quality Manager"
}
]
}

View File

@ -183,6 +183,17 @@
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Asset Activity",
"hidden": 0,
"is_query_report": 0,
"label": "Asset Activity",
"link_count": 0,
"link_to": "Asset Activity",
"link_type": "Report",
"onboard": 0,
"type": "Link"
}
],
"modified": "2023-05-24 14:47:20.243146",