diff --git a/app/Config/Routes.php b/app/Config/Routes.php
index 13aff9ac..e88bbc81 100755
--- a/app/Config/Routes.php
+++ b/app/Config/Routes.php
@@ -214,6 +214,7 @@ $routes->group("/employee", ["filter" => "authMVC"], function ($routes) {
$routes->get("retail-endorsement-list", "EmployeeController::retailendorsementlist");
$routes->post("retail-endorsement-save", "EmployeeController::retailendorsementsave");
$routes->get("getTPADataVariationReport/(:num)", "EmployeeController::getTPADataVariationReport/$1");
+ $routes->get("bulkGenerateEcardAndStoreinS3", "EmployeeController::bulkGenerateEcardAndStoreinS3");
});
@@ -509,6 +510,8 @@ $routes->get('cli/sendZeptoMail', 'MasterController::testZeptoSMTP');
$routes->cli('cli/processjob', 'JobWorker::processJob');
$routes->cli('cli/processjobs', 'JobWorker::processJobs');
+$routes->cli('cli/bulkGenerateEcardAndStoreinS3', 'EmployeeController::bulkGenerateEcardAndStoreinS3');
+
$routes->get("processjob", "JobWorker::processJob");
$routes->cli('cli/new_gmail_token', 'MasterController::generateNewGmailAPIToken');
$routes->cli('cli/send_mail_cli', 'MasterController::testGmailAPIViaCLI');
@@ -767,11 +770,12 @@ $routes->get('claimStatus','VidalApiController::claimStatus');
$routes->get('EcardRequest','MediAssistApiController::EcardRequest');
$routes->get('HospitalNetwork','MediAssistApiController::HospitalNetwork');
$routes->get('GetBenefDetails','MediAssistApiController::GetBenefDetails');
-$routes->get('ClaimDetail','VidalApiController::ClaimDetail');
+$routes->get('ClaimDetail','MediAssistApiController::ClaimDetail');
$routes->get('SubmitClaim','MediAssistApiController::SubmitClaim');
$routes->get('IntimateClaim','MediAssistApiController::IntimateClaim');
$routes->get('IRSubmission','MediAssistApiController::IRSubmission');
$routes->get('ClaimStatusUpdate','MediAssistApiController::ClaimStatusUpdate');
+$routes->get('syncTpaClaimToNhance','MediAssistApiController::syncTpaClaimToNhance');
// API service
diff --git a/app/Config/Services.php b/app/Config/Services.php
index 5f982877..73d75683 100755
--- a/app/Config/Services.php
+++ b/app/Config/Services.php
@@ -9,6 +9,7 @@ use App\Libraries\GmailAPI;
use App\Libraries\MyGoogleDrive;
use App\Libraries\RuleImportService;
use App\Libraries\DataServiceSqlite;
+use App\Libraries\S3Service;
use App\Controllers\Home;
/**
@@ -90,5 +91,14 @@ class Services extends BaseService
return new RuleImportService();
}
+
+ public static function getS3Service($getShared = true)
+ {
+ if ($getShared) {
+ return static::getSharedInstance('getS3Service');
+ }
+
+ return new S3Service();
+ }
}
diff --git a/app/Controllers/EmployeeController.php b/app/Controllers/EmployeeController.php
index 32217c91..fe377c2a 100755
--- a/app/Controllers/EmployeeController.php
+++ b/app/Controllers/EmployeeController.php
@@ -101,6 +101,14 @@ class EmployeeController extends AdminController
public function list()
{
+
+ // $s3 = \Config\Services::getS3Service();
+ // $file = WRITEPATH.'uploads/excel/inception.ods';
+ // print_rr($s3->exists('inception_1766475604.ods'));
+ // print_rr($s3->getPresignedUrl('inception_1766.ods'));
+ // print_rr($s3->upload($file));
+ // print_rr($s3->download('inception_1766475604.ods',__DIR__));
+ // die();
// $model = new UserModel();
$data = [];
$data['status'] = ['draft' => 'Draft', 'enrolled' => 'Enrolled', 'active' => 'Active', 'inactive' => 'In-Active', 'pending' => 'Pending'];
@@ -1429,7 +1437,10 @@ class EmployeeController extends AdminController
//E-CARD DOWNLOAD FUNCTION USING DOM PDF ( CURRENTLY USING THIS ) Dompdf
public function generateIDCardForEmployee($rand_string, $people = 0, $mode = 0)
{
- // dd($rand_string, $people);
+ $mode = (int)$mode;
+
+
+ // dd($rand_string, $people,$mode);
$this->myLogger->logme('error', 'generateIDCardForEmployee params .' . json_encode(["rand_string" => $rand_string, "people" => $people, "mode" => $mode]));
try {
$this->myLogger->logme('error', 'generateIDCardForEmployee function started.');
@@ -1438,7 +1449,7 @@ class EmployeeController extends AdminController
$this->myLogger->logme('error', 'Fetching employee code and client policy ID.');
$get_emp_code_and_client_policy_id = $this->employeePolicyModel
- ->select('employee_polices.client_policy_id, employees.emp_code, tpa.short_name,employees.client_id')
+ ->select('employee_polices.client_policy_id, employees.emp_code,employees.name, employee_polices.tpa_id,tpa.short_name,employees.client_id')
->join('client_policy', 'client_policy.id = employee_polices.client_policy_id')
->join('employees', 'employees.id = employee_polices.employee_id')
->join('tpa', 'tpa.id = client_policy.tpa_id')
@@ -1465,7 +1476,44 @@ class EmployeeController extends AdminController
$client_policy_id = $get_emp_code_and_client_policy_id['client_policy_id'];
$emp_code = $get_emp_code_and_client_policy_id['emp_code'];
$client_id = $get_emp_code_and_client_policy_id['client_id'];
-
+
+ //new step check in S3 if yes then fetch from S3 bucket
+ $s3_key = 'ecard_'.$get_emp_code_and_client_policy_id['name'].'('.$get_emp_code_and_client_policy_id['emp_code'].')'.'_'.$get_emp_code_and_client_policy_id['tpa_id'].'.pdf';
+ // echo $s3_key;die();
+ $s3 = \Config\Services::getS3Service();
+ if($s3->exists($s3_key) && $mode != 2) //2 => for bulk generate so skip s3 check and generate PDF
+ {
+ //get pre signed url & make it download
+ $s3_url = $s3->getPresignedUrl($s3_key );
+
+ $pdf = file_get_contents($s3_url['url']);
+ if ($mode == 1) {
+ // Inline view
+ return $this->response
+ ->setHeader('Content-Type', 'application/pdf')
+ ->setHeader('Content-Disposition', 'inline; filename="' . $s3_key . '"')
+ ->setBody($pdf);
+ } else if($mode == 0){
+ // echo 'Force download';//die();
+ return $this->response
+ ->setHeader('Content-Type', 'application/pdf')
+ ->setHeader('Content-Disposition', 'attachment; filename="' . $s3_key . '"')
+ ->setBody($pdf);
+ }
+ // exit();
+ }
+ else
+ {
+ if($mode !== 2)
+ {
+ $data['message'] = 'Inprogress... Comeback later! or Contact support';
+ return view('errors/404', $data);
+ }
+ }
+
+ //in else generate PDF and upload it in s3
+ // echo 'else';
+ // exit();
// Step 2: Fetch ECard data
$this->myLogger->logme('error', 'Fetching ECard data using client_policy_id: ' . $client_policy_id . ' and emp_code: ' . $emp_code);
$data = $this->employeePolicyModel->getECardDataUsingMd5($client_policy_id, $emp_code,$client_id);
@@ -1562,6 +1610,7 @@ class EmployeeController extends AdminController
}
// return $html;
+ // echo $html;die();
// DomPdf
$options = new Options();
$options->set('isRemoteEnabled', true);
@@ -1585,28 +1634,34 @@ class EmployeeController extends AdminController
$dompdf->addInfo("Title", "E-Card");
$filename = 'ecard_' . date('Y-m-d_H-i-s') . '.pdf';
-
+ $temp_local_path = WRITEPATH.'/tmp/'.$s3_key;
+ file_put_contents(($temp_local_path),$dompdf->output());
// if($mode == 1){
// $dompdf->stream($filename, ['Attachment' => false]); // Inline view
// }else{
// $dompdf->stream($filename, ['Attachment' => true]); // Force download
// }
+ $aws_upload_res = $s3->upload(($temp_local_path));
+ // print_rr($aws_upload_res);
+ // echo '
PDF generated and uploaded to S3 successfully.
';
+ unlink($temp_local_path);
- if ($mode == 1) {
- // Inline view
- return $this->response
- ->setHeader('Content-Type', 'application/pdf')
- ->setHeader('Content-Disposition', 'inline; filename="' . $filename . '"')
- ->setBody($dompdf->output());
- } else {
- // Force download
- return $this->response
- ->setHeader('Content-Type', 'application/pdf')
- ->setHeader('Content-Disposition', 'attachment; filename="' . $filename . '"')
- ->setBody($dompdf->output());
- }
+ // exit();
+ // if ($mode == 1) {
+ // // Inline view
+ // return $this->response
+ // ->setHeader('Content-Type', 'application/pdf')
+ // ->setHeader('Content-Disposition', 'inline; filename="' . $filename . '"')
+ // ->setBody($dompdf->output());
+ // } else {
+ // // Force download
+ // return $this->response
+ // ->setHeader('Content-Type', 'application/pdf')
+ // ->setHeader('Content-Disposition', 'attachment; filename="' . $filename . '"')
+ // ->setBody($dompdf->output());
+ // }
- $this->myLogger->logme('error', 'generateIDCardForEmployee function completed successfully.');
+ // $this->myLogger->logme('error', 'generateIDCardForEmployee function completed successfully.');
} catch (\Exception $e) {
$this->myLogger->logme('error', 'Exception occurred: ' . $e->getMessage());
@@ -3764,206 +3819,389 @@ class EmployeeController extends AdminController
array $notInTPA,
array $notInNhance,
array $reviewNeeded,
- string $filename = 'employee_review.xlsx'
-) {
+ string $filename = 'employee_review.xlsx')
+ {
- function setCell($sheet, int $col, int $row, $value)
-{
- $cell = Coordinate::stringFromColumnIndex($col) . $row;
- $sheet->setCellValue($cell, $value);
-}
+ function setCell($sheet, int $col, int $row, $value)
+ {
+ $cell = Coordinate::stringFromColumnIndex($col) . $row;
+ $sheet->setCellValue($cell, $value);
+ }
-$EXPORT_COLUMNS = [
+ $EXPORT_COLUMNS = [
- // Sheet 1 — Not in TPA
- 'not_in_tpa' => [
- 'emp_code' => 'Employee Code',
- 'name' => 'Employee Name',
- 'relationship' => 'Relation',
- 'dob' => 'Date of Birth',
- 'gender' => 'Gender',
- 'mobile' => 'Mobile No',
- 'email_corporate' => 'Corporate Email',
- 'policy_no' => 'Policy No',
- 'tpa_name' => 'TPA Name',
- 'change_event' => 'Change Event',
- ],
+ // Sheet 1 — Not in TPA
+ 'not_in_tpa' => [
+ 'emp_code' => 'Employee Code',
+ 'name' => 'Employee Name',
+ 'relationship' => 'Relation',
+ 'dob' => 'Date of Birth',
+ 'gender' => 'Gender',
+ 'mobile' => 'Mobile No',
+ 'email_corporate' => 'Corporate Email',
+ 'policy_no' => 'Policy No',
+ 'tpa_name' => 'TPA Name',
+ 'change_event' => 'Change Event',
+ ],
- // Sheet 2 — Not in Nhance
- 'not_in_nhance' => [
- 'emp_code' => 'Employee Code',
- 'name' => 'Employee Name',
- 'relation' => 'Relation',
- 'dob' => 'Date of Birth',
- 'gender' => 'Gender',
- 'age' => 'Age',
- 'tpa_id' => 'TPA Member ID',
- ],
+ // Sheet 2 — Not in Nhance
+ 'not_in_nhance' => [
+ 'emp_code' => 'Employee Code',
+ 'name' => 'Employee Name',
+ 'relation' => 'Relation',
+ 'dob' => 'Date of Birth',
+ 'gender' => 'Gender',
+ 'age' => 'Age',
+ 'tpa_id' => 'TPA Member ID',
+ ],
- // Sheet 3 — Review Needed (DB side)
- 'review_main' => [
- 'emp_code' => 'Employee Code',
- 'name' => 'Employee Name',
- 'relationship' => 'Relation',
- 'dob' => 'Date of Birth',
- 'gender' => 'Gender',
- 'policy_no' => 'Policy No',
- 'uhid' => 'UHID',
- 'change_event' => 'Change event'
- ],
+ // Sheet 3 — Review Needed (DB side)
+ 'review_main' => [
+ 'emp_code' => 'Employee Code',
+ 'name' => 'Employee Name',
+ 'relationship' => 'Relation',
+ 'dob' => 'Date of Birth',
+ 'gender' => 'Gender',
+ 'policy_no' => 'Policy No',
+ 'uhid' => 'UHID',
+ 'change_event' => 'Change event'
+ ],
- // Sheet 3 — Review Needed (TPA side)
- 'review_tpa' => [
- 'emp_code' => 'TPA Employee Code',
- 'name' => 'TPA Name',
- 'relation' => 'TPA Relation',
- 'dob' => 'TPA DOB',
- 'gender' => 'TPA Gender',
- 'tpa_id' => 'TPA Member ID',
- 'age' => 'TPA Age',
- ],
-];
+ // Sheet 3 — Review Needed (TPA side)
+ 'review_tpa' => [
+ 'emp_code' => 'TPA Employee Code',
+ 'name' => 'TPA Name',
+ 'relation' => 'TPA Relation',
+ 'dob' => 'TPA DOB',
+ 'gender' => 'TPA Gender',
+ 'tpa_id' => 'TPA Member ID',
+ 'age' => 'TPA Age',
+ ],
+ ];
- $spreadsheet = new Spreadsheet();
+ $spreadsheet = new Spreadsheet();
- /* =========================================================
- * SHEET 1 — NOT IN TPA
- * ========================================================= */
- $sheet1 = $spreadsheet->getActiveSheet();
- $sheet1->setTitle('Not in TPA');
+ /* =========================================================
+ * SHEET 1 — NOT IN TPA
+ * ========================================================= */
+ $sheet1 = $spreadsheet->getActiveSheet();
+ $sheet1->setTitle('Not in TPA');
- $cols = $EXPORT_COLUMNS['not_in_tpa'];
+ $cols = $EXPORT_COLUMNS['not_in_tpa'];
- $colNo = 1;
- foreach ($cols as $label) {
- setCell($sheet1, $colNo++, 1, $label);
- }
-
- $rowNo = 2;
- foreach ($notInTPA as $row) {
$colNo = 1;
- foreach ($cols as $key => $label) {
- setCell($sheet1, $colNo++, $rowNo, $row[$key] ?? '');
+ foreach ($cols as $label) {
+ setCell($sheet1, $colNo++, 1, $label);
}
- $rowNo++;
- }
- foreach (range(1, count($cols)) as $c) {
- $sheet1->getColumnDimension(Coordinate::stringFromColumnIndex($c))->setAutoSize(true);
- }
+ $rowNo = 2;
+ foreach ($notInTPA as $row) {
+ $colNo = 1;
+ foreach ($cols as $key => $label) {
+ setCell($sheet1, $colNo++, $rowNo, $row[$key] ?? '');
+ }
+ $rowNo++;
+ }
- /* =========================================================
- * SHEET 2 — NOT IN NHANCE
- * ========================================================= */
- $sheet2 = $spreadsheet->createSheet();
- $sheet2->setTitle('Not in Nhance');
+ foreach (range(1, count($cols)) as $c) {
+ $sheet1->getColumnDimension(Coordinate::stringFromColumnIndex($c))->setAutoSize(true);
+ }
- $cols = $EXPORT_COLUMNS['not_in_nhance'];
+ /* =========================================================
+ * SHEET 2 — NOT IN NHANCE
+ * ========================================================= */
+ $sheet2 = $spreadsheet->createSheet();
+ $sheet2->setTitle('Not in Nhance');
- $colNo = 1;
- foreach ($cols as $label) {
- setCell($sheet2, $colNo++, 1, $label);
- }
+ $cols = $EXPORT_COLUMNS['not_in_nhance'];
- $rowNo = 2;
- foreach ($notInNhance as $row) {
$colNo = 1;
- foreach ($cols as $key => $label) {
- setCell($sheet2, $colNo++, $rowNo, $row[$key] ?? '');
+ foreach ($cols as $label) {
+ setCell($sheet2, $colNo++, 1, $label);
}
- $rowNo++;
- }
- foreach (range(1, count($cols)) as $c) {
- $sheet2->getColumnDimension(Coordinate::stringFromColumnIndex($c))->setAutoSize(true);
- }
+ $rowNo = 2;
+ foreach ($notInNhance as $row) {
+ $colNo = 1;
+ foreach ($cols as $key => $label) {
+ setCell($sheet2, $colNo++, $rowNo, $row[$key] ?? '');
+ }
+ $rowNo++;
+ }
- /* =========================================================
- * SHEET 3 — REVIEW NEEDED
- * ========================================================= */
- $sheet3 = $spreadsheet->createSheet();
- $sheet3->setTitle('Review Needed');
+ foreach (range(1, count($cols)) as $c) {
+ $sheet2->getColumnDimension(Coordinate::stringFromColumnIndex($c))->setAutoSize(true);
+ }
- $mainCols = $EXPORT_COLUMNS['review_main'];
- $tpaCols = $EXPORT_COLUMNS['review_tpa'];
+ /* =========================================================
+ * SHEET 3 — REVIEW NEEDED
+ * ========================================================= */
+ $sheet3 = $spreadsheet->createSheet();
+ $sheet3->setTitle('Review Needed');
- // headers
- $colNo = 1;
- foreach ($mainCols as $label) {
- setCell($sheet3, $colNo++, 1, $label);
- }
- foreach ($tpaCols as $label) {
- setCell($sheet3, $colNo++, 1, $label);
- }
+ $mainCols = $EXPORT_COLUMNS['review_main'];
+ $tpaCols = $EXPORT_COLUMNS['review_tpa'];
- // rows
- $rowNo = 2;
- foreach ($reviewNeeded as $row) {
-
- // main (DB)
+ // headers
$colNo = 1;
- foreach ($mainCols as $key => $label) {
- setCell($sheet3, $colNo++, $rowNo, $row[$key] ?? '');
+ foreach ($mainCols as $label) {
+ setCell($sheet3, $colNo++, 1, $label);
+ }
+ foreach ($tpaCols as $label) {
+ setCell($sheet3, $colNo++, 1, $label);
}
- $tpaData = [];
- $notMatching = [];
+ // rows
+ $rowNo = 2;
+ foreach ($reviewNeeded as $row) {
- if (($row['match']['status'] ?? '') === 'matched') {
- $tpaData = $row['match']['tpa_record'] ?? [];
- $notMatching = $row['match']['not_matching'] ?? [];
- }
-
- // TPA
- foreach ($tpaCols as $key => $label) {
- setCell($sheet3, $colNo++, $rowNo, $tpaData[$key] ?? '');
- }
-
- // highlight mismatches
- foreach ($notMatching as $field) {
-
- if (isset($mainCols[$field])) {
- $idx = array_keys($mainCols);
- $pos = array_search($field, $idx);
- $cell = Coordinate::stringFromColumnIndex($pos + 1) . $rowNo;
- $sheet3->getStyle($cell)->getFill()
- ->setFillType(Fill::FILL_SOLID)
- ->getStartColor()->setARGB('FFFFFF00');
+ // main (DB)
+ $colNo = 1;
+ foreach ($mainCols as $key => $label) {
+ setCell($sheet3, $colNo++, $rowNo, $row[$key] ?? '');
}
- if (isset($tpaCols[$field])) {
- $idx = array_keys($tpaCols);
- $pos = array_search($field, $idx);
- $cell = Coordinate::stringFromColumnIndex(count($mainCols) + $pos + 1) . $rowNo;
- $sheet3->getStyle($cell)->getFill()
- ->setFillType(Fill::FILL_SOLID)
- ->getStartColor()->setARGB('FFFFFF00');
+ $tpaData = [];
+ $notMatching = [];
+
+ if (($row['match']['status'] ?? '') === 'matched') {
+ $tpaData = $row['match']['tpa_record'] ?? [];
+ $notMatching = $row['match']['not_matching'] ?? [];
}
+
+ // TPA
+ foreach ($tpaCols as $key => $label) {
+ setCell($sheet3, $colNo++, $rowNo, $tpaData[$key] ?? '');
+ }
+
+ // highlight mismatches
+ foreach ($notMatching as $field) {
+
+ if (isset($mainCols[$field])) {
+ $idx = array_keys($mainCols);
+ $pos = array_search($field, $idx);
+ $cell = Coordinate::stringFromColumnIndex($pos + 1) . $rowNo;
+ $sheet3->getStyle($cell)->getFill()
+ ->setFillType(Fill::FILL_SOLID)
+ ->getStartColor()->setARGB('FFFFFF00');
+ }
+
+ if (isset($tpaCols[$field])) {
+ $idx = array_keys($tpaCols);
+ $pos = array_search($field, $idx);
+ $cell = Coordinate::stringFromColumnIndex(count($mainCols) + $pos + 1) . $rowNo;
+ $sheet3->getStyle($cell)->getFill()
+ ->setFillType(Fill::FILL_SOLID)
+ ->getStartColor()->setARGB('FFFFFF00');
+ }
+ }
+
+ $rowNo++;
}
- $rowNo++;
+ foreach (range(1, count($mainCols) + count($tpaCols)) as $c) {
+ $sheet3->getColumnDimension(Coordinate::stringFromColumnIndex($c))->setAutoSize(true);
+ }
+
+ /* =========================================================
+ * OUTPUT
+ * ========================================================= */
+ $writer = new Xlsx($spreadsheet);
+
+ if (ob_get_length()) ob_end_clean();
+
+ header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
+ header('Content-Disposition: attachment; filename="' . $filename . '"');
+ header('Cache-Control: max-age=0');
+
+ $writer->save('php://output');
+ exit;
}
- foreach (range(1, count($mainCols) + count($tpaCols)) as $c) {
- $sheet3->getColumnDimension(Coordinate::stringFromColumnIndex($c))->setAutoSize(true);
+ public function bulkGenerateEcardAndStoreinS3(array $params = [])
+ {
+ /**
+ * ---------------------------
+ * 1. Detect mode + collect inputs
+ * ---------------------------
+ */
+
+ $client_policy_id = $params['client_policy_id'] ?? null;
+ $emp_code = $params['emp_code'] ?? null;
+ $emp_pk = $params['emp_pk'] ?? null;
+ $batch_size = $params['batch_size'] ?? null;
+ $batch_no = $params['batch_no'] ?? null;
+ $execution_mode = $params['execution_mode'] ?? 'sequential';//parallel
+ $total_batches = $params['total_batches'] ?? 0;//parallel
+
+ if (is_cli()) {
+ $mode = 'CLI';
+
+
+ } elseif (empty($params) && $this->request->getMethod() === 'get') {
+ $mode = 'WEB';
+
+ $client_policy_id = $this->request->getGet('client_policy_id');
+ $emp_code = $this->request->getGet('emp_code');
+ $emp_pk = $this->request->getGet('emp_pk');
+ $execution_mode = $this->request->getGet('execution_mode') ?? 'sequential';
+ $batch_size = (int) ($this->request->getGet('batch_size') ?? 100);
+ if($client_policy_id == null || empty($client_policy_id) || $client_policy_id == '')
+ {
+ return $this->respond(['status' => false, 'code' => 200, 'message' => 'client_policy_id is necessary'], 200);
+ }
+ /**
+ * ---------------------------
+ * 2. Calculate total records
+ * ---------------------------
+ */
+ $total = $this->employeePolicyModel
+ ->join('employees', 'employees.id = employee_polices.employee_id')
+ ->where('employees.emp_status', 'active')
+ ->where('employees.is_active', '1')
+ ->where("employee_polices.tpa_id IS NOT NULL AND employee_polices.tpa_id <> ''")
+ ->where('employee_polices.status', 'active')
+ ->where('employee_polices.is_active', '1')
+ ->where('employee_polices.client_policy_id', $client_policy_id)
+ ->countAllResults();
+
+ $total_batches = (int) ceil($total / $batch_size);
+ // echo $total_batches;die();
+
+
+
+ /**
+ * ---------------------------
+ * 3. Push JOB per batch
+ * ---------------------------
+ */
+ if ($execution_mode === 'parallel') {
+ for ($batch_no = 1; $batch_no <= $total_batches; $batch_no++) {
+ Jobs::addJob([
+ 'job_name' => 'bulkGenerateEcardAndStoreinS3',
+ 'payload' => [
+ 'client_policy_id' => $client_policy_id,
+ 'emp_code' => $emp_code,
+ 'emp_pk' => $emp_pk,
+ 'batch_size' => $batch_size,
+ 'batch_no' => $batch_no,
+ 'total_batches' => $total_batches,
+ 'execution_mode' => 'parallel',
+ ],
+ ]);
+ }
+ }
+
+ if ($execution_mode === 'sequential') {
+ Jobs::addJob([
+ 'job_name' => 'bulkGenerateEcardAndStoreinS3',
+ 'payload' => [
+ 'client_policy_id' => $client_policy_id,
+ 'batch_size' => $batch_size,
+ 'batch_no' => $batch_no ?? 1,
+ 'total_batches' => $total_batches,
+ 'execution_mode' => 'sequential',
+ ],
+ ]);
+ }
+
+ return $this->respond(['status' => true, 'code' => 200, 'message' => 'E-card generation started',
+ 'total' => $total,
+ 'batches' => $total_batches,], 200);
+ // return [
+ // 'status' => true,
+ // 'message' => 'E-card generation started',
+ // 'total' => $total,
+ // 'batches' => $total_batches,
+ // ];
+ }
+ //else {
+ // return true;
+ // }
+
+ /**
+ * ---------------------------
+ * 4. Build query (CLI execution)
+ * ---------------------------
+ */
+ $builder = $this->employeePolicyModel
+ ->select('
+ employee_polices.client_policy_id,
+ employees.emp_code,
+ tpa.short_name,
+ employees.client_id,
+ employee_polices.rand_string,
+ employees.id as emp_id
+ ')
+ ->join('client_policy', 'client_policy.id = employee_polices.client_policy_id')
+ ->join('employees', 'employees.id = employee_polices.employee_id')
+ ->join('tpa', 'tpa.id = client_policy.tpa_id')
+ ->where('employees.emp_status', 'active')
+ ->where('employees.is_active', '1')
+ ->where("employee_polices.tpa_id IS NOT NULL AND employee_polices.tpa_id <> ''")
+ ->where('employee_polices.status', 'active')
+ ->where('employee_polices.is_active', '1')
+ ->where('employee_polices.client_policy_id', $client_policy_id);
+
+ if (!empty($emp_code)) {
+ $builder->where('employees.emp_code', $emp_code);
+ }
+
+ if (!empty($emp_pk)) {
+ $builder->where('employees.id', $emp_pk);
+ }
+
+ /**
+ * ---------------------------
+ * 5. Apply batching (only if provided)
+ * ---------------------------
+ */
+ if (!empty($batch_size) && !empty($batch_no)) {
+ $offset = ($batch_no - 1) * $batch_size;
+ $builder->limit($batch_size, $offset);
+ }
+
+ $res = $builder->findAll();
+
+ /**
+ * ---------------------------
+ * 6. Process batch
+ * ---------------------------
+ */
+ foreach ($res as $row) {
+ $ecard_gen_res = $this->generateIDCardForEmployee(
+ $row['rand_string'],
+ 0,
+ 2 // skip S3 check, force regenerate
+ );
+
+ // print_rr([
+ // 'batch' => $batch_no,
+ // 'emp' => $row['emp_code'],
+ // 'res' => $ecard_gen_res,
+ // ]);
+
+ // echo date('Y-m-d H:i:s') . PHP_EOL;
+ }
+
+ if ($execution_mode === 'sequential' && $batch_no < $total_batches) {
+ Jobs::addJob([
+ 'job_name' => 'bulkGenerateEcardAndStoreinS3',
+ 'payload' => [
+ 'client_policy_id' => $client_policy_id,
+ 'batch_size' => $batch_size,
+ 'batch_no' => ($batch_no + 1),
+ 'total_batches' => $total_batches,
+ 'execution_mode' => 'sequential',
+ ],
+ ]);
+ }
+
+ // echo "BATCH {$batch_no} COMPLETED @ " . date('Y-m-d H:i:s') . PHP_EOL;
+ return array($batch_no);
}
- /* =========================================================
- * OUTPUT
- * ========================================================= */
- $writer = new Xlsx($spreadsheet);
-
- if (ob_get_length()) ob_end_clean();
-
- header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
- header('Content-Disposition: attachment; filename="' . $filename . '"');
- header('Cache-Control: max-age=0');
-
- $writer->save('php://output');
- exit;
-}
-
}
diff --git a/app/Controllers/EmployeeMultiEventServiceController.php b/app/Controllers/EmployeeMultiEventServiceController.php
index 782a189b..3c8846e9 100644
--- a/app/Controllers/EmployeeMultiEventServiceController.php
+++ b/app/Controllers/EmployeeMultiEventServiceController.php
@@ -1029,7 +1029,7 @@ class EmployeeMultiEventServiceController extends BaseController
//check physical file
if (!file_exists($file_name_with_path)) {
//file not found update status and reason
- $message = "Physcial file not found";
+ $message = "Physcial file not found - " . $file_name_with_path;
// echo $message;
$this->myLogger->logme('error', ($message . ' for file id ' . $file_id));
$this->fileModel->where('id', $file_id)->set(['status' => 'failed', 'reason' => json_encode(['error_summary' => array_count_values([5]), 'error_data' => $message])])->update();
@@ -1402,7 +1402,7 @@ class EmployeeMultiEventServiceController extends BaseController
//check physical file
if (!file_exists($file_name_with_path)) {
//file not found update status and reason
- $message = "Physcial file not found";
+ $message = "Physcial file not found - " . $file_name_with_path;
$this->myLogger->logme('error', ($message . ' for file id ' . $file_id));
$this->fileModel->where('id', $file_id)->set(['status' => 'failed', 'reason' => json_encode(['error_summary' => array_count_values([5]), 'error_data' => $message])])->update();
return array('error_summary' => [5], 'error_data' => $message);
diff --git a/app/Controllers/JobWorker.php b/app/Controllers/JobWorker.php
index c32da5e6..4a52fc54 100755
--- a/app/Controllers/JobWorker.php
+++ b/app/Controllers/JobWorker.php
@@ -194,6 +194,10 @@ class JobWorker extends AdminController
'type' => 'CC', // Handler Category
'handler' => 'App\Controllers\MediAssistApiController',
],
+ 'bulkGenerateEcardAndStoreinS3' => [
+ 'type' => 'CC', // Handler Category
+ 'handler' => 'App\Controllers\EmployeeController',
+ ]
];
@@ -253,6 +257,8 @@ class JobWorker extends AdminController
// print_r($jobdata);
// echo 'listen';
// die();
+ // $jobdata = ['id' => 11954,'uuid' => '001700c2-8127-4d02-80b3-ea26e7603ccf'];
+ // dd($job);
$query = "
SELECT id, name, payload, uuid
FROM jobs
diff --git a/app/Controllers/MediAssistApiController.php b/app/Controllers/MediAssistApiController.php
index 245157e4..df595bb5 100644
--- a/app/Controllers/MediAssistApiController.php
+++ b/app/Controllers/MediAssistApiController.php
@@ -6,6 +6,7 @@ use App\Controllers\BaseController;
use App\Models\BatchFileModel;
use App\Models\EmployeePolicyModel;
use App\Models\TpaApiDataModel;
+use App\Models\ClientPolicyModel;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\HTTP\IncomingRequest;
use CodeIgniter\HTTP\RequestInterface;
@@ -25,6 +26,7 @@ class MediAssistApiController extends BaseController
}
+
public function SubmitClaim ($claimId = null)
{
@@ -543,21 +545,23 @@ class MediAssistApiController extends BaseController
}
- // dd($body);
+
// $body = [
// "policyNo" => "97000063250400000031",
- // "startDate" => "31/08/2025",
- // "endDate" => "01/09/2025",
- // "employeeCode" => "CITPL120193",
+ // "startDate" => "01/11/2025",
+ // "endDate" => "05/11/2025",
+ // "employeeCode" => "",
// "memberID" => "",
// "claimNo" => "",
- // "claimRefNo" => "HOSP4078102577_16092025111030"
+ // "claimRefNo" => ""
// ];
// CALL API
$response = call_third_party_api($url, $method, $headers, $body);
+
+
if ($response['status'] != true || empty($response['data']['claimsData'][0])) {
log_message('error', 'CLAIM STATUS FAILED | for ticket ID: ' . $claimId.' | response: '.json_encode($response));
@@ -616,9 +620,9 @@ class MediAssistApiController extends BaseController
// Maping tpa claim status with local claim Status
if (isset($validStatuses[$currentStatus]))
{
- $updateArray = ['claim_status_id' => $validStatuses[$currentStatus] , 'tpa_claim_status' => $currentStatus , 'tpa_claim_id' => $tpa_claim_no , 'updated_at' => date('Y-m-d H:i:s')];
+ $updateArray = ['claim_status_id' => $validStatuses[$currentStatus] , 'tpa_claim_status' => $currentStatus , 'tpa_claim_id' => $tpa_claim_no, 'claim_number' => $tpa_claim_no, 'updated_at' => date('Y-m-d H:i:s')];
}else{
- $updateArray = ['tpa_claim_status' => $currentStatus , 'tpa_claim_id' => $tpa_claim_no , 'updated_at' => date('Y-m-d H:i:s')];
+ $updateArray = ['tpa_claim_status' => $currentStatus , 'tpa_claim_id' => $tpa_claim_no, 'claim_number' => $tpa_claim_no , 'updated_at' => date('Y-m-d H:i:s')];
}
// UPDATE ticket_master
@@ -797,7 +801,6 @@ class MediAssistApiController extends BaseController
// unlink($file_array['json_file_path']); // delete temp json file
}
-
public function ClaimStatusUpdate()
{
helper('api');
@@ -928,9 +931,9 @@ class MediAssistApiController extends BaseController
// Maping tpa claim status with local claim Status
if (isset($validStatuses[$currentStatus]))
{
- $updateArray = ['claim_status_id' => $validStatuses[$currentStatus] , 'tpa_claim_status' => $currentStatus , 'tpa_claim_id' => $tpa_claim_no , 'updated_at' => date('Y-m-d H:i:s')];
+ $updateArray = ['claim_status_id' => $validStatuses[$currentStatus] , 'tpa_claim_status' => $currentStatus , 'tpa_claim_id' => $tpa_claim_no , 'claim_number' => $tpa_claim_no , 'updated_at' => date('Y-m-d H:i:s')];
}else{
- $updateArray = ['tpa_claim_status' => $currentStatus , 'tpa_claim_id' => $tpa_claim_no , 'updated_at' => date('Y-m-d H:i:s')];
+ $updateArray = ['tpa_claim_status' => $currentStatus , 'tpa_claim_id' => $tpa_claim_no , 'claim_number' => $tpa_claim_no, 'updated_at' => date('Y-m-d H:i:s')];
}
// UPDATE ticket_master
@@ -952,6 +955,328 @@ class MediAssistApiController extends BaseController
]);
}
+ public function syncTpaClaimToNhance()
+ {
+ helper(['api', 'date']);
+
+ $url = getenv('MEDI_ASSIST_API_BASE_URL_CLAIMSTATUS');
+ $method = 'POST';
+
+ $headers = [
+ 'Content-Type: application/json',
+ 'Username:' . getenv('MEDI_ASSIST_API_USERNAME'),
+ 'Password:' . getenv('MEDI_ASSIST_API_PASSWORD'),
+ ];
+
+ // REQUEST DATES
+ $reqStartDate = $this->request->getGet('start_date');
+ $reqEndDate = $this->request->getGet('end_date');
+
+ // If start_date OR end_date is missing → last 7 days
+ if (empty($reqStartDate) || empty($reqEndDate)) {
+
+ $endDateObj = new \DateTime(); // today
+ $startDateObj = (clone $endDateObj)->modify('-6 days');
+
+ } else {
+
+ $startDateObj = new \DateTime($reqStartDate);
+ $endDateObj = new \DateTime($reqEndDate);
+ }
+
+ // Final usable variables
+ $reqStartDate = $startDateObj->format('Y-m-d');
+ $reqEndDate = $endDateObj->format('Y-m-d');
+
+ // Final usable variables
+ $reqStartDate = new \DateTime($reqStartDate);
+ $reqEndDate = new \DateTime($reqEndDate);
+
+
+
+ // FETCH POLICY DATA
+ $policies = $this->db->table('client_policy cp')
+ ->select("
+ cp.policy_no AS policyNo,
+ cp.policy_start_date AS policyStartDate,
+ cp.policy_end_date AS policyEndDate
+ ")
+ ->where('cp.is_active', 1)
+ ->where('cp.policy_type_id', 2)
+ ->where('cp.policy_status', 1)
+ // ->where('cp.policy_no', '97000063250400000031')
+ ->where('cp.tpa_id', $this->mediAssistTpaId)
+ ->get()
+ ->getResultArray();
+
+ if (empty($policies)) {
+ log_message('error', 'No policies found for claim sync');
+ return $this->response->setJSON([
+ 'status' => false,
+ 'message' => 'Policies not found'
+ ]);
+ }
+
+
+
+ $finalResult = [];
+ $errorData = [];
+
+
+ // LOOP POLICIES
+
+ foreach ($policies as $policy) {
+
+ $policyStart = new \DateTime($policy['policyStartDate']);
+ $policyEnd = new \DateTime($policy['policyEndDate']);
+
+
+ // ADJUST DATE RANGE (inside policy period)
+
+ $startDate = max($reqStartDate, $policyStart);
+ $endDate = min($reqEndDate, $policyEnd);
+
+
+ if ($startDate > $endDate) {
+ continue; // No valid range for this policy
+ }
+
+
+ // SPLIT INTO 7-DAY CHUNKS
+
+ $chunkStart = clone $startDate;
+
+ while ($chunkStart <= $endDate) {
+
+ $chunkEnd = clone $chunkStart;
+ $chunkEnd->modify('+6 days');
+
+ if ($chunkEnd > $endDate) {
+ $chunkEnd = clone $endDate;
+ }
+
+
+ // PREPARE BODY
+
+ $body = [
+ "policyNo" => $policy['policyNo'],
+ "startDate" => $chunkStart->format('d/m/Y'),
+ "endDate" => $chunkEnd->format('d/m/Y'),
+ "employeeCode" => "",
+ "memberID" => "",
+ "claimNo" => "",
+ "claimRefNo" => "",
+ ];
+
+
+ // CALL TPA API
+
+ $response = call_third_party_api($url, $method, $headers, $body);
+
+ if (empty($response['status']) || empty($response['data']['claimsData'])) {
+
+ log_message('error','CLAIM STATUS FAILED | ' .'policyNo: ' . $policy['policyNo'] .' | ' . $chunkStart->format('Y-m-d') .' to ' . $chunkEnd->format('Y-m-d') .' | response: ' . json_encode($response) );
+
+ $errorData[] = [
+ 'policy_no' => $policy['policyNo'],
+ 'start' => $chunkStart->format('Y-m-d'),
+ 'end' => $chunkEnd->format('Y-m-d'),
+ ];
+
+ } else {
+
+ $finalResult = array_merge($finalResult,$response['data']['claimsData']);
+ }
+
+ // Move to next chunk
+ $chunkStart->modify('+7 days');
+ }
+ }
+
+
+ // 5️⃣ FINAL RESPONSE
+ foreach ($finalResult as $key => $value) {
+
+ $relationship = strtolower(trim($value['relation'] ?? ''));
+
+ if ($relationship === 'employee') {
+ $relationship = 'self';
+ }
+
+ $ticket = $this->db->table('ticket_master tm')->select("tm.id")
+ ->where('tm.policy_no', $value['policY_NUMBER'])
+ ->where('tm.emp_code', $value['employeE_NO'])
+ ->where('tm.claim_number', $value['tpA_CLAIM_NO'])
+ ->get()
+ ->getRowArray();
+
+ if(!$ticket)
+ {
+
+ // VALID STATUS LIST
+ $validStatuses = [
+
+ "Claim Received" => 1,
+ "In Progress" => 5,
+ "Processed" => 11,
+ "Claim Paid" => 11,
+ "Denied" => 13,
+ "Cancelled" => 13,
+
+ "Information Awaited" => 4,
+ "Confirmation Awaited" => 4,
+ "Information Awaited Reminder" => 4,
+ "Information Awaited Final Reminder" => 4,
+ "Insurer Concurrence Awaited" => 6,
+ "Closed" => 12,
+
+ "Physical Documents Awaited" => 9,
+ "Processed - Payment Initiated" => 10,
+ "Processed - Transaction Failed" => 10,
+ "Processed - Account Details Updated" => 10,
+ "Processed - Debit Note Raised With Insurer for Payment"=> 10,
+ "Processed - Payment Initiated by Insurer" => 10,
+ "Payment - Refunded to Insurer" => 14,
+ "Processed - Processing Payment" => 10,
+ "Processed - Physical Documents Awaited" => 9,
+
+ // Extra Mappings (based on your DB list)
+ "NON ID" => 1,
+ "ID NOT GENERATED" => 2,
+ "CDA" => 3,
+ "REJECTED" => 8,
+ "APPROVED" => 9,
+ "PAYMENT INITIATED" => 10,
+ "SETTLED" => 11,
+ "RETURNED" => 14,
+ "UNDER PROCESS - TPA" => 61,
+ "DENIAL REVIEW AWAITED" => 66,
+ ];
+
+ $currentStatus = trim($value['claim_Current_Status'] ?? '');
+
+ $claimStatusId = $validStatuses[$currentStatus] ?? null;
+
+ if (!$claimStatusId) {
+ log_message('error', 'Unknown claim status: '.$currentStatus);
+ continue;
+ }
+
+ $clientpolicy = $this->db->table('client_policy cp')
+ ->select("
+ cp.id as client_policy_id,
+ cp.client_id ,
+ cp.insurer_id ,
+ cp.tpa_id ,
+ client_rm.id as acm_id
+ ")
+ ->join('client_rm', 'client_rm.client_id = cp.client_id AND client_rm.level = 3', 'left')
+ ->where('cp.policy_no', $value['policY_NUMBER'])
+ ->get()
+ ->getRowArray();
+
+ $employee = $this->db->table('employees e')
+ ->select("
+ e.id as emp_id,
+ e.emp_code ,
+ e2.id as insured_emp_id,
+ ")
+ ->join(
+ 'employees e2',
+ "e2.emp_code = e.emp_code AND e2.relationship = ".$this->db->escape($relationship),
+ 'left'
+ )
+ ->where('e.emp_code', $value['employeE_NO'])
+ ->where('e.relationship', 'self')
+ ->get()
+ ->getRowArray();
+
+
+
+ $claimData = [
+ // Core
+ 'ticket_type_id' => 1,
+ 'claim_status_id' => $claimStatusId,
+ 'policy_no' => $value['policY_NUMBER'] ?? null,
+ 'claim_number' => $value['tpA_CLAIM_NO'] ?? null,
+ 'tpa_claim_id' => $value['tpA_CLAIM_NO'] ?? null,
+
+ // local promary id
+ 'tpa_id' => $clientpolicy['tpa_id'] ?? null,
+ 'insurer_id' => $clientpolicy['insurer_id'] ?? null,
+ 'client_policy_id' => $clientpolicy['client_policy_id'] ?? null,
+ 'client_id' => $clientpolicy['client_id'] ?? null,
+ 'acm_id' => $clientpolicy['acm_id'] ?? null,
+
+ // Employee / Insured
+ 'emp_id' => $employee['emp_id'] ?? null,
+ 'insured_emp_id' => $employee['insured_emp_id'] ?? null,
+ 'emp_code' => $value['employeE_NO'] ?? null,
+ 'emp_name' => $value['employeE_NAME'] ?? null,
+ 'insured_name' => $value['beneficiarY_NAME'] ?? null,
+ 'relationship' => $relationship,
+ 'emp_mobile' => $value['mobile'] ?? null,
+ 'emp_mail' => $value['email'] ?? null,
+
+ // Claim info
+ 'claim_type' => 1,
+ 'mode_of_intimation' => 5,
+ 'claim_amount' => $value['estimateD_CLAIM_AMOUNT']
+ ?? $value['finaL_BILL_AMOUNT']
+ ?? null,
+ 'approved_amount' => $value['claiM_APPROVED_AMOUNT'] ?? null,
+
+ // Dates
+ 'dob' => $this->mediDate($value['datE_OF_BIRTH'] ?? null),
+ 'doa' => $this->mediDate($value['datE_OF_ADMISSION'] ?? null),
+ 'dod' => $this->mediDate($value['datE_OF_DISCHARGE'] ?? null),
+ 'raised_date' => $this->mediDate($value['claiM_REGISTERED_DATE'] ?? null),
+ 'approved_date' => $this->mediDate($value['approved_Date'] ?? null),
+ 'settled_date' => $this->mediDate($value['paiD_DATE'] ?? null),
+
+ // Hospital
+ 'hospital_name' => $value['hospitaL_NAME'] ?? null,
+ 'hospital_address' => $value['hospitaL_ADDRESS'] ?? null,
+ 'hospital_state' => $value['hospitaL_STATE'] ?? null,
+ 'hospital_city' => $value['hospitaL_CITY'] ?? null,
+ 'hospital_pin_code'=> $value['hosP_PINCODE'] ?? null,
+
+ // Payment
+ 'utr_details' => $value['banK_CHEQUE_NO'] ?? null,
+ 'settle_letter' => $value['settlement_LetterLink'] ?? null,
+ ];
+
+ $this->db->table('ticket_master')->insert($claimData);
+
+ log_message(
+ 'info',
+ 'New claim created | Policy: '.$claimData['policy_no'].
+ ' | Claim: '.$claimData['claim_number']
+ );
+
+ }
+
+ }
+
+
+ return $this->response->setJSON([
+ 'status' => true,
+ 'message' => 'TPA claim status sync completed',
+ 'total_records' => count($finalResult),
+ 'result' => $finalResult,
+ 'errors' => $errorData
+ ]);
+ }
+
+
+ public function mediDate($date)
+ {
+ if (empty($date)) return null;
+ $dt = \DateTime::createFromFormat('d/m/Y H:i:s', $date);
+ return $dt ? $dt->format('Y-m-d') : null;
+ }
+
+
diff --git a/app/Libraries/S3Service.php b/app/Libraries/S3Service.php
new file mode 100644
index 00000000..872b13ab
--- /dev/null
+++ b/app/Libraries/S3Service.php
@@ -0,0 +1,268 @@
+bucket = getenv('AWS_BUCKET');
+ $this->region = getenv('AWS_DEFAULT_REGION');
+
+ $this->s3Client = new S3Client([
+ 'version' => 'latest',
+ 'region' => $this->region,
+ 'credentials' => [
+ 'key' => getenv('AWS_ACCESS_KEY_ID'),
+ 'secret' => getenv('AWS_SECRET_ACCESS_KEY'),
+ ],
+ ]);
+
+ $this->baseUrl = "https://{$this->bucket}.s3.{$this->region}.amazonaws.com/";
+ // echo $this->baseUrl;die();
+ }
+
+ /**
+ * Upload file to S3
+ *
+ * @param mixed $file File object or file path
+ * @param string $folder Folder path in S3 bucket (optional)
+ * @param string $fileName Custom file name (optional)
+ * @return array ['success' => bool, 'url' => string, 'key' => string, 'message' => string]
+ */
+ public function upload($file, string $folder = '', string $fileName = null): array
+ {
+ try {
+ // Handle CodeIgniter file upload object
+ if (is_object($file) && method_exists($file, 'isValid')) {
+ if (!$file->isValid()) {
+ return [
+ 'success' => false,
+ 'message' => 'Invalid file upload',
+ 'url' => null,
+ 'key' => null
+ ];
+ }
+ $filePath = $file->getTempName();
+ $originalName = $fileName ?? $file->getClientName();
+ } else {
+ // Handle file path string
+ $filePath = $file;
+ $originalName = $fileName ?? basename($file);
+ }
+
+ // Generate unique file name
+ $extension = pathinfo($originalName, PATHINFO_EXTENSION);
+ // $uniqueName = pathinfo($originalName, PATHINFO_FILENAME) . '_' . time() . '.' . $extension;
+ $uniqueName = pathinfo($originalName, PATHINFO_FILENAME) . '.' . $extension;
+
+ // Build S3 key (path)
+ $key = $folder ? rtrim($folder, '/') . '/' . $uniqueName : $uniqueName;
+
+ // Upload to S3
+ // NEW - Faster with streaming and proper content type
+ $finfo = finfo_open(FILEINFO_MIME_TYPE);
+ $mimeType = finfo_file($finfo, $filePath);
+ finfo_close($finfo);
+
+ $result = $this->s3Client->putObject([
+ 'Bucket' => $this->bucket,
+ 'Key' => $key,
+ 'Body' => fopen($filePath, 'rb'), // Stream instead of loading into memory
+ 'ContentType' => $mimeType,
+ // 'ACL' => 'public-read',
+ ]);
+
+ return [
+ 'success' => true,
+ 'url' => $this->baseUrl . $key,
+ 'key' => $key,
+ 'message' => 'File uploaded successfully'
+ ];
+
+ } catch (AwsException $e) {
+ log_message('error', 'S3 Upload Error: ' . $e->getMessage());
+ return [
+ 'success' => false,
+ 'message' => 'Upload failed: ' . $e->getMessage(),
+ 'url' => null,
+ 'key' => null
+ ];
+ }
+ }
+
+ /**
+ * Download file from S3
+ *
+ * @param string $key S3 object key (file path in bucket)
+ * @param string $savePath Local path to save the file (optional)
+ * @return array ['success' => bool, 'path' => string, 'content' => string, 'message' => string]
+ */
+ public function download(string $key, string $savePath = null): array
+ {
+ try {
+ $result = $this->s3Client->getObject([
+ 'Bucket' => $this->bucket,
+ 'Key' => $key,
+ ]);
+
+ $content = (string) $result['Body'];
+
+ // If save path is provided, save to local file
+ if ($savePath) {
+ $directory = dirname($savePath);
+ if (!is_dir($directory)) {
+ mkdir($directory, 0755, true);
+ }
+ file_put_contents($savePath, $content);
+
+ return [
+ 'success' => true,
+ 'path' => $savePath,
+ 'content' => $content,
+ 'message' => 'File downloaded successfully'
+ ];
+ }
+
+ return [
+ 'success' => true,
+ 'path' => null,
+ 'content' => $content,
+ 'message' => 'File content retrieved successfully'
+ ];
+
+ } catch (AwsException $e) {
+ log_message('error', 'S3 Download Error: ' . $e->getMessage());
+ return [
+ 'success' => false,
+ 'path' => null,
+ 'content' => null,
+ 'message' => 'Download failed: ' . $e->getMessage()
+ ];
+ }
+ }
+
+ /**
+ * Get a pre-signed URL for temporary access to a private file
+ *
+ * @param string $key S3 object key
+ * @param int $expiration Expiration time in minutes (default: 60)
+ * @return array ['success' => bool, 'url' => string, 'message' => string]
+ */
+ public function getPresignedUrl(string $key, int $expiration = 60): array
+ {
+ try {
+ $cmd = $this->s3Client->getCommand('GetObject', [
+ 'Bucket' => $this->bucket,
+ 'Key' => $key
+ ]);
+
+ $request = $this->s3Client->createPresignedRequest($cmd, "+{$expiration} minutes");
+ $presignedUrl = (string) $request->getUri();
+
+ return [
+ 'success' => true,
+ 'url' => $presignedUrl,
+ 'message' => 'Pre-signed URL generated successfully'
+ ];
+
+ } catch (AwsException $e) {
+ log_message('error', 'S3 Presigned URL Error: ' . $e->getMessage());
+ return [
+ 'success' => false,
+ 'url' => null,
+ 'message' => 'Failed to generate URL: ' . $e->getMessage()
+ ];
+ }
+ }
+
+ /**
+ * Delete file from S3
+ *
+ * @param string $key S3 object key
+ * @return array ['success' => bool, 'message' => string]
+ */
+ public function delete(string $key): array
+ {
+ try {
+ $this->s3Client->deleteObject([
+ 'Bucket' => $this->bucket,
+ 'Key' => $key,
+ ]);
+
+ return [
+ 'success' => true,
+ 'message' => 'File deleted successfully'
+ ];
+
+ } catch (AwsException $e) {
+ log_message('error', 'S3 Delete Error: ' . $e->getMessage());
+ return [
+ 'success' => false,
+ 'message' => 'Delete failed: ' . $e->getMessage()
+ ];
+ }
+ }
+
+ /**
+ * Check if file exists in S3
+ *
+ * @param string $key S3 object key
+ * @return bool
+ */
+ public function exists(string $key): bool
+ {
+ return $this->s3Client->doesObjectExist($this->bucket, $key);
+ }
+
+ /**
+ * List files in S3 bucket
+ *
+ * @param string $prefix Folder prefix (optional)
+ * @return array ['success' => bool, 'files' => array, 'message' => string]
+ */
+ public function listFiles(string $prefix = ''): array
+ {
+ try {
+ $result = $this->s3Client->listObjectsV2([
+ 'Bucket' => $this->bucket,
+ 'Prefix' => $prefix,
+ ]);
+
+ $files = [];
+ if (isset($result['Contents'])) {
+ foreach ($result['Contents'] as $object) {
+ $files[] = [
+ 'key' => $object['Key'],
+ 'size' => $object['Size'],
+ 'last_modified' => $object['LastModified']->format('Y-m-d H:i:s'),
+ 'url' => $this->baseUrl . $object['Key']
+ ];
+ }
+ }
+
+ return [
+ 'success' => true,
+ 'files' => $files,
+ 'message' => 'Files retrieved successfully'
+ ];
+
+ } catch (AwsException $e) {
+ log_message('error', 'S3 List Error: ' . $e->getMessage());
+ return [
+ 'success' => false,
+ 'files' => [],
+ 'message' => 'Failed to list files: ' . $e->getMessage()
+ ];
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/Views/faq_list.php b/app/Views/faq_list.php
index e725b7e3..9a4d6d8c 100644
--- a/app/Views/faq_list.php
+++ b/app/Views/faq_list.php
@@ -3,6 +3,33 @@
position: absolute;
}
.dataTables_length label {height: 21px !important;}
+
+.custom-dropdown-menu {
+ display: none;
+ position: absolute;
+ background-color: #ffffff !important;
+ border: 1px solid rgba(0, 0, 0, 0.15);
+ border-radius: 0.25rem;
+ padding: 0.5rem 0;
+ min-width: 10rem;
+ z-index: 9999;
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
+}
+
+.custom-dropdown-menu .dropdown-item {
+ display: block !important;
+ width: 100% !important;
+ padding: 0.5rem 1rem !important;
+ color: #212529 !important;
+ text-decoration: none !important;
+ background-color: transparent !important;
+}
+
+.custom-dropdown-menu .dropdown-item:hover {
+ background-color: #f8f9fa !important;
+ color: #16181b !important;
+ cursor: pointer !important;
+}
@@ -16,13 +43,15 @@