commit 9c41cbaf5b978d7aff46d9279503fd4f0a347cb3 Author: Duradundi Hadimani Date: Mon Mar 23 17:34:51 2026 +0530 Initial commit of Asset Lite app diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ba04025 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +*.pyc +*.egg-info +*.swp +tags +node_modules +__pycache__ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..31831cf --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +## Asset Lite + +Asset Management System + +#### License + +mit \ No newline at end of file diff --git a/asset_lite/__init__.py b/asset_lite/__init__.py new file mode 100644 index 0000000..f102a9c --- /dev/null +++ b/asset_lite/__init__.py @@ -0,0 +1 @@ +__version__ = "0.0.1" diff --git a/asset_lite/api/__init__.py b/asset_lite/api/__init__.py new file mode 100644 index 0000000..79efa56 --- /dev/null +++ b/asset_lite/api/__init__.py @@ -0,0 +1 @@ +from . import asset_api \ No newline at end of file diff --git a/asset_lite/api/api.py b/asset_lite/api/api.py new file mode 100644 index 0000000..fa8e1e8 --- /dev/null +++ b/asset_lite/api/api.py @@ -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 \ No newline at end of file diff --git a/asset_lite/api/asset_api.py b/asset_lite/api/asset_api.py new file mode 100644 index 0000000..6f37cef --- /dev/null +++ b/asset_lite/api/asset_api.py @@ -0,0 +1,1122 @@ +import frappe +from frappe import _ + +@frappe.whitelist(allow_guest = True) +def get_assets(filters=None, fields=None, limit=20, offset=0, order_by=None, include_finance_books=True): + """ + Get list of assets with filters and pagination + + Args: + filters: JSON string of filters (e.g., '{"company": "ABC Corp"}') + fields: JSON string of fields to return (e.g., '["asset_name", "location"]') + limit: Number of records to return (default: 20) + offset: Number of records to skip (default: 0) + order_by: Sort order (e.g., "creation desc") + include_finance_books: Include depreciation details (default: True) + + Returns: + { + "assets": [...], + "total_count": int, + "limit": int, + "offset": int, + "has_more": bool + } + """ + try: + import json + frappe.log_error(f"Logged in User ",frappe.session.user) + + + # Parse filters if provided + if filters and isinstance(filters, str): + filters = json.loads(filters) + + # Parse fields if provided + if fields and isinstance(fields, str): + fields = json.loads(fields) + else: + # Default fields to return + fields = [ + 'name', + 'asset_name', + 'company', + 'custom_serial_number', + 'location', + 'custom_manufacturer', + 'department', + 'custom_asset_type', + 'custom_manufacturing_year', + 'custom_model', + 'custom_class', + 'custom_device_status', + 'custom_down_time', + 'asset_owner_company', + 'custom_up_time', + 'custom_modality', + 'custom_attach_image', + 'custom_site_contractor', + 'custom_total_amount', + 'creation', + 'modified', + 'owner', + 'modified_by', + # Depreciation related fields from parent + 'calculate_depreciation', + 'opening_accumulated_depreciation', + 'opening_number_of_booked_depreciations', + 'is_fully_depreciated', + 'depreciation_method', + 'value_after_depreciation', + 'total_number_of_depreciations', + 'frequency_of_depreciation', + 'gross_purchase_amount', + 'total_asset_cost', + 'available_for_use_date', + 'status', + 'custom_delete_status', + 'custom_technical_department' + ] + + # # Get total count + # total_count = frappe.db.count('Asset', filters=filters or {}) + + # # Get assets + # assets = frappe.get_all( + # 'Asset', + # filters=filters or {}, + # fields=fields, + # limit_page_length=int(limit), + # limit_start=int(offset), + # order_by=order_by or 'creation desc' + # ) + + # Build filter conditions for SQL count + all_filters = filters or {} + + # Get assets with user filters applied + assets = frappe.get_all( + 'Asset', + filters=all_filters, + fields=fields, + limit_page_length=int(limit), + limit_start=int(offset), + order_by=order_by or 'creation desc' + ) + + # Safely exclude Deleted (handles NULL/empty = not deleted) + assets = [a for a in assets if a.get('custom_delete_status') != 'Deleted'] + + # Get total count with same user filters + exclude Deleted + # frappe.db.count doesn't support OR conditions, so we fetch all matching and count + all_matching = frappe.get_all( + 'Asset', + filters=all_filters, + fields=['custom_delete_status'], + ) + total_count = sum(1 for a in all_matching if a.get('custom_delete_status') != 'Deleted') + + # Include finance_books (depreciation details) for each asset + if include_finance_books: + for asset in assets: + asset['finance_books'] = get_finance_books(asset['name']) + + # Calculate has_more + has_more = (int(offset) + int(limit)) < total_count + + frappe.response['message'] = { + 'assets': assets, + 'total_count': total_count, + 'limit': int(limit), + 'offset': int(offset), + 'has_more': has_more + } + + except Exception as e: + frappe.log_error(frappe.get_traceback(), 'Get Assets API Error') + frappe.response['message'] = { + 'error': str(e), + 'assets': [], + 'total_count': 0 + } + + +def get_finance_books(asset_name): + """ + Get finance books (depreciation details) for an asset + + Args: + asset_name: Name/ID of the asset + + Returns: + List of finance book entries with depreciation details + """ + finance_books = frappe.get_all( + 'Asset Finance Book', + filters={'parent': asset_name}, + fields=[ + 'name', + 'idx', + 'finance_book', + 'depreciation_method', + 'total_number_of_depreciations', + 'total_number_of_booked_depreciations', + 'daily_prorata_based', + 'shift_based', + 'frequency_of_depreciation', + 'depreciation_start_date', + 'salvage_value_percentage', + 'expected_value_after_useful_life', + 'value_after_depreciation', + 'rate_of_depreciation' + ], + order_by='idx asc' + ) + return finance_books + + +@frappe.whitelist(allow_guest = True) +def get_asset_details(asset_name, include_depreciation_schedule=False): + """ + Get detailed information about a specific asset + + Args: + asset_name: Name/ID of the asset + include_depreciation_schedule: Include depreciation schedule entries (default: False) + + Returns: + Asset document with all fields including finance_books + """ + try: + if not asset_name: + frappe.throw(_('Asset name is required')) + + # Check if user has permission to read this asset + if not frappe.has_permission('Asset', 'read', asset_name): + frappe.throw(_('Not permitted to access this asset')) + + # Get asset details (includes finance_books child table) + asset = frappe.get_doc('Asset', asset_name) + asset_dict = asset.as_dict() + + # Optionally include depreciation schedule + if include_depreciation_schedule: + asset_dict['depreciation_schedule'] = get_depreciation_schedule(asset_name) + + # Add computed depreciation summary + asset_dict['depreciation_summary'] = get_depreciation_summary(asset_name) + + frappe.response['message'] = asset_dict + + except Exception as e: + frappe.log_error(frappe.get_traceback(), 'Get Asset Details API Error') + frappe.response['message'] = { + 'error': str(e) + } + + +def get_depreciation_schedule(asset_name): + """ + Get depreciation schedule entries for an asset + + Args: + asset_name: Name/ID of the asset + + Returns: + List of depreciation schedule entries + """ + schedule = frappe.get_all( + 'Depreciation Schedule', + filters={'parent': asset_name}, + fields=[ + 'name', + 'idx', + 'schedule_date', + 'depreciation_amount', + 'accumulated_depreciation_amount', + 'journal_entry', + 'finance_book', + 'finance_book_id', + 'shift' + ], + order_by='schedule_date asc' + ) + return schedule + + +def get_depreciation_summary(asset_name): + """ + Get computed depreciation summary for an asset + + Args: + asset_name: Name/ID of the asset + + Returns: + Dictionary with depreciation summary + """ + try: + asset = frappe.get_doc('Asset', asset_name) + + # Calculate total depreciation booked + total_depreciation_booked = frappe.db.sql(""" + SELECT COALESCE(SUM(depreciation_amount), 0) as total + FROM `tabDepreciation Schedule` + WHERE parent = %s AND journal_entry IS NOT NULL AND journal_entry != '' + """, asset_name)[0][0] or 0 + + # Calculate pending depreciation entries + pending_entries = frappe.db.count('Depreciation Schedule', { + 'parent': asset_name, + 'journal_entry': ['in', ['', None]] + }) + + # Calculate completed entries + completed_entries = frappe.db.count('Depreciation Schedule', { + 'parent': asset_name, + 'journal_entry': ['not in', ['', None]] + }) + + return { + 'gross_purchase_amount': float(asset.gross_purchase_amount or 0), + 'total_asset_cost': float(asset.total_asset_cost or 0), + 'opening_accumulated_depreciation': float(asset.opening_accumulated_depreciation or 0), + 'total_depreciation_booked': float(total_depreciation_booked), + 'value_after_depreciation': float(asset.value_after_depreciation or 0), + 'is_fully_depreciated': asset.is_fully_depreciated, + 'pending_depreciation_entries': pending_entries, + 'completed_depreciation_entries': completed_entries, + 'calculate_depreciation': asset.calculate_depreciation + } + except Exception: + return {} + + +@frappe.whitelist(allow_guest = True) +def get_asset_finance_books(asset_name): + """ + Get finance books (depreciation configuration) for a specific asset + + Args: + asset_name: Name/ID of the asset + + Returns: + List of finance book entries with all depreciation details + """ + try: + if not asset_name: + frappe.throw(_('Asset name is required')) + + if not frappe.has_permission('Asset', 'read', asset_name): + frappe.throw(_('Not permitted to access this asset')) + + finance_books = frappe.get_all( + 'Asset Finance Book', + filters={'parent': asset_name}, + fields=[ + 'name', + 'idx', + 'finance_book', + 'depreciation_method', + 'total_number_of_depreciations', + 'total_number_of_booked_depreciations', + 'daily_prorata_based', + 'shift_based', + 'frequency_of_depreciation', + 'depreciation_start_date', + 'salvage_value_percentage', + 'expected_value_after_useful_life', + 'value_after_depreciation', + 'rate_of_depreciation' + ], + order_by='idx asc' + ) + + frappe.response['message'] = { + 'asset_name': asset_name, + 'finance_books': finance_books, + 'count': len(finance_books) + } + + except Exception as e: + frappe.log_error(frappe.get_traceback(), 'Get Asset Finance Books API Error') + frappe.response['message'] = { + 'error': str(e) + } + + +@frappe.whitelist(allow_guest = True) +def get_asset_depreciation_schedule(asset_name, finance_book=None): + """ + Get depreciation schedule for a specific asset + + Args: + asset_name: Name/ID of the asset + finance_book: Optional filter by finance book + + Returns: + List of depreciation schedule entries + """ + try: + if not asset_name: + frappe.throw(_('Asset name is required')) + + if not frappe.has_permission('Asset', 'read', asset_name): + frappe.throw(_('Not permitted to access this asset')) + + filters = {'parent': asset_name} + if finance_book: + filters['finance_book'] = finance_book + + schedule = frappe.get_all( + 'Depreciation Schedule', + filters=filters, + fields=[ + 'name', + 'idx', + 'schedule_date', + 'depreciation_amount', + 'accumulated_depreciation_amount', + 'journal_entry', + 'finance_book', + 'finance_book_id', + 'shift' + ], + order_by='schedule_date asc' + ) + + # Add status to each entry + for entry in schedule: + entry['status'] = 'Posted' if entry.get('journal_entry') else 'Pending' + + frappe.response['message'] = { + 'asset_name': asset_name, + 'depreciation_schedule': schedule, + 'total_entries': len(schedule), + 'posted_entries': len([s for s in schedule if s.get('journal_entry')]), + 'pending_entries': len([s for s in schedule if not s.get('journal_entry')]) + } + + except Exception as e: + frappe.log_error(frappe.get_traceback(), 'Get Asset Depreciation Schedule API Error') + frappe.response['message'] = { + 'error': str(e) + } + + +@frappe.whitelist(allow_guest = True) +def create_asset(asset_data): + """ + Create a new asset with finance books (depreciation configuration) + + Args: + asset_data: JSON string containing asset fields including finance_books array + + Example asset_data: + { + "asset_name": "Test Asset", + "company": "My Company", + "item_code": "ITEM-001", + "gross_purchase_amount": 10000, + "calculate_depreciation": 1, + "available_for_use_date": "2025-01-01", + "finance_books": [ + { + "finance_book": "Depreciation Entries", + "depreciation_method": "Straight Line", + "total_number_of_depreciations": 12, + "frequency_of_depreciation": 12, + "depreciation_start_date": "2025-01-01", + "expected_value_after_useful_life": 1000 + } + ] + } + + Returns: + Created asset document with finance_books + """ + try: + import json + + # Parse asset data + if isinstance(asset_data, str): + asset_data = json.loads(asset_data) + + # Check if user has permission to create asset + if not frappe.has_permission('Asset', 'create'): + frappe.throw(_('Not permitted to create asset')) + + # Create new asset + asset = frappe.get_doc({ + 'doctype': 'Asset', + **asset_data + }) + + asset.insert() + frappe.db.commit() + + frappe.response['message'] = { + 'success': True, + 'asset': asset.as_dict(), + 'message': _('Asset created successfully') + } + + except Exception as e: + frappe.db.rollback() + frappe.log_error(frappe.get_traceback(), 'Create Asset API Error') + frappe.response['message'] = { + 'success': False, + 'error': str(e) + } + + +@frappe.whitelist(allow_guest = True) +def update_asset(asset_name, asset_data): + """ + Update an existing asset including finance books + Uses ignore_version flag to bypass timestamp mismatch errors + + Args: + asset_name: Name/ID of the asset + asset_data: JSON string containing fields to update (can include finance_books) + + Returns: + Updated asset document + """ + try: + import json + + if not asset_name: + frappe.throw(_('Asset name is required')) + + # Parse asset data + if isinstance(asset_data, str): + asset_data = json.loads(asset_data) + + # Check if user has permission to update this asset + if not frappe.has_permission('Asset', 'write', asset_name): + frappe.throw(_('Not permitted to update this asset')) + + # Get fresh copy of asset from database + asset = frappe.get_doc('Asset', asset_name) + + # Set flags to ignore version check (bypass timestamp mismatch) + asset.flags.ignore_version = True + + # Handle finance_books separately if provided + finance_books_data = asset_data.pop('finance_books', None) + + # Update regular fields + for key, value in asset_data.items(): + if hasattr(asset, key): + setattr(asset, key, value) + + # Update finance_books if provided + if finance_books_data is not None: + asset.set('finance_books', []) + for fb in finance_books_data: + asset.append('finance_books', fb) + + asset.save() + frappe.db.commit() + + frappe.response['message'] = { + 'success': True, + 'asset': asset.as_dict(), + 'message': _('Asset updated successfully') + } + + except Exception as e: + frappe.db.rollback() + frappe.log_error(frappe.get_traceback(), 'Update Asset API Error') + frappe.response['message'] = { + 'success': False, + 'error': str(e) + } + + +@frappe.whitelist(allow_guest = True) +def submit_asset(asset_name): + """ + Submit an asset (change docstatus from 0 to 1) + Uses ignore_version flag to bypass timestamp mismatch errors + + Args: + asset_name: Name/ID of the asset to submit + + Returns: + Submitted asset document + """ + try: + if not asset_name: + frappe.throw(_('Asset name is required')) + + # Check if user has permission to submit this asset + if not frappe.has_permission('Asset', 'submit', asset_name): + frappe.throw(_('Not permitted to submit this asset')) + + # Get fresh copy of the asset from database + asset = frappe.get_doc('Asset', asset_name) + + # Check if already submitted + if asset.docstatus == 1: + frappe.throw(_('Asset is already submitted')) + + if asset.docstatus == 2: + frappe.throw(_('Cannot submit a cancelled asset')) + + # Set flags to ignore version check (bypass timestamp mismatch) + asset.flags.ignore_version = True + asset.flags.ignore_links = True # Optional: ignore broken links + asset.flags.ignore_validate = False # Still run validations + + # Submit the asset + asset.submit() + + frappe.db.commit() + + frappe.response['message'] = { + 'success': True, + 'asset': asset.as_dict(), + 'message': _('Asset submitted successfully') + } + + except Exception as e: + frappe.db.rollback() + frappe.log_error(frappe.get_traceback(), 'Submit Asset API Error') + frappe.response['message'] = { + 'success': False, + 'error': str(e) + } + + +@frappe.whitelist(allow_guest = True) +def cancel_asset(asset_name): + """ + Cancel a submitted asset (change docstatus from 1 to 2) + Uses ignore_version flag to bypass timestamp mismatch errors + + Args: + asset_name: Name/ID of the asset to cancel + + Returns: + Cancelled asset document + """ + try: + if not asset_name: + frappe.throw(_('Asset name is required')) + + # Check if user has permission to cancel this asset + if not frappe.has_permission('Asset', 'cancel', asset_name): + frappe.throw(_('Not permitted to cancel this asset')) + + # Get fresh copy of the asset from database + asset = frappe.get_doc('Asset', asset_name) + + # Check if can be cancelled + if asset.docstatus == 0: + frappe.throw(_('Cannot cancel a draft asset. Submit it first.')) + + if asset.docstatus == 2: + frappe.throw(_('Asset is already cancelled')) + + # Set flags to ignore version check (bypass timestamp mismatch) + asset.flags.ignore_version = True + asset.flags.ignore_links = True + + # Cancel the asset + asset.cancel() + + frappe.db.commit() + + frappe.response['message'] = { + 'success': True, + 'asset': asset.as_dict(), + 'message': _('Asset cancelled successfully') + } + + except Exception as e: + frappe.db.rollback() + frappe.log_error(frappe.get_traceback(), 'Cancel Asset API Error') + frappe.response['message'] = { + 'success': False, + 'error': str(e) + } + + +@frappe.whitelist(allow_guest = True) +def update_asset_finance_book(asset_name, finance_book_name, finance_book_data): + """ + Update a specific finance book entry for an asset + + Args: + asset_name: Name/ID of the asset + finance_book_name: Name of the finance book row to update + finance_book_data: JSON string containing fields to update + + Returns: + Updated asset document + """ + try: + import json + + if not asset_name: + frappe.throw(_('Asset name is required')) + + if not finance_book_name: + frappe.throw(_('Finance book name is required')) + + # Parse finance book data + if isinstance(finance_book_data, str): + finance_book_data = json.loads(finance_book_data) + + # Check if user has permission to update this asset + if not frappe.has_permission('Asset', 'write', asset_name): + frappe.throw(_('Not permitted to update this asset')) + + # Get asset + asset = frappe.get_doc('Asset', asset_name) + + # Set flags to ignore version check + asset.flags.ignore_version = True + + # Find and update the specific finance book + updated = False + for fb in asset.finance_books: + if fb.name == finance_book_name: + for key, value in finance_book_data.items(): + if hasattr(fb, key): + setattr(fb, key, value) + updated = True + break + + if not updated: + frappe.throw(_('Finance book entry not found')) + + asset.save() + frappe.db.commit() + + frappe.response['message'] = { + 'success': True, + 'asset': asset.as_dict(), + 'message': _('Finance book updated successfully') + } + + except Exception as e: + frappe.db.rollback() + frappe.log_error(frappe.get_traceback(), 'Update Asset Finance Book API Error') + frappe.response['message'] = { + 'success': False, + 'error': str(e) + } + + +@frappe.whitelist(allow_guest = True) +def add_asset_finance_book(asset_name, finance_book_data): + """ + Add a new finance book entry to an asset + + Args: + asset_name: Name/ID of the asset + finance_book_data: JSON string containing finance book fields + + Example finance_book_data: + { + "finance_book": "Depreciation Entries", + "depreciation_method": "Straight Line", + "total_number_of_depreciations": 12, + "frequency_of_depreciation": 12, + "depreciation_start_date": "2025-01-01", + "expected_value_after_useful_life": 1000 + } + + Returns: + Updated asset document + """ + try: + import json + + if not asset_name: + frappe.throw(_('Asset name is required')) + + # Parse finance book data + if isinstance(finance_book_data, str): + finance_book_data = json.loads(finance_book_data) + + # Check if user has permission to update this asset + if not frappe.has_permission('Asset', 'write', asset_name): + frappe.throw(_('Not permitted to update this asset')) + + # Get asset + asset = frappe.get_doc('Asset', asset_name) + + # Set flags to ignore version check + asset.flags.ignore_version = True + + # Add new finance book + asset.append('finance_books', finance_book_data) + + asset.save() + frappe.db.commit() + + frappe.response['message'] = { + 'success': True, + 'asset': asset.as_dict(), + 'message': _('Finance book added successfully') + } + + except Exception as e: + frappe.db.rollback() + frappe.log_error(frappe.get_traceback(), 'Add Asset Finance Book API Error') + frappe.response['message'] = { + 'success': False, + 'error': str(e) + } + + +@frappe.whitelist(allow_guest = True) +def delete_asset_finance_book(asset_name, finance_book_name): + """ + Delete a finance book entry from an asset + + Args: + asset_name: Name/ID of the asset + finance_book_name: Name of the finance book row to delete + + Returns: + Updated asset document + """ + try: + if not asset_name: + frappe.throw(_('Asset name is required')) + + if not finance_book_name: + frappe.throw(_('Finance book name is required')) + + # Check if user has permission to update this asset + if not frappe.has_permission('Asset', 'write', asset_name): + frappe.throw(_('Not permitted to update this asset')) + + # Get asset + asset = frappe.get_doc('Asset', asset_name) + + # Set flags to ignore version check + asset.flags.ignore_version = True + + # Find and remove the specific finance book + for i, fb in enumerate(asset.finance_books): + if fb.name == finance_book_name: + asset.finance_books.remove(fb) + break + else: + frappe.throw(_('Finance book entry not found')) + + asset.save() + frappe.db.commit() + + frappe.response['message'] = { + 'success': True, + 'asset': asset.as_dict(), + 'message': _('Finance book deleted successfully') + } + + except Exception as e: + frappe.db.rollback() + frappe.log_error(frappe.get_traceback(), 'Delete Asset Finance Book API Error') + frappe.response['message'] = { + 'success': False, + 'error': str(e) + } + + +@frappe.whitelist(allow_guest = True) +def delete_asset(asset_name): + """ + Delete an asset + + Args: + asset_name: Name/ID of the asset + + Returns: + Success message + """ + try: + if not asset_name: + frappe.throw(_('Asset name is required')) + + # Check if user has permission to delete this asset + if not frappe.has_permission('Asset', 'delete', asset_name): + frappe.throw(_('Not permitted to delete this asset')) + + # Delete asset + frappe.delete_doc('Asset', asset_name) + frappe.db.commit() + + frappe.response['message'] = { + 'success': True, + 'message': _('Asset deleted successfully') + } + + except Exception as e: + frappe.db.rollback() + frappe.log_error(frappe.get_traceback(), 'Delete Asset API Error') + frappe.response['message'] = { + 'success': False, + 'error': str(e) + } + + +@frappe.whitelist(allow_guest = True) +def get_asset_filters(): + """ + Get available filter options for assets + + Returns: + { + "companies": [...], + "locations": [...], + "departments": [...], + "asset_types": [...], + "manufacturers": [...], + "device_statuses": [...], + "finance_books": [...], + "depreciation_methods": [...] + } + """ + try: + filters = { + 'companies': frappe.get_all('Company', fields=['name'], pluck='name'), + 'locations': frappe.db.get_all('Asset', + filters={'location': ['!=', '']}, + fields=['location'], + distinct=True, + pluck='location' + ), + 'departments': frappe.get_all('Department', fields=['name'], pluck='name'), + 'asset_types': frappe.db.get_all('Asset', + filters={'custom_asset_type': ['!=', '']}, + fields=['custom_asset_type'], + distinct=True, + pluck='custom_asset_type' + ), + 'manufacturers': frappe.db.get_all('Asset', + filters={'custom_manufacturer': ['!=', '']}, + fields=['custom_manufacturer'], + distinct=True, + pluck='custom_manufacturer' + ), + 'device_statuses': frappe.db.get_all('Asset', + filters={'custom_device_status': ['!=', '']}, + fields=['custom_device_status'], + distinct=True, + pluck='custom_device_status' + ), + 'finance_books': frappe.get_all('Finance Book', fields=['name'], pluck='name'), + 'depreciation_methods': [ + 'Straight Line', + 'Double Declining Balance', + 'Written Down Value', + 'Manual' + ] + } + + frappe.response['message'] = filters + + except Exception as e: + frappe.log_error(frappe.get_traceback(), 'Get Asset Filters API Error') + frappe.response['message'] = { + 'error': str(e) + } + + +@frappe.whitelist(allow_guest = True) +def get_asset_stats(): + """ + Get statistics about assets including depreciation stats + + Returns: + { + "total_assets": int, + "by_status": {...}, + "by_company": {...}, + "by_type": {...}, + "total_amount": float, + "depreciation_stats": {...} + } + """ + try: + # Total assets + total_assets = frappe.db.count('Asset') + + # Assets by device status + by_status = {} + status_data = frappe.db.sql(""" + SELECT custom_device_status, COUNT(*) as count + FROM `tabAsset` + WHERE custom_device_status IS NOT NULL AND custom_device_status != '' + GROUP BY custom_device_status + """, as_dict=True) + for row in status_data: + by_status[row.custom_device_status] = row.count + + # Assets by company + by_company = {} + company_data = frappe.db.sql(""" + SELECT company, COUNT(*) as count + FROM `tabAsset` + WHERE company IS NOT NULL AND company != '' + GROUP BY company + """, as_dict=True) + for row in company_data: + by_company[row.company] = row.count + + # Assets by type + by_type = {} + type_data = frappe.db.sql(""" + SELECT custom_asset_type, COUNT(*) as count + FROM `tabAsset` + WHERE custom_asset_type IS NOT NULL AND custom_asset_type != '' + GROUP BY custom_asset_type + """, as_dict=True) + for row in type_data: + by_type[row.custom_asset_type] = row.count + + # Total amount + total_amount = frappe.db.sql(""" + SELECT SUM(custom_total_amount) as total + FROM `tabAsset` + WHERE custom_total_amount IS NOT NULL + """)[0][0] or 0 + + # Depreciation statistics + depreciation_stats = get_depreciation_stats() + + frappe.response['message'] = { + 'total_assets': total_assets, + 'by_status': by_status, + 'by_company': by_company, + 'by_type': by_type, + 'total_amount': float(total_amount), + 'depreciation_stats': depreciation_stats + } + + except Exception as e: + frappe.log_error(frappe.get_traceback(), 'Get Asset Stats API Error') + frappe.response['message'] = { + 'error': str(e) + } + + +def get_depreciation_stats(): + """ + Get depreciation statistics across all assets + + Returns: + Dictionary with depreciation statistics + """ + try: + # Total gross purchase amount + total_gross_amount = frappe.db.sql(""" + SELECT COALESCE(SUM(gross_purchase_amount), 0) as total + FROM `tabAsset` + """)[0][0] or 0 + + # Total accumulated depreciation + total_accumulated_depreciation = frappe.db.sql(""" + SELECT COALESCE(SUM(ds.depreciation_amount), 0) as total + FROM `tabDepreciation Schedule` ds + INNER JOIN `tabAsset` a ON ds.parent = a.name + WHERE ds.journal_entry IS NOT NULL AND ds.journal_entry != '' + """)[0][0] or 0 + + # Total value after depreciation + total_value_after_depreciation = frappe.db.sql(""" + SELECT COALESCE(SUM(value_after_depreciation), 0) as total + FROM `tabAsset` + """)[0][0] or 0 + + # Assets with depreciation enabled + assets_with_depreciation = frappe.db.count('Asset', {'calculate_depreciation': 1}) + + # Fully depreciated assets + fully_depreciated_assets = frappe.db.count('Asset', {'is_fully_depreciated': 1}) + + # Pending depreciation entries + pending_entries = frappe.db.sql(""" + SELECT COUNT(*) as count + FROM `tabDepreciation Schedule` + WHERE (journal_entry IS NULL OR journal_entry = '') + """)[0][0] or 0 + + # By depreciation method + by_depreciation_method = {} + method_data = frappe.db.sql(""" + SELECT depreciation_method, COUNT(*) as count + FROM `tabAsset Finance Book` + WHERE depreciation_method IS NOT NULL AND depreciation_method != '' + GROUP BY depreciation_method + """, as_dict=True) + for row in method_data: + by_depreciation_method[row.depreciation_method] = row.count + + return { + 'total_gross_amount': float(total_gross_amount), + 'total_accumulated_depreciation': float(total_accumulated_depreciation), + 'total_value_after_depreciation': float(total_value_after_depreciation), + 'assets_with_depreciation': assets_with_depreciation, + 'fully_depreciated_assets': fully_depreciated_assets, + 'pending_depreciation_entries': pending_entries, + 'by_depreciation_method': by_depreciation_method + } + except Exception: + return {} + + +@frappe.whitelist(allow_guest = True) +def search_assets(search_term, limit=10): + """ + Search assets by name, serial number, or other fields + + Args: + search_term: Search query string + limit: Maximum number of results (default: 10) + + Returns: + List of matching assets + """ + try: + if not search_term: + frappe.response['message'] = [] + return + + search_term = f"%{search_term}%" + + assets = frappe.db.sql(""" + SELECT + name, + asset_name, + custom_serial_number, + location, + company, + custom_device_status, + calculate_depreciation, + value_after_depreciation, + is_fully_depreciated + FROM `tabAsset` + WHERE + asset_name LIKE %(search)s + OR custom_serial_number LIKE %(search)s + OR location LIKE %(search)s + OR custom_manufacturer LIKE %(search)s + LIMIT %(limit)s + """, { + 'search': search_term, + 'limit': int(limit) + }, as_dict=True) + + frappe.response['message'] = assets + + except Exception as e: + frappe.log_error(frappe.get_traceback(), 'Search Assets API Error') + frappe.response['message'] = { + 'error': str(e) + } \ No newline at end of file diff --git a/asset_lite/api/asset_maintenance_api.py b/asset_lite/api/asset_maintenance_api.py new file mode 100644 index 0000000..c7dd5de --- /dev/null +++ b/asset_lite/api/asset_maintenance_api.py @@ -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) + } \ No newline at end of file diff --git a/asset_lite/api/custom_api.py b/asset_lite/api/custom_api.py new file mode 100644 index 0000000..ea19a36 --- /dev/null +++ b/asset_lite/api/custom_api.py @@ -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 diff --git a/asset_lite/api/dashboard_api.py b/asset_lite/api/dashboard_api.py new file mode 100644 index 0000000..7748bf9 --- /dev/null +++ b/asset_lite/api/dashboard_api.py @@ -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)) \ No newline at end of file diff --git a/asset_lite/api/delete_asset.py b/asset_lite/api/delete_asset.py new file mode 100644 index 0000000..67b6fba --- /dev/null +++ b/asset_lite/api/delete_asset.py @@ -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))) \ No newline at end of file diff --git a/asset_lite/api/delete_request.py b/asset_lite/api/delete_request.py new file mode 100644 index 0000000..a1cb423 --- /dev/null +++ b/asset_lite/api/delete_request.py @@ -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"] + ) \ No newline at end of file diff --git a/asset_lite/api/doctype_fields.py b/asset_lite/api/doctype_fields.py new file mode 100644 index 0000000..7931042 --- /dev/null +++ b/asset_lite/api/doctype_fields.py @@ -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 \ No newline at end of file diff --git a/asset_lite/api/item_api.py b/asset_lite/api/item_api.py new file mode 100644 index 0000000..9c3412d --- /dev/null +++ b/asset_lite/api/item_api.py @@ -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 + } \ No newline at end of file diff --git a/asset_lite/api/ppm_api.py b/asset_lite/api/ppm_api.py new file mode 100644 index 0000000..a52c7a3 --- /dev/null +++ b/asset_lite/api/ppm_api.py @@ -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 + } diff --git a/asset_lite/api/ppm_generator_api.py b/asset_lite/api/ppm_generator_api.py new file mode 100644 index 0000000..3759199 --- /dev/null +++ b/asset_lite/api/ppm_generator_api.py @@ -0,0 +1,1197 @@ +import frappe +from frappe import _ + +# Default fields for PM Schedule Generator doctype +PM_SCHEDULE_GENERATOR_FIELDS = [ + 'name', + 'owner', + 'creation', + 'modified', + 'modified_by', + 'docstatus', + 'idx', + 'hospital', + 'modality', + 'device_status', + 'start_date', + 'maintenance_team', + 'maintenance_manager', + 'end_date', + 'periodicity', + 'assign_to', + 'due_date' +] + +# Child table: PM Entry Line (maintenance_entries) +PM_ENTRY_LINE_FIELDS = [ + 'name', + 'owner', + 'creation', + 'modified', + 'modified_by', + 'docstatus', + 'idx', + 'parent', + 'parentfield', + 'parenttype', + 'asset', + 'asset_name', + 'start_date', + 'end_date', + 'manufacturer', + 'model' +] + +@frappe.whitelist() +def create_bulk_schedules(asset_names, start_date, end_date, maintenance_team=None, periodicity='Monthly', maintenance_type='Preventive'): + """Create maintenance schedules for multiple assets""" + created_schedules = [] + for asset_name in asset_names: + # Create Asset Maintenance record + maintenance = frappe.get_doc({ + 'doctype': 'Asset Maintenance', + 'asset_name': asset_name, + 'maintenance_type': maintenance_type, + 'periodicity': periodicity, + 'maintenance_team': maintenance_team + }) + maintenance.insert() + created_schedules.append(maintenance.name) + return { + 'success': True, + 'created': len(created_schedules), + 'schedules': created_schedules + } + +def get_child_table_data(parent_name, parentfield, child_doctype, fields=None): + """ + Get child table data for a PM Schedule Generator + + Args: + parent_name: Name of the parent PM Schedule Generator + 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': 'PM Schedule Generator' + }, + fields=fields or ['*'], + order_by='idx asc' + ) + except Exception: + return [] + + +@frappe.whitelist(allow_guest=True) +def get_pm_schedules(filters=None, fields=None, limit=20, offset=0, order_by=None, include_child_tables=False): + """ + Get list of PM Schedule Generators with filters and pagination + + Args: + filters: JSON string of filters (e.g., '{"hospital": "Domat Al Jandal Hospital"}') + fields: JSON string of fields to return (e.g., '["hospital", "modality"]') + 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: + { + "pm_schedules": [...], + "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 = PM_SCHEDULE_GENERATOR_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('PM Schedule Generator', filters=filters or {}) + + # Get PM schedules + pm_schedules = frappe.get_all( + 'PM Schedule Generator', + 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 pm_schedule in pm_schedules: + pm_schedule['maintenance_entries'] = get_child_table_data( + pm_schedule['name'], + 'maintenance_entries', + 'PM Entry Line', + PM_ENTRY_LINE_FIELDS + ) + + # Calculate has_more + has_more = (int(offset) + int(limit)) < total_count + + frappe.response['message'] = { + 'pm_schedules': pm_schedules, + '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 PM Schedules API Error') + frappe.response['message'] = { + 'error': str(e), + 'pm_schedules': [], + 'total_count': 0 + } + + +@frappe.whitelist(allow_guest=True) +def get_pm_schedule_details(pm_schedule_name): + """ + Get detailed information about a specific PM Schedule Generator + + Args: + pm_schedule_name: Name/ID of the PM Schedule Generator + + Returns: + PM Schedule Generator document with all fields including child tables + """ + try: + if not pm_schedule_name: + frappe.throw(_('PM Schedule Generator name is required')) + + # Check if user has permission to read this PM Schedule + if not frappe.has_permission('PM Schedule Generator', 'read', pm_schedule_name): + frappe.throw(_('Not permitted to access this PM Schedule Generator')) + + # Get PM Schedule details + pm_schedule = frappe.get_doc('PM Schedule Generator', pm_schedule_name) + + # Convert to dict and include child tables + pm_schedule_dict = pm_schedule.as_dict() + + # Ensure child tables are included with all fields + pm_schedule_dict['maintenance_entries'] = [item.as_dict() for item in pm_schedule.get('maintenance_entries', [])] + + frappe.response['message'] = pm_schedule_dict + + except Exception as e: + frappe.log_error(frappe.get_traceback(), 'Get PM Schedule Details API Error') + frappe.response['message'] = { + 'error': str(e) + } + + +@frappe.whitelist(allow_guest=True) +def create_pm_schedule(pm_schedule_data): + """ + Create a new PM Schedule Generator + + Args: + pm_schedule_data: JSON string containing PM Schedule fields including child tables + Example: + { + "hospital": "Domat Al Jandal Hospital", + "modality": "X-Ray", + "start_date": "2025-12-23", + "end_date": "2026-12-23", + "periodicity": "Monthly", + "maintenance_team": "DAJH Maintenance Team", + "maintenance_manager": "manager@example.com", + "assign_to": "technician@example.com", + "due_date": "2026-01-23", + "maintenance_entries": [ + { + "asset": "ACC-ASS-2025-00100", + "asset_name": "Test Asset 1", + "start_date": "2025-12-23", + "end_date": "2026-12-23", + "manufacturer": "ABV", + "model": "" + } + ] + } + + Returns: + Created PM Schedule Generator document + """ + try: + import json + + # Parse PM schedule data + if isinstance(pm_schedule_data, str): + pm_schedule_data = json.loads(pm_schedule_data) + + # Check if user has permission to create PM Schedule + if not frappe.has_permission('PM Schedule Generator', 'create'): + frappe.throw(_('Not permitted to create PM Schedule Generator')) + + # Create new PM Schedule + pm_schedule = frappe.get_doc({ + 'doctype': 'PM Schedule Generator', + **pm_schedule_data + }) + + pm_schedule.insert() + frappe.db.commit() + + # Return created PM Schedule with child tables + pm_schedule_dict = pm_schedule.as_dict() + pm_schedule_dict['maintenance_entries'] = [item.as_dict() for item in pm_schedule.get('maintenance_entries', [])] + + frappe.response['message'] = { + 'success': True, + 'pm_schedule': pm_schedule_dict, + 'message': _('PM Schedule Generator created successfully') + } + + except Exception as e: + frappe.db.rollback() + frappe.log_error(frappe.get_traceback(), 'Create PM Schedule API Error') + frappe.response['message'] = { + 'success': False, + 'error': str(e) + } + + +@frappe.whitelist(allow_guest=True) +def update_pm_schedule(pm_schedule_name, pm_schedule_data): + """ + Update an existing PM Schedule Generator including child tables + + Args: + pm_schedule_name: Name/ID of the PM Schedule Generator + pm_schedule_data: JSON string containing fields to update + Example: + { + "periodicity": "Quarterly", + "maintenance_entries": [ + { + "asset": "ACC-ASS-2025-00100", + "asset_name": "Test Asset 1", + "start_date": "2025-12-23", + "end_date": "2026-12-23" + } + ] + } + + Returns: + Updated PM Schedule Generator document + """ + try: + import json + + if not pm_schedule_name: + frappe.throw(_('PM Schedule Generator name is required')) + + # Parse PM schedule data + if isinstance(pm_schedule_data, str): + pm_schedule_data = json.loads(pm_schedule_data) + + # Check if user has permission to update this PM Schedule + if not frappe.has_permission('PM Schedule Generator', 'write', pm_schedule_name): + frappe.throw(_('Not permitted to update this PM Schedule Generator')) + + # Get PM Schedule + pm_schedule = frappe.get_doc('PM Schedule Generator', pm_schedule_name) + + # Handle child tables separately + child_tables = ['maintenance_entries'] + + for key, value in pm_schedule_data.items(): + if key in child_tables: + # Clear existing child table entries and add new ones + pm_schedule.set(key, []) + for item in value: + pm_schedule.append(key, item) + elif hasattr(pm_schedule, key): + setattr(pm_schedule, key, value) + + pm_schedule.save() + frappe.db.commit() + + # Return updated PM Schedule with child tables + pm_schedule_dict = pm_schedule.as_dict() + pm_schedule_dict['maintenance_entries'] = [item.as_dict() for item in pm_schedule.get('maintenance_entries', [])] + + frappe.response['message'] = { + 'success': True, + 'pm_schedule': pm_schedule_dict, + 'message': _('PM Schedule Generator updated successfully') + } + + except Exception as e: + frappe.db.rollback() + frappe.log_error(frappe.get_traceback(), 'Update PM Schedule API Error') + frappe.response['message'] = { + 'success': False, + 'error': str(e) + } + + +@frappe.whitelist(allow_guest=True) +def delete_pm_schedule(pm_schedule_name): + """ + Delete a PM Schedule Generator + + Args: + pm_schedule_name: Name/ID of the PM Schedule Generator + + Returns: + Success message + """ + try: + if not pm_schedule_name: + frappe.throw(_('PM Schedule Generator name is required')) + + # Check if user has permission to delete this PM Schedule + if not frappe.has_permission('PM Schedule Generator', 'delete', pm_schedule_name): + frappe.throw(_('Not permitted to delete this PM Schedule Generator')) + + # Delete PM Schedule (child tables will be deleted automatically) + frappe.delete_doc('PM Schedule Generator', pm_schedule_name) + frappe.db.commit() + + frappe.response['message'] = { + 'success': True, + 'message': _('PM Schedule Generator deleted successfully') + } + + except Exception as e: + frappe.db.rollback() + frappe.log_error(frappe.get_traceback(), 'Delete PM Schedule API Error') + frappe.response['message'] = { + 'success': False, + 'error': str(e) + } + + +@frappe.whitelist(allow_guest=True) +def submit_pm_schedule(pm_schedule_name): + """ + Submit a PM Schedule Generator (change docstatus to 1) + + Args: + pm_schedule_name: Name/ID of the PM Schedule Generator + + Returns: + Submitted PM Schedule Generator document + """ + try: + if not pm_schedule_name: + frappe.throw(_('PM Schedule Generator name is required')) + + # Check if user has permission to submit this PM Schedule + if not frappe.has_permission('PM Schedule Generator', 'submit', pm_schedule_name): + frappe.throw(_('Not permitted to submit this PM Schedule Generator')) + + # Get and submit PM Schedule + pm_schedule = frappe.get_doc('PM Schedule Generator', pm_schedule_name) + pm_schedule.submit() + frappe.db.commit() + + # Return submitted PM Schedule with child tables + pm_schedule_dict = pm_schedule.as_dict() + pm_schedule_dict['maintenance_entries'] = [item.as_dict() for item in pm_schedule.get('maintenance_entries', [])] + + frappe.response['message'] = { + 'success': True, + 'pm_schedule': pm_schedule_dict, + 'message': _('PM Schedule Generator submitted successfully') + } + + except Exception as e: + frappe.db.rollback() + frappe.log_error(frappe.get_traceback(), 'Submit PM Schedule API Error') + frappe.response['message'] = { + 'success': False, + 'error': str(e) + } + + +@frappe.whitelist(allow_guest=True) +def cancel_pm_schedule(pm_schedule_name): + """ + Cancel a PM Schedule Generator (change docstatus to 2) + + Args: + pm_schedule_name: Name/ID of the PM Schedule Generator + + Returns: + Cancelled PM Schedule Generator document + """ + try: + if not pm_schedule_name: + frappe.throw(_('PM Schedule Generator name is required')) + + # Check if user has permission to cancel this PM Schedule + if not frappe.has_permission('PM Schedule Generator', 'cancel', pm_schedule_name): + frappe.throw(_('Not permitted to cancel this PM Schedule Generator')) + + # Get and cancel PM Schedule + pm_schedule = frappe.get_doc('PM Schedule Generator', pm_schedule_name) + pm_schedule.cancel() + frappe.db.commit() + + # Return cancelled PM Schedule with child tables + pm_schedule_dict = pm_schedule.as_dict() + pm_schedule_dict['maintenance_entries'] = [item.as_dict() for item in pm_schedule.get('maintenance_entries', [])] + + frappe.response['message'] = { + 'success': True, + 'pm_schedule': pm_schedule_dict, + 'message': _('PM Schedule Generator cancelled successfully') + } + + except Exception as e: + frappe.db.rollback() + frappe.log_error(frappe.get_traceback(), 'Cancel PM Schedule API Error') + frappe.response['message'] = { + 'success': False, + 'error': str(e) + } + + +@frappe.whitelist(allow_guest=True) +def add_maintenance_entry(pm_schedule_name, entry_data): + """ + Add a maintenance entry to PM Schedule's maintenance_entries child table + + Args: + pm_schedule_name: Name/ID of the PM Schedule Generator + entry_data: JSON string containing maintenance entry fields + Example: + { + "asset": "ACC-ASS-2025-00100", + "asset_name": "Test Asset 1", + "start_date": "2025-12-23", + "end_date": "2026-12-23", + "manufacturer": "ABV", + "model": "Model X" + } + + Returns: + Updated PM Schedule with maintenance entries + """ + try: + import json + + if not pm_schedule_name: + frappe.throw(_('PM Schedule Generator name is required')) + + # Parse entry data + if isinstance(entry_data, str): + entry_data = json.loads(entry_data) + + # Check if user has permission to update this PM Schedule + if not frappe.has_permission('PM Schedule Generator', 'write', pm_schedule_name): + frappe.throw(_('Not permitted to update this PM Schedule Generator')) + + # Get PM Schedule and add maintenance entry + pm_schedule = frappe.get_doc('PM Schedule Generator', pm_schedule_name) + pm_schedule.append('maintenance_entries', entry_data) + pm_schedule.save() + frappe.db.commit() + + frappe.response['message'] = { + 'success': True, + 'maintenance_entries': [item.as_dict() for item in pm_schedule.get('maintenance_entries', [])], + 'message': _('Maintenance entry added successfully') + } + + except Exception as e: + frappe.db.rollback() + frappe.log_error(frappe.get_traceback(), 'Add Maintenance Entry API Error') + frappe.response['message'] = { + 'success': False, + 'error': str(e) + } + + +@frappe.whitelist(allow_guest=True) +def remove_maintenance_entry(pm_schedule_name, entry_name): + """ + Remove a maintenance entry from PM Schedule's maintenance_entries child table + + Args: + pm_schedule_name: Name/ID of the PM Schedule Generator + entry_name: Name/ID of the maintenance entry to remove + + Returns: + Updated PM Schedule with remaining maintenance entries + """ + try: + if not pm_schedule_name: + frappe.throw(_('PM Schedule Generator name is required')) + + if not entry_name: + frappe.throw(_('Maintenance entry name is required')) + + # Check if user has permission to update this PM Schedule + if not frappe.has_permission('PM Schedule Generator', 'write', pm_schedule_name): + frappe.throw(_('Not permitted to update this PM Schedule Generator')) + + # Get PM Schedule + pm_schedule = frappe.get_doc('PM Schedule Generator', pm_schedule_name) + + # Find and remove the maintenance entry + entry_to_remove = None + for entry in pm_schedule.maintenance_entries: + if entry.name == entry_name: + entry_to_remove = entry + break + + if entry_to_remove: + pm_schedule.remove(entry_to_remove) + pm_schedule.save() + frappe.db.commit() + + frappe.response['message'] = { + 'success': True, + 'maintenance_entries': [item.as_dict() for item in pm_schedule.get('maintenance_entries', [])], + 'message': _('Maintenance entry removed successfully') + } + else: + frappe.response['message'] = { + 'success': False, + 'error': _('Maintenance entry not found') + } + + except Exception as e: + frappe.db.rollback() + frappe.log_error(frappe.get_traceback(), 'Remove Maintenance Entry API Error') + frappe.response['message'] = { + 'success': False, + 'error': str(e) + } + + +@frappe.whitelist(allow_guest=True) +def update_maintenance_entry(pm_schedule_name, entry_name, entry_data): + """ + Update a specific maintenance entry in the PM Schedule + + Args: + pm_schedule_name: Name/ID of the PM Schedule Generator + entry_name: Name/ID of the maintenance entry to update + entry_data: JSON string containing fields to update + + Returns: + Updated PM Schedule with maintenance entries + """ + try: + import json + + if not pm_schedule_name: + frappe.throw(_('PM Schedule Generator name is required')) + + if not entry_name: + frappe.throw(_('Maintenance entry name is required')) + + # Parse entry data + if isinstance(entry_data, str): + entry_data = json.loads(entry_data) + + # Check if user has permission to update this PM Schedule + if not frappe.has_permission('PM Schedule Generator', 'write', pm_schedule_name): + frappe.throw(_('Not permitted to update this PM Schedule Generator')) + + # Get PM Schedule + pm_schedule = frappe.get_doc('PM Schedule Generator', pm_schedule_name) + + # Find and update the maintenance entry + entry_found = False + for entry in pm_schedule.maintenance_entries: + if entry.name == entry_name: + for key, value in entry_data.items(): + if hasattr(entry, key): + setattr(entry, key, value) + entry_found = True + break + + if entry_found: + pm_schedule.save() + frappe.db.commit() + + frappe.response['message'] = { + 'success': True, + 'maintenance_entries': [item.as_dict() for item in pm_schedule.get('maintenance_entries', [])], + 'message': _('Maintenance entry updated successfully') + } + else: + frappe.response['message'] = { + 'success': False, + 'error': _('Maintenance entry not found') + } + + except Exception as e: + frappe.db.rollback() + frappe.log_error(frappe.get_traceback(), 'Update Maintenance Entry API Error') + frappe.response['message'] = { + 'success': False, + 'error': str(e) + } + + +@frappe.whitelist(allow_guest=True) +def get_pm_schedule_child_tables(pm_schedule_name, child_table=None): + """ + Get child table data for a PM Schedule Generator + + Args: + pm_schedule_name: Name/ID of the PM Schedule Generator + child_table: Specific child table to return ('maintenance_entries') + If None, returns all child tables + + Returns: + Child table data + """ + try: + if not pm_schedule_name: + frappe.throw(_('PM Schedule Generator name is required')) + + # Check if user has permission to read this PM Schedule + if not frappe.has_permission('PM Schedule Generator', 'read', pm_schedule_name): + frappe.throw(_('Not permitted to access this PM Schedule Generator')) + + # Get PM Schedule + pm_schedule = frappe.get_doc('PM Schedule Generator', pm_schedule_name) + + result = {} + + if child_table: + if child_table == 'maintenance_entries': + result['maintenance_entries'] = [item.as_dict() for item in pm_schedule.get('maintenance_entries', [])] + else: + frappe.throw(_('Invalid child table name')) + else: + result = { + 'maintenance_entries': [item.as_dict() for item in pm_schedule.get('maintenance_entries', [])] + } + + frappe.response['message'] = { + 'success': True, + 'pm_schedule_name': pm_schedule_name, + **result + } + + except Exception as e: + frappe.log_error(frappe.get_traceback(), 'Get PM Schedule Child Tables API Error') + frappe.response['message'] = { + 'success': False, + 'error': str(e) + } + + +@frappe.whitelist(allow_guest=True) +def bulk_update_maintenance_entries(pm_schedule_name, maintenance_entries): + """ + Bulk update/replace all maintenance entries in a PM Schedule Generator + + Args: + pm_schedule_name: Name/ID of the PM Schedule Generator + maintenance_entries: JSON string containing list of maintenance entries + + Returns: + Updated PM Schedule with new maintenance entries + """ + try: + import json + + if not pm_schedule_name: + frappe.throw(_('PM Schedule Generator name is required')) + + # Parse maintenance entries + if isinstance(maintenance_entries, str): + maintenance_entries = json.loads(maintenance_entries) + + # Check if user has permission to update this PM Schedule + if not frappe.has_permission('PM Schedule Generator', 'write', pm_schedule_name): + frappe.throw(_('Not permitted to update this PM Schedule Generator')) + + # Get PM Schedule + pm_schedule = frappe.get_doc('PM Schedule Generator', pm_schedule_name) + + # Clear existing maintenance entries and add new ones + pm_schedule.set('maintenance_entries', []) + for entry in maintenance_entries: + pm_schedule.append('maintenance_entries', entry) + + pm_schedule.save() + frappe.db.commit() + + frappe.response['message'] = { + 'success': True, + 'maintenance_entries': [item.as_dict() for item in pm_schedule.get('maintenance_entries', [])], + 'message': _('Maintenance entries updated successfully') + } + + except Exception as e: + frappe.db.rollback() + frappe.log_error(frappe.get_traceback(), 'Bulk Update Maintenance Entries API Error') + frappe.response['message'] = { + 'success': False, + 'error': str(e) + } + + +@frappe.whitelist(allow_guest=True) +def get_pm_schedules_by_hospital(hospital, limit=20, offset=0): + """ + Get PM Schedules filtered by hospital + + Args: + hospital: Hospital name to filter by + limit: Number of records to return (default: 20) + offset: Number of records to skip (default: 0) + + Returns: + List of PM Schedules for the specified hospital + """ + try: + if not hospital: + frappe.throw(_('Hospital name is required')) + + filters = {'hospital': hospital} + + # Get total count + total_count = frappe.db.count('PM Schedule Generator', filters=filters) + + # Get PM schedules + pm_schedules = frappe.get_all( + 'PM Schedule Generator', + filters=filters, + fields=PM_SCHEDULE_GENERATOR_FIELDS, + limit_page_length=int(limit), + limit_start=int(offset), + order_by='creation desc' + ) + + # Include child tables + for pm_schedule in pm_schedules: + pm_schedule['maintenance_entries'] = get_child_table_data( + pm_schedule['name'], + 'maintenance_entries', + 'PM Entry Line', + PM_ENTRY_LINE_FIELDS + ) + + # Calculate has_more + has_more = (int(offset) + int(limit)) < total_count + + frappe.response['message'] = { + 'pm_schedules': pm_schedules, + '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 PM Schedules By Hospital API Error') + frappe.response['message'] = { + 'error': str(e), + 'pm_schedules': [], + 'total_count': 0 + } + + +@frappe.whitelist(allow_guest=True) +def get_pm_schedules_by_maintenance_team(maintenance_team, limit=20, offset=0): + """ + Get PM Schedules filtered by maintenance team + + Args: + maintenance_team: Maintenance team name to filter by + limit: Number of records to return (default: 20) + offset: Number of records to skip (default: 0) + + Returns: + List of PM Schedules for the specified maintenance team + """ + try: + if not maintenance_team: + frappe.throw(_('Maintenance team name is required')) + + filters = {'maintenance_team': maintenance_team} + + # Get total count + total_count = frappe.db.count('PM Schedule Generator', filters=filters) + + # Get PM schedules + pm_schedules = frappe.get_all( + 'PM Schedule Generator', + filters=filters, + fields=PM_SCHEDULE_GENERATOR_FIELDS, + limit_page_length=int(limit), + limit_start=int(offset), + order_by='creation desc' + ) + + # Include child tables + for pm_schedule in pm_schedules: + pm_schedule['maintenance_entries'] = get_child_table_data( + pm_schedule['name'], + 'maintenance_entries', + 'PM Entry Line', + PM_ENTRY_LINE_FIELDS + ) + + # Calculate has_more + has_more = (int(offset) + int(limit)) < total_count + + frappe.response['message'] = { + 'pm_schedules': pm_schedules, + '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 PM Schedules By Maintenance Team API Error') + frappe.response['message'] = { + 'error': str(e), + 'pm_schedules': [], + 'total_count': 0 + } + + +@frappe.whitelist(allow_guest=True) +def get_pm_schedules_by_assignee(assign_to, limit=20, offset=0): + """ + Get PM Schedules filtered by assigned user + + Args: + assign_to: Assigned user email to filter by + limit: Number of records to return (default: 20) + offset: Number of records to skip (default: 0) + + Returns: + List of PM Schedules assigned to the specified user + """ + try: + if not assign_to: + frappe.throw(_('Assignee email is required')) + + filters = {'assign_to': assign_to} + + # Get total count + total_count = frappe.db.count('PM Schedule Generator', filters=filters) + + # Get PM schedules + pm_schedules = frappe.get_all( + 'PM Schedule Generator', + filters=filters, + fields=PM_SCHEDULE_GENERATOR_FIELDS, + limit_page_length=int(limit), + limit_start=int(offset), + order_by='due_date asc' + ) + + # Include child tables + for pm_schedule in pm_schedules: + pm_schedule['maintenance_entries'] = get_child_table_data( + pm_schedule['name'], + 'maintenance_entries', + 'PM Entry Line', + PM_ENTRY_LINE_FIELDS + ) + + # Calculate has_more + has_more = (int(offset) + int(limit)) < total_count + + frappe.response['message'] = { + 'pm_schedules': pm_schedules, + '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 PM Schedules By Assignee API Error') + frappe.response['message'] = { + 'error': str(e), + 'pm_schedules': [], + 'total_count': 0 + } + + +@frappe.whitelist(allow_guest=True) +def get_pm_schedules_by_date_range(start_date=None, end_date=None, limit=20, offset=0): + """ + Get PM Schedules within a date range + + Args: + start_date: Start date filter (YYYY-MM-DD) + end_date: End date filter (YYYY-MM-DD) + limit: Number of records to return (default: 20) + offset: Number of records to skip (default: 0) + + Returns: + List of PM Schedules within the specified date range + """ + try: + filters = {} + + if start_date: + filters['start_date'] = ['>=', start_date] + + if end_date: + filters['end_date'] = ['<=', end_date] + + # Get total count + total_count = frappe.db.count('PM Schedule Generator', filters=filters) + + # Get PM schedules + pm_schedules = frappe.get_all( + 'PM Schedule Generator', + filters=filters, + fields=PM_SCHEDULE_GENERATOR_FIELDS, + limit_page_length=int(limit), + limit_start=int(offset), + order_by='start_date asc' + ) + + # Include child tables + for pm_schedule in pm_schedules: + pm_schedule['maintenance_entries'] = get_child_table_data( + pm_schedule['name'], + 'maintenance_entries', + 'PM Entry Line', + PM_ENTRY_LINE_FIELDS + ) + + # Calculate has_more + has_more = (int(offset) + int(limit)) < total_count + + frappe.response['message'] = { + 'pm_schedules': pm_schedules, + '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 PM Schedules By Date Range API Error') + frappe.response['message'] = { + 'error': str(e), + 'pm_schedules': [], + 'total_count': 0 + } + + +@frappe.whitelist(allow_guest=True) +def get_pm_schedules_by_periodicity(periodicity, limit=20, offset=0): + """ + Get PM Schedules filtered by periodicity + + Args: + periodicity: Periodicity to filter by (e.g., 'Monthly', 'Quarterly', 'Yearly') + limit: Number of records to return (default: 20) + offset: Number of records to skip (default: 0) + + Returns: + List of PM Schedules with the specified periodicity + """ + try: + if not periodicity: + frappe.throw(_('Periodicity is required')) + + filters = {'periodicity': periodicity} + + # Get total count + total_count = frappe.db.count('PM Schedule Generator', filters=filters) + + # Get PM schedules + pm_schedules = frappe.get_all( + 'PM Schedule Generator', + filters=filters, + fields=PM_SCHEDULE_GENERATOR_FIELDS, + limit_page_length=int(limit), + limit_start=int(offset), + order_by='creation desc' + ) + + # Include child tables + for pm_schedule in pm_schedules: + pm_schedule['maintenance_entries'] = get_child_table_data( + pm_schedule['name'], + 'maintenance_entries', + 'PM Entry Line', + PM_ENTRY_LINE_FIELDS + ) + + # Calculate has_more + has_more = (int(offset) + int(limit)) < total_count + + frappe.response['message'] = { + 'pm_schedules': pm_schedules, + '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 PM Schedules By Periodicity API Error') + frappe.response['message'] = { + 'error': str(e), + 'pm_schedules': [], + 'total_count': 0 + } + + +@frappe.whitelist(allow_guest=True) +def get_overdue_pm_schedules(limit=20, offset=0): + """ + Get PM Schedules that are overdue (due_date < today and docstatus != 2) + + Args: + limit: Number of records to return (default: 20) + offset: Number of records to skip (default: 0) + + Returns: + List of overdue PM Schedules + """ + try: + from frappe.utils import today + + filters = { + 'due_date': ['<', today()], + 'docstatus': ['!=', 2] # Exclude cancelled + } + + # Get total count + total_count = frappe.db.count('PM Schedule Generator', filters=filters) + + # Get PM schedules + pm_schedules = frappe.get_all( + 'PM Schedule Generator', + filters=filters, + fields=PM_SCHEDULE_GENERATOR_FIELDS, + limit_page_length=int(limit), + limit_start=int(offset), + order_by='due_date asc' + ) + + # Include child tables + for pm_schedule in pm_schedules: + pm_schedule['maintenance_entries'] = get_child_table_data( + pm_schedule['name'], + 'maintenance_entries', + 'PM Entry Line', + PM_ENTRY_LINE_FIELDS + ) + + # Calculate has_more + has_more = (int(offset) + int(limit)) < total_count + + frappe.response['message'] = { + 'pm_schedules': pm_schedules, + '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 PM Schedules API Error') + frappe.response['message'] = { + 'error': str(e), + 'pm_schedules': [], + 'total_count': 0 + } + + +@frappe.whitelist(allow_guest=True) +def get_upcoming_pm_schedules(days=30, limit=20, offset=0): + """ + Get PM Schedules due within the specified number of days + + Args: + days: Number of days to look ahead (default: 30) + limit: Number of records to return (default: 20) + offset: Number of records to skip (default: 0) + + Returns: + List of upcoming PM Schedules + """ + try: + from frappe.utils import today, add_days + + filters = { + 'due_date': ['between', [today(), add_days(today(), int(days))]], + 'docstatus': ['!=', 2] # Exclude cancelled + } + + # Get total count + total_count = frappe.db.count('PM Schedule Generator', filters=filters) + + # Get PM schedules + pm_schedules = frappe.get_all( + 'PM Schedule Generator', + filters=filters, + fields=PM_SCHEDULE_GENERATOR_FIELDS, + limit_page_length=int(limit), + limit_start=int(offset), + order_by='due_date asc' + ) + + # Include child tables + for pm_schedule in pm_schedules: + pm_schedule['maintenance_entries'] = get_child_table_data( + pm_schedule['name'], + 'maintenance_entries', + 'PM Entry Line', + PM_ENTRY_LINE_FIELDS + ) + + # Calculate has_more + has_more = (int(offset) + int(limit)) < total_count + + frappe.response['message'] = { + 'pm_schedules': pm_schedules, + '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 Upcoming PM Schedules API Error') + frappe.response['message'] = { + 'error': str(e), + 'pm_schedules': [], + 'total_count': 0 + } \ No newline at end of file diff --git a/asset_lite/api/room_filter.py b/asset_lite/api/room_filter.py new file mode 100644 index 0000000..9e617c3 --- /dev/null +++ b/asset_lite/api/room_filter.py @@ -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 diff --git a/asset_lite/api/translation_api.py b/asset_lite/api/translation_api.py new file mode 100644 index 0000000..33b34c8 --- /dev/null +++ b/asset_lite/api/translation_api.py @@ -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 + } \ No newline at end of file diff --git a/asset_lite/api/user_roles.py b/asset_lite/api/user_roles.py new file mode 100644 index 0000000..aa0b680 --- /dev/null +++ b/asset_lite/api/user_roles.py @@ -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" + } diff --git a/asset_lite/api/userperm_api.py b/asset_lite/api/userperm_api.py new file mode 100644 index 0000000..0a37d0b --- /dev/null +++ b/asset_lite/api/userperm_api.py @@ -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} diff --git a/asset_lite/api/work_order_api.py b/asset_lite/api/work_order_api.py new file mode 100644 index 0000000..83678e0 --- /dev/null +++ b/asset_lite/api/work_order_api.py @@ -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) + } \ No newline at end of file diff --git a/asset_lite/asset_lite/__init__.py b/asset_lite/asset_lite/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/dashboard_chart_source/active_map_widget/active_map_widget.js b/asset_lite/asset_lite/dashboard_chart_source/active_map_widget/active_map_widget.js new file mode 100644 index 0000000..de66774 --- /dev/null +++ b/asset_lite/asset_lite/dashboard_chart_source/active_map_widget/active_map_widget.js @@ -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 = ` +
+

Custom Dashboard Content

+
+
+
+
Card Title 1
+

Your custom content here

+ +
+
+
+
+
Card Title 2
+

More custom content

+
+
75%
+
+
+
+
+
+ `; + chart_container.html(custom_html); + return; + } + // Call original render method for other charts + return original_render.call(this, chart_data, chart_container); + }; +}); + diff --git a/asset_lite/asset_lite/dashboard_chart_source/active_map_widget/active_map_widget.json b/asset_lite/asset_lite/dashboard_chart_source/active_map_widget/active_map_widget.json new file mode 100644 index 0000000..3ac228f --- /dev/null +++ b/asset_lite/asset_lite/dashboard_chart_source/active_map_widget/active_map_widget.json @@ -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 +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/__init__.py b/asset_lite/asset_lite/doctype/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/agent/__init__.py b/asset_lite/asset_lite/doctype/agent/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/agent/agent.js b/asset_lite/asset_lite/doctype/agent/agent.js new file mode 100644 index 0000000..60d397e --- /dev/null +++ b/asset_lite/asset_lite/doctype/agent/agent.js @@ -0,0 +1,8 @@ +// Copyright (c) 2024, seyfert and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Agent", { +// refresh(frm) { + +// }, +// }); diff --git a/asset_lite/asset_lite/doctype/agent/agent.json b/asset_lite/asset_lite/doctype/agent/agent.json new file mode 100644 index 0000000..b71b9f3 --- /dev/null +++ b/asset_lite/asset_lite/doctype/agent/agent.json @@ -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": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/agent/agent.py b/asset_lite/asset_lite/doctype/agent/agent.py new file mode 100644 index 0000000..c8ff934 --- /dev/null +++ b/asset_lite/asset_lite/doctype/agent/agent.py @@ -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 diff --git a/asset_lite/asset_lite/doctype/agent/test_agent.py b/asset_lite/asset_lite/doctype/agent/test_agent.py new file mode 100644 index 0000000..ec44bae --- /dev/null +++ b/asset_lite/asset_lite/doctype/agent/test_agent.py @@ -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 diff --git a/asset_lite/asset_lite/doctype/arabic_names/__init__.py b/asset_lite/asset_lite/doctype/arabic_names/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/arabic_names/arabic_names.js b/asset_lite/asset_lite/doctype/arabic_names/arabic_names.js new file mode 100644 index 0000000..51ffe24 --- /dev/null +++ b/asset_lite/asset_lite/doctype/arabic_names/arabic_names.js @@ -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) { + +// }, +// }); diff --git a/asset_lite/asset_lite/doctype/arabic_names/arabic_names.json b/asset_lite/asset_lite/doctype/arabic_names/arabic_names.json new file mode 100644 index 0000000..cb19433 --- /dev/null +++ b/asset_lite/asset_lite/doctype/arabic_names/arabic_names.json @@ -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": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/arabic_names/arabic_names.py b/asset_lite/asset_lite/doctype/arabic_names/arabic_names.py new file mode 100644 index 0000000..a812f42 --- /dev/null +++ b/asset_lite/asset_lite/doctype/arabic_names/arabic_names.py @@ -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 diff --git a/asset_lite/asset_lite/doctype/arabic_names/test_arabic_names.py b/asset_lite/asset_lite/doctype/arabic_names/test_arabic_names.py new file mode 100644 index 0000000..66eeb6d --- /dev/null +++ b/asset_lite/asset_lite/doctype/arabic_names/test_arabic_names.py @@ -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 diff --git a/asset_lite/asset_lite/doctype/asset_item_transfer/__init__.py b/asset_lite/asset_lite/doctype/asset_item_transfer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/asset_item_transfer/asset_item_transfer.json b/asset_lite/asset_lite/doctype/asset_item_transfer/asset_item_transfer.json new file mode 100644 index 0000000..3dfde9a --- /dev/null +++ b/asset_lite/asset_lite/doctype/asset_item_transfer/asset_item_transfer.json @@ -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": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/asset_item_transfer/asset_item_transfer.py b/asset_lite/asset_lite/doctype/asset_item_transfer/asset_item_transfer.py new file mode 100644 index 0000000..cc08c50 --- /dev/null +++ b/asset_lite/asset_lite/doctype/asset_item_transfer/asset_item_transfer.py @@ -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 diff --git a/asset_lite/asset_lite/doctype/asset_type/__init__.py b/asset_lite/asset_lite/doctype/asset_type/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/asset_type/asset_type.js b/asset_lite/asset_lite/doctype/asset_type/asset_type.js new file mode 100644 index 0000000..bf166ba --- /dev/null +++ b/asset_lite/asset_lite/doctype/asset_type/asset_type.js @@ -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) { + +// }, +// }); diff --git a/asset_lite/asset_lite/doctype/asset_type/asset_type.json b/asset_lite/asset_lite/doctype/asset_type/asset_type.json new file mode 100644 index 0000000..5c0b22e --- /dev/null +++ b/asset_lite/asset_lite/doctype/asset_type/asset_type.json @@ -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": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/asset_type/asset_type.py b/asset_lite/asset_lite/doctype/asset_type/asset_type.py new file mode 100644 index 0000000..40625d4 --- /dev/null +++ b/asset_lite/asset_lite/doctype/asset_type/asset_type.py @@ -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 diff --git a/asset_lite/asset_lite/doctype/asset_type/test_asset_type.py b/asset_lite/asset_lite/doctype/asset_type/test_asset_type.py new file mode 100644 index 0000000..ab898f7 --- /dev/null +++ b/asset_lite/asset_lite/doctype/asset_type/test_asset_type.py @@ -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 diff --git a/asset_lite/asset_lite/doctype/building/__init__.py b/asset_lite/asset_lite/doctype/building/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/building/building.js b/asset_lite/asset_lite/doctype/building/building.js new file mode 100644 index 0000000..79c1886 --- /dev/null +++ b/asset_lite/asset_lite/doctype/building/building.js @@ -0,0 +1,8 @@ +// Copyright (c) 2026, seyfert and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Building", { +// refresh(frm) { + +// }, +// }); diff --git a/asset_lite/asset_lite/doctype/building/building.json b/asset_lite/asset_lite/doctype/building/building.json new file mode 100644 index 0000000..953bda1 --- /dev/null +++ b/asset_lite/asset_lite/doctype/building/building.json @@ -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": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/building/building.py b/asset_lite/asset_lite/doctype/building/building.py new file mode 100644 index 0000000..916ca56 --- /dev/null +++ b/asset_lite/asset_lite/doctype/building/building.py @@ -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 diff --git a/asset_lite/asset_lite/doctype/building/test_building.py b/asset_lite/asset_lite/doctype/building/test_building.py new file mode 100644 index 0000000..25d3aab --- /dev/null +++ b/asset_lite/asset_lite/doctype/building/test_building.py @@ -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 diff --git a/asset_lite/asset_lite/doctype/city/__init__.py b/asset_lite/asset_lite/doctype/city/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/city/city.js b/asset_lite/asset_lite/doctype/city/city.js new file mode 100644 index 0000000..fd9a4ed --- /dev/null +++ b/asset_lite/asset_lite/doctype/city/city.js @@ -0,0 +1,8 @@ +// Copyright (c) 2025, seyfert and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("City", { +// refresh(frm) { + +// }, +// }); diff --git a/asset_lite/asset_lite/doctype/city/city.json b/asset_lite/asset_lite/doctype/city/city.json new file mode 100644 index 0000000..63055c8 --- /dev/null +++ b/asset_lite/asset_lite/doctype/city/city.json @@ -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": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/city/city.py b/asset_lite/asset_lite/doctype/city/city.py new file mode 100644 index 0000000..a480771 --- /dev/null +++ b/asset_lite/asset_lite/doctype/city/city.py @@ -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 diff --git a/asset_lite/asset_lite/doctype/city/test_city.py b/asset_lite/asset_lite/doctype/city/test_city.py new file mode 100644 index 0000000..95d3486 --- /dev/null +++ b/asset_lite/asset_lite/doctype/city/test_city.py @@ -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 diff --git a/asset_lite/asset_lite/doctype/delete_request/__init__.py b/asset_lite/asset_lite/doctype/delete_request/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/delete_request/delete_request.js b/asset_lite/asset_lite/doctype/delete_request/delete_request.js new file mode 100644 index 0000000..f9f3a8e --- /dev/null +++ b/asset_lite/asset_lite/doctype/delete_request/delete_request.js @@ -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) { + +// }, +// }); diff --git a/asset_lite/asset_lite/doctype/delete_request/delete_request.json b/asset_lite/asset_lite/doctype/delete_request/delete_request.json new file mode 100644 index 0000000..a536561 --- /dev/null +++ b/asset_lite/asset_lite/doctype/delete_request/delete_request.json @@ -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": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/delete_request/delete_request.py b/asset_lite/asset_lite/doctype/delete_request/delete_request.py new file mode 100644 index 0000000..de5c97c --- /dev/null +++ b/asset_lite/asset_lite/doctype/delete_request/delete_request.py @@ -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 diff --git a/asset_lite/asset_lite/doctype/delete_request/test_delete_request.py b/asset_lite/asset_lite/doctype/delete_request/test_delete_request.py new file mode 100644 index 0000000..6429d8f --- /dev/null +++ b/asset_lite/asset_lite/doctype/delete_request/test_delete_request.py @@ -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 diff --git a/asset_lite/asset_lite/doctype/ecri_umdns/__init__.py b/asset_lite/asset_lite/doctype/ecri_umdns/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/ecri_umdns/ecri_umdns.js b/asset_lite/asset_lite/doctype/ecri_umdns/ecri_umdns.js new file mode 100644 index 0000000..c779c86 --- /dev/null +++ b/asset_lite/asset_lite/doctype/ecri_umdns/ecri_umdns.js @@ -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) { + +// }, +// }); diff --git a/asset_lite/asset_lite/doctype/ecri_umdns/ecri_umdns.json b/asset_lite/asset_lite/doctype/ecri_umdns/ecri_umdns.json new file mode 100644 index 0000000..323b38a --- /dev/null +++ b/asset_lite/asset_lite/doctype/ecri_umdns/ecri_umdns.json @@ -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": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/ecri_umdns/ecri_umdns.py b/asset_lite/asset_lite/doctype/ecri_umdns/ecri_umdns.py new file mode 100644 index 0000000..a7d1fd4 --- /dev/null +++ b/asset_lite/asset_lite/doctype/ecri_umdns/ecri_umdns.py @@ -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 diff --git a/asset_lite/asset_lite/doctype/ecri_umdns/test_ecri_umdns.py b/asset_lite/asset_lite/doctype/ecri_umdns/test_ecri_umdns.py new file mode 100644 index 0000000..57b3a75 --- /dev/null +++ b/asset_lite/asset_lite/doctype/ecri_umdns/test_ecri_umdns.py @@ -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 diff --git a/asset_lite/asset_lite/doctype/extension_directory/__init__.py b/asset_lite/asset_lite/doctype/extension_directory/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/extension_directory/extension_directory.js b/asset_lite/asset_lite/doctype/extension_directory/extension_directory.js new file mode 100644 index 0000000..045530f --- /dev/null +++ b/asset_lite/asset_lite/doctype/extension_directory/extension_directory.js @@ -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) { + +// }, +// }); diff --git a/asset_lite/asset_lite/doctype/extension_directory/extension_directory.json b/asset_lite/asset_lite/doctype/extension_directory/extension_directory.json new file mode 100644 index 0000000..42cc50b --- /dev/null +++ b/asset_lite/asset_lite/doctype/extension_directory/extension_directory.json @@ -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": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/extension_directory/extension_directory.py b/asset_lite/asset_lite/doctype/extension_directory/extension_directory.py new file mode 100644 index 0000000..4e13fb1 --- /dev/null +++ b/asset_lite/asset_lite/doctype/extension_directory/extension_directory.py @@ -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 diff --git a/asset_lite/asset_lite/doctype/extension_directory/test_extension_directory.py b/asset_lite/asset_lite/doctype/extension_directory/test_extension_directory.py new file mode 100644 index 0000000..d36dcbf --- /dev/null +++ b/asset_lite/asset_lite/doctype/extension_directory/test_extension_directory.py @@ -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 diff --git a/asset_lite/asset_lite/doctype/feedback/__init__.py b/asset_lite/asset_lite/doctype/feedback/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/feedback/feedback.js b/asset_lite/asset_lite/doctype/feedback/feedback.js new file mode 100644 index 0000000..e344db7 --- /dev/null +++ b/asset_lite/asset_lite/doctype/feedback/feedback.js @@ -0,0 +1,8 @@ +// Copyright (c) 2025, seyfert and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Feedback", { +// refresh(frm) { + +// }, +// }); diff --git a/asset_lite/asset_lite/doctype/feedback/feedback.json b/asset_lite/asset_lite/doctype/feedback/feedback.json new file mode 100644 index 0000000..a58142b --- /dev/null +++ b/asset_lite/asset_lite/doctype/feedback/feedback.json @@ -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": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/feedback/feedback.py b/asset_lite/asset_lite/doctype/feedback/feedback.py new file mode 100644 index 0000000..7858254 --- /dev/null +++ b/asset_lite/asset_lite/doctype/feedback/feedback.py @@ -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 diff --git a/asset_lite/asset_lite/doctype/feedback/test_feedback.py b/asset_lite/asset_lite/doctype/feedback/test_feedback.py new file mode 100644 index 0000000..cc66323 --- /dev/null +++ b/asset_lite/asset_lite/doctype/feedback/test_feedback.py @@ -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 diff --git a/asset_lite/asset_lite/doctype/feedback_parameters/__init__.py b/asset_lite/asset_lite/doctype/feedback_parameters/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/feedback_parameters/feedback_parameters.js b/asset_lite/asset_lite/doctype/feedback_parameters/feedback_parameters.js new file mode 100644 index 0000000..fab322b --- /dev/null +++ b/asset_lite/asset_lite/doctype/feedback_parameters/feedback_parameters.js @@ -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) { + +// }, +// }); diff --git a/asset_lite/asset_lite/doctype/feedback_parameters/feedback_parameters.json b/asset_lite/asset_lite/doctype/feedback_parameters/feedback_parameters.json new file mode 100644 index 0000000..3781a12 --- /dev/null +++ b/asset_lite/asset_lite/doctype/feedback_parameters/feedback_parameters.json @@ -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": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/feedback_parameters/feedback_parameters.py b/asset_lite/asset_lite/doctype/feedback_parameters/feedback_parameters.py new file mode 100644 index 0000000..373160f --- /dev/null +++ b/asset_lite/asset_lite/doctype/feedback_parameters/feedback_parameters.py @@ -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 diff --git a/asset_lite/asset_lite/doctype/feedback_parameters/test_feedback_parameters.py b/asset_lite/asset_lite/doctype/feedback_parameters/test_feedback_parameters.py new file mode 100644 index 0000000..4fe76b5 --- /dev/null +++ b/asset_lite/asset_lite/doctype/feedback_parameters/test_feedback_parameters.py @@ -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 diff --git a/asset_lite/asset_lite/doctype/feedback_table/__init__.py b/asset_lite/asset_lite/doctype/feedback_table/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/feedback_table/feedback_table.json b/asset_lite/asset_lite/doctype/feedback_table/feedback_table.json new file mode 100644 index 0000000..897e1e5 --- /dev/null +++ b/asset_lite/asset_lite/doctype/feedback_table/feedback_table.json @@ -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": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/feedback_table/feedback_table.py b/asset_lite/asset_lite/doctype/feedback_table/feedback_table.py new file mode 100644 index 0000000..0a7e6ec --- /dev/null +++ b/asset_lite/asset_lite/doctype/feedback_table/feedback_table.py @@ -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 diff --git a/asset_lite/asset_lite/doctype/infrastructure_location/__init__.py b/asset_lite/asset_lite/doctype/infrastructure_location/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/infrastructure_location/infrastructure_location.js b/asset_lite/asset_lite/doctype/infrastructure_location/infrastructure_location.js new file mode 100644 index 0000000..cd7ccca --- /dev/null +++ b/asset_lite/asset_lite/doctype/infrastructure_location/infrastructure_location.js @@ -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) { + +// }, +// }); diff --git a/asset_lite/asset_lite/doctype/infrastructure_location/infrastructure_location.json b/asset_lite/asset_lite/doctype/infrastructure_location/infrastructure_location.json new file mode 100644 index 0000000..14b7b76 --- /dev/null +++ b/asset_lite/asset_lite/doctype/infrastructure_location/infrastructure_location.json @@ -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 +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/infrastructure_location/infrastructure_location.py b/asset_lite/asset_lite/doctype/infrastructure_location/infrastructure_location.py new file mode 100644 index 0000000..3b45aa3 --- /dev/null +++ b/asset_lite/asset_lite/doctype/infrastructure_location/infrastructure_location.py @@ -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 diff --git a/asset_lite/asset_lite/doctype/infrastructure_location/test_infrastructure_location.py b/asset_lite/asset_lite/doctype/infrastructure_location/test_infrastructure_location.py new file mode 100644 index 0000000..6aa7b8b --- /dev/null +++ b/asset_lite/asset_lite/doctype/infrastructure_location/test_infrastructure_location.py @@ -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 diff --git a/asset_lite/asset_lite/doctype/inspection/__init__.py b/asset_lite/asset_lite/doctype/inspection/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/inspection/inspection.js b/asset_lite/asset_lite/doctype/inspection/inspection.js new file mode 100644 index 0000000..e9df76b --- /dev/null +++ b/asset_lite/asset_lite/doctype/inspection/inspection.js @@ -0,0 +1,8 @@ +// Copyright (c) 2026, seyfert and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Inspection", { +// refresh(frm) { + +// }, +// }); diff --git a/asset_lite/asset_lite/doctype/inspection/inspection.json b/asset_lite/asset_lite/doctype/inspection/inspection.json new file mode 100644 index 0000000..a1cc93a --- /dev/null +++ b/asset_lite/asset_lite/doctype/inspection/inspection.json @@ -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 +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/inspection/inspection.py b/asset_lite/asset_lite/doctype/inspection/inspection.py new file mode 100644 index 0000000..e1613e0 --- /dev/null +++ b/asset_lite/asset_lite/doctype/inspection/inspection.py @@ -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 diff --git a/asset_lite/asset_lite/doctype/inspection/test_inspection.py b/asset_lite/asset_lite/doctype/inspection/test_inspection.py new file mode 100644 index 0000000..36d3fdd --- /dev/null +++ b/asset_lite/asset_lite/doctype/inspection/test_inspection.py @@ -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 diff --git a/asset_lite/asset_lite/doctype/item_transfer_table/__init__.py b/asset_lite/asset_lite/doctype/item_transfer_table/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/item_transfer_table/item_transfer_table.json b/asset_lite/asset_lite/doctype/item_transfer_table/item_transfer_table.json new file mode 100644 index 0000000..3a92f47 --- /dev/null +++ b/asset_lite/asset_lite/doctype/item_transfer_table/item_transfer_table.json @@ -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": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/item_transfer_table/item_transfer_table.py b/asset_lite/asset_lite/doctype/item_transfer_table/item_transfer_table.py new file mode 100644 index 0000000..bd90683 --- /dev/null +++ b/asset_lite/asset_lite/doctype/item_transfer_table/item_transfer_table.py @@ -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 diff --git a/asset_lite/asset_lite/doctype/material_transfer/__init__.py b/asset_lite/asset_lite/doctype/material_transfer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/material_transfer/material_transfer.js b/asset_lite/asset_lite/doctype/material_transfer/material_transfer.js new file mode 100644 index 0000000..d7c7c5e --- /dev/null +++ b/asset_lite/asset_lite/doctype/material_transfer/material_transfer.js @@ -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) { + +// }, +// }); diff --git a/asset_lite/asset_lite/doctype/material_transfer/material_transfer.json b/asset_lite/asset_lite/doctype/material_transfer/material_transfer.json new file mode 100644 index 0000000..4dc61d5 --- /dev/null +++ b/asset_lite/asset_lite/doctype/material_transfer/material_transfer.json @@ -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": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/material_transfer/material_transfer.py b/asset_lite/asset_lite/doctype/material_transfer/material_transfer.py new file mode 100644 index 0000000..c3f7642 --- /dev/null +++ b/asset_lite/asset_lite/doctype/material_transfer/material_transfer.py @@ -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 diff --git a/asset_lite/asset_lite/doctype/material_transfer/test_material_transfer.py b/asset_lite/asset_lite/doctype/material_transfer/test_material_transfer.py new file mode 100644 index 0000000..f29131b --- /dev/null +++ b/asset_lite/asset_lite/doctype/material_transfer/test_material_transfer.py @@ -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 diff --git a/asset_lite/asset_lite/doctype/mobile_team_site/__init__.py b/asset_lite/asset_lite/doctype/mobile_team_site/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/mobile_team_site/mobile_team_site.js b/asset_lite/asset_lite/doctype/mobile_team_site/mobile_team_site.js new file mode 100644 index 0000000..83d40e1 --- /dev/null +++ b/asset_lite/asset_lite/doctype/mobile_team_site/mobile_team_site.js @@ -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) { + +// }, +// }); diff --git a/asset_lite/asset_lite/doctype/mobile_team_site/mobile_team_site.json b/asset_lite/asset_lite/doctype/mobile_team_site/mobile_team_site.json new file mode 100644 index 0000000..d26a3bc --- /dev/null +++ b/asset_lite/asset_lite/doctype/mobile_team_site/mobile_team_site.json @@ -0,0 +1,58 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "field:site_name", + "creation": "2025-08-08 15:36:41.575671", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "site_name", + "mobile_team", + "city" + ], + "fields": [ + { + "fieldname": "site_name", + "fieldtype": "Data", + "label": "Site Name", + "unique": 1 + }, + { + "fieldname": "mobile_team", + "fieldtype": "Link", + "label": "Mobile Team", + "options": "Company" + }, + { + "fieldname": "city", + "fieldtype": "Link", + "label": "City", + "options": "City" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2025-08-26 15:26:09.017274", + "modified_by": "Administrator", + "module": "Asset Lite", + "name": "Mobile Team Site", + "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": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/mobile_team_site/mobile_team_site.py b/asset_lite/asset_lite/doctype/mobile_team_site/mobile_team_site.py new file mode 100644 index 0000000..3486d9b --- /dev/null +++ b/asset_lite/asset_lite/doctype/mobile_team_site/mobile_team_site.py @@ -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 MobileTeamSite(Document): + pass diff --git a/asset_lite/asset_lite/doctype/mobile_team_site/test_mobile_team_site.py b/asset_lite/asset_lite/doctype/mobile_team_site/test_mobile_team_site.py new file mode 100644 index 0000000..ce0a149 --- /dev/null +++ b/asset_lite/asset_lite/doctype/mobile_team_site/test_mobile_team_site.py @@ -0,0 +1,9 @@ +# Copyright (c) 2025, seyfert and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestMobileTeamSite(FrappeTestCase): + pass diff --git a/asset_lite/asset_lite/doctype/modality/__init__.py b/asset_lite/asset_lite/doctype/modality/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/modality/modality.js b/asset_lite/asset_lite/doctype/modality/modality.js new file mode 100644 index 0000000..348c437 --- /dev/null +++ b/asset_lite/asset_lite/doctype/modality/modality.js @@ -0,0 +1,8 @@ +// Copyright (c) 2025, seyfert and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Modality", { +// refresh(frm) { + +// }, +// }); diff --git a/asset_lite/asset_lite/doctype/modality/modality.json b/asset_lite/asset_lite/doctype/modality/modality.json new file mode 100644 index 0000000..b6c5ab2 --- /dev/null +++ b/asset_lite/asset_lite/doctype/modality/modality.json @@ -0,0 +1,44 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "field:modality", + "creation": "2025-09-08 12:40:28.438645", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "modality" + ], + "fields": [ + { + "fieldname": "modality", + "fieldtype": "Data", + "label": "Modality", + "unique": 1 + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2025-09-08 12:41:21.725442", + "modified_by": "Administrator", + "module": "Asset Lite", + "name": "Modality", + "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": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/modality/modality.py b/asset_lite/asset_lite/doctype/modality/modality.py new file mode 100644 index 0000000..293bc1f --- /dev/null +++ b/asset_lite/asset_lite/doctype/modality/modality.py @@ -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 Modality(Document): + pass diff --git a/asset_lite/asset_lite/doctype/modality/test_modality.py b/asset_lite/asset_lite/doctype/modality/test_modality.py new file mode 100644 index 0000000..f3af0e5 --- /dev/null +++ b/asset_lite/asset_lite/doctype/modality/test_modality.py @@ -0,0 +1,9 @@ +# Copyright (c) 2025, seyfert and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestModality(FrappeTestCase): + pass diff --git a/asset_lite/asset_lite/doctype/pi_table/__init__.py b/asset_lite/asset_lite/doctype/pi_table/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/pi_table/pi_table.json b/asset_lite/asset_lite/doctype/pi_table/pi_table.json new file mode 100644 index 0000000..029dc0d --- /dev/null +++ b/asset_lite/asset_lite/doctype/pi_table/pi_table.json @@ -0,0 +1,38 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2024-09-20 13:15:43.666184", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "purchase_invoice", + "cost" + ], + "fields": [ + { + "fieldname": "purchase_invoice", + "fieldtype": "Link", + "label": "Purchase Invoice", + "options": "Purchase Invoice" + }, + { + "fetch_from": "purchase_invoice.grand_total", + "fieldname": "cost", + "fieldtype": "Currency", + "label": "Cost" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2024-09-20 13:19:33.308381", + "modified_by": "Administrator", + "module": "Asset Lite", + "name": "PI Table", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/pi_table/pi_table.py b/asset_lite/asset_lite/doctype/pi_table/pi_table.py new file mode 100644 index 0000000..4ef5c2d --- /dev/null +++ b/asset_lite/asset_lite/doctype/pi_table/pi_table.py @@ -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 PITable(Document): + pass diff --git a/asset_lite/asset_lite/doctype/pm_entry_line/__init__.py b/asset_lite/asset_lite/doctype/pm_entry_line/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/pm_entry_line/pm_entry_line.json b/asset_lite/asset_lite/doctype/pm_entry_line/pm_entry_line.json new file mode 100644 index 0000000..78fdb8e --- /dev/null +++ b/asset_lite/asset_lite/doctype/pm_entry_line/pm_entry_line.json @@ -0,0 +1,70 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2025-09-18 13:07:33.907967", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "asset", + "asset_name", + "start_date", + "end_date", + "manufacturer", + "model" + ], + "fields": [ + { + "fieldname": "asset", + "fieldtype": "Link", + "in_global_search": 1, + "in_list_view": 1, + "label": "Asset", + "options": "Asset" + }, + { + "fieldname": "asset_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Asset Name", + "read_only": 1 + }, + { + "fieldname": "start_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Start Date" + }, + { + "fieldname": "end_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "End Date" + }, + { + "fieldname": "manufacturer", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Manufacturer", + "options": "Manufacturer" + }, + { + "fieldname": "model", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Model" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2025-09-18 13:08:35.075783", + "modified_by": "Administrator", + "module": "Asset Lite", + "name": "PM Entry Line", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/pm_entry_line/pm_entry_line.py b/asset_lite/asset_lite/doctype/pm_entry_line/pm_entry_line.py new file mode 100644 index 0000000..5babec9 --- /dev/null +++ b/asset_lite/asset_lite/doctype/pm_entry_line/pm_entry_line.py @@ -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 PMEntryLine(Document): + pass diff --git a/asset_lite/asset_lite/doctype/pm_schedule_generator/__init__.py b/asset_lite/asset_lite/doctype/pm_schedule_generator/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/pm_schedule_generator/pm_schedule_generator.js b/asset_lite/asset_lite/doctype/pm_schedule_generator/pm_schedule_generator.js new file mode 100644 index 0000000..2424038 --- /dev/null +++ b/asset_lite/asset_lite/doctype/pm_schedule_generator/pm_schedule_generator.js @@ -0,0 +1,8 @@ +// Copyright (c) 2025, seyfert and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("PM Schedule Generator", { +// refresh(frm) { + +// }, +// }); diff --git a/asset_lite/asset_lite/doctype/pm_schedule_generator/pm_schedule_generator.json b/asset_lite/asset_lite/doctype/pm_schedule_generator/pm_schedule_generator.json new file mode 100644 index 0000000..24419c2 --- /dev/null +++ b/asset_lite/asset_lite/doctype/pm_schedule_generator/pm_schedule_generator.json @@ -0,0 +1,207 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "format:PMSG-{DD}-{MM}-{YY}-{####}", + "creation": "2025-09-08 13:29:07.175342", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "asset_details_section", + "hospital", + "manufacturer", + "model", + "column_break_qtoh", + "asset_name", + "modality", + "device_status", + "maintenance_details_section", + "start_date", + "maintenance_team", + "maintenance_manager", + "column_break_kapm", + "end_date", + "periodicity", + "assign_to", + "due_date", + "section_break_hkrb", + "maintenance_entries", + "amended_from" + ], + "fields": [ + { + "fieldname": "asset_details_section", + "fieldtype": "Section Break", + "label": "Asset Details" + }, + { + "fieldname": "hospital", + "fieldtype": "Link", + "in_filter": 1, + "in_global_search": 1, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Hospital", + "options": "Company", + "reqd": 1 + }, + { + "fieldname": "manufacturer", + "fieldtype": "Link", + "in_filter": 1, + "in_global_search": 1, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Manufacturer", + "options": "Manufacturer" + }, + { + "fieldname": "model", + "fieldtype": "Data", + "in_global_search": 1, + "in_list_view": 1, + "label": "Model" + }, + { + "fieldname": "column_break_qtoh", + "fieldtype": "Column Break" + }, + { + "fieldname": "asset_name", + "fieldtype": "Data", + "label": "Asset Name", + "read_only": 1 + }, + { + "fieldname": "device_status", + "fieldtype": "Select", + "label": "Device Status", + "options": "\nUp\nDown" + }, + { + "fieldname": "maintenance_details_section", + "fieldtype": "Section Break", + "label": "Maintenance Details" + }, + { + "fieldname": "start_date", + "fieldtype": "Date", + "label": "Start Date", + "reqd": 1 + }, + { + "fieldname": "maintenance_team", + "fieldtype": "Link", + "label": "Maintenance Team", + "mandatory_depends_on": "eval:doc.maintenance_entries && doc.maintenance_entries.length > 0", + "options": "Asset Maintenance Team" + }, + { + "fetch_from": "maintenance_team.maintenance_manager", + "fetch_if_empty": 1, + "fieldname": "maintenance_manager", + "fieldtype": "Data", + "label": "Maintenance Manager" + }, + { + "fieldname": "column_break_kapm", + "fieldtype": "Column Break" + }, + { + "fieldname": "end_date", + "fieldtype": "Date", + "label": "End Date", + "reqd": 1 + }, + { + "fieldname": "periodicity", + "fieldtype": "Select", + "label": "Periodicity", + "mandatory_depends_on": "eval:doc.maintenance_entries && doc.maintenance_entries.length > 0", + "options": "\nDaily\nWeekly\nMonthly\nQuarterly\nHalf-yearly\nYearly\n2 Yearly\n3 Yearly", + "reqd": 1 + }, + { + "fieldname": "assign_to", + "fieldtype": "Link", + "label": "Assign To", + "mandatory_depends_on": "eval:doc.maintenance_entries && doc.maintenance_entries.length > 0", + "options": "User" + }, + { + "fieldname": "due_date", + "fieldtype": "Date", + "label": "Due Date", + "read_only": 1 + }, + { + "fieldname": "section_break_hkrb", + "fieldtype": "Section Break" + }, + { + "fieldname": "maintenance_entries", + "fieldtype": "Table", + "label": "Maintenance Entries", + "options": "PM Entry Line" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "PM Schedule Generator", + "print_hide": 1, + "read_only": 1, + "search_index": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "PM Schedule Generator", + "print_hide": 1, + "read_only": 1, + "search_index": 1 + }, + { + "fieldname": "modality", + "fieldtype": "Link", + "in_global_search": 1, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Modality", + "options": "Modality" + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [ + { + "link_doctype": "Asset Maintenance", + "link_fieldname": "custom_pm_schedule" + } + ], + "modified": "2025-09-08 13:30:20.878126", + "modified_by": "Administrator", + "module": "Asset Lite", + "name": "PM Schedule Generator", + "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": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/pm_schedule_generator/pm_schedule_generator.py b/asset_lite/asset_lite/doctype/pm_schedule_generator/pm_schedule_generator.py new file mode 100644 index 0000000..bf229ec --- /dev/null +++ b/asset_lite/asset_lite/doctype/pm_schedule_generator/pm_schedule_generator.py @@ -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 PMScheduleGenerator(Document): + pass diff --git a/asset_lite/asset_lite/doctype/pm_schedule_generator/test_pm_schedule_generator.py b/asset_lite/asset_lite/doctype/pm_schedule_generator/test_pm_schedule_generator.py new file mode 100644 index 0000000..50eb703 --- /dev/null +++ b/asset_lite/asset_lite/doctype/pm_schedule_generator/test_pm_schedule_generator.py @@ -0,0 +1,9 @@ +# Copyright (c) 2025, seyfert and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestPMScheduleGenerator(FrappeTestCase): + pass diff --git a/asset_lite/asset_lite/doctype/ppm/__init__.py b/asset_lite/asset_lite/doctype/ppm/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/ppm/ppm.js b/asset_lite/asset_lite/doctype/ppm/ppm.js new file mode 100644 index 0000000..ad4b070 --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm/ppm.js @@ -0,0 +1,32 @@ +// Copyright (c) 2024, seyfert and contributors +// For license information, please see license.txt + +frappe.ui.form.on('PPM', { + refresh: function(frm) { + // Hide the default print icon + frm.page.hide_icon_group('print'); + + // Add custom button for PPM Sticker with a print icon + frm.add_custom_button( + ` ${__('PPM Sticker')}`, + function() { + // Set PPM Sticker as the default print format and open print preview + const customLink = `/printview?doctype=PPM&name=${frm.doc.name}&trigger_print=1&format=PPM%20Sticker&no_letterhead=0`; + window.open(customLink); + } + ); + + // Add custom button for PPM Service Report with a print icon + frm.add_custom_button( + ` ${__('Service Report')}`, + function() { + // Set Service Report as the default print format and open print preview + const customLink = `/printview?doctype=PPM&name=${frm.doc.name}&trigger_print=1&format=PPM%20Service%20Report&no_letterhead=0`; + window.open(customLink); + } + ); + } +}); + + + diff --git a/asset_lite/asset_lite/doctype/ppm/ppm.json b/asset_lite/asset_lite/doctype/ppm/ppm.json new file mode 100644 index 0000000..380cb82 --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm/ppm.json @@ -0,0 +1,112 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "format:PPM-{data}-{####}", + "creation": "2024-09-23 17:32:15.127002", + "default_view": "List", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "month", + "year", + "column_break_phdw", + "asset", + "asset_name", + "asset_maintenance_log", + "section_break_3", + "data", + "table", + "amended_from" + ], + "fields": [ + { + "fieldname": "month", + "fieldtype": "Select", + "label": "Month", + "options": "\nJanuary\nFebruary\nMarch\nApril\nMay\nJune\nJuly\nAugust\nSeptember\nOctober\nNovember\nDecember" + }, + { + "fieldname": "year", + "fieldtype": "Data", + "label": "Year" + }, + { + "fieldname": "column_break_phdw", + "fieldtype": "Column Break" + }, + { + "fieldname": "asset_maintenance_log", + "fieldtype": "Link", + "label": "Asset Maintenance Log", + "options": "Asset Maintenance Log", + "read_only": 1 + }, + { + "fieldname": "section_break_3", + "fieldtype": "Section Break" + }, + { + "default": "CT Scan", + "fieldname": "data", + "fieldtype": "Link", + "label": "Template", + "options": "PPM Templates" + }, + { + "fieldname": "table", + "fieldtype": "Table", + "options": "PPM Table" + }, + { + "fetch_from": "asset_maintenance_log.asset_name", + "fieldname": "asset", + "fieldtype": "Link", + "label": "Asset", + "options": "Asset" + }, + { + "fetch_from": "asset.asset_name", + "fieldname": "asset_name", + "fieldtype": "Data", + "label": "Asset Name", + "read_only": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "PPM", + "print_hide": 1, + "read_only": 1, + "search_index": 1 + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2024-09-23 18:16:03.998515", + "modified_by": "Administrator", + "module": "Asset Lite", + "name": "PPM", + "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": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/ppm/ppm.py b/asset_lite/asset_lite/doctype/ppm/ppm.py new file mode 100644 index 0000000..558fceb --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm/ppm.py @@ -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 PPM(Document): + pass diff --git a/asset_lite/asset_lite/doctype/ppm/test_ppm.py b/asset_lite/asset_lite/doctype/ppm/test_ppm.py new file mode 100644 index 0000000..ae51c1a --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm/test_ppm.py @@ -0,0 +1,9 @@ +# Copyright (c) 2024, seyfert and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestPPM(FrappeTestCase): + pass diff --git a/asset_lite/asset_lite/doctype/ppm_ct_scan_machine_template/__init__.py b/asset_lite/asset_lite/doctype/ppm_ct_scan_machine_template/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/ppm_ct_scan_machine_template/ppm_ct_scan_machine_template.js b/asset_lite/asset_lite/doctype/ppm_ct_scan_machine_template/ppm_ct_scan_machine_template.js new file mode 100644 index 0000000..c371ba8 --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_ct_scan_machine_template/ppm_ct_scan_machine_template.js @@ -0,0 +1,8 @@ +// Copyright (c) 2024, seyfert and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("PPM CT SCAN MACHINE TEMPLATE", { +// refresh(frm) { + +// }, +// }); diff --git a/asset_lite/asset_lite/doctype/ppm_ct_scan_machine_template/ppm_ct_scan_machine_template.json b/asset_lite/asset_lite/doctype/ppm_ct_scan_machine_template/ppm_ct_scan_machine_template.json new file mode 100644 index 0000000..8253cea --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_ct_scan_machine_template/ppm_ct_scan_machine_template.json @@ -0,0 +1,43 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2024-09-13 15:46:54.680689", + "default_view": "List", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "table_1" + ], + "fields": [ + { + "fieldname": "table_1", + "fieldtype": "Table", + "options": "PPM Table For CT Scan Machine" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2024-09-13 17:45:05.011562", + "modified_by": "Administrator", + "module": "Asset Lite", + "name": "PPM CT SCAN MACHINE TEMPLATE", + "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": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/ppm_ct_scan_machine_template/ppm_ct_scan_machine_template.py b/asset_lite/asset_lite/doctype/ppm_ct_scan_machine_template/ppm_ct_scan_machine_template.py new file mode 100644 index 0000000..513b7e4 --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_ct_scan_machine_template/ppm_ct_scan_machine_template.py @@ -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 PPMCTSCANMACHINETEMPLATE(Document): + pass diff --git a/asset_lite/asset_lite/doctype/ppm_ct_scan_machine_template/test_ppm_ct_scan_machine_template.py b/asset_lite/asset_lite/doctype/ppm_ct_scan_machine_template/test_ppm_ct_scan_machine_template.py new file mode 100644 index 0000000..bde1c7a --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_ct_scan_machine_template/test_ppm_ct_scan_machine_template.py @@ -0,0 +1,9 @@ +# Copyright (c) 2024, seyfert and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestPPMCTSCANMACHINETEMPLATE(FrappeTestCase): + pass diff --git a/asset_lite/asset_lite/doctype/ppm_electrical_fixtures_inside_rooms_table/__init__.py b/asset_lite/asset_lite/doctype/ppm_electrical_fixtures_inside_rooms_table/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/ppm_electrical_fixtures_inside_rooms_table/ppm_electrical_fixtures_inside_rooms_table.json b/asset_lite/asset_lite/doctype/ppm_electrical_fixtures_inside_rooms_table/ppm_electrical_fixtures_inside_rooms_table.json new file mode 100644 index 0000000..9fe410e --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_electrical_fixtures_inside_rooms_table/ppm_electrical_fixtures_inside_rooms_table.json @@ -0,0 +1,78 @@ +{ + "actions": [], + "autoname": "autoincrement", + "creation": "2024-09-13 09:25:03.360604", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "room_numbers", + "electrical_switches", + "electrical_sockets_outlets", + "fluorescent_tubes", + "column_break_5", + "ballasts", + "starters", + "glass_cover_of_tube_lights" + ], + "fields": [ + { + "fieldname": "room_numbers", + "fieldtype": "Data", + "label": "Room Numbers" + }, + { + "default": "0", + "fieldname": "electrical_switches", + "fieldtype": "Check", + "label": "Electrical switches" + }, + { + "default": "0", + "fieldname": "electrical_sockets_outlets", + "fieldtype": "Check", + "label": "Electrical sockets/outlets" + }, + { + "default": "0", + "fieldname": "fluorescent_tubes", + "fieldtype": "Check", + "label": "Fluorescent tubes" + }, + { + "fieldname": "column_break_5", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "ballasts", + "fieldtype": "Check", + "label": "Ballasts" + }, + { + "default": "0", + "fieldname": "starters", + "fieldtype": "Check", + "label": "Starters" + }, + { + "default": "0", + "fieldname": "glass_cover_of_tube_lights", + "fieldtype": "Check", + "label": "Glass cover of tube lights" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2024-09-13 17:45:12.282804", + "modified_by": "Administrator", + "module": "Asset Lite", + "name": "PPM Electrical Fixtures Inside Rooms Table", + "naming_rule": "Autoincrement", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/ppm_electrical_fixtures_inside_rooms_table/ppm_electrical_fixtures_inside_rooms_table.py b/asset_lite/asset_lite/doctype/ppm_electrical_fixtures_inside_rooms_table/ppm_electrical_fixtures_inside_rooms_table.py new file mode 100644 index 0000000..5e5890b --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_electrical_fixtures_inside_rooms_table/ppm_electrical_fixtures_inside_rooms_table.py @@ -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 PPMElectricalFixturesInsideRoomsTable(Document): + pass diff --git a/asset_lite/asset_lite/doctype/ppm_electricals_panels_table/__init__.py b/asset_lite/asset_lite/doctype/ppm_electricals_panels_table/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/ppm_electricals_panels_table/ppm_electricals_panels_table.json b/asset_lite/asset_lite/doctype/ppm_electricals_panels_table/ppm_electricals_panels_table.json new file mode 100644 index 0000000..7c8656a --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_electricals_panels_table/ppm_electricals_panels_table.json @@ -0,0 +1,92 @@ +{ + "actions": [], + "autoname": "autoincrement", + "creation": "2024-09-13 09:12:39.494608", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "location", + "wires", + "grounding", + "mcbs", + "connections", + "column_break_6", + "panel_door", + "panel_hinges_lock", + "schedule_of_panel", + "cleaning_of_panel" + ], + "fields": [ + { + "fieldname": "location", + "fieldtype": "Data", + "label": "Location" + }, + { + "default": "0", + "fieldname": "wires", + "fieldtype": "Check", + "label": "Wires" + }, + { + "default": "0", + "fieldname": "grounding", + "fieldtype": "Check", + "label": "Grounding" + }, + { + "default": "0", + "fieldname": "mcbs", + "fieldtype": "Check", + "label": "MCB's" + }, + { + "default": "0", + "fieldname": "connections", + "fieldtype": "Check", + "label": "Connections" + }, + { + "fieldname": "column_break_6", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "panel_door", + "fieldtype": "Check", + "label": "Panel door" + }, + { + "default": "0", + "fieldname": "panel_hinges_lock", + "fieldtype": "Check", + "label": "Panel hinges & lock" + }, + { + "default": "0", + "fieldname": "schedule_of_panel", + "fieldtype": "Check", + "label": "Schedule of panel" + }, + { + "default": "0", + "fieldname": "cleaning_of_panel", + "fieldtype": "Check", + "label": "Cleaning of panel" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2024-09-13 17:45:14.076447", + "modified_by": "Administrator", + "module": "Asset Lite", + "name": "PPM electricals Panels table", + "naming_rule": "Autoincrement", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/ppm_electricals_panels_table/ppm_electricals_panels_table.py b/asset_lite/asset_lite/doctype/ppm_electricals_panels_table/ppm_electricals_panels_table.py new file mode 100644 index 0000000..41ac0e0 --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_electricals_panels_table/ppm_electricals_panels_table.py @@ -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 PPMelectricalsPanelstable(Document): + pass diff --git a/asset_lite/asset_lite/doctype/ppm_fire_alarm_device_table/__init__.py b/asset_lite/asset_lite/doctype/ppm_fire_alarm_device_table/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/ppm_fire_alarm_device_table/ppm_fire_alarm_device_table.json b/asset_lite/asset_lite/doctype/ppm_fire_alarm_device_table/ppm_fire_alarm_device_table.json new file mode 100644 index 0000000..7072d77 --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_fire_alarm_device_table/ppm_fire_alarm_device_table.json @@ -0,0 +1,38 @@ +{ + "actions": [], + "autoname": "autoincrement", + "creation": "2024-09-11 17:03:33.429492", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "room_no", + "check_2" + ], + "fields": [ + { + "fieldname": "room_no", + "fieldtype": "Data", + "label": "Room No" + }, + { + "default": "0", + "fieldname": "check_2", + "fieldtype": "Check", + "label": "Value" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2024-09-13 17:45:14.941826", + "modified_by": "Administrator", + "module": "Asset Lite", + "name": "PPM Fire Alarm Device Table", + "naming_rule": "Autoincrement", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/ppm_fire_alarm_device_table/ppm_fire_alarm_device_table.py b/asset_lite/asset_lite/doctype/ppm_fire_alarm_device_table/ppm_fire_alarm_device_table.py new file mode 100644 index 0000000..4eff966 --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_fire_alarm_device_table/ppm_fire_alarm_device_table.py @@ -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 PPMFireAlarmDeviceTable(Document): + pass diff --git a/asset_lite/asset_lite/doctype/ppm_mri_template/__init__.py b/asset_lite/asset_lite/doctype/ppm_mri_template/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/ppm_mri_template/ppm_mri_template.js b/asset_lite/asset_lite/doctype/ppm_mri_template/ppm_mri_template.js new file mode 100644 index 0000000..ea96071 --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_mri_template/ppm_mri_template.js @@ -0,0 +1,8 @@ +// Copyright (c) 2024, seyfert and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("PPM MRI Template", { +// refresh(frm) { + +// }, +// }); diff --git a/asset_lite/asset_lite/doctype/ppm_mri_template/ppm_mri_template.json b/asset_lite/asset_lite/doctype/ppm_mri_template/ppm_mri_template.json new file mode 100644 index 0000000..fdbef23 --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_mri_template/ppm_mri_template.json @@ -0,0 +1,43 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2024-09-13 15:44:37.160329", + "default_view": "List", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "table_1" + ], + "fields": [ + { + "fieldname": "table_1", + "fieldtype": "Table", + "options": "PPM table for MRI" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2024-09-13 17:45:06.173357", + "modified_by": "Administrator", + "module": "Asset Lite", + "name": "PPM MRI Template", + "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": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/ppm_mri_template/ppm_mri_template.py b/asset_lite/asset_lite/doctype/ppm_mri_template/ppm_mri_template.py new file mode 100644 index 0000000..1a453e5 --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_mri_template/ppm_mri_template.py @@ -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 PPMMRITemplate(Document): + pass diff --git a/asset_lite/asset_lite/doctype/ppm_mri_template/test_ppm_mri_template.py b/asset_lite/asset_lite/doctype/ppm_mri_template/test_ppm_mri_template.py new file mode 100644 index 0000000..c7e8c42 --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_mri_template/test_ppm_mri_template.py @@ -0,0 +1,9 @@ +# Copyright (c) 2024, seyfert and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestPPMMRITemplate(FrappeTestCase): + pass diff --git a/asset_lite/asset_lite/doctype/ppm_of_ct_scan_machine/__init__.py b/asset_lite/asset_lite/doctype/ppm_of_ct_scan_machine/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/ppm_of_ct_scan_machine/ppm_of_ct_scan_machine.js b/asset_lite/asset_lite/doctype/ppm_of_ct_scan_machine/ppm_of_ct_scan_machine.js new file mode 100644 index 0000000..b6208a6 --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_of_ct_scan_machine/ppm_of_ct_scan_machine.js @@ -0,0 +1,8 @@ +// Copyright (c) 2024, seyfert and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("PPM OF CT SCAN MACHINE", { +// refresh(frm) { + +// }, +// }); diff --git a/asset_lite/asset_lite/doctype/ppm_of_ct_scan_machine/ppm_of_ct_scan_machine.json b/asset_lite/asset_lite/doctype/ppm_of_ct_scan_machine/ppm_of_ct_scan_machine.json new file mode 100644 index 0000000..c23bba1 --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_of_ct_scan_machine/ppm_of_ct_scan_machine.json @@ -0,0 +1,96 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "format:CT Scan - {####}", + "creation": "2024-09-13 15:47:34.231656", + "default_view": "List", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "month", + "year", + "column_break_phdw", + "asset_maintenance_log", + "section_break_3", + "data", + "table", + "amended_from" + ], + "fields": [ + { + "fieldname": "month", + "fieldtype": "Select", + "label": "Month", + "options": "\nJanuary\nFebruary\nMarch\nApril\nMay\nJune\nJuly\nAugust\nSeptember\nOctober\nNovember\nDecember" + }, + { + "fieldname": "year", + "fieldtype": "Data", + "label": "Year" + }, + { + "fieldname": "section_break_3", + "fieldtype": "Section Break" + }, + { + "default": "CT Scan", + "fieldname": "data", + "fieldtype": "Link", + "label": "Template", + "options": "PPM Templates" + }, + { + "fieldname": "table", + "fieldtype": "Table", + "options": "PPM Table For CT Scan Machine" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "PPM OF CT SCAN MACHINE", + "print_hide": 1, + "read_only": 1, + "search_index": 1 + }, + { + "fieldname": "column_break_phdw", + "fieldtype": "Column Break" + }, + { + "fieldname": "asset_maintenance_log", + "fieldtype": "Link", + "label": "Asset Maintenance Log", + "options": "Asset Maintenance Log", + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2024-09-23 16:47:36.569116", + "modified_by": "Administrator", + "module": "Asset Lite", + "name": "PPM OF CT SCAN MACHINE", + "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": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/ppm_of_ct_scan_machine/ppm_of_ct_scan_machine.py b/asset_lite/asset_lite/doctype/ppm_of_ct_scan_machine/ppm_of_ct_scan_machine.py new file mode 100644 index 0000000..ae76db0 --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_of_ct_scan_machine/ppm_of_ct_scan_machine.py @@ -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 PPMOFCTSCANMACHINE(Document): + pass diff --git a/asset_lite/asset_lite/doctype/ppm_of_ct_scan_machine/test_ppm_of_ct_scan_machine.py b/asset_lite/asset_lite/doctype/ppm_of_ct_scan_machine/test_ppm_of_ct_scan_machine.py new file mode 100644 index 0000000..59ce7cc --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_of_ct_scan_machine/test_ppm_of_ct_scan_machine.py @@ -0,0 +1,9 @@ +# Copyright (c) 2024, seyfert and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestPPMOFCTSCANMACHINE(FrappeTestCase): + pass diff --git a/asset_lite/asset_lite/doctype/ppm_of_electrical_fixtures_inside_rooms/__init__.py b/asset_lite/asset_lite/doctype/ppm_of_electrical_fixtures_inside_rooms/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/ppm_of_electrical_fixtures_inside_rooms/ppm_of_electrical_fixtures_inside_rooms.js b/asset_lite/asset_lite/doctype/ppm_of_electrical_fixtures_inside_rooms/ppm_of_electrical_fixtures_inside_rooms.js new file mode 100644 index 0000000..0ffccd2 --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_of_electrical_fixtures_inside_rooms/ppm_of_electrical_fixtures_inside_rooms.js @@ -0,0 +1,8 @@ +// Copyright (c) 2024, seyfert and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("PPM OF ELECTRICAL FIXTURES INSIDE ROOMS", { +// refresh(frm) { + +// }, +// }); diff --git a/asset_lite/asset_lite/doctype/ppm_of_electrical_fixtures_inside_rooms/ppm_of_electrical_fixtures_inside_rooms.json b/asset_lite/asset_lite/doctype/ppm_of_electrical_fixtures_inside_rooms/ppm_of_electrical_fixtures_inside_rooms.json new file mode 100644 index 0000000..f9c2a36 --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_of_electrical_fixtures_inside_rooms/ppm_of_electrical_fixtures_inside_rooms.json @@ -0,0 +1,131 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "PPM-EF-.####", + "creation": "2024-09-13 09:33:19.484317", + "default_view": "List", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "building", + "month", + "column_break_2", + "floor", + "year", + "section_break_4", + "electrical_fixtures_table", + "notes_if_any", + "section_break_7", + "checked_by", + "electrician", + "column_break_10", + "maintenance_manager", + "amended_from" + ], + "fields": [ + { + "fieldname": "building", + "fieldtype": "Data", + "label": "Building" + }, + { + "fieldname": "month", + "fieldtype": "Select", + "label": "Month", + "options": "\nJanuary\nFebruary\nMarch\nApril\nMay\nJune\nJuly\nAugust\nSeptember\nOctober\nNovember\nDecember" + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "fieldname": "floor", + "fieldtype": "Select", + "label": "Floor", + "options": "\nGF\nFF\nSF\nTF" + }, + { + "fieldname": "year", + "fieldtype": "Data", + "label": "Year" + }, + { + "fieldname": "section_break_4", + "fieldtype": "Section Break" + }, + { + "fieldname": "electrical_fixtures_table", + "fieldtype": "Table", + "label": "Electrical Fixtures table", + "options": "PPM Electrical Fixtures Inside Rooms Table" + }, + { + "fieldname": "notes_if_any", + "fieldtype": "Small Text", + "label": "Notes if Any" + }, + { + "fieldname": "section_break_7", + "fieldtype": "Section Break" + }, + { + "fieldname": "checked_by", + "fieldtype": "Data", + "label": "Checked By" + }, + { + "depends_on": "eval:doc.workflow_state == \"Sent to Electrician\" || doc.workflow_state == \"Sent to Maintenance Manager\" || doc.workflow_state == \"Approved\"", + "fieldname": "electrician", + "fieldtype": "Data", + "hidden": 1, + "label": "Electrician" + }, + { + "fieldname": "column_break_10", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval:doc.workflow_state == \"Sent to Maintenance Manager\" || doc.workflow_state == \"Approved\"", + "fieldname": "maintenance_manager", + "fieldtype": "Data", + "label": "Maintenance Manager" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "PPM OF ELECTRICAL PANELS", + "print_hide": 1, + "read_only": 1, + "search_index": 1 + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2024-09-13 17:45:10.601474", + "modified_by": "Administrator", + "module": "Asset Lite", + "name": "PPM OF ELECTRICAL FIXTURES INSIDE ROOMS", + "naming_rule": "Expression (old style)", + "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": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/ppm_of_electrical_fixtures_inside_rooms/ppm_of_electrical_fixtures_inside_rooms.py b/asset_lite/asset_lite/doctype/ppm_of_electrical_fixtures_inside_rooms/ppm_of_electrical_fixtures_inside_rooms.py new file mode 100644 index 0000000..110e017 --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_of_electrical_fixtures_inside_rooms/ppm_of_electrical_fixtures_inside_rooms.py @@ -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 PPMOFELECTRICALFIXTURESINSIDEROOMS(Document): + pass diff --git a/asset_lite/asset_lite/doctype/ppm_of_electrical_fixtures_inside_rooms/test_ppm_of_electrical_fixtures_inside_rooms.py b/asset_lite/asset_lite/doctype/ppm_of_electrical_fixtures_inside_rooms/test_ppm_of_electrical_fixtures_inside_rooms.py new file mode 100644 index 0000000..db46677 --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_of_electrical_fixtures_inside_rooms/test_ppm_of_electrical_fixtures_inside_rooms.py @@ -0,0 +1,9 @@ +# Copyright (c) 2024, seyfert and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestPPMOFELECTRICALFIXTURESINSIDEROOMS(FrappeTestCase): + pass diff --git a/asset_lite/asset_lite/doctype/ppm_of_electrical_panels/__init__.py b/asset_lite/asset_lite/doctype/ppm_of_electrical_panels/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/ppm_of_electrical_panels/ppm_of_electrical_panels.js b/asset_lite/asset_lite/doctype/ppm_of_electrical_panels/ppm_of_electrical_panels.js new file mode 100644 index 0000000..1ac7e95 --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_of_electrical_panels/ppm_of_electrical_panels.js @@ -0,0 +1,8 @@ +// Copyright (c) 2024, seyfert and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("PPM OF ELECTRICAL PANELS", { +// refresh(frm) { + +// }, +// }); diff --git a/asset_lite/asset_lite/doctype/ppm_of_electrical_panels/ppm_of_electrical_panels.json b/asset_lite/asset_lite/doctype/ppm_of_electrical_panels/ppm_of_electrical_panels.json new file mode 100644 index 0000000..4300277 --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_of_electrical_panels/ppm_of_electrical_panels.json @@ -0,0 +1,118 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "PPM-EP-.####", + "creation": "2024-09-13 09:19:00.454586", + "default_view": "List", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "month", + "column_break_2", + "year", + "section_break_4", + "electrical_panels_table", + "notes_if_any", + "section_break_7", + "checked_by", + "electrician", + "column_break_10", + "maintenance_manager", + "amended_from" + ], + "fields": [ + { + "fieldname": "month", + "fieldtype": "Select", + "label": "Month", + "options": "\nJanuary\nFebruary\nMarch\nApril\nMay\nJune\nJuly\nAugust\nSeptember\nOctober\nNovember\nDecember" + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "fieldname": "year", + "fieldtype": "Data", + "label": "Year" + }, + { + "fieldname": "section_break_4", + "fieldtype": "Section Break" + }, + { + "fieldname": "electrical_panels_table", + "fieldtype": "Table", + "label": "Electrical Panels Table", + "options": "PPM electricals Panels table" + }, + { + "fieldname": "notes_if_any", + "fieldtype": "Small Text", + "label": "Notes if Any" + }, + { + "fieldname": "section_break_7", + "fieldtype": "Section Break" + }, + { + "fieldname": "checked_by", + "fieldtype": "Data", + "label": "Checked By" + }, + { + "depends_on": "eval:doc.workflow_state == \"Sent to Electrician\" || doc.workflow_state == \"Sent to Maintenance Manager\" || doc.workflow_state == \"Approved\"", + "fieldname": "electrician", + "fieldtype": "Data", + "hidden": 1, + "label": "Electrician" + }, + { + "fieldname": "column_break_10", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval:doc.workflow_state == \"Sent to Maintenance Manager\" || doc.workflow_state == \"Approved\"", + "fieldname": "maintenance_manager", + "fieldtype": "Data", + "label": "Maintenance Manager" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "PPM OF ELECTRICAL PANELS", + "print_hide": 1, + "read_only": 1, + "search_index": 1 + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2024-09-13 17:45:13.038073", + "modified_by": "Administrator", + "module": "Asset Lite", + "name": "PPM OF ELECTRICAL PANELS", + "naming_rule": "Expression (old style)", + "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": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/ppm_of_electrical_panels/ppm_of_electrical_panels.py b/asset_lite/asset_lite/doctype/ppm_of_electrical_panels/ppm_of_electrical_panels.py new file mode 100644 index 0000000..ef8ecdb --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_of_electrical_panels/ppm_of_electrical_panels.py @@ -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 PPMOFELECTRICALPANELS(Document): + pass diff --git a/asset_lite/asset_lite/doctype/ppm_of_electrical_panels/test_ppm_of_electrical_panels.py b/asset_lite/asset_lite/doctype/ppm_of_electrical_panels/test_ppm_of_electrical_panels.py new file mode 100644 index 0000000..89ac3d7 --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_of_electrical_panels/test_ppm_of_electrical_panels.py @@ -0,0 +1,9 @@ +# Copyright (c) 2024, seyfert and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestPPMOFELECTRICALPANELS(FrappeTestCase): + pass diff --git a/asset_lite/asset_lite/doctype/ppm_of_fire_alarm_devices/__init__.py b/asset_lite/asset_lite/doctype/ppm_of_fire_alarm_devices/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/ppm_of_fire_alarm_devices/ppm_of_fire_alarm_devices.js b/asset_lite/asset_lite/doctype/ppm_of_fire_alarm_devices/ppm_of_fire_alarm_devices.js new file mode 100644 index 0000000..9b89192 --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_of_fire_alarm_devices/ppm_of_fire_alarm_devices.js @@ -0,0 +1,8 @@ +// Copyright (c) 2024, seyfert and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("PPM OF FIRE ALARM DEVICES", { +// refresh(frm) { + +// }, +// }); diff --git a/asset_lite/asset_lite/doctype/ppm_of_fire_alarm_devices/ppm_of_fire_alarm_devices.json b/asset_lite/asset_lite/doctype/ppm_of_fire_alarm_devices/ppm_of_fire_alarm_devices.json new file mode 100644 index 0000000..b2cd9cf --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_of_fire_alarm_devices/ppm_of_fire_alarm_devices.json @@ -0,0 +1,118 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "PPM-FAD-.####", + "creation": "2024-09-13 09:23:17.327840", + "default_view": "List", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "month", + "column_break_2", + "year", + "section_break_4", + "fire_alaram_table", + "notes_if_any", + "section_break_7", + "checked_by", + "electrician", + "column_break_10", + "maintenance_manager", + "amended_from" + ], + "fields": [ + { + "fieldname": "month", + "fieldtype": "Select", + "label": "Month", + "options": "\nJanuary\nFebruary\nMarch\nApril\nMay\nJune\nJuly\nAugust\nSeptember\nOctober\nNovember\nDecember" + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "fieldname": "year", + "fieldtype": "Data", + "label": "Year" + }, + { + "fieldname": "section_break_4", + "fieldtype": "Section Break" + }, + { + "fieldname": "fire_alaram_table", + "fieldtype": "Table", + "label": "Fire Alaram Table", + "options": "PPM Fire Alarm Device Table" + }, + { + "fieldname": "notes_if_any", + "fieldtype": "Small Text", + "label": "Notes if Any" + }, + { + "fieldname": "section_break_7", + "fieldtype": "Section Break" + }, + { + "fieldname": "checked_by", + "fieldtype": "Data", + "label": "Checked By" + }, + { + "depends_on": "eval:doc.workflow_state == \"Sent to Electrician\" || doc.workflow_state == \"Sent to Maintenance Manager\" || doc.workflow_state == \"Approved\"", + "fieldname": "electrician", + "fieldtype": "Data", + "hidden": 1, + "label": "Electrician" + }, + { + "fieldname": "column_break_10", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval:doc.workflow_state == \"Sent to Maintenance Manager\" || doc.workflow_state == \"Approved\"", + "fieldname": "maintenance_manager", + "fieldtype": "Data", + "label": "Maintenance Manager" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "PPM OF ELECTRICAL PANELS", + "print_hide": 1, + "read_only": 1, + "search_index": 1 + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2024-09-13 17:45:11.276292", + "modified_by": "Administrator", + "module": "Asset Lite", + "name": "PPM OF FIRE ALARM DEVICES", + "naming_rule": "Expression (old style)", + "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": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/ppm_of_fire_alarm_devices/ppm_of_fire_alarm_devices.py b/asset_lite/asset_lite/doctype/ppm_of_fire_alarm_devices/ppm_of_fire_alarm_devices.py new file mode 100644 index 0000000..b0c02ec --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_of_fire_alarm_devices/ppm_of_fire_alarm_devices.py @@ -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 PPMOFFIREALARMDEVICES(Document): + pass diff --git a/asset_lite/asset_lite/doctype/ppm_of_fire_alarm_devices/test_ppm_of_fire_alarm_devices.py b/asset_lite/asset_lite/doctype/ppm_of_fire_alarm_devices/test_ppm_of_fire_alarm_devices.py new file mode 100644 index 0000000..b88680f --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_of_fire_alarm_devices/test_ppm_of_fire_alarm_devices.py @@ -0,0 +1,9 @@ +# Copyright (c) 2024, seyfert and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestPPMOFFIREALARMDEVICES(FrappeTestCase): + pass diff --git a/asset_lite/asset_lite/doctype/ppm_of_mri_scan_machine/__init__.py b/asset_lite/asset_lite/doctype/ppm_of_mri_scan_machine/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/ppm_of_mri_scan_machine/ppm_of_mri_scan_machine.js b/asset_lite/asset_lite/doctype/ppm_of_mri_scan_machine/ppm_of_mri_scan_machine.js new file mode 100644 index 0000000..a40b12d --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_of_mri_scan_machine/ppm_of_mri_scan_machine.js @@ -0,0 +1,8 @@ +// Copyright (c) 2024, seyfert and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("PPM OF MRI SCAN MACHINE", { +// refresh(frm) { + +// }, +// }); diff --git a/asset_lite/asset_lite/doctype/ppm_of_mri_scan_machine/ppm_of_mri_scan_machine.json b/asset_lite/asset_lite/doctype/ppm_of_mri_scan_machine/ppm_of_mri_scan_machine.json new file mode 100644 index 0000000..9eee2ee --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_of_mri_scan_machine/ppm_of_mri_scan_machine.json @@ -0,0 +1,96 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "PPM-MRI-.####", + "creation": "2024-09-13 15:45:26.474798", + "default_view": "List", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "month", + "year", + "column_break_kezq", + "asset_maintenance_log", + "section_break_3", + "date", + "table_5", + "amended_from" + ], + "fields": [ + { + "fieldname": "month", + "fieldtype": "Select", + "label": "Month", + "options": "\nJanuary\nFebruary\nMarch\nApril\nMay\nJune\nJuly\nAugust\nSeptember\nOctober\nNovember\nDecember" + }, + { + "fieldname": "year", + "fieldtype": "Data", + "label": "Year" + }, + { + "fieldname": "section_break_3", + "fieldtype": "Section Break" + }, + { + "default": "MRI Scan", + "fieldname": "date", + "fieldtype": "Link", + "label": "Template", + "options": "PPM Templates" + }, + { + "fieldname": "table_5", + "fieldtype": "Table", + "options": "PPM table for MRI" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "PPM OF MRI SCAN MACHINE", + "print_hide": 1, + "read_only": 1, + "search_index": 1 + }, + { + "fieldname": "column_break_kezq", + "fieldtype": "Column Break" + }, + { + "fieldname": "asset_maintenance_log", + "fieldtype": "Link", + "label": "Asset Maintenance Log", + "options": "Asset Maintenance Log", + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2024-09-23 16:46:57.854116", + "modified_by": "Administrator", + "module": "Asset Lite", + "name": "PPM OF MRI SCAN MACHINE", + "naming_rule": "Expression (old style)", + "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": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/ppm_of_mri_scan_machine/ppm_of_mri_scan_machine.py b/asset_lite/asset_lite/doctype/ppm_of_mri_scan_machine/ppm_of_mri_scan_machine.py new file mode 100644 index 0000000..d8bdb59 --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_of_mri_scan_machine/ppm_of_mri_scan_machine.py @@ -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 PPMOFMRISCANMACHINE(Document): + pass diff --git a/asset_lite/asset_lite/doctype/ppm_of_mri_scan_machine/test_ppm_of_mri_scan_machine.py b/asset_lite/asset_lite/doctype/ppm_of_mri_scan_machine/test_ppm_of_mri_scan_machine.py new file mode 100644 index 0000000..6b9cd96 --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_of_mri_scan_machine/test_ppm_of_mri_scan_machine.py @@ -0,0 +1,9 @@ +# Copyright (c) 2024, seyfert and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestPPMOFMRISCANMACHINE(FrappeTestCase): + pass diff --git a/asset_lite/asset_lite/doctype/ppm_table/__init__.py b/asset_lite/asset_lite/doctype/ppm_table/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/ppm_table/ppm_table.json b/asset_lite/asset_lite/doctype/ppm_table/ppm_table.json new file mode 100644 index 0000000..54c2512 --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_table/ppm_table.json @@ -0,0 +1,52 @@ +{ + "actions": [], + "autoname": "autoincrement", + "creation": "2024-09-23 14:44:23.314567", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "maintenance_name", + "working", + "defect_found", + "not_working" + ], + "fields": [ + { + "fieldname": "maintenance_name", + "fieldtype": "Data", + "label": "Maintenance Name" + }, + { + "default": "0", + "fieldname": "working", + "fieldtype": "Check", + "label": "Working" + }, + { + "default": "0", + "fieldname": "defect_found", + "fieldtype": "Check", + "label": "Defect Found" + }, + { + "default": "0", + "fieldname": "not_working", + "fieldtype": "Check", + "label": "Not Working" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2024-09-23 14:44:23.314567", + "modified_by": "Administrator", + "module": "Asset Lite", + "name": "PPM Table", + "naming_rule": "Autoincrement", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/ppm_table/ppm_table.py b/asset_lite/asset_lite/doctype/ppm_table/ppm_table.py new file mode 100644 index 0000000..3149e88 --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_table/ppm_table.py @@ -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 PPMTable(Document): + pass diff --git a/asset_lite/asset_lite/doctype/ppm_table_for_ct_scan_machine/__init__.py b/asset_lite/asset_lite/doctype/ppm_table_for_ct_scan_machine/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/ppm_table_for_ct_scan_machine/ppm_table_for_ct_scan_machine.json b/asset_lite/asset_lite/doctype/ppm_table_for_ct_scan_machine/ppm_table_for_ct_scan_machine.json new file mode 100644 index 0000000..1f7a512 --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_table_for_ct_scan_machine/ppm_table_for_ct_scan_machine.json @@ -0,0 +1,52 @@ +{ + "actions": [], + "autoname": "autoincrement", + "creation": "2024-09-13 15:46:14.365890", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "maintenance_name", + "working", + "defect_found", + "not_working" + ], + "fields": [ + { + "fieldname": "maintenance_name", + "fieldtype": "Data", + "label": "Maintenance Name" + }, + { + "default": "0", + "fieldname": "working", + "fieldtype": "Check", + "label": "Working" + }, + { + "default": "0", + "fieldname": "defect_found", + "fieldtype": "Check", + "label": "Defect Found" + }, + { + "default": "0", + "fieldname": "not_working", + "fieldtype": "Check", + "label": "Not Working" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2024-09-13 17:45:05.578499", + "modified_by": "Administrator", + "module": "Asset Lite", + "name": "PPM Table For CT Scan Machine", + "naming_rule": "Autoincrement", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/ppm_table_for_ct_scan_machine/ppm_table_for_ct_scan_machine.py b/asset_lite/asset_lite/doctype/ppm_table_for_ct_scan_machine/ppm_table_for_ct_scan_machine.py new file mode 100644 index 0000000..a25eb40 --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_table_for_ct_scan_machine/ppm_table_for_ct_scan_machine.py @@ -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 PPMTableForCTScanMachine(Document): + pass diff --git a/asset_lite/asset_lite/doctype/ppm_table_for_mri/__init__.py b/asset_lite/asset_lite/doctype/ppm_table_for_mri/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/ppm_table_for_mri/ppm_table_for_mri.json b/asset_lite/asset_lite/doctype/ppm_table_for_mri/ppm_table_for_mri.json new file mode 100644 index 0000000..ed917aa --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_table_for_mri/ppm_table_for_mri.json @@ -0,0 +1,53 @@ +{ + "actions": [], + "autoname": "autoincrement", + "creation": "2024-09-13 15:43:50.713290", + "default_view": "List", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "maintenance_name", + "working", + "defect_found", + "not_working" + ], + "fields": [ + { + "fieldname": "maintenance_name", + "fieldtype": "Data", + "label": "Maintenance Name" + }, + { + "default": "0", + "fieldname": "working", + "fieldtype": "Check", + "label": "Working" + }, + { + "default": "0", + "fieldname": "defect_found", + "fieldtype": "Check", + "label": "Defect Found" + }, + { + "default": "0", + "fieldname": "not_working", + "fieldtype": "Check", + "label": "Not Working" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2024-09-13 17:45:06.985231", + "modified_by": "Administrator", + "module": "Asset Lite", + "name": "PPM table for MRI", + "naming_rule": "Autoincrement", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/ppm_table_for_mri/ppm_table_for_mri.py b/asset_lite/asset_lite/doctype/ppm_table_for_mri/ppm_table_for_mri.py new file mode 100644 index 0000000..aab9c2f --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_table_for_mri/ppm_table_for_mri.py @@ -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 PPMtableforMRI(Document): + pass diff --git a/asset_lite/asset_lite/doctype/ppm_templates/__init__.py b/asset_lite/asset_lite/doctype/ppm_templates/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/ppm_templates/ppm_templates.js b/asset_lite/asset_lite/doctype/ppm_templates/ppm_templates.js new file mode 100644 index 0000000..1f2b34c --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_templates/ppm_templates.js @@ -0,0 +1,8 @@ +// Copyright (c) 2024, seyfert and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("PPM Templates", { +// refresh(frm) { + +// }, +// }); diff --git a/asset_lite/asset_lite/doctype/ppm_templates/ppm_templates.json b/asset_lite/asset_lite/doctype/ppm_templates/ppm_templates.json new file mode 100644 index 0000000..e6f3ec0 --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_templates/ppm_templates.json @@ -0,0 +1,71 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "field:name1", + "creation": "2024-09-23 14:42:22.511123", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "name1", + "column_break_wpmh", + "asset_type", + "section_break_dznh", + "ppm_template_table" + ], + "fields": [ + { + "fieldname": "name1", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Name", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "ppm_template_table", + "fieldtype": "Table", + "label": "PPM template Table", + "options": "PPM Table" + }, + { + "fieldname": "column_break_wpmh", + "fieldtype": "Column Break" + }, + { + "fieldname": "asset_type", + "fieldtype": "Link", + "label": "Asset Type", + "options": "Asset Type", + "reqd": 1 + }, + { + "fieldname": "section_break_dznh", + "fieldtype": "Section Break" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2024-09-23 17:59:05.455192", + "modified_by": "Administrator", + "module": "Asset Lite", + "name": "PPM Templates", + "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": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/ppm_templates/ppm_templates.py b/asset_lite/asset_lite/doctype/ppm_templates/ppm_templates.py new file mode 100644 index 0000000..a753495 --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_templates/ppm_templates.py @@ -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 PPMTemplates(Document): + pass diff --git a/asset_lite/asset_lite/doctype/ppm_templates/test_ppm_templates.py b/asset_lite/asset_lite/doctype/ppm_templates/test_ppm_templates.py new file mode 100644 index 0000000..e3a53a5 --- /dev/null +++ b/asset_lite/asset_lite/doctype/ppm_templates/test_ppm_templates.py @@ -0,0 +1,9 @@ +# Copyright (c) 2024, seyfert and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestPPMTemplates(FrappeTestCase): + pass diff --git a/asset_lite/asset_lite/doctype/pr_table/__init__.py b/asset_lite/asset_lite/doctype/pr_table/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/pr_table/pr_table.json b/asset_lite/asset_lite/doctype/pr_table/pr_table.json new file mode 100644 index 0000000..7a6b6fa --- /dev/null +++ b/asset_lite/asset_lite/doctype/pr_table/pr_table.json @@ -0,0 +1,29 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2024-09-17 17:06:51.473708", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "section_break_kinu" + ], + "fields": [ + { + "fieldname": "section_break_kinu", + "fieldtype": "Section Break" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2024-09-17 17:06:51.473708", + "modified_by": "Administrator", + "module": "Asset Lite", + "name": "PR table", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/pr_table/pr_table.py b/asset_lite/asset_lite/doctype/pr_table/pr_table.py new file mode 100644 index 0000000..5359f2e --- /dev/null +++ b/asset_lite/asset_lite/doctype/pr_table/pr_table.py @@ -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 PRtable(Document): + pass diff --git a/asset_lite/asset_lite/doctype/purchase_request/__init__.py b/asset_lite/asset_lite/doctype/purchase_request/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/purchase_request/purchase_request.js b/asset_lite/asset_lite/doctype/purchase_request/purchase_request.js new file mode 100644 index 0000000..b639025 --- /dev/null +++ b/asset_lite/asset_lite/doctype/purchase_request/purchase_request.js @@ -0,0 +1,8 @@ +// Copyright (c) 2024, seyfert and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Purchase Request", { +// refresh(frm) { + +// }, +// }); diff --git a/asset_lite/asset_lite/doctype/purchase_request/purchase_request.json b/asset_lite/asset_lite/doctype/purchase_request/purchase_request.json new file mode 100644 index 0000000..012a9a0 --- /dev/null +++ b/asset_lite/asset_lite/doctype/purchase_request/purchase_request.json @@ -0,0 +1,154 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "format:{pr_no}", + "creation": "2024-09-17 17:13:10.035475", + "default_view": "List", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "pr_no", + "date", + "column_break_4", + "issue", + "asset", + "asset_name", + "data_8", + "pr_table", + "section_break_10", + "priority", + "normal", + "urgent", + "required_date", + "column_break_14", + "intended_use_of_material", + "amended_from" + ], + "fields": [ + { + "fieldname": "pr_no", + "fieldtype": "Data", + "label": "PR NO", + "unique": 1 + }, + { + "default": "Today", + "fieldname": "date", + "fieldtype": "Date", + "label": "Date" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fetch_from": "asset_repair.issue", + "fetch_if_empty": 1, + "fieldname": "issue", + "fieldtype": "Link", + "label": "Work Order", + "options": "Work_Order" + }, + { + "fetch_from": "asset_repair.asset", + "fieldname": "asset", + "fieldtype": "Link", + "label": "Asset", + "options": "Asset" + }, + { + "fieldname": "data_8", + "fieldtype": "Section Break" + }, + { + "fieldname": "pr_table", + "fieldtype": "Table", + "label": "PR Table", + "options": "Purchase Request Table" + }, + { + "fieldname": "section_break_10", + "fieldtype": "Section Break" + }, + { + "fieldname": "priority", + "fieldtype": "Heading", + "label": "Priority" + }, + { + "default": "0", + "fieldname": "normal", + "fieldtype": "Check", + "label": "Normal" + }, + { + "default": "0", + "fieldname": "urgent", + "fieldtype": "Check", + "label": "Urgent" + }, + { + "fieldname": "required_date", + "fieldtype": "Date", + "label": "Required Date" + }, + { + "fieldname": "column_break_14", + "fieldtype": "Column Break" + }, + { + "fieldname": "intended_use_of_material", + "fieldtype": "Text", + "label": "Intended Use of Material" + }, + { + "fetch_from": "asset.asset_name", + "fieldname": "asset_name", + "fieldtype": "Data", + "label": "Asset Name" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Purchase Request", + "print_hide": 1, + "read_only": 1, + "search_index": 1 + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [ + { + "group": "Purchase Order", + "link_doctype": "Purchase Order", + "link_fieldname": "custom_purchase_request" + } + ], + "modified": "2024-09-20 16:11:28.459741", + "modified_by": "Administrator", + "module": "Asset Lite", + "name": "Purchase 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 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/purchase_request/purchase_request.py b/asset_lite/asset_lite/doctype/purchase_request/purchase_request.py new file mode 100644 index 0000000..141fa9a --- /dev/null +++ b/asset_lite/asset_lite/doctype/purchase_request/purchase_request.py @@ -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 PurchaseRequest(Document): + pass diff --git a/asset_lite/asset_lite/doctype/purchase_request/test_purchase_request.py b/asset_lite/asset_lite/doctype/purchase_request/test_purchase_request.py new file mode 100644 index 0000000..926c8c2 --- /dev/null +++ b/asset_lite/asset_lite/doctype/purchase_request/test_purchase_request.py @@ -0,0 +1,9 @@ +# Copyright (c) 2024, seyfert and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestPurchaseRequest(FrappeTestCase): + pass diff --git a/asset_lite/asset_lite/doctype/purchase_request_table/__init__.py b/asset_lite/asset_lite/doctype/purchase_request_table/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/purchase_request_table/purchase_request_table.json b/asset_lite/asset_lite/doctype/purchase_request_table/purchase_request_table.json new file mode 100644 index 0000000..7bec699 --- /dev/null +++ b/asset_lite/asset_lite/doctype/purchase_request_table/purchase_request_table.json @@ -0,0 +1,95 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2024-09-17 17:12:10.133001", + "default_view": "List", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "item_code", + "item_name", + "quantity", + "uom", + "uom_conversion_factor", + "description", + "required_by", + "unit_price", + "amount", + "warehouse" + ], + "fields": [ + { + "fieldname": "item_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Code", + "options": "Item", + "reqd": 1 + }, + { + "fetch_from": "item_code.item_name", + "fieldname": "item_name", + "fieldtype": "Data", + "label": "Item Name" + }, + { + "fieldname": "quantity", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Quantity" + }, + { + "fetch_from": "item_code.stock_uom", + "fieldname": "uom", + "fieldtype": "Data", + "label": "UOM" + }, + { + "fieldname": "uom_conversion_factor", + "fieldtype": "Data", + "label": "UOM Conversion Factor" + }, + { + "fieldname": "description", + "fieldtype": "Text", + "in_list_view": 1, + "label": "Description", + "reqd": 1 + }, + { + "fieldname": "required_by", + "fieldtype": "Date", + "label": "Required By", + "reqd": 1 + }, + { + "fieldname": "unit_price", + "fieldtype": "Data", + "label": "Unit Price" + }, + { + "fieldname": "amount", + "fieldtype": "Data", + "label": "Amount" + }, + { + "fieldname": "warehouse", + "fieldtype": "Link", + "label": "Warehouse", + "options": "Warehouse" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2024-09-18 18:58:55.153194", + "modified_by": "Administrator", + "module": "Asset Lite", + "name": "Purchase Request Table", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/purchase_request_table/purchase_request_table.py b/asset_lite/asset_lite/doctype/purchase_request_table/purchase_request_table.py new file mode 100644 index 0000000..1a521e7 --- /dev/null +++ b/asset_lite/asset_lite/doctype/purchase_request_table/purchase_request_table.py @@ -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 PurchaseRequestTable(Document): + pass diff --git a/asset_lite/asset_lite/doctype/room/__init__.py b/asset_lite/asset_lite/doctype/room/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/room/room.js b/asset_lite/asset_lite/doctype/room/room.js new file mode 100644 index 0000000..776b5b3 --- /dev/null +++ b/asset_lite/asset_lite/doctype/room/room.js @@ -0,0 +1,8 @@ +// Copyright (c) 2026, seyfert and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Room", { +// refresh(frm) { + +// }, +// }); diff --git a/asset_lite/asset_lite/doctype/room/room.json b/asset_lite/asset_lite/doctype/room/room.json new file mode 100644 index 0000000..80dfd25 --- /dev/null +++ b/asset_lite/asset_lite/doctype/room/room.json @@ -0,0 +1,68 @@ +{ + "actions": [], + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:room", + "creation": "2026-01-21 13:49:51.360645", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "room", + "location_name", + "department", + "building" + ], + "fields": [ + { + "fieldname": "room", + "fieldtype": "Data", + "label": "Room", + "unique": 1 + }, + { + "fieldname": "location_name", + "fieldtype": "Link", + "label": "Location Name", + "options": "Location" + }, + { + "fieldname": "department", + "fieldtype": "Link", + "label": "Department", + "options": "Department" + }, + { + "fieldname": "building", + "fieldtype": "Link", + "label": "Building", + "options": "Building" + } + ], + "grid_page_length": 50, + "index_web_pages_for_search": 1, + "links": [], + "modified": "2026-03-10 10:52:18.738312", + "modified_by": "Administrator", + "module": "Asset Lite", + "name": "Room", + "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": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/room/room.py b/asset_lite/asset_lite/doctype/room/room.py new file mode 100644 index 0000000..c7e3f03 --- /dev/null +++ b/asset_lite/asset_lite/doctype/room/room.py @@ -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 Room(Document): + pass diff --git a/asset_lite/asset_lite/doctype/room/test_room.py b/asset_lite/asset_lite/doctype/room/test_room.py new file mode 100644 index 0000000..923cdc4 --- /dev/null +++ b/asset_lite/asset_lite/doctype/room/test_room.py @@ -0,0 +1,9 @@ +# Copyright (c) 2026, seyfert and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestRoom(FrappeTestCase): + pass diff --git a/asset_lite/asset_lite/doctype/service_coverage/__init__.py b/asset_lite/asset_lite/doctype/service_coverage/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/service_coverage/service_coverage.json b/asset_lite/asset_lite/doctype/service_coverage/service_coverage.json new file mode 100644 index 0000000..5f6a869 --- /dev/null +++ b/asset_lite/asset_lite/doctype/service_coverage/service_coverage.json @@ -0,0 +1,57 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2025-04-03 18:34:27.947154", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "service_agreement", + "start_date", + "end_date", + "active", + "no_of_pms" + ], + "fields": [ + { + "fieldname": "service_agreement", + "fieldtype": "Select", + "label": "Service Agreement", + "options": "\nWarranty\nContract\nFrame Work" + }, + { + "fieldname": "start_date", + "fieldtype": "Date", + "label": "Start Date" + }, + { + "fieldname": "end_date", + "fieldtype": "Date", + "label": "End date" + }, + { + "fieldname": "active", + "fieldtype": "Select", + "label": "Active", + "options": "\nYes\nNo" + }, + { + "fieldname": "no_of_pms", + "fieldtype": "Data", + "label": "No Of PMs", + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2025-04-03 20:05:09.090315", + "modified_by": "Administrator", + "module": "Asset Lite", + "name": "Service Coverage", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/service_coverage/service_coverage.py b/asset_lite/asset_lite/doctype/service_coverage/service_coverage.py new file mode 100644 index 0000000..c486d0e --- /dev/null +++ b/asset_lite/asset_lite/doctype/service_coverage/service_coverage.py @@ -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 ServiceCoverage(Document): + pass diff --git a/asset_lite/asset_lite/doctype/site_information/__init__.py b/asset_lite/asset_lite/doctype/site_information/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/site_information/site_information.js b/asset_lite/asset_lite/doctype/site_information/site_information.js new file mode 100644 index 0000000..e39d6a7 --- /dev/null +++ b/asset_lite/asset_lite/doctype/site_information/site_information.js @@ -0,0 +1,8 @@ +// Copyright (c) 2024, seyfert and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Site Information", { +// refresh(frm) { + +// }, +// }); diff --git a/asset_lite/asset_lite/doctype/site_information/site_information.json b/asset_lite/asset_lite/doctype/site_information/site_information.json new file mode 100644 index 0000000..fa4e682 --- /dev/null +++ b/asset_lite/asset_lite/doctype/site_information/site_information.json @@ -0,0 +1,247 @@ +{ + "actions": [], + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:site_name_en", + "creation": "2024-09-24 18:24:05.831422", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "site_name", + "region", + "address_2", + "currency", + "column_break_nmbz", + "site_name_en", + "address1", + "logo", + "contact_information_tab", + "contact_name", + "section_break_xnmk", + "fax_1", + "email_1", + "phone_1", + "phone_3", + "column_break_udpz", + "fax_2", + "email_2", + "phone_2", + "contract_information_tab", + "contract_number", + "contractor_supervisor", + "project_start_date", + "column_break_euyw", + "contract_name", + "site_maintenance_manager", + "project_end_date", + "cut_value__class_tab", + "cut_value_for_class_a_devices", + "cut_value_for_class_b_devices", + "cut_value_for_class_c_devices", + "column_break_ybpx", + "cut_factor_for_class_a_devices", + "cut_factor_for_class_b_devices", + "cut_factor_for_class_c_devices" + ], + "fields": [ + { + "fieldname": "site_name", + "fieldtype": "Data", + "label": "Site Name" + }, + { + "fieldname": "region", + "fieldtype": "Data", + "label": "Region" + }, + { + "fieldname": "address_2", + "fieldtype": "Small Text", + "label": "Address 2" + }, + { + "fieldname": "currency", + "fieldtype": "Link", + "label": "Currency", + "options": "Currency" + }, + { + "fieldname": "column_break_nmbz", + "fieldtype": "Column Break" + }, + { + "fieldname": "site_name_en", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Site Name EN", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "address1", + "fieldtype": "Small Text", + "label": "Address1" + }, + { + "fieldname": "logo", + "fieldtype": "Attach Image", + "label": "Logo" + }, + { + "fieldname": "contact_information_tab", + "fieldtype": "Tab Break", + "label": "Contact Information" + }, + { + "fieldname": "contact_name", + "fieldtype": "Data", + "label": "Contact Name" + }, + { + "fieldname": "section_break_xnmk", + "fieldtype": "Section Break" + }, + { + "fieldname": "fax_1", + "fieldtype": "Data", + "label": "Fax 1" + }, + { + "fieldname": "email_1", + "fieldtype": "Data", + "label": "Email 1" + }, + { + "fieldname": "phone_1", + "fieldtype": "Data", + "label": "Phone 1" + }, + { + "fieldname": "phone_3", + "fieldtype": "Data", + "label": "Phone 3" + }, + { + "fieldname": "column_break_udpz", + "fieldtype": "Column Break" + }, + { + "fieldname": "fax_2", + "fieldtype": "Data", + "label": "Fax 2" + }, + { + "fieldname": "email_2", + "fieldtype": "Data", + "label": "Email 2" + }, + { + "fieldname": "phone_2", + "fieldtype": "Data", + "label": "Phone 2" + }, + { + "fieldname": "contract_information_tab", + "fieldtype": "Tab Break", + "label": "Contract Information" + }, + { + "fieldname": "contract_number", + "fieldtype": "Data", + "label": "Contract Number" + }, + { + "fieldname": "contractor_supervisor", + "fieldtype": "Data", + "label": "Contractor Supervisor" + }, + { + "fieldname": "project_start_date", + "fieldtype": "Date", + "label": "Project Start Date" + }, + { + "fieldname": "column_break_euyw", + "fieldtype": "Column Break" + }, + { + "fieldname": "contract_name", + "fieldtype": "Data", + "label": "Contract Name" + }, + { + "fieldname": "site_maintenance_manager", + "fieldtype": "Data", + "label": "Site Maintenance Manager" + }, + { + "fieldname": "project_end_date", + "fieldtype": "Date", + "label": "Project End Date" + }, + { + "fieldname": "cut_value__class_tab", + "fieldtype": "Tab Break", + "label": "Cut Value / Class" + }, + { + "fieldname": "cut_value_for_class_a_devices", + "fieldtype": "Data", + "label": "Cut Value for Class A devices" + }, + { + "fieldname": "cut_value_for_class_b_devices", + "fieldtype": "Data", + "label": "Cut Value for Class B devices" + }, + { + "fieldname": "cut_value_for_class_c_devices", + "fieldtype": "Data", + "label": "Cut Value for Class C devices" + }, + { + "fieldname": "column_break_ybpx", + "fieldtype": "Column Break" + }, + { + "fieldname": "cut_factor_for_class_a_devices", + "fieldtype": "Data", + "label": "Cut factor for Class A devices" + }, + { + "fieldname": "cut_factor_for_class_b_devices", + "fieldtype": "Data", + "label": "Cut factor for Class B devices" + }, + { + "fieldname": "cut_factor_for_class_c_devices", + "fieldtype": "Data", + "label": "Cut factor for Class C devices" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2025-03-04 16:02:46.101873", + "modified_by": "Administrator", + "module": "Asset Lite", + "name": "Site Information", + "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": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/site_information/site_information.py b/asset_lite/asset_lite/doctype/site_information/site_information.py new file mode 100644 index 0000000..33ba676 --- /dev/null +++ b/asset_lite/asset_lite/doctype/site_information/site_information.py @@ -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 SiteInformation(Document): + pass diff --git a/asset_lite/asset_lite/doctype/site_information/test_site_information.py b/asset_lite/asset_lite/doctype/site_information/test_site_information.py new file mode 100644 index 0000000..a8cca04 --- /dev/null +++ b/asset_lite/asset_lite/doctype/site_information/test_site_information.py @@ -0,0 +1,9 @@ +# Copyright (c) 2024, seyfert and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestSiteInformation(FrappeTestCase): + pass diff --git a/asset_lite/asset_lite/doctype/spare_parts/__init__.py b/asset_lite/asset_lite/doctype/spare_parts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/spare_parts/spare_parts.json b/asset_lite/asset_lite/doctype/spare_parts/spare_parts.json new file mode 100644 index 0000000..78b0ee2 --- /dev/null +++ b/asset_lite/asset_lite/doctype/spare_parts/spare_parts.json @@ -0,0 +1,887 @@ +{ + "actions": [], + "autoname": "hash", + "creation": "2024-09-11 13:39:19.751600", + "default_view": "List", + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "item_code", + "product_bundle", + "col_break1", + "item_name", + "work_order", + "description_section", + "description", + "brand", + "col_break7", + "item_group", + "image", + "image_view", + "quantity_and_rate", + "received_qty", + "qty", + "rejected_qty", + "col_break2", + "uom", + "conversion_factor", + "stock_uom", + "stock_qty", + "sec_break1", + "price_list_rate", + "col_break3", + "base_price_list_rate", + "section_break_26", + "margin_type", + "margin_rate_or_amount", + "rate_with_margin", + "column_break_30", + "discount_percentage", + "discount_amount", + "base_rate_with_margin", + "sec_break2", + "rate", + "amount", + "item_tax_template", + "col_break4", + "base_rate", + "base_amount", + "pricing_rules", + "stock_uom_rate", + "is_free_item", + "section_break_22", + "net_rate", + "net_amount", + "column_break_25", + "base_net_rate", + "base_net_amount", + "valuation_rate", + "item_tax_amount", + "landed_cost_voucher_amount", + "rm_supp_cost", + "warehouse_section", + "warehouse", + "from_warehouse", + "quality_inspection", + "serial_no", + "col_br_wh", + "rejected_warehouse", + "batch_no", + "rejected_serial_no", + "manufacture_details", + "manufacturer", + "column_break_13", + "manufacturer_part_no", + "accounting", + "expense_account", + "col_break5", + "is_fixed_asset", + "asset_location", + "asset_category", + "deferred_expense_section", + "deferred_expense_account", + "service_stop_date", + "enable_deferred_expense", + "column_break_58", + "service_start_date", + "service_end_date", + "reference", + "allow_zero_valuation_rate", + "item_tax_rate", + "bom", + "include_exploded_items", + "purchase_invoice_item", + "col_break6", + "purchase_order", + "po_detail", + "purchase_receipt", + "pr_detail", + "sales_invoice_item", + "item_weight_details", + "weight_per_unit", + "total_weight", + "column_break_38", + "weight_uom", + "accounting_dimensions_section", + "project", + "dimension_col_break", + "cost_center", + "section_break_82", + "page_break" + ], + "fields": [ + { + "bold": 1, + "columns": 3, + "fieldname": "item_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item", + "oldfieldname": "item_code", + "oldfieldtype": "Link", + "options": "Item", + "print_hide": 1, + "search_index": 1 + }, + { + "fieldname": "product_bundle", + "fieldtype": "Link", + "label": "Product Bundle", + "options": "Product Bundle", + "read_only": 1 + }, + { + "fieldname": "col_break1", + "fieldtype": "Column Break" + }, + { + "fetch_from": "item_code.item_name", + "fetch_if_empty": 1, + "fieldname": "item_name", + "fieldtype": "Data", + "in_global_search": 1, + "label": "Item Name", + "oldfieldname": "item_name", + "oldfieldtype": "Data", + "reqd": 1 + }, + { + "collapsible": 1, + "fieldname": "description_section", + "fieldtype": "Section Break", + "label": "Description" + }, + { + "fieldname": "description", + "fieldtype": "Text Editor", + "label": "Description", + "oldfieldname": "description", + "oldfieldtype": "Text", + "print_width": "300px", + "width": "300px" + }, + { + "fieldname": "brand", + "fieldtype": "Link", + "hidden": 1, + "label": "Brand", + "options": "Brand", + "print_hide": 1 + }, + { + "collapsible": 1, + "fieldname": "col_break7", + "fieldtype": "Column Break" + }, + { + "fetch_from": "item_code.item_group", + "fetch_if_empty": 1, + "fieldname": "item_group", + "fieldtype": "Link", + "label": "Item Group", + "options": "Item Group", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "image", + "fieldtype": "Attach", + "hidden": 1, + "label": "Image" + }, + { + "fieldname": "image_view", + "fieldtype": "Image", + "label": "Image View", + "options": "image", + "print_hide": 1 + }, + { + "fieldname": "quantity_and_rate", + "fieldtype": "Section Break", + "label": "Quantity and Rate" + }, + { + "fieldname": "received_qty", + "fieldtype": "Float", + "label": "Received Qty", + "read_only": 1 + }, + { + "bold": 1, + "columns": 2, + "fieldname": "qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Quantity", + "oldfieldname": "qty", + "oldfieldtype": "Currency" + }, + { + "fieldname": "rejected_qty", + "fieldtype": "Float", + "label": "Rejected Qty" + }, + { + "fieldname": "col_break2", + "fieldtype": "Column Break" + }, + { + "default": "Nos", + "fieldname": "uom", + "fieldtype": "Link", + "label": "UOM", + "options": "UOM" + }, + { + "depends_on": "eval:doc.uom != doc.stock_uom", + "fieldname": "conversion_factor", + "fieldtype": "Float", + "label": "UOM Conversion Factor", + "print_hide": 1, + "read_only": 1 + }, + { + "depends_on": "eval:doc.uom != doc.stock_uom", + "fieldname": "stock_uom", + "fieldtype": "Link", + "label": "Stock UOM", + "options": "UOM", + "print_hide": 1, + "read_only": 1 + }, + { + "depends_on": "eval:doc.uom != doc.stock_uom", + "fieldname": "stock_qty", + "fieldtype": "Float", + "label": "Accepted Qty in Stock UOM", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "sec_break1", + "fieldtype": "Section Break" + }, + { + "fieldname": "price_list_rate", + "fieldtype": "Currency", + "label": "Price List Rate", + "options": "currency", + "print_hide": 1 + }, + { + "fieldname": "col_break3", + "fieldtype": "Column Break" + }, + { + "fieldname": "base_price_list_rate", + "fieldtype": "Currency", + "label": "Price List Rate (Company Currency)", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "section_break_26", + "fieldtype": "Section Break", + "label": "Discount and Margin" + }, + { + "depends_on": "price_list_rate", + "fieldname": "margin_type", + "fieldtype": "Select", + "label": "Margin Type", + "options": "\nPercentage\nAmount", + "print_hide": 1 + }, + { + "depends_on": "eval:doc.margin_type && doc.price_list_rate", + "fieldname": "margin_rate_or_amount", + "fieldtype": "Float", + "label": "Margin Rate or Amount", + "print_hide": 1 + }, + { + "depends_on": "eval:doc.margin_type && doc.price_list_rate && doc.margin_rate_or_amount", + "fieldname": "rate_with_margin", + "fieldtype": "Currency", + "label": "Rate With Margin", + "options": "currency", + "read_only": 1 + }, + { + "fieldname": "column_break_30", + "fieldtype": "Column Break" + }, + { + "depends_on": "price_list_rate", + "fieldname": "discount_percentage", + "fieldtype": "Percent", + "label": "Discount on Price List Rate (%)" + }, + { + "depends_on": "price_list_rate", + "fieldname": "discount_amount", + "fieldtype": "Currency", + "label": "Discount Amount", + "options": "currency" + }, + { + "depends_on": "eval:doc.margin_type && doc.price_list_rate && doc.margin_rate_or_amount", + "fieldname": "base_rate_with_margin", + "fieldtype": "Currency", + "label": "Rate With Margin (Company Currency)", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "sec_break2", + "fieldtype": "Section Break" + }, + { + "bold": 1, + "columns": 3, + "fetch_from": "item_code.valuation_rate", + "fetch_if_empty": 1, + "fieldname": "rate", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Rate", + "oldfieldname": "import_rate", + "oldfieldtype": "Currency", + "options": "currency", + "reqd": 1 + }, + { + "columns": 2, + "fieldname": "amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Amount", + "oldfieldname": "import_amount", + "oldfieldtype": "Currency", + "options": "currency", + "reqd": 1 + }, + { + "fieldname": "item_tax_template", + "fieldtype": "Link", + "label": "Item Tax Template", + "options": "Item Tax Template", + "print_hide": 1 + }, + { + "fieldname": "col_break4", + "fieldtype": "Column Break" + }, + { + "fieldname": "base_rate", + "fieldtype": "Currency", + "label": "Rate (Company Currency)", + "oldfieldname": "rate", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "base_amount", + "fieldtype": "Currency", + "label": "Amount (Company Currency)", + "oldfieldname": "amount", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "pricing_rules", + "fieldtype": "Small Text", + "hidden": 1, + "label": "Pricing Rules", + "print_hide": 1, + "read_only": 1 + }, + { + "depends_on": "eval: doc.uom != doc.stock_uom", + "fieldname": "stock_uom_rate", + "fieldtype": "Currency", + "label": "Rate of Stock UOM", + "no_copy": 1, + "options": "currency", + "read_only": 1 + }, + { + "default": "0", + "fieldname": "is_free_item", + "fieldtype": "Check", + "label": "Is Free Item", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "section_break_22", + "fieldtype": "Section Break" + }, + { + "fieldname": "net_rate", + "fieldtype": "Currency", + "label": "Net Rate", + "options": "currency", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "net_amount", + "fieldtype": "Currency", + "label": "Net Amount", + "options": "currency", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_25", + "fieldtype": "Column Break" + }, + { + "fieldname": "base_net_rate", + "fieldtype": "Currency", + "label": "Net Rate (Company Currency)", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "base_net_amount", + "fieldtype": "Currency", + "label": "Net Amount (Company Currency)", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "valuation_rate", + "fieldtype": "Currency", + "hidden": 1, + "label": "Valuation Rate", + "no_copy": 1, + "options": "Company:company:default_currency", + "precision": "6", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "item_tax_amount", + "fieldtype": "Currency", + "hidden": 1, + "label": "Item Tax Amount Included in Value", + "no_copy": 1, + "options": "Company:company:default_currency", + "print_hide": 1, + "print_width": "150px", + "read_only": 1, + "width": "150px" + }, + { + "allow_on_submit": 1, + "fieldname": "landed_cost_voucher_amount", + "fieldtype": "Currency", + "label": "Landed Cost Voucher Amount", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "rm_supp_cost", + "fieldtype": "Currency", + "hidden": 1, + "label": "Raw Materials Supplied Cost", + "no_copy": 1, + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "warehouse_section", + "fieldtype": "Section Break", + "label": "Warehouse" + }, + { + "fieldname": "warehouse", + "fieldtype": "Link", + "label": "Accepted Warehouse", + "options": "Warehouse" + }, + { + "depends_on": "eval:parent.is_internal_supplier && parent.update_stock", + "fieldname": "from_warehouse", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "From Warehouse", + "options": "Warehouse" + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "quality_inspection", + "fieldtype": "Link", + "label": "Quality Inspection", + "no_copy": 1, + "options": "Quality Inspection", + "print_hide": 1 + }, + { + "depends_on": "eval:!doc.is_fixed_asset", + "fieldname": "serial_no", + "fieldtype": "Text", + "label": "Serial No", + "no_copy": 1 + }, + { + "fieldname": "col_br_wh", + "fieldtype": "Column Break" + }, + { + "fieldname": "rejected_warehouse", + "fieldtype": "Link", + "label": "Rejected Warehouse", + "options": "Warehouse" + }, + { + "depends_on": "eval:!doc.is_fixed_asset", + "fieldname": "batch_no", + "fieldtype": "Link", + "label": "Batch No", + "no_copy": 1, + "options": "Batch" + }, + { + "depends_on": "eval:!doc.is_fixed_asset", + "fieldname": "rejected_serial_no", + "fieldtype": "Text", + "label": "Rejected Serial No", + "no_copy": 1, + "print_hide": 1 + }, + { + "collapsible": 1, + "fieldname": "manufacture_details", + "fieldtype": "Section Break", + "label": "Manufacture" + }, + { + "fieldname": "manufacturer", + "fieldtype": "Link", + "label": "Manufacturer", + "options": "Manufacturer" + }, + { + "fieldname": "column_break_13", + "fieldtype": "Column Break" + }, + { + "fieldname": "manufacturer_part_no", + "fieldtype": "Data", + "label": "Manufacturer Part Number" + }, + { + "fieldname": "accounting", + "fieldtype": "Section Break", + "label": "Accounting" + }, + { + "fieldname": "expense_account", + "fieldtype": "Link", + "label": "Expense Head", + "oldfieldname": "expense_head", + "oldfieldtype": "Link", + "options": "Account", + "print_hide": 1, + "print_width": "120px", + "width": "120px" + }, + { + "fieldname": "col_break5", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fetch_from": "item_code.is_fixed_asset", + "fieldname": "is_fixed_asset", + "fieldtype": "Check", + "hidden": 1, + "label": "Is Fixed Asset", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "depends_on": "is_fixed_asset", + "fieldname": "asset_location", + "fieldtype": "Link", + "label": "Asset Location", + "options": "Location" + }, + { + "depends_on": "is_fixed_asset", + "fetch_from": "item_code.asset_category", + "fieldname": "asset_category", + "fieldtype": "Link", + "label": "Asset Category", + "options": "Asset Category", + "read_only": 1 + }, + { + "collapsible": 1, + "collapsible_depends_on": "enable_deferred_expense", + "fieldname": "deferred_expense_section", + "fieldtype": "Section Break", + "label": "Deferred Expense" + }, + { + "depends_on": "enable_deferred_expense", + "fieldname": "deferred_expense_account", + "fieldtype": "Link", + "label": "Deferred Expense Account", + "options": "Account" + }, + { + "allow_on_submit": 1, + "depends_on": "enable_deferred_expense", + "fieldname": "service_stop_date", + "fieldtype": "Date", + "label": "Service Stop Date", + "no_copy": 1 + }, + { + "default": "0", + "fieldname": "enable_deferred_expense", + "fieldtype": "Check", + "label": "Enable Deferred Expense" + }, + { + "fieldname": "column_break_58", + "fieldtype": "Column Break" + }, + { + "depends_on": "enable_deferred_expense", + "fieldname": "service_start_date", + "fieldtype": "Date", + "label": "Service Start Date", + "no_copy": 1 + }, + { + "depends_on": "enable_deferred_expense", + "fieldname": "service_end_date", + "fieldtype": "Date", + "label": "Service End Date", + "no_copy": 1 + }, + { + "fieldname": "reference", + "fieldtype": "Section Break", + "label": "Reference" + }, + { + "default": "0", + "fieldname": "allow_zero_valuation_rate", + "fieldtype": "Check", + "label": "Allow Zero Valuation Rate", + "no_copy": 1, + "print_hide": 1 + }, + { + "description": "Tax detail table fetched from item master as a string and stored in this field.\nUsed for Taxes and Charges", + "fieldname": "item_tax_rate", + "fieldtype": "Code", + "hidden": 1, + "label": "Item Tax Rate", + "oldfieldname": "item_tax_rate", + "oldfieldtype": "Small Text", + "print_hide": 1, + "read_only": 1, + "report_hide": 1 + }, + { + "depends_on": "eval:parent.is_old_subcontracting_flow", + "fieldname": "bom", + "fieldtype": "Link", + "label": "BOM", + "options": "BOM", + "read_only": 1, + "read_only_depends_on": "eval:!parent.is_old_subcontracting_flow" + }, + { + "default": "0", + "depends_on": "eval:parent.is_subcontracted", + "fieldname": "include_exploded_items", + "fieldtype": "Check", + "label": "Include Exploded Items", + "print_hide": 1, + "read_only": 1 + }, + { + "depends_on": "eval:parent.update_stock == 1", + "fieldname": "purchase_invoice_item", + "fieldtype": "Data", + "ignore_user_permissions": 1, + "label": "Purchase Invoice Item", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "col_break6", + "fieldtype": "Column Break" + }, + { + "fieldname": "purchase_order", + "fieldtype": "Link", + "label": "Purchase Order", + "no_copy": 1, + "oldfieldname": "purchase_order", + "oldfieldtype": "Link", + "options": "Purchase Order", + "print_hide": 1, + "read_only": 1, + "search_index": 1 + }, + { + "fieldname": "po_detail", + "fieldtype": "Data", + "hidden": 1, + "label": "Purchase Order Item", + "no_copy": 1, + "oldfieldname": "po_detail", + "oldfieldtype": "Data", + "print_hide": 1, + "read_only": 1, + "search_index": 1 + }, + { + "fieldname": "purchase_receipt", + "fieldtype": "Link", + "label": "Purchase Receipt", + "no_copy": 1, + "oldfieldname": "purchase_receipt", + "oldfieldtype": "Link", + "options": "Purchase Receipt", + "print_hide": 1, + "read_only": 1, + "search_index": 1 + }, + { + "fieldname": "pr_detail", + "fieldtype": "Data", + "hidden": 1, + "label": "Purchase Receipt Detail", + "no_copy": 1, + "oldfieldname": "pr_detail", + "oldfieldtype": "Data", + "print_hide": 1, + "read_only": 1, + "search_index": 1 + }, + { + "fieldname": "sales_invoice_item", + "fieldtype": "Data", + "label": "Sales Invoice Item", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "item_weight_details", + "fieldtype": "Section Break", + "label": "Item Weight Details" + }, + { + "fieldname": "weight_per_unit", + "fieldtype": "Float", + "label": "Weight Per Unit" + }, + { + "fieldname": "total_weight", + "fieldtype": "Float", + "label": "Total Weight", + "read_only": 1 + }, + { + "fieldname": "column_break_38", + "fieldtype": "Column Break" + }, + { + "fieldname": "weight_uom", + "fieldtype": "Link", + "label": "Weight UOM", + "options": "UOM" + }, + { + "collapsible": 1, + "fieldname": "accounting_dimensions_section", + "fieldtype": "Section Break", + "label": "Accounting Dimensions" + }, + { + "fieldname": "project", + "fieldtype": "Link", + "label": "Project", + "options": "Project", + "print_hide": 1 + }, + { + "fieldname": "dimension_col_break", + "fieldtype": "Column Break" + }, + { + "default": ":Company", + "depends_on": "eval:!doc.is_fixed_asset", + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "oldfieldname": "cost_center", + "oldfieldtype": "Link", + "options": "Cost Center", + "print_hide": 1, + "print_width": "120px", + "width": "120px" + }, + { + "fieldname": "section_break_82", + "fieldtype": "Section Break" + }, + { + "allow_on_submit": 1, + "default": "0", + "fieldname": "page_break", + "fieldtype": "Check", + "label": "Page Break", + "no_copy": 1, + "print_hide": 1, + "report_hide": 1 + }, + { + "fieldname": "work_order", + "fieldtype": "Link", + "label": "Work Order", + "options": "Work_Order" + } + ], + "istable": 1, + "links": [], + "modified": "2025-03-09 11:05:37.287039", + "modified_by": "Administrator", + "module": "Asset Lite", + "name": "Spare Parts", + "naming_rule": "Random", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/spare_parts/spare_parts.py b/asset_lite/asset_lite/doctype/spare_parts/spare_parts.py new file mode 100644 index 0000000..040be3e --- /dev/null +++ b/asset_lite/asset_lite/doctype/spare_parts/spare_parts.py @@ -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 SpareParts(Document): + pass diff --git a/asset_lite/asset_lite/doctype/supplier_reason/__init__.py b/asset_lite/asset_lite/doctype/supplier_reason/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/supplier_reason/supplier_reason.js b/asset_lite/asset_lite/doctype/supplier_reason/supplier_reason.js new file mode 100644 index 0000000..d14f0a6 --- /dev/null +++ b/asset_lite/asset_lite/doctype/supplier_reason/supplier_reason.js @@ -0,0 +1,8 @@ +// Copyright (c) 2025, seyfert and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Supplier Reason", { +// refresh(frm) { + +// }, +// }); diff --git a/asset_lite/asset_lite/doctype/supplier_reason/supplier_reason.json b/asset_lite/asset_lite/doctype/supplier_reason/supplier_reason.json new file mode 100644 index 0000000..1eb3c91 --- /dev/null +++ b/asset_lite/asset_lite/doctype/supplier_reason/supplier_reason.json @@ -0,0 +1,44 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "field:reason", + "creation": "2025-03-27 11:32:46.883033", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "reason" + ], + "fields": [ + { + "fieldname": "reason", + "fieldtype": "Data", + "label": "Reason", + "unique": 1 + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2025-03-27 11:35:40.654313", + "modified_by": "Administrator", + "module": "Asset Lite", + "name": "Supplier Reason", + "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": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/supplier_reason/supplier_reason.py b/asset_lite/asset_lite/doctype/supplier_reason/supplier_reason.py new file mode 100644 index 0000000..416c85c --- /dev/null +++ b/asset_lite/asset_lite/doctype/supplier_reason/supplier_reason.py @@ -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 SupplierReason(Document): + pass diff --git a/asset_lite/asset_lite/doctype/supplier_reason/test_supplier_reason.py b/asset_lite/asset_lite/doctype/supplier_reason/test_supplier_reason.py new file mode 100644 index 0000000..3d0fa52 --- /dev/null +++ b/asset_lite/asset_lite/doctype/supplier_reason/test_supplier_reason.py @@ -0,0 +1,9 @@ +# Copyright (c) 2025, seyfert and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestSupplierReason(FrappeTestCase): + pass diff --git a/asset_lite/asset_lite/doctype/support_asset_list/__init__.py b/asset_lite/asset_lite/doctype/support_asset_list/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/support_asset_list/support_asset_list.json b/asset_lite/asset_lite/doctype/support_asset_list/support_asset_list.json new file mode 100644 index 0000000..ebe0342 --- /dev/null +++ b/asset_lite/asset_lite/doctype/support_asset_list/support_asset_list.json @@ -0,0 +1,37 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2024-10-17 13:44:10.569824", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "asset_id", + "asset_name" + ], + "fields": [ + { + "fieldname": "asset_id", + "fieldtype": "Link", + "label": "Asset ID", + "options": "Asset" + }, + { + "fieldname": "asset_name", + "fieldtype": "Data", + "label": "Asset Name" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2024-10-17 13:49:53.892284", + "modified_by": "Administrator", + "module": "Asset Lite", + "name": "Support Asset List", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/support_asset_list/support_asset_list.py b/asset_lite/asset_lite/doctype/support_asset_list/support_asset_list.py new file mode 100644 index 0000000..8d442a3 --- /dev/null +++ b/asset_lite/asset_lite/doctype/support_asset_list/support_asset_list.py @@ -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 SupportAssetList(Document): + pass diff --git a/asset_lite/asset_lite/doctype/support_plans/__init__.py b/asset_lite/asset_lite/doctype/support_plans/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/support_plans/support_plans.js b/asset_lite/asset_lite/doctype/support_plans/support_plans.js new file mode 100644 index 0000000..5309bf9 --- /dev/null +++ b/asset_lite/asset_lite/doctype/support_plans/support_plans.js @@ -0,0 +1,8 @@ +// Copyright (c) 2024, seyfert and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Support Plans", { +// refresh(frm) { + +// }, +// }); diff --git a/asset_lite/asset_lite/doctype/support_plans/support_plans.json b/asset_lite/asset_lite/doctype/support_plans/support_plans.json new file mode 100644 index 0000000..f87d75f --- /dev/null +++ b/asset_lite/asset_lite/doctype/support_plans/support_plans.json @@ -0,0 +1,253 @@ +{ + "actions": [], + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:support_plan", + "creation": "2024-09-13 14:42:48.041382", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "support_plan", + "frequency", + "max_downtime_hrs", + "column_break_ayau", + "asset", + "starting_date", + "penalty_factor", + "section_break_zyxt", + "warranty", + "warranty_start_date", + "warranty_end_date", + "war_status", + "column_break_agzy", + "extended_warranty", + "start", + "end", + "service_contract_section", + "service_contract", + "spare_parts", + "spare_parts_labour", + "labour", + "ppm_only", + "column_break_celd", + "no", + "start_date", + "end_date", + "service_contract_status", + "vendor_details_section", + "vendor", + "section_break_pyrk", + "asset_list" + ], + "fields": [ + { + "fieldname": "support_plan", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Name", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "frequency", + "fieldtype": "Select", + "label": "Frequency", + "options": "\nDaily\nWeekly\nMonthly\nQuarterly\nYearly\n2 Yearly" + }, + { + "fieldname": "asset", + "fieldtype": "Link", + "hidden": 1, + "label": "Asset", + "options": "Asset" + }, + { + "fieldname": "column_break_ayau", + "fieldtype": "Column Break" + }, + { + "fieldname": "starting_date", + "fieldtype": "Date", + "hidden": 1, + "label": "Starting Date" + }, + { + "fieldname": "service_contract_section", + "fieldtype": "Section Break", + "label": "Service Contract" + }, + { + "default": "0", + "depends_on": "eval:doc.service_contract == 1", + "fieldname": "spare_parts", + "fieldtype": "Check", + "label": "Comprehensive" + }, + { + "fieldname": "column_break_celd", + "fieldtype": "Column Break" + }, + { + "default": "0", + "depends_on": "eval:doc.service_contract == 1", + "fieldname": "labour", + "fieldtype": "Check", + "label": "Labour Only" + }, + { + "fieldname": "start_date", + "fieldtype": "Date", + "hidden": 1, + "label": "Start Date", + "mandatory_depends_on": "service_contract" + }, + { + "depends_on": "eval:doc.service_contract == 1", + "fieldname": "service_contract_status", + "fieldtype": "Select", + "label": "Service Contract Status", + "options": "\nActive\nExpired\nNot Applicable" + }, + { + "fieldname": "end_date", + "fieldtype": "Date", + "hidden": 1, + "label": "End Date" + }, + { + "default": "0", + "fieldname": "service_contract", + "fieldtype": "Check", + "label": "Yes" + }, + { + "fieldname": "vendor_details_section", + "fieldtype": "Section Break", + "label": "Vendor Details" + }, + { + "fieldname": "vendor", + "fieldtype": "Link", + "label": "Vendor Name", + "options": "Supplier" + }, + { + "fieldname": "section_break_zyxt", + "fieldtype": "Section Break", + "label": "Warranty Details" + }, + { + "fieldname": "warranty_start_date", + "fieldtype": "Date", + "hidden": 1, + "label": "Warranty Start Date" + }, + { + "fieldname": "warranty_end_date", + "fieldtype": "Date", + "hidden": 1, + "label": "Warranty End Date" + }, + { + "default": "0", + "fieldname": "warranty", + "fieldtype": "Check", + "label": "Warranty" + }, + { + "default": "0", + "depends_on": "eval:doc.warranty == 1", + "fieldname": "extended_warranty", + "fieldtype": "Check", + "label": "Extended Warranty" + }, + { + "fieldname": "column_break_agzy", + "fieldtype": "Column Break" + }, + { + "fieldname": "start", + "fieldtype": "Date", + "hidden": 1, + "label": "Start Date" + }, + { + "fieldname": "end", + "fieldtype": "Date", + "hidden": 1, + "label": "End Date" + }, + { + "depends_on": "eval:doc.warranty == 1", + "fieldname": "war_status", + "fieldtype": "Select", + "label": "Warranty Status", + "options": "\nActive\nExpired\nNA" + }, + { + "default": "0", + "depends_on": "eval:doc.service_contract == 1", + "fieldname": "ppm_only", + "fieldtype": "Check", + "label": "PPM Only" + }, + { + "default": "0", + "depends_on": "eval:doc.service_contract == 1", + "fieldname": "spare_parts_labour", + "fieldtype": "Check", + "label": "Spare Parts & Labour" + }, + { + "default": "0", + "fieldname": "no", + "fieldtype": "Check", + "label": "No" + }, + { + "fieldname": "section_break_pyrk", + "fieldtype": "Section Break" + }, + { + "fieldname": "asset_list", + "fieldtype": "Table", + "label": "Asset List", + "options": "Support Asset List" + }, + { + "fieldname": "max_downtime_hrs", + "fieldtype": "Float", + "label": "Max Downtime Hrs" + }, + { + "fieldname": "penalty_factor", + "fieldtype": "Float", + "label": "Penalty Factor" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2025-03-04 15:35:02.126640", + "modified_by": "Administrator", + "module": "Asset Lite", + "name": "Support Plans", + "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": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/support_plans/support_plans.py b/asset_lite/asset_lite/doctype/support_plans/support_plans.py new file mode 100644 index 0000000..eab7ddf --- /dev/null +++ b/asset_lite/asset_lite/doctype/support_plans/support_plans.py @@ -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 SupportPlans(Document): + pass diff --git a/asset_lite/asset_lite/doctype/support_plans/test_support_plans.py b/asset_lite/asset_lite/doctype/support_plans/test_support_plans.py new file mode 100644 index 0000000..ab26525 --- /dev/null +++ b/asset_lite/asset_lite/doctype/support_plans/test_support_plans.py @@ -0,0 +1,9 @@ +# Copyright (c) 2024, seyfert and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestSupportPlans(FrappeTestCase): + pass diff --git a/asset_lite/asset_lite/doctype/technical_department/__init__.py b/asset_lite/asset_lite/doctype/technical_department/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/technical_department/technical_department.js b/asset_lite/asset_lite/doctype/technical_department/technical_department.js new file mode 100644 index 0000000..29e72d5 --- /dev/null +++ b/asset_lite/asset_lite/doctype/technical_department/technical_department.js @@ -0,0 +1,8 @@ +// Copyright (c) 2026, seyfert and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Technical Department", { +// refresh(frm) { + +// }, +// }); diff --git a/asset_lite/asset_lite/doctype/technical_department/technical_department.json b/asset_lite/asset_lite/doctype/technical_department/technical_department.json new file mode 100644 index 0000000..0275d4d --- /dev/null +++ b/asset_lite/asset_lite/doctype/technical_department/technical_department.json @@ -0,0 +1,47 @@ +{ + "actions": [], + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:department", + "creation": "2026-01-06 17:36:02.667749", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "department" + ], + "fields": [ + { + "fieldname": "department", + "fieldtype": "Data", + "label": "Department", + "unique": 1 + } + ], + "grid_page_length": 50, + "index_web_pages_for_search": 1, + "links": [], + "modified": "2026-03-10 10:52:01.372815", + "modified_by": "Administrator", + "module": "Asset Lite", + "name": "Technical Department", + "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": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/technical_department/technical_department.py b/asset_lite/asset_lite/doctype/technical_department/technical_department.py new file mode 100644 index 0000000..15ba627 --- /dev/null +++ b/asset_lite/asset_lite/doctype/technical_department/technical_department.py @@ -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 TechnicalDepartment(Document): + pass diff --git a/asset_lite/asset_lite/doctype/technical_department/test_technical_department.py b/asset_lite/asset_lite/doctype/technical_department/test_technical_department.py new file mode 100644 index 0000000..b53781e --- /dev/null +++ b/asset_lite/asset_lite/doctype/technical_department/test_technical_department.py @@ -0,0 +1,9 @@ +# Copyright (c) 2026, seyfert and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestTechnicalDepartment(FrappeTestCase): + pass diff --git a/asset_lite/asset_lite/doctype/warranty/__init__.py b/asset_lite/asset_lite/doctype/warranty/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/warranty/test_warranty.py b/asset_lite/asset_lite/doctype/warranty/test_warranty.py new file mode 100644 index 0000000..7b6886c --- /dev/null +++ b/asset_lite/asset_lite/doctype/warranty/test_warranty.py @@ -0,0 +1,9 @@ +# Copyright (c) 2024, seyfert and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestWarranty(FrappeTestCase): + pass diff --git a/asset_lite/asset_lite/doctype/warranty/warranty.js b/asset_lite/asset_lite/doctype/warranty/warranty.js new file mode 100644 index 0000000..7b232e8 --- /dev/null +++ b/asset_lite/asset_lite/doctype/warranty/warranty.js @@ -0,0 +1,8 @@ +// Copyright (c) 2024, seyfert and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Warranty", { +// refresh(frm) { + +// }, +// }); diff --git a/asset_lite/asset_lite/doctype/warranty/warranty.json b/asset_lite/asset_lite/doctype/warranty/warranty.json new file mode 100644 index 0000000..167cf2e --- /dev/null +++ b/asset_lite/asset_lite/doctype/warranty/warranty.json @@ -0,0 +1,110 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "naming_series:", + "creation": "2024-09-17 13:07:02.620103", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "naming_series", + "asset", + "asset_name", + "extended_warranty", + "start_date", + "end_date", + "column_break_dxer", + "warranty_start_date", + "warranty_end_date", + "warranty_status" + ], + "fields": [ + { + "fieldname": "asset", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Asset", + "options": "Asset", + "reqd": 1 + }, + { + "fieldname": "warranty_start_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Warranty Start Date", + "reqd": 1 + }, + { + "fieldname": "column_break_dxer", + "fieldtype": "Column Break" + }, + { + "fetch_from": "asset.asset_name", + "fieldname": "asset_name", + "fieldtype": "Data", + "label": "Asset Name", + "read_only": 1 + }, + { + "fieldname": "warranty_end_date", + "fieldtype": "Date", + "label": "Warranty End Date" + }, + { + "fieldname": "warranty_status", + "fieldtype": "Select", + "label": "Warranty Status", + "options": "\nActive\nExpired\nNot Applicable", + "read_only": 1 + }, + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Naming Series", + "options": "WN-.####", + "reqd": 1 + }, + { + "default": "0", + "fieldname": "extended_warranty", + "fieldtype": "Check", + "label": "Extended Warranty" + }, + { + "depends_on": "eval:doc.extended_warranty == 1", + "fieldname": "start_date", + "fieldtype": "Date", + "label": "Start Date" + }, + { + "depends_on": "eval:doc.extended_warranty == 1", + "fieldname": "end_date", + "fieldtype": "Date", + "label": "End Date" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2024-09-23 15:36:40.924090", + "modified_by": "Administrator", + "module": "Asset Lite", + "name": "Warranty", + "naming_rule": "By \"Naming Series\" field", + "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": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/warranty/warranty.py b/asset_lite/asset_lite/doctype/warranty/warranty.py new file mode 100644 index 0000000..b760e44 --- /dev/null +++ b/asset_lite/asset_lite/doctype/warranty/warranty.py @@ -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 Warranty(Document): + pass diff --git a/asset_lite/asset_lite/doctype/work_order/__init__.py b/asset_lite/asset_lite/doctype/work_order/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/work_order/test_work_order.py b/asset_lite/asset_lite/doctype/work_order/test_work_order.py new file mode 100644 index 0000000..08b553b --- /dev/null +++ b/asset_lite/asset_lite/doctype/work_order/test_work_order.py @@ -0,0 +1,9 @@ +# Copyright (c) 2024, seyfert and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestWork_Order(FrappeTestCase): + pass diff --git a/asset_lite/asset_lite/doctype/work_order/work_order.js b/asset_lite/asset_lite/doctype/work_order/work_order.js new file mode 100644 index 0000000..eac4fa0 --- /dev/null +++ b/asset_lite/asset_lite/doctype/work_order/work_order.js @@ -0,0 +1,75 @@ +// Copyright (c) 2024, seyfert and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Work_Order", { +// refresh(frm) { + +// }, +// }); + +frappe.ui.form.on("Work_Order", { + refresh(frm) { + // Call the server-side method to get the site version type + frm.call({ + method: "check_site_version", + doc: frm.doc, + callback: function(response) { + // The returned site version type + var site_version = response.message; + + if (site_version === "lite") { + frm.set_df_property("need_spare_parts_purchase", "hidden", 1); + // frappe.msgprint("The Need Procurement checkbox is hided as the site version is 'lite'."); + } else { + frm.set_df_property("need_spare_parts_purchase", "hidden", 0); + } + } + }); + + // Hide the default print icon + frm.page.hide_icon_group('print'); + + + // Add custom button for PPM Service Report with a print icon + /*frm.add_custom_button( + ` ${__('Service Report')}`, + function() { + // Set Service Report as the default print format and open print preview + const customLink = `/printview?doctype=Work_Order&name=${frm.doc.name}&trigger_print=0&format=Service%20Report&no_letterhead=0`; + window.open(customLink); + } + );*/ + if(frm.doc.company=='King Fahad Hospital'){ + frm.add_custom_button( + ` ${__('SR-King Fahad Hospital')}`, + function() { + // Set Service Report as the default print format and open print preview + const customLink = `/printview?doctype=Work_Order&name=${frm.doc.name}&trigger_print=0&format=Service%20Report&no_letterhead=0`; + window.open(customLink); + } + ); + } + if(frm.doc.company=='King Khalid Hospital'){ + frm.add_custom_button( + ` ${__('SR-King Khalid Hospital')}`, + function() { + // Set Service Report as the default print format and open print preview + const customLink = `/printview?doctype=Work_Order&name=${frm.doc.name}&trigger_print=0&format=Service%20Report(KK)&no_letterhead=0`; + window.open(customLink); + } + ); + } + + }, + /*setup: (frm) => { + frm.set_query("assign_to", "asset_maintenance_tasks", function (doc) { + return { + query: "erpnext.assets.doctype.asset_maintenance.asset_maintenance.get_team_members", + filters: { + maintenance_team: doc.maintenance_team, + }, + }; + }); + + }*/ +}); diff --git a/asset_lite/asset_lite/doctype/work_order/work_order.json b/asset_lite/asset_lite/doctype/work_order/work_order.json new file mode 100644 index 0000000..c558ac9 --- /dev/null +++ b/asset_lite/asset_lite/doctype/work_order/work_order.json @@ -0,0 +1,766 @@ +{ + "actions": [], + "allow_import": 1, + "autoname": "naming_series:", + "creation": "2024-09-11 13:13:50.398974", + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "naming_series", + "work_order_type", + "asset_type", + "company", + "site_name", + "manufacturer", + "need_procurement", + "column_break_2", + "priority", + "asset", + "department", + "aseet_id", + "recall_reference_number", + "vendor", + "column_break_kmog", + "repair_status", + "asset_name", + "supplier", + "inspection", + "section_break_okba", + "workflow_state", + "warranty_and_service_details_section", + "war", + "warranty", + "service_contract", + "column_break_sdfm", + "service_contract_details", + "covering_spare_parts", + "spare_parts_labour", + "covering_labour", + "ppm_only", + "section_break_5", + "failure_date", + "first_responded_on", + "total_hours_spent", + "penalty", + "feedback", + "feedback_rating", + "column_break_6", + "completion_date", + "job_completed", + "assigned_manager", + "assigned_technician", + "custom_difference", + "accounting_dimensions_section", + "cost_center", + "column_break_14", + "project", + "defective_spare_parts_section", + "spare_parts", + "table_cmqp", + "make_details_section", + "make", + "model", + "column_break_ixht", + "serial_number", + "purchase_details_section", + "invoice_table", + "accounting_details", + "purchase_invoice", + "capitalize_repair_cost", + "stock_consumption", + "column_break_8", + "repair_cost", + "stock_consumption_details_section", + "stock_items", + "total_repair_cost", + "asset_depreciation_details_section", + "increase_in_asset_life", + "section_break_9", + "description", + "column_break_9", + "actions_performed", + "section_break_23", + "downtime", + "column_break_19", + "amended_from", + "section_break_azqe", + "total_main_hour_at_site", + "serviced_by", + "sign1", + "date1", + "column_break_wnzz", + "total_travel_hour", + "end_user", + "sign2", + "date2", + "column_break_pwkx", + "total_hours", + "bio_med_dept", + "sign3", + "date3", + "comments_section", + "customer_comments" + ], + "fields": [ + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Work Order Number", + "options": "WO-.YYYY.-", + "reqd": 1 + }, + { + "fetch_from": "asset.company", + "fieldname": "company", + "fieldtype": "Link", + "label": "Hospital Name", + "options": "Company" + }, + { + "default": "Repair (CM)", + "fieldname": "work_order_type", + "fieldtype": "Link", + "in_filter": 1, + "in_global_search": 1, + "in_standard_filter": 1, + "label": "Work Order Type", + "options": "Issue Type" + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "columns": 1, + "fieldname": "asset", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Asset ID", + "options": "Asset" + }, + { + "fetch_from": "asset.asset_name", + "fieldname": "asset_name", + "fieldtype": "Read Only", + "label": "Asset Name" + }, + { + "fieldname": "priority", + "fieldtype": "Link", + "label": "Priority", + "options": "Issue Priority" + }, + { + "fieldname": "section_break_5", + "fieldtype": "Section Break", + "label": "Response Details" + }, + { + "columns": 1, + "fieldname": "failure_date", + "fieldtype": "Datetime", + "label": "Failure Date", + "reqd": 1 + }, + { + "default": "Open", + "fieldname": "repair_status", + "fieldtype": "Select", + "in_filter": 1, + "in_global_search": 1, + "in_standard_filter": 1, + "label": "Work Order Status", + "no_copy": 1, + "options": "Open\nWork In Progress\nPending Review\nCompleted\nCancelled\nClosed", + "print_hide": 1 + }, + { + "fieldname": "first_responded_on", + "fieldtype": "Datetime", + "label": "First Responded On", + "permlevel": 2 + }, + { + "fieldname": "column_break_6", + "fieldtype": "Column Break" + }, + { + "allow_on_submit": 1, + "depends_on": "eval:!doc.__islocal", + "fieldname": "completion_date", + "fieldtype": "Datetime", + "label": "Completion Date", + "mandatory_depends_on": "eval:doc.repair_status == \"Completed\" || doc.repair_status == \"Closed\"", + "no_copy": 1 + }, + { + "allow_on_submit": 1, + "default": "No", + "fieldname": "job_completed", + "fieldtype": "Select", + "label": "Job Completed", + "options": "\nNo\nYes", + "permlevel": 2, + "read_only": 1 + }, + { + "fieldname": "assigned_manager", + "fieldtype": "Link", + "label": "Assigned Manager", + "options": "User", + "read_only": 1 + }, + { + "depends_on": "eval:doc.assigned_manager", + "fieldname": "assigned_technician", + "fieldtype": "Link", + "label": "Assigned Technician", + "options": "User" + }, + { + "collapsible": 1, + "fieldname": "accounting_dimensions_section", + "fieldtype": "Section Break", + "hidden": 1, + "label": "Accounting Dimensions" + }, + { + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "options": "Cost Center" + }, + { + "fieldname": "column_break_14", + "fieldtype": "Column Break" + }, + { + "fieldname": "project", + "fieldtype": "Link", + "label": "Project", + "options": "Project" + }, + { + "collapsible": 1, + "fieldname": "defective_spare_parts_section", + "fieldtype": "Section Break", + "label": "Defective Spare Parts " + }, + { + "fieldname": "spare_parts", + "fieldtype": "Button", + "label": "Spare Parts" + }, + { + "fieldname": "table_cmqp", + "fieldtype": "Table", + "options": "Spare Parts" + }, + { + "collapsible": 1, + "fieldname": "make_details_section", + "fieldtype": "Section Break", + "label": "Make details" + }, + { + "fetch_from": "asset.custom_make", + "fetch_if_empty": 1, + "fieldname": "make", + "fieldtype": "Data", + "label": "Make" + }, + { + "fetch_from": "asset.custom_model", + "fetch_if_empty": 1, + "fieldname": "model", + "fieldtype": "Data", + "label": "Model" + }, + { + "fieldname": "column_break_ixht", + "fieldtype": "Column Break" + }, + { + "fetch_from": "asset.custom_serial_number", + "fetch_if_empty": 1, + "fieldname": "serial_number", + "fieldtype": "Data", + "label": "Serial Number" + }, + { + "fieldname": "accounting_details", + "fieldtype": "Section Break", + "label": "Accounting Details" + }, + { + "fieldname": "purchase_invoice", + "fieldtype": "Link", + "hidden": 1, + "label": "Purchase Invoice", + "mandatory_depends_on": "eval: doc.repair_status == 'Completed' && doc.repair_cost > 0", + "no_copy": 1, + "options": "Purchase Invoice" + }, + { + "default": "0", + "depends_on": "eval:!doc.__islocal", + "fieldname": "capitalize_repair_cost", + "fieldtype": "Check", + "hidden": 1, + "label": "Capitalize Repair Cost" + }, + { + "default": "0", + "fieldname": "stock_consumption", + "fieldtype": "Check", + "label": "Stock Consumed During Repair" + }, + { + "fieldname": "column_break_8", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "repair_cost", + "fieldtype": "Currency", + "label": "Repair Cost", + "read_only": 1 + }, + { + "depends_on": "stock_consumption", + "fieldname": "stock_consumption_details_section", + "fieldtype": "Section Break", + "label": "Stock Consumption Details" + }, + { + "fieldname": "stock_items", + "fieldtype": "Table", + "label": "Stock Items", + "mandatory_depends_on": "stock_consumption", + "options": "Asset Repair Consumed Item" + }, + { + "depends_on": "eval: doc.stock_consumption && doc.total_repair_cost > 0", + "description": "Sum of Repair Cost and Value of Consumed Stock Items.", + "fieldname": "total_repair_cost", + "fieldtype": "Currency", + "label": "Total Repair Cost", + "read_only": 1 + }, + { + "depends_on": "capitalize_repair_cost", + "fieldname": "asset_depreciation_details_section", + "fieldtype": "Section Break", + "hidden": 1, + "label": "Asset Depreciation Details" + }, + { + "fieldname": "increase_in_asset_life", + "fieldtype": "Int", + "label": "Increase In Asset Life(Months)", + "no_copy": 1 + }, + { + "fieldname": "section_break_9", + "fieldtype": "Section Break", + "label": "Description" + }, + { + "fieldname": "description", + "fieldtype": "Long Text", + "label": "Nature of Complaint" + }, + { + "fieldname": "column_break_9", + "fieldtype": "Column Break" + }, + { + "fieldname": "actions_performed", + "fieldtype": "Long Text", + "label": "Work Performed", + "permlevel": 2 + }, + { + "fieldname": "section_break_23", + "fieldtype": "Section Break" + }, + { + "fieldname": "downtime", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Downtime", + "read_only": 1 + }, + { + "fieldname": "column_break_19", + "fieldtype": "Column Break" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Asset Repair", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "section_break_azqe", + "fieldtype": "Section Break" + }, + { + "fieldname": "total_main_hour_at_site", + "fieldtype": "Data", + "label": "Total Main Hour At Site", + "read_only_depends_on": "eval:!frappe.user.has_role(\"Technician\")" + }, + { + "fieldname": "serviced_by", + "fieldtype": "Data", + "label": "Serviced By" + }, + { + "depends_on": "eval:doc.serviced_by", + "fieldname": "sign1", + "fieldtype": "Data", + "label": "Signature" + }, + { + "depends_on": "eval:doc.serviced_by", + "fieldname": "date1", + "fieldtype": "Date", + "label": "Date" + }, + { + "fieldname": "column_break_wnzz", + "fieldtype": "Column Break" + }, + { + "fieldname": "total_travel_hour", + "fieldtype": "Data", + "label": "Total Travel Hour", + "read_only_depends_on": "eval:!frappe.user.has_role(\"End user\")" + }, + { + "fieldname": "end_user", + "fieldtype": "Data", + "label": "End user" + }, + { + "depends_on": "eval:doc.end_user", + "fieldname": "sign2", + "fieldtype": "Data", + "label": "Signature" + }, + { + "depends_on": "eval:doc.end_user", + "fieldname": "date2", + "fieldtype": "Date", + "label": "Date" + }, + { + "fieldname": "column_break_pwkx", + "fieldtype": "Column Break" + }, + { + "fieldname": "total_hours", + "fieldtype": "Data", + "label": "Total Hours", + "read_only_depends_on": "eval:!frappe.user.has_role(\"Maintenance Manager\")" + }, + { + "fieldname": "bio_med_dept", + "fieldtype": "Data", + "label": "Bio-Med Dept" + }, + { + "depends_on": "eval:doc.bio_med_dept", + "fieldname": "sign3", + "fieldtype": "Data", + "label": "Signature" + }, + { + "depends_on": "eval:doc.bio_med_dept", + "fieldname": "date3", + "fieldtype": "Date", + "label": "Date" + }, + { + "fieldname": "comments_section", + "fieldtype": "Section Break", + "label": "Comments" + }, + { + "fieldname": "customer_comments", + "fieldtype": "Small Text", + "label": "Customer Comments" + }, + { + "fieldname": "warranty_and_service_details_section", + "fieldtype": "Section Break", + "label": "Warranty And Service Contract Details" + }, + { + "default": "0", + "fetch_from": "asset.custom_warranty", + "fieldname": "warranty", + "fieldtype": "Check", + "label": "Warranty", + "read_only": 1 + }, + { + "default": "0", + "fetch_from": "asset.custom__service_contract", + "fieldname": "service_contract", + "fieldtype": "Check", + "hidden": 1, + "label": "Service Contract", + "read_only": 1 + }, + { + "fieldname": "column_break_sdfm", + "fieldtype": "Column Break" + }, + { + "default": "0", + "depends_on": "eval:doc.service_contract == 1", + "fetch_from": "asset.custom_covering_labour", + "fieldname": "covering_labour", + "fieldtype": "Check", + "label": "Labour", + "read_only": 1 + }, + { + "default": "0", + "depends_on": "eval:doc.service_contract == 1", + "fetch_from": "asset.custom_covering_spare_parts", + "fieldname": "covering_spare_parts", + "fieldtype": "Check", + "label": "Comprehensive", + "read_only": 1 + }, + { + "fieldname": "war", + "fieldtype": "HTML", + "label": "Warranty", + "options": "Warranty Details
" + }, + { + "fieldname": "service_contract_details", + "fieldtype": "HTML", + "label": "Service Contract details", + "options": "Service Contract Details
" + }, + { + "fieldname": "purchase_details_section", + "fieldtype": "Section Break", + "label": "Invoice Details" + }, + { + "depends_on": "eval:doc.workflow_state != \"Sent to maintenance Manager\"", + "fieldname": "invoice_table", + "fieldtype": "Table", + "label": "Invoice Table", + "options": "PI Table", + "read_only_depends_on": "eval:doc.workflow_state != \"Pending Purchase\"" + }, + { + "default": "0", + "depends_on": "eval:doc.service_contract == 1", + "fieldname": "ppm_only", + "fieldtype": "Check", + "label": " PPM Only", + "read_only": 1 + }, + { + "default": "0", + "depends_on": "eval:doc.service_contract == 1", + "fetch_from": "asset.custom_spare_parts_labour", + "fieldname": "spare_parts_labour", + "fieldtype": "Check", + "label": "Spare Parts & Labour", + "read_only": 1 + }, + { + "fieldname": "vendor", + "fieldtype": "Data", + "label": "Vendor" + }, + { + "allow_on_submit": 1, + "fetch_from": "asset.department", + "fetch_if_empty": 1, + "fieldname": "department", + "fieldtype": "Link", + "in_filter": 1, + "label": "Department", + "options": "Department" + }, + { + "fetch_from": "asset.custom_manufacturer", + "fetch_if_empty": 1, + "fieldname": "manufacturer", + "fieldtype": "Data", + "label": "Manufacturer" + }, + { + "allow_on_submit": 1, + "fieldname": "supplier", + "fieldtype": "Link", + "in_filter": 1, + "in_global_search": 1, + "in_standard_filter": 1, + "label": "Supplier", + "options": "Supplier" + }, + { + "depends_on": "eval:doc.work_order_type == \"Recall\"", + "fieldname": "recall_reference_number", + "fieldtype": "Data", + "label": "Recall Reference Number" + }, + { + "fieldname": "aseet_id", + "fieldtype": "Link", + "hidden": 1, + "label": "Aseet ID", + "options": "Asset" + }, + { + "allow_on_submit": 1, + "fieldname": "total_hours_spent", + "fieldtype": "Float", + "label": "Total Hours Spent", + "permlevel": 2 + }, + { + "allow_on_submit": 1, + "fieldname": "penalty", + "fieldtype": "Float", + "label": "Penalty" + }, + { + "fieldname": "section_break_okba", + "fieldtype": "Section Break" + }, + { + "fieldname": "workflow_state", + "fieldtype": "Link", + "hidden": 1, + "label": "Workflow State", + "options": "Workflow" + }, + { + "allow_on_submit": 1, + "fieldname": "feedback", + "fieldtype": "Link", + "hidden": 1, + "label": "Feedback", + "options": "Feedback" + }, + { + "fieldname": "custom_difference", + "fieldtype": "Float", + "label": "Difference" + }, + { + "default": "0", + "fieldname": "need_procurement", + "fieldtype": "Check", + "label": "Need Procurement" + }, + { + "fieldname": "column_break_kmog", + "fieldtype": "Column Break" + }, + { + "allow_on_submit": 1, + "fieldname": "feedback_rating", + "fieldtype": "Rating", + "label": "Feedback Rating" + }, + { + "fieldname": "asset_type", + "fieldtype": "Link", + "label": "Asset Type", + "options": "Asset Type", + "reqd": 1 + }, + { + "depends_on": "eval:doc.asset_type == \"Non Biomedical\" || (doc.company && doc.asset_type == \"Biomedical\" && doc.company.startsWith(\"Mobile\"))", + "fetch_from": "asset.custom_site", + "fetch_if_empty": 1, + "fieldname": "site_name", + "fieldtype": "Link", + "label": "Site Name", + "options": "Mobile Team Site" + }, + { + "allow_on_submit": 1, + "fieldname": "inspection", + "fieldtype": "Link", + "label": "Inspection", + "options": "Inspection" + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [ + { + "hidden": 1, + "link_doctype": "Purchase Request", + "link_fieldname": "issue" + }, + { + "link_doctype": "Feedback", + "link_fieldname": "work_order" + } + ], + "modified": "2026-02-12 16:46:24.752129", + "modified_by": "Administrator", + "module": "Asset Lite", + "name": "Work_Order", + "naming_rule": "By \"Naming Series\" field", + "owner": "Administrator", + "permissions": [ + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Manufacturing Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Quality Manager", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "row_format": "Dynamic", + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "title_field": "asset_name", + "track_changes": 1, + "track_seen": 1 +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/work_order/work_order.py b/asset_lite/asset_lite/doctype/work_order/work_order.py new file mode 100644 index 0000000..16b4b86 --- /dev/null +++ b/asset_lite/asset_lite/doctype/work_order/work_order.py @@ -0,0 +1,25 @@ +# Copyright (c) 2024, seyfert and contributors +# For license information, please see license.txt + +import frappe +from frappe.model.document import Document + +class Work_Order(Document): + @frappe.whitelist() + def check_site_version(self): + # Fetch the site version type from the site configuration + site_version_type = frappe.local.conf.get("site_version_type", "") + return site_version_type + + ''' + @frappe.whitelist() + @frappe.validate_and_sanitize_search_inputs + def get_team_members(doctype, txt, searchfield, start, page_len, filters): + return frappe.db.get_values( + "Maintenance Team Member", {"parent": filters.get("maintenance_team")}, "team_member" + ) + + ''' + + + diff --git a/asset_lite/asset_lite/doctype/work_order_requisitor/__init__.py b/asset_lite/asset_lite/doctype/work_order_requisitor/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/work_order_requisitor/test_work_order_requisitor.py b/asset_lite/asset_lite/doctype/work_order_requisitor/test_work_order_requisitor.py new file mode 100644 index 0000000..9755eeb --- /dev/null +++ b/asset_lite/asset_lite/doctype/work_order_requisitor/test_work_order_requisitor.py @@ -0,0 +1,9 @@ +# Copyright (c) 2025, seyfert and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestWork_OrderRequisitor(FrappeTestCase): + pass diff --git a/asset_lite/asset_lite/doctype/work_order_requisitor/work_order_requisitor.js b/asset_lite/asset_lite/doctype/work_order_requisitor/work_order_requisitor.js new file mode 100644 index 0000000..67a1c6e --- /dev/null +++ b/asset_lite/asset_lite/doctype/work_order_requisitor/work_order_requisitor.js @@ -0,0 +1,8 @@ +// Copyright (c) 2025, seyfert and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Work_Order Requisitor", { +// refresh(frm) { + +// }, +// }); diff --git a/asset_lite/asset_lite/doctype/work_order_requisitor/work_order_requisitor.json b/asset_lite/asset_lite/doctype/work_order_requisitor/work_order_requisitor.json new file mode 100644 index 0000000..3c6b465 --- /dev/null +++ b/asset_lite/asset_lite/doctype/work_order_requisitor/work_order_requisitor.json @@ -0,0 +1,50 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "field:requisitor_name", + "creation": "2025-02-07 13:14:17.271763", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "requisitor_name", + "arabic_name" + ], + "fields": [ + { + "fieldname": "requisitor_name", + "fieldtype": "Data", + "label": "Requisitor Name", + "unique": 1 + }, + { + "fieldname": "arabic_name", + "fieldtype": "Data", + "label": "Arabic Name" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2025-02-07 13:19:48.723260", + "modified_by": "Administrator", + "module": "Asset Lite", + "name": "Work_Order Requisitor", + "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": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/work_order_requisitor/work_order_requisitor.py b/asset_lite/asset_lite/doctype/work_order_requisitor/work_order_requisitor.py new file mode 100644 index 0000000..cdab879 --- /dev/null +++ b/asset_lite/asset_lite/doctype/work_order_requisitor/work_order_requisitor.py @@ -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 Work_OrderRequisitor(Document): + pass diff --git a/asset_lite/asset_lite/doctype/work_order_settings/__init__.py b/asset_lite/asset_lite/doctype/work_order_settings/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/work_order_settings/test_work_order_settings.py b/asset_lite/asset_lite/doctype/work_order_settings/test_work_order_settings.py new file mode 100644 index 0000000..fa96c57 --- /dev/null +++ b/asset_lite/asset_lite/doctype/work_order_settings/test_work_order_settings.py @@ -0,0 +1,9 @@ +# Copyright (c) 2026, seyfert and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestWorkOrderSettings(FrappeTestCase): + pass diff --git a/asset_lite/asset_lite/doctype/work_order_settings/work_order_settings.js b/asset_lite/asset_lite/doctype/work_order_settings/work_order_settings.js new file mode 100644 index 0000000..4bedaa2 --- /dev/null +++ b/asset_lite/asset_lite/doctype/work_order_settings/work_order_settings.js @@ -0,0 +1,8 @@ +// Copyright (c) 2026, seyfert and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Work Order Settings", { +// refresh(frm) { + +// }, +// }); diff --git a/asset_lite/asset_lite/doctype/work_order_settings/work_order_settings.json b/asset_lite/asset_lite/doctype/work_order_settings/work_order_settings.json new file mode 100644 index 0000000..d037a80 --- /dev/null +++ b/asset_lite/asset_lite/doctype/work_order_settings/work_order_settings.json @@ -0,0 +1,48 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2026-01-21 13:58:39.073221", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "section_break_e8zu", + "custom_days" + ], + "fields": [ + { + "fieldname": "section_break_e8zu", + "fieldtype": "Section Break" + }, + { + "default": "1", + "fieldname": "custom_days", + "fieldtype": "Int", + "label": "Days" + } + ], + "grid_page_length": 50, + "index_web_pages_for_search": 1, + "issingle": 1, + "links": [], + "modified": "2026-01-21 13:59:04.109864", + "modified_by": "Administrator", + "module": "Asset Lite", + "name": "Work Order Settings", + "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": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/work_order_settings/work_order_settings.py b/asset_lite/asset_lite/doctype/work_order_settings/work_order_settings.py new file mode 100644 index 0000000..01cf93a --- /dev/null +++ b/asset_lite/asset_lite/doctype/work_order_settings/work_order_settings.py @@ -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 WorkOrderSettings(Document): + pass diff --git a/asset_lite/asset_lite/doctype/work_order_table/__init__.py b/asset_lite/asset_lite/doctype/work_order_table/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/doctype/work_order_table/work_order_table.json b/asset_lite/asset_lite/doctype/work_order_table/work_order_table.json new file mode 100644 index 0000000..e546d57 --- /dev/null +++ b/asset_lite/asset_lite/doctype/work_order_table/work_order_table.json @@ -0,0 +1,69 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2025-02-08 00:26:13.321930", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "item_name", + "part_no", + "description", + "column_break_zklj", + "quantity", + "status" + ], + "fields": [ + { + "columns": 2, + "fieldname": "item_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Item Name" + }, + { + "columns": 2, + "fieldname": "part_no", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Part No" + }, + { + "columns": 2, + "fieldname": "description", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Description" + }, + { + "fieldname": "column_break_zklj", + "fieldtype": "Column Break" + }, + { + "columns": 2, + "fieldname": "quantity", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Quantity" + }, + { + "columns": 2, + "fieldname": "status", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Status" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2025-02-10 20:38:06.108151", + "modified_by": "Administrator", + "module": "Asset Lite", + "name": "Work_order Table", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/asset_lite/asset_lite/doctype/work_order_table/work_order_table.py b/asset_lite/asset_lite/doctype/work_order_table/work_order_table.py new file mode 100644 index 0000000..dddb4e4 --- /dev/null +++ b/asset_lite/asset_lite/doctype/work_order_table/work_order_table.py @@ -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 Work_orderTable(Document): + pass diff --git a/asset_lite/asset_lite/page/__init__.py b/asset_lite/asset_lite/page/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/page/active_map/__init__.py b/asset_lite/asset_lite/page/active_map/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/page/active_map/active_map.js b/asset_lite/asset_lite/page/active_map/active_map.js new file mode 100644 index 0000000..febafd3 --- /dev/null +++ b/asset_lite/asset_lite/page/active_map/active_map.js @@ -0,0 +1,749 @@ +frappe.pages['active-map'].on_page_load = function(wrapper) { + var page = frappe.ui.make_app_page({ + parent: wrapper, + title: 'Active Map', + single_column: true + }); + + // Load Leaflet CSS + $('').appendTo('head'); + + // Load Leaflet JS + $.getScript('https://unpkg.com/leaflet@1.7.1/dist/leaflet.js', function() { + // Initialize the map after Leaflet is loaded + new AssetMap(page, wrapper); + }); +}; + +class AssetMap { + constructor(page, wrapper) { + this.page = page; + this.wrapper = wrapper; + this.setup_page(); + } + + setup_page() { + // Add filters + this.setup_filters(); + + // Create map container + this.make_map_container(); + + // Add custom styles + this.add_custom_styles(); + + // Initialize map + this.initialize_map(); + } + + setup_filters() { + let filter_container = $('
').prependTo(this.page.main); + + // Add a filter for hospital/company + this.page.add_field({ + parent: filter_container, + fieldname: 'company', + label: __('Hospital'), + fieldtype: 'Link', + options: 'Location', // Using Location DocType for hospitals + get_query: () => { + return { + filters: { + custom_is_hospital: 1 + } + }; + }, + onchange: () => this.fetch_and_render_data() + }); + } + + make_map_container() { + this.$map_container = $('
') + .appendTo(this.page.main); + } + + add_custom_styles() { + // Add custom CSS for tooltips and popups + if (!$('#asset-map-styles').length) { + $(' + `); + + // Create a container for layout + $(wrapper).append(` +
+
+
+
+
+
+
+
+
+ `); + + // Create Asset Filter + let asset_filter = new frappe.ui.form.ControlLink({ + parent: $("#filter_section"), + df: { + label: "Select Asset", + fieldname: "asset", + options: "Asset", + change: function() { + let asset_id = asset_filter.get_value(); + if (asset_id) { + fetch_asset_details(asset_id); + } + } + } + }); + + asset_filter.make_input(); // Render the filter field + + // **Fix: Wait until the input is ready before setting the value** + let params = new URLSearchParams(window.location.search); + let asset_id = params.get("asset"); // Get asset from URL + + if (asset_id) { + setTimeout(() => { + asset_filter.set_value(asset_id); // Apply the value after render + fetch_asset_details(asset_id); // Fetch data immediately + }, 500); // Delay to ensure input is initialized + } + + // Function to Fetch Asset Details + function fetch_asset_details(asset_id) { + frappe.call({ + method: "frappe.client.get", + args: { + doctype: "Asset", + name: asset_id + }, + callback: function(response) { + if (response.message) { + let asset = response.message; + + $("#content_section").html(` +

🔍 Asset Details

+

ID: ${asset.name}

+

Name: ${asset.asset_name}

+

🏥 Hospital: ${asset.company}

+

📍 Location: ${asset.location}

+

🚛 Supplier: ${asset.supplier}

+

💰 Total Repair Cost: ${asset.custom_total_spare_parts_amount}

+ `); + + $("#work_orders_section").html(`

🔗 Linked Work Orders

`); + $("#spare_parts_section").html(`

🛠️ Items Used for Repair

`); + + // Fetch Work Orders After Asset Details Are Shown + fetch_work_orders(asset_id); + fetch_spare_parts(asset_id); + fetch_maintenance_details(asset_id); + } + } + }); + } + + // Function to Fetch and Display Linked Work Orders + function fetch_work_orders(asset_id) { + frappe.call({ + method: "frappe.client.get_list", + args: { + doctype: "Work_Order", + filters: { asset: asset_id }, + fields: ["name", "work_order_type","repair_status","creation","total_repair_cost"] + }, + callback: function(response) { + if (response.message) { + let work_orders = response.message; + + // If no work orders found + if (work_orders.length === 0) { + $("#work_orders_table").html("

No Work Orders Found for this Asset.

"); + return; + } + + // Create a formatted table for Work Orders + let html = ` +
+ + + + + + + + + + + + `; + + // Append Work Order details in table rows + work_orders.forEach(wo => { + // Determine status badge color + let status_class = ""; + if (wo.repair_status === "Completed") { + status_class = "badge-success"; // Green + } else if (wo.repair_status === "Work In Progress") { + status_class = "badge-warning"; // Yellow + } else { + status_class = "badge-secondary"; // Gray (default) + } + + + html += ` + + + + + + + + `; + }); + + html += ` + +
Work Order NoWork Order TypeRepair StatusRepair CostCreated On
${wo.name}${wo.work_order_type}${wo.repair_status}${parseFloat(wo.total_repair_cost).toFixed(2)}ر.س${wo.creation}
+
+ `; + + // Insert into the work_orders_section + $("#work_orders_table").html(html); + } + } + }); + } + + // Function to Fetch and Display Spare Parts + function fetch_spare_parts(asset_id) { + frappe.call({ + method: "frappe.client.get", + args: { + doctype: "Asset", + name: asset_id + }, + callback: function(response) { + if (response.message && response.message.custom_spare_parts) { + let spare_parts = response.message.custom_spare_parts; + + if (spare_parts.length === 0) { + $("#spare_parts_table").html("

No Spare Parts Used for this Asset.

"); + return; + } + + // Group spare parts by work_order + let grouped_parts = {}; + spare_parts.forEach(sp => { + if (!grouped_parts[sp.work_order]) { + grouped_parts[sp.work_order] = []; + } + grouped_parts[sp.work_order].push(sp); + }); + + // Create HTML for Spare Parts Table + let html = `
`; + + Object.keys(grouped_parts).forEach(work_order => { + html += ` +

Work Order: ${work_order ? work_order : ""}

+ + + + + + + + + + + `; + + grouped_parts[work_order].forEach(sp => { + html += ` + + + + + + + `; + }); + + html += ` + +
Item NameQuantityCostAmount
${sp.item_code}${sp.qty}${sp.rate}ر.س${sp.amount}ر.س
+ `; + }); + + html += `
`; + + // Insert into the spare_parts_section + $("#spare_parts_table").html(html); + } + } + }); + } + + // Fetch Asset Maintenance Details (Including Tasks) +function fetch_maintenance_details(asset_id) { + frappe.call({ + method: "frappe.client.get_list", + args: { + doctype: "Asset Maintenance", + filters: { asset_name: asset_id }, + fields: ["name", "maintenance_team", "custom_type_of_maintenance"], + limit_page_length: 10 + }, + callback: function(response) { + if (response.message) { + let maintenance_records = response.message; + if (maintenance_records.length === 0) { + $("#maintenance_section").html("

No Maintenance Records Found.

"); + return; + } + + let html = `

🛠️ Asset Maintenance Details

`; + + maintenance_records.forEach(m => { + html += ` +
+

Maintenance ID: ${m.name}

+

Maintenance Team: ${m.maintenance_team}

+

Type of Maintenance: ${m.custom_type_of_maintenance}

+
+ `; + + // Fetch Maintenance Tasks (Child Table) for Each Record + fetch_maintenance_tasks(m.name); + }); + + $("#maintenance_section").html(html); + } + } + }); +} + +function fetch_maintenance_tasks(maintenance_id) { + frappe.call({ + method: "frappe.client.get", + args: { + doctype: "Asset Maintenance", + name: maintenance_id + }, + callback: function(response) { + if (response.message && response.message.asset_maintenance_tasks) { + let tasks = response.message.asset_maintenance_tasks; + if (tasks.length === 0) { + $("#maintenance_section").append("

No Maintenance Tasks Found.

"); + return; + } + + // Clear the section to avoid duplicate entries + $("#maintenance_section").find(".maintenance-tasks").remove(); + + let html = ` +
+


Maintenance Tasks for ${maintenance_id}

+
+ + + + + + + + + + `; + + tasks.forEach(task => { + html += ` + + + + + + `; + }); + + html += `
Assigned ToPeriodicityNext Due Date
${task.assign_to_name}${task.periodicity}${task.next_due_date}
`; + + $("#maintenance_section").append(html); + fetch_maintenance_logs(maintenance_id); + } + } + }); +} + +function fetch_maintenance_logs(maintenance_id) { + frappe.call({ + method: "frappe.client.get_list", + args: { + doctype: "Asset Maintenance Log", + filters: { asset_maintenance: maintenance_id }, + fields: ["maintenance_status"] + }, + callback: function(response) { + if (response.message) { + let logs = response.message; + if (logs.length === 0) { + $("#maintenance_section").append("

No Maintenance Logs Found.

"); + return; + } + + // Remove previous log summary + $("#maintenance_section").find(".maintenance-log-summary").remove(); + + let status_counts = {}; + logs.forEach(log => { + if (!status_counts[log.maintenance_status]) { + status_counts[log.maintenance_status] = 0; + } + status_counts[log.maintenance_status]++; + }); + + let html = ` +
+

📜Periodic Maintenance

+
+ + + + + + + + + `; + + for (let status in status_counts) { + html += ` + + + + + `; + } + + html += `
Maintenance StatusCount
${status}${status_counts[status]}
`; + + $("#maintenance_section").append(html); + fetch_detailed_maintenance_logs(maintenance_id); + } + } + }); +} + +function fetch_detailed_maintenance_logs(maintenance_id) { + frappe.call({ + method: "frappe.client.get_list", + args: { + doctype: "Asset Maintenance Log", + filters: { asset_maintenance: maintenance_id }, + fields: [ + "name", + "maintenance_status", + "assign_to_name", + "maintenance_type", + "due_date", + "completion_date", + "periodicity", + "actions_performed" + ], + order_by: "completion_date desc" + }, + callback: function(response) { + if (response.message) { + let logs = response.message; + if (logs.length === 0) { + $("#maintenance_section").append("

No Maintenance Logs Found.

"); + return; + } + + // Remove previous detailed logs + $("#maintenance_section").find(".maintenance-logs").remove(); + + let completed_logs = logs.filter(log => log.maintenance_status === "Completed"); + let remaining_logs = logs.filter(log => log.maintenance_status !== "Completed"); + + let html = `
`; + + // ✅ Display Completed Logs First + if (completed_logs.length > 0) { + html += ` +

✅ Completed Maintenance Logs

+
+ + + + + + + + + + + + + + `; + + completed_logs.forEach(log => { + html += ` + + + + + + + + + + `; + }); + + html += `
Log IDAssigned ToMaintenance TypeDue DateCompletion DatePeriodicityActions Performed
${log.name}${log.assign_to_name}${log.maintenance_type}${log.due_date}${log.completion_date}${log.periodicity}${log.actions_performed ? log.actions_performed : ""}
`; + } + + // ✅ Display Remaining Logs + if (remaining_logs.length > 0) { + html += ` +

⏳ Pending/Planned Maintenance Logs

+
+ + + + + + + + + + + + + + `; + + remaining_logs.forEach(log => { + html += ` + + + + + + + + + + `; + }); + + html += `
Log IDAssigned ToMaintenance TypeDue DatePeriodicityMaintenance StatusActions Performed
${log.name}${log.assign_to_name}${log.maintenance_type}${log.due_date}${log.periodicity}${log.maintenance_status}${log.actions_performed ? log.actions_performed : ""}
`; + } + + html += `
`; // Close maintenance logs section + + $("#maintenance_section").append(html); + } + } + }); +} + + + +}; diff --git a/asset_lite/asset_lite/page/asset_history/asset_history.json b/asset_lite/asset_lite/page/asset_history/asset_history.json new file mode 100644 index 0000000..4f5c82e --- /dev/null +++ b/asset_lite/asset_lite/page/asset_history/asset_history.json @@ -0,0 +1,18 @@ +{ + "content": null, + "creation": "2025-03-09 15:13:49.273077", + "docstatus": 0, + "doctype": "Page", + "idx": 0, + "modified": "2025-03-09 15:13:49.273077", + "modified_by": "Administrator", + "module": "Asset Lite", + "name": "asset-history", + "owner": "Administrator", + "page_name": "asset-history", + "roles": [], + "script": null, + "standard": "Yes", + "style": null, + "system_page": 0 +} \ No newline at end of file diff --git a/asset_lite/asset_lite/page/asset_map/__init__.py b/asset_lite/asset_lite/page/asset_map/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/page/asset_map/asset_map.js b/asset_lite/asset_lite/page/asset_map/asset_map.js new file mode 100644 index 0000000..b42bdb5 --- /dev/null +++ b/asset_lite/asset_lite/page/asset_map/asset_map.js @@ -0,0 +1,103 @@ +frappe.pages['asset-map'].on_page_load = function(wrapper) { + + let page = frappe.ui.make_app_page({ + + parent: wrapper, + + title: 'Asset Map', + + single_column: true + + }); + + // Create map container + + let $mapContainer = $('
').css({ + + height: '500px', + + width: '100%' + + }).appendTo(page.main); + + // Initialize map + + let map = L.map('asset-map-container').setView([20.5937, 78.9629], 5); // Default to India + + L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { + + maxZoom: 19 + + }).addTo(map); + + // Fetch assets with linked locations + + frappe.call({ + + method: 'frappe.client.get_list', + + args: { + + doctype: 'Asset', + + fields: ['name', 'asset_name', 'location'], + filters: { + location: ['=', 'Domat Aljandal'] // Ensures location is not empty + } + + }, + + callback: function(res) { + + console.log('Assets:', res.message); // Log all fetched assets + + if (res.message) { + + res.message.forEach(asset => { + + if (asset.location == "Domat Aljandal") { + + frappe.call({ + + method: 'frappe.client.get', + + args: { + + doctype: 'Location', + + name: asset.location + + }, + + callback: function(r) { + + console.log(`Location for asset ${asset.asset_name}:`, r.message); // Log location details + + if (r.message && r.message.latitude && r.message.longitude && r.message.name === 'Domat Aljandal') { + console.log("Test") + + L.marker([r.message.latitude, r.message.longitude]) + + .addTo(map) + + .bindPopup(`${asset.asset_name}`); + + } + + } + + }); + + } + + }); + + } + + } + + }); + +}; + + \ No newline at end of file diff --git a/asset_lite/asset_lite/page/asset_map/asset_map.json b/asset_lite/asset_lite/page/asset_map/asset_map.json new file mode 100644 index 0000000..f854208 --- /dev/null +++ b/asset_lite/asset_lite/page/asset_map/asset_map.json @@ -0,0 +1,19 @@ +{ + "content": null, + "creation": "2025-03-12 17:08:05.267445", + "docstatus": 0, + "doctype": "Page", + "idx": 0, + "modified": "2025-03-12 17:08:05.267445", + "modified_by": "Administrator", + "module": "Asset Lite", + "name": "asset-map", + "owner": "Administrator", + "page_name": "asset-map", + "roles": [], + "script": null, + "standard": "Yes", + "style": null, + "system_page": 0, + "title": "Asset map" +} \ No newline at end of file diff --git a/asset_lite/asset_lite/page/privacy_policy/__init__.py b/asset_lite/asset_lite/page/privacy_policy/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asset_lite/asset_lite/page/privacy_policy/privacy_policy.js b/asset_lite/asset_lite/page/privacy_policy/privacy_policy.js new file mode 100644 index 0000000..2a522e6 --- /dev/null +++ b/asset_lite/asset_lite/page/privacy_policy/privacy_policy.js @@ -0,0 +1,164 @@ +frappe.pages['privacy-policy'].on_page_load = function(wrapper) { + var page = frappe.ui.make_app_page({ + parent: wrapper, + title: 'Privacy Policy', + single_column: true + }); + + // Insert your full HTML here + $(page.main).html(` +

Privacy Policy

+

Last updated: August 28, 2025

+

This Privacy Policy describes Our policies and procedures on the collection, use and disclosure of Your information when You use the Service and tells You about Your privacy rights and how the law protects You.

+

We use Your Personal data to provide and improve the Service. By using the Service, You agree to the collection and use of information in accordance with this Privacy Policy. This Privacy Policy has been created with the help of the Privacy Policy Generator.

+

Interpretation and Definitions

+

Interpretation

+

The words of which the initial letter is capitalized have meanings defined under the following conditions. The following definitions shall have the same meaning regardless of whether they appear in singular or in plural.

+

Definitions

+

For the purposes of this Privacy Policy:

+ +

Collecting and Using Your Personal Data

+

Types of Data Collected

+

Personal Data

+

While using Our Service, We may ask You to provide Us with certain personally identifiable information that can be used to contact or identify You. Personally identifiable information may include, but is not limited to:

+ +

Usage Data

+

Usage Data is collected automatically when using the Service.

+

Usage Data may include information such as Your Device's Internet Protocol address (e.g. IP address), browser type, browser version, the pages of our Service that You visit, the time and date of Your visit, the time spent on those pages, unique device identifiers and other diagnostic data.

+

When You access the Service by or through a mobile device, We may collect certain information automatically, including, but not limited to, the type of mobile device You use, Your mobile device unique ID, the IP address of Your mobile device, Your mobile operating system, the type of mobile Internet browser You use, unique device identifiers and other diagnostic data.

+

We may also collect information that Your browser sends whenever You visit our Service or when You access the Service by or through a mobile device.

+

Information Collected while Using the Application

+

While using Our Application, in order to provide features of Our Application, We may collect, with Your prior permission:

+ +

We use this information to provide features of Our Service, to improve and customize Our Service. The information may be uploaded to the Company's servers and/or a Service Provider's server or it may be simply stored on Your device.

+

You can enable or disable access to this information at any time, through Your Device settings.

+

Use of Your Personal Data

+

The Company may use Personal Data for the following purposes:

+ +

We may share Your personal information in the following situations:

+ +

Retention of Your Personal Data

+

The Company will retain Your Personal Data only for as long as is necessary for the purposes set out in this Privacy Policy. We will retain and use Your Personal Data to the extent necessary to comply with our legal obligations (for example, if we are required to retain your data to comply with applicable laws), resolve disputes, and enforce our legal agreements and policies.

+

The Company will also retain Usage Data for internal analysis purposes. Usage Data is generally retained for a shorter period of time, except when this data is used to strengthen the security or to improve the functionality of Our Service, or We are legally obligated to retain this data for longer time periods.

+

Transfer of Your Personal Data

+

Your information, including Personal Data, is processed at the Company's operating offices and in any other places where the parties involved in the processing are located. It means that this information may be transferred to — and maintained on — computers located outside of Your state, province, country or other governmental jurisdiction where the data protection laws may differ than those from Your jurisdiction.

+

Your consent to this Privacy Policy followed by Your submission of such information represents Your agreement to that transfer.

+

The Company will take all steps reasonably necessary to ensure that Your data is treated securely and in accordance with this Privacy Policy and no transfer of Your Personal Data will take place to an organization or a country unless there are adequate controls in place including the security of Your data and other personal information.

+

Delete Your Personal Data

+

You have the right to delete or request that We assist in deleting the Personal Data that We have collected about You.

+

Our Service may give You the ability to delete certain information about You from within the Service.

+

You may update, amend, or delete Your information at any time by signing in to Your Account, if you have one, and visiting the account settings section that allows you to manage Your personal information. You may also contact Us to request access to, correct, or delete any personal information that You have provided to Us.

+

Please note, however, that We may need to retain certain information when we have a legal obligation or lawful basis to do so.

+

Disclosure of Your Personal Data

+

Business Transactions

+

If the Company is involved in a merger, acquisition or asset sale, Your Personal Data may be transferred. We will provide notice before Your Personal Data is transferred and becomes subject to a different Privacy Policy.

+

Law enforcement

+

Under certain circumstances, the Company may be required to disclose Your Personal Data if required to do so by law or in response to valid requests by public authorities (e.g. a court or a government agency).

+

Other legal requirements

+

The Company may disclose Your Personal Data in the good faith belief that such action is necessary to:

+ +

Security of Your Personal Data

+

The security of Your Personal Data is important to Us, but remember that no method of transmission over the Internet, or method of electronic storage is 100% secure. While We strive to use commercially acceptable means to protect Your Personal Data, We cannot guarantee its absolute security.

+

Children's Privacy

+

Our Service does not address anyone under the age of 13. We do not knowingly collect personally identifiable information from anyone under the age of 13. If You are a parent or guardian and You are aware that Your child has provided Us with Personal Data, please contact Us. If We become aware that We have collected Personal Data from anyone under the age of 13 without verification of parental consent, We take steps to remove that information from Our servers.

+

If We need to rely on consent as a legal basis for processing Your information and Your country requires consent from a parent, We may require Your parent's consent before We collect and use that information.

+

Links to Other Websites

+

Our Service may contain links to other websites that are not operated by Us. If You click on a third party link, You will be directed to that third party's site. We strongly advise You to review the Privacy Policy of every site You visit.

+

We have no control over and assume no responsibility for the content, privacy policies or practices of any third party sites or services.

+

Changes to this Privacy Policy

+

We may update Our Privacy Policy from time to time. We will notify You of any changes by posting the new Privacy Policy on this page.

+

We will let You know via email and/or a prominent notice on Our Service, prior to the change becoming effective and update the "Last updated" date at the top of this Privacy Policy.

+

You are advised to review this Privacy Policy periodically for any changes. Changes to this Privacy Policy are effective when they are posted on this page.

+

Contact Us

+

If you have any questions about this Privacy Policy, You can contact us:

+