diff --git a/app/Controllers/AbdmGateway.php b/app/Controllers/AbdmGateway.php index e15834e..86a585a 100644 --- a/app/Controllers/AbdmGateway.php +++ b/app/Controllers/AbdmGateway.php @@ -3466,8 +3466,20 @@ class AbdmGateway extends BaseController } } - // Add comorbidities from NABH + // Add comorbidities from NABH, with fallback to latest opd_prescription / history $coMorbRaw = trim((string) ($nabhMeta['co_morbidities'] ?? '')); + if ($coMorbRaw === '' && $patientId > 0 && $this->db && $this->db->tableExists('opd_prescription')) { + $latestPresc = $this->db->table('opd_prescription') + ->select('Prescriber_Remarks') + ->where('p_id', $patientId) + ->orderBy('id', 'DESC') + ->get(1)->getRowArray(); + if (! empty($latestPresc['Prescriber_Remarks'])) { + if (preg_match('/^\s*Co-Morbidities\s*:\s*(.+)$/im', (string) $latestPresc['Prescriber_Remarks'], $m)) { + $coMorbRaw = trim($m[1]); + } + } + } if ($coMorbRaw !== '') { $coMorbParts = preg_split('/[,;\n]+/', $coMorbRaw); foreach ($coMorbParts as $cPart) { diff --git a/app/Controllers/Billing/Ipd.php b/app/Controllers/Billing/Ipd.php index bb3f13d..0ebd3eb 100644 --- a/app/Controllers/Billing/Ipd.php +++ b/app/Controllers/Billing/Ipd.php @@ -1074,6 +1074,16 @@ class Ipd extends BaseController ->where('id', $caseId) ->update(['status' => 1]); } + + // Trigger complete, final FHIR Discharge Summary generation & ABDM sync + try { + $patientId = (int) ($currentIpd['p_id'] ?? 0); + $dischargeCtrl = new \App\Controllers\Ipd_discharge(); + $dischargeCtrl->initController($this->request, $this->response, service('logger')); + $dischargeCtrl->enqueueIpdDischargeSync($ipdId, $patientId, 'final_discharge'); + } catch (\Throwable $e) { + log_message('warning', '[updateDischargeStatus] ABDM discharge sync trigger failed: ' . $e->getMessage()); + } } return $this->buildDischargeProcessResponse($ipdId, 'Discharge status updated.'); diff --git a/app/Controllers/Ipd_discharge.php b/app/Controllers/Ipd_discharge.php index 09850fe..d4c17b2 100644 --- a/app/Controllers/Ipd_discharge.php +++ b/app/Controllers/Ipd_discharge.php @@ -9361,7 +9361,17 @@ class Ipd_discharge extends BaseController // Do not block discharge workflows if queue service is unavailable. } - // Also persist ABDM-compatible FHIR bundle in sync tables/outbox for M2 push. + // Only persist ABDM-compatible FHIR bundle if the patient is officially discharged + // (discarge_patient_status > 0 and valid discharge_date) or explicitly finalized. + // Intermediate draft saves while patient is still admitted must not create incomplete bundles. + $dischargeStatus = (int) ($ipdRow['discarge_patient_status'] ?? 0); + $dischargeDate = trim((string) ($ipdRow['discharge_date'] ?? '')); + $isDischarged = $dischargeStatus > 0 && $dischargeDate !== '' && $dischargeDate !== '0000-00-00'; + + if (! $isDischarged && ! in_array($action, ['final_discharge', 'force', 'auto_push'], true)) { + return; + } + // Fail-open: discharge save flow must never fail due to ABDM sync issues. try { $this->enqueueIpdDischargeFhirSync($ipdId, $patientId, $ipdRow, $patientRow); @@ -9478,8 +9488,19 @@ class Ipd_discharge extends BaseController } } - // Comorbidities from NABH metadata + // Comorbidities from NABH metadata with fallback to latest opd_prescription / history $coMorbRaw = trim((string) ($nabhMeta['co_morbidities'] ?? '')); + if ($coMorbRaw === '' && $patientId > 0 && $this->db->tableExists('opd_prescription')) { + $latestOpdRow = $this->db->table('opd_prescription') + ->where('p_id', $patientId) + ->orderBy('id', 'DESC') + ->get(1) + ->getRowArray(); + if (! empty($latestOpdRow)) { + $hydratedNabh = $this->hydrateNabhFieldsFromOpdRow($latestOpdRow); + $coMorbRaw = trim((string) ($hydratedNabh['co_morbidities'] ?? '')); + } + } if ($coMorbRaw !== '') { $coMorbParts = preg_split('/[,;\n]+/', $coMorbRaw); foreach ($coMorbParts as $cPart) { diff --git a/app/Libraries/Abdm/Fhir/Generators/DischargeFhirGenerator.php b/app/Libraries/Abdm/Fhir/Generators/DischargeFhirGenerator.php index 218e02e..59b4498 100644 --- a/app/Libraries/Abdm/Fhir/Generators/DischargeFhirGenerator.php +++ b/app/Libraries/Abdm/Fhir/Generators/DischargeFhirGenerator.php @@ -326,6 +326,114 @@ class DischargeFhirGenerator extends \App\Libraries\Abdm\Fhir\Generators\Abstrac $builder->addMedicationRequest($medResource); } + // ── Physical Examination & Vital Signs ────────────────────────────── + $rawObservations = (array) ($source['ui_physical_exam'] ?? $source['observations'] ?? []); + $validObservations = []; + foreach ($rawObservations as $idx => $obs) { + if (! is_array($obs)) { + continue; + } + $obsText = trim((string) ($obs['text'] ?? $obs['name'] ?? '')); + $obsValue = trim((string) ($obs['value'] ?? '')); + if ($obsText === '' || ! $this->isMeaningfulValue($obsText)) { + continue; + } + if ($obsValue === '' || ! $this->isMeaningfulValue($obsValue)) { + continue; + } + $validObservations[$idx] = $obs; + } + + foreach ($validObservations as $idx => $obs) { + $obsText = trim((string) ($obs['text'] ?? $obs['name'] ?? '')); + $obsValue = trim((string) ($obs['value'] ?? '')); + $obsCategoryText = trim((string) ($obs['category'] ?? 'Physical Examination')); + $obsCategoryCode = trim((string) ($obs['category_code'] ?? '')); + $effectiveAt = (string) ($obs['effective_at'] ?? $obs['effective_date_time'] ?? $timestamp); + + $isVital = in_array(strtolower($obsCategoryCode), ['vital-signs', 'vitals'], true) + || preg_match('/^(pulse|heart rate|bp|blood pressure|respiration|respiratory rate|temperature|temp|spo2|oxygen)/i', $obsText); + + $catCode = $isVital ? 'vital-signs' : 'exam'; + $catDisplay = $isVital ? 'Vital Signs' : 'Exam'; + + $observationId = 'discharge-observation-' . $recordId . '-' . $idx; + + $coding = []; + $lowerText = strtolower($obsText); + if (str_contains($lowerText, 'pulse') || str_contains($lowerText, 'heart rate')) { + $coding[] = ['system' => 'http://snomed.info/sct', 'code' => '8499008', 'display' => 'Pulse rate']; + } elseif (str_contains($lowerText, 'respiration') || str_contains($lowerText, 'respiratory')) { + $coding[] = ['system' => 'http://snomed.info/sct', 'code' => '86290005', 'display' => 'Respiratory rate']; + } elseif (str_contains($lowerText, 'pallor')) { + $coding[] = ['system' => 'http://snomed.info/sct', 'code' => '271782001', 'display' => 'Pallor']; + } elseif (str_contains($lowerText, 'jaundice') || str_contains($lowerText, 'icterus')) { + $coding[] = ['system' => 'http://snomed.info/sct', 'code' => '18165001', 'display' => 'Jaundice']; + } elseif (str_contains($lowerText, 'cyanosis')) { + $coding[] = ['system' => 'http://snomed.info/sct', 'code' => '3415004', 'display' => 'Cyanosis']; + } elseif (str_contains($lowerText, 'clubbing')) { + $coding[] = ['system' => 'http://snomed.info/sct', 'code' => '30760006', 'display' => 'Clubbing']; + } elseif (str_contains($lowerText, 'edema') || str_contains($lowerText, 'oedema')) { + $coding[] = ['system' => 'http://snomed.info/sct', 'code' => '267038008', 'display' => 'Edema']; + } elseif (str_contains($lowerText, 'jvp') || str_contains($lowerText, 'jugular')) { + $coding[] = ['system' => 'http://snomed.info/sct', 'code' => '271649006', 'display' => 'Jugular venous pressure']; + } elseif (str_contains($lowerText, 'bp') || str_contains($lowerText, 'blood pressure')) { + $coding[] = ['system' => 'http://snomed.info/sct', 'code' => '75367002', 'display' => 'Blood pressure']; + } elseif (str_contains($lowerText, 'temp')) { + $coding[] = ['system' => 'http://snomed.info/sct', 'code' => '386725007', 'display' => 'Body temperature']; + } elseif (str_contains($lowerText, 'spo2') || str_contains($lowerText, 'oxygen saturation')) { + $coding[] = ['system' => 'http://snomed.info/sct', 'code' => '431314004', 'display' => 'Oxygen saturation']; + } else { + $resolved = $this->codingResolver->resolveSnomedForDiagnosisOrFinding((string) ($obs['code'] ?? ''), $obsText); + $coding = $this->withoutPlaceholderCoding((array) ($resolved['coding'] ?? [])); + if ($coding === []) { + $coding[] = ['system' => 'http://snomed.info/sct', 'code' => '404684003', 'display' => 'Clinical finding']; + } + } + + $obsResource = [ + 'resourceType' => 'Observation', + 'id' => $observationId, + 'meta' => ['profile' => ['https://nrces.in/ndhm/fhir/r4/StructureDefinition/Observation']], + 'text' => [ + 'status' => 'generated', + 'div' => '
' . htmlspecialchars($obsText) . ' (' . htmlspecialchars($obsCategoryText) . '): ' . htmlspecialchars($obsValue) . '