From 6e78e6010e8a97df5cb61c875651044f13f6db79 Mon Sep 17 00:00:00 2001 From: DevSoftTech Server Date: Mon, 21 Sep 2026 23:41:24 +0530 Subject: [PATCH] feat: add Day Care & Emergency admission module with IPD conversion and clean patient names - Add Day Care (<24h) and Emergency (Casualty) admission pathways with prefixes DC- and ER- - Add dedicated physical wards, ESI 5-level triage, MLC tracking, and PADSS day care discharge - Implement audited 'Convert to IPD' workflow with SBAR handover and automated bed transfer - Support unified IPD billing with dedicated group headers for Day Care and Emergency charges - Redesign Hospital Admission Desk with executive card layout and modern triage controls - Add comprehensive user documentation and SVG flowchart in public/help.html - Completely remove legacy patient_master.p_lname column from database and application code --- app/Config/Routes.php | 5 + app/Controllers/AbdmGateway.php | 33 +- app/Controllers/AbdmTaskBoard.php | 2 +- app/Controllers/Abha.php | 12 +- app/Controllers/Api/v1/MedicalStoreApi.php | 6 +- app/Controllers/Api/v1/NursingApi.php | 6 +- app/Controllers/Billing/Ipd.php | 191 ++- app/Controllers/DoctorDocument.php | 11 +- app/Controllers/Immunization.php | 2 +- app/Controllers/IpdPatient.php | 4 +- app/Controllers/Ipd_discharge.php | 16 +- app/Controllers/Opd_prescription.php | 14 +- app/Controllers/Patient.php | 2 +- app/Controllers/Payment.php | 2 +- app/Controllers/Report.php | 2 +- app/Controllers/Setting/BedManagement.php | 4 +- ...22-000001_CreateDaycareEmergencyModule.php | 320 +++++ .../Abdm/ConsentSessionListService.php | 2 +- .../AbstractModuleFhirGenerator.php | 25 +- .../Abdm/Sync/AbdmSyncWorkerService.php | 7 +- app/Models/IpdBillingModel.php | 21 + app/Models/IpdModel.php | 114 +- app/Views/Setting/Bed/ward_list.php | 1 + app/Views/billing/Patient_Profile_Opd_V.php | 2 +- app/Views/billing/Person_profile_V.php | 24 +- app/Views/billing/ipd/admit_patient.php | 272 ++++ app/Views/billing/ipd/current_admission.php | 110 +- app/Views/billing/ipd/dashboard.php | 9 + app/Views/billing/ipd/legacy_registration.php | 1171 ++++++++++++----- app/Views/billing/ipd/panel.php | 225 +++- app/Views/immunization/index.php | 2 +- app/Views/partials/sidebar.php | 20 +- public/help.html | 452 ++++++- 33 files changed, 2634 insertions(+), 455 deletions(-) create mode 100644 app/Database/Migrations/2026-09-22-000001_CreateDaycareEmergencyModule.php create mode 100644 app/Views/billing/ipd/admit_patient.php diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 588e3eb..4197f3a 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -425,6 +425,7 @@ $routes->post('setting/template/report_ultrasound_insert/(:num)', 'Setting\Templ $routes->get('IpdNew/show_ipd_items/(:num)', 'Billing\\Ipd::panelTab/$1/ipd-charges'); $routes->get('IpdNew/show_ipd_items/(:num)/(:num)', 'Billing\\Ipd::panelTab/$1/ipd-charges'); // Legacy compatibility: patient profile still calls old new-IPD URL. +$routes->get('IpdNew/admit', 'Billing\\Ipd::admit'); $routes->get('IpdNew/addipd/(:num)', 'Billing\\Ipd::legacyAddIpd/$1'); $routes->post('IpdNew/AddNew', 'Billing\\Ipd::legacyAddNew'); $routes->post('IpdNew/addnew', 'Billing\\Ipd::legacyAddNew'); @@ -498,11 +499,15 @@ $routes->post('ot/cases/(:num)/postop', 'IpdOt::savePostop/$1', ['filter' => 'pe $routes->group('billing', function($routes) { $routes->get('ipd', 'Billing\\Ipd::index'); + $routes->get('ipd/admit', 'Billing\\Ipd::admit'); + $routes->match(['GET', 'POST'], 'ipd/search-patient-admit', 'Billing\\Ipd::searchPatientForAdmit'); $routes->get('ipd/current-admission', 'Billing\\Ipd::currentAdmission'); $routes->get('ipd/invoices', 'Billing\\Ipd::ipdInvoices'); $routes->post('ipd/list', 'Billing\\Ipd::getIpdTable'); $routes->get('ipd/panel/(:num)', 'Billing\\Ipd::panel/$1'); $routes->get('ipd/panel/(:num)/tab/(:segment)', 'Billing\\Ipd::panelTab/$1/$2'); + $routes->post('ipd/convert-to-ipd', 'Billing\\Ipd::convertToIpd'); + $routes->get('ipd/available-ipd-beds', 'Billing\\Ipd::getAvailableIpdBeds'); $routes->post('ipd/panel/(:num)/admission', 'Billing\Ipd::updateAdmission/$1'); $routes->post('ipd/panel/(:num)/diagnosis/include/(:num)', 'Billing\\Ipd::updateDiagnosisChargeInclude/$1/$2'); $routes->post('ipd/panel/(:num)/discharge/update', 'Billing\\Ipd::updateDischargeProcess/$1'); diff --git a/app/Controllers/AbdmGateway.php b/app/Controllers/AbdmGateway.php index 2eb7ec2..7c87dbe 100644 --- a/app/Controllers/AbdmGateway.php +++ b/app/Controllers/AbdmGateway.php @@ -148,11 +148,7 @@ class AbdmGateway extends BaseController if ($match !== null) { $patient = $match['patient']; $patientId = (int) $patient['id']; - $lName = trim((string) ($patient['p_lname'] ?? '')); $patientDisplay = trim((string) ($patient['p_fname'] ?? '')); - if ($lName !== '' && $lName !== '0') { - $patientDisplay .= ' ' . $lName; - } if ($patientDisplay === '') { $patientDisplay = 'Patient ' . $patientRef; } @@ -4627,11 +4623,7 @@ class AbdmGateway extends BaseController $patient = $match['patient']; $patientId = (int) $patient['id']; $patientRef = (string) ($patient['p_code'] ?: ('P-' . $patientId)); - $lName = trim((string) ($patient['p_lname'] ?? '')); $patientDisplay = trim((string) ($patient['p_fname'] ?? '')); - if ($lName !== '' && $lName !== '0') { - $patientDisplay .= ' ' . $lName; - } if ($patientDisplay === '') { $patientDisplay = 'Patient ' . $patientRef; } @@ -4860,9 +4852,7 @@ class AbdmGateway extends BaseController $patientName = ''; if (!empty($patient)) { - $lName = trim((string) ($patient['p_lname'] ?? '')); - $fName = trim((string) ($patient['p_fname'] ?? '')); - $patientName = trim($fName . ($lName !== '' && $lName !== '0' ? ' ' . $lName : '')); + $patientName = trim((string) ($patient['p_fname'] ?? '')); } if ($patientName === '') { $patientName = $patient['patient_name'] ?? 'Patient'; @@ -5285,11 +5275,7 @@ class AbdmGateway extends BaseController $this->db->transComplete(); $patient = $this->db->table('patient_master')->where('id', $patientId)->get(1)->getRowArray() ?? []; - $lName = trim((string) ($patient['p_lname'] ?? '')); $patientDisplay = trim((string) ($patient['p_fname'] ?? '')); - if ($lName !== '' && $lName !== '0') { - $patientDisplay .= ' ' . $lName; - } $patientRef = (string) ($patient['p_code'] ?? $txn['patient_ref'] ?? $patientId); $confirmedContexts = []; @@ -5625,11 +5611,7 @@ class AbdmGateway extends BaseController // 6. Name match if ($name !== '') { - $candLName = trim((string) ($cand['p_lname'] ?? '')); $candName = trim((string) ($cand['p_fname'] ?? '')); - if ($candLName !== '' && $candLName !== '0') { - $candName .= ' ' . $candLName; - } $nameScore = $this->calculateNameMatchScore($name, $candName); if ($nameScore > 0) { $score += (int) ($nameScore * 25); @@ -8462,14 +8444,7 @@ class AbdmGateway extends BaseController private function patientDisplayName(array $patientRow): string { $first = trim((string) ($patientRow['p_fname'] ?? '')); - $last = trim((string) ($patientRow['p_lname'] ?? '')); - - if (in_array(strtolower($last), ['0', '00', 'na', 'n/a', 'null', 'nil', '-'], true)) { - $last = ''; - } - - $full = trim($first . ' ' . $last); - return $full !== '' ? $full : 'Patient'; + return $first !== '' ? $first : 'Patient'; } /** @@ -10339,11 +10314,7 @@ class AbdmGateway extends BaseController $patientId = (int) $patient['id']; $patientRef = (string) ($patient['p_code'] ?: ('P-' . $patientId)); - $lName = trim((string) ($patient['p_lname'] ?? '')); $patientName = trim((string) ($patient['p_fname'] ?? '')); - if ($lName !== '' && $lName !== '0') { - $patientName .= ' ' . $lName; - } if ($patientName === '') { $patientName = 'Patient ' . $patientRef; } diff --git a/app/Controllers/AbdmTaskBoard.php b/app/Controllers/AbdmTaskBoard.php index 72cddc7..06bbb43 100644 --- a/app/Controllers/AbdmTaskBoard.php +++ b/app/Controllers/AbdmTaskBoard.php @@ -665,7 +665,7 @@ class AbdmTaskBoard extends BaseController if ($this->db->tableExists('ipd_master')) { foreach ($this->db->table('ipd_master i') - ->select("i.id AS bill_id, i.ipd_code AS bill_code, i.p_id AS patient_id, COALESCE(NULLIF(NULLIF(TRIM(i.P_name), ''), '0'), TRIM(CONCAT_WS(' ', p.p_fname, p.p_lname))) AS patient_name, COALESCE(i.discharge_date, i.register_date) AS bill_date, i.net_amount AS amount", false) + ->select("i.id AS bill_id, i.ipd_code AS bill_code, i.p_id AS patient_id, COALESCE(NULLIF(NULLIF(TRIM(i.P_name), ''), '0'), NULLIF(TRIM(p.p_fname), '')) AS patient_name, COALESCE(i.discharge_date, i.register_date) AS bill_date, i.net_amount AS amount", false) ->join('patient_master p', 'p.id = i.p_id', 'left') ->orderBy('i.id', 'DESC') ->limit(100) diff --git a/app/Controllers/Abha.php b/app/Controllers/Abha.php index d162c7e..5f958d7 100644 --- a/app/Controllers/Abha.php +++ b/app/Controllers/Abha.php @@ -2034,7 +2034,7 @@ class Abha extends BaseController $candidateAbhaCols = array_values(array_intersect(['abha_id', 'abha_no', 'abha', 'abha_address'], $fields)); $selectFields = array_values(array_intersect( - ['id', 'p_code', 'p_fname', 'p_lname', 'gender', 'dob', 'age', 'mphone1', 'udai', 'add1', 'district', 'state', 'zip', 'profile_file_id', 'profile_picture', 'abha_profile_photo_base64'], + ['id', 'p_code', 'p_fname', 'gender', 'dob', 'age', 'mphone1', 'udai', 'add1', 'district', 'state', 'zip', 'profile_file_id', 'profile_picture', 'abha_profile_photo_base64'], $fields )); foreach ($candidateAbhaCols as $cCol) { @@ -2106,13 +2106,9 @@ class Abha extends BaseController }); if ($nameTokens !== []) { - $hasLastName = in_array('p_lname', $fields, true); - $collect(static function ($builder) use ($nameTokens, $hasLastName): void { + $collect(static function ($builder) use ($nameTokens): void { foreach ($nameTokens as $token) { $builder->orLike('p_fname', $token); - if ($hasLastName) { - $builder->orLike('p_lname', $token); - } } }); } @@ -2123,7 +2119,7 @@ class Abha extends BaseController $candidates = []; foreach ($rows as $row) { - $rowName = strtoupper(trim((string) ($row['p_fname'] ?? '') . ' ' . (string) ($row['p_lname'] ?? ''))); + $rowName = strtoupper(trim((string) ($row['p_fname'] ?? ''))); $rowTokens = array_values(array_filter(preg_split('/\s+/', $rowName) ?: [], fn ($t) => strlen($t) >= 3)); $nameOverlap = $nameTokens !== [] && $rowTokens !== [] && $nameTokens[0] === $rowTokens[0]; @@ -2179,7 +2175,7 @@ class Abha extends BaseController $candidates[] = [ 'id' => (int) ($row['id'] ?? 0), 'p_code' => (string) ($row['p_code'] ?? ''), - 'name' => trim((string) ($row['p_fname'] ?? '') . ' ' . (string) ($row['p_lname'] ?? '')), + 'name' => trim((string) ($row['p_fname'] ?? '')), 'gender' => (int) ($row['gender'] ?? 0), 'gender_label' => $this->genderLabel((int) ($row['gender'] ?? 0)), 'dob' => $rowDob, diff --git a/app/Controllers/Api/v1/MedicalStoreApi.php b/app/Controllers/Api/v1/MedicalStoreApi.php index c68faaa..4de6c7e 100644 --- a/app/Controllers/Api/v1/MedicalStoreApi.php +++ b/app/Controllers/Api/v1/MedicalStoreApi.php @@ -130,13 +130,11 @@ class MedicalStoreApi extends BaseController // Search patient_master by UHID, Mobile, Name, ABHA ID, ABHA Address $builder = $this->db->table('patient_master p'); - $builder->select('p.id as patient_id, p.p_code as uhid, p.p_fname, p.p_lname, p.title, p.mphone1, p.gender, p.age, p.dob, p.add1, p.city, p.abha_id, p.abha_address, p.abha_verified_status, p.abha_kyc_verified'); + $builder->select('p.id as patient_id, p.p_code as uhid, p.p_fname, p.title, p.mphone1, p.gender, p.age, p.dob, p.add1, p.city, p.abha_id, p.abha_address, p.abha_verified_status, p.abha_kyc_verified'); $builder->groupStart() ->like('p.p_code', $q) ->orLike('p.mphone1', $q) ->orLike('p.p_fname', $q) - ->orLike('p.p_lname', $q) - ->orLike("CONCAT(p.p_fname, ' ', p.p_lname)", $q) ->orLike('p.abha_id', $q) ->orLike('p.abha_address', $q) ->groupEnd(); @@ -146,7 +144,7 @@ class MedicalStoreApi extends BaseController foreach ($patients as $pt) { $pId = (int)$pt['patient_id']; $genderStr = ((int)$pt['gender'] === 1) ? 'Male' : (((int)$pt['gender'] === 2) ? 'Female' : 'Other'); - $fullName = trim(($pt['title'] ? $pt['title'] . ' ' : '') . $pt['p_fname'] . ' ' . ($pt['p_lname'] ?? '')); + $fullName = trim(($pt['title'] ? $pt['title'] . ' ' : '') . $pt['p_fname']); // Check Active OPD for today $opdRow = $this->db->table('opd_master') diff --git a/app/Controllers/Api/v1/NursingApi.php b/app/Controllers/Api/v1/NursingApi.php index cb39241..c6b3d95 100644 --- a/app/Controllers/Api/v1/NursingApi.php +++ b/app/Controllers/Api/v1/NursingApi.php @@ -671,7 +671,7 @@ class NursingApi extends BaseController $cleanAbha = str_replace('-', '', $q); $builder = $db->table('patient_master p'); - $builder->select('p.id, p.p_code, p.p_fname, p.p_lname, p.title, p.gender, p.dob, p.age, p.mphone1, p.mphone2, p.email1, p.city, p.district, p.state, p.abha_id, p.abha_address, p.abha_verified_status, p.abha_kyc_verified, p.abha_mobile_verified, p.abha_profile_photo_base64'); + $builder->select('p.id, p.p_code, p.p_fname, p.title, p.gender, p.dob, p.age, p.mphone1, p.mphone2, p.email1, p.city, p.district, p.state, p.abha_id, p.abha_address, p.abha_verified_status, p.abha_kyc_verified, p.abha_mobile_verified, p.abha_profile_photo_base64'); $builder->groupStart(); $builder->where('p.p_code', $q); @@ -696,8 +696,6 @@ class NursingApi extends BaseController if (strlen($q) >= 2) { $builder->orLike('p.p_fname', $q); - $builder->orLike('p.p_lname', $q); - $builder->orLike("CONCAT(p.p_fname, ' ', p.p_lname)", $q); $builder->orLike('p.mphone1', $q); $builder->orLike('p.p_code', $q); } @@ -709,7 +707,7 @@ class NursingApi extends BaseController $today = date('Y-m-d'); foreach ($patients as &$pat) { $pId = (int) $pat['id']; - $pat['fullName'] = trim(($pat['title'] ? $pat['title'] . ' ' : '') . $pat['p_fname'] . ($pat['p_lname'] && $pat['p_lname'] !== '0' ? ' ' . $pat['p_lname'] : '')); + $pat['fullName'] = trim(($pat['title'] ? $pat['title'] . ' ' : '') . $pat['p_fname']); // Check IPD admission $ipdRow = $db->table('ipd_master i') diff --git a/app/Controllers/Billing/Ipd.php b/app/Controllers/Billing/Ipd.php index 72032fe..f72114d 100644 --- a/app/Controllers/Billing/Ipd.php +++ b/app/Controllers/Billing/Ipd.php @@ -184,6 +184,85 @@ class Ipd extends BaseController return view('billing/ipd/panel', $panelData); } + public function admit() + { + $permission = $this->requireAnyPermission([ + 'billing.access', + 'billing.ipd.access', + 'billing.ipd.current-admission', + 'ipd_nursing.view', + ]); + if ($permission) { + return $permission; + } + + $defaultType = strtolower(trim((string) ($this->request->getGet('type') ?? ''))); + if (! in_array($defaultType, ['ipd', 'daycare', 'emergency'], true)) { + $defaultType = 'emergency'; + } + + return view('billing/ipd/admit_patient', [ + 'default_type' => $defaultType, + ]); + } + + public function searchPatientForAdmit() + { + $permission = $this->requireAnyPermission([ + 'billing.access', + 'billing.ipd.access', + 'billing.ipd.current-admission', + 'ipd_nursing.view', + ]); + if ($permission) { + return $this->response->setStatusCode(403)->setJSON(['error' => 'Permission denied']); + } + + $term = trim((string) ($this->request->getGet('q') ?? $this->request->getPost('q') ?? '')); + if ($term === '') { + return $this->response->setJSON([]); + } + + $termClean = preg_replace('/[^A-Za-z0-9 _.@\/-]/', '', $term); + if ($termClean === '') { + return $this->response->setJSON([]); + } + + $builder = $this->db->table('patient_master p'); + $builder->select('p.id, p.p_code, p.p_fname as patient_name, p.gender, p.age, p.dob, p.mphone1, p.mphone2'); + $builder->groupStart() + ->like('p.p_fname', $termClean) + ->orLike('p.p_code', $termClean) + ->orLike('p.mphone1', $termClean) + ->orLike('p.mphone2', $termClean) + ->groupEnd(); + $builder->limit(20); + $results = $builder->get()->getResultArray(); + + $patientIds = array_column($results, 'id'); + $activeIpds = []; + if (! empty($patientIds)) { + $rows = $this->db->table('ipd_master') + ->select('id, p_id, coalesce(ipd_code, pid) as ipd_code, admission_type, ipd_status') + ->whereIn('p_id', $patientIds) + ->where('ipd_status', 0) + ->get() + ->getResultArray(); + foreach ($rows as $r) { + $activeIpds[(int) $r['p_id']] = $r; + } + } + + foreach ($results as &$pt) { + $genderVal = (int) ($pt['gender'] ?? 0); + $pt['gender_text'] = ($genderVal === 1) ? 'Male' : (($genderVal === 2) ? 'Female' : 'Other'); + $pt['active_ipd'] = $activeIpds[(int) $pt['id']] ?? null; + } + unset($pt); + + return $this->response->setJSON($results); + } + public function legacyAddIpd(int $patientId) { $permission = $this->requireAnyPermission([ @@ -217,6 +296,12 @@ class Ipd extends BaseController return $this->response->setStatusCode(404)->setBody('Patient not found'); } + $defaultType = strtolower(trim((string) ($this->request->getGet('type') ?? 'ipd'))); + if (! in_array($defaultType, ['ipd', 'daycare', 'emergency'], true)) { + $defaultType = 'ipd'; + } + $data['default_admission_type'] = $defaultType; + return view('billing/ipd/legacy_registration', $data); } @@ -252,6 +337,19 @@ class Ipd extends BaseController $registerDateInput = (string) ($this->request->getPost('res_date') ?? ''); $registerDate = $registerDateInput !== '' ? str_to_MysqlDate($registerDateInput) : date('Y-m-d'); + $admissionType = strtolower(trim((string) ($this->request->getPost('admission_type') ?? 'ipd'))); + if (! in_array($admissionType, ['ipd', 'daycare', 'emergency'], true)) { + $admissionType = 'ipd'; + } + + $triageLevel = (int) ($this->request->getPost('triage_level') ?? 0); + $isMlc = (int) ($this->request->getPost('optionsRadios_mlc') ?? 0); + $mlcNumber = trim((string) ($this->request->getPost('mlc_number') ?? '')); + $policeStation = trim((string) ($this->request->getPost('police_station') ?? '')); + $policeConstable = trim((string) ($this->request->getPost('police_constable_details') ?? '')); + $broughtBy = trim((string) ($this->request->getPost('brought_by') ?? '')); + $daycareProcedure = trim((string) ($this->request->getPost('daycare_procedure_name') ?? '')); + $master = [ 'p_id' => $patientId, 'P_name' => (string) ($this->request->getPost('pname') ?? ''), @@ -264,11 +362,20 @@ class Ipd extends BaseController 'remark' => (string) ($this->request->getPost('remark') ?? ''), 'P_mobile1' => (string) ($this->request->getPost('phone1') ?? ''), 'P_mobile2' => (string) ($this->request->getPost('phone2') ?? ''), - 'case_type' => (int) ($this->request->getPost('optionsRadios_mlc') ?? 0), + 'case_type' => $isMlc, 'case_id' => (int) ($this->request->getPost('optionsRadios_org') ?? 0), 'reg_time' => (string) ($this->request->getPost('res_time') ?? date('H:i')), 'refer_by' => (int) ($this->request->getPost('refer_by_list') ?? 0), 'dept_id' => (int) ($this->request->getPost('dept_id') ?? 0), + 'admission_type' => $admissionType, + 'triage_level' => $triageLevel > 0 ? $triageLevel : null, + 'triage_time' => $triageLevel > 0 ? date('Y-m-d H:i:s') : null, + 'is_mlc' => $isMlc, + 'mlc_number' => $mlcNumber !== '' ? $mlcNumber : null, + 'police_station' => $policeStation !== '' ? $policeStation : null, + 'police_constable_details' => $policeConstable !== '' ? $policeConstable : null, + 'brought_by' => $broughtBy !== '' ? $broughtBy : null, + 'daycare_procedure_name' => $daycareProcedure !== '' ? $daycareProcedure : null, ]; $insertId = $this->ipdEditModel->insertIpd([ @@ -302,6 +409,83 @@ class Ipd extends BaseController ]); } + public function convertToIpd() + { + $permission = $this->requireAnyPermission([ + 'billing.access', + 'billing.ipd.invoice', + 'ipd_nursing.view', + ]); + if ($permission) { + return $permission; + } + + $ipdId = (int) ($this->request->getPost('ipd_id') ?? 0); + $targetBedId = (int) ($this->request->getPost('target_bed_id') ?? 0); + $doctorId = (int) ($this->request->getPost('doctor_id') ?? 0); + $reason = trim((string) ($this->request->getPost('conversion_reason') ?? '')); + + $sbarSituation = trim((string) ($this->request->getPost('sbar_situation') ?? '')); + $sbarBackground = trim((string) ($this->request->getPost('sbar_background') ?? '')); + $sbarAssessment = trim((string) ($this->request->getPost('sbar_assessment') ?? '')); + $sbarRecommendation = trim((string) ($this->request->getPost('sbar_recommendation') ?? '')); + + $sbarNote = "S (Situation): " . $sbarSituation . "\n" + . "B (Background): " . $sbarBackground . "\n" + . "A (Assessment): " . $sbarAssessment . "\n" + . "R (Recommendation): " . $sbarRecommendation; + + $result = $this->ipdEditModel->convertToIpd($ipdId, [ + 'target_bed_id' => $targetBedId, + 'doctor_id' => $doctorId, + 'conversion_reason' => $reason, + 'sbar_handover' => trim($sbarNote), + ]); + + if (! empty($result['status'])) { + $this->enqueueIpdAdmissionSync($ipdId, 'ipd.admission.escalated'); + } + + return $this->response->setJSON($result); + } + + public function getAvailableIpdBeds() + { + $permission = $this->requireAnyPermission([ + 'billing.access', + 'billing.ipd.invoice', + 'ipd_nursing.view', + ]); + if ($permission) { + return $permission; + } + + $beds = $this->db->table('bed_master b') + ->select('b.id, b.bed_number, b.bed_code, w.ward_name, w.ward_type, w.floor_number') + ->join('ward_master w', 'w.id = b.ward_id', 'left') + ->where('b.bed_status', 'available') + ->where('b.status', 'active') + ->where('b.current_ipd_id IS NULL') + ->orderBy('w.ward_type', 'ASC') + ->orderBy('w.ward_name', 'ASC') + ->orderBy('b.bed_number', 'ASC') + ->get() + ->getResultArray(); + + $doctors = $this->db->table('doctor_master') + ->select("id, concat_ws(' ', 'Dr.', p_fname, p_mname, p_lname) as doc_name") + ->where('status', '1') + ->orderBy('p_fname', 'ASC') + ->get() + ->getResultArray(); + + return $this->response->setJSON([ + 'status' => true, + 'beds' => $beds, + 'doctors' => $doctors, + ]); + } + private function buildLegacyIpdRegistrationData(int $patientId): array { $person = $this->db->table('patient_master') @@ -333,11 +517,12 @@ class Ipd extends BaseController if ($this->db->tableExists('bed_master')) { $ipdBedList = $this->db->query( "select b.id, - concat('Bed No:', coalesce(b.bed_number, b.bed_code), '/', '[', coalesce(w.ward_name, ''), ']') as Bed_Desc + w.ward_type, + concat('Bed No:', coalesce(b.bed_number, b.bed_code), '/', '[', coalesce(w.ward_name, ''), ']', if(w.ward_type is not null and w.ward_type != '', concat(' (', w.ward_type, ')'), '')) as Bed_Desc from bed_master b left join ward_master w on w.id = b.ward_id where b.bed_status = 'available' - order by b.bed_number, b.id" + order by w.ward_type, b.bed_number, b.id" )->getResult(); } elseif ($this->db->tableExists('hc_bed_master')) { $ipdBedList = $this->db->query( diff --git a/app/Controllers/DoctorDocument.php b/app/Controllers/DoctorDocument.php index 24c8d74..b755219 100644 --- a/app/Controllers/DoctorDocument.php +++ b/app/Controllers/DoctorDocument.php @@ -401,7 +401,7 @@ class DoctorDocument extends BaseController } $builder = $this->db->table('ipd_master') - ->select('ipd_master.*, patient_master.p_fname, patient_master.p_lname, patient_master.p_code, patient_master.age, patient_master.gender, patient_master.dob, patient_master.abha_id, patient_master.abha_address, patient_master.mphone1, doctor_master.p_fname as dr_fname, doctor_master.p_lname as dr_lname') + ->select('ipd_master.*, patient_master.p_fname, patient_master.p_code, patient_master.age, patient_master.gender, patient_master.dob, patient_master.abha_id, patient_master.abha_address, patient_master.mphone1, doctor_master.p_fname as dr_fname, doctor_master.p_lname as dr_lname') ->join('patient_master', 'patient_master.id = ipd_master.p_id', 'left') ->join('doctor_master', 'doctor_master.id = ipd_master.r_doc_id', 'left'); @@ -418,7 +418,6 @@ class DoctorDocument extends BaseController ->orLike('ipd_master.P_name', $query) ->orLike('patient_master.p_code', $query) ->orLike('patient_master.p_fname', $query) - ->orLike('patient_master.p_lname', $query) ->groupEnd(); } @@ -434,7 +433,7 @@ class DoctorDocument extends BaseController foreach ($ipdRecords as $row) { $ipdId = (int) $row['id']; $patientId = (int) $row['p_id']; - $patientName = trim(trim((string) ($row['p_fname'] ?? '')) . ' ' . trim((string) ($row['p_lname'] ?? ''))); + $patientName = trim((string) ($row['p_fname'] ?? '')); if ($patientName === '') { $patientName = (string) ($row['P_name'] ?? ('Patient #' . $patientId)); } @@ -777,7 +776,7 @@ class DoctorDocument extends BaseController $lifestyle[] = 'Diet & Lifestyle Advice: ' . $adviceStr; } - $patientName = trim(trim((string) ($patientRow['p_fname'] ?? '')) . ' ' . trim((string) ($patientRow['p_lname'] ?? ''))); + $patientName = trim((string) ($patientRow['p_fname'] ?? '')); $abhaIdRaw = ''; foreach (['abha_id', 'abha_no', 'abha'] as $field) { $candidate = trim((string) ($patientRow[$field] ?? '')); @@ -2157,7 +2156,7 @@ class DoctorDocument extends BaseController $docTitle = 'Medical Certificate'; } - $patientName = trim(trim((string) ($patientRow['p_fname'] ?? '')) . ' ' . trim((string) ($patientRow['p_lname'] ?? ''))); + $patientName = trim((string) ($patientRow['p_fname'] ?? '')); $abhaIdRaw = ''; foreach (['abha_id', 'abha_no', 'abha'] as $field) { $candidate = trim((string) ($patientRow[$field] ?? '')); @@ -2437,7 +2436,7 @@ class DoctorDocument extends BaseController $mimeType = 'text/html'; } - $patientName = trim(trim((string) ($patientRow['p_fname'] ?? '')) . ' ' . trim((string) ($patientRow['p_lname'] ?? ''))); + $patientName = trim((string) ($patientRow['p_fname'] ?? '')); $abhaIdRaw = ''; foreach (['abha_id', 'abha_no', 'abha'] as $field) { $candidate = trim((string) ($patientRow[$field] ?? '')); diff --git a/app/Controllers/Immunization.php b/app/Controllers/Immunization.php index 610e98d..3da6f09 100644 --- a/app/Controllers/Immunization.php +++ b/app/Controllers/Immunization.php @@ -250,7 +250,7 @@ class Immunization extends BaseController { $fields = $this->db->getFieldNames('patient_master') ?? []; $select = ['id']; - foreach (['p_code', 'p_fname', 'p_lname', 'gender', 'dob', 'age', 'age_in_month', 'mphone1', 'abha_id', 'abha_no', 'abha_address', 'abha'] as $field) { + foreach (['p_code', 'p_fname', 'gender', 'dob', 'age', 'age_in_month', 'mphone1', 'abha_id', 'abha_no', 'abha_address', 'abha'] as $field) { if (in_array($field, $fields, true)) { $select[] = $field; } diff --git a/app/Controllers/IpdPatient.php b/app/Controllers/IpdPatient.php index fba723d..c41d9c8 100644 --- a/app/Controllers/IpdPatient.php +++ b/app/Controllers/IpdPatient.php @@ -88,7 +88,7 @@ class IpdPatient extends BaseController if ($this->db->tableExists('patient_master')) { $builder - ->select('p.p_code, p.p_fname, p.p_lname') + ->select('p.p_code, p.p_fname') ->join('patient_master p', 'p.id = i.p_id', 'left'); } } @@ -117,7 +117,7 @@ class IpdPatient extends BaseController } } - $patientName = trim(((string) ($row->p_fname ?? '')) . ' ' . ((string) ($row->p_lname ?? ''))); + $patientName = trim((string) ($row->p_fname ?? '')); if (! isset($floors[$floorNo])) { $floors[$floorNo] = []; diff --git a/app/Controllers/Ipd_discharge.php b/app/Controllers/Ipd_discharge.php index ec47489..86fef95 100644 --- a/app/Controllers/Ipd_discharge.php +++ b/app/Controllers/Ipd_discharge.php @@ -477,6 +477,16 @@ class Ipd_discharge extends BaseController 'hospital_email' => esc($hEmail), ]; + $isDayCare = ($ipd->admission_type ?? '') === 'daycare'; + $isEmergency = ($ipd->admission_type ?? '') === 'emergency'; + $dcScore = (int) ($ipd->discharge_score ?? 10); + + $tokens['DAYCARE_HEADER'] = '

' . ($isDayCare ? 'DAY CARE SURGERY & OBSERVATION DISCHARGE SUMMARY' : ($isEmergency ? 'EMERGENCY & CASUALTY DISCHARGE CARD' : 'INPATIENT DISCHARGE SUMMARY')) . '

' . ($isDayCare ? 'Short Stay Ambulatory Episode (< 24 Hours) • NABH Compliant Discharge Document' : ($isEmergency ? 'Emergency Triage & Observation • Statutory Medical Record' : 'Clinical Summary • Hospital Record')) . '
'; + + $tokens['PADSS_SCORE'] = '

Post-Anaesthesia Discharge Readiness Assessment (PADSS)

Vital Signs: Stable / Within 20% baselineAmbulation: Steady Gait / No DizzinessNausea / Emesis: Minimal / Tolerating Orals
Pain Score: Controlled (≤ 3/10)Surgical Bleeding: Dry / Minimal Dressing SoakageTotal Score: ' . $dcScore . '/10 (Discharge Clearance Met ≥ 9)
'; + + $tokens['RED_FLAGS_WARNINGS'] = '
EMERGENCY RETURN PRECAUTIONS (RED FLAGS):
Seek immediate medical attention or return to the Emergency / Casualty department if you develop: High fever (>101°F) with chills, persistent nausea/vomiting, severe uncontrolled wound pain, sudden bleeding or soakage through dressing, shortness of breath, or chest pain.
'; + // Add patient info table as a token (not included in CONTENT by default) $tokens['PATIENT_INFO_TABLE'] = $this->buildAutoDischargeSummaryTable($panelData); @@ -9009,11 +9019,7 @@ class Ipd_discharge extends BaseController return; } - $lastName = trim((string) ($patientRow['p_lname'] ?? '')); - if (! $this->isMeaningfulDischargeValue($lastName)) { - $lastName = ''; - } - $patientName = trim(trim((string) ($patientRow['p_fname'] ?? '')) . ' ' . $lastName); + $patientName = trim((string) ($patientRow['p_fname'] ?? '')); if ($patientName === '') { $patientName = trim((string) ($ipdRow['P_name'] ?? '')); } diff --git a/app/Controllers/Opd_prescription.php b/app/Controllers/Opd_prescription.php index 6f8abfb..a03e5da 100644 --- a/app/Controllers/Opd_prescription.php +++ b/app/Controllers/Opd_prescription.php @@ -4879,8 +4879,7 @@ class Opd_prescription extends BaseController $abhaAddress = $abhaField !== null ? trim((string) ($patientRow[$abhaField] ?? '')) : ''; $patFirstName = $this->sanitizePersonNamePart((string) ($patientRow['p_fname'] ?? '')); - $patLastName = $this->sanitizePersonNamePart((string) ($patientRow['p_lname'] ?? '')); - $patFullName = trim($patFirstName . ($patLastName !== '' ? ' ' . $patLastName : '')); + $patFullName = $patFirstName; if ($patFullName === '') { $patFullName = trim((string) ($opdRow['P_name'] ?? '')); } @@ -5108,8 +5107,7 @@ class Opd_prescription extends BaseController $pRow = $this->db->table('patient_master')->where('id', $patientId)->get(1)->getRowArray(); if (! empty($pRow)) { $fname = $this->sanitizePersonNamePart((string) ($pRow['p_fname'] ?? '')); - $lname = $this->sanitizePersonNamePart((string) ($pRow['p_lname'] ?? '')); - $patientName = trim($fname . ' ' . $lname); + $patientName = $fname; foreach (['abha_number', 'abha_id', 'abha_address'] as $abhaField) { if (in_array($abhaField, $pFields, true)) { $val = trim((string) ($pRow[$abhaField] ?? '')); @@ -5514,8 +5512,7 @@ class Opd_prescription extends BaseController $pRow = $this->db->table('patient_master')->where('id', $patientId)->get(1)->getRowArray(); if (! empty($pRow)) { $fname = $this->sanitizePersonNamePart((string) ($pRow['p_fname'] ?? '')); - $lname = $this->sanitizePersonNamePart((string) ($pRow['p_lname'] ?? '')); - $patientName = trim($fname . ' ' . $lname); + $patientName = $fname; foreach (['abha_number', 'abha_id', 'abha_address'] as $abhaField) { if (in_array($abhaField, $pFields, true)) { $val = trim((string) ($pRow[$abhaField] ?? '')); @@ -11554,7 +11551,7 @@ class Opd_prescription extends BaseController { try { $escape = static fn (string $value): string => htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); - $patientName = trim($this->sanitizePersonNamePart((string) ($patientRow['p_fname'] ?? '')) . ' ' . $this->sanitizePersonNamePart((string) ($patientRow['p_lname'] ?? ''))); + $patientName = trim($this->sanitizePersonNamePart((string) ($patientRow['p_fname'] ?? ''))); $patientName = $patientName !== '' ? $patientName : 'Patient'; $hospital = $this->getHospitalProfileForFhir(); $hospitalName = trim((string) ($hospital['name'] ?? '')) ?: 'Healthcare Facility'; @@ -11697,8 +11694,7 @@ class Opd_prescription extends BaseController $abhaAddress = $abhaField !== null ? trim((string) ($patientRow[$abhaField] ?? '')) : ''; $patFirstName = $this->sanitizePersonNamePart((string) ($patientRow['p_fname'] ?? '')); - $patLastName = $this->sanitizePersonNamePart((string) ($patientRow['p_lname'] ?? '')); - $patFullName = trim($patFirstName . ($patLastName !== '' ? ' ' . $patLastName : '')); + $patFullName = $patFirstName; if ($patFullName === '') { $patFullName = trim((string) ($opdRow['P_name'] ?? '')); } diff --git a/app/Controllers/Patient.php b/app/Controllers/Patient.php index 7260f30..f59fc62 100644 --- a/app/Controllers/Patient.php +++ b/app/Controllers/Patient.php @@ -5914,7 +5914,7 @@ class Patient extends BaseController $docTitle = 'Patient Scanned Document'; } - $patientName = trim(trim((string) ($patientRow['p_fname'] ?? '')) . ' ' . trim((string) ($patientRow['p_lname'] ?? ''))); + $patientName = trim((string) ($patientRow['p_fname'] ?? '')); $abhaIdRaw = ''; foreach (['abha_id', 'abha_no', 'abha'] as $field) { $candidate = trim((string) ($patientRow[$field] ?? '')); diff --git a/app/Controllers/Payment.php b/app/Controllers/Payment.php index e2e3b5a..7588806 100644 --- a/app/Controllers/Payment.php +++ b/app/Controllers/Payment.php @@ -194,7 +194,7 @@ class Payment extends BaseController } $patient = $this->db->table($patientTable)->where('id', $patientId)->get(1)->getRowArray(); if ($patient !== null) { - $context['patient'] = trim((string) ($patient['p_fname'] ?? '') . ' ' . (string) ($patient['p_lname'] ?? '')); + $context['patient'] = trim((string) ($patient['p_fname'] ?? '')); break; } } diff --git a/app/Controllers/Report.php b/app/Controllers/Report.php index 744f2b3..2b42d9e 100644 --- a/app/Controllers/Report.php +++ b/app/Controllers/Report.php @@ -762,7 +762,7 @@ class Report extends BaseController $builder = $this->db->table('payment_history p'); $builder->select('p.id as payment_id, p.payment_date, inv.inv_date, inv.invoice_code, inv.net_amount') ->select('DATEDIFF(DATE(p.payment_date), DATE(inv.inv_date)) as delay_days', false) - ->select("CONCAT(IFNULL(pat.p_fname,''), ' ', IFNULL(pat.p_lname,'')) as patient_name", false) + ->select("IFNULL(pat.p_fname, '') as patient_name", false) ->select('pat.p_code as patient_code') ->select('p.amount as paid_amount, p.update_by') ->select("CASE p.payment_mode WHEN 1 THEN 'Cash' WHEN 2 THEN 'Bank' ELSE 'Other' END as pay_mode", false) diff --git a/app/Controllers/Setting/BedManagement.php b/app/Controllers/Setting/BedManagement.php index 95a05bf..81718b9 100644 --- a/app/Controllers/Setting/BedManagement.php +++ b/app/Controllers/Setting/BedManagement.php @@ -362,7 +362,7 @@ class BedManagement extends BaseController if ($this->db->tableExists('patient_master')) { $builder - ->select('p.p_code, p.p_fname, p.p_lname') + ->select('p.p_code, p.p_fname') ->join('patient_master p', 'p.id = i.p_id', 'left'); } } @@ -392,7 +392,7 @@ class BedManagement extends BaseController } } - $patientName = trim(((string) ($row->p_fname ?? '')) . ' ' . ((string) ($row->p_lname ?? ''))); + $patientName = trim((string) ($row->p_fname ?? '')); // Initialise floor / ward buckets if (! isset($floors[$floorNo])) { diff --git a/app/Database/Migrations/2026-09-22-000001_CreateDaycareEmergencyModule.php b/app/Database/Migrations/2026-09-22-000001_CreateDaycareEmergencyModule.php new file mode 100644 index 0000000..a5d95a1 --- /dev/null +++ b/app/Database/Migrations/2026-09-22-000001_CreateDaycareEmergencyModule.php @@ -0,0 +1,320 @@ +db->tableExists('ipd_master')) { + $fields = []; + + if (! $this->db->fieldExists('admission_type', 'ipd_master')) { + $fields['admission_type'] = [ + 'type' => 'ENUM', + 'constraint' => ['ipd', 'daycare', 'emergency'], + 'default' => 'ipd', + 'null' => false, + 'after' => 'ipd_code', + ]; + } + + if (! $this->db->fieldExists('triage_level', 'ipd_master')) { + $fields['triage_level'] = [ + 'type' => 'TINYINT', + 'constraint' => 1, + 'null' => true, + 'comment' => 'ESI Triage 1: Red, 2: Orange, 3: Yellow, 4: Green, 5: Blue', + 'after' => 'admission_type', + ]; + } + + if (! $this->db->fieldExists('triage_time', 'ipd_master')) { + $fields['triage_time'] = [ + 'type' => 'DATETIME', + 'null' => true, + 'after' => 'triage_level', + ]; + } + + if (! $this->db->fieldExists('is_mlc', 'ipd_master')) { + $fields['is_mlc'] = [ + 'type' => 'TINYINT', + 'constraint' => 1, + 'default' => 0, + 'null' => false, + 'after' => 'triage_time', + ]; + } + + if (! $this->db->fieldExists('mlc_number', 'ipd_master')) { + $fields['mlc_number'] = [ + 'type' => 'VARCHAR', + 'constraint' => 60, + 'null' => true, + 'after' => 'is_mlc', + ]; + } + + if (! $this->db->fieldExists('police_station', 'ipd_master')) { + $fields['police_station'] = [ + 'type' => 'VARCHAR', + 'constraint' => 120, + 'null' => true, + 'after' => 'mlc_number', + ]; + } + + if (! $this->db->fieldExists('police_constable_details', 'ipd_master')) { + $fields['police_constable_details'] = [ + 'type' => 'VARCHAR', + 'constraint' => 150, + 'null' => true, + 'after' => 'police_station', + ]; + } + + if (! $this->db->fieldExists('brought_by', 'ipd_master')) { + $fields['brought_by'] = [ + 'type' => 'VARCHAR', + 'constraint' => 120, + 'null' => true, + 'after' => 'police_constable_details', + ]; + } + + if (! $this->db->fieldExists('daycare_procedure_name', 'ipd_master')) { + $fields['daycare_procedure_name'] = [ + 'type' => 'VARCHAR', + 'constraint' => 255, + 'null' => true, + 'after' => 'brought_by', + ]; + } + + if (! $this->db->fieldExists('converted_from_type', 'ipd_master')) { + $fields['converted_from_type'] = [ + 'type' => 'VARCHAR', + 'constraint' => 20, + 'null' => true, + 'after' => 'daycare_procedure_name', + ]; + } + + if (! $this->db->fieldExists('converted_from_code', 'ipd_master')) { + $fields['converted_from_code'] = [ + 'type' => 'VARCHAR', + 'constraint' => 50, + 'null' => true, + 'after' => 'converted_from_type', + ]; + } + + if (! $this->db->fieldExists('converted_at', 'ipd_master')) { + $fields['converted_at'] = [ + 'type' => 'DATETIME', + 'null' => true, + 'after' => 'converted_from_code', + ]; + } + + if (! $this->db->fieldExists('conversion_reason', 'ipd_master')) { + $fields['conversion_reason'] = [ + 'type' => 'TEXT', + 'null' => true, + 'after' => 'converted_at', + ]; + } + + if (! $this->db->fieldExists('converted_by', 'ipd_master')) { + $fields['converted_by'] = [ + 'type' => 'INT', + 'constraint' => 11, + 'null' => true, + 'after' => 'conversion_reason', + ]; + } + + if (! $this->db->fieldExists('sbar_handover', 'ipd_master')) { + $fields['sbar_handover'] = [ + 'type' => 'TEXT', + 'null' => true, + 'after' => 'converted_by', + ]; + } + + if (! $this->db->fieldExists('discharge_score', 'ipd_master')) { + $fields['discharge_score'] = [ + 'type' => 'INT', + 'constraint' => 11, + 'null' => true, + 'comment' => 'PADSS or Modified Aldrete Score', + 'after' => 'sbar_handover', + ]; + } + + if (! empty($fields)) { + $this->forge->addColumn('ipd_master', $fields); + } + } + + // 2. Modify ward_master.ward_type to support 'Day Care' + if ($this->db->tableExists('ward_master')) { + $this->db->query(" + ALTER TABLE `ward_master` + MODIFY COLUMN `ward_type` ENUM( + 'General', 'Private', 'Semi-Private', 'Deluxe', + 'ICU', 'NICU', 'PICU', 'HDU', 'ICCU', 'CCU', + 'Emergency', 'Day Care' + ) NOT NULL DEFAULT 'General' + "); + + // Seed default Emergency and Day Care wards if none exist + $erWardCount = $this->db->table('ward_master')->where('ward_type', 'Emergency')->countAllResults(); + if ($erWardCount === 0) { + $this->db->table('ward_master')->insert([ + 'ward_code' => 'ER-01', + 'ward_name' => 'Casualty & Emergency Ward', + 'ward_type' => 'Emergency', + 'gender_type' => 'unisex', + 'ward_category' => 'adult', + 'total_capacity' => 6, + 'floor_number' => 0, + 'has_oxygen' => 1, + 'has_suction' => 1, + 'has_monitor' => 1, + 'status' => 'active', + 'remarks' => 'Default Casualty / Emergency triage and observation unit', + 'created_at' => date('Y-m-d H:i:s'), + ]); + } + + $dcWardCount = $this->db->table('ward_master')->where('ward_type', 'Day Care')->countAllResults(); + if ($dcWardCount === 0) { + $this->db->table('ward_master')->insert([ + 'ward_code' => 'DC-01', + 'ward_name' => 'Day Care Unit', + 'ward_type' => 'Day Care', + 'gender_type' => 'unisex', + 'ward_category' => 'adult', + 'total_capacity' => 6, + 'floor_number' => 1, + 'has_oxygen' => 1, + 'has_suction' => 1, + 'has_monitor' => 1, + 'status' => 'active', + 'remarks' => 'Day Care short stay unit for minor surgeries, dialysis, and chemotherapy', + 'created_at' => date('Y-m-d H:i:s'), + ]); + } + } + + // 3. Ensure dedicated item types in ipd_item_type for separate billing headers + if ($this->db->tableExists('ipd_item_type')) { + $existingEr = $this->db->table('ipd_item_type') + ->groupStart() + ->like('desc', 'EMERGENCY') + ->orLike('group_desc', 'Emergency') + ->groupEnd() + ->countAllResults(); + + if ($existingEr === 0) { + $this->db->table('ipd_item_type')->insert([ + 'desc' => 'EMERGENCY / CASUALTY CHARGES', + 'group_desc' => 'Emergency / Casualty Charges', + ]); + } + + $existingDc = $this->db->table('ipd_item_type') + ->groupStart() + ->like('desc', 'DAY CARE') + ->orLike('group_desc', 'Day Care') + ->groupEnd() + ->countAllResults(); + + if ($existingDc === 0) { + $this->db->table('ipd_item_type')->insert([ + 'desc' => 'DAY CARE CHARGES', + 'group_desc' => 'Day Care Charges', + ]); + } + } + + // 4. Seed default Day Care Discharge Summary Template + if ($this->db->tableExists('ipd_discharge_templates')) { + $dcTemplateExists = $this->db->table('ipd_discharge_templates') + ->where('template_name', 'Day Care Discharge Summary') + ->countAllResults(); + + if ($dcTemplateExists === 0) { + $dcHtml = '{{DAYCARE_HEADER}}' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '
Patient Name: {{PATIENT_NAME}}UHID: {{UHID}}Day Care No.: {{IPD_CODE}}
Age / Gender: {{AGE_GENDER}}Guardian: {{GUARDIAN_RELATION}}{{GUARDIAN_NAME}}Phone: {{PATIENT_MOBILE}}
Admission: {{ADMIT_DATE}}Discharge: {{DISCHARGE_DATE}}Attending Doctor: {{DOCTOR_NAMES}}
' + . '
{{PRESENTING_COMPLAINTS}}
' + . '
{{FINAL_DIAGNOSIS}}
' + . '
{{PROCEDURE}}
' + . '
{{SURGERY}}
' + . '
{{PADSS_SCORE}}
' + . '
{{COURSE_IN_HOSPITAL}}
' + . '
{{DISCHARGE_MEDICATIONS}}
' + . '
{{RED_FLAGS_WARNINGS}}
' + . '
{{DIETARY_ADVICE}}
' + . '
{{REVIEW_AFTER}}
'; + + $this->db->table('ipd_discharge_templates')->insert([ + 'template_name' => 'Day Care Discharge Summary', + 'page_size' => 'A4', + 'custom_width_mm' => 210, + 'custom_height_mm' => 297, + 'page_margin_top_cm' => 0.80, + 'page_margin_bottom_cm' => 0.80, + 'page_margin_left_cm' => 0.80, + 'page_margin_right_cm' => 0.80, + 'margin_header_cm' => 0.50, + 'margin_footer_cm' => 0.50, + 'template_html' => $dcHtml, + 'is_default' => 0, + 'status' => 1, + 'created_at' => date('Y-m-d H:i:s'), + ]); + } + } + } + + public function down() + { + if ($this->db->tableExists('ipd_master')) { + $cols = [ + 'discharge_score', 'sbar_handover', 'converted_by', + 'conversion_reason', 'converted_at', 'converted_from_code', + 'converted_from_type', 'daycare_procedure_name', + 'brought_by', 'police_constable_details', 'police_station', + 'mlc_number', 'is_mlc', 'triage_time', 'triage_level', + 'admission_type' + ]; + foreach ($cols as $col) { + if ($this->db->fieldExists($col, 'ipd_master')) { + $this->forge->dropColumn('ipd_master', $col); + } + } + } + } +} diff --git a/app/Libraries/Abdm/ConsentSessionListService.php b/app/Libraries/Abdm/ConsentSessionListService.php index f186b37..376e502 100644 --- a/app/Libraries/Abdm/ConsentSessionListService.php +++ b/app/Libraries/Abdm/ConsentSessionListService.php @@ -177,7 +177,7 @@ class ConsentSessionListService $idCol = $this->resolveExistingColumn($fields, ['id']); $uhidCol = $this->resolveExistingColumn($fields, ['p_code', 'uhid', 'uhid_no', 'patient_code', 'patient_id']); $nameCol = $this->resolveExistingColumn($fields, ['p_fname', 'patient_name', 'name']); - $lastNameCol = $this->resolveExistingColumn($fields, ['p_lname', 'last_name']); + $lastNameCol = $this->resolveExistingColumn($fields, ['last_name']); $mobileCol = $this->resolveExistingColumn($fields, ['mphone1', 'mphone2', 'p_mobile', 'mobile', 'phone', 'contact_no', 'phone1']); $abhaAddressCol = $this->resolveExistingColumn($fields, ['abha_address', 'abha_addr']); diff --git a/app/Libraries/Abdm/Fhir/Generators/AbstractModuleFhirGenerator.php b/app/Libraries/Abdm/Fhir/Generators/AbstractModuleFhirGenerator.php index ba7e140..93d2a37 100644 --- a/app/Libraries/Abdm/Fhir/Generators/AbstractModuleFhirGenerator.php +++ b/app/Libraries/Abdm/Fhir/Generators/AbstractModuleFhirGenerator.php @@ -286,9 +286,30 @@ abstract class AbstractModuleFhirGenerator || (isset($source['admission_date'])) || (isset($enc['class_code']) && strtoupper((string) $enc['class_code']) === 'IMP'); - $defaultClass = $isIpd ? 'IMP' : 'AMB'; + $admissionType = strtolower(trim((string) ($enc['admission_type'] ?? $source['admission_type'] ?? ''))); + if ($admissionType === 'emergency') { + $defaultClass = 'EMER'; + } elseif ($admissionType === 'daycare') { + $defaultClass = 'SS'; + } else { + $defaultClass = $isIpd ? 'IMP' : 'AMB'; + } + $classCode = strtoupper(trim((string) ($enc['class_code'] ?? $defaultClass))); - $classDisplay = $classCode === 'IMP' ? 'inpatient encounter' : 'ambulatory'; + switch ($classCode) { + case 'EMER': + $classDisplay = 'emergency'; + break; + case 'SS': + $classDisplay = 'short stay'; + break; + case 'IMP': + $classDisplay = 'inpatient encounter'; + break; + default: + $classDisplay = 'ambulatory'; + break; + } $identifiers = [[ 'system' => 'https://hms.local/encounter-id', diff --git a/app/Libraries/Abdm/Sync/AbdmSyncWorkerService.php b/app/Libraries/Abdm/Sync/AbdmSyncWorkerService.php index 1d287a3..64883d4 100644 --- a/app/Libraries/Abdm/Sync/AbdmSyncWorkerService.php +++ b/app/Libraries/Abdm/Sync/AbdmSyncWorkerService.php @@ -305,12 +305,7 @@ class AbdmSyncWorkerService $name = trim((string) ($payload['patient_name'] ?? '')); if ($name === '') { - $first = trim((string) ($row['p_fname'] ?? '')); - $last = trim((string) ($row['p_lname'] ?? '')); - if (in_array(strtolower($last), ['0', '00', 'na', 'n/a', 'null', 'nil', '-'], true)) { - $last = ''; - } - $name = trim($first . ' ' . $last); + $name = trim((string) ($row['p_fname'] ?? '')); } $name = preg_replace('/\s0$/', '', $name) ?? $name; if ($name === '') { diff --git a/app/Models/IpdBillingModel.php b/app/Models/IpdBillingModel.php index 03e685a..4c1a08b 100644 --- a/app/Models/IpdBillingModel.php +++ b/app/Models/IpdBillingModel.php @@ -14,6 +14,8 @@ class IpdBillingModel extends Model { return "CASE " . "WHEN UPPER(" . $column . ") LIKE '%REGISTRATION%' THEN 10 " + . "WHEN UPPER(" . $column . ") LIKE '%EMERGENCY%' OR UPPER(" . $column . ") LIKE '%CASUALTY%' THEN 15 " + . "WHEN UPPER(" . $column . ") LIKE '%DAY CARE%' OR UPPER(" . $column . ") LIKE '%DAYCARE%' THEN 18 " . "WHEN UPPER(" . $column . ") LIKE '%ACCOMMOD%' OR UPPER(" . $column . ") LIKE '%ACCOMOD%' OR UPPER(" . $column . ") LIKE '%BEDSIDE%' THEN 20 " . "WHEN UPPER(" . $column . ") LIKE '%SURGERY%' OR UPPER(" . $column . ") LIKE '%OPERATION%' THEN 30 " . "WHEN UPPER(" . $column . ") LIKE '%PROCEDURE%' THEN 40 " @@ -35,6 +37,7 @@ class IpdBillingModel extends Model { $builder = $this->baseIpdListQuery(); $builder->select("i.id,i.ipd_code,p.p_code,p.p_fname,p.p_rname,") + ->select("i.admission_type,i.triage_level,i.is_mlc,i.converted_from_type,i.converted_from_code,i.daycare_procedure_name,") ->select("concat('Bed No :',b.bed_number,'[',w.ward_name,']') as Bed_Desc,") ->select("date_format(i.register_date,'%d-%m-%Y') as str_register_date,") ->select("if(i.ipd_status = 0,(to_days(sysdate()) - to_days(i.register_date)),(to_days(i.discharge_date) - to_days(i.register_date))) as no_days,") @@ -90,6 +93,7 @@ class IpdBillingModel extends Model $rows = $dataBuilder ->select( "i.ipd_code,p.p_code,p.p_fname, + i.admission_type, i.triage_level, i.is_mlc, i.converted_from_type, i.converted_from_code, if((o.id is not null),in_master.short_name,'Direct') as admit_type, o.insurance_no_1 as insurance_no_1, ipd_doc_list.doc_name as doc_name, @@ -116,6 +120,23 @@ class IpdBillingModel extends Model } $row['action'] = $actionHtml; + $rawCode = (string) ($row['ipd_code'] ?? ''); + $admType = (string) ($row['admission_type'] ?? 'ipd'); + $codeBadge = esc($rawCode); + if ($admType === 'daycare') { + $codeBadge .= ' Day Care'; + } elseif ($admType === 'emergency') { + $triageLvl = (int) ($row['triage_level'] ?? 0); + $badgeColor = $triageLvl === 1 ? 'danger' : ($triageLvl === 2 ? 'warning' : ($triageLvl === 3 ? 'warning text-dark' : 'info text-dark')); + $codeBadge .= ' ER' . ($triageLvl > 0 ? ' ESI-' . $triageLvl : '') . ''; + } elseif (! empty($row['converted_from_type'])) { + $codeBadge .= ' IPD (from ' . ucfirst(esc($row['converted_from_type'])) . ')'; + } + if (! empty($row['is_mlc'])) { + $codeBadge .= ' MLC'; + } + $row['ipd_code'] = $codeBadge; + foreach ($columns as $column) { $item[] = $row[$column] ?? ''; } diff --git a/app/Models/IpdModel.php b/app/Models/IpdModel.php index 564cc86..cc86512 100644 --- a/app/Models/IpdModel.php +++ b/app/Models/IpdModel.php @@ -43,14 +43,25 @@ class IpdModel extends Model $times = (int) ($countRow->xtimes ?? 0); + $admissionType = strtolower(trim((string) ($master['admission_type'] ?? 'ipd'))); + if ($admissionType === 'daycare') { + $prefix = 'DC'; + } elseif ($admissionType === 'emergency') { + $prefix = 'ER'; + } else { + $prefix = 'A'; + $admissionType = 'ipd'; + } + $pid = str_pad(substr((string) $insertId, -7, 7), 7, '0', STR_PAD_LEFT); - $pid = 'A' . date('ym') . $pid; + $pid = $prefix . date('ym') . $pid; $this->db->table('ipd_master') ->where('id', $insertId) ->update([ - 'ipd_code' => $pid, - 'ipd_times' => $times, + 'ipd_code' => $pid, + 'ipd_times' => $times, + 'admission_type' => $admissionType, ]); $docList = $data['doc_list'] ?? []; @@ -637,6 +648,103 @@ class IpdModel extends Model return 1; } + /** + * Converts a Day Care or Emergency admission to full Inpatient (IPD) status. + * Merges clinical notes, updates admission type, and transfers bed while keeping billing ledger unified. + * + * @param int $ipdId + * @param array $data + * @return array + */ + public function convertToIpd(int $ipdId, array $data): array + { + $ipdId = (int) $ipdId; + if ($ipdId <= 0) { + return ['status' => false, 'message' => 'Invalid Admission ID']; + } + + $ipd = $this->db->table('ipd_master')->where('id', $ipdId)->get()->getRowArray(); + if (empty($ipd)) { + return ['status' => false, 'message' => 'Admission record not found']; + } + + if ((int) ($ipd['ipd_status'] ?? 0) !== 0) { + return ['status' => false, 'message' => 'Cannot convert discharged admission']; + } + + $currentType = (string) ($ipd['admission_type'] ?? 'ipd'); + if ($currentType === 'ipd') { + return ['status' => false, 'message' => 'Patient is already admitted as Inpatient (IPD)']; + } + + $targetBedId = (int) ($data['target_bed_id'] ?? 0); + $doctorId = (int) ($data['doctor_id'] ?? 0); + $reason = trim((string) ($data['conversion_reason'] ?? '')); + $sbar = trim((string) ($data['sbar_handover'] ?? '')); + $userId = (int) ($this->getUserIdentity()['id'] ?? 0); + $userSignature = $this->getUserSignature(); + + // 1. Update ipd_master to IPD + $updateMaster = [ + 'admission_type' => 'ipd', + 'converted_from_type' => $currentType, + 'converted_from_code' => (string) ($ipd['ipd_code'] ?? ''), + 'converted_at' => date('Y-m-d H:i:s'), + 'conversion_reason' => $reason, + 'converted_by' => $userId > 0 ? $userId : null, + 'sbar_handover' => $sbar, + ]; + + if ($doctorId > 0) { + $updateMaster['r_doc_id'] = $doctorId; + } + + $this->db->table('ipd_master')->where('id', $ipdId)->update($updateMaster); + + // 2. Transfer Bed if target bed selected + if ($targetBedId > 0) { + $currBed = $this->db->table('bed_master') + ->select('id') + ->where('current_ipd_id', $ipdId) + ->get() + ->getRowArray(); + $fromBedId = $currBed ? (int) $currBed['id'] : null; + + $this->bedAssign([ + 'ipd_id' => $ipdId, + 'bed_id' => $targetBedId, + 'assignment_type' => 'transfer', + 'Fdate' => date('Y-m-d H:i:s'), + 'TDate' => date('Y-m-d H:i:s'), + 'transfer_reason' => 'Escalation/Conversion from ' . ucfirst($currentType) . ' to IPD: ' . $reason, + 'transfer_from_bed_id' => $fromBedId, + 'remarks' => $sbar, + ]); + } + + // 3. Update doctor list if doctorId provided + if ($doctorId > 0) { + $docExists = $this->db->table('ipd_master_doc_list') + ->where('ipd_id', $ipdId) + ->where('doc_id', $doctorId) + ->countAllResults(); + + if ($docExists === 0) { + $this->addIpdDoc([ + 'ipd_id' => $ipdId, + 'doc_id' => $doctorId, + 'log' => 'Assigned on IPD Conversion by ' . $userSignature, + ]); + } + } + + return [ + 'status' => true, + 'message' => 'Successfully converted patient from ' . ucfirst($currentType) . ' to Inpatient (IPD)', + 'ipd_id' => $ipdId, + ]; + } + private function getUserIdentity(): array { $userId = 0; diff --git a/app/Views/Setting/Bed/ward_list.php b/app/Views/Setting/Bed/ward_list.php index efbe6c2..738a3c7 100644 --- a/app/Views/Setting/Bed/ward_list.php +++ b/app/Views/Setting/Bed/ward_list.php @@ -62,6 +62,7 @@ +
diff --git a/app/Views/billing/Patient_Profile_Opd_V.php b/app/Views/billing/Patient_Profile_Opd_V.php index 1f200af..239a44e 100644 --- a/app/Views/billing/Patient_Profile_Opd_V.php +++ b/app/Views/billing/Patient_Profile_Opd_V.php @@ -70,7 +70,7 @@ if ($patientPhotoPath === '') {

OPD History

- - +
+ + + +
- mphone1)) : ?> @@ -514,7 +518,7 @@ $(document).ready(function() { candidates.unshift({ id: currentPatientId, p_code: p_code ?? '')) ?>, - name: p_fname ?? '') . ' ' . (string) ($data[0]->p_lname ?? ''))) ?>, + name: p_fname ?? ''))) ?>, gender: gender ?? 0) ?>, gender_label: xgender ?? '')) ?>, dob: dob ?? '')) ?>, @@ -600,10 +604,22 @@ $(document).ready(function() { load_form('/' + p_id); }); + $('#btn_emergency').off('click.personProfile').on('click.personProfile', function() { + var p_id = getPatientIdOrWarn(); + if (p_id === null) return; + load_form('/' + p_id + '?type=emergency'); + }); + + $('#btn_daycare').off('click.personProfile').on('click.personProfile', function() { + var p_id = getPatientIdOrWarn(); + if (p_id === null) return; + load_form('/' + p_id + '?type=daycare'); + }); + $('#btn_ipd').off('click.personProfile').on('click.personProfile', function() { var p_id = getPatientIdOrWarn(); if (p_id === null) return; - load_form('/' + p_id); + load_form('/' + p_id + '?type=ipd'); }); $('#btn_lab').off('click.personProfile').on('click.personProfile', function() { diff --git a/app/Views/billing/ipd/admit_patient.php b/app/Views/billing/ipd/admit_patient.php new file mode 100644 index 0000000..638a491 --- /dev/null +++ b/app/Views/billing/ipd/admit_patient.php @@ -0,0 +1,272 @@ + +
+
+
+
+
+
+

+ Patient Admission Desk +

+ Register patient admission for Emergency / Casualty, Day Care (< 24h), or Inpatient (IPD) +
+
+ + +
+
+ +
+ +
+ +
+
+ > + +
+
+ > + +
+
+ > + +
+
+
+ + +
+ +
+ + + +
+
+ Start typing at least 2 characters to auto-search + + + Patient not registered yet? Register New Patient → + +
+
+ + + + + +
+
+ +
Search for a patient to begin admission
+

Enter a name, patient code, or phone number in the search bar above.

+
+ +
+ + + + + + + + + + + + +
UHID / CodePatient NameAge / GenderMobileCurrent StatusDirect Admission Action
+
+
+
+
+
+
+
+ + diff --git a/app/Views/billing/ipd/current_admission.php b/app/Views/billing/ipd/current_admission.php index d7dcbb1..e06004d 100644 --- a/app/Views/billing/ipd/current_admission.php +++ b/app/Views/billing/ipd/current_admission.php @@ -1,10 +1,41 @@ +admission_type ?? 'ipd'; + if ($t === 'daycare') $dcCount++; + elseif ($t === 'emergency') $erCount++; + else $ipdCount++; +} +?> +
-
-

Current Admission

+

Current Admissions

+
+
+ + + + +
+ +
@@ -15,22 +46,47 @@ - + - + - + - + - - - - + admission_type ?? 'ipd'; + ?> + + + @@ -39,9 +95,9 @@ @@ -49,15 +105,15 @@ - + - + - + - +
IPD CodeAdmission Code Name/Patient CodeBed No. [Type]Bed No. [Ward] Register Date No. of DaysDr. NameDoctor Name Admit Type AmountAction
ipd_code ?? '') ?>[p_code ?? '') ?>] p_fname ?? '') ?>
p_rname ?? '') ?>
+ ipd_code ?? '') ?> + +
Day Care + + triage_level ?? 3); + $tColor = $tLvl === 1 ? 'danger' : ($tLvl === 2 ? 'warning text-dark' : ($tLvl === 3 ? 'warning text-dark' : 'info text-dark')); + ?> +
ER ESI- + + converted_from_type)): ?> +
from converted_from_type)) ?> + + is_mlc)): ?> +
MLC + +
+ [p_code ?? '') ?>] p_fname ?? '') ?>
+ p_rname ?? '') ?> + daycare_procedure_name)): ?> +
daycare_procedure_name) ?> + +
Bed_Desc ?? '') ?> str_register_date ?? '') ?> no_days ?? '') ?>C:charge_amount ?? '') ?>/M:med_amount ?? '') ?>/P:paid_amount ?? '') ?>/B:balance ?? '') ?> id)) : ?> - + - +
IPD CodeAdmission Code Name/Patient CodeBed No. [Type]Bed No. [Ward] Register Date No. of DaysDr. NameDoctor Name Admit Type AmountAction
@@ -73,6 +129,26 @@ return; } - $('#ipd-current-table').DataTable(); + var table = $('#ipd-current-table').DataTable({ + pageLength: 25, + order: [[0, 'desc']] + }); + + $('#admTypeFilterGroup button').on('click', function() { + $('#admTypeFilterGroup button').removeClass('active'); + $(this).addClass('active'); + var filter = $(this).data('filter'); + + if (filter === 'all') { + table.search('').columns().search('').draw(); + } else if (filter === 'daycare') { + table.search('Day Care').draw(); + } else if (filter === 'emergency') { + table.search('ER').draw(); + } else if (filter === 'ipd') { + table.search('A2').draw(); + } + }); }); + diff --git a/app/Views/billing/ipd/dashboard.php b/app/Views/billing/ipd/dashboard.php index fe2907f..1d6a6f5 100644 --- a/app/Views/billing/ipd/dashboard.php +++ b/app/Views/billing/ipd/dashboard.php @@ -3,6 +3,15 @@

IPD Billing

+ + + diff --git a/app/Views/billing/ipd/legacy_registration.php b/app/Views/billing/ipd/legacy_registration.php index 7b52e1c..9ca392d 100644 --- a/app/Views/billing/ipd/legacy_registration.php +++ b/app/Views/billing/ipd/legacy_registration.php @@ -1,263 +1,590 @@ + -
-

- IPD - Registration -

- -
+
- -
Patient not found.
- -
- -
-
-
-

- Name :p_fname ?? '') ?> - p_relative ?? '') ?> p_rname ?? '') ?> - / Age :age ?? '') ?> - / Gender :xgender ?? '') ?> - / P Code :p_code ?? '') ?> -

- - + +
+
+ +

+ Hospital Admission Desk +

+
+
+ + +
+
+ + +
+ + Patient record not found. Please return to the admission search screen. +
+ p_fname ?? '')); + $nameParts = preg_split('/\s+/', $fullName); + $initials = strtoupper(substr($nameParts[0] ?? '', 0, 1) . substr($nameParts[1] ?? '', 0, 1)); + if ($initials === '') $initials = 'PT'; + + $relativeInfo = trim(($person_info->p_relative ?? '') . ' ' . ($person_info->p_rname ?? '')); + $curAdmType = strtolower($default_admission_type ?? 'ipd'); + ?> + + +
+
+
+
+
+
+
+

+ + p_code ?? ('#' . $person_info->id)) ?> + + + Org. Credit: case_id_code ?? '') ?> + + Direct Cash + +
+
+ + + + Gender: xgender ?? '-') ?> + Age: age ?? '-') ?> + Phone: mphone1 ?? '-') ?> +
-
-
-
-
-
- - -
-
-
-
- - -
-
+
+ + + + + + + +
+ + +
+
+
+ 1. Admission Category & Clinical Pathway +
+ Medical Standard Triage & Bed Protocols
-
-
-
- -
-
- -
- -
-
-
-
-
- -
-
- -
- -
-
-
-
-
-
-
- -
-
- -
- - -
-
-
-
-
-
- -
- -
- +
+
+ +
+ > + +
+ + +
+ > + +
+ + +
+ > + +
+
+ + + + + +
-
-
-
-
- - -
-
-
+
+ + +
+
+
+ 2. Next of Kin & Primary Attendant Contact +
+ Emergency Contact Protocol
- -
+
+
-
-
- - -
+ + +
+
+ + +
+
+ +
+ + +
+
+
+ +
+ +
- - - -
-
-
- - + +
+ +
+ + +
+ +
+ +
-
-
-
-
-
-
- -
- -
- -
-
-
-
-
-
-
- - -
-
-
-
-
-
-
- + +
+ - + @@ -265,140 +592,340 @@
-
-
-
-
-
- - + +
+ +
+ + +
+ Select an available bed suitable for the patient's care category. +
-
-
-
-
-

Remarks - Write some about the problem -

-
- - -
-
-
- -
+ + +
+ +
+ +
+ + + +
+
+ Select one or more primary attending consultants. +
+
+
+
+ + +
+
+
+ 4. Clinical Diagnosis & Initial Assessment +
+ Clinical Documentation +
+
+
+
+ + + Primary ICD clinical indication triggering hospital admission. +
+
+ + +
+
+
+
+ + +
+
+
+ 5. Medico-Legal (MLC) & Billing Classification +
+ Administrative & Statutory Compliance +
+
+
+ +
+ +
+
+ + +
+
+ +
-
-
-
- + + +
+ + +
+
+ + +
+
+ + +
+ + Standard Direct Cash Account + + +
+
+ + + -
-
- + + +
+
+ Admitting Patient: + + p_code ?? ('#' . $person_info->id)) ?> + • Mode: + Inpatient +
+
+ + +
+
+ + + + +
- diff --git a/app/Views/billing/ipd/panel.php b/app/Views/billing/ipd/panel.php index b2b1966..28191c7 100644 --- a/app/Views/billing/ipd/panel.php +++ b/app/Views/billing/ipd/panel.php @@ -75,8 +75,40 @@ if ($person) {
-
IPD : ipd_code ?? '') ?>
+
+
+ admission_type ?? 'ipd') === 'daycare'): ?> + Day Care : ipd_code ?? '') ?> + admission_type ?? 'ipd') === 'emergency'): ?> + Emergency : ipd_code ?? '') ?> + + IPD : ipd_code ?? '') ?> + +
+ admission_type ?? 'ipd') === 'daycare'): ?> + Day Care (< 24 Hrs) + admission_type ?? 'ipd') === 'emergency'): ?> + triage_level ?? 3); + $tColor = $tLevel === 1 ? 'danger' : ($tLevel === 2 ? 'warning text-dark' : ($tLevel === 3 ? 'warning text-dark' : 'info text-dark')); + ?> + Emergency ESI- + + converted_from_type)): ?> + + Converted from converted_from_type)) ?> (converted_from_code) ?>) + + + is_mlc)): ?> + MLC + +
+ admission_type ?? 'ipd', ['daycare', 'emergency'], true) && (int) ($ipd->ipd_status ?? 0) === 0): ?> + + @@ -91,12 +123,37 @@ if ($person) { / Age : / Gender : xgender ?? '') ?> / UHID : p_code ?? '') ?> + / Bed : bed_no ?? 'None') ?> / No of Days : no_days ?? '') ?>

-

+

Admit Date : str_register_date ?? '') ?> / Discharge Date : str_discharge_date ?? '') ?> + daycare_procedure_name)): ?> + / Day Care Procedure : daycare_procedure_name) ?> +

+ admission_type ?? '') === 'emergency' || ! empty($ipd->is_mlc) || ! empty($ipd->converted_from_type)): ?> +
+ admission_type ?? '') === 'emergency'): ?> + Triage Acuity: Level triage_level ?? '3') ?> + brought_by)): ?> + Brought By: brought_by) ?> + + + is_mlc)): ?> + MLC No: mlc_number ?: 'Pending') ?> + police_station)): ?> + Police Station: police_station) ?> + + + converted_from_type)): ?> +
+ Escalated from converted_from_type)) ?> (Original: converted_from_code) ?>) on converted_at) ?>. Reason: conversion_reason ?: 'Clinical escalation') ?> +
+ +
+
@@ -358,5 +415,167 @@ if ($person) { } }); } + + // Convert to IPD Modal handling + var convertModal = document.getElementById('convertToIpdModal'); + if (convertModal) { + convertModal.addEventListener('shown.bs.modal', function() { + var $bedSelect = $('#convert_target_bed_id'); + var $docSelect = $('#convert_doctor_id'); + + $bedSelect.html(''); + + $.getJSON('', function(resp) { + if (resp && resp.status) { + var bedHtml = ''; + var groupedBeds = {}; + (resp.beds || []).forEach(function(b) { + var wName = b.ward_name ? (b.ward_name + ' (' + (b.ward_type || 'General') + ')') : 'General Wards'; + if (!groupedBeds[wName]) { + groupedBeds[wName] = []; + } + groupedBeds[wName].push(b); + }); + + for (var group in groupedBeds) { + bedHtml += ''; + groupedBeds[group].forEach(function(b) { + bedHtml += ''; + }); + bedHtml += ''; + } + $bedSelect.html(bedHtml); + + var docHtml = ''; + (resp.doctors || []).forEach(function(d) { + docHtml += ''; + }); + $docSelect.html(docHtml); + } else { + $bedSelect.html(''); + } + }).fail(function() { + $bedSelect.html(''); + }); + }); + + $('#convertIpdForm').on('submit', function(e) { + e.preventDefault(); + var $btn = $('#btnConfirmConvertToIpd'); + var bedId = $('#convert_target_bed_id').val(); + var reason = $('#convert_reason').val(); + + if (!bedId) { + alert('Please select a target Inpatient Bed.'); + return; + } + if (!reason || reason.trim() === '') { + alert('Please specify the clinical reason for conversion to IPD.'); + return; + } + + $btn.prop('disabled', true).html(' Converting...'); + + $.post('', $(this).serialize(), function(res) { + $btn.prop('disabled', false).html(' Confirm & Transfer to IPD'); + if (res && res.status) { + var modalInstance = bootstrap.Modal.getInstance(convertModal); + if (modalInstance) { + modalInstance.hide(); + } + if (typeof window.notify === 'function') { + window.notify('success', 'IPD Conversion', res.message || 'Converted to Inpatient successfully'); + } else { + alert(res.message || 'Converted to Inpatient successfully'); + } + load_form_div('id ?? 0)) ?>', 'maindiv', 'IPD Panel'); + } else { + alert(res.message || 'Failed to convert to IPD'); + } + }, 'json').fail(function(xhr) { + $btn.prop('disabled', false).html(' Confirm & Transfer to IPD'); + var msg = (xhr.responseJSON && xhr.responseJSON.message) ? xhr.responseJSON.message : 'Server error occurred during conversion'; + alert(msg); + }); + }); + } })(); - \ No newline at end of file + + + + \ No newline at end of file diff --git a/app/Views/immunization/index.php b/app/Views/immunization/index.php index bb0150a..eddc5ba 100644 --- a/app/Views/immunization/index.php +++ b/app/Views/immunization/index.php @@ -296,7 +296,7 @@ $initialPatientId = (int) ($patient_id ?? 0); function renderPatient(data) { var patient = data.patient || {}; - var name = $.trim([(patient.p_fname || ''), (patient.p_lname || '')].join(' ')) || ('Patient #' + currentPatientId); + var name = $.trim(patient.p_fname || '') || ('Patient #' + currentPatientId); $('#patientName').text(name); $('#patientMeta').html([ patient.p_code ? 'UHID: ' + esc(patient.p_code) : '', diff --git a/app/Views/partials/sidebar.php b/app/Views/partials/sidebar.php index 2ad6337..a78db22 100644 --- a/app/Views/partials/sidebar.php +++ b/app/Views/partials/sidebar.php @@ -235,7 +235,19 @@ if (! $canChargesSettings && $user && method_exists($user, 'inGroup')) { + + +