diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 37baad8..192d43b 100755 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -507,6 +507,12 @@ $routes->group("employeeRest", ['filter' => [ 'GlobalPostFileUploadGuard', 'appS $routes->get("copyActiveEmployeeAndDependentDetails", "EmployeeRestController::copyActiveEmployeeAndDependentDetails"); $routes->get("getExcelFileErrors/(:any)", "EmployeeController::getExcelFileErrors/$1"); + $routes->post("sendReminderMail", "EmployeeRestController::sendReminderMail"); + $routes->get("sendReminderMail", "EmployeeRestController::sendReminderMail"); + + $routes->get("getReminderMailConfig", "EmployeeRestController::getReminderMailConfig"); + $routes->post("saveReminderMailConfig", "EmployeeRestController::saveReminderMailConfig"); + }); $routes->group("employeeRest", ['filter' => ['appSignature','AuthApiRateLimitFilter'] ], function ($routes) { diff --git a/app/Controllers/DashboardController.php b/app/Controllers/DashboardController.php index 5eaea14..1739b54 100755 --- a/app/Controllers/DashboardController.php +++ b/app/Controllers/DashboardController.php @@ -19,6 +19,7 @@ use App\Models\JobModel; use App\Models\ClientPolicyModel; use App\Models\NotificationModel; use App\Models\EmployeePolicyModel; +use App\Models\ReminderMailConfigModel; use App\Controllers\PendingActionsContrller; use App\Controllers\EmpDataServiceController; @@ -32,6 +33,7 @@ class DashboardController extends AdminController protected $clientPolicyModel; protected $notificationModel; protected $employeePolicyModel; + protected $reminderMailConfigModel; protected $policyTransactionModel; protected $policyStatus; protected $colorShades; @@ -45,8 +47,9 @@ class DashboardController extends AdminController $this->userMessageModel = new UserMessageModel(); $this->clientPolicyModel = new ClientPolicyModel(); $this->notificationModel = new NotificationModel(); - $this->employeePolicyModel = new EmployeePolicyModel(); - $this->clientModel = new ClientModel(); + $this->employeePolicyModel = new EmployeePolicyModel(); + $this->reminderMailConfigModel = new ReminderMailConfigModel(); + $this->clientModel = new ClientModel(); $this->policyTransactionModel = new PolicyTransactionModel(); $this->myLogger = \Config\Services::mylogger(); @@ -438,14 +441,12 @@ class DashboardController extends AdminController } else { // crone - if (!empty($client_policy['reminder_date'])) { + $shouldSendToday = $this->reminderMailConfigModel->shouldSendTodayForPolicy( + (int) $client_policy['id'], + $client_policy['reminder_date'] ?? null + ); - $currentDate = date('d'); - $remainder_dates = explode(",", $client_policy['reminder_date']); - - foreach ($remainder_dates as $date) { - - if ($currentDate == $date) { + if ($shouldSendToday) { $this->myLogger->logme('error', 'Notification should be sent for client policy ID: ' . $client_policy['id']); @@ -526,11 +527,8 @@ class DashboardController extends AdminController $reminder_whole_mail[] = $mail_result; } - } - } - } else { - $this->myLogger->logme('error', "send Remainder crone --- Remainder date is empty()"); + $this->myLogger->logme('error', 'send Remainder crone --- reminder not scheduled for today'); } } diff --git a/app/Controllers/EmployeeRestController.php b/app/Controllers/EmployeeRestController.php index 2245a06..a4a0f85 100755 --- a/app/Controllers/EmployeeRestController.php +++ b/app/Controllers/EmployeeRestController.php @@ -33,6 +33,7 @@ use App\Models\AuditHistoryModel; use App\Models\SIMappingModel; use App\Models\InsurerModel; use App\Models\HrFileUploadModel; +use App\Models\ReminderMailConfigModel; use App\Controllers\Jobs ; use App\Controllers\JobWorker ; @@ -80,6 +81,7 @@ class EmployeeRestController extends AdminController protected $employeeHelper; protected $insurerModel; protected $hrFileUploadModel; + protected ReminderMailConfigModel $reminderMailConfigModel; public function __construct() @@ -108,6 +110,7 @@ class EmployeeRestController extends AdminController $this->employeeHelper = new EmployeeHelper(); $this->insurerModel = new InsurerModel(); $this->hrFileUploadModel = new HrFileUploadModel(); + $this->reminderMailConfigModel = new ReminderMailConfigModel(); } @@ -3162,8 +3165,9 @@ class EmployeeRestController extends AdminController public function getPolicyLevelEmployeeSummaryData() { - $hr_id = $this->request->getGet('hr_id'); + $client_id = $this->request->getGet('client_id'); + $client_branch_id = $this->request->getGet('client_branch_id'); $restAuthController = new RestAuthenticationController; //call and get allowed policy data from post enrollment $queryParams = [ @@ -3174,35 +3178,78 @@ class EmployeeRestController extends AdminController $HRAccessData = json_decode($HRAccessRes,true); if(isset($HRAccessData['data']['allowed_pre_policies'])) { - $policyId = json_decode($HRAccessData['data']['allowed_pre_policies'],true); + $allowedPolicyIds = json_decode($HRAccessData['data']['allowed_pre_policies'],true); }else{ - $policyId = []; + $allowedPolicyIds = []; } - if(count($policyId) == 0){ - return $this->respond(['status' => 'failed','code' => (count($policyId) ? 200 : 200),'data' => [] ], 200); + if(empty($allowedPolicyIds)){ + return $this->respond(['status' => 'failed','code' => 200,'data' => [] ], 200); + } + + $openEnrollmentPolicies = $this->employeePolicyModel + ->select('employee_polices.client_policy_id') + ->join('employees', 'employee_polices.employee_id = employees.id') + ->whereIn('employee_polices.client_policy_id', $allowedPolicyIds) + ->where('md5(employees.client_id)', $client_id) + ->where('employees.client_branch_id', $client_branch_id) + ->where('employee_polices.enrollment_open_date <= CURDATE()', null, false) + ->where('employee_polices.enrollment_close_date >= CURDATE()', null, false) + ->where('employee_polices.enrollment_open_date IS NOT NULL', null, false) + ->where('employee_polices.enrollment_close_date IS NOT NULL', null, false) + ->where('employees.is_active', 1) + ->where('employee_polices.is_active', 1) + ->whereIn('employee_polices.status', ['draft', 'enrolled']) + ->whereIn('employees.emp_status', ['draft', 'enrolled']) + ->groupBy('employee_polices.client_policy_id') + ->findAll(); + + $policyId = array_column($openEnrollmentPolicies, 'client_policy_id'); + + if(empty($policyId)){ + return $this->respond(['status' => 'failed','code' => 200,'data' => [] ], 200); } - $ClientPolicyData = $this->clientPolicyModel->select('client_policy.id as client_policy_id , client_policy.client_id as client_id, client_policy.policy_type_id as policy_type_id, client_policy.is_addon as is_addon , client_policy.open_for_enrollment as OpenForEnrollment , client_policy.inception_type as inception_type, client_policy.policy_no as policy_no, client_policy.insurer_id as insurer_id, DATE_FORMAT(client_policy.policy_end_date, "%d-%m-%Y") AS policy_expiry_date ') - ->where('md5(client_policy.client_id)', $this->request->getGet('client_id') ) - ->where('client_policy.client_branch_id', $this->request->getGet('client_branch_id') ) + ->where('md5(client_policy.client_id)', $client_id ) + ->where('client_policy.client_branch_id', $client_branch_id ) ->where('client_policy.is_active', 1 ) ->where('client_policy.policy_status', 1) ->whereIn('client_policy.id', $policyId) ->findAll(); + + $loggedInCounts = []; + $clientPolicyIds = array_column($ClientPolicyData, 'client_policy_id'); + if (!empty($clientPolicyIds)) { + $loggedInRows = $this->employeeModel + ->select('employee_polices.client_policy_id, COUNT(DISTINCT employees.id) as logged_in_count') + ->join('employee_polices', 'employees.id = employee_polices.employee_id') + ->join('auth_history', 'employees.id = auth_history.user_id AND auth_history.user_type = "employee"', 'inner') + ->whereIn('employee_polices.client_policy_id', $clientPolicyIds) + ->where('md5(employees.client_id)', $client_id) + ->where('employees.client_branch_id', $client_branch_id) + ->where('employees.relationship', 'Self') + ->where('employees.is_active', 1) + ->where('employee_polices.is_active', 1) + ->groupBy('employee_polices.client_policy_id') + ->findAll(); + + foreach ($loggedInRows as $loggedInRow) { + $loggedInCounts[$loggedInRow['client_policy_id']] = (int) $loggedInRow['logged_in_count']; + } + } + $result = []; - // dd( $ClientPolicyData); foreach ($ClientPolicyData as $key => $value) { $policyTypeData = $this->policyTypeModel->where('id',$value['policy_type_id'])->get()->getRow(); $insurerData = $this->insurerModel->where('id',$value['insurer_id'])->get()->getRow(); - $value['type'] = $policyTypeData->policy_type; - $value['policy_name'] = $policyTypeData->long_name; + $value['type'] = $policyTypeData->policy_type ?? null; + $value['policy_name'] = $policyTypeData->long_name ?? null; $value['insurer_name'] = $insurerData->name ?? null; $value['insurer_short_name'] = $insurerData->short_name ?? null; - $employeeDetails = $this->employeePolicyModel->getEmployeePolicy( client_id:$value['client_id'],policy_id: $value['client_policy_id'],status:0,branch_id:$this->request->getGet('client_branch_id')); + $employeeDetails = $this->employeePolicyModel->getEmployeePolicy( client_id:$value['client_id'],policy_id: $value['client_policy_id'],status:0,branch_id:$client_branch_id); $enrolledCount = 0; $draftCount = 0; if(count($employeeDetails)) @@ -3219,18 +3266,17 @@ class EmployeeRestController extends AdminController $value['totalMembersCount'] = count($employeeDetails); $value['membersCountOfEnrolled'] = $enrolledCount; $value['membersCountOfDraft'] = $draftCount; + $value['membersCountOfLoggedIn'] = $loggedInCounts[$value['client_policy_id']] ?? 0; array_push($result,$value); } - - if($result) { - return $this->respond(['status' => 'success','code' => (count($result) ? 200 : 200),'data' => $result ], 200); + return $this->respond(['status' => 'success','code' => 200,'data' => $result ], 200); }else{ - return $this->respond(['status' => 'failed','code' => (count($result) ? 200 : 200),'data' => [] ], 200); + return $this->respond(['status' => 'failed','code' => 200,'data' => [] ], 200); } } @@ -4634,4 +4680,194 @@ class EmployeeRestController extends AdminController return $policyIds; } + public function getReminderMailConfig() + { + try { + $clientPolicyId = $this->request->getGet('client_policy_id') + ?? $this->request->getPost('client_policy_id'); + + if (empty($clientPolicyId)) { + return $this->respond([ + 'status' => false, + 'code' => 400, + 'message' => 'client_policy_id is required', + ], 200); + } + + $clientPolicy = $this->clientPolicyModel->find($clientPolicyId); + + if (empty($clientPolicy)) { + return $this->respond([ + 'status' => false, + 'code' => 404, + 'message' => 'Client policy not found', + ], 200); + } + + $config = $this->reminderMailConfigModel->getByClientPolicyId((int) $clientPolicyId); + + if (empty($config) && !empty($clientPolicy['reminder_date'])) { + $config = [ + 'client_policy_id' => (int) $clientPolicyId, + 'frequency' => ReminderMailConfigModel::FREQUENCY_CUSTOM, + 'reminder_days' => $clientPolicy['reminder_date'], + 'is_enabled' => 1, + 'is_active' => 1, + 'source' => 'legacy_client_policy', + ]; + } + + if (!empty($config)) { + $config = $this->reminderMailConfigModel->enrichConfig($config); + } + + return $this->respond([ + 'status' => true, + 'code' => 200, + 'data' => $config, + 'working_day_options' => $this->reminderMailConfigModel->getWorkingDayOptions(), + ], 200); + } catch (\Exception $e) { + return $this->respond([ + 'status' => false, + 'code' => 500, + 'message' => $e->getMessage(), + ], 500); + } + } + + public function saveReminderMailConfig() + { + try { + $requestData = $this->getReminderMailConfigRequestData(); + + $clientPolicyId = $requestData['client_policy_id'] ?? null; + $configId = $requestData['id'] ?? null; + $frequency = strtolower(trim((string) ($requestData['frequency'] ?? ''))); + $reminderDays = $requestData['reminder_days'] + ?? $requestData['working_days'] + ?? null; + $isEnabled = $requestData['is_enabled'] ?? null; + $hrId = $requestData['hr_id'] ?? null; + + if (empty($clientPolicyId)) { + return $this->respond([ + 'status' => false, + 'code' => 400, + 'message' => 'client_policy_id is required', + ], 200); + } + + if (empty($frequency)) { + return $this->respond([ + 'status' => false, + 'code' => 400, + 'message' => 'frequency is required', + ], 200); + } + + $clientPolicy = $this->clientPolicyModel->find($clientPolicyId); + + if (empty($clientPolicy)) { + return $this->respond([ + 'status' => false, + 'code' => 404, + 'message' => 'Client policy not found', + ], 200); + } + + $result = $this->reminderMailConfigModel->saveConfig( + (int) $clientPolicyId, + $frequency, + $reminderDays !== null ? (string) $reminderDays : null, + $isEnabled === null ? 1 : (int) $isEnabled, + [ + 'id' => $configId !== null && $configId !== '' ? (int) $configId : null, + 'hr_id' => $hrId !== null && $hrId !== '' ? (int) $hrId : null, + ] + ); + + if (!$result['status']) { + return $this->respond([ + 'status' => false, + 'code' => 400, + 'message' => $result['message'], + ], 200); + } + + return $this->respond([ + 'status' => true, + 'code' => 200, + 'action' => $result['action'] ?? 'saved', + 'message' => 'Reminder mail configuration saved successfully', + 'data' => $result['data'], + ], 200); + } catch (\Exception $e) { + return $this->respond([ + 'status' => false, + 'code' => 500, + 'message' => $e->getMessage(), + ], 500); + } + } + + private function getReminderMailConfigRequestData(): array + { + $json = $this->request->getJSON(true); + + if (is_array($json) && !empty($json)) { + return $json; + } + + return array_filter([ + 'id' => $this->request->getVar('id'), + 'client_policy_id' => $this->request->getVar('client_policy_id'), + 'frequency' => $this->request->getVar('frequency'), + 'reminder_days' => $this->request->getVar('reminder_days'), + 'working_days' => $this->request->getVar('working_days'), + 'is_enabled' => $this->request->getVar('is_enabled'), + 'hr_id' => $this->request->getVar('hr_id'), + ], static fn ($value) => $value !== null && $value !== ''); + } + + public function sendReminderMail() + { + try { + $clientPolicyId = $this->request->getGet('client_policy_id') + ?? $this->request->getPost('client_policy_id'); + + if (empty($clientPolicyId)) { + return $this->respond([ + 'status' => false, + 'code' => 400, + 'message' => 'client_policy_id is required', + ], 200); + } + + $clientPolicy = $this->clientPolicyModel->find($clientPolicyId); + + if (empty($clientPolicy)) { + return $this->respond([ + 'status' => false, + 'code' => 404, + 'message' => 'Client policy not found', + ], 200); + } + + $dashboardController = new DashboardController(); + + return $dashboardController->sendManualReminder( + $clientPolicy['client_id'], + $clientPolicy['client_branch_id'], + $clientPolicyId + ); + } catch (\Exception $e) { + return $this->respond([ + 'status' => false, + 'code' => 500, + 'message' => $e->getMessage(), + ], 500); + } + } + } \ No newline at end of file diff --git a/app/Database/reminder_mail_config.sql b/app/Database/reminder_mail_config.sql new file mode 100644 index 0000000..d2267de --- /dev/null +++ b/app/Database/reminder_mail_config.sql @@ -0,0 +1,18 @@ +-- Reminder mail configuration table (fresh install + upgrade + legacy data migration) + +CREATE TABLE IF NOT EXISTS `reminder_mail_config` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `client_policy_id` INT UNSIGNED NOT NULL, + `frequency` ENUM('daily', 'weekly', 'monthly', 'custom', 'working_days') NOT NULL DEFAULT 'custom', + `reminder_days` VARCHAR(255) DEFAULT NULL COMMENT 'weekly: 0-6 (Sun-Sat); working_days: 1-7 (Mon-Sun) or Mon,Tue,...; monthly/custom: 1-31; daily: NULL', + `is_enabled` TINYINT(1) NOT NULL DEFAULT 1, + `is_active` TINYINT(1) NOT NULL DEFAULT 1, + `created_by` INT UNSIGNED DEFAULT NULL, + `updated_by` INT UNSIGNED DEFAULT NULL, + `created_at` DATETIME DEFAULT NULL, + `updated_at` DATETIME DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_reminder_mail_config_client_policy_id` (`client_policy_id`), + KEY `idx_reminder_mail_config_frequency` (`frequency`), + KEY `idx_reminder_mail_config_is_enabled` (`is_enabled`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/app/Models/EmployeePolicyModel.php b/app/Models/EmployeePolicyModel.php index 1753172..bf6ad26 100755 --- a/app/Models/EmployeePolicyModel.php +++ b/app/Models/EmployeePolicyModel.php @@ -88,14 +88,14 @@ class EmployeePolicyModel extends Model $status_query = "employee_polices.status"; // Default fallback - if ($status_type == "hr") { - $status_query = "CASE - WHEN employee_polices.status = 'enrolled' THEN 'under process' - ELSE employee_polices.status - END as status"; - } + // if ($status_type == "hr") { + // $status_query = "CASE + // WHEN employee_polices.status = 'enrolled' THEN 'under process' + // ELSE employee_polices.status + // END as status"; + // } - $result = $this->select([ + $selectColumns = [ 'employee_polices.*', 'policy_type.policy_type as policy_name', 'im.short_name as insurer_short_name', @@ -214,9 +214,19 @@ class EmployeePolicyModel extends Model ELSE NULL END )) AS newly_added_updated_at", + ]; - $status_query - ], false) + if ($status_type === 'hr') { + $selectColumns[] = "(CASE + WHEN emp.relationship = 'Self' + THEN IF(auth_history.user_id IS NOT NULL, 'Yes', 'No') + ELSE NULL + END) AS logged_in"; + } + + $selectColumns[] = $status_query; + + $result = $this->select($selectColumns, false) ->join('employees emp', 'employee_polices.employee_id = emp.id') ->join('client_policy cp', 'employee_polices.client_policy_id = cp.id') //cp - client policy ->join('policies pm', 'cp.policy_id = pm.id', 'left') //pm - policy master @@ -226,8 +236,18 @@ class EmployeePolicyModel extends Model ->join('tpa tpam', 'cp.tpa_id = tpam.id', 'left') //tpam - tpa master ->join('tpa_branch tpab', 'cp.tpa_branch_id = tpab.id', 'left') //tpab - tpa branch ->join('clients cm', 'cp.client_id = cm.id') //cm - client master - ->join('client_branch', 'emp.client_branch_id = client_branch.id') //cm - client master - ->orderBy('emp.emp_code', 'ASC') + ->join('client_branch', 'emp.client_branch_id = client_branch.id'); //cm - client master + + if ($status_type === 'hr') { + $result->join( + "(SELECT user_id FROM auth_history WHERE user_type = 'employee' GROUP BY user_id) auth_history", + 'emp.id = auth_history.user_id', + 'left', + false + ); + } + + $result->orderBy('emp.emp_code', 'ASC') ->orderBy('employee_polices.employee_id', 'ASC'); diff --git a/app/Models/ReminderMailConfigModel.php b/app/Models/ReminderMailConfigModel.php new file mode 100644 index 0000000..50132cc --- /dev/null +++ b/app/Models/ReminderMailConfigModel.php @@ -0,0 +1,342 @@ + 1, + 'monday' => 1, + 'tue' => 2, + 'tuesday' => 2, + 'wed' => 3, + 'wednesday' => 3, + 'thu' => 4, + 'thursday' => 4, + 'fri' => 5, + 'friday' => 5, + 'sat' => 6, + 'saturday' => 6, + 'sun' => 7, + 'sunday' => 7, + ]; + + private const WORKING_DAY_LABELS = [ + 1 => 'Mon', + 2 => 'Tue', + 3 => 'Wed', + 4 => 'Thu', + 5 => 'Fri', + 6 => 'Sat', + 7 => 'Sun', + ]; + + protected $table = 'reminder_mail_config'; + protected $primaryKey = 'id'; + protected $useAutoIncrement = true; + protected $returnType = 'array'; + protected $useTimestamps = true; + protected $createdField = 'created_at'; + protected $updatedField = 'updated_at'; + protected $allowedFields = [ + 'client_policy_id', + 'frequency', + 'reminder_days', + 'is_enabled', + 'is_active', + 'created_by', + 'updated_by', + ]; + + protected $allowCallbacks = true; + + public function getByClientPolicyId(int $clientPolicyId): ?array + { + return $this->where('client_policy_id', $clientPolicyId) + ->where('is_active', 1) + ->first(); + } + + public function shouldSendTodayForPolicy(int $clientPolicyId, ?string $legacyReminderDate = null): bool + { + $config = $this->getByClientPolicyId($clientPolicyId); + + if ($config) { + if (empty($config['is_enabled'])) { + return false; + } + + return $this->shouldSendToday($config); + } + + if (empty($legacyReminderDate)) { + return false; + } + + $currentDate = date('d'); + + foreach (explode(',', $legacyReminderDate) as $date) { + if ($currentDate == trim($date)) { + return true; + } + } + + return false; + } + + public function shouldSendToday(array $config): bool + { + switch ($config['frequency']) { + case self::FREQUENCY_DAILY: + return true; + + case self::FREQUENCY_WEEKLY: + return in_array((int) date('w'), $this->parseDays($config['reminder_days'] ?? ''), true); + + case self::FREQUENCY_MONTHLY: + case self::FREQUENCY_CUSTOM: + return in_array((int) date('j'), $this->parseDays($config['reminder_days'] ?? ''), true); + + case self::FREQUENCY_WORKING_DAYS: + return in_array((int) date('N'), $this->parseWorkingDays($config['reminder_days'] ?? ''), true); + + default: + return false; + } + } + + public function parseDays(?string $days): array + { + if ($days === null || trim($days) === '') { + return []; + } + + return array_values(array_unique(array_map( + 'intval', + array_filter(array_map('trim', explode(',', $days)), static fn ($day) => $day !== '') + ))); + } + + public function getWorkingDayOptions(): array + { + $options = []; + + foreach (self::WORKING_DAY_LABELS as $value => $label) { + $options[] = [ + 'value' => $value, + 'label' => $label, + 'key' => strtolower($label), + ]; + } + + return $options; + } + + public function parseWorkingDays(?string $days): array + { + if ($days === null || trim($days) === '') { + return []; + } + + $parsed = []; + + foreach (explode(',', $days) as $day) { + $day = trim($day); + + if ($day === '') { + continue; + } + + if (is_numeric($day)) { + $num = (int) $day; + + // Accept 0 as Sunday (same as 7 in ISO-8601 date('N')) + if ($num === 0) { + $num = 7; + } + + $parsed[] = $num; + continue; + } + + $key = strtolower($day); + + if (isset(self::WORKING_DAY_NAMES[$key])) { + $parsed[] = self::WORKING_DAY_NAMES[$key]; + } + } + + return array_values(array_unique($parsed)); + } + + public function normalizeWorkingDays(?string $days): ?string + { + $parsed = $this->parseWorkingDays($days); + + if (empty($parsed)) { + return null; + } + + sort($parsed); + + return implode(',', $parsed); + } + + public function formatWorkingDayLabels(?string $days): array + { + $labels = []; + + foreach ($this->parseWorkingDays($days) as $day) { + if (isset(self::WORKING_DAY_LABELS[$day])) { + $labels[] = self::WORKING_DAY_LABELS[$day]; + } + } + + return $labels; + } + + public function enrichConfig(array $config): array + { + if (($config['frequency'] ?? '') === self::FREQUENCY_WORKING_DAYS) { + $config['working_day_labels'] = $this->formatWorkingDayLabels($config['reminder_days'] ?? null); + } + + return $config; + } + + public function normalizeReminderDays(string $frequency, ?string $reminderDays): ?string + { + if ($frequency === self::FREQUENCY_DAILY) { + return null; + } + + if ($frequency === self::FREQUENCY_WORKING_DAYS) { + return $this->normalizeWorkingDays($reminderDays); + } + + return $reminderDays !== null ? trim((string) $reminderDays) : null; + } + + public function validateConfig(string $frequency, ?string $reminderDays): ?string + { + $allowedFrequencies = [ + self::FREQUENCY_DAILY, + self::FREQUENCY_WEEKLY, + self::FREQUENCY_MONTHLY, + self::FREQUENCY_CUSTOM, + self::FREQUENCY_WORKING_DAYS, + ]; + + if (!in_array($frequency, $allowedFrequencies, true)) { + return 'Invalid frequency. Allowed values: daily, weekly, monthly, custom, working_days.'; + } + + if ($frequency === self::FREQUENCY_DAILY) { + return null; + } + + if ($frequency === self::FREQUENCY_WORKING_DAYS) { + $days = $this->parseWorkingDays($reminderDays); + + if (empty($days)) { + return 'reminder_days is required. Use Mon-Sun or 1-7 (0 also accepted for Sunday).'; + } + + foreach ($days as $day) { + if ($day < 1 || $day > 7) { + return 'Working day reminder_days must be between 1 (Mon) and 7 (Sun).'; + } + } + + return null; + } + + $days = $this->parseDays($reminderDays); + + if (empty($days)) { + return 'reminder_days is required for the selected frequency.'; + } + + if ($frequency === self::FREQUENCY_WEEKLY) { + foreach ($days as $day) { + if ($day < 0 || $day > 6) { + return 'Weekly reminder_days must be between 0 (Sunday) and 6 (Saturday).'; + } + } + + return null; + } + + foreach ($days as $day) { + if ($day < 1 || $day > 31) { + return 'Monthly/custom reminder_days must be between 1 and 31.'; + } + } + + return null; + } + + /** + * @param array{id?: int|null, hr_id?: int|null} $options + */ + public function saveConfig( + int $clientPolicyId, + string $frequency, + ?string $reminderDays, + int $isEnabled = 1, + array $options = [] + ): array { + $id = isset($options['id']) && $options['id'] !== '' ? (int) $options['id'] : null; + $hrId = isset($options['hr_id']) && $options['hr_id'] !== '' + ? (int) $options['hr_id'] + : null; + + $validationError = $this->validateConfig($frequency, $reminderDays); + + if ($validationError !== null) { + return ['status' => false, 'message' => $validationError]; + } + + $payload = [ + 'client_policy_id' => $clientPolicyId, + 'frequency' => $frequency, + 'reminder_days' => $this->normalizeReminderDays($frequency, $reminderDays), + 'is_enabled' => $isEnabled, + 'is_active' => 1, + ]; + + if ($id) { + $existing = $this->where('id', $id)->where('is_active', 1)->first(); + + if (empty($existing)) { + return ['status' => false, 'message' => 'Reminder mail configuration not found']; + } + + if ((int) $existing['client_policy_id'] !== $clientPolicyId) { + return ['status' => false, 'message' => 'client_policy_id does not match the configuration record']; + } + + $payload['updated_by'] = $hrId; + $this->update($id, $payload); + $config = $this->find($id); + + return ['status' => true, 'action' => 'updated', 'data' => $this->enrichConfig($config)]; + } + + $payload['created_by'] = $hrId; + $payload['updated_by'] = $hrId; + + $this->insert($payload); + $config = $this->find($this->getInsertID()); + + return ['status' => true, 'action' => 'created', 'data' => $this->enrichConfig($config)]; + } +} diff --git a/docs/reminder-mail-api.md b/docs/reminder-mail-api.md new file mode 100644 index 0000000..a11435e --- /dev/null +++ b/docs/reminder-mail-api.md @@ -0,0 +1,540 @@ +# Reminder Mail API Documentation + +Base path for all endpoints: `/employeeRest` + +**Authentication:** JWT + app signature (same filters as other `employeeRest` routes) + +**Content types:** JSON body or form-urlencoded (where noted) + +**HTTP status:** All responses return HTTP `200`. Check the JSON `status` and `code` fields for success or failure. + +--- + +## Table of Contents + +1. [Get Configuration](#1-get-configuration) +2. [Save / Update Configuration](#2-save--update-configuration) +3. [Send Reminder Mail (Manual)](#3-send-reminder-mail-manual) +4. [Frequency Reference](#frequency-reference) +5. [Working Days Reference](#working-days-reference) +6. [Quick Reference](#quick-reference) + +--- + +## 1. Get Configuration + +Fetch reminder mail configuration for a client policy. + +| | | +|---|---| +| **Method** | `GET` | +| **URL** | `/employeeRest/getReminderMailConfig` | + +### Request Parameters + +| Field | Required | Type | Description | +|-------|----------|------|-------------| +| `client_policy_id` | Yes | integer | Client policy ID | + +### Sample Request + +```http +GET /employeeRest/getReminderMailConfig?client_policy_id=123 +``` + +### Success Response — Config Exists + +```json +{ + "status": true, + "code": 200, + "data": { + "id": 10, + "client_policy_id": 123, + "frequency": "working_days", + "reminder_days": "1,3,5", + "working_day_labels": ["Mon", "Wed", "Fri"], + "is_enabled": 1, + "is_active": 1, + "created_by": 45, + "updated_by": 45, + "created_at": "2026-06-23 10:00:00", + "updated_at": "2026-06-23 11:30:00" + }, + "working_day_options": [ + { "value": 1, "label": "Mon", "key": "mon" }, + { "value": 2, "label": "Tue", "key": "tue" }, + { "value": 3, "label": "Wed", "key": "wed" }, + { "value": 4, "label": "Thu", "key": "thu" }, + { "value": 5, "label": "Fri", "key": "fri" }, + { "value": 6, "label": "Sat", "key": "sat" }, + { "value": 7, "label": "Sun", "key": "sun" } + ] +} +``` + +### Success Response — Legacy Fallback (no config row, legacy `reminder_date` on policy) + +```json +{ + "status": true, + "code": 200, + "data": { + "client_policy_id": 123, + "frequency": "custom", + "reminder_days": "1,15,28", + "is_enabled": 1, + "is_active": 1, + "source": "legacy_client_policy" + }, + "working_day_options": [ + { "value": 1, "label": "Mon", "key": "mon" }, + { "value": 2, "label": "Tue", "key": "tue" }, + { "value": 3, "label": "Wed", "key": "wed" }, + { "value": 4, "label": "Thu", "key": "thu" }, + { "value": 5, "label": "Fri", "key": "fri" }, + { "value": 6, "label": "Sat", "key": "sat" }, + { "value": 7, "label": "Sun", "key": "sun" } + ] +} +``` + +### Success Response — No Config + +```json +{ + "status": true, + "code": 200, + "data": null, + "working_day_options": [ + { "value": 1, "label": "Mon", "key": "mon" }, + { "value": 2, "label": "Tue", "key": "tue" }, + { "value": 3, "label": "Wed", "key": "wed" }, + { "value": 4, "label": "Thu", "key": "thu" }, + { "value": 5, "label": "Fri", "key": "fri" }, + { "value": 6, "label": "Sat", "key": "sat" }, + { "value": 7, "label": "Sun", "key": "sun" } + ] +} +``` + +### Error Responses + +```json +{ + "status": false, + "code": 400, + "message": "client_policy_id is required" +} +``` + +```json +{ + "status": false, + "code": 404, + "message": "Client policy not found" +} +``` + +--- + +## 2. Save / Update Configuration + +Create or update reminder mail configuration. + +| | | +|---|---| +| **Method** | `POST` | +| **URL** | `/employeeRest/saveReminderMailConfig` | + +### Insert vs Update + +| Condition | Action | +|-----------|--------| +| `id` **not** sent | **Insert** new record | +| `id` **sent** | **Update** existing record by primary key | + +### Request Parameters + +| Field | Required | Type | Description | +|-------|----------|------|-------------| +| `client_policy_id` | Yes | integer | Client policy ID | +| `frequency` | Yes | string | `daily`, `weekly`, `monthly`, `custom`, `working_days` | +| `reminder_days` | Depends | string | Comma-separated days (see [Frequency Reference](#frequency-reference)) | +| `working_days` | No | string | Alias for `reminder_days` when `frequency` is `working_days` | +| `is_enabled` | No | integer | `1` (enabled) or `0` (disabled). Default: `1` | +| `hr_id` | No | integer | Stored as `created_by` / `updated_by`. Null if omitted | +| `id` | No | integer | Primary key — required for update | + +### Sample Payload — Insert (Daily) + +```json +{ + "client_policy_id": 123, + "frequency": "daily", + "is_enabled": 1, + "hr_id": 45 +} +``` + +### Sample Payload — Insert (Working Days — weekdays only) + +```json +{ + "client_policy_id": 123, + "frequency": "working_days", + "reminder_days": "Mon,Wed,Fri", + "is_enabled": 1, + "hr_id": 45 +} +``` + +### Sample Payload — Insert (Working Days — including Sat/Sun) + +```json +{ + "client_policy_id": 123, + "frequency": "working_days", + "reminder_days": "Mon,Wed,Fri,Sat,Sun", + "is_enabled": 1, + "hr_id": 45 +} +``` + +Or numeric: `"1,3,5,6,7"` (Sat=6, Sun=7; `0` is also accepted for Sunday). + +Alternative using `working_days` alias: + +```json +{ + "client_policy_id": 123, + "frequency": "working_days", + "working_days": "Mon,Tue,Thu", + "hr_id": 45 +} +``` + +### Sample Payload — Insert (Weekly) + +```json +{ + "client_policy_id": 123, + "frequency": "weekly", + "reminder_days": "1,3,5", + "hr_id": 45 +} +``` + +### Sample Payload — Insert (Monthly) + +```json +{ + "client_policy_id": 123, + "frequency": "monthly", + "reminder_days": "1,15", + "hr_id": 45 +} +``` + +### Sample Payload — Insert (Custom) + +```json +{ + "client_policy_id": 123, + "frequency": "custom", + "reminder_days": "5,10,20", + "hr_id": 45 +} +``` + +### Sample Payload — Update + +```json +{ + "id": 10, + "client_policy_id": 123, + "frequency": "working_days", + "reminder_days": "Mon,Tue,Wed,Thu,Fri", + "is_enabled": 1, + "hr_id": 45 +} +``` + +### Success Response — Created + +```json +{ + "status": true, + "code": 200, + "action": "created", + "message": "Reminder mail configuration saved successfully", + "data": { + "id": 10, + "client_policy_id": 123, + "frequency": "working_days", + "reminder_days": "1,3,5", + "working_day_labels": ["Mon", "Wed", "Fri"], + "is_enabled": 1, + "is_active": 1, + "created_by": 45, + "updated_by": 45, + "created_at": "2026-06-23 10:00:00", + "updated_at": "2026-06-23 10:00:00" + } +} +``` + +### Success Response — Updated + +```json +{ + "status": true, + "code": 200, + "action": "updated", + "message": "Reminder mail configuration saved successfully", + "data": { + "id": 10, + "client_policy_id": 123, + "frequency": "working_days", + "reminder_days": "1,2,3,4,5", + "working_day_labels": ["Mon", "Tue", "Wed", "Thu", "Fri"], + "is_enabled": 1, + "is_active": 1, + "created_by": 45, + "updated_by": 45, + "created_at": "2026-06-23 10:00:00", + "updated_at": "2026-06-23 12:00:00" + } +} +``` + +### Error Responses + +```json +{ + "status": false, + "code": 400, + "message": "client_policy_id is required" +} +``` + +```json +{ + "status": false, + "code": 400, + "message": "frequency is required" +} +``` + +```json +{ + "status": false, + "code": 404, + "message": "Client policy not found" +} +``` + +```json +{ + "status": false, + "code": 400, + "message": "Reminder mail configuration not found" +} +``` + +```json +{ + "status": false, + "code": 400, + "message": "client_policy_id does not match the configuration record" +} +``` + +```json +{ + "status": false, + "code": 400, + "message": "Invalid frequency. Allowed values: daily, weekly, monthly, custom, working_days." +} +``` + +```json +{ + "status": false, + "code": 400, + "message": "reminder_days is required. Use Mon-Sun or 1-7 (0 also accepted for Sunday)." +} +``` + +```json +{ + "status": false, + "code": 400, + "message": "Working day reminder_days must be between 1 (Mon) and 7 (Sun)." +} +``` + +### Audit Fields (`hr_id`) + +| Action | `created_by` | `updated_by` | +|--------|--------------|--------------| +| Insert + `hr_id` sent | `hr_id` | `hr_id` | +| Insert + `hr_id` omitted | `null` | `null` | +| Update + `hr_id` sent | unchanged | `hr_id` | +| Update + `hr_id` omitted | unchanged | `null` | + +--- + +## 3. Send Reminder Mail (Manual) + +Manually trigger reminder mail for a policy. Sends immediately regardless of schedule configuration. + +| | | +|---|---| +| **Method** | `GET` or `POST` | +| **URL** | `/employeeRest/sendReminderMail` | + +### Request Parameters + +| Field | Required | Type | Description | +|-------|----------|------|-------------| +| `client_policy_id` | Yes | integer | Client policy ID | + +### Sample Request (POST) + +```http +POST /employeeRest/sendReminderMail +Content-Type: application/json + +{ + "client_policy_id": 123 +} +``` + +### Sample Request (GET) + +```http +GET /employeeRest/sendReminderMail?client_policy_id=123 +``` + +### Success Response + +```json +{ + "status": true, + "code": 200, + "message": "Mail sent successfully" +} +``` + +### Error Responses + +```json +{ + "status": false, + "code": 400, + "message": "client_policy_id is required" +} +``` + +```json +{ + "status": false, + "code": 404, + "message": "Client policy not found" +} +``` + +```json +{ + "status": false, + "code": 200, + "message": "There is no data to send", + "message2": "Failed" +} +``` + +```json +{ + "status": false, + "code": 200, + "message": "There is no data to send", + "message2": "No policy data found to send" +} +``` + +--- + +## Frequency Reference + +| `frequency` | `reminder_days` required? | Format | Example | +|-------------|---------------------------|--------|---------| +| `daily` | No | — | — | +| `weekly` | Yes | Weekday `0–6` (Sun–Sat) | `"1,3,5"` | +| `monthly` | Yes | Day of month `1–31` | `"1,15,28"` | +| `custom` | Yes | Day of month `1–31` | `"5,10,20"` | +| `working_days` | Yes | Weekday `1–7` (Mon–Sun) or day names | `"Mon,Wed,Fri,Sat"` or `"1,3,5,6"` | + +--- + +## Working Days Reference + +Used when `frequency = working_days`. Select any combination of weekdays including Saturday and Sunday. + +| Label | Numeric Value | +|-------|---------------| +| Mon | 1 | +| Tue | 2 | +| Wed | 3 | +| Thu | 4 | +| Fri | 5 | +| Sat | 6 | +| Sun | 7 | + +Accepted input formats: + +- Day names: `Mon`, `Tue`, `Wed`, `Thu`, `Fri`, `Sat`, `Sun` (case-insensitive) +- Numeric: `1`–`7` (ISO-8601, matches PHP `date('N')`) +- Sunday alias: `0` is normalized to `7` +- Comma-separated combinations: `"Mon,Wed,Sat"` or `"1,3,6"` + +Stored in DB as normalized numeric string (e.g. `"1,3,6,7"`). + +--- + +## Quick Reference + +| API | Method | Purpose | +|-----|--------|---------| +| `/employeeRest/getReminderMailConfig` | GET | Fetch config by `client_policy_id` | +| `/employeeRest/saveReminderMailConfig` | POST | Create (no `id`) or update (with `id`) | +| `/employeeRest/sendReminderMail` | GET / POST | Manually send reminder mail | + +--- + +## Notes + +1. **Scheduled sends (cron)** use the saved configuration from the `reminder_mail_config` table. +2. **Manual send** via `/sendReminderMail` ignores the schedule and sends immediately. +3. **`working_day_options`** is returned on every get-config response for UI dropdown/checkbox rendering. +4. **Legacy policies** without a config row may still expose `client_policy.reminder_date` as `frequency: custom` with `source: legacy_client_policy`. + +--- + +## Database + +Configuration is stored in the `reminder_mail_config` table. + +| Column | Description | +|--------|-------------| +| `id` | Primary key | +| `client_policy_id` | Unique per policy | +| `frequency` | Schedule type | +| `reminder_days` | Comma-separated day values | +| `is_enabled` | Enable/disable reminders | +| `created_by` / `updated_by` | Set from `hr_id` in API payload | +| `created_at` / `updated_at` | Timestamps | + +SQL migration: + +- `app/Database/reminder_mail_config.sql` diff --git a/public/sample_excel/enrollment.xlsx b/public/sample_excel/enrollment.xlsx index 97cd319..833c360 100755 Binary files a/public/sample_excel/enrollment.xlsx and b/public/sample_excel/enrollment.xlsx differ