/** * Descomplicar® Crescimento Digital * https://descomplicar.pt */ CI =& get_instance(); $this->CI->load->database(); $this->CI->load->helper('date'); // Create notifications table if it doesn't exist $this->_ensureNotificationsTableExists(); } /** * Create a new notification for a client * * @param int $clientId Client ID * @param string $type Notification type * @param string $title Notification title * @param string $message Notification message * @param int|null $documentId Related document ID (optional) * @param string|null $actionUrl Action URL (optional) * @return int|false Notification ID or false on failure */ public function createNotification($clientId, $type, $title, $message, $documentId = null, $actionUrl = null) { try { $data = [ 'client_id' => (int) $clientId, 'type' => $type, 'title' => $title, 'message' => $message, 'document_id' => $documentId ? (int) $documentId : null, 'action_url' => $actionUrl, 'is_read' => 0, 'created_at' => date('Y-m-d H:i:s') ]; // Validate notification type if (!$this->_isValidNotificationType($type)) { throw new Exception('Invalid notification type: ' . $type); } // Validate client exists if (!$this->_clientExists($clientId)) { throw new Exception('Client does not exist: ' . $clientId); } $result = $this->CI->db->insert($this->notificationsTable, $data); if ($result) { $notificationId = $this->CI->db->insert_id(); // Log notification creation log_message('info', "Notification created: ID {$notificationId} for client {$clientId}"); return $notificationId; } return false; } catch (Exception $e) { log_message('error', 'Create notification error: ' . $e->getMessage()); return false; } } /** * Get notifications for a client * * @param int $clientId Client ID * @param bool $unreadOnly Get only unread notifications * @param int $limit Maximum number of notifications * @param int $offset Offset for pagination * @return array Notifications */ public function getClientNotifications($clientId, $unreadOnly = false, $limit = 20, $offset = 0) { try { $this->CI->db->where('client_id', $clientId); if ($unreadOnly) { $this->CI->db->where('is_read', 0); } $query = $this->CI->db->order_by('created_at', 'DESC') ->limit($limit, $offset) ->get($this->notificationsTable); $notifications = $query->result_array(); // Format notifications foreach ($notifications as &$notification) { $notification['id'] = (int) $notification['id']; $notification['client_id'] = (int) $notification['client_id']; $notification['document_id'] = $notification['document_id'] ? (int) $notification['document_id'] : null; $notification['is_read'] = (bool) $notification['is_read']; } return $notifications; } catch (Exception $e) { log_message('error', 'Get client notifications error: ' . $e->getMessage()); return []; } } /** * Get unread notifications count for a client * * @param int $clientId Client ID * @return int Unread count */ public function getUnreadCount($clientId) { try { return $this->CI->db->where('client_id', $clientId) ->where('is_read', 0) ->count_all_results($this->notificationsTable); } catch (Exception $e) { log_message('error', 'Get unread count error: ' . $e->getMessage()); return 0; } } /** * Mark notification as read * * @param int $notificationId Notification ID * @param int $clientId Client ID (for security check) * @return bool Success status */ public function markAsRead($notificationId, $clientId) { try { $this->CI->db->where('id', $notificationId) ->where('client_id', $clientId); $result = $this->CI->db->update($this->notificationsTable, [ 'is_read' => 1, 'read_at' => date('Y-m-d H:i:s') ]); return $result && $this->CI->db->affected_rows() > 0; } catch (Exception $e) { log_message('error', 'Mark notification as read error: ' . $e->getMessage()); return false; } } /** * Mark all notifications as read for a client * * @param int $clientId Client ID * @return bool Success status */ public function markAllAsRead($clientId) { try { $this->CI->db->where('client_id', $clientId) ->where('is_read', 0); $result = $this->CI->db->update($this->notificationsTable, [ 'is_read' => 1, 'read_at' => date('Y-m-d H:i:s') ]); return $result; } catch (Exception $e) { log_message('error', 'Mark all notifications as read error: ' . $e->getMessage()); return false; } } /** * Delete old notifications * * @param int $olderThanDays Delete notifications older than X days * @return int Number of deleted notifications */ public function cleanupOldNotifications($olderThanDays = 90) { try { $cutoffDate = date('Y-m-d H:i:s', strtotime("-{$olderThanDays} days")); $this->CI->db->where('created_at <', $cutoffDate); $result = $this->CI->db->delete($this->notificationsTable); return $this->CI->db->affected_rows(); } catch (Exception $e) { log_message('error', 'Cleanup old notifications error: ' . $e->getMessage()); return 0; } } /** * Create document notification when a new document is available * * @param int $clientId Client ID * @param int $documentId Document ID * @param string $documentType Document type (invoice, estimate, etc.) * @param string $documentNumber Document number * @return int|false Notification ID or false on failure */ public function notifyDocumentCreated($clientId, $documentId, $documentType, $documentNumber) { $title = 'New ' . ucfirst($documentType) . ' Available'; $message = "A new {$documentType} ({$documentNumber}) is now available for viewing."; $actionUrl = site_url("clients/desk_moloni/documents/{$documentId}"); return $this->createNotification( $clientId, 'document_created', $title, $message, $documentId, $actionUrl ); } /** * Create payment received notification * * @param int $clientId Client ID * @param int $documentId Document ID * @param float $amount Payment amount * @param string $documentNumber Document number * @return int|false Notification ID or false on failure */ public function notifyPaymentReceived($clientId, $documentId, $amount, $documentNumber) { $title = 'Payment Received'; $message = "Payment of " . number_format($amount, 2) . " received for {$documentNumber}."; $actionUrl = site_url("clients/desk_moloni/documents/{$documentId}"); return $this->createNotification( $clientId, 'payment_received', $title, $message, $documentId, $actionUrl ); } /** * Create overdue notice notification * * @param int $clientId Client ID * @param int $documentId Document ID * @param string $documentNumber Document number * @param string $dueDate Due date * @return int|false Notification ID or false on failure */ public function notifyOverdue($clientId, $documentId, $documentNumber, $dueDate) { $title = 'Payment Overdue'; $message = "Payment for {$documentNumber} was due on {$dueDate}. Please review your account."; $actionUrl = site_url("clients/desk_moloni/documents/{$documentId}"); return $this->createNotification( $clientId, 'overdue_notice', $title, $message, $documentId, $actionUrl ); } /** * Create system message notification * * @param int $clientId Client ID * @param string $title Message title * @param string $message Message content * @return int|false Notification ID or false on failure */ public function notifySystemMessage($clientId, $title, $message) { return $this->createNotification( $clientId, 'system_message', $title, $message ); } /** * Get notification by ID * * @param int $notificationId Notification ID * @param int $clientId Client ID (for security check) * @return array|null Notification data or null if not found */ public function getNotificationById($notificationId, $clientId) { try { $query = $this->CI->db->where('id', $notificationId) ->where('client_id', $clientId) ->get($this->notificationsTable); $notification = $query->row_array(); if ($notification) { $notification['id'] = (int) $notification['id']; $notification['client_id'] = (int) $notification['client_id']; $notification['document_id'] = $notification['document_id'] ? (int) $notification['document_id'] : null; $notification['is_read'] = (bool) $notification['is_read']; } return $notification; } catch (Exception $e) { log_message('error', 'Get notification by ID error: ' . $e->getMessage()); return null; } } // Private Methods /** * Ensure notifications table exists */ private function _ensureNotificationsTableExists() { if (!$this->CI->db->table_exists($this->notificationsTable)) { $this->_createNotificationsTable(); } } /** * Create notifications table */ private function _createNotificationsTable() { $sql = " CREATE TABLE IF NOT EXISTS `{$this->notificationsTable}` ( `id` int(11) NOT NULL AUTO_INCREMENT, `client_id` int(11) NOT NULL, `type` enum('document_created','payment_received','overdue_notice','system_message') NOT NULL, `title` varchar(255) NOT NULL, `message` text NOT NULL, `document_id` int(11) DEFAULT NULL, `action_url` varchar(500) DEFAULT NULL, `is_read` tinyint(1) NOT NULL DEFAULT 0, `created_at` datetime NOT NULL, `read_at` datetime DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_client_id` (`client_id`), KEY `idx_client_unread` (`client_id`, `is_read`), KEY `idx_created_at` (`created_at`), KEY `idx_document_id` (`document_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; "; $this->CI->db->query($sql); if ($this->CI->db->error()['code'] !== 0) { log_message('error', 'Failed to create notifications table: ' . $this->CI->db->error()['message']); } else { log_message('info', 'Notifications table created successfully'); } } /** * Check if notification type is valid */ private function _isValidNotificationType($type) { $validTypes = [ 'document_created', 'payment_received', 'overdue_notice', 'system_message' ]; return in_array($type, $validTypes); } /** * Check if client exists */ private function _clientExists($clientId) { $count = $this->CI->db->where('userid', $clientId) ->count_all_results('tblclients'); return $count > 0; } }