Extract and display ABDM DocumentReference/Binary attachments (images/PDFs/documents) in fetched record detail view

This commit is contained in:
DevSoftTech Server 2026-07-28 17:52:43 +05:30
parent 9536cd2990
commit 5fb87ea4b4
2 changed files with 132 additions and 0 deletions

View File

@ -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<int, array{title: string, content_type: string, data: string}>
*/
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')) {

View File

@ -853,6 +853,21 @@ $(function() {
return $('<div>').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 += '</ul></div></div>';
html += '</div>';
var attachments = Array.isArray(summary.attachments) ? summary.attachments : [];
html += '<div class="mt-3"><div class="fw-semibold small mb-2">Attached Files</div>';
if (attachments.length) {
html += '<div class="row g-2">';
attachments.forEach(function(att, idx) {
var ct = (att.content_type || '').toString().toLowerCase();
var blobUrl = att.data ? base64ToBlobUrl(att.data, att.content_type) : '';
html += '<div class="col-md-4"><div class="border rounded p-2 h-100 text-center">';
html += '<div class="small fw-semibold mb-1">' + escHtml(att.title || ('Attachment ' + (idx + 1))) + '</div>';
if (!blobUrl) {
html += '<div class="small text-muted">Preview unavailable</div>';
} else if (ct.indexOf('image/') === 0) {
html += '<img src="' + blobUrl + '" class="img-fluid rounded abdm-attachment-img" style="max-height:160px;cursor:pointer;" data-bs-toggle="modal" data-bs-target="#opdScanModal" data-src="' + blobUrl + '" alt="Attached image">';
} else if (ct === 'application/pdf') {
html += '<div class="small text-muted mb-2">PDF Document</div><a href="' + blobUrl + '" target="_blank" rel="noopener" class="btn btn-sm btn-outline-danger">View PDF</a>';
} else {
html += '<div class="small text-muted mb-2">' + escHtml(att.content_type || 'Document') + '</div><a href="' + blobUrl + '" target="_blank" rel="noopener" class="btn btn-sm btn-outline-secondary">Open Document</a>';
}
html += '</div></div>';
});
html += '</div>';
} else {
html += '<div class="small text-muted">No attached images, PDFs, or documents found in this record.</div>';
}
html += '</div>';
$('#abdmDocDetailBox').html(html);
}