diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 569a22f..80256f5 100755 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -511,7 +511,6 @@ $routes->group("employeeRest", ['filter' => [ 'GlobalPostFileUploadGuard', 'appS $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"); diff --git a/app/Controllers/DashboardController.php b/app/Controllers/DashboardController.php index 1739b54..0a62204 100755 --- a/app/Controllers/DashboardController.php +++ b/app/Controllers/DashboardController.php @@ -350,7 +350,13 @@ class DashboardController extends AdminController ->where('template_name', 'member_reminder_mail') ->first(); - if ($notification_data && $notification_data['enabled'] == 1 && !empty($notification_data['mail_content'])) { + $reminderConfig = $this->reminderMailConfigModel->getByClientPolicyId((int) $client_policy['id']); + $notification_data = $this->reminderMailConfigModel->resolveNotificationTemplate( + $reminderConfig, + $notification_data + ); + + if ($this->reminderMailConfigModel->canSendReminderMail($reminderConfig, $notification_data)) { //manaual if (empty($send_mail_for_manual_or_crone)) { diff --git a/app/Controllers/EmployeeRestController.php b/app/Controllers/EmployeeRestController.php index f75e95b..1cc99b3 100755 --- a/app/Controllers/EmployeeRestController.php +++ b/app/Controllers/EmployeeRestController.php @@ -4684,8 +4684,9 @@ class EmployeeRestController extends AdminController public function getReminderMailConfig() { try { - $clientPolicyId = $this->request->getGet('client_policy_id') - ?? $this->request->getPost('client_policy_id'); + + $clientPolicyId = $this->request->getGet('client_policy_id'); + $is_email_template = $this->request->getGet('is_email_template') ?? false; if (empty($clientPolicyId)) { return $this->respond([ @@ -4707,6 +4708,27 @@ class EmployeeRestController extends AdminController $config = $this->reminderMailConfigModel->getByClientPolicyId((int) $clientPolicyId); + if ($is_email_template) { + if (empty($config)) { + + $notification = $this->notificationModel->where('client_id', $clientPolicy['client_id'])->where('template_name', 'member_reminder_mail')->first(); + if (empty($notification)) { + return $this->respond([ + 'status' => false, + 'code' => 404, + 'message' => 'Member reminder mail notification not found', + ], 200); + } + + return $this->respond(['status' => true, 'code' => 200, 'data' => ['email_subject' => $notification['subject'], 'email_body' => $notification['mail_content']]], 200); + } + return $this->respond([ + 'status' => true, + 'code' => 200, + 'data' => ['email_subject' => $config['email_subject'], 'email_body' => $config['email_body']], + ], 200); + } + if (empty($config) && !empty($clientPolicy['reminder_date'])) { $config = [ 'client_policy_id' => (int) $clientPolicyId, @@ -4750,6 +4772,8 @@ class EmployeeRestController extends AdminController ?? null; $isEnabled = $requestData['is_enabled'] ?? null; $hrId = $requestData['hr_id'] ?? null; + $emailSubject = $requestData['email_subject'] ?? null; + $emailBody = $requestData['email_body'] ?? null; if (empty($clientPolicyId)) { return $this->respond([ @@ -4777,15 +4801,25 @@ class EmployeeRestController extends AdminController ], 200); } + $saveOptions = [ + 'id' => $configId !== null && $configId !== '' ? (int) $configId : null, + 'hr_id' => $hrId !== null && $hrId !== '' ? (int) $hrId : null, + ]; + + if (array_key_exists('email_subject', $requestData)) { + $saveOptions['email_subject'] = $emailSubject; + } + + if (array_key_exists('email_body', $requestData)) { + $saveOptions['email_body'] = $emailBody; + } + $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, - ] + $saveOptions ); if (!$result['status']) { @@ -4826,6 +4860,8 @@ class EmployeeRestController extends AdminController 'frequency' => $this->request->getVar('frequency'), 'reminder_days' => $this->request->getVar('reminder_days'), 'working_days' => $this->request->getVar('working_days'), + 'email_subject' => $this->request->getVar('email_subject'), + 'email_body' => $this->request->getVar('email_body'), 'is_enabled' => $this->request->getVar('is_enabled'), 'hr_id' => $this->request->getVar('hr_id'), ], static fn ($value) => $value !== null && $value !== ''); @@ -4834,8 +4870,34 @@ class EmployeeRestController extends AdminController public function sendReminderMail() { try { - $clientPolicyId = $this->request->getGet('client_policy_id') - ?? $this->request->getPost('client_policy_id'); + + if (!$this->request->is('post')) { + return $this->respond([ + 'status' => false, + 'code' => 405, + 'message' => 'Only POST method is allowed', + ], 200); + } + + try { + $requestData = $this->request->getJSON(true); + } catch (\CodeIgniter\HTTP\Exceptions\HTTPException $e) { + return $this->respond([ + 'status' => false, + 'code' => 400, + 'message' => 'Invalid JSON request body', + ], 200); + } + + if (!is_array($requestData) || empty($requestData)) { + return $this->respond([ + 'status' => false, + 'code' => 400, + 'message' => 'JSON request body is required', + ], 200); + } + + $clientPolicyId = $requestData['client_policy_id'] ?? null; if (empty($clientPolicyId)) { return $this->respond([ @@ -4855,9 +4917,20 @@ class EmployeeRestController extends AdminController ], 200); } - $dashboardController = new DashboardController(); + $emailSubject = $requestData['email_subject'] ?? null; + $emailBody = $requestData['email_body'] ?? null; + $hrId = $requestData['hr_id'] ?? null; - return $dashboardController->sendManualReminder( + if (array_key_exists('email_subject', $requestData) || array_key_exists('email_body', $requestData)) { + $this->reminderMailConfigModel->saveEmailTemplate( + (int) $clientPolicyId, + $emailSubject, + $emailBody, + $hrId !== null && $hrId !== '' ? (int) $hrId : null + ); + } + + return $this->dispatchManualReminder( $clientPolicy['client_id'], $clientPolicy['client_branch_id'], $clientPolicyId @@ -4871,4 +4944,15 @@ class EmployeeRestController extends AdminController } } + protected function dispatchManualReminder($clientId, $clientBranchId, $clientPolicyId) + { + $dashboardController = new DashboardController(); + + return $dashboardController->sendManualReminder( + $clientId, + $clientBranchId, + $clientPolicyId + ); + } + } \ No newline at end of file diff --git a/app/Database/reminder_mail_config.sql b/app/Database/reminder_mail_config.sql index d2267de..e38b5b2 100644 --- a/app/Database/reminder_mail_config.sql +++ b/app/Database/reminder_mail_config.sql @@ -5,6 +5,8 @@ CREATE TABLE IF NOT EXISTS `reminder_mail_config` ( `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', + `email_subject` VARCHAR(500) DEFAULT NULL COMMENT 'Custom reminder mail subject; falls back to notification template when NULL', + `email_body` TEXT DEFAULT NULL COMMENT 'Custom reminder mail body; falls back to notification template when NULL', `is_enabled` TINYINT(1) NOT NULL DEFAULT 1, `is_active` TINYINT(1) NOT NULL DEFAULT 1, `created_by` INT UNSIGNED DEFAULT NULL, @@ -16,3 +18,10 @@ CREATE TABLE IF NOT EXISTS `reminder_mail_config` ( 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; + +-- Upgrade existing installations (ignore duplicate-column errors if already applied) +ALTER TABLE `reminder_mail_config` + ADD COLUMN `email_subject` VARCHAR(500) DEFAULT NULL COMMENT 'Custom reminder mail subject; falls back to notification template when NULL' AFTER `reminder_days`; + +ALTER TABLE `reminder_mail_config` + ADD COLUMN `email_body` TEXT DEFAULT NULL COMMENT 'Custom reminder mail body; falls back to notification template when NULL' AFTER `email_subject`; diff --git a/app/Models/ReminderMailConfigModel.php b/app/Models/ReminderMailConfigModel.php index 50132cc..c4d7933 100644 --- a/app/Models/ReminderMailConfigModel.php +++ b/app/Models/ReminderMailConfigModel.php @@ -50,6 +50,8 @@ class ReminderMailConfigModel extends Model 'client_policy_id', 'frequency', 'reminder_days', + 'email_subject', + 'email_body', 'is_enabled', 'is_active', 'created_by', @@ -209,9 +211,90 @@ class ReminderMailConfigModel extends Model $config['working_day_labels'] = $this->formatWorkingDayLabels($config['reminder_days'] ?? null); } + $config['has_custom_template'] = $this->hasCustomTemplate($config); + return $config; } + public function hasCustomTemplate(?array $config): bool + { + if (empty($config)) { + return false; + } + + return trim((string) ($config['email_subject'] ?? '')) !== '' + && trim((string) ($config['email_body'] ?? '')) !== ''; + } + + /** + * Apply reminder_mail_config template to notification data when configured. + */ + public function resolveNotificationTemplate(?array $config, ?array $notificationData): ?array + { + if (!$this->hasCustomTemplate($config)) { + return $notificationData; + } + + $notificationData = is_array($notificationData) ? $notificationData : []; + $notificationData['subject'] = $config['email_subject']; + $notificationData['mail_content'] = $config['email_body']; + $notificationData['enabled'] = 1; + + return $notificationData; + } + + public function canSendReminderMail(?array $config, ?array $notificationData): bool + { + if ($this->hasCustomTemplate($config)) { + return true; + } + + return !empty($notificationData) + && (int) ($notificationData['enabled'] ?? 0) === 1 + && trim((string) ($notificationData['mail_content'] ?? '')) !== ''; + } + + public function saveEmailTemplate( + int $clientPolicyId, + ?string $emailSubject, + ?string $emailBody, + ?int $hrId = null + ): void { + if ($emailSubject === null && $emailBody === null) { + return; + } + + $existing = $this->getByClientPolicyId($clientPolicyId); + + if ($existing) { + $payload = ['updated_by' => $hrId]; + + if ($emailSubject !== null) { + $payload['email_subject'] = $emailSubject; + } + + if ($emailBody !== null) { + $payload['email_body'] = $emailBody; + } + + $this->update((int) $existing['id'], $payload); + + return; + } + + $this->insert([ + 'client_policy_id' => $clientPolicyId, + 'frequency' => self::FREQUENCY_CUSTOM, + 'reminder_days' => null, + 'email_subject' => $emailSubject, + 'email_body' => $emailBody, + 'is_enabled' => 1, + 'is_active' => 1, + 'created_by' => $hrId, + 'updated_by' => $hrId, + ]); + } + public function normalizeReminderDays(string $frequency, ?string $reminderDays): ?string { if ($frequency === self::FREQUENCY_DAILY) { @@ -313,6 +396,14 @@ class ReminderMailConfigModel extends Model 'is_active' => 1, ]; + if (array_key_exists('email_subject', $options)) { + $payload['email_subject'] = $options['email_subject']; + } + + if (array_key_exists('email_body', $options)) { + $payload['email_body'] = $options['email_body']; + } + if ($id) { $existing = $this->where('id', $id)->where('is_active', 1)->first(); diff --git a/tests/_support/Traits/ReminderMailControllerTestTrait.php b/tests/_support/Traits/ReminderMailControllerTestTrait.php new file mode 100644 index 0000000..2583265 --- /dev/null +++ b/tests/_support/Traits/ReminderMailControllerTestTrait.php @@ -0,0 +1,120 @@ +result = $result; + } + + public function where(...$args) + { + return $this; + } + + public function first(): ?array + { + return $this->result; + } + }; + } + + private function buildGetController( + array $getParams, + $clientPolicyModel, + $reminderMailConfigModel, + ?object $notificationModel = null + ): EmployeeRestController { + $request = $this->createMock(IncomingRequest::class); + $request->method('getGet') + ->willReturnCallback(function ($key = null) use ($getParams) { + if ($key === null) { + return $getParams; + } + + return array_key_exists($key, $getParams) ? $getParams[$key] : null; + }); + + return $this->injectReminderMailControllerDependencies( + $request, + $clientPolicyModel, + $reminderMailConfigModel, + $notificationModel + ); + } + + private function buildJsonPostController( + array $jsonData, + $clientPolicyModel, + $reminderMailConfigModel, + bool $isPost = true, + ?object $notificationModel = null, + ?EmployeeRestController $controller = null + ): EmployeeRestController { + $request = $this->createMock(IncomingRequest::class); + $request->method('is') + ->with('post') + ->willReturn($isPost); + $request->method('getJSON') + ->with(true) + ->willReturn($jsonData); + $request->method('getVar') + ->willReturn(null); + + $controller = $controller ?? new EmployeeRestController(); + + return $this->injectReminderMailControllerDependencies( + $request, + $clientPolicyModel, + $reminderMailConfigModel, + $notificationModel, + $controller + ); + } + + private function injectReminderMailControllerDependencies( + IncomingRequest $request, + $clientPolicyModel, + $reminderMailConfigModel, + ?object $notificationModel = null, + ?EmployeeRestController $controller = null + ): EmployeeRestController { + $controller = $controller ?? new EmployeeRestController(); + $controller->initController($request, Services::response(), Services::logger()); + + $reflection = new ReflectionClass($controller); + + foreach ([ + 'clientPolicyModel' => $clientPolicyModel, + 'reminderMailConfigModel' => $reminderMailConfigModel, + 'notificationModel' => $notificationModel, + ] as $property => $value) { + if ($value === null) { + continue; + } + + $prop = $reflection->getProperty($property); + $prop->setAccessible(true); + $prop->setValue($controller, $value); + } + + return $controller; + } + + private function decodeResponse($response): array + { + return json_decode($response->getBody(), true); + } +} diff --git a/tests/unit/GetReminderMailConfigTest.php b/tests/unit/GetReminderMailConfigTest.php new file mode 100644 index 0000000..e86e8b2 --- /dev/null +++ b/tests/unit/GetReminderMailConfigTest.php @@ -0,0 +1,317 @@ +result = $result; + } + + public function where(...$args) + { + return $this; + } + + public function first(): ?array + { + return $this->result; + } + }; + } + + private function buildController( + array $getParams, + $clientPolicyModel, + $reminderMailConfigModel, + ?object $notificationModel = null + ): EmployeeRestController { + $controller = new EmployeeRestController(); + + $request = $this->createMock(IncomingRequest::class); + $request->method('getGet') + ->willReturnCallback(function ($key = null) use ($getParams) { + if ($key === null) { + return $getParams; + } + + return array_key_exists($key, $getParams) ? $getParams[$key] : null; + }); + + $controller->initController($request, Services::response(), Services::logger()); + + $reflection = new ReflectionClass($controller); + + foreach ([ + 'clientPolicyModel' => $clientPolicyModel, + 'reminderMailConfigModel' => $reminderMailConfigModel, + 'notificationModel' => $notificationModel, + ] as $property => $value) { + if ($value === null) { + continue; + } + + $prop = $reflection->getProperty($property); + $prop->setAccessible(true); + $prop->setValue($controller, $value); + } + + return $controller; + } + + private function decodeResponse($response): array + { + return json_decode($response->getBody(), true); + } + + private function mockReminderMailConfigModel(array $methods): ReminderMailConfigModel + { + return $this->getMockBuilder(ReminderMailConfigModel::class) + ->onlyMethods(array_keys($methods)) + ->getMock(); + } + + public function testMissingClientPolicyIdReturns400() + { + $clientPolicyModel = $this->createMock(ClientPolicyModel::class); + $reminderMailConfigModel = $this->mockReminderMailConfigModel([]); + + $controller = $this->buildController([], $clientPolicyModel, $reminderMailConfigModel); + $body = $this->decodeResponse($controller->getReminderMailConfig()); + + $this->assertFalse($body['status']); + $this->assertSame(400, $body['code']); + $this->assertSame('client_policy_id is required', $body['message']); + } + + public function testClientPolicyNotFoundReturns404() + { + $clientPolicyModel = $this->createMock(ClientPolicyModel::class); + $clientPolicyModel->method('find')->with(999)->willReturn(null); + + $reminderMailConfigModel = $this->mockReminderMailConfigModel([]); + + $controller = $this->buildController( + ['client_policy_id' => '999'], + $clientPolicyModel, + $reminderMailConfigModel + ); + $body = $this->decodeResponse($controller->getReminderMailConfig()); + + $this->assertFalse($body['status']); + $this->assertSame(404, $body['code']); + $this->assertSame('Client policy not found', $body['message']); + } + + public function testExistingConfigReturnsEnrichedDataAndWorkingDayOptions() + { + $config = [ + 'id' => 10, + 'client_policy_id' => 123, + 'frequency' => ReminderMailConfigModel::FREQUENCY_WORKING_DAYS, + 'reminder_days' => '1,3,5', + 'email_subject' => 'Reminder', + 'email_body' => '
Please enroll
', + 'is_enabled' => 1, + 'is_active' => 1, + ]; + + $clientPolicyModel = $this->createMock(ClientPolicyModel::class); + $clientPolicyModel->method('find')->with(123)->willReturn([ + 'id' => 123, + 'client_id' => 42, + ]); + + $reminderMailConfigModel = $this->mockReminderMailConfigModel([ + 'getByClientPolicyId' => null, + ]); + $reminderMailConfigModel->method('getByClientPolicyId')->with(123)->willReturn($config); + + $controller = $this->buildController( + ['client_policy_id' => '123'], + $clientPolicyModel, + $reminderMailConfigModel + ); + $body = $this->decodeResponse($controller->getReminderMailConfig()); + + $this->assertTrue($body['status']); + $this->assertSame(200, $body['code']); + $this->assertSame(123, $body['data']['client_policy_id']); + $this->assertSame(['Mon', 'Wed', 'Fri'], $body['data']['working_day_labels']); + $this->assertTrue($body['data']['has_custom_template']); + $this->assertCount(7, $body['working_day_options']); + $this->assertSame('Mon', $body['working_day_options'][0]['label']); + } + + public function testLegacyFallbackWhenNoConfigButPolicyHasReminderDate() + { + $clientPolicyModel = $this->createMock(ClientPolicyModel::class); + $clientPolicyModel->method('find')->with(123)->willReturn([ + 'id' => 123, + 'client_id' => 42, + 'reminder_date' => '1,15,28', + ]); + + $reminderMailConfigModel = $this->mockReminderMailConfigModel([ + 'getByClientPolicyId' => null, + ]); + $reminderMailConfigModel->method('getByClientPolicyId')->with(123)->willReturn(null); + + $controller = $this->buildController( + ['client_policy_id' => '123'], + $clientPolicyModel, + $reminderMailConfigModel + ); + $body = $this->decodeResponse($controller->getReminderMailConfig()); + + $this->assertTrue($body['status']); + $this->assertSame(200, $body['code']); + $this->assertSame(123, $body['data']['client_policy_id']); + $this->assertSame(ReminderMailConfigModel::FREQUENCY_CUSTOM, $body['data']['frequency']); + $this->assertSame('1,15,28', $body['data']['reminder_days']); + $this->assertSame('legacy_client_policy', $body['data']['source']); + $this->assertArrayHasKey('working_day_options', $body); + } + + public function testNoConfigAndNoLegacyReminderDateReturnsNullData() + { + $clientPolicyModel = $this->createMock(ClientPolicyModel::class); + $clientPolicyModel->method('find')->with(123)->willReturn([ + 'id' => 123, + 'client_id' => 42, + ]); + + $reminderMailConfigModel = $this->mockReminderMailConfigModel([ + 'getByClientPolicyId' => null, + ]); + $reminderMailConfigModel->method('getByClientPolicyId')->with(123)->willReturn(null); + + $controller = $this->buildController( + ['client_policy_id' => '123'], + $clientPolicyModel, + $reminderMailConfigModel + ); + $body = $this->decodeResponse($controller->getReminderMailConfig()); + + $this->assertTrue($body['status']); + $this->assertSame(200, $body['code']); + $this->assertNull($body['data']); + $this->assertCount(7, $body['working_day_options']); + } + + public function testEmailTemplateModeReturnsConfigTemplate() + { + $config = [ + 'client_policy_id' => 123, + 'email_subject' => 'Custom subject', + 'email_body' => 'Custom body
', + ]; + + $clientPolicyModel = $this->createMock(ClientPolicyModel::class); + $clientPolicyModel->method('find')->with(123)->willReturn([ + 'id' => 123, + 'client_id' => 42, + ]); + + $reminderMailConfigModel = $this->mockReminderMailConfigModel([ + 'getByClientPolicyId' => null, + ]); + $reminderMailConfigModel->method('getByClientPolicyId')->with(123)->willReturn($config); + + $controller = $this->buildController( + [ + 'client_policy_id' => '123', + 'is_email_template' => '1', + ], + $clientPolicyModel, + $reminderMailConfigModel + ); + $body = $this->decodeResponse($controller->getReminderMailConfig()); + + $this->assertTrue($body['status']); + $this->assertSame(200, $body['code']); + $this->assertSame('Custom subject', $body['data']['email_subject']); + $this->assertSame('Custom body
', $body['data']['email_body']); + $this->assertArrayNotHasKey('working_day_options', $body); + } + + public function testEmailTemplateModeFallsBackToNotificationWhenNoConfig() + { + $clientPolicyModel = $this->createMock(ClientPolicyModel::class); + $clientPolicyModel->method('find')->with(123)->willReturn([ + 'id' => 123, + 'client_id' => 42, + ]); + + $reminderMailConfigModel = $this->mockReminderMailConfigModel([ + 'getByClientPolicyId' => null, + ]); + $reminderMailConfigModel->method('getByClientPolicyId')->with(123)->willReturn(null); + + $notificationModel = $this->createNotificationModelStub([ + 'subject' => 'Default subject', + 'mail_content' => 'Default body
', + ]); + + $controller = $this->buildController( + [ + 'client_policy_id' => '123', + 'is_email_template' => '1', + ], + $clientPolicyModel, + $reminderMailConfigModel, + $notificationModel + ); + $body = $this->decodeResponse($controller->getReminderMailConfig()); + + $this->assertTrue($body['status']); + $this->assertSame(200, $body['code']); + $this->assertSame('Default subject', $body['data']['email_subject']); + $this->assertSame('Default body
', $body['data']['email_body']); + } + + public function testEmailTemplateModeReturns404WhenNotificationMissing() + { + $clientPolicyModel = $this->createMock(ClientPolicyModel::class); + $clientPolicyModel->method('find')->with(123)->willReturn([ + 'id' => 123, + 'client_id' => 42, + ]); + + $reminderMailConfigModel = $this->mockReminderMailConfigModel([ + 'getByClientPolicyId' => null, + ]); + $reminderMailConfigModel->method('getByClientPolicyId')->with(123)->willReturn(null); + + $notificationModel = $this->createNotificationModelStub(null); + + $controller = $this->buildController( + [ + 'client_policy_id' => '123', + 'is_email_template' => '1', + ], + $clientPolicyModel, + $reminderMailConfigModel, + $notificationModel + ); + $body = $this->decodeResponse($controller->getReminderMailConfig()); + + $this->assertFalse($body['status']); + $this->assertSame(404, $body['code']); + $this->assertSame('Member reminder mail notification not found', $body['message']); + } +} diff --git a/tests/unit/ReminderMailConfigModelTest.php b/tests/unit/ReminderMailConfigModelTest.php new file mode 100644 index 0000000..47bc728 --- /dev/null +++ b/tests/unit/ReminderMailConfigModelTest.php @@ -0,0 +1,161 @@ +model = new ReminderMailConfigModel(); + } + + public function testHasCustomTemplateRequiresBothSubjectAndBody() + { + $this->assertFalse($this->model->hasCustomTemplate(null)); + $this->assertFalse($this->model->hasCustomTemplate([])); + $this->assertFalse($this->model->hasCustomTemplate([ + 'email_subject' => 'Reminder', + 'email_body' => '', + ])); + $this->assertFalse($this->model->hasCustomTemplate([ + 'email_subject' => ' ', + 'email_body' => 'Body
', + ])); + $this->assertTrue($this->model->hasCustomTemplate([ + 'email_subject' => 'Reminder', + 'email_body' => 'Body
', + ])); + } + + public function testResolveNotificationTemplateUsesCustomTemplateWhenConfigured() + { + $config = [ + 'email_subject' => 'Custom subject', + 'email_body' => 'Custom body
', + ]; + + $notification = [ + 'id' => 5, + 'enabled' => 0, + 'subject' => 'Default subject', + 'mail_content' => 'Default body
', + ]; + + $resolved = $this->model->resolveNotificationTemplate($config, $notification); + + $this->assertSame('Custom subject', $resolved['subject']); + $this->assertSame('Custom body
', $resolved['mail_content']); + $this->assertSame(1, $resolved['enabled']); + $this->assertSame(5, $resolved['id']); + } + + public function testResolveNotificationTemplateReturnsDefaultWhenCustomTemplateMissing() + { + $notification = [ + 'subject' => 'Default subject', + 'mail_content' => 'Default body
', + ]; + + $resolved = $this->model->resolveNotificationTemplate( + ['email_subject' => 'Only subject'], + $notification + ); + + $this->assertSame($notification, $resolved); + } + + public function testCanSendReminderMailAllowsCustomTemplateEvenWhenNotificationDisabled() + { + $config = [ + 'email_subject' => 'Custom subject', + 'email_body' => 'Custom body
', + ]; + + $notification = [ + 'enabled' => 0, + 'mail_content' => '', + ]; + + $this->assertTrue($this->model->canSendReminderMail($config, $notification)); + } + + public function testCanSendReminderMailRequiresEnabledDefaultNotification() + { + $this->assertFalse($this->model->canSendReminderMail(null, null)); + $this->assertFalse($this->model->canSendReminderMail(null, [ + 'enabled' => 0, + 'mail_content' => 'Body
', + ])); + $this->assertFalse($this->model->canSendReminderMail(null, [ + 'enabled' => 1, + 'mail_content' => '', + ])); + $this->assertTrue($this->model->canSendReminderMail(null, [ + 'enabled' => 1, + 'mail_content' => 'Body
', + ])); + } + + public function testValidateConfigRejectsInvalidFrequency() + { + $error = $this->model->validateConfig('hourly', '1'); + + $this->assertSame( + 'Invalid frequency. Allowed values: daily, weekly, monthly, custom, working_days.', + $error + ); + } + + public function testValidateConfigDailyDoesNotRequireReminderDays() + { + $this->assertNull($this->model->validateConfig( + ReminderMailConfigModel::FREQUENCY_DAILY, + null + )); + } + + public function testValidateConfigWorkingDaysAcceptsDayNames() + { + $this->assertNull($this->model->validateConfig( + ReminderMailConfigModel::FREQUENCY_WORKING_DAYS, + 'Mon,Wed,Fri' + )); + } + + public function testValidateConfigWeeklyRejectsInvalidDay() + { + $error = $this->model->validateConfig( + ReminderMailConfigModel::FREQUENCY_WEEKLY, + '0,8' + ); + + $this->assertSame( + 'Weekly reminder_days must be between 0 (Sunday) and 6 (Saturday).', + $error + ); + } + + public function testEnrichConfigAddsCustomTemplateFlagAndWorkingDayLabels() + { + $config = $this->model->enrichConfig([ + 'frequency' => ReminderMailConfigModel::FREQUENCY_WORKING_DAYS, + 'reminder_days' => '1,3,5', + 'email_subject' => 'Reminder', + 'email_body' => 'Please enroll
', + ]); + + $this->assertTrue($config['has_custom_template']); + $this->assertSame(['Mon', 'Wed', 'Fri'], $config['working_day_labels']); + } + + public function testNormalizeWorkingDaysSortsAndDeduplicates() + { + $this->assertSame('1,3,5', $this->model->normalizeWorkingDays('Fri,Mon,Wed,Mon')); + } +} diff --git a/tests/unit/SaveReminderMailConfigTest.php b/tests/unit/SaveReminderMailConfigTest.php new file mode 100644 index 0000000..abac9a8 --- /dev/null +++ b/tests/unit/SaveReminderMailConfigTest.php @@ -0,0 +1,173 @@ +getMockBuilder(ReminderMailConfigModel::class) + ->onlyMethods(array_keys($methods)) + ->getMock(); + } + + public function testMissingClientPolicyIdReturns400() + { + $controller = $this->buildJsonPostController( + ['frequency' => 'daily'], + $this->createMock(ClientPolicyModel::class), + $this->mockReminderMailConfigModel([]) + ); + + $body = $this->decodeResponse($controller->saveReminderMailConfig()); + + $this->assertFalse($body['status']); + $this->assertSame(400, $body['code']); + $this->assertSame('client_policy_id is required', $body['message']); + } + + public function testMissingFrequencyReturns400() + { + $clientPolicyModel = $this->createMock(ClientPolicyModel::class); + + $controller = $this->buildJsonPostController( + ['client_policy_id' => 123], + $clientPolicyModel, + $this->mockReminderMailConfigModel([]) + ); + + $body = $this->decodeResponse($controller->saveReminderMailConfig()); + + $this->assertFalse($body['status']); + $this->assertSame(400, $body['code']); + $this->assertSame('frequency is required', $body['message']); + } + + public function testClientPolicyNotFoundReturns404() + { + $clientPolicyModel = $this->createMock(ClientPolicyModel::class); + $clientPolicyModel->method('find')->with(123)->willReturn(null); + + $controller = $this->buildJsonPostController( + [ + 'client_policy_id' => 123, + 'frequency' => 'daily', + ], + $clientPolicyModel, + $this->mockReminderMailConfigModel([]) + ); + + $body = $this->decodeResponse($controller->saveReminderMailConfig()); + + $this->assertFalse($body['status']); + $this->assertSame(404, $body['code']); + $this->assertSame('Client policy not found', $body['message']); + } + + public function testSaveConfigValidationErrorReturns400() + { + $clientPolicyModel = $this->createMock(ClientPolicyModel::class); + $clientPolicyModel->method('find')->with(123)->willReturn(['id' => 123]); + + $reminderMailConfigModel = $this->mockReminderMailConfigModel(['saveConfig' => null]); + $reminderMailConfigModel->expects($this->once()) + ->method('saveConfig') + ->with( + 123, + 'weekly', + '9', + 1, + $this->callback(function (array $options): bool { + return !array_key_exists('email_subject', $options) + && !array_key_exists('email_body', $options); + }) + ) + ->willReturn([ + 'status' => false, + 'message' => 'Weekly reminder_days must be between 0 (Sunday) and 6 (Saturday).', + ]); + + $controller = $this->buildJsonPostController( + [ + 'client_policy_id' => 123, + 'frequency' => 'weekly', + 'reminder_days' => '9', + ], + $clientPolicyModel, + $reminderMailConfigModel + ); + + $body = $this->decodeResponse($controller->saveReminderMailConfig()); + + $this->assertFalse($body['status']); + $this->assertSame(400, $body['code']); + $this->assertSame( + 'Weekly reminder_days must be between 0 (Sunday) and 6 (Saturday).', + $body['message'] + ); + } + + public function testSuccessfulSaveIncludesEmailTemplateFields() + { + $savedConfig = [ + 'id' => 10, + 'client_policy_id' => 123, + 'frequency' => ReminderMailConfigModel::FREQUENCY_DAILY, + 'email_subject' => 'Reminder subject', + 'email_body' => 'Reminder body
', + 'is_enabled' => 1, + ]; + + $clientPolicyModel = $this->createMock(ClientPolicyModel::class); + $clientPolicyModel->method('find')->with(123)->willReturn(['id' => 123]); + + $reminderMailConfigModel = $this->mockReminderMailConfigModel(['saveConfig' => null]); + $reminderMailConfigModel->expects($this->once()) + ->method('saveConfig') + ->with( + 123, + 'daily', + null, + 1, + $this->callback(function (array $options): bool { + return ($options['email_subject'] ?? null) === 'Reminder subject' + && ($options['email_body'] ?? null) === 'Reminder body
' + && ($options['hr_id'] ?? null) === 45; + }) + ) + ->willReturn([ + 'status' => true, + 'action' => 'updated', + 'data' => $savedConfig, + ]); + + $controller = $this->buildJsonPostController( + [ + 'client_policy_id' => 123, + 'frequency' => 'daily', + 'email_subject' => 'Reminder subject', + 'email_body' => 'Reminder body
', + 'hr_id' => 45, + ], + $clientPolicyModel, + $reminderMailConfigModel + ); + + $body = $this->decodeResponse($controller->saveReminderMailConfig()); + + $this->assertTrue($body['status']); + $this->assertSame(200, $body['code']); + $this->assertSame('updated', $body['action']); + $this->assertSame('Reminder mail configuration saved successfully', $body['message']); + $this->assertSame('Reminder subject', $body['data']['email_subject']); + $this->assertSame('Reminder body
', $body['data']['email_body']); + } +} diff --git a/tests/unit/SendReminderMailTest.php b/tests/unit/SendReminderMailTest.php new file mode 100644 index 0000000..3e95071 --- /dev/null +++ b/tests/unit/SendReminderMailTest.php @@ -0,0 +1,181 @@ +getMockBuilder(ReminderMailConfigModel::class) + ->onlyMethods(array_keys($methods)) + ->getMock(); + } + + public function testNonPostRequestReturns405() + { + $controller = $this->buildJsonPostController( + ['client_policy_id' => 123], + $this->createMock(ClientPolicyModel::class), + $this->mockReminderMailConfigModel([]), + false + ); + + $body = $this->decodeResponse($controller->sendReminderMail()); + + $this->assertFalse($body['status']); + $this->assertSame(405, $body['code']); + $this->assertSame('Only POST method is allowed', $body['message']); + } + + public function testEmptyJsonBodyReturns400() + { + $controller = $this->buildJsonPostController( + [], + $this->createMock(ClientPolicyModel::class), + $this->mockReminderMailConfigModel([]) + ); + + $body = $this->decodeResponse($controller->sendReminderMail()); + + $this->assertFalse($body['status']); + $this->assertSame(400, $body['code']); + $this->assertSame('JSON request body is required', $body['message']); + } + + public function testMissingClientPolicyIdReturns400() + { + $controller = $this->buildJsonPostController( + ['email_subject' => 'Reminder'], + $this->createMock(ClientPolicyModel::class), + $this->mockReminderMailConfigModel([]) + ); + + $body = $this->decodeResponse($controller->sendReminderMail()); + + $this->assertFalse($body['status']); + $this->assertSame(400, $body['code']); + $this->assertSame('client_policy_id is required', $body['message']); + } + + public function testClientPolicyNotFoundReturns404() + { + $clientPolicyModel = $this->createMock(ClientPolicyModel::class); + $clientPolicyModel->method('find')->with(123)->willReturn(null); + + $controller = $this->buildJsonPostController( + ['client_policy_id' => 123], + $clientPolicyModel, + $this->mockReminderMailConfigModel([]) + ); + + $body = $this->decodeResponse($controller->sendReminderMail()); + + $this->assertFalse($body['status']); + $this->assertSame(404, $body['code']); + $this->assertSame('Client policy not found', $body['message']); + } + + public function testSavesEmailTemplateBeforeSendingMail() + { + $clientPolicyModel = $this->createMock(ClientPolicyModel::class); + $clientPolicyModel->method('find')->with(123)->willReturn([ + 'id' => 123, + 'client_id' => 42, + 'client_branch_id' => 7, + ]); + + $reminderMailConfigModel = $this->mockReminderMailConfigModel(['saveEmailTemplate' => null]); + $reminderMailConfigModel->expects($this->once()) + ->method('saveEmailTemplate') + ->with( + 123, + 'Enrollment reminder', + 'Hi [[member_name]]
', + 45 + ); + + $controller = $this->getMockBuilder(EmployeeRestController::class) + ->onlyMethods(['dispatchManualReminder']) + ->getMock(); + $controller->expects($this->once()) + ->method('dispatchManualReminder') + ->with(42, 7, 123) + ->willReturn( + Services::response()->setJSON([ + 'status' => true, + 'code' => 200, + 'message' => 'Mail sent successfully', + ])->setStatusCode(200) + ); + + $controller = $this->buildJsonPostController( + [ + 'client_policy_id' => 123, + 'email_subject' => 'Enrollment reminder', + 'email_body' => 'Hi [[member_name]]
', + 'hr_id' => 45, + ], + $clientPolicyModel, + $reminderMailConfigModel, + true, + null, + $controller + ); + + $body = $this->decodeResponse($controller->sendReminderMail()); + + $this->assertTrue($body['status']); + $this->assertSame(200, $body['code']); + $this->assertSame('Mail sent successfully', $body['message']); + } + + public function testSkipsTemplateSaveWhenEmailFieldsNotProvided() + { + $clientPolicyModel = $this->createMock(ClientPolicyModel::class); + $clientPolicyModel->method('find')->with(123)->willReturn([ + 'id' => 123, + 'client_id' => 42, + 'client_branch_id' => 7, + ]); + + $reminderMailConfigModel = $this->mockReminderMailConfigModel(['saveEmailTemplate' => null]); + $reminderMailConfigModel->expects($this->never())->method('saveEmailTemplate'); + + $controller = $this->getMockBuilder(EmployeeRestController::class) + ->onlyMethods(['dispatchManualReminder']) + ->getMock(); + $controller->expects($this->once()) + ->method('dispatchManualReminder') + ->with(42, 7, 123) + ->willReturn( + Services::response()->setJSON([ + 'status' => false, + 'code' => 200, + 'message' => 'There is no data to send', + ])->setStatusCode(200) + ); + + $controller = $this->buildJsonPostController( + ['client_policy_id' => 123], + $clientPolicyModel, + $reminderMailConfigModel, + true, + null, + $controller + ); + + $body = $this->decodeResponse($controller->sendReminderMail()); + + $this->assertFalse($body['status']); + $this->assertSame('There is no data to send', $body['message']); + } +} diff --git a/writable/venbaehn_nhance b/writable/venbaehn_nhance new file mode 100644 index 0000000..e69de29