Enhance OPD and patient workflows with live updates and UX improvements
This commit is contained in:
parent
16f654289e
commit
19db43762d
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
});
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,9 +29,10 @@
|
|||
</div>
|
||||
<div class="card-body p-0">
|
||||
<table class="table table-sm mb-0">
|
||||
<tr><td>Booked</td><td class="text-end"><span class="badge bg-secondary\"><?= esc((int) ($row->count_booking ?? 0)) ?></span></td></tr>
|
||||
<tr><td>Waiting</td><td class="text-end"><span class="badge bg-primary\"><?= esc((int) ($row->count_wait ?? 0)) ?></span></td></tr>
|
||||
<tr><td>Visited</td><td class="text-end"><span class="badge bg-info\"><?= esc((int) ($row->count_visit ?? 0)) ?></span></td></tr>
|
||||
<tr><td>Booked</td><td class="text-end"><span class="badge bg-secondary"><?= esc((int) ($row->count_booking ?? 0)) ?></span></td></tr>
|
||||
<tr><td>Waiting</td><td class="text-end"><span class="badge bg-primary"><?= esc((int) ($row->count_wait ?? 0)) ?></span></td></tr>
|
||||
<tr><td>Visited</td><td class="text-end"><span class="badge bg-info"><?= esc((int) ($row->count_visit ?? 0)) ?></span></td></tr>
|
||||
<tr><td>Cancelled</td><td class="text-end"><span class="badge bg-warning text-dark"><?= esc((int) ($row->count_cancel ?? 0)) ?></span></td></tr>
|
||||
<tr><td>Total</td><td class="text-end"><span class="badge bg-danger"><?= esc($total) ?></span></td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -13,10 +13,14 @@ if ($user) {
|
|||
$paymentHistoryRows = $payment_history_rows ?? [];
|
||||
?>
|
||||
<section class="content-header">
|
||||
<h1>OPD Invoice</h1>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="javascript:load_form('<?= base_url('billing/patient/person_record') ?>/<?= esc($opd_master[0]->p_id ?? 0) ?>/0');">Person Home</a></li>
|
||||
</ol>
|
||||
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2">
|
||||
<h1 class="mb-0">OPD Invoice</h1>
|
||||
<button type="button"
|
||||
class="btn btn-outline-primary"
|
||||
onclick="load_form('<?= base_url('billing/patient/person_record') ?>/<?= esc($opd_master[0]->p_id ?? 0) ?>/0','Patient Record');">
|
||||
Person Home
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="invoice">
|
||||
|
|
|
|||
|
|
@ -1,11 +1,193 @@
|
|||
<?php if (!empty($search_result)) : ?>
|
||||
<ul class="list-group">
|
||||
<?php
|
||||
$formatDate = static function ($value): string {
|
||||
$value = trim((string) $value);
|
||||
if ($value === '' || $value === '0000-00-00') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$dt = \DateTime::createFromFormat('Y-m-d', $value);
|
||||
if (! $dt) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $dt->format('d-m-Y');
|
||||
};
|
||||
|
||||
$getAgeDisplay = static function ($row): string {
|
||||
$dob = trim((string) ($row->dob ?? ''));
|
||||
if ($dob !== '' && $dob !== '0000-00-00') {
|
||||
$dobDate = date_create($dob);
|
||||
if ($dobDate !== false) {
|
||||
$ageYears = date_diff($dobDate, new \DateTime('today'))->y;
|
||||
return (string) $ageYears;
|
||||
}
|
||||
}
|
||||
|
||||
$years = trim((string) ($row->age ?? ''));
|
||||
$months = trim((string) ($row->age_in_month ?? ''));
|
||||
if ($years !== '') {
|
||||
return $months !== '' ? ($years . 'y ' . $months . 'm') : $years;
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
$genderLabel = static function ($gender): string {
|
||||
$gender = trim((string) $gender);
|
||||
if ($gender === '1') {
|
||||
return 'Male';
|
||||
}
|
||||
if ($gender === '2') {
|
||||
return 'Female';
|
||||
}
|
||||
|
||||
return $gender !== '' ? $gender : 'NA';
|
||||
};
|
||||
|
||||
$normalize = static function ($value): string {
|
||||
return strtoupper(trim((string) $value));
|
||||
};
|
||||
|
||||
$filters = $filters ?? [];
|
||||
$filterPhone = trim((string) ($filters['input_mphone1'] ?? ''));
|
||||
$filterAadhaar = $normalize($filters['input_udai'] ?? '');
|
||||
$filterAbha = trim((string) ($filters['input_abha_id'] ?? ''));
|
||||
$filterName = $normalize($filters['input_name'] ?? '');
|
||||
$filterRelativeName = $normalize($filters['input_relative_name'] ?? '');
|
||||
|
||||
$getMatchScore = static function ($row) use ($normalize, $filterPhone, $filterAadhaar, $filterAbha, $filterName, $filterRelativeName): int {
|
||||
$score = 0;
|
||||
|
||||
if ($filterPhone !== '' && trim((string) ($row->mphone1 ?? '')) === $filterPhone) {
|
||||
$score++;
|
||||
}
|
||||
|
||||
if ($filterAadhaar !== '' && $normalize($row->udai ?? '') === $filterAadhaar) {
|
||||
$score++;
|
||||
}
|
||||
|
||||
if ($filterAbha !== '' && trim((string) ($row->abha_id ?? '')) === $filterAbha) {
|
||||
$score++;
|
||||
}
|
||||
|
||||
if (
|
||||
$filterName !== ''
|
||||
&& $filterRelativeName !== ''
|
||||
&& $normalize($row->p_fname ?? '') === $filterName
|
||||
&& $normalize($row->p_rname ?? '') === $filterRelativeName
|
||||
) {
|
||||
$score++;
|
||||
}
|
||||
|
||||
return $score;
|
||||
};
|
||||
|
||||
$getConfidence = static function (int $score): array {
|
||||
if ($score >= 3) {
|
||||
return ['High', 'bg-danger'];
|
||||
}
|
||||
|
||||
if ($score === 2) {
|
||||
return ['Medium', 'bg-warning text-dark'];
|
||||
}
|
||||
|
||||
return ['Low', 'bg-secondary'];
|
||||
};
|
||||
|
||||
if (! empty($search_result) && is_array($search_result)) {
|
||||
usort($search_result, static function ($a, $b) use ($getMatchScore) {
|
||||
$scoreA = $getMatchScore($a);
|
||||
$scoreB = $getMatchScore($b);
|
||||
|
||||
if ($scoreA !== $scoreB) {
|
||||
return $scoreB <=> $scoreA;
|
||||
}
|
||||
|
||||
return ((int) ($b->id ?? 0)) <=> ((int) ($a->id ?? 0));
|
||||
});
|
||||
}
|
||||
?>
|
||||
|
||||
<?php if (! empty($search_result)) : ?>
|
||||
<div class="list-group">
|
||||
<?php foreach ($search_result as $row) : ?>
|
||||
<li class="list-group-item">
|
||||
<?= esc($row->Sresult ?? '') ?>
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
<?php
|
||||
$profileUrl = base_url('billing/patient/person_record') . '/' . (int) ($row->id ?? 0);
|
||||
$fullName = trim((string) ($row->p_fname ?? ''));
|
||||
$relative = trim((string) ($row->p_relative ?? '')) . ' ' . trim((string) ($row->p_rname ?? ''));
|
||||
$relative = trim($relative);
|
||||
$addressParts = array_filter([
|
||||
trim((string) ($row->add1 ?? '')),
|
||||
trim((string) ($row->city ?? '')),
|
||||
trim((string) ($row->district ?? '')),
|
||||
trim((string) ($row->state ?? '')),
|
||||
trim((string) ($row->zip ?? '')),
|
||||
], static fn($v) => $v !== '');
|
||||
$addressText = implode(', ', $addressParts);
|
||||
$lastVisit = $formatDate($row->last_visit ?? '');
|
||||
$ageDisplay = $getAgeDisplay($row);
|
||||
$gender = $genderLabel($row->gender ?? '');
|
||||
|
||||
$matchReasons = [];
|
||||
if ($filterPhone !== '' && trim((string) ($row->mphone1 ?? '')) === $filterPhone) {
|
||||
$matchReasons[] = 'Phone';
|
||||
}
|
||||
if ($filterAadhaar !== '' && $normalize($row->udai ?? '') === $filterAadhaar) {
|
||||
$matchReasons[] = 'Aadhaar';
|
||||
}
|
||||
if ($filterAbha !== '' && trim((string) ($row->abha_id ?? '')) === $filterAbha) {
|
||||
$matchReasons[] = 'ABHA';
|
||||
}
|
||||
if (
|
||||
$filterName !== ''
|
||||
&& $filterRelativeName !== ''
|
||||
&& $normalize($row->p_fname ?? '') === $filterName
|
||||
&& $normalize($row->p_rname ?? '') === $filterRelativeName
|
||||
) {
|
||||
$matchReasons[] = 'Name + Relative';
|
||||
}
|
||||
|
||||
$isStrongDuplicate = count($matchReasons) > 1;
|
||||
$matchScore = $getMatchScore($row);
|
||||
[$confidenceText, $confidenceClass] = $getConfidence($matchScore);
|
||||
?>
|
||||
<div class="list-group-item list-group-item-action <?= $isStrongDuplicate ? 'border border-warning' : '' ?>"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
onclick="load_form('<?= esc($profileUrl) ?>', 'Patient Record');"
|
||||
onkeydown="if(event.key==='Enter' || event.key===' '){event.preventDefault();load_form('<?= esc($profileUrl) ?>', 'Patient Record');}">
|
||||
<div class="d-flex justify-content-between align-items-start gap-2">
|
||||
<div class="fw-semibold"><?= esc($fullName !== '' ? $fullName : 'Unknown') ?></div>
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-outline-primary"
|
||||
onclick="event.stopPropagation(); load_form('<?= esc($profileUrl) ?>', 'Patient Record');">
|
||||
View Profile
|
||||
</button>
|
||||
</div>
|
||||
<div class="small text-muted"><?= esc($relative !== '' ? $relative : 'Relative: NA') ?></div>
|
||||
<?php if (! empty($matchReasons)) : ?>
|
||||
<div class="mt-1">
|
||||
<span class="badge <?= esc($confidenceClass) ?> me-1">Confidence: <?= esc($confidenceText) ?></span>
|
||||
<?php foreach ($matchReasons as $reason) : ?>
|
||||
<span class="badge <?= $isStrongDuplicate ? 'bg-warning text-dark' : 'bg-info text-dark' ?> me-1">Match: <?= esc($reason) ?></span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="mt-1 small">
|
||||
<span class="me-2"><strong>Gender:</strong> <?= esc($gender) ?></span>
|
||||
<?php if ($ageDisplay !== '') : ?>
|
||||
<span class="me-2"><strong>Age:</strong> <?= esc($ageDisplay) ?></span>
|
||||
<?php endif; ?>
|
||||
<?php if ($lastVisit !== '') : ?>
|
||||
<span><strong>Last Visit:</strong> <?= esc($lastVisit) ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php if ($addressText !== '') : ?>
|
||||
<div class="small mt-1"><strong>Address:</strong> <?= esc($addressText) ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<div class="text-muted">No matching records.</div>
|
||||
<?php endif ?>
|
||||
<?php endif; ?>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user