FEAT_ENROLLMENT_COLSED_ENROLLED_EMPLOYEE_LIST_EXCEL_MAIL

This commit is contained in:
VENKATESHWARAN 2026-06-25 15:13:08 +05:30
parent 130de0bad6
commit 494885c5ab
7 changed files with 794 additions and 82 deletions

View File

@ -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#' => [

View File

@ -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");

View File

@ -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 '
<div align="center">
<table width="100%" border="0" cellspacing="0" cellpadding="0" style="max-width:600px;">
<tr>
<td align="left" style="padding:15px; font-family:arial, sans-serif; font-size:14px; line-height:22px; color:#333333;">
<p>Dear ' . $hrName . ',</p>
<p>The enrollment window for <strong>' . $clientName . '</strong> has closed.</p>
<p>
<strong>Policy No:</strong> ' . $policyNo . '<br>
<strong>Enrollment Open Date:</strong> ' . $openDate . '<br>
<strong>Enrollment Close Date:</strong> ' . $closeDate . '
</p>
<p>
Please find the attached Excel file with the enrolled employee list
(' . $employeeCount . ' record' . ($employeeCount === 1 ? '' : 's') . ').
</p>
<p>Regards,<br>Nhance Team</p>
</td>
</tr>
</table>
</div>';
}
/**
* Build an Excel attachment of enrolled employees using download_inception query.
*
* @return array{filePath: string, fileName: string, rowCount: int}|null
*/
private function buildEnrolledEmployeeExcelAttachment(int $client_id, int $branch_id, int $policy_id): ?array
{
try {
$result = $this->employeePolicyModel->download_inception(
$client_id,
$branch_id,
$policy_id,
[],
'',
''
);
if (empty($result)) {
return null;
}
$formatted = [];
foreach ($result as $index => $row) {
$formatted[] = ['S.NO' => $index + 1] + $row;
}
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$headers = array_keys($formatted[0]);
$columnWidth = 20;
$colIndex = 1;
foreach ($headers as $header) {
$columnLetter = Coordinate::stringFromColumnIndex($colIndex);
$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 = 2;
foreach ($formatted as $row) {
$colIndex = 1;
foreach ($row as $cell) {
$columnLetter = Coordinate::stringFromColumnIndex($colIndex);
$sheet->setCellValue($columnLetter . $rowNum, $cell);
$colIndex++;
}
$rowNum++;
}
$exportDir = WRITEPATH . 'exports';
if (!is_dir($exportDir) && !mkdir($exportDir, 0777, true) && !is_dir($exportDir)) {
return null;
}
$filename = 'enrolled_employees_policy_' . $policy_id . '_' . date('Ymd_His') . '.xlsx';
$filepath = $exportDir . '/' . $filename;
$writer = new Xlsx($spreadsheet);
$writer->save($filepath);
if (!is_file($filepath)) {
return null;
}
return [
'filePath' => $filepath,
'fileName' => $filename,
'rowCount' => count($formatted),
];
} catch (\Throwable $e) {
log_message('error', 'buildEnrolledEmployeeExcelAttachment: ' . $e->getMessage());
return null;
}
}
}

View File

@ -88,12 +88,12 @@ 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 'submitted'
ELSE employee_polices.status
END as status";
}
$selectColumns = [
'employee_polices.*',
@ -2035,5 +2035,94 @@ class EmployeePolicyModel extends Model
return $res;
}
/**
* Inception files whose enrollment window closed yesterday (send HR summary the next day).
*/
public function getEnrollmentClosedFilesForHrMail()
{
$subQuery = '(SELECT MAX(id) as id
FROM files
WHERE status = "success"
AND enrollment_close_date IS NOT NULL
AND DATE(enrollment_close_date) = DATE_SUB(CURDATE(), INTERVAL 1 DAY)
GROUP BY policy_id) closed_files';
$builder = $this->db->table('files')
->select([
'files.id as file_id',
'files.client_id',
'files.client_branch_id',
'files.policy_id',
'files.enrollment_open_date',
'files.enrollment_close_date',
'cp.policy_type_id',
'cp.is_addon',
])
->join($subQuery, 'closed_files.id = files.id')
->join('client_policy cp', 'cp.id = files.policy_id')
->where('cp.is_active', 1)
->where('cp.policy_status', 1);
return $builder->get()->getResultArray();
}
/**
* All active HR contacts for a client (across branches).
*/
public function getClientHrContactsForMail(int $client_id): array
{
$rows = $this->db->table('level_contacts lc')
->select('lc.id, lc.name, lc.email')
->join('client_branch cb', 'lc.ref_id = cb.id')
->where('lc.contact_type', 'client')
->where('lc.is_active', 1)
->where('cb.client_id', $client_id)
->where('cb.is_active', 1)
->where('lc.email IS NOT NULL', null, false)
->where("lc.email != ''", null, false)
->orderBy('lc.name', 'ASC')
->get()
->getResultArray();
$unique = [];
foreach ($rows as $row) {
$email = strtolower(trim($row['email'] ?? ''));
if ($email === '' || isset($unique[$email])) {
continue;
}
$unique[$email] = [
'id' => $row['id'],
'name' => trim($row['name'] ?? '') ?: 'HR Team',
'email' => trim($row['email']),
];
}
return array_values($unique);
}
/**
* Fetch a single inception file for HR enrollment-closed mail (test/manual use).
*/
public function getEnrollmentClosedFileForHrMailById(int $file_id)
{
return $this->db->table('files')
->select([
'files.id as file_id',
'files.client_id',
'files.client_branch_id',
'files.policy_id',
'files.enrollment_open_date',
'files.enrollment_close_date',
'cp.policy_type_id',
'cp.is_addon',
])
->join('client_policy cp', 'cp.id = files.policy_id')
->where('files.id', $file_id)
->where('files.status', 'success')
->where('cp.is_active', 1)
->get()
->getRowArray();
}
}

View File

@ -858,6 +858,7 @@ function downloadInception(){
$.ajax({
type: 'POST',
url: "<?php echo base_url('employee/download_inception') ?>",
dataType: 'json',
data: {
client: client,
branch: branch,
@ -867,18 +868,19 @@ function downloadInception(){
empName: empName
},
success: function(response) {
if (response.status === 'success') {
if (response.status === true && response.data && response.data.downloadUrl) {
$('<a>', {
href: response.downloadUrl,
download: '',
style: 'display:none'
}).appendTo('body')[0].click();
href: response.data.downloadUrl,
download: '',
style: 'display:none'
}).appendTo('body')[0].click();
} else {
alert(response.message || 'Download failed');
toastr.error(response.message || 'Download failed', 'Error');
}
},
error: function(xhr, status, error) {
console.error('Error:', error);
error: function(xhr) {
const response = xhr.responseJSON;
toastr.error(response?.message || 'An error occurred while generating the export.', 'Error');
},
complete: function() {
$('.loader').fadeOut();

View File

@ -92,7 +92,7 @@ p{
<div style="
width:160px;
height:80px;
background-image:url('<?= $client_logo ?>');
background-image:url('<?= $client_logo ?? "" ?>');
background-size:contain;
background-repeat:no-repeat;
background-position: center;
@ -113,7 +113,7 @@ p{
<!-- mail content -->
<?php
echo $mail_content;
echo $mail_content ?? "";
?>

View File

@ -0,0 +1,187 @@
# Download Inception API
Base path: `/employeeRest`
Generates an Excel (`.xlsx`) export of **enrolled** employees for the selected client, branch, and policy. The API returns a download URL; the file is fetched in a second request.
**Authentication:** JWT + app signature (same filters as other `employeeRest` routes)
**HTTP status:** Responses use HTTP `200` (or `500` on unhandled exceptions). Check JSON `status` and `code` for success or failure.
---
## Endpoints
| Route | Method | Description |
|-------|--------|-------------|
| `/employeeRest/download_inception` | `POST` | Generate Excel export |
| `/employeeRest/download_file?file={filename}` | `GET` | Download generated file |
---
## 1. Generate inception export
| | |
|---|---|
| **Method** | `POST` |
| **URL** | `/employeeRest/download_inception` |
| **Content-Type** | `application/json` **or** `application/x-www-form-urlencoded` |
### Request body
| Field | Required | Type | Description |
|-------|----------|------|-------------|
| `client` | No | integer / string | Client ID (or MD5 client hash) |
| `branch` | No | integer | Client branch ID |
| `policies` | No | integer | Client policy ID |
| `status` | No | array / string | Accepted in payload; export currently uses **enrolled** status only |
| `empCode` | No | string | Filter by employee code |
| `empName` | No | string | Filter by employee name (partial match) |
### Sample request (JSON)
```http
POST /employeeRest/download_inception
Content-Type: application/json
Authorization: Bearer {token}
{
"client": 12,
"branch": 1,
"policies": 12,
"status": ["enrolled"],
"empCode": "",
"empName": ""
}
```
### Sample request (form)
```http
POST /employeeRest/download_inception
Content-Type: application/x-www-form-urlencoded
Authorization: Bearer {token}
client=12&branch=1&policies=12&empCode=&empName=
```
### Success response
```json
{
"status": true,
"code": 200,
"message": "Inception export generated successfully.",
"data": {
"downloadUrl": "https://example.com/employeeRest/download_file?file=inception_export_20260625_121435.xlsx",
"filename": "inception_export_20260625_121435.xlsx",
"rowCount": 9
}
}
```
### Error responses
**Invalid JSON**
```json
{
"status": false,
"code": 400,
"message": "Invalid JSON request body.",
"data": []
}
```
**No data**
```json
{
"status": false,
"code": 404,
"message": "No data found for given filters.",
"data": []
}
```
**Export / server error**
```json
{
"status": false,
"code": 500,
"message": "Failed to generate inception export.",
"data": []
}
```
---
## 2. Download file
After a successful export, download the file using `data.downloadUrl`.
```http
GET /employeeRest/download_file?file=inception_export_20260625_121435.xlsx
Authorization: Bearer {token}
```
| Query param | Required | Description |
|-------------|----------|-------------|
| `file` | Yes | Filename from the generate response (`data.filename`) |
Returns the `.xlsx` file as a download. Only the basename is allowed (path traversal is blocked).
---
## Excel columns
Each row includes (with `S.NO` as the first column):
| Column | Source |
|--------|--------|
| S.NO | Row index |
| Emp ID | Employee code |
| Name of Emp/Dep | Employee / dependent name |
| DOB | Date of birth |
| Gender | Gender |
| Relationship | Relationship |
| Basic cover SI | Sum insured |
| Date of Coverage | Coverage date |
| DOJ | Date of joining |
| Basic Pay | Basic pay |
| Band/Grade | Band / grade |
| Designation | Designation |
| Phone | Mobile |
| Email | Corporate email |
| PRE EXISTING AILMENTS | Pre-existing flag |
| change_event | Change event |
| date_of_exit | Exit date |
| reason_for_exit | Exit reason |
| unit | Unit |
Only **active** employees with **enrolled** policy status are included.
---
## Flow
```mermaid
sequenceDiagram
participant Client
participant API as employeeRest/download_inception
participant Storage as writable/exports
Client->>API: POST filters (client, branch, policies, ...)
API->>Storage: Save inception_export_*.xlsx
API-->>Client: JSON with downloadUrl + filename
Client->>API: GET employeeRest/download_file?file=...
API-->>Client: Excel file download
```
---
## Notes
- Files are stored under `writable/exports/` and named `inception_export_{Ymd_His}.xlsx`.
- Use the returned `downloadUrl` promptly; files may be removed by other processes.