From 05ff0495262a17aff0d84a837b23cb5632d90051 Mon Sep 17 00:00:00 2001 From: DevSoftTech Server Date: Mon, 13 Apr 2026 12:46:42 +0530 Subject: [PATCH] Improve consult medicine search normalization and duplicate blocking --- app/Controllers/Opd_prescription.php | 96 ++++++++++++++++++++++++++-- 1 file changed, 89 insertions(+), 7 deletions(-) diff --git a/app/Controllers/Opd_prescription.php b/app/Controllers/Opd_prescription.php index e1406a7..56a98a6 100644 --- a/app/Controllers/Opd_prescription.php +++ b/app/Controllers/Opd_prescription.php @@ -4968,6 +4968,12 @@ class Opd_prescription extends BaseController return $this->response->setJSON(['rows' => []]); } + $qUpper = strtoupper($q); + $qLoose = trim((string) (preg_replace('/[^A-Z0-9]+/', ' ', $qUpper) ?? $qUpper)); + $qNormalized = trim((string) (preg_replace('/[^A-Z0-9]+/', '', $qUpper) ?? $qUpper)); + $qTokens = array_values(array_filter(preg_split('/\s+/', $qLoose) ?: [], static fn(string $token): bool => $token !== '')); + $qFirstToken = $qTokens[0] ?? ''; + $table = $this->findExistingTable(['opd_med_master']); if ($table === null) { return $this->response->setJSON(['rows' => []]); @@ -5030,15 +5036,19 @@ class Opd_prescription extends BaseController $builder = $this->db->table($table)->select($select); if ($q !== '') { $builder->groupStart() - ->like($nameField, $q, 'after'); + ->like($nameField, $q, 'both'); if ($formField !== null) { - $builder->orLike($formField, $q, 'after'); + $builder->orLike($formField, $q, 'both'); + } + if ($qFirstToken !== '' && mb_strlen($qFirstToken) >= 1) { + $builder->orLike($nameField, $qFirstToken, 'after'); } $builder->groupEnd(); } $rows = $builder - ->limit(120) + ->orderBy($nameField, 'ASC') + ->limit(500) ->get() ->getResultArray(); @@ -5048,12 +5058,34 @@ class Opd_prescription extends BaseController $favoriteMap = $userId > 0 ? $this->getUserFavoriteMedicineIds($userId) : []; $usageMap = $userId > 0 ? $this->getUserMedicineUsageMap($userId) : []; - $qUpper = strtoupper($q); - foreach ($rows as &$row) { $medId = (int) ($row['id'] ?? 0); $name = trim((string) ($row['med_name'] ?? '')); $nameUpper = strtoupper($name); + $nameLoose = trim((string) (preg_replace('/[^A-Z0-9]+/', ' ', $nameUpper) ?? $nameUpper)); + $nameNormalized = trim((string) (preg_replace('/[^A-Z0-9]+/', '', $nameUpper) ?? $nameUpper)); + + $tokenMatch = false; + if (! empty($qTokens)) { + $tokenMatch = true; + foreach ($qTokens as $token) { + if ($token === '' || ! str_contains($nameLoose, $token)) { + $tokenMatch = false; + break; + } + } + } + + $matchesSearch = false; + if ($qUpper !== '') { + $matchesSearch = str_contains($nameUpper, $qUpper) + || ($qNormalized !== '' && str_contains($nameNormalized, $qNormalized)) + || $tokenMatch; + } + + if (! $matchesSearch) { + $row['_skip'] = 1; + } $isFavorite = isset($favoriteMap[$medId]); $useCount = (int) ($usageMap[$medId]['use_count'] ?? 0); @@ -5085,6 +5117,19 @@ class Opd_prescription extends BaseController } elseif (str_contains($nameUpper, $qUpper)) { $score += 100; } + + if ($qNormalized !== '' && str_starts_with($nameNormalized, $qNormalized)) { + $score += 180; + } elseif ($qNormalized !== '' && str_contains($nameNormalized, $qNormalized)) { + $score += 120; + } + + if ($tokenMatch) { + $score += 140; + if ($qFirstToken !== '' && str_starts_with($nameLoose, $qFirstToken)) { + $score += 40; + } + } } $row['is_favorite'] = $isFavorite ? 1 : 0; @@ -5094,6 +5139,8 @@ class Opd_prescription extends BaseController } unset($row); + $rows = array_values(array_filter($rows, static fn(array $row): bool => (int) ($row['_skip'] ?? 0) !== 1)); + if ($scope === 'favorite') { $rows = array_values(array_filter($rows, static fn(array $row): bool => (int) ($row['is_favorite'] ?? 0) === 1)); } elseif ($scope === 'active') { @@ -5115,6 +5162,7 @@ class Opd_prescription extends BaseController $rows = array_slice($rows, 0, $limit); foreach ($rows as &$row) { unset($row['_score']); + unset($row['_skip']); } unset($row); @@ -5855,7 +5903,7 @@ class Opd_prescription extends BaseController $builder->where('id !=', $excludeId); } - foreach (['med_id', 'med_name', 'med_type', 'dosage', 'dosage_when', 'dosage_freq', 'no_of_days', 'qty', 'remark'] as $field) { + foreach (['med_id', 'med_type', 'dosage', 'dosage_when', 'dosage_freq', 'no_of_days', 'qty', 'remark'] as $field) { if (! in_array($field, $fields, true)) { continue; } @@ -5868,7 +5916,41 @@ class Opd_prescription extends BaseController } } - return $builder->get(1)->getRowArray() !== null; + $candidates = $builder->get()->getResultArray(); + if (empty($candidates)) { + return false; + } + + if (! in_array('med_name', $fields, true)) { + return true; + } + + $needle = $this->normalizeMedicineMatchKey((string) ($row['med_name'] ?? '')); + if ($needle === '') { + foreach ($candidates as $candidate) { + $name = trim((string) ($candidate['med_name'] ?? '')); + if ($name === '') { + return true; + } + } + return false; + } + + foreach ($candidates as $candidate) { + $current = $this->normalizeMedicineMatchKey((string) ($candidate['med_name'] ?? '')); + if ($current !== '' && $current === $needle) { + return true; + } + } + + return false; + } + + private function normalizeMedicineMatchKey(string $value): string + { + $key = strtoupper(trim($value)); + $key = preg_replace('/[^A-Z0-9]+/', '', $key) ?? $key; + return trim((string) $key); } public function medicine_remove($id)