fix(abdm): unify immunization payload, resolve CC IMM-53, add on-demand fetch and sync bundle

This commit is contained in:
DevSoftTech Server 2026-10-08 11:07:08 +05:30
parent 91d7fe77d2
commit 75e3a820cc
3 changed files with 129 additions and 82 deletions

View File

@ -241,9 +241,13 @@ class AbdmFhirInspector extends BaseController
return $gw->invoiceFhirPreview(); return $gw->invoiceFhirPreview();
} }
// Immunization: IMM-{id} // Immunization: IMM-{id} or IMM-{patientId}-V{id}-{date}
if (preg_match('/^IMM-(\d+)/i', $ref, $m)) { if (preg_match('/^IMM-(?:PAT-(\d+)|(?:(\d+)-V)?(\d+))/i', $ref, $m)) {
$immId = (int) $m[1]; $immId = (int) (! empty($m[3]) ? $m[3] : (! empty($m[2]) ? $m[2] : ($m[1] ?? 0)));
if ($patientId <= 0 && $this->db->tableExists('immunization_records')) {
$immRec = $this->db->table('immunization_records')->select('patient_id')->where('id', $immId)->get(1)->getRowArray();
$patientId = (int) ($immRec['patient_id'] ?? 0);
}
$gw = new \App\Controllers\AbdmGateway(); $gw = new \App\Controllers\AbdmGateway();
$gw->initController($this->request, $this->response, service('logger')); $gw->initController($this->request, $this->response, service('logger'));
$_GET['record_id'] = $immId; $_GET['record_id'] = $immId;

View File

@ -2581,15 +2581,16 @@ class AbdmGateway extends BaseController
public function immunizationFhirPreview() public function immunizationFhirPreview()
{ {
if (! $this->request->isAJAX()) {
return $this->response->setStatusCode(403)->setJSON(['status' => 'error', 'message' => 'AJAX only']);
}
$patientId = (int) ($this->request->getGet('patient_id') ?? $this->request->getPost('patient_id') ?? 0); $patientId = (int) ($this->request->getGet('patient_id') ?? $this->request->getPost('patient_id') ?? 0);
$recordId = (int) ($this->request->getGet('record_id') ?? $this->request->getPost('record_id') ?? 0); $recordId = (int) ($this->request->getGet('record_id') ?? $this->request->getPost('record_id') ?? 0);
$abhaId = trim((string) ($this->request->getGet('abha_id') ?? $this->request->getPost('abha_id') ?? '')); $abhaId = trim((string) ($this->request->getGet('abha_id') ?? $this->request->getPost('abha_id') ?? ''));
$forceNewRecord = (int) ($this->request->getGet('force_new_record') ?? $this->request->getGet('force_rebuild') ?? $this->request->getPost('force_new_record') ?? 0) === 1; $forceNewRecord = (int) ($this->request->getGet('force_new_record') ?? $this->request->getGet('force_rebuild') ?? $this->request->getPost('force_new_record') ?? 0) === 1;
if ($patientId <= 0 && $recordId > 0 && $this->db->tableExists('immunization_records')) {
$immRec = $this->db->table('immunization_records')->select('patient_id')->where('id', $recordId)->get(1)->getRowArray();
$patientId = (int) ($immRec['patient_id'] ?? 0);
}
if ($patientId <= 0) { if ($patientId <= 0) {
return $this->response->setStatusCode(400)->setJSON([ return $this->response->setStatusCode(400)->setJSON([
'status' => 'error', 'status' => 'error',
@ -3974,10 +3975,14 @@ class AbdmGateway extends BaseController
'care_context_reference' => $ccRef, 'care_context_reference' => $ccRef,
]); ]);
$vaccineDisplay = ! empty($records[0]['vaccine_name']) ? trim((string) $records[0]['vaccine_name']) : 'Vaccine';
$dateStr = date('d M Y', strtotime($visitDate));
$ccDisplay = 'ImmunizationRecord - ' . $vaccineDisplay . ' (' . $dateStr . ')';
return [ return [
'bundle' => $bundle, 'bundle' => $bundle,
'care_context_reference' => $ccRef, 'care_context_reference' => $ccRef,
'care_context_display' => 'Immunization Record - ' . $visitDate, 'care_context_display' => $ccDisplay,
'patient_name' => $patient['name'], 'patient_name' => $patient['name'],
'doctor_name' => (string) ($practitioner['name'] ?? ''), 'doctor_name' => (string) ($practitioner['name'] ?? ''),
'year_of_birth' => $this->resolvePatientBirthYear($patientRow, str_contains($rawAbha, '@') ? $rawAbha : '', str_contains($rawAbha, '@') ? '' : $rawAbha), 'year_of_birth' => $this->resolvePatientBirthYear($patientRow, str_contains($rawAbha, '@') ? $rawAbha : '', str_contains($rawAbha, '@') ? '' : $rawAbha),
@ -5371,6 +5376,60 @@ class AbdmGateway extends BaseController
} }
} }
// Strategy 0e: Live On-Demand Fresh Generation for Immunization Record (IMM-{id})
if (preg_match('/^IMM-(?:PAT-(\d+)|(?:(\d+)-V)?(\d+))/i', $ref, $immM)) {
$immTargetId = (int) (! empty($immM[3]) ? $immM[3] : (! empty($immM[2]) ? $immM[2] : ($immM[1] ?? 0)));
$immPatientId = (int) (! empty($immM[1]) ? $immM[1] : (! empty($immM[2]) ? $immM[2] : 0));
try {
$immPayload = $this->buildImmunizationGatewayPayload($immPatientId, $immTargetId, '', false);
if ($immPayload !== null && ! empty($immPayload['bundle'])) {
$vaccineName = 'Vaccine';
if (! empty($immPayload['records'][0]['vaccine_name'])) {
$vaccineName = trim((string) $immPayload['records'][0]['vaccine_name']);
}
$dateStr = date('d M Y', strtotime((string) ($immPayload['visit_date'] ?? date('Y-m-d'))));
$immDisplay = 'ImmunizationRecord - ' . $vaccineName . ' (' . $dateStr . ')';
$records[] = [
'careContextReference' => $ref,
'hiType' => 'ImmunizationRecord',
'display' => $immDisplay,
'bundle' => $immPayload['bundle'],
];
// Persist or cache to health_records for rapid subsequent fetches
if ($db->tableExists('health_records')) {
$exHr = $db->table('health_records')->where('care_context_reference', $ref)->get(1)->getRowArray();
if ($exHr) {
if (empty($exHr['record_data'])) {
$db->table('health_records')->where('id', (int) $exHr['id'])->update([
'record_data' => json_encode($immPayload['bundle'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
'push_status' => 'linked',
'updated_at' => date('Y-m-d H:i:s'),
]);
}
} else {
$resolvedPatId = $immPatientId > 0 ? $immPatientId : (int) ($immPayload['records'][0]['patient_id'] ?? 0);
$db->table('health_records')->insert([
'patient_id' => $resolvedPatId,
'abha_id' => (string) ($immPayload['records'][0]['abha_id'] ?? $this->resolvePatientAbhaIdentifier($resolvedPatId)),
'hi_type' => 'ImmunizationRecord',
'entity_type' => 'immunization',
'entity_id' => (string) $immTargetId,
'care_context_reference' => $ref,
'record_data' => json_encode($immPayload['bundle'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
'push_status' => 'linked',
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
}
}
continue;
}
} catch (\Throwable $ex) {
log_message('warning', 'Live Immunization FHIR generation in recordsFetch failed: ' . $ex->getMessage());
}
}
// Strategy A: Check if health_records already has stored FHIR record_data // Strategy A: Check if health_records already has stored FHIR record_data
$hrRow = null; $hrRow = null;
if ($db->tableExists('health_records')) { if ($db->tableExists('health_records')) {
@ -7494,6 +7553,15 @@ class AbdmGateway extends BaseController
$ccRef = ! empty($imm['abdm_care_context_reference']) $ccRef = ! empty($imm['abdm_care_context_reference'])
? trim((string) $imm['abdm_care_context_reference']) ? trim((string) $imm['abdm_care_context_reference'])
: ('IMM-' . $imm['id']); : ('IMM-' . $imm['id']);
if (empty($imm['abdm_care_context_reference'])) {
$this->db->table('immunization_records')
->where('id', (int) $imm['id'])
->groupStart()
->where('abdm_care_context_reference', null)
->orWhere('abdm_care_context_reference', '')
->groupEnd()
->update(['abdm_care_context_reference' => $ccRef]);
}
$display = 'ImmunizationRecord - ' . ($imm['vaccine_name'] ?? 'Vaccine') . ' (' . $dateStr . ')'; $display = 'ImmunizationRecord - ' . ($imm['vaccine_name'] ?? 'Vaccine') . ' (' . $dateStr . ')';
$addContext([ $addContext([
@ -10326,95 +10394,48 @@ class AbdmGateway extends BaseController
return null; return null;
} }
$immRows = []; if ($patientId <= 0 && $recordId > 0 && $this->db->tableExists('immunization_records')) {
if ($this->db->tableExists('immunization_records')) { $rec = $this->db->table('immunization_records')->select('patient_id')->where('id', $recordId)->get(1)->getRowArray();
$builder = $this->db->table('immunization_records'); $patientId = (int) ($rec['patient_id'] ?? 0);
if ($recordId > 0) {
$builder->where('id', $recordId);
} elseif ($patientId > 0) {
$builder->where('patient_id', $patientId)->orderBy('id', 'DESC')->limit(10);
}
$immRows = $builder->get()->getResultArray();
} }
if (empty($immRows)) {
return null;
}
$firstRow = $immRows[0];
if ($patientId <= 0) { if ($patientId <= 0) {
$patientId = (int) ($firstRow['patient_id'] ?? 0);
}
$patientRow = $this->loadPatientRow($patientId);
if (empty($patientRow)) {
return null; return null;
} }
if ($abhaId === '') { $gatewayPayload = $this->buildImmunizationGatewayPayload($patientId, $recordId, $abhaId, false);
$abhaId = $this->resolvePatientAbhaIdentifier($patientId); if ($gatewayPayload === null) {
return null;
} }
$vaccines = []; $vaccineName = 'Vaccine';
$visitDate = date('Y-m-d'); if (! empty($gatewayPayload['records'][0]['vaccine_name'])) {
foreach ($immRows as $row) { $vaccineName = trim((string) $gatewayPayload['records'][0]['vaccine_name']);
$vaccineName = trim((string) ($row['vaccine_name'] ?? $row['vaccine_display'] ?? ''));
if ($vaccineName === '' && ! empty($row['vaccine_master_id']) && $this->db->tableExists('immunization_vaccine_master')) {
$vm = $this->db->table('immunization_vaccine_master')->where('id', (int) $row['vaccine_master_id'])->get(1)->getRowArray();
$vaccineName = trim((string) ($vm['vaccine_name'] ?? ''));
}
if ($vaccineName === '') {
$vaccineName = 'Vaccine Dose';
}
$occDate = ! empty($row['given_date']) ? (string) $row['given_date'] : (! empty($row['scheduled_date']) ? (string) $row['scheduled_date'] : date('Y-m-d H:i:s'));
$visitDate = date('Y-m-d', strtotime($occDate));
$vaccines[] = [
'vaccine_name' => $vaccineName,
'name' => $vaccineName,
'snomed_code' => trim((string) ($row['vaccine_code'] ?? '')),
'occurrence_date' => date(DATE_ATOM, strtotime($occDate)),
'status' => trim((string) ($row['status'] ?? 'completed')),
'lot_number' => trim((string) ($row['lot_number'] ?? '')),
];
} }
$dateStr = date('d M Y', strtotime((string) ($gatewayPayload['visit_date'] ?? date('Y-m-d'))));
$ccDisplay = 'ImmunizationRecord - ' . $vaccineName . ' (' . $dateStr . ')';
$ccRef = (string) ($gatewayPayload['care_context_reference'] ?? ('IMM-' . $recordId));
$primaryRecordId = (int) ($firstRow['id'] ?? $recordId); if ($recordId > 0 && $this->db->tableExists('immunization_records')) {
$cleanDate = date('Ymd', strtotime($visitDate)); $this->db->table('immunization_records')
$ccRef = 'IMM-' . $patientId . '-V' . $primaryRecordId . '-' . $cleanDate; ->where('id', $recordId)
$ccDisplay = 'Immunization - ' . date('d/m/Y', strtotime($visitDate)); ->groupStart()
->where('abdm_care_context_reference', null)
$source = [ ->orWhere('abdm_care_context_reference', '')
'record_id' => (string) $primaryRecordId, ->groupEnd()
'visit_date' => $visitDate, ->update(['abdm_care_context_reference' => $ccRef]);
'completed_at' => date(DATE_ATOM, strtotime($visitDate)), }
'patient' => [
'id' => (string) $patientId,
'name' => $this->patientDisplayName($patientRow),
'gender' => strtolower(trim((string) ($patientRow['gender'] ?? 'unknown'))),
'birth_date' => ! empty($patientRow['dob']) ? date('Y-m-d', strtotime((string) $patientRow['dob'])) : '',
'abha_id' => preg_replace('/\D/', '', $abhaId),
'abha_address' => str_contains($abhaId, '@') ? $abhaId : '',
],
'immunizations' => $vaccines,
'vaccines' => $vaccines,
'hfr_id' => $this->connector->getHfrId() ?: 'HFR-IN-HMS',
];
$factory = new FhirGeneratorFactory();
$generatorOutput = $factory->immunization()->generate($source);
$adapter = new \App\Libraries\Abdm\Fhir\Support\GatewayPayloadAdapter();
$gatewayPayload = $adapter->toGatewayPayload($generatorOutput, $source, (string) ($source['hfr_id']));
return [ return [
'hi_type' => 'ImmunizationRecord', 'hi_type' => 'ImmunizationRecord',
'entity_type' => 'immunization', 'entity_type' => 'immunization',
'entity_id' => (string) $primaryRecordId, 'entity_id' => (string) ($recordId > 0 ? $recordId : $patientId),
'patient_id' => $patientId, 'patient_id' => $patientId,
'patient_name' => $this->patientDisplayName($patientRow), 'patient_name' => (string) ($gatewayPayload['patient_name'] ?? ('PATIENT-' . $patientId)),
'visit_date' => $visitDate, 'visit_date' => (string) ($gatewayPayload['visit_date'] ?? date('Y-m-d')),
'care_context_reference' => $ccRef, 'care_context_reference' => $ccRef,
'care_context_display' => $ccDisplay, 'care_context_display' => $ccDisplay,
'bundle' => (array) ($gatewayPayload['fhir_bundle'] ?? []), 'bundle' => (array) ($gatewayPayload['bundle'] ?? []),
]; ];
} }

View File

@ -831,9 +831,11 @@ class AbdmTaskBoardSyncService
$cleanVisitDate = str_replace('-', '', $visitDate); $cleanVisitDate = str_replace('-', '', $visitDate);
$careContextRef = $prefix . $entityId . '-' . $cleanVisitDate; $careContextRef = $prefix . $entityId . '-' . $cleanVisitDate;
if ($taskType === 'immunization_record_publish' && $this->db->tableExists('immunization_records')) { if ($taskType === 'immunization_record_publish' && $this->db->tableExists('immunization_records')) {
$immRec = $this->db->table('immunization_records')->select('abdm_care_context_reference')->where('id', (int) $entityId)->get(1)->getRowArray(); $immRec = $this->db->table('immunization_records')->select('abdm_care_context_reference, vaccine_name')->where('id', (int) $entityId)->get(1)->getRowArray();
if (! empty($immRec['abdm_care_context_reference'])) { if (! empty($immRec['abdm_care_context_reference'])) {
$careContextRef = trim((string) $immRec['abdm_care_context_reference']); $careContextRef = trim((string) $immRec['abdm_care_context_reference']);
} else {
$careContextRef = 'IMM-' . $entityId;
} }
} }
if ($taskType === 'wellness_record_publish' && $this->db->tableExists('patient_wellness_records')) { if ($taskType === 'wellness_record_publish' && $this->db->tableExists('patient_wellness_records')) {
@ -846,7 +848,12 @@ class AbdmTaskBoardSyncService
} }
} }
} }
$careContextDisplay = $hiType . ' ' . $visitDate;
if ($taskType === 'immunization_record_publish' && ! empty($immRec['vaccine_name'])) {
$careContextDisplay = 'ImmunizationRecord - ' . trim((string) $immRec['vaccine_name']) . ' (' . date('d M Y', strtotime($visitDate)) . ')';
} else {
$careContextDisplay = $hiType . ' ' . $visitDate;
}
$pushData = [ $pushData = [
'patient_id' => (string) $patientId, 'patient_id' => (string) $patientId,
@ -913,6 +920,21 @@ class AbdmTaskBoardSyncService
} }
} }
} }
} elseif ($taskType === 'immunization_record_publish') {
if (empty($pushData['bundle']) && class_exists('\App\Controllers\AbdmGateway')) {
try {
$gw = new \App\Controllers\AbdmGateway();
$immPayload = $gw->buildImmunizationRecordPayload((int) $entityId, $patientId, $abhaAddress !== '' ? $abhaAddress : $abhaNumber);
if (! empty($immPayload['bundle'])) {
$pushData['bundle'] = $immPayload['bundle'];
$pushData['fhir_bundle'] = $immPayload['bundle'];
$pushData['record_data'] = $immPayload['bundle'];
$bundleJson = (string) json_encode($immPayload['bundle'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
} catch (\Throwable $ie) {
log_message('warning', '[processIndividualWorkTask] Immunization bundle generation error: ' . $ie->getMessage());
}
}
} }
try { try {