Store ABDM bundles in health records
This commit is contained in:
parent
45abd3ca76
commit
c692340bb2
|
|
@ -1735,16 +1735,21 @@ class AbdmGateway extends BaseController
|
||||||
$abhaId = trim((string) $this->request->getPost('abha_id'));
|
$abhaId = trim((string) $this->request->getPost('abha_id'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decrypt the stored payload (JSON encoded full push payload, may contain 'bundle' key)
|
// Prefer the plain stored bundle; fall back to the encrypted copy for older rows.
|
||||||
$storedPayload = [];
|
$storedPayload = [];
|
||||||
$encPayload = trim((string) ($hr['fhir_bundle_enc'] ?? ''));
|
$plainPayload = trim((string) ($hr['record_data'] ?? ''));
|
||||||
if ($encPayload !== '') {
|
if ($plainPayload !== '') {
|
||||||
try {
|
$storedPayload = json_decode($plainPayload, true) ?? [];
|
||||||
$enc = new \App\Libraries\FhirEncryptionService();
|
} else {
|
||||||
$decrypted = $enc->decrypt($encPayload);
|
$encPayload = trim((string) ($hr['fhir_bundle_enc'] ?? ''));
|
||||||
$storedPayload = json_decode($decrypted, true) ?? [];
|
if ($encPayload !== '') {
|
||||||
} catch (\Throwable $e) {
|
try {
|
||||||
return $this->response->setStatusCode(500)->setJSON(['ok' => 0, 'error_text' => 'Could not decrypt FHIR bundle: ' . $e->getMessage()]);
|
$enc = new \App\Libraries\FhirEncryptionService();
|
||||||
|
$decrypted = $enc->decrypt($encPayload);
|
||||||
|
$storedPayload = json_decode($decrypted, true) ?? [];
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return $this->response->setStatusCode(500)->setJSON(['ok' => 0, 'error_text' => 'Could not decrypt FHIR bundle: ' . $e->getMessage()]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2378,7 +2383,8 @@ class AbdmGateway extends BaseController
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Persist an encrypted FHIR bundle to health_records and return the new row id.
|
* Persist a FHIR bundle to health_records and return the new row id.
|
||||||
|
* Stores both a plain-text copy in record_data and an encrypted copy in fhir_bundle_enc.
|
||||||
* Returns 0 when the table is absent or on any DB error (fail-open).
|
* Returns 0 when the table is absent or on any DB error (fail-open).
|
||||||
*
|
*
|
||||||
* @param array{patient_id: int, abha_id: string, hi_type: string, entity_type: string,
|
* @param array{patient_id: int, abha_id: string, hi_type: string, entity_type: string,
|
||||||
|
|
@ -2407,6 +2413,7 @@ class AbdmGateway extends BaseController
|
||||||
'hi_type' => (string) ($data['hi_type'] ?? 'unknown'),
|
'hi_type' => (string) ($data['hi_type'] ?? 'unknown'),
|
||||||
'entity_type' => (string) ($data['entity_type'] ?? ''),
|
'entity_type' => (string) ($data['entity_type'] ?? ''),
|
||||||
'entity_id' => (string) ($data['entity_id'] ?? ''),
|
'entity_id' => (string) ($data['entity_id'] ?? ''),
|
||||||
|
'record_data' => $rawBundle !== '' ? $rawBundle : null,
|
||||||
'fhir_bundle_enc' => $encPayload !== '' ? $encPayload : null,
|
'fhir_bundle_enc' => $encPayload !== '' ? $encPayload : null,
|
||||||
'push_status' => 'queued',
|
'push_status' => 'queued',
|
||||||
'push_at' => $now,
|
'push_at' => $now,
|
||||||
|
|
|
||||||
|
|
@ -5189,7 +5189,7 @@ class Opd_prescription extends BaseController
|
||||||
// Submit button
|
// Submit button
|
||||||
. 'document.getElementById("btnSubmit").addEventListener("click",function(){'
|
. 'document.getElementById("btnSubmit").addEventListener("click",function(){'
|
||||||
. ' const btn=this,origHtml=btn.innerHTML;'
|
. ' const btn=this,origHtml=btn.innerHTML;'
|
||||||
. ' if(!confirm("Submit FHIR bundle for OPD #' . $opdId . ' to ABDM bridge?"))return;'
|
. ' if(!confirm("Submit FHIR bundle for OPD #' . $opdId . ' to ABDM gateway?"))return;'
|
||||||
. ' btn.disabled=true;btn.innerHTML=\'<span class="spinner-border spinner-border-sm me-1"></span>Submitting\u2026\';'
|
. ' btn.disabled=true;btn.innerHTML=\'<span class="spinner-border spinner-border-sm me-1"></span>Submitting\u2026\';'
|
||||||
. ' const body=new URLSearchParams({opd_id:CTX.opdId,opd_session_id:CTX.sessionId});'
|
. ' const body=new URLSearchParams({opd_id:CTX.opdId,opd_session_id:CTX.sessionId});'
|
||||||
. ' body.append(CSRF_NAME,CSRF_HASH);'
|
. ' body.append(CSRF_NAME,CSRF_HASH);'
|
||||||
|
|
@ -5337,54 +5337,41 @@ class Opd_prescription extends BaseController
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$connector = new \App\Libraries\Abdm\EAtriaBridgeConnector();
|
$gatewayUrl = base_url('AbdmGateway/share_prescription_bundle');
|
||||||
|
$cookieHeader = (string) $this->request->getHeaderLine('Cookie');
|
||||||
|
|
||||||
$visitLabel = $visitDate !== '' ? date('d M Y', strtotime($visitDate)) : date('d M Y');
|
$client = service('curlrequest', [
|
||||||
$displayLabel = 'OPD Visit ' . $visitLabel . ($doctorName !== '' ? ' - Dr. ' . $doctorName : '');
|
'timeout' => 60,
|
||||||
|
'http_errors' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
// Keep a stable OPD visit reference keyed by visit date for ABDM care-context linkage.
|
$response = $client->post($gatewayUrl, [
|
||||||
$ccRef = 'OPD-' . $opdId . '-' . $visitDate;
|
'headers' => array_filter([
|
||||||
|
'X-Requested-With' => 'XMLHttpRequest',
|
||||||
|
'Cookie' => $cookieHeader !== '' ? $cookieHeader : null,
|
||||||
|
]),
|
||||||
|
'form_params' => [
|
||||||
|
'opd_id' => $opdId,
|
||||||
|
'opd_session_id' => $sessionId,
|
||||||
|
'patient_id' => $patientId,
|
||||||
|
'abha_id' => $abhaId,
|
||||||
|
'consent_handle' => (string) ($this->request->getPost('consent_handle') ?? ''),
|
||||||
|
csrf_token() => csrf_hash(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
$pushData = [
|
$httpCode = (int) $response->getStatusCode();
|
||||||
'patient_id' => (string) $patientId,
|
$result = json_decode((string) $response->getBody(), true);
|
||||||
'patient_name' => $patientName,
|
if (! is_array($result)) {
|
||||||
'record_type' => 'OPConsultRecord',
|
$result = [];
|
||||||
'hi_type' => 'OPConsultRecord',
|
|
||||||
'visit_date' => $visitDate,
|
|
||||||
'care_context_reference' => $ccRef,
|
|
||||||
'care_context_display' => $displayLabel,
|
|
||||||
'notes' => $displayLabel,
|
|
||||||
'queue_id' => $ccRef,
|
|
||||||
'abha_address' => $abhaId,
|
|
||||||
'record_data' => $bundle,
|
|
||||||
];
|
|
||||||
if ($doctorName !== '') {
|
|
||||||
$pushData['doctor_name'] = $doctorName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = $connector->pushRecord($pushData);
|
$ok = (int) ($result['ok'] ?? 0);
|
||||||
$httpCode = (int) ($result['http_code'] ?? 0);
|
$queueId = (string) ($result['queue_id'] ?? '');
|
||||||
|
$bridgeId = (int) ($result['bridge_id'] ?? $result['id'] ?? 0);
|
||||||
// 409 = duplicate queue_id — record already stored in bridge
|
$msg = (string) ($result['message'] ?? $result['error_text'] ?? ($ok === 1
|
||||||
if ($httpCode === 409) {
|
? 'FHIR bundle submitted to ABDM gateway successfully.'
|
||||||
return $this->response->setJSON([
|
: 'Submission failed. Check gateway configuration.'));
|
||||||
'ok' => 1,
|
|
||||||
'duplicate' => 1,
|
|
||||||
'bridge_id' => (int) ($result['id'] ?? 0),
|
|
||||||
'queue_id' => (string) ($result['queue_id'] ?? ''),
|
|
||||||
'message' => 'Record already stored in bridge (duplicate queue_id). Use existing record.',
|
|
||||||
'http_code' => $httpCode,
|
|
||||||
'csrfName' => csrf_token(),
|
|
||||||
'csrfHash' => csrf_hash(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$ok = (int) ($result['ok'] ?? 0);
|
|
||||||
$queueId = (string) ($result['queue_id'] ?? '');
|
|
||||||
$bridgeId = (int) ($result['id'] ?? 0);
|
|
||||||
$msg = (string) ($result['message'] ?? ($ok === 1
|
|
||||||
? 'FHIR bundle submitted to ABDM bridge successfully.'
|
|
||||||
: ($result['error_text'] ?? 'Submission failed. Check bridge configuration.')));
|
|
||||||
|
|
||||||
return $this->response->setJSON([
|
return $this->response->setJSON([
|
||||||
'ok' => $ok,
|
'ok' => $ok,
|
||||||
|
|
@ -5399,7 +5386,7 @@ class Opd_prescription extends BaseController
|
||||||
log_message('error', '[fhir_bundle_submit] OPD#' . $opdId . ': ' . $e->getMessage());
|
log_message('error', '[fhir_bundle_submit] OPD#' . $opdId . ': ' . $e->getMessage());
|
||||||
return $this->response->setJSON([
|
return $this->response->setJSON([
|
||||||
'ok' => 0,
|
'ok' => 0,
|
||||||
'message' => 'Bridge connection failed: ' . $e->getMessage(),
|
'message' => 'Gateway handoff failed: ' . $e->getMessage(),
|
||||||
'csrfName' => csrf_token(),
|
'csrfName' => csrf_token(),
|
||||||
'csrfHash' => csrf_hash(),
|
'csrfHash' => csrf_hash(),
|
||||||
]);
|
]);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Database\Migrations;
|
||||||
|
|
||||||
|
use App\Libraries\FhirEncryptionService;
|
||||||
|
use CodeIgniter\Database\Migration;
|
||||||
|
|
||||||
|
class AddRecordDataToHealthRecords extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
if (! $this->db->tableExists('health_records')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fields = $this->db->getFieldNames('health_records');
|
||||||
|
if (! in_array('record_data', $fields, true)) {
|
||||||
|
$this->forge->addColumn('health_records', [
|
||||||
|
'record_data' => [
|
||||||
|
'type' => 'LONGTEXT',
|
||||||
|
'null' => true,
|
||||||
|
'after' => 'fhir_bundle_enc',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$fields = $this->db->getFieldNames('health_records');
|
||||||
|
if (! in_array('fhir_bundle_enc', $fields, true) || ! in_array('record_data', $fields, true)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! FhirEncryptionService::isSupported()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$enc = new FhirEncryptionService();
|
||||||
|
$rows = $this->db->table('health_records')
|
||||||
|
->select('id, fhir_bundle_enc, record_data')
|
||||||
|
->where('record_data IS NULL', null, false)
|
||||||
|
->orWhere('record_data', '')
|
||||||
|
->get()
|
||||||
|
->getResultArray();
|
||||||
|
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$encoded = trim((string) ($row['fhir_bundle_enc'] ?? ''));
|
||||||
|
if ($encoded === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$plaintext = $enc->decrypt($encoded);
|
||||||
|
} catch (\Throwable) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($plaintext === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->db->table('health_records')
|
||||||
|
->where('id', (int) ($row['id'] ?? 0))
|
||||||
|
->update(['record_data' => $plaintext]);
|
||||||
|
}
|
||||||
|
} catch (\Throwable) {
|
||||||
|
// Fail-open: the new column is the important part; backfill is best-effort.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
if (! $this->db->tableExists('health_records')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fields = $this->db->getFieldNames('health_records');
|
||||||
|
if (in_array('record_data', $fields, true)) {
|
||||||
|
$this->forge->dropColumn('health_records', 'record_data');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user