From 75e3a820cc4277c1cea8fcf78476a6ee4898113e Mon Sep 17 00:00:00 2001 From: DevSoftTech Server Date: Thu, 8 Oct 2026 11:07:08 +0530 Subject: [PATCH] fix(abdm): unify immunization payload, resolve CC IMM-53, add on-demand fetch and sync bundle --- app/Controllers/AbdmFhirInspector.php | 10 +- app/Controllers/AbdmGateway.php | 175 ++++++++++-------- .../Abdm/Sync/AbdmTaskBoardSyncService.php | 26 ++- 3 files changed, 129 insertions(+), 82 deletions(-) diff --git a/app/Controllers/AbdmFhirInspector.php b/app/Controllers/AbdmFhirInspector.php index 3228bd8..60772f6 100644 --- a/app/Controllers/AbdmFhirInspector.php +++ b/app/Controllers/AbdmFhirInspector.php @@ -241,9 +241,13 @@ class AbdmFhirInspector extends BaseController return $gw->invoiceFhirPreview(); } - // Immunization: IMM-{id} - if (preg_match('/^IMM-(\d+)/i', $ref, $m)) { - $immId = (int) $m[1]; + // Immunization: IMM-{id} or IMM-{patientId}-V{id}-{date} + if (preg_match('/^IMM-(?:PAT-(\d+)|(?:(\d+)-V)?(\d+))/i', $ref, $m)) { + $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->initController($this->request, $this->response, service('logger')); $_GET['record_id'] = $immId; diff --git a/app/Controllers/AbdmGateway.php b/app/Controllers/AbdmGateway.php index efed66f..d2334b0 100644 --- a/app/Controllers/AbdmGateway.php +++ b/app/Controllers/AbdmGateway.php @@ -2581,15 +2581,16 @@ class AbdmGateway extends BaseController 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); $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') ?? '')); $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) { return $this->response->setStatusCode(400)->setJSON([ 'status' => 'error', @@ -3974,10 +3975,14 @@ class AbdmGateway extends BaseController '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 [ 'bundle' => $bundle, 'care_context_reference' => $ccRef, - 'care_context_display' => 'Immunization Record - ' . $visitDate, + 'care_context_display' => $ccDisplay, 'patient_name' => $patient['name'], 'doctor_name' => (string) ($practitioner['name'] ?? ''), '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 $hrRow = null; if ($db->tableExists('health_records')) { @@ -7494,6 +7553,15 @@ class AbdmGateway extends BaseController $ccRef = ! empty($imm['abdm_care_context_reference']) ? trim((string) $imm['abdm_care_context_reference']) : ('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 . ')'; $addContext([ @@ -10326,95 +10394,48 @@ class AbdmGateway extends BaseController return null; } - $immRows = []; - if ($this->db->tableExists('immunization_records')) { - $builder = $this->db->table('immunization_records'); - 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 ($patientId <= 0 && $recordId > 0 && $this->db->tableExists('immunization_records')) { + $rec = $this->db->table('immunization_records')->select('patient_id')->where('id', $recordId)->get(1)->getRowArray(); + $patientId = (int) ($rec['patient_id'] ?? 0); } - if (empty($immRows)) { - return null; - } - - $firstRow = $immRows[0]; if ($patientId <= 0) { - $patientId = (int) ($firstRow['patient_id'] ?? 0); - } - - $patientRow = $this->loadPatientRow($patientId); - if (empty($patientRow)) { return null; } - if ($abhaId === '') { - $abhaId = $this->resolvePatientAbhaIdentifier($patientId); + $gatewayPayload = $this->buildImmunizationGatewayPayload($patientId, $recordId, $abhaId, false); + if ($gatewayPayload === null) { + return null; } - $vaccines = []; - $visitDate = date('Y-m-d'); - foreach ($immRows as $row) { - $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'] ?? '')), - ]; + $vaccineName = 'Vaccine'; + if (! empty($gatewayPayload['records'][0]['vaccine_name'])) { + $vaccineName = trim((string) $gatewayPayload['records'][0]['vaccine_name']); } + $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); - $cleanDate = date('Ymd', strtotime($visitDate)); - $ccRef = 'IMM-' . $patientId . '-V' . $primaryRecordId . '-' . $cleanDate; - $ccDisplay = 'Immunization - ' . date('d/m/Y', strtotime($visitDate)); - - $source = [ - 'record_id' => (string) $primaryRecordId, - 'visit_date' => $visitDate, - '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'])); + if ($recordId > 0 && $this->db->tableExists('immunization_records')) { + $this->db->table('immunization_records') + ->where('id', $recordId) + ->groupStart() + ->where('abdm_care_context_reference', null) + ->orWhere('abdm_care_context_reference', '') + ->groupEnd() + ->update(['abdm_care_context_reference' => $ccRef]); + } return [ 'hi_type' => 'ImmunizationRecord', 'entity_type' => 'immunization', - 'entity_id' => (string) $primaryRecordId, + 'entity_id' => (string) ($recordId > 0 ? $recordId : $patientId), 'patient_id' => $patientId, - 'patient_name' => $this->patientDisplayName($patientRow), - 'visit_date' => $visitDate, + 'patient_name' => (string) ($gatewayPayload['patient_name'] ?? ('PATIENT-' . $patientId)), + 'visit_date' => (string) ($gatewayPayload['visit_date'] ?? date('Y-m-d')), 'care_context_reference' => $ccRef, 'care_context_display' => $ccDisplay, - 'bundle' => (array) ($gatewayPayload['fhir_bundle'] ?? []), + 'bundle' => (array) ($gatewayPayload['bundle'] ?? []), ]; } diff --git a/app/Libraries/Abdm/Sync/AbdmTaskBoardSyncService.php b/app/Libraries/Abdm/Sync/AbdmTaskBoardSyncService.php index cdd6321..b062dfc 100644 --- a/app/Libraries/Abdm/Sync/AbdmTaskBoardSyncService.php +++ b/app/Libraries/Abdm/Sync/AbdmTaskBoardSyncService.php @@ -831,9 +831,11 @@ class AbdmTaskBoardSyncService $cleanVisitDate = str_replace('-', '', $visitDate); $careContextRef = $prefix . $entityId . '-' . $cleanVisitDate; 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'])) { $careContextRef = trim((string) $immRec['abdm_care_context_reference']); + } else { + $careContextRef = 'IMM-' . $entityId; } } 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 = [ '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 {