756 lines
29 KiB
PHP
756 lines
29 KiB
PHP
<?php
|
|
|
|
namespace App\Controllers;
|
|
|
|
use App\Models\ClaimReportDashboardModel;
|
|
use App\Models\ClaimsCollectionV2DashboardModel;
|
|
use App\Models\ClaimDumpFileModel;
|
|
use CodeIgniter\API\ResponseTrait;
|
|
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
|
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
|
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
|
|
|
/**
|
|
* 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
|
|
{
|
|
use ResponseTrait;
|
|
|
|
/**
|
|
* Resolve policy id from request; optional $fallback (e.g. route default for debug only).
|
|
*/
|
|
protected function resolvePolicyId(?int $fallback = null): int
|
|
{
|
|
$id = (int) (
|
|
$this->request->getGet('client_policy')
|
|
?? $this->request->getGet('client_policy_id')
|
|
?? $this->request->getPost('client_policy')
|
|
?? $this->request->getPost('client_policy_id')
|
|
?? 0
|
|
);
|
|
|
|
if ($id > 0) {
|
|
return $id;
|
|
}
|
|
|
|
if ($fallback !== null && $fallback > 0) {
|
|
return $fallback;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Resolve KPI by Metabase numeric id or method slug.
|
|
*/
|
|
protected function resolveKpiMethod(string $kpiKey): ?string
|
|
{
|
|
$kpiKey = trim($kpiKey);
|
|
if ($kpiKey === '') {
|
|
return null;
|
|
}
|
|
|
|
if (in_array($kpiKey, ClaimsCollectionV2DashboardModel::KPI_MAP, true)) {
|
|
return $kpiKey;
|
|
}
|
|
|
|
if (ctype_digit($kpiKey)) {
|
|
$id = (int) $kpiKey;
|
|
return ClaimsCollectionV2DashboardModel::KPI_MAP[$id] ?? null;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* JSON: single KPI by slug or Metabase id.
|
|
*/
|
|
public function kpi(string $kpiMethod = '')
|
|
{
|
|
$policyId = $this->resolvePolicyId();
|
|
|
|
if ($policyId <= 0) {
|
|
return $this->respond([
|
|
'status' => false,
|
|
'message' => 'client_policy or client_policy_id is required.',
|
|
], 422);
|
|
}
|
|
|
|
$kpiMethod = $this->resolveKpiMethod($kpiMethod);
|
|
if ($kpiMethod === null) {
|
|
return $this->respond([
|
|
'status' => false,
|
|
'message' => 'Unknown KPI. Pass Metabase id or method slug.',
|
|
'allowed' => ClaimsCollectionV2DashboardModel::KPI_MAP,
|
|
], 404);
|
|
}
|
|
|
|
$model = new ClaimReportDashboardModel();
|
|
$metabaseId = array_search($kpiMethod, ClaimsCollectionV2DashboardModel::KPI_MAP, true);
|
|
|
|
return $this->respond([
|
|
'status' => true,
|
|
'policy_id' => $policyId,
|
|
'claims_source' => $model->resolveClaimsTable($policyId),
|
|
'kpi_id' => $metabaseId !== false ? (int) $metabaseId : null,
|
|
'kpi' => $kpiMethod,
|
|
'label' => ClaimsCollectionV2DashboardModel::KPI_LABELS[$kpiMethod] ?? $kpiMethod,
|
|
'rows' => $model->getKpi($kpiMethod, $policyId),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* JSON: all KPIs.
|
|
*/
|
|
public function all()
|
|
{
|
|
$policyId = $this->resolvePolicyId();
|
|
|
|
if ($policyId <= 0) {
|
|
return $this->respond([
|
|
'status' => false,
|
|
'message' => 'client_policy or client_policy_id is required.',
|
|
], 422);
|
|
}
|
|
|
|
$generatedAt = (new ClaimDumpFileModel())->getGeneratedAtForPolicy($policyId);
|
|
// if ($generatedAt === null) {
|
|
// return $this->respond([
|
|
// 'status' => false,
|
|
// 'message' => 'claim_dump_date is mandatory. No claim dump date found for this policy.',
|
|
// ], 422);
|
|
// }
|
|
|
|
$model = new ClaimReportDashboardModel();
|
|
$data = $model->getAllKpis($policyId);
|
|
$source = $data['_meta']['claims_source'] ?? $model->resolveClaimsTable($policyId);
|
|
unset($data['_meta']);
|
|
|
|
return $this->respond([
|
|
'status' => true,
|
|
'policy_id' => $policyId,
|
|
'claims_source' => $source,
|
|
'generated_at' => $generatedAt,
|
|
'data' => $data,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Admin preview UI — KPI grid for manual testing.
|
|
*/
|
|
public function preview(int $policyId = 4687)
|
|
{
|
|
$policyId = $this->resolvePolicyId($policyId);
|
|
$path = $this->request->getUri()->getPath();
|
|
$isJwt = stripos($path, 'employeeRest') !== false;
|
|
$prefix = $isJwt ? 'employeeRest/claims-collection-report' : 'util/claims-collection-report';
|
|
|
|
return view('claims_collection_v2_dashboard', [
|
|
'policy_id' => $policyId,
|
|
'kpi_map' => ClaimsCollectionV2DashboardModel::KPI_MAP,
|
|
'kpi_labels' => ClaimsCollectionV2DashboardModel::KPI_LABELS,
|
|
'api_all_url' => base_url($prefix . '/all'),
|
|
'api_kpi_url' => base_url($prefix . '/kpi'),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 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).
|
|
*/
|
|
public function debug(int $policyId = 4687)
|
|
{
|
|
$policyId = $this->resolvePolicyId($policyId);
|
|
|
|
if ($policyId <= 0) {
|
|
return $this->response
|
|
->setStatusCode(422)
|
|
->setBody('client_policy or client_policy_id is required.');
|
|
}
|
|
|
|
$model = new ClaimReportDashboardModel();
|
|
$data = $model->getAllKpis($policyId);
|
|
$source = $data['_meta']['claims_source'] ?? $model->resolveClaimsTable($policyId);
|
|
unset($data['_meta']);
|
|
|
|
$body = json_encode([
|
|
'status' => true,
|
|
'policy_id' => $policyId,
|
|
'claims_source' => $source,
|
|
'data' => $data,
|
|
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
|
|
|
return $this->response
|
|
->setHeader('Content-Type', 'application/json; charset=UTF-8')
|
|
->setBody($body);
|
|
}
|
|
|
|
/**
|
|
* Download Excel of TPA dump rows linked from claim_report for a policy.
|
|
*
|
|
* Query: client_policy_id (or client_policy) — required.
|
|
* Uses claim_report.source_table + source_row_id to load dump rows.
|
|
* Excel columns = only fields AFTER master_reject_reason (that column and earlier are excluded).
|
|
*
|
|
* GET /util/claims-collection-report/download-excel?client_policy_id=123
|
|
*/
|
|
public function downloadExcel()
|
|
{
|
|
@set_time_limit(0);
|
|
@ini_set('max_execution_time', '0');
|
|
|
|
$policyId = $this->resolvePolicyId();
|
|
if ($policyId <= 0) {
|
|
return $this->respond([
|
|
'status' => false,
|
|
'message' => 'client_policy_id is required.',
|
|
], 422);
|
|
}
|
|
|
|
$db = db_connect();
|
|
|
|
if (!$db->tableExists('claim_report')) {
|
|
return $this->respond([
|
|
'status' => false,
|
|
'message' => 'Table claim_report does not exist.',
|
|
], 500);
|
|
}
|
|
|
|
$allowedDumpTables = [
|
|
'claims_dump_vidal',
|
|
'claims_dump_abhi',
|
|
'claims_dump_medi_assist',
|
|
'claims_dump_fhpl',
|
|
'claims_dump_reliance',
|
|
'claims_dump_icici',
|
|
];
|
|
|
|
$reportRows = $db->table('claim_report')
|
|
->select('source_table, source_row_id')
|
|
->where('client_policy_id', $policyId)
|
|
->where('is_active', 1)
|
|
->where('source_table IS NOT NULL', null, false)
|
|
->where('source_row_id IS NOT NULL', null, false)
|
|
->get()
|
|
->getResultArray();
|
|
|
|
if ($reportRows === []) {
|
|
return $this->respond([
|
|
'status' => false,
|
|
'message' => 'No claim_report records with dump linkage found for this policy.',
|
|
], 404);
|
|
}
|
|
|
|
// Group dump IDs by source_table
|
|
$idsByTable = [];
|
|
foreach ($reportRows as $row) {
|
|
$table = trim((string) ($row['source_table'] ?? ''));
|
|
$dumpId = (int) ($row['source_row_id'] ?? 0);
|
|
if ($table === '' || $dumpId <= 0 || ! in_array($table, $allowedDumpTables, true)) {
|
|
continue;
|
|
}
|
|
if (!$db->tableExists($table)) {
|
|
continue;
|
|
}
|
|
$idsByTable[$table][$dumpId] = $dumpId;
|
|
}
|
|
|
|
if ($idsByTable === []) {
|
|
return $this->respond([
|
|
'status' => false,
|
|
'message' => 'No valid TPA dump table references found for this policy.',
|
|
], 404);
|
|
}
|
|
|
|
$spreadsheet = new Spreadsheet();
|
|
$spreadsheet->removeSheetByIndex(0);
|
|
$sheetIndex = 0;
|
|
$totalRows = 0;
|
|
|
|
foreach ($idsByTable as $dumpTable => $dumpIds) {
|
|
$dumpIds = array_values($dumpIds);
|
|
$exportColumns = $this->getDumpColumnsAfterMasterReject($db, $dumpTable);
|
|
if ($exportColumns === []) {
|
|
continue;
|
|
}
|
|
|
|
$dumpRows = $db->table($dumpTable)
|
|
->whereIn('id', $dumpIds)
|
|
->orderBy('id', 'ASC')
|
|
->get()
|
|
->getResultArray();
|
|
|
|
if ($dumpRows === []) {
|
|
continue;
|
|
}
|
|
|
|
$sheetTitle = substr($dumpTable, 0, 31);
|
|
$sheet = $spreadsheet->createSheet($sheetIndex);
|
|
$sheet->setTitle($sheetTitle);
|
|
$sheetIndex++;
|
|
|
|
// Headers
|
|
foreach ($exportColumns as $colIdx => $colName) {
|
|
$sheet->setCellValue(
|
|
Coordinate::stringFromColumnIndex($colIdx + 1) . '1',
|
|
$colName
|
|
);
|
|
}
|
|
|
|
// Data
|
|
$excelRow = 2;
|
|
foreach ($dumpRows as $dumpRow) {
|
|
foreach ($exportColumns as $colIdx => $colName) {
|
|
$sheet->setCellValue(
|
|
Coordinate::stringFromColumnIndex($colIdx + 1) . $excelRow,
|
|
$dumpRow[$colName] ?? null
|
|
);
|
|
}
|
|
$excelRow++;
|
|
$totalRows++;
|
|
}
|
|
}
|
|
|
|
if ($sheetIndex === 0 || $totalRows === 0) {
|
|
return $this->respond([
|
|
'status' => false,
|
|
'message' => 'No dump table rows found for the linked claim_report records.',
|
|
], 404);
|
|
}
|
|
|
|
$spreadsheet->setActiveSheetIndex(0);
|
|
|
|
$policy = $db->table('client_policy')
|
|
->select('policy_no')
|
|
->where('id', $policyId)
|
|
->get()
|
|
->getRowArray();
|
|
|
|
$policyNo = trim((string) ($policy['policy_no'] ?? ''));
|
|
if ($policyNo === '') {
|
|
$policyNo = (string) $policyId;
|
|
}
|
|
// Safe filename segment
|
|
$policyNoSafe = preg_replace('/[^A-Za-z0-9_\-]+/', '_', $policyNo) ?: (string) $policyId;
|
|
|
|
$filename = 'claim_report_' . $policyNoSafe . '_' . date('Ymd_His') . '.xlsx';
|
|
|
|
ob_start();
|
|
(new Xlsx($spreadsheet))->save('php://output');
|
|
$excelOutput = ob_get_clean();
|
|
|
|
return $this->response
|
|
->setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
|
|
->setHeader('Content-Disposition', 'attachment; filename="' . $filename . '"')
|
|
->setHeader('Cache-Control', 'max-age=0')
|
|
->setBody($excelOutput);
|
|
}
|
|
|
|
/**
|
|
* Return dump-table column names that appear AFTER master_reject_reason
|
|
* (master_reject_reason itself and all earlier columns are excluded).
|
|
*
|
|
* @return list<string>
|
|
*/
|
|
protected function getDumpColumnsAfterMasterReject($db, string $table): array
|
|
{
|
|
$fields = $db->getFieldNames($table);
|
|
if ($fields === [] || $fields === false) {
|
|
return [];
|
|
}
|
|
|
|
$marker = null;
|
|
foreach ($fields as $idx => $name) {
|
|
$lower = strtolower((string) $name);
|
|
if (
|
|
$lower === 'master_reject_reason'
|
|
|| $lower === 'master_rejected_reason'
|
|
|| $lower === 'master_rejection_reason'
|
|
) {
|
|
$marker = $idx;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Marker not found — export nothing rather than leaking internal columns
|
|
if ($marker === null) {
|
|
return [];
|
|
}
|
|
|
|
return array_values(array_slice($fields, $marker + 1));
|
|
}
|
|
|
|
}
|