MERGE_TEST_BUG_FIXES
This commit is contained in:
commit
cc146f5fb6
@ -2,19 +2,18 @@
|
||||
|
||||
namespace App\Commands;
|
||||
|
||||
use App\Libraries\TpaClaimsImportFactory;
|
||||
use App\Libraries\ClaimReportSyncService;
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\CLI;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Copy linked TPA dump + ticket_master claims into claim_report.
|
||||
* Copy linked TPA dump (+ optional ticket_master) claims into claim_report.
|
||||
*
|
||||
* Usage:
|
||||
* php spark claim:sync-report
|
||||
* php spark claim:sync-report --policy=12
|
||||
* php spark claim:sync-report --phase1
|
||||
* php spark claim:sync-report --policy=12 --phase1
|
||||
* php spark claim:sync-report --tpa=mediassist --policy=12
|
||||
* php spark claim:sync-report --limit=500
|
||||
* php spark claim:sync-report --ticket-only
|
||||
*/
|
||||
class SyncClaimReportFromDump extends BaseCommand
|
||||
@ -22,338 +21,43 @@ class SyncClaimReportFromDump extends BaseCommand
|
||||
protected $group = 'Claims';
|
||||
protected $name = 'claim:sync-report';
|
||||
protected $description = 'Sync claim_report from TPA dump tables + ticket_master';
|
||||
protected $usage = 'claim:sync-report [--policy=ID] [--tpa=NAME|all] [--limit=N] [--ticket-only]';
|
||||
protected $usage = 'claim:sync-report [--policy=ID] [--tpa=NAME|all] [--limit=N] [--phase1] [--ticket-only]';
|
||||
protected $options = [
|
||||
'--policy' => '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',
|
||||
];
|
||||
|
||||
/**
|
||||
* @return array<string, array{env:string,table:string}>
|
||||
*/
|
||||
private 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',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function run(array $params)
|
||||
{
|
||||
$db = db_connect();
|
||||
|
||||
if (!$db->tableExists('claim_report')) {
|
||||
CLI::error('Table claim_report does not exist. Run: php spark migrate');
|
||||
return EXIT_ERROR;
|
||||
}
|
||||
|
||||
// Prefer $params (works from HTTP command() helper) then CLI options (spark).
|
||||
$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;
|
||||
}
|
||||
|
||||
$totalMapped = 0;
|
||||
$totalSkipped = 0;
|
||||
$errors = 0;
|
||||
$result = (new ClaimReportSyncService())->sync($policyId, $tpaOpt, $limit, $ticketOnly, $phase1Only);
|
||||
|
||||
if (!$ticketOnly) {
|
||||
CLI::write('Phase 1: sync from TPA dump tables (linked ticket_id rows)...', 'yellow');
|
||||
|
||||
$configs = $this->tpaConfigs();
|
||||
if ($tpaOpt !== '' && $tpaOpt !== 'all') {
|
||||
if (!isset($configs[$tpaOpt])) {
|
||||
CLI::error('Unknown --tpa=' . $tpaOpt . '. Use: ' . implode('|', array_keys($configs)) . '|all');
|
||||
return EXIT_ERROR;
|
||||
}
|
||||
$configs = [$tpaOpt => $configs[$tpaOpt]];
|
||||
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';
|
||||
}
|
||||
|
||||
foreach ($configs as $key => $cfg) {
|
||||
$tpaId = (int) env($cfg['env']);
|
||||
$table = $cfg['table'];
|
||||
|
||||
if ($tpaId <= 0) {
|
||||
CLI::write(" [SKIP] {$key}: env {$cfg['env']} not set", 'light_gray');
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$db->tableExists($table)) {
|
||||
CLI::write(" [SKIP] {$key}: table {$table} missing", 'light_gray');
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$service = TpaClaimsImportFactory::make($tpaId);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
CLI::write(' [SKIP] ' . $key . ': ' . $e->getMessage(), 'light_gray');
|
||||
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;
|
||||
}
|
||||
|
||||
// Group dump IDs by file_id for mapClaimReportData
|
||||
$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']) {
|
||||
CLI::error(" [FAIL] {$key} file_id={$fileId} upsert failed");
|
||||
$errors++;
|
||||
continue;
|
||||
}
|
||||
$tpaMapped += (int) $result['count'];
|
||||
$totalMapped += (int) $result['count'];
|
||||
}
|
||||
|
||||
$offset += count($dumpRows);
|
||||
CLI::write(" {$key}: processed {$offset} dump rows (mapped so far {$tpaMapped})", 'green');
|
||||
|
||||
if (count($dumpRows) < $limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
CLI::write(" [DONE] {$key} mapped≈{$tpaMapped}", 'green');
|
||||
}
|
||||
} else {
|
||||
CLI::write('Skipping dump tables (--ticket-only).', 'yellow');
|
||||
CLI::write($line, $color);
|
||||
}
|
||||
|
||||
CLI::write('Phase 2: fill gaps from ticket_master dump-sourced claims...', 'yellow');
|
||||
$tmResult = $this->syncFromTicketMaster($db, $policyId, $limit);
|
||||
if ($tmResult === false) {
|
||||
return EXIT_ERROR;
|
||||
}
|
||||
|
||||
$totalMapped += $tmResult['mapped'];
|
||||
$totalSkipped += $tmResult['skipped'];
|
||||
|
||||
CLI::write(
|
||||
"Done. dump+ticket mapped≈{$totalMapped}, skipped={$totalSkipped}, errors={$errors}",
|
||||
$errors > 0 ? 'red' : 'green'
|
||||
);
|
||||
|
||||
return $errors > 0 ? EXIT_ERROR : EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{mapped:int,skipped:int}|false
|
||||
*/
|
||||
private function syncFromTicketMaster($db, int $policyId, int $limit)
|
||||
{
|
||||
if (!$db->tableExists('ticket_master')) {
|
||||
CLI::error('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;
|
||||
}
|
||||
|
||||
// Resolve dump provenance when possible
|
||||
$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) {
|
||||
CLI::error('ticket_master upsert failed at offset ' . $offset);
|
||||
return false;
|
||||
}
|
||||
|
||||
$mapped += count($rows);
|
||||
$offset += count($tickets);
|
||||
CLI::write(" ticket_master: processed {$offset} rows (mapped {$mapped})", 'green');
|
||||
|
||||
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) {
|
||||
// Prefer non-empty dump enrichment already written in phase 1:
|
||||
// only overwrite when VALUES has a non-null value.
|
||||
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;
|
||||
return ! empty($result['status']) ? EXIT_SUCCESS : EXIT_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
@ -865,6 +865,7 @@ $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) {
|
||||
|
||||
@ -898,6 +898,7 @@ class ApiServiceController extends BaseController
|
||||
->where('client_policy.tpa_id', $tpa_id)
|
||||
->where('batch_files.is_active', 1)
|
||||
->where('batch_files.event_type', 'api')
|
||||
->where('batch_files.client_policy_id', $policy_id)
|
||||
->where('batch_files.icici_status_flag !=', 'COMPLETED')
|
||||
->countAllResults();
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ namespace App\Controllers;
|
||||
use App\Models\ClaimReportDashboardModel;
|
||||
use App\Models\ClaimsCollectionV2DashboardModel;
|
||||
use App\Models\ClaimDumpFileModel;
|
||||
use App\Libraries\ClaimReportSyncService;
|
||||
use CodeIgniter\API\ResponseTrait;
|
||||
|
||||
/**
|
||||
@ -183,29 +184,26 @@ class ClaimReportDashboardController extends BaseController
|
||||
}
|
||||
|
||||
/**
|
||||
* Run claim:sync-report via URL (authMVC / JWT).
|
||||
* Run claim report sync via URL (authMVC / JWT).
|
||||
* Uses ClaimReportSyncService directly (no spark/CLI).
|
||||
*
|
||||
* Query params (all optional):
|
||||
* client_policy / client_policy_id → --policy=
|
||||
* client_policy / client_policy_id
|
||||
* tpa=icici|abhi|mediassist|fhpl|rcare|vidal|all
|
||||
* limit=500
|
||||
* ticket_only=1
|
||||
*
|
||||
* Examples:
|
||||
* /util/claims-collection-report/sync
|
||||
* /util/claims-collection-report/sync?client_policy=12
|
||||
* /util/claims-collection-report/sync?client_policy=12&tpa=icici&limit=200
|
||||
* phase1=true → TPA dump tables only (skip ticket_master)
|
||||
* ticket_only=1 → ticket_master only (ignored if phase1=true)
|
||||
*/
|
||||
public function sync()
|
||||
{
|
||||
// Avoid request/proxy timeouts on large syncs
|
||||
@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 = (string) ($this->request->getGet('ticket_only') ?? $this->request->getPost('ticket_only') ?? '') !== '';
|
||||
$ticketOnly = $this->isTruthyParam('ticket_only');
|
||||
$phase1Only = $this->isTruthyParam('phase1');
|
||||
|
||||
$allowedTpa = ['all', 'vidal', 'abhi', 'mediassist', 'fhpl', 'rcare', 'icici'];
|
||||
if ($tpa === '' || ! in_array($tpa, $allowedTpa, true)) {
|
||||
@ -222,131 +220,58 @@ class ClaimReportDashboardController extends BaseController
|
||||
$limit = 5000;
|
||||
}
|
||||
|
||||
$parts = ['claim:sync-report', '--tpa', $tpa, '--limit', (string) $limit];
|
||||
if ($policyId > 0) {
|
||||
$parts[] = '--policy';
|
||||
$parts[] = (string) $policyId;
|
||||
}
|
||||
if ($ticketOnly) {
|
||||
$parts[] = '--ticket-only';
|
||||
}
|
||||
|
||||
$cmd = implode(' ', $parts);
|
||||
$startedAt = date('Y-m-d H:i:s');
|
||||
|
||||
try {
|
||||
$output = command($cmd);
|
||||
$result = (new ClaimReportSyncService())->sync(
|
||||
$policyId,
|
||||
$tpa,
|
||||
$limit,
|
||||
$ticketOnly,
|
||||
$phase1Only
|
||||
);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->respond([
|
||||
'status' => false,
|
||||
'message' => 'Sync failed: ' . $e->getMessage(),
|
||||
'command' => $cmd,
|
||||
'started_at' => $startedAt,
|
||||
'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);
|
||||
}
|
||||
|
||||
$output = is_string($output) ? trim($output) : '';
|
||||
$failed = stripos($output, '[FAIL]') !== false
|
||||
|| stripos($output, 'does not exist') !== false
|
||||
|| stripos($output, 'upsert failed') !== false;
|
||||
|
||||
$parsed = $this->parseSyncOutput($output);
|
||||
$ok = ! empty($result['status']);
|
||||
|
||||
return $this->respond([
|
||||
'status' => ! $failed,
|
||||
'message' => $failed ? 'Sync completed with errors. See summary.' : 'Sync completed.',
|
||||
'command' => $cmd,
|
||||
'status' => $ok,
|
||||
'message' => $result['message'] ?? ($ok ? 'Sync completed.' : 'Sync failed.'),
|
||||
'policy_id' => $policyId > 0 ? $policyId : null,
|
||||
'tpa' => $tpa,
|
||||
'limit' => $limit,
|
||||
'ticket_only' => $ticketOnly,
|
||||
'phase1' => $phase1Only,
|
||||
'ticket_only' => $ticketOnly && ! $phase1Only,
|
||||
'started_at' => $startedAt,
|
||||
'finished_at' => date('Y-m-d H:i:s'),
|
||||
'summary' => $parsed['summary'],
|
||||
'phases' => $parsed['phases'],
|
||||
'tpa_results' => $parsed['tpa_results'],
|
||||
'logs' => $parsed['logs'],
|
||||
], $failed ? 500 : 200);
|
||||
'summary' => $result['summary'] ?? null,
|
||||
'phases' => $result['phases'] ?? [],
|
||||
'tpa_results' => $result['tpa_results'] ?? [],
|
||||
'logs' => $result['logs'] ?? [],
|
||||
], $ok ? 200 : 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn CLI sync text into a readable structured payload.
|
||||
*
|
||||
* @return array{
|
||||
* summary: array<string, mixed>,
|
||||
* phases: list<string>,
|
||||
* tpa_results: list<array<string, mixed>>,
|
||||
* logs: list<string>
|
||||
* }
|
||||
* True when GET/POST param is 1/true/yes (case-insensitive).
|
||||
*/
|
||||
protected function parseSyncOutput(string $output): array
|
||||
protected function isTruthyParam(string $name): bool
|
||||
{
|
||||
$lines = preg_split('/\R+/', $output) ?: [];
|
||||
$logs = [];
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line);
|
||||
if ($line !== '') {
|
||||
$logs[] = $line;
|
||||
}
|
||||
$raw = $this->request->getGet($name) ?? $this->request->getPost($name);
|
||||
if ($raw === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$phases = [];
|
||||
$tpaResults = [];
|
||||
$summary = [
|
||||
'mapped' => null,
|
||||
'skipped' => null,
|
||||
'errors' => null,
|
||||
'message' => null,
|
||||
];
|
||||
|
||||
foreach ($logs as $line) {
|
||||
if (stripos($line, 'Phase 1:') === 0 || stripos($line, 'Phase 2:') === 0) {
|
||||
$phases[] = $line;
|
||||
continue;
|
||||
}
|
||||
|
||||
// [DONE] icici mapped≈14
|
||||
if (preg_match('/\[DONE\]\s+(\w+)\s+mapped≈(\d+)/i', $line, $m)) {
|
||||
$tpaResults[] = [
|
||||
'tpa' => strtolower($m[1]),
|
||||
'mapped' => (int) $m[2],
|
||||
'status' => 'done',
|
||||
'detail' => $line,
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
// [SKIP] abhi: table missing
|
||||
if (preg_match('/\[SKIP\]\s+(.+)/i', $line, $m)) {
|
||||
$tpaResults[] = [
|
||||
'tpa' => null,
|
||||
'mapped' => 0,
|
||||
'status' => 'skipped',
|
||||
'detail' => trim($m[1]),
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Done. dump+ticket mapped≈16, skipped=14, errors=0
|
||||
if (preg_match(
|
||||
'/Done\.\s*dump\+ticket mapped≈(\d+),\s*skipped=(\d+),\s*errors=(\d+)/i',
|
||||
$line,
|
||||
$m
|
||||
)) {
|
||||
$summary = [
|
||||
'mapped' => (int) $m[1],
|
||||
'skipped' => (int) $m[2],
|
||||
'errors' => (int) $m[3],
|
||||
'message' => $line,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'summary' => $summary,
|
||||
'phases' => $phases,
|
||||
'tpa_results' => $tpaResults,
|
||||
'logs' => $logs,
|
||||
];
|
||||
return in_array(strtolower(trim((string) $raw)), ['1', 'true', 'yes'], true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -4072,8 +4072,8 @@ class PolicyTransactionController extends BaseController
|
||||
$user_id = $this->request->getGet('user_id') ?? $this->request->getPost('user_id');
|
||||
|
||||
if ($date_type == 'statement_month' && $start_date && $end_date) {
|
||||
$start_date = (string) date('Y-m-01', strtotime($start_date));
|
||||
$end_date = (string) date('Y-m-t', strtotime($end_date));
|
||||
$start_date = (string) date('Y-m-01', strtotime($start_date));
|
||||
$end_date = (string) date('Y-m-t', strtotime($end_date));
|
||||
}
|
||||
|
||||
$normalize = static function ($value) {
|
||||
@ -4090,7 +4090,7 @@ class PolicyTransactionController extends BaseController
|
||||
$ids = array_filter(explode(',', $sanitized_post_data['ids'] ?? ''));
|
||||
|
||||
if (!empty($ids)) {
|
||||
$idsStr = implode(',', array_map('intval', $ids));
|
||||
$idsStr = implode(',', array_map('intval', $ids));
|
||||
$where = "policy_transaction.id IN ($idsStr)";
|
||||
} else {
|
||||
$where = [];
|
||||
|
||||
444
app/Libraries/ClaimReportSyncService.php
Normal file
444
app/Libraries/ClaimReportSyncService.php
Normal file
@ -0,0 +1,444 @@
|
||||
<?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,
|
||||
];
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user