diff --git a/app/Config/Acl.php b/app/Config/Acl.php index a491d3e..1eeed0a 100644 --- a/app/Config/Acl.php +++ b/app/Config/Acl.php @@ -22,6 +22,10 @@ class Acl '#^/claim-form-download#' => ['public' => true], '#^/claims-feedback-form#' => ['public' => true], '#^/autobookstackLogin#' => ['public' => true], + '#^/sendCroneRemainderMail#' => ['public' => true], + '#^/sendEnrollmentClosedHrMail#' => ['public' => true], + '#^/testSendEnrollmentClosedHrMail#' => ['public' => true], + '#^/download_file#' => ['public' => true], // ===================== DASHBOARD ===================== '#^/dashboard#' => [ diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 192d43b..569a22f 100755 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -32,7 +32,10 @@ $routes->get("testMailAttachments", "ClientController::testMailAttachments"); $routes->get("updatePolicyTermsKey", "ClientController::updatePolicyTermsKey"); $routes->get("updateRemainderDate", "ClientController::updateRemainderDate"); $routes->get("sendCroneRemainderMail", "DashboardController::sendCroneRemainderMail"); +$routes->get("sendEnrollmentClosedHrMail", "EmployeeController::sendEnrollmentClosedHrMail"); +$routes->get("testSendEnrollmentClosedHrMail", "EmployeeController::testSendEnrollmentClosedHrMail"); $routes->get("sendextraparam", "ClientController::sendextraparam"); +$routes->get('download_file','EmployeeController::download_file'); // $routes->post("iAgreeForAddOn", "EmployeeRestController::iAgreeForAddOn"); // $routes->get("sendCroneRemainderMail", "DashboardController::sendCroneRemainderMail"); // $routes->post("employeeUpload", "EmployeeRestController::employeeUpload"); @@ -512,7 +515,7 @@ $routes->group("employeeRest", ['filter' => [ 'GlobalPostFileUploadGuard', 'appS $routes->get("getReminderMailConfig", "EmployeeRestController::getReminderMailConfig"); $routes->post("saveReminderMailConfig", "EmployeeRestController::saveReminderMailConfig"); - + $routes->post('download_inception','EmployeeController::download_inception'); }); $routes->group("employeeRest", ['filter' => ['appSignature','AuthApiRateLimitFilter'] ], function ($routes) { @@ -563,6 +566,9 @@ $routes->get("getBackToEnrolledDetails", "EmployeeRestController::getBackToEnrol $routes->cli('cli/reset-token-timeout', 'RestAuthenticationController::resetTokenTimeOut'); $routes->cli('cli/enrollOpendAndClose', 'DashboardController::updatePolicyEnrollmentStatus'); $routes->cli("cli/sendCroneRemainderMail", "DashboardController::sendCroneRemainderMail"); +$routes->cli("cli/sendEnrollmentClosedHrMail", "EmployeeController::sendEnrollmentClosedHrMail"); +$routes->cli('cli/testSendEnrollmentClosedHrMail/(:num)/(:segment)', 'EmployeeController::testSendEnrollmentClosedHrMail/$1/$2'); +$routes->cli('cli/testSendEnrollmentClosedHrMail/(:num)', 'EmployeeController::testSendEnrollmentClosedHrMail/$1'); $routes->cli('cli/update-emp-policy-status', 'ClientController::updateEmpAndPolicyStatus'); $routes->get('enrollOpendAndClose', 'DashboardController::updatePolicyEnrollmentStatus'); $routes->get("sendCroneRemainderMail", "DashboardController::sendCroneRemainderMail"); diff --git a/app/Controllers/EmployeeController.php b/app/Controllers/EmployeeController.php index 9b729ea..9546caa 100755 --- a/app/Controllers/EmployeeController.php +++ b/app/Controllers/EmployeeController.php @@ -37,6 +37,8 @@ use App\Controllers\Jobs\SubJob; use App\Controllers\EmployeeServiceController; use App\Controllers\EmpDataServiceController; +use App\Helpers\MailHelper; + use CodeIgniter\API\ResponseTrait; use PhpOffice\PhpSpreadsheet\Spreadsheet; @@ -824,7 +826,7 @@ class EmployeeController extends AdminController session()->setFlashdata('success', 'File Uploaded Successfully File is being validated.'); return redirect()->to(base_url('employee/upload')); } else { - session()->setFlashdata($return['status'], $return['message']); + session()->setFlashdata($return['status'] ?? 'error', $return['message'] ?? 'File upload failed.'); return redirect()->to(base_url('employee/upload')); } } else if ($event_type == 'correction') { @@ -2938,104 +2940,526 @@ class EmployeeController extends AdminController public function download_inception() { - $client = $this->request->getPost('client'); - $branch = $this->request->getPost('branch'); - $policies = $this->request->getPost('policies'); - $status = $this->request->getPost('status'); - $empCode = $this->request->getPost('empCode'); - $empName = $this->request->getPost('empName'); + $contentType = strtolower($this->request->getHeaderLine('Content-Type')); - $result = $this->employeePolicyModel->download_inception( - $client, $branch, $policies, $status, $empCode, $empName - ); - - if (empty($result)) { - return $this->response->setStatusCode(204)->setBody('No data found'); + if (str_contains($contentType, 'application/json')) { + try { + $payload = $this->request->getJSON(true); + } catch (\CodeIgniter\HTTP\Exceptions\HTTPException $e) { + log_message('error', 'download_inception: Invalid JSON payload - ' . $e->getMessage()); + return $this->respond([ + 'status' => false, + 'code' => 400, + 'message' => 'Invalid JSON request body.', + 'data' => [], + ], 200); + } + $payload = is_array($payload) ? $payload : []; + } else { + $payload = $this->request->getPost() ?? []; } - $formatted = []; - foreach ($result as $index => $row) { - $rowWithSerial = ['S.NO' => $index + 1] + $row; - $formatted[] = $rowWithSerial; - } - $result = $formatted; + $client = $payload['client'] ?? null; + $branch = $payload['branch'] ?? null; + $policies = $payload['policies'] ?? null; + $status = $payload['status'] ?? null; + $empCode = $payload['empCode'] ?? null; + $empName = $payload['empName'] ?? null; - $spreadsheet = new Spreadsheet(); - $sheet = $spreadsheet->getActiveSheet(); + log_message('error', 'download_inception: Request received - ' . json_encode([ + 'client' => $client, + 'branch' => $branch, + 'policies' => $policies, + 'status' => $status, + 'empCode' => $empCode, + 'empName' => $empName, + ])); - $headers = array_keys($result[0]); - $columnWidth = 20; // standard column width + try { + $result = $this->employeePolicyModel->download_inception( + $client, $branch, $policies, $status, $empCode, $empName + ); - $colIndex = 1; - foreach ($headers as $header) { - $columnLetter = Coordinate::stringFromColumnIndex($colIndex); - $cellCoordinate = $columnLetter . '1'; + if (!is_array($result)) { + log_message('error', 'download_inception: Unexpected result type from model'); + return $this->respond([ + 'status' => false, + 'code' => 500, + 'message' => 'Failed to fetch inception data.', + 'data' => [], + ], 200); + } - $sheet->setCellValue($cellCoordinate, ucfirst(str_replace('_', ' ', $header))); + if (empty($result)) { + log_message('error', 'download_inception: No data found for given filters'); + return $this->respond([ + 'status' => false, + 'code' => 404, + 'message' => 'No data found for given filters.', + 'data' => [], + ], 200); + } - $sheet->getColumnDimension($columnLetter)->setWidth($columnWidth); + $formatted = []; + foreach ($result as $index => $row) { + $rowWithSerial = ['S.NO' => $index + 1] + $row; + $formatted[] = $rowWithSerial; + } + $result = $formatted; - $style = $sheet->getStyle($cellCoordinate); - $style->getFont()->setBold(true); - $style->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER); - $style->getBorders()->getAllBorders()->setBorderStyle(Border::BORDER_THIN); + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); - $colIndex++; - } + $headers = array_keys($result[0]); + $columnWidth = 20; // standard column width - $rowNum = 2; - foreach ($result as $row) { $colIndex = 1; - foreach ($row as $cell) { + foreach ($headers as $header) { $columnLetter = Coordinate::stringFromColumnIndex($colIndex); - $cellCoordinate = $columnLetter . $rowNum; - $sheet->setCellValue($cellCoordinate, $cell); + $cellCoordinate = $columnLetter . '1'; + + $sheet->setCellValue($cellCoordinate, ucfirst(str_replace('_', ' ', $header))); + + $sheet->getColumnDimension($columnLetter)->setWidth($columnWidth); + + $style = $sheet->getStyle($cellCoordinate); + $style->getFont()->setBold(true); + $style->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER); + $style->getBorders()->getAllBorders()->setBorderStyle(Border::BORDER_THIN); + $colIndex++; } - $rowNum++; + + $rowNum = 2; + foreach ($result as $row) { + $colIndex = 1; + foreach ($row as $cell) { + $columnLetter = Coordinate::stringFromColumnIndex($colIndex); + $cellCoordinate = $columnLetter . $rowNum; + $sheet->setCellValue($cellCoordinate, $cell); + $colIndex++; + } + $rowNum++; + } + + $exportDir = WRITEPATH . 'exports'; + if (!is_dir($exportDir) && !mkdir($exportDir, 0777, true) && !is_dir($exportDir)) { + log_message('error', 'download_inception: Failed to create export directory - ' . $exportDir); + return $this->respond([ + 'status' => false, + 'code' => 500, + 'message' => 'Failed to prepare export directory.', + 'data' => [], + ], 200); + } + + $filename = 'inception_export_' . date('Ymd_His') . '.xlsx'; + $filepath = $exportDir . '/' . $filename; + + $writer = new Xlsx($spreadsheet); + $writer->save($filepath); + + if (!file_exists($filepath)) { + log_message('error', 'download_inception: Export file was not created - ' . $filepath); + return $this->respond([ + 'status' => false, + 'code' => 500, + 'message' => 'Failed to generate export file.', + 'data' => [], + ], 200); + } + + $downloadUrl = base_url('download_file?file=' . urlencode($filename)); + + log_message('error', 'download_inception: Export successful. Rows: ' . count($result) . ', File: ' . $filename); + + return $this->respond([ + 'status' => true, + 'code' => 200, + 'message' => 'Inception export generated successfully.', + 'data' => [ + 'downloadUrl' => $downloadUrl, + 'filename' => $filename, + 'rowCount' => count($result), + ], + ], 200); + } catch (\Throwable $e) { + log_message('error', 'download_inception: Exception - ' . $e->getMessage()); + return $this->respond([ + 'status' => false, + 'code' => 500, + 'message' => 'Failed to generate inception export.', + 'data' => [], + ], 500); } - - - $exportDir = WRITEPATH . 'exports'; - if (!is_dir($exportDir)) { - mkdir($exportDir, 0777, true); - } else { - // chmod($exportDir, 0777); - } - - $filename = 'inception_export_' . date('Ymd_His') . '.xlsx'; - $filepath = $exportDir . '/' . $filename; - - $writer = new Xlsx($spreadsheet); - $writer->save($filepath); - - return $this->response->setJSON([ - 'status' => 'success', - 'downloadUrl' => base_url('employee/download_file?file=' . urlencode($filename)) - ]); } public function download_file() { $filename = $this->request->getGet('file'); - $filepath = WRITEPATH . 'exports/' . $filename; - if (!file_exists($filepath)) { - return $this->response->setStatusCode(404)->setBody('File not found.'); + log_message('error', 'download_file: Request received - file=' . ($filename ?? '')); + + try { + if (empty($filename)) { + log_message('error', 'download_file: Missing file parameter'); + return $this->response->setStatusCode(400)->setBody('File name is required.'); + } + + $filename = basename($filename); + $filepath = WRITEPATH . 'exports/' . $filename; + + if (!is_file($filepath)) { + log_message('error', 'download_file: File not found - ' . $filepath); + return $this->response->setStatusCode(404)->setBody('File not found.'); + } + + log_message('error', 'download_file: Serving file - ' . $filename); + + return $this->response->download($filepath, null)->setFileName($filename); + } catch (\Throwable $e) { + log_message('error', 'download_file: Exception - ' . $e->getMessage()); + return $this->response->setStatusCode(500)->setBody('Failed to download file.'); } - - return $this->response->download($filepath, null)->setFileName($filename); } + /** + * Cron/manual job: after enrollment close date, email the respective HR with enrolled employee summary. + */ + public function sendEnrollmentClosedHrMail() + { + $this->myLogger->logme('error', 'sendEnrollmentClosedHrMail started at ' . date('d-m-Y H:i:s a')); + $closedFiles = $this->employeePolicyModel->getEnrollmentClosedFilesForHrMail(); + if (empty($closedFiles)) { + $this->myLogger->logme('error', 'sendEnrollmentClosedHrMail: no files with enrollment closed yesterday'); + return $this->respond([ + 'status' => false, + 'message' => 'No enrollment closed files found for processing', + 'sent_count' => 0, + 'files_processed' => 0, + ], 200); + } + $sentCount = 0; + $skippedCount = 0; + foreach ($closedFiles as $file) { + $sentForFile = $this->dispatchEnrollmentClosedHrMail($file); + if ($sentForFile > 0) { + $sentCount += $sentForFile; + } else { + $skippedCount++; + } + } + $this->myLogger->logme('error', 'sendEnrollmentClosedHrMail completed at ' . date('d-m-Y H:i:s a')); + return $this->respond([ + 'status' => true, + 'message' => 'Enrollment closed HR mail process completed', + 'sent_count' => $sentCount, + 'skipped_count' => $skippedCount, + 'files_processed' => count($closedFiles), + ], 200); + } + /** + * Test endpoint: send enrollment-closed HR mail for a specific file (ignores yesterday filter). + * GET /testSendEnrollmentClosedHrMail?file_id=1258&email=test@example.com + */ + public function testSendEnrollmentClosedHrMail($fileIdArg = null, $emailArg = null) + { + if (is_cli()) { + $segments = service('uri')->getSegments(); + $fileId = (int) ($fileIdArg ?? $segments[2] ?? 0); + $testEmail = trim($emailArg ?? $segments[3] ?? ''); + } else { + $fileId = (int) ($this->request->getGet('file_id') ?? $fileIdArg ?? 0); + $testEmail = trim($this->request->getGet('email') ?? $emailArg ?? ''); + } + + if ($fileId <= 0) { + return $this->respond([ + 'status' => false, + 'message' => 'file_id is required. Example: /testSendEnrollmentClosedHrMail?file_id=1258&email=you@example.com', + ], 200); + } + + $file = $this->employeePolicyModel->getEnrollmentClosedFileForHrMailById($fileId); + + if (empty($file)) { + return $this->respond([ + 'status' => false, + 'message' => 'File not found or not eligible for enrollment closed HR mail.', + ], 200); + } + + if ($testEmail !== '') { + $file['test_hr_email'] = $testEmail; + } + + $sentCount = $this->dispatchEnrollmentClosedHrMail($file); + $recipients = $testEmail !== '' + ? [$testEmail] + : array_column( + $this->employeePolicyModel->getClientHrContactsForMail((int) $file['client_id']), + 'email' + ); + + return $this->respond([ + 'status' => $sentCount > 0, + 'message' => $sentCount > 0 + ? 'Test enrollment closed HR mail sent successfully.' + : 'Failed to send test mail. Check application logs for details.', + 'file_id' => $fileId, + 'sent_count' => $sentCount, + 'mail_to' => $recipients, + ], 200); + } + + private function getHrRecipientsForEnrollmentClosedMail(array $file): array + { + if (!empty($file['test_hr_email'])) { + return [[ + 'email' => trim($file['test_hr_email']), + 'name' => 'HR Team', + ]]; + } + + return $this->employeePolicyModel->getClientHrContactsForMail((int) $file['client_id']); + } + + private function dispatchEnrollmentClosedHrMail(array $file): int + { + $fileClientId = (int) $file['client_id']; + $filePolicyId = (int) $file['policy_id']; + + $hrRecipients = $this->getHrRecipientsForEnrollmentClosedMail($file); + + $clientData = $this->clientModel->where('id', $fileClientId)->first(); + + if (empty($clientData)) { + $this->myLogger->logme('error', 'Client not found for file ' . $file['file_id']); + return 0; + } + + if (empty($hrRecipients)) { + $this->myLogger->logme('error', 'No HR contacts configured for client ' . $fileClientId); + return 0; + } + + $excelAttachment = $this->buildEnrolledEmployeeExcelAttachment( + $fileClientId, + (int) $file['client_branch_id'], + $filePolicyId + ); + + if ($excelAttachment === null) { + $this->myLogger->logme('error', 'No enrolled employees or failed Excel for policy ' . $filePolicyId); + return 0; + } + + $sentCount = 0; + try { + $clientPolicy = $this->clientPolicyModel->find($filePolicyId); + $clientLogo = base_url() . 'public/uploads/logo/' . ($clientData['client_logo'] ?? ''); + + $enrollmentOpenDate = !empty($file['enrollment_open_date']) + ? change_date_format($file['enrollment_open_date'], 'Y-m-d', 'F d, Y') + : 'N/A'; + $enrollmentCloseDate = !empty($file['enrollment_close_date']) + ? change_date_format($file['enrollment_close_date'], 'Y-m-d', 'F d, Y') + : 'N/A'; + + $subject = 'Enrollment Closed - Enrolled Employee List | ' . ($clientData['client_name'] ?? ''); + + foreach ($hrRecipients as $hrRecipient) { + $hrEmail = trim($hrRecipient['email'] ?? ''); + $hrName = trim($hrRecipient['name'] ?? '') ?: 'HR Team'; + + if ($hrEmail === '') { + continue; + } + + $mailContent = $this->buildEnrollmentClosedHrMailContent([ + 'hr_name' => $hrName, + 'client_name' => $clientData['client_name'] ?? '', + 'policy_no' => $clientPolicy['policy_no'] ?? 'N/A', + 'enrollment_open_date' => $enrollmentOpenDate, + 'enrollment_close_date' => $enrollmentCloseDate, + 'employee_count' => $excelAttachment['rowCount'] ?? 0, + ]); + + $html = view('mail_template', [ + 'mail_content' => $mailContent, + 'client_logo' => $clientLogo, + 'params' => [ + 'notification' => [ + 'mail_content_json' => json_encode([ + 'body' => ['values' => ['contentWidth' => 600]], + ]), + ], + ], + ]); + + $mailResult = MailHelper::send_email([ + 'mail' => $hrEmail, + 'subject' => $subject, + 'message' => $html, + 'attachments' => [$excelAttachment], + 'common' => [ + 'client_id' => $fileClientId, + 'client_branch_id' => $file['client_branch_id'], + 'client_policy_id' => $filePolicyId, + 'employee_policy_id' => null, + 'employee_id' => null, + 'mail_type' => 'enrollment_closed_hr_mail', + ], + ]); + + $this->myLogger->logme('info', $mailResult); + + $decoded = json_decode($mailResult, true); + if (is_array($decoded) && ($decoded['status'] ?? '') === 'success') { + $sentCount++; + } + } + } catch (\Throwable $e) { + log_message('error', 'dispatchEnrollmentClosedHrMail: ' . $e->getMessage()); + } finally { + $this->deleteGeneratedExportFile($excelAttachment['filePath'] ?? null); + } + + return $sentCount; + } + + private function deleteGeneratedExportFile(?string $filePath): void + { + if (empty($filePath) || !is_file($filePath)) { + return; + } + + if (!@unlink($filePath)) { + log_message('error', 'dispatchEnrollmentClosedHrMail: failed to delete export file - ' . $filePath); + } + } + + private function buildEnrollmentClosedHrMailContent(array $data): string + { + $hrName = htmlspecialchars($data['hr_name'] ?? 'HR Team', ENT_QUOTES, 'UTF-8'); + $clientName = htmlspecialchars($data['client_name'] ?? '', ENT_QUOTES, 'UTF-8'); + $policyNo = htmlspecialchars($data['policy_no'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); + $openDate = htmlspecialchars($data['enrollment_open_date'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); + $closeDate = htmlspecialchars($data['enrollment_close_date'] ?? 'N/A', ENT_QUOTES, 'UTF-8'); + $employeeCount = (int) ($data['employee_count'] ?? 0); + + return ' +
|
+ Dear ' . $hrName . ', +The enrollment window for ' . $clientName . ' has closed. +
+ Policy No: ' . $policyNo . ' + Please find the attached Excel file with the enrolled employee list + (' . $employeeCount . ' record' . ($employeeCount === 1 ? '' : 's') . '). + +Regards, |
+