From d38b70650fab926cdfd4ed114a7b6af817f3bf0e Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Fri, 27 Mar 2026 03:32:55 +0000 Subject: [PATCH] stock module --- assets/css/custom.css | 62 ++++- db/migrations/020_add_hr_module.sql | 134 ++++++++++ db/migrations/021_add_stock_module.sql | 76 ++++++ hr_attendance.php | 227 ++++++++++++++++ hr_dashboard.php | 153 +++++++++++ hr_employees.php | 336 +++++++++++++++++++++++ hr_holidays.php | 183 +++++++++++++ hr_leaves.php | 275 +++++++++++++++++++ hr_payroll.php | 263 ++++++++++++++++++ hr_reports.php | 170 ++++++++++++ includes/header.php | 351 +++++++++++++++++++------ stock_dashboard.php | 149 +++++++++++ stock_in.php | 136 ++++++++++ stock_items.php | 242 +++++++++++++++++ stock_lending.php | 251 ++++++++++++++++++ stock_out.php | 144 ++++++++++ stock_reports.php | 173 ++++++++++++ stock_settings.php | 265 +++++++++++++++++++ users.php | 17 +- 19 files changed, 3527 insertions(+), 80 deletions(-) create mode 100644 db/migrations/020_add_hr_module.sql create mode 100644 db/migrations/021_add_stock_module.sql create mode 100644 hr_attendance.php create mode 100644 hr_dashboard.php create mode 100644 hr_employees.php create mode 100644 hr_holidays.php create mode 100644 hr_leaves.php create mode 100644 hr_payroll.php create mode 100644 hr_reports.php create mode 100644 stock_dashboard.php create mode 100644 stock_in.php create mode 100644 stock_items.php create mode 100644 stock_lending.php create mode 100644 stock_out.php create mode 100644 stock_reports.php create mode 100644 stock_settings.php 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(); + +?> + +
+

سجل الحضور والانصراف

+
+
+ + +
+
+
+ + +
+ + +
+ + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
الموظفالوظيفةالحالةوقت الحضوروقت الانصرافملاحظاتإجراء
+ + 'success', + 'absent' => 'danger', + 'late' => 'warning', + 'excused' => 'info', + 'holiday' => 'primary', + default => 'secondary' + }; + $status_text = match($row['status']) { + 'present' => 'حاضر', + 'absent' => 'غائب', + 'late' => 'تأخير', + 'excused' => 'مأذون', + 'holiday' => 'عطلة', + default => $row['status'] + }; + ?> + + + غير مسجل + + + + + + + + + +
+
+
+
+ + + + + + + \ 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(); +?> + +
+

لوحة الموارد البشرية

+
+ + +
+
+
+
+
+
+
إجمالي الموظفين
+

+
+ +
+
+
+
+
+
+
+
+
+
حضور اليوم
+

+
+ +
+
+
+
+
+
+
+
+
+
في إجازة
+

+
+ +
+
+
+
+
+
+
+
+
+
طلبات إجازة معلقة
+

+
+ +
+
+
+
+
+ +
+ +
+
+
+
أحدث الموظفين
+
+
+
+ + + + + + + + + + + + + + + + + + + + + +
الاسمالوظيفةتاريخ التعيين
لا يوجد موظفين مسجلين حالياً
+
+
+ +
+
+ + + +
+ + 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(); + +?> + +
+

إدارة الموظفين

+
+ + + + +
+
+ + +
+ + +
+ + + + + + + + + +
+
+
+ + + + + + + + + + + + + query($sql); + while ($row = $stmt->fetch()): + ?> + + + + + + + + + + +
الاسمالقسمالمسمى الوظيفيتاريخ التعيينالحالةالإجراءات
+
+
+ +
+
+
+
+
+
+
+ 'success', + 'terminated' => 'danger', + 'resigned' => 'warning', + 'on_leave' => 'info', + default => 'secondary' + }; + ?> + + +
+ + + + +
+ + +
+ +
+
+
+
+
+ + + + \ 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(); +?> + +
+

العطلات الرسمية

+
+ + + +
+
+ + +
+ + +
+ + +
+
+
+ + + + + + + + + + + + + + + = $row['date_from'] && $today <= $row['date_to']) { + $status_cls = 'success'; + $status_txt = 'جارية'; + } elseif ($today < $row['date_from']) { + $status_cls = 'primary'; + $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(); + +?> + +
+

إدارة الإجازات

+
+ + + +
+
+ + +
+ + +
+ + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
الموظفنوع الإجازةالفترةالمدةالسببالحالةالمعتمدإجراءات
لا توجد طلبات.
+ 'سنوية', + 'sick' => 'مرضية', + 'unpaid' => 'بدون راتب', + 'maternity' => 'أمومة', + 'emergency' => 'طارئة', + 'other' => 'أخرى' + ]; + echo $type_map[$req['leave_type']] ?? $req['leave_type']; + ?> + + من
إلى +
يوم + 'success', + 'rejected' => 'danger', + default => 'warning' + }; + $status_txt = match($req['status']) { + 'approved' => 'مقبولة', + 'rejected' => 'مرفوضة', + default => 'معلقة' + }; + ?> + + + + + +
+ + + +
+
+ + + +
+ +
+
+
+
+ + + + + + + \ 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']; + +?> + +
+

مسير الرواتب

+
+
+ + + +
+ + + + +
+
+ + +
+ + +
+ + +
+
+
+
+
إجمالي الرواتب للشهر
+

+
+
+
+
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
الموظفالراتب الأساسيإضافيخصوماتالصافيالحالةإجراءات
لا توجد بيانات لهذا الشهر. اضغط على "توليد الرواتب" للبدء.
+
+
+
+ + + + + + + +
+
+
+
+ + + + + + + + + + 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'); + +?> + +
+

تقارير الموارد البشرية

+
+ +
+
+ +
+ +
+ + + +
+
+
+ +
+ + +
+
+ + +
+
+ +
+
+
+
+ +
+
+
تقرير الحضور لشهر /
+
+
+
+ + + + + + + + + + + + + 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; + ?> + + + + + + + + + + +
الموظفأيام الحضورأيام الغيابالتأخيرإجازاتنسبة الحضور
%
+
+
+
+ + +
+
+
سجل الموظفين الشامل
+
+
+ + + + + + + + + + + + + + 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): + ?> + + + + + + + + + + + +
الرقم الوظيفيالاسمالقسمالوظيفةتاريخ التعيينالراتب الأساسيرقم الهاتف
#
+
+
+ + + + + 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(); + +?> + +
+

لوحة تحكم المخزون

+ +
+ +
+
+
+
+
+
+ +
+
+
إجمالي الأصناف
+

+
+
+
+
+
+
+
+
+
+
+ +
+
+
المستودعات
+

+
+
+
+
+
+
+
+
+
+
+ +
+
+
تنبيهات المخزون المنخفض
+

+
+
+
+
+
+
+ +
+
+
أحدث الحركات
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#النوعالصنفالمستودعالكميةبواسطةالتاريخ
لا توجد حركات حديثة
+ ['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']]; + ?> + +
+
+
+ + 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(); + +?> + +
+

توريد مخزون (وارد)

+ + عودة للوحة التحكم + +
+ + + + + + + + + +
+
+
+
+
+
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+ +
+
+
+
+
+
+ + 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(); + +?> + +
+

إدارة الأصناف

+ + + +
+ + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
اسم الصنفالرمز (SKU)التصنيفالكمية الحاليةالحد الأدنىالوحدةالإجراءات
+ + + + + + + + + +
+
+
+
+ + + + + + + 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(); + +?> + +
+

إعارة المواد

+ +
+ + + + + + + + + +
+
+
الإعارات النشطة
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
المستعيرالمادةالكميةتاريخ الإعارةتاريخ الإرجاع المتوقعالحالةالإجراءات
لا توجد إعارات نشطة حالياً
+ +
+
+ + + نشط +
+ + + +
+
+
+
+ + + + + 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(); + +?> + +
+

صرف مخزون (صادر)

+ + عودة للوحة التحكم + +
+ + + + + + + + + +
+
+
+
+
+
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+ + +
+ +
+ +
+
+
+
+
+
+ + 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(); + +?> + +
+

تقارير حركة المخزون

+ +
+ +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+
+
+ +
+
+
+

تقرير حركة المخزون

+

من إلى

+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#التاريخالنوعالمستودعالصنفالكميةالمستخدمملاحظات
لا توجد بيانات للعرض
+ 'توريد', + 'out' => 'صرف', + 'damage' => 'تالف', + 'lend' => 'إعارة', + 'return' => 'إرجاع', + 'transfer' => 'نقل' + ]; + echo $types[$t['transaction_type']] ?? $t['transaction_type']; + ?> + + + + + + - + + + + المرجع:
+ + +
+
+
+
+ + + + 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(); + +?> + +
+

إعدادات المخزون

+
+ + + + + + + + + + + +
+ +
+
+
قائمة المستودعات
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + +
#اسم المستودعالموقع / العنوانتاريخ الإضافةالإجراءات
+ + + + + + +
+
+
+
+ + +
+
+
تصنيفات المواد
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + +
#اسم التصنيفالوصفتاريخ الإضافةالإجراءات
+ + + + + + +
+
+
+
+
+ + + + + + + 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') {