diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 04a3c0f..dace7c9 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -375,6 +375,10 @@ $routes->get('Ipd_discharge/surgery_master_lookup', 'Ipd_discharge::surgery_mast $routes->get('Ipd_discharge/surgery_master_list', 'Ipd_discharge::surgery_master_list'); $routes->post('Ipd_discharge/surgery_master_save', 'Ipd_discharge::surgery_master_save'); $routes->post('Ipd_discharge/surgery_master_delete', 'Ipd_discharge::surgery_master_delete'); +$routes->get('Ipd_discharge/course_master_lookup', 'Ipd_discharge::course_master_lookup'); +$routes->get('Ipd_discharge/course_master_list', 'Ipd_discharge::course_master_list'); +$routes->post('Ipd_discharge/course_master_save', 'Ipd_discharge::course_master_save'); +$routes->post('Ipd_discharge/course_master_delete', 'Ipd_discharge::course_master_delete'); $routes->get('Ipd_discharge/diagnosis_icd_lookup', 'Ipd_discharge::diagnosis_icd_lookup'); $routes->post('Ipd_discharge/diagnosis_icd_seed_starter', 'Ipd_discharge::diagnosis_icd_seed_starter'); $routes->post('Ipd_discharge/update_complaint_field', 'Ipd_discharge::update_complaint_field'); diff --git a/app/Controllers/Ipd_discharge.php b/app/Controllers/Ipd_discharge.php index d5bf9d6..e87f216 100644 --- a/app/Controllers/Ipd_discharge.php +++ b/app/Controllers/Ipd_discharge.php @@ -2631,6 +2631,218 @@ class Ipd_discharge extends BaseController ]); } + // Course/Treatment Master CRUD + private function ensureDischargeCourseMasterTable(): bool + { + if ($this->db->tableExists('ipd_discharge_course_master')) { + return true; + } + + try { + $sql = "CREATE TABLE IF NOT EXISTS ipd_discharge_course_master ( + id INT AUTO_INCREMENT PRIMARY KEY, + term_name VARCHAR(255) NOT NULL, + term_code VARCHAR(60) DEFAULT NULL, + icd_code VARCHAR(60) DEFAULT NULL, + is_active TINYINT(1) NOT NULL DEFAULT 1, + created_at DATETIME NULL, + updated_at DATETIME NULL, + UNIQUE KEY uniq_term_name (term_name), + INDEX idx_active_name (is_active, term_name) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"; + $this->db->query($sql); + } catch (\Throwable $e) { + return false; + } + + return $this->db->tableExists('ipd_discharge_course_master'); + } + + public function course_master_lookup() + { + $q = trim((string) $this->request->getGet('q')); + $rows = []; + + if ($q !== '') { + if ($this->ensureDischargeCourseMasterTable()) { + $dbRows = $this->db->table('ipd_discharge_course_master') + ->select('id, term_name, term_code, icd_code') + ->where('is_active', 1) + ->groupStart() + ->like('term_name', $q) + ->orLike('term_code', $q) + ->orLike('icd_code', $q) + ->groupEnd() + ->orderBy('term_name', 'ASC') + ->limit(20) + ->get() + ->getResultArray(); + + foreach ($dbRows as $row) { + $rows[] = [ + 'id' => (int) ($row['id'] ?? 0), + 'term_name' => (string) ($row['term_name'] ?? ''), + 'term_code' => (string) ($row['term_code'] ?? ''), + 'icd_code' => (string) ($row['icd_code'] ?? ''), + ]; + } + } + } + + return $this->response->setJSON(['rows' => $rows]); + } + + public function course_master_list() + { + $q = trim((string) $this->request->getGet('q')); + $rows = []; + + if (! $this->ensureDischargeCourseMasterTable()) { + return $this->response->setJSON(['rows' => []]); + } + + $builder = $this->db->table('ipd_discharge_course_master') + ->select('id, term_name, term_code, icd_code, is_active'); + + if ($q !== '') { + $builder->groupStart() + ->like('term_name', $q) + ->orLike('term_code', $q) + ->orLike('icd_code', $q) + ->groupEnd(); + } + + $dbRows = $builder->orderBy('term_name', 'ASC') + ->limit(100) + ->get() + ->getResultArray(); + + foreach ($dbRows as $row) { + $rows[] = [ + 'id' => (int) ($row['id'] ?? 0), + 'term_name' => (string) ($row['term_name'] ?? ''), + 'term_code' => (string) ($row['term_code'] ?? ''), + 'icd_code' => (string) ($row['icd_code'] ?? ''), + 'is_active' => (int) ($row['is_active'] ?? 1), + ]; + } + + return $this->response->setJSON(['rows' => $rows]); + } + + public function course_master_save() + { + if (! $this->request->isAJAX()) { + return $this->response->setStatusCode(400)->setJSON(['update' => 0, 'error_text' => 'Invalid request']); + } + + if (! $this->ensureDischargeCourseMasterTable()) { + return $this->response->setJSON([ + 'update' => 0, + 'error_text' => 'Unable to access course master table', + 'csrfName' => csrf_token(), + 'csrfHash' => csrf_hash(), + ]); + } + + $id = (int) $this->request->getPost('id'); + $name = trim((string) $this->request->getPost('name')); + $code = trim((string) $this->request->getPost('code')); + $icdCode = trim((string) $this->request->getPost('icd_code')); + $isActive = (int) $this->request->getPost('is_active') === 0 ? 0 : 1; + + if ($name === '') { + return $this->response->setJSON([ + 'update' => 0, + 'error_text' => 'Name is required', + 'csrfName' => csrf_token(), + 'csrfHash' => csrf_hash(), + ]); + } + + $now = date('Y-m-d H:i:s'); + $payload = [ + 'term_name' => $name, + 'term_code' => $code !== '' ? $code : null, + 'icd_code' => $icdCode !== '' ? $icdCode : null, + 'is_active' => $isActive, + 'updated_at' => $now, + ]; + + try { + $table = $this->db->table('ipd_discharge_course_master'); + + if ($id > 0) { + $ok = (bool) $table->where('id', $id)->update($payload); + } else { + $payload['created_at'] = $now; + $ok = (bool) $table->insert($payload); + $id = (int) ($this->db->insertID() ?: 0); + } + + if (! $ok) { + return $this->response->setJSON([ + 'update' => 0, + 'error_text' => 'Unable to save record', + 'csrfName' => csrf_token(), + 'csrfHash' => csrf_hash(), + ]); + } + + return $this->response->setJSON([ + 'update' => 1, + 'id' => $id, + 'error_text' => 'Master record saved', + 'csrfName' => csrf_token(), + 'csrfHash' => csrf_hash(), + ]); + } catch (\Throwable $e) { + return $this->response->setJSON([ + 'update' => 0, + 'error_text' => 'Save failed: ' . $e->getMessage(), + 'csrfName' => csrf_token(), + 'csrfHash' => csrf_hash(), + ]); + } + } + + public function course_master_delete() + { + if (! $this->request->isAJAX()) { + return $this->response->setStatusCode(400)->setJSON(['update' => 0, 'error_text' => 'Invalid request']); + } + + if (! $this->ensureDischargeCourseMasterTable()) { + return $this->response->setJSON([ + 'update' => 0, + 'error_text' => 'Unable to access course master table', + 'csrfName' => csrf_token(), + 'csrfHash' => csrf_hash(), + ]); + } + + $id = (int) $this->request->getPost('id'); + if ($id <= 0) { + return $this->response->setJSON([ + 'update' => 0, + 'error_text' => 'Invalid record id', + 'csrfName' => csrf_token(), + 'csrfHash' => csrf_hash(), + ]); + } + + $ok = (bool) $this->db->table('ipd_discharge_course_master') + ->where('id', $id) + ->delete(); + + return $this->response->setJSON([ + 'update' => $ok ? 1 : 0, + 'error_text' => $ok ? 'Record deleted' : 'Unable to delete record', + 'csrfName' => csrf_token(), + 'csrfHash' => csrf_hash(), + ]); + } + public function diagnosis_icd_lookup() { $q = trim((string) $this->request->getGet('q')); diff --git a/app/Views/billing/ipd/discharge_create.php b/app/Views/billing/ipd/discharge_create.php index c7d1058..cbfa016 100644 --- a/app/Views/billing/ipd/discharge_create.php +++ b/app/Views/billing/ipd/discharge_create.php @@ -1165,10 +1165,19 @@ $allergyStatusNoKnown = in_array($allergyStatusNormalized, ['no known drug aller +
-
-
-
+
+
+ + +
+ +
+
+
@@ -2247,18 +2256,28 @@ $allergyStatusNoKnown = in_array($allergyStatusNormalized, ['no known drug aller var csrf = getCsrfPair(form); var payload = { id: 0, - type: type, name: name, code: code, icd_code: icd, is_active: 1 }; + + // For surgery/procedure, include type in payload + if (type === 'surgery' || type === 'procedure') { + payload.type = type; + } + payload[csrf.name] = csrf.value; setQuickStatus('Saving...', 'muted'); btnSave.disabled = true; - $.post('', payload, function(data) { + // Use appropriate endpoint based on type + var saveUrl = type === 'course' + ? '' + : ''; + + $.post(saveUrl, payload, function(data) { updateFormCsrf(form, data); btnSave.disabled = false; @@ -2281,6 +2300,11 @@ $allergyStatusNoKnown = in_array($allergyStatusNormalized, ['no known drug aller var procedureMasterId = document.getElementById('new_procedure_master_id'); if (procedureInput) procedureInput.value = name; if (procedureMasterId) procedureMasterId.value = savedId; + } else if (type === 'course') { + var courseInput = document.getElementById('new_course_name'); + var courseMasterId = document.getElementById('new_course_master_id'); + if (courseInput) courseInput.value = name; + if (courseMasterId) courseMasterId.value = savedId; } // Close modal after short delay @@ -2470,6 +2494,162 @@ $allergyStatusNoKnown = in_array($allergyStatusNormalized, ['no known drug aller } } + // Course/Treatment autocomplete with SNOMED/ICD code display + function initCourseAutocomplete() { + var input = document.getElementById('new_course_name'); + var dropdown = document.getElementById('discharge_course_dropdown'); + var hidden = document.getElementById('new_course_master_id'); + if (!input || !dropdown || !hidden) return; + + var searchTimer = null; + var highlightedIndex = -1; + var currentResults = []; + + input.addEventListener('input', function() { + var q = input.value.trim(); + highlightedIndex = -1; + + if (q.length < 2) { + dropdown.style.display = 'none'; + return; + } + + clearTimeout(searchTimer); + searchTimer = setTimeout(function() { + if (!window.jQuery) return; + + $.get('?q=' + encodeURIComponent(q), function(data) { + currentResults = (data && data.rows) ? data.rows : []; + if (!currentResults.length) { + dropdown.style.display = 'none'; + return; + } + + var html = ''; + currentResults.forEach(function(row) { + var name = row.term_name || ''; + var code = row.term_code || ''; + var icd = row.icd_code || ''; + var codeDisplay = ''; + + if (code && icd) { + codeDisplay = '' + code + '' + icd + ''; + } else if (code) { + codeDisplay = '' + code + ''; + } else if (icd) { + codeDisplay = '' + icd + ''; + } + + html += ''; + }); + + dropdown.innerHTML = html; + dropdown.style.display = 'block'; + + dropdown.querySelectorAll('.dropdown-item').forEach(function(item, idx) { + item.addEventListener('mouseenter', function() { + dropdown.querySelectorAll('.dropdown-item').forEach(function(el) { + el.style.backgroundColor = ''; + }); + this.style.backgroundColor = '#f8f9fa'; + highlightedIndex = idx; + }); + item.addEventListener('mouseleave', function() { + this.style.backgroundColor = ''; + }); + item.addEventListener('click', function() { + var id = this.getAttribute('data-id'); + var name = this.getAttribute('data-name'); + input.value = name; + hidden.value = id; + dropdown.style.display = 'none'; + }); + }); + }, 'json'); + }, 300); + }); + + input.addEventListener('keydown', function(e) { + var items = dropdown.querySelectorAll('.dropdown-item'); + var isVisible = dropdown.style.display === 'block' && items.length > 0; + + if (e.key === 'ArrowDown' && isVisible) { + e.preventDefault(); + highlightedIndex = Math.min(highlightedIndex + 1, items.length - 1); + updateHighlight(items); + } else if (e.key === 'ArrowUp' && isVisible) { + e.preventDefault(); + highlightedIndex = Math.max(highlightedIndex - 1, 0); + updateHighlight(items); + } else if (e.key === 'Enter' && isVisible) { + e.preventDefault(); + if (highlightedIndex >= 0 && items[highlightedIndex]) { + items[highlightedIndex].click(); + } + } else if (e.key === 'Escape') { + dropdown.style.display = 'none'; + highlightedIndex = -1; + } + }); + + input.addEventListener('blur', function() { + setTimeout(function() { + dropdown.style.display = 'none'; + }, 200); + }); + + function updateHighlight(items) { + items.forEach(function(item, idx) { + if (idx === highlightedIndex) { + item.style.backgroundColor = '#007bff'; + item.style.color = '#fff'; + item.scrollIntoView({ block: 'nearest' }); + } else { + item.style.backgroundColor = ''; + item.style.color = ''; + } + }); + } + + // Quick-add button handler + var btnQuickCourse = document.getElementById('btn_quick_add_course'); + if (btnQuickCourse) { + btnQuickCourse.addEventListener('click', function() { + var courseName = input.value.trim(); + + if (!courseName) { + alert('Please enter a course/treatment name first'); + return; + } + + var quickModal = document.getElementById('quickAddTermModal'); + var quickType = document.getElementById('quick_term_type'); + var quickTypeLabel = document.getElementById('quick_term_type_label'); + var quickName = document.getElementById('quick_term_name'); + var quickCode = document.getElementById('quick_term_code'); + var quickIcd = document.getElementById('quick_term_icd'); + var quickStatus = document.getElementById('quick_term_status'); + + if (!quickModal || !quickName) return; + + quickType.value = 'course'; + quickTypeLabel.textContent = 'Course/Treatment'; + quickName.value = courseName; + quickCode.value = ''; + quickIcd.value = ''; + quickStatus.textContent = ''; + quickStatus.className = 'text-muted'; + + showModalById('quickAddTermModal'); + }); + } + } + function showModalById(modalId) { var el = document.getElementById(modalId); if (!el) { @@ -3837,6 +4017,7 @@ $allergyStatusNoKnown = in_array($allergyStatusNormalized, ['no known drug aller initComplaintTools(); initSurgeryProcedureAutocomplete(); initSurgeryTools(getDischargeForm()); + initCourseAutocomplete(); initDiagnosisTools(); initCourseTools(); initMedicineTools();