nhance/app/Commands/TestTpaStatusUpdate.php
2026-07-13 10:14:37 +05:30

552 lines
22 KiB
PHP

<?php
namespace App\Commands;
use App\Libraries\TpaClaimsImportFactory;
use CodeIgniter\CLI\BaseCommand;
use CodeIgniter\CLI\CLI;
/**
* Dry-run / apply existing-ticket status+link update logic for any TPA.
*
* Usage:
* php spark tpa:test-status
* php spark tpa:test-status --tpa=all
* php spark tpa:test-status --tpa=abhi --scenario
* php spark tpa:test-status --tpa=fhpl --scenario --apply
* php spark tpa:test-status --tpa=icici 69 --apply
*/
class TestTpaStatusUpdate extends BaseCommand
{
protected $group = 'TPA';
protected $name = 'tpa:test-status';
protected $description = 'Dry-run / apply existing ticket status+link updates for TPA claim dumps';
protected $usage = 'tpa:test-status [file_id] [--tpa=NAME|all] [--scenario] [--apply] [--new-status=STATUS]';
protected $arguments = [
'file_id' => '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;
}
}