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('= base_url('billing/patient/search_adv') ?>', postData, function(data) {
+ $('#search_result_update').html(data);
+ });
+ }
+
function onchange_feild_number(control_button) {
var input_mphone1 = $('#input_mphone1').val();
- var csrf = getCsrfData();
-
- $.post('= base_url('billing/patient/search_adv') ?>', {
- "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('= base_url('billing/patient/search_adv') ?>', {
- "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('= base_url('billing/patient/search_adv') ?>', {
+ 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 @@
- | Booked | = esc((int) ($row->count_booking ?? 0)) ?> |
- | Waiting | = esc((int) ($row->count_wait ?? 0)) ?> |
- | Visited | = esc((int) ($row->count_visit ?? 0)) ?> |
+ | Booked | = esc((int) ($row->count_booking ?? 0)) ?> |
+ | Waiting | = esc((int) ($row->count_wait ?? 0)) ?> |
+ | Visited | = esc((int) ($row->count_visit ?? 0)) ?> |
+ | Cancelled | = esc((int) ($row->count_cancel ?? 0)) ?> |
| Total | = esc($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 ?? [])],
+];
+?>