diff --git a/app/Config/AuthGroups.php b/app/Config/AuthGroups.php index 623e99a..1a3daab 100644 --- a/app/Config/AuthGroups.php +++ b/app/Config/AuthGroups.php @@ -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', ], diff --git a/app/Config/Filters.php b/app/Config/Filters.php index 3eff990..5a8f94b 100644 --- a/app/Config/Filters.php +++ b/app/Config/Filters.php @@ -84,6 +84,8 @@ class Filters extends BaseFilters 'HealthplixGateway/*', 'stt-proxy', 'stt-proxy/*', + 'billing/charges', + 'billing/charges/*', ], ], // 'honeypot', diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 8c923ee..bc3c3c7 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -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]); diff --git a/app/Controllers/Billing/Charges.php b/app/Controllers/Billing/Charges.php index cb49325..89229ee 100644 --- a/app/Controllers/Billing/Charges.php +++ b/app/Controllers/Billing/Charges.php @@ -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 ''; } echo ''; + echo ''; } 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(); diff --git a/app/Controllers/IpdPatient.php b/app/Controllers/IpdPatient.php index 7b3549d..5a119a5 100644 --- a/app/Controllers/IpdPatient.php +++ b/app/Controllers/IpdPatient.php @@ -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); diff --git a/app/Controllers/Opd.php b/app/Controllers/Opd.php index 9198e9f..4241cc1 100644 --- a/app/Controllers/Opd.php +++ b/app/Controllers/Opd.php @@ -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); } diff --git a/app/Controllers/Opdcase.php b/app/Controllers/Opdcase.php index 043f7f6..efba6bc 100644 --- a/app/Controllers/Opdcase.php +++ b/app/Controllers/Opdcase.php @@ -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, ]); } diff --git a/app/Controllers/Setting/BedManagement.php b/app/Controllers/Setting/BedManagement.php index 81718b9..9f7ad00 100644 --- a/app/Controllers/Setting/BedManagement.php +++ b/app/Controllers/Setting/BedManagement.php @@ -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, diff --git a/app/Controllers/Setting/UserManagement.php b/app/Controllers/Setting/UserManagement.php index 4a3b127..f25b0a6 100644 --- a/app/Controllers/Setting/UserManagement.php +++ b/app/Controllers/Setting/UserManagement.php @@ -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(), ]); } } diff --git a/app/Database/Migrations/2026-10-14-000001_CreateUserProfilesTable.php b/app/Database/Migrations/2026-10-14-000001_CreateUserProfilesTable.php new file mode 100644 index 0000000..06f6e5f --- /dev/null +++ b/app/Database/Migrations/2026-10-14-000001_CreateUserProfilesTable.php @@ -0,0 +1,132 @@ +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); + } +} diff --git a/app/Models/DoctorModel.php b/app/Models/DoctorModel.php index e2f7275..019c372 100644 --- a/app/Models/DoctorModel.php +++ b/app/Models/DoctorModel.php @@ -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 diff --git a/app/Models/NurseModel.php b/app/Models/NurseModel.php index 819ecd3..9671689 100644 --- a/app/Models/NurseModel.php +++ b/app/Models/NurseModel.php @@ -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 diff --git a/app/Models/UserProfileModel.php b/app/Models/UserProfileModel.php new file mode 100644 index 0000000..c975200 --- /dev/null +++ b/app/Models/UserProfileModel.php @@ -0,0 +1,212 @@ +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(); + } +} diff --git a/app/Views/Setting/Admin/UserManagement/create.php b/app/Views/Setting/Admin/UserManagement/create.php index 728ff72..1e18e4e 100644 --- a/app/Views/Setting/Admin/UserManagement/create.php +++ b/app/Views/Setting/Admin/UserManagement/create.php @@ -1,88 +1,239 @@ - - -