diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 589f6d3..ec65cf0 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -470,6 +470,10 @@ $routes->get('Opd/get_appointment_data', 'Opd::get_appointment_data'); $routes->get('Opd/get_appointment_data/(:segment)', 'Opd::get_appointment_data/$1'); $routes->get('Opd/get_appointment_list/(:num)', 'Opd::get_appointment_list/$1'); $routes->get('Opd/get_appointment_list/(:num)/(:segment)', 'Opd::get_appointment_list/$1/$2'); +$routes->get('Opd/get_appointment_digest/(:num)', 'Opd::get_appointment_digest/$1'); +$routes->get('Opd/get_appointment_digest/(:num)/(:segment)', 'Opd::get_appointment_digest/$1/$2'); +$routes->get('Opd/get_appointment_live_rows/(:num)', 'Opd::get_appointment_live_rows/$1'); +$routes->get('Opd/get_appointment_live_rows/(:num)/(:segment)', 'Opd::get_appointment_live_rows/$1/$2'); $routes->match(['get', 'post'], 'Opd/opd_status/(:num)/(:num)', 'Opd::opd_status/$1/$2'); $routes->post('Opd/showfee', 'Opd::showfee'); $routes->post('Opd/confirm_opd', 'Opd::confirm_opd'); diff --git a/app/Controllers/Opd.php b/app/Controllers/Opd.php index dece6ef..ada146b 100644 --- a/app/Controllers/Opd.php +++ b/app/Controllers/Opd.php @@ -934,6 +934,202 @@ class Opd extends BaseController ]); } + /** + * Lightweight digest endpoint for live appointment screen refresh checks. + */ + public function get_appointment_digest(int $docId, string $opdDate = '') + { + if (! $this->canAccessDoctorWorkPanel()) { + return $this->response->setStatusCode(403)->setJSON([ + 'ok' => 0, + 'message' => 'Access denied', + ]); + } + + if ($opdDate === '') { + $opdDate = date('Y-m-d'); + } + + $docId = (int) $docId; + $opdDateSql = $this->db->escape($opdDate); + + $sql = "select + count(*) as total_rows, + sum(case when o.opd_status = 1 and pr.id is null then 1 else 0 end) as count_booking, + sum(case when o.opd_status = 1 and pr.id is not null then 1 else 0 end) as count_wait, + sum(case when o.opd_status = 2 then 1 else 0 end) as count_visit, + sum(case when o.opd_status = 3 then 1 else 0 end) as count_cancel, + coalesce( + bit_xor( + cast( + crc32( + concat_ws('|', + o.opd_id, + o.opd_status, + if(pr.id is null, 0, 1), + if( + coalesce(trim(pr.pulse),'')<>'' + or coalesce(trim(pr.spo2),'')<>'' + or coalesce(trim(pr.bp),'')<>'' + or coalesce(trim(pr.diastolic),'')<>'' + or coalesce(trim(pr.temp),'')<>'' + or coalesce(trim(pr.rr_min),'')<>'' + or coalesce(trim(pr.height),'')<>'' + or coalesce(trim(pr.weight),'')<>'' + or coalesce(trim(pr.waist),'')<>'', + 1,0 + ), + coalesce(pr.queue_no,0), + coalesce(o.payment_status,0), + coalesce(o.confirm_pay_opd,'') + ) + ) as unsigned + ) + ), + 0 + ) as digest_crc + from opd_master o + left join opd_prescription pr on o.opd_id = pr.opd_id + where o.apointment_date = " . $opdDateSql . " and o.doc_id = " . $docId; + + $row = $this->db->query($sql)->getRow(); + + $payload = [ + 'doc_id' => $docId, + 'opd_date' => $opdDate, + 'total' => (int) ($row->total_rows ?? 0), + 'booked' => (int) ($row->count_booking ?? 0), + 'waiting' => (int) ($row->count_wait ?? 0), + 'visited' => (int) ($row->count_visit ?? 0), + 'cancelled' => (int) ($row->count_cancel ?? 0), + 'crc' => (string) ($row->digest_crc ?? '0'), + ]; + + $digest = sha1(json_encode($payload)); + + return $this->response->setJSON([ + 'ok' => 1, + 'digest' => $digest, + 'payload' => $payload, + 'server_time' => date('c'), + ]); + } + + /** + * Live snapshot endpoint for in-place row merge on OPD appointment screen. + */ + public function get_appointment_live_rows(int $docId, string $opdDate = '') + { + if (! $this->canAccessDoctorWorkPanel()) { + return $this->response->setStatusCode(403)->setJSON([ + 'ok' => 0, + 'message' => 'Access denied', + ]); + } + + if ($opdDate === '') { + $opdDate = date('Y-m-d'); + } + + $docId = (int) $docId; + $opdDateSql = $this->db->escape($opdDate); + + $sql = "select + o.opd_id, + o.opd_status, + o.payment_status, + o.opd_fee_type, + o.opd_fee_amount, + o.opd_fee_desc, + coalesce(o.running_opd,0) as running_opd, + o.confirm_pay_opd, + if(pr.id is null, 0, 1) as has_prescription, + if( + coalesce(trim(pr.pulse),'')<>'' + or coalesce(trim(pr.spo2),'')<>'' + or coalesce(trim(pr.bp),'')<>'' + or coalesce(trim(pr.diastolic),'')<>'' + or coalesce(trim(pr.temp),'')<>'' + or coalesce(trim(pr.rr_min),'')<>'' + or coalesce(trim(pr.height),'')<>'' + or coalesce(trim(pr.weight),'')<>'' + or coalesce(trim(pr.waist),'')<>'', + 1,0 + ) as has_vitals, + coalesce(pr.queue_no,0) as queue_no + from opd_master o + left join opd_prescription pr on o.opd_id = pr.opd_id + where o.apointment_date = " . $opdDateSql . " and o.doc_id = " . $docId; + + $rows = $this->db->query($sql)->getResult(); + + $normalized = []; + $counts = [ + 'all' => 0, + 'booked' => 0, + 'waiting' => 0, + 'visited' => 0, + 'cancelled' => 0, + ]; + + foreach ($rows as $row) { + $status = (int) ($row->opd_status ?? 0); + $hasPrescription = (int) ($row->has_prescription ?? 0) === 1; + $paymentStatus = (int) ($row->payment_status ?? 0); + $runningOpd = (int) ($row->running_opd ?? 0) === 1; + $opdFeeType = (int) ($row->opd_fee_type ?? 0); + $opdFeeAmount = (float) ($row->opd_fee_amount ?? 0); + $opdFeeDesc = strtoupper(trim((string) ($row->opd_fee_desc ?? ''))); + $isRunningVisit = $runningOpd || $opdFeeType === 3 || str_contains($opdFeeDesc, 'RUNNING'); + $isConfirmedZeroAmount = $isRunningVisit && $opdFeeAmount <= 0; + $isConfirmedForQueue = $paymentStatus === 1 || $isConfirmedZeroAmount || !empty($row->confirm_pay_opd); + + $statusLabel = 'booked'; + if ($status === 1 && ($hasPrescription || $isConfirmedForQueue)) { + $statusLabel = 'waiting'; + } elseif ($status === 2) { + $statusLabel = 'visited'; + } elseif ($status === 3) { + $statusLabel = 'cancelled'; + } + + $normalized[] = [ + 'opd_id' => (int) ($row->opd_id ?? 0), + 'status' => $statusLabel, + 'has_prescription' => $hasPrescription ? 1 : 0, + 'has_vitals' => (int) ($row->has_vitals ?? 0), + 'queue_no' => (int) ($row->queue_no ?? 0), + 'is_confirmed_for_queue' => $isConfirmedForQueue ? 1 : 0, + ]; + + $counts['all']++; + if (array_key_exists($statusLabel, $counts)) { + $counts[$statusLabel]++; + } + } + + usort($normalized, static function ($left, $right): int { + return ((int) ($left['opd_id'] ?? 0)) <=> ((int) ($right['opd_id'] ?? 0)); + }); + + $digest = sha1(json_encode([ + 'doc_id' => $docId, + 'opd_date' => $opdDate, + 'counts' => $counts, + 'rows' => $normalized, + ])); + + return $this->response->setJSON([ + 'ok' => 1, + 'digest' => $digest, + 'doc_id' => $docId, + 'opd_date' => $opdDate, + 'counts' => $counts, + 'rows' => $normalized, + 'server_time' => date('c'), + ]); + } + /** * Update OPD status from queue actions. */ @@ -3749,6 +3945,8 @@ class Opd extends BaseController $this->db->table('opd_master')->where('opd_id', $oid)->update($data); + $this->autoCreateOpdQueue($oid); + return $this->response->setJSON([ 'update' => 1, 'showcontent' => 'Zero Cost', @@ -3785,6 +3983,8 @@ class Opd extends BaseController $this->db->table('opd_master')->where('opd_id', $oid)->update($data); + $this->autoCreateOpdQueue($oid); + return $this->response->setJSON([ 'update' => 1, 'showcontent' => 'CASH', @@ -3832,6 +4032,8 @@ class Opd extends BaseController $this->db->table('opd_master')->where('opd_id', $oid)->update($data); + $this->autoCreateOpdQueue($oid); + return $this->response->setJSON([ 'update' => 1, 'showcontent' => 'Bank Card', @@ -3852,6 +4054,8 @@ class Opd extends BaseController $this->db->table('opd_master')->where('opd_id', $oid)->update($data); + $this->autoCreateOpdQueue($oid); + return $this->response->setJSON([ 'update' => 1, 'showcontent' => 'Org. Case Credit', @@ -3868,6 +4072,64 @@ class Opd extends BaseController ]); } + private function autoCreateOpdQueue(int $opdId): void + { + if ($opdId <= 0 || ! $this->db->tableExists('opd_prescription')) { + return; + } + + // If a queue row already exists for this OPD, do nothing. + $existing = $this->db->table('opd_prescription') + ->where('opd_id', $opdId) + ->get(1) + ->getRow(); + if ($existing) { + return; + } + + $opdRow = $this->db->table('opd_master') + ->where('opd_id', $opdId) + ->get(1) + ->getRow(); + if (! $opdRow) { + return; + } + + $fields = $this->db->getFieldNames('opd_prescription'); + $insert = []; + if (in_array('opd_id', $fields, true)) { + $insert['opd_id'] = $opdId; + } + if (in_array('p_id', $fields, true)) { + $insert['p_id'] = (int) ($opdRow->p_id ?? 0); + } + if (in_array('doc_id', $fields, true)) { + $insert['doc_id'] = (int) ($opdRow->doc_id ?? 0); + } + if (in_array('date_opd_visit', $fields, true)) { + $insert['date_opd_visit'] = date('Y-m-d'); + } + if (in_array('visit_status', $fields, true)) { + $insert['visit_status'] = 0; + } + if (in_array('session_id', $fields, true)) { + $insert['session_id'] = 0; + } + if (in_array('queue_no', $fields, true)) { + $maxQueue = $this->db->table('opd_prescription') + ->selectMax('queue_no', 'max_queue') + ->where('date_opd_visit', date('Y-m-d')) + ->where('doc_id', (int) ($opdRow->doc_id ?? 0)) + ->get() + ->getRow(); + $insert['queue_no'] = (int) ($maxQueue->max_queue ?? 0) + 1; + } + + if (! empty($insert)) { + $this->db->table('opd_prescription')->insert($insert); + } + } + public function opd_discount_update(int $opdId) { if (!$this->request->isAJAX()) { diff --git a/app/Controllers/Patient.php b/app/Controllers/Patient.php index 8cdf39e..a6ef966 100644 --- a/app/Controllers/Patient.php +++ b/app/Controllers/Patient.php @@ -192,37 +192,62 @@ class Patient extends BaseController { $inputMphone1 = trim((string) $this->request->getPost('input_mphone1')); $inputAadhar = trim((string) $this->request->getPost('input_udai')); + $inputAbhaId = trim((string) $this->request->getPost('input_abha_id')); $inputRelativeName = trim((string) $this->request->getPost('input_relative_name')); $inputName = trim((string) $this->request->getPost('input_name')); + $abhaField = $this->resolvePatientAbhaIdField(); $builder = $this->db->table('patient_master'); - $builder->select("id, CONCAT(p_fname,'/',IF(gender=1,'M','F'),'/',p_relative,' ',p_rname) AS Sresult"); + $builder->select("id,p_fname,p_relative,p_rname,gender,add1,city,district,state,zip,mphone1,udai,last_visit,dob,age,age_in_month,estimate_dob"); + if ($abhaField !== null) { + $builder->select($abhaField . ' AS abha_id'); + } $hasCondition = false; - if ($inputMphone1 !== '') { - $builder->orWhere('mphone1', $inputMphone1); - $hasCondition = true; - } + if ($inputMphone1 !== '' || $inputAadhar !== '' || $inputAbhaId !== '' || ($inputRelativeName !== '' && $inputName !== '')) { + $builder->groupStart(); - if ($inputAadhar !== '') { - $builder->orWhere('udai', $inputAadhar); - $hasCondition = true; - } + if ($inputMphone1 !== '') { + $builder->where('mphone1', $inputMphone1); + $hasCondition = true; + } - if ($inputRelativeName !== '') { - $builder->groupStart() - ->where('p_rname', $inputRelativeName) - ->where('p_fname', $inputName) - ->groupEnd(); - $hasCondition = true; + if ($inputAadhar !== '') { + $builder->orWhere('udai', $inputAadhar); + $hasCondition = true; + } + + if ($inputAbhaId !== '' && $abhaField !== null) { + $builder->orWhere($abhaField, $inputAbhaId); + $hasCondition = true; + } + + if ($inputRelativeName !== '' && $inputName !== '') { + $builder->orGroupStart() + ->where('p_rname', trim(strtoupper($inputRelativeName))) + ->where('p_fname', trim(strtoupper($inputName))) + ->groupEnd(); + $hasCondition = true; + } + + $builder->groupEnd(); } $searchResult = []; if ($hasCondition) { - $searchResult = $builder->orderBy('id', 'DESC')->get()->getResult(); + $searchResult = $builder->orderBy('id', 'DESC')->limit(25)->get()->getResult(); } - return view('billing/search_adv_data', ['search_result' => $searchResult]); + return view('billing/search_adv_data', [ + 'search_result' => $searchResult, + 'filters' => [ + 'input_mphone1' => $inputMphone1, + 'input_udai' => strtoupper($inputAadhar), + 'input_abha_id' => $inputAbhaId, + 'input_relative_name' => strtoupper($inputRelativeName), + 'input_name' => strtoupper($inputName), + ], + ]); } public function abha_fetch_profile() diff --git a/app/Views/billing/Patient_V.php b/app/Views/billing/Patient_V.php index 8737c91..292eabd 100644 --- a/app/Views/billing/Patient_V.php +++ b/app/Views/billing/Patient_V.php @@ -282,6 +282,14 @@ $('#input_abha_id').on('change blur', function() { var abhaId = ($(this).val() || '').toString().trim(); + if (abhaId !== '') { + refreshAdvancedSearchResult({ + input_abha_id: abhaId + }); + } else { + $('#search_result_update').html(''); + } + if (!/^\d{14}$/.test(abhaId)) { return; } @@ -470,41 +478,36 @@ }; } + function refreshAdvancedSearchResult(payload) { + var csrf = getCsrfData(); + var postData = $.extend({}, payload || {}); + postData[csrf.name] = csrf.value; + + $.post('', postData, function(data) { + $('#search_result_update').html(data); + }); + } + function onchange_feild_number(control_button) { var input_mphone1 = $('#input_mphone1').val(); - var csrf = getCsrfData(); - - $.post('', { - "input_mphone1": input_mphone1, - [csrf.name]: csrf.value - }, function(data) { - $('#search_result_update').html(data); + refreshAdvancedSearchResult({ + "input_mphone1": input_mphone1 }); } function onchange_aadhar(control_button) { var input_udai = $('#input_udai').val(); - var csrf = getCsrfData(); - - $.post('', { - "input_udai": input_udai, - [csrf.name]: csrf.value - }, function(data) { - $('#search_result_update').html(data); + refreshAdvancedSearchResult({ + "input_udai": input_udai }); } function onchange_relative_name(control_button) { var input_relative_name = $('#input_relative_name').val(); var input_name = $('#input_name').val(); - var csrf = getCsrfData(); - - $.post('', { + refreshAdvancedSearchResult({ "input_relative_name": input_relative_name, - "input_name": input_name, - [csrf.name]: csrf.value - }, function(data) { - $('#search_result_update').html(data); + "input_name": input_name }); } diff --git a/app/Views/billing/opd_appointment_dashboard.php b/app/Views/billing/opd_appointment_dashboard.php index eba8722..c8a30de 100644 --- a/app/Views/billing/opd_appointment_dashboard.php +++ b/app/Views/billing/opd_appointment_dashboard.php @@ -29,9 +29,10 @@
- - - + + + +
Bookedcount_booking ?? 0)) ?>
Waitingcount_wait ?? 0)) ?>
Visitedcount_visit ?? 0)) ?>
Bookedcount_booking ?? 0)) ?>
Waitingcount_wait ?? 0)) ?>
Visitedcount_visit ?? 0)) ?>
Cancelledcount_cancel ?? 0)) ?>
Total
diff --git a/app/Views/billing/opd_appointment_list.php b/app/Views/billing/opd_appointment_list.php index e3a6c93..a4dacf8 100644 --- a/app/Views/billing/opd_appointment_list.php +++ b/app/Views/billing/opd_appointment_list.php @@ -1,4 +1,34 @@ - +user() : null; +if ($authUser && method_exists($authUser, 'can') && $authUser->can('opd.doctor-panel.access')) { + $canOpenPrescription = true; +} + +$countBooked = (int) ($doc->count_booking ?? 0); +$countWaiting = (int) ($doc->count_wait ?? 0); +$countVisited = (int) ($doc->count_visit ?? 0); +$countCancelled = (int) ($doc->count_cancel ?? 0); +$countAll = (int) ($doc->No_opd ?? 0); + +// Count waiting patients whose vitals are missing (for badge on filter button) +$missingVitalsCount = 0; +foreach (($opd_list_1 ?? []) as $_r) { + if ((int) ($_r->has_prescription ?? 0) === 1 && (int) ($_r->has_vitals ?? 0) !== 1) { + $missingVitalsCount++; + } +} + +// Build merged dataset for the unified table +$mergedGroups = [ + ['status' => 'booked', 'label' => 'Booked', 'badge' => 'bg-secondary', 'tabType' => 'booking', 'rows' => ($opd_list_0 ?? [])], + ['status' => 'waiting', 'label' => 'Waiting', 'badge' => 'bg-primary', 'tabType' => 'waiting', 'rows' => ($opd_list_1 ?? [])], + ['status' => 'visited', 'label' => 'Visited', 'badge' => 'bg-info text-dark', 'tabType' => 'visited', 'rows' => ($opd_list_2 ?? [])], + ['status' => 'cancelled', 'label' => 'Cancelled', 'badge' => 'bg-danger', 'tabType' => 'cancelled', 'rows' => ($opd_list_3 ?? [])], +]; +?>

Dr. p_fname ?? '') ?> Spec ?? '') ?>

@@ -7,42 +37,128 @@
-
-
Booked : count_booking ?? 0)) ?>
-
Waiting : count_wait ?? 0)) ?>
-
Visited : count_visit ?? 0)) ?>
-
Cancelled : count_cancel ?? 0)) ?>
-
Total : No_opd ?? 0)) ?>
-
- - - -
-
- $opd_list_0, 'showQueue' => false, 'tabType' => 'booking', 'opd_date' => $opd_date, 'doc_id' => $doc_id]) ?> + +
+
+ + + + +
-
-
-
- - -
-
- $opd_list_1, 'showQueue' => true, 'tabType' => 'waiting', 'tableId' => 'opdWaitingTable', 'opd_date' => $opd_date, 'doc_id' => $doc_id]) ?> -
All waiting patients already have vitals.
-
-
- $opd_list_2, 'showQueue' => true, 'tabType' => 'visited', 'tableId' => 'opdVisitedTable', 'opd_date' => $opd_date, 'doc_id' => $doc_id]) ?> -
-
- $opd_list_3, 'showQueue' => true, 'tabType' => 'cancelled', 'opd_date' => $opd_date, 'doc_id' => $doc_id]) ?> +
+ +
+ + +
+ + + + + + + + + + + + + + + + has_vitals ?? 0) === 1; + $hasPrescription = (int) ($row->has_prescription ?? 0) === 1; + $isConfirmedForQueue= (int) ($row->is_confirmed_for_queue ?? 0) === 1; + $queueNo = (int) ($row->queue_no ?? 0); + $hasQueueNo = $queueNo > 0; + $showFullActions = $hasPrescription || $isConfirmedForQueue; + $opdId = (int) ($row->opd_id ?? 0); + $patientName = esc(($row->P_name ?? '') . ' { ' . ($row->p_rname ?? '') . ' }'); + $tabType = $group['tabType']; + $status = $group['status']; + ?> + + + + + + + + + + + + + + + +
StatusOPD No.Current PatientQ No.UHIDOPD TypeAction
opd_code ?? '') ?>p_code ?? '') ?>opd_type ?? '') ?> / Amt: opd_fee_amount ?? '') ?> + + + + + + Go For Payment + + + + + + + + + Consult + + + + + + + + + + + + + + +
No OPD records found for this date.
+
+
All waiting patients already have vitals.
No matching records.
- +