diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 3975977..c3f5219 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -477,12 +477,14 @@ $routes->post('Opd/confirm_payment', 'Opd::confirm_payment'); $routes->post('Opd/opd_discount_update/(:num)', 'Opd::opd_discount_update/$1'); $routes->post('Opd/opd_cancel/(:num)', 'Opd::opd_cancel/$1'); $routes->match(['get', 'post'], 'Opd/opd_load_doc/(:num)', 'Opd::opd_load_doc/$1'); +$routes->match(['get', 'post'], 'Opd/opd_file_list/(:num)', 'Opd::opd_file_list/$1'); $routes->match(['get', 'post'], 'Opd/opd_file_last_list', 'Opd::opd_file_last_list'); $routes->match(['get', 'post'], 'Opd/opd_file_last_list/(:num)', 'Opd::opd_file_last_list/$1'); $routes->match(['get', 'post'], 'Opd/save_image/(:num)', 'Opd::save_image/$1'); $routes->post('Opd/scan_ai_process_file', 'Opd::scan_ai_process_file'); $routes->post('Opd/scan_ai_assist', 'Opd::scan_ai_assist'); $routes->match(['get', 'post'], 'Opd/opd_file_hide/(:num)', 'Opd::opd_file_hide/$1'); +$routes->match(['get', 'post'], 'Opd/opd_file_delete/(:num)', 'Opd::opd_file_delete/$1'); $routes->get('Opd/invoice/(:num)', 'Opd::invoice/$1'); $routes->get('Opd/invoice_print/(:num)', 'Opd::invoice_print/$1'); $routes->get('Opd/opd_lettre_print/(:num)', 'Opd::opd_lettre_print/$1'); diff --git a/app/Controllers/Opd.php b/app/Controllers/Opd.php index 1d12e5d..0bf08cf 100644 --- a/app/Controllers/Opd.php +++ b/app/Controllers/Opd.php @@ -1924,6 +1924,30 @@ class Opd extends BaseController foreach ($rxMeds as $med) { $i++; $name = trim((string) ($med['med_name'] ?? $med['drug_name'] ?? $med['medicine_name'] ?? $med['item_name'] ?? '')); + $formulationRaw = trim((string) ($med['formulation'] ?? $med['dosage_form'] ?? $med['form'] ?? $med['medicine_form'] ?? '')); + $formulationPrefixMap = [ + 'tablet' => 'Tab', + 'tab' => 'Tab', + 'capsule' => 'Cap', + 'cap' => 'Cap', + 'syrup' => 'Syp', + 'syp' => 'Syp', + 'injection' => 'Inj', + 'inj' => 'Inj', + 'drop' => 'Drop', + 'drops' => 'Drops', + 'cream' => 'Cream', + 'ointment' => 'Oint', + 'gel' => 'Gel', + 'lotion' => 'Lotion', + 'powder' => 'Powder', + 'sachet' => 'Sachet', + ]; + $formulationKey = strtolower($formulationRaw); + $formulationPrefix = $formulationPrefixMap[$formulationKey] ?? $formulationRaw; + if ($formulationPrefix !== '' && $name !== '' && preg_match('/^' . preg_quote($formulationPrefix, '/') . '\b/i', $name) !== 1) { + $name = trim($formulationPrefix . ' ' . $name); + } $generic = trim((string) ($med['genericname'] ?? $med['generic_name'] ?? $med['salt_name'] ?? '')); $dose = trim((string) ($med['dosage_label'] ?? $med['dosage'] ?? $med['drug_dose'] ?? $med['dose'] ?? '')); $freq = trim((string) ($med['dosage_freq_label'] ?? $med['dosage_freq'] ?? $med['drug_freq'] ?? $med['frequency'] ?? '')); @@ -3257,7 +3281,9 @@ class Opd extends BaseController $mapFreqLocal = []; $mapWhereLocal = []; $medGenericMap = []; + $medFormulationMap = []; $missingGenericMedIds = []; + $missingFormulationMedIds = []; if ($this->db->tableExists('opd_dose_shed')) { $f = $this->db->getFieldNames('opd_dose_shed'); @@ -3380,20 +3406,40 @@ class Opd extends BaseController $missingGenericMedIds[$medId] = true; } } + + $formulationInline = trim((string) ($medRow['formulation'] ?? ($medRow['dosage_form'] ?? ''))); + if ($formulationInline === '') { + $medId = (int) ($medRow['med_id'] ?? 0); + if ($medId > 0) { + $missingFormulationMedIds[$medId] = true; + } + } } unset($medRow); - if (!empty($missingGenericMedIds) && $this->db->tableExists('opd_med_master')) { + if ((!empty($missingGenericMedIds) || !empty($missingFormulationMedIds)) && $this->db->tableExists('opd_med_master')) { $masterFields = $this->db->getFieldNames('opd_med_master'); $masterIdField = $this->resolveFirstField($masterFields, ['id']); $masterGenericField = $this->resolveFirstField($masterFields, ['genericname', 'generic_name']); $masterSaltField = $this->resolveFirstField($masterFields, ['salt_name', 'sal_name', 'salt', 'saltname']); + $masterFormulationField = $this->resolveFirstField($masterFields, ['formulation', 'dosage_form', 'form']); $labelField = $masterSaltField ?? $masterGenericField; + $select = []; + if ($masterIdField !== null) { + $select[] = $masterIdField . ' as med_id'; + } + if ($labelField !== null) { + $select[] = $labelField . ' as generic_fallback'; + } + if ($masterFormulationField !== null) { + $select[] = $masterFormulationField . ' as formulation_fallback'; + } - if ($masterIdField !== null && $labelField !== null) { + if ($masterIdField !== null && !empty($select)) { + $neededIds = array_unique(array_map('intval', array_merge(array_keys($missingGenericMedIds), array_keys($missingFormulationMedIds)))); $rows = $this->db->table('opd_med_master') - ->select($masterIdField . ' as med_id,' . $labelField . ' as generic_fallback') - ->whereIn($masterIdField, array_map('intval', array_keys($missingGenericMedIds))) + ->select(implode(',', $select)) + ->whereIn($masterIdField, $neededIds) ->get() ->getResultArray(); @@ -3403,6 +3449,10 @@ class Opd extends BaseController if ($id > 0 && $label !== '') { $medGenericMap[$id] = $label; } + $formulation = trim((string) ($r['formulation_fallback'] ?? '')); + if ($id > 0 && $formulation !== '') { + $medFormulationMap[$id] = $formulation; + } } } } @@ -3415,6 +3465,14 @@ class Opd extends BaseController $medRow['genericname'] = $fallback; } } + + $formulationInline = trim((string) ($medRow['formulation'] ?? ($medRow['dosage_form'] ?? ''))); + if ($formulationInline === '') { + $formulationFallback = $medFormulationMap[(int) ($medRow['med_id'] ?? 0)] ?? ''; + if ($formulationFallback !== '') { + $medRow['formulation'] = $formulationFallback; + } + } } unset($medRow); } @@ -4193,6 +4251,61 @@ class Opd extends BaseController return view('billing/opd_scan_last_list', ['opd_file_list' => $list]); } + public function opd_file_list(int $opdid) + { + if (! $this->db->tableExists('file_upload_data')) { + return $this->response->setBody('
No scan table found.
'); + } + + $fields = $this->db->getFieldNames('file_upload_data'); + $safeSelect = ['id', 'full_path', 'file_ext', 'insert_date']; + if (in_array('show_type', $fields, true)) { + $safeSelect[] = 'show_type'; + } + + $rows = $this->db->table('file_upload_data') + ->select(implode(',', $safeSelect)) + ->where('opd_id', (int) $opdid) + ->where('show_type', 0) + ->orderBy('id', 'ASC') + ->get() + ->getResultArray(); + + $today = date('Y-m-d'); + $slides = []; + foreach ($rows as $row) { + $rawPath = (string) ($row['full_path'] ?? ''); + $normalizedPath = str_replace('\\', '/', $rawPath); + $publicPath = $normalizedPath; + $uploadPos = strpos($normalizedPath, '/uploads/'); + if ($uploadPos !== false) { + $publicPath = substr($normalizedPath, $uploadPos); + } + + $ext = strtolower((string) ($row['file_ext'] ?? pathinfo($publicPath, PATHINFO_EXTENSION))); + if ($ext !== '' && $ext[0] === '.') { + $ext = substr($ext, 1); + } + + $insertDate = (string) ($row['insert_date'] ?? ''); + $insertYmd = $insertDate !== '' ? date('Y-m-d', strtotime($insertDate)) : ''; + $canDeleteToday = $insertYmd === $today; + + $slides[] = [ + 'id' => (int) ($row['id'] ?? 0), + 'path' => $publicPath, + 'is_pdf' => $ext === 'pdf', + 'insert_date' => $insertDate !== '' ? date('d-m-Y H:i', strtotime($insertDate)) : '', + 'can_delete_today' => $canDeleteToday ? 1 : 0, + ]; + } + + return view('billing/opd_scan_carousel_list', [ + 'opdid' => (int) $opdid, + 'slides' => $slides, + ]); + } + public function scan_ai_assist() { if (! $this->request->isAJAX()) { @@ -4319,6 +4432,59 @@ class Opd extends BaseController return $this->opd_file_last_list((int) ($row['opd_id'] ?? 0)); } + public function opd_file_delete(int $fileId) + { + if (! $this->db->tableExists('file_upload_data')) { + return $this->response->setJSON([ + 'update' => 0, + 'error_text' => 'File table not found', + 'csrfName' => csrf_token(), + 'csrfHash' => csrf_hash(), + ]); + } + + $row = $this->db->table('file_upload_data') + ->select('id,opd_id,full_path,insert_date') + ->where('id', $fileId) + ->get(1) + ->getRowArray(); + + if (empty($row)) { + return $this->response->setJSON([ + 'update' => 0, + 'error_text' => 'File not found', + 'csrfName' => csrf_token(), + 'csrfHash' => csrf_hash(), + ]); + } + + $insertDate = (string) ($row['insert_date'] ?? ''); + $isToday = $insertDate !== '' && date('Y-m-d', strtotime($insertDate)) === date('Y-m-d'); + if (! $isToday) { + return $this->response->setJSON([ + 'update' => 0, + 'error_text' => 'Delete allowed only for current date uploads', + 'csrfName' => csrf_token(), + 'csrfHash' => csrf_hash(), + ]); + } + + $fullPath = (string) ($row['full_path'] ?? ''); + if ($fullPath !== '' && is_file($fullPath)) { + @unlink($fullPath); + } + + $this->db->table('file_upload_data')->where('id', (int) $fileId)->delete(); + + return $this->response->setJSON([ + 'update' => 1, + 'opd_id' => (int) ($row['opd_id'] ?? 0), + 'error_text' => 'Scan deleted', + 'csrfName' => csrf_token(), + 'csrfHash' => csrf_hash(), + ]); + } + private function insertOpdFileUploadRecord(string $fullPath, string $publicPath, int $pid, int $opdid, int $caseId, string $uploadBy, int $uploadById, string $mimeType = 'image/jpeg', int $isImageFile = 1, string $fileExt = '.jpg'): int { if (!$this->db->tableExists('file_upload_data')) { diff --git a/app/Views/billing/opd_appointment_list.php b/app/Views/billing/opd_appointment_list.php index beda09a..0559a90 100644 --- a/app/Views/billing/opd_appointment_list.php +++ b/app/Views/billing/opd_appointment_list.php @@ -197,6 +197,58 @@ }); }); + $(document).off('click.opdscanlistopen', '.btn-opd-scan-list').on('click.opdscanlistopen', '.btn-opd-scan-list', function() { + // Open modal with slide-by-slide scan document list for this OPD. + var opdid = parseInt($(this).data('opdid') || '0', 10); + if (!opdid || !modalObj) { + return; + } + + $('#testentryLabel').text('Scan Document List'); + $('#testentry-bodyc').html('
Loading...
'); + modalObj.show(); + + $.post('/Opd/opd_file_list/' + opdid, {}, function(html) { + $('#testentry-bodyc').html(html || '
No scan documents found.
'); + }).fail(function() { + $('#testentry-bodyc').html('
Unable to load scan document list.
'); + }); + }); + + window.deleteOpdScanFromList = function(fileId, opdid) { + // Delete only allowed for current-date uploads; server enforces this rule. + if (!fileId || !opdid) { + return; + } + if (!window.confirm('Delete this scan document?')) { + return; + } + + var csrf = getCsrfPair(); + var payload = {}; + payload[csrf.name] = csrf.value; + + $.post('/Opd/opd_file_delete/' + fileId, payload, function(resp) { + if (resp && resp.csrfName && resp.csrfHash) { + updateCsrf(resp); + } + + if (!resp || parseInt(resp.update || 0, 10) !== 1) { + var msg = (resp && resp.error_text) ? resp.error_text : 'Unable to delete document.'; + $('#testentry-bodyc').prepend('
' + $('
').text(msg).html() + '
'); + return; + } + + $.post('/Opd/opd_file_list/' + opdid, {}, function(html) { + $('#testentry-bodyc').html(html || '
No scan documents found.
'); + }).fail(function() { + $('#testentry-bodyc').prepend('
Unable to refresh scan list.
'); + }); + }, 'json').fail(function() { + $('#testentry-bodyc').prepend('
Unable to delete document.
'); + }); + }; + $(document).off('click.opdstatus', '.btn-opd-status').on('click.opdstatus', '.btn-opd-status', function() { var opdId = $(this).data('opd-id'); var opdStatus = $(this).data('opd-status'); diff --git a/app/Views/billing/opd_appointment_list_table.php b/app/Views/billing/opd_appointment_list_table.php index 5a4f8d0..2e7d87e 100644 --- a/app/Views/billing/opd_appointment_list_table.php +++ b/app/Views/billing/opd_appointment_list_table.php @@ -44,9 +44,9 @@ if ($authUser) { - - Profile - + + +
+ + + + + + +