From da82ec84d1a612c3b6948ae159a70fbe9d09227d Mon Sep 17 00:00:00 2001 From: DevSoftTech Server Date: Wed, 23 Sep 2026 08:28:02 +0530 Subject: [PATCH] Prevent duplicate final diagnosis submissions and add edit/update capability in IPD discharge --- app/Controllers/Ipd_discharge.php | 139 +++++++++--- app/Views/billing/ipd/discharge_create.php | 236 ++++++++++++++++++--- 2 files changed, 323 insertions(+), 52 deletions(-) diff --git a/app/Controllers/Ipd_discharge.php b/app/Controllers/Ipd_discharge.php index 66e6e6f..89548eb 100644 --- a/app/Controllers/Ipd_discharge.php +++ b/app/Controllers/Ipd_discharge.php @@ -6633,30 +6633,41 @@ class Ipd_discharge extends BaseController $snomedTerm = trim((string) ($this->request->getPost('new_diagnosis_snomed_term') ?? '')); $diagnosisRemarkText = trim((string) ($this->request->getPost('diagnosis_remark') ?? '')); if ($name !== '' && $this->tableHasColumns('ipd_discharge_diagnosis', ['ipd_id', 'comp_report'])) { - $insert = [ - 'ipd_id' => $ipdId, - 'comp_code' => $masterCode, - 'comp_report' => $name, - 'comp_remark' => $remark, - 'update_by' => $userLabel, - ]; - if ($this->db->fieldExists('snomed_concept_id', 'ipd_discharge_diagnosis')) { - $insert['snomed_concept_id'] = $snomedConceptId; + // Prevent duplicate row insertion on slow networks or multiple clicks + $existing = $this->db->table('ipd_discharge_diagnosis') + ->where('ipd_id', $ipdId) + ->where('LOWER(TRIM(comp_report))', strtolower($name)) + ->countAllResults(); + if ($existing > 0) { + $savedAny = true; + $notice = 'Diagnosis is already in the list.'; + $noticeType = 'info'; + } else { + $insert = [ + 'ipd_id' => $ipdId, + 'comp_code' => $masterCode, + 'comp_report' => $name, + 'comp_remark' => $remark, + 'update_by' => $userLabel, + ]; + if ($this->db->fieldExists('snomed_concept_id', 'ipd_discharge_diagnosis')) { + $insert['snomed_concept_id'] = $snomedConceptId; + } + if ($this->db->fieldExists('snomed_term', 'ipd_discharge_diagnosis')) { + $insert['snomed_term'] = $snomedTerm; + } + if ($this->db->fieldExists('snomed_source', 'ipd_discharge_diagnosis')) { + $insert['snomed_source'] = $snomedConceptId !== '' ? 'disease_master' : ''; + } + if ($this->db->fieldExists('order_id', 'ipd_discharge_diagnosis')) { + $insert['order_id'] = 0; + } + $savedAny = (bool) $this->db->table('ipd_discharge_diagnosis')->insert($insert); + $notice = $savedAny ? 'Diagnosis row added.' : 'Unable to add diagnosis row.'; + $noticeType = $savedAny ? 'success' : 'warning'; } - if ($this->db->fieldExists('snomed_term', 'ipd_discharge_diagnosis')) { - $insert['snomed_term'] = $snomedTerm; - } - if ($this->db->fieldExists('snomed_source', 'ipd_discharge_diagnosis')) { - $insert['snomed_source'] = $snomedConceptId !== '' ? 'disease_master' : ''; - } - if ($this->db->fieldExists('order_id', 'ipd_discharge_diagnosis')) { - $insert['order_id'] = 0; - } - $savedAny = (bool) $this->db->table('ipd_discharge_diagnosis')->insert($insert); - $notice = $savedAny ? 'Diagnosis row added.' : 'Unable to add diagnosis row.'; - $noticeType = $savedAny ? 'success' : 'warning'; - if ($this->tableHasColumns('ipd_discharge_diagnosis_remark', ['ipd_id'])) { + if ($this->tableHasColumns('ipd_discharge_diagnosis_remark', ['ipd_id']) && $diagnosisRemarkText !== '') { $savedAny = $this->upsertByIpd('ipd_discharge_diagnosis_remark', $ipdId, [ 'comp_report' => '', 'comp_remark' => $diagnosisRemarkText, @@ -6669,6 +6680,64 @@ class Ipd_discharge extends BaseController : 'Diagnosis table/columns are missing in database.'; $noticeType = 'warning'; } + } elseif ($action === 'update_diagnosis') { + $diagId = (int) ($this->request->getPost('diagnosis_row_id') ?? $this->request->getPost('diagnosis_edit_id') ?? 0); + $name = trim((string) ($this->request->getPost('new_diagnosis_name') ?? '')); + $remark = trim((string) ($this->request->getPost('new_diagnosis_remark') ?? '')); + $masterCode = max(0, (int) ($this->request->getPost('new_diagnosis_master_code') ?? 0)); + $snomedConceptId = trim((string) ($this->request->getPost('new_diagnosis_snomed_concept_id') ?? '')); + $snomedTerm = trim((string) ($this->request->getPost('new_diagnosis_snomed_term') ?? '')); + $diagnosisRemarkText = trim((string) ($this->request->getPost('diagnosis_remark') ?? '')); + + if ($diagId > 0 && $name !== '' && $this->tableHasColumns('ipd_discharge_diagnosis', ['id', 'ipd_id', 'comp_report'])) { + $dupCount = $this->db->table('ipd_discharge_diagnosis') + ->where('ipd_id', $ipdId) + ->where('id !=', $diagId) + ->where('LOWER(TRIM(comp_report))', strtolower($name)) + ->countAllResults(); + if ($dupCount > 0) { + $savedAny = false; + $notice = 'Another row with this diagnosis name already exists.'; + $noticeType = 'warning'; + } else { + $update = [ + 'comp_report' => $name, + 'comp_remark' => $remark, + 'update_by' => $userLabel, + ]; + if ($masterCode > 0) { + $update['comp_code'] = $masterCode; + } + if ($this->db->fieldExists('snomed_concept_id', 'ipd_discharge_diagnosis')) { + $update['snomed_concept_id'] = $snomedConceptId; + } + if ($this->db->fieldExists('snomed_term', 'ipd_discharge_diagnosis')) { + $update['snomed_term'] = $snomedTerm; + } + if ($this->db->fieldExists('snomed_source', 'ipd_discharge_diagnosis')) { + $update['snomed_source'] = $snomedConceptId !== '' ? 'disease_master' : ''; + } + $savedAny = (bool) $this->db->table('ipd_discharge_diagnosis') + ->where('id', $diagId) + ->where('ipd_id', $ipdId) + ->update($update); + $notice = $savedAny ? 'Diagnosis row updated.' : 'Unable to update diagnosis row.'; + $noticeType = $savedAny ? 'success' : 'warning'; + } + + if ($this->tableHasColumns('ipd_discharge_diagnosis_remark', ['ipd_id']) && $diagnosisRemarkText !== '') { + $savedAny = $this->upsertByIpd('ipd_discharge_diagnosis_remark', $ipdId, [ + 'comp_report' => '', + 'comp_remark' => $diagnosisRemarkText, + 'update_by' => $userLabel, + ]) || $savedAny; + } + } else { + $notice = $name === '' + ? 'Enter diagnosis before saving.' + : 'Select a valid diagnosis row to update.'; + $noticeType = 'warning'; + } } elseif ($action === 'remove_diagnosis') { $removeId = (int) ($this->request->getPost('diagnosis_remove_id') ?? 0); $diagnosisRemarkText = trim((string) ($this->request->getPost('diagnosis_remark') ?? '')); @@ -6693,11 +6762,21 @@ class Ipd_discharge extends BaseController } } - if (($this->request->isAJAX() || $this->request->getPost('ajax_mode') === 'json') && in_array($action, ['add_complaint', 'update_complaint', 'remove_complaint', 'add_surgery', 'remove_surgery', 'add_procedure', 'remove_procedure', 'add_diagnosis', 'remove_diagnosis'], true)) { + if (($this->request->isAJAX() || $this->request->getPost('ajax_mode') === 'json') && in_array($action, ['add_complaint', 'update_complaint', 'remove_complaint', 'add_surgery', 'remove_surgery', 'add_procedure', 'remove_procedure', 'add_diagnosis', 'update_diagnosis', 'remove_diagnosis'], true)) { $complaintRows = $this->getDischargeComplaintRows($ipdId); $surgeryRows = $this->byIpdRows('ipd_discharge_surgery', ['id', 'surgery_name', 'surgery_date', 'surgery_remark'], 'id ASC', $ipdId); $procedureRows = $this->byIpdRows('ipd_discharge_procedure', ['id', 'procedure_name', 'procedure_date', 'procedure_remark'], 'id ASC', $ipdId); - $diagnosisRows = $this->byIpdRows('ipd_discharge_diagnosis', ['id', 'comp_report', 'comp_remark'], 'id ASC', $ipdId); + $diagCols = ['id', 'comp_report', 'comp_remark']; + if ($this->db->fieldExists('comp_code', 'ipd_discharge_diagnosis')) { + $diagCols[] = 'comp_code'; + } + if ($this->db->fieldExists('snomed_concept_id', 'ipd_discharge_diagnosis')) { + $diagCols[] = 'snomed_concept_id'; + } + if ($this->db->fieldExists('snomed_term', 'ipd_discharge_diagnosis')) { + $diagCols[] = 'snomed_term'; + } + $diagnosisRows = $this->byIpdRows('ipd_discharge_diagnosis', $diagCols, 'id ASC', $ipdId); $courseRows = $this->byIpdRows('ipd_discharge_course', ['id', 'comp_report', 'comp_remark'], 'id ASC', $ipdId); return $this->response->setJSON([ 'update' => ($savedAny ?? false) ? 1 : 0, @@ -7884,7 +7963,17 @@ class Ipd_discharge extends BaseController $complaintRows = $this->byIpdRows('ipd_discharge_complaint', ['id', 'comp_report', 'comp_remark'], 'id ASC', $ipdId); $surgeryRows = $this->byIpdRows('ipd_discharge_surgery', ['id', 'surgery_name', 'surgery_date', 'surgery_remark'], 'id ASC', $ipdId); $procedureRows = $this->byIpdRows('ipd_discharge_procedure', ['id', 'procedure_name', 'procedure_date', 'procedure_remark'], 'id ASC', $ipdId); - $diagnosisRows = $this->byIpdRows('ipd_discharge_diagnosis', ['id', 'comp_report', 'comp_remark'], 'id ASC', $ipdId); + $diagCols = ['id', 'comp_report', 'comp_remark']; + if ($this->db->fieldExists('comp_code', 'ipd_discharge_diagnosis')) { + $diagCols[] = 'comp_code'; + } + if ($this->db->fieldExists('snomed_concept_id', 'ipd_discharge_diagnosis')) { + $diagCols[] = 'snomed_concept_id'; + } + if ($this->db->fieldExists('snomed_term', 'ipd_discharge_diagnosis')) { + $diagCols[] = 'snomed_term'; + } + $diagnosisRows = $this->byIpdRows('ipd_discharge_diagnosis', $diagCols, 'id ASC', $ipdId); $courseRows = $this->byIpdRows('ipd_discharge_course', ['id', 'comp_report', 'comp_remark'], 'id ASC', $ipdId); $drugRows = $this->byIpdRows('ipd_discharge_drug', ['id', 'drug_name', 'drug_dose', 'drug_day'], 'id ASC', $ipdId); $legacyDrugRows = $this->byIpdRows('ipd_discharge_prescrption_prescribed', ['id', 'med_name', 'med_salt', 'med_type', 'dosage', 'dosage_when', 'dosage_freq', 'no_of_days', 'qty', 'remark'], 'id ASC', $ipdId); diff --git a/app/Views/billing/ipd/discharge_create.php b/app/Views/billing/ipd/discharge_create.php index e5da3cc..4af6237 100644 --- a/app/Views/billing/ipd/discharge_create.php +++ b/app/Views/billing/ipd/discharge_create.php @@ -1145,7 +1145,7 @@ $historyFields = [ Diagnosis Remark - Action + Action @@ -1153,27 +1153,49 @@ $historyFields = [ No diagnosis rows. - - - - - + + + + + +
+ + +
+ + -
+
-
-
+
+
+ + +
Type a diagnosis name, SNOMED term, or ICD code to search master data.
@@ -4696,33 +4718,150 @@ $historyFields = [ initSurgeryMasterCrud(form); } + var isDiagnosisSubmitting = false; + + function resetFinalDiagnosisEditMode() { + $('#diagnosis_edit_id').val('0'); + $('#new_diagnosis_name').val(''); + $('#new_diagnosis_remark').val(''); + $('#new_diagnosis_master_code').val('0'); + $('#new_diagnosis_snomed_concept_id').val(''); + $('#new_diagnosis_snomed_term').val(''); + $('#discharge_final_diagnosis_tbody tr').removeClass('table-warning'); + $('#btn_add_final_diagnosis_row').html('+ADD').removeClass('btn-warning fw-bold').addClass('btn-primary'); + $('#btn_cancel_final_diagnosis_edit').hide(); + } + function renderFinalDiagnosisRows(rows) { var $tbody = $('#discharge_final_diagnosis_tbody'); if (!rows || !rows.length) { $tbody.html('No diagnosis rows.'); return; } + var currentEditId = parseInt($('#diagnosis_edit_id').val() || '0', 10); var html = ''; rows.forEach(function(row) { var id = parseInt(row.id || '0', 10); - var name = $('
').text(row.comp_report || '').html(); - var remark = $('
').text(row.comp_remark || '').html(); - html += '' - + '' + name + '' - + '' + remark + '' - + '' + var name = (row.comp_report || '').toString(); + var remark = (row.comp_remark || '').toString(); + var code = parseInt(row.comp_code || '0', 10); + var snomedId = (row.snomed_concept_id || '').toString(); + var snomedTerm = (row.snomed_term || '').toString(); + + var safeName = $('
').text(name).html(); + var safeRemark = $('
').text(remark).html(); + var safeAttr = function(val) { + return String(val || '') + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/'/g, ''') + .replace(//g, '>'); + }; + + var trClass = (currentEditId > 0 && currentEditId === id) ? ' class="table-warning"' : ''; + + html += '' + + '' + safeName + '' + + '' + safeRemark + '' + + '' + + '
' + + '' + + '' + + '
' + + '' + ''; }); $tbody.html(html); } + $(document).on('click', '.btn-edit-final-diagnosis-row', function() { + var $btn = $(this); + var id = parseInt($btn.data('id') || '0', 10); + if (id <= 0) return; + + var name = String($btn.attr('data-name') || $btn.data('name') || '').trim(); + var remark = String($btn.attr('data-remark') || $btn.data('remark') || '').trim(); + var code = parseInt($btn.attr('data-code') || $btn.data('code') || '0', 10); + var snomedId = String($btn.attr('data-snomed-id') || $btn.data('snomed-id') || '').trim(); + var snomedTerm = String($btn.attr('data-snomed-term') || $btn.data('snomed-term') || '').trim(); + + if (!name) { + var $tr = $btn.closest('tr'); + name = $tr.find('.diag-report-text').text().trim(); + remark = $tr.find('.diag-remark-text').text().trim(); + } + + $('#diagnosis_edit_id').val(id); + $('#new_diagnosis_name').val(name); + $('#new_diagnosis_remark').val(remark); + $('#new_diagnosis_master_code').val(code); + $('#new_diagnosis_snomed_concept_id').val(snomedId); + $('#new_diagnosis_snomed_term').val(snomedTerm); + + $('#discharge_final_diagnosis_tbody tr').removeClass('table-warning'); + $('#diag_row_' + id).addClass('table-warning'); + + $('#btn_add_final_diagnosis_row') + .html('Update') + .removeClass('btn-primary') + .addClass('btn-warning fw-bold'); + $('#btn_cancel_final_diagnosis_edit').show(); + + setSectionStatus('discharge_diagnosis_status', 'Editing diagnosis: update details and click Update.', 'info'); + $('#new_diagnosis_name').trigger('focus'); + }); + + $(document).on('click', '#btn_cancel_final_diagnosis_edit', function() { + resetFinalDiagnosisEditMode(); + setSectionStatus('discharge_diagnosis_status', 'Edit cancelled.', 'info'); + }); + $(document).on('click', '#btn_add_final_diagnosis_row', function() { + if (isDiagnosisSubmitting) { + return false; + } + var form = getDischargeForm(); var name = ($('#new_diagnosis_name').val() || '').toString().trim(); if (!name) { setSectionStatus('discharge_diagnosis_status', 'Enter diagnosis before adding.', 'error'); - return; + $('#new_diagnosis_name').trigger('focus'); + return false; } + + var editId = parseInt($('#diagnosis_edit_id').val() || '0', 10); + var isEdit = (editId > 0); + + // Client-side duplicate check to prevent duplicates on slow network + var normalizedName = name.toLowerCase(); + var isDuplicate = false; + $('#discharge_final_diagnosis_tbody tr').each(function() { + var rowId = parseInt($(this).find('.btn-edit-final-diagnosis-row').data('id') || $(this).find('.btn-remove-final-diagnosis-row').data('id') || 0, 10); + if (isEdit && rowId === editId) { + return; // Skip self when updating + } + var existingName = $(this).find('.diag-report-text').text().trim().toLowerCase(); + if (existingName && existingName === normalizedName) { + isDuplicate = true; + return false; + } + }); + + if (isDuplicate) { + var dupMsg = isEdit + ? 'Another row with this diagnosis name already exists.' + : 'Diagnosis "' + name + '" is already in the list.'; + setSectionStatus('discharge_diagnosis_status', dupMsg, 'warning'); + return false; + } + var remark = ($('#new_diagnosis_remark').val() || '').toString().trim(); var masterCode = parseInt($('#new_diagnosis_master_code').val() || '0', 10); var snomedConceptId = ($('#new_diagnosis_snomed_concept_id').val() || '').toString().trim(); @@ -4731,7 +4870,9 @@ $historyFields = [ var csrf = getCsrfPair(form); var payload = { - action: 'add_diagnosis', + action: isEdit ? 'update_diagnosis' : 'add_diagnosis', + diagnosis_row_id: editId, + diagnosis_edit_id: editId, new_diagnosis_name: name, new_diagnosis_remark: remark, new_diagnosis_master_code: masterCode, @@ -4741,26 +4882,48 @@ $historyFields = [ }; payload[csrf.name] = csrf.value; + // Lock UI against slow network multiple submissions + isDiagnosisSubmitting = true; + var $btn = $('#btn_add_final_diagnosis_row'); + var originalHtml = isEdit ? 'Update' : '+ADD'; + $btn.prop('disabled', true).html('' + (isEdit ? 'Updating...' : 'Adding...')); + $('#btn_cancel_final_diagnosis_edit').prop('disabled', true); + $('#new_diagnosis_name, #new_diagnosis_remark').prop('readonly', true); + $.post($(form).attr('action') || window.location.href, payload, function(data) { updateFormCsrf(form, data); if (data && data.diagnosisRows) { renderFinalDiagnosisRows(data.diagnosisRows); } - $('#new_diagnosis_name').val(''); - $('#new_diagnosis_remark').val(''); - $('#new_diagnosis_master_code').val('0'); - $('#new_diagnosis_snomed_concept_id').val(''); - $('#new_diagnosis_snomed_term').val(''); - setSectionStatus('discharge_diagnosis_status', (data && data.notice) ? data.notice : 'Diagnosis row added.', 'success'); + resetFinalDiagnosisEditMode(); + var notice = (data && data.notice) ? data.notice : (isEdit ? 'Diagnosis row updated.' : 'Diagnosis row added.'); + var noticeType = (data && data.noticeType) ? data.noticeType : (data && data.update ? 'success' : 'warning'); + setSectionStatus('discharge_diagnosis_status', notice, noticeType); + $('#new_diagnosis_name').trigger('focus'); }, 'json').fail(function() { - setSectionStatus('discharge_diagnosis_status', 'Unable to add diagnosis row.', 'error'); + setSectionStatus('discharge_diagnosis_status', isEdit ? 'Unable to update diagnosis row.' : 'Unable to add diagnosis row.', 'error'); + }).always(function() { + isDiagnosisSubmitting = false; + $btn.prop('disabled', false).html(originalHtml); + $('#btn_cancel_final_diagnosis_edit').prop('disabled', false); + $('#new_diagnosis_name, #new_diagnosis_remark').prop('readonly', false); + if ($('#diagnosis_edit_id').val() === '0') { + $btn.html('+ADD').removeClass('btn-warning fw-bold').addClass('btn-primary'); + } }); }); $(document).on('click', '.btn-remove-final-diagnosis-row', function() { + var $btn = $(this); + if ($btn.prop('disabled')) return; var form = getDischargeForm(); - var id = parseInt($(this).data('id') || '0', 10); + var id = parseInt($btn.data('id') || '0', 10); if (id <= 0) return; + + if (parseInt($('#diagnosis_edit_id').val() || '0', 10) === id) { + resetFinalDiagnosisEditMode(); + } + var diagnosisRemarkText = ($('#diagnosis_remark').val() || '').toString().trim(); var csrf = getCsrfPair(form); var payload = { @@ -4770,13 +4933,27 @@ $historyFields = [ }; payload[csrf.name] = csrf.value; + $btn.prop('disabled', true).text('Removing...'); + $.post($(form).attr('action') || window.location.href, payload, function(data) { updateFormCsrf(form, data); if (data && data.diagnosisRows) { renderFinalDiagnosisRows(data.diagnosisRows); } - setSectionStatus('discharge_diagnosis_status', (data && data.notice) ? data.notice : 'Diagnosis row removed.', 'success'); - }, 'json'); + var notice = (data && data.notice) ? data.notice : 'Diagnosis row removed.'; + var noticeType = (data && data.noticeType) ? data.noticeType : 'success'; + setSectionStatus('discharge_diagnosis_status', notice, noticeType); + }, 'json').fail(function() { + $btn.prop('disabled', false).text('Remove'); + setSectionStatus('discharge_diagnosis_status', 'Unable to remove diagnosis row.', 'error'); + }); + }); + + $(document).on('keydown', '#new_diagnosis_remark', function(e) { + if (e.key === 'Enter') { + e.preventDefault(); + $('#btn_add_final_diagnosis_row').trigger('click'); + } }); function bindDiagnosisIcdLookup(form) { @@ -4875,6 +5052,11 @@ $historyFields = [ if ($dd.is(':visible') && ddIdx >= 0 && ddIdx < $items.length) { e.preventDefault(); $items.eq(ddIdx).trigger('click'); + setTimeout(function() { $('#new_diagnosis_remark').trigger('focus'); }, 50); + } else { + e.preventDefault(); + closeDd(); + $('#new_diagnosis_remark').trigger('focus'); } } else if (e.key === 'Escape') { closeDd();