diff --git a/.env.sample b/.env.sample index 49343c62..50244384 100755 --- a/.env.sample +++ b/.env.sample @@ -129,4 +129,7 @@ HEALTH_INDIA_TOKEN_URL = HEALTH_INDIA_USERNAME = HEALTH_INDIA_PASSWORD = -HEALTH_INDIA_PRIMARY_KEY_CONSTANT = \ No newline at end of file +HEALTH_INDIA_PRIMARY_KEY_CONSTANT = + +# BDS Daily Report Emails Configuration +bds.dailyReportEmails = 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 37e7f840..cf5fe44e 100755 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -444,11 +444,17 @@ $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->get('croneDailyActivityReport', 'DashboardController::croneDailyActivityReport'); }); $routes->post("policy_tranction/sendInstallmentRemainderMail","PolicyTransactionController::sendInstallmentRemainderMail"); $routes->cli("cli/sendInstallmentRemainderMail","PolicyTransactionController::sendInstallmentRemainderMail"); +$routes->cli("cli/cronDailyBDSReport", "PolicyTransactionController::cronDailyBDSReport"); +$routes->cli('cli/croneDailyActivityReport', 'DashboardController::croneDailyActivityReport'); + + $routes->group("policy_tranction", ["filter" => "authMVC"], function ($routes) { @@ -496,6 +502,7 @@ $routes->group("policy_tranction", ["filter" => "authMVC"], function ($routes) { //$routes->post('failedStatement',"PolicyTransactionController::failedStatementList"); }); + $routes->get("cronDailyBDSReport", "PolicyTransactionController::cronDailyBDSReport"); }); $routes->group("leads", ["filter" => "authMVC"], function ($routes) { @@ -1011,4 +1018,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 24c0f2ec..ee3a8098 100755 --- a/app/Controllers/ClientController.php +++ b/app/Controllers/ClientController.php @@ -6969,16 +6969,23 @@ 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->tpaClaimDumpImporter(["file_id" => 48]); //vidal` + // $response = $ticketServiceController->tpaClaimDumpImporter(["file_id" => 57]); //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/DashboardController.php b/app/Controllers/DashboardController.php index 1dab325b..b4d224fe 100755 --- a/app/Controllers/DashboardController.php +++ b/app/Controllers/DashboardController.php @@ -7,6 +7,7 @@ use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; use Psr\Log\LoggerInterface; use App\Helpers\sendMailNotification; +use App\Helpers\MailHelper; use CodeIgniter\API\ResponseTrait; @@ -24,6 +25,11 @@ use App\Controllers\EmpDataServiceController; use App\Models\TicketMasterModel; use App\Models\LeadsModel; use App\Models\TicketClaimStatusModel; +use App\Models\FileModel; +use App\Models\BatchFileModel; +use App\Models\SalesActivityModel; +use App\Models\SalesActualLeadModel; + class DashboardController extends AdminController @@ -43,6 +49,13 @@ class DashboardController extends AdminController protected $policyStatus; protected $colorShades; protected $claimDashLimit; + protected $filesModel; + protected $batchFilesModel; + protected $leadsModel; + protected $ticketMasterModel; + protected $ticketClaimStatusModel; + protected $salesActivityModel; + protected $salesActualModel; protected $myLogger; @@ -59,6 +72,13 @@ class DashboardController extends AdminController $this->ticketModel = new TicketMasterModel(); $this->leadModel = new LeadsModel(); $this->ticketStatusModel = new TicketClaimStatusModel(); + $this->filesModel = new FileModel(); + $this->batchFilesModel = new BatchFileModel(); + $this->leadsModel = new LeadsModel(); + $this->ticketMasterModel = new TicketMasterModel(); + $this->ticketClaimStatusModel = new TicketClaimStatusModel(); + $this->salesActivityModel = new SalesActivityModel(); + $this->salesActualModel = new SalesActualLeadModel(); $this->myLogger = \Config\Services::mylogger(); @@ -787,4 +807,298 @@ class DashboardController extends AdminController $data['ticket_type_id'] = $ticketTypeId; return $this->respond(['status' => "success", "data" => $data], 200); } + + public function croneDailyActivityReport() + { + + // $today = date('Y-m-d'); + $today = date('Y-m-d', strtotime('-1 day')); + + // Inception & endorsement counts (file uploads) + $total_inception_count = $this->filesModel + ->where('is_active', 1) + ->where('DATE(created_at)', $today) + ->where('action', 'inception') + ->where('status', 'success') + ->countAllResults(); + + $total_endorsement_count = $this->filesModel + ->where('is_active', 1) + ->where('DATE(created_at)', $today) + ->where('action !=', 'inception') + ->where('status', 'success') + ->countAllResults(); + + // TPA & Insurer batch file counts + $total_tpa_incetion_count = $this->batchFilesModel + ->where('is_active', 1) + ->where('DATE(created_at)', $today) + ->where('event_type', 'inception') + ->where('insurer_or_tpa', 'tpa') + ->where('status', 'success') + ->countAllResults(); + + $total_tpa_endorsement_count = $this->batchFilesModel + ->where('is_active', 1) + ->where('DATE(created_at)', $today) + ->where('event_type !=', 'inception') + ->where('insurer_or_tpa', 'tpa') + ->where('status', 'success') + ->countAllResults(); + + $total_insurer_incetion_count = $this->batchFilesModel + ->where('is_active', 1) + ->where('DATE(created_at)', $today) + ->where('event_type', 'inception') + ->where('insurer_or_tpa', 'insurer') + ->where('status', 'success') + ->countAllResults(); + + $total_insurer_endorsement_count = $this->batchFilesModel + ->where('is_active', 1) + ->where('DATE(created_at)', $today) + ->where('event_type !=', 'inception') + ->where('insurer_or_tpa', 'insurer') + ->where('status', 'success') + ->countAllResults(); + + // Claim counts + $total_claim_count = $this->ticketMasterModel + ->where('is_active', 1) + ->where('DATE(created_at)', $today) + ->countAllResults(); + + $total_gmc_status_wise_claim_count = $this->ticketMasterModel + ->select('tcs.claim_status, count(*) as count') + ->join('ticket_claim_status tcs', 'tcs.id = ticket_master.claim_status_id', 'left') + ->where('ticket_master.is_active', 1) + ->where('ticket_master.ticket_type_id', 1) + // ->where('DATE(ticket_master.created_at)', $today) + ->where('tcs.is_active', 1) + ->groupBy('tcs.claim_status') + ->findAll(); + + $total_gpa_status_wise_claim_count = $this->ticketMasterModel + ->select('tcs.claim_status, count(*) as count') + ->join('ticket_claim_status tcs', 'tcs.id = ticket_master.claim_status_id', 'left') + ->where('ticket_master.is_active', 1) + ->where('ticket_master.ticket_type_id', 2) + // ->where('DATE(ticket_master.created_at)', $today) + ->where('tcs.is_active', 1) + ->groupBy('tcs.claim_status') + ->findAll(); + + $total_edli_status_wise_claim_count = $this->ticketMasterModel + ->select('tcs.claim_status, count(*) as count') + ->join('ticket_claim_status tcs', 'tcs.id = ticket_master.claim_status_id', 'left') + ->where('ticket_master.is_active', 1) + ->where('ticket_master.ticket_type_id', 3) + // ->where('DATE(ticket_master.created_at)', $today) + ->where('tcs.is_active', 1) + ->groupBy('tcs.claim_status') + ->findAll(); + + $total_gtli_status_wise_claim_count = $this->ticketMasterModel + ->select('tcs.claim_status, count(*) as count') + ->join('ticket_claim_status tcs', 'tcs.id = ticket_master.claim_status_id', 'left') + ->where('ticket_master.is_active', 1) + ->where('ticket_master.ticket_type_id', 4) + // ->where('DATE(ticket_master.created_at)', $today) + ->where('tcs.is_active', 1) + ->groupBy('tcs.claim_status') + ->findAll(); + + // Sales / lead counts + $total_opportunity_count = $this->leadsModel + ->where('is_active', 1) + ->where('DATE(created_at)', $today) + ->countAllResults(); + + $total_rfq_created_count = $this->leadsModel + ->where('is_active', 1) + ->where('DATE(created_at)', $today) + ->where('status', 'rfq_created') + ->countAllResults(); + + $total_rfq_insurer_send_count = $this->leadsModel + ->where('is_active', 1) + ->where('DATE(created_at)', $today) + ->where('status', 'rfq_sent') + ->countAllResults(); + + $total_qcr_created_count = $this->leadsModel + ->where('is_active', 1) + ->where('DATE(created_at)', $today) + ->where('status', 'qcr_created') + ->countAllResults(); + + $total_qcr_client_send_count = $this->leadsModel + ->where('is_active', 1) + ->where('DATE(created_at)', $today) + ->where('status', 'qcr_sent') + ->countAllResults(); + + $total_placement_count = $this->leadsModel + ->where('is_active', 1) + ->where('DATE(created_at)', $today) + ->where('status', 'won') + ->countAllResults(); + + $total_activity_count = $this->salesActivityModel + ->where('DATE(created_at)', $today) + ->countAllResults(); + + $total_lead_count = $this->salesActualModel + ->where('DATE(created_at)', $today) + ->countAllResults(); + + $total_bds_count = $this->policyTransactionModel + ->select('pt_co_share_details.*') + ->join('pt_co_share_details', 'policy_transaction.id = pt_co_share_details.pt_id') + ->where('DATE(policy_transaction.created_at)', $today) + ->where('pt_co_share_details.is_active', 1) + ->where('policy_transaction.is_active', 1) + ->countAllResults(); + + $total_bds_policy_wise_count = $this->policyTransactionModel + ->select('pt_co_share_details.*') + ->join('pt_co_share_details', 'policy_transaction.id = pt_co_share_details.pt_id') + ->where('DATE(policy_transaction.created_at)', $today) + ->where('pt_co_share_details.is_active', 1) + ->where('policy_transaction.action_type', 'inception') + ->where('policy_transaction.is_active', 1) + ->countAllResults(); + + + $total_bds_endorsement_wise_count = $this->policyTransactionModel + ->select('pt_co_share_details.*') + ->join('pt_co_share_details', 'policy_transaction.id = pt_co_share_details.pt_id') + ->where('DATE(policy_transaction.created_at)', $today) + ->where('pt_co_share_details.is_active', 1) + ->where('policy_transaction.action_type !=', 'inception') + ->where('policy_transaction.is_active', 1) + ->countAllResults(); + + $total_bds_policy_type_wise_count = $this->policyTransactionModel + ->select('policy_type.policy_type, count(*) as count') + ->join('pt_co_share_details', 'policy_transaction.id = pt_co_share_details.pt_id') + ->join('policy_type', 'policy_transaction.policy_type_id = policy_type.id') + ->where('DATE(policy_transaction.created_at)', $today) + ->where('pt_co_share_details.is_active', 1) + ->where('policy_transaction.is_active', 1) + ->groupBy('policy_transaction.policy_type_id') + ->findAll(); + + // Connect Pre DB for Employee Enrollment Count + + $db2 = \Config\Database::connect('preDB'); + + $builder = $db2->table('employees'); + $total_employee_draft_count = $builder->join('employee_polices', 'employees.id = employee_polices.employee_id') + ->select('employee_polices.status, count(*) as count') + ->where('DATE(employee_polices.created_at)', $today) + ->where('employee_polices.is_active', 1) + ->where('employees.is_active', 1) + ->whereIn('employee_polices.status', ['draft']) + ->groupBy('employee_polices.status') + ->countAllResults(); + + + $builder1 = $db2->table('employees'); + $total_employee_enrolled_count = $builder1->join('employee_polices', 'employees.id = employee_polices.employee_id') + ->select('employee_polices.status, count(*) as count') + ->where('DATE(employee_polices.created_at)', $today) + ->where('employee_polices.is_active', 1) + ->where('employees.is_active', 1) + ->whereIn('employee_polices.status', ['enrolled']) + ->groupBy('employee_polices.status') + ->countAllResults(); + + $builder2 = $db2->table('files'); + $total_open_for_enrollemnt_policy_count = $builder2 + ->select('COUNT(*) as count') + ->where('enrollment_open_date <=', $today) + ->where('enrollment_close_date >=', $today) + ->where('is_active', 1) + ->where('status', 'success') + ->countAllResults(); + + $data = [ + 'total_inception_count' => $total_inception_count, + 'total_endorsement_count' => $total_endorsement_count, + 'total_tpa_incetion_count' => $total_tpa_incetion_count, + 'total_tpa_endorsement_count' => $total_tpa_endorsement_count, + 'total_insurer_incetion_count' => $total_insurer_incetion_count, + 'total_insurer_endorsement_count' => $total_insurer_endorsement_count, + 'total_claim_count' => $total_claim_count, + 'total_gmc_status_wise_claim_count' => $total_gmc_status_wise_claim_count, + 'total_gpa_status_wise_claim_count' => $total_gpa_status_wise_claim_count, + 'total_edli_status_wise_claim_count' => $total_edli_status_wise_claim_count, + 'total_gtli_status_wise_claim_count' => $total_gtli_status_wise_claim_count, + 'total_opportunity_count' => $total_opportunity_count, + 'total_rfq_created_count' => $total_rfq_created_count, + 'total_rfq_insurer_send_count' => $total_rfq_insurer_send_count, + 'total_qcr_created_count' => $total_qcr_created_count, + 'total_qcr_client_send_count' => $total_qcr_client_send_count, + 'total_placement_count' => $total_placement_count, + 'total_activity_count' => $total_activity_count, + 'total_lead_count' => $total_lead_count, + 'total_bds_count' => $total_bds_count, + 'total_bds_policy_wise_count' => $total_bds_policy_wise_count, + 'total_bds_endorsement_wise_count' => $total_bds_endorsement_wise_count, + 'total_bds_policy_type_wise_count' => $total_bds_policy_type_wise_count, + 'total_employee_draft_count' => $total_employee_draft_count, + 'total_employee_enrolled_count' => $total_employee_enrolled_count, + 'total_open_for_enrollemnt_policy_count' => $total_open_for_enrollemnt_policy_count, + ]; + + // dd($data); + + $today = date('d-m-Y', strtotime($today)); + $data['today'] = $today; + + // Render the HTML email using the dedicated view + $message = view('daily_report_email_template', $data); + // return $message; + + // Recipients: prefer dedicated env, fallback to BDS report emails if not set + $emailList = getenv('activity.dailyReportEmails') ?: getenv('bds.dailyReportEmails') ?: ''; + $recipientEmails = array_filter(array_map('trim', explode(',', $emailList))); + + if (empty($recipientEmails)) { + $this->myLogger->logme('error', 'croneDailyActivityReport: No recipients configured (set activity.dailyReportEmails in .env).'); + return $this->respond([ + 'status' => 'success', + 'message' => 'Daily activity data prepared but no recipients configured.', + 'data' => $data, + ], 200); + } + + $subject = "Daily Activity Report - {$today}"; + + $res = MailHelper::send_email([ + 'mail' => $recipientEmails, + 'subject' => $subject, + 'message' => $message, + ]); + + $resDecoded = is_string($res) ? json_decode($res, true) : $res; + + if (isset($resDecoded['status']) && $resDecoded['status'] === 'success') { + $this->myLogger->logme('error', 'croneDailyActivityReport: Report email sent to ' . count($recipientEmails) . ' recipients'); + return $this->respond([ + 'status' => true, + 'message' => 'Daily activity report emailed successfully.', + 'recipients' => count($recipientEmails), + ], 200); + } + + $this->myLogger->logme('error', 'croneDailyActivityReport: Email send failed - ' . json_encode($resDecoded)); + return $this->respond([ + 'status' => false, + 'message' => 'Daily activity data prepared but email send failed.', + 'data' => $data, + ], 500); + } + } diff --git a/app/Controllers/EmployeeRestController.php b/app/Controllers/EmployeeRestController.php index 3a0bea5b..df5dcfab 100755 --- a/app/Controllers/EmployeeRestController.php +++ b/app/Controllers/EmployeeRestController.php @@ -2418,6 +2418,11 @@ class EmployeeRestController extends AdminController ["ticket_type" => "4", "type_name" => "GTLI"], ]; + $claim_type = $this->ticketController->claimType; + unset($claim_type[1][2]); + unset($claim_type[1][4]); + $data['claim_type'] = $claim_type; + return $this->respond(['status' => (count($data) ? 'success' : 'failed'), 'code' => (count($data) ? 200 : 404), 'data' => $data], 200); } @@ -2581,61 +2586,90 @@ class EmployeeRestController extends AdminController $required_docs = $this->ticketMaster->select('required_docs')->where('id', $ticket_id)->first(); $data['required_docs'] = json_decode($required_docs['required_docs'] ?? '{}', true) ?? []; - // $ticketData = $data['ticket_data']; - // $ticketHistory = $data['ticket_history']; - // print_r($ticketHistory); die; + // print_rr($data['ticket_history']); die; + + $filteredArray = array_filter($data['ticket_history'], function($item) { + return $item['field_name'] == 'claim_status_id'; + }); + + $filteredArray = array_reverse(array_values($filteredArray)); $currentClaimStatus = $this->claimStatusModel->select("claim_status")->where('id', $data['claims_data']['claim_status_id'])->where('is_active', 1)->first(); $ticketClaimStatus = $this->claimStatusModel->select('claim_status, display_name')->where('display_name is not null')->where('ticket_type', $data['claims_data']['ticket_type_id'])->where('is_active', 1)->findAll(); $status_list = array_column($ticketClaimStatus, 'display_name', 'claim_status'); - // print_r($currentClaimStatus); die; + // dd($filteredArray, $status_list); die; - $data['ticket_data'] = array_fill_keys(array_keys($data['ticket_data']), []); + $filtered_history = []; + $counter = 0; + + foreach ($filteredArray as $key => $value) { + foreach ($status_list as $status => $display_name) { + if ($value['new_value'] == $status) { - // Loop through ticket_data - foreach ($data['ticket_data'] as $status => &$fields) { - // Search ticket_history for matching old_status_value - foreach ($data['ticket_history'] as $history) { + if($display_name == 'Under Process'){ + $unique_key = $display_name . str_repeat("\u{200B}", $counter++); + }else{ + $unique_key = $display_name; + } - if ($history['old_status_value'] === $status) { - // Attach modified_by and created_at - // $fields['modified_by'] = $history['modified_by']; - $fields['modified_by'] = ""; - $fields['modified_at'] = date('d-m-Y h:i A', strtotime($history['created_at'])); - // Break after first match (assuming latest entry is enough) - break; + $filtered_history[$unique_key] = [ + 'modified_by' => "", + 'modified_at' => date('d-m-Y h:i A', strtotime($value['created_at'])), + ]; } } } + // dd($filteredArray, $status_list, $filtered_history); die; - $data['ticket_data'][$currentClaimStatus['claim_status']]['modified_by'] = ""; - $data['ticket_data'][$currentClaimStatus['claim_status']]['modified_at'] = $formatted = date('d-m-Y h:i A', strtotime($data['claims_data']['updated_at'])); - $new_ticket_data = []; - foreach ($data['ticket_data'] as $oldKey => $value) { + // $data['ticket_data'] = array_fill_keys(array_keys($data['ticket_data']), []); - // Only process if key exists in status_list - if (! isset($status_list[$oldKey])) { - continue; // skip and do NOT add to new array - } + // // Loop through ticket_data + // foreach ($data['ticket_data'] as $status => &$fields) { + // // Search ticket_history for matching old_status_value + // foreach ($data['ticket_history'] as $history) { - // Get new key based on mapping - $newKey = $status_list[$oldKey]; + // if ($history['old_status_value'] === $status) { + // // Attach modified_by and created_at + // // $fields['modified_by'] = $history['modified_by']; + // $fields['modified_by'] = ""; + // $fields['modified_at'] = date('d-m-Y h:i A', strtotime($history['created_at'])); + // // Break after first match (assuming latest entry is enough) + // break; + // } + // } + // } - // Avoid duplicates - if (! isset($new_ticket_data[$newKey])) { - $new_ticket_data[$newKey] = $value; - } - } + // $data['ticket_data'][$currentClaimStatus['claim_status']]['modified_by'] = ""; + // $data['ticket_data'][$currentClaimStatus['claim_status']]['modified_at'] = $formatted = date('d-m-Y h:i A', strtotime($data['claims_data']['updated_at'])); - uasort($new_ticket_data, function ($a, $b) { - $timeA = \DateTime::createFromFormat('d-m-Y h:i A', $a['modified_at']); - $timeB = \DateTime::createFromFormat('d-m-Y h:i A', $b['modified_at']); - return $timeA <=> $timeB; // Ascending - }); + // $new_ticket_data = []; - $data['ticket_data'] = $new_ticket_data; + // foreach ($data['ticket_data'] as $oldKey => $value) { + + // // Only process if key exists in status_list + // if (! isset($status_list[$oldKey])) { + // continue; // skip and do NOT add to new array + // } + + // // Get new key based on mapping + // $newKey = $status_list[$oldKey]; + + // // Avoid duplicates + // if (! isset($new_ticket_data[$newKey])) { + // $new_ticket_data[$newKey] = $value; + // } + // } + + // uasort($new_ticket_data, function ($a, $b) { + // $timeA = \DateTime::createFromFormat('d-m-Y h:i A', $a['modified_at']); + // $timeB = \DateTime::createFromFormat('d-m-Y h:i A', $b['modified_at']); + // return $timeA <=> $timeB; // Ascending + // }); + + // $data['ticket_data'] = $new_ticket_data; + $data['ticket_data'] = $filtered_history; $ticketMesssageModel = new TicketMessageModel(); $ticket_message = $ticketMesssageModel @@ -5451,7 +5485,7 @@ class EmployeeRestController extends AdminController 'dashboard' => $database_id, ], 'exp' => time() + (10 * 60), - 'params' => (object) [], + 'params' => (object) ['client_policy' => $policy_id ], // MUST be object for Metabase ]; $token = JWT::encode($payload, $METABASE_SECRET_KEY, 'HS256'); diff --git a/app/Controllers/ExpenseController.php b/app/Controllers/ExpenseController.php new file mode 100644 index 00000000..c734284d --- /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('id', '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') + ->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/PolicyTransactionController.php b/app/Controllers/PolicyTransactionController.php index 59c115fe..44ed315b 100644 --- a/app/Controllers/PolicyTransactionController.php +++ b/app/Controllers/PolicyTransactionController.php @@ -6098,8 +6098,189 @@ class PolicyTransactionController extends BaseController return array('status' => false, 'message' => 'file_id is required'); } - $this->policyTransactionModel->select()->findAll(); + $data['is_active'] = 0; + $this->policyTransactionModel->where('file_id', $file_id)->set($data)->update(); + $this->PTCOShareDetailsModel->where('file_id', $file_id)->set($data)->update(); + return $this->respond(['status' => true, 'code' => 200, 'message' => 'BDS bulk upload data truncated successfully'], 200); + } + + // ------------------------------------------------------------------------------------------------------------------ + + /** + * Cron job: Daily BDS Report + * Fetches today's BDS entries, generates Excel (matching report_bds.php column order), and emails to recipients from .env. + * Recipients: comma-separated emails in bds.reportEmails + * File stored temporarily in writable/tmp/ and deleted after sending. + */ + public function cronDailyBDSReport() + { + + helper('excel_import_export_helper'); + + $filePath = null; + try { + + $today = date('Y-m-d', strtotime('-1 day')); + $reportList = $this->policyTransactionModel->getBDSReportList($today, $today,0,0,0,'created_at',0,0,0,0,0,[]); + + if (empty($reportList)) { + $this->myLogger->logme('error', "cronDailyBDSReport: No BDS records for {$today}"); + return $this->respond(['status' => 'success', 'message' => 'No BDS records for today. No report sent.'], 200); + } + + // Column headers exactly matching report_bds.php order (including display:none columns) + $headers = [ + 'S. No', 'User', 'Month', 'Business Type', 'Client Type', 'Insured Name', 'Transaction Type', + 'Policy Type', 'BAP Group', 'Vehicle Number', 'Policy No', 'Endorsement No', 'Insurer Branch', + 'Endorsement Effective Date', 'Policy Effective Date', 'Policy Expiry Date', 'Reference', 'Remarks', + 'BP Premium', 'TP Premium', 'Premium (without GST)', 'Total Premium', 'Agreed BP %', 'Agreed TP %', + 'Rewards', 'Agreed Amount', 'Invoiced Amount', 'Outstanding Amount', + 'Salse Person', 'Service Person', 'Salse Person Branch', 'Installment', 'Data Received Date', 'Renewal Date', + 'Co-Premium', 'Remuneration Pay By Leader', 'Salse Person Manager', 'Service Person Manager', + 'Service Person Branch', 'Rollover Date', 'Policyholder Name', 'Insured (Same as Proposer)', + 'Follower Policy No', 'Co-Share %', 'Non Commissional Premium Amount', 'CGST', 'SGST', 'IGST', + 'Stamp Duty', 'Standard BP %', 'Standard TP %', 'Actual BP Amount', 'Actual TP Amount', + 'Actual BP %', 'Actual TP %', 'Actual BP Remuneration Amount', 'Actual TP Remuneration Amount', + 'CD Account No' + ]; + + $excelData = []; + foreach ($reportList as $idx => $row) { + $totalIrda = (float)($row['total_irda_amt'] ?? 0); + $billedAmt = (float)($row['billed_amt'] ?? 0); + $unbilledAmt = $totalIrda - $billedAmt; + if ($totalIrda == 0) { + $unbilledAmt = abs($unbilledAmt); + } + $unbilledAmt = ($unbilledAmt == 0 && $billedAmt == 0) ? $totalIrda : $unbilledAmt; + + $hasIrda = ($totalIrda != 0); + + $excelData[] = [ + $idx + 1, + $row['user_name'] ?? 'N/A', + $row['policy_issue_month'] ?? 'N/A', + $row['revenue_type'] ?? 'N/A', + $row['client_type'] ?? 'N/A', + $row['client_name'] ?? 'N/A', + $row['action_type'] ?? 'N/A', + $row['policy_type'] ?? 'N/A', + $row['bap'] ?? 'N/A', + $row['vehicle_no'] ?? 'N/A', + $row['policy_no'] ?? 'N/A', + $row['endorsement_no'] ?? 'N/A', + $row['insurer_branch_name'] ?? 'N/A', + !empty($row['endorse_eff_date']) ? change_date_format($row['endorse_eff_date'], 'Y-m-d', 'd/m/Y') : 'N/A', + !empty($row['policy_start_date']) ? change_date_format($row['policy_start_date'], 'Y-m-d', 'd/m/Y') : 'N/A', + !empty($row['policy_end_date']) ? change_date_format($row['policy_end_date'], 'Y-m-d', 'd/m/Y') : 'N/A', + $row['ref'] ?? 'N/A', + $row['remarks'] ?? 'N/A', + $hasIrda ? ($row['bp_amt'] ?? '0.00') : '0.00', + $hasIrda ? ($row['tp_or_ter'] ?? '0.00') : '0.00', + $hasIrda ? ($row['premium_wo_gst'] ?? '0.00') : '0.00', + $hasIrda ? ($row['total_premium'] ?? '0.00') : '0.00', + $hasIrda ? ($row['agreed_bp_per'] ?? '0.00') . '%' : '0.00%', + $hasIrda ? ($row['agreed_tp_or_ter_per'] ?? '0.00') . '%' : '0.00%', + $row['reward'] ?? '0.00', + $row['total_irda_amt'] ?? '0.00', + !empty($row['billed_amt']) ? $row['billed_amt'] : '0.00', + number_format((float)$unbilledAmt, 2, '.', ''), + $row['salse_person_name'] ?? 'N/A', + $row['service_person_name'] ?? 'N/A', + $row['nhance_branch'] ?? 'N/A', + $row['installment'] ?? 'N/A', + !empty($row['data_received_date']) ? change_date_format($row['data_received_date'], 'Y-m-d', 'd/m/Y') : 'N/A', + !empty($row['renewal_date']) ? change_date_format($row['renewal_date'], 'Y-m-d', 'd/m/Y') : 'N/A', + $row['co_share'] ?? 'No', + $row['bro_payable_by'] ?? 'No', + $row['salse_manager_name'] ?? 'N/A', + $row['service_manager_name'] ?? 'N/A', + $row['service_branch'] ?? 'N/A', + !empty($row['rollover_date']) ? change_date_format($row['rollover_date'], 'Y-m-d', 'd/m/Y') : 'N/A', + $row['policy_holder_name'] ?? 'N/A', + $row['same_as_proposer'] ?? 'No', + $row['follower_policy_no'] ?? 'N/A', + number_format((float)($row['co_share_per'] ?? 0), 2), + number_format((float)($row['non_comm_per_amt'] ?? 0), 2), + number_format((float)($row['bp_cgst'] ?? 0), 2), + number_format((float)($row['bp_sgst'] ?? 0), 2), + number_format((float)($row['bp_igst'] ?? 0), 2), + number_format((float)($row['stamp_duty'] ?? 0), 2), + number_format((float)($row['standerd_bp_per'] ?? 0), 2), + number_format((float)($row['standerd_tp_per'] ?? 0), 2), + number_format((float)($row['actual_bp_amt'] ?? 0), 2), + number_format((float)($row['actual_tp_amt'] ?? 0), 2), + number_format((float)($row['actual_bp_per'] ?? 0), 2), + number_format((float)($row['actual_tp_per'] ?? 0), 2), + number_format((float)($row['actual_tep_brokerage_amt'] ?? 0), 2), + number_format((float)($row['actual_tp_brokerage_amt'] ?? 0), 2), + $row['cd_ac_no'] ?? 'N/A' + ]; + } + + $today = date('d-m-Y', strtotime($today)); + $fileName = 'BDS_Daily_Report_' . $today . '.xlsx'; + $tmpDir = WRITEPATH . 'tmp' . DIRECTORY_SEPARATOR; + if (!is_dir($tmpDir)) { + mkdir($tmpDir, 0755, true); + } + $filePath = $tmpDir . $fileName; + + $generated = generate_excel($headers, $excelData, $filePath); + if (!$generated) { + $this->myLogger->logme('error', 'cronDailyBDSReport: Excel generation failed'); + return $this->respond(['status' => false, 'message' => 'Excel generation failed'], 500); + } + + $emailList = getenv('bds.dailyReportEmails') ?: ''; + $recipientEmails = array_filter(array_map('trim', explode(',', $emailList))); + if (empty($recipientEmails)) { + @unlink($filePath); + $this->myLogger->logme('error', 'cronDailyBDSReport: No recipients in bds.dailyReportEmails. Report generated but not sent.'); + return $this->respond([ + 'status' => 'success', + 'message' => 'Report generated. No recipients configured (set bds.dailyReportEmails in .env).', + 'file' => $fileName + ], 200); + } + + $subject = "BDS Report Up to - {$today}"; + $message = "

Please find attached the BDS report up to {$today}.

"; + $message .= "

Total records: " . count($reportList) . "

"; + + $attachments = [ + ['filePath' => $filePath, 'fileName' => $fileName] + ]; + + $res = MailHelper::send_email([ + 'mail' => $recipientEmails, + 'subject' => $subject, + 'message' => $message, + 'attachments' => $attachments + ]); + + @unlink($filePath); + + $resDecoded = is_string($res) ? json_decode($res, true) : $res; + if (isset($resDecoded['status']) && $resDecoded['status'] === 'success') { + $this->myLogger->logme('error', "cronDailyBDSReport: Report sent to " . count($recipientEmails) . " recipients"); + return $this->respond([ + 'status' => true, + 'message' => 'Report generated and emailed successfully.', + 'recipients' => count($recipientEmails) + ], 200); + } + + $this->myLogger->logme('error', 'cronDailyBDSReport: Email send failed - ' . json_encode($resDecoded)); + return $this->respond(['status' => false, 'message' => 'Report generated but email send failed'], 500); + } catch (Exception $e) { + if ($filePath && is_file($filePath)) { + @unlink($filePath); + } + $this->myLogger->logme('error', 'cronDailyBDSReport Exception: ' . $e->getMessage()); + return $this->respond(['status' => false, 'message' => $e->getMessage()], 500); + } } } diff --git a/app/Controllers/TestingController.php b/app/Controllers/TestingController.php index daae4293..c83fe172 100644 --- a/app/Controllers/TestingController.php +++ b/app/Controllers/TestingController.php @@ -1097,10 +1097,12 @@ class TestingController extends BaseController 'dashboard' => $database_id ], 'exp' => time() + (10 * 60), // 10 minutes - 'params' => (object)[] + 'params' => (object) ['client_policy' => $policy_id ], // MUST be object for Metabase + ]; $token = JWT::encode($payload, $METABASE_SECRET_KEY, 'HS256'); + // dd($token); if ($this->request->getGet('api') == 1) { return $this->respond([ diff --git a/app/Controllers/TicketController.php b/app/Controllers/TicketController.php index 01109672..7c70ec06 100644 --- a/app/Controllers/TicketController.php +++ b/app/Controllers/TicketController.php @@ -1173,9 +1173,13 @@ class TicketController extends BaseController 'regex_match' => 'Policy Number can only contain letters, numbers, spaces, hyphens(-) underscores(_), and slashes(/).' ] ], - 'tpa_no' => ['label' => 'TPA ID','rules' => 'permit_empty|regex_match[/^[a-zA-Z0-9]+$/]','errors' => [ - 'regex_match' => 'TPA ID can only contain letters and numbers.' - ]], + 'tpa_no' => [ + 'label' => 'TPA ID', + 'rules' => 'permit_empty|regex_match[/^[a-zA-Z0-9\/]+$/]', + 'errors' => [ + 'regex_match' => 'TPA ID can only contain letters, numbers, and /.' + ] + ], 'emp_mobile' => ['label' => 'Employee Mobile No','rules' => 'required|numeric|exact_length[10]', 'errors' => [ 'required' => 'Mobile number is required', @@ -1522,9 +1526,13 @@ class TicketController extends BaseController 'regex_match' => 'Policy Number can only contain letters, numbers, spaces, hyphens(-) underscores(_), and slashes(/).' ] ], - 'tpa_no' => ['label' => 'TPA ID','rules' => 'permit_empty|regex_match[/^[a-zA-Z0-9]+$/]','errors' => [ - 'regex_match' => 'TPA ID can only contain letters and numbers.' - ]], + 'tpa_no' => [ + 'label' => 'TPA ID', + 'rules' => 'permit_empty|regex_match[/^[a-zA-Z0-9\/]+$/]', + 'errors' => [ + 'regex_match' => 'TPA ID can only contain letters, numbers, and /.' + ] + ], 'emp_mobile' => ['label' => 'Employee Mobile No','rules' => 'required|numeric|exact_length[10]', 'errors' => [ 'required' => 'Mobile number is required', 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/MyGoogleDrive.php b/app/Libraries/MyGoogleDrive.php index 566098eb..2fea0f9e 100644 --- a/app/Libraries/MyGoogleDrive.php +++ b/app/Libraries/MyGoogleDrive.php @@ -16,7 +16,7 @@ class MyGoogleDrive { $this->myLogger = \Config\Services::mylogger(); $this->client = new Google_Client(); - $this->client->setAuthConfig(ROOTPATH . 'nhance-app-google-drive.json'); // App credentials + $this->client->setAuthConfig(ROOTPATH . 'nhance-ee8d1-e3c5269b1ec7.json'); // App credentials // putenv('GOOGLE_APPLICATION_CREDENTIALS=' . ROOTPATH . 'nhance-app-google-drive.json'); $this->client->useApplicationDefaultCredentials(); 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..3ccbcadd 100644 --- a/app/Libraries/TPAClaimsImportServices/BaseTpaClaimImportService.php +++ b/app/Libraries/TPAClaimsImportServices/BaseTpaClaimImportService.php @@ -9,17 +9,30 @@ use App\Models\TicketMasterModel; use App\Models\EmployeeModel; use App\Models\ClaimDumpFileModel; use App\Models\ClaimsDumpFhplModel; +use App\Models\ClientPolicyModel; + use RuntimeException; abstract class BaseTpaClaimImportService { protected BaseConnection $db; protected $claimDumpFileModel; + protected $clientPolicyModel; + protected $policyNumberMapping; public function __construct() { $this->db = db_connect(); $this->claimDumpFileModel = new ClaimDumpFileModel(); + $this->clientPolicyModel = new ClientPolicyModel(); + $this->policyNumberMapping = [ + (int) env('VIDAL_PRIMARY_KEY_CONSTANT') => 'Insurer Policy Number', + (int) env('ABHI_PRIMARY_KEY_CONSTANT') => 'Policy Number', + (int) env('MEDI_ASSIST_PRIMARY_KEY_CONSTANT') => 'policy_no', + (int) env('FHPL_PRIMARY_KEY_CONSTANT') => 'Policy No', + (int) env('R_CARE_PRIMARY_KEY_CONSTANT') => 'Policy Number', + (int) env('ICICI_PRIMARY_KEY_CONSTANT') => 'POLICY_NO', + ]; } /** @@ -33,6 +46,8 @@ abstract class BaseTpaClaimImportService try { $fileData = $this->claimDumpFileModel->where('id', $fileId)->first(); + $client_policy_data = $this->clientPolicyModel->where('id', $fileData['client_policy_id'])->first(); + // Determine sheet name logic... if (env('FHPL_PRIMARY_KEY_CONSTANT') == $fileData['tpa_id']) { $rows = $this->readExcelBySheetName($filePath, 'Claims&Preauth'); @@ -47,6 +62,18 @@ abstract class BaseTpaClaimImportService return ['status' => false, 'message' => 'Excel file contains no data or wrong file upload']; } + if(isset($this->policyNumberMapping[$fileData['tpa_id']]) && !empty($this->policyNumberMapping[$fileData['tpa_id']])){ + $policy_number_column = $this->policyNumberMapping[$fileData['tpa_id']]; + }else{ + $policy_number_column = 'policy_no'; + } + + + if($client_policy_data['policy_no'] != ($rows[0][$policy_number_column] ?? '')){ + $this->db->transRollback(); + return ['status' => false, 'message' => 'Policy number mismatch in the file and in the system']; + } + $tpaInsertData = $this->mapTPAData($rows, $fileId); if (empty($tpaInsertData)) { @@ -81,7 +108,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 +128,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 +361,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 +392,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 @@ += '$startDate' "; $conditions .= " AND pcsd.pt_policy_issue_date <= '$endDate' "; + } else if ($date_type === "created_at") { + $conditions .= " AND pt.created_at <= '$endDate' "; } else { $conditions .= " AND pt.$date_type >= '$startDate' "; $conditions .= " AND pt.$date_type <= '$endDate' "; diff --git a/app/Views/claim_dump_file_list.php b/app/Views/claim_dump_file_list.php index 1330cded..9ba0451b 100644 --- a/app/Views/claim_dump_file_list.php +++ b/app/Views/claim_dump_file_list.php @@ -24,6 +24,66 @@ .dataTables_length label {height: 21px !important;} .readonly-select { background-color: #f3f3f3 !important; cursor: not-allowed; pointer-events: none; } + +.custom-tooltip { + position: relative; + display: inline-block; + cursor: pointer; +} + +.info-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; + background-color: #007bff; + color: #fff; + border-radius: 50%; + font-size: 14px; + font-style: italic; + font-weight: bold; + font-family: Georgia, serif; + line-height: 1; + vertical-align: middle; +} + +.tooltiptext { + visibility: hidden; + opacity: 0; + position: fixed; /* fixed instead of absolute to escape modal overflow */ + background: #333; + color: #fff; + padding: 10px 14px; + border-radius: 6px; + white-space: normal; /* allow text wrapping */ + width: 320px; /* fixed width for multiline */ + font-size: 12px; + font-weight: normal; + font-style: normal; + line-height: 1.6; + z-index: 99999; + transition: opacity 0.2s ease; + pointer-events: none; + box-shadow: 0 4px 12px rgba(0,0,0,0.3); + top: 20px !important; + left: 155px !important; +} + +.tooltiptext::after { + content: ""; + position: absolute; + top: 100%; + left: 20px; + border: 6px solid transparent; + border-top-color: #333; +} + +.custom-tooltip:hover .tooltiptext { + visibility: visible; + opacity: 1; +} + @@ -151,7 +221,16 @@