feat(abdm): add Encounter period admission/discharge times and location ward/bed mapping

This commit is contained in:
DevSoftTech Server 2026-09-04 05:44:37 +05:30
parent 4776bfb37b
commit 5714f9f755
4 changed files with 237 additions and 3 deletions

View File

@ -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<int,array<string,string>> */
private function buildIpdPdfDocuments(int $ipdId, string $dischargeRaw): array
{

View File

@ -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];

View File

@ -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<string,mixed> $source */

View File

@ -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);