load->model('desk_moloni/desk_moloni_config_model', 'config_model'); $this->load->model('desk_moloni/desk_moloni_sync_queue_model', 'queue_model'); $this->load->model('desk_moloni/desk_moloni_mapping_model', 'mapping_model'); $this->load->model('desk_moloni/desk_moloni_sync_log_model', 'sync_log_model'); $this->load->helper('desk_moloni'); $this->load->library('form_validation'); } /** * Queue management interface */ public function index() { if (!has_permission('desk_moloni', '', 'view')) { access_denied('desk_moloni'); } $data = [ 'title' => _l('desk_moloni_queue_management'), 'queue_summary' => $this->_get_queue_summary(), 'task_types' => $this->_get_task_types(), 'entity_types' => ['client', 'product', 'invoice', 'estimate', 'credit_note'] ]; $this->load->view('admin/includes/header', $data); $this->load->view('admin/modules/desk_moloni/queue_management', $data); $this->load->view('admin/includes/footer'); } /** * Get queue status with pagination and filtering */ public function get_queue_status() { if (!has_permission('desk_moloni', '', 'view')) { $this->output ->set_status_header(403) ->set_content_type('application/json') ->set_output(json_encode(['success' => false, 'message' => _l('access_denied')])); return; } try { $filters = [ 'status' => $this->input->get('status'), 'entity_type' => $this->input->get('entity_type'), 'task_type' => $this->input->get('task_type'), 'priority' => $this->input->get('priority'), 'date_from' => $this->input->get('date_from'), 'date_to' => $this->input->get('date_to') ]; $pagination = [ 'limit' => (int) $this->input->get('limit') ?: 50, 'offset' => (int) $this->input->get('offset') ?: 0 ]; $queue_data = $this->queue_model->get_filtered_queue($filters, $pagination); $this->output ->set_content_type('application/json') ->set_output(json_encode([ 'success' => true, 'data' => [ 'total_tasks' => $queue_data['total'], 'tasks' => $queue_data['tasks'], 'summary' => $this->queue_model->get_filtered_summary($filters), 'pagination' => [ 'current_page' => floor($pagination['offset'] / $pagination['limit']) + 1, 'per_page' => $pagination['limit'], 'total_items' => $queue_data['total'], 'total_pages' => ceil($queue_data['total'] / $pagination['limit']) ] ] ])); } catch (Exception $e) { log_message('error', 'Desk-Moloni queue status error: ' . $e->getMessage()); $this->output ->set_status_header(500) ->set_content_type('application/json') ->set_output(json_encode([ 'success' => false, 'message' => $e->getMessage() ])); } } /** * Add new sync task to queue */ public function add_task() { if (!has_permission('desk_moloni', '', 'create')) { $this->output ->set_status_header(403) ->set_content_type('application/json') ->set_output(json_encode(['success' => false, 'message' => _l('access_denied')])); return; } try { $task_data = [ 'task_type' => $this->input->post('task_type'), 'entity_type' => $this->input->post('entity_type'), 'entity_id' => (int) $this->input->post('entity_id'), 'priority' => (int) $this->input->post('priority') ?: 5, 'payload' => $this->input->post('payload') ? json_decode($this->input->post('payload'), true) : null ]; // Validate required fields if (empty($task_data['task_type']) || empty($task_data['entity_type']) || empty($task_data['entity_id'])) { throw new Exception(_l('desk_moloni_task_missing_required_fields')); } // Validate entity exists if (!$this->_validate_entity_exists($task_data['entity_type'], $task_data['entity_id'])) { throw new Exception(_l('desk_moloni_entity_not_found')); } $task_id = $this->queue_model->add_task($task_data); $this->output ->set_content_type('application/json') ->set_output(json_encode([ 'success' => true, 'message' => _l('desk_moloni_task_added_successfully'), 'data' => ['task_id' => $task_id] ])); } catch (Exception $e) { log_message('error', 'Desk-Moloni add task error: ' . $e->getMessage()); $this->output ->set_status_header(400) ->set_content_type('application/json') ->set_output(json_encode([ 'success' => false, 'message' => $e->getMessage() ])); } } /** * Cancel sync task */ public function cancel_task($task_id) { if (!has_permission('desk_moloni', '', 'edit')) { $this->output ->set_status_header(403) ->set_content_type('application/json') ->set_output(json_encode(['success' => false, 'message' => _l('access_denied')])); return; } try { $task_id = (int) $task_id; if (!$task_id) { throw new Exception(_l('desk_moloni_invalid_task_id')); } $result = $this->queue_model->cancel_task($task_id); if (!$result) { throw new Exception(_l('desk_moloni_task_cancel_failed')); } $this->output ->set_content_type('application/json') ->set_output(json_encode([ 'success' => true, 'message' => _l('desk_moloni_task_cancelled_successfully') ])); } catch (Exception $e) { log_message('error', 'Desk-Moloni cancel task error: ' . $e->getMessage()); $this->output ->set_status_header(400) ->set_content_type('application/json') ->set_output(json_encode([ 'success' => false, 'message' => $e->getMessage() ])); } } /** * Retry sync task */ public function retry_task($task_id) { if (!has_permission('desk_moloni', '', 'edit')) { $this->output ->set_status_header(403) ->set_content_type('application/json') ->set_output(json_encode(['success' => false, 'message' => _l('access_denied')])); return; } try { $task_id = (int) $task_id; if (!$task_id) { throw new Exception(_l('desk_moloni_invalid_task_id')); } $result = $this->queue_model->retry_task($task_id); if (!$result) { throw new Exception(_l('desk_moloni_task_retry_failed')); } $this->output ->set_content_type('application/json') ->set_output(json_encode([ 'success' => true, 'message' => _l('desk_moloni_task_retried_successfully') ])); } catch (Exception $e) { log_message('error', 'Desk-Moloni retry task error: ' . $e->getMessage()); $this->output ->set_status_header(400) ->set_content_type('application/json') ->set_output(json_encode([ 'success' => false, 'message' => $e->getMessage() ])); } } /** * Bulk operations on tasks */ public function bulk_operation() { if (!has_permission('desk_moloni', '', 'edit')) { $this->output ->set_status_header(403) ->set_content_type('application/json') ->set_output(json_encode(['success' => false, 'message' => _l('access_denied')])); return; } try { $operation = $this->input->post('operation'); $task_ids = $this->input->post('task_ids'); if (empty($operation) || empty($task_ids) || !is_array($task_ids)) { throw new Exception(_l('desk_moloni_bulk_operation_invalid_params')); } $results = []; $success_count = 0; $error_count = 0; foreach ($task_ids as $task_id) { try { $task_id = (int) $task_id; $result = false; switch ($operation) { case 'retry': $result = $this->queue_model->retry_task($task_id); break; case 'cancel': $result = $this->queue_model->cancel_task($task_id); break; case 'delete': $result = $this->queue_model->delete_task($task_id); break; case 'priority_high': $result = $this->queue_model->set_task_priority($task_id, 1); break; case 'priority_normal': $result = $this->queue_model->set_task_priority($task_id, 5); break; case 'priority_low': $result = $this->queue_model->set_task_priority($task_id, 9); break; default: throw new Exception(_l('desk_moloni_invalid_bulk_operation')); } if ($result) { $success_count++; $results[$task_id] = ['success' => true]; } else { $error_count++; $results[$task_id] = ['success' => false, 'error' => 'Operation failed']; } } catch (Exception $e) { $error_count++; $results[$task_id] = ['success' => false, 'error' => $e->getMessage()]; } } $this->output ->set_content_type('application/json') ->set_output(json_encode([ 'success' => $success_count > 0, 'message' => sprintf( _l('desk_moloni_bulk_operation_results'), $success_count, $error_count ), 'data' => [ 'success_count' => $success_count, 'error_count' => $error_count, 'results' => $results ] ])); } catch (Exception $e) { log_message('error', 'Desk-Moloni bulk operation error: ' . $e->getMessage()); $this->output ->set_status_header(400) ->set_content_type('application/json') ->set_output(json_encode([ 'success' => false, 'message' => $e->getMessage() ])); } } /** * Clear completed tasks from queue */ public function clear_completed() { if (!has_permission('desk_moloni', '', 'delete')) { $this->output ->set_status_header(403) ->set_content_type('application/json') ->set_output(json_encode(['success' => false, 'message' => _l('access_denied')])); return; } try { $days_old = (int) $this->input->post('days_old') ?: 7; $deleted_count = $this->queue_model->clear_completed_tasks($days_old); $this->output ->set_content_type('application/json') ->set_output(json_encode([ 'success' => true, 'message' => sprintf( _l('desk_moloni_completed_tasks_cleared'), $deleted_count ), 'data' => ['deleted_count' => $deleted_count] ])); } catch (Exception $e) { log_message('error', 'Desk-Moloni clear completed error: ' . $e->getMessage()); $this->output ->set_status_header(500) ->set_content_type('application/json') ->set_output(json_encode([ 'success' => false, 'message' => $e->getMessage() ])); } } /** * Pause/Resume queue processing */ public function toggle_processing() { if (!has_permission('desk_moloni', '', 'edit')) { $this->output ->set_status_header(403) ->set_content_type('application/json') ->set_output(json_encode(['success' => false, 'message' => _l('access_denied')])); return; } try { $current_status = $this->config_model->get_config('queue_processing_enabled') === '1'; $new_status = !$current_status; $this->config_model->set_config('queue_processing_enabled', $new_status ? '1' : '0'); $message = $new_status ? _l('desk_moloni_queue_processing_resumed') : _l('desk_moloni_queue_processing_paused'); $this->output ->set_content_type('application/json') ->set_output(json_encode([ 'success' => true, 'message' => $message, 'data' => ['queue_processing_enabled' => $new_status] ])); } catch (Exception $e) { log_message('error', 'Desk-Moloni toggle processing error: ' . $e->getMessage()); $this->output ->set_status_header(500) ->set_content_type('application/json') ->set_output(json_encode([ 'success' => false, 'message' => $e->getMessage() ])); } } /** * Get queue statistics */ public function get_statistics() { if (!has_permission('desk_moloni', '', 'view')) { $this->output ->set_status_header(403) ->set_content_type('application/json') ->set_output(json_encode(['success' => false, 'message' => _l('access_denied')])); return; } try { $days = (int) $this->input->get('days') ?: 7; $statistics = [ 'queue_summary' => $this->queue_model->get_queue_summary(), 'processing_stats' => method_exists($this->queue_model, 'get_processing_statistics') ? $this->queue_model->get_processing_statistics($days) : [], 'performance_metrics' => method_exists($this->queue_model, 'get_performance_metrics') ? $this->queue_model->get_performance_metrics($days) : [], 'error_analysis' => method_exists($this->queue_model, 'get_error_analysis') ? $this->queue_model->get_error_analysis($days) : [] ]; $this->output ->set_content_type('application/json') ->set_output(json_encode([ 'success' => true, 'data' => $statistics ])); } catch (Exception $e) { log_message('error', 'Desk-Moloni queue statistics error: ' . $e->getMessage()); $this->output ->set_status_header(500) ->set_content_type('application/json') ->set_output(json_encode([ 'success' => false, 'message' => $e->getMessage() ])); } } /** * Validate that entity exists in Perfex */ private function _validate_entity_exists($entity_type, $entity_id) { $this->load->model('clients_model'); $this->load->model('invoices_model'); $this->load->model('estimates_model'); $this->load->model('credit_notes_model'); $this->load->model('items_model'); switch ($entity_type) { case 'client': return $this->clients_model->get($entity_id) !== false; case 'invoice': return $this->invoices_model->get($entity_id) !== false; case 'estimate': return $this->estimates_model->get($entity_id) !== false; case 'credit_note': return $this->credit_notes_model->get($entity_id) !== false; case 'product': return $this->items_model->get($entity_id) !== false; default: return false; } } /** * Get queue summary statistics */ private function _get_queue_summary() { try { return [ 'total_tasks' => $this->queue_model->countTasks(), 'pending_tasks' => $this->queue_model->countTasks(['status' => 'pending']), 'processing_tasks' => $this->queue_model->countTasks(['status' => 'processing']), 'failed_tasks' => $this->queue_model->countTasks(['status' => 'failed']), 'completed_tasks' => $this->queue_model->countTasks(['status' => 'completed']) ]; } catch (Exception $e) { log_message('error', 'Desk-Moloni queue summary error: ' . $e->getMessage()); return []; } } /** * Get available task types */ private function _get_task_types() { return [ 'sync_client' => 'Client Sync', 'sync_product' => 'Product Sync', 'sync_invoice' => 'Invoice Sync', 'sync_estimate' => 'Estimate Sync', 'sync_credit_note' => 'Credit Note Sync' ]; } }