diff --git a/.env.sample b/.env.sample index e8a13f85..3d57c40a 100755 --- a/.env.sample +++ b/.env.sample @@ -116,6 +116,21 @@ VIDAL_PRIMARY_KEY_CONSTANT = MEDI_ASSIST_PRIMARY_KEY_CONSTANT = +#-------------------------------------------------------------------- +# TPA PRIMARY KEY CONSTANTS LIVE +#-------------------------------------------------------------------- + +MEDI_ASSIST_PRIMARY_KEY_CONSTANT_LIVE = +ICICI_PRIMARY_KEY_CONSTANT_LIVE = +ABHI_PRIMARY_KEY_CONSTANT_LIVE = +R_CARE_PRIMARY_KEY_CONSTANT_LIVE = +FHPL_PRIMARY_KEY_CONSTANT_LIVE = +VIDAL_PRIMARY_KEY_CONSTANT_LIVE = +VOLO_PRIMARY_KEY_CONSTANT_LIVE = + +# When true, claims-collection-report falls back to ticket_master only if the +# policy TPA has no mapped dump table (or dump table missing). Dump TPAs always use claim_report. +CLAIM_REPORT_FALLBACK_TO_TICKET_MASTER = false FHPL_TOKEN_URL = FHPL_BASE_URL = diff --git a/app/Commands/BackfillClaimReport.php b/app/Commands/BackfillClaimReport.php deleted file mode 100644 index ee247d3d..00000000 --- a/app/Commands/BackfillClaimReport.php +++ /dev/null @@ -1,200 +0,0 @@ - 'Optional client_policy_id to limit backfill', - '--limit' => 'Batch size (default 1000)', - ]; - - public function run(array $params) - { - $db = db_connect(); - - if (!$db->tableExists('claim_report')) { - CLI::error('Table claim_report does not exist. Run migrations first.'); - return EXIT_ERROR; - } - - if (!$db->tableExists('ticket_master')) { - CLI::error('Table ticket_master does not exist.'); - return EXIT_ERROR; - } - - $policyId = (int) (CLI::getOption('policy') ?? 0); - $limit = (int) (CLI::getOption('limit') ?? 1000); - if ($limit <= 0) { - $limit = 1000; - } - - $offset = 0; - $totalInserted = 0; - $totalUpdated = 0; - $totalSkipped = 0; - $now = date('Y-m-d H:i:s'); - - CLI::write('Backfilling claim_report from ticket_master (dump-sourced claims)...', 'yellow'); - - while (true) { - $builder = $db->table('ticket_master') - ->where('is_active', 1) - ->groupStart() - ->where('claim_dump_ref_id IS NOT NULL', null, false) - ->orWhere('file_id IS NOT NULL', null, false) - ->groupEnd() - ->where("claim_number IS NOT NULL AND TRIM(claim_number) != ''", null, false) - ->orderBy('id', 'ASC') - ->limit($limit, $offset); - - if ($policyId > 0) { - $builder->where('client_policy_id', $policyId); - } - - $tickets = $builder->get()->getResultArray(); - if ($tickets === []) { - break; - } - - $rows = []; - foreach ($tickets as $tm) { - $claimNumber = trim((string) ($tm['claim_number'] ?? '')); - $clientPolicyId = (int) ($tm['client_policy_id'] ?? 0); - if ($claimNumber === '' || $clientPolicyId <= 0) { - $totalSkipped++; - continue; - } - - $rows[] = [ - 'tpa_id' => $tm['tpa_id'] ?? null, - 'client_id' => $tm['client_id'] ?? null, - 'client_policy_id' => $clientPolicyId, - 'file_id' => $tm['file_id'] ?? null, - 'ticket_id' => $tm['id'] ?? null, - 'source_table' => null, - 'source_row_id' => $tm['claim_dump_ref_id'] ?? null, - 'claim_number' => $claimNumber, - 'emp_code' => $tm['emp_code'] ?? null, - 'tpa_no' => $tm['tpa_no'] ?? null, - 'emp_id' => $tm['emp_id'] ?? null, - 'insured_emp_id' => $tm['insured_emp_id'] ?? null, - 'claim_amount' => $tm['claim_amount'] ?? null, - 'approved_amount' => $tm['approved_amount'] ?? null, - 'incurred_amount' => $tm['approved_amount'] ?? $tm['claim_amount'] ?? null, - 'si_amt' => $tm['si_amt'] ?? null, - 'tpa_claim_status' => $tm['tpa_claim_status'] ?? null, - 'claim_status_id' => $tm['claim_status_id'] ?? null, - 'tpa_claim_type' => $tm['tpa_claim_type'] ?? null, - 'tpa_ailments' => $tm['tpa_ailments'] ?? null, - 'doa' => $tm['doa'] ?? null, - 'dod' => $tm['dod'] ?? null, - 'date_of_intimat' => $tm['date_of_intimat'] ?? null, - 'settled_date' => $tm['settled_date'] ?? null, - 'approved_date' => $tm['approved_date'] ?? null, - 'claim_dump_date' => $tm['claim_dump_date'] ?? null, - 'hospital_name' => $tm['hospital_name'] ?? null, - 'hospital_city' => $tm['hospital_city'] ?? null, - 'hospital_state' => $tm['hospital_state'] ?? null, - 'hospital_pin_code'=> $tm['hospital_pin_code'] ?? null, - 'hospital_address' => $tm['hospital_address'] ?? null, - 'gender' => null, - 'age' => null, - 'relation' => $tm['relationship'] ?? null, - 'is_active' => 1, - 'created_at' => $now, - 'updated_at' => $now, - ]; - } - - if ($rows !== []) { - $result = $this->upsertRows($db, $rows); - if ($result === false) { - CLI::error('Upsert failed at offset ' . $offset); - return EXIT_ERROR; - } - $totalInserted += $result['inserted']; - $totalUpdated += $result['updated']; - } - - $offset += count($tickets); - CLI::write("Processed {$offset} ticket_master rows...", 'green'); - - if (count($tickets) < $limit) { - break; - } - } - - CLI::write("Done. inserted≈{$totalInserted}, updated≈{$totalUpdated}, skipped={$totalSkipped}", 'green'); - return EXIT_SUCCESS; - } - - /** - * @param list> $rows - * @return array{inserted:int,updated:int}|false - */ - private function upsertRows($db, array $rows) - { - $columns = [ - 'tpa_id', 'client_id', 'client_policy_id', 'file_id', 'ticket_id', - 'source_table', 'source_row_id', 'claim_number', 'emp_code', 'tpa_no', - 'emp_id', 'insured_emp_id', 'claim_amount', 'approved_amount', 'incurred_amount', - 'si_amt', 'tpa_claim_status', 'claim_status_id', 'tpa_claim_type', 'tpa_ailments', - 'doa', 'dod', 'date_of_intimat', 'settled_date', 'approved_date', 'claim_dump_date', - 'hospital_name', 'hospital_city', 'hospital_state', 'hospital_pin_code', 'hospital_address', - 'gender', 'age', 'relation', 'is_active', 'created_at', 'updated_at', - ]; - - $updateCols = array_values(array_diff($columns, ['client_policy_id', 'claim_number', 'created_at'])); - $inserted = 0; - $updated = 0; - - foreach (array_chunk($rows, 100) as $chunk) { - $placeholders = []; - $binds = []; - foreach ($chunk as $row) { - $rowPlaceholders = []; - foreach ($columns as $col) { - $rowPlaceholders[] = '?'; - $binds[] = $row[$col] ?? null; - } - $placeholders[] = '(' . implode(', ', $rowPlaceholders) . ')'; - } - - $updates = []; - foreach ($updateCols as $col) { - $updates[] = '`' . $col . '` = VALUES(`' . $col . '`)'; - } - - $sql = 'INSERT INTO `claim_report` (`' . implode('`, `', $columns) . '`) VALUES ' - . implode(', ', $placeholders) - . ' ON DUPLICATE KEY UPDATE ' . implode(', ', $updates); - - if ($db->query($sql, $binds) === false) { - return false; - } - - $affected = $db->affectedRows(); - // MySQL: 1 = insert, 2 = update existing - $updated += (int) floor($affected / 2); - $inserted += max(0, $affected - (2 * (int) floor($affected / 2))); - } - - return ['inserted' => $inserted, 'updated' => $updated]; - } -} diff --git a/app/Commands/SyncClaimReportFromDump.php b/app/Commands/SyncClaimReportFromDump.php deleted file mode 100644 index bb4a581b..00000000 --- a/app/Commands/SyncClaimReportFromDump.php +++ /dev/null @@ -1,63 +0,0 @@ - 'Optional client_policy_id', - '--tpa' => 'icici|abhi|mediassist|fhpl|rcare|vidal|all (default: all)', - '--limit' => 'Batch size per dump query (default 500)', - '--phase1' => 'TPA dump tables only (skip ticket_master)', - '--ticket-only' => 'Skip dump tables; copy only from ticket_master', - ]; - - public function run(array $params) - { - $policyId = (int) ($params['policy'] ?? CLI::getOption('policy') ?? 0); - $limit = (int) ($params['limit'] ?? CLI::getOption('limit') ?? 500); - $ticketOnly = array_key_exists('ticket-only', $params) || CLI::getOption('ticket-only') !== null; - $phase1Only = array_key_exists('phase1', $params) || CLI::getOption('phase1') !== null; - $tpaOpt = strtolower(trim((string) ($params['tpa'] ?? CLI::getOption('tpa') ?? 'all'))); - - if ($limit <= 0) { - $limit = 500; - } - - $result = (new ClaimReportSyncService())->sync($policyId, $tpaOpt, $limit, $ticketOnly, $phase1Only); - - foreach ($result['logs'] as $line) { - $color = 'white'; - if (str_starts_with($line, '[FAIL]')) { - $color = 'red'; - } elseif (str_starts_with($line, '[DONE]') || str_starts_with($line, 'Done.')) { - $color = 'green'; - } elseif (str_starts_with($line, '[SKIP]') || str_starts_with($line, 'Phase') || str_starts_with($line, 'Skipping')) { - $color = 'yellow'; - } elseif (str_starts_with($line, ' ')) { - $color = 'green'; - } - CLI::write($line, $color); - } - - return ! empty($result['status']) ? EXIT_SUCCESS : EXIT_ERROR; - } -} diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 1628b476..c757d986 100755 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -510,7 +510,7 @@ $routes->group("/util", ["filter" => "authMVC"], function ($routes) { $routes->get('all', 'ClaimReportDashboardController::all'); $routes->get('debug', 'ClaimReportDashboardController::debug'); $routes->get('debug/(:num)', 'ClaimReportDashboardController::debug/$1'); - $routes->match(['get', 'post'], 'sync', 'ClaimReportDashboardController::sync'); + $routes->get('sync', 'ClaimReportDashboardController::sync'); }); $routes->group('enrollment-collection-v1', static function ($routes) { @@ -865,7 +865,6 @@ $routes->group("employeeRest", ["filter" => ['GlobalPostFileUploadGuard', 'ratel $routes->get('all', 'ClaimReportDashboardController::all'); $routes->get('debug', 'ClaimReportDashboardController::debug'); $routes->get('debug/(:num)', 'ClaimReportDashboardController::debug/$1'); - $routes->match(['get', 'post'], 'sync', 'ClaimReportDashboardController::sync'); }); $routes->group('enrollment-collection-v1', static function ($routes) { diff --git a/app/Controllers/ClaimReportDashboardController.php b/app/Controllers/ClaimReportDashboardController.php index 7e277754..acbe8e8d 100644 --- a/app/Controllers/ClaimReportDashboardController.php +++ b/app/Controllers/ClaimReportDashboardController.php @@ -5,11 +5,12 @@ namespace App\Controllers; use App\Models\ClaimReportDashboardModel; use App\Models\ClaimsCollectionV2DashboardModel; use App\Models\ClaimDumpFileModel; -use App\Libraries\ClaimReportSyncService; use CodeIgniter\API\ResponseTrait; /** - * Claims Collection dashboard API using claim_report (falls back to ticket_master). + * Claims Collection dashboard API using claim_report. + * ticket_master fallback is env-gated (CLAIM_REPORT_FALLBACK_TO_TICKET_MASTER) + * and only applies when the policy TPA has no dump table. */ class ClaimReportDashboardController extends BaseController { @@ -153,6 +154,376 @@ class ClaimReportDashboardController extends BaseController ]); } + /** + * Sync TPA dump tables → claim_report. + * Only rows where ticket_id IS NOT NULL and is_active = 1 are copied. + * Idempotent: existing (client_policy_id, claim_number) rows are skipped. + * No request parameters required. + * + * GET /util/claims-collection-report/sync + */ + public function sync() + { + @set_time_limit(0); + @ini_set('max_execution_time', '0'); + + $db = db_connect(); + + if (!$db->tableExists('claim_report')) { + return $this->respond([ + 'status' => false, + 'message' => 'Table claim_report does not exist. Run migrations first.', + ], 500); + } + + // env_key => [dump_table, claim_number_col, dump_col => claim_report_col mapping] + $tpaTableMap = [ + 'VIDAL_PRIMARY_KEY_CONSTANT' => [ + 'table' => 'claims_dump_vidal', + 'claim_col' => 'insurer_claim_number', + 'mapping' => [ + 'insurer_claim_number' => 'claim_number', + 'employee_number' => 'emp_code', + 'date_of_admission' => 'doa', + 'date_of_discharge' => 'dod', + 'claim_amount' => 'claim_amount', + 'approved_amount' => 'approved_amount', + 'sum_insured' => 'si_amt', + 'claim_status' => 'tpa_claim_status', + 'hospital_name' => 'hospital_name', + 'hospital_address' => 'hospital_address', + 'hospital_city' => 'hospital_city', + 'hospital_state' => 'hospital_state', + 'hospital_pincode' => 'hospital_pin_code', + 'type_of_claim' => 'tpa_claim_type', + 'diagnosis' => 'tpa_ailments', + 'tpa_claim_number' => 'tpa_no', + ], + ], + 'ABHI_PRIMARY_KEY_CONSTANT' => [ + 'table' => 'claims_dump_abhi', + 'claim_col' => 'abhi_claim_no', + 'mapping' => [ + 'abhi_claim_no' => 'claim_number', + 'member_code' => 'emp_code', + 'doa' => 'doa', + 'dod' => 'dod', + 'intimation_date' => 'date_of_intimat', + 'claim_status' => 'tpa_claim_status', + 'claimed_amount' => 'claim_amount', + 'hospital_name' => 'hospital_name', + 'hospital_city' => 'hospital_city', + 'hospital_state' => 'hospital_state', + 'settled_date' => 'settled_date', + 'healthcard_id' => 'tpa_no', + 'claim_type' => 'tpa_claim_type', + 'diagnosis' => 'tpa_ailments', + 'abhi_amount_less_coins_current_month' => 'approved_amount', + 'patient_age' => 'age', + 'gender' => 'gender', + 'relation' => 'relation', + ], + ], + 'MEDI_ASSIST_PRIMARY_KEY_CONSTANT' => [ + 'table' => 'claims_dump_medi_assist', + 'claim_col' => 'claim_id', + 'mapping' => [ + 'claim_id' => 'claim_number', + 'pribenef_employee_code' => 'emp_code', + 'date_of_admission' => 'doa', + 'date_of_discharge' => 'dod', + 'intimation_date' => 'date_of_intimat', + 'settled_date' => 'settled_date', + 'processed_date' => 'approved_date', + 'claim_status' => 'tpa_claim_status', + 'claim_amount' => 'claim_amount', + 'claim_approved_amount' => 'approved_amount', + 'hospital_name' => 'hospital_name', + 'hospital_address' => 'hospital_address', + 'hospital_city' => 'hospital_city', + 'hospital_state' => 'hospital_state', + 'hospital_pincode' => 'hospital_pin_code', + 'claim_type' => 'tpa_claim_type', + 'primary_ailment_name' => 'tpa_ailments', + 'benef_sum_insured' => 'si_amt', + 'benef_gender' => 'gender', + 'benef_age' => 'age', + 'benef_relation' => 'relation', + 'incurred_amount' => 'incurred_amount', + ], + ], + 'FHPL_PRIMARY_KEY_CONSTANT' => [ + 'table' => 'claims_dump_fhpl', + 'claim_col' => 'claim_id', + 'mapping' => [ + 'claim_id' => 'claim_number', + 'employee_id' => 'emp_code', + 'admission_date' => 'doa', + 'discharge_date' => 'dod', + 'claim_received_date' => 'date_of_intimat', + 'claim_passed_date' => 'approved_date', + 'settled_date' => 'settled_date', + 'current_claim_status' => 'tpa_claim_status', + 'claim_amount' => 'claim_amount', + 'settled_amount' => 'approved_amount', + 'incurred_amount' => 'incurred_amount', + 'coverage_amount' => 'si_amt', + 'provider_name' => 'hospital_name', + 'provider_address' => 'hospital_address', + 'provider_state' => 'hospital_state', + 'provider_place' => 'hospital_city', + 'provider_pincode' => 'hospital_pin_code', + 'claim_type' => 'tpa_claim_type', + 'diagnosis' => 'tpa_ailments', + 'uhid_no' => 'tpa_no', + 'gender' => 'gender', + 'years' => 'age', + 'relationship' => 'relation', + ], + ], + 'R_CARE_PRIMARY_KEY_CONSTANT' => [ + 'table' => 'claims_dump_reliance', + 'claim_col' => 'cl_inward_no', + 'mapping' => [ + 'cl_inward_no' => 'claim_number', + 'employee_member_id' => 'emp_code', + 'doa_opd_treatment_from' => 'doa', + 'dod_opd_treatment_to' => 'dod', + 'approved_date' => 'approved_date', + 'cheque_neft_date' => 'settled_date', + 'claimed_amount' => 'claim_amount', + 'net_sanction_amount' => 'approved_amount', + 'final_status' => 'tpa_claim_status', + 'hospital_name' => 'hospital_name', + 'hospital_state' => 'hospital_state', + 'hospital_district' => 'hospital_city', + 'uhid' => 'tpa_no', + 'diagnosis' => 'tpa_ailments', + 'member_reimbursement_cl_type' => 'tpa_claim_type', + 'gender' => 'gender', + 'age' => 'age', + 'relation' => 'relation', + 'sum_insured' => 'si_amt', + ], + ], + 'ICICI_PRIMARY_KEY_CONSTANT' => [ + 'table' => 'claims_dump_icici', + 'claim_col' => 'claim_number', + 'mapping' => [ + 'claim_number' => 'claim_number', + 'employee_member_id' => 'emp_code', + 'doa' => 'doa', + 'dod' => 'dod', + 'payment_date' => 'settled_date', + 'claimed_amount' => 'claim_amount', + 'net_sanct_amt' => 'approved_amount', + 'updated_status' => 'tpa_claim_status', + 'hospital_name' => 'hospital_name', + 'hospital_city' => 'hospital_city', + 'hospital_state' => 'hospital_state', + 'type_of_claim' => 'tpa_claim_type', + 'diagnosis' => 'tpa_ailments', + 'uhid' => 'tpa_no', + 'sum_insured' => 'si_amt', + 'gender' => 'gender', + 'age' => 'age', + 'relation' => 'relation', + ], + ], + ]; + + // Build tpa_id → config map from env + $tpaMap = []; + foreach ($tpaTableMap as $envKey => $cfg) { + $tpaId = (int) env($envKey); + if ($tpaId > 0) { + $tpaMap[$tpaId] = $cfg; + } + } + + $totalFound = 0; + $totalInserted = 0; + $totalSkipped = 0; + $totalFailed = 0; + $tpaResults = []; + $now = date('Y-m-d H:i:s'); + + foreach ($tpaMap as $tpaId => $cfg) { + $dumpTable = $cfg['table']; + $claimCol = $cfg['claim_col']; + $mapping = $cfg['mapping']; + + if (!$db->tableExists($dumpTable)) { + $tpaResults[] = [ + 'tpa_id' => $tpaId, + 'table' => $dumpTable, + 'status' => 'skipped', + 'detail' => 'Dump table does not exist.', + 'found' => 0, + 'inserted' => 0, + 'skipped' => 0, + 'failed' => 0, + ]; + continue; + } + + // Fetch all active dump rows with a ticket_id (all columns) + $dumpRows = $db->table($dumpTable) + ->where('is_active', 1) + ->where('ticket_id IS NOT NULL', null, false) + ->get() + ->getResultArray(); + + // Pre-load all related ticket_master rows in one query + $ticketIds = array_filter(array_column($dumpRows, 'ticket_id')); + $ticketsById = []; + if (!empty($ticketIds)) { + $ticketRows = $db->table('ticket_master') + ->whereIn('id', array_values(array_unique($ticketIds))) + ->get() + ->getResultArray(); + foreach ($ticketRows as $tm) { + $ticketsById[(int) $tm['id']] = $tm; + } + } + + $found = count($dumpRows); + $inserted = 0; + $skipped = 0; + $failed = 0; + $totalFound += $found; + + foreach ($dumpRows as $row) { + $claimNumber = trim((string) ($row[$claimCol] ?? '')); + $clientPolicyId = (int) ($row['client_policy_id'] ?? 0); + + if ($claimNumber === '' || $clientPolicyId <= 0) { + $skipped++; + $totalSkipped++; + continue; + } + + // Fetch linked ticket_master row for emp_id / insured_emp_id + $ticket = $ticketsById[(int) ($row['ticket_id'] ?? 0)] ?? null; + + // Check if already exists in claim_report + $existing = $db->table('claim_report') + ->where('client_policy_id', $clientPolicyId) + ->where('claim_number', $claimNumber) + ->get() + ->getRowArray(); + + // Base row metadata from dump; employee/system IDs always from ticket_master + $reportRow = [ + 'tpa_id' => $tpaId, + 'client_id' => $row['client_id'] ?? ($ticket['client_id'] ?? null), + 'file_id' => $row['file_id'] ?? ($ticket['file_id'] ?? null), + 'ticket_id' => $row['ticket_id'], + 'source_table' => $dumpTable, + 'source_row_id' => $row['id'], + 'is_active' => 1, + 'updated_at' => $now, + 'emp_id' => $ticket['emp_id'] ?? null, + 'insured_emp_id' => $ticket['insured_emp_id'] ?? null, + 'claim_status_id' => $ticket['claim_status_id'] ?? null, + ]; + + // 1) Fill ALL claim_report data fields from ticket_master first + $ticketCols = [ + 'emp_code', 'tpa_no', + 'gender', 'age', 'relation', + 'claim_amount', 'approved_amount', 'si_amt', 'incurred_amount', + 'tpa_claim_status', 'tpa_claim_type', 'tpa_ailments', + 'doa', 'dod', 'date_of_intimat', 'settled_date', 'approved_date', + 'hospital_name', 'hospital_city', 'hospital_state', + 'hospital_pin_code', 'hospital_address', + 'claim_dump_date', + ]; + if ($ticket) { + foreach ($ticketCols as $col) { + $ticketVal = $ticket[$col] ?? null; + // ticket_master uses "relationship"; claim_report uses "relation" + if ($col === 'relation' && ($ticketVal === null || $ticketVal === '')) { + $ticketVal = $ticket['relationship'] ?? null; + } + if ($ticketVal !== null && $ticketVal !== '') { + $reportRow[$col] = $ticketVal; + } + } + } + + // 2) Dump table fills only fields still null/empty after ticket_master + foreach ($mapping as $dumpCol => $reportCol) { + $current = $reportRow[$reportCol] ?? null; + if ($current !== null && $current !== '') { + continue; + } + if (array_key_exists($dumpCol, $row) && $row[$dumpCol] !== null && $row[$dumpCol] !== '') { + $reportRow[$reportCol] = $row[$dumpCol]; + } + } + + // incurred_amount final fallback + if (empty($reportRow['incurred_amount'])) { + $reportRow['incurred_amount'] = $reportRow['approved_amount'] ?? $reportRow['claim_amount'] ?? null; + } + + if ($existing) { + // Update existing row with full data + $ok = $db->table('claim_report') + ->where('client_policy_id', $clientPolicyId) + ->where('claim_number', $claimNumber) + ->update($reportRow); + if ($ok) { + $skipped++; + $totalSkipped++; + } else { + $failed++; + $totalFailed++; + } + continue; + } + + // New insert + $reportRow['client_policy_id'] = $clientPolicyId; + $reportRow['claim_number'] = $claimNumber; + $reportRow['created_at'] = $now; + + $ok = $db->table('claim_report')->insert($reportRow); + + if ($ok) { + $inserted++; + $totalInserted++; + } else { + $failed++; + $totalFailed++; + } + } + + $tpaResults[] = [ + 'tpa_id' => $tpaId, + 'table' => $dumpTable, + 'status' => 'done', + 'found' => $found, + 'inserted' => $inserted, + 'skipped' => $skipped, + 'failed' => $failed, + ]; + } + + return $this->respond([ + 'status' => $totalFailed === 0, + 'message' => $totalFailed === 0 ? 'Sync completed successfully.' : 'Sync completed with some failures.', + 'summary' => [ + 'total_found' => $totalFound, + 'total_inserted' => $totalInserted, + 'total_skipped' => $totalSkipped, + 'total_failed' => $totalFailed, + ], + 'tpa_results' => $tpaResults, + ]); + } + /** * Admin check only: raw JSON on screen (no dashboard UI). */ @@ -183,95 +554,4 @@ class ClaimReportDashboardController extends BaseController ->setBody($body); } - /** - * Run claim report sync via URL (authMVC / JWT). - * Uses ClaimReportSyncService directly (no spark/CLI). - * - * Query params (all optional): - * client_policy / client_policy_id - * tpa=icici|abhi|mediassist|fhpl|rcare|vidal|all - * limit=500 - * phase1=true → TPA dump tables only (skip ticket_master) - * ticket_only=1 → ticket_master only (ignored if phase1=true) - */ - public function sync() - { - @set_time_limit(0); - @ini_set('max_execution_time', '0'); - - $policyId = $this->resolvePolicyId(); - $tpa = strtolower(trim((string) ($this->request->getGet('tpa') ?? $this->request->getPost('tpa') ?? 'all'))); - $limit = (int) ($this->request->getGet('limit') ?? $this->request->getPost('limit') ?? 500); - $ticketOnly = $this->isTruthyParam('ticket_only'); - $phase1Only = $this->isTruthyParam('phase1'); - - $allowedTpa = ['all', 'vidal', 'abhi', 'mediassist', 'fhpl', 'rcare', 'icici']; - if ($tpa === '' || ! in_array($tpa, $allowedTpa, true)) { - return $this->respond([ - 'status' => false, - 'message' => 'Invalid tpa. Allowed: ' . implode(', ', $allowedTpa), - ], 422); - } - - if ($limit <= 0) { - $limit = 500; - } - if ($limit > 5000) { - $limit = 5000; - } - - $startedAt = date('Y-m-d H:i:s'); - - try { - $result = (new ClaimReportSyncService())->sync( - $policyId, - $tpa, - $limit, - $ticketOnly, - $phase1Only - ); - } catch (\Throwable $e) { - return $this->respond([ - 'status' => false, - 'message' => 'Sync failed: ' . $e->getMessage(), - 'policy_id' => $policyId > 0 ? $policyId : null, - 'tpa' => $tpa, - 'limit' => $limit, - 'phase1' => $phase1Only, - 'ticket_only' => $ticketOnly, - 'started_at' => $startedAt, - ], 500); - } - - $ok = ! empty($result['status']); - - return $this->respond([ - 'status' => $ok, - 'message' => $result['message'] ?? ($ok ? 'Sync completed.' : 'Sync failed.'), - 'policy_id' => $policyId > 0 ? $policyId : null, - 'tpa' => $tpa, - 'limit' => $limit, - 'phase1' => $phase1Only, - 'ticket_only' => $ticketOnly && ! $phase1Only, - 'started_at' => $startedAt, - 'finished_at' => date('Y-m-d H:i:s'), - 'summary' => $result['summary'] ?? null, - 'phases' => $result['phases'] ?? [], - 'tpa_results' => $result['tpa_results'] ?? [], - 'logs' => $result['logs'] ?? [], - ], $ok ? 200 : 500); - } - - /** - * True when GET/POST param is 1/true/yes (case-insensitive). - */ - protected function isTruthyParam(string $name): bool - { - $raw = $this->request->getGet($name) ?? $this->request->getPost($name); - if ($raw === null) { - return false; - } - - return in_array(strtolower(trim((string) $raw)), ['1', 'true', 'yes'], true); - } } diff --git a/app/Libraries/ClaimReportSyncService.php b/app/Libraries/ClaimReportSyncService.php deleted file mode 100644 index b787c876..00000000 --- a/app/Libraries/ClaimReportSyncService.php +++ /dev/null @@ -1,444 +0,0 @@ - - */ - public function tpaConfigs(): array - { - return [ - 'vidal' => [ - 'env' => 'VIDAL_PRIMARY_KEY_CONSTANT', - 'table' => 'claims_dump_vidal', - ], - 'abhi' => [ - 'env' => 'ABHI_PRIMARY_KEY_CONSTANT', - 'table' => 'claims_dump_abhi', - ], - 'mediassist' => [ - 'env' => 'MEDI_ASSIST_PRIMARY_KEY_CONSTANT', - 'table' => 'claims_dump_medi_assist', - ], - 'fhpl' => [ - 'env' => 'FHPL_PRIMARY_KEY_CONSTANT', - 'table' => 'claims_dump_fhpl', - ], - 'rcare' => [ - 'env' => 'R_CARE_PRIMARY_KEY_CONSTANT', - 'table' => 'claims_dump_reliance', - ], - 'icici' => [ - 'env' => 'ICICI_PRIMARY_KEY_CONSTANT', - 'table' => 'claims_dump_icici', - ], - ]; - } - - /** - * @return array{ - * status: bool, - * message: string, - * summary: array{mapped:int,skipped:int,errors:int,message:?string}, - * phases: list, - * tpa_results: list>, - * logs: list - * } - */ - public function sync( - int $policyId = 0, - string $tpa = 'all', - int $limit = 500, - bool $ticketOnly = false, - bool $phase1Only = false - ): array { - $logs = []; - $phases = []; - $tpaResults = []; - $db = db_connect(); - - $log = static function (string $line) use (&$logs): void { - $logs[] = $line; - }; - - if (!$db->tableExists('claim_report')) { - $msg = 'Table claim_report does not exist. Run: php spark migrate'; - $log($msg); - return $this->result(false, $msg, 0, 0, 1, $phases, $tpaResults, $logs); - } - - // phase1Only and ticketOnly are mutually exclusive; phase1 wins. - if ($phase1Only) { - $ticketOnly = false; - } - - $tpa = strtolower(trim($tpa)); - if ($tpa === '') { - $tpa = 'all'; - } - if ($limit <= 0) { - $limit = 500; - } - - $totalMapped = 0; - $totalSkipped = 0; - $errors = 0; - - if (!$ticketOnly) { - $phase1 = 'Phase 1: sync from TPA dump tables (linked ticket_id rows)...'; - $phases[] = $phase1; - $log($phase1); - - $configs = $this->tpaConfigs(); - if ($tpa !== 'all') { - if (!isset($configs[$tpa])) { - $msg = 'Unknown tpa=' . $tpa . '. Use: ' . implode('|', array_keys($configs)) . '|all'; - $log($msg); - return $this->result(false, $msg, 0, 0, 1, $phases, $tpaResults, $logs); - } - $configs = [$tpa => $configs[$tpa]]; - } - - foreach ($configs as $key => $cfg) { - $tpaId = (int) env($cfg['env']); - $table = $cfg['table']; - - if ($tpaId <= 0) { - $detail = "{$key}: env {$cfg['env']} not set"; - $log('[SKIP] ' . $detail); - $tpaResults[] = ['tpa' => $key, 'mapped' => 0, 'status' => 'skipped', 'detail' => $detail]; - continue; - } - - if (!$db->tableExists($table)) { - $detail = "{$key}: table {$table} missing"; - $log('[SKIP] ' . $detail); - $tpaResults[] = ['tpa' => $key, 'mapped' => 0, 'status' => 'skipped', 'detail' => $detail]; - continue; - } - - try { - $service = TpaClaimsImportFactory::make($tpaId); - } catch (InvalidArgumentException $e) { - $detail = $key . ': ' . $e->getMessage(); - $log('[SKIP] ' . $detail); - $tpaResults[] = ['tpa' => $key, 'mapped' => 0, 'status' => 'skipped', 'detail' => $detail]; - continue; - } - - $offset = 0; - $tpaMapped = 0; - - while (true) { - $builder = $db->table($table) - ->select('id, file_id, ticket_id, client_policy_id') - ->where('is_active', 1) - ->where('ticket_id IS NOT NULL', null, false) - ->orderBy('id', 'ASC') - ->limit($limit, $offset); - - if ($policyId > 0) { - $builder->where('client_policy_id', $policyId); - } - - $dumpRows = $builder->get()->getResultArray(); - if ($dumpRows === []) { - break; - } - - $byFile = []; - foreach ($dumpRows as $row) { - $fileId = (int) ($row['file_id'] ?? 0); - $dumpId = (int) ($row['id'] ?? 0); - if ($fileId <= 0 || $dumpId <= 0) { - $totalSkipped++; - continue; - } - $byFile[$fileId][] = $dumpId; - } - - foreach ($byFile as $fileId => $dumpIds) { - $result = $service->backfillClaimReportByDumpIds((int) $fileId, $dumpIds); - if (!$result['status']) { - $log("[FAIL] {$key} file_id={$fileId} upsert failed"); - $errors++; - continue; - } - $tpaMapped += (int) $result['count']; - $totalMapped += (int) $result['count']; - } - - $offset += count($dumpRows); - $log(" {$key}: processed {$offset} dump rows (mapped so far {$tpaMapped})"); - - if (count($dumpRows) < $limit) { - break; - } - } - - $done = "[DONE] {$key} mapped≈{$tpaMapped}"; - $log($done); - $tpaResults[] = [ - 'tpa' => $key, - 'mapped' => $tpaMapped, - 'status' => 'done', - 'detail' => $done, - ]; - } - } else { - $phase = 'Skipping dump tables (ticket-only).'; - $phases[] = $phase; - $log($phase); - } - - if ($phase1Only) { - $skip = 'Phase 2 skipped (phase1=true — TPA dump tables only).'; - $phases[] = $skip; - $log($skip); - $doneMsg = "Done. dump mapped≈{$totalMapped}, skipped={$totalSkipped}, errors={$errors}"; - $log($doneMsg); - - return $this->result( - $errors === 0, - $errors === 0 ? 'Phase 1 sync completed.' : 'Phase 1 sync completed with errors.', - $totalMapped, - $totalSkipped, - $errors, - $phases, - $tpaResults, - $logs, - $doneMsg - ); - } - - $phase2 = 'Phase 2: fill gaps from ticket_master dump-sourced claims...'; - $phases[] = $phase2; - $log($phase2); - - $tmResult = $this->syncFromTicketMaster($db, $policyId, $limit, $log); - if ($tmResult === false) { - return $this->result(false, 'ticket_master sync failed', $totalMapped, $totalSkipped, $errors + 1, $phases, $tpaResults, $logs); - } - - $totalMapped += $tmResult['mapped']; - $totalSkipped += $tmResult['skipped']; - - $doneMsg = "Done. dump+ticket mapped≈{$totalMapped}, skipped={$totalSkipped}, errors={$errors}"; - $log($doneMsg); - - return $this->result( - $errors === 0, - $errors === 0 ? 'Sync completed.' : 'Sync completed with errors. See summary.', - $totalMapped, - $totalSkipped, - $errors, - $phases, - $tpaResults, - $logs, - $doneMsg - ); - } - - /** - * @param callable(string):void $log - * @return array{mapped:int,skipped:int}|false - */ - private function syncFromTicketMaster($db, int $policyId, int $limit, callable $log) - { - if (!$db->tableExists('ticket_master')) { - $log('ticket_master missing'); - return false; - } - - $offset = 0; - $mapped = 0; - $skipped = 0; - $now = date('Y-m-d H:i:s'); - - while (true) { - $builder = $db->table('ticket_master') - ->where('is_active', 1) - ->groupStart() - ->where('claim_dump_ref_id IS NOT NULL', null, false) - ->orWhere('file_id IS NOT NULL', null, false) - ->groupEnd() - ->where("claim_number IS NOT NULL AND TRIM(claim_number) != ''", null, false) - ->orderBy('id', 'ASC') - ->limit($limit, $offset); - - if ($policyId > 0) { - $builder->where('client_policy_id', $policyId); - } - - $tickets = $builder->get()->getResultArray(); - if ($tickets === []) { - break; - } - - $rows = []; - foreach ($tickets as $tm) { - $claimNumber = trim((string) ($tm['claim_number'] ?? '')); - $clientPolicyId = (int) ($tm['client_policy_id'] ?? 0); - if ($claimNumber === '' || $clientPolicyId <= 0) { - $skipped++; - continue; - } - - $sourceTable = null; - $tpaId = (int) ($tm['tpa_id'] ?? 0); - foreach ($this->tpaConfigs() as $cfg) { - if ($tpaId === (int) env($cfg['env'])) { - $sourceTable = $cfg['table']; - break; - } - } - - $rows[] = [ - 'tpa_id' => $tm['tpa_id'] ?? null, - 'client_id' => $tm['client_id'] ?? null, - 'client_policy_id' => $clientPolicyId, - 'file_id' => $tm['file_id'] ?? null, - 'ticket_id' => $tm['id'] ?? null, - 'source_table' => $sourceTable, - 'source_row_id' => $tm['claim_dump_ref_id'] ?? null, - 'claim_number' => $claimNumber, - 'emp_code' => $tm['emp_code'] ?? null, - 'tpa_no' => $tm['tpa_no'] ?? null, - 'emp_id' => $tm['emp_id'] ?? null, - 'insured_emp_id' => $tm['insured_emp_id'] ?? null, - 'claim_amount' => $tm['claim_amount'] ?? null, - 'approved_amount' => $tm['approved_amount'] ?? null, - 'incurred_amount' => $tm['approved_amount'] ?? $tm['claim_amount'] ?? null, - 'si_amt' => $tm['si_amt'] ?? null, - 'tpa_claim_status' => $tm['tpa_claim_status'] ?? null, - 'claim_status_id' => $tm['claim_status_id'] ?? null, - 'tpa_claim_type' => $tm['tpa_claim_type'] ?? null, - 'tpa_ailments' => $tm['tpa_ailments'] ?? null, - 'doa' => $tm['doa'] ?? null, - 'dod' => $tm['dod'] ?? null, - 'date_of_intimat' => $tm['date_of_intimat'] ?? null, - 'settled_date' => $tm['settled_date'] ?? null, - 'approved_date' => $tm['approved_date'] ?? null, - 'claim_dump_date' => $tm['claim_dump_date'] ?? null, - 'hospital_name' => $tm['hospital_name'] ?? null, - 'hospital_city' => $tm['hospital_city'] ?? null, - 'hospital_state' => $tm['hospital_state'] ?? null, - 'hospital_pin_code' => $tm['hospital_pin_code'] ?? null, - 'hospital_address' => $tm['hospital_address'] ?? null, - 'gender' => null, - 'age' => null, - 'relation' => $tm['relationship'] ?? null, - 'is_active' => 1, - 'created_at' => $now, - 'updated_at' => $now, - ]; - } - - if ($rows !== [] && $this->upsertRows($db, $rows) === false) { - $log('ticket_master upsert failed at offset ' . $offset); - return false; - } - - $mapped += count($rows); - $offset += count($tickets); - $log(" ticket_master: processed {$offset} rows (mapped {$mapped})"); - - if (count($tickets) < $limit) { - break; - } - } - - return ['mapped' => $mapped, 'skipped' => $skipped]; - } - - /** - * @param list> $rows - */ - private function upsertRows($db, array $rows): bool - { - $columns = [ - 'tpa_id', 'client_id', 'client_policy_id', 'file_id', 'ticket_id', - 'source_table', 'source_row_id', 'claim_number', 'emp_code', 'tpa_no', - 'emp_id', 'insured_emp_id', 'claim_amount', 'approved_amount', 'incurred_amount', - 'si_amt', 'tpa_claim_status', 'claim_status_id', 'tpa_claim_type', 'tpa_ailments', - 'doa', 'dod', 'date_of_intimat', 'settled_date', 'approved_date', 'claim_dump_date', - 'hospital_name', 'hospital_city', 'hospital_state', 'hospital_pin_code', 'hospital_address', - 'gender', 'age', 'relation', 'is_active', 'created_at', 'updated_at', - ]; - - $updateCols = array_values(array_diff($columns, ['client_policy_id', 'claim_number', 'created_at'])); - - foreach (array_chunk($rows, 100) as $chunk) { - $placeholders = []; - $binds = []; - foreach ($chunk as $row) { - $rowPlaceholders = []; - foreach ($columns as $col) { - $rowPlaceholders[] = '?'; - $binds[] = $row[$col] ?? null; - } - $placeholders[] = '(' . implode(', ', $rowPlaceholders) . ')'; - } - - $updates = []; - foreach ($updateCols as $col) { - if (in_array($col, ['gender', 'age', 'relation', 'approved_amount', 'incurred_amount', 'tpa_ailments', 'tpa_claim_type', 'source_table', 'source_row_id'], true)) { - $updates[] = '`' . $col . '` = COALESCE(VALUES(`' . $col . '`), `' . $col . '`)'; - } else { - $updates[] = '`' . $col . '` = VALUES(`' . $col . '`)'; - } - } - - $sql = 'INSERT INTO `claim_report` (`' . implode('`, `', $columns) . '`) VALUES ' - . implode(', ', $placeholders) - . ' ON DUPLICATE KEY UPDATE ' . implode(', ', $updates); - - if ($db->query($sql, $binds) === false) { - return false; - } - } - - return true; - } - - /** - * @param list $phases - * @param list> $tpaResults - * @param list $logs - * @return array - */ - private function result( - bool $status, - string $message, - int $mapped, - int $skipped, - int $errors, - array $phases, - array $tpaResults, - array $logs, - ?string $summaryMessage = null - ): array { - return [ - 'status' => $status, - 'message' => $message, - 'summary' => [ - 'mapped' => $mapped, - 'skipped' => $skipped, - 'errors' => $errors, - 'message' => $summaryMessage, - ], - 'phases' => $phases, - 'tpa_results' => $tpaResults, - 'logs' => $logs, - ]; - } -} diff --git a/app/Libraries/TPAClaimsImportServices/AbhiClaimImportService.php b/app/Libraries/TPAClaimsImportServices/AbhiClaimImportService.php index db74db09..7e8851f3 100644 --- a/app/Libraries/TPAClaimsImportServices/AbhiClaimImportService.php +++ b/app/Libraries/TPAClaimsImportServices/AbhiClaimImportService.php @@ -115,18 +115,6 @@ class AbhiClaimImportService extends BaseTpaClaimImportService /** * Extra dump → claim_report fields beyond ticketMasterMapping. - * report_column => dump_column - */ - protected $claimReportEnrichment = [ - 'approved_amount' => 'abhi_amount_less_coins_current_month', - 'incurred_amount' => 'claimed_amount', - 'tpa_ailments' => 'diagnosis', - 'tpa_claim_type' => 'claim_type', - 'gender' => 'gender', - 'age' => 'patient_age', - 'relation' => 'relation', - ]; - protected $statusMapping = [ 'Settled' => 11, 'Rejected' => 8, diff --git a/app/Libraries/TPAClaimsImportServices/BaseTpaClaimImportService.php b/app/Libraries/TPAClaimsImportServices/BaseTpaClaimImportService.php index c501f486..533394b1 100644 --- a/app/Libraries/TPAClaimsImportServices/BaseTpaClaimImportService.php +++ b/app/Libraries/TPAClaimsImportServices/BaseTpaClaimImportService.php @@ -330,11 +330,6 @@ abstract class BaseTpaClaimImportService return $this->failTicketMasterInsert($file_id, 'No data found to process.'); } - // Upsert normalized analytics rows into claim_report (same transaction) - if (!$this->syncClaimReportForJob2($file_id, $ticketMasterData)) { - return $this->failTicketMasterInsert($file_id, 'Claim report upsert failed'); - } - // 2. Commit the transaction $this->db->transCommit(); @@ -1169,289 +1164,6 @@ abstract class BaseTpaClaimImportService ->where('is_active', 1) ->countAllResults() > 0; } - - /** - * Collect dump row IDs processed in Job 2 and upsert claim_report rows. - */ - protected function syncClaimReportForJob2(int $fileId, array $ticketMasterData): bool - { - $dumpIds = []; - - foreach ($ticketMasterData['mapped_array'] ?? [] as $row) { - if (!empty($row['claim_dump_ref_id'])) { - $dumpIds[] = (int) $row['claim_dump_ref_id']; - } - } - - foreach ($ticketMasterData['status_update_array'] ?? [] as $row) { - if (!empty($row['claim_dump_ref_id'])) { - $dumpIds[] = (int) $row['claim_dump_ref_id']; - } - } - - foreach ($ticketMasterData['rejected_reason_array'] ?? [] as $row) { - // Linked existing tickets: ticket_id set, no reject reason - if (!empty($row['id']) && !empty($row['ticket_id']) && empty($row['master_reject_reason'])) { - $dumpIds[] = (int) $row['id']; - } - } - - $dumpIds = array_values(array_unique(array_filter($dumpIds))); - if ($dumpIds === []) { - return true; - } - - $rows = $this->mapClaimReportData($fileId, $dumpIds); - $this->logClaimDump('info', 'CLAIM_REPORT_UPSERT', [ - 'file_id' => $fileId, - 'dump_ids' => count($dumpIds), - 'report_rows' => count($rows), - ]); - - return $this->upsertClaimReport($rows); - } - - /** - * Public backfill entry: map dump row IDs for a file into claim_report. - * - * @param list $dumpRowIds - * @return array{status:bool,count:int} - */ - public function backfillClaimReportByDumpIds(int $fileId, array $dumpRowIds): array - { - $dumpRowIds = array_values(array_unique(array_filter(array_map('intval', $dumpRowIds)))); - if ($fileId <= 0 || $dumpRowIds === []) { - return ['status' => true, 'count' => 0]; - } - - $rows = $this->mapClaimReportData($fileId, $dumpRowIds); - $ok = $this->upsertClaimReport($rows); - - return ['status' => $ok, 'count' => count($rows)]; - } - - /** - * Map dump rows (+ linked ticket_master) into claim_report-shaped rows. - * - * @param list $dumpRowIds - * @return list> - */ - protected function mapClaimReportData(int $fileId, array $dumpRowIds): array - { - if ($fileId <= 0 || $dumpRowIds === []) { - return []; - } - - $fileData = $this->claimDumpFileModel->where('id', $fileId)->first(); - if (empty($fileData)) { - return []; - } - - $tpaId = (int) ($fileData['tpa_id'] ?? 0); - $tpaTable = $this->tpaTableMapping[$tpaId] ?? null; - if ($tpaTable === null || !$this->db->tableExists($tpaTable)) { - return []; - } - - $dumpRows = $this->db->table($tpaTable) - ->whereIn('id', $dumpRowIds) - ->where('is_active', 1) - ->get() - ->getResultArray(); - - if ($dumpRows === []) { - return []; - } - - $ticketIds = []; - $refIds = []; - foreach ($dumpRows as $dump) { - if (!empty($dump['ticket_id'])) { - $ticketIds[] = (int) $dump['ticket_id']; - } - $refIds[] = (int) $dump['id']; - } - - $ticketsById = []; - $ticketsByRef = []; - if ($ticketIds !== []) { - $ticketRows = $this->db->table('ticket_master') - ->whereIn('id', array_values(array_unique($ticketIds))) - ->get() - ->getResultArray(); - foreach ($ticketRows as $ticket) { - $ticketsById[(int) $ticket['id']] = $ticket; - } - } - if ($refIds !== []) { - $ticketRows = $this->db->table('ticket_master') - ->where('file_id', $fileId) - ->whereIn('claim_dump_ref_id', array_values(array_unique($refIds))) - ->get() - ->getResultArray(); - foreach ($ticketRows as $ticket) { - $ticketsByRef[(int) $ticket['claim_dump_ref_id']] = $ticket; - $ticketsById[(int) $ticket['id']] = $ticket; - } - } - - $mapping = property_exists($this, 'ticketMasterMapping') ? ($this->ticketMasterMapping ?? []) : []; - $enrichment = property_exists($this, 'claimReportEnrichment') ? ($this->claimReportEnrichment ?? []) : []; - - $reportFieldsFromTicketMap = [ - 'claim_number', 'emp_code', 'tpa_no', 'claim_amount', 'approved_amount', 'si_amt', - 'tpa_claim_status', 'tpa_claim_type', 'tpa_ailments', - 'doa', 'dod', 'date_of_intimat', 'settled_date', 'approved_date', 'registration_date', - 'hospital_name', 'hospital_city', 'hospital_state', 'hospital_pin_code', 'hospital_address', - 'denial_reason', 'denial_date', 'utr_details', 'return_remark', - ]; - - $out = []; - foreach ($dumpRows as $dump) { - $dumpId = (int) ($dump['id'] ?? 0); - $ticket = null; - if (!empty($dump['ticket_id']) && isset($ticketsById[(int) $dump['ticket_id']])) { - $ticket = $ticketsById[(int) $dump['ticket_id']]; - } elseif (isset($ticketsByRef[$dumpId])) { - $ticket = $ticketsByRef[$dumpId]; - } - - // Skip dump rows that never became / linked to a ticket - if (empty($ticket) && empty($dump['ticket_id'])) { - continue; - } - - $item = [ - 'tpa_id' => $tpaId ?: ($ticket['tpa_id'] ?? null), - 'client_id' => $dump['client_id'] ?? $fileData['client_id'] ?? ($ticket['client_id'] ?? null), - 'client_policy_id' => (int) ($dump['client_policy_id'] ?? $fileData['client_policy_id'] ?? ($ticket['client_policy_id'] ?? 0)), - 'file_id' => $fileId, - 'ticket_id' => $ticket['id'] ?? ($dump['ticket_id'] ?? null), - 'source_table' => $tpaTable, - 'source_row_id' => $dumpId, - 'claim_dump_date' => $fileData['claim_dump_date'] ?? ($ticket['claim_dump_date'] ?? null), - 'is_active' => 1, - ]; - - foreach ($mapping as $dumpCol => $ticketCol) { - if (!in_array($ticketCol, $reportFieldsFromTicketMap, true)) { - continue; - } - // Map ticket-shaped columns that exist on claim_report - $reportCol = $ticketCol === 'registration_date' ? 'date_of_intimat' : $ticketCol; - if (!array_key_exists($reportCol, $item) || $item[$reportCol] === null) { - $item[$reportCol] = array_key_exists($dumpCol, $dump) ? $dump[$dumpCol] : null; - } - } - - foreach ($enrichment as $reportCol => $dumpCol) { - if ($dumpCol === null || $dumpCol === '') { - continue; - } - $value = $dump[$dumpCol] ?? null; - if ($value !== null && $value !== '') { - $item[$reportCol] = $value; - } - } - - if ($ticket) { - foreach (['emp_id', 'insured_emp_id', 'claim_status_id', 'emp_code', 'claim_number', 'claim_amount', 'approved_amount', 'tpa_claim_type', 'tpa_ailments', 'hospital_name', 'hospital_city', 'hospital_state', 'hospital_pin_code', 'hospital_address', 'doa', 'dod', 'date_of_intimat', 'settled_date', 'approved_date', 'si_amt', 'tpa_claim_status', 'tpa_no'] as $col) { - if ((!isset($item[$col]) || $item[$col] === null || $item[$col] === '') && isset($ticket[$col]) && $ticket[$col] !== null && $ticket[$col] !== '') { - $item[$col] = $ticket[$col]; - } - } - if (empty($item['ticket_id'])) { - $item['ticket_id'] = $ticket['id']; - } - } - - if (empty($item['incurred_amount'])) { - $item['incurred_amount'] = $item['approved_amount'] ?? $item['claim_amount'] ?? null; - } - - $claimNumber = trim((string) ($item['claim_number'] ?? '')); - if ($claimNumber === '' || (int) ($item['client_policy_id'] ?? 0) <= 0) { - continue; - } - $item['claim_number'] = $claimNumber; - - // Drop fields that are not on claim_report - unset($item['denial_reason'], $item['denial_date'], $item['utr_details'], $item['return_remark'], $item['registration_date']); - - $out[] = $item; - } - - return $out; - } - - /** - * Insert or update claim_report by UNIQUE(client_policy_id, claim_number). - * - * @param list> $rows - */ - protected function upsertClaimReport(array $rows): bool - { - if ($rows === []) { - return true; - } - - if (!$this->db->tableExists('claim_report')) { - $this->logClaimDump('warning', 'CLAIM_REPORT_TABLE_MISSING', []); - return true; - } - - $columns = [ - 'tpa_id', 'client_id', 'client_policy_id', 'file_id', 'ticket_id', - 'source_table', 'source_row_id', 'claim_number', 'emp_code', 'tpa_no', - 'emp_id', 'insured_emp_id', 'claim_amount', 'approved_amount', 'incurred_amount', - 'si_amt', 'tpa_claim_status', 'claim_status_id', 'tpa_claim_type', 'tpa_ailments', - 'doa', 'dod', 'date_of_intimat', 'settled_date', 'approved_date', 'claim_dump_date', - 'hospital_name', 'hospital_city', 'hospital_state', 'hospital_pin_code', 'hospital_address', - 'gender', 'age', 'relation', 'is_active', 'created_at', 'updated_at', - ]; - - $updateCols = array_values(array_diff($columns, ['client_policy_id', 'claim_number', 'created_at'])); - $now = date('Y-m-d H:i:s'); - - foreach (array_chunk($rows, 100) as $chunk) { - $placeholders = []; - $binds = []; - - foreach ($chunk as $row) { - $rowPlaceholders = []; - foreach ($columns as $col) { - $rowPlaceholders[] = '?'; - if ($col === 'created_at' || $col === 'updated_at') { - $binds[] = $now; - } elseif ($col === 'is_active') { - $binds[] = isset($row[$col]) ? (int) $row[$col] : 1; - } else { - $binds[] = $row[$col] ?? null; - } - } - $placeholders[] = '(' . implode(', ', $rowPlaceholders) . ')'; - } - - $updates = []; - foreach ($updateCols as $col) { - if ($col === 'updated_at') { - $updates[] = '`updated_at` = VALUES(`updated_at`)'; - } else { - $updates[] = '`' . $col . '` = VALUES(`' . $col . '`)'; - } - } - - $sql = 'INSERT INTO `claim_report` (`' . implode('`, `', $columns) . '`) VALUES ' - . implode(', ', $placeholders) - . ' ON DUPLICATE KEY UPDATE ' . implode(', ', $updates); - - if ($this->db->query($sql, $binds) === false) { - return false; - } - } - - return true; - } /** * Map Excel rows to TPA table structure diff --git a/app/Libraries/TPAClaimsImportServices/FhplClaimImportService.php b/app/Libraries/TPAClaimsImportServices/FhplClaimImportService.php index 725ee2f5..496c61cf 100644 --- a/app/Libraries/TPAClaimsImportServices/FhplClaimImportService.php +++ b/app/Libraries/TPAClaimsImportServices/FhplClaimImportService.php @@ -177,19 +177,6 @@ class FhplClaimImportService extends BaseTpaClaimImportService /** * Extra dump → claim_report fields beyond ticketMasterMapping. - * report_column => dump_column - */ - protected $claimReportEnrichment = [ - 'approved_amount' => 'settled_amount', - 'incurred_amount' => 'incurred_amount', - 'tpa_ailments' => 'diagnosis', - 'tpa_claim_type' => 'claim_type', - 'gender' => 'gender', - 'age' => 'years', - 'relation' => 'relationship', - 'si_amt' => 'coverage_amount', - ]; - protected $statusMapping = [ 'Settled' => 11, 'Rejected' => 8, diff --git a/app/Libraries/TPAClaimsImportServices/IciciClaimImportService.php b/app/Libraries/TPAClaimsImportServices/IciciClaimImportService.php index 97096210..98a6d5d0 100644 --- a/app/Libraries/TPAClaimsImportServices/IciciClaimImportService.php +++ b/app/Libraries/TPAClaimsImportServices/IciciClaimImportService.php @@ -106,19 +106,6 @@ class IciciClaimImportService extends BaseTpaClaimImportService /** * Extra dump → claim_report fields beyond ticketMasterMapping. - * report_column => dump_column - */ - protected $claimReportEnrichment = [ - 'approved_amount' => 'net_sanct_amt', - 'incurred_amount' => 'claimed_amount', - 'tpa_ailments' => 'diagnosis', - 'tpa_claim_type' => 'type_of_claim', - 'gender' => 'gender', - 'age' => 'age', - 'relation' => 'relation', - 'si_amt' => 'sum_insured', - ]; - protected $statusMapping = [ 'PAID' => 11, 'SETTLED' => 11, diff --git a/app/Libraries/TPAClaimsImportServices/MediAssistClaimImportService.php b/app/Libraries/TPAClaimsImportServices/MediAssistClaimImportService.php index b1d0aefa..beb08156 100644 --- a/app/Libraries/TPAClaimsImportServices/MediAssistClaimImportService.php +++ b/app/Libraries/TPAClaimsImportServices/MediAssistClaimImportService.php @@ -157,19 +157,6 @@ class MediAssistClaimImportService extends BaseTpaClaimImportService /** * Extra dump → claim_report fields beyond ticketMasterMapping. - * report_column => dump_column - */ - protected $claimReportEnrichment = [ - 'approved_amount' => 'claim_approved_amount', - 'incurred_amount' => 'incurred_amount', - 'tpa_ailments' => 'primary_ailment_name', - 'tpa_claim_type' => 'claim_type', - 'gender' => 'benef_gender', - 'age' => 'benef_age', - 'relation' => 'benef_relation', - 'si_amt' => 'benef_sum_insured', - ]; - protected $statusMapping = [ 'Settled' => 11, 'Rejected' => 8, diff --git a/app/Libraries/TPAClaimsImportServices/RcareClaimImportService.php b/app/Libraries/TPAClaimsImportServices/RcareClaimImportService.php index 477922f3..0eb3d9d3 100644 --- a/app/Libraries/TPAClaimsImportServices/RcareClaimImportService.php +++ b/app/Libraries/TPAClaimsImportServices/RcareClaimImportService.php @@ -94,19 +94,6 @@ class RcareClaimImportService extends BaseTpaClaimImportService /** * Extra dump → claim_report fields beyond ticketMasterMapping. - * report_column => dump_column - */ - protected $claimReportEnrichment = [ - 'approved_amount' => 'net_sanction_amount', - 'incurred_amount' => 'claimed_amount', - 'tpa_ailments' => 'diagnosis', - 'tpa_claim_type' => 'member_reimbursement_cl_type', - 'gender' => 'gender', - 'age' => 'age', - 'relation' => 'relation', - 'si_amt' => 'sum_insured', - ]; - protected $statusMapping = [ 'CL Paid with Settlement Letter' => 11, 'Settled' => 11, diff --git a/app/Libraries/TPAClaimsImportServices/VidalClaimImportService.php b/app/Libraries/TPAClaimsImportServices/VidalClaimImportService.php index 921ec6ba..44d2a6a5 100644 --- a/app/Libraries/TPAClaimsImportServices/VidalClaimImportService.php +++ b/app/Libraries/TPAClaimsImportServices/VidalClaimImportService.php @@ -350,19 +350,6 @@ class VidalClaimImportService extends BaseTpaClaimImportService /** * Extra dump → claim_report fields beyond ticketMasterMapping. - * report_column => dump_column - */ - protected $claimReportEnrichment = [ - 'approved_amount' => 'approved_amount', - 'incurred_amount' => 'total_incurred_amount', - 'tpa_ailments' => 'diagnosis', - 'tpa_claim_type' => 'type_of_claim', - 'gender' => 'gender', - 'age' => 'age', - 'relation' => 'relation', - 'si_amt' => 'sum_insured', - ]; - protected $statusMapping = [ 'Settled' => 11, 'Rejected' => 8, diff --git a/app/Models/ClaimReportDashboardModel.php b/app/Models/ClaimReportDashboardModel.php index 6bee42b5..61f00511 100644 --- a/app/Models/ClaimReportDashboardModel.php +++ b/app/Models/ClaimReportDashboardModel.php @@ -3,7 +3,8 @@ namespace App\Models; /** - * Claims Collection dashboard backed by claim_report with ticket_master fallback. + * Claims Collection dashboard backed by claim_report. + * Falls back to ticket_master only for non-dump TPAs when env flag is true. * Reuses V2 KPI SQL; claim fact tables are rewritten at query time. */ class ClaimReportDashboardModel extends ClaimsCollectionV2DashboardModel @@ -13,7 +14,10 @@ class ClaimReportDashboardModel extends ClaimsCollectionV2DashboardModel /** * Resolve claim fact table for a policy. - * Uses claim_report when TPA dump table exists and claim_report has rows; else ticket_master. + * + * - TPA dump table exists → always claim_report (even if empty; never ticket_master). + * - Dump table missing / TPA unmapped → ticket_master only when + * CLAIM_REPORT_FALLBACK_TO_TICKET_MASTER=true; otherwise claim_report. */ public function resolveClaimsTable(int $policyId): string { @@ -21,11 +25,18 @@ class ClaimReportDashboardModel extends ClaimsCollectionV2DashboardModel return $this->claimsTableCache[$policyId]; } - $table = 'ticket_master'; - $db = \Config\Database::connect($this->DBGroup); + $db = \Config\Database::connect($this->DBGroup); + $fallbackEnabled = $this->isTicketMasterFallbackEnabled(); - if (!$db->tableExists('claim_report') || $policyId <= 0) { - return $this->claimsTableCache[$policyId] = $table; + if ($policyId <= 0) { + return $this->claimsTableCache[$policyId] = $fallbackEnabled + ? 'ticket_master' + : 'claim_report'; + } + + // Table missing — cannot query claim_report; ticket_master is the only option. + if (!$db->tableExists('claim_report')) { + return $this->claimsTableCache[$policyId] = 'ticket_master'; } $policy = $db->table('client_policy') @@ -34,23 +45,26 @@ class ClaimReportDashboardModel extends ClaimsCollectionV2DashboardModel ->get() ->getRowArray(); - $tpaId = (int) ($policy['tpa_id'] ?? 0); + $tpaId = (int) ($policy['tpa_id'] ?? 0); $dumpTable = $this->getTpaDumpTableMap()[$tpaId] ?? null; - if ($dumpTable === null || !$db->tableExists($dumpTable)) { - return $this->claimsTableCache[$policyId] = $table; + // Mapped dump TPA with existing dump table → claim_report only. + if ($dumpTable !== null && $db->tableExists($dumpTable)) { + return $this->claimsTableCache[$policyId] = 'claim_report'; } - $hasReportRows = $db->table('claim_report') - ->where('client_policy_id', $policyId) - ->where('is_active', 1) - ->countAllResults() > 0; + // Non-dump / missing dump table → env-gated ticket_master fallback. + return $this->claimsTableCache[$policyId] = $fallbackEnabled + ? 'ticket_master' + : 'claim_report'; + } - if ($hasReportRows) { - $table = 'claim_report'; - } - - return $this->claimsTableCache[$policyId] = $table; + protected function isTicketMasterFallbackEnabled(): bool + { + return filter_var( + env('CLAIM_REPORT_FALLBACK_TO_TICKET_MASTER', false), + FILTER_VALIDATE_BOOLEAN + ); } /** diff --git a/tests/smoke_claim_report_dashboard.php b/tests/smoke_claim_report_dashboard.php deleted file mode 100644 index 381b4b33..00000000 --- a/tests/smoke_claim_report_dashboard.php +++ /dev/null @@ -1,374 +0,0 @@ -systemDirectory, '\\/ ') . DIRECTORY_SEPARATOR . 'bootstrap.php'; -require_once SYSTEMPATH . 'Config/DotEnv.php'; -(new CodeIgniter\Config\DotEnv(ROOTPATH))->load(); - -defined('ENVIRONMENT') || define('ENVIRONMENT', env('CI_ENVIRONMENT', 'development')); - -$boot = APPPATH . 'Config/Boot/' . ENVIRONMENT . '.php'; -if (is_file($boot)) { - require_once $boot; -} - -helper('url'); - -use App\Controllers\ClaimReportDashboardController; -use App\Models\ClaimReportDashboardModel; -use App\Models\ClaimsCollectionV2DashboardModel; -use Config\Services; - -$policyId = isset($argv[1]) ? (int) $argv[1] : 0; -$pass = 0; -$fail = 0; -$results = []; - -function ok(string $label, bool $cond, string $detail = ''): void -{ - global $pass, $fail, $results; - if ($cond) { - $pass++; - $results[] = '[PASS] ' . $label . ($detail ? " — {$detail}" : ''); - } else { - $fail++; - $results[] = '[FAIL] ' . $label . ($detail ? " — {$detail}" : ''); - } -} - -function info(string $line): void -{ - global $results; - $results[] = $line; -} - -$db = \Config\Database::connect(); - -// Default: prefer a policy that already has claim_report or ticket_master rows. -if ($policyId <= 0) { - $pick = null; - if ($db->tableExists('claim_report')) { - $pick = $db->query( - 'SELECT client_policy_id AS id FROM claim_report WHERE is_active = 1 AND client_policy_id > 0 ORDER BY id DESC LIMIT 1' - )->getRowArray(); - } - if (empty($pick)) { - $pick = $db->query( - "SELECT client_policy_id AS id FROM ticket_master - WHERE is_active = 1 AND client_policy_id > 0 - AND (claim_dump_ref_id IS NOT NULL OR file_id IS NOT NULL) - ORDER BY id DESC LIMIT 1" - )->getRowArray(); - } - if (empty($pick)) { - $pick = $db->query('SELECT id FROM client_policy WHERE is_active = 1 ORDER BY id DESC LIMIT 1')->getRowArray(); - } - $policyId = (int) ($pick['id'] ?? 0); -} - -$model = new ClaimReportDashboardModel(); -$kpiMap = ClaimsCollectionV2DashboardModel::KPI_MAP; - -info('=== Schema ==='); - -$hasTable = $db->tableExists('claim_report'); -ok('claim_report table exists', $hasTable); - -if ($hasTable) { - $fields = $db->getFieldNames('claim_report'); - $required = [ - 'id', 'tpa_id', 'client_id', 'client_policy_id', 'file_id', 'ticket_id', - 'source_table', 'source_row_id', 'claim_number', 'claim_amount', 'approved_amount', - 'incurred_amount', 'tpa_claim_type', 'tpa_ailments', 'claim_status_id', - 'hospital_name', 'doa', 'dod', 'claim_dump_date', 'is_active', - ]; - $missing = array_values(array_diff($required, $fields)); - ok('claim_report required columns', $missing === [], $missing === [] ? count($fields) . ' cols' : 'missing: ' . implode(', ', $missing)); - - $indexes = $db->query('SHOW INDEX FROM `claim_report`')->getResultArray(); - $indexNames = array_unique(array_column($indexes, 'Key_name')); - ok('unique key uq_claim_report_policy_claim', in_array('uq_claim_report_policy_claim', $indexNames, true)); -} - -info(''); -info('=== Source resolver (policy_id=' . $policyId . ') ==='); - -$policy = $policyId > 0 - ? $db->table('client_policy')->select('id, tpa_id, policy_no')->where('id', $policyId)->get()->getRowArray() - : null; -if (empty($policy)) { - info('[WARN] policy id ' . $policyId . ' not found — resolver/KPI checks will use empty data'); - ok('policy id resolved for test', $policyId > 0, 'policy_id=' . $policyId); -} else { - ok('policy exists', true, 'tpa_id=' . ($policy['tpa_id'] ?? 'null') . ' policy_no=' . ($policy['policy_no'] ?? '')); -} - -$tpaTableMap = [ - (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', -]; - -$tpaId = (int) ($policy['tpa_id'] ?? 0); -$dumpTable = $tpaTableMap[$tpaId] ?? null; -$dumpExists = $dumpTable !== null && $db->tableExists($dumpTable); -$reportCount = $hasTable - ? (int) $db->table('claim_report')->where('client_policy_id', $policyId)->where('is_active', 1)->countAllResults() - : 0; -$tmCount = (int) $db->table('ticket_master')->where('client_policy_id', $policyId)->where('is_active', 1)->countAllResults(); - -info('[INFO] dump_table=' . ($dumpTable ?? 'none') . ' exists=' . ($dumpExists ? 'yes' : 'no')); -info('[INFO] claim_report rows=' . $reportCount . ' ticket_master rows=' . $tmCount); - -$expectedSource = ($dumpExists && $reportCount > 0) ? 'claim_report' : 'ticket_master'; -$actualSource = $model->resolveClaimsTable($policyId); -ok('resolveClaimsTable matches expectation', $actualSource === $expectedSource, "expected={$expectedSource} actual={$actualSource}"); - -info(''); -info('=== KPI model ==='); - -ok('KPI_MAP count', count($kpiMap) === 36, (string) count($kpiMap)); -ok('id 207 maps to incurred_ratio', ($kpiMap[207] ?? '') === 'incurred_ratio'); - -try { - $rows = $model->policy_exposure_summary($policyId); - ok('model policy_exposure_summary', is_array($rows), 'rows=' . count($rows)); -} catch (Throwable $e) { - ok('model policy_exposure_summary', false, $e->getMessage()); -} - -try { - $rows = $model->getKpi('incurred_ratio', $policyId); - ok('model getKpi(incurred_ratio)', is_array($rows), 'rows=' . count($rows)); -} catch (Throwable $e) { - ok('model getKpi(incurred_ratio)', false, $e->getMessage()); -} - -try { - $rows = $model->getKpi('total_claims', $policyId); - ok('model getKpi(total_claims)', is_array($rows), 'rows=' . count($rows)); -} catch (Throwable $e) { - ok('model getKpi(total_claims)', false, $e->getMessage()); -} - -try { - $rows = $model->getKpi('claim_amount_by_gender', $policyId); - ok('model getKpi(claim_amount_by_gender)', is_array($rows), 'rows=' . count($rows)); -} catch (Throwable $e) { - ok('model getKpi(claim_amount_by_gender)', false, $e->getMessage()); -} - -$sampleKpis = [ - 'policy_exposure_summary', - 'premium_as_on_date', - 'total_claims', - 'incurred_amount', - 'incurred_ratio', - 'claim_amount_by_gender', - 'top_5_hospitals_by_incurred_amount', - 'cashless_claim_amt', - 'top_10_ailments_by_claim_count', -]; -$samplePass = 0; -foreach ($sampleKpis as $method) { - try { - $rows = $model->getKpi($method, $policyId); - if (is_array($rows)) { - $samplePass++; - } - } catch (Throwable $e) { - info('[WARN] sample KPI ' . $method . ': ' . $e->getMessage()); - } -} -ok('sample claim KPIs runnable', $samplePass === count($sampleKpis), "{$samplePass}/" . count($sampleKpis)); - -try { - $all = $model->getAllKpis($policyId); - $metaSource = $all['_meta']['claims_source'] ?? null; - unset($all['_meta']); - ok('model getAllKpis', count($all) === 36, 'kpis=' . count($all)); - ok('getAllKpis _meta.claims_source', $metaSource === $actualSource, (string) $metaSource); -} catch (Throwable $e) { - // Older MySQL without CTE support can fail on age_band (WITH ...); not claim_report-specific. - info('[WARN] getAllKpis: ' . $e->getMessage()); - info('[SKIP] model getAllKpis — CTE/MySQL limitation; sample KPIs already verified'); - ok('resolver still available after getAllKpis skip', $actualSource !== '', 'source=' . $actualSource); -} - -info(''); -info('=== Controller ==='); - -$request = Services::request(null, false); -$response = Services::response(); -$request->setGlobal('get', ['client_policy' => (string) $policyId]); - -$controller = new ClaimReportDashboardController(); -$controller->initController($request, $response, service('logger')); - -$slugResp = json_decode($controller->kpi('incurred_ratio')->getJSON(), true); -ok( - 'controller kpi by slug', - ($slugResp['status'] ?? false) === true - && ($slugResp['kpi'] ?? '') === 'incurred_ratio' - && ($slugResp['claims_source'] ?? '') === $actualSource, - 'source=' . ($slugResp['claims_source'] ?? 'null') -); - -$idResp = json_decode($controller->kpi('207')->getJSON(), true); -ok('controller kpi by id 207', ($idResp['status'] ?? false) === true && ($idResp['kpi_id'] ?? 0) === 207); - -$badResp = json_decode($controller->kpi('not_a_kpi')->getJSON(), true); -ok('controller unknown kpi 404', ($badResp['status'] ?? true) === false); - -$missingPolicyReq = Services::request(null, false); -$missingPolicyReq->setGlobal('get', []); -$missingCtrl = new ClaimReportDashboardController(); -$missingCtrl->initController($missingPolicyReq, Services::response(), service('logger')); -$missingResp = json_decode($missingCtrl->all()->getJSON(), true); -ok('controller all requires policy', ($missingResp['status'] ?? true) === false); - -try { - $allResp = json_decode($controller->all()->getJSON(), true); - ok( - 'controller all KPIs', - ($allResp['status'] ?? false) === true - && count($allResp['data'] ?? []) === 36 - && ($allResp['claims_source'] ?? '') === $actualSource, - 'source=' . ($allResp['claims_source'] ?? 'null') . ' kpis=' . count($allResp['data'] ?? []) - ); -} catch (Throwable $e) { - info('[WARN] controller all: ' . $e->getMessage()); - info('[SKIP] controller all KPIs — CTE/MySQL limitation on age_band'); - ok('controller single-kpi path still healthy', ($slugResp['status'] ?? false) === true); -} - -try { - $debugOut = $controller->debug($policyId); - $debugBody = is_string($debugOut) ? $debugOut : $debugOut->getBody(); - $debugJson = json_decode($debugBody, true); - ok( - 'controller debug JSON', - ($debugJson['status'] ?? false) === true - && isset($debugJson['data']) - && ($debugJson['claims_source'] ?? '') === $actualSource - ); -} catch (Throwable $e) { - info('[WARN] controller debug: ' . $e->getMessage()); - info('[SKIP] controller debug — CTE/MySQL limitation on age_band'); - ok('controller debug skipped safely', true); -} - -$previewOut = $controller->preview($policyId); -$previewHtml = is_string($previewOut) ? $previewOut : $previewOut->getBody(); -ok( - 'controller preview HTML', - str_contains($previewHtml, 'kpi-grid') - && str_contains($previewHtml, 'claims-collection-report') -); - -info(''); -info('=== Routes (static check of Routes.php) ==='); - -$routesFile = APPPATH . 'Config/Routes.php'; -$routesSrc = is_file($routesFile) ? (string) file_get_contents($routesFile) : ''; -ok('Routes.php readable', $routesSrc !== ''); -ok( - 'Routes.php defines claims-collection-report group', - str_contains($routesSrc, "group('claims-collection-report'") -); -ok( - 'Routes.php wires ClaimReportDashboardController', - substr_count($routesSrc, 'ClaimReportDashboardController::') >= 6, - 'refs=' . substr_count($routesSrc, 'ClaimReportDashboardController::') -); -ok( - 'Routes.php has util + employeeRest groups for report', - substr_count($routesSrc, "group('claims-collection-report'") >= 2, - 'groups=' . substr_count($routesSrc, "group('claims-collection-report'") -); - -info(''); -info('=== Spot-check vs V2 (same policy) ==='); - -try { - $v2 = new ClaimsCollectionV2DashboardModel(); - $v2Rows = $v2->getKpi('total_claims', $policyId); - $crRows = $model->getKpi('total_claims', $policyId); - ok('total_claims both return arrays', is_array($v2Rows) && is_array($crRows), 'v2=' . count($v2Rows) . ' report=' . count($crRows)); - - // When source is ticket_master, totals should match V2 closely. - if ($actualSource === 'ticket_master' && $v2Rows !== [] && $crRows !== []) { - $v2Val = json_encode($v2Rows[0] ?? []); - $crVal = json_encode($crRows[0] ?? []); - ok('total_claims matches V2 when source=ticket_master', $v2Val === $crVal, $crVal ?: 'empty'); - } else { - info('[INFO] skip strict V2 equality (source=' . $actualSource . ')'); - ok('total_claims callable on both models', true, 'skipped equality'); - } -} catch (Throwable $e) { - ok('spot-check vs V2', false, $e->getMessage()); -} - -$baseUrl = rtrim((string) env('app.baseURL', ''), '/'); -if ($baseUrl !== '') { - info(''); - info('=== HTTP auth gate checks (no session/token) ==='); - $urls = [ - 'MVC preview' => $baseUrl . '/util/claims-collection-report/preview?client_policy=' . $policyId, - 'MVC kpi slug' => $baseUrl . '/util/claims-collection-report/kpi/incurred_ratio?client_policy=' . $policyId, - 'MVC kpi id' => $baseUrl . '/util/claims-collection-report/kpi/207?client_policy=' . $policyId, - 'MVC all' => $baseUrl . '/util/claims-collection-report/all?client_policy=' . $policyId, - 'MVC debug' => $baseUrl . '/util/claims-collection-report/debug?client_policy=' . $policyId, - 'JWT kpi slug' => $baseUrl . '/employeeRest/claims-collection-report/kpi/incurred_ratio?client_policy=' . $policyId, - 'JWT debug' => $baseUrl . '/employeeRest/claims-collection-report/debug?client_policy=' . $policyId, - ]; - foreach ($urls as $label => $url) { - $ctx = stream_context_create(['http' => ['ignore_errors' => true, 'timeout' => 10]]); - $body = @file_get_contents($url, false, $ctx); - $code = 0; - if (isset($http_response_header[0]) && preg_match('/\s(\d{3})\s/', $http_response_header[0], $m)) { - $code = (int) $m[1]; - } - $blocked = in_array($code, [401, 403, 302, 303], true); - $reachable = $code >= 200 && $code < 500; - ok("HTTP {$label} (" . ($code ?: 'no connection') . ')', $blocked || $reachable, $url); - } -} else { - info('[SKIP] HTTP checks — app.baseURL not set in .env'); -} - -info(''); -info('=== Manual follow-ups ==='); -info('[HINT] Backfill: php spark claim:backfill-report --policy=' . $policyId); -info('[HINT] After Job 2 / backfill, re-run this script and expect claims_source=claim_report when dump table exists.'); - -ob_end_clean(); -echo '=== Claim Report dashboard smoke test (policy_id=' . $policyId . ') ===' . PHP_EOL . PHP_EOL; -echo implode(PHP_EOL, $results) . PHP_EOL; -echo PHP_EOL . "=== Summary: {$pass} passed, {$fail} failed ===" . PHP_EOL; -exit($fail > 0 ? 1 : 0); diff --git a/tests/sync_claim_report_from_dump.php b/tests/sync_claim_report_from_dump.php deleted file mode 100644 index 03406525..00000000 --- a/tests/sync_claim_report_from_dump.php +++ /dev/null @@ -1,36 +0,0 @@ -