diff --git a/app/Controllers/AbdmGateway.php b/app/Controllers/AbdmGateway.php index f82900a..ad6110b 100644 --- a/app/Controllers/AbdmGateway.php +++ b/app/Controllers/AbdmGateway.php @@ -2007,11 +2007,11 @@ class AbdmGateway extends BaseController return $this->response->setStatusCode(403)->setJSON(['error' => 'AJAX only']); } - $labReqId = (int) ($this->request->getPost('lab_req_id') ?? 0); - $patientId = (int) ($this->request->getPost('patient_id') ?? 0); - $abhaId = trim((string) ($this->request->getPost('abha_id') ?? '')); - $abhaAddressPost = trim((string) ($this->request->getPost('abha_address') ?? '')); - $consentHandle = trim((string) ($this->request->getPost('consent_handle') ?? '')); + $labReqId = (int) ($this->request->getPost('lab_req_id') ?? $_POST['lab_req_id'] ?? 0); + $patientId = (int) ($this->request->getPost('patient_id') ?? $_POST['patient_id'] ?? 0); + $abhaId = trim((string) ($this->request->getPost('abha_id') ?? $_POST['abha_id'] ?? '')); + $abhaAddressPost = trim((string) ($this->request->getPost('abha_address') ?? $_POST['abha_address'] ?? '')); + $consentHandle = trim((string) ($this->request->getPost('consent_handle') ?? $_POST['consent_handle'] ?? '')); if ($labReqId <= 0 || $patientId <= 0) { return $this->response->setJSON(['ok' => 0, 'error' => 'lab_req_id and patient_id are required']); @@ -2103,14 +2103,16 @@ class AbdmGateway extends BaseController } // -- Load LOINC code for the panel from lab_repo ----------------------- - $labRepoRow = $this->db->table('lab_request lr') - ->select('lr.lab_repo_id, repo.loinc_code AS repo_loinc_code, repo.Title') - ->join('lab_repo repo', 'repo.mstRepoKey = lr.lab_repo_id', 'left') - ->where('lr.id', $labReqId) - ->get(1) - ->getRowArray() ?? []; - - $repoLoincCode = trim((string) ($labRepoRow['repo_loinc_code'] ?? '')); + $repoLoincCode = ''; + if ($this->db->tableExists('lab_repo')) { + $labRepoRow = $this->db->table('lab_request lr') + ->select('lr.lab_repo_id, repo.loinc_code AS repo_loinc_code, repo.Title') + ->join('lab_repo repo', 'repo.mstRepoKey = lr.lab_repo_id', 'left') + ->where('lr.id', $labReqId) + ->get(1) + ->getRowArray() ?? []; + $repoLoincCode = trim((string) ($labRepoRow['repo_loinc_code'] ?? '')); + } if ($repoLoincCode !== '') { $diagnosticReport['loinc_code'] = $repoLoincCode; } @@ -2144,7 +2146,16 @@ class AbdmGateway extends BaseController $bundleJson = (string) json_encode($bundle, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); // -- Store health_record ----------------------------------------------- - $ccRef = 'LAB-' . $labReqId . '-' . $visitDate; + $requestedCcRef = trim((string) ($this->request->getPost('careContextId') ?? $this->request->getPost('care_context_reference') ?? '')); + if ($requestedCcRef !== '') { + $ccRef = $requestedCcRef; + } else { + $prefix = $isImaging ? 'RAD' : 'LAB'; + $dateClean = str_replace('-', '', $visitDate); + $ccRef = $prefix . '-' . $labReqId . '-' . $dateClean; + } + $careContextDisplay = ($isImaging ? 'Radiology Report - ' : 'Diagnostic Report - ') . ($testTitle !== '' ? $testTitle : ($isImaging ? 'Radiology Report' : 'Lab Report')) . ' (' . date('d M Y', strtotime($visitDate)) . ')'; + $healthRecordId = $this->storeHealthRecord([ 'patient_id' => $patientId, 'abha_id' => $abhaId, @@ -2181,6 +2192,7 @@ class AbdmGateway extends BaseController 'status' => 'local_stored', 'queue_id' => null, 'consent_handle' => null, + 'care_context_reference' => $ccRef, 'message' => 'ABHA not available. Record stored locally for later discovery.', ]); } @@ -2200,9 +2212,9 @@ class AbdmGateway extends BaseController 'record_type' => 'DiagnosticReportRecord', 'visit_date' => $visitDate, 'care_context_reference' => $ccRef, - 'care_context_display' => $testTitle !== '' ? $testTitle : 'Lab Report', - 'notes' => $testTitle !== '' ? $testTitle : 'Lab Report', - 'queue_id' => 'LAB-' . $labReqId . '-' . $visitDate, + 'care_context_display' => $careContextDisplay, + 'notes' => $careContextDisplay, + 'queue_id' => $ccRef, 'record_data' => $bundle, ]); $this->logGatewayPushResolution('diagnostic_report', $result); @@ -2235,13 +2247,14 @@ class AbdmGateway extends BaseController ]); return $this->response->setJSON([ - 'ok' => $connectorError === null ? 1 : 0, - 'queue_id' => $queueId, - 'consent_handle' => $effectiveConsent, - 'status' => $connectorError === null ? 'queued' : 'failed', - 'message' => $connectorError === null && $consentWarning !== '' ? 'Record pushed to gateway without active consent. Recheck link status in M2 ABDM Gateway.' : null, - 'warning' => $consentWarning !== '' ? $consentWarning : null, - 'error' => $connectorError, + 'ok' => $connectorError === null ? 1 : 0, + 'queue_id' => $queueId, + 'consent_handle' => $effectiveConsent, + 'care_context_reference' => $ccRef, + 'status' => $connectorError === null ? 'queued' : 'failed', + 'message' => $connectorError === null && $consentWarning !== '' ? 'Record pushed to gateway without active consent. Recheck link status in M2 ABDM Gateway.' : null, + 'warning' => $consentWarning !== '' ? $consentWarning : null, + 'error' => $connectorError, ]); } diff --git a/app/Libraries/Abdm/Sync/AbdmTaskBoardSyncService.php b/app/Libraries/Abdm/Sync/AbdmTaskBoardSyncService.php index f232d41..fe7e1d6 100644 --- a/app/Libraries/Abdm/Sync/AbdmTaskBoardSyncService.php +++ b/app/Libraries/Abdm/Sync/AbdmTaskBoardSyncService.php @@ -726,14 +726,16 @@ class AbdmTaskBoardSyncService $visitDate = date('Y-m-d'); $prefix = match ($taskType) { - 'lab_report_publish', 'radiology_report_publish' => 'LAB-', + 'radiology_report_publish' => 'RAD-', + 'lab_report_publish' => 'LAB-', 'immunization_record_publish' => 'IMM-', - 'wellness_record_publish' => 'WELLNESS-', - 'health_document_publish' => 'DOC-', - 'ipd_discharge_publish' => 'IPD-', + 'wellness_record_publish' => 'WELLNESS-', + 'health_document_publish' => 'DOC-', + 'ipd_discharge_publish' => 'DISCHARGE-', default => 'REC-', }; - $careContextRef = $prefix . $entityId . '-' . $visitDate; + $cleanVisitDate = str_replace('-', '', $visitDate); + $careContextRef = $prefix . $entityId . '-' . $cleanVisitDate; $careContextDisplay = $hiType . ' ' . $visitDate; $pushData = [ diff --git a/app/Views/partials/abdm_hip_link_modal.php b/app/Views/partials/abdm_hip_link_modal.php index 5cd831f..532674c 100644 --- a/app/Views/partials/abdm_hip_link_modal.php +++ b/app/Views/partials/abdm_hip_link_modal.php @@ -596,6 +596,64 @@ modal.show(); }; + function syncAndPushTaskFhirRecord(selectedContexts, alertBox, abhaAddress) { + if (!window.currentHipTaskContext || !window.currentHipTaskContext.taskType || !window.currentHipTaskContext.entityId) { + return; + } + var ctxTaskType = window.currentHipTaskContext.taskType; + var ctxEntityId = window.currentHipTaskContext.entityId; + var ctxPatientId = window.currentHipTaskContext.patientId || 0; + var firstRef = (selectedContexts && selectedContexts.length > 0) ? selectedContexts[0].ref : ''; + var csrf = getCsrfData(); + + if (ctxTaskType === 'radiology_report_publish' || ctxTaskType === 'lab_report_publish') { + var pushData = new URLSearchParams(); + pushData.append('lab_req_id', ctxEntityId); + pushData.append('patient_id', ctxPatientId); + pushData.append('abha_id', abhaAddress); + 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: Report 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 === 'opd_prescription_publish') { + var pushData = new URLSearchParams(); + pushData.append('opd_id', ctxEntityId); + 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: Consultation 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() {}); + } + } + /** * Execute 2-step HIP-Initiated Linking via Method 4. */ @@ -665,6 +723,8 @@ + 'The selected care context(s) are already linked with this patient\'s ABHA. Patient can view and fetch the records directly in their PHR app.'; alertBox.classList.remove('d-none'); + syncAndPushTaskFhirRecord(selectedContexts, alertBox, abhaAddress); + // If initiated for a specific task board task, sync task to completed so status isn't stuck on Ready to link! if (window.currentHipTaskContext && window.currentHipTaskContext.taskId > 0) { var tId = window.currentHipTaskContext.taskId; @@ -783,6 +843,8 @@ alertBox.innerHTML = 'Success! ' + 'Care contexts successfully submitted to ABDM for linking. Patient can now discover and access them in their ABHA / PHR app.'; + syncAndPushTaskFhirRecord(selectedContexts, alertBox, abhaAddress); + // If initiated for a specific task board task, mark the task as completed if (window.currentHipTaskContext && window.currentHipTaskContext.taskId > 0) { var tId = window.currentHipTaskContext.taskId; @@ -832,6 +894,8 @@ alertBox.innerHTML = 'Already Linked! ' + 'ABDM reports this care context is already linked to patient\'s ABHA. Task status updated to Linked.'; + syncAndPushTaskFhirRecord(selectedContexts, alertBox, abhaAddress); + if (window.currentHipTaskContext && window.currentHipTaskContext.taskId > 0) { var tId = window.currentHipTaskContext.taskId; var taskNote = 'Care context confirmed already linked in ABDM: ' + selectedContexts.map(function(c) { return c.ref; }).join(', '); diff --git a/tests/unit/Abdm/CareContextDiscoveryTest.php b/tests/unit/Abdm/CareContextDiscoveryTest.php index d53d154..3c53525 100644 --- a/tests/unit/Abdm/CareContextDiscoveryTest.php +++ b/tests/unit/Abdm/CareContextDiscoveryTest.php @@ -20,6 +20,7 @@ final class CareContextDiscoveryTest extends CIUnitTestCase $db->query("CREATE TABLE IF NOT EXISTS " . $db->prefixTable('lab_request') . " ( id INTEGER PRIMARY KEY, patient_id INTEGER, + patient_name TEXT, lab_type INTEGER, charge_id INTEGER, report_name TEXT, @@ -33,7 +34,8 @@ final class CareContextDiscoveryTest extends CIUnitTestCase $db->query("CREATE TABLE IF NOT EXISTS " . $db->prefixTable('charge_master') . " ( id INTEGER PRIMARY KEY, - charge_name TEXT + charge_name TEXT, + charge_type TEXT )"); $db->query("CREATE TABLE IF NOT EXISTS " . $db->prefixTable('opd_prescription') . " ( @@ -49,7 +51,12 @@ final class CareContextDiscoveryTest extends CIUnitTestCase patient_id INTEGER, abha_id TEXT, hi_type TEXT, + entity_type TEXT, + entity_id TEXT, + fhir_bundle TEXT, care_context_reference TEXT, + consent_handle TEXT, + push_status TEXT, created_at TEXT, updated_at TEXT )"); @@ -280,4 +287,41 @@ final class CareContextDiscoveryTest extends CIUnitTestCase $this->assertTrue($foundIpd, 'IPD DischargeSummaryRecord should be discovered'); $this->assertTrue($foundImm, 'ImmunizationRecord should be discovered'); } + + public function testShareDiagnosisReportBundleFormatsRadiologyCareContext(): void + { + $_SERVER['HTTP_X_REQUESTED_WITH'] = 'xmlhttprequest'; + $_POST['lab_req_id'] = '31'; + $_POST['patient_id'] = '12'; + $_POST['abha_id'] = 'singhkeshav301976@sbx'; + $_POST['care_context_reference'] = 'RAD-31-20260929'; + + $req = service('request'); + $req->setHeader('X-Requested-With', 'XMLHttpRequest'); + $this->gateway->initController($req, service('response'), service('logger')); + + $mockConnector = $this->createMock(\App\Libraries\Abdm\AbdmConnectorInterface::class); + $mockConnector->method('pushRecord') + ->willReturnCallback(function (array $data) { + return [ + 'ok' => 1, + 'http_code' => 201, + 'status' => 'queued', + 'queue_id' => $data['care_context_reference'], + 'record_id' => 999, + ]; + }); + + $refGateway = new ReflectionClass($this->gateway); + $prop = $refGateway->getProperty('connector'); + $prop->setAccessible(true); + $prop->setValue($this->gateway, $mockConnector); + + $res = $this->gateway->shareDiagnosisReportBundle(); + $body = json_decode($res->getBody(), true); + + $this->assertSame(1, $body['ok'] ?? 0); + $this->assertSame('RAD-31-20260929', $body['care_context_reference'] ?? ''); + } } +