feat(abdm): add professional PDF export and print button for fetched health records

This commit is contained in:
DevSoftTech Server 2026-10-08 20:52:42 +05:30
parent 92acaee56f
commit 6c8e062558
6 changed files with 795 additions and 17 deletions

View File

@ -569,6 +569,7 @@ $routes->group('billing', function($routes) {
$routes->get('patient/show_profile_opd/(:num)', 'Patient::show_profile_opd/$1');
$routes->get('patient/show_profile_opd/(:num)/(:num)', 'Patient::show_profile_opd/$1/$2');
$routes->get('patient/abdm_documents/(:num)', 'Patient::abdm_documents/$1');
$routes->get('patient/abdm_records_pdf/(:num)', 'Patient::abdm_records_pdf/$1');
$routes->get('patient/abdm_document_detail/(:num)/(:num)', 'Patient::abdm_document_detail/$1/$2');
$routes->get('patient/abdm_content_request/(:num)', 'Patient::abdm_content_request/$1');
$routes->get('patient/abdm_content_auto_flow/(:num)', 'Patient::abdm_content_auto_flow/$1');

View File

@ -2128,6 +2128,173 @@ class Patient extends BaseController
]);
}
public function abdm_records_pdf(int $pno)
{
if (! $this->db->tableExists('patient_master')) {
return $this->response->setStatusCode(500)->setBody('patient_master table not found');
}
$patientRow = $this->db->table('patient_master')->where('id', $pno)->get()->getRowArray();
if (! is_array($patientRow)) {
return $this->response->setStatusCode(404)->setBody('Patient not found');
}
$abhaContext = $this->buildAbhaProfileContext($patientRow);
$patientAbha = trim((string) ($abhaContext['abha_address'] ?? ''));
if (! $this->db->tableExists('abdm_hiu_documents')) {
return $this->response->setStatusCode(404)->setBody('No ABDM documents table found');
}
$docId = (int) ($this->request->getGet('doc_id') ?? 0);
$consentRequestId = trim((string) ($this->request->getGet('consent_request_id') ?? ''));
$builder = $this->db->table('abdm_hiu_documents d')
->select('*')
->orderBy('d.document_date', 'DESC')
->orderBy('d.id', 'DESC');
$builder->groupStart()
->where('d.patient_id', $pno);
if ($patientAbha !== '') {
$builder->orWhere('d.abha_address', $patientAbha);
}
$builder->groupEnd();
if ($docId > 0) {
$builder->where('d.id', $docId);
} elseif ($consentRequestId !== '') {
$docFields = $this->db->getFieldNames('abdm_hiu_documents') ?? [];
$sessionIds = $this->resolveAllRelatedConsentIds($consentRequestId);
if ($sessionIds !== []) {
$builder->groupStart();
$hasClause = false;
if (in_array('consent_request_id', $docFields, true)) {
$builder->whereIn('d.consent_request_id', $sessionIds);
$hasClause = true;
}
if (in_array('consent_artifact_id', $docFields, true)) {
if ($hasClause) {
$builder->orWhereIn('d.consent_artifact_id', $sessionIds);
} else {
$builder->whereIn('d.consent_artifact_id', $sessionIds);
$hasClause = true;
}
}
if (in_array('consent_ref', $docFields, true)) {
if ($hasClause) {
$builder->orWhereIn('d.consent_ref', $sessionIds);
} else {
$builder->whereIn('d.consent_ref', $sessionIds);
$hasClause = true;
}
}
$builder->groupEnd();
}
}
$rows = $builder->get()->getResultArray();
foreach ($rows as &$r) {
$summary = json_decode((string) ($r['summary_json'] ?? ''), true);
$r['summary'] = is_array($summary) ? $summary : [];
}
unset($r);
$hospitalName = defined('H_Name') ? (string) constant('H_Name') : 'Hospital';
$hospitalAddress1 = defined('H_address_1') ? (string) constant('H_address_1') : '';
$hospitalAddress2 = defined('H_address_2') ? (string) constant('H_address_2') : '';
$hospitalPhone = defined('H_phone_No') ? (string) constant('H_phone_No') : '';
$hospitalEmail = defined('H_Email') ? (string) constant('H_Email') : '';
$hospitalLogoName = defined('H_logo') ? trim((string) constant('H_logo')) : '';
$logoPathCandidates = [];
if ($hospitalLogoName !== '') {
$logoPathCandidates[] = FCPATH . 'assets/images/' . $hospitalLogoName;
$logoPathCandidates[] = FCPATH . 'assets/img/' . $hospitalLogoName;
}
$logoPathCandidates[] = FCPATH . 'assets/img/logo.png';
$logoPathCandidates[] = FCPATH . 'assets/images/logo.png';
$logoSrc = '';
foreach ($logoPathCandidates as $candidate) {
if (is_file($candidate)) {
$logoSrc = str_replace('\\', '/', $candidate);
break;
}
}
$patientName = trim((string) (($patientRow['title'] ?? '') . ' ' . ($patientRow['p_fname'] ?? '') . ' ' . ($patientRow['p_lname'] ?? '')));
if ($patientName === '' || $patientName === '0') {
$patientName = trim((string) ($patientRow['p_fname'] ?? ''));
}
$genderVal = (int) ($patientRow['gender'] ?? 0);
$genderStr = $genderVal === 1 ? 'Male' : ($genderVal === 2 ? 'Female' : ($genderVal === 3 ? 'Other' : '-'));
$ageStr = ! empty($patientRow['age']) ? ($patientRow['age'] . ' Year') : '';
$ageGender = trim($ageStr . ($ageStr !== '' && $genderStr !== '-' ? ' / ' : '') . ($genderStr !== '-' ? $genderStr : ''));
$data = [
'title' => 'ABDM Health Records - ' . $patientName,
'patient' => $patientRow,
'patientId' => (int) ($patientRow['id'] ?? $pno),
'patientCode' => (string) ($patientRow['p_code'] ?? ('#' . $pno)),
'patientName' => $patientName,
'ageGender' => $ageGender,
'mobile' => (string) ($patientRow['mphone1'] ?? ''),
'abhaAddress' => $patientAbha,
'abhaNumber' => (string) ($patientRow['abha_id'] ?? ''),
'consentRequestId' => $consentRequestId,
'documents' => $rows,
'hospitalName' => $hospitalName,
'hospitalAddress' => trim($hospitalAddress1 . ($hospitalAddress2 !== '' ? ', ' . $hospitalAddress2 : '')),
'hospitalPhone' => $hospitalPhone,
'hospitalEmail' => $hospitalEmail,
'logoSrc' => $logoSrc,
'printedOn' => date('d-m-Y h:i A'),
];
$html = view('billing/abdm_records_pdf_v', $data);
$mpdfTempDir = WRITEPATH . 'cache' . DIRECTORY_SEPARATOR . 'mpdf';
if (! is_dir($mpdfTempDir)) {
mkdir($mpdfTempDir, 0755, true);
}
$mpdf = new \Mpdf\Mpdf([
'mode' => 'utf-8',
'format' => 'A4',
'orientation' => 'P',
'margin_left' => 10,
'margin_right' => 10,
'margin_top' => 10,
'margin_bottom' => 12,
'default_font' => 'freeserif',
'tempDir' => $mpdfTempDir,
]);
$mpdf->autoScriptToLang = true;
$mpdf->autoLangToFont = true;
$footerHtml = '<table style="width: 100%; border-top: 1px solid #cbd5e1; font-size: 7.5pt; color: #64748b; padding-top: 3px;">'
. '<tr>'
. '<td style="text-align: left; width: 75%;">Ayushman Bharat Digital Mission (ABDM) HIU &bull; Confidential &bull; For Clinical Evaluation Only</td>'
. '<td style="text-align: right; width: 25%;">Page {PAGENO} of {nbpg}</td>'
. '</tr>'
. '</table>';
$mpdf->SetHTMLFooter($footerHtml, 'O');
$mpdf->SetHTMLFooter($footerHtml, 'E');
$mpdf->SetTitle('ABDM Health Records - ' . $patientName);
$mpdf->WriteHTML($html);
$safeName = preg_replace('/[^A-Za-z0-9_-]/', '_', $patientName);
$fileName = 'ABDM_Records_' . $safeName . '_' . date('Ymd_His') . '.pdf';
return $this->response
->setHeader('Content-Type', 'application/pdf')
->setHeader('Content-Disposition', 'inline; filename="' . $fileName . '"')
->setBody($mpdf->Output($fileName, 'S'));
}
public function abdm_content_request(int $pno)
{
if (! $this->db->tableExists('patient_master')) {

View File

@ -76,6 +76,9 @@ class M3HiuGatewayClient
if (isset($payload['consentId']) && is_string($payload['consentId']) && str_contains($payload['consentId'], ':')) {
$payload['consentId'] = trim((string) explode(':', $payload['consentId'])[0]);
}
if (empty($payload['consent_request_id']) && !empty($payload['abdm_consent_request_id'])) {
$payload['consent_request_id'] = trim((string) $payload['abdm_consent_request_id']);
}
return $this->callGet('/v1/hiu/consent/status', $payload, 'consent.status.reconcile');
}

View File

@ -433,7 +433,7 @@ class M3HiuWorkflowService
$this->rememberArtifact($resolvedConsentId, $singleArtifactId, $payload['abha_address'] ?? '', $payload['hfr_id'] ?? '');
}
foreach ($this->getKnownArtifactIds($resolvedConsentId) as $artifactId) {
foreach ($this->getKnownArtifactIds($resolvedConsentId, true) as $artifactId) {
if ($artifactId === '' || $artifactId === $resolvedConsentId) {
continue;
}
@ -534,16 +534,20 @@ class M3HiuWorkflowService
->update($update);
}
private function getKnownArtifactIds(string $consentRequestId): array
private function getKnownArtifactIds(string $consentRequestId, bool $pendingOnly = false): array
{
if (! $this->db->tableExists('abdm_hiu_consent_artifacts') || $consentRequestId === '') {
return [];
}
$rows = $this->db->table('abdm_hiu_consent_artifacts')
$builder = $this->db->table('abdm_hiu_consent_artifacts')
->select('artifact_id')
->where('consent_request_id', $consentRequestId)
->get()
->getResultArray();
->where('consent_request_id', $consentRequestId);
if ($pendingOnly) {
$builder->whereNotIn('last_status', ['RECEIVED', 'DATA_RECEIVED', 'COMPLETED', 'EXPIRED', 'REVOKED', 'DENIED']);
}
$rows = $builder->get()->getResultArray();
return array_values(array_unique(array_map(
static fn (array $row): string => trim((string) ($row['artifact_id'] ?? '')),

View File

@ -376,7 +376,10 @@ if ($patientPhotoPath === '') {
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-2">
<div class="fw-semibold">Consent Request History</div>
<div>
<div class="d-flex align-items-center">
<a href="<?= base_url('billing/patient/abdm_records_pdf/' . (int) ($patient->id ?? 0)) ?>" target="_blank" class="btn btn-outline-primary btn-sm py-0 px-2 me-3" title="Print all fetched health records for this patient as PDF">
<i class="fa fa-print me-1"></i> Print All Records
</a>
<button type="button" class="btn btn-link btn-sm p-0 me-3" id="btnCheckLiveAbdmStatus">
<span class="spinner-border spinner-border-sm d-none" id="abdmLiveStatusSpinner"></span>
Check Live Status
@ -606,9 +609,14 @@ if ($patientPhotoPath === '') {
<div class="modal fade" id="abdmFetchResultModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Fetched Health Records</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
<div class="modal-header d-flex justify-content-between align-items-center">
<h5 class="modal-title"><i class="fa fa-folder-open text-primary me-2"></i>Fetched Health Records</h5>
<div class="d-flex align-items-center gap-2">
<button type="button" class="btn btn-sm btn-primary" id="abdmPrintFetchedRecordsBtn" style="display:none;" title="Print or Download PDF of all fetched records">
<i class="fa fa-print me-1"></i> Print / PDF
</button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
</div>
<div class="modal-body" id="abdmFetchResultModalBody">
<div class="text-muted small text-center">Loading...</div>
@ -1129,6 +1137,7 @@ if ($patientPhotoPath === '') {
$(function() {
var patientId = <?= (int) ($patient->id ?? 0) ?>;
var abdmDocumentsUrl = '<?= base_url('billing/patient/abdm_documents/' . (int) ($patient->id ?? 0)) ?>';
var abdmPrintPdfUrl = '<?= base_url('billing/patient/abdm_records_pdf/' . (int) ($patient->id ?? 0)) ?>';
var abdmAutoFlowUrl = '<?= base_url('billing/patient/abdm_content_auto_flow/' . (int) ($patient->id ?? 0)) ?>';
var abdmFetchOnlyUrl = '<?= base_url('billing/patient/abdm_content_fetch_only/' . (int) ($patient->id ?? 0)) ?>';
var abdmConsentDetailUrl = '<?= base_url('billing/patient/abdm_consent_detail/' . (int) ($patient->id ?? 0)) ?>';
@ -1138,6 +1147,7 @@ $(function() {
var abdmDocDetailBaseUrl = '<?= base_url('billing/patient/abdm_document_detail/' . (int) ($patient->id ?? 0)) ?>';
var currentAbdmRows = [];
var currentAbdmRequests = [];
var currentAbdmFetchConsentRequestId = '';
var autoFlowTimer = null;
var autoFlowRequestId = '';
var autoFlowAttempts = 0;
@ -1371,8 +1381,9 @@ $(function() {
}
function loadAbdmFetchResultModalData(consent) {
var url = abdmDocumentsUrl + '?limit=200&include_summary=1';
var reqId = (consent && consent.consent_request_id) ? consent.consent_request_id : '';
currentAbdmFetchConsentRequestId = reqId;
var url = abdmDocumentsUrl + '?limit=200&include_summary=1';
if (reqId !== '') {
url += '&consent_request_id=' + encodeURIComponent(reqId);
}
@ -1386,12 +1397,14 @@ $(function() {
renderAbdmFetchResultModal(Array.isArray(data.items) ? data.items : []);
})
.catch(function(err) {
$('#abdmPrintFetchedRecordsBtn').hide();
$('#abdmFetchResultModalBody').html('<div class="text-danger small text-center">' + escHtml('Failed to load records: ' + (err.message || err)) + '</div>');
});
}
function renderAbdmFetchResultModal(docs) {
if (!docs || !docs.length) {
$('#abdmPrintFetchedRecordsBtn').hide();
var fetchBtnHtml = currentAbdmFetchModalIdx >= 0
? '<div class="text-center mt-2"><button type="button" class="btn btn-sm btn-primary abdm-fetch-again-btn" data-idx="' + currentAbdmFetchModalIdx + '">Fetch Data</button></div>'
: '';
@ -1399,6 +1412,8 @@ $(function() {
return;
}
$('#abdmPrintFetchedRecordsBtn').show();
var html = '';
var docIds = [];
docs.forEach(function(doc, di) {
@ -1409,13 +1424,24 @@ $(function() {
docIds.push(docId);
}
html += '<div class="card mb-3">'
var singlePrintUrl = abdmPrintPdfUrl + '?doc_id=' + encodeURIComponent(docId);
html += '<div class="card mb-3 shadow-sm border">'
+ '<div class="card-body">'
+ '<div class="fw-semibold">' + escHtml(doc.document_title || 'ABDM Document') + '</div>'
+ '<div class="small text-muted mb-2">'
+ escHtml(doc.organization_name || '-') + ' &nbsp;|&nbsp; '
+ escHtml(doc.practitioner_name || '-') + ' &nbsp;|&nbsp; '
+ 'Date: ' + escHtml(fmtDateTime(doc.document_date || doc.created_at))
+ '<div class="d-flex justify-content-between align-items-start mb-2">'
+ ' <div>'
+ ' <div class="fw-bold text-primary fs-6">' + escHtml(doc.document_title || 'ABDM Document') + '</div>'
+ ' <div class="small text-muted">'
+ escHtml(doc.organization_name || '-') + ' &nbsp;|&nbsp; '
+ escHtml(doc.practitioner_name || '-') + ' &nbsp;|&nbsp; '
+ 'Date: ' + escHtml(fmtDateTime(doc.document_date || doc.created_at))
+ ' </div>'
+ ' </div>'
+ ' <div>'
+ ' <a href="' + escHtml(singlePrintUrl) + '" target="_blank" class="btn btn-outline-primary btn-sm py-1 px-2" title="Print this health record as PDF">'
+ ' <i class="fa fa-print me-1"></i> Print'
+ ' </a>'
+ ' </div>'
+ '</div>';
if (sections.length) {
@ -1878,6 +1904,14 @@ $(function() {
runFetchRecordsForRow(idx);
});
$(document).off('click.abdmOpd', '#abdmPrintFetchedRecordsBtn').on('click.abdmOpd', '#abdmPrintFetchedRecordsBtn', function() {
var printUrl = abdmPrintPdfUrl;
if (currentAbdmFetchConsentRequestId) {
printUrl += '?consent_request_id=' + encodeURIComponent(currentAbdmFetchConsentRequestId);
}
window.open(printUrl, '_blank');
});
$(document).off('shown.bs.tab.abdmOpd', '[data-bs-target="#opd-abdm-tab"]').on('shown.bs.tab.abdmOpd', '[data-bs-target="#opd-abdm-tab"]', function() {
if (!currentAbdmRows.length) {
loadAbdmDocs();

View File

@ -0,0 +1,569 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title><?= esc($title ?? 'ABDM Fetched Health Records') ?></title>
<style>
body {
font-family: 'freeserif', 'DejaVu Sans', sans-serif;
font-size: 9.5pt;
color: #1e293b;
line-height: 1.45;
margin: 0;
padding: 0;
}
/* Header Layout */
table.header-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 8px;
}
table.header-table td {
vertical-align: top;
padding: 0;
}
.hospital-logo {
max-height: 48px;
max-width: 170px;
margin-bottom: 4px;
}
.hospital-name {
font-size: 15pt;
font-weight: bold;
color: #0f2d59;
text-transform: uppercase;
letter-spacing: 0.5px;
line-height: 1.2;
}
.hospital-meta {
font-size: 8pt;
color: #475569;
line-height: 1.35;
margin-top: 2px;
}
.abdm-badge-box {
background-color: #f0f7ff;
border: 1px solid #bae6fd;
padding: 6px 10px;
text-align: right;
border-radius: 4px;
}
.abdm-badge-title {
font-size: 8pt;
font-weight: bold;
color: #0284c7;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.abdm-badge-sub {
font-size: 7.5pt;
color: #0369a1;
margin-top: 1px;
}
.abdm-report-type {
font-size: 10.5pt;
font-weight: bold;
color: #0f172a;
margin-top: 3px;
}
.report-meta-text {
font-size: 8pt;
color: #64748b;
margin-top: 3px;
}
.accent-divider {
height: 3px;
background-color: #0284c7;
margin: 6px 0 10px 0;
}
/* Patient Info Card */
table.patient-card {
width: 100%;
border-collapse: collapse;
margin-bottom: 12px;
border: 1px solid #cbd5e1;
background-color: #f8fafc;
}
table.patient-card td {
padding: 5px 8px;
border: 1px solid #e2e8f0;
font-size: 8.5pt;
vertical-align: top;
}
.p-lbl {
font-size: 7pt;
color: #64748b;
text-transform: uppercase;
font-weight: bold;
display: block;
margin-bottom: 2px;
}
.p-val {
font-size: 9pt;
font-weight: bold;
color: #0f172a;
}
.p-val-lg {
font-size: 10pt;
font-weight: bold;
color: #0f2d59;
text-transform: uppercase;
}
.abha-val {
color: #0284c7;
font-weight: bold;
}
.badge-verified {
display: inline-block;
background-color: #dcfce7;
color: #15803d;
border: 1px solid #86efac;
font-size: 7pt;
font-weight: bold;
padding: 1px 4px;
border-radius: 3px;
margin-left: 4px;
}
/* Document Card */
.doc-card {
margin-bottom: 14px;
border: 1px solid #cbd5e1;
page-break-inside: avoid;
}
table.doc-header {
width: 100%;
border-collapse: collapse;
background-color: #0f2d59;
color: #ffffff;
}
table.doc-header td {
padding: 6px 10px;
vertical-align: middle;
}
.doc-title {
font-size: 11pt;
font-weight: bold;
color: #ffffff;
letter-spacing: 0.3px;
}
.doc-badge {
background-color: #0284c7;
color: #ffffff;
font-size: 7.5pt;
font-weight: bold;
padding: 2px 6px;
border-radius: 3px;
text-transform: uppercase;
margin-right: 6px;
}
.doc-date {
font-size: 8.5pt;
color: #e0f2fe;
text-align: right;
}
table.doc-submeta {
width: 100%;
border-collapse: collapse;
background-color: #f1f5f9;
border-bottom: 1px solid #cbd5e1;
}
table.doc-submeta td {
padding: 4px 10px;
font-size: 8.5pt;
color: #334155;
}
.doc-body {
padding: 8px 10px;
background-color: #ffffff;
}
/* Section Styling */
.section-wrap {
margin-bottom: 10px;
}
.section-heading {
font-size: 9pt;
font-weight: bold;
color: #0f2d59;
text-transform: uppercase;
letter-spacing: 0.3px;
background-color: #f0f7ff;
border-left: 3px solid #0284c7;
padding: 3px 6px;
margin-bottom: 4px;
}
/* Lists */
ul.section-list {
margin: 3px 0 6px 16px;
padding: 0;
}
ul.section-list li {
margin-bottom: 2px;
font-size: 8.5pt;
color: #1e293b;
}
/* Tables (Vitals, Medications, Lab) */
table.grid-table {
width: 100%;
border-collapse: collapse;
margin: 4px 0 8px 0;
border: 1px solid #cbd5e1;
}
table.grid-table th {
background-color: #f8fafc;
color: #334155;
font-size: 8pt;
font-weight: bold;
text-transform: uppercase;
text-align: left;
padding: 4px 8px;
border: 1px solid #cbd5e1;
}
table.grid-table td {
padding: 4px 8px;
font-size: 8.5pt;
border: 1px solid #e2e8f0;
vertical-align: top;
}
table.grid-table tr:nth-child(even) td {
background-color: #fbfcfe;
}
/* Diagnosis Highlight Box */
.diagnosis-box {
background-color: #fefce8;
border: 1px solid #fde047;
border-left: 3px solid #ca8a04;
padding: 6px 8px;
margin: 4px 0 8px 0;
border-radius: 2px;
}
.diagnosis-item {
font-size: 9pt;
font-weight: bold;
color: #713f12;
margin-bottom: 2px;
}
/* Care Plan / Follow-up Box */
.careplan-box {
background-color: #f0fdf4;
border: 1px solid #bbf7d0;
border-left: 3px solid #16a34a;
padding: 6px 8px;
margin: 4px 0 8px 0;
border-radius: 2px;
}
/* Attached files note */
.attachments-box {
background-color: #f8fafc;
border: 1px dashed #94a3b8;
padding: 5px 8px;
margin-top: 6px;
font-size: 8pt;
color: #475569;
}
.no-records-box {
border: 1px solid #cbd5e1;
padding: 24px;
text-align: center;
color: #64748b;
background-color: #f8fafc;
margin-top: 20px;
}
</style>
</head>
<body>
<!-- Hospital Header -->
<table class="header-table">
<tr>
<td style="width: 58%;">
<?php if (!empty($logoSrc)): ?>
<img src="<?= $logoSrc ?>" class="hospital-logo" alt="Logo" /><br>
<?php endif; ?>
<div class="hospital-name"><?= esc($hospitalName) ?></div>
<div class="hospital-meta">
<?php if (!empty($hospitalAddress)): ?>
<?= esc($hospitalAddress) ?><br>
<?php endif; ?>
<?php
$contactParts = [];
if (!empty($hospitalPhone)) { $contactParts[] = 'Phone: ' . esc($hospitalPhone); }
if (!empty($hospitalEmail)) { $contactParts[] = 'Email: ' . esc($hospitalEmail); }
echo implode(' | ', $contactParts);
?>
</div>
</td>
<td style="width: 42%;">
<div class="abdm-badge-box">
<div class="abdm-badge-title">Ayushman Bharat Digital Mission (ABDM)</div>
<div class="abdm-badge-sub">Health Information User (HIU) Service</div>
<div class="abdm-report-type">Fetched Health Records</div>
<div class="report-meta-text">
Generated: <strong><?= esc($printedOn) ?></strong>
<?php if (!empty($consentRequestId)): ?>
<br>Consent Ref: <strong><?= esc($consentRequestId) ?></strong>
<?php endif; ?>
</div>
</div>
</td>
</tr>
</table>
<div class="accent-divider"></div>
<!-- Patient Demographic Banner -->
<table class="patient-card">
<tr>
<td style="width: 32%;">
<span class="p-lbl">Patient Name</span>
<span class="p-val-lg"><?= esc($patientName ?: 'Unknown Patient') ?></span>
</td>
<td style="width: 18%;">
<span class="p-lbl">UHID / Patient ID</span>
<span class="p-val"><?= esc($patientCode ?: ('#' . $patientId)) ?></span>
</td>
<td style="width: 25%;">
<span class="p-lbl">Age / Gender</span>
<span class="p-val"><?= esc($ageGender ?: '-') ?></span>
</td>
<td style="width: 25%;">
<span class="p-lbl">Contact / Mobile</span>
<span class="p-val"><?= esc($mobile ?: '-') ?></span>
</td>
</tr>
<tr>
<td colspan="2" style="background-color: #f0f7ff;">
<span class="p-lbl">ABHA Address (PHR)</span>
<span class="p-val abha-val"><?= esc($abhaAddress ?: 'Not Linked') ?></span>
<?php if (!empty($abhaAddress)): ?>
<span class="badge-verified">&check; VERIFIED</span>
<?php endif; ?>
</td>
<td colspan="2" style="background-color: #f0f7ff;">
<span class="p-lbl">ABHA Number</span>
<span class="p-val"><?= esc($abhaNumber ?: '-') ?></span>
</td>
</tr>
</table>
<!-- Fetched Documents Loop -->
<?php if (empty($documents)): ?>
<div class="no-records-box">
<h4 style="margin: 0 0 4px 0; color: #334155;">No Fetched Health Records Found</h4>
<p style="margin: 0; font-size: 8.5pt;">There are no clinical records stored under this consent request for the patient.</p>
</div>
<?php else: ?>
<?php foreach ($documents as $docIdx => $doc): ?>
<?php
$summary = $doc['summary'] ?? [];
$sections = is_array($summary['sections'] ?? null) ? $summary['sections'] : [];
$docTitle = trim((string) ($doc['document_title'] ?? 'Health Record'));
$docType = trim((string) ($doc['bundle_type'] ?? 'RECORD'));
$docDate = !empty($doc['document_date']) ? date('d-m-Y h:i A', strtotime($doc['document_date'])) : (!empty($doc['created_at']) ? date('d-m-Y h:i A', strtotime($doc['created_at'])) : '-');
$docFacility = trim((string) ($doc['organization_name'] ?? ''));
$docDoctor = trim((string) ($doc['practitioner_name'] ?? ''));
$careContext = trim((string) ($doc['care_context_reference'] ?? ''));
$attachments = is_array($summary['attachments'] ?? null) ? $summary['attachments'] : [];
?>
<div class="doc-card">
<!-- Document Header -->
<table class="doc-header">
<tr>
<td style="width: 70%;">
<span class="doc-badge"><?= esc($docType) ?></span>
<span class="doc-title"><?= esc($docTitle) ?></span>
</td>
<td style="width: 30%;" class="doc-date">
Encounter Date: <strong><?= esc($docDate) ?></strong>
</td>
</tr>
</table>
<!-- Submeta info -->
<table class="doc-submeta">
<tr>
<td>
<strong>Facility:</strong> <?= esc($docFacility ?: 'Healthcare Facility') ?>
&nbsp;&bull;&nbsp;
<strong>Doctor:</strong> <?= esc($docDoctor ?: 'Treating Physician') ?>
<?php if ($careContext !== ''): ?>
&nbsp;&bull;&nbsp;
<strong>Context Ref:</strong> <?= esc($careContext) ?>
<?php endif; ?>
</td>
</tr>
</table>
<!-- Document Body -->
<div class="doc-body">
<?php if (empty($sections)): ?>
<div style="font-size: 8.5pt; color: #64748b; font-style: italic;">
No structured clinical sections were returned in this bundle.
</div>
<?php else: ?>
<?php foreach ($sections as $section): ?>
<?php
$secTitle = trim((string) ($section['title'] ?? 'Clinical Section'));
$items = is_array($section['items'] ?? null) ? $section['items'] : [];
$narrative = trim((string) ($section['narrative'] ?? ''));
$secLower = strtolower($secTitle);
if (empty($items) && $narrative === '') {
continue;
}
?>
<div class="section-wrap">
<div class="section-heading"><?= esc($secTitle) ?></div>
<?php if (str_contains($secLower, 'vital') || str_contains($secLower, 'examination')): ?>
<!-- Vitals / Physical Examination Table -->
<table class="grid-table">
<thead>
<tr>
<th style="width: 58%;">Parameter / Vital Sign</th>
<th style="width: 42%;">Finding / Measurement</th>
</tr>
</thead>
<tbody>
<?php foreach ($items as $item): ?>
<?php
$itemStr = trim((string) $item);
$param = $itemStr;
$val = '';
if (str_contains($itemStr, ':')) {
$parts = explode(':', $itemStr, 2);
$param = trim($parts[0]);
$val = trim($parts[1] ?? '');
}
?>
<tr>
<td><strong><?= esc($param) ?></strong></td>
<td><?= esc($val !== '' ? $val : '-') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php elseif (str_contains($secLower, 'medication') || str_contains($secLower, 'prescription')): ?>
<!-- Medications / Prescription Table -->
<table class="grid-table">
<thead>
<tr>
<th style="width: 6%; text-align: center;">#</th>
<th style="width: 50%;">Medicine / Formulation</th>
<th style="width: 44%;">Dosage / Instructions</th>
</tr>
</thead>
<tbody>
<?php foreach ($items as $mIdx => $item): ?>
<?php
$itemStr = trim((string) $item);
$medName = $itemStr;
$dosage = '';
if (str_contains($itemStr, '—')) {
$parts = explode('—', $itemStr, 2);
$medName = trim($parts[0]);
$dosage = trim($parts[1] ?? '');
} elseif (str_contains($itemStr, ' - ')) {
$parts = explode(' - ', $itemStr, 2);
$medName = trim($parts[0]);
$dosage = trim($parts[1] ?? '');
}
?>
<tr>
<td style="text-align: center; color: #64748b;"><?= $mIdx + 1 ?></td>
<td><strong style="color: #0f2d59;"><?= esc($medName) ?></strong></td>
<td><?= esc($dosage !== '' ? $dosage : 'As directed') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php elseif (str_contains($secLower, 'diagnos') || str_contains($secLower, 'problem')): ?>
<!-- Diagnoses Highlight Box -->
<div class="diagnosis-box">
<?php if (!empty($items)): ?>
<?php foreach ($items as $item): ?>
<div class="diagnosis-item">&bull; <?= esc($item) ?></div>
<?php endforeach; ?>
<?php elseif ($narrative !== ''): ?>
<div class="diagnosis-item"><?= nl2br(esc($narrative)) ?></div>
<?php endif; ?>
</div>
<?php elseif (str_contains($secLower, 'care plan') || str_contains($secLower, 'follow up') || str_contains($secLower, 'advice')): ?>
<!-- Care Plan / Follow-up Box -->
<div class="careplan-box">
<?php if (!empty($items)): ?>
<ul class="section-list" style="margin-left: 12px;">
<?php foreach ($items as $item): ?>
<li><?= esc($item) ?></li>
<?php endforeach; ?>
</ul>
<?php elseif ($narrative !== ''): ?>
<div style="font-size: 8.5pt; color: #14532d;"><?= nl2br(esc($narrative)) ?></div>
<?php endif; ?>
</div>
<?php else: ?>
<!-- Standard List or Narrative -->
<?php if (!empty($items)): ?>
<ul class="section-list">
<?php foreach ($items as $item): ?>
<li><?= esc($item) ?></li>
<?php endforeach; ?>
</ul>
<?php elseif ($narrative !== ''): ?>
<div style="font-size: 8.5pt; color: #334155; margin-left: 4px;"><?= nl2br(esc($narrative)) ?></div>
<?php endif; ?>
<?php endif; ?>
</div>
<?php endforeach; ?>
<?php endif; ?>
<!-- Attached files note -->
<?php if (!empty($attachments)): ?>
<div class="attachments-box">
<strong>Attached Files / Scanned Documents:</strong>
<?= count($attachments) ?> file(s) attached in digital record
<?php
$attTitles = [];
foreach ($attachments as $att) {
if (!empty($att['title'])) {
$attTitles[] = esc($att['title']);
}
}
if (!empty($attTitles)) {
echo ' (' . implode(', ', $attTitles) . ')';
}
?>
</div>
<?php endif; ?>
</div>
</div>
<!-- Page Break between major documents (except after last document) -->
<?php if ($docIdx < count($documents) - 1): ?>
<pagebreak />
<?php endif; ?>
<?php endforeach; ?>
<?php endif; ?>
</body>
</html>