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) }