nhance/app/Libraries/ClaimReportSyncService.php
2026-07-29 11:15:54 +05:30

445 lines
16 KiB
PHP

<?php
namespace App\Libraries;
use App\Libraries\TpaClaimsImportFactory;
use InvalidArgumentException;
/**
* Sync claim_report from TPA dump tables (+ optional ticket_master).
* Safe to call from HTTP or spark (no CLI constants required).
*/
class ClaimReportSyncService
{
/**
* @return array<string, array{env:string,table:string}>
*/
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<string>,
* tpa_results: list<array<string,mixed>>,
* logs: list<string>
* }
*/
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<array<string, mixed>> $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<string> $phases
* @param list<array<string,mixed>> $tpaResults
* @param list<string> $logs
* @return array<string, mixed>
*/
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,
];
}
}