FEAT_TPA_REPORT_HTML

This commit is contained in:
VENKATESHWARAN 2026-08-07 10:19:31 +05:30
parent 30ceb22676
commit 281516c5dc
30 changed files with 12763 additions and 28 deletions

View File

@ -257,6 +257,8 @@ $routes->group("/employee", ["filter" => "authMVC"], function ($routes) {
$routes->get('clearCdSession', 'EmployeeController::clearCdSession');
$routes->get('checkSessionStatus', 'EmployeeController::checkSessionStatus');
$routes->get("tpaReportsDashboard", "EmployeeController::tpaReportsDashboard");
$routes->get("tpaReportsMetabase", "EmployeeController::tpaReportsMetabase");
$routes->get("tpaMisReport", "EmployeeController::tpaMisReport");
});
@ -513,6 +515,13 @@ $routes->group("/util", ["filter" => "authMVC"], function ($routes) {
$routes->get('debug/(:num)', 'ClaimReportDashboardController::debug/$1');
$routes->get('sync', 'ClaimReportDashboardController::sync');
$routes->get('download-excel', 'ClaimReportDashboardController::downloadExcel');
$routes->get('mis', 'ClaimReportDashboardController::mis');
$routes->get('mis-pdf', 'ClaimReportDashboardController::misPdf');
});
$routes->group('tpa-reports', static function ($routes) {
$routes->get('metabase', 'EmployeeController::tpaReportsMetabase');
$routes->get('mis', 'EmployeeController::tpaMisReport');
});
$routes->group('enrollment-collection-v1', static function ($routes) {
@ -883,6 +892,13 @@ $routes->group("employeeRest", ["filter" => ['GlobalPostFileUploadGuard', 'ratel
$routes->get('debug', 'ClaimReportDashboardController::debug');
$routes->get('debug/(:num)', 'ClaimReportDashboardController::debug/$1');
$routes->get('download-excel', 'ClaimReportDashboardController::downloadExcel');
$routes->get('mis', 'ClaimReportDashboardController::mis');
$routes->get('mis-pdf', 'ClaimReportDashboardController::misPdf');
});
$routes->group('tpa-reports', static function ($routes) {
$routes->get('metabase', 'EmployeeController::tpaReportsMetabase');
$routes->get('mis', 'EmployeeController::tpaMisReport');
});
$routes->group('enrollment-collection-v1', static function ($routes) {

View File

@ -5,6 +5,14 @@ namespace App\Controllers;
use App\Models\ClaimReportDashboardModel;
use App\Models\ClaimsCollectionV2DashboardModel;
use App\Models\ClaimDumpFileModel;
use App\Libraries\AbhiMisReportService;
use App\Libraries\AbhiMisPdfService;
use App\Libraries\IciciMisReportService;
use App\Libraries\IciciMisPdfService;
use App\Libraries\MediAssistMisReportService;
use App\Libraries\MediAssistMisPdfService;
use App\Libraries\VidalMisReportService;
use App\Libraries\VidalMisPdfService;
use CodeIgniter\API\ResponseTrait;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
@ -752,4 +760,257 @@ class ClaimReportDashboardController extends BaseController
return array_values(array_slice($fields, $marker + 1));
}
/**
* HTML preview of TPA-specific MIS report for a policy.
* Resolves TPA from client_policy and routes to ABHI / ICICI templates.
*
* GET /util/claims-collection-report/mis?client_policy_id=
*/
public function mis()
{
$policyId = $this->resolvePolicyId();
if ($policyId <= 0) {
return $this->respond([
'status' => false,
'message' => 'client_policy or client_policy_id is required.',
], 422);
}
$resolved = $this->resolveMisKind($policyId);
if (($resolved['kind'] ?? null) === null) {
return $this->respond([
'status' => false,
'message' => $resolved['message'] ?? 'MIS report is not available for this policy.',
], 404);
}
$path = $this->request->getUri()->getPath();
$isJwt = stripos($path, 'employeeRest') !== false;
$prefix = $isJwt ? 'employeeRest/claims-collection-report' : 'util/claims-collection-report';
$pdfUrl = base_url($prefix . '/mis-pdf?client_policy_id=' . $policyId);
if ($resolved['kind'] === 'icici') {
$built = (new IciciMisReportService())->build($policyId);
if (!$built['status']) {
return $this->respond([
'status' => false,
'message' => $built['message'] ?? 'Unable to build ICICI MIS report.',
], 404);
}
return view('claims_mis_icici', [
'report' => $built['data'],
'view_model' => $built['data']['view_model'] ?? [],
'policy_id' => $policyId,
'pdf_url' => $pdfUrl,
'embed' => false,
]);
}
if ($resolved['kind'] === 'medi_assist') {
$built = (new MediAssistMisReportService())->build($policyId);
if (!$built['status']) {
return $this->respond([
'status' => false,
'message' => $built['message'] ?? 'Unable to build Medi Assist MIS report.',
], 404);
}
return view('claims_mis_medi_assist', [
'report' => $built['data'],
'view_model' => $built['data']['view_model'] ?? [],
'policy_id' => $policyId,
'pdf_url' => $pdfUrl,
'embed' => false,
]);
}
if ($resolved['kind'] === 'vidal') {
$built = (new VidalMisReportService())->build($policyId);
if (!$built['status']) {
return $this->respond([
'status' => false,
'message' => $built['message'] ?? 'Unable to build Vidal MIS report.',
], 404);
}
return view('claims_mis_vidal', [
'report' => $built['data'],
'view_model' => $built['data']['view_model'] ?? [],
'policy_id' => $policyId,
'pdf_url' => $pdfUrl,
'embed' => false,
]);
}
$built = (new AbhiMisReportService())->build($policyId);
if (!$built['status']) {
return $this->respond([
'status' => false,
'message' => $built['message'] ?? 'Unable to build MIS report.',
], 404);
}
return view('claims_mis_abhi', [
'report' => $built['data'],
'view_model' => $built['data']['view_model'] ?? [],
'policy_id' => $policyId,
'pdf_url' => $pdfUrl,
'embed' => false,
]);
}
/**
* Download TPA-specific MIS report as PDF.
* Resolves TPA from client_policy and routes to ABHI / ICICI PDF generators.
*
* GET /util/claims-collection-report/mis-pdf?client_policy_id=
*/
public function misPdf()
{
$policyId = $this->resolvePolicyId();
if ($policyId <= 0) {
return $this->respond([
'status' => false,
'message' => 'client_policy or client_policy_id is required.',
], 422);
}
$resolved = $this->resolveMisKind($policyId);
if (($resolved['kind'] ?? null) === null) {
return $this->respond([
'status' => false,
'message' => $resolved['message'] ?? 'MIS report is not available for this policy.',
], 404);
}
if ($resolved['kind'] === 'icici') {
$built = (new IciciMisReportService())->build($policyId);
if (!$built['status']) {
return $this->respond([
'status' => false,
'message' => $built['message'] ?? 'Unable to build ICICI MIS report.',
], 404);
}
$pdf = (new IciciMisPdfService())->generateFromReport($built['data'], $policyId);
if (!$pdf['status']) {
return $this->respond([
'status' => false,
'message' => $pdf['message'] ?? 'PDF generation failed.',
], 500);
}
return $this->response
->setHeader('Content-Type', 'application/pdf')
->setHeader('Content-Disposition', 'attachment; filename="' . ($pdf['filename'] ?? 'ICICI_MIS.pdf') . '"')
->setBody($pdf['content'] ?? '');
}
if ($resolved['kind'] === 'medi_assist') {
$built = (new MediAssistMisReportService())->build($policyId);
if (!$built['status']) {
return $this->respond([
'status' => false,
'message' => $built['message'] ?? 'Unable to build Medi Assist MIS report.',
], 404);
}
$pdf = (new MediAssistMisPdfService())->generateFromReport($built['data'], $policyId);
if (!$pdf['status']) {
return $this->respond([
'status' => false,
'message' => $pdf['message'] ?? 'PDF generation failed.',
], 500);
}
return $this->response
->setHeader('Content-Type', 'application/pdf')
->setHeader('Content-Disposition', 'attachment; filename="' . ($pdf['filename'] ?? 'MA_MIS.pdf') . '"')
->setBody($pdf['content'] ?? '');
}
if ($resolved['kind'] === 'vidal') {
$built = (new VidalMisReportService())->build($policyId);
if (!$built['status']) {
return $this->respond([
'status' => false,
'message' => $built['message'] ?? 'Unable to build Vidal MIS report.',
], 404);
}
$pdf = (new VidalMisPdfService())->generateFromReport($built['data'], $policyId);
if (!$pdf['status']) {
return $this->respond([
'status' => false,
'message' => $pdf['message'] ?? 'PDF generation failed.',
], 500);
}
return $this->response
->setHeader('Content-Type', 'application/pdf')
->setHeader('Content-Disposition', 'attachment; filename="' . ($pdf['filename'] ?? 'VIDAL_MIS.pdf') . '"')
->setBody($pdf['content'] ?? '');
}
$built = (new AbhiMisReportService())->build($policyId);
if (!$built['status']) {
return $this->respond([
'status' => false,
'message' => $built['message'] ?? 'Unable to build MIS report.',
], 404);
}
$pdf = (new AbhiMisPdfService())->generateFromReport($built['data'], $policyId);
if (!$pdf['status']) {
return $this->respond([
'status' => false,
'message' => $pdf['message'] ?? 'PDF generation failed.',
], 500);
}
return $this->response
->setHeader('Content-Type', 'application/pdf')
->setHeader('Content-Disposition', 'attachment; filename="' . ($pdf['filename'] ?? 'ABH_MIS.pdf') . '"')
->setBody($pdf['content'] ?? '');
}
/**
* Resolve which MIS report to serve from the policy's TPA.
*
* @return array{kind:?string,message?:string,tpa_id?:int}
*/
private function resolveMisKind(int $policyId): array
{
$db = db_connect();
$policy = $db->table('client_policy')
->select('tpa_id')
->where('id', $policyId)
->get()
->getRowArray();
if (!$policy) {
return ['kind' => null, 'message' => 'Policy not found.'];
}
$tpaId = (int) ($policy['tpa_id'] ?? 0);
$map = [
(int) env('ABHI_PRIMARY_KEY_CONSTANT') => 'abhi',
(int) env('ICICI_PRIMARY_KEY_CONSTANT') => 'icici',
(int) env('MEDI_ASSIST_PRIMARY_KEY_CONSTANT') => 'medi_assist',
(int) env('VIDAL_PRIMARY_KEY_CONSTANT') => 'vidal',
];
$kind = $map[$tpaId] ?? null;
if ($kind === null) {
return [
'kind' => null,
'tpa_id' => $tpaId,
'message' => 'MIS report is not available for this policy TPA. Supported: ABHI, ICICI, Medi Assist, Vidal.',
];
}
return ['kind' => $kind, 'tpa_id' => $tpaId];
}
}

View File

@ -7408,21 +7408,31 @@ class EmployeeController extends AdminController
}
/**
* TPA Reports dashboard page native MIS only (ABHI / ICICI / Medi Assist / Vidal).
*/
public function tpaReportsDashboard()
{
$data = [
'tab_name' => 'TPA Reports',
'page_name' => 'TPA Reports',
'misApiUrl' => base_url('/util/tpa-reports/mis'),
];
return $this->loadLayout('tpa_mis_reports_dashboard', $data);
}
/**
* Metabase signed-embed token for a client policy.
* GET /util/tpa-reports/metabase?client_policy=
* GET /employeeRest/tpa-reports/metabase?client_policy=
*/
public function tpaReportsMetabase()
{
$METABASE_SECRET_KEY = getenv('METABASE_SECRET_KEY');
$policy_id = $this->request->getGet('client_policy') ?? null;
if ($this->request->getGet('api') != 1) {
$data = [
'tab_name' => 'TPA Reports',
'page_name' => 'TPA Reports',
'metabaseUrl' => 'https://nsights.nhanceindia.in',
'dashboardApiUrl' => base_url('/employee/tpaReportsDashboard'),
];
return $this->loadLayout('meta_dashboard_demo_one', $data);
}
$policy_id = $this->request->getGet('client_policy')
?? $this->request->getGet('client_policy_id')
?? null;
if (empty($policy_id)) {
return $this->respond([
@ -7441,31 +7451,22 @@ class EmployeeController extends AdminController
$database_id = isset($client_policy_data['dashboard_id']) ? (int) $client_policy_data['dashboard_id'] : null;
if (empty($database_id)) {
if ($this->request->getGet('api') == 1) {
return $this->respond([
'status' => 'failed',
'message' => 'There is no dashboard for this TPA.',
'data' => []
]);
}
return view('errors/404', [
'message' => 'There is no dashboard for this TPA.'
return $this->respond([
'status' => 'failed',
'message' => 'There is no dashboard for this TPA.',
'data' => [],
]);
}
$payload = [
'resource' => [
'dashboard' => $database_id
'dashboard' => $database_id,
],
'exp' => time() + (10 * 60), // 10 minutes
'params' => (object) ['client_policy' => $policy_id ], // MUST be object for Metabase
'exp' => time() + (10 * 60),
'params' => (object) ['client_policy' => $policy_id],
];
$token = JWT::encode($payload, $METABASE_SECRET_KEY, 'HS256');
// dd($token);
return $this->respond([
'status' => 'success',
@ -7477,5 +7478,84 @@ class EmployeeController extends AdminController
]);
}
/**
* Native TPA MIS report embed info for the frontend (preview + PDF URLs).
* GET /util/tpa-reports/mis?client_policy=
* GET /employeeRest/tpa-reports/mis?client_policy=
*/
public function tpaMisReport()
{
$policyId = (int) (
$this->request->getGet('client_policy')
?? $this->request->getGet('client_policy_id')
?? 0
);
if ($policyId <= 0) {
return $this->respond([
'status' => 'failed',
'message' => 'Client policy is required.',
'data' => [],
]);
}
$policy = $this->clientPolicyModel
->select('id, tpa_id')
->where('id', $policyId)
->first();
if (!$policy) {
return $this->respond([
'status' => 'failed',
'message' => 'Policy not found.',
'data' => [],
]);
}
$tpaId = (int) ($policy['tpa_id'] ?? 0);
$map = [
(int) env('ABHI_PRIMARY_KEY_CONSTANT') => 'abhi',
(int) env('ICICI_PRIMARY_KEY_CONSTANT') => 'icici',
(int) env('MEDI_ASSIST_PRIMARY_KEY_CONSTANT') => 'medi_assist',
(int) env('VIDAL_PRIMARY_KEY_CONSTANT') => 'vidal',
];
$kind = $map[$tpaId] ?? null;
if ($kind === null) {
return $this->respond([
'status' => 'failed',
'message' => 'MIS report is not available for this policy TPA. Supported: ABHI, ICICI, Medi Assist, Vidal.',
'data' => [
'supported' => false,
'tpa_id' => $tpaId,
],
]);
}
$path = $this->request->getUri()->getPath();
$isJwt = stripos($path, 'employeeRest') !== false;
$prefix = $isJwt ? 'employeeRest/claims-collection-report' : 'util/claims-collection-report';
$labels = [
'abhi' => 'ABHI MIS Report',
'icici' => 'ICICI Portfolio Analysis',
'medi_assist' => 'Medi Assist Portfolio Analysis',
'vidal' => 'Vidal Corporate Analysis',
];
return $this->respond([
'status' => 'success',
'message' => 'MIS report available.',
'data' => [
'supported' => true,
'kind' => $kind,
'tpa_id' => $tpaId,
'title' => $labels[$kind] ?? 'TPA MIS Report',
'preview_url' => base_url($prefix . '/mis?client_policy_id=' . $policyId),
'pdf_url' => base_url($prefix . '/mis-pdf?client_policy_id=' . $policyId),
],
]);
}
}

View File

@ -0,0 +1,396 @@
<?php
namespace App\Libraries;
/**
* Renders simple chart PNGs with GD for ABHI MIS PDF (mPDF cannot run Chart.js).
*/
class AbhiMisChartImageService
{
private string $dir;
public function __construct(?string $dir = null)
{
$this->dir = $dir ?: (rtrim(WRITEPATH, '/\\') . '/cache/abhi_mis_charts');
if (!is_dir($this->dir)) {
@mkdir($this->dir, 0775, true);
}
}
/**
* @param array<string,mixed> $vm
* @return array<string,string> chart key => absolute PNG path
*/
public function buildAll(array $vm): array
{
$out = [];
$status = $vm['claimStatus'] ?? [];
$out['donut'] = $this->pie(
'donut',
array_map(static fn ($r) => (string) ($r['status'] ?? ''), $status),
array_map(static fn ($r) => (float) ($r['reported'] ?? 0), $status),
['#2E86DE', '#1B3F8C', '#E8862C', '#6A3D9A']
);
$pre = $vm['preAuthTAT'] ?? ['within' => 100, 'above' => 0];
$out['preAuth'] = $this->pie(
'preauth',
['Within 2 Hours %', 'Above 2 Hours %'],
[(float) ($pre['within'] ?? 0), (float) ($pre['above'] ?? 0)],
['#2E86DE', '#1B3F8C']
);
$bars = $vm['premiumBars'] ?? [0, 0, 0];
$out['premiumBars'] = $this->vBars(
'premium_bars',
['Premium', 'Earn Premium', 'Claim Incurred'],
[(float) ($bars[0] ?? 0), (float) ($bars[1] ?? 0), (float) ($bars[2] ?? 0)],
['#2E86DE', '#1B3F8C', '#E8862C']
);
$gender = $vm['gender'] ?? [];
$gLabels = [];
$gCounts = [];
$gAmts = [];
$gColors = [];
foreach (['Male' => '#6A3D9A', 'Female' => '#1A3399'] as $label => $color) {
$row = null;
foreach ($gender as $g) {
if (($g['label'] ?? '') === $label) {
$row = $g;
break;
}
}
$gLabels[] = $label;
$gCounts[] = (float) ($row['count'] ?? 0);
$gAmts[] = (float) ($row['amount'] ?? 0);
$gColors[] = $color;
}
$out['genderCount'] = $this->hBars('gender_count', $gLabels, $gCounts, $gColors, false);
$out['genderAmt'] = $this->hBars('gender_amt', $gLabels, $gAmts, $gColors, true);
$pc = $vm['paidCharts'] ?? [];
$cbt = $pc['countByType'] ?? [];
$out['paidCount'] = $this->vBars(
'paid_count',
['Cashless', 'Reimbursement'],
[(float) ($pc['paidCount'][0] ?? 0), (float) ($pc['paidCount'][1] ?? 0)],
['#2E86DE', '#1B3F8C']
);
$out['paidAmtAcs'] = $this->groupedVBars(
'paid_amt_acs',
['Cashless', 'Reimbursement'],
[
['label' => 'Paid Amt', 'values' => [(float) ($pc['paidAmt'][0] ?? 0), (float) ($pc['paidAmt'][1] ?? 0)], 'color' => '#2E86DE'],
['label' => 'ACS', 'values' => [(float) ($pc['acs'][0] ?? 0), (float) ($pc['acs'][1] ?? 0)], 'color' => '#E8862C'],
]
);
$out['countStatus'] = $this->groupedVBars(
'count_status',
['Settled', 'Outstanding', 'Rejected'],
[
[
'label' => 'Cashless',
'values' => [
(float) ($cbt['Settled']['Cashless'] ?? 0),
(float) ($cbt['Outstanding']['Cashless'] ?? 0),
(float) ($cbt['Rejected']['Cashless'] ?? 0),
],
'color' => '#2E86DE',
],
[
'label' => 'Reimbursement',
'values' => [
(float) ($cbt['Settled']['Reimbursement'] ?? 0),
(float) ($cbt['Outstanding']['Reimbursement'] ?? 0),
(float) ($cbt['Rejected']['Reimbursement'] ?? 0),
],
'color' => '#1B3F8C',
],
]
);
$out['amtStatus'] = $this->vBars(
'amt_status',
['Settled', 'Outstanding', 'Rejected'],
[
(float) ($pc['amtByStatus'][0] ?? 0),
(float) ($pc['amtByStatus'][1] ?? 0),
(float) ($pc['amtByStatus'][2] ?? 0),
],
['#2E86DE', '#E8862C', '#6A3D9A']
);
$months = $vm['months'] ?? [];
$out['monthCount'] = $this->groupedVBars(
'month_count',
$months,
[
['label' => 'Paid Count', 'values' => array_map('floatval', $vm['paidCountByMonth'] ?? []), 'color' => '#2E86DE'],
['label' => 'Outstanding Count', 'values' => array_map('floatval', $vm['outCountByMonth'] ?? []), 'color' => '#1B3F8C'],
],
720,
280
);
$out['monthAmt'] = $this->groupedVBars(
'month_amt',
$months,
[
['label' => 'Paid Amount', 'values' => array_map('floatval', $vm['paidAmtByMonth'] ?? []), 'color' => '#2E86DE'],
['label' => 'Outstanding Amount', 'values' => array_map('floatval', $vm['outAmtByMonth'] ?? []), 'color' => '#1B3F8C'],
],
720,
280
);
return array_filter($out);
}
/**
* @param list<string> $labels
* @param list<float> $values
* @param list<string> $colors
*/
public function pie(string $key, array $labels, array $values, array $colors, int $w = 420, int $h = 260): string
{
$img = imagecreatetruecolor($w, $h);
$white = imagecolorallocate($img, 255, 255, 255);
imagefilledrectangle($img, 0, 0, $w, $h, $white);
$total = array_sum($values);
$cx = (int) ($w * 0.32);
$cy = (int) ($h / 2);
$rx = 95;
$ry = 70;
$start = 0.0;
foreach ($values as $i => $val) {
$slice = $total > 0 ? ($val / $total) * 360.0 : 0.0;
if ($slice <= 0) {
continue;
}
$rgb = $this->hex($colors[$i % count($colors)]);
$col = imagecolorallocate($img, $rgb[0], $rgb[1], $rgb[2]);
$this->filledArc($img, $cx, $cy, $rx * 2, $ry * 2, (int) $start, (int) ($start + $slice), $col);
$start += $slice;
}
// hole for donut
$hole = imagecolorallocate($img, 255, 255, 255);
imagefilledellipse($img, $cx, $cy, (int) ($rx * 0.9), (int) ($ry * 0.9), $hole);
$black = imagecolorallocate($img, 40, 40, 40);
$lx = (int) ($w * 0.58);
$ly = 40;
foreach ($labels as $i => $label) {
$rgb = $this->hex($colors[$i % count($colors)]);
$col = imagecolorallocate($img, $rgb[0], $rgb[1], $rgb[2]);
imagefilledrectangle($img, $lx, $ly, $lx + 12, $ly + 12, $col);
$pct = $total > 0 ? round(($values[$i] / $total) * 100, 1) : 0;
imagestring($img, 3, $lx + 18, $ly - 1, $this->fit($label . ' ' . $pct . '%', 28), $black);
$ly += 22;
}
return $this->save($key, $img);
}
/**
* @param list<string> $labels
* @param list<float> $values
* @param list<string> $colors
*/
public function hBars(string $key, array $labels, array $values, array $colors, bool $indianFmt, int $w = 480, int $h = 200): string
{
$img = imagecreatetruecolor($w, $h);
$white = imagecolorallocate($img, 255, 255, 255);
$black = imagecolorallocate($img, 40, 40, 40);
imagefilledrectangle($img, 0, 0, $w, $h, $white);
$max = max(1.0, ...array_map('floatval', $values ?: [1]));
$left = 70;
$barH = 36;
$gap = 28;
$y = 40;
$maxW = $w - $left - 30;
foreach ($labels as $i => $label) {
$val = (float) ($values[$i] ?? 0);
$bw = (int) (($val / $max) * $maxW);
$rgb = $this->hex($colors[$i % count($colors)]);
$col = imagecolorallocate($img, $rgb[0], $rgb[1], $rgb[2]);
imagestring($img, 3, 8, $y + 10, $this->fit($label, 10), $black);
imagefilledrectangle($img, $left, $y, $left + max(2, $bw), $y + $barH, $col);
$txt = $indianFmt ? $this->fmtIn($val) : (string) (int) $val;
$tx = $left + (int) ($bw / 2) - (int) (strlen($txt) * 3);
$tx = max($left + 4, min($tx, $w - 80));
$box = imagecolorallocate($img, 255, 255, 255);
imagefilledrectangle($img, $tx - 2, $y + 10, $tx + strlen($txt) * 7 + 2, $y + 26, $box);
imagestring($img, 3, $tx, $y + 12, $txt, $black);
$y += $barH + $gap;
}
return $this->save($key, $img);
}
/**
* @param list<string> $labels
* @param list<float> $values
* @param list<string> $colors
*/
public function vBars(string $key, array $labels, array $values, array $colors, int $w = 480, int $h = 240): string
{
$img = imagecreatetruecolor($w, $h);
$white = imagecolorallocate($img, 255, 255, 255);
$black = imagecolorallocate($img, 40, 40, 40);
$grid = imagecolorallocate($img, 230, 230, 230);
imagefilledrectangle($img, 0, 0, $w, $h, $white);
$max = max(1.0, ...array_map('floatval', $values ?: [1]));
$top = 20;
$bottom = $h - 40;
$left = 40;
$right = $w - 20;
$plotH = $bottom - $top;
$n = max(1, count($labels));
$slot = ($right - $left) / $n;
$barW = max(12, (int) ($slot * 0.45));
for ($g = 0; $g <= 4; $g++) {
$gy = (int) ($bottom - ($plotH * $g / 4));
imageline($img, $left, $gy, $right, $gy, $grid);
}
foreach ($labels as $i => $label) {
$val = (float) ($values[$i] ?? 0);
$bh = (int) (($val / $max) * $plotH);
$x = (int) ($left + $slot * $i + ($slot - $barW) / 2);
$y = $bottom - $bh;
$rgb = $this->hex($colors[$i % count($colors)]);
$col = imagecolorallocate($img, $rgb[0], $rgb[1], $rgb[2]);
imagefilledrectangle($img, $x, $y, $x + $barW, $bottom, $col);
$txt = $this->fmtShort($val);
imagestring($img, 2, $x, max(4, $y - 14), $txt, $black);
imagestring($img, 2, $x - 4, $bottom + 8, $this->fit($label, 12), $black);
}
return $this->save($key, $img);
}
/**
* @param list<string> $labels
* @param list<array{label:string,values:list<float>,color:string}> $series
*/
public function groupedVBars(string $key, array $labels, array $series, int $w = 520, int $h = 240): string
{
$img = imagecreatetruecolor($w, $h);
$white = imagecolorallocate($img, 255, 255, 255);
$black = imagecolorallocate($img, 40, 40, 40);
$grid = imagecolorallocate($img, 230, 230, 230);
imagefilledrectangle($img, 0, 0, $w, $h, $white);
$all = [];
foreach ($series as $s) {
foreach ($s['values'] as $v) {
$all[] = (float) $v;
}
}
$max = max(1.0, ...($all ?: [1.0]));
$top = 28;
$bottom = $h - 50;
$left = 30;
$right = $w - 20;
$plotH = $bottom - $top;
$n = max(1, count($labels));
$slot = ($right - $left) / $n;
$seriesN = max(1, count($series));
$barW = max(8, (int) (($slot * 0.7) / $seriesN));
for ($g = 0; $g <= 4; $g++) {
$gy = (int) ($bottom - ($plotH * $g / 4));
imageline($img, $left, $gy, $right, $gy, $grid);
}
foreach ($labels as $i => $label) {
foreach ($series as $si => $s) {
$val = (float) ($s['values'][$i] ?? 0);
$bh = (int) (($val / $max) * $plotH);
$x = (int) ($left + $slot * $i + ($slot - $barW * $seriesN) / 2 + $si * $barW);
$y = $bottom - $bh;
$rgb = $this->hex($s['color']);
$col = imagecolorallocate($img, $rgb[0], $rgb[1], $rgb[2]);
imagefilledrectangle($img, $x, $y, $x + $barW - 2, $bottom, $col);
}
imagestring($img, 2, (int) ($left + $slot * $i + 4), $bottom + 6, $this->fit((string) $label, 10), $black);
}
$lx = $left;
foreach ($series as $s) {
$rgb = $this->hex($s['color']);
$col = imagecolorallocate($img, $rgb[0], $rgb[1], $rgb[2]);
imagefilledrectangle($img, $lx, 8, $lx + 10, 18, $col);
imagestring($img, 2, $lx + 14, 7, $this->fit($s['label'], 18), $black);
$lx += 120;
}
return $this->save($key, $img);
}
/** @return array{0:int,1:int,2:int} */
private function hex(string $hex): array
{
$hex = ltrim($hex, '#');
if (strlen($hex) === 3) {
$hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
}
if (strlen($hex) < 6) {
return [46, 134, 222];
}
return [
hexdec(substr($hex, 0, 2)),
hexdec(substr($hex, 2, 2)),
hexdec(substr($hex, 4, 2)),
];
}
private function filledArc($img, int $cx, int $cy, int $w, int $h, int $s, int $e, $col): void
{
if ($e <= $s) {
return;
}
imagefilledarc($img, $cx, $cy, $w, $h, $s, $e, $col, IMG_ARC_PIE);
}
private function fit(string $s, int $max): string
{
$s = preg_replace('/[^\x20-\x7E]/', '', $s) ?? $s;
return strlen($s) > $max ? substr($s, 0, $max - 1) . '.' : $s;
}
private function fmtIn(float $n): string
{
return number_format($n, 0, '.', ',');
}
private function fmtShort(float $n): string
{
if (abs($n) >= 100000) {
return number_format($n / 100000, 1) . 'L';
}
if (abs($n) >= 1000) {
return number_format($n / 1000, 1) . 'k';
}
return (string) (int) $n;
}
/** @param resource|\GdImage $img */
private function save(string $key, $img): string
{
$path = $this->dir . DIRECTORY_SEPARATOR . $key . '_' . uniqid('', true) . '.png';
imagepng($img, $path);
imagedestroy($img);
return $path;
}
}

View File

@ -0,0 +1,161 @@
<?php
namespace App\Libraries;
use Dompdf\Dompdf;
use Dompdf\Options;
/**
* Renders ABHI MIS HTML page to PDF via DomPDF (same layout as online report).
*/
class AbhiMisPdfService
{
/**
* Build full MIS PDF from report payload using the same view as the online page.
*
* @param array<string,mixed> $report
* @return array{status:bool,message?:string,content?:string,filename?:string}
*/
public function generateFromReport(array $report, int $policyId): array
{
$vm = $report['view_model'] ?? [];
$policyNo = trim((string) ($report['header']['policy_number'] ?? (string) $policyId));
$charts = [];
try {
$chartSvc = new AbhiMisChartImageService();
$charts = $chartSvc->buildAll(is_array($vm) ? $vm : []);
$html = view('claims_mis_abhi', [
'report' => $report,
'view_model' => $vm,
'policy_id' => $policyId,
'pdf_url' => '',
'embed' => true,
'charts' => $charts,
]);
return $this->generate($html, $policyNo);
} finally {
foreach ($charts as $path) {
if (is_string($path) && is_file($path)) {
@unlink($path);
}
}
}
}
/**
* @return array{status:bool,message?:string,content?:string,filename?:string}
*/
public function generate(string $html, string $policyNo): array
{
$safePolicy = preg_replace('/[^A-Za-z0-9_\-]+/', '_', $policyNo) ?: 'policy';
$filename = 'ABH_MIS_' . $safePolicy . '_' . date('Ymd') . '.pdf';
try {
$options = new Options();
$options->set('isHtml5ParserEnabled', true);
$options->set('isRemoteEnabled', true);
$options->set('isFontSubsettingEnabled', true);
$options->set('defaultFont', 'DejaVu Sans');
$options->setChroot([
rtrim(ROOTPATH, '/\\'),
rtrim(FCPATH, '/\\'),
rtrim(WRITEPATH, '/\\'),
sys_get_temp_dir(),
]);
$dompdf = new Dompdf($options);
$dompdf->loadHtml($this->prepareHtml($html));
$dompdf->setPaper('A4', 'landscape');
$dompdf->render();
$content = $dompdf->output();
if ($content === '' || $content === null) {
return ['status' => false, 'message' => 'PDF generation failed: empty output.'];
}
return [
'status' => true,
'content' => $content,
'filename' => $filename,
];
} catch (\Throwable $e) {
log_message('error', 'AbhiMisPdfService::generate failed | ' . $e->getMessage());
return ['status' => false, 'message' => 'PDF generation failed: ' . $e->getMessage()];
}
}
private function prepareHtml(string $html): string
{
$html = preg_replace('#<script\b[^>]*>.*?</script>#is', '', $html) ?? $html;
$html = preg_replace('/[\x{1F300}-\x{1FAFF}]/u', '', $html) ?? $html;
// Replace CSS custom properties DomPDF cannot resolve.
$vars = [
'var(--red-dark)' => '#9c0c22',
'var(--red)' => '#C8102E',
'var(--navy)' => '#152A54',
'var(--blue-dark)' => '#1B3F8C',
'var(--blue)' => '#2E86DE',
'var(--orange)' => '#E8862C',
'var(--purple)' => '#6A3D9A',
'var(--gold)' => '#D9A441',
'var(--bg)' => '#EFEFF2',
'var(--panel)' => '#FFFFFF',
'var(--line)' => '#E2E2E6',
'var(--text)' => '#26262B',
'var(--muted)' => '#6B6B75',
];
$html = str_replace(array_keys($vars), array_values($vars), $html);
$html = preg_replace('#:root\s*\{[^}]*\}#is', '', $html) ?? $html;
// Embed local images as data URIs.
$html = preg_replace_callback(
'#(<img\b[^>]*\bsrc=["\'])([^"\']+)(["\'][^>]*>)#i',
function (array $m): string {
$src = html_entity_decode($m[2], ENT_QUOTES);
$dataUri = $this->toDataUri($src);
return $m[1] . ($dataUri ?: $src) . $m[3];
},
$html
) ?? $html;
return $html;
}
private function toDataUri(string $path): ?string
{
$path = trim($path);
if ($path === '' || str_starts_with($path, 'data:')) {
return null;
}
if (preg_match('#^file://#i', $path)) {
$path = preg_replace('#^file://#i', '', $path) ?? $path;
}
if (!is_file($path) || !is_readable($path)) {
return null;
}
$ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
$mime = match ($ext) {
'png' => 'image/png',
'jpg', 'jpeg' => 'image/jpeg',
'gif' => 'image/gif',
'webp' => 'image/webp',
default => 'application/octet-stream',
};
$bytes = @file_get_contents($path);
if ($bytes === false || $bytes === '') {
return null;
}
return 'data:' . $mime . ';base64,' . base64_encode($bytes);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,137 @@
<?php
namespace App\Libraries;
use Dompdf\Dompdf;
use Dompdf\Options;
/**
* Renders ICICI Portfolio Analysis MIS HTML to PDF via DomPDF.
*/
class IciciMisPdfService
{
/**
* @param array<string,mixed> $report
* @return array{status:bool,message?:string,content?:string,filename?:string}
*/
public function generateFromReport(array $report, int $policyId): array
{
$vm = $report['view_model'] ?? [];
$policyNo = trim((string) ($report['header']['policy_number'] ?? (string) $policyId));
$html = view('claims_mis_icici', [
'report' => $report,
'view_model' => $vm,
'policy_id' => $policyId,
'pdf_url' => '',
'embed' => true,
]);
return $this->generate($html, $policyNo);
}
/**
* @return array{status:bool,message?:string,content?:string,filename?:string}
*/
public function generate(string $html, string $policyNo): array
{
$safePolicy = preg_replace('/[^A-Za-z0-9_\-]+/', '_', $policyNo) ?: 'policy';
$filename = 'ICICI_MIS_' . $safePolicy . '_' . date('Ymd') . '.pdf';
try {
$options = new Options();
$options->set('isHtml5ParserEnabled', true);
$options->set('isRemoteEnabled', true);
$options->set('isFontSubsettingEnabled', true);
$options->set('defaultFont', 'DejaVu Sans');
$options->setChroot([
rtrim(ROOTPATH, '/\\'),
rtrim(FCPATH, '/\\'),
rtrim(WRITEPATH, '/\\'),
sys_get_temp_dir(),
]);
$dompdf = new Dompdf($options);
$dompdf->loadHtml($this->prepareHtml($html));
$dompdf->setPaper('A4', 'portrait');
$dompdf->render();
$content = $dompdf->output();
if ($content === '' || $content === null) {
return ['status' => false, 'message' => 'PDF generation failed: empty output.'];
}
return [
'status' => true,
'content' => $content,
'filename' => $filename,
];
} catch (\Throwable $e) {
log_message('error', 'IciciMisPdfService::generate failed | ' . $e->getMessage());
return ['status' => false, 'message' => 'PDF generation failed: ' . $e->getMessage()];
}
}
private function prepareHtml(string $html): string
{
$html = preg_replace('#<script\b[^>]*>.*?</script>#is', '', $html) ?? $html;
$html = preg_replace('/[\x{1F300}-\x{1FAFF}]/u', '', $html) ?? $html;
$vars = [
'var(--navy)' => '#1b3a6b',
'var(--navy-dark)' => '#16305a',
'var(--red)' => '#a63a3a',
'var(--red-header)' => '#a13a3a',
'var(--border)' => '#7f7f7f',
'var(--page-bg)' => '#ffffff',
];
$html = str_replace(array_keys($vars), array_values($vars), $html);
$html = preg_replace('#:root\s*\{[^}]*\}#is', '', $html) ?? $html;
$html = preg_replace_callback(
'#(<img\b[^>]*\bsrc=["\'])([^"\']+)(["\'][^>]*>)#i',
function (array $m): string {
$src = html_entity_decode($m[2], ENT_QUOTES);
$dataUri = $this->toDataUri($src);
return $m[1] . ($dataUri ?: $src) . $m[3];
},
$html
) ?? $html;
return $html;
}
private function toDataUri(string $path): ?string
{
$path = trim($path);
if ($path === '' || str_starts_with($path, 'data:')) {
return null;
}
if (preg_match('#^file://#i', $path)) {
$path = preg_replace('#^file://#i', '', $path) ?? $path;
}
if (!is_file($path) || !is_readable($path)) {
return null;
}
$ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
$mime = match ($ext) {
'png' => 'image/png',
'jpg', 'jpeg' => 'image/jpeg',
'gif' => 'image/gif',
'webp' => 'image/webp',
default => 'application/octet-stream',
};
$bytes = @file_get_contents($path);
if ($bytes === false || $bytes === '') {
return null;
}
return 'data:' . $mime . ';base64,' . base64_encode($bytes);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,139 @@
<?php
namespace App\Libraries;
use Dompdf\Dompdf;
use Dompdf\Options;
/**
* Renders Medi Assist Portfolio Analysis MIS HTML to PDF via DomPDF.
*/
class MediAssistMisPdfService
{
/**
* @param array<string,mixed> $report
* @return array{status:bool,message?:string,content?:string,filename?:string}
*/
public function generateFromReport(array $report, int $policyId): array
{
$vm = $report['view_model'] ?? $report;
$policyNo = trim((string) ($report['header']['policy_number'] ?? (string) $policyId));
$html = view('claims_mis_medi_assist', [
'report' => $report,
'view_model' => $vm,
'policy_id' => $policyId,
'pdf_url' => '',
'embed' => true,
]);
return $this->generate($html, $policyNo);
}
/**
* @return array{status:bool,message?:string,content?:string,filename?:string}
*/
public function generate(string $html, string $policyNo): array
{
$safePolicy = preg_replace('/[^A-Za-z0-9_\-]+/', '_', $policyNo) ?: 'policy';
$filename = 'MA_MIS_' . $safePolicy . '_' . date('Ymd') . '.pdf';
try {
$options = new Options();
$options->set('isHtml5ParserEnabled', true);
$options->set('isRemoteEnabled', true);
$options->set('isFontSubsettingEnabled', true);
$options->set('defaultFont', 'DejaVu Sans');
$options->setChroot([
rtrim(ROOTPATH, '/\\'),
rtrim(FCPATH, '/\\'),
rtrim(WRITEPATH, '/\\'),
sys_get_temp_dir(),
]);
$dompdf = new Dompdf($options);
$dompdf->loadHtml($this->prepareHtml($html));
$dompdf->setPaper('A4', 'landscape');
$dompdf->render();
$content = $dompdf->output();
if ($content === '' || $content === null) {
return ['status' => false, 'message' => 'PDF generation failed: empty output.'];
}
return [
'status' => true,
'content' => $content,
'filename' => $filename,
];
} catch (\Throwable $e) {
log_message('error', 'MediAssistMisPdfService::generate failed | ' . $e->getMessage());
return ['status' => false, 'message' => 'PDF generation failed: ' . $e->getMessage()];
}
}
private function prepareHtml(string $html): string
{
$html = preg_replace('#<script\b[^>]*>.*?</script>#is', '', $html) ?? $html;
$html = preg_replace('/[\x{1F300}-\x{1FAFF}]/u', '', $html) ?? $html;
$vars = [
'var(--blue)' => '#1f4e9c',
'var(--lightblue)' => '#eaf1fb',
'var(--header-blue)'=> '#dbe7f8',
'var(--border)' => '#9fb8dd',
'var(--row-alt)' => '#f4f7fc',
'var(--red)' => '#e94b5c',
'var(--text)' => '#222',
'var(--muted)' => '#555',
];
$html = str_replace(array_keys($vars), array_values($vars), $html);
$html = preg_replace('#:root\s*\{[^}]*\}#is', '', $html) ?? $html;
$html = preg_replace_callback(
'#(<img\b[^>]*\bsrc=["\'])([^"\']+)(["\'][^>]*>)#i',
function (array $m): string {
$src = html_entity_decode($m[2], ENT_QUOTES);
$dataUri = $this->toDataUri($src);
return $m[1] . ($dataUri ?: $src) . $m[3];
},
$html
) ?? $html;
return $html;
}
private function toDataUri(string $path): ?string
{
$path = trim($path);
if ($path === '' || str_starts_with($path, 'data:')) {
return null;
}
if (preg_match('#^file://#i', $path)) {
$path = preg_replace('#^file://#i', '', $path) ?? $path;
}
if (!is_file($path) || !is_readable($path)) {
return null;
}
$ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
$mime = match ($ext) {
'png' => 'image/png',
'jpg', 'jpeg' => 'image/jpeg',
'gif' => 'image/gif',
'webp' => 'image/webp',
default => 'application/octet-stream',
};
$bytes = @file_get_contents($path);
if ($bytes === false || $bytes === '') {
return null;
}
return 'data:' . $mime . ';base64,' . base64_encode($bytes);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,139 @@
<?php
namespace App\Libraries;
use Dompdf\Dompdf;
use Dompdf\Options;
/**
* Renders Vidal Health Corporate Analysis MIS HTML to PDF via DomPDF.
*/
class VidalMisPdfService
{
/**
* @param array<string,mixed> $report
* @return array{status:bool,message?:string,content?:string,filename?:string}
*/
public function generateFromReport(array $report, int $policyId): array
{
$vm = $report['view_model'] ?? $report;
$policyNo = trim((string) ($report['header']['policy_number'] ?? (string) $policyId));
$html = view('claims_mis_vidal', [
'report' => $report,
'view_model' => $vm,
'policy_id' => $policyId,
'pdf_url' => '',
'embed' => true,
]);
return $this->generate($html, $policyNo);
}
/**
* @return array{status:bool,message?:string,content?:string,filename?:string}
*/
public function generate(string $html, string $policyNo): array
{
$safePolicy = preg_replace('/[^A-Za-z0-9_\-]+/', '_', $policyNo) ?: 'policy';
$filename = 'VIDAL_MIS_' . $safePolicy . '_' . date('Ymd') . '.pdf';
try {
$options = new Options();
$options->set('isHtml5ParserEnabled', true);
$options->set('isRemoteEnabled', true);
$options->set('isFontSubsettingEnabled', true);
$options->set('defaultFont', 'DejaVu Sans');
$options->setChroot([
rtrim(ROOTPATH, '/\\'),
rtrim(FCPATH, '/\\'),
rtrim(WRITEPATH, '/\\'),
sys_get_temp_dir(),
]);
$dompdf = new Dompdf($options);
$dompdf->loadHtml($this->prepareHtml($html));
$dompdf->setPaper('A4', 'portrait');
$dompdf->render();
$content = $dompdf->output();
if ($content === '' || $content === null) {
return ['status' => false, 'message' => 'PDF generation failed: empty output.'];
}
return [
'status' => true,
'content' => $content,
'filename' => $filename,
];
} catch (\Throwable $e) {
log_message('error', 'VidalMisPdfService::generate failed | ' . $e->getMessage());
return ['status' => false, 'message' => 'PDF generation failed: ' . $e->getMessage()];
}
}
private function prepareHtml(string $html): string
{
$html = preg_replace('#<script\b[^>]*>.*?</script>#is', '', $html) ?? $html;
$html = preg_replace('/[\x{1F300}-\x{1FAFF}]/u', '', $html) ?? $html;
$vars = [
'var(--blue)' => '#0072bc',
'var(--header-blue)' => '#dbe9f5',
'var(--group-header)' => '#b9d6ec',
'var(--border)' => '#888',
'var(--text)' => '#111',
'var(--muted)' => '#444',
'var(--male)' => '#4472c4',
'var(--female)' => '#ed7d31',
];
$html = str_replace(array_keys($vars), array_values($vars), $html);
$html = preg_replace('#:root\s*\{[^}]*\}#is', '', $html) ?? $html;
$html = preg_replace_callback(
'#(<img\b[^>]*\bsrc=["\'])([^"\']+)(["\'][^>]*>)#i',
function (array $m): string {
$src = html_entity_decode($m[2], ENT_QUOTES);
$dataUri = $this->toDataUri($src);
return $m[1] . ($dataUri ?: $src) . $m[3];
},
$html
) ?? $html;
return $html;
}
private function toDataUri(string $path): ?string
{
$path = trim($path);
if ($path === '' || str_starts_with($path, 'data:')) {
return null;
}
if (preg_match('#^file://#i', $path)) {
$path = preg_replace('#^file://#i', '', $path) ?? $path;
}
if (!is_file($path) || !is_readable($path)) {
return null;
}
$ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
$mime = match ($ext) {
'png' => 'image/png',
'jpg', 'jpeg' => 'image/jpeg',
'gif' => 'image/gif',
'webp' => 'image/webp',
default => 'application/octet-stream',
};
$bytes = @file_get_contents($path);
if ($bytes === false || $bytes === '') {
return null;
}
return 'data:' . $mime . ';base64,' . base64_encode($bytes);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,772 @@
<?php
/** @var array $report */
/** @var array $view_model */
/** @var int $policy_id */
/** @var string $pdf_url */
/** @var bool $embed */
$embed = $embed ?? false;
$report = $report ?? [];
$view_model = $view_model ?? ($report['view_model'] ?? []);
$header = $report['header'] ?? [];
$summary = $report['summary'] ?? [];
$meta = $report['meta'] ?? [];
$vm = $view_model;
$charts = $charts ?? [];
$fmt = static fn ($n, $d = 0) => number_format((float) $n, $d);
$logoSrc = !empty($embed)
? (rtrim(ROOTPATH, '/\\') . '/public/assets/images/abhi.png')
: base_url('public/assets/images/abhi.png');
include APPPATH . 'Views/partials/claims_mis_abhi_helpers.php';
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ABH MIS Report Policy <?= esc($header['policy_number'] ?? $policy_id) ?></title>
<style>
:root{
--red:#C8102E;
--red-dark:#9c0c22;
--navy:#152A54;
--blue:#2E86DE;
--blue-dark:#1B3F8C;
--orange:#E8862C;
--purple:#6A3D9A;
--gold:#D9A441;
--bg:#f6f1ee;
--panel:#FFFFFF;
--line:#eee0e0;
--text:#2b2020;
--muted:#8a7676;
--maroon-900:#4a0e10;
--maroon-700:#7a1116;
--maroon-600:#9c151b;
--maroon-500:#b91c23;
--maroon-050:#fbecec;
}
*{box-sizing:border-box;}
body{
margin:0;
background:var(--bg);
font-family:"Segoe UI", Arial, sans-serif;
color:var(--text);
padding:28px 48px;
}
.wrap{max-width:1580px;margin:0 auto;}
/* ---- Header (unified card) ---- */
.header{
background:var(--panel);
border-radius:16px;
border:1px solid var(--line);
box-shadow:0 1px 3px rgba(74,14,16,0.06);
display:flex;
align-items:stretch;
overflow:hidden;
margin-bottom:18px;
}
.brand{
display:flex;
align-items:center;
gap:14px;
padding:18px 24px;
border-right:1px solid var(--line);
min-width:280px;
flex-shrink:0;
}
.brand-logo{
display:block;
height:64px;
width:auto;
max-width:260px;
object-fit:contain;
object-position:left center;
}
.hdr-stats{
flex:1;
display:flex;
align-items:center;
min-width:0;
}
.hdr-stat{
flex:1;
padding:16px 20px;
display:flex;
align-items:center;
gap:12px;
border-right:1px solid var(--line);
min-width:0;
}
.hdr-stat:last-child{border-right:none;}
.hdr-stat-icon{
width:36px;
height:36px;
border-radius:10px;
background:var(--maroon-050);
color:var(--maroon-600);
display:flex;
align-items:center;
justify-content:center;
flex-shrink:0;
}
.hdr-stat-icon svg{width:18px;height:18px;}
.hdr-stat-body{min-width:0;}
.hdr-stat-value{
font-size:14px;
font-weight:700;
color:var(--text);
white-space:nowrap;
overflow:hidden;
text-overflow:ellipsis;
}
.hdr-stat-label{
font-size:11px;
color:var(--muted);
margin-top:2px;
letter-spacing:0.3px;
}
/* ---- Panel ---- */
.panel{
background:var(--panel);
border-radius:10px;
overflow:hidden;
box-shadow:0 1px 3px rgba(0,0,0,.08);
margin-bottom:16px;
}
.panel h2{
background:var(--red);
color:#fff;
font-size:13px;
font-weight:700;
margin:0;
padding:9px 16px;
text-align:center;
letter-spacing:.3px;
}
.panel .body{padding:14px 16px;}
table{width:100%;border-collapse:collapse;font-size:12.5px;}
th,td{padding:7px 10px;text-align:left;}
thead th{
background:var(--red);
color:#fff;
font-size:12px;
font-weight:700;
}
tbody tr:nth-child(even){background:#F7F7F9;}
tbody tr:hover{background:#FDEDEF;}
td.num, th.num{text-align:right;}
tfoot td{font-weight:700;background:#F0F0F3;border-top:2px solid var(--red);}
.kv-table td:first-child{font-weight:600;color:var(--navy);width:55%;}
.kv-table td:last-child{font-weight:600;}
.kv-block{margin-bottom:16px;}
.kv-block-title{
font-size:14px;
font-weight:700;
font-style:italic;
color:#111;
margin:0 0 8px 2px;
}
.kv-block .panel{margin-bottom:0;}
.kv-block .panel .body{padding:0;}
.kv-block table{margin:0;}
.kv-block thead th{text-align:left;}
.kv-block thead th:last-child{text-align:left;}
.kv-block tbody td{border-bottom:1px solid var(--line);}
.kv-block tbody tr:last-child td{border-bottom:none;}
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:16px;}
.grid3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;}
.grid4{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;}
.stat-panel{
background:var(--panel);
border-radius:10px;
box-shadow:0 1px 3px rgba(0,0,0,.08);
padding:20px;
display:flex;
align-items:center;
justify-content:space-around;
text-align:center;
}
.stat-num{font-size:26px;font-weight:800;color:var(--navy);}
.stat-label{font-size:12px;font-weight:700;color:var(--muted);margin-top:2px;}
.stat-icon{font-size:34px;}
.chart-wrap{position:relative;height:260px;}
.chart-wrap canvas{width:100% !important;height:100% !important;display:block;}
.chart-wrap.short{height:220px;}
.chart-wrap.medium{height:280px;}
.chart-wrap.tall{height:400px;}
.center-metric{
position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);
text-align:center;font-size:12px;font-weight:700;color:var(--navy);
}
.pct-hero{
font-size:28px;font-weight:800;color:var(--blue);text-align:center;margin-top:20px;
}
.section-title{
font-size:15px;font-weight:800;color:var(--navy);
margin:26px 0 10px 2px;
border-left:5px solid var(--red);
padding-left:10px;
}
.legend-row{display:flex;gap:14px;justify-content:center;flex-wrap:wrap;font-size:11.5px;margin-bottom:6px;}
.legend-row span{display:inline-flex;align-items:center;gap:5px;}
.dot{width:10px;height:10px;border-radius:50%;display:inline-block;}
.subhead{
background:#F0F0F3;
font-weight:700;
font-size:12.5px;
color:var(--navy);
}
.scroll-y{max-height:420px;overflow-y:auto;}
@media(max-width:1100px){
body{padding:24px 28px;}
.header{flex-wrap:wrap;}
.brand{width:100%;border-right:none;border-bottom:1px solid var(--line);min-width:0;}
.hdr-stats{flex-wrap:wrap;}
.hdr-stat{min-width:50%;border-bottom:1px solid var(--line);}
.grid2,.grid3,.grid4{grid-template-columns:1fr;}
}
.chart-img{display:block;width:100%;max-width:100%;height:auto;margin:0 auto;}
<?php if (!empty($embed)): ?>
/* DomPDF-safe overrides (no CSS variables / flex / grid) */
body{background:#f6f1ee;color:#2b2020;font-family:DejaVu Sans, Arial, sans-serif;padding:12px 18px;}
.wrap{max-width:100%;}
.header{display:table;width:100%;margin-bottom:12px;background:#fff;border:1px solid #eee0e0;border-radius:10px;}
.brand,.hdr-stats{display:table-cell;vertical-align:middle;}
.brand{width:220px;padding:10px 12px;border-right:1px solid #eee0e0;}
.brand-logo{height:48px;width:auto;max-width:200px;}
.hdr-stats{padding:0;}
.hdr-stat{display:inline-block;vertical-align:middle;padding:8px 10px;width:24%;}
.hdr-stat-icon{display:none;}
.hdr-stat-value{font-size:11px;font-weight:700;color:#2b2020;}
.hdr-stat-label{font-size:9px;color:#8a7676;}
.panel{background:#fff;border-radius:8px;overflow:hidden;box-shadow:none;border:1px solid #E2E2E6;margin-bottom:10px;}
.panel h2,.kv-block-title{background:#C8102E;color:#fff;font-size:12px;font-weight:700;margin:0;padding:7px 10px;text-align:center;}
.kv-block-title{background:transparent;color:#111;font-style:italic;text-align:left;padding:0 0 4px;}
.panel .body{padding:8px 10px;}
table{width:100%;border-collapse:collapse;font-size:10px;}
th,td{padding:5px 6px;text-align:left;border:1px solid #E2E2E6;}
thead th{background:#C8102E;color:#fff;font-weight:700;}
tbody tr:nth-child(even){background:#F7F7F9;}
td.num,th.num{text-align:right;}
tfoot td{font-weight:700;background:#F0F0F3;border-top:2px solid #C8102E;}
.kv-table td:first-child{font-weight:600;color:#152A54;width:55%;}
.grid2,.grid3,.grid4{display:table;width:100%;table-layout:fixed;border-collapse:separate;border-spacing:8px;margin:0 -8px 8px;}
.grid2 > .panel,.grid3 > .panel,.grid4 > .panel{display:table-cell;vertical-align:top;margin:0;}
.grid2 > .panel{width:50%;}
.grid3 > .panel{width:33.33%;}
.grid4 > .panel{width:25%;}
.section-title{font-size:13px;font-weight:800;color:#152A54;margin:12px 0 6px;border-left:4px solid #C8102E;padding-left:8px;}
.chart-wrap,.chart-wrap.short,.chart-wrap.medium,.chart-wrap.tall{height:auto !important;min-height:0;}
.scroll-y{max-height:none;overflow:visible;}
.stat-panel{display:block;text-align:center;padding:12px;}
.stat-num{font-size:20px;font-weight:800;color:#152A54;}
.stat-label{font-size:11px;font-weight:700;color:#6B6B75;}
.stat-icon{display:none;}
.legend-row{font-size:10px;margin-bottom:4px;}
.dot{background:#2E86DE;}
<?php endif; ?>
</style>
</head>
<body>
<div class="wrap">
<!-- HEADER -->
<div class="header">
<div class="brand">
<img class="brand-logo" src="<?= esc($logoSrc) ?>" alt="Aditya Birla Capital Health Insurance">
</div>
<div class="hdr-stats">
<div class="hdr-stat">
<div class="hdr-stat-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M3 9h18M8 4v5"/></svg>
</div>
<div class="hdr-stat-body">
<div class="hdr-stat-value" title="<?= esc($header['policy_number'] ?? '') ?>"><?= esc($header['policy_number'] ?? '') ?></div>
<div class="hdr-stat-label">Policy number</div>
</div>
</div>
<div class="hdr-stat">
<div class="hdr-stat-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="8" r="4"/><path d="M4 21c0-4 4-6 8-6s8 2 8 6"/></svg>
</div>
<div class="hdr-stat-body">
<div class="hdr-stat-value" title="<?= esc($meta['client_name'] ?? '') ?>"><?= esc($meta['client_name'] ?? '') ?></div>
<div class="hdr-stat-label">Client name</div>
</div>
</div>
<div class="hdr-stat">
<div class="hdr-stat-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2 3 7v6c0 5 4 8 9 9 5-1 9-4 9-9V7l-9-5z"/></svg>
</div>
<div class="hdr-stat-body">
<div class="hdr-stat-value" title="<?= esc($meta['tpa_name'] ?? ($header['claim_processor'] ?? '')) ?>"><?= esc($meta['tpa_name'] ?? ($header['claim_processor'] ?? '')) ?></div>
<div class="hdr-stat-label">TPA name</div>
</div>
</div>
<div class="hdr-stat">
<div class="hdr-stat-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 3"/></svg>
</div>
<div class="hdr-stat-body">
<div class="hdr-stat-value"><?= esc($meta['last_refresh'] ?? date('d-M-Y')) ?></div>
<div class="hdr-stat-label">Last refresh</div>
</div>
</div>
</div>
</div>
<!-- PREMIUM / CLAIM SUMMARY -->
<div class="grid2">
<div class="panel">
<h2>Premium</h2>
<div class="body">
<table class="kv-table">
<thead><tr><th>Parameter</th><th>Value</th></tr></thead>
<tbody>
<tr><td>Insurer Name</td><td><?= esc($header['insurer_name'] ?? '') ?></td></tr>
<tr><td>Claim processing team</td><td><?= esc($header['claim_processor'] ?? '') ?></td></tr>
<tr><td>Policy Number</td><td><?= esc($header['policy_number'] ?? '') ?></td></tr>
<tr><td>Policy Start Date</td><td><?= esc($header['policy_start_date'] ?? '') ?></td></tr>
<tr><td>Policy End Date</td><td><?= esc($header['policy_end_date'] ?? '') ?></td></tr>
<tr><td>Lives as on date</td><td><?= esc($fmt($header['lives'] ?? 0, 0)) ?></td></tr>
<tr><td>Premium paid as on date</td><td><?= esc($fmt($header['premium_paid'] ?? 0)) ?></td></tr>
</tbody>
</table>
</div>
</div>
<div class="panel">
<h2>Claim</h2>
<div class="body">
<table class="kv-table">
<thead><tr><th>Parameter</th><th>Value</th></tr></thead>
<tbody>
<tr><td>Claim Paid + Outstanding</td><td><?= esc($fmt($summary['claim_paid_outstanding'] ?? 0)) ?></td></tr>
<tr><td>Claims paid Amount</td><td><?= esc($fmt($summary['claims_paid_amount'] ?? 0)) ?></td></tr>
<tr><td>Claim Outstanding Amount</td><td><?= esc($fmt($summary['claim_outstanding_amount'] ?? 0)) ?></td></tr>
<tr><td>ACS IPD Claim</td><td><?= esc($fmt($summary['acs_ipd_claim'] ?? 0)) ?></td></tr>
<tr><td>No. of claims paid</td><td><?= esc($fmt($summary['claims_paid_count'] ?? 0, 0)) ?></td></tr>
<tr><td>No. of claims Outstanding</td><td><?= esc($fmt($summary['claims_outstanding_count'] ?? 0, 0)) ?></td></tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- DASHBOARD ROW 1 -->
<div class="grid2">
<div class="panel">
<h2>Total Claim Count/Amount</h2>
<div class="body">
<div class="legend-row">
<span><i class="dot" style="background:#2E86DE"></i>Reported</span>
<span><i class="dot" style="background:#1B3F8C"></i>Settled</span>
<span><i class="dot" style="background:#E8862C"></i>Outstanding</span>
<span><i class="dot" style="background:#6A3D9A"></i>Rejected</span>
</div>
<div class="chart-wrap"><?= $chartHtml('donut', 'chartDonut1') ?></div>
</div>
</div>
<div class="panel">
<h2>Pre-Auth TAT By Claim Count</h2>
<div class="body">
<div class="legend-row">
<span><i class="dot" style="background:#2E86DE"></i>With in 2 Hours %</span>
<span><i class="dot" style="background:#1B3F8C"></i>Above 2 Hours %</span>
</div>
<div class="chart-wrap short"><?= $chartHtml('preAuth', 'chartPreAuth') ?></div>
</div>
</div>
</div>
<div class="grid3">
<div class="panel">
<h2>Claim Status</h2>
<div class="body scroll-y">
<table>
<thead><tr><th>Claim Status</th><th class="num">Number</th><th class="num">Reported Amount</th><th class="num">Incurred Amount</th></tr></thead>
<tbody id="tblClaimStatus">
<?php foreach ($vm['claimStatus'] ?? [] as $r): ?>
<tr>
<td><?= esc($r['status'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['number'] ?? 0, 0)) ?></td>
<td class="num"><?= esc($fmt($r['reported'] ?? 0)) ?></td>
<td class="num"><?= esc($fmt($r['incurred'] ?? 0)) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<div class="panel">
<h2>Policy Premium</h2>
<div class="body">
<table>
<thead><tr><th>SrNo</th><th>Amount</th><th class="num">Value</th></tr></thead>
<tbody id="tblPolicyPremium">
<?php foreach ($vm['policyPremium'] ?? [] as $r): ?>
<tr>
<td><?= esc($r['sr'] ?? '') ?></td>
<td><?= esc($r['label'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['amount'] ?? 0)) ?></td>
</tr>
<?php endforeach; ?>
<tr style="font-weight:700;background:#F0F0F3;"><td colspan="2">Total</td><td class="num"><?= esc($fmt($vm['policyPremiumTotal'] ?? 0)) ?></td></tr>
</tbody>
</table>
</div>
</div>
<div class="panel">
<h2>&nbsp;</h2>
<div class="body">
<div class="stat-panel">
<div>
<div class="stat-icon">👥</div>
<div class="stat-num"><?= esc($fmt($vm['lives'] ?? ($header['lives'] ?? 0), 0)) ?></div>
<div class="stat-label">Lives</div>
</div>
<div>
<div class="stat-icon">📊</div>
<div class="stat-num"><?= esc(number_format((float) ($vm['icr'] ?? ($summary['icr_pct'] ?? 0)), 2)) ?>%</div>
<div class="stat-label">ICR</div>
</div>
</div>
</div>
</div>
</div>
<div class="grid2">
<div class="panel">
<h2>Reimbursement TAT By Claim Count</h2>
<div class="body">
<table>
<thead><tr><th>Date Difference</th><th class="num">Claim Count</th><th class="num">TAT %</th></tr></thead>
<tbody id="tblReimbTAT">
<?php foreach ($vm['reimbTAT'] ?? [] as $r): ?>
<tr>
<td><?= esc($r['range'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['count'] ?? 0, 0)) ?></td>
<td class="num"><?= esc($r['pct'] ?? 0) ?></td>
</tr>
<?php endforeach; ?>
<tr style="font-weight:700;background:#F0F0F3;">
<td>Total</td>
<td class="num"><?= esc($fmt($vm['reimbTATTotal']['count'] ?? 0, 0)) ?></td>
<td class="num"><?= esc($vm['reimbTATTotal']['pct'] ?? 0) ?></td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="panel">
<h2>Premium, Earn Premium and Claim Incurred Amount</h2>
<div class="body">
<div class="chart-wrap"><?= $chartHtml('premiumBars', 'chartPremiumBars') ?></div>
</div>
</div>
</div>
<!-- GENDER -->
<div class="section-title">Gender-wise Claims</div>
<div class="grid3">
<div class="panel">
<h2>Total Claim Number</h2>
<div class="body"><div class="chart-wrap medium"><?= $chartHtml('genderCount', 'chartGenderCount') ?></div></div>
</div>
<div class="panel">
<h2>Total Claim Amount</h2>
<div class="body"><div class="chart-wrap medium"><?= $chartHtml('genderAmt', 'chartGenderAmt') ?></div></div>
</div>
<div class="panel">
<h2>Gender Incurred</h2>
<div class="body">
<table>
<thead><tr><th>Gender</th><th class="num">Claim Count</th><th class="num">Claim Incurred Amount</th></tr></thead>
<tbody id="tblGender">
<?php foreach (['Male', 'Female'] as $gLabel):
$grow = null;
foreach ($vm['gender'] ?? [] as $g) {
if (($g['label'] ?? '') === $gLabel) { $grow = $g; break; }
}
$grow = $grow ?: ['label' => $gLabel, 'count' => 0, 'amount' => 0];
?>
<tr>
<td><?= esc($grow['label']) ?></td>
<td class="num"><?= esc($fmt($grow['count'] ?? 0, 0)) ?></td>
<td class="num"><?= esc($fmt($grow['amount'] ?? 0)) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot><tr><td>Total</td><td class="num" id="tblGenderCountTotal"><?= esc($fmt($vm['genderTotal']['count'] ?? 0, 0)) ?></td><td class="num" id="tblGenderAmtTotal"><?= esc($fmt($vm['genderTotal']['incurred_amount'] ?? 0)) ?></td></tr></tfoot>
</table>
</div>
</div>
</div>
<!-- CLAIM TYPE -->
<div class="panel">
<h2>Claim Type</h2>
<div class="body">
<table>
<thead><tr><th>Claim Type</th><th class="num">Claim Count</th><th class="num">Claim Count %</th><th class="num">Claim Amount</th><th class="num">Claim Amount %</th><th class="num">Claim Incurred Amount</th><th class="num">Claim Incurred Amount %</th></tr></thead>
<tbody id="tblClaimType">
<?php foreach ($vm['claimType'] ?? [] as $r): ?>
<tr<?= !empty($r['header']) ? ' class="subhead"' : '' ?>>
<td><?= !empty($r['header']) ? '' : '&nbsp;&nbsp;&nbsp;' ?><?= esc($r['type'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['count'] ?? 0, 0)) ?></td>
<td class="num"><?= esc($r['countPct'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['amount'] ?? 0)) ?></td>
<td class="num"><?= esc($r['amtPct'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['incurred'] ?? 0)) ?></td>
<td class="num"><?= esc($r['incPct'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot><tr><td>Total</td><td class="num" id="tblClaimTypeCount"><?= esc($fmt($vm['claimTypeTotal']['count'] ?? 0, 0)) ?></td><td class="num">100.00</td><td class="num" id="tblClaimTypeAmt"><?= esc($fmt($vm['claimTypeTotal']['amount'] ?? 0)) ?></td><td class="num">100.00</td><td class="num" id="tblClaimTypeInc"><?= esc($fmt($vm['claimTypeTotal']['incurred'] ?? 0)) ?></td><td class="num">100.00</td></tr></tfoot>
</table>
</div>
</div>
<!-- RELATION / MEMBER TYPE -->
<div class="grid2">
<div class="panel">
<h2>Relation</h2>
<div class="body">
<table>
<thead><tr><th>Relation</th><th class="num">Incurred Claim Count</th><th class="num">Claim Count %</th><th class="num">Claim Incurred Amount</th><th class="num">Claim Incurred Amount %</th></tr></thead>
<tbody id="tblRelation">
<?php foreach ($vm['relation'] ?? [] as $r): ?>
<tr>
<td><?= esc($r['label'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['count'] ?? 0, 0)) ?></td>
<td class="num"><?= esc($r['cpct'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['amt'] ?? 0)) ?></td>
<td class="num"><?= esc($r['apct'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot><tr><td>Total</td><td class="num" id="tblRelationCount"><?= esc($fmt($vm['relationTotal']['count'] ?? 0, 0)) ?></td><td class="num">100.00</td><td class="num" id="tblRelationAmt"><?= esc($fmt($vm['relationTotal']['incurred_amount'] ?? 0)) ?></td><td class="num">100.00</td></tr></tfoot>
</table>
</div>
</div>
<div class="panel">
<h2>Member Type</h2>
<div class="body">
<table>
<thead><tr><th>Member Type</th><th class="num">Incurred Claim Count</th><th class="num">Claim Count %</th><th class="num">Claim Incurred Amount</th><th class="num">Claim Incurred Amount %</th></tr></thead>
<tbody id="tblMemberType">
<?php foreach ($vm['memberType'] ?? [] as $r): ?>
<tr>
<td><?= esc($r['label'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['count'] ?? 0, 0)) ?></td>
<td class="num"><?= esc($r['cpct'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['amt'] ?? 0)) ?></td>
<td class="num"><?= esc($r['apct'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot><tr><td>Total</td><td class="num" id="tblMemberCount"><?= esc($fmt($vm['memberTypeTotal']['count'] ?? 0, 0)) ?></td><td class="num">100.00</td><td class="num" id="tblMemberAmt"><?= esc($fmt($vm['memberTypeTotal']['incurred_amount'] ?? 0)) ?></td><td class="num">100.00</td></tr></tfoot>
</table>
</div>
</div>
</div>
<!-- AGE / DIAGNOSIS -->
<div class="grid2">
<div class="panel">
<h2>Age</h2>
<div class="body">
<table>
<thead><tr><th>Age</th><th class="num">Incurred Claim Count</th><th class="num">Claim Count %</th><th class="num">Claim Incurred Amount</th><th class="num">Claim Incurred Amount %</th></tr></thead>
<tbody id="tblAge">
<?php foreach ($vm['age'] ?? [] as $r): ?>
<tr>
<td><?= esc($r['band'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['count'] ?? 0, 0)) ?></td>
<td class="num"><?= esc($r['cpct'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['amt'] ?? 0)) ?></td>
<td class="num"><?= esc($r['apct'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot><tr><td>Total</td><td class="num" id="tblAgeCount"><?= esc($fmt($vm['ageTotal']['count'] ?? 0, 0)) ?></td><td class="num">100.00</td><td class="num" id="tblAgeAmt"><?= esc($fmt($vm['ageTotal']['incurred_amount'] ?? 0)) ?></td><td class="num">100.00</td></tr></tfoot>
</table>
</div>
</div>
<div class="panel">
<h2>Diagnosis</h2>
<div class="body scroll-y">
<table>
<thead><tr><th>Diagnosis</th><th class="num">Count</th><th class="num">Count %</th><th class="num">Amount</th><th class="num">Amt %</th><th class="num">ACS</th><th class="num">LR %</th></tr></thead>
<tbody id="tblDiagnosis">
<?php foreach ($vm['diagnosis'] ?? [] as $r): ?>
<tr>
<td><?= esc($r['name'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['count'] ?? 0, 0)) ?></td>
<td class="num"><?= esc($r['cpct'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['amt'] ?? 0)) ?></td>
<td class="num"><?= esc($r['apct'] ?? '') ?></td>
<td class="num"><?= esc(!empty($r['acs']) ? $fmt($r['acs']) : '') ?></td>
<td class="num"><?= esc($r['lr'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot><tr><td>Total</td><td class="num" id="tblDiagCount"><?= esc($fmt($vm['diagnosisTotal']['count'] ?? 0, 0)) ?></td><td class="num">100.00</td><td class="num" id="tblDiagAmt"><?= esc($fmt($vm['diagnosisTotal']['amt'] ?? 0)) ?></td><td class="num">100.00</td><td class="num" id="tblDiagAcs"><?= esc($fmt($vm['diagnosisTotal']['acs'] ?? 0)) ?></td><td class="num" id="tblDiagLr"><?= esc($vm['diagnosisTotal']['lr'] ?? '') ?></td></tr></tfoot>
</table>
</div>
</div>
</div>
<!-- UTILIZATION REPORTS -->
<div class="grid2">
<div class="panel">
<h2>Utilization Report for Employees (In-Patient Claims)</h2>
<div class="body">
<table>
<thead><tr><th>No. Of Claim</th><th class="num">Beneficiaries Count</th><th class="num">Incurred Claim Count</th><th class="num">Claim Count %</th><th class="num">Claim Incurred Amount</th><th class="num">Claim Incurred Amount %</th></tr></thead>
<tbody id="tblUtilEmp">
<?php
$utilEmp = $vm['utilizationEmployees'] ?? [];
$utilEmpRows = $utilEmp['rows'] ?? (is_array($utilEmp) && isset($utilEmp[0]) ? $utilEmp : []);
foreach ($utilEmpRows as $r):
?>
<tr>
<td><?= esc($r['claims'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['beneficiaries'] ?? 0, 0)) ?></td>
<td class="num"><?= esc($fmt($r['incurredCount'] ?? 0, 0)) ?></td>
<td class="num"><?= esc($r['countPct'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['amount'] ?? 0)) ?></td>
<td class="num"><?= esc($r['amountPct'] ?? '') ?></td>
</tr>
<?php endforeach; $utEmp = $utilEmp['total'] ?? []; ?>
</tbody>
<tfoot><tr><td>Total</td><td class="num" id="tblUtilEmpBen"><?= esc($fmt($utEmp['beneficiaries'] ?? 0, 0)) ?></td><td class="num" id="tblUtilEmpCnt"><?= esc($fmt($utEmp['incurredCount'] ?? 0, 0)) ?></td><td class="num">100.00</td><td class="num" id="tblUtilEmpAmt"><?= esc($fmt($utEmp['amount'] ?? 0)) ?></td><td class="num">100.00</td></tr></tfoot>
</table>
</div>
</div>
<div class="panel">
<h2>Utilization Report for Dependent (In-Patient Claims)</h2>
<div class="body">
<table>
<thead><tr><th>No. of Claims</th><th class="num">Beneficiaries Count</th><th class="num">Incurred Claim Count</th><th class="num">Claim Count %</th><th class="num">Claim Incurred Amount</th><th class="num">Claim Incurred Amount %</th></tr></thead>
<tbody id="tblUtilDep">
<?php
$utilDep = $vm['utilizationDependents'] ?? [];
$utilDepRows = $utilDep['rows'] ?? (is_array($utilDep) && isset($utilDep[0]) ? $utilDep : []);
foreach ($utilDepRows as $r):
?>
<tr>
<td><?= esc($r['claims'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['beneficiaries'] ?? 0, 0)) ?></td>
<td class="num"><?= esc($fmt($r['incurredCount'] ?? 0, 0)) ?></td>
<td class="num"><?= esc($r['countPct'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['amount'] ?? 0)) ?></td>
<td class="num"><?= esc($r['amountPct'] ?? '') ?></td>
</tr>
<?php endforeach; $utDep = $utilDep['total'] ?? []; ?>
</tbody>
<tfoot><tr><td>Total</td><td class="num" id="tblUtilDepBen"><?= esc($fmt($utDep['beneficiaries'] ?? 0, 0)) ?></td><td class="num" id="tblUtilDepCnt"><?= esc($fmt($utDep['incurredCount'] ?? 0, 0)) ?></td><td class="num">100.00</td><td class="num" id="tblUtilDepAmt"><?= esc($fmt($utDep['amount'] ?? 0)) ?></td><td class="num">100.00</td></tr></tfoot>
</table>
</div>
</div>
</div>
<!-- HOSPITAL NAME -->
<div class="panel">
<h2>Hospital Name</h2>
<div class="body scroll-y">
<table>
<thead><tr><th>Hospital Name</th><th class="num">Incurred Claim Count</th><th class="num">Claim Count %</th><th class="num">Claim Incurred Amount</th><th class="num">Claim Incurred Amount %</th></tr></thead>
<tbody id="tblHospital">
<?php foreach ($vm['hospitals'] ?? [] as $r): ?>
<tr>
<td><?= esc($r['name'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['count'] ?? 0, 0)) ?></td>
<td class="num"><?= esc($r['cpct'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['amt'] ?? 0)) ?></td>
<td class="num"><?= esc($r['apct'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot><tr><td>Total</td><td class="num" id="tblHospitalCount"><?= esc($fmt($vm['hospitalTotal']['count'] ?? 0, 0)) ?></td><td class="num">100.00</td><td class="num" id="tblHospitalAmt"><?= esc($fmt($vm['hospitalTotal']['incurred_amount'] ?? 0)) ?></td><td class="num">100.00</td></tr></tfoot>
</table>
</div>
</div>
<!-- CLAIM PAID / TYPE CHARTS -->
<div class="grid4">
<div class="panel">
<h2>Claim Paid Count By Claim Type</h2>
<div class="body"><div class="chart-wrap medium"><?= $chartHtml('paidCount', 'chartPaidCount') ?></div></div>
</div>
<div class="panel">
<h2>Claim Paid Amount and ACS</h2>
<div class="body"><div class="chart-wrap medium"><?= $chartHtml('paidAmtAcs', 'chartPaidAmtACS') ?></div></div>
</div>
<div class="panel">
<h2>Claim Count By Claim Status</h2>
<div class="body"><div class="chart-wrap medium"><?= $chartHtml('countStatus', 'chartCountStatus') ?></div></div>
</div>
<div class="panel">
<h2>Claim Amount By Claim Status</h2>
<div class="body"><div class="chart-wrap medium"><?= $chartHtml('amtStatus', 'chartAmtStatus') ?></div></div>
</div>
</div>
<!-- HOSPITAL CITY -->
<div class="panel">
<h2>Hospital City</h2>
<div class="body scroll-y">
<table>
<thead><tr><th>Hospital City</th><th class="num">Claim Count</th><th class="num">Claim Amount</th><th class="num">Claim Paid Count</th><th class="num">Claim Paid Amount</th><th class="num">Claim Outstanding Count</th><th class="num">Claim Outstanding Amount</th><th class="num">Total ACS</th></tr></thead>
<tbody id="tblCity">
<?php foreach ($vm['cities'] ?? [] as $r): ?>
<tr>
<td><?= esc($r['city'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['count'] ?? 0, 0)) ?></td>
<td class="num"><?= esc($fmt($r['amt'] ?? 0)) ?></td>
<td class="num"><?= esc(($r['paidCount'] === '' || $r['paidCount'] === null) ? '—' : $fmt($r['paidCount'], 0)) ?></td>
<td class="num"><?= esc(($r['paidAmt'] === '' || $r['paidAmt'] === null) ? '—' : $fmt($r['paidAmt'])) ?></td>
<td class="num"><?= esc(($r['outCount'] === '' || $r['outCount'] === null) ? '—' : $fmt($r['outCount'], 0)) ?></td>
<td class="num"><?= esc(($r['outAmt'] === '' || $r['outAmt'] === null) ? '—' : $fmt($r['outAmt'])) ?></td>
<td class="num"><?= esc(($r['acs'] === '' || $r['acs'] === null) ? '—' : $fmt($r['acs'])) ?></td>
</tr>
<?php endforeach; $cty = $vm['cityTotals'] ?? []; ?>
</tbody>
<tfoot><tr><td>Total</td><td class="num" id="tblCityCount"><?= esc($fmt($cty['count'] ?? 0, 0)) ?></td><td class="num" id="tblCityAmt"><?= esc($fmt($cty['amt'] ?? 0)) ?></td><td class="num" id="tblCityPaidCnt"><?= esc($fmt($cty['paidCount'] ?? 0, 0)) ?></td><td class="num" id="tblCityPaidAmt"><?= esc($fmt($cty['paidAmt'] ?? 0)) ?></td><td class="num" id="tblCityOutCnt"><?= esc($fmt($cty['outCount'] ?? 0, 0)) ?></td><td class="num" id="tblCityOutAmt"><?= esc($fmt($cty['outAmt'] ?? 0)) ?></td><td class="num" id="tblCityAcs"><?= esc($fmt($cty['acs'] ?? 0)) ?></td></tr></tfoot>
</table>
</div>
</div>
<!-- REGISTRATION MONTH -->
<div class="grid2">
<div class="panel">
<h2>Claim Paid Count and Claim Outstanding Count by Registration Month</h2>
<div class="body"><div class="chart-wrap tall"><?= $chartHtml('monthCount', 'chartMonthCount') ?></div></div>
</div>
<div class="panel">
<h2>Claim Paid Amount, Claim Outstanding Amount by Registration Date</h2>
<div class="body"><div class="chart-wrap tall"><?= $chartHtml('monthAmt', 'chartMonthAmt') ?></div></div>
</div>
</div>
</div>
<?php if (!$embed): ?>
<script src="<?= base_url('assets/libs/chart.js/chart.umd.min.js') ?>"></script>
<?php include APPPATH . 'Views/partials/claims_mis_abhi_script.php'; ?>
<?php endif; ?>
</body>
</html>

View File

@ -0,0 +1,558 @@
<?php
/** @var array $report */
/** @var array $view_model */
/** @var array $charts */
/** @var int $policy_id */
$report = $report ?? [];
$view_model = $view_model ?? ($report['view_model'] ?? []);
$header = $report['header'] ?? [];
$summary = $report['summary'] ?? [];
$meta = $report['meta'] ?? [];
$vm = $view_model;
$charts = $charts ?? [];
$fmt = static fn ($n, $d = 0) => number_format((float) $n, $d);
$dash = static fn ($v) => ($v === '' || $v === null) ? '—' : $v;
$logoPath = rtrim(ROOTPATH, '/\\') . '/public/assets/images/abhi.png';
$img = static function (string $key) use ($charts): string {
$path = $charts[$key] ?? '';
if ($path === '' || !is_file($path)) {
return '<p class="muted">—</p>';
}
return '<img class="chart-img" src="' . htmlspecialchars($path, ENT_QUOTES) . '" alt="">';
};
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ABH MIS Report <?= esc($header['policy_number'] ?? $policy_id) ?></title>
</head>
<body>
<div class="wrap">
<table class="header-table" width="100%" cellpadding="0" cellspacing="0">
<tr>
<td width="160" valign="middle">
<?php if (is_file($logoPath)): ?>
<img src="<?= esc($logoPath) ?>" width="140" alt="ABHI">
<?php endif; ?>
</td>
<td valign="middle">
<div class="info-pill"><?= esc($header['policy_number'] ?? '') ?><small>Policy Number</small></div>
<div class="info-pill"><?= esc($meta['client_name'] ?? '') ?><small>Client Name</small></div>
<div class="info-pill"><?= esc($meta['tpa_name'] ?? ($header['claim_processor'] ?? '')) ?><small>TPA Name</small></div>
<div class="info-pill"><?= esc($meta['last_refresh'] ?? date('d-M-Y')) ?><small>Last Refresh</small></div>
</td>
</tr>
</table>
<table class="layout2" width="100%" cellpadding="0" cellspacing="8">
<tr>
<td width="50%" valign="top">
<div class="kv-title">Premium</div>
<table class="data kv">
<thead><tr><th>Parameter</th><th>Value</th></tr></thead>
<tbody>
<tr><td>Insurer Name</td><td><?= esc($header['insurer_name'] ?? '') ?></td></tr>
<tr><td>Claim processing team</td><td><?= esc($header['claim_processor'] ?? '') ?></td></tr>
<tr><td>Policy Number</td><td><?= esc($header['policy_number'] ?? '') ?></td></tr>
<tr><td>Policy Start Date</td><td><?= esc($header['policy_start_date'] ?? '') ?></td></tr>
<tr><td>Policy End Date</td><td><?= esc($header['policy_end_date'] ?? '') ?></td></tr>
<tr><td>Lives as on date</td><td><?= esc($fmt($header['lives'] ?? 0, 0)) ?></td></tr>
<tr><td>Premium paid as on date</td><td><?= esc($fmt($header['premium_paid'] ?? 0)) ?></td></tr>
</tbody>
</table>
</td>
<td width="50%" valign="top">
<div class="kv-title">Claim</div>
<table class="data kv">
<thead><tr><th>Parameter</th><th>Value</th></tr></thead>
<tbody>
<tr><td>Claim Paid + Outstanding</td><td><?= esc($fmt($summary['claim_paid_outstanding'] ?? 0)) ?></td></tr>
<tr><td>Claims paid Amount</td><td><?= esc($fmt($summary['claims_paid_amount'] ?? 0)) ?></td></tr>
<tr><td>Claim Outstanding Amount</td><td><?= esc($fmt($summary['claim_outstanding_amount'] ?? 0)) ?></td></tr>
<tr><td>ACS IPD Claim</td><td><?= esc($fmt($summary['acs_ipd_claim'] ?? 0)) ?></td></tr>
<tr><td>No. of claims paid</td><td><?= esc($fmt($summary['claims_paid_count'] ?? 0, 0)) ?></td></tr>
<tr><td>No. of claims Outstanding</td><td><?= esc($fmt($summary['claims_outstanding_count'] ?? 0, 0)) ?></td></tr>
</tbody>
</table>
</td>
</tr>
</table>
<table class="layout2" width="100%" cellpadding="0" cellspacing="8">
<tr>
<td width="50%" valign="top">
<div class="panel-h">Total Claim Count/Amount</div>
<div class="panel-b"><?= $img('donut') ?></div>
</td>
<td width="50%" valign="top">
<div class="panel-h">Pre-Auth TAT By Claim Count</div>
<div class="panel-b"><?= $img('preAuth') ?></div>
</td>
</tr>
</table>
<table class="layout3" width="100%" cellpadding="0" cellspacing="8">
<tr>
<td width="40%" valign="top">
<div class="panel-h">Claim Status</div>
<table class="data">
<thead><tr><th>Claim Status</th><th class="num">Number</th><th class="num">Reported Amount</th><th class="num">Incurred Amount</th></tr></thead>
<tbody>
<?php foreach ($vm['claimStatus'] ?? [] as $r): ?>
<tr>
<td><?= esc($r['status'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['number'] ?? 0, 0)) ?></td>
<td class="num"><?= esc($fmt($r['reported'] ?? 0)) ?></td>
<td class="num"><?= esc($fmt($r['incurred'] ?? 0)) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</td>
<td width="35%" valign="top">
<div class="panel-h">Policy Premium</div>
<table class="data">
<thead><tr><th>SrNo</th><th>Amount</th><th class="num">Value</th></tr></thead>
<tbody>
<?php foreach ($vm['policyPremium'] ?? [] as $r): ?>
<tr>
<td><?= esc($r['sr'] ?? '') ?></td>
<td><?= esc($r['label'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['amount'] ?? 0)) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot><tr><td colspan="2">Total</td><td class="num"><?= esc($fmt($vm['policyPremiumTotal'] ?? 0)) ?></td></tr></tfoot>
</table>
</td>
<td width="25%" valign="top">
<div class="panel-h">&nbsp;</div>
<div class="stat-box">
<div class="stat-num"><?= esc($fmt($vm['lives'] ?? ($header['lives'] ?? 0), 0)) ?></div>
<div class="stat-label">Lives</div>
<div class="stat-num" style="margin-top:12px;"><?= esc(number_format((float) ($vm['icr'] ?? ($summary['icr_pct'] ?? 0)), 2)) ?>%</div>
<div class="stat-label">ICR</div>
</div>
</td>
</tr>
</table>
<table class="layout2" width="100%" cellpadding="0" cellspacing="8">
<tr>
<td width="45%" valign="top">
<div class="panel-h">Reimbursement TAT By Claim Count</div>
<table class="data">
<thead><tr><th>Date Difference</th><th class="num">Claim Count</th><th class="num">TAT %</th></tr></thead>
<tbody>
<?php foreach ($vm['reimbTAT'] ?? [] as $r): ?>
<tr>
<td><?= esc($r['range'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['count'] ?? 0, 0)) ?></td>
<td class="num"><?= esc($r['pct'] ?? 0) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot>
<tr>
<td>Total</td>
<td class="num"><?= esc($fmt($vm['reimbTATTotal']['count'] ?? 0, 0)) ?></td>
<td class="num"><?= esc($vm['reimbTATTotal']['pct'] ?? 0) ?></td>
</tr>
</tfoot>
</table>
</td>
<td width="55%" valign="top">
<div class="panel-h">Premium, Earn Premium and Claim Incurred Amount</div>
<div class="panel-b"><?= $img('premiumBars') ?></div>
</td>
</tr>
</table>
<div class="section-title">Gender-wise Claims</div>
<table class="layout3" width="100%" cellpadding="0" cellspacing="8">
<tr>
<td width="34%" valign="top">
<div class="panel-h">Total Claim Number</div>
<div class="panel-b"><?= $img('genderCount') ?></div>
</td>
<td width="34%" valign="top">
<div class="panel-h">Total Claim Amount</div>
<div class="panel-b"><?= $img('genderAmt') ?></div>
</td>
<td width="32%" valign="top">
<div class="panel-h">Gender Incurred</div>
<table class="data">
<thead><tr><th>Gender</th><th class="num">Claim Count</th><th class="num">Claim Incurred Amount</th></tr></thead>
<tbody>
<?php foreach (['Male', 'Female'] as $gLabel):
$row = null;
foreach ($vm['gender'] ?? [] as $g) {
if (($g['label'] ?? '') === $gLabel) { $row = $g; break; }
}
$row = $row ?: ['label' => $gLabel, 'count' => 0, 'amount' => 0];
?>
<tr>
<td><?= esc($row['label']) ?></td>
<td class="num"><?= esc($fmt($row['count'] ?? 0, 0)) ?></td>
<td class="num"><?= esc($fmt($row['amount'] ?? 0)) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot>
<tr>
<td>Total</td>
<td class="num"><?= esc($fmt($vm['genderTotal']['count'] ?? 0, 0)) ?></td>
<td class="num"><?= esc($fmt($vm['genderTotal']['incurred_amount'] ?? 0)) ?></td>
</tr>
</tfoot>
</table>
</td>
</tr>
</table>
<div class="panel-h">Claim Type</div>
<table class="data">
<thead><tr><th>Claim Type</th><th class="num">Claim Count</th><th class="num">Claim Count %</th><th class="num">Claim Amount</th><th class="num">Claim Amount %</th><th class="num">Claim Incurred Amount</th><th class="num">Claim Incurred Amount %</th></tr></thead>
<tbody>
<?php foreach ($vm['claimType'] ?? [] as $r): ?>
<tr>
<td><?= !empty($r['header']) ? '<strong>' : '&nbsp;&nbsp;' ?><?= esc($r['type'] ?? '') ?><?= !empty($r['header']) ? '</strong>' : '' ?></td>
<td class="num"><?= esc($fmt($r['count'] ?? 0, 0)) ?></td>
<td class="num"><?= esc($r['countPct'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['amount'] ?? 0)) ?></td>
<td class="num"><?= esc($r['amtPct'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['incurred'] ?? 0)) ?></td>
<td class="num"><?= esc($r['incPct'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot>
<tr>
<td>Total</td>
<td class="num"><?= esc($fmt($vm['claimTypeTotal']['count'] ?? 0, 0)) ?></td>
<td class="num">100.00</td>
<td class="num"><?= esc($fmt($vm['claimTypeTotal']['amount'] ?? 0)) ?></td>
<td class="num">100.00</td>
<td class="num"><?= esc($fmt($vm['claimTypeTotal']['incurred'] ?? 0)) ?></td>
<td class="num">100.00</td>
</tr>
</tfoot>
</table>
<table class="layout2" width="100%" cellpadding="0" cellspacing="8">
<tr>
<td width="50%" valign="top">
<div class="panel-h">Relation</div>
<table class="data">
<thead><tr><th>Relation</th><th class="num">Incurred Claim Count</th><th class="num">Claim Count %</th><th class="num">Claim Incurred Amount</th><th class="num">Claim Incurred Amount %</th></tr></thead>
<tbody>
<?php foreach ($vm['relation'] ?? [] as $r): ?>
<tr>
<td><?= esc($r['label'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['count'] ?? 0, 0)) ?></td>
<td class="num"><?= esc($r['cpct'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['amt'] ?? 0)) ?></td>
<td class="num"><?= esc($r['apct'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot>
<tr>
<td>Total</td>
<td class="num"><?= esc($fmt($vm['relationTotal']['count'] ?? 0, 0)) ?></td>
<td class="num">100.00</td>
<td class="num"><?= esc($fmt($vm['relationTotal']['incurred_amount'] ?? 0)) ?></td>
<td class="num">100.00</td>
</tr>
</tfoot>
</table>
</td>
<td width="50%" valign="top">
<div class="panel-h">Member Type</div>
<table class="data">
<thead><tr><th>Member Type</th><th class="num">Incurred Claim Count</th><th class="num">Claim Count %</th><th class="num">Claim Incurred Amount</th><th class="num">Claim Incurred Amount %</th></tr></thead>
<tbody>
<?php foreach ($vm['memberType'] ?? [] as $r): ?>
<tr>
<td><?= esc($r['label'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['count'] ?? 0, 0)) ?></td>
<td class="num"><?= esc($r['cpct'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['amt'] ?? 0)) ?></td>
<td class="num"><?= esc($r['apct'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot>
<tr>
<td>Total</td>
<td class="num"><?= esc($fmt($vm['memberTypeTotal']['count'] ?? 0, 0)) ?></td>
<td class="num">100.00</td>
<td class="num"><?= esc($fmt($vm['memberTypeTotal']['incurred_amount'] ?? 0)) ?></td>
<td class="num">100.00</td>
</tr>
</tfoot>
</table>
</td>
</tr>
</table>
<table class="layout2" width="100%" cellpadding="0" cellspacing="8">
<tr>
<td width="45%" valign="top">
<div class="panel-h">Age</div>
<table class="data">
<thead><tr><th>Age</th><th class="num">Incurred Claim Count</th><th class="num">Claim Count %</th><th class="num">Claim Incurred Amount</th><th class="num">Claim Incurred Amount %</th></tr></thead>
<tbody>
<?php foreach ($vm['age'] ?? [] as $r): ?>
<tr>
<td><?= esc($r['band'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['count'] ?? 0, 0)) ?></td>
<td class="num"><?= esc($r['cpct'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['amt'] ?? 0)) ?></td>
<td class="num"><?= esc($r['apct'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot>
<tr>
<td>Total</td>
<td class="num"><?= esc($fmt($vm['ageTotal']['count'] ?? 0, 0)) ?></td>
<td class="num">100.00</td>
<td class="num"><?= esc($fmt($vm['ageTotal']['incurred_amount'] ?? 0)) ?></td>
<td class="num">100.00</td>
</tr>
</tfoot>
</table>
</td>
<td width="55%" valign="top">
<div class="panel-h">Diagnosis</div>
<table class="data">
<thead><tr><th>Diagnosis</th><th class="num">Count</th><th class="num">Count %</th><th class="num">Amount</th><th class="num">Amt %</th><th class="num">ACS</th><th class="num">LR %</th></tr></thead>
<tbody>
<?php foreach ($vm['diagnosis'] ?? [] as $r): ?>
<tr>
<td><?= esc($r['name'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['count'] ?? 0, 0)) ?></td>
<td class="num"><?= esc($r['cpct'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['amt'] ?? 0)) ?></td>
<td class="num"><?= esc($r['apct'] ?? '') ?></td>
<td class="num"><?= esc(!empty($r['acs']) ? $fmt($r['acs']) : '') ?></td>
<td class="num"><?= esc($r['lr'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot>
<tr>
<td>Total</td>
<td class="num"><?= esc($fmt($vm['diagnosisTotal']['count'] ?? 0, 0)) ?></td>
<td class="num">100.00</td>
<td class="num"><?= esc($fmt($vm['diagnosisTotal']['amt'] ?? 0)) ?></td>
<td class="num">100.00</td>
<td class="num"><?= esc($fmt($vm['diagnosisTotal']['acs'] ?? 0)) ?></td>
<td class="num"><?= esc($vm['diagnosisTotal']['lr'] ?? '') ?></td>
</tr>
</tfoot>
</table>
</td>
</tr>
</table>
<table class="layout2" width="100%" cellpadding="0" cellspacing="8">
<tr>
<td width="50%" valign="top">
<div class="panel-h">Utilization Report for Employees (In-Patient Claims)</div>
<table class="data">
<thead><tr><th>No. Of Claim</th><th class="num">Beneficiaries</th><th class="num">Incurred Claim Count</th><th class="num">Claim Count %</th><th class="num">Claim Incurred Amount</th><th class="num">Claim Incurred Amount %</th></tr></thead>
<tbody>
<?php
$utilEmp = $vm['utilizationEmployees'] ?? [];
$utilEmpRows = $utilEmp['rows'] ?? $utilEmp;
foreach ($utilEmpRows as $r):
?>
<tr>
<td><?= esc($r['claims'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['beneficiaries'] ?? 0, 0)) ?></td>
<td class="num"><?= esc($fmt($r['incurredCount'] ?? 0, 0)) ?></td>
<td class="num"><?= esc($r['countPct'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['amount'] ?? 0)) ?></td>
<td class="num"><?= esc($r['amountPct'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot>
<?php $ut = $utilEmp['total'] ?? []; ?>
<tr>
<td>Total</td>
<td class="num"><?= esc($fmt($ut['beneficiaries'] ?? 0, 0)) ?></td>
<td class="num"><?= esc($fmt($ut['incurredCount'] ?? 0, 0)) ?></td>
<td class="num">100.00</td>
<td class="num"><?= esc($fmt($ut['amount'] ?? 0)) ?></td>
<td class="num">100.00</td>
</tr>
</tfoot>
</table>
</td>
<td width="50%" valign="top">
<div class="panel-h">Utilization Report for Dependent (In-Patient Claims)</div>
<table class="data">
<thead><tr><th>No. of Claims</th><th class="num">Beneficiaries</th><th class="num">Incurred Claim Count</th><th class="num">Claim Count %</th><th class="num">Claim Incurred Amount</th><th class="num">Claim Incurred Amount %</th></tr></thead>
<tbody>
<?php
$utilDep = $vm['utilizationDependents'] ?? [];
$utilDepRows = $utilDep['rows'] ?? $utilDep;
foreach ($utilDepRows as $r):
?>
<tr>
<td><?= esc($r['claims'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['beneficiaries'] ?? 0, 0)) ?></td>
<td class="num"><?= esc($fmt($r['incurredCount'] ?? 0, 0)) ?></td>
<td class="num"><?= esc($r['countPct'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['amount'] ?? 0)) ?></td>
<td class="num"><?= esc($r['amountPct'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot>
<?php $dt = $utilDep['total'] ?? []; ?>
<tr>
<td>Total</td>
<td class="num"><?= esc($fmt($dt['beneficiaries'] ?? 0, 0)) ?></td>
<td class="num"><?= esc($fmt($dt['incurredCount'] ?? 0, 0)) ?></td>
<td class="num">100.00</td>
<td class="num"><?= esc($fmt($dt['amount'] ?? 0)) ?></td>
<td class="num">100.00</td>
</tr>
</tfoot>
</table>
</td>
</tr>
</table>
<div class="panel-h">Hospital Name</div>
<table class="data">
<thead><tr><th>Hospital Name</th><th class="num">Incurred Claim Count</th><th class="num">Claim Count %</th><th class="num">Claim Incurred Amount</th><th class="num">Claim Incurred Amount %</th></tr></thead>
<tbody>
<?php foreach ($vm['hospitals'] ?? [] as $r): ?>
<tr>
<td><?= esc($r['name'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['count'] ?? 0, 0)) ?></td>
<td class="num"><?= esc($r['cpct'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['amt'] ?? 0)) ?></td>
<td class="num"><?= esc($r['apct'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot>
<tr>
<td>Total</td>
<td class="num"><?= esc($fmt($vm['hospitalTotal']['count'] ?? 0, 0)) ?></td>
<td class="num">100.00</td>
<td class="num"><?= esc($fmt($vm['hospitalTotal']['incurred_amount'] ?? 0)) ?></td>
<td class="num">100.00</td>
</tr>
</tfoot>
</table>
<table class="layout2" width="100%" cellpadding="0" cellspacing="8">
<tr>
<td width="50%" valign="top">
<div class="panel-h">Claim Paid Count By Claim Type</div>
<div class="panel-b"><?= $img('paidCount') ?></div>
</td>
<td width="50%" valign="top">
<div class="panel-h">Claim Paid Amount and ACS</div>
<div class="panel-b"><?= $img('paidAmtAcs') ?></div>
</td>
</tr>
</table>
<table class="layout2" width="100%" cellpadding="0" cellspacing="8">
<tr>
<td width="50%" valign="top">
<div class="panel-h">Claim Count By Claim Status</div>
<div class="panel-b"><?= $img('countStatus') ?></div>
</td>
<td width="50%" valign="top">
<div class="panel-h">Claim Amount By Claim Status</div>
<div class="panel-b"><?= $img('amtStatus') ?></div>
</td>
</tr>
</table>
<div class="panel-h">Hospital City</div>
<table class="data">
<thead><tr><th>Hospital City</th><th class="num">Claim Count</th><th class="num">Claim Amount</th><th class="num">Claim Paid Count</th><th class="num">Claim Paid Amount</th><th class="num">Claim Outstanding Count</th><th class="num">Claim Outstanding Amount</th><th class="num">Total ACS</th></tr></thead>
<tbody>
<?php foreach ($vm['cities'] ?? [] as $r): ?>
<tr>
<td><?= esc($r['city'] ?? '') ?></td>
<td class="num"><?= esc($fmt($r['count'] ?? 0, 0)) ?></td>
<td class="num"><?= esc($fmt($r['amt'] ?? 0)) ?></td>
<td class="num"><?= esc($dash($r['paidCount'] ?? '')) ?></td>
<td class="num"><?= esc(($r['paidAmt'] === '' || $r['paidAmt'] === null) ? '—' : $fmt($r['paidAmt'])) ?></td>
<td class="num"><?= esc($dash($r['outCount'] ?? '')) ?></td>
<td class="num"><?= esc(($r['outAmt'] === '' || $r['outAmt'] === null) ? '—' : $fmt($r['outAmt'])) ?></td>
<td class="num"><?= esc($dash($r['acs'] ?? '')) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot>
<?php $ct = $vm['cityTotals'] ?? []; ?>
<tr>
<td>Total</td>
<td class="num"><?= esc($fmt($ct['count'] ?? 0, 0)) ?></td>
<td class="num"><?= esc($fmt($ct['amt'] ?? 0)) ?></td>
<td class="num"><?= esc($fmt($ct['paidCount'] ?? 0, 0)) ?></td>
<td class="num"><?= esc($fmt($ct['paidAmt'] ?? 0)) ?></td>
<td class="num"><?= esc($fmt($ct['outCount'] ?? 0, 0)) ?></td>
<td class="num"><?= esc($fmt($ct['outAmt'] ?? 0)) ?></td>
<td class="num"><?= esc($fmt($ct['acs'] ?? 0)) ?></td>
</tr>
</tfoot>
</table>
<table class="layout2" width="100%" cellpadding="0" cellspacing="8">
<tr>
<td width="50%" valign="top">
<div class="panel-h">Claim Paid Count and Claim Outstanding Count by Registration Month</div>
<div class="panel-b"><?= $img('monthCount') ?></div>
</td>
<td width="50%" valign="top">
<div class="panel-h">Claim Paid Amount, Claim Outstanding Amount by Registration Date</div>
<div class="panel-b"><?= $img('monthAmt') ?></div>
</td>
</tr>
</table>
<?php if (!empty($vm['months'])): ?>
<div class="panel-h">Registration Month Detail</div>
<table class="data">
<thead>
<tr>
<th>Month</th>
<th class="num">Paid Count</th>
<th class="num">Outstanding Count</th>
<th class="num">Paid Amount</th>
<th class="num">Outstanding Amount</th>
</tr>
</thead>
<tbody>
<?php foreach ($vm['months'] as $i => $month): ?>
<tr>
<td><?= esc($month) ?></td>
<td class="num"><?= esc($fmt($vm['paidCountByMonth'][$i] ?? 0, 0)) ?></td>
<td class="num"><?= esc($fmt($vm['outCountByMonth'][$i] ?? 0, 0)) ?></td>
<td class="num"><?= esc($fmt($vm['paidAmtByMonth'][$i] ?? 0)) ?></td>
<td class="num"><?= esc($fmt($vm['outAmtByMonth'][$i] ?? 0)) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
</body>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,869 @@
<?php
/** @var array $report */
/** @var array $view_model */
/** @var int $policy_id */
/** @var string $pdf_url */
/** @var bool $embed */
use App\Libraries\VidalMisReportService as VidalFmt;
$embed = $embed ?? false;
$report = $report ?? [];
$vm = $view_model ?? ($report['view_model'] ?? $report);
$meta = $report['meta'] ?? ($vm['meta'] ?? []);
$header = $report['header'] ?? ($vm['header'] ?? []);
$toc = $vm['toc'] ?? ($report['toc'] ?? []);
$icr = $vm['icr'] ?? [];
$hosp = $vm['hospitalization'] ?? [];
$memberGender = $vm['member_gender'] ?? [];
$memberAge = $vm['member_age'] ?? [];
$claimsAge = $vm['claims_age'] ?? [];
$claimsAmount = $vm['claims_amount'] ?? [];
$ailments = $vm['ailments'] ?? [];
$hospitals = $vm['hospitals'] ?? [];
$cmSummary = $vm['cashless_member_summary'] ?? [];
$tat = $vm['tat'] ?? [];
$mom = $vm['month_on_month'] ?? [];
$payout = $vm['payout'] ?? [];
$chartGender = $vm['chart_gender'] ?? [];
$chartAge = $vm['chart_age'] ?? [];
$ageRelations = ['Self', 'Spouse', 'Partner', 'Child', 'Parents', 'In Law', 'Other'];
$logoSrc = !empty($embed)
? (rtrim(ROOTPATH, '/\\') . '/public/assets/images/vidal_mis.png')
: base_url('public/assets/images/vidal_mis.png');
$num = static function ($n, int $d = 0): string {
return VidalFmt::fmtNum($n, $d);
};
$pct = static function ($n, int $d = 0, bool $withSymbol = true): string {
return VidalFmt::fmtPct($n, $d, $withSymbol);
};
$pct2 = static function ($n): string {
return VidalFmt::fmtPct($n, 2, true);
};
$cellCa = static function ($cell) use ($num): array {
$c = is_array($cell) ? $cell : [];
return [
'count' => $num($c['count'] ?? 0),
'amount' => $num($c['amount'] ?? 0),
];
};
$genderMax = 1;
foreach ($chartGender as $g) {
$genderMax = max($genderMax, (int) ($g['male'] ?? 0) + (int) ($g['female'] ?? 0));
}
$ageMax = 1;
foreach ($chartAge as $a) {
$ageMax = max($ageMax, (int) ($a['total'] ?? 0));
}
$ageBarColor = static function (string $dominant): string {
return match (true) {
$dominant === 'Child' => '#4472c4',
str_contains($dominant, 'Self') => '#264478',
str_contains($dominant, 'Parents') => '#ffc000',
default => '#70ad47',
};
};
/** DomPDF-safe horizontal bar (nested table widths). */
$renderBar = static function (float $pct, string $color): void {
$filled = (int) max(0, min(100, round($pct)));
$empty = 100 - $filled;
echo '<table width="100%" cellspacing="0" cellpadding="0"><tr>';
if ($filled > 0) {
echo '<td width="' . $filled . '%" style="background:' . esc($color) . ';height:10px;font-size:1px;line-height:1px;">&nbsp;</td>';
}
if ($empty > 0) {
echo '<td width="' . $empty . '%" style="background:#f2f2f2;height:10px;font-size:1px;line-height:1px;">&nbsp;</td>';
}
if ($filled === 0 && $empty === 0) {
echo '<td width="100%" style="background:#f2f2f2;height:10px;font-size:1px;line-height:1px;">&nbsp;</td>';
}
echo '</tr></table>';
};
$renderRelationClaimRow = static function (
string $label,
array $row,
array $ageRelations,
bool $isTotal = false
) use ($num, $pct2, $cellCa): void {
$cls = $isTotal ? ' class="total-row"' : '';
echo '<tr' . $cls . '><td class="rowlabel">' . esc($label) . '</td>';
foreach ($ageRelations as $rel) {
$ca = $cellCa($row[$rel] ?? []);
echo '<td>' . esc($ca['count']) . '</td><td class="num">' . esc($ca['amount']) . '</td>';
}
$tot = $cellCa($row['total'] ?? ($row['_total'] ?? []));
echo '<td>' . esc($tot['count']) . '</td><td class="num">' . esc($tot['amount']) . '</td>';
$pct = $row['pct'] ?? [];
if (is_array($pct) && (isset($pct['count']) || isset($pct['amount']))) {
echo '<td>' . esc($pct2($pct['count'] ?? 0) . ' / ' . $pct2($pct['amount'] ?? 0)) . '</td>';
} else {
echo '<td></td>';
}
echo '</tr>';
};
$renderRelationPctRow = static function (array $colPct, array $ageRelations) use ($pct): void {
echo '<tr><td class="rowlabel">%</td>';
foreach ($ageRelations as $rel) {
$p = $colPct[$rel] ?? [];
if (is_array($p)) {
echo '<td>' . esc($pct($p['count'] ?? 0)) . '</td><td>' . esc($pct($p['amount'] ?? 0)) . '</td>';
} else {
echo '<td>' . esc($pct($p)) . '</td><td></td>';
}
}
echo '<td>' . esc($pct(100)) . '</td><td>' . esc($pct(100)) . '</td><td></td></tr>';
};
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vidal Health - Corporate Analysis Report</title>
<style>
:root {
--blue: #0072bc;
--header-blue: #dbe9f5;
--group-header: #b9d6ec;
--border: #888;
--text: #111;
--muted: #444;
--male: #4472c4;
--female: #ed7d31;
}
* { box-sizing: border-box; }
body {
font-family: DejaVu Sans, Arial, Helvetica, sans-serif;
font-size: 11px;
color: var(--text);
background: #e8e8e8;
margin: 0;
padding: 20px 0;
}
.page {
background: #fff;
width: 900px;
margin: 0 auto 20px auto;
padding: 18px 28px 28px 28px;
border: 1px solid #ccc;
}
.header-top {
border-bottom: 1.5px solid #2f5f8f;
padding-bottom: 6px;
margin-bottom: 8px;
text-align: left;
}
.logo-block {
display: block;
text-align: left;
margin: 0;
padding: 0;
line-height: 0;
}
.brand-logo {
height: 72px;
width: auto;
max-width: 420px;
object-fit: contain;
object-position: left top;
display: block;
margin: 0;
padding: 0;
}
.addr-line {
font-size: 10px;
font-weight: bold;
margin-top: 10px;
line-height: 1.35;
text-align: left;
}
.report-title {
text-align: left;
font-weight: bold;
font-size: 12px;
margin: 8px 0 6px 0;
}
.top-grid-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 14px;
}
.top-grid-table > tbody > tr > td {
vertical-align: top;
width: 50%;
padding: 0 16px 0 0;
border: none;
}
.top-grid-table > tbody > tr > td:last-child { padding: 0 0 0 16px; }
.policy-details table { border-collapse: collapse; }
.policy-details td { padding: 1px 6px 1px 0; font-size: 11px; border: none; text-align: left; }
.policy-details td.label { font-weight: bold; }
.section-label { font-weight: bold; text-decoration: underline; margin-bottom: 4px; }
.toc ol { margin: 0; padding-left: 18px; font-size: 11px; line-height: 1.6; }
h3.section-title {
font-size: 12px;
font-weight: bold;
margin: 12px 0 6px 0;
border-bottom: 1px solid #999;
padding-bottom: 2px;
}
table.section-wrap {
width: 100%;
border-collapse: collapse;
margin: 0;
}
table.section-wrap > tbody > tr > td {
border: none;
padding: 0;
vertical-align: top;
}
table.data {
border-collapse: collapse;
width: 100%;
font-size: 10.5px;
margin-bottom: 6px;
}
table.data th, table.data td {
border: 1px solid var(--border);
padding: 3px 5px;
text-align: center;
}
table.data th {
background: var(--header-blue);
font-weight: bold;
}
table.data td.rowlabel, table.data th.rowlabel { text-align: left; }
table.data tr.total-row td {
font-weight: bold;
background: #f2f2f2;
}
.group-header { background: var(--group-header) !important; }
table.data td.num, table.data th.num { text-align: right; }
.two-col-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 8px;
}
.two-col-table > tbody > tr > td {
vertical-align: top;
width: 50%;
padding: 0 10px 0 0;
border: none;
}
.two-col-table > tbody > tr > td:last-child { padding: 0 0 0 10px; }
.notes-box {
font-size: 9.5px;
border: 1px solid #aaa;
padding: 6px 10px;
background: #fafafa;
margin-top: 4px;
line-height: 1.5;
}
.summary-mini { margin-top: 6px; }
.summary-mini table { border-collapse: collapse; }
.summary-mini td, .summary-mini th {
border: 1px solid var(--border);
padding: 3px 8px;
font-size: 10.5px;
}
.chart-container { width: 100%; }
table.bar-chart {
width: 100%;
border-collapse: collapse;
font-size: 9.5px;
}
table.bar-chart td {
border: none;
padding: 2px 0;
vertical-align: middle;
}
table.bar-chart td.bar-label { width: 70px; text-align: left; white-space: nowrap; }
table.bar-chart td.bar-track { padding: 1px 0; }
table.bar-chart td.bar-track table {
width: 100%;
border-collapse: collapse;
border: 1px solid #ccc;
}
table.bar-chart td.bar-track table td {
height: 12px;
font-size: 1px;
line-height: 1px;
padding: 0;
border: none;
}
table.bar-chart td.bar-value { width: 80px; padding-left: 6px; text-align: left; white-space: nowrap; font-size: 9px; }
.legend { margin-top: 6px; font-size: 9.5px; }
.legend-swatch { width: 10px; height: 10px; display: inline-block; margin-right: 4px; vertical-align: middle; }
.legend span { margin-right: 12px; }
.footer-disclaimer {
font-size: 8.5px;
color: var(--muted);
border-top: 1px solid #ccc;
margin-top: 20px;
padding-top: 6px;
line-height: 1.4;
}
.small-note { font-size: 9.5px; margin: 2px 0 6px 0; }
.table-narrow { width: 60%; }
.table-tat { width: 40%; }
.page-break { page-break-before: always; }
.keep-together { page-break-inside: avoid; }
@media print {
body { background: #fff; padding: 0; }
.page { box-shadow: none; border: none; margin: 0 auto; }
}
<?php if (!empty($embed)): ?>
body { background: #fff; padding: 0; }
.page {
width: 100%;
max-width: none;
margin: 0;
padding: 10px 12px;
border: none;
}
.brand-logo { height: 64px; width: auto; max-width: 380px; object-fit: contain; object-position: left top; }
.addr-line { font-size: 8px; font-weight: bold; margin-top: 8px; line-height: 1.3; text-align: left; }
.report-title { margin: 4px 0 4px 0; font-size: 11px; }
.top-grid-table { margin-bottom: 6px; }
.top-grid-table > tbody > tr > td { padding: 0 10px 0 0; }
.top-grid-table > tbody > tr > td:last-child { padding: 0 0 0 10px; }
.policy-details td { font-size: 9px; padding: 0 4px 0 0; }
.toc ol { font-size: 9px; line-height: 1.35; padding-left: 16px; }
.two-col-table > tbody > tr > td { padding: 0 6px 0 0; }
.two-col-table > tbody > tr > td:last-child { padding: 0 0 0 6px; }
table.data { font-size: 8px; table-layout: fixed; }
table.data th, table.data td { padding: 2px 2px; word-wrap: break-word; }
h3.section-title { margin: 8px 0 3px 0; font-size: 10px; }
table.bar-chart { font-size: 8px; }
table.bar-chart td.bar-label { width: 55px; }
table.bar-chart td.bar-track table td { height: 10px; }
table.bar-chart td.bar-value { width: 70px; font-size: 7.5px; }
.table-narrow { width: 70%; }
.table-tat { width: 45%; }
.summary-mini table { font-size: 8.5px; }
.notes-box { font-size: 7.5px; page-break-inside: avoid; }
.footer-disclaimer { font-size: 7px; }
.page { padding: 8px 10px; }
table.section-wrap { page-break-inside: avoid; }
.intro-block, .section-block { page-break-inside: avoid; }
<?php endif; ?>
</style>
</head>
<body>
<div class="page">
<table class="section-wrap intro-block">
<tr><td>
<!-- HEADER: logo + address above blue line -->
<div class="header-top">
<div class="logo-block">
<img src="<?= esc($logoSrc) ?>" alt="Vidal Health" class="brand-logo">
</div>
<div class="addr-line">
<b>Corporate Office :</b> Tower-2, 1st floor, SJR I Park, Plot No: 13,14,15 , EPIP Zone, Whitefield, Bangalore-560066<br>
Phone: 91-80-40125678 &nbsp; Fax : 91-80-41159215 &nbsp; Email: care@vidalhealthtpa.com &nbsp; Website : www.vidalhealthtpa.com
</div>
</div>
<div class="report-title">Corporate Analysis Report</div>
<!-- POLICY DETAILS + TOC -->
<table class="top-grid-table">
<tr>
<td class="policy-details">
<div class="section-label">Policy Details:</div>
<table>
<tr><td class="label">Corporate Name:</td><td><?= esc($header['corporate_name'] ?? '') ?></td></tr>
<tr><td class="label">Insurer Policy Number:</td><td><?= esc($header['policy_number'] ?? '') ?></td></tr>
<tr><td class="label">Policy Start Date:</td><td><?= esc($header['start'] ?? '') ?></td></tr>
<tr><td class="label">Policy End Date:</td><td><?= esc($header['end'] ?? '') ?></td></tr>
<tr><td class="label">Total Premium:(in Rs.)</td><td><?= esc($num($header['premium'] ?? 0)) ?></td></tr>
<tr><td class="label">Earned Premium:(in Rs.)</td><td><?= esc($num($header['earned'] ?? 0)) ?></td></tr>
<tr><td class="label">Lives Covered:(in Nos.)</td><td><?= esc($num($header['lives'] ?? 0)) ?></td></tr>
<tr><td class="label">Report Generated By(Broker):</td><td><?= esc($header['generated_by'] ?? '') ?></td></tr>
<tr><td class="label">Report Generated Date:</td><td><?= esc($header['generated_at'] ?? ($meta['generated_at'] ?? '')) ?></td></tr>
</table>
</td>
<td class="toc">
<div class="section-label">Table of Contents</div>
<ol>
<?php foreach (($toc ?: []) as $item): ?>
<li><?= esc((string) $item) ?></li>
<?php endforeach; ?>
</ol>
</td>
</tr>
</table>
</td></tr>
</table>
<!-- 1. ICR -->
<table class="section-wrap section-block">
<tr><td>
<h3 class="section-title">1. Incurred Claims Ratio (ICR):</h3>
<table class="data">
<tr>
<th class="rowlabel">Claim Status</th>
<th colspan="2">Cashless</th>
<th colspan="2">Member</th>
<th colspan="2">Total</th>
</tr>
<tr>
<th></th><th>Nos.</th><th>Amt. (in Rs.)</th><th>Nos.</th><th>Amt. (in Rs.)</th><th>Nos.</th><th>Amt. (in Rs.)</th>
</tr>
<?php foreach (($icr['rows'] ?? []) as $row):
$isIncurred = str_contains((string) ($row['label'] ?? ''), 'Incurred');
$c = $cellCa($row['cashless'] ?? []);
$m = $cellCa($row['member'] ?? []);
$t = $cellCa($row['total'] ?? []);
?>
<tr<?= $isIncurred ? ' class="total-row"' : '' ?>>
<td class="rowlabel"><?= esc($row['label'] ?? '') ?></td>
<td><?= esc($c['count']) ?></td><td class="num"><?= esc($c['amount']) ?></td>
<td><?= esc($m['count']) ?></td><td class="num"><?= esc($m['amount']) ?></td>
<td><?= esc($t['count']) ?></td><td class="num"><?= esc($t['amount']) ?></td>
</tr>
<?php endforeach; ?>
</table>
<?php
$sum = $icr['summary'] ?? [];
$dr = $sum['disposal_rate'] ?? [];
$cpc = $sum['cpc'] ?? [];
?>
<div class="summary-mini">
<table>
<tr><td>ICR On EP*</td><td colspan="2"></td><td><b><?= esc($pct($sum['icr_on_ep'] ?? 0, 1)) ?></b></td></tr>
<tr><td>Incidence Rate</td><td colspan="2"></td><td><b><?= esc($pct($sum['incidence_rate'] ?? 0, 1)) ?></b></td></tr>
<tr>
<td>Disposal Rate</td>
<td><?= esc($pct($dr['cashless'] ?? 0)) ?></td>
<td><?= esc($pct($dr['member'] ?? 0)) ?></td>
<td><b><?= esc($pct($dr['total'] ?? 0)) ?></b></td>
</tr>
<tr>
<td>Cost per Claims(CPC)</td>
<td><?= esc($num($cpc['cashless'] ?? 0)) ?></td>
<td><?= esc($num($cpc['member'] ?? 0)) ?></td>
<td><b><?= esc($num($cpc['total'] ?? 0)) ?></b></td>
</tr>
</table>
</div>
</td></tr>
</table>
<!-- 2. Hospitalisation Type -->
<table class="section-wrap section-block">
<tr><td>
<h3 class="section-title">2. Hospitalisation Type Details:</h3>
<table class="data">
<tr>
<th class="rowlabel">Claim Subtype</th>
<th colspan="2">Cashless</th>
<th colspan="2">Member</th>
</tr>
<tr><th></th><th>Nos.</th><th>Amt. (in Rs.)</th><th>Nos.</th><th>Amt. (in Rs.)</th></tr>
<?php foreach (($hosp['rows'] ?? []) as $row):
$c = $cellCa($row['cashless'] ?? []);
$m = $cellCa($row['member'] ?? []);
?>
<tr>
<td class="rowlabel"><?= esc($row['label'] ?? '') ?></td>
<td><?= esc($c['count']) ?></td><td class="num"><?= esc($c['amount']) ?></td>
<td><?= esc($m['count']) ?></td><td class="num"><?= esc($m['amount']) ?></td>
</tr>
<?php endforeach;
$ht = $hosp['total'] ?? [];
$hc = $cellCa($ht['cashless'] ?? []);
$hm = $cellCa($ht['member'] ?? []);
?>
<tr class="total-row">
<td class="rowlabel">Total</td>
<td><?= esc($hc['count']) ?></td><td class="num"><?= esc($hc['amount']) ?></td>
<td><?= esc($hm['count']) ?></td><td class="num"><?= esc($hm['amount']) ?></td>
</tr>
</table>
<div class="small-note">*Considering Only Settled ,Approved and UTR Awaiting (Cheque Pending)</div>
<div class="notes-box">
<b>Notes:</b><br>
ICR = (Settled Amt + Outstanding Amt) / Annual Premium<br>
ICR on EP* = (Settled Amt + Outstanding Amt) / Earned Premium<br>
Earned Premium = Prorated premium as on report generated date<br>
Cost Per Claim(CPC) = Approved Amt / Number of Events(Main Claims) for IPD + Daycare Cases<br>
Incidents Rate = No of Claim Events/ Lives<br>
Disposal Rate = (Settled+Rejected+Awaiting UTR+Cancelled / Claims Reported)<br>
* EP- Earned Premium ; O/S - Outstanding<br>
* Event = Main Claims Only (Excluding Prepost and Addendum)
</div>
</td></tr>
</table>
<!-- 3. Member Details - Relationship & Gender -->
<table class="section-wrap section-block">
<tr><td>
<h3 class="section-title">3. Member Details - Relationship &amp; Gender wise :</h3>
<table class="two-col-table">
<tr>
<td class="tbl-side">
<table class="data">
<tr><th class="rowlabel">Relation</th><th>Male</th><th>Female</th><th>Total</th><th>%</th></tr>
<?php foreach (($memberGender['rows'] ?? []) as $row): ?>
<tr>
<td class="rowlabel"><?= esc($row['relation'] ?? '') ?></td>
<td><?= esc($num($row['male'] ?? 0)) ?></td>
<td><?= esc($num($row['female'] ?? 0)) ?></td>
<td><?= esc($num($row['total'] ?? 0)) ?></td>
<td><?= esc($pct2($row['pct'] ?? 0)) ?></td>
</tr>
<?php endforeach;
$mgt = $memberGender['total'] ?? [];
$mgp = $memberGender['pct_gender'] ?? [];
?>
<tr class="total-row">
<td class="rowlabel">Total</td>
<td><?= esc($num($mgt['male'] ?? 0)) ?></td>
<td><?= esc($num($mgt['female'] ?? 0)) ?></td>
<td><?= esc($num($mgt['total'] ?? 0)) ?></td>
<td><?= esc($pct2(100)) ?></td>
</tr>
<tr>
<td class="rowlabel">%</td>
<td><?= esc($pct($mgp['male'] ?? 0)) ?></td>
<td><?= esc($pct($mgp['female'] ?? 0)) ?></td>
<td><?= esc($pct(100)) ?></td>
<td></td>
</tr>
</table>
</td>
<td class="chart-side">
<div class="chart-container">
<table class="bar-chart">
<?php foreach ($chartGender as $g):
$rel = (string) ($g['relation'] ?? '');
$male = (int) ($g['male'] ?? 0);
$female = (int) ($g['female'] ?? 0);
$total = $male + $female;
$w = ($total / $genderMax) * 100;
$color = ($female > $male) ? '#ed7d31' : '#4472c4';
?>
<tr>
<td class="bar-label"><?= esc($rel) ?></td>
<td class="bar-track"><?php $renderBar($w, $color); ?></td>
<td class="bar-value"><?= esc($num($total)) ?></td>
</tr>
<?php endforeach; ?>
</table>
<div class="legend">
<span><span class="legend-swatch" style="background:#4472c4;"></span>Male</span>
<span><span class="legend-swatch" style="background:#ed7d31;"></span>Female</span>
</div>
</div>
</td>
</tr>
</table>
</td></tr>
</table>
<!-- 4. Member Details Age Band -->
<table class="section-wrap section-block">
<tr><td>
<h3 class="section-title">4. Member Details - Age Band &amp; Relationship wise :</h3>
<table class="two-col-table">
<tr>
<td class="tbl-side">
<table class="data">
<tr>
<th class="rowlabel">AgeBand</th>
<?php foreach ($ageRelations as $rel): ?><th><?= esc($rel) ?></th><?php endforeach; ?>
<th>Total</th><th>%</th>
</tr>
<?php foreach (($memberAge['rows'] ?? []) as $row): ?>
<tr>
<td class="rowlabel"><?= esc($row['band'] ?? '') ?></td>
<?php foreach ($ageRelations as $rel): ?>
<td><?= esc($num($row[$rel] ?? 0)) ?></td>
<?php endforeach; ?>
<td><?= esc($num($row['total'] ?? 0)) ?></td>
<td><?= esc($pct2($row['pct'] ?? 0)) ?></td>
</tr>
<?php endforeach;
$mat = $memberAge['total'] ?? [];
$mac = $memberAge['col_pct'] ?? [];
?>
<tr class="total-row">
<td class="rowlabel">Total</td>
<?php foreach ($ageRelations as $rel): ?>
<td><?= esc($num($mat[$rel] ?? 0)) ?></td>
<?php endforeach; ?>
<td><?= esc($num($mat['_total'] ?? 0)) ?></td>
<td><?= esc($pct2(100)) ?></td>
</tr>
<tr>
<td class="rowlabel">%</td>
<?php foreach ($ageRelations as $rel): ?>
<td><?= esc($pct($mac[$rel] ?? 0)) ?></td>
<?php endforeach; ?>
<td><?= esc($pct(100)) ?></td>
<td></td>
</tr>
</table>
</td>
<td class="chart-side">
<div class="chart-container">
<table class="bar-chart">
<?php foreach ($chartAge as $a):
$total = (int) ($a['total'] ?? 0);
$w = ($total / $ageMax) * 100;
$dom = (string) ($a['dominant'] ?? '');
$color = $ageBarColor($dom);
$label = $num($total) . ($dom !== '' ? ' (' . $dom . ')' : '');
?>
<tr>
<td class="bar-label"><?= esc($a['band'] ?? '') ?></td>
<td class="bar-track"><?php $renderBar($w, $color); ?></td>
<td class="bar-value"><?= esc($label) ?></td>
</tr>
<?php endforeach; ?>
</table>
<div class="legend">
<span><span class="legend-swatch" style="background:#264478;"></span>Self</span>
<span><span class="legend-swatch" style="background:#a5a5a5;"></span>Spouse</span>
<span><span class="legend-swatch" style="background:#ffc000;"></span>Parents</span>
<span><span class="legend-swatch" style="background:#4472c4;"></span>Child</span>
<span><span class="legend-swatch" style="background:#70ad47;"></span>In Law/Other</span>
</div>
</div>
</td>
</tr>
</table>
</td></tr>
</table>
<!-- 5. Claims Approved Age Band -->
<h3 class="section-title">5. Claims Approved - Age Band &amp; Relationship wise :</h3>
<table class="data">
<tr>
<th class="rowlabel">Age Band</th>
<?php foreach ($ageRelations as $rel): ?><th colspan="2"><?= esc($rel) ?></th><?php endforeach; ?>
<th colspan="2">Total</th><th>Total %</th>
</tr>
<tr>
<th></th>
<?php foreach ($ageRelations as $_): ?><th>No.</th><th>Amt.(Rs.)</th><?php endforeach; ?>
<th>No.</th><th>Amt.(Rs.)</th><th>No.%/Amt.%</th>
</tr>
<?php foreach (($claimsAge['rows'] ?? []) as $row):
$renderRelationClaimRow((string) ($row['band'] ?? ''), $row, $ageRelations);
endforeach;
$cat = $claimsAge['total'] ?? [];
$totalRow = $cat;
$totalRow['total'] = $cat['_total'] ?? [];
$totalRow['pct'] = ['count' => 100, 'amount' => 100];
$renderRelationClaimRow('Total', $totalRow, $ageRelations, true);
$renderRelationPctRow($claimsAge['col_pct'] ?? [], $ageRelations);
?>
</table>
<div class="small-note">* Count is only for Approved Claims(Settled and Awaiting UTR(Cheque Pending)) .</div>
<!-- 6. Claims Approved Amount Band -->
<h3 class="section-title">6. Claims Approved - Amount Band &amp; Relationship wise :</h3>
<table class="data">
<tr>
<th class="rowlabel">Amount Band</th>
<?php foreach ($ageRelations as $rel): ?><th colspan="2"><?= esc($rel) ?></th><?php endforeach; ?>
<th colspan="2">Total</th><th>Total %</th>
</tr>
<tr>
<th></th>
<?php foreach ($ageRelations as $_): ?><th>No.</th><th>Amt.(Rs.)</th><?php endforeach; ?>
<th>No.</th><th>Amt.(Rs.)</th><th>No.%/Amt.%</th>
</tr>
<?php foreach (($claimsAmount['rows'] ?? []) as $row):
$renderRelationClaimRow((string) ($row['band'] ?? ''), $row, $ageRelations);
endforeach;
$amtT = $claimsAmount['total'] ?? [];
$amtTotalRow = $amtT;
$amtTotalRow['total'] = $amtT['_total'] ?? [];
$amtTotalRow['pct'] = ['count' => 100, 'amount' => 100];
$renderRelationClaimRow('Total', $amtTotalRow, $ageRelations, true);
$renderRelationPctRow($claimsAmount['col_pct'] ?? [], $ageRelations);
?>
</table>
<div class="small-note">* Count is only for Approved Claims(Settled and Awaiting UTR (Cheque Pending)). &nbsp; * Banding for Incurred Amount</div>
<!-- 7. Ailment wise -->
<h3 class="section-title">7. Claims Approved - Top 15 Ailment wise :</h3>
<table class="data">
<tr>
<th class="rowlabel">Ailment Group</th>
<?php foreach ($ageRelations as $rel): ?><th colspan="2"><?= esc($rel) ?></th><?php endforeach; ?>
<th colspan="2">Total</th><th>Total %</th>
</tr>
<tr>
<th></th>
<?php foreach ($ageRelations as $_): ?><th>No.</th><th>Amt.(Rs.)</th><?php endforeach; ?>
<th>No.</th><th>Amt.(Rs.)</th><th>No.%/Amt.%</th>
</tr>
<?php foreach (($ailments['rows'] ?? []) as $row):
$renderRelationClaimRow((string) ($row['ailment'] ?? ''), $row, $ageRelations);
endforeach;
$ailT = $ailments['total'] ?? [];
$ailTotalRow = $ailT;
$ailTotalRow['total'] = $ailT['_total'] ?? [];
$ailTotalRow['pct'] = ['count' => 100, 'amount' => 100];
$renderRelationClaimRow('Total', $ailTotalRow, $ageRelations, true);
$renderRelationPctRow($ailments['col_pct'] ?? [], $ageRelations);
?>
</table>
<div class="small-note">* Count is only for Approved Claims(Settled and Awaiting UTR (Cheque Pending)) .</div>
<!-- 8. Top 15 Hospitals -->
<h3 class="section-title">8. Top 15 Cashless Hospital wise utilization:</h3>
<table class="data">
<tr>
<th class="rowlabel">Hospital_ID</th>
<th class="rowlabel">Hospital_Name</th>
<th>No of Claims</th>
<th>Amount</th>
</tr>
<?php if (($hospitals ?? []) === []): ?>
<tr><td class="rowlabel" colspan="4">No cashless hospital utilization</td></tr>
<?php else: foreach ($hospitals as $h): ?>
<tr>
<td class="rowlabel"><?= esc($h['hospital_id'] ?? '') ?></td>
<td class="rowlabel"><?= esc($h['hospital_name'] ?? '') ?></td>
<td><?= esc($num($h['count'] ?? 0)) ?></td>
<td class="num"><?= esc($num($h['amount'] ?? 0)) ?></td>
</tr>
<?php endforeach; endif; ?>
</table>
<!-- 9. Cashless & Member Summary -->
<h3 class="section-title">9. Claims Approved - Cashless &amp; Member Summary:</h3>
<table class="data table-narrow">
<tr>
<th class="rowlabel">Type of claim</th>
<th>Events</th><th>Events%</th><th>Amount</th><th>Amount%</th>
</tr>
<?php foreach (($cmSummary['rows'] ?? []) as $row): ?>
<tr>
<td class="rowlabel"><?= esc($row['label'] ?? '') ?></td>
<td><?= esc($num($row['count'] ?? 0)) ?></td>
<td><?= esc($pct2($row['count_pct'] ?? 0)) ?></td>
<td class="num"><?= esc($num($row['amount'] ?? 0)) ?></td>
<td><?= esc($pct2($row['amount_pct'] ?? 0)) ?></td>
</tr>
<?php endforeach;
$cmt = $cmSummary['total'] ?? [];
?>
<tr class="total-row">
<td class="rowlabel">TOTAL</td>
<td><?= esc($num($cmt['count'] ?? 0)) ?></td>
<td><?= esc($pct2(100)) ?></td>
<td class="num"><?= esc($num($cmt['amount'] ?? 0)) ?></td>
<td><?= esc($pct2(100)) ?></td>
</tr>
</table>
<!-- 10. TAT -->
<h3 class="section-title">10. Turn Around Time (TAT) :</h3>
<div class="small-note"><b>Claim Process TAT :</b></div>
<table class="data table-tat">
<tr><th class="rowlabel">TAT Band</th><th>Nos.</th><th>%</th></tr>
<?php foreach (($tat['rows'] ?? []) as $row): ?>
<tr>
<td class="rowlabel"><?= esc($row['band'] ?? '') ?></td>
<td><?= esc($num($row['count'] ?? 0)) ?></td>
<td><?= esc($pct2($row['pct'] ?? 0)) ?></td>
</tr>
<?php endforeach; ?>
<tr class="total-row">
<td class="rowlabel">Total</td>
<td><?= esc($num($tat['total'] ?? 0)) ?></td>
<td><?= esc($pct2(100)) ?></td>
</tr>
</table>
<div class="small-note">Note: Only Settled,Awaiting UTR, Approved and Rejected claims are considered<br>* LDR to Decision date<br>* only for Member claims</div>
<!-- 11. Month on Month -->
<h3 class="section-title">11. Month on Month</h3>
<table class="data">
<tr>
<th class="rowlabel">Admission Month</th>
<th colspan="2">Hospitalization and Daycare</th>
<th colspan="2">Other than Hospitalization</th>
<th colspan="2">Total</th>
</tr>
<tr>
<th></th>
<th>Inc Count</th><th>Inc Amount</th>
<th>Inc Count</th><th>Inc Amount</th>
<th>Inc Count</th><th>Inc Amount</th>
</tr>
<?php foreach (($mom['rows'] ?? []) as $row):
$h = $cellCa($row['hosp'] ?? []);
$o = $cellCa($row['other'] ?? []);
$t = $cellCa($row['total'] ?? []);
?>
<tr>
<td class="rowlabel"><?= esc($row['month'] ?? '') ?></td>
<td><?= esc($h['count']) ?></td><td class="num"><?= esc($h['amount']) ?></td>
<td><?= esc($o['count']) ?></td><td class="num"><?= esc($o['amount']) ?></td>
<td><?= esc($t['count']) ?></td><td class="num"><?= esc($t['amount']) ?></td>
</tr>
<?php endforeach;
$mt = $mom['total'] ?? [];
$mh = $cellCa($mt['hosp'] ?? []);
$mo = $cellCa($mt['other'] ?? []);
$mtt = $cellCa($mt['total'] ?? []);
?>
<tr class="total-row">
<td class="rowlabel">TOTAL</td>
<td><?= esc($mh['count']) ?></td><td class="num"><?= esc($mh['amount']) ?></td>
<td><?= esc($mo['count']) ?></td><td class="num"><?= esc($mo['amount']) ?></td>
<td><?= esc($mtt['count']) ?></td><td class="num"><?= esc($mtt['amount']) ?></td>
</tr>
</table>
<!-- 12. Payout Ratio -->
<h3 class="section-title">12. Payout Ratio</h3>
<table class="data table-narrow">
<tr><th>Claimed Amount</th><th>Settled Amount</th><th>Payout %</th></tr>
<tr>
<td class="num"><?= esc($num($payout['claimed'] ?? 0)) ?></td>
<td class="num"><?= esc($num($payout['settled'] ?? 0)) ?></td>
<td><?= esc($pct($payout['payout_pct'] ?? 0)) ?></td>
</tr>
</table>
<!-- Footer -->
<div class="footer-disclaimer">
<b>DISCLAIMER:</b> Confidential information, not intended for public dissemination. Vidal Health Insurance TPA Pvt ltd makes no representations or warranties, express, implied or otherwise, regarding the accuracy and
completeness of the information, and shall have no liability resulting from the use of the information. The Receiving Party will use information received in a safe and prudent manner and is responsible for all risk or loss arising
out of its use of such information. Data will be refreshed on every day night. Report is based on previous Day Data.
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,31 @@
<?php
/**
* Shared helpers for ABHI MIS HTML + PDF embed mode.
* Expects: $vm, $fmt, $embed, $charts
*/
$charts = $charts ?? [];
$embed = !empty($embed);
$chartHtml = static function (string $key, string $canvasId) use ($embed, $charts): string {
if ($embed && !empty($charts[$key]) && is_file((string) $charts[$key])) {
return '<img class="chart-img" src="' . htmlspecialchars((string) $charts[$key], ENT_QUOTES) . '" alt="">';
}
return '<canvas id="' . htmlspecialchars($canvasId, ENT_QUOTES) . '"></canvas>';
};
$num = static function ($n, int $d = 0) use ($fmt): string {
return esc($fmt($n, $d));
};
$dash = static function ($v) use ($fmt): string {
if ($v === '' || $v === null) {
return '—';
}
if (is_numeric($v)) {
return esc($fmt($v));
}
return esc((string) $v);
};

View File

@ -0,0 +1,550 @@
<script>
(function () {
const MIS_DATA = <?= json_encode($vm ?? [], JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP) ?>;
const embedMode = <?= !empty($embed) ? 'true' : 'false' ?>;
const COLORS = {
blue: '#2E86DE',
navy: '#1B3F8C',
orange: '#E8862C',
purple: '#6A3D9A',
red: '#C8102E',
female: '#1A3399',
male: '#6A3D9A'
};
function fmt(n) {
if (n === '' || n === null || n === undefined) return '';
return Number(n).toLocaleString('en-IN');
}
function dash(v) { return (v === 0 || v === '0') ? '—' : v; }
function el(id) { return document.getElementById(id); }
function setHtml(id, html) { const node = el(id); if (node) node.innerHTML = html; }
function setText(id, text) { const node = el(id); if (node) node.textContent = text; }
const claimStatus = MIS_DATA.claimStatus || [];
setHtml('tblClaimStatus', claimStatus.map(r =>
`<tr><td>${r.status}</td><td class="num">${r.number}</td><td class="num">${fmt(r.reported)}</td><td class="num">${fmt(r.incurred)}</td></tr>`
).join(''));
const policyPremium = MIS_DATA.policyPremium || [];
setHtml('tblPolicyPremium', policyPremium.map(r =>
`<tr><td>${r.sr}</td><td>${r.label}</td><td class="num">${fmt(r.amount)}</td></tr>`
).join('') +
`<tr style="font-weight:700;background:#F0F0F3;border-top:2px solid var(--red)"><td colspan="2">Total</td><td class="num">${fmt(MIS_DATA.policyPremiumTotal || 0)}</td></tr>`);
const reimbTAT = MIS_DATA.reimbTAT || [];
setHtml('tblReimbTAT', reimbTAT.map(r =>
`<tr><td>${r.range}</td><td class="num">${r.count}</td><td class="num">${r.pct}</td></tr>`
).join('') +
`<tr style="font-weight:700;background:#F0F0F3;border-top:2px solid var(--red)"><td>Total</td><td class="num">${MIS_DATA.reimbTATTotal?.count || 0}</td><td class="num">${MIS_DATA.reimbTATTotal?.pct || 0}</td></tr>`);
const gender = MIS_DATA.gender || [];
const genderOrder = ['Male', 'Female'];
setHtml('tblGender', genderOrder.map(label => {
const row = gender.find(g => g.label === label) || { label, count: 0, amount: 0 };
return `<tr><td>${row.label}</td><td class="num">${row.count}</td><td class="num">${fmt(row.amount)}</td></tr>`;
}).join(''));
setText('tblGenderCountTotal', MIS_DATA.genderTotal?.count || 0);
setText('tblGenderAmtTotal', fmt(MIS_DATA.genderTotal?.incurred_amount || 0));
const claimType = MIS_DATA.claimType || [];
setHtml('tblClaimType', claimType.map(r =>
`<tr ${r.header ? 'class="subhead"' : ''}><td>${r.header ? '' : '&nbsp;&nbsp;&nbsp;'}${r.type}</td><td class="num">${r.count}</td><td class="num">${r.countPct}</td><td class="num">${fmt(r.amount)}</td><td class="num">${r.amtPct}</td><td class="num">${fmt(r.incurred)}</td><td class="num">${r.incPct}</td></tr>`
).join(''));
const ct = MIS_DATA.claimTypeTotal || {};
setText('tblClaimTypeCount', ct.count || 0);
setText('tblClaimTypeAmt', fmt(ct.amount || 0));
setText('tblClaimTypeInc', fmt(ct.incurred || 0));
const relation = MIS_DATA.relation || [];
setHtml('tblRelation', relation.map(r =>
`<tr><td>${r.label}</td><td class="num">${r.count}</td><td class="num">${r.cpct}</td><td class="num">${fmt(r.amt)}</td><td class="num">${r.apct}</td></tr>`
).join(''));
setText('tblRelationCount', MIS_DATA.relationTotal?.count || 0);
setText('tblRelationAmt', fmt(MIS_DATA.relationTotal?.incurred_amount || 0));
const memberType = MIS_DATA.memberType || [];
setHtml('tblMemberType', memberType.map(r =>
`<tr><td>${r.label}</td><td class="num">${r.count}</td><td class="num">${r.cpct}</td><td class="num">${fmt(r.amt)}</td><td class="num">${r.apct}</td></tr>`
).join(''));
setText('tblMemberCount', MIS_DATA.memberTypeTotal?.count || 0);
setText('tblMemberAmt', fmt(MIS_DATA.memberTypeTotal?.incurred_amount || 0));
const age = MIS_DATA.age || [];
setHtml('tblAge', age.map(r =>
`<tr><td>${r.band}</td><td class="num">${r.count}</td><td class="num">${r.cpct}</td><td class="num">${fmt(r.amt)}</td><td class="num">${r.apct}</td></tr>`
).join(''));
setText('tblAgeCount', MIS_DATA.ageTotal?.count || 0);
setText('tblAgeAmt', fmt(MIS_DATA.ageTotal?.incurred_amount || 0));
const diagnosis = MIS_DATA.diagnosis || [];
setHtml('tblDiagnosis', diagnosis.map(r =>
`<tr><td>${r.name}</td><td class="num">${r.count}</td><td class="num">${r.cpct}</td><td class="num">${fmt(r.amt)}</td><td class="num">${r.apct}</td><td class="num">${r.acs ? fmt(r.acs) : ''}</td><td class="num">${r.lr}</td></tr>`
).join(''));
const dt = MIS_DATA.diagnosisTotal || {};
setText('tblDiagCount', dt.count || 0);
setText('tblDiagAmt', fmt(dt.amt || 0));
setText('tblDiagAcs', fmt(dt.acs || 0));
setText('tblDiagLr', dt.lr || 0);
function renderUtil(id, data, prefix) {
const rows = data?.rows || [];
setHtml(id, rows.map(r =>
`<tr><td>${r.claims}</td><td class="num">${r.beneficiaries}</td><td class="num">${r.incurredCount}</td><td class="num">${r.countPct || dash(0)}</td><td class="num">${fmt(r.amount)}</td><td class="num">${r.amountPct || dash(0)}</td></tr>`
).join(''));
const t = data?.total || {};
setText(prefix + 'Ben', t.beneficiaries || 0);
setText(prefix + 'Cnt', t.incurredCount || 0);
setText(prefix + 'Amt', fmt(t.amount || 0));
}
renderUtil('tblUtilEmp', MIS_DATA.utilizationEmployees, 'tblUtilEmp');
renderUtil('tblUtilDep', MIS_DATA.utilizationDependents, 'tblUtilDep');
const hospitals = MIS_DATA.hospitals || [];
setHtml('tblHospital', hospitals.map(r =>
`<tr><td>${r.name}</td><td class="num">${r.count}</td><td class="num">${r.cpct}</td><td class="num">${fmt(r.amt)}</td><td class="num">${r.apct}</td></tr>`
).join(''));
const ht = MIS_DATA.hospitalTotal || {};
setText('tblHospitalCount', ht.count || 0);
setText('tblHospitalAmt', fmt(ht.incurred_amount || 0));
const cities = MIS_DATA.cities || [];
setHtml('tblCity', cities.map(r =>
`<tr><td>${r.city}</td><td class="num">${r.count}</td><td class="num">${fmt(r.amt)}</td><td class="num">${r.paidCount}</td><td class="num">${r.paidAmt !== '' && r.paidAmt !== null ? Number(r.paidAmt).toFixed(2) : ''}</td><td class="num">${r.outCount}</td><td class="num">${r.outAmt !== '' && r.outAmt !== null ? fmt(r.outAmt) : ''}</td><td class="num">${r.acs !== '' && r.acs !== null ? fmt(r.acs) : ''}</td></tr>`
).join(''));
const ct2 = MIS_DATA.cityTotals || {};
setText('tblCityCount', ct2.count || 0);
setText('tblCityAmt', fmt(ct2.amt || 0));
setText('tblCityPaidCnt', ct2.paidCount || 0);
setText('tblCityPaidAmt', Number(ct2.paidAmt || 0).toFixed(2));
setText('tblCityOutCnt', ct2.outCount || 0);
setText('tblCityOutAmt', fmt(ct2.outAmt || 0));
setText('tblCityAcs', fmt(ct2.acs || 0));
if (embedMode || typeof Chart === 'undefined') {
return;
}
Chart.defaults.font.family = 'Segoe UI, Arial, sans-serif';
Chart.defaults.font.size = 11;
Chart.defaults.color = '#333';
const statusColors = [COLORS.blue, COLORS.navy, COLORS.orange, COLORS.purple];
const statusLabels = ['Reported', 'Settled', 'Outstanding', 'Rejected'];
const amountData = claimStatus.map(r => r.reported || 0);
const countData = claimStatus.map(r => r.number || 0);
/* Nested donut: outer = Claim Amount, inner = Claim Count */
const donutLeaderPlugin = {
id: 'donutLeaderPlugin',
afterDatasetsDraw(chart) {
const { ctx } = chart;
const outerMeta = chart.getDatasetMeta(0);
const innerMeta = chart.getDatasetMeta(1);
if (!outerMeta || !innerMeta) return;
outerMeta.data.forEach((arc, i) => {
const val = amountData[i];
if (!val) return;
const pos = arc.tooltipPosition();
const angle = (arc.startAngle + arc.endAngle) / 2;
const r = arc.outerRadius + 18;
const x = arc.x + Math.cos(angle) * r;
const y = arc.y + Math.sin(angle) * r;
const lx = arc.x + Math.cos(angle) * (arc.outerRadius + 4);
const ly = arc.y + Math.sin(angle) * (arc.outerRadius + 4);
ctx.save();
ctx.strokeStyle = '#999';
ctx.lineWidth = 0.8;
ctx.beginPath();
ctx.moveTo(lx, ly);
ctx.lineTo(x, y);
ctx.stroke();
ctx.font = 'bold 11px Segoe UI, Arial';
ctx.fillStyle = '#222';
ctx.textAlign = x < arc.x ? 'right' : 'left';
ctx.fillText(fmt(val), x + (x < arc.x ? -4 : 4), y + 4);
ctx.restore();
});
innerMeta.data.forEach((arc, i) => {
const val = countData[i];
if (!val) return;
const pos = arc.tooltipPosition();
ctx.save();
ctx.font = 'bold 12px Segoe UI, Arial';
ctx.fillStyle = '#fff';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(String(val), pos.x, pos.y);
ctx.restore();
});
}
};
new Chart(el('chartDonut1'), {
type: 'doughnut',
data: {
labels: statusLabels,
datasets: [
{
label: 'Claim Amount',
data: amountData,
backgroundColor: statusColors,
borderWidth: 2,
borderColor: '#fff',
weight: 1.1
},
{
label: 'Claim Count',
data: countData,
backgroundColor: statusColors,
borderWidth: 2,
borderColor: '#fff',
weight: 0.55
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
cutout: '42%',
plugins: {
legend: { display: false },
tooltip: {
callbacks: {
label: (c) => c.dataset.label + ': ' + (c.datasetIndex === 1 ? c.raw : fmt(c.raw))
}
}
}
},
plugins: [donutLeaderPlugin]
});
/* Pre-Auth TAT — column chart with % labels above bars */
const preAuth = MIS_DATA.preAuthTAT || { within: 100, above: 0 };
const preAuthValues = [preAuth.within, preAuth.above];
function maxInDatasets(datasets) {
let max = 0;
(datasets || []).forEach(ds => {
(ds.data || []).forEach(v => { max = Math.max(max, Number(v) || 0); });
});
return max;
}
const barTopLabelsPlugin = {
id: 'barTopLabels',
afterDatasetsDraw(chart) {
const cfg = chart.options.plugins?.barTopLabels || {};
if (cfg.enabled === false || chart.options.indexAxis === 'y') return;
const formatType = cfg.format || 'number';
const formatters = {
percent: v => Number(v).toFixed(2) + '%',
count: v => String(Math.round(Number(v))),
amount: v => fmt(v),
number: v => fmt(v)
};
const labelFn = formatters[formatType] || formatters.number;
const hideZero = cfg.hideZero !== false;
const { ctx } = chart;
const dsCount = chart.data.datasets.length;
chart.data.datasets.forEach((ds, di) => {
chart.getDatasetMeta(di).data.forEach((bar, i) => {
const val = Number(ds.data[i]);
if (hideZero && (!val || val === 0)) return;
const pos = bar.tooltipPosition();
const offsetX = dsCount > 1 ? (di - (dsCount - 1) / 2) * 14 : 0;
ctx.save();
ctx.font = 'bold 10px Segoe UI, Arial';
ctx.fillStyle = '#222';
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
ctx.fillText(labelFn(val), pos.x + offsetX, pos.y - 5);
ctx.restore();
});
});
}
};
const chartLayout = { padding: { top: 28, left: 4, right: 4 } };
const hBarValuePlugin = {
id: 'hBarValuePlugin',
afterDatasetsDraw(chart) {
const cfg = chart.options.plugins?.hBarLabels || {};
if (cfg.enabled === false || chart.options.indexAxis !== 'y') return;
const formatType = cfg.format || 'amount';
const formatters = {
count: v => String(Math.round(Number(v))),
amount: v => fmt(v)
};
const labelFn = formatters[formatType] || formatters.amount;
const { ctx } = chart;
chart.getDatasetMeta(0).data.forEach((bar, i) => {
const val = chart.data.datasets[0].data[i];
if (!val && val !== 0) return;
const props = bar.getProps(['x', 'y', 'base', 'width'], true);
const barLeft = Math.min(props.x, props.base);
const barRight = Math.max(props.x, props.base);
const barW = Math.abs(barRight - barLeft);
const midX = barLeft + barW / 2;
const text = labelFn(val);
ctx.save();
ctx.font = 'bold 11px Segoe UI, Arial';
const tw = ctx.measureText(text).width + 14;
const th = 20;
const rx = midX - tw / 2;
const ry = props.y - th / 2;
ctx.fillStyle = 'rgba(255,255,255,0.92)';
ctx.strokeStyle = '#d1d5db';
ctx.lineWidth = 0.5;
ctx.fillRect(rx, ry, tw, th);
ctx.strokeRect(rx, ry, tw, th);
ctx.fillStyle = '#222';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(text, midX, props.y);
ctx.restore();
});
}
};
Chart.register(barTopLabelsPlugin, hBarValuePlugin);
new Chart(el('chartPreAuth'), {
type: 'bar',
data: {
labels: ['With in 2 Hours %', 'Above 2 Hours %'],
datasets: [{ data: preAuthValues, backgroundColor: [COLORS.blue, COLORS.navy], barPercentage: 0.45, categoryPercentage: 0.7 }]
},
options: {
responsive: true,
maintainAspectRatio: false,
layout: chartLayout,
plugins: { legend: { display: false }, barTopLabels: { format: 'percent', hideZero: false } },
scales: {
x: { grid: { display: false }, border: { display: false } },
y: { display: false, suggestedMax: 115 }
}
}
});
/* Premium / Earn Premium / Claim Incurred — horizontal bars with value pills */
const premiumBars = MIS_DATA.premiumBars || [0, 0, 0];
const premiumLabels = ['Premium', 'Earn Premium', 'Claim Incurred Amount'];
new Chart(el('chartPremiumBars'), {
type: 'bar',
data: {
labels: premiumLabels,
datasets: [{ data: premiumBars, backgroundColor: [COLORS.navy, COLORS.blue, COLORS.orange], barThickness: 28 }]
},
options: {
indexAxis: 'y',
responsive: true,
maintainAspectRatio: false,
layout: { padding: { top: 8, right: 16, bottom: 8, left: 8 } },
plugins: { legend: { display: false }, barTopLabels: { enabled: false }, hBarLabels: { format: 'amount' } },
scales: {
x: { display: false, grid: { display: false }, suggestedMax: Math.max(...premiumBars, 1) * 1.15 },
y: { grid: { display: false }, border: { display: false }, ticks: { font: { weight: 'bold', size: 12 } } }
}
}
});
/* Gender — horizontal bar charts (Male purple, Female navy) */
const genderSorted = genderOrder.map(label => {
const found = gender.find(g => g.label === label);
return found || { label, count: 0, amount: 0 };
});
function genderBar(canvasId, valueKey) {
const labels = genderSorted.map(g => g.label);
const values = genderSorted.map(g => g[valueKey] || 0);
const colors = labels.map(l => l.toLowerCase() === 'female' ? COLORS.female : COLORS.male);
const maxVal = Math.max(...values, 1);
new Chart(el(canvasId), {
type: 'bar',
data: { labels, datasets: [{ data: values, backgroundColor: colors, barThickness: 32 }] },
options: {
indexAxis: 'y',
responsive: true,
maintainAspectRatio: false,
layout: { padding: { top: 10, right: 20, bottom: 10, left: 4 } },
plugins: {
legend: { display: false },
barTopLabels: { enabled: false },
hBarLabels: { format: valueKey === 'count' ? 'count' : 'amount' }
},
scales: {
x: { display: false, grid: { display: false }, suggestedMax: maxVal * 1.2 },
y: { grid: { display: false }, border: { display: false }, ticks: { font: { weight: 'bold', size: 12 } } }
}
}
});
}
genderBar('chartGenderCount', 'count');
genderBar('chartGenderAmt', 'amount');
const pc = MIS_DATA.paidCharts || {};
const cbt = pc.countByType || {};
const paidCountData = pc.paidCount || [0, 0];
const paidAmtData = pc.paidAmt || [0, 0];
const acsData = pc.acs || [0, 0];
const amtStatusData = pc.amtByStatus || [0, 0, 0];
const countStatusCashless = [cbt.Settled?.Cashless || 0, cbt.Outstanding?.Cashless || 0, cbt.Rejected?.Cashless || 0];
const countStatusReimb = [cbt.Settled?.Reimbursement || 0, cbt.Outstanding?.Reimbursement || 0, cbt.Rejected?.Reimbursement || 0];
/* Claim Paid Count By Claim Type */
new Chart(el('chartPaidCount'), {
type: 'bar',
data: {
labels: ['Cashless', 'Reimbursement'],
datasets: [{ data: paidCountData, backgroundColor: [COLORS.blue, COLORS.navy], barPercentage: 0.5 }]
},
options: {
responsive: true,
maintainAspectRatio: false,
layout: chartLayout,
plugins: { legend: { display: false }, barTopLabels: { format: 'count' } },
scales: {
y: {
beginAtZero: true,
ticks: { stepSize: 1, precision: 0 },
suggestedMax: Math.max(1, maxInDatasets([{ data: paidCountData }]) * 1.25)
}
}
}
});
/* Claim Paid Amount and ACS */
const paidAmtDatasets = [
{ label: 'Claim Paid Amount', data: paidAmtData, backgroundColor: COLORS.blue },
{ label: 'ACS', data: acsData, backgroundColor: COLORS.orange }
];
new Chart(el('chartPaidAmtACS'), {
type: 'bar',
data: {
labels: ['Cashless', 'Reimbursement'],
datasets: paidAmtDatasets
},
options: {
responsive: true,
maintainAspectRatio: false,
layout: chartLayout,
plugins: { legend: { position: 'bottom', labels: { boxWidth: 10, font: { size: 10 } } }, barTopLabels: { format: 'amount' } },
scales: {
y: {
beginAtZero: true,
ticks: { callback: v => fmt(v) },
suggestedMax: maxInDatasets(paidAmtDatasets) * 1.2 || undefined
}
}
}
});
/* Claim Count By Claim Status */
const countStatusDatasets = [
{ label: 'Cashless', data: countStatusCashless, backgroundColor: COLORS.blue },
{ label: 'Reimbursement', data: countStatusReimb, backgroundColor: COLORS.navy }
];
new Chart(el('chartCountStatus'), {
type: 'bar',
data: {
labels: ['Settled', 'Outstanding', 'Rejected'],
datasets: countStatusDatasets
},
options: {
responsive: true,
maintainAspectRatio: false,
layout: chartLayout,
plugins: { legend: { position: 'bottom', labels: { boxWidth: 10, font: { size: 10 } } }, barTopLabels: { format: 'count' } },
scales: {
y: {
beginAtZero: true,
ticks: { stepSize: 1, precision: 0 },
suggestedMax: Math.max(1, maxInDatasets(countStatusDatasets) * 1.25)
}
}
}
});
/* Claim Amount By Claim Status */
new Chart(el('chartAmtStatus'), {
type: 'bar',
data: {
labels: ['Settled', 'Outstanding', 'Rejected'],
datasets: [{ data: amtStatusData, backgroundColor: [COLORS.blue, COLORS.orange, COLORS.purple], barPercentage: 0.55 }]
},
options: {
responsive: true,
maintainAspectRatio: false,
layout: chartLayout,
plugins: { legend: { display: false }, barTopLabels: { format: 'amount' } },
scales: {
y: {
beginAtZero: true,
ticks: { callback: v => fmt(v) },
suggestedMax: maxInDatasets([{ data: amtStatusData }]) * 1.2 || undefined
}
}
}
});
/* Monthly registration charts */
const months = MIS_DATA.months || [];
const paidCountMonth = MIS_DATA.paidCountByMonth || [];
const outCountMonth = MIS_DATA.outCountByMonth || [];
const paidAmtMonth = MIS_DATA.paidAmtByMonth || [];
const outAmtMonth = MIS_DATA.outAmtByMonth || [];
const monthCountDatasets = [
{ label: 'Claim Paid Count', data: paidCountMonth, backgroundColor: COLORS.blue, order: 2, barPercentage: 0.7, categoryPercentage: 0.8 },
{ label: 'Claim Outstanding Count', data: outCountMonth, backgroundColor: 'rgba(46,134,222,0.12)', borderColor: COLORS.navy, borderWidth: 2, order: 1, barPercentage: 0.95, categoryPercentage: 0.8 }
];
const monthAmtDatasets = [
{ label: 'Claim Paid Amount', data: paidAmtMonth, backgroundColor: COLORS.blue, order: 2, barPercentage: 0.7, categoryPercentage: 0.8 },
{ label: 'Claim Outstanding Amount', data: outAmtMonth, backgroundColor: 'rgba(46,134,222,0.12)', borderColor: COLORS.navy, borderWidth: 2, order: 1, barPercentage: 0.95, categoryPercentage: 0.8 }
];
const monthCountMax = maxInDatasets(monthCountDatasets);
const monthAmtMax = maxInDatasets(monthAmtDatasets);
new Chart(el('chartMonthCount'), {
type: 'bar',
data: { labels: months, datasets: monthCountDatasets },
options: {
responsive: true,
maintainAspectRatio: false,
layout: chartLayout,
plugins: { legend: { position: 'bottom', labels: { boxWidth: 10, font: { size: 10 } } }, barTopLabels: { format: 'count' } },
scales: {
y: { position: 'right', beginAtZero: true, suggestedMax: monthCountMax * 1.2 || undefined, ticks: { precision: 0 } },
x: { grid: { display: false } }
}
}
});
new Chart(el('chartMonthAmt'), {
type: 'bar',
data: { labels: months, datasets: monthAmtDatasets },
options: {
responsive: true,
maintainAspectRatio: false,
layout: chartLayout,
plugins: { legend: { position: 'bottom', labels: { boxWidth: 10, font: { size: 10 } } }, barTopLabels: { format: 'amount' } },
scales: {
y: { position: 'right', beginAtZero: true, suggestedMax: monthAmtMax * 1.2 || undefined, ticks: { callback: v => fmt(v) } },
x: { grid: { display: false } }
}
}
});
})();
</script>

View File

@ -0,0 +1,401 @@
<style>
.tpa-mis-dashboard .filter-bar {
display: flex;
flex-wrap: wrap;
gap: 12px 20px;
align-items: flex-end;
background: #eef5fa;
border-radius: 10px;
padding: 10px 14px;
margin-bottom: 10px;
}
.tpa-mis-dashboard .filter-field {
flex: 1 1 140px;
min-width: 120px;
max-width: 220px;
}
.tpa-mis-dashboard .filter-field label {
display: block;
font-size: 12px;
font-weight: 700;
margin-bottom: 4px;
color: #111;
}
.tpa-mis-dashboard .filter-field .select2-container {
width: 100% !important;
}
.tpa-mis-dashboard .filter-field .select2-selection--single {
height: 32px !important;
min-height: 32px;
border: 1px solid #d8e2ea !important;
border-radius: 8px !important;
font-size: 12px;
}
.tpa-mis-dashboard .filter-field .select2-selection__rendered {
line-height: 30px !important;
padding-left: 10px !important;
color: #333;
}
.tpa-mis-dashboard .filter-field .select2-selection__arrow {
height: 30px !important;
}
.tpa-mis-dashboard .filter-field .select2-container--disabled .select2-selection--single {
background-color: #f5f7f9 !important;
cursor: not-allowed;
opacity: 0.85;
}
.tpa-mis-dashboard .dashboard-status {
font-size: 12px;
color: #5c6b7a;
padding: 4px 0 10px;
min-height: 20px;
}
.tpa-mis-dashboard .dashboard-status.is-error { color: #c0392b; }
.tpa-mis-dashboard .dashboard-status.is-loading { color: #0a8794; }
.tpa-mis-dashboard .dashboard-placeholder {
display: flex;
align-items: center;
justify-content: center;
min-height: 200px;
color: #7a8a99;
font-size: 13px;
background: #fff;
border: 1px dashed #d0dce6;
border-radius: 8px;
}
.tpa-mis-dashboard .mis-toolbar {
display: none;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 10px;
flex-wrap: wrap;
}
.tpa-mis-dashboard .mis-toolbar.is-visible {
display: flex;
}
.tpa-mis-dashboard .mis-toolbar h5 {
margin: 0;
font-size: 15px;
font-weight: 700;
color: #152A54;
}
.tpa-mis-dashboard .mis-frame-wrap {
display: none;
background: #fff;
border: 1px solid #d0dce6;
border-radius: 8px;
overflow: visible;
}
.tpa-mis-dashboard .mis-frame-wrap.is-visible {
display: block;
}
.tpa-mis-dashboard #mis-report-frame {
display: block;
width: 100%;
height: 0;
min-height: 0;
border: 0;
background: #fff;
overflow: hidden;
}
</style>
<div class="row tpa-mis-dashboard">
<div class="col-xl-12">
<div class="card">
<div class="card-body">
<div class="filter-bar">
<div class="filter-field">
<label for="filter-client">Client</label>
<select id="filter-client" class="form-control form-control-sm tpa-mis-select2">
<option value="">Select</option>
</select>
</div>
<div class="filter-field">
<label for="filter-branch">Branch</label>
<select id="filter-branch" class="form-control form-control-sm tpa-mis-select2" disabled>
<option value="">Select</option>
</select>
</div>
<div class="filter-field">
<label for="filter-policy">Policy</label>
<select id="filter-policy" class="form-control form-control-sm tpa-mis-select2" disabled>
<option value="">Select</option>
</select>
</div>
</div>
<div id="dashboard-status" class="dashboard-status"></div>
<div id="mis-toolbar" class="mis-toolbar">
<h5 id="mis-title">TPA MIS Report</h5>
<a id="mis-pdf-link" class="btn btn-sm btn-outline-primary" href="#" target="_blank" rel="noopener" style="display:none;">Download PDF</a>
</div>
<div id="dashboard-container">
<div class="dashboard-placeholder" id="dashboard-placeholder">
Select client, branch, and policy to load the report.
</div>
<div id="mis-frame-wrap" class="mis-frame-wrap">
<iframe id="mis-report-frame" title="TPA MIS Report" scrolling="no"></iframe>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
$(function () {
const CLIENTS_API = <?= json_encode(base_url('/util/getClientAndBranchAndPolicy')) ?>;
const MIS_API = <?= json_encode($misApiUrl ?? base_url('/util/tpa-reports/mis')) ?>;
const select2Options = {
placeholder: 'Select',
allowClear: true,
width: '100%',
minimumResultsForSearch: 0,
dropdownParent: $(document.body)
};
let branchByClient = {};
let policyByClient = {};
const $elClient = $('#filter-client');
const $elBranch = $('#filter-branch');
const $elPolicy = $('#filter-policy');
const elStatus = document.getElementById('dashboard-status');
const elPlaceholder = document.getElementById('dashboard-placeholder');
const elMisToolbar = document.getElementById('mis-toolbar');
const elMisFrameWrap = document.getElementById('mis-frame-wrap');
const elMisFrame = document.getElementById('mis-report-frame');
const elMisTitle = document.getElementById('mis-title');
const elMisPdf = document.getElementById('mis-pdf-link');
function setStatus(message, type) {
elStatus.textContent = message || '';
elStatus.className = 'dashboard-status' + (type ? ' is-' + type : '');
}
function initSelect2($select, disabled) {
if ($select.hasClass('select2-hidden-accessible')) {
$select.select2('destroy');
}
$select.prop('disabled', false);
$select.select2(select2Options);
if (disabled) {
$select.prop('disabled', true);
}
}
function rebuildSelect($select, placeholder, disabled, items, getValue, getLabel) {
if ($select.hasClass('select2-hidden-accessible')) {
$select.select2('destroy');
}
$select.empty().append($('<option>', { value: '', text: placeholder }));
(items || []).forEach(function (item) {
$select.append($('<option>', {
value: String(getValue(item)),
text: getLabel(item)
}));
});
$select.val('');
initSelect2($select, disabled);
}
function filtersComplete() {
return !!($elClient.val() && $elBranch.val() && $elPolicy.val());
}
function clearMisReport() {
if (elMisToolbar) elMisToolbar.classList.remove('is-visible');
if (elMisFrameWrap) elMisFrameWrap.classList.remove('is-visible');
if (elMisFrame) {
elMisFrame.onload = null;
elMisFrame.removeAttribute('src');
elMisFrame.style.height = '0px';
}
if (elMisPdf) {
elMisPdf.style.display = 'none';
elMisPdf.setAttribute('href', '#');
}
if (elPlaceholder) elPlaceholder.style.display = '';
}
function prepareMisDocument(doc) {
if (!doc || doc.getElementById('tpa-mis-embed-style')) return;
const style = doc.createElement('style');
style.id = 'tpa-mis-embed-style';
style.textContent = [
'html, body { height: auto !important; min-height: 0 !important; overflow: hidden !important; margin: 0 !important; padding: 0 !important; background: #fff !important; }',
'.page, .wrap { margin: 0 auto !important; box-shadow: none !important; }',
'body { padding: 0 !important; }'
].join('\n');
(doc.head || doc.documentElement).appendChild(style);
}
/** Fit iframe to report content only — avoids leftover grey scroll after load. */
function resizeMisFrame() {
if (!elMisFrame || !elMisFrame.contentWindow) return;
try {
const doc = elMisFrame.contentDocument || elMisFrame.contentWindow.document;
if (!doc || !doc.body) return;
prepareMisDocument(doc);
const roots = doc.querySelectorAll('.page, .wrap');
let height = 0;
if (roots.length) {
roots.forEach(function (el) {
const bottom = el.offsetTop + el.offsetHeight;
if (bottom > height) height = bottom;
});
} else {
height = doc.body.scrollHeight || doc.body.offsetHeight || 0;
}
// Prefer content box over inflated html/body min-heights.
height = Math.ceil(height);
if (height < 200) {
height = Math.max(
doc.body.scrollHeight || 0,
(doc.documentElement && doc.documentElement.scrollHeight) || 0
);
}
if (height > 0) {
const next = height + 8;
const prev = parseInt(elMisFrame.style.height, 10) || 0;
// Only grow when content actually needs more room; shrink when excess grey space.
if (Math.abs(next - prev) > 4) {
elMisFrame.style.height = next + 'px';
}
}
} catch (e) {
// Same-origin expected; leave height alone on failure.
}
}
function bindMisFrameResize() {
if (!elMisFrame) return;
elMisFrame.onload = function () {
resizeMisFrame();
// Charts/images can settle after first paint (ABHI Chart.js).
[200, 600, 1200, 2000].forEach(function (ms) {
setTimeout(resizeMisFrame, ms);
});
};
}
async function loadFilterData() {
setStatus('Loading clients…', 'loading');
try {
const res = await fetch(CLIENTS_API, { credentials: 'same-origin' });
const json = await res.json();
if (!json.status) {
setStatus('Could not load client list.', 'error');
return;
}
branchByClient = json.branch_data || {};
policyByClient = json.policyListByClient || {};
rebuildSelect($elClient, 'Select', false, json.client_data || [], function (c) { return c.id; }, function (c) {
return c.client_name || c.client_short_name || ('Client ' + c.id);
});
rebuildSelect($elBranch, 'Select', true);
rebuildSelect($elPolicy, 'Select', true);
setStatus('');
} catch (e) {
setStatus('Failed to load filter options.', 'error');
}
}
async function fetchMisReport(policyId) {
clearMisReport();
if (!filtersComplete() || !policyId) {
setStatus('Select client, branch, and policy to load the report.');
return;
}
setStatus('Loading MIS report…', 'loading');
try {
const url = MIS_API + '?client_policy=' + encodeURIComponent(policyId);
const res = await fetch(url, { credentials: 'same-origin' });
const json = await res.json();
if (json.status !== 'success' || !json.data || !json.data.preview_url) {
setStatus(json.message || 'No MIS report for this TPA.', 'error');
return;
}
if (elMisTitle) elMisTitle.textContent = json.data.title || 'TPA MIS Report';
if (elMisFrame) {
bindMisFrameResize();
elMisFrame.src = json.data.preview_url;
}
if (elMisPdf && json.data.pdf_url) {
elMisPdf.href = json.data.pdf_url;
elMisPdf.style.display = '';
}
if (elPlaceholder) elPlaceholder.style.display = 'none';
if (elMisToolbar) elMisToolbar.classList.add('is-visible');
if (elMisFrameWrap) elMisFrameWrap.classList.add('is-visible');
setStatus('');
} catch (e) {
setStatus('Failed to load MIS report.', 'error');
}
}
$(window).on('resize', function () {
// Parent width change may reflow report; re-measure after layout.
setTimeout(resizeMisFrame, 100);
});
$elClient.on('change', function () {
const clientId = $(this).val();
rebuildSelect($elBranch, 'Select', !clientId);
rebuildSelect($elPolicy, 'Select', true);
clearMisReport();
setStatus(clientId ? '' : 'Select client, branch, and policy to load the report.');
if (!clientId) return;
const branches = branchByClient[clientId] || [];
rebuildSelect($elBranch, 'Select', branches.length === 0, branches, function (b) { return b.id; }, function (b) {
return b.branch_name || ('Branch ' + b.id);
});
});
$elBranch.on('change', function () {
const branchId = $(this).val();
const clientId = $elClient.val();
rebuildSelect($elPolicy, 'Select', !branchId);
clearMisReport();
setStatus(branchId ? '' : 'Select branch and policy to load the report.');
if (!branchId || !clientId) return;
const policiesForClient = policyByClient[clientId] || [];
const policies = policiesForClient.filter(function (p) {
return String(p.client_branch_id) === String(branchId);
});
rebuildSelect($elPolicy, 'Select', policies.length === 0, policies, function (p) { return p.id; }, function (p) {
const type = p.policy_type || '';
const no = p.policy_no || '';
return (type && no) ? (type + ' - ' + no) : (no || type || ('Policy ' + p.id));
});
if (policies.length === 0) {
setStatus('No policies found for this branch.', 'error');
}
});
$elPolicy.on('change', function () {
fetchMisReport($(this).val());
});
initSelect2($elClient, false);
initSelect2($elBranch, true);
initSelect2($elPolicy, true);
loadFilterData();
});
</script>

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,72 @@
<?php
/**
* Smoke test: ABHI MIS report service.
* Run: php tests/smoke_abhi_mis_report.php [policy_id]
*/
declare(strict_types=1);
define('FCPATH', __DIR__ . '/../public/');
chdir(FCPATH);
require FCPATH . '../app/Config/Paths.php';
$paths = new Config\Paths();
require rtrim($paths->systemDirectory, '\\/ ') . DIRECTORY_SEPARATOR . 'bootstrap.php';
require_once SYSTEMPATH . 'Config/DotEnv.php';
(new CodeIgniter\Config\DotEnv(ROOTPATH))->load();
defined('ENVIRONMENT') || define('ENVIRONMENT', env('CI_ENVIRONMENT', 'development'));
$boot = APPPATH . 'Config/Boot/' . ENVIRONMENT . '.php';
if (is_file($boot)) {
require_once $boot;
}
require_once APPPATH . 'Config/Constants.php';
$policyId = (int) ($argv[1] ?? 0);
$db = \Config\Database::connect();
if ($policyId <= 0) {
$row = $db->query(
"SELECT cr.client_policy_id, COUNT(*) AS cnt
FROM claim_report cr
WHERE cr.source_table = 'claims_dump_abhi'
AND cr.is_active = 1
AND cr.source_row_id IS NOT NULL
GROUP BY cr.client_policy_id
ORDER BY cnt DESC
LIMIT 1"
)->getRowArray();
$policyId = (int) ($row['client_policy_id'] ?? 0);
echo "Auto-selected policy_id: {$policyId}\n";
}
if ($policyId <= 0) {
echo "FAIL: No policy with ABHI claim_report linkage found.\n";
exit(1);
}
$service = new \App\Libraries\AbhiMisReportService();
$built = $service->build($policyId);
if (!$built['status']) {
echo 'FAIL: ' . ($built['message'] ?? 'unknown') . "\n";
exit(1);
}
$data = $built['data'];
$status = $data['status_breakdown']['buckets'] ?? [];
echo "OK policy_id={$policyId}\n";
echo 'Policy: ' . ($data['header']['policy_number'] ?? '') . "\n";
echo 'Dump rows: ' . ($data['dump_row_count'] ?? 0) . "\n";
echo 'Reported: ' . ($status['Reported']['count'] ?? 0) . "\n";
echo 'Settled: ' . ($status['Settled']['count'] ?? 0) . "\n";
echo 'Outstanding: ' . ($status['Outstanding']['count'] ?? 0) . "\n";
echo 'Rejected: ' . ($status['Rejected']['count'] ?? 0) . "\n";
echo 'ICR: ' . ($data['summary']['icr_pct'] ?? 0) . "%\n";
echo 'Incurred: ' . ($data['summary']['claim_paid_outstanding'] ?? 0) . "\n";
exit(0);

View File

@ -0,0 +1,140 @@
<?php
/**
* Smoke test: ICICI Portfolio Analysis MIS (service + HTML + PDF).
* Run: php tests/smoke_icici_mis_report.php [policy_id]
*/
declare(strict_types=1);
define('FCPATH', __DIR__ . '/../public/');
chdir(FCPATH);
require FCPATH . '../app/Config/Paths.php';
$paths = new Config\Paths();
require rtrim($paths->systemDirectory, '\\/ ') . DIRECTORY_SEPARATOR . 'bootstrap.php';
require_once SYSTEMPATH . 'Config/DotEnv.php';
(new CodeIgniter\Config\DotEnv(ROOTPATH))->load();
defined('ENVIRONMENT') || define('ENVIRONMENT', env('CI_ENVIRONMENT', 'development'));
$boot = APPPATH . 'Config/Boot/' . ENVIRONMENT . '.php';
if (is_file($boot)) {
require_once $boot;
}
require_once APPPATH . 'Config/Constants.php';
$policyId = (int) ($argv[1] ?? 0);
$db = \Config\Database::connect();
if ($policyId <= 0) {
$row = $db->query(
"SELECT cr.client_policy_id, COUNT(*) AS cnt
FROM claim_report cr
WHERE cr.source_table = 'claims_dump_icici'
AND cr.is_active = 1
AND cr.source_row_id IS NOT NULL
GROUP BY cr.client_policy_id
ORDER BY cnt DESC
LIMIT 1"
)->getRowArray();
$policyId = (int) ($row['client_policy_id'] ?? 0);
echo "Auto-selected policy_id: {$policyId}\n";
}
if ($policyId <= 0) {
echo "FAIL: No policy with ICICI claim_report linkage found.\n";
exit(1);
}
$service = new \App\Libraries\IciciMisReportService();
$built = $service->build($policyId);
if (!$built['status']) {
echo 'FAIL: ' . ($built['message'] ?? 'unknown') . "\n";
exit(1);
}
$data = $built['data'];
$vm = $data['view_model'] ?? [];
$requiredSections = [
'meta', 'premium_lr', 'claims_summary', 'iltc_summary', 'enrollment',
'age_analysis', 'relation_acs', 'amount_bands', 'si_bands', 'iltc_app',
'utilization_employees', 'utilization_dependents', 'disease',
'hospitals', 'cities', 'tat', 'takeaways',
];
foreach ($requiredSections as $key) {
if (!array_key_exists($key, $vm)) {
echo "FAIL: missing view_model.{$key}\n";
exit(1);
}
}
echo "OK policy_id={$policyId}\n";
echo 'Policy: ' . ($data['header']['policy_number'] ?? '') . "\n";
echo 'Client: ' . ($vm['meta']['client_name'] ?? '') . "\n";
echo 'Dump rows: ' . ($data['dump_row_count'] ?? 0) . "\n";
echo 'Claims summary rows: ' . count($vm['claims_summary']['rows'] ?? []) . "\n";
echo 'Age rows: ' . count($vm['age_analysis']['rows'] ?? []) . "\n";
echo 'Disease rows: ' . count($vm['disease']['rows'] ?? []) . "\n";
echo 'Hospitals: ' . count($vm['hospitals']['rows'] ?? []) . "\n";
echo 'Cities: ' . count($vm['cities']['rows'] ?? []) . "\n";
echo 'TAT months: ' . count($vm['tat'] ?? []) . "\n";
$html = view('claims_mis_icici', [
'report' => $data,
'view_model' => $vm,
'policy_id' => $policyId,
'pdf_url' => '',
'embed' => true,
]);
$sectionMarkers = [
'Portfolio Analysis',
'Enrollment Summary',
'Age-wise Analysis',
'Relation ACS',
'Amount Band-wise Analysis',
'SI Band-wise Analysis',
'IL Take Care',
'Disease-Analysis',
'Claim Utilization report',
'Top Hospitals',
'Top Cities',
'TAT Analysis',
];
foreach ($sectionMarkers as $marker) {
if (stripos($html, $marker) === false) {
echo "FAIL: HTML missing section '{$marker}'\n";
exit(1);
}
}
if (stripos($html, 'icici_lambard_new') === false && stripos($html, 'data:image') === false) {
if (stripos($html, 'icici_lambard_new.png') === false) {
echo "FAIL: HTML missing ICICI logo reference\n";
exit(1);
}
}
echo "OK HTML sections present (" . strlen($html) . " bytes)\n";
$pdf = (new \App\Libraries\IciciMisPdfService())->generateFromReport($data, $policyId);
if (!$pdf['status']) {
echo 'FAIL PDF: ' . ($pdf['message'] ?? 'unknown') . "\n";
exit(1);
}
$outDir = WRITEPATH . 'uploads/';
if (!is_dir($outDir)) {
@mkdir($outDir, 0775, true);
}
$outFile = $outDir . ($pdf['filename'] ?? 'ICICI_MIS_smoke.pdf');
file_put_contents($outFile, $pdf['content']);
echo 'OK PDF: ' . $outFile . ' (' . strlen((string) $pdf['content']) . " bytes)\n";
exit(0);

View File

@ -0,0 +1,128 @@
<?php
/**
* Smoke test: Medi Assist Portfolio Analysis MIS.
* Run: php tests/smoke_medi_assist_mis_report.php [policy_id]
*/
declare(strict_types=1);
define('FCPATH', __DIR__ . '/../public/');
chdir(FCPATH);
require FCPATH . '../app/Config/Paths.php';
$paths = new Config\Paths();
require rtrim($paths->systemDirectory, '\\/ ') . DIRECTORY_SEPARATOR . 'bootstrap.php';
require_once SYSTEMPATH . 'Config/DotEnv.php';
(new CodeIgniter\Config\DotEnv(ROOTPATH))->load();
defined('ENVIRONMENT') || define('ENVIRONMENT', env('CI_ENVIRONMENT', 'development'));
$boot = APPPATH . 'Config/Boot/' . ENVIRONMENT . '.php';
if (is_file($boot)) {
require_once $boot;
}
require_once APPPATH . 'Config/Constants.php';
$policyId = (int) ($argv[1] ?? 0);
$db = \Config\Database::connect();
if ($policyId <= 0) {
$row = $db->query(
"SELECT cr.client_policy_id, COUNT(*) AS cnt
FROM claim_report cr
WHERE cr.source_table = 'claims_dump_medi_assist'
AND cr.is_active = 1
AND cr.source_row_id IS NOT NULL
GROUP BY cr.client_policy_id
ORDER BY cnt DESC
LIMIT 1"
)->getRowArray();
$policyId = (int) ($row['client_policy_id'] ?? 0);
echo "Auto-selected policy_id: {$policyId}\n";
}
if ($policyId <= 0) {
echo "FAIL: No policy with Medi Assist claim_report linkage found.\n";
exit(1);
}
$service = new \App\Libraries\MediAssistMisReportService();
$built = $service->build($policyId);
if (!$built['status']) {
echo 'FAIL: ' . ($built['message'] ?? 'unknown') . "\n";
exit(1);
}
$data = $built['data'];
$vm = $data['view_model'] ?? [];
$required = [
'meta', 'header', 'index_policies', 'portfolio_summary', 'policy_lives', 'policy_premium',
'claim_status_rows', 'claim_type_sections', 'savings', 'top_providers', 'top_ailments',
'beneficiary', 'age_bands', 'utilization_employees', 'utilization_dependents',
'amount_bands_cashless', 'amount_bands_reimbursement', 'glossary',
];
foreach ($required as $key) {
if (!array_key_exists($key, $vm)) {
echo "FAIL: missing view_model.{$key}\n";
exit(1);
}
}
echo "OK policy_id={$policyId}\n";
echo 'Policy: ' . ($data['header']['policy_number'] ?? '') . "\n";
echo 'Dump rows: ' . ($data['dump_row_count'] ?? 0) . "\n";
echo 'Claims: ' . ($vm['portfolio_summary']['claims_count'] ?? 0) . "\n";
$html = view('claims_mis_medi_assist', [
'report' => $data,
'view_model' => $vm,
'policy_id' => $policyId,
'pdf_url' => '',
'embed' => true,
]);
$markers = [
'Portfolio Analysis Report',
'Portfolio Report',
'Savings Summary',
'Top 10 Distribution Across Providers',
'Distribution Across Beneficiary',
'Utilization Report for Employees',
'Distribution Across Amount Bands',
'Glossary',
];
foreach ($markers as $m) {
if (stripos($html, $m) === false) {
echo "FAIL: HTML missing '{$m}'\n";
exit(1);
}
}
if (stripos($html, 'medi_assist.png') === false && stripos($html, 'data:image') === false) {
echo "FAIL: logo missing\n";
exit(1);
}
echo 'OK HTML (' . strlen($html) . " bytes)\n";
$pdf = (new \App\Libraries\MediAssistMisPdfService())->generateFromReport($data, $policyId);
if (!$pdf['status']) {
echo 'FAIL PDF: ' . ($pdf['message'] ?? '') . "\n";
exit(1);
}
$outDir = WRITEPATH . 'uploads/';
if (!is_dir($outDir)) {
@mkdir($outDir, 0775, true);
}
$outFile = $outDir . ($pdf['filename'] ?? 'MA_MIS_smoke.pdf');
file_put_contents($outFile, $pdf['content']);
echo 'OK PDF: ' . $outFile . ' (' . strlen((string) $pdf['content']) . " bytes)\n";
exit(0);

View File

@ -0,0 +1,133 @@
<?php
/**
* Smoke test: Vidal Corporate Analysis MIS.
* Run: php tests/smoke_vidal_mis_report.php [policy_id]
*/
declare(strict_types=1);
define('FCPATH', __DIR__ . '/../public/');
chdir(FCPATH);
require FCPATH . '../app/Config/Paths.php';
$paths = new Config\Paths();
require rtrim($paths->systemDirectory, '\\/ ') . DIRECTORY_SEPARATOR . 'bootstrap.php';
require_once SYSTEMPATH . 'Config/DotEnv.php';
(new CodeIgniter\Config\DotEnv(ROOTPATH))->load();
defined('ENVIRONMENT') || define('ENVIRONMENT', env('CI_ENVIRONMENT', 'development'));
$boot = APPPATH . 'Config/Boot/' . ENVIRONMENT . '.php';
if (is_file($boot)) {
require_once $boot;
}
require_once APPPATH . 'Config/Constants.php';
$policyId = (int) ($argv[1] ?? 0);
$db = \Config\Database::connect();
if ($policyId <= 0) {
$row = $db->query(
"SELECT cr.client_policy_id, COUNT(*) AS cnt
FROM claim_report cr
WHERE cr.source_table = 'claims_dump_vidal'
AND cr.is_active = 1
AND cr.source_row_id IS NOT NULL
GROUP BY cr.client_policy_id
ORDER BY cnt DESC
LIMIT 1"
)->getRowArray();
$policyId = (int) ($row['client_policy_id'] ?? 0);
echo "Auto-selected policy_id: {$policyId}\n";
}
if ($policyId <= 0) {
echo "FAIL: No policy with Vidal claim_report linkage found.\n";
exit(1);
}
$service = new \App\Libraries\VidalMisReportService();
$built = $service->build($policyId);
if (!$built['status']) {
echo 'FAIL: ' . ($built['message'] ?? 'unknown') . "\n";
exit(1);
}
$data = $built['data'];
$vm = $data['view_model'] ?? [];
$required = [
'meta', 'header', 'toc', 'icr', 'hospitalization', 'member_gender', 'member_age',
'claims_age', 'claims_amount', 'ailments', 'hospitals', 'cashless_member_summary',
'tat', 'month_on_month', 'payout', 'chart_gender', 'chart_age',
];
foreach ($required as $key) {
if (!array_key_exists($key, $vm)) {
echo "FAIL: missing view_model.{$key}\n";
exit(1);
}
}
echo "OK policy_id={$policyId}\n";
echo 'Policy: ' . ($data['header']['policy_number'] ?? '') . "\n";
echo 'Dump rows: ' . ($data['dump_row_count'] ?? 0) . "\n";
$html = view('claims_mis_vidal', [
'report' => $data,
'view_model' => $vm,
'policy_id' => $policyId,
'pdf_url' => '',
'embed' => true,
]);
$markers = [
'Corporate Analysis Report',
'Incurred Claims Ratio',
'Hospitalisation Type Details',
'Member Details - Relationship',
'Claims Approved - Age Band',
'Claims Approved - Amount Band',
'Ailment',
'Hospital',
'Turn Around Time',
'Month on Month',
'Payout Ratio',
'DISCLAIMER',
];
foreach ($markers as $m) {
if (stripos($html, $m) === false) {
echo "FAIL: HTML missing '{$m}'\n";
exit(1);
}
}
if (
stripos($html, 'vidal.png') === false
&& stripos($html, 'vidal_mis.png') === false
&& stripos($html, 'data:image') === false
) {
echo "FAIL: logo missing\n";
exit(1);
}
echo 'OK HTML (' . strlen($html) . " bytes)\n";
$pdf = (new \App\Libraries\VidalMisPdfService())->generateFromReport($data, $policyId);
if (!$pdf['status']) {
echo 'FAIL PDF: ' . ($pdf['message'] ?? '') . "\n";
exit(1);
}
$outDir = WRITEPATH . 'uploads/';
if (!is_dir($outDir)) {
@mkdir($outDir, 0775, true);
}
$outFile = $outDir . ($pdf['filename'] ?? 'VIDAL_MIS_smoke.pdf');
file_put_contents($outFile, $pdf['content']);
echo 'OK PDF: ' . $outFile . ' (' . strlen((string) $pdf['content']) . " bytes)\n";
exit(0);