diff --git a/app/Controllers/AbdmGateway.php b/app/Controllers/AbdmGateway.php index 6eefcc4..a4894b8 100644 --- a/app/Controllers/AbdmGateway.php +++ b/app/Controllers/AbdmGateway.php @@ -2947,6 +2947,7 @@ class AbdmGateway extends BaseController $hospital = $this->getHospitalProfileForFhir(); $hfrId = trim((string) ($hospital['hfr_id'] ?? '')); $documents = $this->buildIpdPdfDocuments($ipdId, $dischargeRaw); + $locationInfo = $this->resolveIpdLocation($ipdId); $source = [ 'record_id' => (string) $ipdId, @@ -2977,9 +2978,14 @@ class AbdmGateway extends BaseController ], 'encounter' => [ 'id' => trim((string) ($ipdRow['ipd_code'] ?? '')) ?: (string) $ipdId, + 'ipd_no' => trim((string) ($ipdRow['ipd_code'] ?? '')) ?: ('IPD-' . $ipdId), 'class_code' => 'IMP', 'start' => $this->toIsoDateTimeOrNow($admissionRaw), 'end' => $this->toIsoDateTimeOrNow($dischargeRaw), + 'location_display' => (string) ($locationInfo['display'] ?? ''), + 'ward' => (string) ($locationInfo['ward'] ?? ''), + 'room' => (string) ($locationInfo['room'] ?? ''), + 'bed' => (string) ($locationInfo['bed'] ?? ''), ], 'chief_complaints' => $chiefComplaints, 'chief_complaint_narrative' => $chiefComplaintNarrative, @@ -3010,6 +3016,82 @@ class AbdmGateway extends BaseController ]; } + private function resolveIpdLocation(int $ipdId): array + { + $bedNo = ''; + $wardName = ''; + $roomName = ''; + + if ($ipdId > 0) { + if ($this->db->tableExists('bed_master')) { + $row = $this->db->table('bed_master bm') + ->select('bm.bed_number, wm.ward_name, wm.ward_type') + ->join('ward_master wm', 'wm.id = bm.ward_id', 'left') + ->where('bm.current_ipd_id', $ipdId) + ->limit(1) + ->get() + ->getRowArray(); + if (! empty($row)) { + $bedNo = trim((string) ($row['bed_number'] ?? '')); + $wardName = trim((string) ($row['ward_name'] ?? $row['ward_type'] ?? '')); + } + } + if ($bedNo === '' && $this->db->tableExists('bed_assignment_history')) { + $bahRow = $this->db->table('bed_assignment_history bah') + ->select('bm.bed_number, wm.ward_name, wm.ward_type') + ->join('bed_master bm', 'bm.id = bah.bed_id', 'left') + ->join('ward_master wm', 'wm.id = bah.ward_id', 'left') + ->where('bah.ipd_id', $ipdId) + ->orderBy('bah.id', 'DESC') + ->limit(1) + ->get() + ->getRowArray(); + if (! empty($bahRow)) { + $bedNo = trim((string) ($bahRow['bed_number'] ?? '')); + $wardName = trim((string) ($bahRow['ward_name'] ?? $bahRow['ward_type'] ?? '')); + } + } + if ($bedNo === '' && $this->db->tableExists('ward_beds')) { + $wbRow = $this->db->table('ward_beds wb') + ->select('wb.bed_number, wm.ward_name') + ->join('ward_master wm', 'wm.id = wb.ward_id', 'left') + ->where('wb.ipd_id', $ipdId) + ->limit(1) + ->get() + ->getRowArray(); + if (! empty($wbRow)) { + $bedNo = trim((string) ($wbRow['bed_number'] ?? '')); + $wardName = trim((string) ($wbRow['ward_name'] ?? '')); + } + } + if ($bedNo === '' && $this->db->tableExists('ipd_master')) { + $ipd = $this->db->table('ipd_master')->where('id', $ipdId)->get(1)->getRowArray() ?? []; + $bedNo = trim((string) ($ipd['bed_no'] ?? $ipd['bed_id'] ?? '')); + $roomName = trim((string) ($ipd['room_no'] ?? $ipd['room_id'] ?? '')); + } + } + + $parts = []; + if ($wardName !== '') { + $parts[] = 'Ward: ' . $wardName; + } + if ($roomName !== '') { + $parts[] = 'Room: ' . $roomName; + } + if ($bedNo !== '') { + $parts[] = 'Bed: ' . $bedNo; + } + + $display = ! empty($parts) ? implode(', ', $parts) : ($bedNo !== '' ? ('Bed ' . $bedNo) : ''); + + return [ + 'ward' => $wardName, + 'room' => $roomName, + 'bed' => $bedNo, + 'display' => $display, + ]; + } + /** @return array> */ private function buildIpdPdfDocuments(int $ipdId, string $dischargeRaw): array { diff --git a/app/Controllers/Ipd_discharge.php b/app/Controllers/Ipd_discharge.php index 789e9be..8472318 100644 --- a/app/Controllers/Ipd_discharge.php +++ b/app/Controllers/Ipd_discharge.php @@ -9310,6 +9310,7 @@ class Ipd_discharge extends BaseController } $documentsList = $this->buildIpdPdfDocumentsList($ipdId, $dischargeIso); + $locationInfo = $this->resolveIpdLocation($ipdId); $genderRaw = trim((string) ($patientRow['gender'] ?? '')); if ($genderRaw === '') { @@ -9351,6 +9352,10 @@ class Ipd_discharge extends BaseController 'class_code' => 'IMP', 'start' => $admissionIso, 'end' => $dischargeIso, + 'location_display' => (string) ($locationInfo['display'] ?? ''), + 'ward' => (string) ($locationInfo['ward'] ?? ''), + 'room' => (string) ($locationInfo['room'] ?? ''), + 'bed' => (string) ($locationInfo['bed'] ?? ''), ], 'chief_complaints' => $chiefComplaintsList, 'chief_complaint_narrative' => $complaintRemarkText, @@ -9437,6 +9442,82 @@ class Ipd_discharge extends BaseController return date(DATE_ATOM, $ts); } + private function resolveIpdLocation(int $ipdId): array + { + $bedNo = ''; + $wardName = ''; + $roomName = ''; + + if ($ipdId > 0) { + if ($this->db->tableExists('bed_master')) { + $row = $this->db->table('bed_master bm') + ->select('bm.bed_number, wm.ward_name, wm.ward_type') + ->join('ward_master wm', 'wm.id = bm.ward_id', 'left') + ->where('bm.current_ipd_id', $ipdId) + ->limit(1) + ->get() + ->getRowArray(); + if (! empty($row)) { + $bedNo = trim((string) ($row['bed_number'] ?? '')); + $wardName = trim((string) ($row['ward_name'] ?? $row['ward_type'] ?? '')); + } + } + if ($bedNo === '' && $this->db->tableExists('bed_assignment_history')) { + $bahRow = $this->db->table('bed_assignment_history bah') + ->select('bm.bed_number, wm.ward_name, wm.ward_type') + ->join('bed_master bm', 'bm.id = bah.bed_id', 'left') + ->join('ward_master wm', 'wm.id = bah.ward_id', 'left') + ->where('bah.ipd_id', $ipdId) + ->orderBy('bah.id', 'DESC') + ->limit(1) + ->get() + ->getRowArray(); + if (! empty($bahRow)) { + $bedNo = trim((string) ($bahRow['bed_number'] ?? '')); + $wardName = trim((string) ($bahRow['ward_name'] ?? $bahRow['ward_type'] ?? '')); + } + } + if ($bedNo === '' && $this->db->tableExists('ward_beds')) { + $wbRow = $this->db->table('ward_beds wb') + ->select('wb.bed_number, wm.ward_name') + ->join('ward_master wm', 'wm.id = wb.ward_id', 'left') + ->where('wb.ipd_id', $ipdId) + ->limit(1) + ->get() + ->getRowArray(); + if (! empty($wbRow)) { + $bedNo = trim((string) ($wbRow['bed_number'] ?? '')); + $wardName = trim((string) ($wbRow['ward_name'] ?? '')); + } + } + if ($bedNo === '' && $this->db->tableExists('ipd_master')) { + $ipd = $this->db->table('ipd_master')->where('id', $ipdId)->get(1)->getRowArray() ?? []; + $bedNo = trim((string) ($ipd['bed_no'] ?? $ipd['bed_id'] ?? '')); + $roomName = trim((string) ($ipd['room_no'] ?? $ipd['room_id'] ?? '')); + } + } + + $parts = []; + if ($wardName !== '') { + $parts[] = 'Ward: ' . $wardName; + } + if ($roomName !== '') { + $parts[] = 'Room: ' . $roomName; + } + if ($bedNo !== '') { + $parts[] = 'Bed: ' . $bedNo; + } + + $display = ! empty($parts) ? implode(', ', $parts) : ($bedNo !== '' ? ('Bed ' . $bedNo) : ''); + + return [ + 'ward' => $wardName, + 'room' => $roomName, + 'bed' => $bedNo, + 'display' => $display, + ]; + } + private function getNextVisitOptions(string $baseDate): array { $fallbackDays = [3, 4, 5, 7, 10, 15, 20, 30, 60]; diff --git a/app/Libraries/Abdm/Fhir/Generators/AbstractModuleFhirGenerator.php b/app/Libraries/Abdm/Fhir/Generators/AbstractModuleFhirGenerator.php index 8353a91..9f84c54 100644 --- a/app/Libraries/Abdm/Fhir/Generators/AbstractModuleFhirGenerator.php +++ b/app/Libraries/Abdm/Fhir/Generators/AbstractModuleFhirGenerator.php @@ -206,7 +206,57 @@ abstract class AbstractModuleFhirGenerator ]; } - return [ + $start = trim((string) ($enc['start'] ?? $enc['admission_date'] ?? $source['admission_date'] ?? $source['completed_at'] ?? '')); + if ($start === '') { + $start = date(DATE_ATOM); + } else { + $ts = strtotime($start); + $start = $ts !== false ? date(DATE_ATOM, $ts) : $start; + } + + $end = trim((string) ($enc['end'] ?? $enc['discharge_date'] ?? $source['discharge_date'] ?? $source['completed_at'] ?? '')); + if ($end === '') { + $end = date(DATE_ATOM); + } else { + $ts = strtotime($end); + $end = $ts !== false ? date(DATE_ATOM, $ts) : $end; + } + + $locationList = []; + $locDisplay = trim((string) ($enc['location_display'] ?? $enc['location'] ?? $source['location_display'] ?? $source['location'] ?? '')); + $ward = trim((string) ($enc['ward'] ?? $enc['ward_name'] ?? $source['ward'] ?? $source['ward_name'] ?? '')); + $room = trim((string) ($enc['room'] ?? $enc['room_name'] ?? $source['room'] ?? $source['room_name'] ?? '')); + $bed = trim((string) ($enc['bed'] ?? $enc['bed_no'] ?? $enc['bed_number'] ?? $source['bed'] ?? $source['bed_no'] ?? $source['bed_number'] ?? '')); + + if ($locDisplay === '') { + $locParts = []; + if ($ward !== '') { + $locParts[] = 'Ward: ' . $ward; + } + if ($room !== '') { + $locParts[] = 'Room: ' . $room; + } + if ($bed !== '') { + $locParts[] = 'Bed: ' . $bed; + } + $locDisplay = implode(', ', $locParts); + } + + if ($locDisplay !== '') { + $locItem = [ + 'location' => [ + 'display' => $locDisplay, + ], + 'status' => 'active', + 'period' => [ + 'start' => $start, + 'end' => $end, + ], + ]; + $locationList[] = $locItem; + } + + $encounterResource = [ 'resourceType' => 'Encounter', 'id' => 'encounter-' . $id, 'meta' => ['profile' => [ @@ -222,11 +272,17 @@ abstract class AbstractModuleFhirGenerator 'reference' => 'urn:uuid:patient-' . (string) ($source['patient']['id'] ?? ''), ], 'period' => [ - 'start' => (string) ($enc['start'] ?? $source['completed_at'] ?? date(DATE_ATOM)), - 'end' => (string) ($enc['end'] ?? $source['completed_at'] ?? date(DATE_ATOM)), + 'start' => $start, + 'end' => $end, ], 'identifier' => $identifiers, ]; + + if (! empty($locationList)) { + $encounterResource['location'] = $locationList; + } + + return $encounterResource; } /** @param array $source */ diff --git a/tests/unit/Abdm/FhirGeneratorsTest.php b/tests/unit/Abdm/FhirGeneratorsTest.php index a1acc2d..fee21cc 100644 --- a/tests/unit/Abdm/FhirGeneratorsTest.php +++ b/tests/unit/Abdm/FhirGeneratorsTest.php @@ -229,6 +229,9 @@ final class FhirGeneratorsTest extends CIUnitTestCase 'class_code' => 'IMP', 'start' => '2026-09-01T04:53:00+05:30', 'end' => '2026-09-04T04:58:00+05:30', + 'location_display' => 'Ward: General Ward, Bed: GW-05', + 'ward' => 'General Ward', + 'bed' => 'GW-05', ], 'doctor' => ['id' => 9, 'name' => 'Dr. Sanjay Kumar', 'registration_number' => 'DOC12345'], 'organization' => ['id' => 'IN0510000000', 'name' => 'DevSoft Tech'], @@ -333,6 +336,18 @@ final class FhirGeneratorsTest extends CIUnitTestCase $this->assertSame('General Examination on Admission', $observations[0]['resource']['category'][0]['text']); $this->assertSame('Examination on Discharge', $observations[7]['resource']['category'][0]['text']); + // Verify Encounter period & location + $encounters = array_values(array_filter($bundle['entry'], static fn (array $e): bool => ($e['resource']['resourceType'] ?? '') === 'Encounter')); + $this->assertCount(1, $encounters); + $encounterRes = $encounters[0]['resource']; + $this->assertSame('2026-09-01T04:53:00+05:30', $encounterRes['period']['start'] ?? null); + $this->assertSame('2026-09-04T04:58:00+05:30', $encounterRes['period']['end'] ?? null); + $this->assertNotEmpty($encounterRes['location'] ?? []); + $this->assertSame('Ward: General Ward, Bed: GW-05', $encounterRes['location'][0]['location']['display'] ?? null); + $this->assertSame('active', $encounterRes['location'][0]['status'] ?? null); + $this->assertSame('2026-09-01T04:53:00+05:30', $encounterRes['location'][0]['period']['start'] ?? null); + $this->assertSame('2026-09-04T04:58:00+05:30', $encounterRes['location'][0]['period']['end'] ?? null); + // Verify all references resolve $fullUrls = array_column($bundle['entry'], 'fullUrl'); $encoded = json_encode($bundle, JSON_UNESCAPED_SLASHES);