diff --git a/app/Libraries/Abdm/M3HiuDocumentRepository.php b/app/Libraries/Abdm/M3HiuDocumentRepository.php index 965770d..0080465 100644 --- a/app/Libraries/Abdm/M3HiuDocumentRepository.php +++ b/app/Libraries/Abdm/M3HiuDocumentRepository.php @@ -166,6 +166,13 @@ class M3HiuDocumentRepository $row['summary'] = json_decode((string) ($row['summary_json'] ?? ''), true) ?: []; $row['bundle'] = json_decode((string) ($row['raw_bundle'] ?? ''), true) ?: []; + // Documents saved before attachment extraction was added won't have + // 'attachments' in their stored summary_json; derive it live from the + // raw bundle instead of requiring a brand-new fetch. + if (empty($row['summary']['attachments']) && ! empty($row['bundle'])) { + $row['summary']['attachments'] = $this->extractAttachments($row['bundle']); + } + return $row; } @@ -280,9 +287,93 @@ class M3HiuDocumentRepository 'conditions' => $conditions, 'vitals' => $vitals, 'medications' => $medications, + 'attachments' => $this->extractAttachments($bundle), ]; } + /** + * Extracts attached scanned images/PDFs/generic documents (DocumentReference + * resources, whose content may carry the base64 attachment data inline or + * reference a separate Binary resource) from a FHIR bundle. + * + * @return array + */ + public function extractAttachments(array $bundle): array + { + $entries = is_array($bundle['entry'] ?? null) ? (array) $bundle['entry'] : []; + $documentReferences = []; + + // Binary resources are usually referenced by DocumentReference.content[].attachment.url + // (e.g. "Binary/xyz" or a matching fullUrl) rather than carrying the base64 data inline, + // so index them by both id and fullUrl for lookup in the second pass below. + $binariesById = []; + foreach ($entries as $entry) { + $resource = is_array($entry['resource'] ?? null) ? (array) $entry['resource'] : []; + if (trim((string) ($resource['resourceType'] ?? '')) !== 'Binary') { + continue; + } + $data = trim((string) ($resource['data'] ?? '')); + $contentType = trim((string) ($resource['contentType'] ?? '')); + if ($data === '') { + continue; + } + $binary = ['data' => $data, 'content_type' => $contentType]; + $id = trim((string) ($resource['id'] ?? '')); + if ($id !== '') { + $binariesById[$id] = $binary; + } + $fullUrl = trim((string) ($entry['fullUrl'] ?? '')); + if ($fullUrl !== '') { + $binariesById[$fullUrl] = $binary; + } + } + + foreach ($entries as $entry) { + $resource = is_array($entry['resource'] ?? null) ? (array) $entry['resource'] : []; + if (trim((string) ($resource['resourceType'] ?? '')) !== 'DocumentReference') { + continue; + } + + foreach ((array) ($resource['content'] ?? []) as $content) { + $attachment = is_array($content['attachment'] ?? null) ? (array) $content['attachment'] : []; + if ($attachment === []) { + continue; + } + + $contentType = trim((string) ($attachment['contentType'] ?? '')); + $data = trim((string) ($attachment['data'] ?? '')); + if ($data === '') { + $refUrl = trim((string) ($attachment['url'] ?? '')); + $refId = str_replace('Binary/', '', $refUrl); + $binary = $binariesById[$refUrl] ?? $binariesById[$refId] ?? null; + if (is_array($binary)) { + $data = (string) ($binary['data'] ?? ''); + if ($contentType === '') { + $contentType = (string) ($binary['content_type'] ?? ''); + } + } + } + + if ($data === '') { + continue; + } + + $documentReferences[] = [ + 'title' => trim((string) ( + ($resource['description'] ?? '') + ?: ($resource['type']['text'] ?? '') + ?: ($resource['type']['coding'][0]['display'] ?? '') + ?: ($attachment['title'] ?? 'Attached Document') + )), + 'content_type' => $contentType !== '' ? $contentType : 'application/octet-stream', + 'data' => $data, + ]; + } + } + + return $documentReferences; + } + private function mapPatientByAbha(string $abhaAddress, string $abhaNumber): array { if (! $this->db->tableExists('patient_master')) { diff --git a/app/Views/billing/Patient_Profile_Opd_V.php b/app/Views/billing/Patient_Profile_Opd_V.php index 04e4f3f..1dac82e 100644 --- a/app/Views/billing/Patient_Profile_Opd_V.php +++ b/app/Views/billing/Patient_Profile_Opd_V.php @@ -853,6 +853,21 @@ $(function() { return $('
').text(val == null ? '' : String(val)).html(); } + function base64ToBlobUrl(base64, contentType) { + try { + var byteChars = atob(base64); + var byteNumbers = new Array(byteChars.length); + for (var i = 0; i < byteChars.length; i++) { + byteNumbers[i] = byteChars.charCodeAt(i); + } + var byteArray = new Uint8Array(byteNumbers); + var blob = new Blob([byteArray], { type: contentType || 'application/octet-stream' }); + return URL.createObjectURL(blob); + } catch (e) { + return ''; + } + } + function fmtDateTime(val) { var text = (val || '').toString().trim(); if (!text) { @@ -930,6 +945,32 @@ $(function() { html += '
'; html += ''; + var attachments = Array.isArray(summary.attachments) ? summary.attachments : []; + html += '
Attached Files
'; + if (attachments.length) { + html += '
'; + attachments.forEach(function(att, idx) { + var ct = (att.content_type || '').toString().toLowerCase(); + var blobUrl = att.data ? base64ToBlobUrl(att.data, att.content_type) : ''; + html += '
'; + html += '
' + escHtml(att.title || ('Attachment ' + (idx + 1))) + '
'; + if (!blobUrl) { + html += '
Preview unavailable
'; + } else if (ct.indexOf('image/') === 0) { + html += 'Attached image'; + } else if (ct === 'application/pdf') { + html += '
PDF Document
View PDF'; + } else { + html += '
' + escHtml(att.content_type || 'Document') + '
Open Document'; + } + html += '
'; + }); + html += '
'; + } else { + html += '
No attached images, PDFs, or documents found in this record.
'; + } + html += '
'; + $('#abdmDocDetailBox').html(html); }