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 */ 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)); } }