feat(user-management): enhance user management, permission handling, and charges invoice workflows

- Add UserProfileModel and migration for user profile data
- Enhance User Management views, controller, and doctor/nurse search integration
- Refactor handleAuthError to separate 401 session expiry from 403 access restriction
- Apply granular permission checks to sidebar navigation links
- Open Bed Status view for all authenticated users
- Support GET and POST on charges item routes and exempt billing charges from CSRF desync
This commit is contained in:
DevSoftTech Server 2026-10-14 20:08:10 +05:30
parent 81f3501157
commit a3c544a53c
24 changed files with 1623 additions and 449 deletions

View File

@ -345,6 +345,7 @@ class AuthGroups extends ShieldAuthGroups
'ipd_ot.schedule.manage',
'ipd_ot.status.manage',
'ipd_ot.postop.manage',
'settings.bed_status.view',
],
'nurse' => [
'ipd_nursing.view',
@ -356,9 +357,11 @@ class AuthGroups extends ShieldAuthGroups
'ipd_ot.request.manage',
'ipd_ot.status.manage',
'ipd_ot.postop.manage',
'settings.bed_status.view',
],
'pharmacy_admin' => [
'pharmacy.*',
'settings.bed_status.view',
],
'stock_manager' => [
'hospital_stock.access',
@ -366,28 +369,33 @@ class AuthGroups extends ShieldAuthGroups
'hospital_stock.indent.approve',
'hospital_stock.purchase.manage',
'hospital_stock.report.view',
'settings.bed_status.view',
],
'stock_requester' => [
'hospital_stock.access',
'hospital_stock.indent.create',
'hospital_stock.report.view',
'settings.bed_status.view',
],
'stock_issuer' => [
'hospital_stock.access',
'hospital_stock.issue',
'hospital_stock.purchase.manage',
'hospital_stock.report.view',
'settings.bed_status.view',
],
'department_head' => [
'hospital_stock.access',
'hospital_stock.indent.create',
'hospital_stock.report.view',
'settings.bed_status.view',
],
'storekeeper' => [
'hospital_stock.access',
'hospital_stock.issue',
'hospital_stock.purchase.manage',
'hospital_stock.report.view',
'settings.bed_status.view',
],
'billing_cashier' => [
'finance.workflow.view',
@ -399,6 +407,7 @@ class AuthGroups extends ShieldAuthGroups
'billing.refund.view',
'billing.refund.manage',
'ipd_discharge.view',
'settings.bed_status.view',
],
'accounts_officer' => [
'finance.workflow.view',
@ -407,8 +416,11 @@ class AuthGroups extends ShieldAuthGroups
'finance.bank.audit',
'finance.bank.statement.update',
'finance.payment.edit',
'settings.bed_status.view',
],
'user' => [
'settings.bed_status.view',
],
'user' => [],
'beta' => [
'beta.access',
],

View File

@ -84,6 +84,8 @@ class Filters extends BaseFilters
'HealthplixGateway/*',
'stt-proxy',
'stt-proxy/*',
'billing/charges',
'billing/charges/*',
],
],
// 'honeypot',

View File

@ -632,16 +632,16 @@ $routes->post('patient/abdm_content_request_custom/(:num)', 'Patient::abdm_conte
$routes->get('charges/add/(:num)/(:num)', 'Billing\\Charges::add/$1/$2');
$routes->get('charges/add/(:num)/(:num)/(:num)', 'Billing\\Charges::add/$1/$2/$3');
$routes->get('charges/edit/(:num)', 'Billing\\Charges::edit/$1');
$routes->post('charges/list-by-type', 'Billing\\Charges::listByType');
$routes->post('charges/item/(:num)', 'Billing\\Charges::item/$1');
$routes->post('charges/update-refer', 'Billing\\Charges::updateReferDoc');
$routes->match(['GET', 'POST'], 'charges/list-by-type', 'Billing\\Charges::listByType');
$routes->match(['GET', 'POST'], 'charges/item/(:num)', 'Billing\\Charges::item/$1');
$routes->match(['GET', 'POST'], 'charges/update-refer', 'Billing\\Charges::updateReferDoc');
$routes->post('charges/confirm-payment', 'Billing\\Charges::confirm_payment');
$routes->post('charges/cancel-invoice/(:num)', 'Billing\\Charges::cancel_invoice/$1');
$routes->post('charges/update-correction', 'Billing\\Charges::update_correction_charges');
$routes->get('charges/show/(:num)', 'Billing\\Charges::show/$1');
$routes->get('charges/print_invoice/(:num)/(:num)', 'Billing\\Charges::print_invoice/$1/$2');
$routes->post('charges/delete', 'Billing\\Charges::delete');
$routes->post('charges/update-discount', 'Billing\\Charges::updateDiscount');
$routes->match(['GET', 'POST'], 'charges/delete', 'Billing\\Charges::delete');
$routes->match(['GET', 'POST'], 'charges/update-discount', 'Billing\\Charges::updateDiscount');
});
$routes->get('ipd/patient', 'IpdPatient::index', ['filter' => 'permission:ipd_nursing.view']);
@ -1355,6 +1355,7 @@ $routes->group('setting', static function($routes) {
$routes->get('admin/user-management/sessions', 'Setting\\UserManagement::sessions', ['filter' => 'permission:users.edit,users.manage-admins']);
$routes->post('admin/user-management/sessions/force-logout/(:num)', 'Setting\\UserManagement::forceLogout/$1', ['filter' => ['permission:users.edit,users.manage-admins', 'csrf']]);
$routes->post('admin/user-management/delete/(:num)', 'Setting\\UserManagement::delete/$1', ['filter' => ['permission:users.delete,users.manage-admins', 'csrf']]);
$routes->post('admin/user-management/toggle-status/(:num)', 'Setting\\UserManagement::toggleStatus/$1', ['filter' => ['permission:users.edit,users.manage-admins', 'csrf']]);
$routes->get('admin/roles', 'Setting\\RoleManagement::index', ['filter' => 'permission:users.manage-admins']);
$routes->get('admin/roles/new', 'Setting\\RoleManagement::create', ['filter' => 'permission:users.manage-admins']);
$routes->post('admin/roles', 'Setting\\RoleManagement::store', ['filter' => ['permission:users.manage-admins', 'csrf']]);
@ -1462,7 +1463,7 @@ $routes->group('setting', static function($routes) {
$routes->post('admin/departments/save', 'Setting\\BedManagement::saveDepartment', ['filter' => $settingsBedFilter]);
$routes->post('admin/departments/delete', 'Setting\\BedManagement::deleteDepartment', ['filter' => $settingsBedFilter]);
$routes->get('admin/wards', 'Setting\\BedManagement::wards', ['filter' => $settingsBedFilter]);
$routes->get('admin/bed-status', 'Setting\\BedManagement::bedStatus', ['filter' => $settingsBedFilter]);
$routes->get('admin/bed-status', 'Setting\\BedManagement::bedStatus');
$routes->post('admin/wards/save', 'Setting\\BedManagement::saveWard', ['filter' => $settingsBedFilter]);
$routes->post('admin/wards/delete', 'Setting\\BedManagement::deleteWard', ['filter' => $settingsBedFilter]);
$routes->get('admin/bed-categories', 'Setting\\BedManagement::categories', ['filter' => $settingsBedFilter]);

View File

@ -267,8 +267,8 @@ class Charges extends BaseController
return $resp;
}
$tid = (int) $this->request->getPost('itype_idv');
$insId = (int) $this->request->getPost('ins_id');
$tid = (int) ($this->request->getVar('itype_idv') ?? 0);
$insId = (int) ($this->request->getVar('ins_id') ?? 0);
if ($insId > 1) {
$sql = "select m.id,m.itype,m.idesc,m.amount,t.itype_id,t.is_ipd_opd,t.group_desc,
@ -292,6 +292,7 @@ class Charges extends BaseController
echo '<option value=' . $row->id . '>' . $row->sdesc . '</option>';
}
echo '</select></div>';
echo '<input type="hidden" name="' . csrf_token() . '" value="' . csrf_hash() . '" class="ajax-csrf-token" />';
}
public function updateReferDoc()
@ -300,9 +301,9 @@ class Charges extends BaseController
return $resp;
}
$invoiceId = (int) $this->request->getPost('inv_id');
$docId = (int) $this->request->getPost('doc_id');
$rawDate = (string) $this->request->getPost('inv_date');
$invoiceId = (int) ($this->request->getVar('inv_id') ?? 0);
$docId = (int) ($this->request->getVar('doc_id') ?? 0);
$rawDate = (string) ($this->request->getVar('inv_date') ?? '');
$invDate = str_to_MysqlDate($rawDate);
if (strpos($rawDate, '-') !== false && strlen($rawDate) === 10) {
$invDate = $rawDate;
@ -318,7 +319,7 @@ class Charges extends BaseController
}
if ($docId < 1) {
$referName = (string) $this->request->getPost('refername');
$referName = (string) ($this->request->getVar('refername') ?? '');
} else {
$sql = "select * from doctor_master where id=" . $docId;
$query = $this->db->query($sql);
@ -346,8 +347,8 @@ class Charges extends BaseController
$invoiceModel = new InvoiceModel();
if ($type === 1) {
$insId = (int) $this->request->getPost('ins_id');
$itemId = (int) $this->request->getPost('itype_name_id');
$insId = (int) ($this->request->getVar('ins_id') ?? 0);
$itemId = (int) ($this->request->getVar('itype_name_id') ?? 0);
if ($insId > 1) {
$sql = "select m.id,m.itype,m.idesc as sdesc,m.amount,t.itype_id,t.is_ipd_opd,t.group_desc,i.isdelete,
@ -361,11 +362,11 @@ class Charges extends BaseController
$itemRate = $itemlist[0]->amount1 ?? 0;
$orgCode = $itemlist[0]->org_code ?? '';
$customRate = (float) $this->request->getPost('input_rate');
$customRate = (float) ($this->request->getVar('input_rate') ?? 0);
if ($customRate > 0) {
$itemRate = $customRate;
}
$amountValue = (float) $this->request->getPost('input_qty') * $itemRate;
$amountValue = (float) ($this->request->getVar('input_qty') ?? 1) * $itemRate;
} else {
$sql = "select id,idesc as sdesc,amount from hc_items where id=" . $itemId;
$query = $this->db->query($sql);
@ -373,27 +374,27 @@ class Charges extends BaseController
$itemRate = $itemlist[0]->amount ?? 0;
$orgCode = '';
$customRate = (float) $this->request->getPost('input_rate');
$customRate = (float) ($this->request->getVar('input_rate') ?? 0);
if ($customRate > 0) {
$itemRate = $customRate;
}
$amountValue = (float) $this->request->getPost('input_qty') * $itemRate;
$amountValue = (float) ($this->request->getVar('input_qty') ?? 1) * $itemRate;
}
$insertInvoiceItem = [
'inv_master_id' => (int) $this->request->getPost('lab_invoice_id'),
'item_type' => (int) $this->request->getPost('itype_idv'),
'inv_master_id' => (int) ($this->request->getVar('lab_invoice_id') ?? 0),
'item_type' => (int) ($this->request->getVar('itype_idv') ?? 0),
'item_id' => $itemId,
'item_name' => $itemlist[0]->sdesc ?? '',
'item_rate' => $itemRate,
'item_added_date' => str_to_MysqlDate(date('d/m/Y')),
'item_qty' => (float) $this->request->getPost('input_qty'),
'item_qty' => (float) ($this->request->getVar('input_qty') ?? 1),
'item_amount' => $amountValue,
'ins_id' => $insId,
'org_code' => $orgCode,
];
$sql = "select * from invoice_item where inv_master_id=" . (int) $this->request->getPost('lab_invoice_id') . " and item_id=" . $itemId;
$sql = "select * from invoice_item where inv_master_id=" . (int) ($this->request->getVar('lab_invoice_id') ?? 0) . " and item_id=" . $itemId;
$query = $this->db->query($sql);
$itemCheck = $query->getResult();
@ -401,18 +402,18 @@ class Charges extends BaseController
$invoiceModel->addInvoiceItem($insertInvoiceItem);
}
} elseif ($type === 2) {
$amountValue = (float) $this->request->getPost('update_qty') * (float) $this->request->getPost('item_rate');
$amountValue = (float) ($this->request->getVar('update_qty') ?? 1) * (float) ($this->request->getVar('item_rate') ?? 0);
$updateInvoiceItem = [
'item_added_date' => str_to_MysqlDate(date('d/m/Y')),
'item_qty' => (float) $this->request->getPost('update_qty'),
'item_qty' => (float) ($this->request->getVar('update_qty') ?? 1),
'item_amount' => $amountValue,
];
$invoiceModel->updateItem($updateInvoiceItem, (int) $this->request->getPost('itemid'));
$invoiceModel->updateItem($updateInvoiceItem, (int) ($this->request->getVar('itemid') ?? 0));
} else {
$invoiceModel->deleteInvoiceItem((int) $this->request->getPost('itemid'));
$invoiceModel->deleteInvoiceItem((int) ($this->request->getVar('itemid') ?? 0));
}
$invoiceId = (int) $this->request->getPost('lab_invoice_id');
$invoiceId = (int) ($this->request->getVar('lab_invoice_id') ?? 0);
$sql = "select i.*,t.group_desc from invoice_item i join hc_item_type t on i.item_type=t.itype_id where i.inv_master_id=" . $invoiceId . " Order by i.id";
$query = $this->db->query($sql);
$invoiceDetails = $query->getResult();

View File

@ -250,6 +250,15 @@ class IpdPatient extends BaseController
$nurseModel = new \App\Models\NurseModel();
$nurseList = $nurseModel->getActiveNurses();
$defaultNurseId = 0;
$user = function_exists('auth') ? auth()->user() : null;
if ($user && ! empty($user->id)) {
$profile = (new \App\Models\UserProfileModel())->getProfile((int) $user->id);
if (! empty($profile['nurse_id'])) {
$defaultNurseId = (int) $profile['nurse_id'];
}
}
return view('ipd_nursing/workspace', [
'ipd_info' => $panelData['ipd_info'] ?? null,
'person_info' => $person,
@ -260,6 +269,7 @@ class IpdPatient extends BaseController
'doctor_visit_fee_map' => $this->ipdBillingModel->getDoctorVisitFeeMap($doctorIds),
'doc_list' => $docList,
'nurse_list' => $nurseList,
'default_nurse_id' => $defaultNurseId,
'history_fields' => $historyFields,
'patient_history_row' => $patientHistoryRow,
'opd_history_snapshot' => $opdHistorySnapshot,
@ -412,6 +422,12 @@ class IpdPatient extends BaseController
$recordedById = $user->id ?? null;
$nurseIdPost = (int) ($this->request->getPost('recorded_by_nurse_id') ?? 0);
if ($nurseIdPost <= 0 && $user && ! empty($user->id)) {
$profile = (new \App\Models\UserProfileModel())->getProfile((int) $user->id);
if (! empty($profile['nurse_id'])) {
$nurseIdPost = (int) $profile['nurse_id'];
}
}
if ($nurseIdPost > 0) {
$nurseModel = new \App\Models\NurseModel();
$nurseRow = $nurseModel->getNurseById($nurseIdPost);

View File

@ -1520,6 +1520,16 @@ class Opd extends BaseController
$data['org_case_master'] = $query->getResult();
}
$defaultDocId = 0;
$user = function_exists('auth') ? auth()->user() : null;
if ($user && ! empty($user->id)) {
$profile = (new \App\Models\UserProfileModel())->getProfile((int) $user->id);
if (! empty($profile['doctor_id'])) {
$defaultDocId = (int) $profile['doctor_id'];
}
}
$data['default_doc_id'] = $defaultDocId;
return view('billing/opd_appointment_V', $data);
}

View File

@ -41,11 +41,21 @@ class Opdcase extends BaseController
$insuranceId = (int) $row->insurance_id;
}
$defaultDocId = 0;
$user = function_exists('auth') ? auth()->user() : null;
if ($user && ! empty($user->id)) {
$profile = (new \App\Models\UserProfileModel())->getProfile((int) $user->id);
if (! empty($profile['doctor_id'])) {
$defaultDocId = (int) $profile['doctor_id'];
}
}
return view('billing/opd_app_insurance_V', [
'person_info' => $data['person_info'],
'doc_spec_l' => $data['doc_spec_l'],
'person_info' => $data['person_info'],
'doc_spec_l' => $data['doc_spec_l'],
'insurance_cards' => $cards,
'insurance_id' => $insuranceId,
'insurance_id' => $insuranceId,
'default_doc_id' => $defaultDocId,
]);
}

View File

@ -329,8 +329,16 @@ class BedManagement extends BaseController
]);
}
public function bedStatus(): string
public function bedStatus()
{
if (function_exists('auth') && ! auth()->loggedIn()) {
if ($this->request->isAJAX()) {
return $this->response->setStatusCode(401)->setBody('Unauthorized');
}
return redirect()->to(base_url('login'));
}
// ── Stats counters ────────────────────────────────────────────────
$stats = [
'total' => 0,

View File

@ -4,11 +4,16 @@ namespace App\Controllers\Setting;
use App\Controllers\BaseController;
use App\Libraries\UserSessionRegistry;
use App\Models\UserProfileModel;
use CodeIgniter\Shield\Entities\User;
use CodeIgniter\Shield\Models\UserModel;
class UserManagement extends BaseController
{
private function profileModel(): UserProfileModel
{
return model(UserProfileModel::class);
}
private function currentUserId(): int
{
return function_exists('auth') ? (int) (auth()->user()->id ?? 0) : 0;
@ -38,41 +43,60 @@ class UserManagement extends BaseController
public function index(): string
{
$userModel = model(UserModel::class);
$profileModel = $this->profileModel();
$filters = [
'role' => (string) ($this->request->getGet('role') ?? ''),
'active' => (string) ($this->request->getGet('active') ?? ''),
'search' => (string) ($this->request->getGet('search') ?? ''),
];
$users = $userModel
->withIdentities()
->withGroups()
->findAll();
$users = $profileModel->getUsersList($filters);
return view('Setting/Admin/UserManagement/index', [
'users' => $users,
'users' => $users,
'roles' => $this->availableRoles(),
'filters' => $filters,
]);
}
public function create(): string
{
$profileModel = $this->profileModel();
return view('Setting/Admin/UserManagement/create', [
'roles' => $this->availableRoles(),
'roles' => $this->availableRoles(),
'doctors' => $profileModel->getActiveDoctors(),
'nurses' => $profileModel->getActiveNurses(),
'selected_doctor_id' => (int) ($this->request->getGet('doctor_id') ?? 0),
'selected_nurse_id' => (int) ($this->request->getGet('nurse_id') ?? 0),
]);
}
public function store()
{
$rules = [
'username' => 'required|min_length[3]|max_length[30]|regex_match[/^[a-zA-Z0-9.]+$/]',
'email' => 'required|valid_email|max_length[254]',
'password' => 'required|min_length[8]',
'role' => 'required',
'person_name' => 'permit_empty|max_length[120]',
'phone_no' => 'permit_empty|regex_match[/^[0-9+()\\s-]{7,20}$/]',
'username' => 'required|min_length[3]|max_length[30]|regex_match[/^[a-zA-Z0-9.]+$/]',
'email' => 'required|valid_email|max_length[254]',
'password' => 'required|min_length[8]',
'role' => 'required',
'person_name' => 'permit_empty|max_length[120]',
'phone_no' => 'permit_empty|regex_match[/^[0-9+()\\s-]{7,20}$/]',
'doctor_id' => 'permit_empty|is_natural_no_zero',
'nurse_id' => 'permit_empty|is_natural_no_zero',
'department' => 'permit_empty|max_length[100]',
'designation' => 'permit_empty|max_length[100]',
'employee_code' => 'permit_empty|max_length[50]',
];
$profileModel = $this->profileModel();
if (! $this->validate($rules)) {
if ($this->request->isAJAX()) {
return view('Setting/Admin/UserManagement/create', [
'roles' => $this->availableRoles(),
'errors' => $this->validator->getErrors(),
'roles' => $this->availableRoles(),
'doctors' => $profileModel->getActiveDoctors(),
'nurses' => $profileModel->getActiveNurses(),
'errors' => $this->validator->getErrors(),
'formData' => $this->request->getPost(),
]);
}
@ -86,8 +110,10 @@ class UserManagement extends BaseController
$errors = ['role' => 'Invalid role selected.'];
if ($this->request->isAJAX()) {
return view('Setting/Admin/UserManagement/create', [
'roles' => $this->availableRoles(),
'errors' => $errors,
'roles' => $this->availableRoles(),
'doctors' => $profileModel->getActiveDoctors(),
'nurses' => $profileModel->getActiveNurses(),
'errors' => $errors,
'formData' => $this->request->getPost(),
]);
}
@ -108,8 +134,10 @@ class UserManagement extends BaseController
}
} catch (\Throwable $e) {
return view('Setting/Admin/UserManagement/create', [
'roles' => $this->availableRoles(),
'errors' => ['user' => $e->getMessage() !== '' ? $e->getMessage() : 'Unable to create user.'],
'roles' => $this->availableRoles(),
'doctors' => $profileModel->getActiveDoctors(),
'nurses' => $profileModel->getActiveNurses(),
'errors' => ['user' => $e->getMessage() !== '' ? $e->getMessage() : 'Unable to create user.'],
'formData' => $this->request->getPost(),
]);
}
@ -117,26 +145,41 @@ class UserManagement extends BaseController
$savedUser = $userModel->find($userModel->getInsertID());
if ($savedUser !== null) {
$savedUser->addGroup($role);
$this->syncIdentityMeta(
(int) $savedUser->id,
trim((string) $this->request->getPost('person_name')),
trim((string) $this->request->getPost('phone_no'))
);
$personName = trim((string) $this->request->getPost('person_name'));
$phoneNo = trim((string) $this->request->getPost('phone_no'));
$doctorId = (int) $this->request->getPost('doctor_id');
$nurseId = (int) $this->request->getPost('nurse_id');
$department = trim((string) $this->request->getPost('department'));
$designation = trim((string) $this->request->getPost('designation'));
$employeeCode = trim((string) $this->request->getPost('employee_code'));
$profileModel->saveProfile((int) $savedUser->id, [
'full_name' => $personName,
'phone_no' => $phoneNo,
'doctor_id' => $doctorId > 0 ? $doctorId : null,
'nurse_id' => $nurseId > 0 ? $nurseId : null,
'department' => $department,
'designation' => $designation,
'employee_code' => $employeeCode,
]);
$this->syncIdentityMeta((int) $savedUser->id, $personName, $phoneNo);
$this->auditClinicalUpdate('user_management', 'create', $savedUser->id, null, [
'username' => $savedUser->username,
'role' => $role,
'active' => 1,
'username' => $savedUser->username,
'role' => $role,
'active' => 1,
'doctor_id' => $doctorId > 0 ? $doctorId : null,
'nurse_id' => $nurseId > 0 ? $nurseId : null,
'department' => $department,
'employee_code' => $employeeCode,
]);
}
if ($this->request->isAJAX()) {
$users = $userModel
->withIdentities()
->withGroups()
->findAll();
return view('Setting/Admin/UserManagement/index', [
'users' => $users,
'users' => $profileModel->getUsersList(),
'roles' => $this->availableRoles(),
'message' => 'User created successfully.',
]);
}
@ -180,36 +223,42 @@ class UserManagement extends BaseController
->withGroups()
->find($userId);
if ($user === null) {
$users = $userModel
->withIdentities()
->withGroups()
->findAll();
$profileModel = $this->profileModel();
if ($user === null) {
return view('Setting/Admin/UserManagement/index', [
'users' => $users,
'errors' => ['User not found.'],
'users' => $profileModel->getUsersList(),
'roles' => $this->availableRoles(),
'errors' => ['User not found.'],
]);
}
if ($this->isPrivilegedUser($user) && ! $this->canManageAdmins()) {
return view('Setting/Admin/UserManagement/index', [
'users' => $userModel->withIdentities()->withGroups()->findAll(),
'errors' => ['You cannot edit an administrator account.'],
'users' => $profileModel->getUsersList(),
'roles' => $this->availableRoles(),
'errors' => ['You cannot edit an administrator account.'],
]);
}
$emailIdentity = $user->getEmailIdentity();
$email = $emailIdentity ? (string) ($emailIdentity->secret ?? '') : '';
$meta = $this->decodeIdentityExtra($emailIdentity->extra ?? null);
$profile = $profileModel->getProfile($userId);
return view('Setting/Admin/UserManagement/edit', [
'user' => $user,
'email' => $email,
'person_name' => trim((string) ($meta['full_name'] ?? '')),
'phone_no' => trim((string) ($meta['phone_no'] ?? '')),
'roles' => $this->availableRoles(),
'current_role' => (string) (($user->getGroups() ?? [])[0] ?? ''),
'user' => $user,
'email' => $email,
'person_name' => trim((string) ($profile['full_name'] ?? '')),
'phone_no' => trim((string) ($profile['phone_no'] ?? '')),
'doctor_id' => (int) ($profile['doctor_id'] ?? 0),
'nurse_id' => (int) ($profile['nurse_id'] ?? 0),
'department' => trim((string) ($profile['department'] ?? '')),
'designation' => trim((string) ($profile['designation'] ?? '')),
'employee_code' => trim((string) ($profile['employee_code'] ?? '')),
'roles' => $this->availableRoles(),
'current_role' => (string) (($user->getGroups() ?? [])[0] ?? ''),
'doctors' => $profileModel->getActiveDoctors(),
'nurses' => $profileModel->getActiveNurses(),
]);
}
@ -217,22 +266,21 @@ class UserManagement extends BaseController
{
$userModel = model(UserModel::class);
$user = $userModel->withGroups()->find($userId);
if ($user === null) {
$users = $userModel
->withIdentities()
->withGroups()
->findAll();
$profileModel = $this->profileModel();
if ($user === null) {
return view('Setting/Admin/UserManagement/index', [
'users' => $users,
'errors' => ['User not found.'],
'users' => $profileModel->getUsersList(),
'roles' => $this->availableRoles(),
'errors' => ['User not found.'],
]);
}
if ($this->isPrivilegedUser($user) && ! $this->canManageAdmins()) {
return view('Setting/Admin/UserManagement/index', [
'users' => $userModel->withIdentities()->withGroups()->findAll(),
'errors' => ['You cannot edit an administrator account.'],
'users' => $profileModel->getUsersList(),
'roles' => $this->availableRoles(),
'errors' => ['You cannot edit an administrator account.'],
]);
}
@ -240,14 +288,19 @@ class UserManagement extends BaseController
$email = trim((string) $this->request->getPost('email'));
$personName = trim((string) $this->request->getPost('person_name'));
$phoneNo = trim((string) $this->request->getPost('phone_no'));
$doctorId = (int) $this->request->getPost('doctor_id');
$nurseId = (int) $this->request->getPost('nurse_id');
$department = trim((string) $this->request->getPost('department'));
$designation = trim((string) $this->request->getPost('designation'));
$employeeCode = trim((string) $this->request->getPost('employee_code'));
$password = (string) $this->request->getPost('password');
$active = $this->request->getPost('active') ? 1 : 0;
$role = strtolower(trim((string) $this->request->getPost('role')));
$oldGroups = $user->getGroups() ?? [];
$oldState = [
'username' => (string) ($user->username ?? ''),
'groups' => $oldGroups,
'active' => (int) ($user->active ?? 0),
'groups' => $oldGroups,
'active' => (int) ($user->active ?? 0),
];
$errors = [];
@ -302,20 +355,32 @@ class UserManagement extends BaseController
if (! empty($errors)) {
return view('Setting/Admin/UserManagement/edit', [
'user' => $userModel->withIdentities()->find($userId),
'email' => $email,
'person_name' => $personName,
'phone_no' => $phoneNo,
'roles' => $this->availableRoles(),
'current_role' => $role,
'errors' => $errors,
'formData' => [
'username' => $username,
'email' => $email,
'person_name' => $personName,
'phone_no' => $phoneNo,
'active' => $active,
'role' => $role,
'user' => $userModel->withIdentities()->find($userId),
'email' => $email,
'person_name' => $personName,
'phone_no' => $phoneNo,
'doctor_id' => $doctorId,
'nurse_id' => $nurseId,
'department' => $department,
'designation' => $designation,
'employee_code' => $employeeCode,
'roles' => $this->availableRoles(),
'current_role' => $role,
'doctors' => $profileModel->getActiveDoctors(),
'nurses' => $profileModel->getActiveNurses(),
'errors' => $errors,
'formData' => [
'username' => $username,
'email' => $email,
'person_name' => $personName,
'phone_no' => $phoneNo,
'doctor_id' => $doctorId,
'nurse_id' => $nurseId,
'department' => $department,
'designation' => $designation,
'employee_code' => $employeeCode,
'active' => $active,
'role' => $role,
],
]);
}
@ -329,24 +394,35 @@ class UserManagement extends BaseController
$userModel->save($user);
$user->syncGroups($role);
$profileModel->saveProfile($userId, [
'full_name' => $personName,
'phone_no' => $phoneNo,
'doctor_id' => $doctorId > 0 ? $doctorId : null,
'nurse_id' => $nurseId > 0 ? $nurseId : null,
'department' => $department,
'designation' => $designation,
'employee_code' => $employeeCode,
]);
$this->syncIdentityMeta($userId, $personName, $phoneNo);
$this->auditClinicalUpdate('user_management', 'update', $userId, $oldState, [
'username' => $username,
'groups' => [$role],
'active' => $active,
'username' => $username,
'groups' => [$role],
'active' => $active,
'doctor_id' => $doctorId > 0 ? $doctorId : null,
'nurse_id' => $nurseId > 0 ? $nurseId : null,
'department' => $department,
'employee_code' => $employeeCode,
]);
if ($active !== 1) {
(new UserSessionRegistry($this->db))->revokeUser($userId, $this->currentUserId(), 'account_deactivated');
}
$users = $userModel
->withIdentities()
->withGroups()
->findAll();
return view('Setting/Admin/UserManagement/index', [
'users' => $users,
'users' => $profileModel->getUsersList(),
'roles' => $this->availableRoles(),
'message' => 'User updated successfully.',
]);
}
@ -707,21 +783,79 @@ class UserManagement extends BaseController
return $this->response->setStatusCode(403)->setJSON(['update' => 0, 'error_text' => 'You cannot delete an administrator.']);
}
// Deactivate first to ensure no lingering access
$user->active = 0;
$userModel->save($user);
(new UserSessionRegistry($this->db))->revokeUser($userId, $this->currentUserId(), 'account_deleted');
$this->auditClinicalUpdate('user_management', 'delete', $userId, [
'username' => $user->username,
'groups' => $user->getGroups() ?? [],
], ['deleted' => true]);
'groups' => $user->getGroups() ?? [],
], ['deleted' => true, 'soft_delete' => true]);
if (! $userModel->delete($userId)) {
return $this->response->setJSON(['update' => 0, 'error_text' => 'Unable to delete user.']);
}
return $this->response->setJSON([
'update' => 1,
'error_text' => 'User deleted successfully.',
'csrfName' => csrf_token(),
'csrfHash' => csrf_hash(),
'update' => 1,
'error_text' => 'User deactivated and removed from active list.',
'csrfName' => csrf_token(),
'csrfHash' => csrf_hash(),
]);
}
public function toggleStatus(int $userId)
{
if ($userId === $this->currentUserId()) {
return $this->response->setStatusCode(400)->setJSON([
'success' => 0,
'error_text' => 'You cannot deactivate your own account.',
'csrfName' => csrf_token(),
'csrfHash' => csrf_hash(),
]);
}
$userModel = model(UserModel::class);
$user = $userModel->withGroups()->find($userId);
if ($user === null) {
return $this->response->setStatusCode(404)->setJSON([
'success' => 0,
'error_text' => 'User not found.',
'csrfName' => csrf_token(),
'csrfHash' => csrf_hash(),
]);
}
if ($this->isPrivilegedUser($user) && ! $this->canManageAdmins()) {
return $this->response->setStatusCode(403)->setJSON([
'success' => 0,
'error_text' => 'You cannot change the status of an administrator account.',
'csrfName' => csrf_token(),
'csrfHash' => csrf_hash(),
]);
}
$newActive = (int) ($user->active ?? 0) === 1 ? 0 : 1;
$user->active = $newActive;
$userModel->save($user);
if ($newActive === 0) {
(new UserSessionRegistry($this->db))->revokeUser($userId, $this->currentUserId(), 'account_deactivated');
}
$this->auditClinicalUpdate('user_management', 'status_toggle', $userId, [
'active' => 1 - $newActive,
], [
'active' => $newActive,
]);
return $this->response->setJSON([
'success' => 1,
'active' => $newActive,
'message' => $newActive === 1 ? 'User activated successfully.' : 'User deactivated and active session revoked.',
'csrfName' => csrf_token(),
'csrfHash' => csrf_hash(),
]);
}
}

View File

@ -0,0 +1,132 @@
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class CreateUserProfilesTable extends Migration
{
public function up()
{
if (! $this->db->tableExists('user_profiles')) {
$this->forge->addField([
'id' => [
'type' => 'BIGINT',
'unsigned' => true,
'auto_increment' => true,
],
'user_id' => [
'type' => 'INT',
'unsigned' => true,
],
'full_name' => [
'type' => 'VARCHAR',
'constraint' => 120,
'null' => true,
],
'phone_no' => [
'type' => 'VARCHAR',
'constraint' => 20,
'null' => true,
],
'doctor_id' => [
'type' => 'INT',
'unsigned' => true,
'null' => true,
],
'nurse_id' => [
'type' => 'INT',
'unsigned' => true,
'null' => true,
],
'department' => [
'type' => 'VARCHAR',
'constraint' => 100,
'null' => true,
],
'designation' => [
'type' => 'VARCHAR',
'constraint' => 100,
'null' => true,
],
'employee_code' => [
'type' => 'VARCHAR',
'constraint' => 50,
'null' => true,
],
'avatar' => [
'type' => 'VARCHAR',
'constraint' => 255,
'null' => true,
],
'created_at' => [
'type' => 'DATETIME',
'null' => true,
],
'updated_at' => [
'type' => 'DATETIME',
'null' => true,
],
]);
$this->forge->addPrimaryKey('id');
$this->forge->addUniqueKey('user_id', 'uk_user_profiles_user_id');
$this->forge->addKey('doctor_id', false, false, 'idx_user_profiles_doctor_id');
$this->forge->addKey('nurse_id', false, false, 'idx_user_profiles_nurse_id');
$this->forge->addKey('phone_no', false, false, 'idx_user_profiles_phone_no');
$this->forge->addKey('employee_code', false, false, 'idx_user_profiles_employee_code');
$this->forge->createTable('user_profiles', true, ['ENGINE' => 'InnoDB']);
}
// Backfill existing users from auth_identities.extra
$users = $this->db->table('users')->get()->getResultArray();
$now = date('Y-m-d H:i:s');
foreach ($users as $user) {
$userId = (int) $user['id'];
$existing = $this->db->table('user_profiles')->where('user_id', $userId)->get(1)->getRowArray();
if ($existing !== null) {
continue;
}
$identity = $this->db->table('auth_identities')
->where('user_id', $userId)
->where('type', 'email_password')
->get(1)
->getRowArray();
$fullName = null;
$phoneNo = null;
$avatar = null;
if (! empty($identity['extra'])) {
$extra = $identity['extra'];
if (is_string($extra)) {
$decoded = json_decode($extra, true);
if (is_array($decoded)) {
$extra = $decoded;
}
}
if (is_array($extra)) {
$fullName = ! empty($extra['full_name']) ? trim((string) $extra['full_name']) : null;
$phoneNo = ! empty($extra['phone_no']) ? trim((string) $extra['phone_no']) : null;
$avatar = ! empty($extra['profile_photo']) ? trim((string) $extra['profile_photo']) : null;
}
}
$this->db->table('user_profiles')->insert([
'user_id' => $userId,
'full_name' => $fullName,
'phone_no' => $phoneNo,
'avatar' => $avatar,
'created_at' => $now,
'updated_at' => $now,
]);
}
}
public function down()
{
$this->forge->dropTable('user_profiles', true);
}
}

View File

@ -27,7 +27,16 @@ class DoctorModel
public function getDoctors(): array
{
return $this->db->table('doctor_master')->get()->getResult();
$builder = $this->db->table('doctor_master d');
if ($this->db->tableExists('user_profiles')) {
$builder->select('d.*, u.id as linked_user_id, u.username as linked_username, u.active as linked_user_active')
->join('user_profiles up', 'up.doctor_id = d.id', 'left')
->join('users u', 'u.id = up.user_id AND u.deleted_at IS NULL', 'left');
} else {
$builder->select('d.*');
}
return $builder->get()->getResult();
}
public function getDoctorById(int $id): array

View File

@ -61,18 +61,27 @@ class NurseModel
public function getNurses(string $q = ''): array
{
$builder = $this->db->table('nurse_master');
$builder = $this->db->table('nurse_master n');
if ($this->db->tableExists('user_profiles')) {
$builder->select('n.*, u.id as linked_user_id, u.username as linked_username, u.active as linked_user_active')
->join('user_profiles up', 'up.nurse_id = n.id', 'left')
->join('users u', 'u.id = up.user_id AND u.deleted_at IS NULL', 'left');
} else {
$builder->select('n.*');
}
if ($q !== '') {
$builder->groupStart()
->like('name', $q)
->orLike('nurse_code', $q)
->orLike('hpr_id', $q)
->orLike('registration_no', $q)
->orLike('qualification', $q)
->orLike('contact_no', $q)
->like('n.name', $q)
->orLike('n.nurse_code', $q)
->orLike('n.hpr_id', $q)
->orLike('n.registration_no', $q)
->orLike('n.qualification', $q)
->orLike('n.contact_no', $q)
->groupEnd();
}
return $builder->orderBy('name', 'ASC')->get()->getResultArray();
return $builder->orderBy('n.name', 'ASC')->get()->getResultArray();
}
public function getActiveNurses(): array

View File

@ -0,0 +1,212 @@
<?php
namespace App\Models;
use CodeIgniter\Model;
class UserProfileModel extends Model
{
protected $table = 'user_profiles';
protected $primaryKey = 'id';
protected $useAutoIncrement = true;
protected $returnType = 'array';
protected $useSoftDeletes = false;
protected $protectFields = true;
protected $allowedFields = [
'user_id',
'full_name',
'phone_no',
'doctor_id',
'nurse_id',
'department',
'designation',
'employee_code',
'avatar',
'created_at',
'updated_at',
];
protected $useTimestamps = true;
protected $createdField = 'created_at';
protected $updatedField = 'updated_at';
/**
* Get profile for a specific user.
*/
public function getProfile(int $userId): ?array
{
if ($userId <= 0) {
return null;
}
return $this->where('user_id', $userId)->first();
}
/**
* Save or update user profile.
*/
public function saveProfile(int $userId, array $data): bool
{
if ($userId <= 0) {
return false;
}
$existing = $this->where('user_id', $userId)->first();
$fields = [
'full_name' => isset($data['full_name']) ? trim((string) $data['full_name']) : null,
'phone_no' => isset($data['phone_no']) ? trim((string) $data['phone_no']) : null,
'doctor_id' => ! empty($data['doctor_id']) ? (int) $data['doctor_id'] : null,
'nurse_id' => ! empty($data['nurse_id']) ? (int) $data['nurse_id'] : null,
'department' => isset($data['department']) ? trim((string) $data['department']) : null,
'designation' => isset($data['designation']) ? trim((string) $data['designation']) : null,
'employee_code' => isset($data['employee_code']) ? trim((string) $data['employee_code']) : null,
];
if (isset($data['avatar'])) {
$fields['avatar'] = trim((string) $data['avatar']);
}
if ($existing) {
return (bool) $this->update($existing['id'], $fields);
}
$fields['user_id'] = $userId;
return (bool) $this->insert($fields);
}
/**
* Delete profile for a specific user.
*/
public function deleteProfile(int $userId): bool
{
if ($userId <= 0) {
return false;
}
return (bool) $this->where('user_id', $userId)->delete();
}
/**
* Get users with full profile, clinical link, and identity information.
*/
public function getUsersList(array $filters = []): array
{
$builder = $this->db->table('users u')
->select('u.id, u.username, u.active, u.status, u.created_at, u.deleted_at')
->select('p.full_name, p.phone_no, p.doctor_id, p.nurse_id, p.department, p.designation, p.employee_code, p.avatar')
->select('ai.secret as email, ai.force_reset')
->select('(CASE WHEN p.doctor_id IS NOT NULL AND d.id IS NOT NULL THEN TRIM(CONCAT(COALESCE(d.p_title, "Dr."), " ", COALESCE(d.p_fname, ""), " ", COALESCE(d.p_lname, ""))) ELSE NULL END) as linked_doctor_name')
->select('d.doctor_reg_no as linked_doctor_reg')
->select('(CASE WHEN p.nurse_id IS NOT NULL AND n.id IS NOT NULL THEN n.name ELSE NULL END) as linked_nurse_name, n.nurse_code as linked_nurse_code')
->join('user_profiles p', 'p.user_id = u.id', 'left')
->join('auth_identities ai', 'ai.user_id = u.id AND ai.type = "email_password"', 'left')
->join('doctor_master d', 'd.id = p.doctor_id', 'left')
->join('nurse_master n', 'n.id = p.nurse_id', 'left');
// By default show non-deleted users
if (! empty($filters['include_deleted'])) {
// Include soft-deleted
} else {
$builder->where('u.deleted_at IS NULL');
}
if (isset($filters['active']) && $filters['active'] !== '') {
$builder->where('u.active', (int) $filters['active']);
}
if (! empty($filters['search'])) {
$s = trim((string) $filters['search']);
$builder->groupStart()
->like('u.username', $s)
->orLike('p.full_name', $s)
->orLike('p.phone_no', $s)
->orLike('ai.secret', $s)
->orLike('p.employee_code', $s)
->orLike('p.department', $s)
->groupEnd();
}
$builder->orderBy('u.id', 'ASC');
$users = $builder->get()->getResultArray();
// Attach groups/roles
$groups = $this->db->table('auth_groups_users')->get()->getResultArray();
$userGroups = [];
foreach ($groups as $g) {
$userGroups[(int) $g['user_id']][] = $g['group'];
}
foreach ($users as &$user) {
$uid = (int) $user['id'];
$user['groups'] = $userGroups[$uid] ?? [];
}
unset($user);
// Filter by role if requested
if (! empty($filters['role'])) {
$roleFilter = strtolower(trim((string) $filters['role']));
$users = array_values(array_filter($users, fn($u) => in_array($roleFilter, $u['groups'], true)));
}
return $users;
}
/**
* Get active doctors for dropdown mapping.
*/
public function getActiveDoctors(): array
{
if (! $this->db->tableExists('doctor_master')) {
return [];
}
return $this->db->table('doctor_master')
->select('id, p_title, p_fname, p_lname, email1, mphone1, doctor_reg_no, active')
->where('active', '1')
->orWhere('active', 1)
->orderBy('p_fname', 'ASC')
->get()
->getResultArray();
}
/**
* Get active nurses for dropdown mapping.
*/
public function getActiveNurses(): array
{
if (! $this->db->tableExists('nurse_master')) {
return [];
}
return $this->db->table('nurse_master')
->select('id, nurse_code, name, email, contact_no, designation, is_active')
->where('is_active', 1)
->orderBy('name', 'ASC')
->get()
->getResultArray();
}
/**
* Get user profile by linked doctor ID.
*/
public function getByDoctorId(int $doctorId): ?array
{
if ($doctorId <= 0) {
return null;
}
return $this->where('doctor_id', $doctorId)->first();
}
/**
* Get user profile by linked nurse ID.
*/
public function getByNurseId(int $nurseId): ?array
{
if ($nurseId <= 0) {
return null;
}
return $this->where('nurse_id', $nurseId)->first();
}
}

View File

@ -1,88 +1,239 @@
<div class="card">
<div class="card-header d-flex align-items-center justify-content-between flex-wrap gap-2">
<h3 class="card-title mb-0">New Users</h3>
<div class="card-tools ms-auto">
<button class="btn btn-light" type="button" onclick="load_form_div('<?= base_url('setting/admin/user-management') ?>','maindiv','User Management');">
<i class="bi bi-arrow-left"></i>
Back to User List
</button>
</div>
<div class="card shadow-sm border-0">
<div class="card-header bg-white py-3 d-flex align-items-center justify-content-between flex-wrap gap-2 border-bottom">
<div>
<h4 class="card-title mb-0 fw-bold text-dark">New User &amp; Staff Account</h4>
<div class="text-muted small">Create credentials, assign baseline role, and optionally link to a Doctor or Nurse profile</div>
</div>
<div class="card-body">
<?php $errors = $errors ?? session('errors'); ?>
<?php $formData = $formData ?? []; ?>
<?php foreach ((array) $errors as $error) : ?><span class="d-none" data-page-notification data-notification-type="error" data-notification-title="Create User" data-notification-message="<?= esc((string) $error, 'attr') ?>"></span><?php endforeach ?>
<form class="needs-validation" novalidate action="<?= base_url('setting/admin/user-management/new') ?>" method="post">
<?= csrf_field() ?>
<div class="card-tools ms-auto">
<button class="btn btn-outline-secondary" type="button" onclick="load_form_div('<?= base_url('setting/admin/user-management') ?>','maindiv','User Management');">
<i class="bi bi-arrow-left"></i> Back to User List
</button>
</div>
</div>
<div class="card-body p-4">
<?php $errors = $errors ?? session('errors'); ?>
<?php $formData = $formData ?? []; ?>
<?php foreach ((array) $errors as $error) : ?><span class="d-none" data-page-notification data-notification-type="error" data-notification-title="Create User" data-notification-message="<?= esc((string) $error, 'attr') ?>"></span><?php endforeach ?>
<form id="frm_user_create" class="needs-validation" novalidate action="<?= base_url('setting/admin/user-management/new') ?>" method="post">
<?= csrf_field() ?>
<!-- Section 1: Account Credentials -->
<div class="mb-4">
<h6 class="fw-bold text-primary text-uppercase border-bottom pb-2 mb-3">
<i class="bi bi-shield-lock"></i> Account Credentials &amp; Baseline Role
</h6>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label" for="username">Username</label>
<input class="form-control" id="username" name="username" type="text" value="<?= esc($formData['username'] ?? old('username')) ?>" required>
<div class="invalid-feedback">Username is required.</div>
<div class="col-md-4">
<label class="form-label fw-semibold" for="username">Login ID / Username <span class="text-danger">*</span></label>
<input class="form-control" id="username" name="username" type="text" maxlength="30" placeholder="e.g. dr.sharma or rajesh.k" value="<?= esc($formData['username'] ?? old('username')) ?>" required>
<div class="form-text">3-30 characters, letters, numbers, and dot.</div>
</div>
<div class="col-md-6">
<label class="form-label" for="email">Email</label>
<input class="form-control" id="email" name="email" type="email" value="<?= esc($formData['email'] ?? old('email')) ?>" required>
<div class="invalid-feedback">Email is required.</div>
<div class="col-md-4">
<label class="form-label fw-semibold" for="email">Email Address <span class="text-danger">*</span></label>
<input class="form-control" id="email" name="email" type="email" placeholder="staff@hospital.com" value="<?= esc($formData['email'] ?? old('email')) ?>" required>
<div class="form-text">Used for notifications and password recovery.</div>
</div>
<div class="col-md-6">
<label class="form-label" for="person_name">Person Name</label>
<input class="form-control" id="person_name" name="person_name" type="text" maxlength="120" value="<?= esc($formData['person_name'] ?? old('person_name')) ?>">
<div class="col-md-4">
<label class="form-label fw-semibold" for="password">Password <span class="text-danger">*</span></label>
<input class="form-control" id="password" name="password" type="password" minlength="8" placeholder="Minimum 8 characters" required>
<div class="form-text">At least 8 characters.</div>
</div>
<div class="col-md-6">
<label class="form-label" for="phone_no">Phone No.</label>
<input class="form-control" id="phone_no" name="phone_no" type="text" maxlength="20" value="<?= esc($formData['phone_no'] ?? old('phone_no')) ?>">
</div>
<div class="col-md-6">
<label class="form-label" for="password">Password</label>
<input class="form-control" id="password" name="password" type="password" required>
<div class="invalid-feedback">Password is required.</div>
</div>
<div class="col-md-6">
<label class="form-label" for="role">Role</label>
<?php
$defaultRole = $formData['role'] ?? old('role');
if (empty($defaultRole) && ! empty($selected_doctor_id)) {
$defaultRole = 'doctor';
} elseif (empty($defaultRole) && ! empty($selected_nurse_id)) {
$defaultRole = 'nurse';
}
$defaultDoctorId = (int) ($formData['doctor_id'] ?? old('doctor_id') ?? $selected_doctor_id ?? 0);
$defaultNurseId = (int) ($formData['nurse_id'] ?? old('nurse_id') ?? $selected_nurse_id ?? 0);
?>
<div class="col-md-4">
<label class="form-label fw-semibold" for="role">Assigned Role <span class="text-danger">*</span></label>
<select class="form-select" id="role" name="role" required>
<option value="">Select role</option>
<?php if (! empty($roles)) : ?>
<?php foreach ($roles as $roleKey => $roleInfo) : ?>
<option value="<?= esc($roleKey) ?>" <?= ($formData['role'] ?? old('role')) === $roleKey ? 'selected' : '' ?>>
<option value="<?= esc($roleKey) ?>" <?= $defaultRole === $roleKey ? 'selected' : '' ?>>
<?= esc($roleInfo['title'] ?? $roleKey) ?>
</option>
<?php endforeach ?>
<?php endif ?>
</select>
<div class="invalid-feedback">Role is required.</div>
<div class="form-text">Choose the closest job role for baseline access. Add exceptional access later from Additional User Permissions.</div>
<div class="form-text">Provides baseline module permissions.</div>
</div>
<!-- Dynamic Clinical Linkage for Doctor -->
<div class="col-md-4 clinical-link-field clinical-doctor-link" style="display:none;">
<label class="form-label fw-semibold text-info-emphasis" for="doctor_id">
<i class="bi bi-hospital"></i> Link to Doctor Master
</label>
<select class="form-select border-info" id="doctor_id" name="doctor_id">
<option value="">-- No Doctor Linked --</option>
<?php if (! empty($doctors)) : ?>
<?php foreach ($doctors as $doc) : ?>
<?php
$docName = trim(($doc['p_title'] ?? 'Dr.') . ' ' . ($doc['p_fname'] ?? '') . ' ' . ($doc['p_lname'] ?? ''));
?>
<option value="<?= (int) $doc['id'] ?>" <?= ($defaultDoctorId === (int) $doc['id']) ? 'selected' : '' ?>>
<?= esc($docName) ?> <?= ! empty($doc['doctor_reg_no']) ? '(Reg: ' . esc($doc['doctor_reg_no']) . ')' : '' ?>
</option>
<?php endforeach ?>
<?php endif ?>
</select>
<div class="form-text text-info">Auto-fills doctor details &amp; sets doctor context in OPD/IPD.</div>
</div>
<!-- Dynamic Clinical Linkage for Nurse -->
<div class="col-md-4 clinical-link-field clinical-nurse-link" style="display:none;">
<label class="form-label fw-semibold text-purple" for="nurse_id">
<i class="bi bi-heart-pulse"></i> Link to Nurse Master
</label>
<select class="form-select border-primary" id="nurse_id" name="nurse_id">
<option value="">-- No Nurse Linked --</option>
<?php if (! empty($nurses)) : ?>
<?php foreach ($nurses as $nurse) : ?>
<option value="<?= (int) $nurse['id'] ?>" <?= ($defaultNurseId === (int) $nurse['id']) ? 'selected' : '' ?>>
<?= esc($nurse['name']) ?> (<?= esc($nurse['nurse_code'] ?? 'Staff') ?>)
</option>
<?php endforeach ?>
<?php endif ?>
</select>
<div class="form-text">Auto-fills nursing details &amp; logs bedside notes.</div>
</div>
</div>
</div>
<div class="mt-4 d-flex gap-2">
<button class="btn btn-primary" type="submit">Create User</button>
<button class="btn btn-light" type="button" onclick="load_form_div('<?= base_url('setting/admin/user-management') ?>','maindiv','User Management');">Cancel</button>
<!-- Section 2: Staff Details & Hospital Profile -->
<div class="mb-4">
<h6 class="fw-bold text-secondary text-uppercase border-bottom pb-2 mb-3">
<i class="bi bi-person-lines-fill"></i> Staff Profile &amp; Hospital Details
</h6>
<div class="row g-3">
<div class="col-md-4">
<label class="form-label" for="person_name">Person / Staff Full Name</label>
<input class="form-control" id="person_name" name="person_name" type="text" maxlength="120" placeholder="e.g. Dr. Rajesh Sharma" value="<?= esc($formData['person_name'] ?? old('person_name')) ?>">
</div>
<div class="col-md-4">
<label class="form-label" for="phone_no">Mobile Phone No.</label>
<input class="form-control" id="phone_no" name="phone_no" type="text" maxlength="20" placeholder="e.g. 9876543210" value="<?= esc($formData['phone_no'] ?? old('phone_no')) ?>">
</div>
<div class="col-md-4">
<label class="form-label" for="employee_code">Employee / Staff Code</label>
<input class="form-control" id="employee_code" name="employee_code" type="text" maxlength="50" placeholder="e.g. EMP-104" value="<?= esc($formData['employee_code'] ?? old('employee_code')) ?>">
</div>
<div class="col-md-6">
<label class="form-label" for="department">Department</label>
<input class="form-control" id="department" name="department" type="text" maxlength="100" placeholder="e.g. General Medicine, Nursing Station 1, Billing Counter" value="<?= esc($formData['department'] ?? old('department')) ?>">
</div>
<div class="col-md-6">
<label class="form-label" for="designation">Designation</label>
<input class="form-control" id="designation" name="designation" type="text" maxlength="100" placeholder="e.g. Consultant Physician, Staff Nurse, Head Cashier" value="<?= esc($formData['designation'] ?? old('designation')) ?>">
</div>
</div>
</form>
</div>
</div>
<div class="d-flex gap-2 pt-3 border-top">
<button class="btn btn-primary px-4" type="submit">
<i class="bi bi-check-circle"></i> Create User
</button>
<button class="btn btn-light px-3" type="button" onclick="load_form_div('<?= base_url('setting/admin/user-management') ?>','maindiv','User Management');">
Cancel
</button>
</div>
</form>
</div>
</div>
<script>
(function() {
var form = document.querySelector('form[action="<?= base_url('setting/admin/user-management/new') ?>"]');
if (!form || !window.jQuery) {
return;
}
<script>
(function() {
var form = document.getElementById('frm_user_create');
if (!form || !window.jQuery) {
return;
}
$(form).off('submit.userCreate').on('submit.userCreate', function(event) {
event.preventDefault();
var doctorsData = <?= json_encode($doctors ?? []) ?>;
var nursesData = <?= json_encode($nurses ?? []) ?>;
$.post($(form).attr('action'), $(form).serialize())
.done(function(html) {
$('#maindiv').html(html);
showPageNotifications(document.getElementById('maindiv'));
})
.fail(function(xhr) {
notify('error', 'Create User', (xhr.responseJSON && xhr.responseJSON.error_text) || 'Request failed. Please try again.');
});
function handleRoleChange() {
var role = ($('#role').val() || '').toLowerCase();
$('.clinical-link-field').hide();
if (role === 'doctor') {
$('.clinical-doctor-link').fadeIn(150);
} else if (role === 'nurse') {
$('.clinical-nurse-link').fadeIn(150);
}
}
$('#role').off('change.roleSelect').on('change.roleSelect', handleRoleChange);
handleRoleChange();
// Auto-populate when selecting a Doctor
$('#doctor_id').off('change.docSelect').on('change.docSelect', function() {
var docId = parseInt($(this).val() || '0', 10);
if (docId <= 0) return;
var doc = doctorsData.find(function(d) { return parseInt(d.id, 10) === docId; });
if (!doc) return;
var title = (doc.p_title || 'Dr.').trim();
var fname = (doc.p_fname || '').trim();
var lname = (doc.p_lname || '').trim();
var fullName = (title + ' ' + fname + ' ' + lname).trim();
if (!$('#person_name').val()) $('#person_name').val(fullName);
if (!$('#email').val() && doc.email1) $('#email').val(doc.email1);
if (!$('#phone_no').val() && doc.mphone1) $('#phone_no').val(doc.mphone1);
if (!$('#designation').val()) $('#designation').val('Doctor / Consultant');
if (!$('#department').val()) $('#department').val('Clinical / OPD');
if (!$('#username').val() && fname) {
var suggestedUser = 'dr.' + fname.toLowerCase();
$('#username').val(suggestedUser);
}
});
// Auto-populate when selecting a Nurse
$('#nurse_id').off('change.nurseSelect').on('change.nurseSelect', function() {
var nurseId = parseInt($(this).val() || '0', 10);
if (nurseId <= 0) return;
var nurse = nursesData.find(function(n) { return parseInt(n.id, 10) === nurseId; });
if (!nurse) return;
if (!$('#person_name').val() && nurse.name) $('#person_name').val(nurse.name);
if (!$('#email').val() && nurse.email) $('#email').val(nurse.email);
if (!$('#phone_no').val() && nurse.contact_no) $('#phone_no').val(nurse.contact_no);
if (!$('#employee_code').val() && nurse.nurse_code) $('#employee_code').val(nurse.nurse_code);
if (!$('#designation').val()) $('#designation').val(nurse.designation || 'Staff Nurse');
if (!$('#department').val()) $('#department').val(nurse.department || 'Nursing');
if (!$('#username').val() && nurse.nurse_code) {
$('#username').val(nurse.nurse_code.toLowerCase());
}
});
<?php if (! empty($selected_doctor_id)) : ?>
$('#doctor_id').trigger('change.docSelect');
<?php elseif (! empty($selected_nurse_id)) : ?>
$('#nurse_id').trigger('change.nurseSelect');
<?php endif; ?>
// AJAX Form submission
$(form).off('submit.userCreate').on('submit.userCreate', function(event) {
event.preventDefault();
$.post($(form).attr('action'), $(form).serialize())
.done(function(html) {
$('#maindiv').html(html);
if (typeof showPageNotifications === 'function') {
showPageNotifications(document.getElementById('maindiv'));
}
})
.fail(function(xhr) {
notify('error', 'Create User', (xhr.responseJSON && xhr.responseJSON.error_text) || 'Request failed. Please check form validation.');
});
})();
</script>
});
})();
</script>

View File

@ -1,64 +1,141 @@
<div class="card">
<div class="card-header d-flex align-items-center justify-content-between flex-wrap gap-2">
<h3 class="card-title mb-0">Edit User</h3>
<div class="card shadow-sm border-0">
<div class="card-header bg-white py-3 d-flex align-items-center justify-content-between flex-wrap gap-2 border-bottom">
<div>
<h4 class="card-title mb-0 fw-bold text-dark">Edit User &amp; Staff Account</h4>
<div class="text-muted small">Update login credentials, role assignments, staff metadata, and clinical links</div>
</div>
<div class="card-tools ms-auto">
<button class="btn btn-light" type="button" onclick="load_form_div('<?= base_url('setting/admin/user-management') ?>','maindiv','User Management');">
<i class="bi bi-arrow-left"></i>
Back to User List
<button class="btn btn-outline-secondary" type="button" onclick="load_form_div('<?= base_url('setting/admin/user-management') ?>','maindiv','User Management');">
<i class="bi bi-arrow-left"></i> Back to User List
</button>
</div>
</div>
<div class="card-body">
<div class="card-body p-4">
<?php $errors = $errors ?? session('errors'); ?>
<?php $formData = $formData ?? []; ?>
<?php foreach ((array) $errors as $error) : ?><span class="d-none" data-page-notification data-notification-type="error" data-notification-title="Edit User" data-notification-message="<?= esc((string) $error, 'attr') ?>"></span><?php endforeach ?>
<form id="frm_user_edit" class="needs-validation" novalidate action="<?= base_url('setting/admin/user-management/edit/' . (int) ($user->id ?? 0)) ?>" method="post">
<?= csrf_field() ?>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label" for="username">Login ID</label>
<input class="form-control" id="username" name="username" type="text" maxlength="30" value="<?= esc($formData['username'] ?? ($user->username ?? '')) ?>" required>
</div>
<div class="col-md-6">
<label class="form-label" for="person_name">Person Name</label>
<input class="form-control" id="person_name" name="person_name" type="text" maxlength="120" value="<?= esc($formData['person_name'] ?? ($person_name ?? '')) ?>">
</div>
<div class="col-md-6">
<label class="form-label" for="email">Email</label>
<input class="form-control" id="email" name="email" type="email" value="<?= esc($formData['email'] ?? ($email ?? '')) ?>" required>
</div>
<div class="col-md-6">
<label class="form-label" for="phone_no">Phone No.</label>
<input class="form-control" id="phone_no" name="phone_no" type="text" maxlength="20" value="<?= esc($formData['phone_no'] ?? ($phone_no ?? '')) ?>">
</div>
<div class="col-md-6">
<label class="form-label" for="role">Role</label>
<select class="form-select" id="role" name="role" required>
<option value="">Select role</option>
<?php foreach (($roles ?? []) as $roleKey => $roleInfo) : ?>
<option value="<?= esc($roleKey) ?>" <?= (string) ($formData['role'] ?? ($current_role ?? '')) === (string) $roleKey ? 'selected' : '' ?>>
<?= esc($roleInfo['title'] ?? $roleKey) ?>
</option>
<?php endforeach ?>
</select>
<div class="form-text">The role provides baseline access. Use Additional User Permissions only when this user needs extra access beyond the role.</div>
</div>
<div class="col-md-6">
<label class="form-label" for="password">New Password (Optional)</label>
<input class="form-control" id="password" name="password" type="password" autocomplete="new-password" placeholder="Leave blank to keep current password">
</div>
<div class="col-md-6 d-flex align-items-center">
<div class="form-check mt-4">
<input class="form-check-input" id="active" name="active" type="checkbox" value="1" <?= (int) ($formData['active'] ?? ($user->active ?? 0)) === 1 ? 'checked' : '' ?>>
<label class="form-check-label" for="active">Active User</label>
<!-- Section 1: Account Credentials -->
<div class="mb-4">
<h6 class="fw-bold text-primary text-uppercase border-bottom pb-2 mb-3">
<i class="bi bi-shield-lock"></i> Account Credentials &amp; Role
</h6>
<div class="row g-3">
<div class="col-md-4">
<label class="form-label fw-semibold" for="username">Login ID / Username <span class="text-danger">*</span></label>
<input class="form-control" id="username" name="username" type="text" maxlength="30" value="<?= esc($formData['username'] ?? ($user->username ?? '')) ?>" required>
</div>
<div class="col-md-4">
<label class="form-label fw-semibold" for="email">Email Address <span class="text-danger">*</span></label>
<input class="form-control" id="email" name="email" type="email" value="<?= esc($formData['email'] ?? ($email ?? '')) ?>" required>
</div>
<div class="col-md-4">
<label class="form-label fw-semibold" for="password">New Password (Optional)</label>
<input class="form-control" id="password" name="password" type="password" minlength="8" autocomplete="new-password" placeholder="Leave blank to keep current">
</div>
<div class="col-md-4">
<label class="form-label fw-semibold" for="role">Assigned Role <span class="text-danger">*</span></label>
<select class="form-select" id="role" name="role" required>
<option value="">Select role</option>
<?php foreach (($roles ?? []) as $roleKey => $roleInfo) : ?>
<option value="<?= esc($roleKey) ?>" <?= (string) ($formData['role'] ?? ($current_role ?? '')) === (string) $roleKey ? 'selected' : '' ?>>
<?= esc($roleInfo['title'] ?? $roleKey) ?>
</option>
<?php endforeach ?>
</select>
</div>
<!-- Clinical Linkage for Doctor -->
<div class="col-md-4 clinical-link-field clinical-doctor-link" style="display:none;">
<label class="form-label fw-semibold text-info-emphasis" for="doctor_id">
<i class="bi bi-hospital"></i> Link to Doctor Master
</label>
<select class="form-select border-info" id="doctor_id" name="doctor_id">
<option value="">-- No Doctor Linked --</option>
<?php if (! empty($doctors)) : ?>
<?php foreach ($doctors as $doc) : ?>
<?php
$docName = trim(($doc['p_title'] ?? 'Dr.') . ' ' . ($doc['p_fname'] ?? '') . ' ' . ($doc['p_lname'] ?? ''));
$selectedDoc = (int) ($formData['doctor_id'] ?? ($doctor_id ?? 0));
?>
<option value="<?= (int) $doc['id'] ?>" <?= ($selectedDoc === (int) $doc['id']) ? 'selected' : '' ?>>
<?= esc($docName) ?> <?= ! empty($doc['doctor_reg_no']) ? '(Reg: ' . esc($doc['doctor_reg_no']) . ')' : '' ?>
</option>
<?php endforeach ?>
<?php endif ?>
</select>
<div class="form-text text-info">Locks doctor context in OPD and prescriptions.</div>
</div>
<!-- Clinical Linkage for Nurse -->
<div class="col-md-4 clinical-link-field clinical-nurse-link" style="display:none;">
<label class="form-label fw-semibold text-purple" for="nurse_id">
<i class="bi bi-heart-pulse"></i> Link to Nurse Master
</label>
<select class="form-select border-primary" id="nurse_id" name="nurse_id">
<option value="">-- No Nurse Linked --</option>
<?php if (! empty($nurses)) : ?>
<?php foreach ($nurses as $nurse) : ?>
<?php $selectedNurse = (int) ($formData['nurse_id'] ?? ($nurse_id ?? 0)); ?>
<option value="<?= (int) $nurse['id'] ?>" <?= ($selectedNurse === (int) $nurse['id']) ? 'selected' : '' ?>>
<?= esc($nurse['name']) ?> (<?= esc($nurse['nurse_code'] ?? 'Staff') ?>)
</option>
<?php endforeach ?>
<?php endif ?>
</select>
<div class="form-text">Used for nursing chart signing and bedside records.</div>
</div>
<div class="col-md-4 d-flex align-items-center">
<div class="form-check form-switch mt-3">
<input class="form-check-input" id="active" name="active" type="checkbox" role="switch" value="1" <?= (int) ($formData['active'] ?? ($user->active ?? 0)) === 1 ? 'checked' : '' ?>>
<label class="form-check-label fw-semibold" for="active">Account Active</label>
<div class="form-text">Deactivating terminates all active sessions immediately.</div>
</div>
</div>
</div>
</div>
<div class="mt-4 d-flex gap-2">
<button class="btn btn-primary" type="submit">Save Changes</button>
<button class="btn btn-light" type="button" onclick="load_form_div('<?= base_url('setting/admin/user-management') ?>','maindiv','User Management');">Cancel</button>
<!-- Section 2: Staff Details -->
<div class="mb-4">
<h6 class="fw-bold text-secondary text-uppercase border-bottom pb-2 mb-3">
<i class="bi bi-person-lines-fill"></i> Staff Profile Details
</h6>
<div class="row g-3">
<div class="col-md-4">
<label class="form-label" for="person_name">Person / Staff Full Name</label>
<input class="form-control" id="person_name" name="person_name" type="text" maxlength="120" value="<?= esc($formData['person_name'] ?? ($person_name ?? '')) ?>">
</div>
<div class="col-md-4">
<label class="form-label" for="phone_no">Mobile Phone No.</label>
<input class="form-control" id="phone_no" name="phone_no" type="text" maxlength="20" value="<?= esc($formData['phone_no'] ?? ($phone_no ?? '')) ?>">
</div>
<div class="col-md-4">
<label class="form-label" for="employee_code">Employee / Staff Code</label>
<input class="form-control" id="employee_code" name="employee_code" type="text" maxlength="50" placeholder="e.g. EMP-104" value="<?= esc($formData['employee_code'] ?? ($employee_code ?? '')) ?>">
</div>
<div class="col-md-6">
<label class="form-label" for="department">Department</label>
<input class="form-control" id="department" name="department" type="text" maxlength="100" placeholder="e.g. General Medicine, Nursing Station 1, Billing Counter" value="<?= esc($formData['department'] ?? ($department ?? '')) ?>">
</div>
<div class="col-md-6">
<label class="form-label" for="designation">Designation</label>
<input class="form-control" id="designation" name="designation" type="text" maxlength="100" placeholder="e.g. Consultant Physician, Staff Nurse, Head Cashier" value="<?= esc($formData['designation'] ?? ($designation ?? '')) ?>">
</div>
</div>
</div>
<div class="d-flex gap-2 pt-3 border-top">
<button class="btn btn-primary px-4" type="submit">
<i class="bi bi-check2-circle"></i> Save Changes
</button>
<button class="btn btn-light px-3" type="button" onclick="load_form_div('<?= base_url('setting/admin/user-management') ?>','maindiv','User Management');">
Cancel
</button>
</div>
</form>
</div>
@ -71,12 +148,65 @@
return;
}
var doctorsData = <?= json_encode($doctors ?? []) ?>;
var nursesData = <?= json_encode($nurses ?? []) ?>;
function handleRoleChange() {
var role = ($('#role').val() || '').toLowerCase();
$('.clinical-link-field').hide();
if (role === 'doctor') {
$('.clinical-doctor-link').fadeIn(150);
} else if (role === 'nurse') {
$('.clinical-nurse-link').fadeIn(150);
}
}
$('#role').off('change.roleSelect').on('change.roleSelect', handleRoleChange);
handleRoleChange();
// Auto-fill when choosing doctor
$('#doctor_id').off('change.docSelect').on('change.docSelect', function() {
var docId = parseInt($(this).val() || '0', 10);
if (docId <= 0) return;
var doc = doctorsData.find(function(d) { return parseInt(d.id, 10) === docId; });
if (!doc) return;
var title = (doc.p_title || 'Dr.').trim();
var fname = (doc.p_fname || '').trim();
var lname = (doc.p_lname || '').trim();
var fullName = (title + ' ' + fname + ' ' + lname).trim();
if (!$('#person_name').val()) $('#person_name').val(fullName);
if (!$('#phone_no').val() && doc.mphone1) $('#phone_no').val(doc.mphone1);
if (!$('#designation').val()) $('#designation').val('Doctor / Consultant');
if (!$('#department').val()) $('#department').val('Clinical / OPD');
});
// Auto-fill when choosing nurse
$('#nurse_id').off('change.nurseSelect').on('change.nurseSelect', function() {
var nurseId = parseInt($(this).val() || '0', 10);
if (nurseId <= 0) return;
var nurse = nursesData.find(function(n) { return parseInt(n.id, 10) === nurseId; });
if (!nurse) return;
if (!$('#person_name').val() && nurse.name) $('#person_name').val(nurse.name);
if (!$('#phone_no').val() && nurse.contact_no) $('#phone_no').val(nurse.contact_no);
if (!$('#employee_code').val() && nurse.nurse_code) $('#employee_code').val(nurse.nurse_code);
if (!$('#designation').val()) $('#designation').val(nurse.designation || 'Staff Nurse');
if (!$('#department').val()) $('#department').val(nurse.department || 'Nursing');
});
$(form).off('submit.userEdit').on('submit.userEdit', function(event) {
event.preventDefault();
$.post($(form).attr('action'), $(form).serialize())
.done(function(html) {
$('#maindiv').html(html);
showPageNotifications(document.getElementById('maindiv'));
if (typeof showPageNotifications === 'function') {
showPageNotifications(document.getElementById('maindiv'));
}
})
.fail(function(xhr) {
notify('error', 'Edit User', (xhr.responseJSON && xhr.responseJSON.error_text) || 'Request failed. Please try again.');

View File

@ -1,108 +1,208 @@
<div id="maindiv">
<div class="card">
<div class="card-header d-flex align-items-center justify-content-between flex-wrap gap-2">
<h3 class="card-title mb-0">Users</h3>
<div class="card shadow-sm border-0">
<div class="card-header bg-white py-3 d-flex align-items-center justify-content-between flex-wrap gap-2 border-bottom">
<div>
<h4 class="card-title mb-0 fw-bold text-dark">User Management</h4>
<div class="text-muted small">Manage hospital staff accounts, roles, clinical linkages, and credentials</div>
</div>
<div class="card-tools ms-auto">
<button class="btn btn-primary" type="button" onclick="load_form_div('<?= base_url('setting/admin/user-management/new') ?>','maindiv','New User');">
<i class="bi bi-person-plus"></i>
New User
<div class="card-tools ms-auto d-flex align-items-center gap-2 flex-wrap">
<button class="btn btn-outline-info" type="button" onclick="load_form_div('<?= base_url('setting/admin/user-management/sessions') ?>','maindiv','Active Sessions');">
<i class="bi bi-broadcast"></i> Active Sessions
</button>
<button class="btn btn-outline-dark" type="button" onclick="load_form_div('<?= base_url('setting/admin/roles') ?>','maindiv','Role Master');">
<i class="bi bi-person-badge"></i>
Role Master
<i class="bi bi-shield-check"></i> Role Master
</button>
<button class="btn btn-primary" type="button" onclick="load_form_div('<?= base_url('setting/admin/user-management/new') ?>','maindiv','New User');">
<i class="bi bi-person-plus-fill"></i> New User
</button>
</div>
</div>
<div class="card-body">
<div class="card-body p-3">
<?php $message = $message ?? session('message'); ?>
<?php $errors = $errors ?? session('errors'); ?>
<?php if (! empty($message)) : ?><span class="d-none" data-page-notification data-notification-type="success" data-notification-title="User Management" data-notification-message="<?= esc((string) $message, 'attr') ?>"></span><?php endif ?>
<?php foreach ((array) $errors as $error) : ?><span class="d-none" data-page-notification data-notification-type="error" data-notification-title="User Management" data-notification-message="<?= esc((string) $error, 'attr') ?>"></span><?php endforeach ?>
<div class="row g-2 mb-3">
<div class="col-md-4">
<label class="form-label mb-1" for="um_filter_person">Filter by Person Name</label>
<input type="text" id="um_filter_person" class="form-control form-control-sm" placeholder="Type person name...">
<!-- Advanced Filter Bar -->
<div class="row g-2 mb-3 align-items-end bg-light p-2 rounded border">
<div class="col-md-4 col-sm-6">
<label class="form-label small fw-semibold text-secondary mb-1" for="um_filter_search">Search Staff / User</label>
<div class="input-group input-group-sm">
<span class="input-group-text bg-white"><i class="bi bi-search"></i></span>
<input type="text" id="um_filter_search" class="form-control" placeholder="Search by name, login ID, phone, email, emp code...">
</div>
</div>
<div class="col-md-4">
<label class="form-label mb-1" for="um_filter_phone">Filter by Phone No.</label>
<input type="text" id="um_filter_phone" class="form-control form-control-sm" placeholder="Type phone no...">
<div class="col-md-3 col-sm-6">
<label class="form-label small fw-semibold text-secondary mb-1" for="um_filter_role">Filter by Role</label>
<select id="um_filter_role" class="form-select form-select-sm">
<option value="">All Roles</option>
<?php if (! empty($roles)) : ?>
<?php foreach ($roles as $roleKey => $roleInfo) : ?>
<option value="<?= esc($roleKey) ?>"><?= esc($roleInfo['title'] ?? $roleKey) ?></option>
<?php endforeach ?>
<?php endif ?>
</select>
</div>
<div class="col-md-4 d-flex align-items-end">
<button type="button" class="btn btn-outline-secondary btn-sm" id="um_filter_reset">Clear Filters</button>
<div class="col-md-3 col-sm-6">
<label class="form-label small fw-semibold text-secondary mb-1" for="um_filter_status">Filter by Status</label>
<select id="um_filter_status" class="form-select form-select-sm">
<option value="">All Statuses</option>
<option value="active">Active Only</option>
<option value="inactive">Inactive Only</option>
</select>
</div>
<div class="col-md-2 col-sm-6 d-flex gap-2">
<button type="button" class="btn btn-outline-secondary btn-sm w-100" id="um_filter_reset">
<i class="bi bi-x-circle"></i> Reset
</button>
</div>
</div>
<div class="table-responsive">
<table class="table table-striped datatable">
<thead>
<table class="table table-hover table-striped align-middle mb-0" id="tbl_user_list">
<thead class="table-light">
<tr>
<th>#</th>
<th>Login ID</th>
<th>Person Name</th>
<th>Phone No.</th>
<th>Email</th>
<th style="width: 50px;">#</th>
<th>Staff Member</th>
<th>Clinical / Staff Link</th>
<th>Contact Details</th>
<th>Role</th>
<th>Status</th>
<th>Action</th>
<th style="width: 140px;">Status</th>
<th style="width: 170px;" class="text-end">Actions</th>
</tr>
</thead>
<tbody>
<?php if (! empty($users)) : ?>
<?php $rolesConfig = setting('AuthGroups.groups'); ?>
<?php
$rolesConfig = setting('AuthGroups.groups');
$currentUid = function_exists('auth') ? (int) (auth()->user()->id ?? 0) : 0;
?>
<?php foreach ($users as $index => $user) : ?>
<?php
$emailIdentity = $user->getEmailIdentity();
$email = $emailIdentity ? $emailIdentity->secret : '';
$personName = '';
$phoneNo = '';
if ($emailIdentity && isset($emailIdentity->extra)) {
$extra = $emailIdentity->extra;
if (is_string($extra) && trim($extra) !== '') {
$decodedExtra = json_decode($extra, true);
if (is_array($decodedExtra)) {
$personName = trim((string) ($decodedExtra['full_name'] ?? ''));
$phoneNo = trim((string) ($decodedExtra['phone_no'] ?? ''));
}
} elseif (is_array($extra)) {
$personName = trim((string) ($extra['full_name'] ?? ''));
$phoneNo = trim((string) ($extra['phone_no'] ?? ''));
}
}
$groups = $user->getGroups() ?? [];
$roleLabels = [];
foreach ($groups as $group) {
$roleLabels[] = $rolesConfig[$group]['title'] ?? $group;
}
$roleText = $roleLabels !== [] ? implode(', ', $roleLabels) : '-';
$userId = (int) ($user['id'] ?? 0);
$isSelf = ($userId === $currentUid);
$groups = (array) ($user['groups'] ?? []);
$personName = trim((string) ($user['full_name'] ?? ''));
$phoneNo = trim((string) ($user['phone_no'] ?? ''));
$email = trim((string) ($user['email'] ?? ''));
$empCode = trim((string) ($user['employee_code'] ?? ''));
$dept = trim((string) ($user['department'] ?? ''));
$desig = trim((string) ($user['designation'] ?? ''));
$isActive = ! empty($user['active']);
// Clinical mapping
$linkedDoctor = trim((string) ($user['linked_doctor_name'] ?? ''));
$linkedNurse = trim((string) ($user['linked_nurse_name'] ?? ''));
?>
<tr>
<td><?= $index + 1 ?></td>
<td><?= esc($user->username ?? '') ?></td>
<td><?= esc($personName !== '' ? $personName : '-') ?></td>
<td><?= esc($phoneNo !== '' ? $phoneNo : '-') ?></td>
<td><?= esc($email) ?></td>
<td><?= esc($roleText) ?></td>
<tr data-user-id="<?= $userId ?>"
data-username="<?= esc(strtolower($user['username'] ?? ''), 'attr') ?>"
data-person-name="<?= esc(strtolower($personName), 'attr') ?>"
data-phone="<?= esc($phoneNo, 'attr') ?>"
data-email="<?= esc(strtolower($email), 'attr') ?>"
data-emp-code="<?= esc(strtolower($empCode), 'attr') ?>"
data-roles="<?= esc(strtolower(implode(' ', $groups)), 'attr') ?>"
data-status="<?= $isActive ? 'active' : 'inactive' ?>">
<td class="text-muted small"><?= $index + 1 ?></td>
<td>
<?php if (! empty($user->active)) : ?>
<span class="badge bg-success">Active</span>
<div class="d-flex align-items-center gap-2">
<div class="avatar-circle d-flex align-items-center justify-content-center fw-bold bg-primary text-white rounded-circle" style="width:34px; height:34px; font-size:13px;">
<?= strtoupper(substr($personName !== '' ? $personName : ($user['username'] ?? 'U'), 0, 1)) ?>
</div>
<div>
<div class="fw-bold text-dark">
<?= esc($personName !== '' ? $personName : $user['username']) ?>
<?php if ($isSelf) : ?>
<span class="badge bg-secondary ms-1" style="font-size:10px;">You</span>
<?php endif ?>
</div>
<div class="small text-muted d-flex align-items-center gap-2">
<code><?= esc($user['username'] ?? '') ?></code>
<?php if ($empCode !== '') : ?>
<span class="badge bg-light text-secondary border py-0">Emp: <?= esc($empCode) ?></span>
<?php endif ?>
<?php if ($desig !== '' || $dept !== '') : ?>
<span>&bull; <?= esc(implode(' / ', array_filter([$desig, $dept]))) ?></span>
<?php endif ?>
</div>
</div>
</div>
</td>
<td>
<?php if ($linkedDoctor !== '') : ?>
<span class="badge rounded-pill" style="background-color:#0d9488; color:#fff;" title="Linked Doctor">
<i class="bi bi-hospital"></i> <?= esc($linkedDoctor) ?>
</span>
<?php if (! empty($user['linked_doctor_reg'])) : ?>
<div class="text-muted" style="font-size:10px;">Reg: <?= esc($user['linked_doctor_reg']) ?></div>
<?php endif ?>
<?php elseif ($linkedNurse !== '') : ?>
<span class="badge rounded-pill" style="background-color:#d946ef; color:#fff;" title="Linked Nurse">
<i class="bi bi-heart-pulse"></i> <?= esc($linkedNurse) ?>
</span>
<?php else : ?>
<span class="badge bg-secondary">Inactive</span>
<span class="text-muted small">&mdash;</span>
<?php endif ?>
</td>
<td>
<div class="d-flex gap-1">
<button type="button" class="btn btn-sm btn-outline-primary" title="Edit user" aria-label="Edit user" onclick="load_form_div('<?= base_url('setting/admin/user-management/edit/' . (int) ($user->id ?? 0)) ?>','maindiv','Edit User');">
<i class="bi bi-pencil-square" aria-hidden="true"></i>
<div><?= esc($phoneNo !== '' ? $phoneNo : '-') ?></div>
<div class="small text-muted"><?= esc($email !== '' ? $email : '-') ?></div>
</td>
<td>
<?php if (! empty($groups)) : ?>
<?php foreach ($groups as $g) : ?>
<?php
$roleTitle = $rolesConfig[$g]['title'] ?? $g;
$badgeStyle = 'background-color:#64748b; color:#fff;';
if (in_array($g, ['superadmin', 'admin'], true)) {
$badgeStyle = 'background-color:#dc2626; color:#fff;';
} elseif ($g === 'doctor') {
$badgeStyle = 'background-color:#0284c7; color:#fff;';
} elseif ($g === 'nurse') {
$badgeStyle = 'background-color:#7c3aed; color:#fff;';
} elseif (strpos($g, 'billing') !== false || strpos($g, 'cashier') !== false) {
$badgeStyle = 'background-color:#16a34a; color:#fff;';
} elseif (strpos($g, 'pharmacy') !== false) {
$badgeStyle = 'background-color:#ea580c; color:#fff;';
} elseif (strpos($g, 'stock') !== false) {
$badgeStyle = 'background-color:#475569; color:#fff;';
}
?>
<span class="badge rounded-pill mb-1" style="<?= $badgeStyle ?>"><?= esc($roleTitle) ?></span>
<?php endforeach ?>
<?php else : ?>
<span class="badge bg-light text-muted border">No role</span>
<?php endif ?>
</td>
<td>
<div class="form-check form-switch d-inline-flex align-items-center gap-1 mb-0">
<input class="form-check-input btn-toggle-user-status"
type="checkbox"
role="switch"
data-user-id="<?= $userId ?>"
data-username="<?= esc((string) ($user['username'] ?? ''), 'attr') ?>"
<?= $isActive ? 'checked' : '' ?>
<?= $isSelf ? 'disabled title="You cannot deactivate your own account"' : '' ?>>
<span class="status-label small fw-semibold <?= $isActive ? 'text-success' : 'text-secondary' ?>">
<?= $isActive ? 'Active' : 'Inactive' ?>
</span>
</div>
</td>
<td class="text-end">
<div class="btn-group btn-group-sm">
<button type="button" class="btn btn-outline-primary" title="Edit user" aria-label="Edit user" onclick="load_form_div('<?= base_url('setting/admin/user-management/edit/' . $userId) ?>','maindiv','Edit User');">
<i class="bi bi-pencil-square"></i>
</button>
<button type="button" class="btn btn-sm btn-outline-warning" title="Reset password" aria-label="Reset password" onclick="load_form_div('<?= base_url('setting/admin/user-management/reset-password/' . (int) ($user->id ?? 0)) ?>','maindiv','Reset Password');">
<i class="bi bi-key" aria-hidden="true"></i>
<button type="button" class="btn btn-outline-warning" title="Set Temporary PIN" aria-label="Reset password" onclick="load_form_div('<?= base_url('setting/admin/user-management/reset-password/' . $userId) ?>','maindiv','Reset Password');">
<i class="bi bi-key-fill"></i>
</button>
<button type="button" class="btn btn-sm btn-outline-info" title="Additional permissions" aria-label="Additional permissions" onclick="load_form_div('<?= base_url('setting/admin/user-management/permissions?user_id=' . (int) ($user->id ?? 0)) ?>','maindiv','Additional Permissions');">
<i class="bi bi-shield-lock" aria-hidden="true"></i>
<button type="button" class="btn btn-outline-info" title="Additional permissions" aria-label="Additional permissions" onclick="load_form_div('<?= base_url('setting/admin/user-management/permissions?user_id=' . $userId) ?>','maindiv','Additional Permissions');">
<i class="bi bi-shield-lock"></i>
</button>
<?php if ((int) ($user->id ?? 0) !== (int) (auth()->user()->id ?? 0)) : ?>
<button type="button" class="btn btn-sm btn-outline-danger btn-delete-user" title="Delete user" aria-label="Delete user" data-user-id="<?= (int) ($user->id ?? 0) ?>" data-username="<?= esc((string) ($user->username ?? ''), 'attr') ?>">
<i class="bi bi-trash" aria-hidden="true"></i>
<?php if (! $isSelf) : ?>
<button type="button" class="btn btn-outline-danger btn-delete-user" title="Deactivate & remove user" aria-label="Delete user" data-user-id="<?= $userId ?>" data-username="<?= esc((string) ($user['username'] ?? ''), 'attr') ?>">
<i class="bi bi-trash"></i>
</button>
<?php endif ?>
</div>
@ -111,7 +211,7 @@
<?php endforeach ?>
<?php else : ?>
<tr>
<td colspan="8" class="text-center text-muted">No users loaded yet.</td>
<td colspan="7" class="text-center text-muted py-4">No users found. Click <strong>New User</strong> to add your first staff member.</td>
</tr>
<?php endif ?>
</tbody>
@ -122,49 +222,110 @@
<script>
(function() {
function applyUserFilters() {
var personNeedle = ($('#um_filter_person').val() || '').toString().trim().toLowerCase();
var phoneNeedle = ($('#um_filter_phone').val() || '').toString().trim().toLowerCase();
function filterUsersTable() {
var query = ($('#um_filter_search').val() || '').toString().trim().toLowerCase();
var selectedRole = ($('#um_filter_role').val() || '').toString().trim().toLowerCase();
var selectedStatus = ($('#um_filter_status').val() || '').toString().trim().toLowerCase();
$('table.datatable tbody tr').each(function() {
$('#tbl_user_list tbody tr').each(function() {
var $row = $(this);
var personText = ($row.find('td').eq(2).text() || '').toString().trim().toLowerCase();
var phoneText = ($row.find('td').eq(3).text() || '').toString().trim().toLowerCase();
if ($row.find('td').length <= 1) return; // skip empty state row
var personOk = personNeedle === '' || personText.indexOf(personNeedle) !== -1;
var phoneOk = phoneNeedle === '' || phoneText.indexOf(phoneNeedle) !== -1;
var username = ($row.data('username') || '').toString();
var personName = ($row.data('person-name') || '').toString();
var phone = ($row.data('phone') || '').toString();
var email = ($row.data('email') || '').toString();
var empCode = ($row.data('emp-code') || '').toString();
var roles = ($row.data('roles') || '').toString();
var status = ($row.data('status') || '').toString();
$row.toggle(personOk && phoneOk);
var matchesQuery = (query === '') ||
(username.indexOf(query) !== -1) ||
(personName.indexOf(query) !== -1) ||
(phone.indexOf(query) !== -1) ||
(email.indexOf(query) !== -1) ||
(empCode.indexOf(query) !== -1);
var matchesRole = (selectedRole === '') || (roles.indexOf(selectedRole) !== -1);
var matchesStatus = (selectedStatus === '') || (status === selectedStatus);
$row.toggle(matchesQuery && matchesRole && matchesStatus);
});
}
$(document).off('input.umFilter', '#um_filter_person,#um_filter_phone').on('input.umFilter', '#um_filter_person,#um_filter_phone', function() {
applyUserFilters();
$(document).off('input.umFilter', '#um_filter_search').on('input.umFilter', '#um_filter_search', filterUsersTable);
$(document).off('change.umFilter', '#um_filter_role,#um_filter_status').on('change.umFilter', '#um_filter_role,#um_filter_status', filterUsersTable);
$(document).off('click.umReset', '#um_filter_reset').on('click.umReset', '#um_filter_reset', function() {
$('#um_filter_search,#um_filter_role,#um_filter_status').val('');
filterUsersTable();
});
$(document).off('click.umFilterReset', '#um_filter_reset').on('click.umFilterReset', '#um_filter_reset', function() {
$('#um_filter_person,#um_filter_phone').val('');
applyUserFilters();
});
// Quick Active / Inactive Status Switcher
$(document).off('change.umToggleStatus', '.btn-toggle-user-status').on('change.umToggleStatus', '.btn-toggle-user-status', function(e) {
var $checkbox = $(this);
var userId = parseInt($checkbox.data('user-id') || '0', 10);
var username = ($checkbox.data('username') || 'user').toString();
var targetChecked = $checkbox.is(':checked');
$(document).off('click.userDelete', '.btn-delete-user').on('click.userDelete', '.btn-delete-user', function() {
var userId = parseInt($(this).data('user-id') || '0', 10);
var username = ($(this).data('username') || 'this user').toString();
if (userId <= 0 || !window.confirm('Delete ' + username + '? The account will no longer be able to sign in.')) {
var confirmMsg = targetChecked ?
('Activate account for ' + username + '?') :
('Deactivate ' + username + '? Any active login sessions will be terminated immediately.');
if (!window.confirm(confirmMsg)) {
$checkbox.prop('checked', !targetChecked);
return;
}
$.post('<?= base_url('setting/admin/user-management/delete') ?>/' + userId, {
'<?= csrf_token() ?>': $('input[name="<?= csrf_token() ?>"]').first().val() || '<?= csrf_hash() ?>'
}, function(data) {
if (!data || parseInt(data.update || '0', 10) !== 1) {
notify('error', 'Delete User', (data && data.error_text) || 'Unable to delete user.');
var postData = {};
postData['<?= csrf_token() ?>'] = $('input[name="<?= csrf_token() ?>"]').first().val() || '<?= csrf_hash() ?>';
$.post('<?= base_url('setting/admin/user-management/toggle-status') ?>/' + userId, postData, function(res) {
if (!res || parseInt(res.success || '0', 10) !== 1) {
notify('error', 'Status Toggle', (res && res.error_text) || 'Failed to update user status.');
$checkbox.prop('checked', !targetChecked);
return;
}
notify('success', 'Delete User', data.error_text || 'User deleted successfully.');
var newActive = parseInt(res.active || '0', 10) === 1;
$checkbox.prop('checked', newActive);
var $row = $checkbox.closest('tr');
$row.attr('data-status', newActive ? 'active' : 'inactive');
var $label = $checkbox.siblings('.status-label');
if (newActive) {
$label.removeClass('text-secondary').addClass('text-success').text('Active');
} else {
$label.removeClass('text-success').addClass('text-secondary').text('Inactive');
}
notify('success', 'User Status', res.message || 'Status updated successfully.');
}, 'json').fail(function(xhr) {
notify('error', 'Status Toggle', (xhr.responseJSON && xhr.responseJSON.error_text) || 'Request failed.');
$checkbox.prop('checked', !targetChecked);
});
});
// Delete (Deactivate) User
$(document).off('click.userDelete', '.btn-delete-user').on('click.userDelete', '.btn-delete-user', function() {
var userId = parseInt($(this).data('user-id') || '0', 10);
var username = ($(this).data('username') || 'this user').toString();
if (userId <= 0 || !window.confirm('Deactivate and archive account for ' + username + '? The account will be disabled immediately.')) {
return;
}
var postData = {};
postData['<?= csrf_token() ?>'] = $('input[name="<?= csrf_token() ?>"]').first().val() || '<?= csrf_hash() ?>';
$.post('<?= base_url('setting/admin/user-management/delete') ?>/' + userId, postData, function(data) {
if (!data || parseInt(data.update || '0', 10) !== 1) {
notify('error', 'User Deactivation', (data && data.error_text) || 'Unable to deactivate user.');
return;
}
notify('success', 'User Deactivation', data.error_text || 'User deactivated successfully.');
load_form_div('<?= base_url('setting/admin/user-management') ?>', 'maindiv', 'User Management');
}, 'json').fail(function(xhr) {
notify('error', 'Delete User', (xhr.responseJSON && xhr.responseJSON.error_text) || 'Unable to delete user.');
notify('error', 'User Deactivation', (xhr.responseJSON && xhr.responseJSON.error_text) || 'Unable to deactivate user.');
});
});
})();

View File

@ -24,6 +24,7 @@
<th>Name</th>
<th>Phone Number</th>
<th>Email</th>
<th>User Account</th>
<th>Action</th>
</tr>
</thead>
@ -34,6 +35,17 @@
<td><?= esc(($row->p_title ?? '') . ' ' . ($row->p_fname ?? '')) ?></td>
<td><?= esc($row->mphone1 ?? '') ?></td>
<td><?= esc($row->email1 ?? '') ?></td>
<td>
<?php if (! empty($row->linked_username)) : ?>
<span class="badge bg-success-subtle text-success border border-success-subtle" title="Linked User Account">
<i class="bi bi-person-check me-1"></i><?= esc($row->linked_username) ?>
</span>
<?php else : ?>
<button type="button" onclick="load_form_div('<?= base_url('setting/admin/user-management/new?doctor_id=' . (int)$row->id) ?>','maindiv','Create User');" class="btn btn-outline-secondary btn-sm py-0 px-2" style="font-size: 11px;">
<i class="bi bi-person-plus me-1"></i>Link User
</button>
<?php endif; ?>
</td>
<td>
<button type="button" class="btn btn-outline-dark btn-sm me-1 btn-qr-doctor" data-id="<?= (int)$row->id ?>" data-name="<?= esc(($row->p_title ?? '') . ' ' . ($row->p_fname ?? '')) ?>">
<i class="bi bi-qr-code"></i> QR
@ -44,7 +56,7 @@
<?php endforeach ?>
<?php else : ?>
<tr>
<td colspan="4" class="text-center text-muted">No doctors found.</td>
<td colspan="5" class="text-center text-muted">No doctors found.</td>
</tr>
<?php endif ?>
</tbody>

View File

@ -41,6 +41,7 @@
<th style="width: 120px;">Designation</th>
<th>Qualification</th>
<th>Contact / Mobile</th>
<th style="width: 120px;">User Account</th>
<th style="width: 90px;">Status</th>
<th style="width: 110px;" class="text-end pe-3">Actions</th>
</tr>
@ -48,7 +49,7 @@
<tbody id="nurse_table_body">
<?php if (empty($nurses)): ?>
<tr>
<td colspan="10" class="text-center py-4 text-muted">
<td colspan="11" class="text-center py-4 text-muted">
<i class="bi bi-person-x fs-3 d-block mb-1"></i>
No nursing staff records found. Click <strong>"Add New Nurse"</strong> to create one.
</td>
@ -75,6 +76,17 @@
<td><span class="badge bg-secondary-subtle text-dark border"><?= esc($nurse['designation'] ?: 'Staff Nurse') ?></span></td>
<td><?= esc($nurse['qualification'] ?: '-') ?></td>
<td><?= esc($nurse['contact_no'] ?: '-') ?></td>
<td>
<?php if (!empty($nurse['linked_username'])): ?>
<span class="badge bg-success-subtle text-success border border-success-subtle" title="Linked User Account">
<i class="bi bi-person-check me-1"></i><?= esc($nurse['linked_username']) ?>
</span>
<?php else: ?>
<button type="button" onclick="load_form_div('<?= base_url('setting/admin/user-management/new?nurse_id=' . (int)$nurse['id']) ?>','maindiv','Create User');" class="btn btn-outline-secondary btn-sm py-0 px-2" style="font-size: 11px;">
<i class="bi bi-person-plus me-1"></i>Link User
</button>
<?php endif; ?>
</td>
<td>
<?php if ((int)$nurse['is_active'] === 1): ?>
<span class="badge bg-success">Active</span>

View File

@ -8,7 +8,7 @@
</ol>
</section>
<section class="content">
<form role="form" class="form1">
<form role="form" class="form1" onsubmit="return false;">
<?= csrf_field() ?>
<?php
$user = auth()->user();
@ -215,51 +215,135 @@ $(document).ready(function(){
load_form('<?= base_url('billing/charges/show') ?>/' + inv_id);
});
});
$('#input_qty, #input_rate').on('keydown', function(e) {
if (e.key === 'Enter') {
e.preventDefault();
add_item_invoice();
}
});
});
function getChargesCsrf() {
return $('input[name="<?= csrf_token() ?>"]').last().val() || '<?= csrf_hash() ?>';
}
function updateChargesCsrf(data) {
if (!data) return;
try {
var $parsed = $('<div>').append($.parseHTML(data));
var newCsrf = $parsed.find('input[name="<?= csrf_token() ?>"]').val();
if (newCsrf) {
$('input[name="<?= csrf_token() ?>"]').val(newCsrf);
}
} catch (e) {}
}
function add_item_invoice() {
var csrf_value = $('input[name="<?= csrf_token() ?>"]').val();
if ($('#input_qty').val() > 0 && $('#itype_name_id').val() > 0) {
$.post('<?= base_url('billing/charges/item') ?>/1', {
"itype_name_id": $('#itype_name_id').val(),
var csrf_value = getChargesCsrf();
var qty = parseFloat($('#input_qty').val()) || 0;
var itemId = parseInt($('#itype_name_id').val(), 10) || 0;
var labInvoiceId = parseInt($('#lab_invoice_id').val(), 10) || 0;
if (itemId <= 0) {
alert('Please select a Charge Name before adding.');
$('#itype_name_id').select2('open');
return;
}
if (qty <= 0) {
alert('Quantity must be greater than 0.');
$('#input_qty').focus();
return;
}
var $btn = $('#additem');
$btn.prop('disabled', true);
$.ajax({
url: '<?= base_url('billing/charges/item') ?>/1',
type: 'POST',
data: {
"itype_name_id": itemId,
"itype_idv": $('#itype_idv').val(),
"lab_invoice_id": $('#lab_invoice_id').val(),
"lab_invoice_id": labInvoiceId,
"ins_id": $('#ins_id').val(),
"input_qty": $('#input_qty').val(),
"input_rate": $('#input_rate').val(),
"<?= csrf_token() ?>": csrf_value
}, function(data){
},
success: function(data){
$('#show_item_list').html(data);
});
}
updateChargesCsrf(data);
$('#input_rate').val('0.00');
$('#input_qty').val('1');
$('#itype_name_id').focus();
},
error: function(jqXHR, textStatus, errorThrown) {
console.error('add_item_invoice error', jqXHR.status, errorThrown);
var errMsg = 'Failed to add item.';
if (jqXHR.status === 403) {
errMsg = 'Permission Denied: Your account does not have permission to add or edit charges.';
} else if (jqXHR.responseText && jqXHR.responseText.indexOf('route') === -1) {
errMsg = jqXHR.responseText.replace(/<[^>]*>/g, ' ').substring(0, 150);
}
if (typeof window.notify === 'function') {
window.notify('danger', 'Charges Error: ', errMsg);
} else {
alert(errMsg);
}
},
complete: function() {
$btn.prop('disabled', false);
}
});
}
function remove_item_invoice(itemid) {
var csrf_value = $('input[name="<?= csrf_token() ?>"]').val();
if (confirm('Are you sure Remove this item')) {
$.post('<?= base_url('billing/charges/item') ?>/0', {
"itemid": itemid,
"lab_invoice_id": $('#lab_invoice_id').val(),
"<?= csrf_token() ?>": csrf_value
}, function(data){
$('#show_item_list').html(data);
var csrf_value = getChargesCsrf();
if (confirm('Are you sure you want to remove this item?')) {
$.ajax({
url: '<?= base_url('billing/charges/item') ?>/0',
type: 'POST',
data: {
"itemid": itemid,
"lab_invoice_id": $('#lab_invoice_id').val(),
"<?= csrf_token() ?>": csrf_value
},
success: function(data){
$('#show_item_list').html(data);
updateChargesCsrf(data);
},
error: function(jqXHR, textStatus, errorThrown) {
console.error('remove_item_invoice error', jqXHR.status, errorThrown);
alert('Error removing item: ' + (jqXHR.statusText || 'Failed'));
}
});
}
}
function update_qty(itemid) {
var csrf_value = $('input[name="<?= csrf_token() ?>"]').val();
if (confirm('Are you sure Update this item')) {
var update_qty = $('#input_qty_' + itemid).val();
var item_rate = $('#hidden_rate_' + itemid).val();
$.post('<?= base_url('billing/charges/item') ?>/2', {
"itemid": itemid,
"lab_invoice_id": $('#lab_invoice_id').val(),
"update_qty": update_qty,
"item_rate": item_rate,
"<?= csrf_token() ?>": csrf_value
}, function(data){
$('#show_item_list').html(data);
var csrf_value = getChargesCsrf();
if (confirm('Are you sure you want to update this item?')) {
var update_qty_val = $('#input_qty_' + itemid).val();
var item_rate_val = $('#hidden_rate_' + itemid).val();
$.ajax({
url: '<?= base_url('billing/charges/item') ?>/2',
type: 'POST',
data: {
"itemid": itemid,
"lab_invoice_id": $('#lab_invoice_id').val(),
"update_qty": update_qty_val,
"item_rate": item_rate_val,
"<?= csrf_token() ?>": csrf_value
},
success: function(data){
$('#show_item_list').html(data);
updateChargesCsrf(data);
},
error: function(jqXHR, textStatus, errorThrown) {
console.error('update_qty error', jqXHR.status, errorThrown);
alert('Error updating item: ' + (jqXHR.statusText || 'Failed'));
}
});
}
}

View File

@ -42,7 +42,7 @@
<div class="border rounded p-2">
<?php foreach ($doc_spec_l as $row) : ?>
<label class="d-block">
<input type="radio" id="rdoc_id" name="rdoc_id" class="form-check-input me-1" value="<?= esc($row->id) ?>">
<input type="radio" id="rdoc_id" name="rdoc_id" class="form-check-input me-1" value="<?= esc($row->id) ?>" <?= (! empty($default_doc_id) && (int) $default_doc_id === (int) $row->id) ? 'checked' : '' ?>>
<?= esc($row->p_fname) ?> [<i><?= esc($row->SpecName) ?></i>]
</label>
<?php endforeach ?>

View File

@ -38,7 +38,7 @@
<div class="border rounded p-2">
<?php foreach ($doc_spec_l as $row) : ?>
<label class="d-block">
<input type="radio" id="rdoc_id" name="rdoc_id" class="form-check-input me-1" value="<?= esc($row->id) ?>">
<input type="radio" id="rdoc_id" name="rdoc_id" class="form-check-input me-1" value="<?= esc($row->id) ?>" <?= (! empty($default_doc_id) && (int) $default_doc_id === (int) $row->id) ? 'checked' : '' ?>>
<?= esc($row->p_fname) ?> [<i><?= esc($row->SpecName) ?></i>]
</label>
<?php endforeach ?>

View File

@ -91,6 +91,9 @@ $admRecordedAt = !empty($latestAdmissionEntry['recorded_at'])
? date('Y-m-d\TH:i', strtotime($latestAdmissionEntry['recorded_at']))
: date('Y-m-d\TH:i');
$admNurseId = (int) ($latestAdmissionEntry['recorded_by_id'] ?? 0);
if ($admNurseId === 0 && ! empty($default_nurse_id)) {
$admNurseId = (int) $default_nurse_id;
}
$admComplaints = (string) ($latestAdmissionEntry['treatment_text'] ?? '');
$admTempF = (isset($latestAdmissionEntry['temperature_c']) && $latestAdmissionEntry['temperature_c'] !== null && $latestAdmissionEntry['temperature_c'] !== '')
? $toFahrenheit($latestAdmissionEntry['temperature_c'])
@ -560,7 +563,7 @@ $treatmentEntries = array_values(array_filter($entries, static fn ($r) => ($r['e
<select name="recorded_by_nurse_id" class="form-select">
<option value="0">-- Select Nursing Staff --</option>
<?php foreach (($nurse_list ?? []) as $nRow): ?>
<option value="<?= (int) $nRow['id'] ?>">
<option value="<?= (int) $nRow['id'] ?>" <?= (! empty($default_nurse_id) && (int) $default_nurse_id === (int) $nRow['id']) ? 'selected' : '' ?>>
<?= esc(($nRow['nurse_code'] ? '[' . $nRow['nurse_code'] . '] ' : '') . $nRow['name']) ?>
</option>
<?php endforeach; ?>
@ -653,7 +656,7 @@ $treatmentEntries = array_values(array_filter($entries, static fn ($r) => ($r['e
<select name="recorded_by_nurse_id" class="form-select">
<option value="0">-- Select Nursing Staff --</option>
<?php foreach (($nurse_list ?? []) as $nRow): ?>
<option value="<?= (int) $nRow['id'] ?>">
<option value="<?= (int) $nRow['id'] ?>" <?= (! empty($default_nurse_id) && (int) $default_nurse_id === (int) $nRow['id']) ? 'selected' : '' ?>>
<?= esc(($nRow['nurse_code'] ? '[' . $nRow['nurse_code'] . '] ' : '') . $nRow['name']) ?>
</option>
<?php endforeach; ?>
@ -757,7 +760,7 @@ $treatmentEntries = array_values(array_filter($entries, static fn ($r) => ($r['e
<select name="recorded_by_nurse_id" class="form-select">
<option value="0">-- Select Nursing Staff --</option>
<?php foreach (($nurse_list ?? []) as $nRow): ?>
<option value="<?= (int) $nRow['id'] ?>">
<option value="<?= (int) $nRow['id'] ?>" <?= (! empty($default_nurse_id) && (int) $default_nurse_id === (int) $nRow['id']) ? 'selected' : '' ?>>
<?= esc(($nRow['nurse_code'] ? '[' . $nRow['nurse_code'] . '] ' : '') . $nRow['name']) ?>
</option>
<?php endforeach; ?>

View File

@ -1,5 +1,11 @@
<?php
$user = function_exists('auth') ? auth()->user() : null;
$isAdmin = false;
if ($user && method_exists($user, 'inGroup')) {
$isAdmin = $user->inGroup('superadmin', 'admin', 'developer');
}
$canTemplate = false;
if ($user && method_exists($user, 'can')) {
$canTemplate = $user->can('template.pathology')
@ -11,18 +17,10 @@ if ($user && method_exists($user, 'can')) {
|| $user->can('template.*');
}
if (! $canTemplate && $user && method_exists($user, 'inGroup')) {
$canTemplate = $user->inGroup('superadmin', 'admin', 'developer');
if (! $canTemplate && $isAdmin) {
$canTemplate = true;
}
$canIpdBilling = $user && method_exists($user, 'can')
? ($user->can('billing.ipd.access')
|| $user->can('billing.ipd.current-admission')
|| $user->can('billing.ipd.invoice')
|| $user->can('billing.ipd.cash-balance')
|| $user->can('billing.access'))
: false;
$canIpdNursing = $user && method_exists($user, 'can')
? ($user->can('ipd_nursing.view') || $user->can('ipd_nursing.*'))
: false;
@ -32,43 +30,34 @@ $canIpdDischarge = $user && method_exists($user, 'can')
$canIpdOt = $user && method_exists($user, 'can')
? ($user->can('ipd_ot.view') || $user->can('ipd_ot.request.manage') || $user->can('ipd_ot.schedule.manage') || $user->can('ipd_ot.status.manage') || $user->can('ipd_ot.postop.manage') || $user->can('ipd_ot.*'))
: false;
if ($user && method_exists($user, 'inGroup') && $user->inGroup('superadmin', 'admin', 'developer')) {
if ($isAdmin) {
$canIpdNursing = true;
$canIpdDischarge = true;
$canIpdOt = true;
}
$canBilling = false;
if ($user && method_exists($user, 'can')) {
$canBilling = $user->can('billing.access')
|| $user->can('billing.opd.edit')
|| $user->can('billing.opd.pay')
|| $user->can('billing.charges.view')
|| $user->can('billing.charges.edit')
|| $user->can('billing.charges.date-edit')
|| $user->can('billing.charges.pay')
|| $user->can('billing.charges.cancel')
|| $user->can('billing.charges.correct')
|| $user->can('billing.ipd.access')
|| $user->can('billing.ipd.current-admission')
|| $user->can('billing.ipd.invoice')
|| $user->can('billing.ipd.cash-balance')
|| $user->can('billing.ipd.export')
|| $user->can('billing.*');
}
// Granular billing links visibility:
$canPatientList = $isAdmin || ($user && method_exists($user, 'can') && ($user->can('billing.access') || $user->can('billing.opd.edit') || $user->can('billing.*')));
$canOpdInvoice = $isAdmin || ($user && method_exists($user, 'can') && ($user->can('billing.access') || $user->can('billing.opd.edit') || $user->can('billing.*')));
$canChargesInvoice = $isAdmin || ($user && method_exists($user, 'can') && ($user->can('billing.charges.view') || $user->can('billing.access') || $user->can('billing.*')));
$canIpdBilling = $isAdmin || ($user && method_exists($user, 'can') && ($user->can('billing.ipd.access') || $user->can('billing.ipd.current-admission') || $user->can('billing.ipd.invoice') || $user->can('billing.ipd.cash-balance') || $user->can('billing.access') || $user->can('billing.*')));
$canOrgOpdInvoice = $isAdmin || ($user && method_exists($user, 'can') && ($user->can('billing.access') || $user->can('billing.opd.edit') || $user->can('billing.*')));
$canPaymentRequest = $isAdmin || ($user && method_exists($user, 'can') && ($user->can('billing.payment_request.view') || $user->can('billing.payment_request.manage') || $user->can('billing.*')));
$canRefundRequest = $isAdmin || ($user && method_exists($user, 'can') && ($user->can('billing.refund.view') || $user->can('billing.refund.manage') || $user->can('billing.*')));
$canReportPanel = $isAdmin || ($user && method_exists($user, 'can') && ($user->can('billing.access') || $user->can('reports.access') || $user->can('billing.*')));
$canOrgPacking = $isAdmin || ($user && method_exists($user, 'can') && ($user->can('billing.access') || $user->can('billing.charges.view') || $user->can('billing.*')));
$canFinanceBilling = $isAdmin || ($user && method_exists($user, 'can') && ($user->can('finance.cash.billing.submit') || $user->can('finance.*')));
if (! $canBilling && $user && method_exists($user, 'inGroup')) {
$canBilling = $user->inGroup('superadmin', 'admin', 'developer');
}
$canBilling = $canPatientList || $canOpdInvoice || $canChargesInvoice || $canIpdBilling || $canOrgOpdInvoice || $canPaymentRequest || $canRefundRequest || $canReportPanel || $canOrgPacking || $canFinanceBilling;
$canPharmacy = true;
$canPharmacy = false;
if ($user && method_exists($user, 'can')) {
$canPharmacy = $user->can('pharmacy.access')
|| $user->can('billing.access');
|| $user->can('pharmacy.*');
}
if (! $canPharmacy && $user && method_exists($user, 'inGroup')) {
$canPharmacy = $user->inGroup('superadmin', 'admin', 'developer');
if (! $canPharmacy && ($isAdmin || ($user && method_exists($user, 'inGroup') && $user->inGroup('pharmacy_admin')))) {
$canPharmacy = true;
}
$canHospitalStock = false;
@ -150,30 +139,18 @@ if (! $canAbdm && $user && method_exists($user, 'inGroup')) {
$canAbdm = $user->inGroup('superadmin', 'admin', 'developer');
}
$canBedStatus = false;
if ($user && method_exists($user, 'can')) {
$canBedStatus = $user->can('settings.bed_status.view')
|| $user->can('admin.settings')
|| $user->can('admin.access');
}
// Bed Status is open for all logged-in users
$canBedStatus = ($user !== null);
if (! $canBedStatus && $user && method_exists($user, 'inGroup')) {
$canBedStatus = $user->inGroup('superadmin', 'admin', 'developer');
}
$canReports = false;
if ($user && method_exists($user, 'can')) {
$canReports = $user->can('reports.access')
|| $user->can('reports.collection.view')
|| $user->can('reports.insurance_credit.view')
|| $user->can('reports.nabh_audit.view')
|| $user->can('diagnosis.report.view')
|| $user->can('diagnosis.access');
}
// Granular reports permissions:
$canCollectionReport = $isAdmin || ($user && method_exists($user, 'can') && ($user->can('reports.collection.view') || $user->can('reports.access') || $user->can('reports.*')));
$canOldPaymentReport = $isAdmin || ($user && method_exists($user, 'can') && ($user->can('reports.collection.view') || $user->can('reports.access') || $user->can('reports.*')));
$canOpdTotalReport = $isAdmin || ($user && method_exists($user, 'can') && ($user->can('reports.access') || $user->can('reports.*') || $user->can('billing.access')));
$canInsuranceCreditReport = $isAdmin || ($user && method_exists($user, 'can') && ($user->can('reports.insurance_credit.view') || $user->can('reports.access') || $user->can('reports.*')));
$canNabhAuditReport = $isAdmin || ($user && method_exists($user, 'can') && ($user->can('reports.nabh_audit.view') || $user->can('reports.access') || $user->can('reports.*')));
if (! $canReports && $user && method_exists($user, 'inGroup')) {
$canReports = $user->inGroup('superadmin', 'admin', 'developer');
}
$canReports = $canCollectionReport || $canOldPaymentReport || $canOpdTotalReport || $canDiagnosis || $canInsuranceCreditReport || $canNabhAuditReport;
$canAdminPanel = false;
if ($user && method_exists($user, 'can')) {
@ -212,63 +189,79 @@ if (! $canChargesSettings && $user && method_exists($user, 'inGroup')) {
</li>
<?php } ?>
<?php if ($canBilling) { ?>
<li class="nav-heading">Billing</li>
<?php if ($canPatientList) { ?>
<li class="nav-item">
<a class="nav-link collapsed" href="javascript:load_form('<?= base_url('/billing/patient') ?>','Patient List')">
<i class="bi bi-person-lines-fill"></i>
<span>Patient List</span>
</a>
</li>
<?php } ?>
<?php if ($canOpdInvoice) { ?>
<li class="nav-item">
<a class="nav-link collapsed" href="javascript:load_form('<?= base_url('Invoice/opdlist') ?>','OPD Invoice')">
<i class="bi bi-receipt"></i>
<span>OPD Invoice</span>
</a>
</li>
<?php } ?>
<?php if ($canChargesInvoice) { ?>
<li class="nav-item">
<a class="nav-link collapsed" href="javascript:load_form('<?= base_url('Invoice/chargeslist') ?>','Charges Invoice')">
<i class="bi bi-journal-text"></i>
<span>Charges Invoice</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link collapsed" href="javascript:load_form('<?= base_url('/billing/ipd') ?>','IPD List')">
<i class="bi bi-hospital"></i>
<span>IPD Billing</span>
</a>
</li>
<?php } ?>
<?php if ($canIpdBilling) { ?>
<li class="nav-item">
<a class="nav-link collapsed" href="javascript:load_form('<?= base_url('/billing/ipd') ?>','IPD List')">
<i class="bi bi-hospital"></i>
<span>IPD Billing</span>
</a>
</li>
<?php } ?>
<?php if ($canOrgOpdInvoice) { ?>
<li class="nav-item">
<a class="nav-link collapsed" href="javascript:load_form('<?= base_url('Orgcase/search_all') ?>','Org. OPD\'s Invoice')">
<i class="bi bi-receipt-cutoff"></i>
<span>Org. OPD's Invoice</span>
</a>
</li>
<?php } ?>
<?php if ($canPaymentRequest) { ?>
<li class="nav-item">
<a class="nav-link collapsed" href="javascript:load_form('<?= base_url('Invoice/list_req_payment') ?>','Payment Request')">
<i class="bi bi-cash-coin"></i>
<span>Payment Request</span>
</a>
</li>
<?php } ?>
<?php if ($canRefundRequest) { ?>
<li class="nav-item">
<a class="nav-link collapsed" href="javascript:load_form('<?= base_url('Invoice/list_refund') ?>','Refund Amount Request')">
<i class="bi bi-arrow-counterclockwise"></i>
<span>Refund Amount Request</span>
</a>
</li>
<?php } ?>
<?php if ($canReportPanel) { ?>
<li class="nav-item">
<a class="nav-link collapsed" href="javascript:load_form('<?= base_url('Report/index') ?>','Report Panel')">
<i class="bi bi-grid-1x2"></i>
<span>Report Panel</span>
</a>
</li>
<?php } ?>
<?php if ($canOrgPacking) { ?>
<li class="nav-item">
<a class="nav-link collapsed" href="javascript:load_form('<?= base_url('org-packing') ?>','Organization Packing')">
<i class="bi bi-box-seam"></i>
<span>Org Packing </span>
</a>
</li>
<?php } ?>
<?php if ($canFinanceBilling) { ?>
<li class="nav-item">
<a class="nav-link collapsed" href="javascript:load_form('<?= base_url('billing/cash-submission/create') ?>','Cash Submission')">
@ -489,24 +482,30 @@ if (! $canChargesSettings && $user && method_exists($user, 'inGroup')) {
<?php if ($canReports) { ?>
<li class="nav-heading">Reports</li>
<?php if ($canCollectionReport) { ?>
<li class="nav-item">
<a class="nav-link collapsed" href="javascript:load_form('<?= base_url('Report/collection_report') ?>','Collection Report')">
<i class="bi bi-graph-up"></i>
<span>Collection Report</span>
</a>
</li>
<?php } ?>
<?php if ($canOldPaymentReport) { ?>
<li class="nav-item">
<a class="nav-link collapsed" href="javascript:load_form('<?= base_url('Report/old_payment_received_report') ?>','Old Payment Received Report')">
<i class="bi bi-clock-history"></i>
<span>Old Payment Received</span>
</a>
</li>
<?php } ?>
<?php if ($canOpdTotalReport) { ?>
<li class="nav-item">
<a class="nav-link collapsed" href="javascript:load_form('<?= base_url('Report/report_opd_total') ?>','OPD Total Report')">
<i class="bi bi-bar-chart"></i>
<span>OPD Total Report</span>
</a>
</li>
<?php } ?>
<?php if ($canDiagnosis) { ?>
<li class="nav-item">
<a class="nav-link collapsed" href="javascript:load_form('<?= base_url('Report/diagnosis_report') ?>','Diagnosis Report')">
@ -515,18 +514,22 @@ if (! $canChargesSettings && $user && method_exists($user, 'inGroup')) {
</a>
</li>
<?php } ?>
<?php if ($canInsuranceCreditReport) { ?>
<li class="nav-item">
<a class="nav-link collapsed" href="javascript:load_form('<?= base_url('Report/insurance_credit_main') ?>','Insurance Credit')">
<i class="bi bi-file-medical"></i>
<span>Insurance Credit </span>
</a>
</li>
<?php } ?>
<?php if ($canNabhAuditReport) { ?>
<li class="nav-item">
<a class="nav-link collapsed" href="javascript:load_form('<?= base_url('Report/nabh_audit_report') ?>','NABH Audit Report')">
<i class="bi bi-clipboard2-check"></i>
<span>NABH Audit Report</span>
</a>
</li>
<?php } ?>
<?php } ?>

View File

@ -295,12 +295,74 @@
});
}
function handleAuthError(jqXHR) {
if (jqXHR.status === 401 || jqXHR.status === 403) {
alert('You have been logged out. Please login again.');
window.location.href = '/login';
function handleAuthError(jqXHR, containerId) {
if (!jqXHR) {
return false;
}
// HTTP 401: Unauthorized -> User session is genuinely lost or expired
if (jqXHR.status === 401) {
alert('Your session has expired or you have been logged out. Please login again.');
window.location.href = '<?= base_url('login') ?>';
return true;
}
// HTTP 403: Forbidden -> User IS logged in, but lacks permission for this action/link
if (jqXHR.status === 403) {
var raw = (jqXHR.responseText ? String(jqXHR.responseText) : '');
var detailMsg = '';
if (raw && !/<!DOCTYPE/i.test(raw)) {
detailMsg = raw.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim();
}
var accessDeniedHtml =
'<div class="container-fluid py-5">' +
'<div class="row justify-content-center">' +
'<div class="col-12 col-md-8 col-lg-6">' +
'<div class="card shadow-sm border-0 text-center p-4" style="border-radius: 12px; background: #fff;">' +
'<div class="card-body p-3">' +
'<div class="mb-3">' +
'<span class="d-inline-flex align-items-center justify-content-center rounded-circle bg-danger-subtle text-danger" style="width: 76px; height: 76px; font-size: 36px;">' +
'<i class="bi bi-shield-lock-fill"></i>' +
'</span>' +
'</div>' +
'<h4 class="fw-bold text-dark mb-2">Access Restricted</h4>' +
'<p class="text-muted mb-3" style="font-size: 14px;">You do not have permission to access this module or view this information with your current user account.</p>' +
'<div class="alert alert-warning border-0 bg-warning-subtle text-warning-emphasis text-start p-3 mb-4 rounded-3 small">' +
'<div class="fw-bold mb-1"><i class="bi bi-info-circle-fill me-1"></i> How to get access:</div>' +
'<div>Please communicate with your <strong>Hospital Administrator</strong> or IT Department to assign the required permissions or roles to your account.</div>' +
(detailMsg ? '<div class="mt-2 pt-2 border-top border-warning-subtle text-muted" style="font-size: 11px;">Notice: ' + detailMsg.substring(0, 160) + '</div>' : '') +
'</div>' +
'<div class="d-flex justify-content-center gap-2 flex-wrap">' +
'<button type="button" class="btn btn-outline-secondary px-3 btn-sm" onclick="if(window.history.length > 1){ window.history.back(); } else { load_form(\'<?= base_url('dashboard') ?>\', \'Dashboard\'); }">' +
'<i class="bi bi-arrow-left me-1"></i> Go Back' +
'</button>' +
'<button type="button" class="btn btn-primary px-3 btn-sm" onclick="load_form(\'<?= base_url('dashboard') ?>\', \'Dashboard\');">' +
'<i class="bi bi-grid me-1"></i> Back to Dashboard' +
'</button>' +
'</div>' +
'</div>' +
'</div>' +
'</div>' +
'</div>' +
'</div>';
if (containerId) {
var targetEl = document.getElementById(String(containerId));
if (targetEl) {
targetEl.innerHTML = accessDeniedHtml;
} else if (window.jQuery) {
$('#' + containerId).html(accessDeniedHtml);
}
}
if (typeof window.notify === 'function') {
window.notify('danger', 'Access Restricted: ', 'You do not have permission to access this module. Please communicate with your Hospital Administrator.');
}
return true;
}
return false;
}
@ -377,7 +439,7 @@
})
.fail(function(jqXHR, textStatus, errorThrown) {
$("#wait").css("display", "none");
if (handleAuthError(jqXHR)) return;
if (handleAuthError(jqXHR, 'main')) return;
console.error('load_form failed', {
url: normalizedUrl,
status: jqXHR.status,
@ -428,7 +490,7 @@
if (top_title !== '') document.title = top_title;
})
.fail(function(jqXHR, textStatus, errorThrown) {
if (handleAuthError(jqXHR)) return;
if (handleAuthError(jqXHR, xdiv)) return;
console.error('load_form_div failed', {
url: ourl,
status: jqXHR.status,