diff --git a/assets/css/custom.css b/assets/css/custom.css
index 3496b5f..8c68d28 100644
--- a/assets/css/custom.css
+++ b/assets/css/custom.css
@@ -459,4 +459,64 @@ body {
font-size: 0.9rem;
}
.table-compact tr td:first-child { border-radius: 4px 0 0 4px; }
-.table-compact tr td:last-child { border-radius: 0 4px 4px 0; }
\ No newline at end of file
+.table-compact tr td:last-child { border-radius: 0 4px 4px 0; }
+
+/* ---------------------------------------------------------
+ NEW SIDEBAR STYLES (Collapsible Groups)
+ --------------------------------------------------------- */
+.sidebar-group-btn {
+ width: 100%;
+ text-align: right; /* RTL */
+ padding: 12px 20px;
+ background: transparent;
+ border: none;
+ color: rgba(255,255,255,0.7);
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ transition: all 0.2s;
+ font-weight: 600;
+ font-size: 0.95rem;
+ cursor: pointer;
+}
+.sidebar-group-btn:hover {
+ color: #fff;
+ background-color: rgba(255,255,255,0.05);
+}
+.sidebar-group-btn[aria-expanded="true"] {
+ background-color: rgba(255,255,255,0.05);
+ color: #fff;
+}
+.sidebar-group-btn .group-content {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+.sidebar-group-btn .arrow-icon {
+ font-size: 0.8em;
+ transition: transform 0.3s;
+ opacity: 0.5;
+}
+.sidebar-group-btn[aria-expanded="true"] .arrow-icon {
+ transform: rotate(-180deg); /* Adjust for RTL if needed, usually just flips vertically */
+}
+
+/* Colors for groups (Text & Icon) */
+.group-mail { color: #ffca28 !important; } /* Amber */
+.group-acct { color: #42a5f5 !important; } /* Blue */
+.group-hr { color: #66bb6a !important; } /* Green */
+.group-admin { color: #ef5350 !important; } /* Red */
+.group-reports { color: #ab47bc !important; }/* Purple */
+
+/* Submenu indentation */
+.sidebar .collapse .nav-link {
+ padding-right: 45px; /* RTL indent */
+ padding-left: 20px;
+ font-size: 0.9em;
+ color: rgba(255,255,255,0.6);
+}
+.sidebar .collapse .nav-link:hover,
+.sidebar .collapse .nav-link.active {
+ color: #fff;
+}
+.group-stock { color: #fd7e14 !important; }
diff --git a/db/migrations/020_add_hr_module.sql b/db/migrations/020_add_hr_module.sql
new file mode 100644
index 0000000..94d7a85
--- /dev/null
+++ b/db/migrations/020_add_hr_module.sql
@@ -0,0 +1,134 @@
+-- Migration: Add HR Module
+-- 1. Departments
+CREATE TABLE IF NOT EXISTS hr_departments (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(100) NOT NULL,
+ description TEXT,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- 2. Employees (Linked to users optionally)
+CREATE TABLE IF NOT EXISTS hr_employees (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ user_id INT UNIQUE DEFAULT NULL, -- Link to system login if applicable
+ department_id INT,
+ first_name VARCHAR(50) NOT NULL,
+ last_name VARCHAR(50) NOT NULL,
+ email VARCHAR(100),
+ phone VARCHAR(20),
+ gender ENUM('male', 'female') DEFAULT 'male',
+ birth_date DATE,
+ join_date DATE NOT NULL,
+ job_title VARCHAR(100),
+ basic_salary DECIMAL(10, 2) DEFAULT 0.00,
+ status ENUM('active', 'terminated', 'resigned', 'on_leave') DEFAULT 'active',
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL,
+ FOREIGN KEY (department_id) REFERENCES hr_departments(id) ON DELETE SET NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- 3. Attendance
+CREATE TABLE IF NOT EXISTS hr_attendance (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ employee_id INT NOT NULL,
+ date DATE NOT NULL,
+ check_in TIME,
+ check_out TIME,
+ status ENUM('present', 'absent', 'late', 'excused', 'holiday') DEFAULT 'absent',
+ notes TEXT,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ UNIQUE KEY emp_date (employee_id, date),
+ FOREIGN KEY (employee_id) REFERENCES hr_employees(id) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- 4. Leaves
+CREATE TABLE IF NOT EXISTS hr_leaves (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ employee_id INT NOT NULL,
+ leave_type ENUM('annual', 'sick', 'unpaid', 'maternity', 'emergency', 'other') NOT NULL,
+ start_date DATE NOT NULL,
+ end_date DATE NOT NULL,
+ days_count INT DEFAULT 1,
+ reason TEXT,
+ status ENUM('pending', 'approved', 'rejected') DEFAULT 'pending',
+ approved_by INT DEFAULT NULL,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (employee_id) REFERENCES hr_employees(id) ON DELETE CASCADE,
+ FOREIGN KEY (approved_by) REFERENCES users(id) ON DELETE SET NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- 5. Holidays
+CREATE TABLE IF NOT EXISTS hr_holidays (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(100) NOT NULL,
+ date_from DATE NOT NULL,
+ date_to DATE NOT NULL,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- 6. Payroll
+CREATE TABLE IF NOT EXISTS hr_payroll (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ employee_id INT NOT NULL,
+ month INT NOT NULL,
+ year INT NOT NULL,
+ basic_salary DECIMAL(10, 2) NOT NULL,
+ bonuses DECIMAL(10, 2) DEFAULT 0.00,
+ deductions DECIMAL(10, 2) DEFAULT 0.00,
+ net_salary DECIMAL(10, 2) NOT NULL,
+ status ENUM('pending', 'paid') DEFAULT 'pending',
+ payment_date DATE,
+ notes TEXT,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ UNIQUE KEY emp_period (employee_id, month, year),
+ FOREIGN KEY (employee_id) REFERENCES hr_employees(id) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- Permissions for HR module (Assign to Admin by default)
+INSERT IGNORE INTO user_permissions (user_id, page, can_view, can_add, can_edit, can_delete)
+SELECT id, 'hr_dashboard',
+ IF(role = 'admin', 1, 0),
+ IF(role = 'admin', 1, 0),
+ IF(role = 'admin', 1, 0),
+ IF(role = 'admin', 1, 0)
+FROM users;
+
+INSERT IGNORE INTO user_permissions (user_id, page, can_view, can_add, can_edit, can_delete)
+SELECT id, 'hr_employees',
+ IF(role = 'admin', 1, 0),
+ IF(role = 'admin', 1, 0),
+ IF(role = 'admin', 1, 0),
+ IF(role = 'admin', 1, 0)
+FROM users;
+
+INSERT IGNORE INTO user_permissions (user_id, page, can_view, can_add, can_edit, can_delete)
+SELECT id, 'hr_attendance',
+ IF(role = 'admin', 1, 0),
+ IF(role = 'admin', 1, 0),
+ IF(role = 'admin', 1, 0),
+ IF(role = 'admin', 1, 0)
+FROM users;
+
+INSERT IGNORE INTO user_permissions (user_id, page, can_view, can_add, can_edit, can_delete)
+SELECT id, 'hr_leaves',
+ IF(role = 'admin', 1, 0),
+ IF(role = 'admin', 1, 0),
+ IF(role = 'admin', 1, 0),
+ IF(role = 'admin', 1, 0)
+FROM users;
+
+INSERT IGNORE INTO user_permissions (user_id, page, can_view, can_add, can_edit, can_delete)
+SELECT id, 'hr_payroll',
+ IF(role = 'admin', 1, 0),
+ IF(role = 'admin', 1, 0),
+ IF(role = 'admin', 1, 0),
+ IF(role = 'admin', 1, 0)
+FROM users;
+
+INSERT IGNORE INTO user_permissions (user_id, page, can_view, can_add, can_edit, can_delete)
+SELECT id, 'hr_reports',
+ IF(role = 'admin', 1, 0),
+ IF(role = 'admin', 1, 0),
+ IF(role = 'admin', 1, 0),
+ IF(role = 'admin', 1, 0)
+FROM users;
diff --git a/db/migrations/021_add_stock_module.sql b/db/migrations/021_add_stock_module.sql
new file mode 100644
index 0000000..57b6a2e
--- /dev/null
+++ b/db/migrations/021_add_stock_module.sql
@@ -0,0 +1,76 @@
+-- 021_add_stock_module.sql
+
+CREATE TABLE IF NOT EXISTS stock_stores (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(255) NOT NULL,
+ location VARCHAR(255),
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+CREATE TABLE IF NOT EXISTS stock_categories (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(255) NOT NULL,
+ description TEXT,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+CREATE TABLE IF NOT EXISTS stock_items (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ category_id INT,
+ name VARCHAR(255) NOT NULL,
+ sku VARCHAR(100),
+ description TEXT,
+ min_quantity INT DEFAULT 0,
+ unit VARCHAR(50) DEFAULT 'piece',
+ image_path VARCHAR(255),
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (category_id) REFERENCES stock_categories(id) ON DELETE SET NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+CREATE TABLE IF NOT EXISTS stock_quantities (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ store_id INT NOT NULL,
+ item_id INT NOT NULL,
+ quantity DECIMAL(10,2) DEFAULT 0,
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ UNIQUE KEY unique_item_store (store_id, item_id),
+ FOREIGN KEY (store_id) REFERENCES stock_stores(id) ON DELETE CASCADE,
+ FOREIGN KEY (item_id) REFERENCES stock_items(id) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+CREATE TABLE IF NOT EXISTS stock_transactions (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ transaction_type ENUM('in', 'out', 'transfer', 'damage', 'lend', 'return') NOT NULL,
+ store_id INT NOT NULL,
+ item_id INT NOT NULL,
+ quantity DECIMAL(10,2) NOT NULL,
+ user_id INT,
+ reference VARCHAR(255),
+ notes TEXT,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (store_id) REFERENCES stock_stores(id),
+ FOREIGN KEY (item_id) REFERENCES stock_items(id),
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+CREATE TABLE IF NOT EXISTS stock_lending (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ transaction_id INT NOT NULL,
+ borrower_name VARCHAR(255) NOT NULL,
+ borrower_phone VARCHAR(50),
+ expected_return_date DATE,
+ return_transaction_id INT,
+ status ENUM('active', 'returned', 'overdue') DEFAULT 'active',
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (transaction_id) REFERENCES stock_transactions(id) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- Add default store if none exists
+INSERT INTO stock_stores (name, location)
+SELECT 'المستودع الرئيسي', 'المقر الرئيسي'
+WHERE NOT EXISTS (SELECT 1 FROM stock_stores);
+
+-- Add default category
+INSERT INTO stock_categories (name)
+SELECT 'عام'
+WHERE NOT EXISTS (SELECT 1 FROM stock_categories);
diff --git a/hr_attendance.php b/hr_attendance.php
new file mode 100644
index 0000000..2a3b5c5
--- /dev/null
+++ b/hr_attendance.php
@@ -0,0 +1,227 @@
+ليس لديك صلاحية للوصول إلى هذه الصفحة.";
+ require_once 'includes/footer.php';
+ exit;
+}
+
+$date = $_GET['date'] ?? date('Y-m-d');
+$error = '';
+$success = '';
+
+// Handle Attendance Submission
+if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['save_attendance'])) {
+ if (!canAdd('hr_attendance') && !canEdit('hr_attendance')) {
+ $error = "لا تملك صلاحية التعديل.";
+ } else {
+ $emp_id = $_POST['employee_id'];
+ $att_date = $_POST['date'];
+ $status = $_POST['status'];
+ $check_in = !empty($_POST['check_in']) ? $_POST['check_in'] : null;
+ $check_out = !empty($_POST['check_out']) ? $_POST['check_out'] : null;
+ $notes = $_POST['notes'];
+
+ try {
+ // Check if exists
+ $stmt = db()->prepare("SELECT id FROM hr_attendance WHERE employee_id = ? AND date = ?");
+ $stmt->execute([$emp_id, $att_date]);
+ $exists = $stmt->fetch();
+
+ if ($exists) {
+ $stmt = db()->prepare("UPDATE hr_attendance SET status = ?, check_in = ?, check_out = ?, notes = ? WHERE id = ?");
+ $stmt->execute([$status, $check_in, $check_out, $notes, $exists['id']]);
+ $success = "تم تحديث الحضور بنجاح.";
+ } else {
+ $stmt = db()->prepare("INSERT INTO hr_attendance (employee_id, date, status, check_in, check_out, notes) VALUES (?, ?, ?, ?, ?, ?)");
+ $stmt->execute([$emp_id, $att_date, $status, $check_in, $check_out, $notes]);
+ $success = "تم تسجيل الحضور بنجاح.";
+ }
+ } catch (PDOException $e) {
+ $error = "خطأ: " . $e->getMessage();
+ }
+ }
+}
+
+// Fetch Employees and their attendance for the selected date
+$sql = "SELECT e.id, e.first_name, e.last_name, e.job_title,
+ a.id as att_id, a.status, a.check_in, a.check_out, a.notes
+ FROM hr_employees e
+ LEFT JOIN hr_attendance a ON e.id = a.employee_id AND a.date = ?
+ WHERE e.status = 'active'
+ ORDER BY e.first_name";
+$stmt = db()->prepare($sql);
+$stmt->execute([$date]);
+$records = $stmt->fetchAll();
+
+?>
+
+
+
سجل الحضور والانصراف
+
+
+
+
+
+
+ = htmlspecialchars($error) ?>
+
+
+ = htmlspecialchars($success) ?>
+
+
+
+
+
+
+
+
+ الموظف
+ الوظيفة
+ الحالة
+ وقت الحضور
+ وقت الانصراف
+ ملاحظات
+ إجراء
+
+
+
+
+
+ = htmlspecialchars($row['first_name'] . ' ' . $row['last_name']) ?>
+ = htmlspecialchars($row['job_title']) ?>
+
+
+ 'success',
+ 'absent' => 'danger',
+ 'late' => 'warning',
+ 'excused' => 'info',
+ 'holiday' => 'primary',
+ default => 'secondary'
+ };
+ $status_text = match($row['status']) {
+ 'present' => 'حاضر',
+ 'absent' => 'غائب',
+ 'late' => 'تأخير',
+ 'excused' => 'مأذون',
+ 'holiday' => 'عطلة',
+ default => $row['status']
+ };
+ ?>
+ = $status_text ?>
+
+ غير مسجل
+
+
+ = $row['check_in'] ? date('h:i A', strtotime($row['check_in'])) : '-' ?>
+ = $row['check_out'] ? date('h:i A', strtotime($row['check_out'])) : '-' ?>
+ = htmlspecialchars($row['notes'] ?? '') ?>
+
+
+
+
+
+
+
+
+ تسجيل
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/hr_dashboard.php b/hr_dashboard.php
new file mode 100644
index 0000000..ac9ef42
--- /dev/null
+++ b/hr_dashboard.php
@@ -0,0 +1,153 @@
+ليس لديك صلاحية للوصول إلى هذه الصفحة.";
+ require_once 'includes/footer.php';
+ exit;
+}
+
+// Fetch Stats
+$total_employees = db()->query("SELECT COUNT(*) FROM hr_employees WHERE status = 'active'")->fetchColumn();
+$employees_present = db()->query("SELECT COUNT(*) FROM hr_attendance WHERE date = CURDATE() AND status = 'present'")->fetchColumn();
+$on_leave = db()->query("SELECT COUNT(*) FROM hr_leaves WHERE CURDATE() BETWEEN start_date AND end_date AND status = 'approved'")->fetchColumn();
+$pending_leaves = db()->query("SELECT COUNT(*) FROM hr_leaves WHERE status = 'pending'")->fetchColumn();
+
+// Recent Employees
+$recent_employees = db()->query("SELECT * FROM hr_employees ORDER BY join_date DESC LIMIT 5")->fetchAll();
+?>
+
+
+
لوحة الموارد البشرية
+
+
+
+
+
+
+
+
+
+
إجمالي الموظفين
+ = number_format($total_employees) ?>
+
+
+
+
+
+
+
+
+
+
+
+
حضور اليوم
+ = number_format($employees_present) ?>
+
+
+
+
+
+
+
+
+
+
+
+
في إجازة
+ = number_format($on_leave) ?>
+
+
+
+
+
+
+
+
+
+
+
+
طلبات إجازة معلقة
+ = number_format($pending_leaves) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ الاسم
+ الوظيفة
+ تاريخ التعيين
+
+
+
+
+ لا يوجد موظفين مسجلين حالياً
+
+
+
+ = htmlspecialchars($emp['first_name'] . ' ' . $emp['last_name']) ?>
+ = htmlspecialchars($emp['job_title']) ?>
+ = date('Y-m-d', strtotime($emp['join_date'])) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/hr_employees.php b/hr_employees.php
new file mode 100644
index 0000000..f26ed66
--- /dev/null
+++ b/hr_employees.php
@@ -0,0 +1,336 @@
+ليس لديك صلاحية للوصول إلى هذه الصفحة.";
+ require_once 'includes/footer.php';
+ exit;
+}
+
+$error = '';
+$success = '';
+
+// Handle Form Submissions
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ if (isset($_POST['save_employee'])) {
+ if (!canAdd('hr_employees') && !canEdit('hr_employees')) {
+ $error = "لا تملك صلاحية التعديل.";
+ } else {
+ $id = !empty($_POST['id']) ? $_POST['id'] : null;
+ $first_name = trim($_POST['first_name']);
+ $last_name = trim($_POST['last_name']);
+ $email = trim($_POST['email']);
+ $phone = trim($_POST['phone']);
+ $department_id = !empty($_POST['department_id']) ? $_POST['department_id'] : null;
+ $job_title = trim($_POST['job_title']);
+ $basic_salary = floatval($_POST['basic_salary']);
+ $join_date = $_POST['join_date'];
+ $status = $_POST['status'];
+ $gender = $_POST['gender'];
+ $birth_date = !empty($_POST['birth_date']) ? $_POST['birth_date'] : null;
+
+ if (empty($first_name) || empty($last_name) || empty($join_date)) {
+ $error = "يرجى تعبئة الحقول الإلزامية.";
+ } else {
+ try {
+ if ($id) {
+ // Update
+ $stmt = db()->prepare("UPDATE hr_employees SET first_name=?, last_name=?, email=?, phone=?, department_id=?, job_title=?, basic_salary=?, join_date=?, status=?, gender=?, birth_date=? WHERE id=?");
+ $stmt->execute([$first_name, $last_name, $email, $phone, $department_id, $job_title, $basic_salary, $join_date, $status, $gender, $birth_date, $id]);
+ $success = "تم تحديث بيانات الموظف بنجاح.";
+ } else {
+ // Insert
+ $stmt = db()->prepare("INSERT INTO hr_employees (first_name, last_name, email, phone, department_id, job_title, basic_salary, join_date, status, gender, birth_date) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
+ $stmt->execute([$first_name, $last_name, $email, $phone, $department_id, $job_title, $basic_salary, $join_date, $status, $gender, $birth_date]);
+ $success = "تم إضافة الموظف بنجاح.";
+ }
+ } catch (PDOException $e) {
+ $error = "خطأ في قاعدة البيانات: " . $e->getMessage();
+ }
+ }
+ }
+ } elseif (isset($_POST['delete_employee'])) {
+ if (!canDelete('hr_employees')) {
+ $error = "لا تملك صلاحية الحذف.";
+ } else {
+ $id = $_POST['id'];
+ try {
+ $stmt = db()->prepare("DELETE FROM hr_employees WHERE id = ?");
+ $stmt->execute([$id]);
+ $success = "تم حذف الموظف بنجاح.";
+ } catch (PDOException $e) {
+ $error = "لا يمكن حذف الموظف لوجود سجلات مرتبطة به.";
+ }
+ }
+ } elseif (isset($_POST['save_department'])) {
+ $dept_name = trim($_POST['name']);
+ if (!empty($dept_name)) {
+ $stmt = db()->prepare("INSERT INTO hr_departments (name) VALUES (?)");
+ $stmt->execute([$dept_name]);
+ $success = "تم إضافة القسم بنجاح.";
+ }
+ } elseif (isset($_POST['delete_department'])) {
+ $dept_id = $_POST['id'];
+ try {
+ $stmt = db()->prepare("DELETE FROM hr_departments WHERE id = ?");
+ $stmt->execute([$dept_id]);
+ $success = "تم حذف القسم.";
+ } catch (PDOException $e) {
+ $error = "لا يمكن حذف القسم لأنه مرتبط بموظفين.";
+ }
+ }
+}
+
+// Fetch Departments for Dropdown
+$departments = db()->query("SELECT * FROM hr_departments ORDER BY name")->fetchAll();
+
+?>
+
+
+
إدارة الموظفين
+
+
+
+ الأقسام
+
+
+ إضافة موظف
+
+
+
+
+
+
+ = htmlspecialchars($error) ?>
+
+
+ = htmlspecialchars($success) ?>
+
+
+
+
+
+
+
+
+
+
+ إضافة
+
+
+
+
+ = htmlspecialchars($dept['name']) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ الاسم
+ القسم
+ المسمى الوظيفي
+ تاريخ التعيين
+ الحالة
+ الإجراءات
+
+
+
+ query($sql);
+ while ($row = $stmt->fetch()):
+ ?>
+
+
+
+
+ = mb_substr($row['first_name'], 0, 1) . mb_substr($row['last_name'], 0, 1) ?>
+
+
+
= htmlspecialchars($row['first_name'] . ' ' . $row['last_name']) ?>
+
= htmlspecialchars($row['email']) ?>
+
+
+
+ = htmlspecialchars($row['dept_name'] ?? '-') ?>
+ = htmlspecialchars($row['job_title']) ?>
+ = $row['join_date'] ?>
+
+ 'success',
+ 'terminated' => 'danger',
+ 'resigned' => 'warning',
+ 'on_leave' => 'info',
+ default => 'secondary'
+ };
+ ?>
+ = htmlspecialchars($row['status']) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/hr_holidays.php b/hr_holidays.php
new file mode 100644
index 0000000..ab37e37
--- /dev/null
+++ b/hr_holidays.php
@@ -0,0 +1,183 @@
+ليس لديك صلاحية للوصول إلى هذه الصفحة.";
+ require_once 'includes/footer.php';
+ exit;
+}
+
+$error = '';
+$success = '';
+
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ if (isset($_POST['save_holiday'])) {
+ if (!canAdd('hr_attendance') && !canEdit('hr_attendance')) {
+ $error = "لا تملك صلاحية التعديل.";
+ } else {
+ $id = $_POST['id'] ?? null;
+ $name = trim($_POST['name']);
+ $from = $_POST['date_from'];
+ $to = $_POST['date_to'];
+
+ if (!empty($name) && !empty($from) && !empty($to)) {
+ if ($id) {
+ $stmt = db()->prepare("UPDATE hr_holidays SET name=?, date_from=?, date_to=? WHERE id=?");
+ $stmt->execute([$name, $from, $to, $id]);
+ $success = "تم تحديث العطلة بنجاح.";
+ } else {
+ $stmt = db()->prepare("INSERT INTO hr_holidays (name, date_from, date_to) VALUES (?, ?, ?)");
+ $stmt->execute([$name, $from, $to]);
+ $success = "تم إضافة العطلة بنجاح.";
+ }
+ }
+ }
+ } elseif (isset($_POST['delete_holiday'])) {
+ if (!canDelete('hr_attendance')) {
+ $error = "لا تملك صلاحية الحذف.";
+ } else {
+ $id = $_POST['id'];
+ $stmt = db()->prepare("DELETE FROM hr_holidays WHERE id = ?");
+ $stmt->execute([$id]);
+ $success = "تم حذف العطلة.";
+ }
+ }
+}
+
+$holidays = db()->query("SELECT * FROM hr_holidays ORDER BY date_from DESC")->fetchAll();
+?>
+
+
+
العطلات الرسمية
+
+
+
+ إضافة عطلة
+
+
+
+
+
+
+ = htmlspecialchars($error) ?>
+
+
+ = htmlspecialchars($success) ?>
+
+
+
+
+
+
+
+
+ اسم العطلة
+ من تاريخ
+ إلى تاريخ
+ الحالة
+ إجراءات
+
+
+
+
+ لا توجد عطلات مسجلة.
+
+ = $row['date_from'] && $today <= $row['date_to']) {
+ $status_cls = 'success';
+ $status_txt = 'جارية';
+ } elseif ($today < $row['date_from']) {
+ $status_cls = 'primary';
+ $status_txt = 'قادمة';
+ }
+ ?>
+
+ = htmlspecialchars($row['name']) ?>
+ = $row['date_from'] ?>
+ = $row['date_to'] ?>
+ = $status_txt ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/hr_leaves.php b/hr_leaves.php
new file mode 100644
index 0000000..cc80148
--- /dev/null
+++ b/hr_leaves.php
@@ -0,0 +1,275 @@
+ليس لديك صلاحية للوصول إلى هذه الصفحة.";
+ require_once 'includes/footer.php';
+ exit;
+}
+
+$tab = $_GET['tab'] ?? 'pending';
+$error = '';
+$success = '';
+
+// Handle Form Submissions
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ if (isset($_POST['request_leave'])) {
+ if (!canAdd('hr_leaves')) {
+ $error = "لا تملك صلاحية الإضافة.";
+ } else {
+ $id = $_POST['id'] ?? null; // For edit
+ $emp_id = $_POST['employee_id'];
+ $type = $_POST['leave_type'];
+ $start = $_POST['start_date'];
+ $end = $_POST['end_date'];
+ $reason = trim($_POST['reason']);
+
+ $start_dt = new DateTime($start);
+ $end_dt = new DateTime($end);
+ $days = $end_dt->diff($start_dt)->days + 1;
+
+ if ($days <= 0) {
+ $error = "تاريخ النهاية يجب أن يكون بعد تاريخ البداية.";
+ } else {
+ try {
+ if ($id) {
+ // Update existing request
+ $stmt = db()->prepare("UPDATE hr_leaves SET employee_id=?, leave_type=?, start_date=?, end_date=?, days_count=?, reason=? WHERE id=? AND status='pending'");
+ $stmt->execute([$emp_id, $type, $start, $end, $days, $reason, $id]);
+ $success = "تم تحديث طلب الإجازة بنجاح.";
+ } else {
+ // New request
+ $stmt = db()->prepare("INSERT INTO hr_leaves (employee_id, leave_type, start_date, end_date, days_count, reason, status) VALUES (?, ?, ?, ?, ?, ?, 'pending')");
+ $stmt->execute([$emp_id, $type, $start, $end, $days, $reason]);
+ $success = "تم تقديم طلب الإجازة بنجاح.";
+ }
+ } catch (PDOException $e) {
+ $error = "خطأ: " . $e->getMessage();
+ }
+ }
+ }
+ } elseif (isset($_POST['update_status'])) {
+ if (!canEdit('hr_leaves')) {
+ $error = "لا تملك صلاحية الاعتماد.";
+ } else {
+ $id = $_POST['id'];
+ $status = $_POST['status'];
+ $stmt = db()->prepare("UPDATE hr_leaves SET status = ?, approved_by = ? WHERE id = ?");
+ $stmt->execute([$status, $_SESSION['user_id'], $id]);
+ $success = "تم تحديث حالة الطلب.";
+ }
+ }
+}
+
+// Fetch Employees for Dropdown
+$employees = db()->query("SELECT id, first_name, last_name FROM hr_employees WHERE status = 'active' ORDER BY first_name")->fetchAll();
+
+// Fetch Leaves based on Tab
+$where_clause = $tab === 'pending' ? "WHERE l.status = 'pending'" : "WHERE 1=1";
+$sql = "SELECT l.*, e.first_name, e.last_name, u.full_name as approver_name
+ FROM hr_leaves l
+ JOIN hr_employees e ON l.employee_id = e.id
+ LEFT JOIN users u ON l.approved_by = u.id
+ $where_clause
+ ORDER BY l.created_at DESC";
+$requests = db()->query($sql)->fetchAll();
+
+?>
+
+
+
إدارة الإجازات
+
+
+
+ طلب إجازة جديد
+
+
+
+
+
+
+ = htmlspecialchars($error) ?>
+
+
+ = htmlspecialchars($success) ?>
+
+
+
+
+
+
+
+
+
+
+ الموظف
+ نوع الإجازة
+ الفترة
+ المدة
+ السبب
+ الحالة
+ المعتمد
+ إجراءات
+
+
+
+
+ لا توجد طلبات.
+
+
+
+ = htmlspecialchars($req['first_name'] . ' ' . $req['last_name']) ?>
+
+ 'سنوية',
+ 'sick' => 'مرضية',
+ 'unpaid' => 'بدون راتب',
+ 'maternity' => 'أمومة',
+ 'emergency' => 'طارئة',
+ 'other' => 'أخرى'
+ ];
+ echo $type_map[$req['leave_type']] ?? $req['leave_type'];
+ ?>
+
+
+ من = $req['start_date'] ?> إلى = $req['end_date'] ?>
+
+ = $req['days_count'] ?> يوم
+ = htmlspecialchars($req['reason']) ?>
+
+ 'success',
+ 'rejected' => 'danger',
+ default => 'warning'
+ };
+ $status_txt = match($req['status']) {
+ 'approved' => 'مقبولة',
+ 'rejected' => 'مرفوضة',
+ default => 'معلقة'
+ };
+ ?>
+ = $status_txt ?>
+
+
+ = htmlspecialchars($req['approver_name'] ?? '-') ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ الموظف
+
+ -- اختر الموظف --
+
+ = htmlspecialchars($emp['first_name'] . ' ' . $emp['last_name']) ?>
+
+
+
+
+ نوع الإجازة
+
+ سنوية
+ مرضية
+ طارئة
+ بدون راتب
+ أمومة
+ أخرى
+
+
+
+
+ السبب
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/hr_payroll.php b/hr_payroll.php
new file mode 100644
index 0000000..4ef13cf
--- /dev/null
+++ b/hr_payroll.php
@@ -0,0 +1,263 @@
+ليس لديك صلاحية للوصول إلى هذه الصفحة.";
+ require_once 'includes/footer.php';
+ exit;
+}
+
+$month = $_GET['month'] ?? date('m');
+$year = $_GET['year'] ?? date('Y');
+$error = '';
+$success = '';
+
+// Handle Payroll Actions
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ if (isset($_POST['generate_payroll'])) {
+ if (!canAdd('hr_payroll')) {
+ $error = "لا تملك صلاحية التوليد.";
+ } else {
+ $gen_month = $_POST['month'];
+ $gen_year = $_POST['year'];
+
+ // Get all active employees
+ $employees = db()->query("SELECT id, basic_salary FROM hr_employees WHERE status = 'active'")->fetchAll();
+ $count = 0;
+
+ foreach ($employees as $emp) {
+ // Check if already exists
+ $stmt = db()->prepare("SELECT id FROM hr_payroll WHERE employee_id = ? AND month = ? AND year = ?");
+ $stmt->execute([$emp['id'], $gen_month, $gen_year]);
+ if ($stmt->fetch()) continue; // Skip if exists
+
+ // Calculate Absent Deductions
+ $stmt = db()->prepare("SELECT COUNT(*) FROM hr_attendance WHERE employee_id = ? AND status = 'absent' AND MONTH(date) = ? AND YEAR(date) = ?");
+ $stmt->execute([$emp['id'], $gen_month, $gen_year]);
+ $absent_days = $stmt->fetchColumn();
+
+ $daily_rate = $emp['basic_salary'] / 30;
+ $deductions = round($absent_days * $daily_rate, 2);
+ $net = $emp['basic_salary'] - $deductions;
+
+ $stmt = db()->prepare("INSERT INTO hr_payroll (employee_id, month, year, basic_salary, deductions, net_salary, status) VALUES (?, ?, ?, ?, ?, ?, 'pending')");
+ $stmt->execute([$emp['id'], $gen_month, $gen_year, $emp['basic_salary'], $deductions, $net]);
+ $count++;
+ }
+ $success = "تم توليد الرواتب لـ $count موظف.";
+ }
+ } elseif (isset($_POST['update_payroll'])) {
+ if (!canEdit('hr_payroll')) {
+ $error = "لا تملك صلاحية التعديل.";
+ } else {
+ $id = $_POST['id'];
+ $bonuses = floatval($_POST['bonuses']);
+ $deductions = floatval($_POST['deductions']);
+ $status = $_POST['status'];
+
+ // Recalculate Net
+ $stmt = db()->prepare("SELECT basic_salary FROM hr_payroll WHERE id = ?");
+ $stmt->execute([$id]);
+ $current = $stmt->fetch();
+
+ if ($current) {
+ $net = $current['basic_salary'] + $bonuses - $deductions;
+ $payment_date = ($status == 'paid') ? date('Y-m-d') : null;
+
+ $stmt = db()->prepare("UPDATE hr_payroll SET bonuses = ?, deductions = ?, net_salary = ?, status = ?, payment_date = ? WHERE id = ?");
+ $stmt->execute([$bonuses, $deductions, $net, $status, $payment_date, $id]);
+ $success = "تم تحديث الراتب.";
+ }
+ }
+ }
+}
+
+// Fetch Payroll Records
+$sql = "SELECT p.*, e.first_name, e.last_name, e.job_title
+ FROM hr_payroll p
+ JOIN hr_employees e ON p.employee_id = e.id
+ WHERE p.month = ? AND p.year = ?
+ ORDER BY e.first_name";
+$stmt = db()->prepare($sql);
+$stmt->execute([$month, $year]);
+$payrolls = $stmt->fetchAll();
+
+// Calculate Totals
+$total_salaries = 0;
+foreach ($payrolls as $p) $total_salaries += $p['net_salary'];
+
+?>
+
+
+
مسير الرواتب
+
+
+
+
+ >= date('F', mktime(0, 0, 0, $m, 1)) ?>
+
+
+
+
+ >= $y ?>
+
+
+ عرض
+
+
+
+
+ توليد الرواتب
+
+
+
+
+
+
+ = htmlspecialchars($error) ?>
+
+
+ = htmlspecialchars($success) ?>
+
+
+
+
+
+
+
إجمالي الرواتب للشهر
+ = number_format($total_salaries, 2) ?>
+
+
+
+
+
+
+
+
+
+
+
+ الموظف
+ الراتب الأساسي
+ إضافي
+ خصومات
+ الصافي
+ الحالة
+ إجراءات
+
+
+
+
+ لا توجد بيانات لهذا الشهر. اضغط على "توليد الرواتب" للبدء.
+
+
+
+
+ = htmlspecialchars($row['first_name'] . ' ' . $row['last_name']) ?>
+ = htmlspecialchars($row['job_title']) ?>
+
+ = number_format($row['basic_salary'], 2) ?>
+ = number_format($row['bonuses'], 2) ?>
+ = number_format($row['deductions'], 2) ?>
+ = number_format($row['net_salary'], 2) ?>
+
+
+ = $row['status'] == 'paid' ? 'مدفوع' : 'معلق' ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
سيتم حساب الرواتب لجميع الموظفين النشطين لشهر: = $month ?> / = $year ?>
+
سيتم احتساب الخصومات تلقائياً بناءً على أيام الغياب المسجلة.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ مكافآت / إضافي
+
+
+
+ خصومات
+
+
+
+ الحالة
+
+ معلق
+ مدفوع
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/hr_reports.php b/hr_reports.php
new file mode 100644
index 0000000..0d990c2
--- /dev/null
+++ b/hr_reports.php
@@ -0,0 +1,170 @@
+ليس لديك صلاحية للوصول إلى هذه الصفحة.";
+ require_once 'includes/footer.php';
+ exit;
+}
+
+$report_type = $_GET['type'] ?? 'attendance_summary';
+$month = $_GET['month'] ?? date('m');
+$year = $_GET['year'] ?? date('Y');
+
+?>
+
+
+
تقارير الموارد البشرية
+
+
+ طباعة
+
+
+
+
+
+
+
+
+
+
+
+
+
+ الشهر
+
+
+ >= date('F', mktime(0, 0, 0, $m, 1)) ?>
+
+
+
+
+ السنة
+
+
+ >= $y ?>
+
+
+
+
+ عرض التقرير
+
+
+
+
+
+
+
+
+
+
+
+
+ الموظف
+ أيام الحضور
+ أيام الغياب
+ التأخير
+ إجازات
+ نسبة الحضور
+
+
+
+ prepare($sql);
+ $stmt->execute([$month, $year]);
+ $report_data = $stmt->fetchAll();
+
+ foreach ($report_data as $row):
+ $total_days_recorded = $row['present_days'] + $row['absent_days'] + $row['late_days'] + $row['leave_days'];
+ $attendance_rate = $total_days_recorded > 0
+ ? round((($row['present_days'] + $row['late_days']) / $total_days_recorded) * 100, 1)
+ : 0;
+ ?>
+
+ = htmlspecialchars($row['first_name'] . ' ' . $row['last_name']) ?>
+ = $row['present_days'] ?>
+ = $row['absent_days'] ?>
+ = $row['late_days'] ?>
+ = $row['leave_days'] ?>
+ = $attendance_rate ?>%
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ الرقم الوظيفي
+ الاسم
+ القسم
+ الوظيفة
+ تاريخ التعيين
+ الراتب الأساسي
+ رقم الهاتف
+
+
+
+ query("SELECT e.*, d.name as dept_name FROM hr_employees e LEFT JOIN hr_departments d ON e.department_id = d.id WHERE e.status = 'active' ORDER BY e.id")->fetchAll();
+ foreach ($employees as $emp):
+ ?>
+
+ #= $emp['id'] ?>
+ = htmlspecialchars($emp['first_name'] . ' ' . $emp['last_name']) ?>
+ = htmlspecialchars($emp['dept_name'] ?? '-') ?>
+ = htmlspecialchars($emp['job_title']) ?>
+ = $emp['join_date'] ?>
+ = number_format($emp['basic_salary'], 2) ?>
+ = htmlspecialchars($emp['phone']) ?>
+
+
+
+
+
+
+
+
+
+
+
diff --git a/includes/header.php b/includes/header.php
index fae0721..7150c72 100644
--- a/includes/header.php
+++ b/includes/header.php
@@ -114,6 +114,27 @@ if (isLoggedIn()) {
if (!isLoggedIn() && basename($_SERVER['PHP_SELF']) !== 'login.php' && basename($_SERVER['PHP_SELF']) !== 'forgot_password.php' && basename($_SERVER['PHP_SELF']) !== 'install.php') {
redirect('login.php');
}
+
+// Determine active groups
+$cp = basename($_SERVER['PHP_SELF']);
+
+$mail_pages = ['inbound.php', 'outbound.php', 'internal_inbox.php', 'internal_outbox.php'];
+$is_mail_open = in_array($cp, $mail_pages);
+
+$acct_pages = ['accounting.php', 'trial_balance.php', 'balance_sheet.php', 'accounts.php'];
+$is_acct_open = in_array($cp, $acct_pages);
+
+$hr_pages = ['hr_dashboard.php', 'hr_employees.php', 'hr_attendance.php', 'hr_leaves.php', 'hr_holidays.php', 'hr_payroll.php', 'hr_reports.php'];
+$is_hr_open = in_array($cp, $hr_pages);
+
+$stock_pages = ['stock_dashboard.php', 'stock_items.php', 'stock_in.php', 'stock_out.php', 'stock_lending.php', 'stock_reports.php', 'stock_settings.php'];
+$is_stock_open = in_array($cp, $stock_pages);
+
+$report_pages = ['overdue_report.php'];
+$is_report_open = in_array($cp, $report_pages);
+
+$admin_pages = ['index.php', 'users.php', 'charity-settings.php'];
+$is_admin_open = in_array($cp, $admin_pages);
?>
@@ -244,104 +265,280 @@ if (!isLoggedIn() && basename($_SERVER['PHP_SELF']) !== 'login.php' && basename(
diff --git a/stock_dashboard.php b/stock_dashboard.php
new file mode 100644
index 0000000..06c559a
--- /dev/null
+++ b/stock_dashboard.php
@@ -0,0 +1,149 @@
+عذراً، ليس لديك صلاحية الوصول لهذه الصفحة.';
+ require_once __DIR__ . '/includes/footer.php';
+ exit;
+}
+
+// Stats
+$total_items = db()->query("SELECT COUNT(*) FROM stock_items")->fetchColumn();
+$total_stores = db()->query("SELECT COUNT(*) FROM stock_stores")->fetchColumn();
+$low_stock_count = db()->query("
+ SELECT COUNT(*) FROM stock_quantities q
+ JOIN stock_items i ON q.item_id = i.id
+ WHERE q.quantity <= i.min_quantity
+")->fetchColumn();
+
+// Recent Transactions
+$stmt = db()->query("
+ SELECT t.*, i.name as item_name, s.name as store_name, u.full_name as user_name
+ FROM stock_transactions t
+ JOIN stock_items i ON t.item_id = i.id
+ JOIN stock_stores s ON t.store_id = s.id
+ LEFT JOIN users u ON t.user_id = u.id
+ ORDER BY t.created_at DESC LIMIT 10
+");
+$recent_transactions = $stmt->fetchAll();
+
+?>
+
+
+
+
+
+
+
+
+
+
+
+
+
إجمالي الأصناف
+ = number_format($total_items) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
المستودعات
+ = number_format($total_stores) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
تنبيهات المخزون المنخفض
+ = number_format($low_stock_count) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ #
+ النوع
+ الصنف
+ المستودع
+ الكمية
+ بواسطة
+ التاريخ
+
+
+
+
+
+ لا توجد حركات حديثة
+
+
+
+
+ = $t['id'] ?>
+
+ ['bg-success', 'توريد'],
+ 'out' => ['bg-danger', 'صرف'],
+ 'damage' => ['bg-dark', 'تالف'],
+ 'lend' => ['bg-info text-dark', 'إعارة'],
+ 'return' => ['bg-primary', 'إرجاع'],
+ 'transfer' => ['bg-secondary', 'نقل']
+ ];
+ $b = $badges[$t['transaction_type']] ?? ['bg-secondary', $t['transaction_type']];
+ ?>
+ = $b[1] ?>
+
+ = htmlspecialchars($t['item_name']) ?>
+ = htmlspecialchars($t['store_name']) ?>
+ = number_format($t['quantity'], 2) ?>
+ = htmlspecialchars($t['user_name'] ?? '-') ?>
+ = date('Y-m-d H:i', strtotime($t['created_at'])) ?>
+
+
+
+
+
+
+
+
+
diff --git a/stock_in.php b/stock_in.php
new file mode 100644
index 0000000..0f71bf0
--- /dev/null
+++ b/stock_in.php
@@ -0,0 +1,136 @@
+عذراً، ليس لديك صلاحية الوصول لهذه الصفحة.';
+ require_once __DIR__ . '/includes/footer.php';
+ exit;
+}
+
+$success = '';
+$error = '';
+
+// Handle Form Submission
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ $store_id = $_POST['store_id'] ?? null;
+ $item_id = $_POST['item_id'] ?? null;
+ $quantity = $_POST['quantity'] ?? 0;
+ $reference = $_POST['reference'] ?? '';
+ $notes = $_POST['notes'] ?? '';
+
+ if ($store_id && $item_id && $quantity > 0) {
+ try {
+ $pdo = db();
+ $pdo->beginTransaction();
+
+ // 1. Create Transaction
+ $stmt = $pdo->prepare("INSERT INTO stock_transactions (transaction_type, store_id, item_id, quantity, user_id, reference, notes) VALUES ('in', ?, ?, ?, ?, ?, ?)");
+ $stmt->execute([$store_id, $item_id, $quantity, $_SESSION['user_id'], $reference, $notes]);
+
+ // 2. Update Quantity
+ // Check if record exists
+ $check = $pdo->prepare("SELECT id, quantity FROM stock_quantities WHERE store_id = ? AND item_id = ?");
+ $check->execute([$store_id, $item_id]);
+ $exists = $check->fetch();
+
+ if ($exists) {
+ $new_qty = $exists['quantity'] + $quantity;
+ $update = $pdo->prepare("UPDATE stock_quantities SET quantity = ? WHERE id = ?");
+ $update->execute([$new_qty, $exists['id']]);
+ } else {
+ $insert = $pdo->prepare("INSERT INTO stock_quantities (store_id, item_id, quantity) VALUES (?, ?, ?)");
+ $insert->execute([$store_id, $item_id, $quantity]);
+ }
+
+ $pdo->commit();
+ $success = 'تم تسجيل عملية التوريد بنجاح';
+ } catch (PDOException $e) {
+ $pdo->rollBack();
+ $error = 'حدث خطأ: ' . $e->getMessage();
+ }
+ } else {
+ $error = 'يرجى تعبئة جميع الحقول المطلوبة';
+ }
+}
+
+// Fetch Data for Dropdowns
+$stores = db()->query("SELECT * FROM stock_stores ORDER BY name ASC")->fetchAll();
+$items = db()->query("SELECT * FROM stock_items ORDER BY name ASC")->fetchAll();
+
+?>
+
+
+
توريد مخزون (وارد)
+
+ عودة للوحة التحكم
+
+
+
+
+
+ = $success ?>
+
+
+
+
+
+
+ = $error ?>
+
+
+
+
+
+
+
+
+
+
+
+ المستودع *
+
+ -- اختر المستودع --
+
+ = htmlspecialchars($store['name']) ?>
+
+
+
+
+ الصنف *
+
+ -- اختر الصنف --
+
+ = htmlspecialchars($item['name']) ?> (= htmlspecialchars($item['sku'] ?: '-') ?>)
+
+
+
+
+
+
+
+
+ ملاحظات
+
+
+
+
+
+ حفظ عملية التوريد
+
+
+
+
+
+
+
+
+
diff --git a/stock_items.php b/stock_items.php
new file mode 100644
index 0000000..4722961
--- /dev/null
+++ b/stock_items.php
@@ -0,0 +1,242 @@
+عذراً، ليس لديك صلاحية الوصول لهذه الصفحة.';
+ require_once __DIR__ . '/includes/footer.php';
+ exit;
+}
+
+$success = '';
+$error = '';
+
+// Handle Actions
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ $action = $_POST['action'] ?? '';
+ $id = $_POST['id'] ?? 0;
+ $name = $_POST['name'] ?? '';
+ $sku = $_POST['sku'] ?? '';
+ $category_id = $_POST['category_id'] ?? null;
+ $min_quantity = $_POST['min_quantity'] ?? 0;
+ $unit = $_POST['unit'] ?? 'piece';
+ $description = $_POST['description'] ?? '';
+
+ if ($action === 'add' && canAdd('stock_items')) {
+ if ($name) {
+ $stmt = db()->prepare("INSERT INTO stock_items (name, sku, category_id, min_quantity, unit, description) VALUES (?, ?, ?, ?, ?, ?)");
+ if ($stmt->execute([$name, $sku, $category_id, $min_quantity, $unit, $description])) {
+ $success = 'تم إضافة الصنف بنجاح';
+ } else {
+ $error = 'حدث خطأ أثناء الإضافة';
+ }
+ }
+ } elseif ($action === 'edit' && canEdit('stock_items')) {
+ if ($name && $id) {
+ $stmt = db()->prepare("UPDATE stock_items SET name=?, sku=?, category_id=?, min_quantity=?, unit=?, description=? WHERE id=?");
+ if ($stmt->execute([$name, $sku, $category_id, $min_quantity, $unit, $description, $id])) {
+ $success = 'تم تحديث الصنف بنجاح';
+ } else {
+ $error = 'حدث خطأ أثناء التحديث';
+ }
+ }
+ } elseif ($action === 'delete' && canDelete('stock_items')) {
+ if ($id) {
+ $stmt = db()->prepare("DELETE FROM stock_items WHERE id=?");
+ if ($stmt->execute([$id])) {
+ $success = 'تم حذف الصنف بنجاح';
+ } else {
+ $error = 'لا يمكن حذف الصنف لوجود حركات مرتبطة به';
+ }
+ }
+ }
+}
+
+// Fetch Items with Category Name and Total Quantity
+$query = "
+ SELECT i.*, c.name as category_name,
+ (SELECT SUM(quantity) FROM stock_quantities q WHERE q.item_id = i.id) as total_quantity
+ FROM stock_items i
+ LEFT JOIN stock_categories c ON i.category_id = c.id
+ ORDER BY i.name ASC
+";
+$items = db()->query($query)->fetchAll();
+
+// Fetch Categories for Dropdown
+$categories = db()->query("SELECT * FROM stock_categories ORDER BY name ASC")->fetchAll();
+
+?>
+
+
+
إدارة الأصناف
+
+
+ إضافة صنف جديد
+
+
+
+
+
+
+ = $success ?>
+
+
+
+
+
+
+ = $error ?>
+
+
+
+
+
+
+
+
+
+
+ اسم الصنف
+ الرمز (SKU)
+ التصنيف
+ الكمية الحالية
+ الحد الأدنى
+ الوحدة
+ الإجراءات
+
+
+
+
+
+ = htmlspecialchars($item['name']) ?>
+ = htmlspecialchars($item['sku'] ?? '-') ?>
+ = htmlspecialchars($item['category_name'] ?? 'عام') ?>
+
+
+ = number_format($qty, 2) ?>
+
+ = number_format($item['min_quantity']) ?>
+ = htmlspecialchars($item['unit']) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ اسم الصنف *
+
+
+
+
+
+ الرمز (SKU)
+
+
+
+ التصنيف
+
+ -- اختر --
+
+ = htmlspecialchars($cat['name']) ?>
+
+
+
+
+
+
+
+
+ الوصف
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/stock_lending.php b/stock_lending.php
new file mode 100644
index 0000000..ba78274
--- /dev/null
+++ b/stock_lending.php
@@ -0,0 +1,251 @@
+عذراً، ليس لديك صلاحية الوصول لهذه الصفحة.';
+ require_once __DIR__ . '/includes/footer.php';
+ exit;
+}
+
+$success = '';
+$error = '';
+
+// Handle Actions
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ $action = $_POST['action'] ?? '';
+
+ if ($action === 'lend') {
+ $store_id = $_POST['store_id'];
+ $item_id = $_POST['item_id'];
+ $quantity = $_POST['quantity'];
+ $borrower = $_POST['borrower_name'];
+ $phone = $_POST['borrower_phone'];
+ $date = $_POST['expected_return_date'];
+
+ try {
+ $pdo = db();
+ $pdo->beginTransaction();
+
+ // Check stock
+ $check = $pdo->prepare("SELECT id, quantity FROM stock_quantities WHERE store_id = ? AND item_id = ? FOR UPDATE");
+ $check->execute([$store_id, $item_id]);
+ $stock = $check->fetch();
+
+ if (!$stock || $stock['quantity'] < $quantity) {
+ throw new Exception("الكمية غير متوفرة للإعارة");
+ }
+
+ // Deduct Stock
+ $pdo->prepare("UPDATE stock_quantities SET quantity = quantity - ? WHERE id = ?")->execute([$quantity, $stock['id']]);
+
+ // Create Transaction
+ $stmt = $pdo->prepare("INSERT INTO stock_transactions (transaction_type, store_id, item_id, quantity, user_id, reference) VALUES ('lend', ?, ?, ?, ?, ?)");
+ $stmt->execute([$store_id, $item_id, $quantity, $_SESSION['user_id'], "إعارة: $borrower"]);
+ $trans_id = $pdo->lastInsertId();
+
+ // Create Lending Record
+ $stmt = $pdo->prepare("INSERT INTO stock_lending (transaction_id, borrower_name, borrower_phone, expected_return_date, status) VALUES (?, ?, ?, ?, 'active')");
+ $stmt->execute([$trans_id, $borrower, $phone, $date]);
+
+ $pdo->commit();
+ $success = 'تم تسجيل الإعارة بنجاح';
+ } catch (Exception $e) {
+ $pdo->rollBack();
+ $error = $e->getMessage();
+ }
+ } elseif ($action === 'return') {
+ $lending_id = $_POST['lending_id'];
+
+ try {
+ $pdo = db();
+ $pdo->beginTransaction();
+
+ // Get Lending Info
+ $lend = $pdo->query("SELECT l.*, t.store_id, t.item_id, t.quantity FROM stock_lending l JOIN stock_transactions t ON l.transaction_id = t.id WHERE l.id = $lending_id")->fetch();
+
+ if ($lend && $lend['status'] === 'active') {
+ // Add Stock Back
+ $check = $pdo->prepare("SELECT id FROM stock_quantities WHERE store_id = ? AND item_id = ?");
+ $check->execute([$lend['store_id'], $lend['item_id']]);
+ $s_id = $check->fetchColumn();
+
+ if ($s_id) {
+ $pdo->prepare("UPDATE stock_quantities SET quantity = quantity + ? WHERE id = ?")->execute([$lend['quantity'], $s_id]);
+ } else {
+ $pdo->prepare("INSERT INTO stock_quantities (store_id, item_id, quantity) VALUES (?, ?, ?)")->execute([$lend['store_id'], $lend['item_id'], $lend['quantity']]);
+ }
+
+ // Create Return Transaction
+ $stmt = $pdo->prepare("INSERT INTO stock_transactions (transaction_type, store_id, item_id, quantity, user_id, reference) VALUES ('return', ?, ?, ?, ?, ?)");
+ $stmt->execute([$lend['store_id'], $lend['item_id'], $lend['quantity'], $_SESSION['user_id'], "إرجاع إعارة: " . $lend['borrower_name']]);
+ $ret_id = $pdo->lastInsertId();
+
+ // Update Lending Record
+ $pdo->prepare("UPDATE stock_lending SET status = 'returned', return_transaction_id = ? WHERE id = ?")->execute([$ret_id, $lending_id]);
+
+ $pdo->commit();
+ $success = 'تم إرجاع المواد بنجاح';
+ }
+ } catch (Exception $e) {
+ $pdo->rollBack();
+ $error = $e->getMessage();
+ }
+ }
+}
+
+// Fetch Active Loans
+$loans = db()->query("
+ SELECT l.*, i.name as item_name, s.name as store_name, t.quantity, t.created_at as lend_date
+ FROM stock_lending l
+ JOIN stock_transactions t ON l.transaction_id = t.id
+ JOIN stock_items i ON t.item_id = i.id
+ JOIN stock_stores s ON t.store_id = s.id
+ WHERE l.status = 'active'
+ ORDER BY l.expected_return_date ASC
+")->fetchAll();
+
+$stores = db()->query("SELECT * FROM stock_stores ORDER BY name ASC")->fetchAll();
+$items = db()->query("SELECT * FROM stock_items ORDER BY name ASC")->fetchAll();
+
+?>
+
+
+
إعارة المواد
+
+ إعارة جديدة
+
+
+
+
+
+ = $success ?>
+
+
+
+
+
+
+ = $error ?>
+
+
+
+
+
+
+
+
+
+
+ المستعير
+ المادة
+ الكمية
+ تاريخ الإعارة
+ تاريخ الإرجاع المتوقع
+ الحالة
+ الإجراءات
+
+
+
+
+
+ لا توجد إعارات نشطة حالياً
+
+
+
+
+
+ = htmlspecialchars($loan['borrower_name']) ?>
+ = htmlspecialchars($loan['borrower_phone']) ?>
+
+ = htmlspecialchars($loan['item_name']) ?>
+ = number_format($loan['quantity'], 2) ?>
+ = date('Y-m-d', strtotime($loan['lend_date'])) ?>
+
+
+ = date('Y-m-d', $due) ?>
+
+ نشط
+
+
+
+
+
+ تسجيل إرجاع
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ المستودع
+
+
+ = htmlspecialchars($store['name']) ?>
+
+
+
+
+ المادة
+
+
+ = htmlspecialchars($item['name']) ?>
+
+
+
+
+
+
+ الكمية
+
+
+
+
+
+
+ تاريخ الإرجاع المتوقع
+
+
+
+
+
+
+
+
+
+
diff --git a/stock_out.php b/stock_out.php
new file mode 100644
index 0000000..7d1dbb6
--- /dev/null
+++ b/stock_out.php
@@ -0,0 +1,144 @@
+عذراً، ليس لديك صلاحية الوصول لهذه الصفحة.';
+ require_once __DIR__ . '/includes/footer.php';
+ exit;
+}
+
+$success = '';
+$error = '';
+
+// Handle Form Submission
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ $store_id = $_POST['store_id'] ?? null;
+ $item_id = $_POST['item_id'] ?? null;
+ $quantity = $_POST['quantity'] ?? 0;
+ $type = $_POST['type'] ?? 'out'; // 'out' or 'damage'
+ $reference = $_POST['reference'] ?? '';
+ $notes = $_POST['notes'] ?? '';
+
+ if ($store_id && $item_id && $quantity > 0) {
+ try {
+ $pdo = db();
+ // Check availability first
+ $check = $pdo->prepare("SELECT id, quantity FROM stock_quantities WHERE store_id = ? AND item_id = ?");
+ $check->execute([$store_id, $item_id]);
+ $stock = $check->fetch();
+
+ if (!$stock || $stock['quantity'] < $quantity) {
+ $error = 'الكمية غير متوفرة في المستودع المحدد. الكمية الحالية: ' . ($stock['quantity'] ?? 0);
+ } else {
+ $pdo->beginTransaction();
+
+ // 1. Create Transaction
+ $stmt = $pdo->prepare("INSERT INTO stock_transactions (transaction_type, store_id, item_id, quantity, user_id, reference, notes) VALUES (?, ?, ?, ?, ?, ?, ?)");
+ $stmt->execute([$type, $store_id, $item_id, $quantity, $_SESSION['user_id'], $reference, $notes]);
+
+ // 2. Update Quantity
+ $new_qty = $stock['quantity'] - $quantity;
+ $update = $pdo->prepare("UPDATE stock_quantities SET quantity = ? WHERE id = ?");
+ $update->execute([$new_qty, $stock['id']]);
+
+ $pdo->commit();
+ $success = 'تم تسجيل عملية الصرف بنجاح';
+ }
+ } catch (PDOException $e) {
+ if ($pdo->inTransaction()) $pdo->rollBack();
+ $error = 'حدث خطأ: ' . $e->getMessage();
+ }
+ } else {
+ $error = 'يرجى تعبئة جميع الحقول المطلوبة';
+ }
+}
+
+// Fetch Data for Dropdowns
+$stores = db()->query("SELECT * FROM stock_stores ORDER BY name ASC")->fetchAll();
+$items = db()->query("SELECT * FROM stock_items ORDER BY name ASC")->fetchAll();
+
+?>
+
+
+
صرف مخزون (صادر)
+
+ عودة للوحة التحكم
+
+
+
+
+
+ = $success ?>
+
+
+
+
+
+
+ = $error ?>
+
+
+
+
+
+
+
+
+
+
+
+ المستودع *
+
+ -- اختر المستودع --
+
+ = htmlspecialchars($store['name']) ?>
+
+
+
+
+ الصنف *
+
+ -- اختر الصنف --
+
+ = htmlspecialchars($item['name']) ?> (= htmlspecialchars($item['sku'] ?: '-') ?>)
+
+
+
+
+
+
+
+ الكمية *
+
+
+
+ نوع العملية
+
+ صرف (استهلاك/بيع)
+ تالف / منتهي الصلاحية
+
+
+
+
+
+ رقم المرجع / للمستلم
+
+
+
+
+ ملاحظات
+
+
+
+
+
+ تنفيذ الصرف
+
+
+
+
+
+
+
+
+
diff --git a/stock_reports.php b/stock_reports.php
new file mode 100644
index 0000000..5229830
--- /dev/null
+++ b/stock_reports.php
@@ -0,0 +1,173 @@
+عذراً، ليس لديك صلاحية الوصول لهذه الصفحة.';
+ require_once __DIR__ . '/includes/footer.php';
+ exit;
+}
+
+$start_date = $_GET['start_date'] ?? date('Y-m-01');
+$end_date = $_GET['end_date'] ?? date('Y-m-d');
+$store_id = $_GET['store_id'] ?? '';
+$item_id = $_GET['item_id'] ?? '';
+$type = $_GET['type'] ?? '';
+
+// Build Query
+$sql = "
+ SELECT t.*, i.name as item_name, s.name as store_name, u.full_name as user_name
+ FROM stock_transactions t
+ JOIN stock_items i ON t.item_id = i.id
+ JOIN stock_stores s ON t.store_id = s.id
+ LEFT JOIN users u ON t.user_id = u.id
+ WHERE DATE(t.created_at) BETWEEN ? AND ?
+";
+$params = [$start_date, $end_date];
+
+if ($store_id) {
+ $sql .= " AND t.store_id = ?";
+ $params[] = $store_id;
+}
+if ($item_id) {
+ $sql .= " AND t.item_id = ?";
+ $params[] = $item_id;
+}
+if ($type) {
+ $sql .= " AND t.transaction_type = ?";
+ $params[] = $type;
+}
+
+$sql .= " ORDER BY t.created_at DESC";
+
+$stmt = db()->prepare($sql);
+$stmt->execute($params);
+$transactions = $stmt->fetchAll();
+
+$stores = db()->query("SELECT * FROM stock_stores ORDER BY name ASC")->fetchAll();
+$items = db()->query("SELECT * FROM stock_items ORDER BY name ASC")->fetchAll();
+
+?>
+
+
+
تقارير حركة المخزون
+
+ طباعة التقرير
+
+
+
+
+
+
+
+ من تاريخ
+
+
+
+ إلى تاريخ
+
+
+
+ المستودع
+
+ الكل
+
+ >= htmlspecialchars($s['name']) ?>
+
+
+
+
+ الصنف
+
+ الكل
+
+ >= htmlspecialchars($i['name']) ?>
+
+
+
+
+
+ عرض
+
+
+
+
+
+
+
+
+
+
تقرير حركة المخزون
+
من = $start_date ?> إلى = $end_date ?>
+
+
+
+
+
+
+ #
+ التاريخ
+ النوع
+ المستودع
+ الصنف
+ الكمية
+ المستخدم
+ ملاحظات
+
+
+
+
+
+ لا توجد بيانات للعرض
+
+
+
+
+ = $t['id'] ?>
+ = date('Y-m-d H:i', strtotime($t['created_at'])) ?>
+
+ 'توريد',
+ 'out' => 'صرف',
+ 'damage' => 'تالف',
+ 'lend' => 'إعارة',
+ 'return' => 'إرجاع',
+ 'transfer' => 'نقل'
+ ];
+ echo $types[$t['transaction_type']] ?? $t['transaction_type'];
+ ?>
+
+ = htmlspecialchars($t['store_name']) ?>
+ = htmlspecialchars($t['item_name']) ?>
+
+
+ += number_format($t['quantity'], 2) ?>
+
+ -= number_format($t['quantity'], 2) ?>
+
+
+ = htmlspecialchars($t['user_name'] ?? '-') ?>
+
+
+ المرجع: = htmlspecialchars($t['reference']) ?>
+
+ = htmlspecialchars($t['notes'] ?? '') ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/stock_settings.php b/stock_settings.php
new file mode 100644
index 0000000..55db2eb
--- /dev/null
+++ b/stock_settings.php
@@ -0,0 +1,265 @@
+عذراً، ليس لديك صلاحية الوصول لهذه الصفحة.';
+ require_once __DIR__ . '/includes/footer.php';
+ exit;
+}
+
+$success = '';
+$error = '';
+
+// Handle Actions
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ $action = $_POST['action'] ?? '';
+ $type = $_POST['type'] ?? ''; // 'store' or 'category'
+ $id = $_POST['id'] ?? 0;
+ $name = $_POST['name'] ?? '';
+ $desc = $_POST['description'] ?? ''; // location or description
+
+ if ($action === 'add' && canAdd('stock_settings')) {
+ if ($name) {
+ if ($type === 'store') {
+ $stmt = db()->prepare("INSERT INTO stock_stores (name, location) VALUES (?, ?)");
+ $stmt->execute([$name, $desc]);
+ } else {
+ $stmt = db()->prepare("INSERT INTO stock_categories (name, description) VALUES (?, ?)");
+ $stmt->execute([$name, $desc]);
+ }
+ $success = 'تم الإضافة بنجاح';
+ }
+ } elseif ($action === 'edit' && canEdit('stock_settings')) {
+ if ($name && $id) {
+ if ($type === 'store') {
+ $stmt = db()->prepare("UPDATE stock_stores SET name=?, location=? WHERE id=?");
+ $stmt->execute([$name, $desc, $id]);
+ } else {
+ $stmt = db()->prepare("UPDATE stock_categories SET name=?, description=? WHERE id=?");
+ $stmt->execute([$name, $desc, $id]);
+ }
+ $success = 'تم التحديث بنجاح';
+ }
+ } elseif ($action === 'delete' && canDelete('stock_settings')) {
+ if ($id) {
+ try {
+ if ($type === 'store') {
+ $stmt = db()->prepare("DELETE FROM stock_stores WHERE id=?");
+ $stmt->execute([$id]);
+ } else {
+ $stmt = db()->prepare("DELETE FROM stock_categories WHERE id=?");
+ $stmt->execute([$id]);
+ }
+ $success = 'تم الحذف بنجاح';
+ } catch (PDOException $e) {
+ $error = 'لا يمكن الحذف لوجود بيانات مرتبطة';
+ }
+ }
+ }
+}
+
+$stores = db()->query("SELECT * FROM stock_stores ORDER BY name ASC")->fetchAll();
+$categories = db()->query("SELECT * FROM stock_categories ORDER BY name ASC")->fetchAll();
+
+?>
+
+
+
إعدادات المخزون
+
+
+
+
+ = $success ?>
+
+
+
+
+
+
+ = $error ?>
+
+
+
+
+
+
+ المستودعات
+
+
+ التصنيفات
+
+
+
+
+
+
+
+
قائمة المستودعات
+
+
+ إضافة مستودع
+
+
+
+
+
+
+
+
+ #
+ اسم المستودع
+ الموقع / العنوان
+ تاريخ الإضافة
+ الإجراءات
+
+
+
+
+
+ = $s['id'] ?>
+ = htmlspecialchars($s['name']) ?>
+ = htmlspecialchars($s['location'] ?? '-') ?>
+ = date('Y-m-d', strtotime($s['created_at'])) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
تصنيفات المواد
+
+
+ إضافة تصنيف
+
+
+
+
+
+
+
+
+ #
+ اسم التصنيف
+ الوصف
+ تاريخ الإضافة
+ الإجراءات
+
+
+
+
+
+ = $c['id'] ?>
+ = htmlspecialchars($c['name']) ?>
+ = htmlspecialchars($c['description'] ?? '-') ?>
+ = date('Y-m-d', strtotime($c['created_at'])) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ الاسم *
+
+
+
+
+ الوصف / الموقع
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/users.php b/users.php
index 9598a5c..a9082f0 100644
--- a/users.php
+++ b/users.php
@@ -15,7 +15,20 @@ $modules = [
'users' => 'إدارة المستخدمين',
'settings' => 'الإعدادات',
'reports' => 'التقارير',
- 'accounting' => 'المحاسبة'
+ 'accounting' => 'المحاسبة',
+ 'hr_dashboard' => 'HR - لوحة التحكم',
+ 'hr_employees' => 'HR - الموظفين',
+ 'hr_attendance' => 'HR - الحضور والعطلات',
+ 'hr_leaves' => 'HR - الإجازات',
+ 'hr_payroll' => 'HR - الرواتب',
+ 'hr_reports' => 'HR - التقارير',
+ 'stock_dashboard' => 'المخزون - لوحة التحكم',
+ 'stock_items' => 'المخزون - الأصناف',
+ 'stock_in' => 'المخزون - توريد (وارد)',
+ 'stock_out' => 'المخزون - صرف (صادر)',
+ 'stock_lending' => 'المخزون - الإعارة',
+ 'stock_reports' => 'المخزون - التقارير',
+ 'stock_settings' => 'المخزون - الإعدادات'
];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
@@ -384,7 +397,7 @@ function applyRolePresets(role) {
view.checked = add.checked = edit.checked = del.checked = false;
} else {
view.checked = add.checked = edit.checked = true;
- del.checked = (m === 'reports' ? false : false);
+ del.checked = false; // Clerk can't delete by default
}
} else {
if (m === 'inbound' || m === 'outbound' || m === 'internal') {