fix(abdm): sync care context reference format and auto-push FHIR bundle on linking
- Align care context reference format (RAD- prefix for radiology, clean Ymd date) between discovery, shareDiagnosisReportBundle, and AbdmTaskBoardSyncService - Automatically push FHIR document bundle to Bridge (POST /api/v3/records/push) on care context linking or already-linked confirmation in abdm_hip_link_modal - Provide clear instructions to user regarding PHR app 'Fetch Records' pull flow - Add unit test testShareDiagnosisReportBundleFormatsRadiologyCareContext
This commit is contained in:
parent
41d89a1969
commit
1a21f16f7b
|
|
@ -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,
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = [
|
||||
|
|
|
|||
|
|
@ -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('<?= base_url('AbdmGateway/share_diagnosis_report_bundle') ?>', {
|
||||
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 += '<div class="mt-2 pt-2 border-top border-success-subtle text-dark small"><i class="bi bi-file-earmark-medical text-success me-1"></i><strong>FHIR Document Stored on Bridge:</strong> Report bundle is active on Bridge for Care Context <code>' + (pRes.care_context_reference || firstRef) + '</code>.<br><i class="bi bi-info-circle text-primary me-1"></i><strong>To view in PHR App:</strong> Open your PHR app (e.g. ABHA App) → tap <em>Linked Facilities</em> → tap <em>"Fetch Records" / "Pull Records"</em>.</div>';
|
||||
}
|
||||
}).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('<?= base_url('AbdmGateway/share_prescription_bundle') ?>', {
|
||||
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 += '<div class="mt-2 pt-2 border-top border-success-subtle text-dark small"><i class="bi bi-file-earmark-medical text-success me-1"></i><strong>FHIR Document Stored on Bridge:</strong> Consultation bundle is active on Bridge for Care Context <code>' + (pRes.care_context_reference || firstRef) + '</code>.<br><i class="bi bi-info-circle text-primary me-1"></i><strong>To view in PHR App:</strong> Open your PHR app (e.g. ABHA App) → tap <em>Linked Facilities</em> → tap <em>"Fetch Records" / "Pull Records"</em>.</div>';
|
||||
}
|
||||
}).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 = '<strong><i class="bi bi-check-circle-fill me-1"></i>Success!</strong> '
|
||||
+ '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 = '<strong><i class="bi bi-check-circle-fill me-1"></i>Already Linked!</strong> '
|
||||
+ '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(', ');
|
||||
|
|
|
|||
|
|
@ -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'] ?? '');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user