Files
desk-moloni/modules/desk_moloni/libraries/ClientNotificationService.php
Emanuel Almeida 8c4f68576f chore: add spec-kit and standardize signatures
- Added GitHub spec-kit for development workflow
- Standardized file signatures to Descomplicar® format
- Updated development configuration

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-12 01:27:37 +01:00

413 lines
13 KiB
PHP

/**
* Descomplicar® Crescimento Digital
* https://descomplicar.pt
*/
<?php
defined('BASEPATH') or exit('No direct script access allowed');
/**
* Client Notification Service
* Handles notifications for client portal users
*
* @package Desk-Moloni
* @version 3.0.0
* @author Descomplicar Business Solutions
*/
class ClientNotificationService
{
private $CI;
private $notificationsTable = 'desk_moloni_client_notifications';
public function __construct()
{
$this->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;
}
}