db->table_exists(db_prefix() . 'desk_moloni_sync_queue')) { $CI->db->query('CREATE TABLE `' . db_prefix() . 'desk_moloni_sync_queue` ( `id` int(11) NOT NULL AUTO_INCREMENT, `entity_type` varchar(50) NOT NULL, `entity_id` int(11) NOT NULL, `perfex_id` int(11) DEFAULT NULL, `moloni_id` int(11) DEFAULT NULL, `action` enum(\'create\',\'update\',\'delete\',\'sync\') NOT NULL DEFAULT \'sync\', `direction` enum(\'perfex_to_moloni\',\'moloni_to_perfex\',\'bidirectional\') NOT NULL DEFAULT \'bidirectional\', `priority` enum(\'low\',\'normal\',\'high\',\'critical\') NOT NULL DEFAULT \'normal\', `status` enum(\'pending\',\'processing\',\'completed\',\'failed\',\'cancelled\') NOT NULL DEFAULT \'pending\', `attempts` int(11) NOT NULL DEFAULT 0, `max_attempts` int(11) NOT NULL DEFAULT 3, `data` longtext DEFAULT NULL COMMENT \'JSON data for sync\', `error_message` text DEFAULT NULL, `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `scheduled_at` timestamp NULL DEFAULT NULL, `started_at` timestamp NULL DEFAULT NULL, `completed_at` timestamp NULL DEFAULT NULL, `created_by` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_entity_type_id` (`entity_type`, `entity_id`), KEY `idx_status_priority` (`status`, `priority`), KEY `idx_scheduled_at` (`scheduled_at`), KEY `idx_perfex_id` (`perfex_id`), KEY `idx_moloni_id` (`moloni_id`), KEY `idx_created_by` (`created_by`) ) ENGINE=InnoDB DEFAULT CHARSET=' . $CI->db->char_set . ';'); } /** * Create Sync Logs Table */ if (!$CI->db->table_exists(db_prefix() . 'desk_moloni_sync_logs')) { $CI->db->query('CREATE TABLE `' . db_prefix() . 'desk_moloni_sync_logs` ( `id` int(11) NOT NULL AUTO_INCREMENT, `queue_id` int(11) DEFAULT NULL, `entity_type` varchar(50) NOT NULL, `entity_id` int(11) NOT NULL, `action` varchar(50) NOT NULL, `direction` varchar(50) NOT NULL, `status` enum(\'started\',\'success\',\'error\',\'warning\') NOT NULL, `message` text DEFAULT NULL, `request_data` longtext DEFAULT NULL COMMENT \'JSON request data\', `response_data` longtext DEFAULT NULL COMMENT \'JSON response data\', `execution_time` decimal(10,4) DEFAULT NULL COMMENT \'Execution time in seconds\', `memory_usage` int(11) DEFAULT NULL COMMENT \'Memory usage in bytes\', `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `created_by` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_queue_id` (`queue_id`), KEY `idx_entity_type_id` (`entity_type`, `entity_id`), KEY `idx_status` (`status`), KEY `idx_created_at` (`created_at`) ) ENGINE=InnoDB DEFAULT CHARSET=' . $CI->db->char_set . ';'); } /** * Create Entity Mappings Table */ if (!$CI->db->table_exists(db_prefix() . 'desk_moloni_entity_mappings')) { $CI->db->query('CREATE TABLE `' . db_prefix() . 'desk_moloni_entity_mappings` ( `id` int(11) NOT NULL AUTO_INCREMENT, `entity_type` varchar(50) NOT NULL, `perfex_id` int(11) NOT NULL, `moloni_id` int(11) NOT NULL, `perfex_hash` varchar(64) DEFAULT NULL COMMENT \'Hash of Perfex entity data\', `moloni_hash` varchar(64) DEFAULT NULL COMMENT \'Hash of Moloni entity data\', `sync_status` enum(\'synced\',\'pending\',\'error\',\'conflict\') NOT NULL DEFAULT \'synced\', `last_sync_at` timestamp NULL DEFAULT NULL, `last_perfex_update` timestamp NULL DEFAULT NULL, `last_moloni_update` timestamp NULL DEFAULT NULL, `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `metadata` json DEFAULT NULL COMMENT \'Additional mapping metadata\', PRIMARY KEY (`id`), UNIQUE KEY `uk_entity_perfex` (`entity_type`, `perfex_id`), UNIQUE KEY `uk_entity_moloni` (`entity_type`, `moloni_id`), KEY `idx_sync_status` (`sync_status`), KEY `idx_last_sync` (`last_sync_at`) ) ENGINE=InnoDB DEFAULT CHARSET=' . $CI->db->char_set . ';'); } /** * Create Configuration Table */ if (!$CI->db->table_exists(db_prefix() . 'desk_moloni_configuration')) { $CI->db->query('CREATE TABLE `' . db_prefix() . 'desk_moloni_configuration` ( `id` int(11) NOT NULL AUTO_INCREMENT, `config_key` varchar(100) NOT NULL, `config_value` longtext DEFAULT NULL, `config_type` enum(\'string\',\'integer\',\'boolean\',\'json\',\'encrypted\') NOT NULL DEFAULT \'string\', `description` text DEFAULT NULL, `category` varchar(50) DEFAULT NULL, `is_system` tinyint(1) NOT NULL DEFAULT 0, `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `updated_by` int(11) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_config_key` (`config_key`), KEY `idx_category` (`category`), KEY `idx_is_system` (`is_system`) ) ENGINE=InnoDB DEFAULT CHARSET=' . $CI->db->char_set . ';'); } /** * Create API Tokens Table */ if (!$CI->db->table_exists(db_prefix() . 'desk_moloni_api_tokens')) { $CI->db->query('CREATE TABLE `' . db_prefix() . 'desk_moloni_api_tokens` ( `id` int(11) NOT NULL AUTO_INCREMENT, `token_type` enum(\'access_token\',\'refresh_token\',\'webhook_token\') NOT NULL, `token_value` text NOT NULL COMMENT \'Encrypted token value\', `expires_at` timestamp NULL DEFAULT NULL, `company_id` int(11) DEFAULT NULL, `scopes` json DEFAULT NULL, `metadata` json DEFAULT NULL, `active` tinyint(1) NOT NULL DEFAULT 1, `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `last_used_at` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_token_type` (`token_type`), KEY `idx_company_id` (`company_id`), KEY `idx_active` (`active`), KEY `idx_expires_at` (`expires_at`) ) ENGINE=InnoDB DEFAULT CHARSET=' . $CI->db->char_set . ';'); } /** * Create Webhook Logs Table */ if (!$CI->db->table_exists(db_prefix() . 'desk_moloni_webhook_logs')) { $CI->db->query('CREATE TABLE `' . db_prefix() . 'desk_moloni_webhook_logs` ( `id` int(11) NOT NULL AUTO_INCREMENT, `webhook_id` varchar(100) DEFAULT NULL, `event_type` varchar(50) NOT NULL, `source` enum(\'moloni\',\'perfex\') NOT NULL, `payload` longtext DEFAULT NULL COMMENT \'JSON webhook payload\', `headers` json DEFAULT NULL COMMENT \'Request headers\', `signature` varchar(255) DEFAULT NULL, `signature_valid` tinyint(1) DEFAULT NULL, `processed` tinyint(1) NOT NULL DEFAULT 0, `processing_result` text DEFAULT NULL, `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `processed_at` timestamp NULL DEFAULT NULL, `ip_address` varchar(45) DEFAULT NULL, `user_agent` text DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_event_type` (`event_type`), KEY `idx_source` (`source`), KEY `idx_processed` (`processed`), KEY `idx_created_at` (`created_at`) ) ENGINE=InnoDB DEFAULT CHARSET=' . $CI->db->char_set . ';'); } /** * Create Performance Metrics Table */ if (!$CI->db->table_exists(db_prefix() . 'desk_moloni_performance_metrics')) { $CI->db->query('CREATE TABLE `' . db_prefix() . 'desk_moloni_performance_metrics` ( `id` int(11) NOT NULL AUTO_INCREMENT, `metric_type` varchar(50) NOT NULL, `metric_name` varchar(100) NOT NULL, `metric_value` decimal(15,4) NOT NULL, `metric_unit` varchar(20) DEFAULT NULL, `entity_type` varchar(50) DEFAULT NULL, `entity_id` int(11) DEFAULT NULL, `tags` json DEFAULT NULL COMMENT \'Additional metric tags\', `recorded_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `date_hour` varchar(13) NOT NULL COMMENT \'YYYY-MM-DD HH for aggregation\', `date_day` date NOT NULL COMMENT \'Date for daily aggregation\', PRIMARY KEY (`id`), KEY `idx_metric_type_name` (`metric_type`, `metric_name`), KEY `idx_recorded_at` (`recorded_at`), KEY `idx_date_hour` (`date_hour`), KEY `idx_date_day` (`date_day`), KEY `idx_entity` (`entity_type`, `entity_id`) ) ENGINE=InnoDB DEFAULT CHARSET=' . $CI->db->char_set . ';'); } /** * Create Error Logs Table */ if (!$CI->db->table_exists(db_prefix() . 'desk_moloni_error_logs')) { $CI->db->query('CREATE TABLE `' . db_prefix() . 'desk_moloni_error_logs` ( `id` int(11) NOT NULL AUTO_INCREMENT, `error_code` varchar(50) DEFAULT NULL, `error_type` varchar(50) NOT NULL, `severity` enum(\'low\',\'medium\',\'high\',\'critical\') NOT NULL DEFAULT \'medium\', `message` text NOT NULL, `context` json DEFAULT NULL COMMENT \'Error context data\', `stack_trace` longtext DEFAULT NULL, `entity_type` varchar(50) DEFAULT NULL, `entity_id` int(11) DEFAULT NULL, `queue_id` int(11) DEFAULT NULL, `resolved` tinyint(1) NOT NULL DEFAULT 0, `resolved_at` timestamp NULL DEFAULT NULL, `resolved_by` int(11) DEFAULT NULL, `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `ip_address` varchar(45) DEFAULT NULL, `user_agent` text DEFAULT NULL, `created_by` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_error_type` (`error_type`), KEY `idx_severity` (`severity`), KEY `idx_resolved` (`resolved`), KEY `idx_created_at` (`created_at`), KEY `idx_entity` (`entity_type`, `entity_id`), KEY `idx_queue_id` (`queue_id`) ) ENGINE=InnoDB DEFAULT CHARSET=' . $CI->db->char_set . ';'); } /** * Create Document Cache Table */ if (!$CI->db->table_exists(db_prefix() . 'desk_moloni_document_cache')) { $CI->db->query('CREATE TABLE `' . db_prefix() . 'desk_moloni_document_cache` ( `id` int(11) NOT NULL AUTO_INCREMENT, `cache_key` varchar(255) NOT NULL, `entity_type` varchar(50) NOT NULL, `entity_id` int(11) NOT NULL, `document_type` varchar(50) DEFAULT NULL, `document_data` longtext DEFAULT NULL COMMENT \'Cached document data\', `file_path` varchar(500) DEFAULT NULL, `file_size` int(11) DEFAULT NULL, `mime_type` varchar(100) DEFAULT NULL, `expires_at` timestamp NULL DEFAULT NULL, `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `accessed_at` timestamp NULL DEFAULT NULL, `access_count` int(11) NOT NULL DEFAULT 0, PRIMARY KEY (`id`), UNIQUE KEY `uk_cache_key` (`cache_key`), KEY `idx_entity` (`entity_type`, `entity_id`), KEY `idx_expires_at` (`expires_at`), KEY `idx_document_type` (`document_type`) ) ENGINE=InnoDB DEFAULT CHARSET=' . $CI->db->char_set . ';'); } /** * Create Config Table (for backward compatibility) */ if (!$CI->db->table_exists(db_prefix() . 'desk_moloni_config')) { $CI->db->query('CREATE TABLE `' . db_prefix() . 'desk_moloni_config` ( `id` int(11) NOT NULL AUTO_INCREMENT, `setting_key` varchar(255) NOT NULL, `setting_value` longtext DEFAULT NULL, `encrypted` tinyint(1) NOT NULL DEFAULT 0, `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_setting_key` (`setting_key`), KEY `idx_setting_key` (`setting_key`), KEY `idx_encrypted` (`encrypted`) ) ENGINE=InnoDB DEFAULT CHARSET=' . $CI->db->char_set . ';'); } /** * Create Mapping Table (for backward compatibility) */ if (!$CI->db->table_exists(db_prefix() . 'desk_moloni_mapping')) { $CI->db->query('CREATE TABLE `' . db_prefix() . 'desk_moloni_mapping` ( `id` int(11) NOT NULL AUTO_INCREMENT, `entity_type` enum(\'client\',\'product\',\'invoice\',\'estimate\',\'credit_note\') NOT NULL, `perfex_id` int(11) NOT NULL, `moloni_id` int(11) NOT NULL, `sync_direction` enum(\'perfex_to_moloni\',\'moloni_to_perfex\',\'bidirectional\') NOT NULL DEFAULT \'bidirectional\', `last_sync_at` timestamp NULL DEFAULT NULL, `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `unique_perfex_mapping` (`entity_type`, `perfex_id`), UNIQUE KEY `unique_moloni_mapping` (`entity_type`, `moloni_id`), KEY `idx_entity_perfex` (`entity_type`, `perfex_id`), KEY `idx_entity_moloni` (`entity_type`, `moloni_id`), KEY `idx_last_sync` (`last_sync_at`) ) ENGINE=InnoDB DEFAULT CHARSET=' . $CI->db->char_set . ';'); } /** * Create Sync Log Table (for backward compatibility) */ if (!$CI->db->table_exists(db_prefix() . 'desk_moloni_sync_log')) { $CI->db->query('CREATE TABLE `' . db_prefix() . 'desk_moloni_sync_log` ( `id` int(11) NOT NULL AUTO_INCREMENT, `operation_type` enum(\'create\',\'update\',\'delete\',\'status_change\') NOT NULL, `entity_type` enum(\'client\',\'product\',\'invoice\',\'estimate\',\'credit_note\') NOT NULL, `perfex_id` int(11) DEFAULT NULL, `moloni_id` int(11) DEFAULT NULL, `direction` enum(\'perfex_to_moloni\',\'moloni_to_perfex\') NOT NULL, `status` enum(\'success\',\'error\',\'warning\') NOT NULL, `request_data` longtext DEFAULT NULL COMMENT \'JSON request data\', `response_data` longtext DEFAULT NULL COMMENT \'JSON response data\', `error_message` text DEFAULT NULL, `execution_time_ms` int(11) DEFAULT NULL COMMENT \'Execution time in milliseconds\', `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_entity_status` (`entity_type`, `status`), KEY `idx_perfex_entity` (`perfex_id`, `entity_type`), KEY `idx_moloni_entity` (`moloni_id`, `entity_type`), KEY `idx_created_at` (`created_at`), KEY `idx_status_direction` (`status`, `direction`), KEY `idx_log_analytics` (`entity_type`, `operation_type`, `status`, `created_at`) ) ENGINE=InnoDB DEFAULT CHARSET=' . $CI->db->char_set . ';'); } /** * Insert default configurations */ $default_configs = [ [ 'config_key' => 'api_configuration', 'config_value' => json_encode([ 'base_url' => 'https://api.moloni.pt/v1/', 'oauth_url' => 'https://www.moloni.pt/v1/', 'timeout' => 30, 'max_retries' => 3, 'user_agent' => 'Desk-Moloni-Integration/3.0.0' ]), 'config_type' => 'json', 'description' => 'API connection configuration', 'category' => 'api', 'is_system' => 1 ], [ 'config_key' => 'sync_configuration', 'config_value' => json_encode([ 'auto_sync' => true, 'realtime_sync' => false, 'batch_size' => 10, 'default_delay' => 300, 'max_attempts' => 3 ]), 'config_type' => 'json', 'description' => 'Synchronization behavior configuration', 'category' => 'sync', 'is_system' => 0 ], [ 'config_key' => 'performance_configuration', 'config_value' => json_encode([ 'monitoring_enabled' => true, 'caching_enabled' => true, 'cache_ttl' => 3600, 'log_slow_queries' => true, 'slow_query_threshold' => 1000 ]), 'config_type' => 'json', 'description' => 'Performance and monitoring settings', 'category' => 'performance', 'is_system' => 0 ] ]; foreach ($default_configs as $config) { // Check if configuration already exists $existing = $CI->db->get_where(db_prefix() . 'desk_moloni_configuration', ['config_key' => $config['config_key']])->row(); if (!$existing) { $CI->db->insert(db_prefix() . 'desk_moloni_configuration', $config); } } /** * Add module permissions */ if (!$CI->db->get_where('tblpermissions', ['name' => 'desk_moloni'])->row()) { $permissions = [ ['name' => 'desk_moloni', 'shortname' => 'view', 'description' => 'View Desk-Moloni module'], ['name' => 'desk_moloni', 'shortname' => 'create', 'description' => 'Create sync tasks and configurations'], ['name' => 'desk_moloni', 'shortname' => 'edit', 'description' => 'Edit configurations and mappings'], ['name' => 'desk_moloni', 'shortname' => 'delete', 'description' => 'Delete sync tasks and clear data'] ]; foreach ($permissions as $permission) { $CI->db->insert('tblpermissions', $permission); } } /** * Create directories if they don't exist */ $directories = [ APP_MODULES_PATH . 'desk_moloni/uploads/', APP_MODULES_PATH . 'desk_moloni/logs/', APP_MODULES_PATH . 'desk_moloni/cache/', APP_MODULES_PATH . 'desk_moloni/temp/' ]; foreach ($directories as $dir) { if (!is_dir($dir)) { mkdir($dir, 0755, true); // Create index.html for security file_put_contents($dir . 'index.html', ''); } } // Log installation log_activity('Desk-Moloni v3.0 module installed successfully');