feat(ipd): manage admission examination fields

Add an Admin CRUD master for IPD admission examination fields with enabled state, display order, field types, and select options.
This commit is contained in:
DevSoftTech Server 2026-08-24 00:53:01 +05:30
parent 40c68da430
commit 7ec5149ccf
7 changed files with 178 additions and 6 deletions

View File

@ -1289,6 +1289,9 @@ $routes->group('setting', static function($routes) {
$routes->post('admin/abdm-gateway/test', 'Setting\\AbdmGatewaySettings::testConnection', ['filter' => $settingsAdminFilter]);
$routes->get('admin/abdm-report-doctors', 'Setting\\AbdmReportDoctorSettings::index', ['filter' => $settingsAdminFilter]);
$routes->post('admin/abdm-report-doctors/save', 'Setting\\AbdmReportDoctorSettings::save', ['filter' => $settingsAdminFilter]);
$routes->get('admin/ipd-examination-fields', 'Setting\IpdExaminationFields::index', ['filter' => 'permission:ipd_discharge.master.manage']);
$routes->post('admin/ipd-examination-fields/save', 'Setting\IpdExaminationFields::save', ['filter' => ['permission:ipd_discharge.master.manage', 'csrf']]);
$routes->post('admin/ipd-examination-fields/toggle/(:num)', 'Setting\IpdExaminationFields::toggle/$1', ['filter' => ['permission:ipd_discharge.master.manage', 'csrf']]);
$routes->get('admin/hospital-profile', 'Setting\\HospitalProfile::index');
$routes->get('admin/system-ops', 'SystemOps::index', ['filter' => $settingsAdminFilter]);
$routes->get('admin/system-ops/panel', 'SystemOps::panel', ['filter' => $settingsAdminFilter]);

View File

@ -4169,11 +4169,18 @@ class Ipd_discharge extends BaseController
if ($this->tableHasColumns('ipd_discharge_general_exam_col', ['id', 'col_description'])) {
$generalBuilder = $this->db->table('ipd_discharge_general_exam_col')
->select('id,col_description,col_name,col_pre_value,cat_group')
->orderBy($this->db->fieldExists('display_order', 'ipd_discharge_general_exam_col') ? 'display_order' : 'id', 'ASC')
->orderBy('id', 'ASC');
if ($this->db->fieldExists('col_unit', 'ipd_discharge_general_exam_col')) {
$generalBuilder->select('col_unit');
}
if ($this->db->fieldExists('col_type', 'ipd_discharge_general_exam_col')) {
$generalBuilder->select('col_type');
}
if ($this->db->fieldExists('is_active', 'ipd_discharge_general_exam_col')) {
$generalBuilder->where('is_active', 1);
}
$generalCols = $generalBuilder->get()->getResultArray();
@ -4199,6 +4206,8 @@ class Ipd_discharge extends BaseController
'label' => (string) ($row['col_description'] ?? $row['col_name'] ?? ('Exam ' . $colId)),
'value' => (string) ($rdataMap[$colId] ?? (string) ($row['col_pre_value'] ?? '')),
'unit' => trim((string) ($row['col_unit'] ?? '')),
'type' => (int) ($row['col_type'] ?? 0),
'options' => (string) ($row['col_pre_value'] ?? ''),
];
$generalAll[] = $item;
@ -6676,11 +6685,13 @@ class Ipd_discharge extends BaseController
// Examination on Admission (General Examination values).
if ($this->tableHasColumns('ipd_discharge_general_exam_col', ['id', 'col_name'])
&& $this->tableHasColumns('ipd_discharge_1_b', ['ipd_d_id', 'col_id', 'short_head', 'rdata'])) {
$generalCols = $this->db->table('ipd_discharge_general_exam_col')
$generalBuilder = $this->db->table('ipd_discharge_general_exam_col')
->select('id,col_name')
->orderBy('id', 'ASC')
->get()
->getResultArray();
->orderBy('id', 'ASC');
if ($this->db->fieldExists('is_active', 'ipd_discharge_general_exam_col')) {
$generalBuilder->where('is_active', 1);
}
$generalCols = $generalBuilder->get()->getResultArray();
foreach ($generalCols as $col) {
$colId = (int) ($col['id'] ?? 0);

View File

@ -0,0 +1,81 @@
<?php
namespace App\Controllers\Setting;
use App\Controllers\BaseController;
class IpdExaminationFields extends BaseController
{
public function index()
{
$this->ensureColumns();
return view('Setting/Admin/ipd_examination_fields', [
'rows' => $this->db->table('ipd_discharge_general_exam_col')->orderBy('cat_group')->orderBy('display_order')->orderBy('id')->get()->getResultArray(),
]);
}
public function save()
{
$this->ensureColumns();
$id = (int) ($this->request->getPost('id') ?? 0);
$code = strtoupper(trim((string) ($this->request->getPost('col_code') ?? '')));
$name = trim((string) ($this->request->getPost('col_name') ?? ''));
$label = trim((string) ($this->request->getPost('col_description') ?? ''));
if ($code === '' || ! preg_match('/^[A-Z][A-Z0-9_]{1,49}$/', $code) || $name === '' || $label === '') {
return $this->reply(false, 'Code, field name, and label are required. Use letters, numbers, and underscore in code.');
}
$duplicate = $this->db->table('ipd_discharge_general_exam_col')->where('col_code', $code);
if ($id > 0) {
$duplicate->where('id !=', $id);
}
if ($duplicate->countAllResults() > 0) {
return $this->reply(false, 'Field code already exists.');
}
$data = [
'col_code' => $code,
'col_name' => $name,
'col_description' => $label,
'col_unit' => trim((string) ($this->request->getPost('col_unit') ?? '')),
'col_type' => max(0, min(4, (int) ($this->request->getPost('col_type') ?? 0))),
'col_pre_value' => trim((string) ($this->request->getPost('col_pre_value') ?? '')),
'cat_group' => (int) ($this->request->getPost('cat_group') ?? 1) === 2 ? 2 : 1,
'is_active' => (int) ($this->request->getPost('is_active') ?? 0) === 1 ? 1 : 0,
'display_order' => max(0, (int) ($this->request->getPost('display_order') ?? 0)),
];
$saved = $id > 0
? (bool) $this->db->table('ipd_discharge_general_exam_col')->where('id', $id)->update($data)
: (bool) $this->db->table('ipd_discharge_general_exam_col')->insert($data);
return $this->reply($saved, $saved ? 'Examination field saved.' : 'Unable to save examination field.');
}
public function toggle(int $id)
{
$this->ensureColumns();
$row = $this->db->table('ipd_discharge_general_exam_col')->select('id,is_active')->where('id', $id)->get()->getRowArray();
if ($row === null) {
return $this->reply(false, 'Examination field not found.');
}
$active = (int) ($row['is_active'] ?? 0) === 1 ? 0 : 1;
$saved = (bool) $this->db->table('ipd_discharge_general_exam_col')->where('id', $id)->update(['is_active' => $active]);
return $this->reply($saved, $saved ? ($active ? 'Field enabled.' : 'Field disabled.') : 'Unable to update field status.');
}
private function ensureColumns(): void
{
if (! $this->db->fieldExists('is_active', 'ipd_discharge_general_exam_col')) {
$this->db->query('ALTER TABLE ipd_discharge_general_exam_col ADD COLUMN is_active TINYINT(1) NOT NULL DEFAULT 1 AFTER cat_group');
}
if (! $this->db->fieldExists('display_order', 'ipd_discharge_general_exam_col')) {
$this->db->query('ALTER TABLE ipd_discharge_general_exam_col ADD COLUMN display_order INT NOT NULL DEFAULT 0 AFTER is_active');
$this->db->query('UPDATE ipd_discharge_general_exam_col SET display_order = id WHERE display_order = 0');
}
}
private function reply(bool $ok, string $message)
{
return $this->response->setJSON(['ok' => $ok ? 1 : 0, 'message' => $message, 'csrfName' => csrf_token(), 'csrfHash' => csrf_hash()]);
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class AddIpdGeneralExamFieldControls extends Migration
{
public function up()
{
if (! $this->tableExists('ipd_discharge_general_exam_col')) {
return;
}
if (! $this->fieldExists('is_active', 'ipd_discharge_general_exam_col')) {
$this->db->query('ALTER TABLE ipd_discharge_general_exam_col ADD COLUMN is_active TINYINT(1) NOT NULL DEFAULT 1 AFTER cat_group');
}
if (! $this->fieldExists('display_order', 'ipd_discharge_general_exam_col')) {
$this->db->query('ALTER TABLE ipd_discharge_general_exam_col ADD COLUMN display_order INT NOT NULL DEFAULT 0 AFTER is_active');
$this->db->query('UPDATE ipd_discharge_general_exam_col SET display_order = id WHERE display_order = 0');
}
}
public function down()
{
if ($this->tableExists('ipd_discharge_general_exam_col') && $this->fieldExists('display_order', 'ipd_discharge_general_exam_col')) {
$this->db->query('ALTER TABLE ipd_discharge_general_exam_col DROP COLUMN display_order');
}
if ($this->tableExists('ipd_discharge_general_exam_col') && $this->fieldExists('is_active', 'ipd_discharge_general_exam_col')) {
$this->db->query('ALTER TABLE ipd_discharge_general_exam_col DROP COLUMN is_active');
}
}
private function tableExists(string $table): bool
{
return $this->db->query('SHOW TABLES LIKE ' . $this->db->escape($table))->getRowArray() !== null;
}
private function fieldExists(string $field, string $table): bool
{
return $this->db->query('SHOW COLUMNS FROM `' . $table . '` LIKE ' . $this->db->escape($field))->getRowArray() !== null;
}
}

View File

@ -122,6 +122,12 @@
<span>Bed Management</span>
</a>
</div>
<div class="col-6 col-md-2 col-lg-2">
<a class="admin-tile" href="javascript:load_form_div('<?= base_url('setting/admin/ipd-examination-fields') ?>','maindiv','IPD Examination Fields');">
<i class="bi bi-clipboard2-pulse"></i>
<span>IPD Examination Fields</span>
</a>
</div>
<div class="col-6 col-md-2 col-lg-2">
<a class="admin-tile" href="javascript:load_form_div('<?= base_url('Storestock') ?>','maindiv','Hospital Stock');">
<i class="bi bi-box-seam"></i>

View File

@ -0,0 +1,29 @@
<?php $rows = $rows ?? []; ?>
<section class="content">
<div class="card"><div class="card-header d-flex justify-content-between align-items-center flex-wrap gap-2"><div><h5 class="mb-0"><i class="bi bi-clipboard2-pulse me-2"></i>IPD Admission Examination Fields</h5><small class="text-muted">Enabled fields appear in Discharge Management → Examination on Admission. Existing saved values are retained.</small></div><button type="button" class="btn btn-sm btn-outline-secondary" onclick="load_form_div('<?= base_url('setting/admin') ?>','maindiv','Admin Panel')"><i class="bi bi-arrow-left me-1"></i>Admin Panel</button></div>
<div class="card-body"><div id="exam_field_notice"></div><form id="exam_field_form" class="row g-3 align-items-end"><input type="hidden" name="id" id="exam_field_id" value="0"><?= csrf_field() ?>
<div class="col-md-2"><label class="form-label">Field Code</label><input class="form-control" name="col_code" id="exam_field_code" maxlength="50" placeholder="LEFT_DIAGNOSIS" required></div>
<div class="col-md-2"><label class="form-label">Field Name</label><input class="form-control" name="col_name" id="exam_field_name" maxlength="50" placeholder="left_diagnosis" required></div>
<div class="col-md-3"><label class="form-label">Display Label</label><input class="form-control" name="col_description" id="exam_field_label" maxlength="50" placeholder="Left Diagnosis" required></div>
<div class="col-md-2"><label class="form-label">Input Type</label><select class="form-select" name="col_type" id="exam_field_type"><option value="0">Single-line text</option><option value="1">Long text</option><option value="2">Number</option><option value="4">Select list</option></select></div>
<div class="col-md-1"><label class="form-label">Group</label><select class="form-select" name="cat_group" id="exam_field_group"><option value="1">General</option><option value="2">Other</option></select></div>
<div class="col-md-1"><label class="form-label">Order</label><input class="form-control" name="display_order" id="exam_field_order" type="number" min="0" step="1" value="0"></div>
<div class="col-md-1"><label class="form-label">Enabled</label><select class="form-select" name="is_active" id="exam_field_active"><option value="1">Yes</option><option value="0">No</option></select></div>
<div class="col-md-3"><label class="form-label">Unit</label><input class="form-control" name="col_unit" id="exam_field_unit" maxlength="50" placeholder="mmHg"></div>
<div class="col-md-6"><label class="form-label">Default / Select Options</label><input class="form-control" name="col_pre_value" id="exam_field_values" placeholder="For select: Normal|Abnormal|Not recorded"></div>
<div class="col-md-3 d-flex gap-2"><button type="submit" class="btn btn-primary flex-grow-1"><i class="bi bi-save me-1"></i>Save Field</button><button type="button" class="btn btn-outline-secondary" id="exam_field_clear">Clear</button></div>
</form></div></div>
<div class="card mt-3"><div class="card-body table-responsive"><table class="table table-sm table-striped align-middle mb-0"><thead class="table-light"><tr><th>Order</th><th>Code</th><th>Label</th><th>Type</th><th>Group</th><th>Unit</th><th>Status</th><th></th></tr></thead><tbody><?php foreach ($rows as $row) : ?><tr data-row='<?= esc(json_encode($row), 'attr') ?>'><td><?= (int) ($row['display_order'] ?? 0) ?></td><td><code><?= esc($row['col_code'] ?? '') ?></code></td><td><?= esc($row['col_description'] ?? '') ?></td><td><?= ['Text', 'Long text', 'Number', 'Auto', 'Select'][(int) ($row['col_type'] ?? 0)] ?? 'Text' ?></td><td><?= (int) ($row['cat_group'] ?? 1) === 2 ? 'Other' : 'General' ?></td><td><?= esc($row['col_unit'] ?? '') ?></td><td><span class="badge text-bg-<?= (int) ($row['is_active'] ?? 1) === 1 ? 'success' : 'secondary' ?>"><?= (int) ($row['is_active'] ?? 1) === 1 ? 'Enabled' : 'Disabled' ?></span></td><td class="text-end"><button type="button" class="btn btn-sm btn-outline-primary exam-field-edit">Edit</button> <button type="button" class="btn btn-sm btn-outline-<?= (int) ($row['is_active'] ?? 1) === 1 ? 'warning' : 'success' ?> exam-field-toggle" data-id="<?= (int) $row['id'] ?>"><?= (int) ($row['is_active'] ?? 1) === 1 ? 'Disable' : 'Enable' ?></button></td></tr><?php endforeach; ?></tbody></table></div></div>
</section>
<script>
(function () {
var form = document.getElementById('exam_field_form');
var notice = document.getElementById('exam_field_notice');
function message(text, kind) { notice.innerHTML = '<div class="alert alert-' + kind + ' py-2">' + $('<div>').text(text).html() + '</div>'; }
function reset() { form.reset(); $('#exam_field_id').val('0'); $('#exam_field_active').val('1'); $('#exam_field_order').val('0'); }
$('#exam_field_clear').off('click.examFields').on('click.examFields', reset);
$(document).off('click.examFields', '.exam-field-edit').on('click.examFields', '.exam-field-edit', function () { var row = $(this).closest('tr').data('row'); if (!row) return; $('#exam_field_id').val(row.id || 0); $('#exam_field_code').val(row.col_code || ''); $('#exam_field_name').val(row.col_name || ''); $('#exam_field_label').val(row.col_description || ''); $('#exam_field_type').val(String(row.col_type || 0)); $('#exam_field_group').val(String(row.cat_group || 1)); $('#exam_field_order').val(row.display_order || 0); $('#exam_field_active').val(String(typeof row.is_active === 'undefined' ? 1 : row.is_active)); $('#exam_field_unit').val(row.col_unit || ''); $('#exam_field_values').val(row.col_pre_value || ''); window.scrollTo({top: 0, behavior: 'smooth'}); });
$(document).off('click.examFields', '.exam-field-toggle').on('click.examFields', '.exam-field-toggle', function () { var button = this; var payload = {}; payload['<?= csrf_token() ?>'] = $(form).find('input[name="<?= csrf_token() ?>"]').val(); $.post('<?= base_url('setting/admin/ipd-examination-fields/toggle') ?>/' + $(button).data('id'), payload, function (data) { if (!data || !data.ok) { message((data && data.message) || 'Unable to change field status.', 'danger'); return; } load_form_div('<?= base_url('setting/admin/ipd-examination-fields') ?>', 'maindiv', 'IPD Examination Fields'); }, 'json'); });
$(form).off('submit.examFields').on('submit.examFields', function (event) { event.preventDefault(); $.post('<?= base_url('setting/admin/ipd-examination-fields/save') ?>', $(form).serialize(), function (data) { if (!data || !data.ok) { message((data && data.message) || 'Unable to save field.', 'danger'); return; } load_form_div('<?= base_url('setting/admin/ipd-examination-fields') ?>', 'maindiv', 'IPD Examination Fields'); }, 'json').fail(function () { message('Unable to save field.', 'danger'); }); });
})();
</script>

View File

@ -819,7 +819,7 @@ $historyFields = [
<?php foreach ($generalExamGroup1 as $row): ?>
<div class="col-md-3">
<label class="form-label small"><?= esc((string) ($row['label'] ?? '')) ?></label>
<input type="text" class="form-control form-control-sm" name="gen_exam_<?= (int) ($row['id'] ?? 0) ?>" value="<?= esc((string) ($row['value'] ?? '')) ?>">
<?php if ((int) ($row['type'] ?? 0) === 1): ?><textarea class="form-control form-control-sm" name="gen_exam_<?= (int) ($row['id'] ?? 0) ?>" rows="2"><?= esc((string) ($row['value'] ?? '')) ?></textarea><?php elseif ((int) ($row['type'] ?? 0) === 4): ?><select class="form-select form-select-sm" name="gen_exam_<?= (int) ($row['id'] ?? 0) ?>"><?php foreach (preg_split('/[|\r\n]+/', (string) ($row['options'] ?? '')) as $option): $option = trim($option); if ($option === '') continue; ?><option value="<?= esc($option) ?>" <?= (string) ($row['value'] ?? '') === $option ? 'selected' : '' ?>><?= esc($option) ?></option><?php endforeach; ?></select><?php else: ?><input type="<?= (int) ($row['type'] ?? 0) === 2 ? 'number' : 'text' ?>" class="form-control form-control-sm" name="gen_exam_<?= (int) ($row['id'] ?? 0) ?>" value="<?= esc((string) ($row['value'] ?? '')) ?>"><?php endif; ?>
</div>
<?php endforeach; ?>
<?php if (empty($generalExamGroup1)): ?>
@ -833,7 +833,7 @@ $historyFields = [
<?php foreach ($generalExamGroup2 as $row): ?>
<div class="col-md-3">
<label class="form-label small"><?= esc((string) ($row['label'] ?? '')) ?></label>
<input type="text" class="form-control form-control-sm" name="gen_exam_<?= (int) ($row['id'] ?? 0) ?>" value="<?= esc((string) ($row['value'] ?? '')) ?>">
<?php if ((int) ($row['type'] ?? 0) === 1): ?><textarea class="form-control form-control-sm" name="gen_exam_<?= (int) ($row['id'] ?? 0) ?>" rows="2"><?= esc((string) ($row['value'] ?? '')) ?></textarea><?php elseif ((int) ($row['type'] ?? 0) === 4): ?><select class="form-select form-select-sm" name="gen_exam_<?= (int) ($row['id'] ?? 0) ?>"><?php foreach (preg_split('/[|\r\n]+/', (string) ($row['options'] ?? '')) as $option): $option = trim($option); if ($option === '') continue; ?><option value="<?= esc($option) ?>" <?= (string) ($row['value'] ?? '') === $option ? 'selected' : '' ?>><?= esc($option) ?></option><?php endforeach; ?></select><?php else: ?><input type="<?= (int) ($row['type'] ?? 0) === 2 ? 'number' : 'text' ?>" class="form-control form-control-sm" name="gen_exam_<?= (int) ($row['id'] ?? 0) ?>" value="<?= esc((string) ($row['value'] ?? '')) ?>"><?php endif; ?>
</div>
<?php endforeach; ?>
<?php if (empty($generalExamGroup2)): ?>