Speed up medicine autocomplete with cache and 10-row limit

This commit is contained in:
DevSoftTech Server 2026-04-08 19:47:38 +05:30
parent 935d6edd0b
commit fad0d89260
2 changed files with 104 additions and 20 deletions

View File

@ -2242,8 +2242,17 @@ class Opd_prescription extends BaseController
unset($row);
$userId = (int) ($this->getCurrentUserId() ?? 0);
$favoriteMap = $userId > 0 ? $this->getUserFavoriteMedicineIds($userId) : [];
$usageMap = $userId > 0 ? $this->getUserMedicineUsageMap($userId) : [];
$candidateIds = [];
foreach ($rows as $row) {
$medId = (int) ($row['id'] ?? 0);
if ($medId > 0) {
$candidateIds[] = $medId;
}
}
$candidateIds = array_values(array_unique($candidateIds));
$favoriteMap = $userId > 0 ? $this->getUserFavoriteMedicineIds($userId, $candidateIds) : [];
$usageMap = $userId > 0 ? $this->getUserMedicineUsageMap($userId, $candidateIds) : [];
foreach ($rows as &$row) {
$medId = (int) ($row['id'] ?? 0);
@ -4435,10 +4444,19 @@ class Opd_prescription extends BaseController
{
$q = trim((string) $this->request->getGet('q'));
$scope = strtolower(trim((string) $this->request->getGet('scope')));
$limit = (int) ($this->request->getGet('limit') ?? 10);
if ($limit <= 0) {
$limit = 10;
}
$limit = min(10, $limit);
if (! in_array($scope, ['active', 'favorite', 'all'], true)) {
$scope = 'active';
}
if ($q === '') {
return $this->response->setJSON(['rows' => []]);
}
$table = $this->findExistingTable(['opd_med_master']);
if ($table === null) {
return $this->response->setJSON(['rows' => []]);
@ -4486,6 +4504,18 @@ class Opd_prescription extends BaseController
$select .= ',' . $remarkField . ' as remark';
}
$userId = (int) ($this->getCurrentUserId() ?? 0);
$cacheKey = 'medicine_search:v2:' . md5(json_encode([
'q' => strtoupper($q),
'scope' => $scope,
'user' => $userId,
'limit' => $limit,
]));
$cachedRows = cache($cacheKey);
if (is_array($cachedRows)) {
return $this->response->setJSON(['rows' => $cachedRows]);
}
$builder = $this->db->table($table)->select($select);
if ($q !== '') {
$builder->groupStart()
@ -4497,7 +4527,7 @@ class Opd_prescription extends BaseController
}
$rows = $builder
->limit(500)
->limit(120)
->get()
->getResultArray();
@ -4505,7 +4535,6 @@ class Opd_prescription extends BaseController
return $this->response->setJSON(['rows' => []]);
}
$userId = (int) ($this->getCurrentUserId() ?? 0);
$favoriteMap = $userId > 0 ? $this->getUserFavoriteMedicineIds($userId) : [];
$usageMap = $userId > 0 ? $this->getUserMedicineUsageMap($userId) : [];
$qUpper = strtoupper($q);
@ -4572,12 +4601,14 @@ class Opd_prescription extends BaseController
return $nameA <=> $nameB;
});
$rows = array_slice($rows, 0, 40);
$rows = array_slice($rows, 0, $limit);
foreach ($rows as &$row) {
unset($row['_score']);
}
unset($row);
cache()->save($cacheKey, $rows, 120);
return $this->response->setJSON(['rows' => $rows]);
}
@ -5105,17 +5136,22 @@ class Opd_prescription extends BaseController
/**
* @return array<int, bool>
*/
private function getUserFavoriteMedicineIds(int $userId): array
private function getUserFavoriteMedicineIds(int $userId, array $medIds = []): array
{
if ($userId <= 0 || ! $this->db->tableExists('opd_medicine_favorites')) {
return [];
}
$rows = $this->db->table('opd_medicine_favorites')
$builder = $this->db->table('opd_medicine_favorites')
->select('med_id')
->where('user_id', $userId)
->get()
->getResultArray();
->where('user_id', $userId);
$medIds = array_values(array_unique(array_filter(array_map('intval', $medIds), static fn (int $id): bool => $id > 0)));
if (! empty($medIds)) {
$builder->whereIn('med_id', $medIds);
}
$rows = $builder->get()->getResultArray();
$out = [];
foreach ($rows as $row) {
@ -5131,17 +5167,22 @@ class Opd_prescription extends BaseController
/**
* @return array<int, array{use_count:int,last_used_at:string}>
*/
private function getUserMedicineUsageMap(int $userId): array
private function getUserMedicineUsageMap(int $userId, array $medIds = []): array
{
if ($userId <= 0 || ! $this->db->tableExists('opd_medicine_usage')) {
return [];
}
$rows = $this->db->table('opd_medicine_usage')
$builder = $this->db->table('opd_medicine_usage')
->select('med_id,use_count,last_used_at')
->where('user_id', $userId)
->get()
->getResultArray();
->where('user_id', $userId);
$medIds = array_values(array_unique(array_filter(array_map('intval', $medIds), static fn (int $id): bool => $id > 0)));
if (! empty($medIds)) {
$builder->whereIn('med_id', $medIds);
}
$rows = $builder->get()->getResultArray();
$out = [];
foreach ($rows as $row) {

View File

@ -1112,6 +1112,10 @@
var investigationBatchActive = false;
var activeMedicineScope = 'all';
var medicineSuggestRows = [];
var medicineSearchCache = {};
var medicineSearchCacheTtlMs = 120000;
var medInputTimer = null;
var crgcMedInputTimer = null;
var medicineDoseMasterCache = { dose: [], when: [], freq: [], where: [] };
var investigationProfiles = {
cardiac: ['ECG', 'Lipid Profile', 'CBC'],
@ -1173,6 +1177,33 @@
}, 'json');
}
function getMedicineSearchCacheKey(query, scope) {
return (scope || 'all') + '|' + (query || '').trim().toUpperCase();
}
function fetchMedicineSuggestions(query, scope, done) {
var q = (query || '').trim();
if (q.length < 2) {
done([]);
return;
}
var useScope = (scope || 'all').toString();
var cacheKey = getMedicineSearchCacheKey(q, useScope);
var now = Date.now();
var hit = medicineSearchCache[cacheKey];
if (hit && (now - (hit.ts || 0) < medicineSearchCacheTtlMs)) {
done(hit.rows || []);
return;
}
apiGet('<?= base_url('Opd_prescription/medicine_search') ?>?q=' + encodeURIComponent(q) + '&scope=' + encodeURIComponent(useScope) + '&limit=10', function(data) {
var rows = (data && data.rows) ? data.rows : [];
medicineSearchCache[cacheKey] = { ts: now, rows: rows };
done(rows);
});
}
function renderMedicineMasterSelectOptions($select, rows, placeholder) {
var html = '<option value="">' + $('<div>').text(placeholder || 'Select').html() + '</option>';
(rows || []).forEach(function(row) {
@ -4803,13 +4834,18 @@
// Medicine name autocomplete inside create modal
$('#crgc_med_name').on('input', function() {
var q = ($(this).val() || '').trim();
if (crgcMedInputTimer) {
clearTimeout(crgcMedInputTimer);
crgcMedInputTimer = null;
}
if (q.length < 2) {
crgcMedSuggestRows = [];
$('#crgc_med_suggest').html('');
return;
}
apiGet('<?= base_url('Opd_prescription/medicine_search') ?>?q=' + encodeURIComponent(q) + '&scope=all', function(data) {
crgcMedSuggestRows = data.rows || [];
crgcMedInputTimer = setTimeout(function() {
fetchMedicineSuggestions(q, 'all', function(rows) {
crgcMedSuggestRows = rows || [];
var html = '';
crgcMedSuggestRows.forEach(function(row) {
html += '<option value="' + $('<div>').text(row.med_name || '').html() + '"'
@ -4826,7 +4862,8 @@
break;
}
}
});
});
}, 180);
});
$('#crgc_med_name').on('change', function() {
@ -5025,14 +5062,19 @@
$('#med_name').on('input', function() {
var q = ($(this).val() || '').trim();
if (medInputTimer) {
clearTimeout(medInputTimer);
medInputTimer = null;
}
if (q.length < 2) {
medicineSuggestRows = [];
$('#medicine_suggest').html('');
return;
}
apiGet('<?= base_url('Opd_prescription/medicine_search') ?>?q=' + encodeURIComponent(q) + '&scope=' + encodeURIComponent(activeMedicineScope), function(data) {
medInputTimer = setTimeout(function() {
fetchMedicineSuggestions(q, activeMedicineScope, function(rows) {
var html = '';
medicineSuggestRows = data.rows || [];
medicineSuggestRows = rows || [];
medicineSuggestRows.forEach(function(row) {
html += '<option '
+ 'value="' + $('<div>').text(row.med_name || '').html() + '" '
@ -5060,6 +5102,7 @@
}
}
});
}, 180);
});
$('#med_name').on('change', function() {