'Optional claim_dump_files.id to process', ]; protected $options = [ '--tpa' => 'TPA key: icici|abhi|mediassist|fhpl|rcare|vidal|all (default: all for --scenario)', '--scenario' => 'Seed one pending dump row matching an existing ticket', '--apply' => 'Actually run runTicketMasterInsert (default is map-only dry-run)', '--new-status' => 'Dump status string for scenario (defaults per TPA)', ]; /** * Per-TPA dump column mapping used to seed scenario rows. */ private function tpaConfigs(): array { return [ 'icici' => [ 'env' => 'ICICI_PRIMARY_KEY_CONSTANT', 'table' => 'claims_dump_icici', 'status_column' => 'updated_status', 'default_status'=> 'REJECTED', 'same_status' => 'PAID', 'dump_to_ticket'=> [ 'employee_member_id' => 'emp_code', 'uhid' => 'tpa_no', 'claimed_amount' => 'claim_amount', 'doa' => 'doa', ], 'extra_dump' => [ 'relation' => 'SELF', ], ], 'abhi' => [ 'env' => 'ABHI_PRIMARY_KEY_CONSTANT', 'table' => 'claims_dump_abhi', 'status_column' => 'claim_status', 'default_status'=> 'Rejected', 'same_status' => 'Settled', 'dump_to_ticket'=> [ 'member_code' => 'emp_code', 'healthcard_id' => 'tpa_no', 'claimed_amount' => 'claim_amount', 'doa' => 'doa', ], 'extra_dump' => [ 'relation' => 'SELF', ], ], 'mediassist' => [ 'env' => 'MEDI_ASSIST_PRIMARY_KEY_CONSTANT', 'table' => 'claims_dump_medi_assist', 'status_column' => 'claim_status', 'default_status'=> 'Rejected', 'same_status' => 'Settled', 'dump_to_ticket'=> [ 'pribenef_employee_code' => 'emp_code', 'event_id' => 'tpa_no', 'claim_amount' => 'claim_amount', 'date_of_admission' => 'doa', ], 'extra_dump' => [ 'benef_relation' => 'SELF', ], ], 'fhpl' => [ 'env' => 'FHPL_PRIMARY_KEY_CONSTANT', 'table' => 'claims_dump_fhpl', 'status_column' => 'current_claim_status', 'default_status'=> 'Rejected', 'same_status' => 'Settled', 'dump_to_ticket'=> [ 'employee_id' => 'emp_code', 'uhid_no' => 'tpa_no', 'claim_amount' => 'claim_amount', 'admission_date' => 'doa', ], 'extra_dump' => [ 'relationship' => 'SELF', ], ], 'rcare' => [ 'env' => 'R_CARE_PRIMARY_KEY_CONSTANT', 'table' => 'claims_dump_reliance', 'status_column' => 'final_status', 'default_status'=> 'Rejected', 'same_status' => 'Settled', 'dump_to_ticket'=> [ 'employee_member_id' => 'emp_code', 'uhid' => 'tpa_no', 'claimed_amount' => 'claim_amount', 'doa_opd_treatment_from' => 'doa', ], 'extra_dump' => [ 'relation' => 'SELF', ], ], 'vidal' => [ 'env' => 'VIDAL_PRIMARY_KEY_CONSTANT', 'table' => 'claims_dump_vidal', 'status_column' => 'claim_status', 'default_status'=> 'Rejected', 'same_status' => 'Settled', 'dump_to_ticket'=> [ 'employee_number' => 'emp_code', 'primary_policy_holder_card_id' => 'tpa_no', 'claim_amount' => 'claim_amount', 'date_of_admission' => 'doa', ], 'extra_dump' => [ 'relation' => 'SELF', ], ], ]; } public function run(array $params) { helper('utility_helper'); $db = db_connect(); $apply = $this->hasFlag('apply'); $scenario = $this->hasFlag('scenario'); $tpaOpt = strtolower((string) ($this->resolveOptionValue('tpa', '') ?: (CLI::getOption('tpa') ?? ''))); $fileId = $this->resolveFileId($params); $newStatusOverride = $this->resolveOptionValue('new-status', ''); $configs = $this->tpaConfigs(); // Resolve which TPAs to run if ($tpaOpt === '' || $tpaOpt === 'all') { $selected = array_keys($configs); if (!$scenario && $fileId <= 0) { $this->printUsage($configs); return; } } elseif (isset($configs[$tpaOpt])) { $selected = [$tpaOpt]; } else { CLI::error("Unknown --tpa={$tpaOpt}. Use: " . implode('|', array_keys($configs)) . '|all'); return; } foreach ($selected as $tpaKey) { CLI::newLine(); CLI::write(str_repeat('=', 60), 'yellow'); CLI::write('TPA: ' . strtoupper($tpaKey), 'yellow'); CLI::write(str_repeat('=', 60), 'yellow'); try { $this->runForTpa( $db, $tpaKey, $configs[$tpaKey], $scenario, $apply, $fileId, $newStatusOverride ); } catch (\Throwable $e) { CLI::error("[{$tpaKey}] " . $e->getMessage()); } } } private function runForTpa( $db, string $tpaKey, array $cfg, bool $scenario, bool $apply, int $fileId, string $newStatusOverride ): void { $tpaId = (int) env($cfg['env']); CLI::write("{$cfg['env']} = {$tpaId}", 'cyan'); CLI::write("table={$cfg['table']}, status_column={$cfg['status_column']}", 'cyan'); if (!$this->tableExists($db, $cfg['table'])) { CLI::error("Table {$cfg['table']} does not exist. Skipping."); return; } $newStatus = $newStatusOverride !== '' ? $newStatusOverride : $cfg['default_status']; $currentFileId = $fileId; if ($scenario) { $currentFileId = $this->seedScenario($db, $tpaKey, $cfg, $tpaId, $newStatus); if ($currentFileId <= 0) { return; } } if ($currentFileId <= 0) { $this->listRecentFiles($db, $tpaId, $tpaKey); return; } $file = $db->table('claim_dump_files')->where('id', $currentFileId)->get()->getRowArray(); if (empty($file)) { CLI::error("claim_dump_files id={$currentFileId} not found"); return; } CLI::write('File: ' . json_encode([ 'id' => $file['id'], 'tpa_id' => $file['tpa_id'], 'status' => $file['status'], 'file_name' => $file['file_name'], 'client_policy_id' => $file['client_policy_id'], ])); if ((int) $file['tpa_id'] !== $tpaId) { CLI::error("File tpa_id={$file['tpa_id']} does not match {$tpaKey} ({$tpaId}). Skipping."); return; } $pending = $db->table($cfg['table']) ->where('file_id', $currentFileId) ->where('is_active', 1) ->where('ticket_id IS NULL', null, false) ->where('master_reject_reason IS NULL', null, false) ->countAllResults(); CLI::write("Pending dump rows: {$pending}"); $service = TpaClaimsImportFactory::make($tpaId); if ($apply) { CLI::write('APPLY mode...', 'light_red'); $beforeDump = $db->table($cfg['table']) ->select($this->dumpSelectColumns($cfg)) ->where('file_id', $currentFileId) ->where('is_active', 1) ->get()->getResultArray(); CLI::write('Dump BEFORE: ' . json_encode($beforeDump, JSON_PRETTY_PRINT)); $result = $service->runTicketMasterInsert(['file_id' => $currentFileId]); CLI::write('Result: ' . json_encode($result, JSON_PRETTY_PRINT)); // Mirror TicketServiceController Job 2 success/fail file status update. if (!empty($result['status'])) { $db->table('claim_dump_files')->where('id', $currentFileId)->update([ 'status' => 'success', 'reason' => null, ]); CLI::write("claim_dump_files.status => success", 'green'); } else { $db->table('claim_dump_files')->where('id', $currentFileId)->update([ 'status' => 'failed', 'reason' => json_encode([ 'error_summary' => [5 => 1], 'error_data' => $result['message'] ?? 'Job 2 failed', ]), ]); CLI::write("claim_dump_files.status => failed", 'red'); } $afterDump = $db->table($cfg['table']) ->select($this->dumpSelectColumns($cfg)) ->where('file_id', $currentFileId) ->where('is_active', 1) ->get()->getResultArray(); CLI::write('Dump AFTER: ' . json_encode($afterDump, JSON_PRETTY_PRINT)); $ticketIds = array_values(array_filter(array_column($afterDump, 'ticket_id'))); if (!empty($ticketIds)) { $tickets = $db->table('ticket_master') ->select('id, claim_status_id, claim_dump_ref_id, emp_code, tpa_no, claim_amount, doa') ->whereIn('id', $ticketIds) ->get()->getResultArray(); CLI::write('Tickets AFTER: ' . json_encode($tickets, JSON_PRETTY_PRINT)); } return; } CLI::write('DRY-RUN mode (mapClaimMasterData only)...', 'green'); $ref = new \ReflectionClass($service); $method = $ref->getMethod('mapClaimMasterData'); $method->setAccessible(true); $mapped = $method->invoke($service, $currentFileId); if (empty($mapped['status'])) { CLI::write('mapClaimMasterData: ' . ($mapped['message'] ?? 'failed')); if (!empty($mapped['already_processed'])) { CLI::write('(already processed — dump rows were NOT deleted)', 'green'); } return; } $inserts = $mapped['mapped_array'] ?? []; $rejects = $mapped['rejected_reason_array'] ?? []; $updates = $mapped['status_update_array'] ?? []; CLI::write('inserts=' . count($inserts) . ' updates=' . count($updates) . ' rejects=' . count($rejects)); CLI::write('status_update_array: ' . (empty($updates) ? '(none)' : json_encode($updates, JSON_PRETTY_PRINT))); CLI::write('rejected_reason_array: ' . json_encode(array_slice($rejects, 0, 10), JSON_PRETTY_PRINT)); if (!empty($updates)) { $ids = array_column($updates, 'id'); $before = $db->table('ticket_master') ->select('id, emp_code, tpa_no, claim_amount, doa, claim_status_id, claim_dump_ref_id') ->whereIn('id', $ids) ->get()->getResultArray(); CLI::write('Tickets BEFORE: ' . json_encode($before, JSON_PRETTY_PRINT)); } CLI::write("To apply: php spark tpa:test-status --tpa={$tpaKey} {$currentFileId} --apply", 'green'); } private function seedScenario($db, string $tpaKey, array $cfg, int $tpaId, string $newStatus): int { CLI::write("Seeding scenario for {$tpaKey} (new_status={$newStatus})...", 'cyan'); $ticket = $db->table('ticket_master') ->select('id, client_id, client_policy_id, emp_code, tpa_no, claim_amount, doa, claim_status_id, claim_dump_ref_id, tpa_id') ->where('tpa_id', $tpaId) ->where('is_active', 1) ->where('emp_code IS NOT NULL', null, false) ->where('tpa_no IS NOT NULL', null, false) ->where('claim_amount IS NOT NULL', null, false) ->where('doa IS NOT NULL', null, false) ->orderBy('id', 'DESC') ->get()->getRowArray(); if (empty($ticket)) { // Fall back: any ticket with identity keys (still allows match by doa/emp/amount/tpa_no) $ticket = $db->table('ticket_master') ->select('id, client_id, client_policy_id, emp_code, tpa_no, claim_amount, doa, claim_status_id, claim_dump_ref_id, tpa_id') ->where('is_active', 1) ->where('emp_code IS NOT NULL', null, false) ->where('tpa_no IS NOT NULL', null, false) ->where('claim_amount IS NOT NULL', null, false) ->where('doa IS NOT NULL', null, false) ->orderBy('id', 'DESC') ->get()->getRowArray(); } if (empty($ticket)) { CLI::error("[{$tpaKey}] No suitable existing ticket found to seed."); return 0; } CLI::write('Using ticket: ' . json_encode([ 'id' => $ticket['id'], 'emp_code' => $ticket['emp_code'], 'tpa_no' => $ticket['tpa_no'], 'claim_amount' => $ticket['claim_amount'], 'doa' => $ticket['doa'], 'claim_status_id' => $ticket['claim_status_id'], 'claim_dump_ref_id' => $ticket['claim_dump_ref_id'], 'tpa_id' => $ticket['tpa_id'], ])); if (!empty($ticket['claim_dump_ref_id'])) { $db->table('ticket_master')->where('id', $ticket['id'])->update(['claim_dump_ref_id' => null]); CLI::write('Cleared stale ticket.claim_dump_ref_id for retest.', 'yellow'); } $uploadDir = WRITEPATH . 'uploads' . DIRECTORY_SEPARATOR . 'claim_dump_excel' . DIRECTORY_SEPARATOR; if (!is_dir($uploadDir)) { mkdir($uploadDir, 0775, true); } $fileName = "{$tpaKey}_status_test_" . date('Ymd_His') . '.xlsx'; file_put_contents($uploadDir . $fileName, 'placeholder'); $fileInsert = [ 'tpa_id' => $tpaId, 'client_id' => $ticket['client_id'], 'client_policy_id' => $ticket['client_policy_id'], 'file_name' => $fileName, 'status' => 'pending', 'created_by' => 1, 'created_at' => date('Y-m-d H:i:s'), 'is_active' => 1, ]; $fileCols = array_column($db->query('SHOW COLUMNS FROM claim_dump_files')->getResultArray(), 'Field'); $fileInsert = array_intersect_key($fileInsert, array_flip($fileCols)); $db->table('claim_dump_files')->insert($fileInsert); $fileId = (int) $db->insertID(); if ($fileId <= 0) { CLI::error("[{$tpaKey}] Failed to create claim_dump_files row."); return 0; } $dumpInsert = [ 'client_id' => $ticket['client_id'], 'client_policy_id' => $ticket['client_policy_id'], 'file_id' => $fileId, 'is_active' => 1, 'created_by' => 1, 'created_at' => date('Y-m-d H:i:s'), 'ticket_id' => null, 'master_reject_reason' => null, $cfg['status_column'] => $newStatus, ]; foreach ($cfg['dump_to_ticket'] as $dumpCol => $ticketCol) { $dumpInsert[$dumpCol] = $ticket[$ticketCol] ?? null; } foreach ($cfg['extra_dump'] as $col => $val) { $dumpInsert[$col] = $val; } $dumpCols = array_column($db->query('SHOW COLUMNS FROM ' . $cfg['table'])->getResultArray(), 'Field'); $dumpInsert = array_intersect_key($dumpInsert, array_flip($dumpCols)); $db->table($cfg['table'])->insert($dumpInsert); $dumpId = (int) $db->insertID(); CLI::write("Seeded file_id={$fileId}, dump_id={$dumpId}", 'green'); return $fileId; } private function dumpSelectColumns(array $cfg): string { $cols = ['id', 'ticket_id', 'master_reject_reason', 'file_id', $cfg['status_column']]; foreach (array_keys($cfg['dump_to_ticket']) as $col) { $cols[] = $col; } return implode(', ', array_unique($cols)); } private function listRecentFiles($db, int $tpaId, string $tpaKey): void { $recent = $db->table('claim_dump_files') ->select('id, tpa_id, client_id, client_policy_id, file_name, status, created_at') ->where('tpa_id', $tpaId) ->orderBy('id', 'DESC') ->limit(5) ->get()->getResultArray(); CLI::write("Recent {$tpaKey} claim_dump_files:"); if (empty($recent)) { CLI::write('(none)'); } else { foreach ($recent as $row) { CLI::write(json_encode($row)); } } CLI::write("Seed: php spark tpa:test-status --tpa={$tpaKey} --scenario", 'green'); CLI::write("Apply: php spark tpa:test-status --tpa={$tpaKey} --scenario --apply", 'green'); } private function printUsage(array $configs): void { CLI::write('Test existing-ticket status/link update for TPA claim dumps.', 'cyan'); CLI::newLine(); CLI::write('Dry-run all TPAs (seed scenario, no DB ticket update):'); CLI::write(' php spark tpa:test-status --tpa=all --scenario'); CLI::newLine(); CLI::write('Apply all TPAs (writes to DB):'); CLI::write(' php spark tpa:test-status --tpa=all --scenario --apply'); CLI::newLine(); CLI::write('Single TPA:'); foreach (array_keys($configs) as $key) { CLI::write(" php spark tpa:test-status --tpa={$key} --scenario"); CLI::write(" php spark tpa:test-status --tpa={$key} --scenario --apply"); } CLI::newLine(); CLI::write('Same-status (no status change, only null-link fill):'); CLI::write(' php spark tpa:test-status --tpa=abhi --scenario --new-status Settled'); } private function tableExists($db, string $table): bool { return !empty($db->query("SHOW TABLES LIKE " . $db->escape($table))->getResultArray()); } private function resolveFileId(array $params): int { if (!empty($params['file_id'])) { return (int) $params['file_id']; } if (!empty($params[0]) && is_numeric($params[0])) { return (int) $params[0]; } $argv = $_SERVER['argv'] ?? []; foreach ($argv as $i => $arg) { if (preg_match('/^--file[_-]id=(.+)$/', (string) $arg, $m)) { return (int) $m[1]; } if (in_array($arg, ['--file_id', '--file-id'], true) && isset($argv[$i + 1])) { return (int) $argv[$i + 1]; } // Bare numeric arg (e.g. `tpa:test-status --tpa=icici 70 --apply`) if (is_numeric($arg) && (int) $arg > 0 && $i > 0 && strpos((string) $argv[$i - 1], 'spark') === false) { $prev = (string) ($argv[$i - 1] ?? ''); // Skip values belonging to options like --tpa=... or --new-status STATUS if ($prev === '--tpa' || $prev === '--new-status' || $prev === '--file_id' || $prev === '--file-id') { continue; } return (int) $arg; } } return (int) (CLI::getOption('file_id') ?? CLI::getOption('file-id') ?? 0); } private function hasFlag(string $name): bool { if (CLI::getOption($name) !== null) { return true; } return in_array('--' . $name, $_SERVER['argv'] ?? [], true); } private function resolveOptionValue(string $name, string $default): string { $opt = CLI::getOption($name); if (is_string($opt) && $opt !== '') { return $opt; } $argv = $_SERVER['argv'] ?? []; foreach ($argv as $i => $arg) { if (preg_match('/^--' . preg_quote($name, '/') . '=(.+)$/', (string) $arg, $m)) { return trim($m[1]); } if ($arg === '--' . $name && isset($argv[$i + 1]) && strpos((string) $argv[$i + 1], '--') !== 0) { return (string) $argv[$i + 1]; } } return $default; } }