Initial commit of Asset Lite app
This commit is contained in:
commit
9c41cbaf5b
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
.DS_Store
|
||||||
|
*.pyc
|
||||||
|
*.egg-info
|
||||||
|
*.swp
|
||||||
|
tags
|
||||||
|
node_modules
|
||||||
|
__pycache__
|
||||||
7
README.md
Normal file
7
README.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
## Asset Lite
|
||||||
|
|
||||||
|
Asset Management System
|
||||||
|
|
||||||
|
#### License
|
||||||
|
|
||||||
|
mit
|
||||||
1
asset_lite/__init__.py
Normal file
1
asset_lite/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
__version__ = "0.0.1"
|
||||||
1
asset_lite/api/__init__.py
Normal file
1
asset_lite/api/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from . import asset_api
|
||||||
30
asset_lite/api/api.py
Normal file
30
asset_lite/api/api.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
def set_default_homepage():
|
||||||
|
|
||||||
|
"""
|
||||||
|
Set the default workspace based on the user's role.
|
||||||
|
"""
|
||||||
|
# Get the current user
|
||||||
|
current_user = frappe.session.user
|
||||||
|
|
||||||
|
# Skip for system users
|
||||||
|
if current_user in ("Administrator", "Guest"):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Define role-based workspaces
|
||||||
|
role_based_workspaces = {
|
||||||
|
"Maintenance Manager": "asset-management",
|
||||||
|
#"Maintenance User": "asset-management",
|
||||||
|
#"Technician": "asset-management"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get the user's roles
|
||||||
|
user_roles = frappe.get_roles(current_user)
|
||||||
|
|
||||||
|
# Determine the default workspace
|
||||||
|
for role, workspace in role_based_workspaces.items():
|
||||||
|
if role in user_roles:
|
||||||
|
# Set the session home page
|
||||||
|
frappe.local.response["home_page"] = f"/app/{workspace}"
|
||||||
|
return
|
||||||
1122
asset_lite/api/asset_api.py
Normal file
1122
asset_lite/api/asset_api.py
Normal file
File diff suppressed because it is too large
Load Diff
712
asset_lite/api/asset_maintenance_api.py
Normal file
712
asset_lite/api/asset_maintenance_api.py
Normal file
@ -0,0 +1,712 @@
|
|||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest = True)
|
||||||
|
def get_asset_maintenance_logs(filters=None, fields=None, limit=20, offset=0, order_by=None, include_child_tables=False):
|
||||||
|
"""
|
||||||
|
Get list of asset maintenance logs with filters and pagination
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filters: JSON string of filters (e.g., '{"maintenance_status": "Planned"}')
|
||||||
|
fields: JSON string of fields to return (e.g., '["asset_name", "due_date"]')
|
||||||
|
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_child_tables: Whether to include child table data (default: False)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
{
|
||||||
|
"asset_maintenance_logs": [...],
|
||||||
|
"total_count": int,
|
||||||
|
"limit": int,
|
||||||
|
"offset": int,
|
||||||
|
"has_more": bool
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
|
||||||
|
# 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_maintenance',
|
||||||
|
'naming_series',
|
||||||
|
'asset_name',
|
||||||
|
'custom_asset_type',
|
||||||
|
'item_code',
|
||||||
|
'item_name',
|
||||||
|
'custom_asset_names',
|
||||||
|
'custom_hospital_name',
|
||||||
|
'task',
|
||||||
|
'task_name',
|
||||||
|
'maintenance_type',
|
||||||
|
'periodicity',
|
||||||
|
'has_certificate',
|
||||||
|
'custom_early_completion',
|
||||||
|
'maintenance_status',
|
||||||
|
'custom_pm_overdue_reason',
|
||||||
|
'custom_accepted_by_moh',
|
||||||
|
'assign_to_name',
|
||||||
|
'due_date',
|
||||||
|
'completion_date',
|
||||||
|
'custom_early_completion_reason',
|
||||||
|
'custom_accepted_by_moh_',
|
||||||
|
'custom_template',
|
||||||
|
'workflow_state',
|
||||||
|
'creation',
|
||||||
|
'modified',
|
||||||
|
'owner',
|
||||||
|
'modified_by',
|
||||||
|
'docstatus',
|
||||||
|
'idx'
|
||||||
|
]
|
||||||
|
|
||||||
|
# Get total count
|
||||||
|
total_count = frappe.db.count('Asset Maintenance Log', filters=filters or {})
|
||||||
|
|
||||||
|
# Get asset maintenance logs
|
||||||
|
asset_maintenance_logs = frappe.get_all(
|
||||||
|
'Asset Maintenance Log',
|
||||||
|
filters=filters or {},
|
||||||
|
fields=fields,
|
||||||
|
limit_page_length=int(limit),
|
||||||
|
limit_start=int(offset),
|
||||||
|
order_by=order_by or 'creation desc'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Include child tables if requested
|
||||||
|
if include_child_tables and include_child_tables != 'false':
|
||||||
|
for log in asset_maintenance_logs:
|
||||||
|
log['custom_table'] = frappe.get_all(
|
||||||
|
'PPM Table',
|
||||||
|
filters={'parent': log['name']},
|
||||||
|
fields=['name', 'idx', 'maintenance_name', 'working', 'defect_found', 'not_working'],
|
||||||
|
order_by='idx asc'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Calculate has_more
|
||||||
|
has_more = (int(offset) + int(limit)) < total_count
|
||||||
|
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'asset_maintenance_logs': asset_maintenance_logs,
|
||||||
|
'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 Asset Maintenance Logs API Error')
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'error': str(e),
|
||||||
|
'asset_maintenance_logs': [],
|
||||||
|
'total_count': 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest = True)
|
||||||
|
def get_asset_maintenance_log_details(log_name, include_child_tables=True):
|
||||||
|
"""
|
||||||
|
Get detailed information about a specific asset maintenance log
|
||||||
|
|
||||||
|
Args:
|
||||||
|
log_name: Name/ID of the asset maintenance log
|
||||||
|
include_child_tables: Whether to include child table data (default: True)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Asset Maintenance Log document with all fields including child tables
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if not log_name:
|
||||||
|
frappe.throw(_('Asset Maintenance Log name is required'))
|
||||||
|
|
||||||
|
# Check if user has permission to read this log
|
||||||
|
if not frappe.has_permission('Asset Maintenance Log', 'read', log_name):
|
||||||
|
frappe.throw(_('Not permitted to access this asset maintenance log'))
|
||||||
|
|
||||||
|
# Get asset maintenance log details
|
||||||
|
log = frappe.get_doc('Asset Maintenance Log', log_name)
|
||||||
|
|
||||||
|
frappe.response['message'] = log.as_dict()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(frappe.get_traceback(), 'Get Asset Maintenance Log Details API Error')
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'error': str(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest = True)
|
||||||
|
def create_asset_maintenance_log(log_data):
|
||||||
|
"""
|
||||||
|
Create a new asset maintenance log
|
||||||
|
|
||||||
|
Args:
|
||||||
|
log_data: JSON string containing asset maintenance log fields
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Created asset maintenance log document
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
|
||||||
|
# Parse log data
|
||||||
|
if isinstance(log_data, str):
|
||||||
|
log_data = json.loads(log_data)
|
||||||
|
|
||||||
|
# Check if user has permission to create asset maintenance log
|
||||||
|
if not frappe.has_permission('Asset Maintenance Log', 'create'):
|
||||||
|
frappe.throw(_('Not permitted to create asset maintenance log'))
|
||||||
|
|
||||||
|
# Extract child table data
|
||||||
|
custom_table_data = log_data.pop('custom_table', [])
|
||||||
|
|
||||||
|
# Create new asset maintenance log
|
||||||
|
log = frappe.get_doc({
|
||||||
|
'doctype': 'Asset Maintenance Log',
|
||||||
|
**log_data
|
||||||
|
})
|
||||||
|
|
||||||
|
# Add child table rows
|
||||||
|
if custom_table_data:
|
||||||
|
for row_data in custom_table_data:
|
||||||
|
log.append('custom_table', {
|
||||||
|
'maintenance_name': row_data.get('maintenance_name', ''),
|
||||||
|
'working': row_data.get('working', 0),
|
||||||
|
'defect_found': row_data.get('defect_found', 0),
|
||||||
|
'not_working': row_data.get('not_working', 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
log.insert()
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': True,
|
||||||
|
'asset_maintenance_log': log.as_dict(),
|
||||||
|
'message': _('Asset Maintenance Log created successfully')
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.db.rollback()
|
||||||
|
frappe.log_error(frappe.get_traceback(), 'Create Asset Maintenance Log API Error')
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': False,
|
||||||
|
'error': str(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest = True)
|
||||||
|
def update_asset_maintenance_log(log_name, log_data):
|
||||||
|
"""
|
||||||
|
Update an existing asset maintenance log
|
||||||
|
|
||||||
|
Args:
|
||||||
|
log_name: Name/ID of the asset maintenance log
|
||||||
|
log_data: JSON string containing fields to update
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Updated asset maintenance log document
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
|
||||||
|
if not log_name:
|
||||||
|
frappe.throw(_('Asset Maintenance Log name is required'))
|
||||||
|
|
||||||
|
# Parse log data
|
||||||
|
if isinstance(log_data, str):
|
||||||
|
log_data = json.loads(log_data)
|
||||||
|
|
||||||
|
# Check if user has permission to update this log
|
||||||
|
if not frappe.has_permission('Asset Maintenance Log', 'write', log_name):
|
||||||
|
frappe.throw(_('Not permitted to update this asset maintenance log'))
|
||||||
|
|
||||||
|
# Get asset maintenance log
|
||||||
|
log = frappe.get_doc('Asset Maintenance Log', log_name)
|
||||||
|
|
||||||
|
# Extract child table data before processing other fields
|
||||||
|
custom_table_data = log_data.pop('custom_table', None)
|
||||||
|
|
||||||
|
# List of child table fields to skip in regular update
|
||||||
|
child_table_fields = ['custom_table', 'table']
|
||||||
|
|
||||||
|
# Update regular fields (not child tables)
|
||||||
|
for key, value in log_data.items():
|
||||||
|
if key not in child_table_fields and hasattr(log, key):
|
||||||
|
setattr(log, key, value)
|
||||||
|
|
||||||
|
# Handle child table update if provided
|
||||||
|
if custom_table_data is not None:
|
||||||
|
# Clear existing child table rows
|
||||||
|
log.custom_table = []
|
||||||
|
|
||||||
|
# Add new child table rows
|
||||||
|
for row_data in custom_table_data:
|
||||||
|
log.append('custom_table', {
|
||||||
|
'maintenance_name': row_data.get('maintenance_name', ''),
|
||||||
|
'working': row_data.get('working', 0),
|
||||||
|
'defect_found': row_data.get('defect_found', 0),
|
||||||
|
'not_working': row_data.get('not_working', 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
log.save()
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': True,
|
||||||
|
'asset_maintenance_log': log.as_dict(),
|
||||||
|
'message': _('Asset Maintenance Log updated successfully')
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.db.rollback()
|
||||||
|
frappe.log_error(frappe.get_traceback(), 'Update Asset Maintenance Log API Error')
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': False,
|
||||||
|
'error': str(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest = True)
|
||||||
|
def delete_asset_maintenance_log(log_name):
|
||||||
|
"""
|
||||||
|
Delete an asset maintenance log
|
||||||
|
|
||||||
|
Args:
|
||||||
|
log_name: Name/ID of the asset maintenance log
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Success message
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if not log_name:
|
||||||
|
frappe.throw(_('Asset Maintenance Log name is required'))
|
||||||
|
|
||||||
|
# Check if user has permission to delete this log
|
||||||
|
if not frappe.has_permission('Asset Maintenance Log', 'delete', log_name):
|
||||||
|
frappe.throw(_('Not permitted to delete this asset maintenance log'))
|
||||||
|
|
||||||
|
# Delete asset maintenance log
|
||||||
|
frappe.delete_doc('Asset Maintenance Log', log_name)
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': True,
|
||||||
|
'message': _('Asset Maintenance Log deleted successfully')
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.db.rollback()
|
||||||
|
frappe.log_error(frappe.get_traceback(), 'Delete Asset Maintenance Log API Error')
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': False,
|
||||||
|
'error': str(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest = True)
|
||||||
|
def update_maintenance_status(log_name, maintenance_status=None, workflow_state=None):
|
||||||
|
"""
|
||||||
|
Update asset maintenance log status
|
||||||
|
|
||||||
|
Args:
|
||||||
|
log_name: Name/ID of the asset maintenance log
|
||||||
|
maintenance_status: New maintenance status (e.g., 'Planned', 'Completed', 'Overdue')
|
||||||
|
workflow_state: New workflow state
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Updated asset maintenance log document
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if not log_name:
|
||||||
|
frappe.throw(_('Asset Maintenance Log name is required'))
|
||||||
|
|
||||||
|
# Check if user has permission to update this log
|
||||||
|
if not frappe.has_permission('Asset Maintenance Log', 'write', log_name):
|
||||||
|
frappe.throw(_('Not permitted to update this asset maintenance log'))
|
||||||
|
|
||||||
|
# Get asset maintenance log
|
||||||
|
log = frappe.get_doc('Asset Maintenance Log', log_name)
|
||||||
|
|
||||||
|
# Update status fields
|
||||||
|
if maintenance_status:
|
||||||
|
log.maintenance_status = maintenance_status
|
||||||
|
|
||||||
|
if workflow_state:
|
||||||
|
log.workflow_state = workflow_state
|
||||||
|
|
||||||
|
log.save()
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': True,
|
||||||
|
'asset_maintenance_log': log.as_dict(),
|
||||||
|
'message': _('Asset Maintenance Log status updated successfully')
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.db.rollback()
|
||||||
|
frappe.log_error(frappe.get_traceback(), 'Update Maintenance Status API Error')
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': False,
|
||||||
|
'error': str(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest = True)
|
||||||
|
def get_maintenance_logs_by_asset(asset_name, filters=None, limit=20, offset=0, include_child_tables=False):
|
||||||
|
"""
|
||||||
|
Get all maintenance logs for a specific asset
|
||||||
|
|
||||||
|
Args:
|
||||||
|
asset_name: Name/ID of the asset
|
||||||
|
filters: Additional JSON string of filters
|
||||||
|
limit: Number of records to return (default: 20)
|
||||||
|
offset: Number of records to skip (default: 0)
|
||||||
|
include_child_tables: Whether to include child table data (default: False)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of maintenance logs for the asset
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
|
||||||
|
if not asset_name:
|
||||||
|
frappe.throw(_('Asset name is required'))
|
||||||
|
|
||||||
|
# Parse additional filters if provided
|
||||||
|
additional_filters = {}
|
||||||
|
if filters and isinstance(filters, str):
|
||||||
|
additional_filters = json.loads(filters)
|
||||||
|
|
||||||
|
# Combine filters
|
||||||
|
combined_filters = {'asset_name': asset_name, **additional_filters}
|
||||||
|
|
||||||
|
# Get total count
|
||||||
|
total_count = frappe.db.count('Asset Maintenance Log', filters=combined_filters)
|
||||||
|
|
||||||
|
# Get maintenance logs
|
||||||
|
logs = frappe.get_all(
|
||||||
|
'Asset Maintenance Log',
|
||||||
|
filters=combined_filters,
|
||||||
|
fields=['*'],
|
||||||
|
limit_page_length=int(limit),
|
||||||
|
limit_start=int(offset),
|
||||||
|
order_by='due_date desc'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Include child tables if requested
|
||||||
|
if include_child_tables and include_child_tables != 'false':
|
||||||
|
for log in logs:
|
||||||
|
log['custom_table'] = frappe.get_all(
|
||||||
|
'PPM Table',
|
||||||
|
filters={'parent': log['name']},
|
||||||
|
fields=['name', 'idx', 'maintenance_name', 'working', 'defect_found', 'not_working'],
|
||||||
|
order_by='idx asc'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Calculate has_more
|
||||||
|
has_more = (int(offset) + int(limit)) < total_count
|
||||||
|
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'asset_maintenance_logs': logs,
|
||||||
|
'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 Maintenance Logs By Asset API Error')
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'error': str(e),
|
||||||
|
'asset_maintenance_logs': [],
|
||||||
|
'total_count': 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest = True)
|
||||||
|
def get_overdue_maintenance_logs(filters=None, limit=20, offset=0, include_child_tables=False):
|
||||||
|
"""
|
||||||
|
Get all overdue maintenance logs
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filters: Additional JSON string of filters
|
||||||
|
limit: Number of records to return (default: 20)
|
||||||
|
offset: Number of records to skip (default: 0)
|
||||||
|
include_child_tables: Whether to include child table data (default: False)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of overdue maintenance logs
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
from frappe.utils import today
|
||||||
|
|
||||||
|
# Parse additional filters if provided
|
||||||
|
additional_filters = {}
|
||||||
|
if filters and isinstance(filters, str):
|
||||||
|
additional_filters = json.loads(filters)
|
||||||
|
|
||||||
|
# Combine filters - get logs with due_date less than today and status not completed
|
||||||
|
combined_filters = {
|
||||||
|
'due_date': ['<', today()],
|
||||||
|
'maintenance_status': ['!=', 'Completed'],
|
||||||
|
**additional_filters
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get total count
|
||||||
|
total_count = frappe.db.count('Asset Maintenance Log', filters=combined_filters)
|
||||||
|
|
||||||
|
# Get overdue logs
|
||||||
|
logs = frappe.get_all(
|
||||||
|
'Asset Maintenance Log',
|
||||||
|
filters=combined_filters,
|
||||||
|
fields=['*'],
|
||||||
|
limit_page_length=int(limit),
|
||||||
|
limit_start=int(offset),
|
||||||
|
order_by='due_date asc'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Include child tables if requested
|
||||||
|
if include_child_tables and include_child_tables != 'false':
|
||||||
|
for log in logs:
|
||||||
|
log['custom_table'] = frappe.get_all(
|
||||||
|
'PPM Table',
|
||||||
|
filters={'parent': log['name']},
|
||||||
|
fields=['name', 'idx', 'maintenance_name', 'working', 'defect_found', 'not_working'],
|
||||||
|
order_by='idx asc'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Calculate has_more
|
||||||
|
has_more = (int(offset) + int(limit)) < total_count
|
||||||
|
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'asset_maintenance_logs': logs,
|
||||||
|
'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 Overdue Maintenance Logs API Error')
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'error': str(e),
|
||||||
|
'asset_maintenance_logs': [],
|
||||||
|
'total_count': 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest = True)
|
||||||
|
def add_ppm_table_row(log_name, row_data):
|
||||||
|
"""
|
||||||
|
Add a PPM table row to a maintenance log
|
||||||
|
|
||||||
|
Args:
|
||||||
|
log_name: Name/ID of the asset maintenance log
|
||||||
|
row_data: JSON string containing row fields
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Updated custom_table array
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
|
||||||
|
if not log_name:
|
||||||
|
frappe.throw(_('Asset Maintenance Log name is required'))
|
||||||
|
|
||||||
|
# Parse row data
|
||||||
|
if isinstance(row_data, str):
|
||||||
|
row_data = json.loads(row_data)
|
||||||
|
|
||||||
|
# Check permission
|
||||||
|
if not frappe.has_permission('Asset Maintenance Log', 'write', log_name):
|
||||||
|
frappe.throw(_('Not permitted to update this asset maintenance log'))
|
||||||
|
|
||||||
|
# Get log and add row
|
||||||
|
log = frappe.get_doc('Asset Maintenance Log', log_name)
|
||||||
|
log.append('custom_table', {
|
||||||
|
'maintenance_name': row_data.get('maintenance_name', ''),
|
||||||
|
'working': row_data.get('working', 0),
|
||||||
|
'defect_found': row_data.get('defect_found', 0),
|
||||||
|
'not_working': row_data.get('not_working', 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
log.save()
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
# Return updated child table
|
||||||
|
custom_table = []
|
||||||
|
for row in log.custom_table:
|
||||||
|
custom_table.append({
|
||||||
|
'name': row.name,
|
||||||
|
'idx': row.idx,
|
||||||
|
'maintenance_name': row.maintenance_name,
|
||||||
|
'working': row.working,
|
||||||
|
'defect_found': row.defect_found,
|
||||||
|
'not_working': row.not_working
|
||||||
|
})
|
||||||
|
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': True,
|
||||||
|
'custom_table': custom_table,
|
||||||
|
'message': _('PPM table row added successfully')
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.db.rollback()
|
||||||
|
frappe.log_error(frappe.get_traceback(), 'Add PPM Table Row API Error')
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': False,
|
||||||
|
'error': str(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest = True)
|
||||||
|
def remove_ppm_table_row(log_name, row_name):
|
||||||
|
"""
|
||||||
|
Remove a PPM table row from a maintenance log
|
||||||
|
|
||||||
|
Args:
|
||||||
|
log_name: Name/ID of the asset maintenance log
|
||||||
|
row_name: Name/ID of the row to remove
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Updated custom_table array
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if not log_name:
|
||||||
|
frappe.throw(_('Asset Maintenance Log name is required'))
|
||||||
|
|
||||||
|
if not row_name:
|
||||||
|
frappe.throw(_('Row name is required'))
|
||||||
|
|
||||||
|
# Check permission
|
||||||
|
if not frappe.has_permission('Asset Maintenance Log', 'write', log_name):
|
||||||
|
frappe.throw(_('Not permitted to update this asset maintenance log'))
|
||||||
|
|
||||||
|
# Get log and remove row
|
||||||
|
log = frappe.get_doc('Asset Maintenance Log', log_name)
|
||||||
|
|
||||||
|
# Find and remove the row
|
||||||
|
row_to_remove = None
|
||||||
|
for row in log.custom_table:
|
||||||
|
if row.name == row_name:
|
||||||
|
row_to_remove = row
|
||||||
|
break
|
||||||
|
|
||||||
|
if row_to_remove:
|
||||||
|
log.remove(row_to_remove)
|
||||||
|
log.save()
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
# Return updated child table
|
||||||
|
custom_table = []
|
||||||
|
for row in log.custom_table:
|
||||||
|
custom_table.append({
|
||||||
|
'name': row.name,
|
||||||
|
'idx': row.idx,
|
||||||
|
'maintenance_name': row.maintenance_name,
|
||||||
|
'working': row.working,
|
||||||
|
'defect_found': row.defect_found,
|
||||||
|
'not_working': row.not_working
|
||||||
|
})
|
||||||
|
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': True,
|
||||||
|
'custom_table': custom_table,
|
||||||
|
'message': _('PPM table row removed successfully')
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.db.rollback()
|
||||||
|
frappe.log_error(frappe.get_traceback(), 'Remove PPM Table Row API Error')
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': False,
|
||||||
|
'error': str(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest = True)
|
||||||
|
def update_ppm_table_row(log_name, row_name, row_data):
|
||||||
|
"""
|
||||||
|
Update a PPM table row in a maintenance log
|
||||||
|
|
||||||
|
Args:
|
||||||
|
log_name: Name/ID of the asset maintenance log
|
||||||
|
row_name: Name/ID of the row to update
|
||||||
|
row_data: JSON string containing fields to update
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Updated custom_table array
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
|
||||||
|
if not log_name:
|
||||||
|
frappe.throw(_('Asset Maintenance Log name is required'))
|
||||||
|
|
||||||
|
if not row_name:
|
||||||
|
frappe.throw(_('Row name is required'))
|
||||||
|
|
||||||
|
# Parse row data
|
||||||
|
if isinstance(row_data, str):
|
||||||
|
row_data = json.loads(row_data)
|
||||||
|
|
||||||
|
# Check permission
|
||||||
|
if not frappe.has_permission('Asset Maintenance Log', 'write', log_name):
|
||||||
|
frappe.throw(_('Not permitted to update this asset maintenance log'))
|
||||||
|
|
||||||
|
# Get log and update row
|
||||||
|
log = frappe.get_doc('Asset Maintenance Log', log_name)
|
||||||
|
|
||||||
|
# Find and update the row
|
||||||
|
for row in log.custom_table:
|
||||||
|
if row.name == row_name:
|
||||||
|
if 'maintenance_name' in row_data:
|
||||||
|
row.maintenance_name = row_data['maintenance_name']
|
||||||
|
if 'working' in row_data:
|
||||||
|
row.working = row_data['working']
|
||||||
|
if 'defect_found' in row_data:
|
||||||
|
row.defect_found = row_data['defect_found']
|
||||||
|
if 'not_working' in row_data:
|
||||||
|
row.not_working = row_data['not_working']
|
||||||
|
break
|
||||||
|
|
||||||
|
log.save()
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
# Return updated child table
|
||||||
|
custom_table = []
|
||||||
|
for row in log.custom_table:
|
||||||
|
custom_table.append({
|
||||||
|
'name': row.name,
|
||||||
|
'idx': row.idx,
|
||||||
|
'maintenance_name': row.maintenance_name,
|
||||||
|
'working': row.working,
|
||||||
|
'defect_found': row.defect_found,
|
||||||
|
'not_working': row.not_working
|
||||||
|
})
|
||||||
|
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': True,
|
||||||
|
'custom_table': custom_table,
|
||||||
|
'message': _('PPM table row updated successfully')
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.db.rollback()
|
||||||
|
frappe.log_error(frappe.get_traceback(), 'Update PPM Table Row API Error')
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': False,
|
||||||
|
'error': str(e)
|
||||||
|
}
|
||||||
163
asset_lite/api/custom_api.py
Normal file
163
asset_lite/api/custom_api.py
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
from frappe.utils import now, today, get_datetime
|
||||||
|
import json
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest=False)
|
||||||
|
def get_user_details(user_id=None):
|
||||||
|
"""
|
||||||
|
Get detailed user information
|
||||||
|
Usage: /api/method/asset_lite.api.custom_api.get_user_details
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if not user_id:
|
||||||
|
user_id = frappe.session.user
|
||||||
|
|
||||||
|
user = frappe.get_doc("User", user_id)
|
||||||
|
|
||||||
|
# Get user roles
|
||||||
|
roles = frappe.get_roles(user_id)
|
||||||
|
|
||||||
|
response_data = {
|
||||||
|
"user_id": user_id,
|
||||||
|
"full_name": user.full_name,
|
||||||
|
"email": user.email,
|
||||||
|
"user_image": user.user_image,
|
||||||
|
"roles": roles,
|
||||||
|
"last_login": user.last_login,
|
||||||
|
"enabled": user.enabled,
|
||||||
|
"creation": user.creation,
|
||||||
|
"modified": user.modified
|
||||||
|
}
|
||||||
|
|
||||||
|
frappe.response.message = response_data
|
||||||
|
frappe.response.status_code = 200
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(f"Error in get_user_details: {str(e)}")
|
||||||
|
frappe.response.message = {"error": str(e)}
|
||||||
|
frappe.response.status_code = 500
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest=False)
|
||||||
|
def get_doctype_records(doctype, filters=None, fields=None, limit=20, offset=0):
|
||||||
|
"""
|
||||||
|
Get records from any DocType with filtering and pagination
|
||||||
|
Usage: /api/method/asset_lite.api.custom_api.get_doctype_records
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Parse filters and fields if provided as JSON strings
|
||||||
|
if isinstance(filters, str):
|
||||||
|
filters = json.loads(filters)
|
||||||
|
if isinstance(fields, str):
|
||||||
|
fields = json.loads(fields)
|
||||||
|
|
||||||
|
# Build the query
|
||||||
|
query_filters = filters or {}
|
||||||
|
|
||||||
|
# Get records
|
||||||
|
records = frappe.get_list(
|
||||||
|
doctype,
|
||||||
|
filters=query_filters,
|
||||||
|
fields=fields or ["*"],
|
||||||
|
limit=limit,
|
||||||
|
start=offset,
|
||||||
|
order_by="creation desc"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get total count for pagination
|
||||||
|
total_count = frappe.db.count(doctype, query_filters)
|
||||||
|
|
||||||
|
response_data = {
|
||||||
|
"records": records,
|
||||||
|
"total_count": total_count,
|
||||||
|
"limit": limit,
|
||||||
|
"offset": offset,
|
||||||
|
"has_more": (offset + limit) < total_count
|
||||||
|
}
|
||||||
|
|
||||||
|
frappe.response.message = response_data
|
||||||
|
frappe.response.status_code = 200
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(f"Error in get_doctype_records: {str(e)}")
|
||||||
|
frappe.response.message = {"error": str(e)}
|
||||||
|
frappe.response.status_code = 500
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest=False)
|
||||||
|
def get_dashboard_stats():
|
||||||
|
"""
|
||||||
|
Get dashboard statistics
|
||||||
|
Usage: /api/method/asset_lite.api.custom_api.get_dashboard_stats
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Example: Get counts for different DocTypes
|
||||||
|
stats = {
|
||||||
|
"total_users": frappe.db.count("User", {"enabled": 1}),
|
||||||
|
"total_customers": frappe.db.count("Customer"),
|
||||||
|
"total_items": frappe.db.count("Item"),
|
||||||
|
"total_orders": frappe.db.count("Sales Order"),
|
||||||
|
"recent_activities": []
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get recent activities (example)
|
||||||
|
recent_users = frappe.get_list(
|
||||||
|
"User",
|
||||||
|
fields=["name", "full_name", "creation"],
|
||||||
|
limit=5,
|
||||||
|
order_by="creation desc"
|
||||||
|
)
|
||||||
|
|
||||||
|
stats["recent_activities"] = recent_users
|
||||||
|
|
||||||
|
frappe.response.message = stats
|
||||||
|
frappe.response.status_code = 200
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(f"Error in get_dashboard_stats: {str(e)}")
|
||||||
|
frappe.response.message = {"error": str(e)}
|
||||||
|
frappe.response.status_code = 500
|
||||||
|
|
||||||
|
# Example KYC API for your KYCDetails component
|
||||||
|
@frappe.whitelist(allow_guest=False)
|
||||||
|
def get_kyc_details():
|
||||||
|
"""
|
||||||
|
Get KYC details - customize this based on your actual KYC DocType
|
||||||
|
Usage: /api/method/asset_lite.api.custom_api.get_kyc_details
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Replace 'KYC' with your actual DocType name
|
||||||
|
kyc_records = frappe.get_list(
|
||||||
|
"KYC", # Change this to your actual DocType
|
||||||
|
fields=["name", "kyc_status", "kyc_type", "creation"],
|
||||||
|
limit=50,
|
||||||
|
order_by="creation desc"
|
||||||
|
)
|
||||||
|
|
||||||
|
frappe.response.message = kyc_records
|
||||||
|
frappe.response.status_code = 200
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(f"Error in get_kyc_details: {str(e)}")
|
||||||
|
frappe.response.message = {"error": str(e)}
|
||||||
|
frappe.response.status_code = 500
|
||||||
|
|
||||||
|
# Simple test endpoint to verify API is working
|
||||||
|
@frappe.whitelist(allow_guest=False)
|
||||||
|
def test_api():
|
||||||
|
"""
|
||||||
|
Simple test endpoint to verify the API is working
|
||||||
|
Usage: /api/method/asset_lite.api.custom_api.test_api
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
frappe.response.message = {
|
||||||
|
"status": "success",
|
||||||
|
"message": "API is working!",
|
||||||
|
"user": frappe.session.user,
|
||||||
|
"timestamp": now()
|
||||||
|
}
|
||||||
|
frappe.response.status_code = 200
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(f"Error in test_api: {str(e)}")
|
||||||
|
frappe.response.message = {"error": str(e)}
|
||||||
|
frappe.response.status_code = 500
|
||||||
538
asset_lite/api/dashboard_api.py
Normal file
538
asset_lite/api/dashboard_api.py
Normal file
@ -0,0 +1,538 @@
|
|||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
from frappe.utils import nowdate
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
def _ok(payload, code=200):
|
||||||
|
frappe.response.status_code = code
|
||||||
|
frappe.response.message = payload
|
||||||
|
|
||||||
|
|
||||||
|
def _err(msg, code=500):
|
||||||
|
frappe.response.status_code = code
|
||||||
|
frappe.response.message = {"error": msg}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest=True)
|
||||||
|
def get_number_cards():
|
||||||
|
"""
|
||||||
|
Returns counts for Number Cards:
|
||||||
|
- total_assets
|
||||||
|
- work_orders_open
|
||||||
|
- work_orders_in_progress
|
||||||
|
- work_orders_completed
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
total_assets = frappe.db.count("Asset")
|
||||||
|
work_orders_open = frappe.db.count("Work Order", {"status": ["in", ["Not Started", "Open", "Pending"]]})
|
||||||
|
work_orders_in_progress = frappe.db.count("Work Order", {"status": ["in", ["In Process", "In Progress", "Started"]]})
|
||||||
|
work_orders_completed = frappe.db.count("Work Order", {"status": ["in", ["Completed", "Closed", "Finished"]]})
|
||||||
|
|
||||||
|
_ok({
|
||||||
|
"total_assets": total_assets,
|
||||||
|
"work_orders_open": work_orders_open,
|
||||||
|
"work_orders_in_progress": work_orders_in_progress,
|
||||||
|
"work_orders_completed": work_orders_completed,
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(frappe.get_traceback(), "get_number_cards")
|
||||||
|
_err(str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest=True)
|
||||||
|
def list_dashboard_charts(search=None, public_only=True, limit=50):
|
||||||
|
"""
|
||||||
|
List available Dashboard Chart docs and their y-axis rows.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
filters = {}
|
||||||
|
if str(public_only) in ("1", "true", "True"): # tolerate string flags
|
||||||
|
filters["is_public"] = 1
|
||||||
|
|
||||||
|
charts = frappe.get_all(
|
||||||
|
"Dashboard Chart",
|
||||||
|
filters=filters,
|
||||||
|
fields=[
|
||||||
|
"name",
|
||||||
|
"chart_name",
|
||||||
|
"type",
|
||||||
|
"is_public",
|
||||||
|
"chart_type",
|
||||||
|
"report_name",
|
||||||
|
"use_report_chart",
|
||||||
|
"x_field",
|
||||||
|
"time_interval",
|
||||||
|
"timespan",
|
||||||
|
"custom_options",
|
||||||
|
],
|
||||||
|
limit=int(limit or 50),
|
||||||
|
order_by="modified desc",
|
||||||
|
)
|
||||||
|
|
||||||
|
for c in charts:
|
||||||
|
y_rows = frappe.get_all(
|
||||||
|
"Dashboard Chart Field",
|
||||||
|
filters={"parenttype": "Dashboard Chart", "parent": c["name"]},
|
||||||
|
fields=["y_field", "color"],
|
||||||
|
order_by="idx asc",
|
||||||
|
)
|
||||||
|
c["y_axes"] = y_rows
|
||||||
|
|
||||||
|
_ok({"charts": charts})
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(frappe.get_traceback(), "list_dashboard_charts")
|
||||||
|
_err(str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest=True)
|
||||||
|
def get_dashboard_chart_data(chart_name, report_filters=None):
|
||||||
|
"""
|
||||||
|
Return chart-ready JSON for any Dashboard Chart (Report-based or Custom).
|
||||||
|
Supports:
|
||||||
|
- Report-based charts (with or without use_report_chart)
|
||||||
|
- Custom/Document-type based charts
|
||||||
|
- Multi-series (stacked) bar charts
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if isinstance(report_filters, str):
|
||||||
|
report_filters = json.loads(report_filters or "{}")
|
||||||
|
report_filters = report_filters or {}
|
||||||
|
|
||||||
|
# Try to find chart by name first, then by chart_name
|
||||||
|
chart = None
|
||||||
|
if frappe.db.exists("Dashboard Chart", chart_name):
|
||||||
|
chart = frappe.get_doc("Dashboard Chart", chart_name)
|
||||||
|
else:
|
||||||
|
# Search by chart_name field
|
||||||
|
chart_doc_name = frappe.db.get_value("Dashboard Chart", {"chart_name": chart_name}, "name")
|
||||||
|
if chart_doc_name:
|
||||||
|
chart = frappe.get_doc("Dashboard Chart", chart_doc_name)
|
||||||
|
else:
|
||||||
|
# Try case-insensitive search
|
||||||
|
chart_doc_name = frappe.db.sql("""
|
||||||
|
SELECT name FROM `tabDashboard Chart`
|
||||||
|
WHERE LOWER(chart_name) = LOWER(%s) OR LOWER(name) = LOWER(%s)
|
||||||
|
LIMIT 1
|
||||||
|
""", (chart_name, chart_name), as_dict=True)
|
||||||
|
if chart_doc_name:
|
||||||
|
chart = frappe.get_doc("Dashboard Chart", chart_doc_name[0].name)
|
||||||
|
|
||||||
|
if not chart:
|
||||||
|
_err(f"Dashboard Chart '{chart_name}' not found. Use list_dashboard_charts API to see available charts.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get y-axes configuration
|
||||||
|
y_axes = frappe.get_all(
|
||||||
|
"Dashboard Chart Field",
|
||||||
|
filters={"parenttype": "Dashboard Chart", "parent": chart.name},
|
||||||
|
fields=["y_field", "color"],
|
||||||
|
order_by="idx asc",
|
||||||
|
)
|
||||||
|
|
||||||
|
# PRIORITY 1: Handle Report-based charts (check report_name first)
|
||||||
|
if chart.report_name:
|
||||||
|
return _process_report_chart(chart, y_axes, report_filters)
|
||||||
|
|
||||||
|
# PRIORITY 2: Handle Custom/Document-type charts (only if NO report_name)
|
||||||
|
if chart.chart_type == "Custom" or (chart.document_type and chart.based_on):
|
||||||
|
return _process_custom_chart(chart, y_axes)
|
||||||
|
|
||||||
|
# PRIORITY 3: Handle Group By / Count / Sum charts
|
||||||
|
if chart.chart_type in ("Group By", "Count", "Sum"):
|
||||||
|
return _process_group_by_chart(chart)
|
||||||
|
|
||||||
|
_err(f"Unsupported chart type: {chart.chart_type} for chart: {chart.name}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(frappe.get_traceback(), "get_dashboard_chart_data")
|
||||||
|
_err(str(e))
|
||||||
|
|
||||||
|
|
||||||
|
def _process_report_chart(chart, y_axes, report_filters):
|
||||||
|
"""
|
||||||
|
Process a Report-based Dashboard Chart.
|
||||||
|
Runs the report and extracts data based on x_field and y_axes configuration.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Merge chart's default filters with provided filters
|
||||||
|
chart_filters = _parse_custom_options(chart.filters_json) if chart.filters_json else {}
|
||||||
|
merged_filters = {**chart_filters, **report_filters}
|
||||||
|
|
||||||
|
# Run the report
|
||||||
|
run = frappe.get_attr("frappe.desk.query_report.run")
|
||||||
|
report_result = run(chart.report_name, filters=merged_filters)
|
||||||
|
rows = report_result.get("result", []) or []
|
||||||
|
|
||||||
|
# Filter out total rows
|
||||||
|
data_rows = [r for r in rows if not (isinstance(r, dict) and r.get("is_total_row"))]
|
||||||
|
|
||||||
|
# Get x-axis labels
|
||||||
|
x_key = chart.x_field
|
||||||
|
labels = []
|
||||||
|
for r in data_rows:
|
||||||
|
if isinstance(r, dict):
|
||||||
|
val = r.get(x_key)
|
||||||
|
if val is not None:
|
||||||
|
labels.append(str(val))
|
||||||
|
|
||||||
|
# Build datasets from y-axes
|
||||||
|
datasets = []
|
||||||
|
|
||||||
|
# Color palette for multi-series charts
|
||||||
|
default_colors = [
|
||||||
|
"#6366F1", # Indigo
|
||||||
|
"#10B981", # Green
|
||||||
|
"#3B82F6", # Blue
|
||||||
|
"#F59E0B", # Amber
|
||||||
|
"#EC4899", # Pink
|
||||||
|
"#8B5CF6", # Purple
|
||||||
|
"#06B6D4", # Cyan
|
||||||
|
"#EF4444", # Red
|
||||||
|
]
|
||||||
|
|
||||||
|
for idx, y in enumerate(y_axes):
|
||||||
|
y_field = y.get("y_field")
|
||||||
|
series_name = y_field # Use field name as series name
|
||||||
|
values = []
|
||||||
|
|
||||||
|
for r in data_rows:
|
||||||
|
if isinstance(r, dict):
|
||||||
|
val = r.get(y_field)
|
||||||
|
try:
|
||||||
|
values.append(float(val) if val is not None else 0)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
values.append(0)
|
||||||
|
|
||||||
|
# Use provided color or default from palette
|
||||||
|
color = y.get("color") or default_colors[idx % len(default_colors)]
|
||||||
|
datasets.append({
|
||||||
|
"name": series_name,
|
||||||
|
"values": values,
|
||||||
|
"color": color
|
||||||
|
})
|
||||||
|
|
||||||
|
chart_type = (chart.type or "Bar").title()
|
||||||
|
custom_options = _parse_custom_options(chart.custom_options)
|
||||||
|
|
||||||
|
# Handle Pie charts (only use first dataset)
|
||||||
|
if chart_type.lower() == "pie":
|
||||||
|
ds = datasets[0] if datasets else {"name": "value", "values": []}
|
||||||
|
_ok({
|
||||||
|
"labels": labels,
|
||||||
|
"datasets": [ds],
|
||||||
|
"type": "Pie",
|
||||||
|
"options": custom_options,
|
||||||
|
"source": {"report": chart.report_name},
|
||||||
|
})
|
||||||
|
return
|
||||||
|
|
||||||
|
# Return data for Bar/Line charts (supports multi-series/stacked)
|
||||||
|
_ok({
|
||||||
|
"labels": labels,
|
||||||
|
"datasets": datasets,
|
||||||
|
"type": chart_type,
|
||||||
|
"options": custom_options,
|
||||||
|
"source": {"report": chart.report_name},
|
||||||
|
# Include result for frontend transformation if needed
|
||||||
|
"result": data_rows if len(y_axes) > 1 else None,
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(frappe.get_traceback(), f"Report Chart Error: {chart.name}")
|
||||||
|
_err(f"Error processing report chart: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
def _process_custom_chart(chart, y_axes):
|
||||||
|
"""
|
||||||
|
Process a Custom/Document-type based Dashboard Chart.
|
||||||
|
Queries the source doctype directly based on configuration.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
source = chart.document_type
|
||||||
|
based_on = chart.based_on
|
||||||
|
value_based_on = chart.value_based_on or "name"
|
||||||
|
|
||||||
|
if not source or not based_on:
|
||||||
|
_err(f"Custom chart '{chart.name}' missing document_type or based_on configuration")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Build aggregation query
|
||||||
|
if chart.type and chart.type.lower() == "pie":
|
||||||
|
# Pie chart: Group by based_on field and count
|
||||||
|
data = frappe.db.sql(f"""
|
||||||
|
SELECT `{based_on}` as label, COUNT(`{value_based_on}`) as value
|
||||||
|
FROM `tab{source}`
|
||||||
|
GROUP BY `{based_on}`
|
||||||
|
ORDER BY value DESC
|
||||||
|
""", as_dict=True)
|
||||||
|
|
||||||
|
labels = [str(d.get("label") or "Unknown") for d in data]
|
||||||
|
values = [float(d.get("value") or 0) for d in data]
|
||||||
|
|
||||||
|
_ok({
|
||||||
|
"labels": labels,
|
||||||
|
"datasets": [{"name": "count", "values": values}],
|
||||||
|
"type": "Pie",
|
||||||
|
"options": _parse_custom_options(chart.custom_options),
|
||||||
|
"source": {"doctype": source},
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
# Bar chart: Group by based_on
|
||||||
|
data = frappe.db.sql(f"""
|
||||||
|
SELECT `{based_on}` as label, COUNT(`{value_based_on}`) as value
|
||||||
|
FROM `tab{source}`
|
||||||
|
GROUP BY `{based_on}`
|
||||||
|
ORDER BY value DESC
|
||||||
|
LIMIT 20
|
||||||
|
""", as_dict=True)
|
||||||
|
|
||||||
|
labels = [str(d.get("label") or "Unknown") for d in data]
|
||||||
|
values = [float(d.get("value") or 0) for d in data]
|
||||||
|
|
||||||
|
_ok({
|
||||||
|
"labels": labels,
|
||||||
|
"datasets": [{"name": "count", "values": values, "color": "#4F46E5"}],
|
||||||
|
"type": "Bar",
|
||||||
|
"options": _parse_custom_options(chart.custom_options),
|
||||||
|
"source": {"doctype": source},
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(frappe.get_traceback(), f"Custom Chart Error: {chart.name}")
|
||||||
|
_err(f"Error processing custom chart: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
def _process_group_by_chart(chart):
|
||||||
|
"""
|
||||||
|
Process a Group By / Count / Sum type Dashboard Chart.
|
||||||
|
These charts aggregate data from a doctype without using a report.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
source = chart.document_type
|
||||||
|
if not source:
|
||||||
|
_err(f"Group By chart '{chart.name}' missing document_type")
|
||||||
|
return
|
||||||
|
|
||||||
|
group_by_field = chart.group_by_based_on or chart.based_on
|
||||||
|
aggregate_function = chart.chart_type # Count, Sum, etc.
|
||||||
|
value_field = chart.aggregate_function_based_on or "name"
|
||||||
|
|
||||||
|
if not group_by_field:
|
||||||
|
_err(f"Group By chart '{chart.name}' missing group_by field")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Build query based on aggregate function
|
||||||
|
if aggregate_function == "Count":
|
||||||
|
query = f"""
|
||||||
|
SELECT `{group_by_field}` as label, COUNT(*) as value
|
||||||
|
FROM `tab{source}`
|
||||||
|
WHERE `{group_by_field}` IS NOT NULL
|
||||||
|
GROUP BY `{group_by_field}`
|
||||||
|
ORDER BY value DESC
|
||||||
|
LIMIT 20
|
||||||
|
"""
|
||||||
|
elif aggregate_function == "Sum":
|
||||||
|
query = f"""
|
||||||
|
SELECT `{group_by_field}` as label, SUM(`{value_field}`) as value
|
||||||
|
FROM `tab{source}`
|
||||||
|
WHERE `{group_by_field}` IS NOT NULL
|
||||||
|
GROUP BY `{group_by_field}`
|
||||||
|
ORDER BY value DESC
|
||||||
|
LIMIT 20
|
||||||
|
"""
|
||||||
|
else:
|
||||||
|
# Default to count
|
||||||
|
query = f"""
|
||||||
|
SELECT `{group_by_field}` as label, COUNT(*) as value
|
||||||
|
FROM `tab{source}`
|
||||||
|
WHERE `{group_by_field}` IS NOT NULL
|
||||||
|
GROUP BY `{group_by_field}`
|
||||||
|
ORDER BY value DESC
|
||||||
|
LIMIT 20
|
||||||
|
"""
|
||||||
|
|
||||||
|
data = frappe.db.sql(query, as_dict=True)
|
||||||
|
|
||||||
|
labels = [str(d.get("label") or "Unknown") for d in data]
|
||||||
|
values = [float(d.get("value") or 0) for d in data]
|
||||||
|
|
||||||
|
chart_type = (chart.type or "Bar").title()
|
||||||
|
|
||||||
|
_ok({
|
||||||
|
"labels": labels,
|
||||||
|
"datasets": [{"name": aggregate_function.lower(), "values": values, "color": "#6366F1"}],
|
||||||
|
"type": chart_type,
|
||||||
|
"options": _parse_custom_options(chart.custom_options),
|
||||||
|
"source": {"doctype": source},
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(frappe.get_traceback(), f"Group By Chart Error: {chart.name}")
|
||||||
|
_err(f"Error processing group by chart: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_custom_options(raw):
|
||||||
|
"""Parse custom_options JSON string to dict."""
|
||||||
|
if not raw:
|
||||||
|
return {}
|
||||||
|
try:
|
||||||
|
if isinstance(raw, dict):
|
||||||
|
return raw
|
||||||
|
return json.loads(raw)
|
||||||
|
except Exception:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest=True)
|
||||||
|
def get_repair_cost_by_item(year=None):
|
||||||
|
"""
|
||||||
|
Example specialized endpoint for 'Repair Cost' report style chart
|
||||||
|
(X: item_code, Y: amount, Filter: Year)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
year = int(year or frappe.utils.getdate(nowdate()).year)
|
||||||
|
rows = frappe.db.sql(
|
||||||
|
"""
|
||||||
|
SELECT item_code, SUM(amount) as amount
|
||||||
|
FROM `tabWork Order` wo
|
||||||
|
WHERE YEAR(wo.posting_date) = %(year)s
|
||||||
|
GROUP BY item_code
|
||||||
|
ORDER BY amount DESC
|
||||||
|
""",
|
||||||
|
{"year": year},
|
||||||
|
as_dict=True,
|
||||||
|
)
|
||||||
|
labels = [r.item_code or "Unknown" for r in rows]
|
||||||
|
values = [float(r.amount or 0) for r in rows]
|
||||||
|
_ok({
|
||||||
|
"labels": labels,
|
||||||
|
"datasets": [{"name": f"Repair Cost {year}", "values": values}],
|
||||||
|
"type": "Bar",
|
||||||
|
"options": {},
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(frappe.get_traceback(), "get_repair_cost_by_item")
|
||||||
|
_err(str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest=True)
|
||||||
|
def get_technician_working_hours(filters=None):
|
||||||
|
"""
|
||||||
|
Dedicated endpoint for Technician Working Hours chart.
|
||||||
|
Returns hours worked by each technician.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if isinstance(filters, str):
|
||||||
|
filters = json.loads(filters or "{}")
|
||||||
|
filters = filters or {}
|
||||||
|
|
||||||
|
# Run the report
|
||||||
|
run = frappe.get_attr("frappe.desk.query_report.run")
|
||||||
|
report_result = run("Technicians working Hours", filters=filters)
|
||||||
|
rows = report_result.get("result", []) or []
|
||||||
|
|
||||||
|
# Filter out total rows
|
||||||
|
data_rows = [r for r in rows if isinstance(r, dict) and not r.get("is_total_row")]
|
||||||
|
|
||||||
|
labels = [str(r.get("technician_name") or "Unknown") for r in data_rows]
|
||||||
|
values = [float(r.get("total_hours") or 0) for r in data_rows]
|
||||||
|
|
||||||
|
_ok({
|
||||||
|
"labels": labels,
|
||||||
|
"datasets": [{
|
||||||
|
"name": "total_hours",
|
||||||
|
"values": values,
|
||||||
|
"color": "#6366F1"
|
||||||
|
}],
|
||||||
|
"type": "Bar",
|
||||||
|
"options": {"barOptions": {"stacked": False}},
|
||||||
|
"result": data_rows,
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(frappe.get_traceback(), "get_technician_working_hours")
|
||||||
|
_err(str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest=True)
|
||||||
|
def get_technician_work_summary(filters=None):
|
||||||
|
"""
|
||||||
|
Dedicated endpoint for Technician Work Order Summary chart.
|
||||||
|
Returns work order counts (total, completed, in_progress, open) by technician.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if isinstance(filters, str):
|
||||||
|
filters = json.loads(filters or "{}")
|
||||||
|
filters = filters or {}
|
||||||
|
|
||||||
|
# Run the report
|
||||||
|
run = frappe.get_attr("frappe.desk.query_report.run")
|
||||||
|
report_result = run("Technician Work Order Summary", filters=filters)
|
||||||
|
rows = report_result.get("result", []) or []
|
||||||
|
|
||||||
|
# Filter out total rows
|
||||||
|
data_rows = [r for r in rows if isinstance(r, dict) and not r.get("is_total_row")]
|
||||||
|
|
||||||
|
labels = [str(r.get("assigned_technician") or "Unknown") for r in data_rows]
|
||||||
|
|
||||||
|
# Build multi-series datasets
|
||||||
|
datasets = [
|
||||||
|
{
|
||||||
|
"name": "total",
|
||||||
|
"values": [float(r.get("total") or 0) for r in data_rows],
|
||||||
|
"color": "#6366F1" # Indigo
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "completed",
|
||||||
|
"values": [float(r.get("completed") or 0) for r in data_rows],
|
||||||
|
"color": "#10B981" # Green
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "in_progress",
|
||||||
|
"values": [float(r.get("in_progress") or 0) for r in data_rows],
|
||||||
|
"color": "#3B82F6" # Blue
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "open",
|
||||||
|
"values": [float(r.get("open") or 0) for r in data_rows],
|
||||||
|
"color": "#F59E0B" # Amber
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
# Filter out datasets with all zeros
|
||||||
|
datasets = [ds for ds in datasets if any(v > 0 for v in ds["values"])]
|
||||||
|
|
||||||
|
_ok({
|
||||||
|
"labels": labels,
|
||||||
|
"datasets": datasets,
|
||||||
|
"type": "Bar",
|
||||||
|
"options": {"barOptions": {"stacked": True}},
|
||||||
|
"result": data_rows,
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(frappe.get_traceback(), "get_technician_work_summary")
|
||||||
|
_err(str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest=True)
|
||||||
|
def debug_list_charts():
|
||||||
|
"""
|
||||||
|
Debug endpoint to list all Dashboard Chart names.
|
||||||
|
Use this to verify exact chart names in the database.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
charts = frappe.db.sql("""
|
||||||
|
SELECT name, chart_name, chart_type, report_name, is_public, type
|
||||||
|
FROM `tabDashboard Chart`
|
||||||
|
ORDER BY modified DESC
|
||||||
|
""", as_dict=True)
|
||||||
|
|
||||||
|
_ok({
|
||||||
|
"total": len(charts),
|
||||||
|
"charts": charts
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(frappe.get_traceback(), "debug_list_charts")
|
||||||
|
_err(str(e))
|
||||||
128
asset_lite/api/delete_asset.py
Normal file
128
asset_lite/api/delete_asset.py
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
|
||||||
|
|
||||||
|
def _cancel_and_delete(doctype: str, name: str) -> None:
|
||||||
|
doc = frappe.get_doc(doctype, name)
|
||||||
|
doc.flags.ignore_permissions = True
|
||||||
|
doc.flags.ignore_links = True
|
||||||
|
if doc.docstatus == 1:
|
||||||
|
doc.cancel()
|
||||||
|
frappe.db.commit()
|
||||||
|
frappe.delete_doc(doctype, name, force=True, ignore_permissions=True, ignore_on_trash=True)
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
|
||||||
|
def _safe_collect(fn, *args) -> dict:
|
||||||
|
results = {"deleted": [], "errors": []}
|
||||||
|
try:
|
||||||
|
results["deleted"] = fn(*args)
|
||||||
|
except Exception as e:
|
||||||
|
results["errors"].append(str(e))
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def _del_asset_activities(asset_name: str) -> list:
|
||||||
|
deleted = []
|
||||||
|
for name in frappe.get_all("Asset Activity", filters={"asset": asset_name}, pluck="name"):
|
||||||
|
try:
|
||||||
|
frappe.delete_doc("Asset Activity", name, force=True, ignore_permissions=True, ignore_on_trash=True)
|
||||||
|
frappe.db.commit()
|
||||||
|
deleted.append(name)
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(f"Asset Activity delete error [{name}]: {e}")
|
||||||
|
return deleted
|
||||||
|
|
||||||
|
|
||||||
|
def _del_asset_movements(asset_name: str) -> list:
|
||||||
|
deleted = []
|
||||||
|
parent_names = list(set(
|
||||||
|
frappe.get_all("Asset Movement Item", filters={"asset": asset_name}, pluck="parent")
|
||||||
|
))
|
||||||
|
for name in parent_names:
|
||||||
|
try:
|
||||||
|
_cancel_and_delete("Asset Movement", name)
|
||||||
|
deleted.append(name)
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(f"Asset Movement delete error [{name}]: {e}")
|
||||||
|
return deleted
|
||||||
|
|
||||||
|
|
||||||
|
def _del_asset_value_adjustments(asset_name: str) -> list:
|
||||||
|
deleted = []
|
||||||
|
for name in frappe.get_all("Asset Value Adjustment", filters={"asset": asset_name}, pluck="name"):
|
||||||
|
try:
|
||||||
|
_cancel_and_delete("Asset Value Adjustment", name)
|
||||||
|
deleted.append(name)
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(f"Asset Value Adjustment delete error [{name}]: {e}")
|
||||||
|
return deleted
|
||||||
|
|
||||||
|
|
||||||
|
def _del_asset_depreciation_schedules(asset_name: str) -> list:
|
||||||
|
deleted = []
|
||||||
|
for name in frappe.get_all("Asset Depreciation Schedule", filters={"asset": asset_name}, pluck="name"):
|
||||||
|
try:
|
||||||
|
_cancel_and_delete("Asset Depreciation Schedule", name)
|
||||||
|
deleted.append(name)
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(f"Asset Depreciation Schedule delete error [{name}]: {e}")
|
||||||
|
return deleted
|
||||||
|
|
||||||
|
|
||||||
|
def _del_asset_maintenance(asset_name: str) -> list:
|
||||||
|
deleted = []
|
||||||
|
for name in frappe.get_all("Asset Maintenance", filters={"asset_name": asset_name}, pluck="name"):
|
||||||
|
try:
|
||||||
|
_cancel_and_delete("Asset Maintenance", name)
|
||||||
|
deleted.append(name)
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(f"Asset Maintenance delete error [{name}]: {e}")
|
||||||
|
return deleted
|
||||||
|
|
||||||
|
|
||||||
|
def _del_asset_repair(asset_name: str) -> list:
|
||||||
|
deleted = []
|
||||||
|
for name in frappe.get_all("Asset Repair", filters={"asset_name": asset_name}, pluck="name"):
|
||||||
|
try:
|
||||||
|
_cancel_and_delete("Asset Repair", name)
|
||||||
|
deleted.append(name)
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(f"Asset Repair delete error [{name}]: {e}")
|
||||||
|
return deleted
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def delete_asset(asset_name: str) -> dict:
|
||||||
|
if not frappe.db.exists("Asset", asset_name):
|
||||||
|
frappe.throw(_("Asset {0} does not exist.").format(asset_name), frappe.DoesNotExistError)
|
||||||
|
|
||||||
|
if not frappe.has_permission("Asset", "delete", asset_name):
|
||||||
|
frappe.throw(_("You do not have permission to delete Asset {0}.").format(asset_name), frappe.PermissionError)
|
||||||
|
|
||||||
|
details = {}
|
||||||
|
try:
|
||||||
|
details["asset_activities"] = _safe_collect(_del_asset_activities, asset_name)
|
||||||
|
details["asset_movements"] = _safe_collect(_del_asset_movements, asset_name)
|
||||||
|
details["asset_value_adjustments"] = _safe_collect(_del_asset_value_adjustments, asset_name)
|
||||||
|
details["asset_depreciation_schedules"] = _safe_collect(_del_asset_depreciation_schedules, asset_name)
|
||||||
|
details["asset_maintenance"] = _safe_collect(_del_asset_maintenance, asset_name)
|
||||||
|
# details["asset_repair"] = _safe_collect(_del_asset_repair, asset_name)
|
||||||
|
|
||||||
|
asset_doc = frappe.get_doc("Asset", asset_name)
|
||||||
|
asset_doc.flags.ignore_permissions = True
|
||||||
|
asset_doc.flags.ignore_links = True
|
||||||
|
|
||||||
|
if asset_doc.docstatus == 1:
|
||||||
|
asset_doc.cancel()
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
frappe.delete_doc("Asset", asset_name, force=True, ignore_permissions=True, ignore_on_trash=True)
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
return {"success": True, "asset_name": asset_name, "details": details}
|
||||||
|
|
||||||
|
except Exception as exc:
|
||||||
|
frappe.db.rollback()
|
||||||
|
frappe.log_error(title=f"Asset deletion failed: {asset_name}", message=frappe.get_traceback())
|
||||||
|
frappe.throw(_("Failed to delete asset {0}: {1}").format(asset_name, str(exc)))
|
||||||
29
asset_lite/api/delete_request.py
Normal file
29
asset_lite/api/delete_request.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def create_delete_request(target_doctype, target_name, target_display, reason, department):
|
||||||
|
"""Create a deletion request for any doctype"""
|
||||||
|
doc = frappe.new_doc("Delete Request")
|
||||||
|
doc.target_doctype = target_doctype
|
||||||
|
doc.target_name = target_name
|
||||||
|
doc.target_display = target_display or target_name
|
||||||
|
doc.reason = reason or ""
|
||||||
|
doc.department = department or ""
|
||||||
|
doc.requested_by = frappe.session.user
|
||||||
|
doc.status = "Pending"
|
||||||
|
doc.workflow_state = "Pending Supervisor"
|
||||||
|
doc.insert(ignore_permissions=False)
|
||||||
|
return {"name": doc.name, "status": "created"}
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_pending_requests(doctype_filter=None):
|
||||||
|
"""Get pending requests for current approver"""
|
||||||
|
filters = {"workflow_state": ["in", ["Pending Supervisor", "Pending Cluster Manager"]]}
|
||||||
|
if doctype_filter:
|
||||||
|
filters["target_doctype"] = doctype_filter
|
||||||
|
return frappe.get_list(
|
||||||
|
"Delete Request",
|
||||||
|
filters=filters,
|
||||||
|
fields=["name", "target_doctype", "target_name", "target_display",
|
||||||
|
"reason", "department", "requested_by", "workflow_state", "creation"]
|
||||||
|
)
|
||||||
107
asset_lite/api/doctype_fields.py
Normal file
107
asset_lite/api/doctype_fields.py
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
# asset_lite/api/doctype_fields.py
|
||||||
|
#
|
||||||
|
# WHY THIS EXISTS:
|
||||||
|
# Frappe's /api/resource/DocType and /api/resource/Custom Field endpoints are
|
||||||
|
# role-filtered — they silently omit fields the current user's role cannot read.
|
||||||
|
# This causes the export modal to show different field counts per role (6 for
|
||||||
|
# Contractor Engineer, 45 for admin, etc.).
|
||||||
|
#
|
||||||
|
# This whitelisted function runs as the SYSTEM user (via frappe.get_doc /
|
||||||
|
# frappe.get_all which bypass field-level permission checks), so it always
|
||||||
|
# returns the complete field list regardless of who is logged in.
|
||||||
|
# The React hook calls this endpoint instead of the meta APIs.
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
|
||||||
|
# Fields that are never useful in an export UI
|
||||||
|
SKIP_FIELDTYPES = {
|
||||||
|
"Section Break", "Column Break", "Tab Break",
|
||||||
|
"HTML", "Button", "Fold", "Heading",
|
||||||
|
"Image", "Signature", "Geolocation", "Barcode",
|
||||||
|
"Table", "Table MultiSelect",
|
||||||
|
}
|
||||||
|
|
||||||
|
SKIP_FIELDNAMES = {
|
||||||
|
"docstatus", "idx", "naming_series", "amended_from",
|
||||||
|
"amendment_date", "lft", "rgt", "old_parent",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Always include these meta fields first
|
||||||
|
META_FIELDS = [
|
||||||
|
{"fieldname": "name", "label": "ID", "fieldtype": "Data"},
|
||||||
|
{"fieldname": "owner", "label": "Created By", "fieldtype": "Data"},
|
||||||
|
{"fieldname": "creation", "label": "Created On", "fieldtype": "Datetime"},
|
||||||
|
{"fieldname": "modified_by", "label": "Modified By", "fieldtype": "Data"},
|
||||||
|
{"fieldname": "modified", "label": "Modified On", "fieldtype": "Datetime"},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_export_fields(doctype: str) -> list:
|
||||||
|
"""
|
||||||
|
Returns the complete list of exportable fields for a DocType.
|
||||||
|
Ignores field-level role permissions intentionally — this endpoint
|
||||||
|
is for the export column picker UI only, not for data access control.
|
||||||
|
Data access is still enforced when the actual export fetch runs.
|
||||||
|
|
||||||
|
Returns a list of dicts: { fieldname, label, fieldtype }
|
||||||
|
"""
|
||||||
|
# Basic validation — ensure the doctype itself exists and user can read it
|
||||||
|
if not frappe.db.exists("DocType", doctype):
|
||||||
|
frappe.throw(_("DocType {0} does not exist").format(doctype), frappe.DoesNotExistError)
|
||||||
|
|
||||||
|
# Check the user can at least read this doctype (doc-level permission, not field-level)
|
||||||
|
if not frappe.has_permission(doctype, "read"):
|
||||||
|
frappe.throw(_("No read permission for {0}").format(doctype), frappe.PermissionError)
|
||||||
|
|
||||||
|
meta_fieldnames = {f["fieldname"] for f in META_FIELDS}
|
||||||
|
seen = set(meta_fieldnames)
|
||||||
|
result = []
|
||||||
|
|
||||||
|
# ── Standard DocType fields ──────────────────────────────────────────────
|
||||||
|
# frappe.get_meta() loads DocType metadata as SYSTEM — not role-filtered
|
||||||
|
meta = frappe.get_meta(doctype)
|
||||||
|
|
||||||
|
for f in meta.fields:
|
||||||
|
if f.fieldtype in SKIP_FIELDTYPES:
|
||||||
|
continue
|
||||||
|
if f.fieldname in SKIP_FIELDNAMES:
|
||||||
|
continue
|
||||||
|
if f.hidden:
|
||||||
|
continue
|
||||||
|
if f.fieldname in seen:
|
||||||
|
continue
|
||||||
|
seen.add(f.fieldname)
|
||||||
|
result.append({
|
||||||
|
"fieldname": f.fieldname,
|
||||||
|
"label": f.label or f.fieldname,
|
||||||
|
"fieldtype": f.fieldtype or "Data",
|
||||||
|
})
|
||||||
|
|
||||||
|
# ── Custom fields ────────────────────────────────────────────────────────
|
||||||
|
# frappe.get_all() with ignore_permissions=True bypasses role filtering
|
||||||
|
custom_fields = frappe.get_all(
|
||||||
|
"Custom Field",
|
||||||
|
filters={"dt": doctype, "hidden": 0},
|
||||||
|
fields=["fieldname", "label", "fieldtype"],
|
||||||
|
ignore_permissions=True, # <-- KEY: bypasses role-based field restrictions
|
||||||
|
limit_page_length=500,
|
||||||
|
)
|
||||||
|
|
||||||
|
for f in custom_fields:
|
||||||
|
if f.fieldtype in SKIP_FIELDTYPES:
|
||||||
|
continue
|
||||||
|
if f.fieldname in SKIP_FIELDNAMES:
|
||||||
|
continue
|
||||||
|
if f.fieldname in seen:
|
||||||
|
continue
|
||||||
|
seen.add(f.fieldname)
|
||||||
|
result.append({
|
||||||
|
"fieldname": f["fieldname"],
|
||||||
|
"label": f["label"] or f["fieldname"],
|
||||||
|
"fieldtype": f["fieldtype"] or "Data",
|
||||||
|
})
|
||||||
|
|
||||||
|
# Return meta fields first, then the rest
|
||||||
|
return META_FIELDS + result
|
||||||
46
asset_lite/api/item_api.py
Normal file
46
asset_lite/api/item_api.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import frappe
|
||||||
|
import json
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_items(filters=None, fields=None, limit=20, offset=0, order_by="creation desc"):
|
||||||
|
import json
|
||||||
|
|
||||||
|
if isinstance(filters, str):
|
||||||
|
filters = json.loads(filters)
|
||||||
|
if isinstance(fields, str):
|
||||||
|
fields = json.loads(fields)
|
||||||
|
if isinstance(limit, str):
|
||||||
|
limit = int(limit)
|
||||||
|
if isinstance(offset, str):
|
||||||
|
offset = int(offset)
|
||||||
|
|
||||||
|
if not fields:
|
||||||
|
fields = ["name", "item_code", "item_name", "item_group", "stock_uom",
|
||||||
|
"custom_hospital_name", "custom_serial_no", "custom_date_in",
|
||||||
|
"custom_code", "custom_type", "custom_volts", "custom_w",
|
||||||
|
"custom_delete_status", "creation", "modified", "owner", "docstatus",
|
||||||
|
"custom_technical_department", "disabled", "is_stock_item"]
|
||||||
|
|
||||||
|
data = frappe.get_all(
|
||||||
|
"Item",
|
||||||
|
filters=filters or [],
|
||||||
|
fields=fields,
|
||||||
|
limit_start=offset,
|
||||||
|
limit_page_length=limit,
|
||||||
|
order_by=order_by,
|
||||||
|
ignore_permissions=False
|
||||||
|
)
|
||||||
|
|
||||||
|
total_count = len(frappe.get_all(
|
||||||
|
"Item",
|
||||||
|
filters=filters or [],
|
||||||
|
fields=["name"],
|
||||||
|
limit_page_length=0, # 0 = no limit = all
|
||||||
|
ignore_permissions=False
|
||||||
|
))
|
||||||
|
|
||||||
|
return {
|
||||||
|
"data": data,
|
||||||
|
"total": total_count,
|
||||||
|
"has_more": (offset + limit) < total_count
|
||||||
|
}
|
||||||
565
asset_lite/api/ppm_api.py
Normal file
565
asset_lite/api/ppm_api.py
Normal file
@ -0,0 +1,565 @@
|
|||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest = True)
|
||||||
|
def get_asset_maintenances(filters=None, fields=None, limit=20, offset=0, order_by=None):
|
||||||
|
"""
|
||||||
|
Get list of asset maintenances (PPM schedules) with filters and pagination
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filters: JSON string of filters (e.g., '{"company": "Al Jouf Hospital"}')
|
||||||
|
fields: JSON string of fields to return (e.g., '["asset_name", "maintenance_team"]')
|
||||||
|
limit: Number of records to return (default: 20)
|
||||||
|
offset: Number of records to skip (default: 0)
|
||||||
|
order_by: Sort order (e.g., "creation desc")
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
{
|
||||||
|
"asset_maintenances": [...],
|
||||||
|
"total_count": int,
|
||||||
|
"limit": int,
|
||||||
|
"offset": int,
|
||||||
|
"has_more": bool
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Parse filters if provided
|
||||||
|
if filters and isinstance(filters, str):
|
||||||
|
import json
|
||||||
|
filters = json.loads(filters)
|
||||||
|
|
||||||
|
# Parse fields if provided
|
||||||
|
if fields and isinstance(fields, str):
|
||||||
|
import json
|
||||||
|
fields = json.loads(fields)
|
||||||
|
else:
|
||||||
|
# Default fields to return
|
||||||
|
fields = [
|
||||||
|
'name',
|
||||||
|
'company',
|
||||||
|
'asset_name',
|
||||||
|
'custom_asset_type',
|
||||||
|
'asset_category',
|
||||||
|
'custom_type_of_maintenance',
|
||||||
|
'custom_asset_name',
|
||||||
|
'item_code',
|
||||||
|
'item_name',
|
||||||
|
'maintenance_team',
|
||||||
|
'custom_pm_schedule',
|
||||||
|
'maintenance_manager',
|
||||||
|
'maintenance_manager_name',
|
||||||
|
'custom_warranty',
|
||||||
|
'custom_warranty_status',
|
||||||
|
'custom_service_contract',
|
||||||
|
'custom_service_contract_status',
|
||||||
|
'custom_frequency',
|
||||||
|
'custom_total_amount',
|
||||||
|
'custom_no_of_pms',
|
||||||
|
'custom_price_per_pm',
|
||||||
|
'creation',
|
||||||
|
'modified',
|
||||||
|
'owner',
|
||||||
|
'modified_by',
|
||||||
|
'docstatus',
|
||||||
|
'idx'
|
||||||
|
]
|
||||||
|
|
||||||
|
# Get total count
|
||||||
|
total_count = frappe.db.count('Asset Maintenance', filters=filters or {})
|
||||||
|
|
||||||
|
# Get asset maintenances
|
||||||
|
asset_maintenances = frappe.get_all(
|
||||||
|
'Asset Maintenance',
|
||||||
|
filters=filters or {},
|
||||||
|
fields=fields,
|
||||||
|
limit_page_length=int(limit),
|
||||||
|
limit_start=int(offset),
|
||||||
|
order_by=order_by or 'creation desc'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Calculate has_more
|
||||||
|
has_more = (int(offset) + int(limit)) < total_count
|
||||||
|
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'asset_maintenances': asset_maintenances,
|
||||||
|
'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 Asset Maintenances API Error')
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'error': str(e),
|
||||||
|
'asset_maintenances': [],
|
||||||
|
'total_count': 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest = True)
|
||||||
|
def get_asset_maintenance_details(maintenance_name):
|
||||||
|
"""
|
||||||
|
Get detailed information about a specific asset maintenance (PPM schedule)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
maintenance_name: Name/ID of the asset maintenance
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Asset Maintenance document with all fields including child tables
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if not maintenance_name:
|
||||||
|
frappe.throw(_('Asset Maintenance name is required'))
|
||||||
|
|
||||||
|
# Check if user has permission to read this maintenance
|
||||||
|
if not frappe.has_permission('Asset Maintenance', 'read', maintenance_name):
|
||||||
|
frappe.throw(_('Not permitted to access this asset maintenance'))
|
||||||
|
|
||||||
|
# Get asset maintenance details
|
||||||
|
maintenance = frappe.get_doc('Asset Maintenance', maintenance_name)
|
||||||
|
|
||||||
|
frappe.response['message'] = maintenance.as_dict()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(frappe.get_traceback(), 'Get Asset Maintenance Details API Error')
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'error': str(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest = True)
|
||||||
|
def create_asset_maintenance(maintenance_data):
|
||||||
|
"""
|
||||||
|
Create a new asset maintenance (PPM schedule)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
maintenance_data: JSON string containing asset maintenance fields
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Created asset maintenance document
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
|
||||||
|
# Parse maintenance data
|
||||||
|
if isinstance(maintenance_data, str):
|
||||||
|
maintenance_data = json.loads(maintenance_data)
|
||||||
|
|
||||||
|
# Check if user has permission to create asset maintenance
|
||||||
|
if not frappe.has_permission('Asset Maintenance', 'create'):
|
||||||
|
frappe.throw(_('Not permitted to create asset maintenance'))
|
||||||
|
|
||||||
|
# Create new asset maintenance
|
||||||
|
maintenance = frappe.get_doc({
|
||||||
|
'doctype': 'Asset Maintenance',
|
||||||
|
**maintenance_data
|
||||||
|
})
|
||||||
|
|
||||||
|
maintenance.insert()
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': True,
|
||||||
|
'asset_maintenance': maintenance.as_dict(),
|
||||||
|
'message': _('Asset Maintenance created successfully')
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.db.rollback()
|
||||||
|
frappe.log_error(frappe.get_traceback(), 'Create Asset Maintenance API Error')
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': False,
|
||||||
|
'error': str(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest = True)
|
||||||
|
def update_asset_maintenance(maintenance_name, maintenance_data):
|
||||||
|
"""
|
||||||
|
Update an existing asset maintenance (PPM schedule)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
maintenance_name: Name/ID of the asset maintenance
|
||||||
|
maintenance_data: JSON string containing fields to update
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Updated asset maintenance document
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
|
||||||
|
if not maintenance_name:
|
||||||
|
frappe.throw(_('Asset Maintenance name is required'))
|
||||||
|
|
||||||
|
# Parse maintenance data
|
||||||
|
if isinstance(maintenance_data, str):
|
||||||
|
maintenance_data = json.loads(maintenance_data)
|
||||||
|
|
||||||
|
# Check if user has permission to update this maintenance
|
||||||
|
if not frappe.has_permission('Asset Maintenance', 'write', maintenance_name):
|
||||||
|
frappe.throw(_('Not permitted to update this asset maintenance'))
|
||||||
|
|
||||||
|
# Get and update asset maintenance
|
||||||
|
maintenance = frappe.get_doc('Asset Maintenance', maintenance_name)
|
||||||
|
|
||||||
|
# Update fields
|
||||||
|
for key, value in maintenance_data.items():
|
||||||
|
if hasattr(maintenance, key):
|
||||||
|
setattr(maintenance, key, value)
|
||||||
|
|
||||||
|
maintenance.save()
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': True,
|
||||||
|
'asset_maintenance': maintenance.as_dict(),
|
||||||
|
'message': _('Asset Maintenance updated successfully')
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.db.rollback()
|
||||||
|
frappe.log_error(frappe.get_traceback(), 'Update Asset Maintenance API Error')
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': False,
|
||||||
|
'error': str(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest = True)
|
||||||
|
def delete_asset_maintenance(maintenance_name):
|
||||||
|
"""
|
||||||
|
Delete an asset maintenance (PPM schedule)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
maintenance_name: Name/ID of the asset maintenance
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Success message
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if not maintenance_name:
|
||||||
|
frappe.throw(_('Asset Maintenance name is required'))
|
||||||
|
|
||||||
|
# Check if user has permission to delete this maintenance
|
||||||
|
if not frappe.has_permission('Asset Maintenance', 'delete', maintenance_name):
|
||||||
|
frappe.throw(_('Not permitted to delete this asset maintenance'))
|
||||||
|
|
||||||
|
# Delete asset maintenance
|
||||||
|
frappe.delete_doc('Asset Maintenance', maintenance_name)
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': True,
|
||||||
|
'message': _('Asset Maintenance deleted successfully')
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.db.rollback()
|
||||||
|
frappe.log_error(frappe.get_traceback(), 'Delete Asset Maintenance API Error')
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': False,
|
||||||
|
'error': str(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest = True)
|
||||||
|
def get_maintenance_tasks(maintenance_name):
|
||||||
|
"""
|
||||||
|
Get all maintenance tasks for a specific asset maintenance
|
||||||
|
|
||||||
|
Args:
|
||||||
|
maintenance_name: Name/ID of the asset maintenance
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of maintenance tasks (asset_maintenance_tasks child table)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if not maintenance_name:
|
||||||
|
frappe.throw(_('Asset Maintenance name is required'))
|
||||||
|
|
||||||
|
# Check if user has permission to read this maintenance
|
||||||
|
if not frappe.has_permission('Asset Maintenance', 'read', maintenance_name):
|
||||||
|
frappe.throw(_('Not permitted to access this asset maintenance'))
|
||||||
|
|
||||||
|
# Get maintenance tasks
|
||||||
|
tasks = frappe.get_all(
|
||||||
|
'Asset Maintenance Task',
|
||||||
|
filters={'parent': maintenance_name},
|
||||||
|
fields=['*'],
|
||||||
|
order_by='idx asc'
|
||||||
|
)
|
||||||
|
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'maintenance_tasks': tasks,
|
||||||
|
'total_count': len(tasks)
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(frappe.get_traceback(), 'Get Maintenance Tasks API Error')
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'error': str(e),
|
||||||
|
'maintenance_tasks': []
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest = True)
|
||||||
|
def get_service_coverage(maintenance_name):
|
||||||
|
"""
|
||||||
|
Get service coverage details for a specific asset maintenance
|
||||||
|
|
||||||
|
Args:
|
||||||
|
maintenance_name: Name/ID of the asset maintenance
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of service coverage (custom_service_coverage_table child table)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if not maintenance_name:
|
||||||
|
frappe.throw(_('Asset Maintenance name is required'))
|
||||||
|
|
||||||
|
# Check if user has permission to read this maintenance
|
||||||
|
if not frappe.has_permission('Asset Maintenance', 'read', maintenance_name):
|
||||||
|
frappe.throw(_('Not permitted to access this asset maintenance'))
|
||||||
|
|
||||||
|
# Get service coverage
|
||||||
|
coverage = frappe.get_all(
|
||||||
|
'Service Coverage',
|
||||||
|
filters={'parent': maintenance_name},
|
||||||
|
fields=['*'],
|
||||||
|
order_by='idx asc'
|
||||||
|
)
|
||||||
|
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'service_coverage': coverage,
|
||||||
|
'total_count': len(coverage)
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(frappe.get_traceback(), 'Get Service Coverage API Error')
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'error': str(e),
|
||||||
|
'service_coverage': []
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest = True)
|
||||||
|
def add_maintenance_task(maintenance_name, task_data):
|
||||||
|
"""
|
||||||
|
Add a new maintenance task to an asset maintenance
|
||||||
|
|
||||||
|
Args:
|
||||||
|
maintenance_name: Name/ID of the asset maintenance
|
||||||
|
task_data: JSON string containing task fields
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Updated asset maintenance document
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
|
||||||
|
if not maintenance_name:
|
||||||
|
frappe.throw(_('Asset Maintenance name is required'))
|
||||||
|
|
||||||
|
# Parse task data
|
||||||
|
if isinstance(task_data, str):
|
||||||
|
task_data = json.loads(task_data)
|
||||||
|
|
||||||
|
# Check if user has permission to update this maintenance
|
||||||
|
if not frappe.has_permission('Asset Maintenance', 'write', maintenance_name):
|
||||||
|
frappe.throw(_('Not permitted to update this asset maintenance'))
|
||||||
|
|
||||||
|
# Get asset maintenance
|
||||||
|
maintenance = frappe.get_doc('Asset Maintenance', maintenance_name)
|
||||||
|
|
||||||
|
# Add new task
|
||||||
|
maintenance.append('asset_maintenance_tasks', task_data)
|
||||||
|
maintenance.save()
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': True,
|
||||||
|
'asset_maintenance': maintenance.as_dict(),
|
||||||
|
'message': _('Maintenance task added successfully')
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.db.rollback()
|
||||||
|
frappe.log_error(frappe.get_traceback(), 'Add Maintenance Task API Error')
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': False,
|
||||||
|
'error': str(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest = True)
|
||||||
|
def update_maintenance_task(task_name, task_data):
|
||||||
|
"""
|
||||||
|
Update a specific maintenance task
|
||||||
|
|
||||||
|
Args:
|
||||||
|
task_name: Name/ID of the maintenance task
|
||||||
|
task_data: JSON string containing fields to update
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Updated task details
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
|
||||||
|
if not task_name:
|
||||||
|
frappe.throw(_('Maintenance task name is required'))
|
||||||
|
|
||||||
|
# Parse task data
|
||||||
|
if isinstance(task_data, str):
|
||||||
|
task_data = json.loads(task_data)
|
||||||
|
|
||||||
|
# Get the task to find parent
|
||||||
|
task = frappe.get_doc('Asset Maintenance Task', task_name)
|
||||||
|
|
||||||
|
# Check if user has permission to update parent maintenance
|
||||||
|
if not frappe.has_permission('Asset Maintenance', 'write', task.parent):
|
||||||
|
frappe.throw(_('Not permitted to update this maintenance task'))
|
||||||
|
|
||||||
|
# Update task fields
|
||||||
|
for key, value in task_data.items():
|
||||||
|
if hasattr(task, key):
|
||||||
|
setattr(task, key, value)
|
||||||
|
|
||||||
|
task.save()
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': True,
|
||||||
|
'maintenance_task': task.as_dict(),
|
||||||
|
'message': _('Maintenance task updated successfully')
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.db.rollback()
|
||||||
|
frappe.log_error(frappe.get_traceback(), 'Update Maintenance Task API Error')
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': False,
|
||||||
|
'error': str(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest = True)
|
||||||
|
def get_maintenances_by_asset(asset_name, filters=None, limit=20, offset=0):
|
||||||
|
"""
|
||||||
|
Get all maintenance schedules for a specific asset
|
||||||
|
|
||||||
|
Args:
|
||||||
|
asset_name: Name/ID of the asset
|
||||||
|
filters: Additional JSON string of filters
|
||||||
|
limit: Number of records to return (default: 20)
|
||||||
|
offset: Number of records to skip (default: 0)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of maintenance schedules for the asset
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
|
||||||
|
if not asset_name:
|
||||||
|
frappe.throw(_('Asset name is required'))
|
||||||
|
|
||||||
|
# Parse additional filters if provided
|
||||||
|
additional_filters = {}
|
||||||
|
if filters and isinstance(filters, str):
|
||||||
|
additional_filters = json.loads(filters)
|
||||||
|
|
||||||
|
# Combine filters
|
||||||
|
combined_filters = {'asset_name': asset_name, **additional_filters}
|
||||||
|
|
||||||
|
# Get total count
|
||||||
|
total_count = frappe.db.count('Asset Maintenance', filters=combined_filters)
|
||||||
|
|
||||||
|
# Get maintenances
|
||||||
|
maintenances = frappe.get_all(
|
||||||
|
'Asset Maintenance',
|
||||||
|
filters=combined_filters,
|
||||||
|
fields=['*'],
|
||||||
|
limit_page_length=int(limit),
|
||||||
|
limit_start=int(offset),
|
||||||
|
order_by='creation desc'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Calculate has_more
|
||||||
|
has_more = (int(offset) + int(limit)) < total_count
|
||||||
|
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'asset_maintenances': maintenances,
|
||||||
|
'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 Maintenances By Asset API Error')
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'error': str(e),
|
||||||
|
'asset_maintenances': [],
|
||||||
|
'total_count': 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest = True)
|
||||||
|
def get_active_service_contracts(filters=None, limit=20, offset=0):
|
||||||
|
"""
|
||||||
|
Get all asset maintenances with active service contracts
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filters: Additional JSON string of filters
|
||||||
|
limit: Number of records to return (default: 20)
|
||||||
|
offset: Number of records to skip (default: 0)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of asset maintenances with active contracts
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
|
||||||
|
# Parse additional filters if provided
|
||||||
|
additional_filters = {}
|
||||||
|
if filters and isinstance(filters, str):
|
||||||
|
additional_filters = json.loads(filters)
|
||||||
|
|
||||||
|
# Combine filters - get maintenances with service contract = 1
|
||||||
|
combined_filters = {
|
||||||
|
'custom_service_contract': 1,
|
||||||
|
**additional_filters
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get total count
|
||||||
|
total_count = frappe.db.count('Asset Maintenance', filters=combined_filters)
|
||||||
|
|
||||||
|
# Get maintenances
|
||||||
|
maintenances = frappe.get_all(
|
||||||
|
'Asset Maintenance',
|
||||||
|
filters=combined_filters,
|
||||||
|
fields=['*'],
|
||||||
|
limit_page_length=int(limit),
|
||||||
|
limit_start=int(offset),
|
||||||
|
order_by='creation desc'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Calculate has_more
|
||||||
|
has_more = (int(offset) + int(limit)) < total_count
|
||||||
|
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'asset_maintenances': maintenances,
|
||||||
|
'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 Active Service Contracts API Error')
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'error': str(e),
|
||||||
|
'asset_maintenances': [],
|
||||||
|
'total_count': 0
|
||||||
|
}
|
||||||
1197
asset_lite/api/ppm_generator_api.py
Normal file
1197
asset_lite/api/ppm_generator_api.py
Normal file
File diff suppressed because it is too large
Load Diff
214
asset_lite/api/room_filter.py
Normal file
214
asset_lite/api/room_filter.py
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
import frappe
|
||||||
|
import json
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_filtered_rooms(txt='', doctype='Room', searchfield='name', start=0, page_len=50, filters=None):
|
||||||
|
"""
|
||||||
|
Custom query for Room LinkField that filters based on Infrastructure Location mapping.
|
||||||
|
Returns rooms that are mapped to the specified building/department in Infrastructure Location.
|
||||||
|
"""
|
||||||
|
# Parse filters if string
|
||||||
|
if isinstance(filters, str):
|
||||||
|
try:
|
||||||
|
filters = json.loads(filters)
|
||||||
|
except:
|
||||||
|
filters = {}
|
||||||
|
|
||||||
|
if not filters:
|
||||||
|
filters = {}
|
||||||
|
|
||||||
|
building = filters.get('building', '')
|
||||||
|
department = filters.get('department', '')
|
||||||
|
|
||||||
|
txt = txt or ''
|
||||||
|
start = int(start)
|
||||||
|
page_len = int(page_len)
|
||||||
|
|
||||||
|
# If building or department is specified, filter via Infrastructure Location
|
||||||
|
if building or department:
|
||||||
|
infra_conditions = []
|
||||||
|
values = {
|
||||||
|
'txt': f'%{txt}%',
|
||||||
|
'start': start,
|
||||||
|
'page_len': page_len
|
||||||
|
}
|
||||||
|
|
||||||
|
if building:
|
||||||
|
infra_conditions.append("il.building = %(building)s")
|
||||||
|
values['building'] = building
|
||||||
|
|
||||||
|
if department:
|
||||||
|
infra_conditions.append("il.department = %(department)s")
|
||||||
|
values['department'] = department
|
||||||
|
|
||||||
|
infra_where = " AND ".join(infra_conditions)
|
||||||
|
|
||||||
|
# Query rooms that exist in Infrastructure Location with given filters
|
||||||
|
query = f"""
|
||||||
|
SELECT DISTINCT r.name, r.name as description
|
||||||
|
FROM `tabRoom` r
|
||||||
|
INNER JOIN `tabInfrastructure Location` il ON il.room_no = r.name
|
||||||
|
WHERE ({infra_where})
|
||||||
|
AND r.name LIKE %(txt)s
|
||||||
|
ORDER BY r.name
|
||||||
|
LIMIT %(start)s, %(page_len)s
|
||||||
|
"""
|
||||||
|
|
||||||
|
results = frappe.db.sql(query, values, as_list=True)
|
||||||
|
else:
|
||||||
|
# No building/department filter - return all rooms matching search text
|
||||||
|
results = frappe.db.sql("""
|
||||||
|
SELECT name, name as description
|
||||||
|
FROM `tabRoom`
|
||||||
|
WHERE name LIKE %(txt)s
|
||||||
|
ORDER BY name
|
||||||
|
LIMIT %(start)s, %(page_len)s
|
||||||
|
""", {
|
||||||
|
'txt': f'%{txt}%',
|
||||||
|
'start': start,
|
||||||
|
'page_len': page_len
|
||||||
|
}, as_list=True)
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_filtered_departments(txt='', doctype='Department', searchfield='name', start=0, page_len=50, filters=None):
|
||||||
|
"""
|
||||||
|
Custom query for Department LinkField that filters based on Infrastructure Location mapping.
|
||||||
|
Returns departments that are mapped to the specified building in Infrastructure Location.
|
||||||
|
"""
|
||||||
|
# Parse filters if string
|
||||||
|
if isinstance(filters, str):
|
||||||
|
try:
|
||||||
|
filters = json.loads(filters)
|
||||||
|
except:
|
||||||
|
filters = {}
|
||||||
|
|
||||||
|
if not filters:
|
||||||
|
filters = {}
|
||||||
|
|
||||||
|
building = filters.get('building', '')
|
||||||
|
company = filters.get('company', '')
|
||||||
|
|
||||||
|
txt = txt or ''
|
||||||
|
start = int(start)
|
||||||
|
page_len = int(page_len)
|
||||||
|
|
||||||
|
values = {
|
||||||
|
'txt': f'%{txt}%',
|
||||||
|
'start': start,
|
||||||
|
'page_len': page_len
|
||||||
|
}
|
||||||
|
|
||||||
|
# If building is specified, filter via Infrastructure Location
|
||||||
|
if building:
|
||||||
|
query = """
|
||||||
|
SELECT DISTINCT d.name, d.name as description
|
||||||
|
FROM `tabDepartment` d
|
||||||
|
INNER JOIN `tabInfrastructure Location` il ON il.department = d.name
|
||||||
|
WHERE il.building = %(building)s
|
||||||
|
AND d.name LIKE %(txt)s
|
||||||
|
"""
|
||||||
|
values['building'] = building
|
||||||
|
|
||||||
|
# Add company filter if provided
|
||||||
|
if company:
|
||||||
|
query += " AND d.company = %(company)s"
|
||||||
|
values['company'] = company
|
||||||
|
|
||||||
|
query += """
|
||||||
|
ORDER BY d.name
|
||||||
|
LIMIT %(start)s, %(page_len)s
|
||||||
|
"""
|
||||||
|
|
||||||
|
results = frappe.db.sql(query, values, as_list=True)
|
||||||
|
else:
|
||||||
|
# No building filter - return departments with optional company filter
|
||||||
|
if company:
|
||||||
|
results = frappe.db.sql("""
|
||||||
|
SELECT name, name as description
|
||||||
|
FROM `tabDepartment`
|
||||||
|
WHERE name LIKE %(txt)s
|
||||||
|
AND company = %(company)s
|
||||||
|
ORDER BY name
|
||||||
|
LIMIT %(start)s, %(page_len)s
|
||||||
|
""", {
|
||||||
|
'txt': f'%{txt}%',
|
||||||
|
'company': company,
|
||||||
|
'start': start,
|
||||||
|
'page_len': page_len
|
||||||
|
}, as_list=True)
|
||||||
|
else:
|
||||||
|
results = frappe.db.sql("""
|
||||||
|
SELECT name, name as description
|
||||||
|
FROM `tabDepartment`
|
||||||
|
WHERE name LIKE %(txt)s
|
||||||
|
ORDER BY name
|
||||||
|
LIMIT %(start)s, %(page_len)s
|
||||||
|
""", {
|
||||||
|
'txt': f'%{txt}%',
|
||||||
|
'start': start,
|
||||||
|
'page_len': page_len
|
||||||
|
}, as_list=True)
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_room_count(building=None, department=None):
|
||||||
|
"""
|
||||||
|
Get count of rooms for a building/department combination from Infrastructure Location.
|
||||||
|
Used to show "X room(s) available" message.
|
||||||
|
"""
|
||||||
|
if not building and not department:
|
||||||
|
return frappe.db.count('Room')
|
||||||
|
|
||||||
|
conditions = []
|
||||||
|
values = {}
|
||||||
|
|
||||||
|
if building:
|
||||||
|
conditions.append("building = %(building)s")
|
||||||
|
values['building'] = building
|
||||||
|
|
||||||
|
if department:
|
||||||
|
conditions.append("department = %(department)s")
|
||||||
|
values['department'] = department
|
||||||
|
|
||||||
|
where_clause = " AND ".join(conditions)
|
||||||
|
|
||||||
|
count = frappe.db.sql(f"""
|
||||||
|
SELECT COUNT(DISTINCT room_no)
|
||||||
|
FROM `tabInfrastructure Location`
|
||||||
|
WHERE {where_clause}
|
||||||
|
AND room_no IS NOT NULL
|
||||||
|
AND room_no != ''
|
||||||
|
""", values)[0][0]
|
||||||
|
|
||||||
|
return count or 0
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_department_count(building=None, company=None):
|
||||||
|
"""
|
||||||
|
Get count of departments for a building from Infrastructure Location.
|
||||||
|
Used to show "X department(s) available" message.
|
||||||
|
"""
|
||||||
|
if not building:
|
||||||
|
if company:
|
||||||
|
return frappe.db.count('Department', {'company': company})
|
||||||
|
return frappe.db.count('Department')
|
||||||
|
|
||||||
|
values = {'building': building}
|
||||||
|
|
||||||
|
query = """
|
||||||
|
SELECT COUNT(DISTINCT department)
|
||||||
|
FROM `tabInfrastructure Location`
|
||||||
|
WHERE building = %(building)s
|
||||||
|
AND department IS NOT NULL
|
||||||
|
AND department != ''
|
||||||
|
"""
|
||||||
|
|
||||||
|
count = frappe.db.sql(query, values)[0][0]
|
||||||
|
|
||||||
|
return count or 0
|
||||||
139
asset_lite/api/translation_api.py
Normal file
139
asset_lite/api/translation_api.py
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest = True)
|
||||||
|
def get_translations(language='en'):
|
||||||
|
"""
|
||||||
|
Get all translations for a specific language from Frappe's Translation doctype
|
||||||
|
This returns a dictionary of source text -> translated text
|
||||||
|
|
||||||
|
Usage: /api/method/asset_lite.api.translation_api.get_translations?language=ar
|
||||||
|
|
||||||
|
Args:
|
||||||
|
language: Language code (e.g., 'en', 'ar')
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary mapping source text to translated text
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Validate language parameter
|
||||||
|
if not language:
|
||||||
|
language = 'en'
|
||||||
|
|
||||||
|
# Get all translations for the specified language
|
||||||
|
translations = frappe.get_all(
|
||||||
|
'Translation',
|
||||||
|
filters={
|
||||||
|
'language': language
|
||||||
|
},
|
||||||
|
fields=['source_text', 'translated_text'],
|
||||||
|
limit_page_length=0 # 0 means no limit (get all)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Convert to dictionary format: {source_text: translated_text}
|
||||||
|
translation_dict = {}
|
||||||
|
for trans in translations:
|
||||||
|
source = trans.get('source_text')
|
||||||
|
translated = trans.get('translated_text')
|
||||||
|
if source and translated:
|
||||||
|
translation_dict[source] = translated
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"language": language,
|
||||||
|
"count": len(translation_dict),
|
||||||
|
"translations": translation_dict
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(f"Error in get_translations: {str(e)}", "Translation API Error")
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": str(e),
|
||||||
|
"translations": {}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest=False)
|
||||||
|
def get_available_languages():
|
||||||
|
"""
|
||||||
|
Get list of all available languages that have translations
|
||||||
|
|
||||||
|
Usage: /api/method/asset_lite.api.translation_api.get_available_languages
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of language codes (e.g., ['en', 'ar'])
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Get distinct languages from Translation doctype
|
||||||
|
languages = frappe.db.sql("""
|
||||||
|
SELECT DISTINCT language
|
||||||
|
FROM `tabTranslation`
|
||||||
|
WHERE language IS NOT NULL AND language != ''
|
||||||
|
ORDER BY language
|
||||||
|
""", as_dict=True)
|
||||||
|
|
||||||
|
language_list = [lang['language'] for lang in languages if lang.get('language')]
|
||||||
|
|
||||||
|
# Always include English as default
|
||||||
|
if 'en' not in language_list:
|
||||||
|
language_list.insert(0, 'en')
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"languages": language_list
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(f"Error in get_available_languages: {str(e)}", "Translation API Error")
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": str(e),
|
||||||
|
"languages": ['en']
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest=False)
|
||||||
|
def get_translation(source_text, language='ar'):
|
||||||
|
"""
|
||||||
|
Get a single translation for a specific text
|
||||||
|
|
||||||
|
Usage: /api/method/asset_lite.api.translation_api.get_translation?source_text=Comprehensive&language=ar
|
||||||
|
|
||||||
|
Args:
|
||||||
|
source_text: The text to translate
|
||||||
|
language: Target language code (default: 'ar')
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Translated text or original if not found
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if not source_text:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": "source_text is required"
|
||||||
|
}
|
||||||
|
|
||||||
|
translation = frappe.db.get_value(
|
||||||
|
'Translation',
|
||||||
|
filters={
|
||||||
|
'language': language,
|
||||||
|
'source_text': source_text
|
||||||
|
},
|
||||||
|
fieldname='translated_text'
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"source_text": source_text,
|
||||||
|
"translated_text": translation or source_text, # Return original if not found
|
||||||
|
"found": bool(translation)
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(f"Error in get_translation: {str(e)}", "Translation API Error")
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": str(e),
|
||||||
|
"translated_text": source_text
|
||||||
|
}
|
||||||
152
asset_lite/api/user_roles.py
Normal file
152
asset_lite/api/user_roles.py
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_user_roles():
|
||||||
|
"""Get roles for the current logged-in user - no permission check needed"""
|
||||||
|
user = frappe.session.user
|
||||||
|
|
||||||
|
if not user or user == "Guest":
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Get roles using ignore_permissions
|
||||||
|
roles = frappe.get_roles(user)
|
||||||
|
|
||||||
|
return roles
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_user_info_with_roles():
|
||||||
|
"""Get current user info along with their roles"""
|
||||||
|
user = frappe.session.user
|
||||||
|
|
||||||
|
if not user or user == "Guest":
|
||||||
|
return {"user": None, "roles": []}
|
||||||
|
|
||||||
|
roles = frappe.get_roles(user)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"user": user,
|
||||||
|
"roles": roles,
|
||||||
|
"full_name": frappe.db.get_value("User", user, "full_name")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest=False)
|
||||||
|
def check_has_role(roles):
|
||||||
|
"""Check if current user has any of the specified roles
|
||||||
|
|
||||||
|
Args:
|
||||||
|
roles: comma-separated string or list of role names
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict with has_role (bool) and matching_roles (list)
|
||||||
|
"""
|
||||||
|
user = frappe.session.user
|
||||||
|
|
||||||
|
if not user or user == "Guest":
|
||||||
|
return {"has_role": False, "matching_roles": [], "user_roles": []}
|
||||||
|
|
||||||
|
# Handle both string and list input
|
||||||
|
if isinstance(roles, str):
|
||||||
|
check_roles = [r.strip() for r in roles.split(",")]
|
||||||
|
else:
|
||||||
|
check_roles = roles
|
||||||
|
|
||||||
|
user_roles = frappe.get_roles(user)
|
||||||
|
matching_roles = [r for r in check_roles if r in user_roles]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"has_role": len(matching_roles) > 0,
|
||||||
|
"matching_roles": matching_roles,
|
||||||
|
"user_roles": user_roles
|
||||||
|
}
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_users_with_role(role):
|
||||||
|
"""
|
||||||
|
Get all enabled users who have a specific role
|
||||||
|
|
||||||
|
Args:
|
||||||
|
role: Role name (e.g., 'Technician')
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of users with name and full_name
|
||||||
|
"""
|
||||||
|
if not role:
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Get all users who have this role from Has Role child table
|
||||||
|
users_with_role = frappe.get_all(
|
||||||
|
"Has Role",
|
||||||
|
filters={
|
||||||
|
"role": role,
|
||||||
|
"parenttype": "User"
|
||||||
|
},
|
||||||
|
fields=["parent"],
|
||||||
|
distinct=True
|
||||||
|
)
|
||||||
|
|
||||||
|
if not users_with_role:
|
||||||
|
return []
|
||||||
|
|
||||||
|
user_names = [u.parent for u in users_with_role]
|
||||||
|
|
||||||
|
# Get user details for enabled users only
|
||||||
|
user_details = frappe.get_all(
|
||||||
|
"User",
|
||||||
|
filters={
|
||||||
|
"name": ["in", user_names],
|
||||||
|
"enabled": 1
|
||||||
|
},
|
||||||
|
fields=["name", "full_name"],
|
||||||
|
order_by="full_name asc"
|
||||||
|
)
|
||||||
|
|
||||||
|
return user_details
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def has_create_permission(doctype):
|
||||||
|
"""
|
||||||
|
Check if current user has create permission for a doctype
|
||||||
|
Uses ignore_permissions to query Custom DocPerm
|
||||||
|
"""
|
||||||
|
user = frappe.session.user
|
||||||
|
|
||||||
|
if not user or user == "Guest":
|
||||||
|
return {"has_permission": False, "reason": "Not logged in"}
|
||||||
|
|
||||||
|
# Get user's roles
|
||||||
|
user_roles = frappe.get_roles(user)
|
||||||
|
|
||||||
|
# System Manager and Administrator always have permission
|
||||||
|
if "System Manager" in user_roles or "Administrator" in user_roles:
|
||||||
|
return {
|
||||||
|
"has_permission": True,
|
||||||
|
"reason": "System Manager/Administrator",
|
||||||
|
"role": "System Manager"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check Custom DocPerm with ignore_permissions=True
|
||||||
|
custom_perms = frappe.get_all(
|
||||||
|
"Custom DocPerm",
|
||||||
|
filters={
|
||||||
|
"parent": doctype,
|
||||||
|
"role": ["in", user_roles],
|
||||||
|
"create": 1
|
||||||
|
},
|
||||||
|
fields=["name", "role"],
|
||||||
|
ignore_permissions=True, # ← This is the key!
|
||||||
|
limit=1
|
||||||
|
)
|
||||||
|
|
||||||
|
if custom_perms and len(custom_perms) > 0:
|
||||||
|
return {
|
||||||
|
"has_permission": True,
|
||||||
|
"reason": "Custom DocPerm",
|
||||||
|
"role": custom_perms[0].get("role")
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"has_permission": False,
|
||||||
|
"reason": "No create permission found in Custom DocPerm"
|
||||||
|
}
|
||||||
310
asset_lite/api/userperm_api.py
Normal file
310
asset_lite/api/userperm_api.py
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# CONFIGURATION: Define field mappings for each doctype
|
||||||
|
# Add new doctypes here as needed - this is the ONLY place you need to update
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
DOCTYPE_PERMISSION_MAPPINGS = {
|
||||||
|
"Asset": {
|
||||||
|
"Company": "company",
|
||||||
|
"Location": "location",
|
||||||
|
"Department": "department",
|
||||||
|
"Manufacturer": "custom_manufacturer",
|
||||||
|
"Supplier": "supplier",
|
||||||
|
"Modality": "custom_modality",
|
||||||
|
"Cost Center": "cost_center",
|
||||||
|
"Asset Type":"custom_asset_type",
|
||||||
|
"Asset Category": "asset_category"
|
||||||
|
},
|
||||||
|
"Work_Order": {
|
||||||
|
"Company": "company",
|
||||||
|
"Location": "location",
|
||||||
|
"Department": "department",
|
||||||
|
"Issue Type": "work_order_type"
|
||||||
|
|
||||||
|
},
|
||||||
|
"Asset Maintenance": {
|
||||||
|
"Company": "company",
|
||||||
|
"Asset": "asset_name",
|
||||||
|
"Supplier": "supplier"
|
||||||
|
},
|
||||||
|
"Asset Maintenance Log": {
|
||||||
|
"Company": "company",
|
||||||
|
"Asset": "asset_name"
|
||||||
|
}
|
||||||
|
# Add more doctypes as needed - just add them here!
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# HELPER FUNCTION
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
def is_system_user(user):
|
||||||
|
"""Check if user is Administrator or has System Manager role."""
|
||||||
|
if user == "Administrator":
|
||||||
|
return True
|
||||||
|
|
||||||
|
roles = frappe.get_roles(user)
|
||||||
|
return "System Manager" in roles
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# CORE API FUNCTIONS - These 4 functions handle everything
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest = True)
|
||||||
|
def get_user_permissions(user=None):
|
||||||
|
"""
|
||||||
|
Get all user permissions for the logged-in user.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: User permissions grouped by 'allow' doctype
|
||||||
|
"""
|
||||||
|
if not user:
|
||||||
|
user = frappe.session.user
|
||||||
|
|
||||||
|
if is_system_user(user):
|
||||||
|
return {
|
||||||
|
"is_admin": True,
|
||||||
|
"permissions": {},
|
||||||
|
"user": user,
|
||||||
|
"total_permissions": 0
|
||||||
|
}
|
||||||
|
|
||||||
|
permissions = frappe.get_all(
|
||||||
|
"User Permission",
|
||||||
|
filters={"user": user},
|
||||||
|
fields=["name", "allow", "for_value", "is_default", "apply_to_all_doctypes", "applicable_for"],
|
||||||
|
order_by="allow asc"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Group by 'allow' doctype
|
||||||
|
grouped = {}
|
||||||
|
for perm in permissions:
|
||||||
|
allow_doctype = perm.get("allow")
|
||||||
|
if allow_doctype not in grouped:
|
||||||
|
grouped[allow_doctype] = []
|
||||||
|
grouped[allow_doctype].append({
|
||||||
|
"for_value": perm.get("for_value"),
|
||||||
|
"is_default": perm.get("is_default"),
|
||||||
|
"apply_to_all_doctypes": perm.get("apply_to_all_doctypes"),
|
||||||
|
"applicable_for": perm.get("applicable_for")
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
"is_admin": False,
|
||||||
|
"permissions": grouped,
|
||||||
|
"user": user,
|
||||||
|
"total_permissions": len(permissions),
|
||||||
|
"permission_types": list(grouped.keys())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest = True)
|
||||||
|
def get_permission_filters(target_doctype, user=None):
|
||||||
|
"""
|
||||||
|
Get permission filters for ANY doctype.
|
||||||
|
This is the MAIN function - use this for all doctypes.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
target_doctype: The doctype (e.g., "Asset", "Work Order", "Project")
|
||||||
|
user: Optional user email
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Filters to apply for queries
|
||||||
|
"""
|
||||||
|
if not user:
|
||||||
|
user = frappe.session.user
|
||||||
|
|
||||||
|
# System users have full access
|
||||||
|
if is_system_user(user):
|
||||||
|
return {
|
||||||
|
"is_admin": True,
|
||||||
|
"filters": {},
|
||||||
|
"restrictions": {},
|
||||||
|
"target_doctype": target_doctype,
|
||||||
|
"user": user
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get field mapping for this doctype
|
||||||
|
field_mapping = DOCTYPE_PERMISSION_MAPPINGS.get(target_doctype, {})
|
||||||
|
|
||||||
|
if not field_mapping:
|
||||||
|
return {
|
||||||
|
"is_admin": False,
|
||||||
|
"filters": {},
|
||||||
|
"restrictions": {},
|
||||||
|
"target_doctype": target_doctype,
|
||||||
|
"user": user,
|
||||||
|
"warning": f"No permission mapping defined for {target_doctype}"
|
||||||
|
}
|
||||||
|
|
||||||
|
filters = {}
|
||||||
|
restrictions = {}
|
||||||
|
|
||||||
|
for allow_doctype, target_field in field_mapping.items():
|
||||||
|
permissions = frappe.get_all(
|
||||||
|
"User Permission",
|
||||||
|
filters={
|
||||||
|
"user": user,
|
||||||
|
"allow": allow_doctype
|
||||||
|
},
|
||||||
|
fields=["for_value", "applicable_for", "apply_to_all_doctypes"]
|
||||||
|
)
|
||||||
|
|
||||||
|
if permissions:
|
||||||
|
# Filter permissions that apply to this doctype
|
||||||
|
applicable = [
|
||||||
|
p for p in permissions
|
||||||
|
if p.get("apply_to_all_doctypes") == 1
|
||||||
|
or not p.get("applicable_for")
|
||||||
|
or p.get("applicable_for") == target_doctype
|
||||||
|
]
|
||||||
|
|
||||||
|
if applicable:
|
||||||
|
allowed_values = list(set([p.get("for_value") for p in applicable]))
|
||||||
|
filters[target_field] = ["in", allowed_values]
|
||||||
|
restrictions[allow_doctype] = {
|
||||||
|
"field": target_field,
|
||||||
|
"values": allowed_values,
|
||||||
|
"count": len(allowed_values)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"is_admin": False,
|
||||||
|
"filters": filters,
|
||||||
|
"restrictions": restrictions,
|
||||||
|
"target_doctype": target_doctype,
|
||||||
|
"user": user,
|
||||||
|
"total_restrictions": len(restrictions)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest = True)
|
||||||
|
def get_allowed_values(allow_doctype, user=None):
|
||||||
|
"""
|
||||||
|
Get allowed values for a specific permission type.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
allow_doctype: e.g., "Company", "Location", "Department"
|
||||||
|
user: Optional user email
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: List of allowed values
|
||||||
|
"""
|
||||||
|
if not user:
|
||||||
|
user = frappe.session.user
|
||||||
|
|
||||||
|
if is_system_user(user):
|
||||||
|
return {
|
||||||
|
"is_admin": True,
|
||||||
|
"allowed_values": [],
|
||||||
|
"has_restriction": False
|
||||||
|
}
|
||||||
|
|
||||||
|
permissions = frappe.get_all(
|
||||||
|
"User Permission",
|
||||||
|
filters={"user": user, "allow": allow_doctype},
|
||||||
|
fields=["for_value", "is_default"]
|
||||||
|
)
|
||||||
|
|
||||||
|
allowed_values = list(set([p.get("for_value") for p in permissions]))
|
||||||
|
default_value = next((p.get("for_value") for p in permissions if p.get("is_default")), None)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"is_admin": False,
|
||||||
|
"allowed_values": sorted(allowed_values),
|
||||||
|
"default_value": default_value,
|
||||||
|
"has_restriction": len(allowed_values) > 0,
|
||||||
|
"allow_doctype": allow_doctype
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest = True)
|
||||||
|
def check_document_access(doctype, docname, user=None):
|
||||||
|
"""
|
||||||
|
Check if user has access to a specific document.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
doctype: e.g., "Asset", "Work Order"
|
||||||
|
docname: The document name/ID
|
||||||
|
user: Optional user email
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Access status
|
||||||
|
"""
|
||||||
|
if not user:
|
||||||
|
user = frappe.session.user
|
||||||
|
|
||||||
|
if is_system_user(user):
|
||||||
|
return {"has_access": True, "is_admin": True}
|
||||||
|
|
||||||
|
try:
|
||||||
|
doc = frappe.get_doc(doctype, docname)
|
||||||
|
except frappe.DoesNotExistError:
|
||||||
|
return {"has_access": False, "error": f"{doctype} '{docname}' not found"}
|
||||||
|
except frappe.PermissionError:
|
||||||
|
return {"has_access": False, "error": "Permission denied"}
|
||||||
|
|
||||||
|
# Get permission filters
|
||||||
|
perm_result = get_permission_filters(doctype, user)
|
||||||
|
|
||||||
|
if perm_result.get("is_admin"):
|
||||||
|
return {"has_access": True, "is_admin": True}
|
||||||
|
|
||||||
|
restrictions = perm_result.get("restrictions", {})
|
||||||
|
|
||||||
|
if not restrictions:
|
||||||
|
return {"has_access": True, "no_restrictions": True}
|
||||||
|
|
||||||
|
# Check each restriction
|
||||||
|
for allow_doctype, info in restrictions.items():
|
||||||
|
field = info.get("field")
|
||||||
|
allowed_values = info.get("values", [])
|
||||||
|
doc_value = getattr(doc, field, None)
|
||||||
|
|
||||||
|
if doc_value and doc_value not in allowed_values:
|
||||||
|
return {
|
||||||
|
"has_access": False,
|
||||||
|
"denied_by": allow_doctype,
|
||||||
|
"field": field,
|
||||||
|
"document_value": doc_value,
|
||||||
|
"allowed_values": allowed_values
|
||||||
|
}
|
||||||
|
|
||||||
|
return {"has_access": True}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest = True)
|
||||||
|
def get_configured_doctypes():
|
||||||
|
"""Get list of doctypes that have permission mappings configured."""
|
||||||
|
return {
|
||||||
|
"doctypes": list(DOCTYPE_PERMISSION_MAPPINGS.keys()),
|
||||||
|
"mappings": {
|
||||||
|
dt: list(mapping.keys())
|
||||||
|
for dt, mapping in DOCTYPE_PERMISSION_MAPPINGS.items()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest = True)
|
||||||
|
def get_user_defaults(user=None):
|
||||||
|
"""Get default values from user permissions (where is_default=1)."""
|
||||||
|
if not user:
|
||||||
|
user = frappe.session.user
|
||||||
|
|
||||||
|
if is_system_user(user):
|
||||||
|
return {"is_admin": True, "defaults": {}}
|
||||||
|
|
||||||
|
permissions = frappe.get_all(
|
||||||
|
"User Permission",
|
||||||
|
filters={"user": user, "is_default": 1},
|
||||||
|
fields=["allow", "for_value"]
|
||||||
|
)
|
||||||
|
|
||||||
|
defaults = {p.get("allow"): p.get("for_value") for p in permissions}
|
||||||
|
|
||||||
|
return {"is_admin": False, "defaults": defaults}
|
||||||
940
asset_lite/api/work_order_api.py
Normal file
940
asset_lite/api/work_order_api.py
Normal file
@ -0,0 +1,940 @@
|
|||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
|
||||||
|
# Default fields for Work_Order doctype
|
||||||
|
WORK_ORDER_FIELDS = [
|
||||||
|
'name',
|
||||||
|
'owner',
|
||||||
|
'creation',
|
||||||
|
'modified',
|
||||||
|
'modified_by',
|
||||||
|
'docstatus',
|
||||||
|
'idx',
|
||||||
|
'workflow_state',
|
||||||
|
'company',
|
||||||
|
'naming_series',
|
||||||
|
'work_order_type',
|
||||||
|
'asset_type',
|
||||||
|
'manufacturer',
|
||||||
|
'serial_number',
|
||||||
|
'custom_priority_',
|
||||||
|
'asset',
|
||||||
|
'custom_maintenance_manager',
|
||||||
|
'department',
|
||||||
|
'repair_status',
|
||||||
|
'asset_name',
|
||||||
|
'supplier',
|
||||||
|
'custom_pending_reason',
|
||||||
|
'make',
|
||||||
|
'model',
|
||||||
|
'custom_site_contractor',
|
||||||
|
'custom_subcontractor',
|
||||||
|
'custom_service_agreement',
|
||||||
|
'custom_service_coverage',
|
||||||
|
'custom_start_date',
|
||||||
|
'custom_end_date',
|
||||||
|
'custom_total_amount',
|
||||||
|
'warranty',
|
||||||
|
'service_contract',
|
||||||
|
'covering_spare_parts',
|
||||||
|
'spare_parts_labour',
|
||||||
|
'covering_labour',
|
||||||
|
'ppm_only',
|
||||||
|
'failure_date',
|
||||||
|
'total_hours_spent',
|
||||||
|
'job_completed',
|
||||||
|
'custom_difference',
|
||||||
|
'custom_vendors_hrs',
|
||||||
|
'custom_deadline_date',
|
||||||
|
'custom_diffrence',
|
||||||
|
'feedback_rating',
|
||||||
|
'first_responded_on',
|
||||||
|
'assigned_manager',
|
||||||
|
'penalty',
|
||||||
|
'custom_assigned_supervisor',
|
||||||
|
'stock_consumption',
|
||||||
|
'need_procurement',
|
||||||
|
'repair_cost',
|
||||||
|
'total_repair_cost',
|
||||||
|
'capitalize_repair_cost',
|
||||||
|
'increase_in_asset_life',
|
||||||
|
'description',
|
||||||
|
'actions_performed',
|
||||||
|
'end_user',
|
||||||
|
'bio_med_dept'
|
||||||
|
]
|
||||||
|
|
||||||
|
# Child table: Asset Repair Consumed Item (stock_items)
|
||||||
|
STOCK_ITEMS_FIELDS = [
|
||||||
|
'name',
|
||||||
|
'owner',
|
||||||
|
'creation',
|
||||||
|
'modified',
|
||||||
|
'modified_by',
|
||||||
|
'docstatus',
|
||||||
|
'idx',
|
||||||
|
'parent',
|
||||||
|
'parentfield',
|
||||||
|
'parenttype',
|
||||||
|
'item_code',
|
||||||
|
'warehouse',
|
||||||
|
'valuation_rate',
|
||||||
|
'total_value',
|
||||||
|
'custom_available_stock'
|
||||||
|
]
|
||||||
|
|
||||||
|
# Child table: Invoice Table
|
||||||
|
INVOICE_TABLE_FIELDS = [
|
||||||
|
'name',
|
||||||
|
'owner',
|
||||||
|
'creation',
|
||||||
|
'modified',
|
||||||
|
'modified_by',
|
||||||
|
'docstatus',
|
||||||
|
'idx',
|
||||||
|
'parent',
|
||||||
|
'parentfield',
|
||||||
|
'parenttype',
|
||||||
|
'invoice_number',
|
||||||
|
'invoice_date',
|
||||||
|
'invoice_amount',
|
||||||
|
'vendor',
|
||||||
|
'description'
|
||||||
|
]
|
||||||
|
|
||||||
|
# Child table: CMQP Table
|
||||||
|
TABLE_CMQP_FIELDS = [
|
||||||
|
'name',
|
||||||
|
'owner',
|
||||||
|
'creation',
|
||||||
|
'modified',
|
||||||
|
'modified_by',
|
||||||
|
'docstatus',
|
||||||
|
'idx',
|
||||||
|
'parent',
|
||||||
|
'parentfield',
|
||||||
|
'parenttype',
|
||||||
|
'parameter',
|
||||||
|
'value',
|
||||||
|
'status',
|
||||||
|
'remarks'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def get_child_table_data(parent_name, parentfield, child_doctype, fields=None):
|
||||||
|
"""
|
||||||
|
Get child table data for a work order
|
||||||
|
|
||||||
|
Args:
|
||||||
|
parent_name: Name of the parent work order
|
||||||
|
parentfield: Field name of the child table in parent
|
||||||
|
child_doctype: Doctype of the child table
|
||||||
|
fields: List of fields to return
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of child table records
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return frappe.get_all(
|
||||||
|
child_doctype,
|
||||||
|
filters={
|
||||||
|
'parent': parent_name,
|
||||||
|
'parentfield': parentfield,
|
||||||
|
'parenttype': 'Work_Order'
|
||||||
|
},
|
||||||
|
fields=fields or ['*'],
|
||||||
|
order_by='idx asc'
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest=True)
|
||||||
|
def get_work_orders(filters=None, fields=None, limit=20, offset=0, order_by=None, include_child_tables=False):
|
||||||
|
"""
|
||||||
|
Get list of work orders with filters and pagination
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filters: JSON string of filters (e.g., '{"company": "ABC Corp"}')
|
||||||
|
fields: JSON string of fields to return (e.g., '["work_order_type", "asset_name"]')
|
||||||
|
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_child_tables: Whether to include child table data (default: False)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
{
|
||||||
|
"work_orders": [...],
|
||||||
|
"total_count": int,
|
||||||
|
"limit": int,
|
||||||
|
"offset": int,
|
||||||
|
"has_more": bool
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
|
||||||
|
# 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:
|
||||||
|
fields = WORK_ORDER_FIELDS.copy()
|
||||||
|
|
||||||
|
# Parse include_child_tables
|
||||||
|
if isinstance(include_child_tables, str):
|
||||||
|
include_child_tables = include_child_tables.lower() in ('true', '1', 'yes')
|
||||||
|
|
||||||
|
# Get total count
|
||||||
|
total_count = frappe.db.count('Work_Order', filters=filters or {})
|
||||||
|
|
||||||
|
# Get work orders
|
||||||
|
work_orders = frappe.get_all(
|
||||||
|
'Work_Order',
|
||||||
|
filters=filters or {},
|
||||||
|
fields=fields,
|
||||||
|
limit_page_length=int(limit),
|
||||||
|
limit_start=int(offset),
|
||||||
|
order_by=order_by or 'creation desc'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Include child tables if requested
|
||||||
|
if include_child_tables:
|
||||||
|
for work_order in work_orders:
|
||||||
|
work_order['stock_items'] = get_child_table_data(
|
||||||
|
work_order['name'],
|
||||||
|
'stock_items',
|
||||||
|
'Asset Repair Consumed Item',
|
||||||
|
STOCK_ITEMS_FIELDS
|
||||||
|
)
|
||||||
|
work_order['invoice_table'] = get_child_table_data(
|
||||||
|
work_order['name'],
|
||||||
|
'invoice_table',
|
||||||
|
'PI Table', # Adjust doctype name as needed
|
||||||
|
INVOICE_TABLE_FIELDS
|
||||||
|
)
|
||||||
|
work_order['table_cmqp'] = get_child_table_data(
|
||||||
|
work_order['name'],
|
||||||
|
'table_cmqp',
|
||||||
|
'Spare Parts', # Adjust doctype name as needed
|
||||||
|
TABLE_CMQP_FIELDS
|
||||||
|
)
|
||||||
|
|
||||||
|
# Calculate has_more
|
||||||
|
has_more = (int(offset) + int(limit)) < total_count
|
||||||
|
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'work_orders': work_orders,
|
||||||
|
'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 Work Orders API Error')
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'error': str(e),
|
||||||
|
'work_orders': [],
|
||||||
|
'total_count': 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest=True)
|
||||||
|
def get_work_order_details(work_order_name):
|
||||||
|
"""
|
||||||
|
Get detailed information about a specific work order
|
||||||
|
|
||||||
|
Args:
|
||||||
|
work_order_name: Name/ID of the work order
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Work Order document with all fields including child tables
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if not work_order_name:
|
||||||
|
frappe.throw(_('Work Order name is required'))
|
||||||
|
|
||||||
|
# Check if user has permission to read this work order
|
||||||
|
if not frappe.has_permission('Work_Order', 'read', work_order_name):
|
||||||
|
frappe.throw(_('Not permitted to access this work order'))
|
||||||
|
|
||||||
|
# Get work order details
|
||||||
|
work_order = frappe.get_doc('Work_Order', work_order_name)
|
||||||
|
|
||||||
|
# Convert to dict and include child tables
|
||||||
|
work_order_dict = work_order.as_dict()
|
||||||
|
|
||||||
|
# Ensure child tables are included with all fields
|
||||||
|
work_order_dict['stock_items'] = [item.as_dict() for item in work_order.get('stock_items', [])]
|
||||||
|
work_order_dict['invoice_table'] = [item.as_dict() for item in work_order.get('invoice_table', [])]
|
||||||
|
work_order_dict['table_cmqp'] = [item.as_dict() for item in work_order.get('table_cmqp', [])]
|
||||||
|
|
||||||
|
frappe.response['message'] = work_order_dict
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(frappe.get_traceback(), 'Get Work Order Details API Error')
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'error': str(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest=True)
|
||||||
|
def create_work_order(work_order_data):
|
||||||
|
"""
|
||||||
|
Create a new work order
|
||||||
|
|
||||||
|
Args:
|
||||||
|
work_order_data: JSON string containing work order fields including child tables
|
||||||
|
Example:
|
||||||
|
{
|
||||||
|
"company": "ABC Corp",
|
||||||
|
"work_order_type": "Repair (CM)",
|
||||||
|
"asset": "ASSET-001",
|
||||||
|
"stock_items": [
|
||||||
|
{"item_code": "ITEM-001", "warehouse": "Main Warehouse", "valuation_rate": 100}
|
||||||
|
],
|
||||||
|
"invoice_table": [...],
|
||||||
|
"table_cmqp": [...]
|
||||||
|
}
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Created work order document
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
|
||||||
|
# Parse work order data
|
||||||
|
if isinstance(work_order_data, str):
|
||||||
|
work_order_data = json.loads(work_order_data)
|
||||||
|
|
||||||
|
# Check if user has permission to create work order
|
||||||
|
if not frappe.has_permission('Work_Order', 'create'):
|
||||||
|
frappe.throw(_('Not permitted to create work order'))
|
||||||
|
|
||||||
|
# Create new work order
|
||||||
|
work_order = frappe.get_doc({
|
||||||
|
'doctype': 'Work_Order',
|
||||||
|
**work_order_data
|
||||||
|
})
|
||||||
|
|
||||||
|
work_order.insert()
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
# Return created work order with child tables
|
||||||
|
work_order_dict = work_order.as_dict()
|
||||||
|
work_order_dict['stock_items'] = [item.as_dict() for item in work_order.get('stock_items', [])]
|
||||||
|
work_order_dict['invoice_table'] = [item.as_dict() for item in work_order.get('invoice_table', [])]
|
||||||
|
work_order_dict['table_cmqp'] = [item.as_dict() for item in work_order.get('table_cmqp', [])]
|
||||||
|
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': True,
|
||||||
|
'work_order': work_order_dict,
|
||||||
|
'message': _('Work Order created successfully')
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.db.rollback()
|
||||||
|
frappe.log_error(frappe.get_traceback(), 'Create Work Order API Error')
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': False,
|
||||||
|
'error': str(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest=True)
|
||||||
|
def update_work_order(work_order_name, work_order_data):
|
||||||
|
"""
|
||||||
|
Update an existing work order including child tables
|
||||||
|
|
||||||
|
Args:
|
||||||
|
work_order_name: Name/ID of the work order
|
||||||
|
work_order_data: JSON string containing fields to update
|
||||||
|
Example:
|
||||||
|
{
|
||||||
|
"repair_status": "In Progress",
|
||||||
|
"stock_items": [
|
||||||
|
{"item_code": "ITEM-001", "warehouse": "Main Warehouse", "valuation_rate": 100}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Updated work order document
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
|
||||||
|
if not work_order_name:
|
||||||
|
frappe.throw(_('Work Order name is required'))
|
||||||
|
|
||||||
|
# Parse work order data
|
||||||
|
if isinstance(work_order_data, str):
|
||||||
|
work_order_data = json.loads(work_order_data)
|
||||||
|
|
||||||
|
# Check if user has permission to update this work order
|
||||||
|
if not frappe.has_permission('Work_Order', 'write', work_order_name):
|
||||||
|
frappe.throw(_('Not permitted to update this work order'))
|
||||||
|
|
||||||
|
# Get work order
|
||||||
|
work_order = frappe.get_doc('Work_Order', work_order_name)
|
||||||
|
|
||||||
|
# Handle child tables separately
|
||||||
|
child_tables = ['stock_items', 'invoice_table', 'table_cmqp']
|
||||||
|
|
||||||
|
for key, value in work_order_data.items():
|
||||||
|
if key in child_tables:
|
||||||
|
# Clear existing child table entries and add new ones
|
||||||
|
work_order.set(key, [])
|
||||||
|
for item in value:
|
||||||
|
work_order.append(key, item)
|
||||||
|
elif hasattr(work_order, key):
|
||||||
|
setattr(work_order, key, value)
|
||||||
|
|
||||||
|
work_order.save()
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
# Return updated work order with child tables
|
||||||
|
work_order_dict = work_order.as_dict()
|
||||||
|
work_order_dict['stock_items'] = [item.as_dict() for item in work_order.get('stock_items', [])]
|
||||||
|
work_order_dict['invoice_table'] = [item.as_dict() for item in work_order.get('invoice_table', [])]
|
||||||
|
work_order_dict['table_cmqp'] = [item.as_dict() for item in work_order.get('table_cmqp', [])]
|
||||||
|
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': True,
|
||||||
|
'work_order': work_order_dict,
|
||||||
|
'message': _('Work Order updated successfully')
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.db.rollback()
|
||||||
|
frappe.log_error(frappe.get_traceback(), 'Update Work Order API Error')
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': False,
|
||||||
|
'error': str(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest=True)
|
||||||
|
def delete_work_order(work_order_name):
|
||||||
|
"""
|
||||||
|
Delete a work order
|
||||||
|
|
||||||
|
Args:
|
||||||
|
work_order_name: Name/ID of the work order
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Success message
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if not work_order_name:
|
||||||
|
frappe.throw(_('Work Order name is required'))
|
||||||
|
|
||||||
|
# Check if user has permission to delete this work order
|
||||||
|
if not frappe.has_permission('Work_Order', 'delete', work_order_name):
|
||||||
|
frappe.throw(_('Not permitted to delete this work order'))
|
||||||
|
|
||||||
|
# Delete work order (child tables will be deleted automatically)
|
||||||
|
frappe.delete_doc('Work_Order', work_order_name)
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': True,
|
||||||
|
'message': _('Work Order deleted successfully')
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.db.rollback()
|
||||||
|
frappe.log_error(frappe.get_traceback(), 'Delete Work Order API Error')
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': False,
|
||||||
|
'error': str(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest=True)
|
||||||
|
def update_work_order_status(work_order_name, repair_status=None, workflow_state=None):
|
||||||
|
"""
|
||||||
|
Update work order status
|
||||||
|
|
||||||
|
Args:
|
||||||
|
work_order_name: Name/ID of the work order
|
||||||
|
repair_status: New repair status (e.g., 'Open', 'In Progress', 'Completed')
|
||||||
|
workflow_state: New workflow state
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Updated work order document
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if not work_order_name:
|
||||||
|
frappe.throw(_('Work Order name is required'))
|
||||||
|
|
||||||
|
# Check if user has permission to update this work order
|
||||||
|
if not frappe.has_permission('Work_Order', 'write', work_order_name):
|
||||||
|
frappe.throw(_('Not permitted to update this work order'))
|
||||||
|
|
||||||
|
# Get work order
|
||||||
|
work_order = frappe.get_doc('Work_Order', work_order_name)
|
||||||
|
|
||||||
|
# Update status fields
|
||||||
|
if repair_status:
|
||||||
|
work_order.repair_status = repair_status
|
||||||
|
|
||||||
|
if workflow_state:
|
||||||
|
work_order.workflow_state = workflow_state
|
||||||
|
|
||||||
|
work_order.save()
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
# Return updated work order with child tables
|
||||||
|
work_order_dict = work_order.as_dict()
|
||||||
|
work_order_dict['stock_items'] = [item.as_dict() for item in work_order.get('stock_items', [])]
|
||||||
|
work_order_dict['invoice_table'] = [item.as_dict() for item in work_order.get('invoice_table', [])]
|
||||||
|
work_order_dict['table_cmqp'] = [item.as_dict() for item in work_order.get('table_cmqp', [])]
|
||||||
|
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': True,
|
||||||
|
'work_order': work_order_dict,
|
||||||
|
'message': _('Work Order status updated successfully')
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.db.rollback()
|
||||||
|
frappe.log_error(frappe.get_traceback(), 'Update Work Order Status API Error')
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': False,
|
||||||
|
'error': str(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest=True)
|
||||||
|
def add_stock_item(work_order_name, item_data):
|
||||||
|
"""
|
||||||
|
Add a stock item to work order's stock_items child table
|
||||||
|
|
||||||
|
Args:
|
||||||
|
work_order_name: Name/ID of the work order
|
||||||
|
item_data: JSON string containing stock item fields
|
||||||
|
Example:
|
||||||
|
{
|
||||||
|
"item_code": "ITEM-001",
|
||||||
|
"warehouse": "Main Warehouse",
|
||||||
|
"valuation_rate": 100,
|
||||||
|
"total_value": 500,
|
||||||
|
"custom_available_stock": 10
|
||||||
|
}
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Updated work order with stock items
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
|
||||||
|
if not work_order_name:
|
||||||
|
frappe.throw(_('Work Order name is required'))
|
||||||
|
|
||||||
|
# Parse item data
|
||||||
|
if isinstance(item_data, str):
|
||||||
|
item_data = json.loads(item_data)
|
||||||
|
|
||||||
|
# Check if user has permission to update this work order
|
||||||
|
if not frappe.has_permission('Work_Order', 'write', work_order_name):
|
||||||
|
frappe.throw(_('Not permitted to update this work order'))
|
||||||
|
|
||||||
|
# Get work order and add stock item
|
||||||
|
work_order = frappe.get_doc('Work_Order', work_order_name)
|
||||||
|
work_order.append('stock_items', item_data)
|
||||||
|
work_order.save()
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': True,
|
||||||
|
'stock_items': [item.as_dict() for item in work_order.get('stock_items', [])],
|
||||||
|
'message': _('Stock item added successfully')
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.db.rollback()
|
||||||
|
frappe.log_error(frappe.get_traceback(), 'Add Stock Item API Error')
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': False,
|
||||||
|
'error': str(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest=True)
|
||||||
|
def remove_stock_item(work_order_name, item_name):
|
||||||
|
"""
|
||||||
|
Remove a stock item from work order's stock_items child table
|
||||||
|
|
||||||
|
Args:
|
||||||
|
work_order_name: Name/ID of the work order
|
||||||
|
item_name: Name/ID of the stock item to remove
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Updated work order with remaining stock items
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if not work_order_name:
|
||||||
|
frappe.throw(_('Work Order name is required'))
|
||||||
|
|
||||||
|
if not item_name:
|
||||||
|
frappe.throw(_('Stock item name is required'))
|
||||||
|
|
||||||
|
# Check if user has permission to update this work order
|
||||||
|
if not frappe.has_permission('Work_Order', 'write', work_order_name):
|
||||||
|
frappe.throw(_('Not permitted to update this work order'))
|
||||||
|
|
||||||
|
# Get work order
|
||||||
|
work_order = frappe.get_doc('Work_Order', work_order_name)
|
||||||
|
|
||||||
|
# Find and remove the stock item
|
||||||
|
item_to_remove = None
|
||||||
|
for item in work_order.stock_items:
|
||||||
|
if item.name == item_name:
|
||||||
|
item_to_remove = item
|
||||||
|
break
|
||||||
|
|
||||||
|
if item_to_remove:
|
||||||
|
work_order.remove(item_to_remove)
|
||||||
|
work_order.save()
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': True,
|
||||||
|
'stock_items': [item.as_dict() for item in work_order.get('stock_items', [])],
|
||||||
|
'message': _('Stock item removed successfully')
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': False,
|
||||||
|
'error': _('Stock item not found')
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.db.rollback()
|
||||||
|
frappe.log_error(frappe.get_traceback(), 'Remove Stock Item API Error')
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': False,
|
||||||
|
'error': str(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest=True)
|
||||||
|
def add_invoice(work_order_name, invoice_data):
|
||||||
|
"""
|
||||||
|
Add an invoice to work order's invoice_table child table
|
||||||
|
|
||||||
|
Args:
|
||||||
|
work_order_name: Name/ID of the work order
|
||||||
|
invoice_data: JSON string containing invoice fields
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Updated work order with invoices
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
|
||||||
|
if not work_order_name:
|
||||||
|
frappe.throw(_('Work Order name is required'))
|
||||||
|
|
||||||
|
# Parse invoice data
|
||||||
|
if isinstance(invoice_data, str):
|
||||||
|
invoice_data = json.loads(invoice_data)
|
||||||
|
|
||||||
|
# Check if user has permission to update this work order
|
||||||
|
if not frappe.has_permission('Work_Order', 'write', work_order_name):
|
||||||
|
frappe.throw(_('Not permitted to update this work order'))
|
||||||
|
|
||||||
|
# Get work order and add invoice
|
||||||
|
work_order = frappe.get_doc('Work_Order', work_order_name)
|
||||||
|
work_order.append('invoice_table', invoice_data)
|
||||||
|
work_order.save()
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': True,
|
||||||
|
'invoice_table': [item.as_dict() for item in work_order.get('invoice_table', [])],
|
||||||
|
'message': _('Invoice added successfully')
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.db.rollback()
|
||||||
|
frappe.log_error(frappe.get_traceback(), 'Add Invoice API Error')
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': False,
|
||||||
|
'error': str(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest=True)
|
||||||
|
def remove_invoice(work_order_name, invoice_name):
|
||||||
|
"""
|
||||||
|
Remove an invoice from work order's invoice_table child table
|
||||||
|
|
||||||
|
Args:
|
||||||
|
work_order_name: Name/ID of the work order
|
||||||
|
invoice_name: Name/ID of the invoice to remove
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Updated work order with remaining invoices
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if not work_order_name:
|
||||||
|
frappe.throw(_('Work Order name is required'))
|
||||||
|
|
||||||
|
if not invoice_name:
|
||||||
|
frappe.throw(_('Invoice name is required'))
|
||||||
|
|
||||||
|
# Check if user has permission to update this work order
|
||||||
|
if not frappe.has_permission('Work_Order', 'write', work_order_name):
|
||||||
|
frappe.throw(_('Not permitted to update this work order'))
|
||||||
|
|
||||||
|
# Get work order
|
||||||
|
work_order = frappe.get_doc('Work_Order', work_order_name)
|
||||||
|
|
||||||
|
# Find and remove the invoice
|
||||||
|
item_to_remove = None
|
||||||
|
for item in work_order.invoice_table:
|
||||||
|
if item.name == invoice_name:
|
||||||
|
item_to_remove = item
|
||||||
|
break
|
||||||
|
|
||||||
|
if item_to_remove:
|
||||||
|
work_order.remove(item_to_remove)
|
||||||
|
work_order.save()
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': True,
|
||||||
|
'invoice_table': [item.as_dict() for item in work_order.get('invoice_table', [])],
|
||||||
|
'message': _('Invoice removed successfully')
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': False,
|
||||||
|
'error': _('Invoice not found')
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.db.rollback()
|
||||||
|
frappe.log_error(frappe.get_traceback(), 'Remove Invoice API Error')
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': False,
|
||||||
|
'error': str(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest=True)
|
||||||
|
def add_cmqp_item(work_order_name, cmqp_data):
|
||||||
|
"""
|
||||||
|
Add a CMQP item to work order's table_cmqp child table
|
||||||
|
|
||||||
|
Args:
|
||||||
|
work_order_name: Name/ID of the work order
|
||||||
|
cmqp_data: JSON string containing CMQP item fields
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Updated work order with CMQP items
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
|
||||||
|
if not work_order_name:
|
||||||
|
frappe.throw(_('Work Order name is required'))
|
||||||
|
|
||||||
|
# Parse CMQP data
|
||||||
|
if isinstance(cmqp_data, str):
|
||||||
|
cmqp_data = json.loads(cmqp_data)
|
||||||
|
|
||||||
|
# Check if user has permission to update this work order
|
||||||
|
if not frappe.has_permission('Work_Order', 'write', work_order_name):
|
||||||
|
frappe.throw(_('Not permitted to update this work order'))
|
||||||
|
|
||||||
|
# Get work order and add CMQP item
|
||||||
|
work_order = frappe.get_doc('Work_Order', work_order_name)
|
||||||
|
work_order.append('table_cmqp', cmqp_data)
|
||||||
|
work_order.save()
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': True,
|
||||||
|
'table_cmqp': [item.as_dict() for item in work_order.get('table_cmqp', [])],
|
||||||
|
'message': _('CMQP item added successfully')
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.db.rollback()
|
||||||
|
frappe.log_error(frappe.get_traceback(), 'Add CMQP Item API Error')
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': False,
|
||||||
|
'error': str(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest=True)
|
||||||
|
def remove_cmqp_item(work_order_name, cmqp_name):
|
||||||
|
"""
|
||||||
|
Remove a CMQP item from work order's table_cmqp child table
|
||||||
|
|
||||||
|
Args:
|
||||||
|
work_order_name: Name/ID of the work order
|
||||||
|
cmqp_name: Name/ID of the CMQP item to remove
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Updated work order with remaining CMQP items
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if not work_order_name:
|
||||||
|
frappe.throw(_('Work Order name is required'))
|
||||||
|
|
||||||
|
if not cmqp_name:
|
||||||
|
frappe.throw(_('CMQP item name is required'))
|
||||||
|
|
||||||
|
# Check if user has permission to update this work order
|
||||||
|
if not frappe.has_permission('Work_Order', 'write', work_order_name):
|
||||||
|
frappe.throw(_('Not permitted to update this work order'))
|
||||||
|
|
||||||
|
# Get work order
|
||||||
|
work_order = frappe.get_doc('Work_Order', work_order_name)
|
||||||
|
|
||||||
|
# Find and remove the CMQP item
|
||||||
|
item_to_remove = None
|
||||||
|
for item in work_order.table_cmqp:
|
||||||
|
if item.name == cmqp_name:
|
||||||
|
item_to_remove = item
|
||||||
|
break
|
||||||
|
|
||||||
|
if item_to_remove:
|
||||||
|
work_order.remove(item_to_remove)
|
||||||
|
work_order.save()
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': True,
|
||||||
|
'table_cmqp': [item.as_dict() for item in work_order.get('table_cmqp', [])],
|
||||||
|
'message': _('CMQP item removed successfully')
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': False,
|
||||||
|
'error': _('CMQP item not found')
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.db.rollback()
|
||||||
|
frappe.log_error(frappe.get_traceback(), 'Remove CMQP Item API Error')
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': False,
|
||||||
|
'error': str(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest=True)
|
||||||
|
def get_work_order_child_tables(work_order_name, child_table=None):
|
||||||
|
"""
|
||||||
|
Get child table data for a work order
|
||||||
|
|
||||||
|
Args:
|
||||||
|
work_order_name: Name/ID of the work order
|
||||||
|
child_table: Specific child table to return ('stock_items', 'invoice_table', 'table_cmqp')
|
||||||
|
If None, returns all child tables
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Child table data
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if not work_order_name:
|
||||||
|
frappe.throw(_('Work Order name is required'))
|
||||||
|
|
||||||
|
# Check if user has permission to read this work order
|
||||||
|
if not frappe.has_permission('Work_Order', 'read', work_order_name):
|
||||||
|
frappe.throw(_('Not permitted to access this work order'))
|
||||||
|
|
||||||
|
# Get work order
|
||||||
|
work_order = frappe.get_doc('Work_Order', work_order_name)
|
||||||
|
|
||||||
|
result = {}
|
||||||
|
|
||||||
|
if child_table:
|
||||||
|
if child_table == 'stock_items':
|
||||||
|
result['stock_items'] = [item.as_dict() for item in work_order.get('stock_items', [])]
|
||||||
|
elif child_table == 'invoice_table':
|
||||||
|
result['invoice_table'] = [item.as_dict() for item in work_order.get('invoice_table', [])]
|
||||||
|
elif child_table == 'table_cmqp':
|
||||||
|
result['table_cmqp'] = [item.as_dict() for item in work_order.get('table_cmqp', [])]
|
||||||
|
else:
|
||||||
|
frappe.throw(_('Invalid child table name'))
|
||||||
|
else:
|
||||||
|
result = {
|
||||||
|
'stock_items': [item.as_dict() for item in work_order.get('stock_items', [])],
|
||||||
|
'invoice_table': [item.as_dict() for item in work_order.get('invoice_table', [])],
|
||||||
|
'table_cmqp': [item.as_dict() for item in work_order.get('table_cmqp', [])]
|
||||||
|
}
|
||||||
|
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': True,
|
||||||
|
'work_order_name': work_order_name,
|
||||||
|
**result
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(frappe.get_traceback(), 'Get Work Order Child Tables API Error')
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': False,
|
||||||
|
'error': str(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest=True)
|
||||||
|
def bulk_update_stock_items(work_order_name, stock_items):
|
||||||
|
"""
|
||||||
|
Bulk update/replace all stock items in a work order
|
||||||
|
|
||||||
|
Args:
|
||||||
|
work_order_name: Name/ID of the work order
|
||||||
|
stock_items: JSON string containing list of stock items
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Updated work order with new stock items
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
|
||||||
|
if not work_order_name:
|
||||||
|
frappe.throw(_('Work Order name is required'))
|
||||||
|
|
||||||
|
# Parse stock items
|
||||||
|
if isinstance(stock_items, str):
|
||||||
|
stock_items = json.loads(stock_items)
|
||||||
|
|
||||||
|
# Check if user has permission to update this work order
|
||||||
|
if not frappe.has_permission('Work_Order', 'write', work_order_name):
|
||||||
|
frappe.throw(_('Not permitted to update this work order'))
|
||||||
|
|
||||||
|
# Get work order
|
||||||
|
work_order = frappe.get_doc('Work_Order', work_order_name)
|
||||||
|
|
||||||
|
# Clear existing stock items and add new ones
|
||||||
|
work_order.set('stock_items', [])
|
||||||
|
for item in stock_items:
|
||||||
|
work_order.append('stock_items', item)
|
||||||
|
|
||||||
|
work_order.save()
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': True,
|
||||||
|
'stock_items': [item.as_dict() for item in work_order.get('stock_items', [])],
|
||||||
|
'message': _('Stock items updated successfully')
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.db.rollback()
|
||||||
|
frappe.log_error(frappe.get_traceback(), 'Bulk Update Stock Items API Error')
|
||||||
|
frappe.response['message'] = {
|
||||||
|
'success': False,
|
||||||
|
'error': str(e)
|
||||||
|
}
|
||||||
0
asset_lite/asset_lite/__init__.py
Normal file
0
asset_lite/asset_lite/__init__.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
frappe.dashboards.chart_sources["Active Map Widget"] = {
|
||||||
|
method: "asset_lite.map.get_custom_html_data",
|
||||||
|
filters: []
|
||||||
|
};
|
||||||
|
|
||||||
|
// Override the chart rendering after data is loaded
|
||||||
|
frappe.provide('frappe.dashboards');
|
||||||
|
|
||||||
|
$(document).on('app_ready', function() {
|
||||||
|
// Override the render method for custom HTML charts
|
||||||
|
const original_render = frappe.ui.Dashboard.prototype.render_chart;
|
||||||
|
|
||||||
|
frappe.ui.Dashboard.prototype.render_chart = function(chart_data, chart_container) {
|
||||||
|
if (chart_data.custom_html) {
|
||||||
|
// Clear the container and add custom HTML
|
||||||
|
chart_container.empty();
|
||||||
|
const custom_html = `
|
||||||
|
<div style="padding: 20px; background: #f8f9fa; border-radius: 8px; margin: 10px;">
|
||||||
|
<h4 style="margin-bottom: 15px; color: #333;">Custom Dashboard Content</h4>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card" style="border: 1px solid #ddd; padding: 15px;">
|
||||||
|
<h5>Card Title 1</h5>
|
||||||
|
<p>Your custom content here</p>
|
||||||
|
<button class="btn btn-primary btn-sm">Action Button</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card" style="border: 1px solid #ddd; padding: 15px;">
|
||||||
|
<h5>Card Title 2</h5>
|
||||||
|
<p>More custom content</p>
|
||||||
|
<div class="progress" style="height: 20px;">
|
||||||
|
<div class="progress-bar" style="width: 75%">75%</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
chart_container.html(custom_html);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Call original render method for other charts
|
||||||
|
return original_render.call(this, chart_data, chart_container);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"creation": "2025-06-25 18:03:16.704998",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Dashboard Chart Source",
|
||||||
|
"idx": 0,
|
||||||
|
"modified": "2025-06-25 18:03:16.704998",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Asset Lite",
|
||||||
|
"name": "Active Map Widget",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"source_name": "Active Map Widget",
|
||||||
|
"timeseries": 0
|
||||||
|
}
|
||||||
0
asset_lite/asset_lite/doctype/__init__.py
Normal file
0
asset_lite/asset_lite/doctype/__init__.py
Normal file
0
asset_lite/asset_lite/doctype/agent/__init__.py
Normal file
0
asset_lite/asset_lite/doctype/agent/__init__.py
Normal file
8
asset_lite/asset_lite/doctype/agent/agent.js
Normal file
8
asset_lite/asset_lite/doctype/agent/agent.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2024, seyfert and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("Agent", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
45
asset_lite/asset_lite/doctype/agent/agent.json
Normal file
45
asset_lite/asset_lite/doctype/agent/agent.json
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_import": 1,
|
||||||
|
"allow_rename": 1,
|
||||||
|
"autoname": "field:agent_name",
|
||||||
|
"creation": "2024-12-13 14:37:38.780730",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"agent_name"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "agent_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Agent Name",
|
||||||
|
"unique": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2024-12-13 15:15:47.447599",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Asset Lite",
|
||||||
|
"name": "Agent",
|
||||||
|
"naming_rule": "By fieldname",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
|
}
|
||||||
9
asset_lite/asset_lite/doctype/agent/agent.py
Normal file
9
asset_lite/asset_lite/doctype/agent/agent.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2024, seyfert and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class Agent(Document):
|
||||||
|
pass
|
||||||
9
asset_lite/asset_lite/doctype/agent/test_agent.py
Normal file
9
asset_lite/asset_lite/doctype/agent/test_agent.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2024, seyfert and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestAgent(FrappeTestCase):
|
||||||
|
pass
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2025, seyfert and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("Arabic Names", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
50
asset_lite/asset_lite/doctype/arabic_names/arabic_names.json
Normal file
50
asset_lite/asset_lite/doctype/arabic_names/arabic_names.json
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"autoname": "field:name1",
|
||||||
|
"creation": "2025-02-07 16:56:53.486694",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"name1",
|
||||||
|
"arabic_name"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "name1",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Name",
|
||||||
|
"unique": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "arabic_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Arabic Name"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2025-02-07 16:57:50.823039",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Asset Lite",
|
||||||
|
"name": "Arabic Names",
|
||||||
|
"naming_rule": "By fieldname",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2025, seyfert and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class ArabicNames(Document):
|
||||||
|
pass
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2025, seyfert and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestArabicNames(FrappeTestCase):
|
||||||
|
pass
|
||||||
@ -0,0 +1,59 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"creation": "2025-08-26 21:00:34.676325",
|
||||||
|
"default_view": "List",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"asset",
|
||||||
|
"asset_name",
|
||||||
|
"qty",
|
||||||
|
"column_break_kbew",
|
||||||
|
"return_inspection_committee"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "asset",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Asset",
|
||||||
|
"options": "Asset"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "asset.asset_name",
|
||||||
|
"fetch_if_empty": 1,
|
||||||
|
"fieldname": "asset_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Asset Name"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "1",
|
||||||
|
"fieldname": "qty",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Qty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_kbew",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "return_inspection_committee",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Return Inspection Committee",
|
||||||
|
"options": "\nFor Repair\nFor Sale\nFor Disposal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2025-08-26 21:00:34.676325",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Asset Lite",
|
||||||
|
"name": "Asset Item Transfer",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2025, seyfert and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class AssetItemTransfer(Document):
|
||||||
|
pass
|
||||||
8
asset_lite/asset_lite/doctype/asset_type/asset_type.js
Normal file
8
asset_lite/asset_lite/doctype/asset_type/asset_type.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2024, seyfert and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("Asset Type", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
47
asset_lite/asset_lite/doctype/asset_type/asset_type.json
Normal file
47
asset_lite/asset_lite/doctype/asset_type/asset_type.json
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"autoname": "field:asset_type",
|
||||||
|
"creation": "2024-09-23 14:16:49.149093",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"asset_type"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "asset_type",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Asset Type",
|
||||||
|
"reqd": 1,
|
||||||
|
"unique": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2024-09-23 14:17:58.073254",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Asset Lite",
|
||||||
|
"name": "Asset Type",
|
||||||
|
"naming_rule": "By fieldname",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
|
}
|
||||||
9
asset_lite/asset_lite/doctype/asset_type/asset_type.py
Normal file
9
asset_lite/asset_lite/doctype/asset_type/asset_type.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2024, seyfert and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class AssetType(Document):
|
||||||
|
pass
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2024, seyfert and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestAssetType(FrappeTestCase):
|
||||||
|
pass
|
||||||
0
asset_lite/asset_lite/doctype/building/__init__.py
Normal file
0
asset_lite/asset_lite/doctype/building/__init__.py
Normal file
8
asset_lite/asset_lite/doctype/building/building.js
Normal file
8
asset_lite/asset_lite/doctype/building/building.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2026, seyfert and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("Building", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
47
asset_lite/asset_lite/doctype/building/building.json
Normal file
47
asset_lite/asset_lite/doctype/building/building.json
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_import": 1,
|
||||||
|
"allow_rename": 1,
|
||||||
|
"autoname": "field:building",
|
||||||
|
"creation": "2026-01-21 14:00:25.681969",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"building"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "building",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Building",
|
||||||
|
"unique": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"grid_page_length": 50,
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2026-03-10 10:52:11.526339",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Asset Lite",
|
||||||
|
"name": "Building",
|
||||||
|
"naming_rule": "By fieldname",
|
||||||
|
"owner": "support@seeraarabia.com",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"row_format": "Dynamic",
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
|
}
|
||||||
9
asset_lite/asset_lite/doctype/building/building.py
Normal file
9
asset_lite/asset_lite/doctype/building/building.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2026, seyfert and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class Building(Document):
|
||||||
|
pass
|
||||||
9
asset_lite/asset_lite/doctype/building/test_building.py
Normal file
9
asset_lite/asset_lite/doctype/building/test_building.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2026, seyfert and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestBuilding(FrappeTestCase):
|
||||||
|
pass
|
||||||
0
asset_lite/asset_lite/doctype/city/__init__.py
Normal file
0
asset_lite/asset_lite/doctype/city/__init__.py
Normal file
8
asset_lite/asset_lite/doctype/city/city.js
Normal file
8
asset_lite/asset_lite/doctype/city/city.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2025, seyfert and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("City", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
44
asset_lite/asset_lite/doctype/city/city.json
Normal file
44
asset_lite/asset_lite/doctype/city/city.json
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"autoname": "field:city",
|
||||||
|
"creation": "2025-08-26 15:20:17.013649",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"city"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "city",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "City",
|
||||||
|
"unique": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2025-08-26 20:43:50.686274",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Asset Lite",
|
||||||
|
"name": "City",
|
||||||
|
"naming_rule": "By fieldname",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
|
}
|
||||||
9
asset_lite/asset_lite/doctype/city/city.py
Normal file
9
asset_lite/asset_lite/doctype/city/city.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2025, seyfert and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class City(Document):
|
||||||
|
pass
|
||||||
9
asset_lite/asset_lite/doctype/city/test_city.py
Normal file
9
asset_lite/asset_lite/doctype/city/test_city.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2025, seyfert and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestCity(FrappeTestCase):
|
||||||
|
pass
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2026, seyfert and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("Delete Request", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
125
asset_lite/asset_lite/doctype/delete_request/delete_request.json
Normal file
125
asset_lite/asset_lite/doctype/delete_request/delete_request.json
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"autoname": "format:{target_doctype}-{target_name}",
|
||||||
|
"creation": "2026-02-18 11:23:18.627887",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"target_doctype",
|
||||||
|
"target_display",
|
||||||
|
"department",
|
||||||
|
"status",
|
||||||
|
"column_break_nhyd",
|
||||||
|
"target_name",
|
||||||
|
"reason",
|
||||||
|
"requested_by",
|
||||||
|
"section_break_lwex",
|
||||||
|
"supervisor_comment",
|
||||||
|
"column_break_wsat",
|
||||||
|
"cluster_manager_comment",
|
||||||
|
"amended_from"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "target_doctype",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Target Doctype",
|
||||||
|
"options": "DocType"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "department",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Department",
|
||||||
|
"options": "Issue Type"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_on_submit": 1,
|
||||||
|
"fieldname": "status",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Status",
|
||||||
|
"options": "Pending\nApproved\nRejected"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_nhyd",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "target_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Target Name"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "reason",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Reason"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "requested_by",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Requested By",
|
||||||
|
"options": "User"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_lwex",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "supervisor_comment",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Supervisor Comment"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_wsat",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "cluster_manager_comment",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "CM Comment"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "target_display",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Target Display"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "amended_from",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Amended From",
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "Delete Request",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1,
|
||||||
|
"search_index": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"grid_page_length": 50,
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"is_submittable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2026-02-18 15:22:54.215648",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Asset Lite",
|
||||||
|
"name": "Delete Request",
|
||||||
|
"naming_rule": "Expression",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"row_format": "Dynamic",
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2026, seyfert and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class DeleteRequest(Document):
|
||||||
|
pass
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2026, seyfert and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestDeleteRequest(FrappeTestCase):
|
||||||
|
pass
|
||||||
8
asset_lite/asset_lite/doctype/ecri_umdns/ecri_umdns.js
Normal file
8
asset_lite/asset_lite/doctype/ecri_umdns/ecri_umdns.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2024, seyfert and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("ECRI UMDNS", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
51
asset_lite/asset_lite/doctype/ecri_umdns/ecri_umdns.json
Normal file
51
asset_lite/asset_lite/doctype/ecri_umdns/ecri_umdns.json
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_import": 1,
|
||||||
|
"allow_rename": 1,
|
||||||
|
"autoname": "field:ecri",
|
||||||
|
"creation": "2024-12-03 14:43:40.910282",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"ecri",
|
||||||
|
"device_name"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "ecri",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "ECRI",
|
||||||
|
"unique": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "device_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Device Name"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2024-12-03 18:43:19.609779",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Asset Lite",
|
||||||
|
"name": "ECRI UMDNS",
|
||||||
|
"naming_rule": "By fieldname",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
|
}
|
||||||
9
asset_lite/asset_lite/doctype/ecri_umdns/ecri_umdns.py
Normal file
9
asset_lite/asset_lite/doctype/ecri_umdns/ecri_umdns.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2024, seyfert and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class ECRIUMDNS(Document):
|
||||||
|
pass
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2024, seyfert and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestECRIUMDNS(FrappeTestCase):
|
||||||
|
pass
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2025, seyfert and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("Extension Directory", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_import": 1,
|
||||||
|
"allow_rename": 1,
|
||||||
|
"autoname": "field:extension_number",
|
||||||
|
"creation": "2025-12-12 13:47:32.707063",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"extension_number"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "extension_number",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Extension Number",
|
||||||
|
"unique": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"grid_page_length": 50,
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2026-01-12 15:40:06.059968",
|
||||||
|
"modified_by": "support@seeraarabia.com",
|
||||||
|
"module": "Asset Lite",
|
||||||
|
"name": "Extension Directory",
|
||||||
|
"naming_rule": "By fieldname",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"row_format": "Dynamic",
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2025, seyfert and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class ExtensionDirectory(Document):
|
||||||
|
pass
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2025, seyfert and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestExtensionDirectory(FrappeTestCase):
|
||||||
|
pass
|
||||||
0
asset_lite/asset_lite/doctype/feedback/__init__.py
Normal file
0
asset_lite/asset_lite/doctype/feedback/__init__.py
Normal file
8
asset_lite/asset_lite/doctype/feedback/feedback.js
Normal file
8
asset_lite/asset_lite/doctype/feedback/feedback.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2025, seyfert and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("Feedback", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
77
asset_lite/asset_lite/doctype/feedback/feedback.json
Normal file
77
asset_lite/asset_lite/doctype/feedback/feedback.json
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"creation": "2025-03-05 19:53:51.796362",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"work_order",
|
||||||
|
"column_break_eytf",
|
||||||
|
"feedback_by",
|
||||||
|
"section_break_rvtb",
|
||||||
|
"parameters",
|
||||||
|
"section_break_zmrx",
|
||||||
|
"overall"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "work_order",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Work Order",
|
||||||
|
"options": "Work_Order"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_eytf",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "feedback_by",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Feedback by",
|
||||||
|
"options": "User"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_rvtb",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "parameters",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Parameters",
|
||||||
|
"options": "Feedback Table"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_zmrx",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "overall",
|
||||||
|
"fieldtype": "Rating",
|
||||||
|
"label": "Overall"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2025-03-05 19:56:58.056262",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Asset Lite",
|
||||||
|
"name": "Feedback",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
|
}
|
||||||
9
asset_lite/asset_lite/doctype/feedback/feedback.py
Normal file
9
asset_lite/asset_lite/doctype/feedback/feedback.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2025, seyfert and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class Feedback(Document):
|
||||||
|
pass
|
||||||
9
asset_lite/asset_lite/doctype/feedback/test_feedback.py
Normal file
9
asset_lite/asset_lite/doctype/feedback/test_feedback.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2025, seyfert and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestFeedback(FrappeTestCase):
|
||||||
|
pass
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2026, seyfert and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("Feedback Parameters", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"creation": "2026-02-05 16:07:06.424217",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"section_break_et0g"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_et0g",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"grid_page_length": 50,
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"issingle": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2026-02-05 16:07:06.424217",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Asset Lite",
|
||||||
|
"name": "Feedback Parameters",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"row_format": "Dynamic",
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2026, seyfert and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class FeedbackParameters(Document):
|
||||||
|
pass
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2026, seyfert and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestFeedbackParameters(FrappeTestCase):
|
||||||
|
pass
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"creation": "2025-03-05 20:36:14.651301",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"parameter",
|
||||||
|
"rating",
|
||||||
|
"feedback"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "parameter",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Parameter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "rating",
|
||||||
|
"fieldtype": "Rating",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Rating",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "feedback",
|
||||||
|
"fieldtype": "Text",
|
||||||
|
"label": "Feedback"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2025-03-05 20:49:43.458939",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Asset Lite",
|
||||||
|
"name": "Feedback Table",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2025, seyfert and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class FeedbackTable(Document):
|
||||||
|
pass
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2026, seyfert and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("Infrastructure Location", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
@ -0,0 +1,97 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_import": 1,
|
||||||
|
"allow_rename": 1,
|
||||||
|
"autoname": "format:{building}-{department}-{room_no}-{location}",
|
||||||
|
"creation": "2026-02-02 09:53:38.751717",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"building",
|
||||||
|
"room_no",
|
||||||
|
"check_xqxx",
|
||||||
|
"column_break_jbgn",
|
||||||
|
"department",
|
||||||
|
"location"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "building",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_global_search": 1,
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Building",
|
||||||
|
"options": "Building"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "room_no",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_filter": 1,
|
||||||
|
"in_global_search": 1,
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Room No",
|
||||||
|
"options": "Room"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_jbgn",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "department",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_filter": 1,
|
||||||
|
"in_global_search": 1,
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Department",
|
||||||
|
"options": "Department"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "location",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_filter": 1,
|
||||||
|
"in_global_search": 1,
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Location",
|
||||||
|
"options": "Location"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "check_xqxx",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "enable"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"grid_page_length": 50,
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2026-03-02 16:28:35.630556",
|
||||||
|
"modified_by": "support@seeraarabia.com",
|
||||||
|
"module": "Asset Lite",
|
||||||
|
"name": "Infrastructure Location",
|
||||||
|
"naming_rule": "Expression",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"row_format": "Dynamic",
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2026, seyfert and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class InfrastructureLocation(Document):
|
||||||
|
pass
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2026, seyfert and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestInfrastructureLocation(FrappeTestCase):
|
||||||
|
pass
|
||||||
8
asset_lite/asset_lite/doctype/inspection/inspection.js
Normal file
8
asset_lite/asset_lite/doctype/inspection/inspection.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2026, seyfert and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("Inspection", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
215
asset_lite/asset_lite/doctype/inspection/inspection.json
Normal file
215
asset_lite/asset_lite/doctype/inspection/inspection.json
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"autoname": "format:INS-{YYYY}-{###}",
|
||||||
|
"creation": "2026-01-06 12:12:55.013069",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"inspection_type",
|
||||||
|
"work_order_type",
|
||||||
|
"inspection_date",
|
||||||
|
"target_closure_date",
|
||||||
|
"directed_to_responsible",
|
||||||
|
"linked_corrective_wo_no",
|
||||||
|
"status1",
|
||||||
|
"attachment",
|
||||||
|
"custom_delete_status",
|
||||||
|
"column_break_wcyc",
|
||||||
|
"status",
|
||||||
|
"requested_by",
|
||||||
|
"department",
|
||||||
|
"location",
|
||||||
|
"assigned_technician",
|
||||||
|
"technician_name",
|
||||||
|
"technician_department",
|
||||||
|
"extension_no",
|
||||||
|
"attachment_on_close",
|
||||||
|
"amended_from",
|
||||||
|
"section_break_ajvn",
|
||||||
|
"observation_note",
|
||||||
|
"column_break_sfev",
|
||||||
|
"technical_response"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "inspection_type",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Inspection Type",
|
||||||
|
"options": "\nSafety Inspection\nInspection"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "work_order_type",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Work Order Type",
|
||||||
|
"options": "Issue Type"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "inspection_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "Inspection Date"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "target_closure_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": " Target Closure Date"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "directed_to_responsible",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Directed To (Responsible)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "linked_corrective_wo_no",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Linked Corrective WO No",
|
||||||
|
"options": "Work_Order"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "status1",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": " Status1",
|
||||||
|
"options": "\nOpen\nIn Progress\nClosed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "attachment",
|
||||||
|
"fieldtype": "Attach",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Attachment on Open"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_on_submit": 1,
|
||||||
|
"fieldname": "custom_delete_status",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Delete Status",
|
||||||
|
"options": "\nDelete Request With Supervisor\nDelete Request With CM\nDeleted"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_wcyc",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "status",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": " Status",
|
||||||
|
"options": "\nOpen\nIn Progress\nPending Review\nClosed",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "requested_by",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Requested By"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "department",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Department",
|
||||||
|
"options": "Department"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "location",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Location",
|
||||||
|
"options": "Location"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "assigned_technician",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Assigned Technician",
|
||||||
|
"options": "User"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "assigned_technician.full_name",
|
||||||
|
"fetch_if_empty": 1,
|
||||||
|
"fieldname": "technician_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Technician Name",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "technician_department",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Technician Department",
|
||||||
|
"options": "Technical Department"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "extension_no",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Extension No",
|
||||||
|
"options": "Extension Directory",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "linked_corrective_wo_no.custom_attachment_on_close",
|
||||||
|
"fetch_if_empty": 1,
|
||||||
|
"fieldname": "attachment_on_close",
|
||||||
|
"fieldtype": "Attach",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Attachment on Close"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "amended_from",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Amended From",
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "Inspection",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1,
|
||||||
|
"search_index": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_ajvn",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "observation_note",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": " Observation / Note"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_sfev",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "technical_response",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Technical Response"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"grid_page_length": 50,
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"is_submittable": 1,
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"link_doctype": "Work_Order",
|
||||||
|
"link_fieldname": "inspection"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"modified": "2026-03-10 10:50:50.987891",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Asset Lite",
|
||||||
|
"name": "Inspection",
|
||||||
|
"naming_rule": "Expression",
|
||||||
|
"owner": "support@seeraarabia.com",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"row_format": "Dynamic",
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
9
asset_lite/asset_lite/doctype/inspection/inspection.py
Normal file
9
asset_lite/asset_lite/doctype/inspection/inspection.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2026, seyfert and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class Inspection(Document):
|
||||||
|
pass
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2026, seyfert and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestInspection(FrappeTestCase):
|
||||||
|
pass
|
||||||
@ -0,0 +1,64 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"creation": "2025-08-26 21:01:28.397119",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"item",
|
||||||
|
"qty",
|
||||||
|
"uom",
|
||||||
|
"column_break_gcjw",
|
||||||
|
"item_name",
|
||||||
|
"return_inspection_committee"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "item",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Item",
|
||||||
|
"options": "Item"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "qty",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Qty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "item.stock_uom",
|
||||||
|
"fetch_if_empty": 1,
|
||||||
|
"fieldname": "uom",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "UOM",
|
||||||
|
"options": "UOM"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_gcjw",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "item_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Item Name"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "return_inspection_committee",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Return Inspection Committee",
|
||||||
|
"options": "\nFor Repair\nFor Sale\nFor Disposal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2025-08-26 21:01:28.397119",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Asset Lite",
|
||||||
|
"name": "Item Transfer Table",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2025, seyfert and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class ItemTransferTable(Document):
|
||||||
|
pass
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2025, seyfert and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("Material Transfer", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
@ -0,0 +1,128 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"autoname": "format:Material-Transfer-{####}",
|
||||||
|
"creation": "2025-08-26 21:02:44.817236",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"hospital",
|
||||||
|
"purpose",
|
||||||
|
"source_warehouse",
|
||||||
|
"asset",
|
||||||
|
"column_break_irlz",
|
||||||
|
"date",
|
||||||
|
"reason_for_return",
|
||||||
|
"transfer_type",
|
||||||
|
"target_warehouse",
|
||||||
|
"item",
|
||||||
|
"section_break_bunw",
|
||||||
|
"item_table",
|
||||||
|
"asset_transfer"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "hospital",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Hospital",
|
||||||
|
"options": "Company"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "purpose",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Purpose",
|
||||||
|
"options": "Transfer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "source_warehouse",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Source Warehouse",
|
||||||
|
"options": "Warehouse"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.transfer_type=='Asset'",
|
||||||
|
"fieldname": "asset",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Asset",
|
||||||
|
"options": "Asset"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_irlz",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "Today",
|
||||||
|
"fieldname": "date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "Date"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "reason_for_return",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Reason for Return",
|
||||||
|
"options": "\nPurpose Completed\nSurplus\nUnusable\nDamaged"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "transfer_type",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Transfer Type",
|
||||||
|
"options": "\nAsset\nItem"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "target_warehouse",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Target Warehouse",
|
||||||
|
"options": "Warehouse"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.transfer_type=='Item'",
|
||||||
|
"fieldname": "item",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Item",
|
||||||
|
"options": "Item"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_bunw",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.transfer_type=='Item'",
|
||||||
|
"fieldname": "item_table",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Item Table",
|
||||||
|
"options": "Item Transfer Table"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.transfer_type=='Asset'",
|
||||||
|
"fieldname": "asset_transfer",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Asset Transfer",
|
||||||
|
"options": "Asset Item Transfer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2025-08-26 21:02:44.817236",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Asset Lite",
|
||||||
|
"name": "Material Transfer",
|
||||||
|
"naming_rule": "Expression",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2025, seyfert and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class MaterialTransfer(Document):
|
||||||
|
pass
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2025, seyfert and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestMaterialTransfer(FrappeTestCase):
|
||||||
|
pass
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2025, seyfert and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("Mobile Team Site", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user