From c63d11299f03fff39268fab497711f2aac55cd99 Mon Sep 17 00:00:00 2001 From: velz Date: Wed, 24 Dec 2025 18:41:31 +0530 Subject: [PATCH] FEAT_ECARD_S3MIG --- app/Config/Routes.php | 3 + app/Config/Services.php | 10 + app/Controllers/EmployeeController.php | 598 ++++++++++++------ .../EmployeeMultiEventServiceController.php | 4 +- app/Controllers/JobWorker.php | 6 + app/Libraries/S3Service.php | 268 ++++++++ composer.json | 3 +- 7 files changed, 709 insertions(+), 183 deletions(-) create mode 100644 app/Libraries/S3Service.php diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 13aff9ac..4f942872 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'); 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/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/composer.json b/composer.json index e86456ca..0aed2a9b 100755 --- a/composer.json +++ b/composer.json @@ -14,6 +14,7 @@ "ext-intl": "*", "ext-json": "*", "ext-mbstring": "*", + "aws/aws-sdk-php": "^3.369", "botman/botman": "^2.8", "botman/driver-web": "^1.5", "dompdf/dompdf": "^2.0", @@ -72,7 +73,7 @@ ] }, "config": { - "optimize-autoloader": true, + "optimize-autoloader": false, "preferred-install": "dist", "sort-packages": true, "process-timeout": 900