fix(nursing): auto-create patient_wellness_records, generate full NRCES FHIR bundle on save, and discover standalone wellness care contexts

This commit is contained in:
DevSoftTech Server 2026-10-07 09:47:23 +05:30
parent 8d00536d3b
commit 86dc076f5d
2 changed files with 124 additions and 8 deletions

View File

@ -6463,8 +6463,10 @@ class AbdmGateway extends BaseController
if ($ccRef === '') {
$ccRef = 'HR-' . (int) ($row['id'] ?? 0);
}
// Skip redundant/legacy separate PRESC- and WELLNESS- contexts (now unified under OPD-)
if (str_starts_with($ccRef, 'PRESC-') || str_starts_with($ccRef, 'WELLNESS-')) {
$rowEntType = strtolower(trim((string) ($row['entity_type'] ?? '')));
// Skip redundant/legacy separate PRESC- and WELLNESS- contexts (now unified under OPD-),
// but allow genuine standalone nursing Wellness records (entity_type = 'wellness')
if ($rowEntType !== 'wellness' && (str_starts_with($ccRef, 'PRESC-') || str_starts_with($ccRef, 'WELLNESS-'))) {
continue;
}
// OPD- and INVOICE- contexts are enriched with doctor/session/bill details in steps 4 & 6
@ -6473,7 +6475,7 @@ class AbdmGateway extends BaseController
}
$hiType = trim((string) ($row['hi_type'] ?? 'HealthDocumentRecord'));
$dateStr = date('d M Y', strtotime((string) ($row['created_at'] ?? $row['updated_at'] ?? 'now')));
$display = $hiType . ' - ' . $dateStr;
$display = ($hiType === 'WellnessRecord' ? 'Wellness & Vitals Record - ' : ($hiType . ' - ')) . $dateStr;
$addContext([
'careContextId' => $ccRef,

View File

@ -820,21 +820,134 @@ class NursingApi extends BaseController
'updated_at' => date('Y-m-d H:i:s'),
];
if (! $db->tableExists('patient_wellness_records')) {
$db->query("CREATE TABLE IF NOT EXISTS `patient_wellness_records` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`patient_id` int unsigned NOT NULL,
`uhid` varchar(50) DEFAULT NULL,
`abha_id` varchar(30) DEFAULT NULL,
`abha_address` varchar(120) DEFAULT NULL,
`recorded_at` datetime NOT NULL,
`temperature_f` decimal(5,2) DEFAULT NULL,
`temperature_c` decimal(5,2) DEFAULT NULL,
`pulse_rate` int DEFAULT NULL,
`resp_rate` int DEFAULT NULL,
`bp_systolic` int DEFAULT NULL,
`bp_diastolic` int DEFAULT NULL,
`spo2` int DEFAULT NULL,
`weight_kg` decimal(5,2) DEFAULT NULL,
`height_cm` decimal(5,2) DEFAULT NULL,
`bmi` decimal(5,2) DEFAULT NULL,
`general_advice` text,
`diet_lifestyle_note` text,
`recorded_by` varchar(120) DEFAULT 'Staff Nurse',
`recorded_by_id` int DEFAULT NULL,
`fhir_bundle_json` longtext,
`abdm_status` varchar(30) DEFAULT 'ready',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_patient_id` (`patient_id`),
KEY `idx_uhid` (`uhid`),
KEY `idx_recorded_at` (`recorded_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
}
$db->table('patient_wellness_records')->insert($wellnessData);
$wellnessId = $db->insertID();
// Also register in health_records for ABDM M2 / M3 discovery & push
// Build compliant ABDM NRCES FHIR R4 WellnessRecord Document Bundle
$cleanDate = date('Ymd', strtotime($recordedAt));
$careContextRef = 'WELLNESS-' . $patientId . '-W' . $wellnessId . '-' . $cleanDate;
$careContextDisplay = 'Wellness & Vitals Record - ' . date('d M Y', strtotime($recordedAt));
$fhirBundleJson = null;
try {
$sourceVitals = [];
$addVital = static function (string $code, string $display, mixed $val, string $unit, string $ucum) use (&$sourceVitals): void {
if ($val !== null && trim((string) $val) !== '') {
$sourceVitals[] = [
'loinc_code' => $code,
'code' => $code,
'display' => $display,
'value' => (float) $val,
'unit' => $unit,
'ucum_code' => $ucum,
];
}
};
$addVital('8480-6', 'Systolic blood pressure', $wellnessData['bp_systolic'], 'mmHg', 'mm[Hg]');
$addVital('8462-4', 'Diastolic blood pressure', $wellnessData['bp_diastolic'], 'mmHg', 'mm[Hg]');
$addVital('8867-4', 'Heart rate', $wellnessData['pulse_rate'], '/min', '/min');
$addVital('8302-2', 'Body height', $wellnessData['height_cm'], 'cm', 'cm');
$addVital('29463-7', 'Body weight', $wellnessData['weight_kg'], 'kg', 'kg');
$addVital('39156-5', 'Body Mass Index', $wellnessData['bmi'], 'kg/m2', 'kg/m2');
$addVital('8310-5', 'Body temperature', $wellnessData['temperature_c'] ?? $wellnessData['temperature_f'], 'Cel', 'Cel');
$addVital('9279-1', 'Respiratory rate', $wellnessData['resp_rate'], '/min', '/min');
$addVital('59408-5', 'Oxygen saturation in Arterial blood by Pulse oximetry', $wellnessData['spo2'], '%', '%');
$lifestyle = [];
if (! empty($wellnessData['diet_lifestyle_note'])) {
$lifestyle[] = [
'code' => 'diet-lifestyle',
'display' => 'Diet & Lifestyle Guidance',
'value' => $wellnessData['diet_lifestyle_note'],
];
}
if (! empty($wellnessData['general_advice'])) {
$lifestyle[] = [
'code' => 'general-nursing',
'display' => 'General Nursing Observations',
'value' => $wellnessData['general_advice'],
];
}
$source = [
'record_id' => (string) $wellnessId,
'visit_date' => date('Y-m-d', strtotime($recordedAt)),
'completed_at' => date(DATE_ATOM, strtotime($recordedAt)),
'hfr_id' => 'IN0510000828',
'patient' => [
'id' => $patientId,
'name' => trim(($patient['p_fname'] ?? '') . ' ' . ($patient['p_lname'] ?? '')),
'gender' => strtolower((string) ($patient['gender'] ?? 'male')),
'dob' => ! empty($patient['dob']) ? date('Y-m-d', strtotime((string) $patient['dob'])) : null,
'mobile' => (string) ($patient['mphone1'] ?? ''),
'abha_id' => (string) ($patient['abha_id'] ?? ''),
'abha_address' => (string) ($patient['abha_address'] ?? ''),
'p_code' => (string) ($patient['p_code'] ?? ''),
],
'practitioner' => [
'id' => (string) ($wellnessData['recorded_by_id'] ?? '1'),
'name' => (string) ($wellnessData['recorded_by'] ?? 'Staff Nurse'),
],
'vitals' => $sourceVitals,
'lifestyle' => $lifestyle,
];
$generator = new \App\Libraries\Abdm\Fhir\Generators\WellnessFhirGenerator();
$genResult = $generator->generate($source);
$fhirBundle = $genResult['fhir_bundle'] ?? [];
if (! empty($fhirBundle)) {
$fhirBundleJson = json_encode($fhirBundle, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$db->table('patient_wellness_records')->where('id', $wellnessId)->update(['fhir_bundle_json' => $fhirBundleJson]);
}
} catch (\Throwable $e) {
log_message('warning', '[savePatientWellness] FHIR generation error: ' . $e->getMessage());
}
// Also register in health_records for ABDM discovery & push
if ($db->tableExists('health_records')) {
$careContextRef = 'WELLNESS-' . $wellnessId . '-' . date('Ymd');
$db->table('health_records')->insert([
'patient_id' => $patientId,
'abha_id' => $patient['abha_id'] ?? null,
'abha_id' => $patient['abha_address'] ?? $patient['abha_id'] ?? null,
'hi_type' => 'WellnessRecord',
'entity_type' => 'wellness',
'entity_id' => (string) $wellnessId,
'push_status' => 'pending',
'push_status' => 'local_discovery_ready',
'care_context_reference' => $careContextRef,
'record_data' => json_encode($wellnessData),
'record_data' => $fhirBundleJson ?? json_encode($wellnessData),
'created_by_name' => (string) ($post['recorded_by'] ?? 'Staff Nurse'),
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
@ -845,6 +958,7 @@ class NursingApi extends BaseController
'status' => 1,
'message' => 'ABDM M2 Wellness & Vitals recorded successfully',
'wellness_id' => $wellnessId,
'care_context_reference' => $careContextRef,
'bmi' => $bmi,
]);
}