From 0fe48f2ec8c09c32a26fb1bd17f72d6281236c61 Mon Sep 17 00:00:00 2001 From: DevSoftTech Server Date: Wed, 7 Oct 2026 12:16:56 +0530 Subject: [PATCH] feat(abdm): show health documents in HIP link modal and create dedicated ABHA Filter & FHIR Inspector page --- app/Config/Routes.php | 5 + app/Controllers/AbdmFhirInspector.php | 362 +++++++++ app/Controllers/AbdmGateway.php | 132 +++- app/Controllers/DoctorDocument.php | 10 + .../HealthDocumentFhirGenerator.php | 6 +- app/Views/abdm/fhir_inspector.php | 736 ++++++++++++++++++ app/Views/abdm/task_board.php | 12 +- app/Views/partials/abdm_hip_link_modal.php | 25 + app/Views/partials/sidebar.php | 6 + 9 files changed, 1285 insertions(+), 9 deletions(-) create mode 100644 app/Controllers/AbdmFhirInspector.php create mode 100644 app/Views/abdm/fhir_inspector.php diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 513147a..924594b 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -857,6 +857,11 @@ $routes->get('AbdmTaskBoard', 'AbdmTaskBoard::index', ['filter' => $abdmPermFilt $routes->get('AbdmTaskBoard/list', 'AbdmTaskBoard::list', ['filter' => $abdmPermFilter]); $routes->post('AbdmTaskBoard/perform_action', 'AbdmTaskBoard::performAction', ['filter' => $abdmPermFilter]); $routes->post('AbdmTaskBoard/mark_status', 'AbdmTaskBoard::markStatus', ['filter' => $abdmPermFilter]); +// ABHA Filter & FHIR Inspector +$routes->get('AbdmFhirInspector', 'AbdmFhirInspector::index', ['filter' => $abdmPermFilter]); +$routes->get('AbdmFhirInspector/patient_data', 'AbdmFhirInspector::patientData', ['filter' => $abdmPermFilter]); +$routes->get('AbdmFhirInspector/gateway_check', 'AbdmFhirInspector::gatewayCheck', ['filter' => $abdmPermFilter]); +$routes->get('AbdmFhirInspector/preview_bundle', 'AbdmFhirInspector::previewBundle', ['filter' => $abdmPermFilter]); // ABDM Bridge Log viewer $routes->get('AbdmBridgeLog', 'AbdmBridgeLog::index', ['filter' => 'permission:abdm.access,abdm.taskboard.access,abdm.gateway.use,abdm.bridge_log.view']); $routes->get('AbdmBridgeLog/list', 'AbdmBridgeLog::list', ['filter' => 'permission:abdm.access,abdm.taskboard.access,abdm.gateway.use,abdm.bridge_log.view']); diff --git a/app/Controllers/AbdmFhirInspector.php b/app/Controllers/AbdmFhirInspector.php new file mode 100644 index 0000000..97a561c --- /dev/null +++ b/app/Controllers/AbdmFhirInspector.php @@ -0,0 +1,362 @@ +db = \Config\Database::connect(); + try { + $this->connector = AbdmConnectorFactory::bridge(); + } catch (\Throwable $e) { + $this->connector = null; + } + } + + /** + * Main page view for ABHA Filter & FHIR Inspector + */ + public function index() + { + $filterAbha = trim((string) ($this->request->getGet('abha_address') ?? $this->request->getGet('abha') ?? '')); + $patientId = (int) ($this->request->getGet('patient_id') ?? $this->request->getGet('id') ?? 0); + $uhid = trim((string) ($this->request->getGet('uhid') ?? '')); + + $abhaPatients = $this->getAbhaPatientsList(); + + return view('abdm/fhir_inspector', [ + 'filter_abha' => $filterAbha, + 'patient_id' => $patientId, + 'filter_uhid' => $uhid, + 'abha_patients' => $abhaPatients, + 'recent_pills' => array_slice($abhaPatients, 0, 8), + ]); + } + + /** + * AJAX endpoint: Get patient demographics, care contexts, and link status + * GET /AbdmFhirInspector/patient_data?abha_address=...&patient_id=... + */ + public function patientData() + { + $patientId = (int) ($this->request->getGet('patient_id') ?? 0); + $abhaAddress = trim((string) ($this->request->getGet('abha_address') ?? $this->request->getGet('abha') ?? '')); + $pCode = trim((string) ($this->request->getGet('p_code') ?? $this->request->getGet('uhid') ?? '')); + + if ($patientId <= 0 && $abhaAddress === '' && $pCode === '') { + return $this->response->setJSON(['ok' => 0, 'error_text' => 'ABHA Address, Patient ID, or UHID is required']); + } + + // Delegate to AbdmGateway for canonical patient discovery & care contexts + try { + $gateway = new \App\Controllers\AbdmGateway(); + $gateway->initController($this->request, $this->response, service('logger')); + return $gateway->hipPatientCareContexts(); + } catch (\Throwable $e) { + log_message('error', '[AbdmFhirInspector::patientData] {msg}', ['msg' => $e->getMessage()]); + return $this->response->setJSON([ + 'ok' => 0, + 'error_text' => 'Unable to load patient care contexts: ' . $e->getMessage(), + ]); + } + } + + /** + * AJAX endpoint: Live Gateway Link Check for an ABHA address + * GET /AbdmFhirInspector/gateway_check?abha_address=... + */ + public function gatewayCheck() + { + $abha = trim((string) ($this->request->getGet('abha_address') ?? $this->request->getGet('abha') ?? '')); + if ($abha === '') { + return $this->response->setJSON(['ok' => 0, 'error_text' => 'ABHA address is required']); + } + + try { + if ($this->connector === null) { + $this->connector = AbdmConnectorFactory::bridge(); + } + $result = $this->connector->hipGetPatientLinks(['abha_address' => $abha]); + $careContexts = []; + if (! empty($result['data']) && is_array($result['data'])) { + foreach ($result['data'] as $patientRec) { + foreach ($patientRec['careContexts'] ?? [] as $cc) { + $careContexts[] = [ + 'reference' => trim((string) ($cc['referenceNumber'] ?? $cc['ref'] ?? '')), + 'display' => trim((string) ($cc['display'] ?? '')), + 'status' => trim((string) ($cc['status'] ?? 'linked')), + ]; + } + } + } + + return $this->response->setJSON([ + 'ok' => 1, + 'abha_address' => $abha, + 'count' => count($careContexts), + 'care_contexts' => $careContexts, + 'raw_bridge' => $result, + ]); + } catch (\Throwable $e) { + return $this->response->setJSON([ + 'ok' => 0, + 'error_text' => 'Bridge check failed: ' . $e->getMessage(), + ]); + } + } + + /** + * AJAX endpoint: Preview FHIR R4 Bundle for any care context reference + * GET /AbdmFhirInspector/preview_bundle?ref=...&patient_id=...&abha=... + */ + public function previewBundle() + { + $ref = trim((string) ($this->request->getGet('ref') ?? '')); + $patientId = (int) ($this->request->getGet('patient_id') ?? 0); + $abha = trim((string) ($this->request->getGet('abha') ?? '')); + + if ($ref === '') { + return $this->response->setStatusCode(400)->setJSON(['ok' => 0, 'error_text' => 'Care Context Reference is required']); + } + + // 1. Check health_records table for stored bundle + if ($this->db->tableExists('health_records')) { + $hr = $this->db->table('health_records') + ->where('care_context_reference', $ref) + ->orderBy('id', 'DESC') + ->get(1) + ->getRowArray(); + + if (! empty($hr)) { + $rawBundle = $hr['fhir_bundle'] ?? $hr['bundle'] ?? null; + $decoded = null; + if (is_string($rawBundle) && $rawBundle !== '') { + $decoded = json_decode($rawBundle, true); + } elseif (is_array($rawBundle)) { + $decoded = $rawBundle; + } + + if (! empty($decoded)) { + return $this->response->setJSON([ + 'ok' => 1, + 'source' => 'health_records_stored', + 'care_context_reference' => $ref, + 'care_context_display' => $hr['care_context_display'] ?? $ref, + 'hi_type' => $hr['hi_type'] ?? 'HealthRecord', + 'bundle' => $decoded, + 'fhir_bundle' => $decoded, + 'resource_type' => $decoded['resourceType'] ?? 'Bundle', + 'total_entries' => count($decoded['entry'] ?? []), + 'entry_types' => array_values(array_unique(array_filter(array_map(function ($e) { + return $e['resource']['resourceType'] ?? null; + }, $decoded['entry'] ?? [])))), + ]); + } + } + } + + // 2. Dynamic generation based on prefix + try { + // OPD Consult + if (preg_match('/^OPD-(\d+)(?:-S(\d+))?/i', $ref, $m)) { + $opdId = (int) $m[1]; + $sessionId = ! empty($m[2]) ? (int) $m[2] : $opdId; + $opdController = new \App\Controllers\Opd_prescription(); + $opdController->initController($this->request, $this->response, service('logger')); + return $opdController->fhir_bundle_preview($opdId, $sessionId); + } + + // Health Document: DOC-file-{id} or DOC-{id} + if (preg_match('/^DOC-(?:file-)?(\d+)/i', $ref, $m)) { + $docId = (int) $m[1]; + $docController = new \App\Controllers\DoctorDocument(); + $docController->initController($this->request, $this->response, service('logger')); + return $docController->health_document_fhir_preview($docId); + } + + // Diagnostic Report: LAB-{id} or RAD-{id} + if (preg_match('/^(?:LAB|RAD)-(\d+)/i', $ref, $m)) { + $labId = (int) $m[1]; + $gw = new \App\Controllers\AbdmGateway(); + $gw->initController($this->request, $this->response, service('logger')); + // Simulate request param + $_GET['request_id'] = $labId; + return $gw->diagnosisReportFhirPreview(); + } + + // Invoices: INVOICE-OPD-{id}, INVOICE-CHG-{id}, INVOICE-IPD-{id} + if (preg_match('/^INVOICE-([A-Za-z]+)-(\d+)/i', $ref, $m)) { + $typeCode = strtoupper($m[1]); + $billId = (int) $m[2]; + $source = match ($typeCode) { + 'OPD' => 'opd_invoice', + 'CHG' => 'charges_invoice', + 'IPD' => 'ipd_invoice', + default => 'opd_invoice' + }; + $gw = new \App\Controllers\AbdmGateway(); + $gw->initController($this->request, $this->response, service('logger')); + $_GET['source'] = $source; + $_GET['bill_id'] = $billId; + $_GET['patient_id'] = $patientId; + return $gw->invoiceFhirPreview(); + } + + // Immunization: IMM-{id} + if (preg_match('/^IMM-(\d+)/i', $ref, $m)) { + $immId = (int) $m[1]; + $gw = new \App\Controllers\AbdmGateway(); + $gw->initController($this->request, $this->response, service('logger')); + $_GET['record_id'] = $immId; + $_GET['patient_id'] = $patientId; + return $gw->immunizationFhirPreview(); + } + + // Discharge Summary: DISCHARGE-{id} + if (preg_match('/^DISCHARGE-(\d+)/i', $ref, $m)) { + $ipdId = (int) $m[1]; + $gw = new \App\Controllers\AbdmGateway(); + $gw->initController($this->request, $this->response, service('logger')); + $_GET['ipd_id'] = $ipdId; + return $gw->ipdDischargeFhirPreview(); + } + + // Wellness Record: WELLNESS-{id} + if (preg_match('/^WELLNESS-(?:(\d+)-W)?(\d+)/i', $ref, $m)) { + $wId = ! empty($m[2]) ? (int) $m[2] : (int) $m[1]; + $pId = ! empty($m[1]) ? (int) $m[1] : $patientId; + $docController = new \App\Controllers\DoctorDocument(); + $docController->initController($this->request, $this->response, service('logger')); + return $docController->wellness_record_fhir_preview($pId, $wId); + } + } catch (\Throwable $e) { + log_message('error', '[AbdmFhirInspector::previewBundle] Generation failed: ' . $e->getMessage()); + } + + return $this->response->setStatusCode(404)->setJSON([ + 'ok' => 0, + 'error_text' => 'Unable to dynamically generate FHIR bundle for care context: ' . $ref, + ]); + } + + /** + * Query patient_master for patients with ABHA data + */ + private function getAbhaPatientsList(): array + { + if (! $this->db->tableExists('patient_master')) { + return []; + } + + $fields = $this->db->getFieldNames('patient_master') ?? []; + $hasAbhaAddress = in_array('abha_address', $fields, true); + $hasAbhaId = in_array('abha_id', $fields, true); + $hasAbhaNo = in_array('abha_no', $fields, true); + + if (! $hasAbhaAddress && ! $hasAbhaId && ! $hasAbhaNo) { + return []; + } + + $select = ['id', 'p_code', 'p_fname', 'gender', 'dob']; + if (in_array('p_lname', $fields, true)) { + $select[] = 'p_lname'; + } + if (in_array('mphone1', $fields, true)) { + $select[] = 'mphone1'; + } + if ($hasAbhaAddress) { + $select[] = 'abha_address'; + } + if ($hasAbhaId) { + $select[] = 'abha_id'; + } + if ($hasAbhaNo) { + $select[] = 'abha_no'; + } + + $builder = $this->db->table('patient_master')->select(implode(', ', $select)); + + $whereOr = []; + if ($hasAbhaAddress) { + $whereOr[] = "NULLIF(TRIM(abha_address), '') IS NOT NULL"; + } + if ($hasAbhaId) { + $whereOr[] = "NULLIF(TRIM(abha_id), '') IS NOT NULL"; + } + if ($hasAbhaNo) { + $whereOr[] = "NULLIF(TRIM(abha_no), '') IS NOT NULL"; + } + + if ($whereOr !== []) { + $builder->where('(' . implode(' OR ', $whereOr) . ')', null, false); + } + + $rows = $builder->orderBy('id', 'DESC')->limit(300)->get()->getResultArray(); + $patients = []; + + foreach ($rows as $row) { + $rawAddress = trim((string) ($row['abha_address'] ?? '')); + $rawId = trim((string) ($row['abha_id'] ?? $row['abha_no'] ?? '')); + + $addr = ''; + $num = ''; + + if (str_contains($rawAddress, '@')) { + $addr = $rawAddress; + } elseif (str_contains($rawId, '@')) { + $addr = $rawId; + } + + $d1 = preg_replace('/\D/', '', $rawId); + $d2 = preg_replace('/\D/', '', $rawAddress); + if (is_string($d1) && strlen($d1) === 14) { + $num = $d1; + } elseif (is_string($d2) && strlen($d2) === 14) { + $num = $d2; + } + + if ($addr === '' && $num === '') { + continue; + } + + $name = trim(($row['p_fname'] ?? '') . ' ' . ($row['p_lname'] ?? '')); + if ($name === '') { + $name = 'Patient #' . $row['id']; + } + + $patients[] = [ + 'id' => (int) $row['id'], + 'p_code' => (string) ($row['p_code'] ?? ('P-' . $row['id'])), + 'name' => $name, + 'gender' => (string) ($row['gender'] ?? 'M'), + 'dob' => (string) ($row['dob'] ?? ''), + 'phone' => (string) ($row['mphone1'] ?? ''), + 'abha_address' => $addr, + 'abha_number' => $num, + ]; + } + + return $patients; + } +} diff --git a/app/Controllers/AbdmGateway.php b/app/Controllers/AbdmGateway.php index d772259..78f855b 100644 --- a/app/Controllers/AbdmGateway.php +++ b/app/Controllers/AbdmGateway.php @@ -6450,13 +6450,58 @@ class AbdmGateway extends BaseController ]); } } elseif ($taskType === 'health_document_publish') { - if ($this->db->tableExists('patient_doc')) { + $foundDoc = false; + if ($this->db->tableExists('file_upload_data')) { + $fileRow = $this->db->table('file_upload_data')->where('id', $taskEntityId)->get(1)->getRowArray(); + if (! empty($fileRow)) { + $vDate = ! empty($fileRow['insert_date']) ? $fileRow['insert_date'] : date('Y-m-d'); + $dateStr = date('d M Y', strtotime((string) $vDate)); + $cleanDate = date('Ymd', strtotime((string) $vDate)); + $docTitle = trim((string) ($fileRow['document_type'] ?? $fileRow['scan_type'] ?? $fileRow['content_description'] ?? '')); + if ($docTitle === '' || strcasecmp($docTitle, 'Queued for AI analysis') === 0) { + $docTitle = 'Health Document'; + } + $ccRef = 'DOC-file-' . $taskEntityId . '-' . $cleanDate; + $aliasRef = 'DOC-' . $taskEntityId . '-' . $cleanDate; + + if ($this->db->tableExists('health_records')) { + $existingHr = $this->db->table('health_records') + ->where('patient_id', $patientId) + ->groupStart() + ->where('entity_id', (string) $taskEntityId) + ->orWhere('care_context_reference LIKE', 'DOC-%' . $taskEntityId . '%') + ->groupEnd() + ->orderBy('id', 'DESC') + ->get(1) + ->getRowArray(); + if (! empty($existingHr['care_context_reference'])) { + $ccRef = trim((string) $existingHr['care_context_reference']); + } + } + + $display = 'HealthDocumentRecord - ' . $docTitle . ' (' . $dateStr . ')'; + $addContext([ + 'careContextId' => $ccRef, + 'referenceNumber' => $ccRef, + 'alias_reference' => $aliasRef, + 'display' => $display, + 'record_type' => 'HealthDocumentRecord', + 'patient_id' => $patientId, + 'is_fhir_ready' => true, + 'is_primary' => true, + ]); + $foundDoc = true; + } + } + if (! $foundDoc && $this->db->tableExists('patient_doc')) { $pdoc = $this->db->table('patient_doc')->where('id', $taskEntityId)->get(1)->getRowArray(); if (! empty($pdoc)) { $docDate = ! empty($pdoc['date_issue']) ? $pdoc['date_issue'] : (! empty($pdoc['created_at']) ? $pdoc['created_at'] : date('Y-m-d')); $dateStr = date('d M Y', strtotime((string) $docDate)); - $ccRef = 'DOC-' . $taskEntityId . '-' . date('Ymd', strtotime((string) $docDate)); - $display = 'HealthDocumentRecord - ' . (! empty($pdoc['doc_name']) ? $pdoc['doc_name'] : 'Medical Document') . ' (' . $dateStr . ')'; + $cleanDate = date('Ymd', strtotime((string) $docDate)); + $ccRef = 'DOC-' . $taskEntityId . '-' . $cleanDate; + $docName = ! empty($pdoc['doc_name']) ? $pdoc['doc_name'] : 'Medical Document'; + $display = 'HealthDocumentRecord - ' . $docName . ' (' . $dateStr . ')'; $addContext([ 'careContextId' => $ccRef, 'referenceNumber' => $ccRef, @@ -7043,6 +7088,79 @@ class AbdmGateway extends BaseController } } + // 6d. Query patient health documents (file_upload_data & patient_doc) + if ($this->db->tableExists('file_upload_data')) { + $fileDocs = $this->db->table('file_upload_data') + ->where('pid', $patientId) + ->orderBy('id', 'DESC') + ->limit(20) + ->get() + ->getResultArray(); + foreach ($fileDocs as $fd) { + $vDate = ! empty($fd['insert_date']) ? $fd['insert_date'] : date('Y-m-d'); + $dateStr = date('d M Y', strtotime((string) $vDate)); + $cleanDate = date('Ymd', strtotime((string) $vDate)); + $docTitle = trim((string) ($fd['document_type'] ?? $fd['scan_type'] ?? $fd['content_description'] ?? '')); + if ($docTitle === '' || strcasecmp($docTitle, 'Queued for AI analysis') === 0) { + $docTitle = 'Health Document'; + } + $ccRef = 'DOC-file-' . $fd['id'] . '-' . $cleanDate; + $aliasRef = 'DOC-' . $fd['id'] . '-' . $cleanDate; + + if ($this->db->tableExists('health_records')) { + $existingHr = $this->db->table('health_records') + ->where('patient_id', $patientId) + ->groupStart() + ->where('entity_id', (string) $fd['id']) + ->orWhere('care_context_reference LIKE', 'DOC-%' . $fd['id'] . '%') + ->groupEnd() + ->orderBy('id', 'DESC') + ->get(1) + ->getRowArray(); + if (! empty($existingHr['care_context_reference'])) { + $ccRef = trim((string) $existingHr['care_context_reference']); + } + } + + $display = 'HealthDocumentRecord - ' . $docTitle . ' (' . $dateStr . ')'; + $addContext([ + 'careContextId' => $ccRef, + 'referenceNumber' => $ccRef, + 'alias_reference' => $aliasRef, + 'display' => $display, + 'record_type' => 'HealthDocumentRecord', + 'patient_id' => $patientId, + 'is_fhir_ready' => true, + 'is_primary' => false, + ]); + } + } + if ($this->db->tableExists('patient_doc')) { + $pDocs = $this->db->table('patient_doc') + ->where('p_id', $patientId) + ->orderBy('id', 'DESC') + ->limit(20) + ->get() + ->getResultArray(); + foreach ($pDocs as $pd) { + $docDate = ! empty($pd['date_issue']) ? $pd['date_issue'] : (! empty($pd['created_at']) ? $pd['created_at'] : date('Y-m-d')); + $dateStr = date('d M Y', strtotime((string) $docDate)); + $cleanDate = date('Ymd', strtotime((string) $docDate)); + $ccRef = 'DOC-' . $pd['id'] . '-' . $cleanDate; + $docName = ! empty($pd['doc_name']) ? $pd['doc_name'] : 'Medical Document'; + $display = 'HealthDocumentRecord - ' . $docName . ' (' . $dateStr . ')'; + $addContext([ + 'careContextId' => $ccRef, + 'referenceNumber' => $ccRef, + 'display' => $display, + 'record_type' => 'HealthDocumentRecord', + 'patient_id' => $patientId, + 'is_fhir_ready' => true, + 'is_primary' => false, + ]); + } + } + // 7. Fallback: default registration file care context if (empty($careContextsV3)) { $ccRef = 'REG-' . $patientRef; @@ -9547,14 +9665,18 @@ class AbdmGateway extends BaseController $adapter = new \App\Libraries\Abdm\Fhir\Support\GatewayPayloadAdapter(); $gatewayPayload = $adapter->toGatewayPayload($output, $source, $hfrId); + $isFromFile = isset($source['record_id']) && str_starts_with((string)$source['record_id'], 'file-'); + $reqCcRef = trim((string) ($this->request->getPost('care_context_reference') ?? '')); + $resolvedCcRef = $reqCcRef !== '' ? $reqCcRef : (string) ($gatewayPayload['care_context_reference'] ?? ('DOC-' . ($source['record_id'] ?? $targetRecordId) . '-' . date('Ymd', strtotime((string)($source['visit_date'] ?? date('Y-m-d')))))); + return [ 'hi_type' => 'HealthDocumentRecord', - 'entity_type' => 'patient_doc', + 'entity_type' => $isFromFile ? 'patient_document' : 'patient_doc', 'entity_id' => (string) $targetRecordId, 'patient_id' => $patientId, 'patient_name' => $this->patientDisplayName($patientRow), 'visit_date' => (string) ($source['visit_date'] ?? date('Y-m-d')), - 'care_context_reference' => (string) ($gatewayPayload['care_context_reference'] ?? ('HDOC-' . $patientId . '-' . $targetRecordId)), + 'care_context_reference' => $resolvedCcRef, 'care_context_display' => (string) ($source['document_title'] ?? 'Health Document'), 'bundle' => (array) ($gatewayPayload['fhir_bundle'] ?? []), ]; diff --git a/app/Controllers/DoctorDocument.php b/app/Controllers/DoctorDocument.php index ad9727c..6b9fa20 100644 --- a/app/Controllers/DoctorDocument.php +++ b/app/Controllers/DoctorDocument.php @@ -2279,11 +2279,16 @@ class DoctorDocument extends BaseController 'hospitalization' => false, ]; + $cleanVisitDate = date('Ymd', strtotime($visitDate)); + $ccRef = 'DOC-' . $patientDocId . '-' . $cleanVisitDate; + return [ 'record_id' => (string) $patientDocId, 'session_id' => (string) $patientDocId, 'visit_date' => $visitDate, 'completed_at' => $issueIso, + 'care_context_reference' => $ccRef, + 'care_context_display' => $docTitle . ' (' . date('d M Y', strtotime($visitDate)) . ')', 'document_title' => $docTitle, 'document_data_base64' => $docDataBase64, 'content_type' => $contentType, @@ -2551,11 +2556,16 @@ class DoctorDocument extends BaseController 'hospitalization' => false, ]; + $cleanVisitDate = date('Ymd', strtotime($visitDate)); + $ccRef = 'DOC-file-' . $fileUploadId . '-' . $cleanVisitDate; + return [ 'record_id' => 'file-' . $fileUploadId, 'session_id' => (string) ($fileRow['opd_id'] ?? $fileUploadId), 'visit_date' => $visitDate, 'completed_at' => $completedAt, + 'care_context_reference' => $ccRef, + 'care_context_display' => $docTitle . ' (' . date('d M Y', strtotime($visitDate)) . ')', 'document_title' => $docTitle, 'document_data_base64' => $bytes !== '' ? base64_encode($bytes) : '', 'content_type' => $mimeType, diff --git a/app/Libraries/Abdm/Fhir/Generators/HealthDocumentFhirGenerator.php b/app/Libraries/Abdm/Fhir/Generators/HealthDocumentFhirGenerator.php index 79893cc..9ef7298 100644 --- a/app/Libraries/Abdm/Fhir/Generators/HealthDocumentFhirGenerator.php +++ b/app/Libraries/Abdm/Fhir/Generators/HealthDocumentFhirGenerator.php @@ -20,8 +20,10 @@ class HealthDocumentFhirGenerator extends AbstractModuleFhirGenerator $docTitle = 'Medical Certificate'; } - $careContextReference = 'DOC-' . $recordId . '-' . $visitDate . '-' . date('His'); - $careContextDisplay = $docTitle . ' (' . $visitDate . ')'; + $cleanDate = str_replace('-', '', $visitDate); + $defaultRef = 'DOC-' . $recordId . '-' . $cleanDate; + $careContextReference = (string) ($source['care_context_reference'] ?? $defaultRef); + $careContextDisplay = (string) ($source['care_context_display'] ?? ($docTitle . ' (' . $visitDate . ')')); $builder = new \App\Libraries\Abdm\Fhir\FhirDocumentBuilder(); diff --git a/app/Views/abdm/fhir_inspector.php b/app/Views/abdm/fhir_inspector.php new file mode 100644 index 0000000..7ccecc6 --- /dev/null +++ b/app/Views/abdm/fhir_inspector.php @@ -0,0 +1,736 @@ + + +
+ +
+
+ + + +
+

+ ABHA Filter & FHIR Inspector + ABDM R4 +

+
Inspect patient care contexts, verify live national gateway links, and preview FHIR R4 clinical bundles
+
+
+
+ + ABDM Task Board + + + + +
+
+ + +
+
+
+
+ +
+ + + + +
+ + + + + + + +
+
+ + + +
+ Quick Inspect: + + + + +
+ +
+
+ + +
+
+ + + +
Select or Search an ABHA Patient
+

+ Enter an ABHA Address (e.g. 91310013085603@sbx), 14-digit ABHA Number, or Patient UHID above, or click any patient from the Quick Inspect bar to view all clinical visits, gateway link status, and inspect FHIR R4 bundles. +

+
+
+ + +
+ + +
+
+
+ +
Patient Demographics
+ M + YOB: - + UHID: - +
+
+ + + +
+
+
+
+
+
ABHA Address
+
+ - + +
+
+
+
14-Digit ABHA Number
+
-
+
+
+
Mobile Number
+
-
+
+
+
Gateway Link Status
+
Unchecked
+
+
+ + +
+
+
+
Total Care Contexts
+
0
+
+
+
+
+
Linked Contexts
+
0
+
+
+
+
+
Unlinked Contexts
+
0
+
+
+
+
+
FHIR Ready Bundles
+
0
+
+
+
+
+
+ + +
+
+
+ Patient Care Contexts & Clinical Bundles + 0 records +
+ +
+ + + + + + + + +
+
+
+
+ + + + + + + + + + + + + + + + +
Care Context RefVisit / Record DescriptionHI TypeFHIR Bundle StatusContext Link StatusActions
+
+ Loading patient care contexts & FHIR status... +
+
+
+
+ +
+
+ + + + + + + + + + diff --git a/app/Views/abdm/task_board.php b/app/Views/abdm/task_board.php index 2dd7f67..b964b8e 100644 --- a/app/Views/abdm/task_board.php +++ b/app/Views/abdm/task_board.php @@ -25,13 +25,16 @@
ABDM operational dashboard and work queues
+ + ABHA Filter & FHIR Inspector + - +
@@ -57,7 +60,12 @@
- ABHA Filter & FHIR Inspector + + ABHA Filter & FHIR Inspector + + [Open Full Page ] + + Filter board & inspect patient care contexts
diff --git a/app/Views/partials/abdm_hip_link_modal.php b/app/Views/partials/abdm_hip_link_modal.php index c45d6ef..5fa6689 100644 --- a/app/Views/partials/abdm_hip_link_modal.php +++ b/app/Views/partials/abdm_hip_link_modal.php @@ -747,6 +747,31 @@ alertBox.innerHTML += '
FHIR Document Stored on Bridge: Wellness bundle is active on Bridge for Care Context ' + (pRes.care_context_reference || firstRef) + '.
To view in PHR App: Open your PHR app (e.g. ABHA App) → tap Linked Facilities → tap "Fetch Records" / "Pull Records".
'; } }).catch(function() {}); + } else if (ctxTaskType === 'health_document_publish') { + var pushData = new URLSearchParams(); + pushData.append('record_id', ctxEntityId); + pushData.append('entity_id', ctxEntityId); + pushData.append('task_id', (window.currentHipTaskContext && window.currentHipTaskContext.taskId) ? window.currentHipTaskContext.taskId : 0); + pushData.append('patient_id', ctxPatientId); + pushData.append('abha_id', abhaAddress); + pushData.append('push_to_gateway', '1'); + if (firstRef) pushData.append('care_context_reference', firstRef); + pushData.append(csrf.name || 'csrf_hms', csrf.hash); + + fetch('', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + 'X-Requested-With': 'XMLHttpRequest', + 'Accept': 'application/json' + }, + body: pushData.toString() + }).then(function(r) { return r.json(); }).then(function(pRes) { + updateCsrf(pRes); + if (pRes && pRes.ok === 1 && alertBox) { + alertBox.innerHTML += '
FHIR Document Stored on Bridge: Health Document bundle is active on Bridge for Care Context ' + (pRes.care_context_reference || firstRef) + '.
To view in PHR App: Open your PHR app (e.g. ABHA App) → tap Linked Facilities → tap "Fetch Records" / "Pull Records".
'; + } + }).catch(function() {}); } } diff --git a/app/Views/partials/sidebar.php b/app/Views/partials/sidebar.php index 27df873..1f9c835 100644 --- a/app/Views/partials/sidebar.php +++ b/app/Views/partials/sidebar.php @@ -460,6 +460,12 @@ if (! $canChargesSettings && $user && method_exists($user, 'inGroup')) { ABDM Task Board +