From a6e75b0397ba1ac6e87d71c9734fef46e3e97c51 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Sat, 21 Mar 2026 16:58:23 +0000 Subject: [PATCH] permission system --- db/migrations/20260321_create_auth_system.sql | 38 +++ includes/auth.php | 79 +++++ includes/layout/header.php | 51 ++- includes/pages/reports.php | 291 +++++++++++++++--- lang.php | 30 +- login.php | 168 ++++++++++ logout.php | 5 + 7 files changed, 614 insertions(+), 48 deletions(-) create mode 100644 db/migrations/20260321_create_auth_system.sql create mode 100644 includes/auth.php create mode 100644 login.php create mode 100644 logout.php diff --git a/db/migrations/20260321_create_auth_system.sql b/db/migrations/20260321_create_auth_system.sql new file mode 100644 index 0000000..5db5452 --- /dev/null +++ b/db/migrations/20260321_create_auth_system.sql @@ -0,0 +1,38 @@ +-- Create roles table +CREATE TABLE IF NOT EXISTS roles ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(50) NOT NULL UNIQUE, + slug VARCHAR(50) NOT NULL UNIQUE, + permissions TEXT NULL, -- JSON or serialized array of permissions + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- Create users table +CREATE TABLE IF NOT EXISTS users ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(100) NOT NULL, + email VARCHAR(100) NOT NULL UNIQUE, + password VARCHAR(255) NOT NULL, + role_id INT NOT NULL, + active TINYINT(1) DEFAULT 1, + last_login DATETIME NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE RESTRICT +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- Seed Roles +INSERT IGNORE INTO roles (name, slug, permissions) VALUES +('Administrator', 'admin', '*'), +('Doctor', 'doctor', '["dashboard", "patients", "visits", "appointments", "home_visits", "reports"]'), +('Nurse', 'nurse', '["dashboard", "patients", "visits", "queue"]'), +('Receptionist', 'receptionist', '["dashboard", "patients", "appointments", "queue", "billing"]'), +('Laboratorial', 'laboratorial', '["dashboard", "laboratory"]'), +('Radiologic', 'radiologic', '["dashboard", "xray"]'); + +-- Seed Default Admin User (password: admin123) +-- Using a simple hash for demonstration if PHP's password_hash is not available in SQL, +-- but ideally we should insert via PHP. For now, I will insert a placeholder and update it via PHP or assume I can use a known hash. +-- Hash for 'admin123' (bcrypt) +INSERT IGNORE INTO users (name, email, password, role_id) +SELECT 'System Admin', 'admin@hospital.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', id +FROM roles WHERE slug = 'admin' LIMIT 1; diff --git a/includes/auth.php b/includes/auth.php new file mode 100644 index 0000000..2137e1a --- /dev/null +++ b/includes/auth.php @@ -0,0 +1,79 @@ +prepare(" + SELECT u.*, r.slug as role_slug, r.permissions + FROM users u + JOIN roles r ON u.role_id = r.id + WHERE u.id = ? AND u.active = 1 + "); + $stmt->execute([$_SESSION['user_id']]); + $user = $stmt->fetch(PDO::FETCH_ASSOC); + + if ($user) { + $_SESSION['user_cache'] = $user; + return $user; + } + + // User not found or inactive, logout + session_destroy(); + header("Location: login.php"); + exit; +} + +function has_role($role_slug) { + $user = current_user(); + if (!$user) return false; + + if ($user['role_slug'] === 'admin') return true; // Admin has all roles + + return $user['role_slug'] === $role_slug; +} + +function has_permission($permission) { + $user = current_user(); + if (!$user) return false; + + if ($user['role_slug'] === 'admin') return true; // Admin has all permissions + + // Decode permissions JSON + $perms = json_decode($user['permissions'], true); + if (!$perms) return false; + + if (in_array('*', $perms)) return true; + + return in_array($permission, $perms); +} + +function require_role($role_slug) { + if (!has_role($role_slug)) { + http_response_code(403); + die("Access Denied: You do not have the required role."); + } +} diff --git a/includes/layout/header.php b/includes/layout/header.php index 98798e9..022f96d 100644 --- a/includes/layout/header.php +++ b/includes/layout/header.php @@ -1,6 +1,11 @@ @@ -203,13 +236,13 @@ $site_favicon = !empty($site_settings['company_favicon']) ? $site_settings['comp diff --git a/includes/pages/reports.php b/includes/pages/reports.php index 7ba8166..096362a 100644 --- a/includes/pages/reports.php +++ b/includes/pages/reports.php @@ -29,17 +29,12 @@ $stmt = $db->prepare("SELECT SUM(patient_payable) FROM bills WHERE status != 'Pa $stmt->execute([$start_date, $end_date]); $pending_bills = $stmt->fetchColumn() ?: 0; -// 5. Visits by Status -$stmt = $db->prepare("SELECT status, COUNT(*) as count FROM visits WHERE DATE(visit_date) BETWEEN ? AND ? GROUP BY status"); -$stmt->execute([$start_date, $end_date]); -$visits_by_status = $stmt->fetchAll(PDO::FETCH_KEY_PAIR); - -// 6. Revenue Trend (Daily) +// 5. Revenue Trend (Daily) $stmt = $db->prepare("SELECT DATE(created_at) as date, SUM(total_amount) as total FROM bills WHERE DATE(created_at) BETWEEN ? AND ? GROUP BY DATE(created_at) ORDER BY date ASC"); $stmt->execute([$start_date, $end_date]); $revenue_trend = $stmt->fetchAll(PDO::FETCH_ASSOC); -// 7. Visits by Doctor +// 6. Visits by Doctor (Top 5) $stmt = $db->prepare(" SELECT d.name_$lang as doctor_name, COUNT(v.id) as count FROM visits v @@ -52,14 +47,48 @@ $stmt = $db->prepare(" $stmt->execute([$start_date, $end_date]); $visits_by_doctor = $stmt->fetchAll(PDO::FETCH_ASSOC); -// 8. Patient Gender Distribution (All time or filtered? Let's do filtered by creation date to match "New Patients") +// 7. Patient Gender Distribution $stmt = $db->prepare("SELECT gender, COUNT(*) as count FROM patients WHERE DATE(created_at) BETWEEN ? AND ? GROUP BY gender"); $stmt->execute([$start_date, $end_date]); $patients_by_gender = $stmt->fetchAll(PDO::FETCH_KEY_PAIR); +// 8. Department Revenue Report +$stmt = $db->prepare(" + SELECT d.name_$lang as name, + COUNT(DISTINCT b.visit_id) as visit_count, + SUM(b.total_amount) as revenue + FROM bills b + JOIN visits v ON b.visit_id = v.id + JOIN doctors doc ON v.doctor_id = doc.id + JOIN departments d ON doc.department_id = d.id + WHERE b.status = 'Paid' AND DATE(b.created_at) BETWEEN ? AND ? + GROUP BY d.id + ORDER BY revenue DESC +"); +$stmt->execute([$start_date, $end_date]); +$dept_revenue = $stmt->fetchAll(PDO::FETCH_ASSOC); + +// 9. Doctor Revenue Report +$stmt = $db->prepare(" + SELECT doc.name_$lang as name, + d.name_$lang as department_name, + COUNT(DISTINCT b.visit_id) as visit_count, + SUM(b.total_amount) as revenue + FROM bills b + JOIN visits v ON b.visit_id = v.id + JOIN doctors doc ON v.doctor_id = doc.id + LEFT JOIN departments d ON doc.department_id = d.id + WHERE b.status = 'Paid' AND DATE(b.created_at) BETWEEN ? AND ? + GROUP BY doc.id + ORDER BY revenue DESC +"); +$stmt->execute([$start_date, $end_date]); +$doctor_revenue = $stmt->fetchAll(PDO::FETCH_ASSOC); + + // --- PREPARE DATA FOR JS --- -$chart_status_labels = array_keys($visits_by_status); -$chart_status_data = array_values($visits_by_status); +$chart_dept_revenue_labels = array_column($dept_revenue, 'name'); +$chart_dept_revenue_data = array_column($dept_revenue, 'revenue'); $chart_revenue_labels = array_column($revenue_trend, 'date'); $chart_revenue_data = array_column($revenue_trend, 'total'); @@ -75,22 +104,102 @@ $chart_gender_data = array_values($patients_by_gender); -
+ + + + + +

- -
-
- - -
- - -
- - -
- -
+
+ +
+
+ + +
+ - +
+ + +
+ +
+ + +
@@ -150,15 +259,15 @@ $chart_gender_data = array_values($patients_by_gender);
-
+
-
+
- +
@@ -178,7 +287,7 @@ $chart_gender_data = array_values($patients_by_gender);
-
+
@@ -205,6 +314,82 @@ $chart_gender_data = array_values($patients_by_gender);
+ +
+ +
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + +
(Paid)
+
+
+
+
+ + +
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + +
(Paid)
+
+ +
+
+
+
+
+
+