Enhance patient search and enforce refer master validation

This commit is contained in:
DevSoftTech Server 2026-08-07 12:24:48 +05:30
parent 780ebaa263
commit 5f443f307a
9 changed files with 592 additions and 24 deletions

View File

@ -111,6 +111,21 @@ class Patient extends BaseController
->with('error', $abhaError);
}
$referByName = trim((string) $this->request->getPost('refer_by_name'));
$referByError = $this->validatePatientReferByInput($referByName);
if ($referByError !== null) {
if ($isAjax) {
return $this->response->setJSON([
'insertid' => 0,
'error_text' => $referByError,
]);
}
return redirect()->to(base_url('billing/patient'))
->withInput()
->with('error', $referByError);
}
$bloodGroup = trim((string) $this->request->getPost('input_blood_group'));
if ($bloodGroup === '') {
$bloodGroup = 'Not Define';
@ -132,7 +147,7 @@ class Patient extends BaseController
'udai' => strtoupper((string) $this->request->getPost('input_udai')),
'estimate_dob' => $estimate_dob,
];
$this->applyPatientReferbyField($data, (string) $this->request->getPost('refer_by_name'));
$this->applyPatientReferbyField($data, $referByName);
$this->applyPatientAbhaFieldValues($data, $abhaId, $abhaAddress);
if ($chk_age === 'on') {
@ -220,6 +235,9 @@ class Patient extends BaseController
$builder = $this->db->table('patient_master');
$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 ($this->db->fieldExists('referby', 'patient_master')) {
$builder->select('referby');
}
if ($abhaField !== null) {
$builder->select($abhaField . ' AS abha_id');
}
@ -436,7 +454,31 @@ class Patient extends BaseController
$sdata = (string) $this->request->getPost('txtsearch');
$sdata = preg_replace('/[^A-Za-z0-9 _.@\/-]/', '', trim($sdata ?? ''));
$advSearchBy = trim((string) $this->request->getPost('adv_search_by'));
$advSearchValue = preg_replace('/[^A-Za-z0-9 _.@\/-]/', '', trim((string) $this->request->getPost('adv_search_value')));
$advAgeMode = trim((string) $this->request->getPost('adv_age_mode'));
$advAgeValue = preg_replace('/[^0-9]/', '', trim((string) $this->request->getPost('adv_age_value')));
$advAgeFrom = preg_replace('/[^0-9]/', '', trim((string) $this->request->getPost('adv_age_from')));
$advAgeTo = preg_replace('/[^0-9]/', '', trim((string) $this->request->getPost('adv_age_to')));
$advAgeTolerance = preg_replace('/[^0-9]/', '', trim((string) $this->request->getPost('adv_age_tolerance')));
$advAgeToleranceInt = $advAgeTolerance === '' ? 2 : (int) $advAgeTolerance;
if ($advAgeToleranceInt < 0) {
$advAgeToleranceInt = 0;
}
if ($advAgeToleranceInt > 20) {
$advAgeToleranceInt = 20;
}
$data['search_query'] = $sdata;
$data['advanced_filters'] = [
'adv_search_by' => $advSearchBy,
'adv_search_value' => $advSearchValue,
'adv_age_mode' => $advAgeMode,
'adv_age_value' => $advAgeValue,
'adv_age_from' => $advAgeFrom,
'adv_age_to' => $advAgeTo,
'adv_age_tolerance' => (string) $advAgeToleranceInt,
];
return view('billing/Patient_Search_V', $data);
}
@ -508,10 +550,29 @@ class Patient extends BaseController
// Get search value from DataTables search box or from initial search_query
$dtSearch = trim((string) ($request['search']['value'] ?? ''));
$initialSearch = trim((string) ($request['search_query'] ?? ''));
$advSearchBy = trim((string) ($request['adv_search_by'] ?? ''));
$advSearchValue = trim((string) ($request['adv_search_value'] ?? ''));
$advAgeMode = trim((string) ($request['adv_age_mode'] ?? ''));
$advAgeValue = trim((string) ($request['adv_age_value'] ?? ''));
$advAgeFrom = trim((string) ($request['adv_age_from'] ?? ''));
$advAgeTo = trim((string) ($request['adv_age_to'] ?? ''));
$advAgeTolerance = trim((string) ($request['adv_age_tolerance'] ?? ''));
// Use DataTables search if provided, otherwise use initial search_query
$sdata = $dtSearch !== '' ? $dtSearch : $initialSearch;
$sdata = preg_replace('/[^A-Za-z0-9 _.@\/-]/', '', $sdata);
$advSearchValue = preg_replace('/[^A-Za-z0-9 _.@\/-]/', '', $advSearchValue);
$advAgeValue = preg_replace('/[^0-9]/', '', $advAgeValue);
$advAgeFrom = preg_replace('/[^0-9]/', '', $advAgeFrom);
$advAgeTo = preg_replace('/[^0-9]/', '', $advAgeTo);
$advAgeTolerance = preg_replace('/[^0-9]/', '', $advAgeTolerance);
$advAgeToleranceInt = $advAgeTolerance === '' ? 2 : (int) $advAgeTolerance;
if ($advAgeToleranceInt < 0) {
$advAgeToleranceInt = 0;
}
if ($advAgeToleranceInt > 20) {
$advAgeToleranceInt = 20;
}
// Detect ABHA and legacy UHID columns in patient_master
$abhaField = null;
@ -524,6 +585,72 @@ class Patient extends BaseController
if (in_array($f, $pmFields, true)) { $oldUhidField = $f; break; }
}
$hasAdvancedFilters = false;
$advancedClause = '';
if ($advSearchBy === 'age') {
$ageExpr = "(CASE
WHEN p.dob IS NOT NULL AND p.dob != '0000-00-00' THEN TIMESTAMPDIFF(YEAR, p.dob, CURDATE())
WHEN p.age REGEXP '^[0-9]+$' THEN CAST(p.age AS UNSIGNED)
ELSE NULL
END)";
if ($advAgeMode === 'between' && $advAgeFrom !== '' && $advAgeTo !== '') {
$from = (int) $advAgeFrom;
$to = (int) $advAgeTo;
if ($from > $to) {
[$from, $to] = [$to, $from];
}
$advancedClause = " and {$ageExpr} between {$from} and {$to}";
$hasAdvancedFilters = true;
} elseif ($advAgeMode === 'approx' && $advAgeValue !== '') {
$age = (int) $advAgeValue;
$min = max(0, $age - $advAgeToleranceInt);
$max = $age + $advAgeToleranceInt;
$advancedClause = " and {$ageExpr} between {$min} and {$max}";
$hasAdvancedFilters = true;
}
} elseif ($advSearchValue !== '') {
$escapedLike = $this->db->escape('%' . $advSearchValue . '%');
$escapedExact = $this->db->escape($advSearchValue);
switch ($advSearchBy) {
case 'patient_uhid':
$advancedClause = " and (p.p_code LIKE {$escapedLike}";
if (ctype_digit($advSearchValue)) {
$advancedClause .= ' or p.id = ' . (int) $advSearchValue;
}
$advancedClause .= ')';
$hasAdvancedFilters = true;
break;
case 'patient_name':
$advancedClause = " and p.p_fname LIKE {$escapedLike}";
$hasAdvancedFilters = true;
break;
case 'relative_name':
$advancedClause = " and p.p_rname LIKE {$escapedLike}";
$hasAdvancedFilters = true;
break;
case 'phone':
$advancedClause = " and (p.mphone1 = {$escapedExact} or p.mphone1 LIKE {$escapedLike})";
$hasAdvancedFilters = true;
break;
case 'refer_by':
if (in_array('referby', $pmFields, true)) {
$advancedClause = " and p.referby LIKE {$escapedLike}";
$hasAdvancedFilters = true;
}
break;
case 'old_uhid':
$oldUhidClause = $this->buildOldUhidSearchClause($advSearchValue, $oldUhidField);
if ($oldUhidClause !== '') {
$advancedClause = ' and (' . $oldUhidClause . ')';
$hasAdvancedFilters = true;
}
break;
}
}
// Build WHERE clause based on search query
$searchString = ' 1=1 ';
if (strlen($sdata) > 0) {
@ -563,6 +690,25 @@ class Patient extends BaseController
}
}
$searchString .= $advancedClause;
$useLatestThirtyOnly = (strlen($sdata) === 0 && ! $hasAdvancedFilters);
if ($useLatestThirtyOnly) {
$searchString .= " and (
(
p.last_visit IS NOT NULL
AND p.last_visit != '0000-00-00'
AND DATE(p.last_visit) >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
)
OR
(
p.insert_date IS NOT NULL
AND p.insert_date != '0000-00-00'
AND DATE(p.insert_date) >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
)
)";
}
// Count total records (without filtering)
$totalSql = "SELECT COUNT(DISTINCT p.id) as total FROM patient_master p";
$totalData = (int) $this->db->query($totalSql)->getRow()->total;
@ -606,6 +752,11 @@ class Patient extends BaseController
$patientId = (int) ($row->id ?? 0);
$age = esc(get_age_1($row->dob ?? null, $row->age ?? '', $row->age_in_month ?? '', $row->estimate_dob ?? '', $row->Last_Visit ?? null));
$oldUhid = '';
$patientNameCell = esc(($row->p_fname ?? '') . ' {' . ($row->p_rname ?? '') . '}');
$referBy = trim((string) ($row->referby ?? ''));
if ($referBy !== '') {
$patientNameCell .= '<br><small><i>Refer By: ' . esc(ucwords(strtolower($referBy))) . '</i></small>';
}
if ($oldUhidField !== null && $oldUhidField !== '' && isset($row->{$oldUhidField})) {
$oldUhid = (string) $row->{$oldUhidField};
}
@ -613,7 +764,7 @@ class Patient extends BaseController
$data[] = [
$start + $index + 1,
$this->formatPatientCodeCell($patientId, (string) ($row->p_code ?? ''), $oldUhid),
esc(($row->p_fname ?? '') . ' {' . ($row->p_rname ?? '') . '}'),
$patientNameCell,
$age,
esc($row->Last_Visit ?? ''),
esc($row->insurance_status ?? 'Self'),
@ -811,6 +962,16 @@ class Patient extends BaseController
]);
}
$referByName = trim((string) $this->request->getPost('refer_by_name'));
$existingReferBy = is_array($existingPatient) ? (string) ($existingPatient['referby'] ?? '') : null;
$referByError = $this->validatePatientReferByInput($referByName, $existingReferBy);
if ($referByError !== null) {
return $this->response->setJSON([
'update' => 0,
'error_text' => $referByError,
]);
}
$data = [
'mphone1' => $this->request->getPost('input_mphone1'),
'p_fname' => strtoupper((string) $this->request->getPost('input_name')),
@ -828,7 +989,7 @@ class Patient extends BaseController
'estimate_dob' => $estimate_dob,
'blood_group' => $this->request->getPost('input_blood_group'),
];
$this->applyPatientReferbyField($data, (string) $this->request->getPost('refer_by_name'));
$this->applyPatientReferbyField($data, $referByName);
if ($isAbhaVerifiedLocked) {
$data['p_fname'] = (string) ($existingPatient['p_fname'] ?? $data['p_fname']);
@ -891,6 +1052,34 @@ class Patient extends BaseController
$data['referby'] = trim(strtoupper($referByName));
}
private function validatePatientReferByInput(string $referByName, ?string $existingReferBy = null): ?string
{
$referByName = trim($referByName);
if ($referByName === '') {
return null;
}
$normalize = static fn (string $v): string => strtoupper(trim(preg_replace('/\s+/', ' ', $v)));
$typed = $normalize($referByName);
if ($typed === '') {
return null;
}
if ($existingReferBy !== null && $normalize($existingReferBy) === $typed) {
return null;
}
$activeRefs = $this->getActiveReferMasters();
foreach ($activeRefs as $row) {
$label = trim((string) (($row->title ?? '') . ' ' . ($row->f_name ?? '')));
if ($label !== '' && $normalize($label) === $typed) {
return null;
}
}
return 'Refer By must be selected from Refer Master list.';
}
public function update_aadhar()
{
if (!$this->request->isAJAX()) {

View File

@ -9,6 +9,7 @@ class Reffer extends BaseController
{
public function index(): string
{
$this->ensureReferMasterAddressColumns();
$model = new RefferModel();
return view('Setting/Reffer/reffer_index', [
@ -18,6 +19,7 @@ class Reffer extends BaseController
public function create(): string
{
$this->ensureReferMasterAddressColumns();
$model = new RefferModel();
return view('Setting/Reffer/reffer_add', [
@ -27,6 +29,7 @@ class Reffer extends BaseController
public function edit(int $id): string
{
$this->ensureReferMasterAddressColumns();
$model = new RefferModel();
return view('Setting/Reffer/reffer_edit', [
@ -37,6 +40,7 @@ class Reffer extends BaseController
public function store()
{
$this->ensureReferMasterAddressColumns();
if (! $this->request->isAJAX()) {
return $this->response->setJSON(['insertid' => 0, 'error_text' => 'Invalid request']);
}
@ -66,6 +70,8 @@ class Reffer extends BaseController
'active' => 1,
];
$data = array_merge($data, $this->getReferAddressData());
$insertId = $model->insert($data);
return $this->response->setJSON([
@ -76,6 +82,7 @@ class Reffer extends BaseController
public function update(int $id)
{
$this->ensureReferMasterAddressColumns();
if (! $this->request->isAJAX()) {
return $this->response->setJSON(['update' => 0, 'error_text' => 'Invalid request']);
}
@ -105,6 +112,8 @@ class Reffer extends BaseController
'active' => 1,
];
$data = array_merge($data, $this->getReferAddressData());
$updated = $model->updateReffer($data, $id);
return $this->response->setJSON([
@ -196,4 +205,50 @@ class Reffer extends BaseController
'errors' => $errors,
]);
}
private function getReferAddressData(): array
{
$db = db_connect();
$data = [];
if ($db->fieldExists('place', 'refer_master')) {
$data['place'] = trim((string) $this->request->getPost('input_place'));
}
if ($db->fieldExists('city', 'refer_master')) {
$data['city'] = trim((string) $this->request->getPost('input_city'));
}
if ($db->fieldExists('district', 'refer_master')) {
$data['district'] = trim((string) $this->request->getPost('input_district'));
}
if ($db->fieldExists('state', 'refer_master')) {
$data['state'] = trim((string) $this->request->getPost('input_state'));
}
return $data;
}
private function ensureReferMasterAddressColumns(): void
{
try {
$db = db_connect();
if (! $db->tableExists('refer_master')) {
return;
}
$columnsToEnsure = [
'place' => "ALTER TABLE refer_master ADD COLUMN place varchar(150) DEFAULT NULL AFTER phone_number",
'city' => "ALTER TABLE refer_master ADD COLUMN city varchar(100) DEFAULT NULL AFTER place",
'district' => "ALTER TABLE refer_master ADD COLUMN district varchar(100) DEFAULT NULL AFTER city",
'state' => "ALTER TABLE refer_master ADD COLUMN state varchar(100) DEFAULT NULL AFTER district",
];
foreach ($columnsToEnsure as $column => $sql) {
if (! $db->fieldExists($column, 'refer_master')) {
$db->query($sql);
}
}
} catch (\Throwable $e) {
// Fail-open so referral pages still load if schema update is blocked.
}
}
}

View File

@ -33,6 +33,22 @@
<?php endforeach ?>
</select>
</div>
<div class="col-md-3">
<label class="form-label">Place</label>
<input class="form-control" name="input_place" placeholder="Place" value="" type="text" autocomplete="off">
</div>
<div class="col-md-3">
<label class="form-label">City</label>
<input class="form-control" name="input_city" placeholder="City" value="" type="text" autocomplete="off">
</div>
<div class="col-md-3">
<label class="form-label">District</label>
<input class="form-control" name="input_district" placeholder="District" value="" type="text" autocomplete="off">
</div>
<div class="col-md-3">
<label class="form-label">State</label>
<input class="form-control" name="input_state" placeholder="State" value="" type="text" autocomplete="off">
</div>
</div>
<div class="row mt-3">
<div class="col-md-3">

View File

@ -34,6 +34,22 @@
<?php endforeach ?>
</select>
</div>
<div class="col-md-3">
<label class="form-label">Place</label>
<input class="form-control" name="input_place" placeholder="Place" value="<?= esc($data[0]->place ?? '') ?>" type="text" autocomplete="off">
</div>
<div class="col-md-3">
<label class="form-label">City</label>
<input class="form-control" name="input_city" placeholder="City" value="<?= esc($data[0]->city ?? '') ?>" type="text" autocomplete="off">
</div>
<div class="col-md-3">
<label class="form-label">District</label>
<input class="form-control" name="input_district" placeholder="District" value="<?= esc($data[0]->district ?? '') ?>" type="text" autocomplete="off">
</div>
<div class="col-md-3">
<label class="form-label">State</label>
<input class="form-control" name="input_state" placeholder="State" value="<?= esc($data[0]->state ?? '') ?>" type="text" autocomplete="off">
</div>
</div>
<div class="row mt-3">
<div class="col-md-3">

View File

@ -19,6 +19,7 @@
<tr>
<th>Name</th>
<th>Type</th>
<th>Address</th>
<th>Register Date</th>
<th>Status</th>
<th>Edit</th>
@ -27,9 +28,19 @@
<tbody>
<?php if (! empty($refer_master)) : ?>
<?php foreach ($refer_master as $row) : ?>
<?php
$addressParts = array_filter([
trim((string) ($row->place ?? '')),
trim((string) ($row->city ?? '')),
trim((string) ($row->district ?? '')),
trim((string) ($row->state ?? '')),
], static fn ($v) => $v !== '');
$addressText = $addressParts === [] ? 'NA' : implode(', ', $addressParts);
?>
<tr>
<td><?= esc(($row->title ?? '') . ' ' . ($row->f_name ?? '')) ?></td>
<td><?= esc($row->type_desc ?? 'Others') ?></td>
<td><?= esc($addressText) ?></td>
<td><?= esc($row->str_dateadd ?? '') ?></td>
<td>
<?php if (! empty($row->active)) : ?>
@ -45,7 +56,7 @@
<?php endforeach ?>
<?php else : ?>
<tr>
<td colspan="5" class="text-center text-muted">No referral clients found.</td>
<td colspan="6" class="text-center text-muted">No referral clients found.</td>
</tr>
<?php endif ?>
</tbody>

View File

@ -38,6 +38,7 @@
<script>
(function() {
var searchQuery = '<?= esc($search_query ?? '', 'js') ?>';
var advancedFilters = <?= json_encode($advanced_filters ?? [], JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP) ?>;
if (window.jQuery && $.fn.DataTable) {
$('#example1').DataTable({
@ -46,9 +47,16 @@
ajax: {
url: '<?= base_url('billing/patient/search_ajax') ?>',
type: 'GET',
data: {
search_query: searchQuery
}
data: function(d) {
d.search_query = searchQuery;
d.adv_search_by = advancedFilters.adv_search_by || '';
d.adv_search_value = advancedFilters.adv_search_value || '';
d.adv_age_mode = advancedFilters.adv_age_mode || '';
d.adv_age_value = advancedFilters.adv_age_value || '';
d.adv_age_from = advancedFilters.adv_age_from || '';
d.adv_age_to = advancedFilters.adv_age_to || '';
d.adv_age_tolerance = advancedFilters.adv_age_tolerance || '2';
}
},
columns: [
{ data: 0, orderable: false },
@ -60,11 +68,12 @@
{ data: 6, orderable: false }
],
order: [[4, 'desc']], // Order by Last Visit (column index 4) DESC
pageLength: 25,
searching: false,
pageLength: 30,
lengthMenu: [[10, 25, 50, 100, -1], [10, 25, 50, 100, "All"]],
language: {
processing: '<i class="fa fa-spinner fa-spin fa-3x fa-fw"></i><span class="sr-only">Loading...</span>',
emptyTable: searchQuery ? 'No patients found matching your search' : 'No patient records available',
emptyTable: searchQuery ? 'No patients found matching your search' : 'No patients available for the selected criteria',
info: 'Showing _START_ to _END_ of _TOTAL_ patients',
infoEmpty: 'Showing 0 to 0 of 0 patients',
infoFiltered: '(filtered from _MAX_ total patients)',

View File

@ -315,6 +315,72 @@
<button type="submit" class="btn btn-info btn-flat">Go!</button>
</span>
</div>
<div class="mt-2 d-flex justify-content-between align-items-center flex-wrap gap-2">
<small class="text-muted">Blank search shows patients from last 30 days by register date or last visit date.</small>
<button type="button" id="toggle_advanced_search" class="btn btn-outline-secondary btn-sm">
<i class="bi bi-funnel"></i> Advanced Search
</button>
</div>
<div id="advanced_search_panel" class="card mt-2" style="display:none;">
<div class="card-body py-3">
<div class="row g-2 align-items-end">
<div class="col-md-4">
<label class="form-label">Search By</label>
<select class="form-select" name="adv_search_by" id="adv_search_by">
<option value="">Select field</option>
<option value="patient_uhid">Patient / UHID</option>
<option value="patient_name">Patient Name</option>
<option value="relative_name">Relative Name</option>
<option value="phone">Phone No</option>
<option value="age">Age</option>
<option value="refer_by">Refer By</option>
<option value="old_uhid">OLD UHID</option>
</select>
</div>
<div class="col-md-4" id="adv_value_col">
<label class="form-label">Search Value</label>
<input type="text" class="form-control" name="adv_search_value" id="adv_search_value" placeholder="Enter value">
<datalist id="adv_refer_by_list">
<?php foreach (($refer_master ?? []) as $row) : ?>
<?php $advReferLabel = trim((string) (($row->title ?? '') . ' ' . ($row->f_name ?? ''))); ?>
<?php if ($advReferLabel !== '') : ?>
<option value="<?= esc($advReferLabel) ?>"></option>
<?php endif; ?>
<?php endforeach; ?>
</datalist>
</div>
<div class="col-md-4 d-none" id="adv_age_mode_col">
<label class="form-label">Age Mode</label>
<select class="form-select" name="adv_age_mode" id="adv_age_mode">
<option value="approx">Approx</option>
<option value="between">Between</option>
</select>
</div>
<div class="col-md-4 d-none" id="adv_age_approx_col">
<label class="form-label">Approx Age</label>
<input type="number" min="0" max="150" class="form-control" name="adv_age_value" id="adv_age_value" placeholder="e.g. 35">
<small class="text-muted d-block mt-1" id="adv_age_effective_hint">Approx range: -</small>
</div>
<div class="col-md-2 d-none" id="adv_age_tolerance_col">
<label class="form-label">Tolerance (+/- Years)</label>
<input type="number" min="0" max="20" class="form-control" name="adv_age_tolerance" id="adv_age_tolerance" value="2">
</div>
<div class="col-md-2 d-none" id="adv_age_from_col">
<label class="form-label">Age From</label>
<input type="number" min="0" max="150" class="form-control" name="adv_age_from" id="adv_age_from" placeholder="0">
</div>
<div class="col-md-2 d-none" id="adv_age_to_col">
<label class="form-label">Age To</label>
<input type="number" min="0" max="150" class="form-control" name="adv_age_to" id="adv_age_to" placeholder="60">
</div>
</div>
<div class="mt-3 d-flex justify-content-end">
<button type="button" id="btn_advanced_search" class="btn btn-primary btn-sm">
<i class="bi bi-search"></i> Search Advanced
</button>
</div>
</div>
</div>
</form>
<div id="search_loading" class="alert alert-info mt-2" style="display:none;">
<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
@ -791,7 +857,144 @@
return fallbackText;
}
function syncAdvancedSearchFields() {
var searchBy = String($('#adv_search_by').val() || '');
var ageMode = String($('#adv_age_mode').val() || 'approx');
var isAge = searchBy === 'age';
var isReferBy = searchBy === 'refer_by';
$('#adv_value_col').toggleClass('d-none', isAge);
$('#adv_age_mode_col').toggleClass('d-none', !isAge);
if (!isAge) {
$('#adv_search_value').attr('placeholder', isReferBy ? 'Type referral name' : 'Enter value');
if (isReferBy) {
$('#adv_search_value').attr('list', 'adv_refer_by_list');
} else {
$('#adv_search_value').removeAttr('list');
}
}
if (!isAge) {
$('#adv_age_approx_col,#adv_age_tolerance_col,#adv_age_from_col,#adv_age_to_col').addClass('d-none');
$('#adv_age_value,#adv_age_from,#adv_age_to').val('');
$('#adv_age_tolerance').val('2');
$('#adv_age_effective_hint').text('Approx range: -');
return;
}
if (ageMode === 'between') {
$('#adv_age_approx_col,#adv_age_tolerance_col').addClass('d-none');
$('#adv_age_value').val('');
$('#adv_age_tolerance').val('2');
$('#adv_age_effective_hint').text('Approx range: -');
$('#adv_age_from_col,#adv_age_to_col').removeClass('d-none');
} else {
$('#adv_age_from_col,#adv_age_to_col').addClass('d-none');
$('#adv_age_from,#adv_age_to').val('');
$('#adv_age_approx_col').removeClass('d-none');
$('#adv_age_tolerance_col').removeClass('d-none');
syncAgeApproxHint();
}
}
function syncAgeApproxHint() {
var ageValue = parseInt($('#adv_age_value').val(), 10);
var tolerance = parseInt($('#adv_age_tolerance').val(), 10);
if (isNaN(tolerance) || tolerance < 0) {
tolerance = 0;
}
if (tolerance > 20) {
tolerance = 20;
}
if (isNaN(ageValue) || ageValue < 0) {
$('#adv_age_effective_hint').text('Approx range: -');
return;
}
var minAge = Math.max(0, ageValue - tolerance);
var maxAge = ageValue + tolerance;
$('#adv_age_effective_hint').text('Approx range: ' + minAge + ' to ' + maxAge + ' years');
}
function getDatalistValues(listId) {
return $('#' + listId + ' option').map(function() {
return String($(this).val() || '').trim().toUpperCase();
}).get().filter(function(v) {
return v !== '';
});
}
function validateAdvancedReferBy() {
var searchBy = String($('#adv_search_by').val() || '');
if (searchBy !== 'refer_by') {
return true;
}
var typedValue = String($('#adv_search_value').val() || '').trim();
if (typedValue === '') {
return true;
}
var allowedValues = getDatalistValues('adv_refer_by_list');
var typedUpper = typedValue.toUpperCase();
var isValid = allowedValues.indexOf(typedUpper) !== -1;
if (!isValid) {
if (typeof notify === 'function') {
notify('warning', 'Please Attention', 'Refer By must be selected from Refer Master list.');
}
$('#adv_search_value').focus();
return false;
}
return true;
}
function validateReferByAgainstMaster(inputSelector, listId) {
var typedValue = String($(inputSelector).val() || '').trim();
if (typedValue === '') {
return true;
}
var allowedValues = getDatalistValues(listId);
var typedUpper = typedValue.toUpperCase();
var isValid = allowedValues.indexOf(typedUpper) !== -1;
if (!isValid) {
if (typeof notify === 'function') {
notify('warning', 'Please Attention', 'Refer By must be selected from Refer Master list.');
}
$(inputSelector).focus();
return false;
}
return true;
}
$('#toggle_advanced_search').on('click', function() {
$('#advanced_search_panel').stop(true, true).slideToggle(120);
});
$('#adv_search_by').on('change', syncAdvancedSearchFields);
$('#adv_age_mode').on('change', syncAdvancedSearchFields);
$('#adv_age_value,#adv_age_tolerance').on('input change', syncAgeApproxHint);
$('#adv_search_value').on('blur', function() {
validateAdvancedReferBy();
});
$('#refer_by_name').on('blur', function() {
validateReferByAgainstMaster('#refer_by_name', 'refer_by_name_list');
});
syncAdvancedSearchFields();
$('form.form1').on('submit', function(form) {
if (!validateReferByAgainstMaster('#refer_by_name', 'refer_by_name_list')) {
form.preventDefault();
return;
}
$("#RegisterPatient").prop("disabled", true);
form.preventDefault();
@ -805,24 +1008,44 @@
}, 'json');
});
function runPatientSearch(payload, triggerButton, buttonText) {
var loadingDiv = $('#search_loading');
var originalText = buttonText || triggerButton.text();
loadingDiv.show();
triggerButton.prop('disabled', true);
triggerButton.text('Searching...');
$.post('<?= base_url('billing/patient/search') ?>', payload, function(data) {
$('div.searchresult').html(data);
loadingDiv.hide();
triggerButton.prop('disabled', false);
triggerButton.text(originalText);
}).fail(function() {
loadingDiv.hide();
triggerButton.prop('disabled', false);
triggerButton.text(originalText);
});
}
$('form.form2').on('submit', function(form) {
form.preventDefault();
var searchBtn = $('form.form2 button[type="submit"]');
var loadingDiv = $('#search_loading');
// Show loading indicator and disable button
loadingDiv.show();
searchBtn.prop('disabled', true);
searchBtn.text('Searching...');
$.post('<?= base_url('billing/patient/search') ?>', $('form.form2').serialize(), function(data) {
$('div.searchresult').html(data);
// Hide loading indicator and re-enable button
loadingDiv.hide();
searchBtn.prop('disabled', false);
searchBtn.text('Go!');
// Classic Go search should keep old behavior: only txtsearch + csrf.
var basicFields = $('form.form2').serializeArray().filter(function(field) {
return field.name.indexOf('adv_') !== 0;
});
var searchBtn = $('form.form2 button[type="submit"]');
runPatientSearch($.param(basicFields), searchBtn, 'Go!');
});
$('#btn_advanced_search').on('click', function() {
if (!validateAdvancedReferBy()) {
return;
}
var advancedBtn = $(this);
runPatientSearch($('form.form2').serialize(), advancedBtn, 'Search Advanced');
});
/* ===================== ABHA CREATION WIZARD ===================== */

View File

@ -243,6 +243,7 @@
<label>Refer By</label>
<input class="form-control input-sm" name="refer_by_name" id="refer_by_name"
list="refer_by_name_list" placeholder="Type to search referral"
data-original-value="<?= esc($data[0]->referby ?? '') ?>"
value="<?= esc($data[0]->referby ?? '') ?>" autocomplete="off">
<datalist id="refer_by_name_list">
<?php foreach (($refer_master ?? []) as $row) : ?>
@ -374,11 +375,55 @@ function onchange_title() {
}
}
function getReferMasterValues() {
return $('#refer_by_name_list option').map(function() {
return String($(this).val() || '').trim().toUpperCase();
}).get().filter(function(v) {
return v !== '';
});
}
function normalizeReferValue(v) {
return String(v || '').trim().replace(/\s+/g, ' ').toUpperCase();
}
function validateEditReferBy() {
var typedValue = String($('#refer_by_name').val() || '').trim();
if (typedValue === '') {
return true;
}
var originalValue = String($('#refer_by_name').attr('data-original-value') || '').trim();
if (normalizeReferValue(originalValue) !== '' && normalizeReferValue(originalValue) === normalizeReferValue(typedValue)) {
return true;
}
var allowedValues = getReferMasterValues();
var isValid = allowedValues.indexOf(normalizeReferValue(typedValue)) !== -1;
if (!isValid) {
if (typeof notify === 'function') {
notify('warning', 'Please Attention', 'Refer By must be selected from Refer Master list.');
}
$('#refer_by_name').focus();
return false;
}
return true;
}
$(document).ready(function() {
document.title = 'Pt.:<?=$data[0]->p_fname ?>/<?=$data[0]->id ?>';
$('#refer_by_name').on('blur', function() {
validateEditReferBy();
});
$('#btn_update').click(function() {
if (!validateEditReferBy()) {
return;
}
$.post('<?= base_url('billing/patient/update') ?>', $('form.form1').serialize(), function(data) {
var resp = data;
if (typeof data === 'string') {

View File

@ -114,6 +114,7 @@ if (! empty($search_result) && is_array($search_result)) {
<?php
$profileUrl = base_url('billing/patient/person_record') . '/' . (int) ($row->id ?? 0);
$fullName = trim((string) ($row->p_fname ?? ''));
$referBy = trim((string) ($row->referby ?? ''));
$relative = trim((string) ($row->p_relative ?? '')) . ' ' . trim((string) ($row->p_rname ?? ''));
$relative = trim($relative);
$addressParts = array_filter([
@ -164,6 +165,9 @@ if (! empty($search_result) && is_array($search_result)) {
View Profile
</button>
</div>
<?php if ($referBy !== '') : ?>
<div class="small fst-italic text-muted">Refer By: <?= esc(ucwords(strtolower($referBy))) ?></div>
<?php endif; ?>
<div class="small text-muted"><?= esc($relative !== '' ? $relative : 'Relative: NA') ?></div>
<?php if (! empty($matchReasons)) : ?>
<div class="mt-1">