2026-03-23 17:34:51 +05:30

1122 lines
36 KiB
Python

import frappe
from frappe import _
@frappe.whitelist(allow_guest = True)
def get_assets(filters=None, fields=None, limit=20, offset=0, order_by=None, include_finance_books=True):
"""
Get list of assets with filters and pagination
Args:
filters: JSON string of filters (e.g., '{"company": "ABC Corp"}')
fields: JSON string of fields to return (e.g., '["asset_name", "location"]')
limit: Number of records to return (default: 20)
offset: Number of records to skip (default: 0)
order_by: Sort order (e.g., "creation desc")
include_finance_books: Include depreciation details (default: True)
Returns:
{
"assets": [...],
"total_count": int,
"limit": int,
"offset": int,
"has_more": bool
}
"""
try:
import json
frappe.log_error(f"Logged in User ",frappe.session.user)
# Parse filters if provided
if filters and isinstance(filters, str):
filters = json.loads(filters)
# Parse fields if provided
if fields and isinstance(fields, str):
fields = json.loads(fields)
else:
# Default fields to return
fields = [
'name',
'asset_name',
'company',
'custom_serial_number',
'location',
'custom_manufacturer',
'department',
'custom_asset_type',
'custom_manufacturing_year',
'custom_model',
'custom_class',
'custom_device_status',
'custom_down_time',
'asset_owner_company',
'custom_up_time',
'custom_modality',
'custom_attach_image',
'custom_site_contractor',
'custom_total_amount',
'creation',
'modified',
'owner',
'modified_by',
# Depreciation related fields from parent
'calculate_depreciation',
'opening_accumulated_depreciation',
'opening_number_of_booked_depreciations',
'is_fully_depreciated',
'depreciation_method',
'value_after_depreciation',
'total_number_of_depreciations',
'frequency_of_depreciation',
'gross_purchase_amount',
'total_asset_cost',
'available_for_use_date',
'status',
'custom_delete_status',
'custom_technical_department'
]
# # Get total count
# total_count = frappe.db.count('Asset', filters=filters or {})
# # Get assets
# assets = frappe.get_all(
# 'Asset',
# filters=filters or {},
# fields=fields,
# limit_page_length=int(limit),
# limit_start=int(offset),
# order_by=order_by or 'creation desc'
# )
# Build filter conditions for SQL count
all_filters = filters or {}
# Get assets with user filters applied
assets = frappe.get_all(
'Asset',
filters=all_filters,
fields=fields,
limit_page_length=int(limit),
limit_start=int(offset),
order_by=order_by or 'creation desc'
)
# Safely exclude Deleted (handles NULL/empty = not deleted)
assets = [a for a in assets if a.get('custom_delete_status') != 'Deleted']
# Get total count with same user filters + exclude Deleted
# frappe.db.count doesn't support OR conditions, so we fetch all matching and count
all_matching = frappe.get_all(
'Asset',
filters=all_filters,
fields=['custom_delete_status'],
)
total_count = sum(1 for a in all_matching if a.get('custom_delete_status') != 'Deleted')
# Include finance_books (depreciation details) for each asset
if include_finance_books:
for asset in assets:
asset['finance_books'] = get_finance_books(asset['name'])
# Calculate has_more
has_more = (int(offset) + int(limit)) < total_count
frappe.response['message'] = {
'assets': assets,
'total_count': total_count,
'limit': int(limit),
'offset': int(offset),
'has_more': has_more
}
except Exception as e:
frappe.log_error(frappe.get_traceback(), 'Get Assets API Error')
frappe.response['message'] = {
'error': str(e),
'assets': [],
'total_count': 0
}
def get_finance_books(asset_name):
"""
Get finance books (depreciation details) for an asset
Args:
asset_name: Name/ID of the asset
Returns:
List of finance book entries with depreciation details
"""
finance_books = frappe.get_all(
'Asset Finance Book',
filters={'parent': asset_name},
fields=[
'name',
'idx',
'finance_book',
'depreciation_method',
'total_number_of_depreciations',
'total_number_of_booked_depreciations',
'daily_prorata_based',
'shift_based',
'frequency_of_depreciation',
'depreciation_start_date',
'salvage_value_percentage',
'expected_value_after_useful_life',
'value_after_depreciation',
'rate_of_depreciation'
],
order_by='idx asc'
)
return finance_books
@frappe.whitelist(allow_guest = True)
def get_asset_details(asset_name, include_depreciation_schedule=False):
"""
Get detailed information about a specific asset
Args:
asset_name: Name/ID of the asset
include_depreciation_schedule: Include depreciation schedule entries (default: False)
Returns:
Asset document with all fields including finance_books
"""
try:
if not asset_name:
frappe.throw(_('Asset name is required'))
# Check if user has permission to read this asset
if not frappe.has_permission('Asset', 'read', asset_name):
frappe.throw(_('Not permitted to access this asset'))
# Get asset details (includes finance_books child table)
asset = frappe.get_doc('Asset', asset_name)
asset_dict = asset.as_dict()
# Optionally include depreciation schedule
if include_depreciation_schedule:
asset_dict['depreciation_schedule'] = get_depreciation_schedule(asset_name)
# Add computed depreciation summary
asset_dict['depreciation_summary'] = get_depreciation_summary(asset_name)
frappe.response['message'] = asset_dict
except Exception as e:
frappe.log_error(frappe.get_traceback(), 'Get Asset Details API Error')
frappe.response['message'] = {
'error': str(e)
}
def get_depreciation_schedule(asset_name):
"""
Get depreciation schedule entries for an asset
Args:
asset_name: Name/ID of the asset
Returns:
List of depreciation schedule entries
"""
schedule = frappe.get_all(
'Depreciation Schedule',
filters={'parent': asset_name},
fields=[
'name',
'idx',
'schedule_date',
'depreciation_amount',
'accumulated_depreciation_amount',
'journal_entry',
'finance_book',
'finance_book_id',
'shift'
],
order_by='schedule_date asc'
)
return schedule
def get_depreciation_summary(asset_name):
"""
Get computed depreciation summary for an asset
Args:
asset_name: Name/ID of the asset
Returns:
Dictionary with depreciation summary
"""
try:
asset = frappe.get_doc('Asset', asset_name)
# Calculate total depreciation booked
total_depreciation_booked = frappe.db.sql("""
SELECT COALESCE(SUM(depreciation_amount), 0) as total
FROM `tabDepreciation Schedule`
WHERE parent = %s AND journal_entry IS NOT NULL AND journal_entry != ''
""", asset_name)[0][0] or 0
# Calculate pending depreciation entries
pending_entries = frappe.db.count('Depreciation Schedule', {
'parent': asset_name,
'journal_entry': ['in', ['', None]]
})
# Calculate completed entries
completed_entries = frappe.db.count('Depreciation Schedule', {
'parent': asset_name,
'journal_entry': ['not in', ['', None]]
})
return {
'gross_purchase_amount': float(asset.gross_purchase_amount or 0),
'total_asset_cost': float(asset.total_asset_cost or 0),
'opening_accumulated_depreciation': float(asset.opening_accumulated_depreciation or 0),
'total_depreciation_booked': float(total_depreciation_booked),
'value_after_depreciation': float(asset.value_after_depreciation or 0),
'is_fully_depreciated': asset.is_fully_depreciated,
'pending_depreciation_entries': pending_entries,
'completed_depreciation_entries': completed_entries,
'calculate_depreciation': asset.calculate_depreciation
}
except Exception:
return {}
@frappe.whitelist(allow_guest = True)
def get_asset_finance_books(asset_name):
"""
Get finance books (depreciation configuration) for a specific asset
Args:
asset_name: Name/ID of the asset
Returns:
List of finance book entries with all depreciation details
"""
try:
if not asset_name:
frappe.throw(_('Asset name is required'))
if not frappe.has_permission('Asset', 'read', asset_name):
frappe.throw(_('Not permitted to access this asset'))
finance_books = frappe.get_all(
'Asset Finance Book',
filters={'parent': asset_name},
fields=[
'name',
'idx',
'finance_book',
'depreciation_method',
'total_number_of_depreciations',
'total_number_of_booked_depreciations',
'daily_prorata_based',
'shift_based',
'frequency_of_depreciation',
'depreciation_start_date',
'salvage_value_percentage',
'expected_value_after_useful_life',
'value_after_depreciation',
'rate_of_depreciation'
],
order_by='idx asc'
)
frappe.response['message'] = {
'asset_name': asset_name,
'finance_books': finance_books,
'count': len(finance_books)
}
except Exception as e:
frappe.log_error(frappe.get_traceback(), 'Get Asset Finance Books API Error')
frappe.response['message'] = {
'error': str(e)
}
@frappe.whitelist(allow_guest = True)
def get_asset_depreciation_schedule(asset_name, finance_book=None):
"""
Get depreciation schedule for a specific asset
Args:
asset_name: Name/ID of the asset
finance_book: Optional filter by finance book
Returns:
List of depreciation schedule entries
"""
try:
if not asset_name:
frappe.throw(_('Asset name is required'))
if not frappe.has_permission('Asset', 'read', asset_name):
frappe.throw(_('Not permitted to access this asset'))
filters = {'parent': asset_name}
if finance_book:
filters['finance_book'] = finance_book
schedule = frappe.get_all(
'Depreciation Schedule',
filters=filters,
fields=[
'name',
'idx',
'schedule_date',
'depreciation_amount',
'accumulated_depreciation_amount',
'journal_entry',
'finance_book',
'finance_book_id',
'shift'
],
order_by='schedule_date asc'
)
# Add status to each entry
for entry in schedule:
entry['status'] = 'Posted' if entry.get('journal_entry') else 'Pending'
frappe.response['message'] = {
'asset_name': asset_name,
'depreciation_schedule': schedule,
'total_entries': len(schedule),
'posted_entries': len([s for s in schedule if s.get('journal_entry')]),
'pending_entries': len([s for s in schedule if not s.get('journal_entry')])
}
except Exception as e:
frappe.log_error(frappe.get_traceback(), 'Get Asset Depreciation Schedule API Error')
frappe.response['message'] = {
'error': str(e)
}
@frappe.whitelist(allow_guest = True)
def create_asset(asset_data):
"""
Create a new asset with finance books (depreciation configuration)
Args:
asset_data: JSON string containing asset fields including finance_books array
Example asset_data:
{
"asset_name": "Test Asset",
"company": "My Company",
"item_code": "ITEM-001",
"gross_purchase_amount": 10000,
"calculate_depreciation": 1,
"available_for_use_date": "2025-01-01",
"finance_books": [
{
"finance_book": "Depreciation Entries",
"depreciation_method": "Straight Line",
"total_number_of_depreciations": 12,
"frequency_of_depreciation": 12,
"depreciation_start_date": "2025-01-01",
"expected_value_after_useful_life": 1000
}
]
}
Returns:
Created asset document with finance_books
"""
try:
import json
# Parse asset data
if isinstance(asset_data, str):
asset_data = json.loads(asset_data)
# Check if user has permission to create asset
if not frappe.has_permission('Asset', 'create'):
frappe.throw(_('Not permitted to create asset'))
# Create new asset
asset = frappe.get_doc({
'doctype': 'Asset',
**asset_data
})
asset.insert()
frappe.db.commit()
frappe.response['message'] = {
'success': True,
'asset': asset.as_dict(),
'message': _('Asset created successfully')
}
except Exception as e:
frappe.db.rollback()
frappe.log_error(frappe.get_traceback(), 'Create Asset API Error')
frappe.response['message'] = {
'success': False,
'error': str(e)
}
@frappe.whitelist(allow_guest = True)
def update_asset(asset_name, asset_data):
"""
Update an existing asset including finance books
Uses ignore_version flag to bypass timestamp mismatch errors
Args:
asset_name: Name/ID of the asset
asset_data: JSON string containing fields to update (can include finance_books)
Returns:
Updated asset document
"""
try:
import json
if not asset_name:
frappe.throw(_('Asset name is required'))
# Parse asset data
if isinstance(asset_data, str):
asset_data = json.loads(asset_data)
# Check if user has permission to update this asset
if not frappe.has_permission('Asset', 'write', asset_name):
frappe.throw(_('Not permitted to update this asset'))
# Get fresh copy of asset from database
asset = frappe.get_doc('Asset', asset_name)
# Set flags to ignore version check (bypass timestamp mismatch)
asset.flags.ignore_version = True
# Handle finance_books separately if provided
finance_books_data = asset_data.pop('finance_books', None)
# Update regular fields
for key, value in asset_data.items():
if hasattr(asset, key):
setattr(asset, key, value)
# Update finance_books if provided
if finance_books_data is not None:
asset.set('finance_books', [])
for fb in finance_books_data:
asset.append('finance_books', fb)
asset.save()
frappe.db.commit()
frappe.response['message'] = {
'success': True,
'asset': asset.as_dict(),
'message': _('Asset updated successfully')
}
except Exception as e:
frappe.db.rollback()
frappe.log_error(frappe.get_traceback(), 'Update Asset API Error')
frappe.response['message'] = {
'success': False,
'error': str(e)
}
@frappe.whitelist(allow_guest = True)
def submit_asset(asset_name):
"""
Submit an asset (change docstatus from 0 to 1)
Uses ignore_version flag to bypass timestamp mismatch errors
Args:
asset_name: Name/ID of the asset to submit
Returns:
Submitted asset document
"""
try:
if not asset_name:
frappe.throw(_('Asset name is required'))
# Check if user has permission to submit this asset
if not frappe.has_permission('Asset', 'submit', asset_name):
frappe.throw(_('Not permitted to submit this asset'))
# Get fresh copy of the asset from database
asset = frappe.get_doc('Asset', asset_name)
# Check if already submitted
if asset.docstatus == 1:
frappe.throw(_('Asset is already submitted'))
if asset.docstatus == 2:
frappe.throw(_('Cannot submit a cancelled asset'))
# Set flags to ignore version check (bypass timestamp mismatch)
asset.flags.ignore_version = True
asset.flags.ignore_links = True # Optional: ignore broken links
asset.flags.ignore_validate = False # Still run validations
# Submit the asset
asset.submit()
frappe.db.commit()
frappe.response['message'] = {
'success': True,
'asset': asset.as_dict(),
'message': _('Asset submitted successfully')
}
except Exception as e:
frappe.db.rollback()
frappe.log_error(frappe.get_traceback(), 'Submit Asset API Error')
frappe.response['message'] = {
'success': False,
'error': str(e)
}
@frappe.whitelist(allow_guest = True)
def cancel_asset(asset_name):
"""
Cancel a submitted asset (change docstatus from 1 to 2)
Uses ignore_version flag to bypass timestamp mismatch errors
Args:
asset_name: Name/ID of the asset to cancel
Returns:
Cancelled asset document
"""
try:
if not asset_name:
frappe.throw(_('Asset name is required'))
# Check if user has permission to cancel this asset
if not frappe.has_permission('Asset', 'cancel', asset_name):
frappe.throw(_('Not permitted to cancel this asset'))
# Get fresh copy of the asset from database
asset = frappe.get_doc('Asset', asset_name)
# Check if can be cancelled
if asset.docstatus == 0:
frappe.throw(_('Cannot cancel a draft asset. Submit it first.'))
if asset.docstatus == 2:
frappe.throw(_('Asset is already cancelled'))
# Set flags to ignore version check (bypass timestamp mismatch)
asset.flags.ignore_version = True
asset.flags.ignore_links = True
# Cancel the asset
asset.cancel()
frappe.db.commit()
frappe.response['message'] = {
'success': True,
'asset': asset.as_dict(),
'message': _('Asset cancelled successfully')
}
except Exception as e:
frappe.db.rollback()
frappe.log_error(frappe.get_traceback(), 'Cancel Asset API Error')
frappe.response['message'] = {
'success': False,
'error': str(e)
}
@frappe.whitelist(allow_guest = True)
def update_asset_finance_book(asset_name, finance_book_name, finance_book_data):
"""
Update a specific finance book entry for an asset
Args:
asset_name: Name/ID of the asset
finance_book_name: Name of the finance book row to update
finance_book_data: JSON string containing fields to update
Returns:
Updated asset document
"""
try:
import json
if not asset_name:
frappe.throw(_('Asset name is required'))
if not finance_book_name:
frappe.throw(_('Finance book name is required'))
# Parse finance book data
if isinstance(finance_book_data, str):
finance_book_data = json.loads(finance_book_data)
# Check if user has permission to update this asset
if not frappe.has_permission('Asset', 'write', asset_name):
frappe.throw(_('Not permitted to update this asset'))
# Get asset
asset = frappe.get_doc('Asset', asset_name)
# Set flags to ignore version check
asset.flags.ignore_version = True
# Find and update the specific finance book
updated = False
for fb in asset.finance_books:
if fb.name == finance_book_name:
for key, value in finance_book_data.items():
if hasattr(fb, key):
setattr(fb, key, value)
updated = True
break
if not updated:
frappe.throw(_('Finance book entry not found'))
asset.save()
frappe.db.commit()
frappe.response['message'] = {
'success': True,
'asset': asset.as_dict(),
'message': _('Finance book updated successfully')
}
except Exception as e:
frappe.db.rollback()
frappe.log_error(frappe.get_traceback(), 'Update Asset Finance Book API Error')
frappe.response['message'] = {
'success': False,
'error': str(e)
}
@frappe.whitelist(allow_guest = True)
def add_asset_finance_book(asset_name, finance_book_data):
"""
Add a new finance book entry to an asset
Args:
asset_name: Name/ID of the asset
finance_book_data: JSON string containing finance book fields
Example finance_book_data:
{
"finance_book": "Depreciation Entries",
"depreciation_method": "Straight Line",
"total_number_of_depreciations": 12,
"frequency_of_depreciation": 12,
"depreciation_start_date": "2025-01-01",
"expected_value_after_useful_life": 1000
}
Returns:
Updated asset document
"""
try:
import json
if not asset_name:
frappe.throw(_('Asset name is required'))
# Parse finance book data
if isinstance(finance_book_data, str):
finance_book_data = json.loads(finance_book_data)
# Check if user has permission to update this asset
if not frappe.has_permission('Asset', 'write', asset_name):
frappe.throw(_('Not permitted to update this asset'))
# Get asset
asset = frappe.get_doc('Asset', asset_name)
# Set flags to ignore version check
asset.flags.ignore_version = True
# Add new finance book
asset.append('finance_books', finance_book_data)
asset.save()
frappe.db.commit()
frappe.response['message'] = {
'success': True,
'asset': asset.as_dict(),
'message': _('Finance book added successfully')
}
except Exception as e:
frappe.db.rollback()
frappe.log_error(frappe.get_traceback(), 'Add Asset Finance Book API Error')
frappe.response['message'] = {
'success': False,
'error': str(e)
}
@frappe.whitelist(allow_guest = True)
def delete_asset_finance_book(asset_name, finance_book_name):
"""
Delete a finance book entry from an asset
Args:
asset_name: Name/ID of the asset
finance_book_name: Name of the finance book row to delete
Returns:
Updated asset document
"""
try:
if not asset_name:
frappe.throw(_('Asset name is required'))
if not finance_book_name:
frappe.throw(_('Finance book name is required'))
# Check if user has permission to update this asset
if not frappe.has_permission('Asset', 'write', asset_name):
frappe.throw(_('Not permitted to update this asset'))
# Get asset
asset = frappe.get_doc('Asset', asset_name)
# Set flags to ignore version check
asset.flags.ignore_version = True
# Find and remove the specific finance book
for i, fb in enumerate(asset.finance_books):
if fb.name == finance_book_name:
asset.finance_books.remove(fb)
break
else:
frappe.throw(_('Finance book entry not found'))
asset.save()
frappe.db.commit()
frappe.response['message'] = {
'success': True,
'asset': asset.as_dict(),
'message': _('Finance book deleted successfully')
}
except Exception as e:
frappe.db.rollback()
frappe.log_error(frappe.get_traceback(), 'Delete Asset Finance Book API Error')
frappe.response['message'] = {
'success': False,
'error': str(e)
}
@frappe.whitelist(allow_guest = True)
def delete_asset(asset_name):
"""
Delete an asset
Args:
asset_name: Name/ID of the asset
Returns:
Success message
"""
try:
if not asset_name:
frappe.throw(_('Asset name is required'))
# Check if user has permission to delete this asset
if not frappe.has_permission('Asset', 'delete', asset_name):
frappe.throw(_('Not permitted to delete this asset'))
# Delete asset
frappe.delete_doc('Asset', asset_name)
frappe.db.commit()
frappe.response['message'] = {
'success': True,
'message': _('Asset deleted successfully')
}
except Exception as e:
frappe.db.rollback()
frappe.log_error(frappe.get_traceback(), 'Delete Asset API Error')
frappe.response['message'] = {
'success': False,
'error': str(e)
}
@frappe.whitelist(allow_guest = True)
def get_asset_filters():
"""
Get available filter options for assets
Returns:
{
"companies": [...],
"locations": [...],
"departments": [...],
"asset_types": [...],
"manufacturers": [...],
"device_statuses": [...],
"finance_books": [...],
"depreciation_methods": [...]
}
"""
try:
filters = {
'companies': frappe.get_all('Company', fields=['name'], pluck='name'),
'locations': frappe.db.get_all('Asset',
filters={'location': ['!=', '']},
fields=['location'],
distinct=True,
pluck='location'
),
'departments': frappe.get_all('Department', fields=['name'], pluck='name'),
'asset_types': frappe.db.get_all('Asset',
filters={'custom_asset_type': ['!=', '']},
fields=['custom_asset_type'],
distinct=True,
pluck='custom_asset_type'
),
'manufacturers': frappe.db.get_all('Asset',
filters={'custom_manufacturer': ['!=', '']},
fields=['custom_manufacturer'],
distinct=True,
pluck='custom_manufacturer'
),
'device_statuses': frappe.db.get_all('Asset',
filters={'custom_device_status': ['!=', '']},
fields=['custom_device_status'],
distinct=True,
pluck='custom_device_status'
),
'finance_books': frappe.get_all('Finance Book', fields=['name'], pluck='name'),
'depreciation_methods': [
'Straight Line',
'Double Declining Balance',
'Written Down Value',
'Manual'
]
}
frappe.response['message'] = filters
except Exception as e:
frappe.log_error(frappe.get_traceback(), 'Get Asset Filters API Error')
frappe.response['message'] = {
'error': str(e)
}
@frappe.whitelist(allow_guest = True)
def get_asset_stats():
"""
Get statistics about assets including depreciation stats
Returns:
{
"total_assets": int,
"by_status": {...},
"by_company": {...},
"by_type": {...},
"total_amount": float,
"depreciation_stats": {...}
}
"""
try:
# Total assets
total_assets = frappe.db.count('Asset')
# Assets by device status
by_status = {}
status_data = frappe.db.sql("""
SELECT custom_device_status, COUNT(*) as count
FROM `tabAsset`
WHERE custom_device_status IS NOT NULL AND custom_device_status != ''
GROUP BY custom_device_status
""", as_dict=True)
for row in status_data:
by_status[row.custom_device_status] = row.count
# Assets by company
by_company = {}
company_data = frappe.db.sql("""
SELECT company, COUNT(*) as count
FROM `tabAsset`
WHERE company IS NOT NULL AND company != ''
GROUP BY company
""", as_dict=True)
for row in company_data:
by_company[row.company] = row.count
# Assets by type
by_type = {}
type_data = frappe.db.sql("""
SELECT custom_asset_type, COUNT(*) as count
FROM `tabAsset`
WHERE custom_asset_type IS NOT NULL AND custom_asset_type != ''
GROUP BY custom_asset_type
""", as_dict=True)
for row in type_data:
by_type[row.custom_asset_type] = row.count
# Total amount
total_amount = frappe.db.sql("""
SELECT SUM(custom_total_amount) as total
FROM `tabAsset`
WHERE custom_total_amount IS NOT NULL
""")[0][0] or 0
# Depreciation statistics
depreciation_stats = get_depreciation_stats()
frappe.response['message'] = {
'total_assets': total_assets,
'by_status': by_status,
'by_company': by_company,
'by_type': by_type,
'total_amount': float(total_amount),
'depreciation_stats': depreciation_stats
}
except Exception as e:
frappe.log_error(frappe.get_traceback(), 'Get Asset Stats API Error')
frappe.response['message'] = {
'error': str(e)
}
def get_depreciation_stats():
"""
Get depreciation statistics across all assets
Returns:
Dictionary with depreciation statistics
"""
try:
# Total gross purchase amount
total_gross_amount = frappe.db.sql("""
SELECT COALESCE(SUM(gross_purchase_amount), 0) as total
FROM `tabAsset`
""")[0][0] or 0
# Total accumulated depreciation
total_accumulated_depreciation = frappe.db.sql("""
SELECT COALESCE(SUM(ds.depreciation_amount), 0) as total
FROM `tabDepreciation Schedule` ds
INNER JOIN `tabAsset` a ON ds.parent = a.name
WHERE ds.journal_entry IS NOT NULL AND ds.journal_entry != ''
""")[0][0] or 0
# Total value after depreciation
total_value_after_depreciation = frappe.db.sql("""
SELECT COALESCE(SUM(value_after_depreciation), 0) as total
FROM `tabAsset`
""")[0][0] or 0
# Assets with depreciation enabled
assets_with_depreciation = frappe.db.count('Asset', {'calculate_depreciation': 1})
# Fully depreciated assets
fully_depreciated_assets = frappe.db.count('Asset', {'is_fully_depreciated': 1})
# Pending depreciation entries
pending_entries = frappe.db.sql("""
SELECT COUNT(*) as count
FROM `tabDepreciation Schedule`
WHERE (journal_entry IS NULL OR journal_entry = '')
""")[0][0] or 0
# By depreciation method
by_depreciation_method = {}
method_data = frappe.db.sql("""
SELECT depreciation_method, COUNT(*) as count
FROM `tabAsset Finance Book`
WHERE depreciation_method IS NOT NULL AND depreciation_method != ''
GROUP BY depreciation_method
""", as_dict=True)
for row in method_data:
by_depreciation_method[row.depreciation_method] = row.count
return {
'total_gross_amount': float(total_gross_amount),
'total_accumulated_depreciation': float(total_accumulated_depreciation),
'total_value_after_depreciation': float(total_value_after_depreciation),
'assets_with_depreciation': assets_with_depreciation,
'fully_depreciated_assets': fully_depreciated_assets,
'pending_depreciation_entries': pending_entries,
'by_depreciation_method': by_depreciation_method
}
except Exception:
return {}
@frappe.whitelist(allow_guest = True)
def search_assets(search_term, limit=10):
"""
Search assets by name, serial number, or other fields
Args:
search_term: Search query string
limit: Maximum number of results (default: 10)
Returns:
List of matching assets
"""
try:
if not search_term:
frappe.response['message'] = []
return
search_term = f"%{search_term}%"
assets = frappe.db.sql("""
SELECT
name,
asset_name,
custom_serial_number,
location,
company,
custom_device_status,
calculate_depreciation,
value_after_depreciation,
is_fully_depreciated
FROM `tabAsset`
WHERE
asset_name LIKE %(search)s
OR custom_serial_number LIKE %(search)s
OR location LIKE %(search)s
OR custom_manufacturer LIKE %(search)s
LIMIT %(limit)s
""", {
'search': search_term,
'limit': int(limit)
}, as_dict=True)
frappe.response['message'] = assets
except Exception as e:
frappe.log_error(frappe.get_traceback(), 'Search Assets API Error')
frappe.response['message'] = {
'error': str(e)
}