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,
'abha_id' => $abhaId,
'hi_type' => 'DischargeSummaryRecord',
'record_type' => 'discharge_summary',
'record_type' => 'DischargeSummaryRecord',
'visit_date' => $visitDate,
'doctor_name' => $doctorName,
'care_context_reference' => $ccRef,
'care_context_display' => 'Discharge Summary - ' . ($ipdCode !== '' ? 'IPD#' . $ipdCode : 'IPD#' . $ipdId),
'notes' => 'Discharge Summary - ' . ($ipdCode !== '' ? 'IPD#' . $ipdCode : 'IPD#' . $ipdId),
'queue_id' => 'IPD-' . $ipdId . '-' . $visitDate,
'record_data' => $bundle,
]);
$queueId = (string) ($result['queue_id'] ?? '');
@ -1014,10 +1016,12 @@ class AbdmGateway extends BaseController
'patient_name' => $patName,
'abha_id' => $abhaId,
'hi_type' => 'DiagnosticReportRecord',
'record_type' => 'lab_report',
'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,
'record_data' => $bundle,
]);
$queueId = (string) ($result['queue_id'] ?? '');
@ -1769,7 +1773,9 @@ class AbdmGateway extends BaseController
'doctor_name' => $doctorName,
'department' => $department,
'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'] ?? '')),
'queue_id' => trim((string) ($hr['care_context_reference'] ?? '')),
'record_data' => $fhirBundle,
]);
$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.
* Bridge accepts: prescription | lab_report | discharge_summary | wellness_record | health_document
* Maps HMS inputs to official ABDM HI record types.
*/
private function mapHiTypeToRecordType(string $hiType): string
{
return match (strtolower($hiType)) {
'opconsultrecord', 'prescriptionrecord' => 'prescription',
'diagnosticreportrecord' => 'lab_report',
'dischargesummaryrecord' => 'discharge_summary',
'wellnessrecord' => 'wellness_record',
default => 'health_document',
'opconsultrecord', 'prescriptionrecord', 'prescription' => 'PrescriptionRecord',
'diagnosticreportrecord', 'lab_report', 'diagnosticreport' => 'DiagnosticReportRecord',
'dischargesummaryrecord', 'discharge_summary', 'dischargesummary' => 'DischargeSummaryRecord',
'wellnessrecord' => 'WellnessRecord',
'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 {
$connector = new \App\Libraries\Abdm\EAtriaBridgeConnector();
$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.
// Use OPD-{id}-{date} so different visit dates generate distinct care contexts.
$ccRef = 'OPD-' . $opdId . '-' . ($visitDate !== '' ? $visitDate : date('Y-m-d'));
// Use OPD-{id}-{session} so the reference stays stable for a specific saved record.
$ccRef = 'OPD-' . $opdId . '-' . $recordId;
$pushData = [
'patient_id' => (string) $patientId,
'patient_name' => $patientName,
'record_type' => 'prescription',
'record_type' => 'PrescriptionRecord',
'hi_type' => 'PrescriptionRecord',
'visit_date' => $visitDate,
'hospital_id' => (string) $opdId,
'care_context_reference' => $ccRef,
'care_context_display' => $displayLabel,
'notes' => $displayLabel,
'queue_id' => 'OPD-' . $opdId . '-' . $recordId,
'abha_address' => $abhaId,
'record_data' => $bundle,
];
if ($abhaId !== '') {
$pushData['abha_address'] = $abhaId;
}
if ($doctorName !== '') {
$pushData['doctor_name'] = $doctorName;
}

View File

@ -21,6 +21,7 @@ class EAtriaBridgeConnector implements AbdmConnectorInterface
private string $baseUrl;
private string $token;
private string $hfrId;
private string $hospitalId;
private int $timeoutSec;
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->token = (string) ($config->eatriaBridgeToken ?? '');
$this->hfrId = '';
$this->hospitalId = '';
$this->timeoutSec = (int) ($config->eatriaBridgeTimeoutSec ?? 30);
// DB (Admin Panel → ABDM Gateway Config) is the authoritative source for
@ -41,7 +43,7 @@ class EAtriaBridgeConnector implements AbdmConnectorInterface
if ($db->tableExists('hospital_setting')) {
$rows = $db->table('hospital_setting')
->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()
->getResultArray();
@ -56,6 +58,11 @@ class EAtriaBridgeConnector implements AbdmConnectorInterface
if (! empty($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) {
// DB unavailable — continue with config/env values
@ -351,59 +358,75 @@ class EAtriaBridgeConnector implements AbdmConnectorInterface
public function pushRecord(array $data): array
{
// Normalize to gateway-enforced HI types for /v3/records/push.
// Runtime valid_types include *Record variants.
$recordTypeRaw = (string) ($data['record_type'] ?? '');
$hiTypeRaw = (string) ($data['hi_type'] ?? '');
$hiType = match ($hiTypeRaw) {
'OPConsultRecord', 'OPConsultation' => 'OPConsultRecord',
'PrescriptionRecord', 'Prescription' => 'PrescriptionRecord',
'DiagnosticReportRecord', 'DiagnosticReport' => 'DiagnosticReportRecord',
'DischargeSummaryRecord', 'DischargeSummary' => 'DischargeSummaryRecord',
'WellnessRecord' => 'WellnessRecord',
'ImmunizationRecord' => 'ImmunizationRecord',
'InvoiceRecord' => 'InvoiceRecord',
'HealthDocumentRecord' => 'HealthDocumentRecord',
'' => '',
default => $hiTypeRaw,
$normalizeHiType = static function (string $value): string {
return match ($value) {
'OPConsultation', 'OPConsultRecord', 'Prescription', 'PrescriptionRecord' => 'PrescriptionRecord',
'DiagnosticReport', 'DiagnosticReportRecord', 'Lab_Report', 'lab_report' => 'DiagnosticReportRecord',
'DischargeSummary', 'DischargeSummaryRecord' => 'DischargeSummaryRecord',
'ImmunizationRecord' => 'ImmunizationRecord',
'WellnessRecord' => 'WellnessRecord',
'HealthDocumentRecord' => 'HealthDocumentRecord',
'InvoiceRecord' => 'InvoiceRecord',
default => '',
};
};
// Bridge internal record_type enum (sent alongside hi_type for categorisation)
$recordTypeIn = (string) ($data['record_type'] ?? '');
$validRecordTypes = ['prescription', 'lab_report', 'discharge_summary', 'wellness_record', 'health_document'];
if (in_array($recordTypeIn, $validRecordTypes, true)) {
$recordType = $recordTypeIn;
} else {
// Derive record_type from hi_type
$recordType = match ($hiType) {
'OPConsultation', 'Prescription' => 'prescription',
'DiagnosticReport' => 'lab_report',
'DischargeSummary' => 'discharge_summary',
'WellnessRecord', 'ImmunizationRecord' => 'wellness_record',
default => 'health_document',
};
$normalizeRecordType = static function (string $value) use ($normalizeHiType): string {
return $normalizeHiType($value);
};
$recordType = $normalizeRecordType($recordTypeRaw);
if ($recordType === '') {
$recordType = $normalizeRecordType($hiTypeRaw);
}
if ($recordType === '') {
$recordType = 'HealthDocumentRecord';
}
// Derive hi_type from record_type if still empty.
// Gateway requires hi_type with the exact ABDM HI type name.
$hiType = $normalizeHiType($hiTypeRaw);
if ($hiType === '') {
$hiType = match ($recordType) {
'prescription' => 'OPConsultRecord',
'lab_report' => 'DiagnosticReportRecord',
'discharge_summary' => 'DischargeSummaryRecord',
'wellness_record' => 'WellnessRecord',
default => 'HealthDocumentRecord',
};
$hiType = $recordType;
}
$patientName = trim((string) ($data['patient_name'] ?? ''));
$abhaAddress = trim((string) ($data['abha_address'] ?? ''));
$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).
$body = [
'patient_id' => (string) ($data['patient_id'] ?? ''),
'patient_name' => (string) ($data['patient_name'] ?? ''),
'patient_name' => $patientName,
'record_type' => $recordType,
'hi_type' => $hiType,
'visit_date' => (string) ($data['visit_date'] ?? date('Y-m-d')),
'record_data' => $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.
@ -411,7 +434,11 @@ class EAtriaBridgeConnector implements AbdmConnectorInterface
$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])) {
$body[$optional] = (string) $data[$optional];
}