diff --git a/app/Config/Acl.php b/app/Config/Acl.php index 2960595f..b4d85eac 100644 --- a/app/Config/Acl.php +++ b/app/Config/Acl.php @@ -32,6 +32,8 @@ class Acl '#^/metaTpaDashboardDemo#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID]], '#^/sales/dashboard#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID, STAFF_ROLE_ID]], '#^/sales#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID, STAFF_ROLE_ID]], + '#^/expense#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID, STAFF_ROLE_ID]], + diff --git a/app/Config/Routes.php b/app/Config/Routes.php index d9a05733..123cf819 100755 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -444,7 +444,9 @@ $routes->group("/util", ["filter" => "authMVC"], function ($routes) { $routes->get('proceedExcelFileDataValidation', 'EmployeeController::proceedExcelFileDataValidation'); $routes->get('checkTpaApiEnable', 'EmployeeRestController::checkTpaApiEnable'); $routes->get('generateDemographyDataTable', 'LeadsController::generateDemographyDataTable'); - $routes->post('insufficientCdBalanceHrMailSend', 'EmployeeController::insufficientCdBalanceHrMailSend'); + $routes->post('insufficientCdBalanceHrMailSend', 'EmployeeController::insufficientCdBalanceHrMailSend'); + $routes->get('getTpaClaimDumpErrorData/(:any)', 'TicketServiceController::getTpaClaimDumpErrorData/$1'); + }); $routes->post("policy_tranction/sendInstallmentRemainderMail","PolicyTransactionController::sendInstallmentRemainderMail"); @@ -1002,4 +1004,13 @@ $routes->group('sales', function($routes) { $routes->get('salesManagerLevelDashboard', 'SalesController::salesManagerLevelDashboard'); }); +// Expence Module Route Group +$routes->group('expense', ["filter" => "authMVC", 'namespace' => 'App\Controllers'], static function($routes) { + $routes->get('/', 'ExpenseController::index'); + $routes->post('save', 'ExpenseController::save'); + $routes->get('get/(:num)', 'ExpenseController::getExpense/$1'); + $routes->post('delete/(:num)', 'ExpenseController::delete/$1'); + $routes->get('client-policies', 'ExpenseController::clientPolicies'); +}); + diff --git a/app/Controllers/ClientController.php b/app/Controllers/ClientController.php index 62989198..1914d45b 100755 --- a/app/Controllers/ClientController.php +++ b/app/Controllers/ClientController.php @@ -6955,16 +6955,21 @@ class ClientController extends AdminController // $response = $ticketServiceController->getClaimExcelErrorData(["file_id" => 41]); // $response = $ticketServiceController->claimDumpOnBoardProcess(["file_id" => 17]); // $response = $ticketServiceController->extractExcelData("claims_dump_form_client.xlsx"); + // $response = $ticketServiceController->tpaClaimDumpImporter(["file_id" => 51]); //abhi // $response = $ticketServiceController->tpaClaimDumpImporter(["file_id" => 50]); //fhpl // $response = $ticketServiceController->tpaClaimDumpImporter(["file_id" => 53]); //icici // $response = $ticketServiceController->tpaClaimDumpImporter(["file_id" => 54]); //mediassist // $response = $ticketServiceController->tpaClaimDumpImporter(["file_id" => 52]); //reliance // $response = $ticketServiceController->tpaClaimDumpImporter(["file_id" => 48]); //vidal - // $response = $ticketServiceController->tpaClaimDumpToTicketMasterImporters(["file_id" => 48]); //vidal - // $response = $ticketServiceController->tpaClaimDumpToTicketMasterImporters(["file_id" => 54]); //mediassist - // $response = $ticketServiceController->tpaClaimDumpToTicketMasterImporters(["file_id" => 4]); //abhi + // $response = $ticketServiceController->tpaClaimDumpToTicketMasterImporters(["file_id" => 51]); //abhi + // $response = $ticketServiceController->tpaClaimDumpToTicketMasterImporters(["file_id" => 50]); //fhpl + // $response = $ticketServiceController->tpaClaimDumpToTicketMasterImporters(["file_id" => 53]); //icici + // $response = $ticketServiceController->tpaClaimDumpToTicketMasterImporters(["file_id" => 54]); //mediassist + // $response = $ticketServiceController->tpaClaimDumpToTicketMasterImporters(["file_id" => 52]); //reliance + // $response = $ticketServiceController->tpaClaimDumpToTicketMasterImporters(["file_id" => 48]); //vidal + // $response = $ticketServiceController->getTpaClaimDumpErrorData(["file_id" => 48]); // dd($response); // ---------- TICKET CONTROLLER -------------------------------------------------------------------------------- diff --git a/app/Controllers/ExpenseController.php b/app/Controllers/ExpenseController.php new file mode 100644 index 00000000..cf9a1bbe --- /dev/null +++ b/app/Controllers/ExpenseController.php @@ -0,0 +1,412 @@ +myLogger = \Config\Services::mylogger(); + $this->expenseModel = new ExpenseModel(); + $this->clientModel = new ClientModel(); + $this->clientPolicyModel = new ClientPolicyModel(); + } + + /** + * Web list + form view + */ + public function index() + { + try { + $data['tab_name'] = 'Expense'; + $data['page_name'] = 'Expense'; + $descriptionPattern = '/^[a-zA-Z0-9\s\.\,\-\(\)\/\&\+]+$/u'; + + // Raw GET filters + $rawFilters = $this->request->getGet() ?? []; + $rawFilters = is_array($rawFilters) ? $rawFilters : []; + + // Sanitize input array using existing helper + $sanitized = sanitizeInputArrayAdvanced($rawFilters); + + $filters = [ + 'client_id' => trim($sanitized['client_id'] ?? ''), + 'client_policy_id' => trim($sanitized['client_policy_id'] ?? ''), + 'approved_by' => trim($sanitized['approved_by'] ?? ''), + 'description' => trim($sanitized['description'] ?? ''), + 'amount' => trim($sanitized['amount'] ?? ''), + 'expense_date' => trim($sanitized['expense_date'] ?? ''), + ]; + + $validationErrors = []; + + // Basic type / format validation for filters + if ($filters['client_id'] !== '' && ! ctype_digit($filters['client_id'])) { + $validationErrors[] = 'Invalid client selected for search.'; + $filters['client_id'] = ''; + } + + if ($filters['client_policy_id'] !== '' && ! ctype_digit($filters['client_policy_id'])) { + $validationErrors[] = 'Invalid policy selected for search.'; + $filters['client_policy_id'] = ''; + } + + if ($filters['approved_by'] !== '' && ! ctype_digit($filters['approved_by'])) { + $validationErrors[] = 'Invalid approver selected for search.'; + $filters['approved_by'] = ''; + } + + if ($filters['description'] !== '' && ! preg_match($descriptionPattern, $filters['description'])) { + $validationErrors[] = 'Description filter contains invalid characters.'; + $filters['description'] = ''; + } + + if ($filters['amount'] !== '') { + if (! is_numeric($filters['amount']) || (float) $filters['amount'] < 0) { + $validationErrors[] = 'Amount filter must be a non-negative number.'; + $filters['amount'] = ''; + } + } + + if ($filters['expense_date'] !== '') { + $dt = \DateTime::createFromFormat('d-m-Y', $filters['expense_date']); + $errors = $dt ? \DateTime::getLastErrors() : ['warning_count' => 1, 'error_count' => 1]; + if (! $dt || ! empty($errors['warning_count']) || ! empty($errors['error_count'])) { + $validationErrors[] = 'Expense Date filter must be in DD-MM-YYYY format.'; + $filters['expense_date'] = ''; + } + } + + $data['filters'] = $filters; + $data['validation_errors'] = $validationErrors; + + // Clients for dropdown + $data['clients'] = $this->clientModel + ->select('id, client_name, short_name') + ->where('is_active', 1) + ->orderBy('client_name', 'ASC') + ->findAll(); + + // Approved by (users) dropdown + $db = db_connect(); + $data['approved_users'] = $db->table('user_profiles') + ->select('id, first_name') + ->where('is_active', 1) + ->whereIn('id', [7, 8]) + ->orderBy('first_name', 'ASC') + ->get() + ->getResultArray(); + + // Policies for filter dropdown (when client filter is selected) + $data['policies_for_filter'] = []; + if ($filters['client_id'] !== '') { + $data['policies_for_filter'] = $this->clientPolicyModel + ->select('id, policy_no') + ->where('client_id', (int) $filters['client_id']) + ->where('is_active', 1) + ->orderBy('policy_no', 'ASC') + ->findAll(); + } + + // Existing expenses with optional filters + $builder = $this->expenseModel + ->select(' + expenses.*, + clients.client_name, + clients.short_name, + client_policy.policy_no, + user_profiles.first_name AS approved_by_name + ') + ->join('clients', 'clients.id = expenses.client_id', 'left') + ->join('client_policy', 'client_policy.id = expenses.client_policy_id', 'left') + ->join('user_profiles', 'user_profiles.id = expenses.approved_by', 'left') + ->where('expenses.is_active', 1); + + if ($filters['client_id'] !== '') { + $builder->where('expenses.client_id', (int) $filters['client_id']); + } + + if ($filters['client_policy_id'] !== '') { + $builder->where('expenses.client_policy_id', (int) $filters['client_policy_id']); + } + + if ($filters['approved_by'] !== '') { + $builder->where('expenses.approved_by', (int) $filters['approved_by']); + } + + if ($filters['description'] !== '') { + $builder->like('expenses.description', (string) $filters['description']); + } + + if ($filters['amount'] !== '') { + $builder->where('expenses.amount', (float) $filters['amount']); + } + + if ($filters['expense_date'] !== '') { + $dt = \DateTime::createFromFormat('d-m-Y', $filters['expense_date']); + if ($dt) { + $builder->where('expenses.expense_date', $dt->format('Y-m-d')); + } + } + + $data['expenses'] = $builder + ->orderBy('expenses.id', 'DESC') + ->findAll(); + + return $this->loadLayout('expense_list', $data); + } catch (\Throwable $e) { + return handle_exception($e, $this->myLogger, $this->response); + } + } + + /** + * Create / update expense (AJAX) + */ + public function save() + { + try { + if ($this->request->getMethod() !== 'post') { + return $this->response + ->setStatusCode(405) + ->setJSON([ + 'status' => false, + 'message' => 'Invalid request method', + ]); + } + + $rawData = $this->request->getPost(); + $data = sanitizeInputArrayAdvanced($rawData); + + $id = isset($data['id']) && $data['id'] !== '' ? (int) $data['id'] : null; + + $rules = [ + 'client_id' => [ + 'rules' => 'required|is_natural_no_zero', + 'errors' => [ + 'required' => 'Client is required', + ], + ], + 'client_policy_id' => [ + 'rules' => 'required|is_natural_no_zero', + 'errors' => [ + 'required' => 'Policy is required', + ], + ], + 'description' => [ + 'rules' => 'required|string|min_length[1]|max_length[2000]|regex_match[/^[a-zA-Z0-9\s\.\,\-\(\)\/\&\+]+$/]', + 'errors' => [ + 'required' => 'Description is required', + 'min_length' => 'Description cannot be empty', + 'regex_match' => 'Description contains invalid characters.', + ], + ], + 'expense_date' => [ + 'rules' => 'required|valid_date[d-m-Y]', + 'errors' => [ + 'required' => 'Expense Date is required', + 'valid_date' => 'Expense Date must be in DD-MM-YYYY format', + ], + ], + 'approved_by' => [ + 'rules' => 'required|is_natural_no_zero', + 'errors' => [ + 'required' => 'Approved By is required', + ], + ], + 'amount' => [ + 'rules' => 'required|numeric|greater_than_equal_to[0]', + 'errors' => [ + 'required' => 'Amount is required', + 'numeric' => 'Amount must be numeric', + 'greater_than_equal_to' => 'Amount cannot be negative', + ], + ], + ]; + + if (! $this->validate($rules)) { + return $this->response->setStatusCode(400)->setJSON([ + 'status' => false, + 'message' => 'Input validation failed', + 'errors' => $this->validator->getErrors(), + ]); + } + + $payload = [ + 'client_id' => (int) ($data['client_id'] ?? 0), + 'client_policy_id' => (int) ($data['client_policy_id'] ?? 0), + 'description' => $data['description'] ?? null, + 'approved_by' => (int) ($data['approved_by'] ?? 0), + 'amount' => $data['amount'] ?? null, + 'is_active' => 1, + ]; + + $expenseDate = $data['expense_date'] ?? null; + if (! empty($expenseDate)) { + $dt = \DateTime::createFromFormat('d-m-Y', $expenseDate); + $payload['expense_date'] = $dt ? $dt->format('Y-m-d') : null; + } else { + $payload['expense_date'] = null; + } + + if ($id === null) { + $insertId = $this->expenseModel->insert($payload, true); + $success = ! empty($insertId); + $id = $insertId; + $message = $success + ? 'Expense created successfully' + : 'Unable to create expense. Please try again.'; + } else { + $success = $this->expenseModel->update($id, $payload); + $message = $success + ? 'Expense updated successfully' + : 'Unable to update expense. Please try again.'; + } + + return $this->response + ->setStatusCode($success ? 200 : 400) + ->setJSON([ + 'status' => (bool) $success, + 'message' => $message, + 'id' => $id, + ]); + } catch (\Throwable $e) { + return handle_exception($e, $this->myLogger, $this->response); + } + } + + /** + * Get single expense (AJAX) + */ + public function getExpense($id = null) + { + try { + $id = (int) $id; + + if (empty($id)) { + return $this->response->setStatusCode(400)->setJSON([ + 'status' => false, + 'message' => 'Invalid expense id', + ]); + } + + $expense = $this->expenseModel + ->select(' + expenses.*, + clients.client_name, + clients.short_name, + client_policy.policy_no, + user_profiles.first_name AS approved_by_name + ') + ->join('clients', 'clients.id = expenses.client_id', 'left') + ->join('client_policy', 'client_policy.id = expenses.client_policy_id', 'left') + ->join('user_profiles', 'user_profiles.id = expenses.approved_by', 'left') + ->where('expenses.id', $id) + ->where('expenses.is_active', 1) + ->first(); + + if (empty($expense)) { + return $this->response->setStatusCode(404)->setJSON([ + 'status' => false, + 'message' => 'Expense not found', + ]); + } + + return $this->response->setStatusCode(200)->setJSON([ + 'status' => true, + 'data' => $expense, + ]); + } catch (\Throwable $e) { + return handle_exception($e, $this->myLogger, $this->response); + } + } + + /** + * Soft delete expense (AJAX) + */ + public function delete($id = null) + { + try { + if ($this->request->getMethod() !== 'post') { + return $this->response + ->setStatusCode(405) + ->setJSON([ + 'status' => false, + 'message' => 'Invalid request method', + ]); + } + + $id = (int) $id; + + if (empty($id)) { + return $this->response->setStatusCode(400)->setJSON([ + 'status' => false, + 'message' => 'Invalid expense id', + ]); + } + + $payload = [ + 'is_active' => 0, + ]; + + $success = $this->expenseModel->update($id, $payload); + + return $this->response + ->setStatusCode($success ? 200 : 400) + ->setJSON([ + 'status' => (bool) $success, + 'message' => $success + ? 'Expense deleted successfully' + : 'Unable to delete expense. Please try again.', + ]); + } catch (\Throwable $e) { + return handle_exception($e, $this->myLogger, $this->response); + } + } + + /** + * Get policies by client for dropdown (AJAX) + */ + public function clientPolicies() + { + try { + $clientId = (int) ($this->request->getGet('client_id') ?? 0); + + if (empty($clientId)) { + return $this->response->setStatusCode(400)->setJSON([ + 'status' => false, + 'message' => 'Client is required', + ]); + } + + $policies = $this->clientPolicyModel + ->select('client_policy.id, client_policy.policy_no, policy_type.policy_type') + ->join('policy_type', 'policy_type.id = client_policy.policy_type_id AND policy_type.is_active = 1') + ->where('client_policy.client_id', $clientId) + ->where('client_policy.is_active', 1) + ->orderBy('client_policy.id', 'ASC') + ->findAll(); + + return $this->response->setStatusCode(200)->setJSON([ + 'status' => true, + 'data' => $policies, + ]); + } catch (\Throwable $e) { + return handle_exception($e, $this->myLogger, $this->response); + } + } +} + diff --git a/app/Controllers/TicketServiceController.php b/app/Controllers/TicketServiceController.php index f95f547d..8e96a780 100644 --- a/app/Controllers/TicketServiceController.php +++ b/app/Controllers/TicketServiceController.php @@ -30,8 +30,9 @@ use App\Models\TicketMasterModel; use PhpOffice\PhpSpreadsheet\IOFactory; use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PhpOffice\PhpSpreadsheet\Style\Fill; -class TicketServiceController extends BaseController +class TicketServiceController extends AdminController { use ResponseTrait; @@ -1840,39 +1841,86 @@ class TicketServiceController extends BaseController { } - - + + // -------------------------------------------------------------------------------------------------------------------------------- - - public function tpaClaimDumpImporter($params) + /** + * Resolve and validate Claim Dump file metadata and physical file for TPA imports. + * + * @param int|null $fileId + * @return array{status:bool,message?:string,fileData?:array,filePath?:string} + */ + private function resolveTpaClaimDumpFile(?int $fileId): array { - $file_id = $params['file_id'] ?? null; // Move outside try to ensure catch can see it + if (empty($fileId)) { + return [ + 'status' => false, + 'message' => 'File ID is missing', + ]; + } + + $fileData = $this->claimDumpFileModel->find((int) $fileId); + if (!$fileData) { + return [ + 'status' => false, + 'message' => 'Invalid file ID. No file data found', + ]; + } + + $filePath = WRITEPATH . 'uploads' . DIRECTORY_SEPARATOR . 'claim_dump_excel' . DIRECTORY_SEPARATOR . $fileData['file_name']; + + if (!is_file($filePath)) { + return [ + 'status' => false, + 'message' => 'Claim dump file not found', + ]; + } + + return [ + 'status' => true, + 'fileData' => $fileData, + 'filePath' => $filePath, + ]; + } + + /** + * First TPA-wise job – parse Excel and push data into TPA staging table. + * + * @param array $params + * @return array + */ + public function tpaClaimDumpImporter(array $params) + { + $file_id = isset($params['file_id']) ? (int) $params['file_id'] : null; try { - $file_path = WRITEPATH . 'uploads' . DIRECTORY_SEPARATOR . 'claim_dump_excel' . DIRECTORY_SEPARATOR; + $resolved = $this->resolveTpaClaimDumpFile($file_id); - if (!$file_id) { - return ['status' => false, 'message' => 'File ID is missing']; + if ($resolved['status'] === false) { + return [ + 'status' => false, + 'message' => $resolved['message'] ?? 'Unable to resolve claim dump file', + ]; } - $fileData = $this->claimDumpFileModel->find((int)$file_id); - if (!$fileData) { - return ['status' => false, 'message' => 'Invalid file ID. No file data found']; - } - - $file_full_path = $file_path . $fileData['file_name']; - if (!is_file($file_full_path)) { - return ['status' => false, 'message' => 'Claim dump file not found']; - } + $fileData = $resolved['fileData']; + $filePath = $resolved['filePath']; $handler = TpaClaimsImportFactory::make($fileData['tpa_id'] ?? 0); - $result = $handler->runTpaClaimDumpInsert($file_full_path, $file_id); + $result = $handler->runTpaClaimDumpInsert($filePath, $file_id); if (!empty($result['status']) && $result['status'] === true) { - Jobs::addJob(['job_name' => 'tpaClaimDumpToTicketMasterImporters', 'payload' => ['file_id' => $file_id]]); + Jobs::addJob([ + 'job_name' => 'tpaClaimDumpToTicketMasterImporters', + 'payload' => ['file_id' => $file_id], + ]); } else { // FORCE FAIL LOGIC - $this->markAsFailed($file_id, $result['message'] ?? 'System error contact admin', $fileData['created_by'] ?? null); + $this->markAsFailed( + $file_id, + $result['message'] ?? 'System error contact admin', + $fileData['created_by'] ?? null + ); } return $result; @@ -1884,9 +1932,9 @@ class TicketServiceController extends BaseController } return [ - 'status' => false, - 'message' => 'TPA Claim dump import failed', - 'error_data' => $th->getMessage() + 'status' => false, + 'message' => 'TPA Claim dump import failed', + 'error_data' => $th->getMessage(), ]; } } @@ -1908,30 +1956,37 @@ class TicketServiceController extends BaseController return $this->claimDumpFileModel->update($file_id, $data); } - public function tpaClaimDumpToTicketMasterImporters($params) + /** + * Second TPA-wise job – move data from TPA staging into ticket_master. + * + * @param array $params + * @return array + */ + public function tpaClaimDumpToTicketMasterImporters(array $params) { - $file_id = $params['file_id'] ?? null; + $file_id = isset($params['file_id']) ? (int) $params['file_id'] : null; $fileData = null; try { - if (!$file_id) { - return ['status' => false, 'message' => 'File ID is missing']; + $resolved = $this->resolveTpaClaimDumpFile($file_id); + + if ($resolved['status'] === false) { + return [ + 'status' => false, + 'message' => $resolved['message'] ?? 'Unable to resolve claim dump file', + ]; } - $fileData = $this->claimDumpFileModel->where('id', $file_id)->first(); - - if (!$fileData) { - return ['status' => false, 'message' => 'Invalid file ID. No file data found to import']; - } + $fileData = $resolved['fileData']; $handler = TpaClaimsImportFactory::make($fileData['tpa_id'] ?? 0); - $result = $handler->runTicketMasterInsert($params); + $result = $handler->runTicketMasterInsert($params); if (!empty($result['status']) && $result['status'] === true) { // Success: Update the status to success $this->claimDumpFileModel->update($file_id, [ 'status' => 'success', - 'reason' => null + 'reason' => null, ]); } else { // Logic failure: The runTicketMasterInsert returned status false @@ -1957,8 +2012,8 @@ class TicketServiceController extends BaseController 'message' => $th->getMessage(), 'error_data' => [ 'line' => $th->getLine(), - 'file' => $th->getFile() - ] + 'file' => $th->getFile(), + ], ]; } } @@ -2011,5 +2066,176 @@ class TicketServiceController extends BaseController return $data; } - + public function getTpaClaimDumpErrorData($file_id) + { + $file_id = (int) $file_id; + + // Ensure we always have a Response object, even if controller was instantiated manually + $response = $this->response ?? service('response'); + + if ($file_id <= 0) { + return $response + ->setStatusCode(ResponseInterface::HTTP_BAD_REQUEST) + ->setJSON(['status' => false, 'message' => 'Invalid file id']); + } + + $fileData = $this->claimDumpFileModel->find($file_id); + + if (!$fileData) { + return $response + ->setStatusCode(ResponseInterface::HTTP_NOT_FOUND) + ->setJSON(['status' => false, 'message' => 'File record not found']); + } + + $tpaId = (int) ($fileData['tpa_id'] ?? 0); + + if ($tpaId <= 0) { + return $response + ->setStatusCode(ResponseInterface::HTTP_BAD_REQUEST) + ->setJSON(['status' => false, 'message' => 'TPA not linked with this file']); + } + + // Resolve TPA staging table based on configured TPA IDs + $tableName = match ($tpaId) { + (int) env('VIDAL_PRIMARY_KEY_CONSTANT') => 'claims_dump_vidal', + (int) env('ABHI_PRIMARY_KEY_CONSTANT') => 'claims_dump_abhi', + (int) env('MEDI_ASSIST_PRIMARY_KEY_CONSTANT') => 'claims_dump_medi_assist', + (int) env('FHPL_PRIMARY_KEY_CONSTANT') => 'claims_dump_fhpl', + (int) env('R_CARE_PRIMARY_KEY_CONSTANT') => 'claims_dump_reliance', + (int) env('ICICI_PRIMARY_KEY_CONSTANT') => 'claims_dump_icici', + default => null, + }; + + if ($tableName === null) { + return $response + ->setStatusCode(ResponseInterface::HTTP_BAD_REQUEST) + ->setJSON(['status' => false, 'message' => 'Unsupported TPA for error dump export']); + } + + $db = \Config\Database::connect(); + $builder = $db->table($tableName); + + // Fetch only records belonging to this file and having a rejection reason + $rows = $builder + ->where('file_id', $file_id) + ->where('is_active', 1) + ->where('master_reject_reason IS NOT NULL', null, false) + ->get() + ->getResultArray(); + + if (empty($rows)) { + // No error records – return a small Excel file with just a message + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setTitle('Errors'); + $sheet->setCellValue('A1', 'Message'); + $sheet->setCellValue('A2', 'No rejected records found for this file.'); + + $writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); + ob_start(); + $writer->save('php://output'); + $excelOutput = ob_get_clean(); + + $filename = 'tpa_claim_dump_errors_' . $file_id . '.xlsx'; + + return $response + ->setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') + ->setHeader('Content-Disposition', 'attachment; filename="' . $filename . '"') + ->setHeader('Cache-Control', 'max-age=0') + ->setBody($excelOutput); + } + + // Columns that must NOT be included in the Excel export + $excludedColumns = [ + 'id', + 'client_id', + 'client_policy_id', + 'ticket_id', + 'file_id', + 'created_at', + 'updated_at', + 'created_by', + 'updated_by', + 'is_active', + ]; + + $firstRow = $rows[0]; + + // Build header mapping and track the "Rejected Reason" column index + $dbColumnOrder = []; + $displayHeaderLabels = []; + $rejectedReasonColIdx = null; // 1-based index for Excel column + + foreach ($firstRow as $columnName => $_) { + if (in_array($columnName, $excludedColumns, true)) { + continue; + } + + $dbColumnOrder[] = $columnName; + + if ($columnName === 'master_reject_reason' || $columnName === 'master_rejection_reason_key') { + $displayHeaderLabels[] = 'Rejected Reason'; + $rejectedReasonColIdx = count($displayHeaderLabels); // current column index (1-based) + } else { + $displayHeaderLabels[] = $columnName; + } + } + + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setTitle('Errors'); + + // Header row + $rowIndex = 1; + foreach ($displayHeaderLabels as $colIndex => $headerLabel) { + $columnLetter = Coordinate::stringFromColumnIndex($colIndex + 1); + $cellAddress = $columnLetter . $rowIndex; + $sheet->setCellValue($cellAddress, $headerLabel); + } + + // Data rows + $rowIndex = 2; + foreach ($rows as $row) { + foreach ($dbColumnOrder as $i => $columnName) { + $colIndex = $i + 1; + $columnLetter = Coordinate::stringFromColumnIndex($colIndex); + $cellAddress = $columnLetter . $rowIndex; + $value = $row[$columnName] ?? null; + + $sheet->setCellValue($cellAddress, $value); + + // Highlight the "Rejected Reason" values + if ($rejectedReasonColIdx !== null && $colIndex === $rejectedReasonColIdx) { + $sheet->getStyle($cellAddress) + ->getFill() + ->setFillType(Fill::FILL_SOLID) + ->getStartColor() + ->setARGB('FFFFF4B2'); // light yellow + } + } + + $rowIndex++; + } + + // Autosize columns for better readability + $highestColumnIndex = count($displayHeaderLabels); + for ($col = 1; $col <= $highestColumnIndex; $col++) { + $columnLetter = Coordinate::stringFromColumnIndex($col); + $sheet->getColumnDimension($columnLetter)->setAutoSize(true); + } + + $writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); + ob_start(); + $writer->save('php://output'); + $excelOutput = ob_get_clean(); + + $filename = 'tpa_claim_dump_errors_' . $file_id . '.xlsx'; + + return $response + ->setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') + ->setHeader('Content-Disposition', 'attachment; filename="' . $filename . '"') + ->setHeader('Cache-Control', 'max-age=0') + ->setBody($excelOutput); + } + } diff --git a/app/Libraries/TPAClaimsImportServices/AbhiClaimImportService.php b/app/Libraries/TPAClaimsImportServices/AbhiClaimImportService.php index ffb4b970..103989bf 100644 --- a/app/Libraries/TPAClaimsImportServices/AbhiClaimImportService.php +++ b/app/Libraries/TPAClaimsImportServices/AbhiClaimImportService.php @@ -119,6 +119,19 @@ class AbhiClaimImportService extends BaseTpaClaimImportService 'Settled' => 11, 'Rejected' => 8, 'Cancelled' => 13, + 'Approved' => 9, + 'Closed' => 12, + 'Query' => 4, + 'Required Information' => 4, + 'In-Progress' => 5, + 'Paid' => 11, + 'Denied' => 8, + 'Cancelled' => 13, + 'Processed' => 61, + 'Information Awaited' => 4, + 'Denied Letter Sent' => 66, + 'Cashless Document Awaited' => 3, + 'RI Cancelled' => 13, ]; protected $dateColumns = [ @@ -164,14 +177,30 @@ class AbhiClaimImportService extends BaseTpaClaimImportService } - public function updateTicketIdInTPATable(): bool + public function updateTicketIdInTPATable(int $fileId): bool { - if (empty($data)) { - return false; + $rows = $this->db->table('claims_dump_abhi cd') + ->select('cd.id, tm.id AS ticket_id') + ->join('ticket_master tm', 'tm.claim_dump_ref_id = cd.id AND tm.file_id = cd.file_id', 'inner') + ->where('cd.is_active', 1) + ->where('cd.file_id', $fileId) + ->where('cd.ticket_id IS NULL') + ->get() + ->getResultArray(); + + if (empty($rows)) { + return true; } - $builder = $this->db->table('claims_dump_abhi'); - return $builder->updateBatch($data, 'id'); + $updateData = []; + foreach ($rows as $row) { + $updateData[] = [ + 'id' => $row['id'], + 'ticket_id' => $row['ticket_id'], + ]; + } + + return $this->db->table('claims_dump_abhi')->updateBatch($updateData, 'id') !== false; } @@ -329,7 +358,7 @@ class AbhiClaimImportService extends BaseTpaClaimImportService } // Meta fields - $item['claim_status_id'] = $statusMapping[$row['claim_status']] ?? 61; + $item['claim_status_id'] = $this->checkStatusMapping($this->statusMapping, $row['claim_status']); $item['file_id'] = $file_id; $item['claim_dump_ref_id'] = $row['id']; $item['created_by'] = $file_data['created_by'] ?? null; diff --git a/app/Libraries/TPAClaimsImportServices/BaseTpaClaimImportService.php b/app/Libraries/TPAClaimsImportServices/BaseTpaClaimImportService.php index 3a6a5777..137caf25 100644 --- a/app/Libraries/TPAClaimsImportServices/BaseTpaClaimImportService.php +++ b/app/Libraries/TPAClaimsImportServices/BaseTpaClaimImportService.php @@ -81,7 +81,7 @@ abstract class BaseTpaClaimImportService $this->db->transBegin(); try { - $file_id = $params['file_id']; + $file_id = $params['file_id']; $ticketMasterData = $this->mapClaimMasterData($file_id); // Check if mapping failed @@ -101,6 +101,12 @@ abstract class BaseTpaClaimImportService $this->db->transRollback(); return ['status' => false, 'message' => 'Ticket Master Claim bulk insert failed']; } + // Map newly created ticket IDs back to the TPA staging table + if (!$this->updateTicketIdInTPATable($file_id)) { + $this->db->transRollback(); + return ['status' => false, 'message' => 'Updating ticket_id in TPA table failed']; + } + $message .= 'Ticket Master Claim bulk insert success. '; $hasExecutedTask = true; }else{ @@ -328,6 +334,16 @@ abstract class BaseTpaClaimImportService return $employeeData ?? []; } + public function checkStatusMapping($statusArray, $statusString) + { + foreach ($statusArray as $key => $value) { + if (strtolower($statusString) == strtolower($key) || strtolower($statusString) == strtolower(trim($key))) { + return $value; + } + } + return null; + } + /** * Map Excel rows to TPA table structure */ @@ -349,9 +365,9 @@ abstract class BaseTpaClaimImportService abstract protected function importClaimMaster(array $data): bool; /** - * Update TPA table with ticket_master primary key + * Update TPA table with ticket_master primary key for a given file. */ - abstract protected function updateTicketIdInTPATable(): bool; + abstract protected function updateTicketIdInTPATable(int $fileId): bool; /** * Update TPA table with ticket_master insert rejected reason diff --git a/app/Libraries/TPAClaimsImportServices/FhplClaimImportService.php b/app/Libraries/TPAClaimsImportServices/FhplClaimImportService.php index 324fd3a7..7123230c 100644 --- a/app/Libraries/TPAClaimsImportServices/FhplClaimImportService.php +++ b/app/Libraries/TPAClaimsImportServices/FhplClaimImportService.php @@ -181,6 +181,13 @@ class FhplClaimImportService extends BaseTpaClaimImportService protected $statusMapping = [ 'Settled' => 11, 'Rejected' => 8, + 'Paid' => 11, + 'Approved' => 9, + 'Closed' => 12, + 'Under Process' => 5, + 'Query' => 4, + 'Required Information' => 4, + 'In-Progress' => 5, ]; protected $dateColumns = [ @@ -235,14 +242,31 @@ class FhplClaimImportService extends BaseTpaClaimImportService } - public function updateTicketIdInTPATable(): bool + public function updateTicketIdInTPATable(int $fileId): bool { - if (empty($data)) { - return false; + // Join ticket_master with FHPL staging on file_id + claim_dump_ref_id -> id + $rows = $this->db->table('claims_dump_fhpl cd') + ->select('cd.id, tm.id AS ticket_id') + ->join('ticket_master tm', 'tm.claim_dump_ref_id = cd.id AND tm.file_id = cd.file_id', 'inner') + ->where('cd.is_active', 1) + ->where('cd.file_id', $fileId) + ->where('cd.ticket_id IS NULL') + ->get() + ->getResultArray(); + + if (empty($rows)) { + return true; } - - $builder = $this->db->table('claims_dump_fhpl'); - return $builder->updateBatch($data, 'id'); + + $updateData = []; + foreach ($rows as $row) { + $updateData[] = [ + 'id' => $row['id'], + 'ticket_id' => $row['ticket_id'], + ]; + } + + return $this->db->table('claims_dump_fhpl')->updateBatch($updateData, 'id') !== false; } @@ -400,7 +424,7 @@ class FhplClaimImportService extends BaseTpaClaimImportService } // Meta fields - $item['claim_status_id'] = $statusMapping[$row['current_claim_status']] ?? 61; + $item['claim_status_id'] = $this->checkStatusMapping($this->statusMapping, $row['current_claim_status']) ?? 61; $item['file_id'] = $file_id; $item['claim_dump_ref_id'] = $row['id']; $item['created_by'] = $file_data['created_by'] ?? null; diff --git a/app/Libraries/TPAClaimsImportServices/IciciClaimImportService.php b/app/Libraries/TPAClaimsImportServices/IciciClaimImportService.php index 132986a2..6e055139 100644 --- a/app/Libraries/TPAClaimsImportServices/IciciClaimImportService.php +++ b/app/Libraries/TPAClaimsImportServices/IciciClaimImportService.php @@ -106,7 +106,13 @@ class IciciClaimImportService extends BaseTpaClaimImportService protected $statusMapping = [ 'PAID' => 11, + 'SETTLED' => 11, 'REJECTED' => 8, + 'APPROVED' => 9, + 'CLOSED' => 12, + 'QUERY' => 4, + 'REQUIRED INFORMATION' => 4, + 'IN-PROGRESS' => 5, ]; protected $dateColumns = [ @@ -152,14 +158,30 @@ class IciciClaimImportService extends BaseTpaClaimImportService } - public function updateTicketIdInTPATable(): bool + public function updateTicketIdInTPATable(int $fileId): bool { - if (empty($data)) { - return false; + $rows = $this->db->table('claims_dump_icici cd') + ->select('cd.id, tm.id AS ticket_id') + ->join('ticket_master tm', 'tm.claim_dump_ref_id = cd.id AND tm.file_id = cd.file_id', 'inner') + ->where('cd.is_active', 1) + ->where('cd.file_id', $fileId) + ->where('cd.ticket_id IS NULL') + ->get() + ->getResultArray(); + + if (empty($rows)) { + return true; } - - $builder = $this->db->table('claims_dump_icici'); - return $builder->upsertBatch($data, 'id'); + + $updateData = []; + foreach ($rows as $row) { + $updateData[] = [ + 'id' => $row['id'], + 'ticket_id' => $row['ticket_id'], + ]; + } + + return $this->db->table('claims_dump_icici')->updateBatch($updateData, 'id') !== false; } @@ -287,7 +309,7 @@ class IciciClaimImportService extends BaseTpaClaimImportService $item['tpa_id'] = $client_policy_data['tpa_id'] ?? null; $item['acm_id'] = $client_policy_data['acm_id'] ?? null; $item['policy_no'] = $client_policy_data['policy_no'] ?? null; - $item['relationship'] = $this->convertRelation($row['relation_group'] ?? ''); + $item['relationship'] = $this->convertRelation($row['relation'] ?? ''); $employee_data = $this->getEmployeeDetails($file_data['client_id'], $file_data['client_policy_id'], $row['employee_member_id'], $item['relationship']); @@ -317,7 +339,7 @@ class IciciClaimImportService extends BaseTpaClaimImportService } // Meta fields - $item['claim_status_id'] = $statusMapping[$row['updated_status']] ?? 61; + $item['claim_status_id'] = $this->checkStatusMapping($this->statusMapping, $row['updated_status']) ?? 61; $item['claim_dump_ref_id'] = $row['id']; $item['file_id'] = $file_id; $item['created_by'] = $file_data['created_by'] ?? null; diff --git a/app/Libraries/TPAClaimsImportServices/MediAssistClaimImportService.php b/app/Libraries/TPAClaimsImportServices/MediAssistClaimImportService.php index d052c9f5..4a2952fb 100644 --- a/app/Libraries/TPAClaimsImportServices/MediAssistClaimImportService.php +++ b/app/Libraries/TPAClaimsImportServices/MediAssistClaimImportService.php @@ -158,12 +158,15 @@ class MediAssistClaimImportService extends BaseTpaClaimImportService protected $statusMapping = [ 'Settled' => 11, 'Rejected' => 8, + 'Paid' => 11, 'Denied' => 8, 'Cancelled' => 13, 'Processed' => 61, 'Information Awaited' => 4, 'Denied Letter Sent' => 66, 'Cashless Document Awaited' => 3, + 'Approved' => 9, + 'Closed' => 12, ]; /** @@ -192,16 +195,30 @@ class MediAssistClaimImportService extends BaseTpaClaimImportService } - public function updateTicketIdInTPATable(): bool + public function updateTicketIdInTPATable(int $fileId): bool { - if (empty($data)) { - return false; - } - - $builder = $this->db->table('claims_dump_medi_assist'); - $builder->insertBatch($data); + $rows = $this->db->table('claims_dump_medi_assist cd') + ->select('cd.id, tm.id AS ticket_id') + ->join('ticket_master tm', 'tm.claim_dump_ref_id = cd.id AND tm.file_id = cd.file_id', 'inner') + ->where('cd.is_active', 1) + ->where('cd.file_id', $fileId) + ->where('cd.ticket_id IS NULL') + ->get() + ->getResultArray(); - return true; + if (empty($rows)) { + return true; + } + + $updateData = []; + foreach ($rows as $row) { + $updateData[] = [ + 'id' => $row['id'], + 'ticket_id' => $row['ticket_id'], + ]; + } + + return $this->db->table('claims_dump_medi_assist')->updateBatch($updateData, 'id') !== false; } @@ -356,8 +373,8 @@ class MediAssistClaimImportService extends BaseTpaClaimImportService // Meta fields $item['claim_dump_ref_id'] = $row['id']; - $item['file_id'] = $file_id; - $item['claim_status_id'] = $statusMapping[$row['claim_status']] ?? 61; + $item['file_id'] = $file_id; + $item['claim_status_id'] = $this->checkStatusMapping($this->statusMapping, $row['claim_status']) ?? 61; $item['created_by'] = $file_data['created_by'] ?? null; $item['claim_type'] = 1; $item['priority'] = 1; diff --git a/app/Libraries/TPAClaimsImportServices/RcareClaimImportService.php b/app/Libraries/TPAClaimsImportServices/RcareClaimImportService.php index 770caadd..afe95c4f 100644 --- a/app/Libraries/TPAClaimsImportServices/RcareClaimImportService.php +++ b/app/Libraries/TPAClaimsImportServices/RcareClaimImportService.php @@ -96,14 +96,15 @@ class RcareClaimImportService extends BaseTpaClaimImportService ]; protected $statusMapping = [ + 'CL Paid with Settlement Letter' => 11, 'Settled' => 11, + 'Paid' => 11, 'Rejected' => 8, - 'Denied' => 8, - 'Cancelled' => 13, - 'Processed' => 61, - 'Information Awaited' => 4, - 'Denied Letter Sent' => 66, - 'Cashless Document Awaited' => 3, + 'Approved' => 9, + 'Closed' => 12, + 'CL Rejected' => 8, + 'CL Approved' => 9, + 'AL Closed' => 12, ]; protected $dateColumns = [ @@ -143,14 +144,30 @@ class RcareClaimImportService extends BaseTpaClaimImportService } - public function updateTicketIdInTPATable(): bool + public function updateTicketIdInTPATable(int $fileId): bool { - if (empty($data)) { - return false; + $rows = $this->db->table('claims_dump_reliance cd') + ->select('cd.id, tm.id AS ticket_id') + ->join('ticket_master tm', 'tm.claim_dump_ref_id = cd.id AND tm.file_id = cd.file_id', 'inner') + ->where('cd.is_active', 1) + ->where('cd.file_id', $fileId) + ->where('cd.ticket_id IS NULL') + ->get() + ->getResultArray(); + + if (empty($rows)) { + return true; } - - $builder = $this->db->table('claims_dump_reliance'); - return $builder->updateBatch($data, 'id'); + + $updateData = []; + foreach ($rows as $row) { + $updateData[] = [ + 'id' => $row['id'], + 'ticket_id' => $row['ticket_id'], + ]; + } + + return $this->db->table('claims_dump_reliance')->updateBatch($updateData, 'id') !== false; } @@ -307,7 +324,7 @@ class RcareClaimImportService extends BaseTpaClaimImportService } // Meta fields - $item['claim_status_id'] = $statusMapping[$row['final_status']] ?? 61; + $item['claim_status_id'] = $this->checkStatusMapping($this->statusMapping, $row['final_status']) ?? 61; $item['file_id'] = $file_id; $item['created_by'] = $file_data['created_by'] ?? null; $item['claim_dump_ref_id'] = $row['id']; diff --git a/app/Libraries/TPAClaimsImportServices/VidalClaimImportService.php b/app/Libraries/TPAClaimsImportServices/VidalClaimImportService.php index c90d580b..70a1340c 100644 --- a/app/Libraries/TPAClaimsImportServices/VidalClaimImportService.php +++ b/app/Libraries/TPAClaimsImportServices/VidalClaimImportService.php @@ -349,10 +349,17 @@ class VidalClaimImportService extends BaseTpaClaimImportService ]; protected $statusMapping = [ - 'CL Paid with Settlement Letter' => 11, - 'CL Rejected' => 8, - 'CL Approved' => 9, - 'AL Closed' => 12, + 'Settled' => 11, + 'Rejected' => 8, + 'Paid' => 11, + 'Denied' => 8, + 'Cancelled' => 13, + 'Processed' => 61, + 'Information Awaited' => 4, + 'Denied Letter Sent' => 66, + 'Cashless Document Awaited' => 3, + 'Approved' => 9, + 'Closed' => 12, ]; @@ -382,14 +389,30 @@ class VidalClaimImportService extends BaseTpaClaimImportService } - public function updateTicketIdInTPATable(): bool + public function updateTicketIdInTPATable(int $fileId): bool { - if (empty($data)) { - return false; + $rows = $this->db->table('claims_dump_vidal cd') + ->select('cd.id, tm.id AS ticket_id') + ->join('ticket_master tm', 'tm.claim_dump_ref_id = cd.id AND tm.file_id = cd.file_id', 'inner') + ->where('cd.is_active', 1) + ->where('cd.file_id', $fileId) + ->where('cd.ticket_id IS NULL') + ->get() + ->getResultArray(); + + if (empty($rows)) { + return true; } - - $builder = $this->db->table('claims_dump_vidal'); - return $builder->updateBatch($data, 'id'); + + $updateData = []; + foreach ($rows as $row) { + $updateData[] = [ + 'id' => $row['id'], + 'ticket_id' => $row['ticket_id'], + ]; + } + + return $this->db->table('claims_dump_vidal')->updateBatch($updateData, 'id') !== false; } @@ -542,7 +565,7 @@ class VidalClaimImportService extends BaseTpaClaimImportService } // Meta fields - $item['claim_status_id'] = $statusMapping[$row['claim_status']] ?? 61; + $item['claim_status_id'] = $this->checkStatusMapping($this->statusMapping, $row['claim_status']) ?? 61; $item['file_id'] = $file_id; $item['claim_dump_ref_id'] = $row['id']; $item['created_by'] = $file_data['created_by'] ?? null; diff --git a/app/Models/ExpenseModel.php b/app/Models/ExpenseModel.php new file mode 100644 index 00000000..7d4a37c1 --- /dev/null +++ b/app/Models/ExpenseModel.php @@ -0,0 +1,73 @@ + @@ -151,7 +221,16 @@