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'));
|
||||
}
|
||||
|
||||
// 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 = [];
|
||||
$encPayload = trim((string) ($hr['fhir_bundle_enc'] ?? ''));
|
||||
if ($encPayload !== '') {
|
||||
try {
|
||||
$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()]);
|
||||
$plainPayload = trim((string) ($hr['record_data'] ?? ''));
|
||||
if ($plainPayload !== '') {
|
||||
$storedPayload = json_decode($plainPayload, true) ?? [];
|
||||
} else {
|
||||
$encPayload = trim((string) ($hr['fhir_bundle_enc'] ?? ''));
|
||||
if ($encPayload !== '') {
|
||||
try {
|
||||
$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).
|
||||
*
|
||||
* @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'),
|
||||
'entity_type' => (string) ($data['entity_type'] ?? ''),
|
||||
'entity_id' => (string) ($data['entity_id'] ?? ''),
|
||||
'record_data' => $rawBundle !== '' ? $rawBundle : null,
|
||||
'fhir_bundle_enc' => $encPayload !== '' ? $encPayload : null,
|
||||
'push_status' => 'queued',
|
||||
'push_at' => $now,
|
||||
|
|
|
|||
|
|
@ -5189,7 +5189,7 @@ class Opd_prescription extends BaseController
|
|||
// Submit button
|
||||
. 'document.getElementById("btnSubmit").addEventListener("click",function(){'
|
||||
. ' 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\';'
|
||||
. ' const body=new URLSearchParams({opd_id:CTX.opdId,opd_session_id:CTX.sessionId});'
|
||||
. ' body.append(CSRF_NAME,CSRF_HASH);'
|
||||
|
|
@ -5337,54 +5337,41 @@ class Opd_prescription extends BaseController
|
|||
}
|
||||
|
||||
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');
|
||||
$displayLabel = 'OPD Visit ' . $visitLabel . ($doctorName !== '' ? ' - Dr. ' . $doctorName : '');
|
||||
$client = service('curlrequest', [
|
||||
'timeout' => 60,
|
||||
'http_errors' => false,
|
||||
]);
|
||||
|
||||
// Keep a stable OPD visit reference keyed by visit date for ABDM care-context linkage.
|
||||
$ccRef = 'OPD-' . $opdId . '-' . $visitDate;
|
||||
$response = $client->post($gatewayUrl, [
|
||||
'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 = [
|
||||
'patient_id' => (string) $patientId,
|
||||
'patient_name' => $patientName,
|
||||
'record_type' => 'OPConsultRecord',
|
||||
'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;
|
||||
$httpCode = (int) $response->getStatusCode();
|
||||
$result = json_decode((string) $response->getBody(), true);
|
||||
if (! is_array($result)) {
|
||||
$result = [];
|
||||
}
|
||||
|
||||
$result = $connector->pushRecord($pushData);
|
||||
$httpCode = (int) ($result['http_code'] ?? 0);
|
||||
|
||||
// 409 = duplicate queue_id — record already stored in bridge
|
||||
if ($httpCode === 409) {
|
||||
return $this->response->setJSON([
|
||||
'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.')));
|
||||
$ok = (int) ($result['ok'] ?? 0);
|
||||
$queueId = (string) ($result['queue_id'] ?? '');
|
||||
$bridgeId = (int) ($result['bridge_id'] ?? $result['id'] ?? 0);
|
||||
$msg = (string) ($result['message'] ?? $result['error_text'] ?? ($ok === 1
|
||||
? 'FHIR bundle submitted to ABDM gateway successfully.'
|
||||
: 'Submission failed. Check gateway configuration.'));
|
||||
|
||||
return $this->response->setJSON([
|
||||
'ok' => $ok,
|
||||
|
|
@ -5399,7 +5386,7 @@ class Opd_prescription extends BaseController
|
|||
log_message('error', '[fhir_bundle_submit] OPD#' . $opdId . ': ' . $e->getMessage());
|
||||
return $this->response->setJSON([
|
||||
'ok' => 0,
|
||||
'message' => 'Bridge connection failed: ' . $e->getMessage(),
|
||||
'message' => 'Gateway handoff failed: ' . $e->getMessage(),
|
||||
'csrfName' => csrf_token(),
|
||||
'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