- Bump DESK_MOLONI version to 3.0.1 across module - Normalize hooks to after_client_* and instantiate PerfexHooks safely - Fix OAuthController view path and API client class name - Add missing admin views for webhook config/logs; adjust view loading - Harden client portal routes and admin routes mapping - Make Dashboard/Logs/Queue tolerant to optional model methods - Align log details query with existing schema; avoid broken joins This makes the module operational in Perfex (admin + client), reduces 404s, and avoids fatal errors due to inconsistent tables/methods.
544 lines
19 KiB
PHP
544 lines
19 KiB
PHP
<?php
|
|
|
|
defined('BASEPATH') or exit('No direct script access allowed');
|
|
|
|
/**
|
|
* Desk-Moloni Queue Controller
|
|
* Handles queue management and bulk operations
|
|
*
|
|
* @package Desk-Moloni
|
|
* @version 3.0.0
|
|
* @author Descomplicar Business Solutions
|
|
*/
|
|
class Queue extends AdminController
|
|
{
|
|
public function __construct()
|
|
{
|
|
parent::__construct();
|
|
|
|
// Check if user is authenticated
|
|
if (!is_staff_logged_in()) {
|
|
redirect(admin_url('authentication'));
|
|
}
|
|
|
|
// Load models with correct names
|
|
$this->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'
|
|
];
|
|
}
|
|
} |