Harden ABDM health record push payloads

This commit is contained in:
DevSoftTech Server 2026-06-01 13:23:54 +05:30
parent 07ed8ff305
commit e8ec06e8f8
3 changed files with 101 additions and 55 deletions

View File

@ -823,11 +823,13 @@ class AbdmGateway extends BaseController
'patient_name' => $patName, 'patient_name' => $patName,
'abha_id' => $abhaId, 'abha_id' => $abhaId,
'hi_type' => 'DischargeSummaryRecord', 'hi_type' => 'DischargeSummaryRecord',
'record_type' => 'discharge_summary', 'record_type' => 'DischargeSummaryRecord',
'visit_date' => $visitDate, 'visit_date' => $visitDate,
'doctor_name' => $doctorName, 'doctor_name' => $doctorName,
'care_context_reference' => $ccRef, 'care_context_reference' => $ccRef,
'care_context_display' => 'Discharge Summary - ' . ($ipdCode !== '' ? 'IPD#' . $ipdCode : 'IPD#' . $ipdId),
'notes' => 'Discharge Summary - ' . ($ipdCode !== '' ? 'IPD#' . $ipdCode : 'IPD#' . $ipdId), 'notes' => 'Discharge Summary - ' . ($ipdCode !== '' ? 'IPD#' . $ipdCode : 'IPD#' . $ipdId),
'queue_id' => 'IPD-' . $ipdId . '-' . $visitDate,
'record_data' => $bundle, 'record_data' => $bundle,
]); ]);
$queueId = (string) ($result['queue_id'] ?? ''); $queueId = (string) ($result['queue_id'] ?? '');
@ -1014,10 +1016,12 @@ class AbdmGateway extends BaseController
'patient_name' => $patName, 'patient_name' => $patName,
'abha_id' => $abhaId, 'abha_id' => $abhaId,
'hi_type' => 'DiagnosticReportRecord', 'hi_type' => 'DiagnosticReportRecord',
'record_type' => 'lab_report', 'record_type' => 'DiagnosticReportRecord',
'visit_date' => $visitDate, 'visit_date' => $visitDate,
'care_context_reference' => $ccRef, 'care_context_reference' => $ccRef,
'care_context_display' => $testTitle !== '' ? $testTitle : 'Lab Report',
'notes' => $testTitle !== '' ? $testTitle : 'Lab Report', 'notes' => $testTitle !== '' ? $testTitle : 'Lab Report',
'queue_id' => 'LAB-' . $labReqId . '-' . $visitDate,
'record_data' => $bundle, 'record_data' => $bundle,
]); ]);
$queueId = (string) ($result['queue_id'] ?? ''); $queueId = (string) ($result['queue_id'] ?? '');
@ -1769,7 +1773,9 @@ class AbdmGateway extends BaseController
'doctor_name' => $doctorName, 'doctor_name' => $doctorName,
'department' => $department, 'department' => $department,
'care_context_reference' => trim((string) ($hr['care_context_reference'] ?? '')), 'care_context_reference' => trim((string) ($hr['care_context_reference'] ?? '')),
'care_context_display' => trim((string) ($storedPayload['care_context_display'] ?? '')),
'notes' => trim((string) ($storedPayload['care_context_display'] ?? '')), 'notes' => trim((string) ($storedPayload['care_context_display'] ?? '')),
'queue_id' => trim((string) ($hr['care_context_reference'] ?? '')),
'record_data' => $fhirBundle, 'record_data' => $fhirBundle,
]); ]);
$queueId = $result['queue_id'] ?? null; $queueId = $result['queue_id'] ?? null;
@ -2958,17 +2964,19 @@ class AbdmGateway extends BaseController
} }
/** /**
* Maps HMS internal ABDM hi_type strings to bridge API record_type enum values. * Maps HMS inputs to official ABDM HI record types.
* Bridge accepts: prescription | lab_report | discharge_summary | wellness_record | health_document
*/ */
private function mapHiTypeToRecordType(string $hiType): string private function mapHiTypeToRecordType(string $hiType): string
{ {
return match (strtolower($hiType)) { return match (strtolower($hiType)) {
'opconsultrecord', 'prescriptionrecord' => 'prescription', 'opconsultrecord', 'prescriptionrecord', 'prescription' => 'PrescriptionRecord',
'diagnosticreportrecord' => 'lab_report', 'diagnosticreportrecord', 'lab_report', 'diagnosticreport' => 'DiagnosticReportRecord',
'dischargesummaryrecord' => 'discharge_summary', 'dischargesummaryrecord', 'discharge_summary', 'dischargesummary' => 'DischargeSummaryRecord',
'wellnessrecord' => 'wellness_record', 'wellnessrecord' => 'WellnessRecord',
default => 'health_document', 'immunizationrecord' => 'ImmunizationRecord',
'invoicerecord' => 'InvoiceRecord',
'healthdocumentrecord', 'health_document' => 'HealthDocumentRecord',
default => 'HealthDocumentRecord',
}; };
} }

View File

@ -5281,28 +5281,39 @@ class Opd_prescription extends BaseController
} }
} }
if ($abhaId === '') {
return $this->response->setJSON([
'ok' => 0,
'error_text' => 'ABHA address is required before pushing to ABDM. Update the patient record first.',
'csrfName' => csrf_token(),
'csrfHash' => csrf_hash(),
]);
}
try { try {
$connector = new \App\Libraries\Abdm\EAtriaBridgeConnector(); $connector = new \App\Libraries\Abdm\EAtriaBridgeConnector();
$visitLabel = $visitDate !== '' ? date('d M Y', strtotime($visitDate)) : date('d M Y'); $visitLabel = $visitDate !== '' ? date('d M Y', strtotime($visitDate)) : date('d M Y');
$displayLabel = 'OPD Consultation - ' . $visitLabel . ($doctorName !== '' ? ' - Dr. ' . $doctorName : ''); $displayLabel = 'OPD Visit ' . $visitLabel . ($doctorName !== '' ? ' - Dr. ' . $doctorName : '');
// care_context_reference must be unique per visit in ABDM spec. // care_context_reference must be unique per visit in ABDM spec.
// Use OPD-{id}-{date} so different visit dates generate distinct care contexts. // Use OPD-{id}-{session} so the reference stays stable for a specific saved record.
$ccRef = 'OPD-' . $opdId . '-' . ($visitDate !== '' ? $visitDate : date('Y-m-d')); $ccRef = 'OPD-' . $opdId . '-' . $recordId;
$pushData = [ $pushData = [
'patient_id' => (string) $patientId, 'patient_id' => (string) $patientId,
'patient_name' => $patientName, 'patient_name' => $patientName,
'record_type' => 'prescription', 'record_type' => 'PrescriptionRecord',
'hi_type' => 'PrescriptionRecord',
'visit_date' => $visitDate, 'visit_date' => $visitDate,
'hospital_id' => (string) $opdId,
'care_context_reference' => $ccRef, 'care_context_reference' => $ccRef,
'care_context_display' => $displayLabel,
'notes' => $displayLabel, 'notes' => $displayLabel,
'queue_id' => 'OPD-' . $opdId . '-' . $recordId,
'abha_address' => $abhaId,
'record_data' => $bundle, 'record_data' => $bundle,
]; ];
if ($abhaId !== '') {
$pushData['abha_address'] = $abhaId;
}
if ($doctorName !== '') { if ($doctorName !== '') {
$pushData['doctor_name'] = $doctorName; $pushData['doctor_name'] = $doctorName;
} }

View File

@ -21,6 +21,7 @@ class EAtriaBridgeConnector implements AbdmConnectorInterface
private string $baseUrl; private string $baseUrl;
private string $token; private string $token;
private string $hfrId; private string $hfrId;
private string $hospitalId;
private int $timeoutSec; private int $timeoutSec;
public function __construct() public function __construct()
@ -30,6 +31,7 @@ class EAtriaBridgeConnector implements AbdmConnectorInterface
$this->baseUrl = rtrim((string) ($config->eatriaBridgeUrl ?? 'https://abdm-bridge.e-atria.in/api'), '/'); $this->baseUrl = rtrim((string) ($config->eatriaBridgeUrl ?? 'https://abdm-bridge.e-atria.in/api'), '/');
$this->token = (string) ($config->eatriaBridgeToken ?? ''); $this->token = (string) ($config->eatriaBridgeToken ?? '');
$this->hfrId = ''; $this->hfrId = '';
$this->hospitalId = '';
$this->timeoutSec = (int) ($config->eatriaBridgeTimeoutSec ?? 30); $this->timeoutSec = (int) ($config->eatriaBridgeTimeoutSec ?? 30);
// DB (Admin Panel → ABDM Gateway Config) is the authoritative source for // DB (Admin Panel → ABDM Gateway Config) is the authoritative source for
@ -41,7 +43,7 @@ class EAtriaBridgeConnector implements AbdmConnectorInterface
if ($db->tableExists('hospital_setting')) { if ($db->tableExists('hospital_setting')) {
$rows = $db->table('hospital_setting') $rows = $db->table('hospital_setting')
->select('s_name, s_value') ->select('s_name, s_value')
->whereIn('s_name', ['EATRIA_BRIDGE_TOKEN', 'EATRIA_BRIDGE_URL', 'ABDM_HFR_ID']) ->whereIn('s_name', ['EATRIA_BRIDGE_TOKEN', 'EATRIA_BRIDGE_URL', 'ABDM_HFR_ID', 'ABDM_BRIDGE_HOSPITAL_ID', 'HOSPITAL_ID'])
->get() ->get()
->getResultArray(); ->getResultArray();
@ -56,6 +58,11 @@ class EAtriaBridgeConnector implements AbdmConnectorInterface
if (! empty($dbSettings['ABDM_HFR_ID'])) { if (! empty($dbSettings['ABDM_HFR_ID'])) {
$this->hfrId = trim($dbSettings['ABDM_HFR_ID']); $this->hfrId = trim($dbSettings['ABDM_HFR_ID']);
} }
if (! empty($dbSettings['ABDM_BRIDGE_HOSPITAL_ID'])) {
$this->hospitalId = trim($dbSettings['ABDM_BRIDGE_HOSPITAL_ID']);
} elseif (! empty($dbSettings['HOSPITAL_ID'])) {
$this->hospitalId = trim($dbSettings['HOSPITAL_ID']);
}
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
// DB unavailable — continue with config/env values // DB unavailable — continue with config/env values
@ -351,59 +358,75 @@ class EAtriaBridgeConnector implements AbdmConnectorInterface
public function pushRecord(array $data): array public function pushRecord(array $data): array
{ {
// Normalize to gateway-enforced HI types for /v3/records/push. $recordTypeRaw = (string) ($data['record_type'] ?? '');
// Runtime valid_types include *Record variants.
$hiTypeRaw = (string) ($data['hi_type'] ?? ''); $hiTypeRaw = (string) ($data['hi_type'] ?? '');
$hiType = match ($hiTypeRaw) {
'OPConsultRecord', 'OPConsultation' => 'OPConsultRecord', $normalizeHiType = static function (string $value): string {
'PrescriptionRecord', 'Prescription' => 'PrescriptionRecord', return match ($value) {
'DiagnosticReportRecord', 'DiagnosticReport' => 'DiagnosticReportRecord', 'OPConsultation', 'OPConsultRecord', 'Prescription', 'PrescriptionRecord' => 'PrescriptionRecord',
'DischargeSummaryRecord', 'DischargeSummary' => 'DischargeSummaryRecord', 'DiagnosticReport', 'DiagnosticReportRecord', 'Lab_Report', 'lab_report' => 'DiagnosticReportRecord',
'WellnessRecord' => 'WellnessRecord', 'DischargeSummary', 'DischargeSummaryRecord' => 'DischargeSummaryRecord',
'ImmunizationRecord' => 'ImmunizationRecord', 'ImmunizationRecord' => 'ImmunizationRecord',
'InvoiceRecord' => 'InvoiceRecord', 'WellnessRecord' => 'WellnessRecord',
'HealthDocumentRecord' => 'HealthDocumentRecord', 'HealthDocumentRecord' => 'HealthDocumentRecord',
'' => '', 'InvoiceRecord' => 'InvoiceRecord',
default => $hiTypeRaw, default => '',
};
}; };
// Bridge internal record_type enum (sent alongside hi_type for categorisation) $normalizeRecordType = static function (string $value) use ($normalizeHiType): string {
$recordTypeIn = (string) ($data['record_type'] ?? ''); return $normalizeHiType($value);
$validRecordTypes = ['prescription', 'lab_report', 'discharge_summary', 'wellness_record', 'health_document']; };
if (in_array($recordTypeIn, $validRecordTypes, true)) {
$recordType = $recordTypeIn; $recordType = $normalizeRecordType($recordTypeRaw);
} else { if ($recordType === '') {
// Derive record_type from hi_type $recordType = $normalizeRecordType($hiTypeRaw);
$recordType = match ($hiType) { }
'OPConsultation', 'Prescription' => 'prescription', if ($recordType === '') {
'DiagnosticReport' => 'lab_report', $recordType = 'HealthDocumentRecord';
'DischargeSummary' => 'discharge_summary',
'WellnessRecord', 'ImmunizationRecord' => 'wellness_record',
default => 'health_document',
};
} }
// Derive hi_type from record_type if still empty. $hiType = $normalizeHiType($hiTypeRaw);
// Gateway requires hi_type with the exact ABDM HI type name.
if ($hiType === '') { if ($hiType === '') {
$hiType = match ($recordType) { $hiType = $recordType;
'prescription' => 'OPConsultRecord', }
'lab_report' => 'DiagnosticReportRecord',
'discharge_summary' => 'DischargeSummaryRecord', $patientName = trim((string) ($data['patient_name'] ?? ''));
'wellness_record' => 'WellnessRecord', $abhaAddress = trim((string) ($data['abha_address'] ?? ''));
default => 'HealthDocumentRecord', $abhaId = trim((string) ($data['abha_id'] ?? ''));
}; $careContextReference = trim((string) ($data['care_context_reference'] ?? ''));
$careContextDisplay = trim((string) ($data['care_context_display'] ?? $data['notes'] ?? ''));
$queueId = trim((string) ($data['queue_id'] ?? ''));
if ($patientName === '') {
return ['ok' => 0, 'http_code' => 0, 'error_text' => 'patient_name is required'];
}
if ($abhaAddress === '') {
return ['ok' => 0, 'http_code' => 0, 'error_text' => 'abha_address is required before pushing health record'];
}
if ($careContextReference === '') {
return ['ok' => 0, 'http_code' => 0, 'error_text' => 'care_context_reference is required'];
}
if ($careContextDisplay === '') {
$careContextDisplay = $recordType;
}
if ($queueId === '') {
$queueId = $careContextReference;
} }
// Gateway requires BOTH record_type (lowercase alias) and hi_type (official ABDM name). // Gateway requires BOTH record_type (lowercase alias) and hi_type (official ABDM name).
$body = [ $body = [
'patient_id' => (string) ($data['patient_id'] ?? ''), 'patient_id' => (string) ($data['patient_id'] ?? ''),
'patient_name' => (string) ($data['patient_name'] ?? ''), 'patient_name' => $patientName,
'record_type' => $recordType, 'record_type' => $recordType,
'hi_type' => $hiType, 'hi_type' => $hiType,
'visit_date' => (string) ($data['visit_date'] ?? date('Y-m-d')), 'visit_date' => (string) ($data['visit_date'] ?? date('Y-m-d')),
'record_data' => $data['record_data'] ?? $data['bundle'] ?? (object) [], 'record_data' => $data['record_data'] ?? $data['bundle'] ?? (object) [],
'fhir_bundle' => $data['record_data'] ?? $data['bundle'] ?? (object) [], 'fhir_bundle' => $data['record_data'] ?? $data['bundle'] ?? (object) [],
'care_context_reference' => $careContextReference,
'care_context_display' => $careContextDisplay,
'abha_address' => $abhaAddress,
'queue_id' => $queueId,
]; ];
// hfr_id is required in every push request alongside the Bearer token. // hfr_id is required in every push request alongside the Bearer token.
@ -411,7 +434,11 @@ class EAtriaBridgeConnector implements AbdmConnectorInterface
$body['hfr_id'] = $this->hfrId; $body['hfr_id'] = $this->hfrId;
} }
foreach (['abha_id', 'abha_address', 'doctor_name', 'department', 'care_context_reference', 'care_context_display', 'notes', 'queue_id', 'gender', 'year_of_birth'] as $optional) { if ($this->hospitalId !== '') {
$body['hospital_id'] = $this->hospitalId;
}
foreach (['abha_id', 'doctor_name', 'department', 'notes', 'gender', 'year_of_birth'] as $optional) {
if (! empty($data[$optional])) { if (! empty($data[$optional])) {
$body[$optional] = (string) $data[$optional]; $body[$optional] = (string) $data[$optional];
} }