Autosave: 20260327-034455
This commit is contained in:
parent
d38b70650f
commit
110626a686
@ -507,6 +507,8 @@ body {
|
||||
.group-hr { color: #66bb6a !important; } /* Green */
|
||||
.group-admin { color: #ef5350 !important; } /* Red */
|
||||
.group-reports { color: #ab47bc !important; }/* Purple */
|
||||
.group-stock { color: #fd7e14 !important; } /* Orange */
|
||||
.group-expenses { color: #e83e8c !important; } /* Pink */
|
||||
|
||||
/* Submenu indentation */
|
||||
.sidebar .collapse .nav-link {
|
||||
@ -519,4 +521,3 @@ body {
|
||||
.sidebar .collapse .nav-link.active {
|
||||
color: #fff;
|
||||
}
|
||||
.group-stock { color: #fd7e14 !important; }
|
||||
|
||||
53
db/migrations/022_add_expenses_module.sql
Normal file
53
db/migrations/022_add_expenses_module.sql
Normal file
@ -0,0 +1,53 @@
|
||||
-- Expenses module tables
|
||||
|
||||
CREATE TABLE IF NOT EXISTS expense_categories (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
description TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS expenses (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
date DATE NOT NULL,
|
||||
category_id INT NOT NULL,
|
||||
amount DECIMAL(15,2) NOT NULL,
|
||||
reference VARCHAR(100),
|
||||
description TEXT,
|
||||
vendor VARCHAR(100),
|
||||
payment_method ENUM('Cash', 'Bank Transfer', 'Credit Card', 'Check', 'Other') DEFAULT 'Cash',
|
||||
receipt_file VARCHAR(255),
|
||||
user_id INT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (category_id) REFERENCES expense_categories(id) ON DELETE RESTRICT
|
||||
);
|
||||
|
||||
-- Seed default categories
|
||||
INSERT INTO expense_categories (name, description) VALUES
|
||||
('Office Supplies', 'Pens, paper, toner, etc.'),
|
||||
('Travel', 'Flights, hotels, transport'),
|
||||
('Meals & Entertainment', 'Client lunches, team events'),
|
||||
('Utilities', 'Electricity, water, internet'),
|
||||
('Rent', 'Office rent'),
|
||||
('Salaries', 'Employee salaries'),
|
||||
('Maintenance', 'Repairs and maintenance'),
|
||||
('Advertising', 'Marketing and ads'),
|
||||
('Software', 'Subscriptions and licenses'),
|
||||
('Other', 'Miscellaneous expenses');
|
||||
|
||||
-- Add permissions
|
||||
INSERT IGNORE INTO user_permissions (user_id, page, can_view, can_add, can_edit, can_delete)
|
||||
SELECT id, 'expenses',
|
||||
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, 'expense_settings',
|
||||
IF(role = 'admin', 1, 0),
|
||||
IF(role = 'admin', 1, 0),
|
||||
IF(role = 'admin', 1, 0),
|
||||
IF(role = 'admin', 1, 0)
|
||||
FROM users;
|
||||
194
expense_categories.php
Normal file
194
expense_categories.php
Normal file
@ -0,0 +1,194 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/includes/header.php';
|
||||
|
||||
if (!canView('expense_settings')) {
|
||||
redirect('index.php');
|
||||
}
|
||||
|
||||
$error = '';
|
||||
$success = '';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (!canEdit('expense_settings')) redirect('expense_categories.php');
|
||||
|
||||
$action = $_POST['action'] ?? '';
|
||||
$id = $_POST['id'] ?? 0;
|
||||
$name = trim($_POST['name'] ?? '');
|
||||
$description = trim($_POST['description'] ?? '');
|
||||
|
||||
if ($name) {
|
||||
try {
|
||||
$db = db();
|
||||
if ($action === 'add') {
|
||||
$stmt = $db->prepare("INSERT INTO expense_categories (name, description) VALUES (?, ?)");
|
||||
$stmt->execute([$name, $description]);
|
||||
$_SESSION['success'] = 'تم إضافة التصنيف بنجاح';
|
||||
} elseif ($action === 'edit' && $id) {
|
||||
$stmt = $db->prepare("UPDATE expense_categories SET name = ?, description = ? WHERE id = ?");
|
||||
$stmt->execute([$name, $description, $id]);
|
||||
$_SESSION['success'] = 'تم تحديث التصنيف بنجاح';
|
||||
}
|
||||
redirect('expense_categories.php');
|
||||
} catch (PDOException $e) {
|
||||
$error = 'حدث خطأ: ' . $e->getMessage();
|
||||
}
|
||||
} else {
|
||||
$error = 'اسم التصنيف مطلوب';
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($_GET['action']) && $_GET['action'] === 'delete' && isset($_GET['id'])) {
|
||||
if (!canDelete('expense_settings')) redirect('expense_categories.php');
|
||||
$id = $_GET['id'];
|
||||
try {
|
||||
$db = db();
|
||||
$stmt = $db->prepare("DELETE FROM expense_categories WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$_SESSION['success'] = 'تم حذف التصنيف بنجاح';
|
||||
} catch (PDOException $e) {
|
||||
if ($e->getCode() == 23000) {
|
||||
$_SESSION['error'] = 'لا يمكن حذف هذا التصنيف لأنه مرتبط بمصروفات مسجلة';
|
||||
} else {
|
||||
$_SESSION['error'] = 'حدث خطأ: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
redirect('expense_categories.php');
|
||||
}
|
||||
|
||||
$categories = db()->query("SELECT * FROM expense_categories ORDER BY name")->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if (isset($_SESSION['success'])) {
|
||||
$success = $_SESSION['success'];
|
||||
unset($_SESSION['success']);
|
||||
}
|
||||
if (isset($_SESSION['error'])) {
|
||||
$error = $_SESSION['error'];
|
||||
unset($_SESSION['error']);
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||
<h1 class="h2">إعدادات تصنيفات المصروفات</h1>
|
||||
<?php if (canAdd('expense_settings')): ?>
|
||||
<button type="button" class="btn btn-primary shadow-sm" onclick="openModal('add')">
|
||||
<i class="fas fa-plus"></i> إضافة تصنيف جديد
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($success): ?>
|
||||
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||
<?= $success ?>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||
<?= $error ?>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th class="ps-4">اسم التصنيف</th>
|
||||
<th>الوصف</th>
|
||||
<th class="text-center">الإجراءات</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($categories as $cat): ?>
|
||||
<tr>
|
||||
<td class="ps-4 fw-bold"><?= htmlspecialchars($cat['name']) ?></td>
|
||||
<td><?= htmlspecialchars($cat['description']) ?></td>
|
||||
<td class="text-center">
|
||||
<?php if (canEdit('expense_settings')): ?>
|
||||
<button class="btn btn-sm btn-outline-primary" onclick='openModal("edit", <?= json_encode($cat, JSON_HEX_APOS | JSON_HEX_QUOT) ?>)'>
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
<?php if (canDelete('expense_settings')): ?>
|
||||
<a href="javascript:void(0)" onclick="confirmDelete(<?= $cat['id'] ?>)" class="btn btn-sm btn-outline-danger">
|
||||
<i class="fas fa-trash"></i>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal -->
|
||||
<div class="modal fade" id="categoryModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-primary text-white">
|
||||
<h5 class="modal-title" id="modalTitle">تصنيف جديد</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form method="POST">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" name="action" id="modalAction" value="add">
|
||||
<input type="hidden" name="id" id="modalId" value="0">
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-bold">اسم التصنيف</label>
|
||||
<input type="text" name="name" id="modalName" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-bold">الوصف</label>
|
||||
<textarea name="description" id="modalDescription" class="form-control" rows="3"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">إلغاء</button>
|
||||
<button type="submit" class="btn btn-primary">حفظ</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let categoryModal;
|
||||
|
||||
function openModal(action, data = null) {
|
||||
if (!categoryModal) {
|
||||
categoryModal = new bootstrap.Modal(document.getElementById('categoryModal'));
|
||||
}
|
||||
|
||||
document.getElementById('modalAction').value = action;
|
||||
const title = document.getElementById('modalTitle');
|
||||
|
||||
if (action === 'add') {
|
||||
title.textContent = 'تصنيف جديد';
|
||||
document.getElementById('modalId').value = 0;
|
||||
document.getElementById('modalName').value = '';
|
||||
document.getElementById('modalDescription').value = '';
|
||||
} else {
|
||||
title.textContent = 'تعديل التصنيف';
|
||||
document.getElementById('modalId').value = data.id;
|
||||
document.getElementById('modalName').value = data.name;
|
||||
document.getElementById('modalDescription').value = data.description;
|
||||
}
|
||||
|
||||
categoryModal.show();
|
||||
}
|
||||
|
||||
function confirmDelete(id) {
|
||||
if (confirm('هل أنت متأكد من الحذف؟')) {
|
||||
window.location.href = 'expense_categories.php?action=delete&id=' + id;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
199
expense_reports.php
Normal file
199
expense_reports.php
Normal file
@ -0,0 +1,199 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/includes/header.php';
|
||||
|
||||
if (!canView('expenses')) {
|
||||
redirect('index.php');
|
||||
}
|
||||
|
||||
// Params
|
||||
$date_from = $_GET['date_from'] ?? date('Y-m-01');
|
||||
$date_to = $_GET['date_to'] ?? date('Y-m-t');
|
||||
$category_id = $_GET['category_id'] ?? '';
|
||||
$vendor = $_GET['vendor'] ?? '';
|
||||
$payment_method = $_GET['payment_method'] ?? '';
|
||||
|
||||
// Build Query
|
||||
$sql = "SELECT e.*, c.name as category_name, u.username as created_by
|
||||
FROM expenses e
|
||||
LEFT JOIN expense_categories c ON e.category_id = c.id
|
||||
LEFT JOIN users u ON e.user_id = u.id
|
||||
WHERE e.date BETWEEN ? AND ?";
|
||||
$params = [$date_from, $date_to];
|
||||
|
||||
if ($category_id) {
|
||||
$sql .= " AND e.category_id = ?";
|
||||
$params[] = $category_id;
|
||||
}
|
||||
if ($vendor) {
|
||||
$sql .= " AND e.vendor LIKE ?";
|
||||
$params[] = "%$vendor%";
|
||||
}
|
||||
if ($payment_method) {
|
||||
$sql .= " AND e.payment_method = ?";
|
||||
$params[] = $payment_method;
|
||||
}
|
||||
|
||||
$sql .= " ORDER BY e.date ASC";
|
||||
|
||||
$stmt = db()->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$expenses = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Calculate Totals
|
||||
$total_amount = 0;
|
||||
$category_breakdown = [];
|
||||
foreach ($expenses as $exp) {
|
||||
$total_amount += $exp['amount'];
|
||||
if (!isset($category_breakdown[$exp['category_name']])) {
|
||||
$category_breakdown[$exp['category_name']] = 0;
|
||||
}
|
||||
$category_breakdown[$exp['category_name']] += $exp['amount'];
|
||||
}
|
||||
|
||||
// Fetch Categories
|
||||
$categories = db()->query("SELECT * FROM expense_categories ORDER BY name")->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
?>
|
||||
|
||||
<style>
|
||||
@media print {
|
||||
.no-print { display: none !important; }
|
||||
.sidebar, .top-navbar { display: none !important; }
|
||||
.main-content { margin: 0 !important; padding: 0 !important; }
|
||||
.card { border: none !important; shadow: none !important; }
|
||||
body { background: white !important; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom no-print">
|
||||
<h1 class="h2">تقارير المصروفات</h1>
|
||||
<button onclick="window.print()" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-print"></i> طباعة التقرير
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="card shadow-sm border-0 mb-4 no-print">
|
||||
<div class="card-body bg-light">
|
||||
<form method="GET" class="row g-3">
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">من تاريخ</label>
|
||||
<input type="date" name="date_from" class="form-control" value="<?= $date_from ?>">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">إلى تاريخ</label>
|
||||
<input type="date" name="date_to" class="form-control" value="<?= $date_to ?>">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">التصنيف</label>
|
||||
<select name="category_id" class="form-select">
|
||||
<option value="">الكل</option>
|
||||
<?php foreach ($categories as $cat): ?>
|
||||
<option value="<?= $cat['id'] ?>" <?= $category_id == $cat['id'] ? 'selected' : '' ?>><?= htmlspecialchars($cat['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">طريقة الدفع</label>
|
||||
<select name="payment_method" class="form-select">
|
||||
<option value="">الكل</option>
|
||||
<option value="Cash" <?= $payment_method == 'Cash' ? 'selected' : '' ?>>نقد</option>
|
||||
<option value="Bank Transfer" <?= $payment_method == 'Bank Transfer' ? 'selected' : '' ?>>تحويل بنكي</option>
|
||||
<option value="Credit Card" <?= $payment_method == 'Credit Card' ? 'selected' : '' ?>>بطاقة ائتمان</option>
|
||||
<option value="Check" <?= $payment_method == 'Check' ? 'selected' : '' ?>>شيك</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-12 text-end">
|
||||
<button type="submit" class="btn btn-primary px-4"><i class="fas fa-filter me-1"></i> عرض التقرير</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Report Header (Print Only) -->
|
||||
<div class="d-none d-print-block text-center mb-4">
|
||||
<h3>تقرير المصروفات التفصيلي</h3>
|
||||
<p class="text-muted">الفترة من <?= $date_from ?> إلى <?= $date_to ?></p>
|
||||
</div>
|
||||
|
||||
<!-- Summary Cards -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-4">
|
||||
<div class="card shadow-sm border-0 h-100 bg-primary text-white">
|
||||
<div class="card-body text-center">
|
||||
<h6 class="text-white-50 mb-2">إجمالي المصروفات</h6>
|
||||
<h2 class="fw-bold mb-0"><?= number_format($total_amount, 2) ?> ر.س</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="card shadow-sm border-0 h-100">
|
||||
<div class="card-body">
|
||||
<h6 class="text-muted mb-3">ملخص حسب التصنيف</h6>
|
||||
<div class="row g-2">
|
||||
<?php foreach ($category_breakdown as $name => $amount): ?>
|
||||
<div class="col-6 col-md-4">
|
||||
<div class="p-2 border rounded bg-light">
|
||||
<small class="d-block text-muted"><?= htmlspecialchars($name) ?></small>
|
||||
<span class="fw-bold"><?= number_format($amount, 2) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Detailed Table -->
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-header bg-white border-bottom py-3">
|
||||
<h5 class="mb-0">سجل العمليات</h5>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered align-middle mb-0">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th class="ps-3">التاريخ</th>
|
||||
<th>التصنيف</th>
|
||||
<th>الوصف</th>
|
||||
<th>المورد</th>
|
||||
<th>المرجع</th>
|
||||
<th>طريقة الدفع</th>
|
||||
<th>بواسطة</th>
|
||||
<th class="text-end pe-3">المبلغ</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($expenses)): ?>
|
||||
<tr>
|
||||
<td colspan="8" class="text-center py-4 text-muted">لا توجد بيانات للفترة المحددة</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($expenses as $exp): ?>
|
||||
<tr>
|
||||
<td class="ps-3"><?= $exp['date'] ?></td>
|
||||
<td><?= htmlspecialchars($exp['category_name']) ?></td>
|
||||
<td><?= htmlspecialchars($exp['description']) ?></td>
|
||||
<td><?= htmlspecialchars($exp['vendor'] ?: '-') ?></td>
|
||||
<td><?= htmlspecialchars($exp['reference'] ?: '-') ?></td>
|
||||
<td><?= htmlspecialchars($exp['payment_method']) ?></td>
|
||||
<td><small><?= htmlspecialchars($exp['created_by'] ?: '-') ?></small></td>
|
||||
<td class="text-end pe-3 fw-bold"><?= number_format($exp['amount'], 2) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<tr class="bg-light fw-bold">
|
||||
<td colspan="7" class="text-end ps-3">الإجمالي النهائي:</td>
|
||||
<td class="text-end pe-3 text-danger"><?= number_format($total_amount, 2) ?></td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-center text-muted small d-none d-print-block">
|
||||
تم استخراج التقرير في <?= date('Y-m-d H:i:s') ?> بواسطة <?= $_SESSION['name'] ?? 'System' ?>
|
||||
</div>
|
||||
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
367
expenses.php
Normal file
367
expenses.php
Normal file
@ -0,0 +1,367 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/includes/header.php';
|
||||
|
||||
if (!canView('expenses')) {
|
||||
redirect('index.php');
|
||||
}
|
||||
|
||||
$error = '';
|
||||
$success = '';
|
||||
|
||||
// Handle Actions
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$action = $_POST['action'] ?? '';
|
||||
$id = $_POST['id'] ?? 0;
|
||||
|
||||
if ($action === 'add' || $action === 'edit') {
|
||||
if (($action === 'add' && !canAdd('expenses')) || ($action === 'edit' && !canEdit('expenses'))) {
|
||||
$error = 'ليس لديك صلاحية للقيام بهذا الإجراء';
|
||||
} else {
|
||||
$date = $_POST['date'] ?? date('Y-m-d');
|
||||
$category_id = $_POST['category_id'] ?? 0;
|
||||
$amount = $_POST['amount'] ?? 0;
|
||||
$description = $_POST['description'] ?? '';
|
||||
$reference = $_POST['reference'] ?? '';
|
||||
$vendor = $_POST['vendor'] ?? '';
|
||||
$payment_method = $_POST['payment_method'] ?? 'Cash';
|
||||
|
||||
// File Upload
|
||||
$receipt_path = null;
|
||||
if (isset($_FILES['receipt']) && $_FILES['receipt']['error'] === UPLOAD_ERR_OK) {
|
||||
$upload_dir = 'uploads/receipts/';
|
||||
if (!is_dir($upload_dir)) mkdir($upload_dir, 0775, true);
|
||||
|
||||
$ext = pathinfo($_FILES['receipt']['name'], PATHINFO_EXTENSION);
|
||||
$filename = time() . '_' . uniqid() . '.' . $ext;
|
||||
$target = $upload_dir . $filename;
|
||||
|
||||
if (move_uploaded_file($_FILES['receipt']['tmp_name'], $target)) {
|
||||
$receipt_path = $target;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$db = db();
|
||||
if ($action === 'add') {
|
||||
$stmt = $db->prepare("INSERT INTO expenses (date, category_id, amount, description, reference, vendor, payment_method, receipt_file, user_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$date, $category_id, $amount, $description, $reference, $vendor, $payment_method, $receipt_path, $_SESSION['user_id']]);
|
||||
$_SESSION['success'] = 'تم إضافة المصروف بنجاح';
|
||||
} else {
|
||||
// Get old file if not replaced
|
||||
if (!$receipt_path) {
|
||||
$stmt = $db->prepare("SELECT receipt_file FROM expenses WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$receipt_path = $stmt->fetchColumn();
|
||||
}
|
||||
|
||||
$stmt = $db->prepare("UPDATE expenses SET date=?, category_id=?, amount=?, description=?, reference=?, vendor=?, payment_method=?, receipt_file=? WHERE id=?");
|
||||
$stmt->execute([$date, $category_id, $amount, $description, $reference, $vendor, $payment_method, $receipt_path, $id]);
|
||||
$_SESSION['success'] = 'تم تحديث المصروف بنجاح';
|
||||
}
|
||||
redirect('expenses.php');
|
||||
} catch (PDOException $e) {
|
||||
$error = 'حدث خطأ: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($_GET['action']) && $_GET['action'] === 'delete' && isset($_GET['id'])) {
|
||||
if (!canDelete('expenses')) redirect('expenses.php');
|
||||
$id = $_GET['id'];
|
||||
$db = db();
|
||||
// Get file to delete
|
||||
$stmt = $db->prepare("SELECT receipt_file FROM expenses WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$file = $stmt->fetchColumn();
|
||||
if ($file && file_exists($file)) unlink($file);
|
||||
|
||||
$stmt = $db->prepare("DELETE FROM expenses WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$_SESSION['success'] = 'تم حذف المصروف بنجاح';
|
||||
redirect('expenses.php');
|
||||
}
|
||||
|
||||
// Fetch Data for List
|
||||
$date_from = $_GET['date_from'] ?? date('Y-m-01');
|
||||
$date_to = $_GET['date_to'] ?? date('Y-m-t');
|
||||
$category_filter = $_GET['category_id'] ?? '';
|
||||
$search = $_GET['search'] ?? '';
|
||||
|
||||
$sql = "SELECT e.*, c.name as category_name, u.username as created_by_name
|
||||
FROM expenses e
|
||||
LEFT JOIN expense_categories c ON e.category_id = c.id
|
||||
LEFT JOIN users u ON e.user_id = u.id
|
||||
WHERE e.date BETWEEN ? AND ?";
|
||||
$params = [$date_from, $date_to];
|
||||
|
||||
if ($category_filter) {
|
||||
$sql .= " AND e.category_id = ?";
|
||||
$params[] = $category_filter;
|
||||
}
|
||||
if ($search) {
|
||||
$sql .= " AND (e.description LIKE ? OR e.vendor LIKE ? OR e.reference LIKE ?)";
|
||||
$params[] = "%$search%";
|
||||
$params[] = "%$search%";
|
||||
$params[] = "%$search%";
|
||||
}
|
||||
|
||||
$sql .= " ORDER BY e.date DESC, e.id DESC";
|
||||
|
||||
$stmt = db()->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$expenses = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Fetch Categories for Dropdown
|
||||
$categories = db()->query("SELECT * FROM expense_categories ORDER BY name")->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if (isset($_SESSION['success'])) {
|
||||
$success = $_SESSION['success'];
|
||||
unset($_SESSION['success']);
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||
<h1 class="h2">سجل المصروفات</h1>
|
||||
<?php if (canAdd('expenses')): ?>
|
||||
<button type="button" class="btn btn-primary shadow-sm" onclick="openModal('add')">
|
||||
<i class="fas fa-plus"></i> تسجيل مصروف جديد
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($success): ?>
|
||||
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||
<?= $success ?>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||
<?= $error ?>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="card shadow-sm border-0 mb-4">
|
||||
<div class="card-body bg-light">
|
||||
<form method="GET" class="row g-3">
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">من تاريخ</label>
|
||||
<input type="date" name="date_from" class="form-control" value="<?= $date_from ?>">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">إلى تاريخ</label>
|
||||
<input type="date" name="date_to" class="form-control" value="<?= $date_to ?>">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">التصنيف</label>
|
||||
<select name="category_id" class="form-select">
|
||||
<option value="">الكل</option>
|
||||
<?php foreach ($categories as $cat): ?>
|
||||
<option value="<?= $cat['id'] ?>" <?= $category_filter == $cat['id'] ? 'selected' : '' ?>><?= htmlspecialchars($cat['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">بحث</label>
|
||||
<div class="input-group">
|
||||
<input type="text" name="search" class="form-control" placeholder="وصف، مورد، مرجع..." value="<?= htmlspecialchars($search) ?>">
|
||||
<button class="btn btn-primary" type="submit"><i class="fas fa-search"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th class="ps-4">التاريخ</th>
|
||||
<th>التصنيف</th>
|
||||
<th>الوصف</th>
|
||||
<th>المورد</th>
|
||||
<th>المبلغ</th>
|
||||
<th>طريقة الدفع</th>
|
||||
<th>الإيصال</th>
|
||||
<th class="text-center">الإجراءات</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($expenses)): ?>
|
||||
<tr>
|
||||
<td colspan="8" class="text-center py-5 text-muted">لا توجد سجلات مطابقة</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($expenses as $exp): ?>
|
||||
<tr>
|
||||
<td class="ps-4"><?= $exp['date'] ?></td>
|
||||
<td><span class="badge bg-secondary bg-opacity-10 text-secondary"><?= htmlspecialchars($exp['category_name']) ?></span></td>
|
||||
<td>
|
||||
<?= htmlspecialchars($exp['description']) ?>
|
||||
<?php if ($exp['reference']): ?>
|
||||
<br><small class="text-muted">Ref: <?= htmlspecialchars($exp['reference']) ?></small>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><?= htmlspecialchars($exp['vendor'] ?: '-') ?></td>
|
||||
<td class="fw-bold text-danger"><?= number_format($exp['amount'], 2) ?></td>
|
||||
<td><?= htmlspecialchars($exp['payment_method']) ?></td>
|
||||
<td>
|
||||
<?php if ($exp['receipt_file']): ?>
|
||||
<a href="<?= htmlspecialchars($exp['receipt_file']) ?>" target="_blank" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
<?php else: ?>
|
||||
-
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<?php if (canEdit('expenses')): ?>
|
||||
<button class="btn btn-sm btn-outline-primary" onclick='openModal("edit", <?= json_encode($exp, JSON_HEX_APOS | JSON_HEX_QUOT) ?>)'>
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
<?php if (canDelete('expenses')): ?>
|
||||
<a href="javascript:void(0)" onclick="confirmDelete(<?= $exp['id'] ?>)" class="btn btn-sm btn-outline-danger">
|
||||
<i class="fas fa-trash"></i>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<tr class="bg-light fw-bold">
|
||||
<td colspan="4" class="text-end">الإجمالي:</td>
|
||||
<td class="text-danger"><?= number_format(array_sum(array_column($expenses, 'amount')), 2) ?></td>
|
||||
<td colspan="3"></td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal -->
|
||||
<div class="modal fade" id="expenseModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-primary text-white">
|
||||
<h5 class="modal-title" id="modalTitle">تسجيل مصروف جديد</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form method="POST" enctype="multipart/form-data">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" name="action" id="modalAction" value="add">
|
||||
<input type="hidden" name="id" id="modalId" value="0">
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-bold">التاريخ</label>
|
||||
<input type="date" name="date" id="modalDate" class="form-control" required value="<?= date('Y-m-d') ?>">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-bold">التصنيف</label>
|
||||
<select name="category_id" id="modalCategory" class="form-select" required>
|
||||
<option value="">اختر التصنيف...</option>
|
||||
<?php foreach ($categories as $cat): ?>
|
||||
<option value="<?= $cat['id'] ?>"><?= htmlspecialchars($cat['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label fw-bold">المبلغ</label>
|
||||
<input type="number" step="0.01" name="amount" id="modalAmount" class="form-control" required>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label fw-bold">طريقة الدفع</label>
|
||||
<select name="payment_method" id="modalPaymentMethod" class="form-select">
|
||||
<option value="Cash">نقد (Cash)</option>
|
||||
<option value="Bank Transfer">تحويل بنكي</option>
|
||||
<option value="Credit Card">بطاقة ائتمان</option>
|
||||
<option value="Check">شيك</option>
|
||||
<option value="Other">أخرى</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-bold">المورد / المستفيد</label>
|
||||
<input type="text" name="vendor" id="modalVendor" class="form-control" placeholder="اسم المحل، الشركة، الشخص...">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-bold">الوصف</label>
|
||||
<textarea name="description" id="modalDescription" class="form-control" rows="2"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-bold">رقم مرجعي (اختياري)</label>
|
||||
<input type="text" name="reference" id="modalReference" class="form-control" placeholder="رقم فاتورة، رقم إيصال...">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-bold">صورة الإيصال (اختياري)</label>
|
||||
<input type="file" name="receipt" class="form-control" accept="image/*,.pdf">
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">إلغاء</button>
|
||||
<button type="submit" class="btn btn-primary">حفظ</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let expenseModal;
|
||||
|
||||
function openModal(action, data = null) {
|
||||
if (!expenseModal) {
|
||||
expenseModal = new bootstrap.Modal(document.getElementById('expenseModal'));
|
||||
}
|
||||
|
||||
document.getElementById('modalAction').value = action;
|
||||
const title = document.getElementById('modalTitle');
|
||||
|
||||
if (action === 'add') {
|
||||
title.textContent = 'تسجيل مصروف جديد';
|
||||
document.getElementById('modalId').value = 0;
|
||||
document.getElementById('modalAmount').value = '';
|
||||
document.getElementById('modalDescription').value = '';
|
||||
document.getElementById('modalVendor').value = '';
|
||||
document.getElementById('modalReference').value = '';
|
||||
document.getElementById('modalDate').value = new Date().toISOString().split('T')[0];
|
||||
document.getElementById('modalCategory').value = '';
|
||||
document.getElementById('modalPaymentMethod').value = 'Cash';
|
||||
} else {
|
||||
title.textContent = 'تعديل المصروف';
|
||||
document.getElementById('modalId').value = data.id;
|
||||
document.getElementById('modalDate').value = data.date;
|
||||
document.getElementById('modalCategory').value = data.category_id;
|
||||
document.getElementById('modalAmount').value = data.amount;
|
||||
document.getElementById('modalDescription').value = data.description;
|
||||
document.getElementById('modalVendor').value = data.vendor;
|
||||
document.getElementById('modalReference').value = data.reference;
|
||||
document.getElementById('modalPaymentMethod').value = data.payment_method;
|
||||
}
|
||||
|
||||
expenseModal.show();
|
||||
}
|
||||
|
||||
function confirmDelete(id) {
|
||||
if (confirm('هل أنت متأكد من حذف هذا المصروف؟')) {
|
||||
window.location.href = 'expenses.php?action=delete&id=' + id;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
228
expenses_dashboard.php
Normal file
228
expenses_dashboard.php
Normal file
@ -0,0 +1,228 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/includes/header.php';
|
||||
|
||||
if (!canView('expenses')) {
|
||||
redirect('index.php');
|
||||
}
|
||||
|
||||
// Helper to get totals
|
||||
function getExpenseStats($month, $year) {
|
||||
$db = db();
|
||||
$start_date = "$year-$month-01";
|
||||
$end_date = date("Y-m-t", strtotime($start_date));
|
||||
|
||||
// Total for month
|
||||
$stmt = $db->prepare("SELECT SUM(amount) FROM expenses WHERE date BETWEEN ? AND ?");
|
||||
$stmt->execute([$start_date, $end_date]);
|
||||
$total = $stmt->fetchColumn() ?: 0;
|
||||
|
||||
// By Category
|
||||
$stmt = $db->prepare("SELECT c.name, SUM(e.amount) as total
|
||||
FROM expenses e
|
||||
JOIN expense_categories c ON e.category_id = c.id
|
||||
WHERE e.date BETWEEN ? AND ?
|
||||
GROUP BY c.name
|
||||
ORDER BY total DESC");
|
||||
$stmt->execute([$start_date, $end_date]);
|
||||
$by_category = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
return ['total' => $total, 'by_category' => $by_category];
|
||||
}
|
||||
|
||||
// Current month stats
|
||||
$current_month = date('m');
|
||||
$current_year = date('Y');
|
||||
$current_stats = getExpenseStats($current_month, $current_year);
|
||||
|
||||
// Last 6 months trend
|
||||
$trend_data = [];
|
||||
for ($i = 5; $i >= 0; $i--) {
|
||||
$d = strtotime("-$i months");
|
||||
$m = date('m', $d);
|
||||
$y = date('Y', $d);
|
||||
$s = getExpenseStats($m, $y);
|
||||
$trend_data[] = [
|
||||
'label' => date('M Y', $d), // English month names for Chart.js
|
||||
'display_label' => date('m/Y', $d),
|
||||
'total' => $s['total']
|
||||
];
|
||||
}
|
||||
|
||||
// Recent Expenses
|
||||
$stmt = db()->query("SELECT e.*, c.name as category_name
|
||||
FROM expenses e
|
||||
JOIN expense_categories c ON e.category_id = c.id
|
||||
ORDER BY e.date DESC, e.id DESC LIMIT 5");
|
||||
$recent_expenses = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
?>
|
||||
|
||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||
<h1 class="h2">لوحة تحكم المصروفات</h1>
|
||||
<div class="btn-toolbar mb-2 mb-md-0">
|
||||
<a href="expenses.php" class="btn btn-sm btn-primary">
|
||||
<i class="fas fa-list"></i> عرض السجل
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stats Cards -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-4">
|
||||
<div class="card shadow-sm border-0 h-100">
|
||||
<div class="card-body">
|
||||
<h6 class="text-muted mb-2">إجمالي مصروفات هذا الشهر</h6>
|
||||
<h3 class="fw-bold text-danger mb-0"><?= number_format($current_stats['total'], 2) ?> ر.س</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card shadow-sm border-0 h-100">
|
||||
<div class="card-body">
|
||||
<h6 class="text-muted mb-2">أعلى تصنيف للصرف</h6>
|
||||
<?php if (!empty($current_stats['by_category'])): ?>
|
||||
<h3 class="fw-bold text-primary mb-0"><?= htmlspecialchars($current_stats['by_category'][0]['name']) ?></h3>
|
||||
<small class="text-muted"><?= number_format($current_stats['by_category'][0]['total'], 2) ?> ر.س</small>
|
||||
<?php else: ?>
|
||||
<h3 class="fw-bold text-muted mb-0">-</h3>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card shadow-sm border-0 h-100">
|
||||
<div class="card-body">
|
||||
<h6 class="text-muted mb-2">متوسط الصرف (آخر 6 أشهر)</h6>
|
||||
<?php
|
||||
$avg = array_sum(array_column($trend_data, 'total')) / count($trend_data);
|
||||
?>
|
||||
<h3 class="fw-bold text-info mb-0"><?= number_format($avg, 2) ?> ر.س</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-4">
|
||||
<!-- Trend Chart -->
|
||||
<div class="col-md-8 mb-4 mb-md-0">
|
||||
<div class="card shadow-sm border-0 h-100">
|
||||
<div class="card-header bg-transparent border-0 d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">اتجاه المصروفات (آخر 6 أشهر)</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<canvas id="trendChart" height="120"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Category Pie Chart -->
|
||||
<div class="col-md-4">
|
||||
<div class="card shadow-sm border-0 h-100">
|
||||
<div class="card-header bg-transparent border-0">
|
||||
<h5 class="mb-0">توزيع المصروفات (<?= date('m/Y') ?>)</h5>
|
||||
</div>
|
||||
<div class="card-body position-relative">
|
||||
<canvas id="categoryChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Expenses -->
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-header bg-transparent border-0">
|
||||
<h5 class="mb-0">آخر المصروفات المسجلة</h5>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th class="ps-4">التاريخ</th>
|
||||
<th>التصنيف</th>
|
||||
<th>الوصف/المورد</th>
|
||||
<th>المبلغ</th>
|
||||
<th>طريقة الدفع</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($recent_expenses)): ?>
|
||||
<tr>
|
||||
<td colspan="5" class="text-center py-4 text-muted">لا توجد مصروفات مسجلة</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($recent_expenses as $exp): ?>
|
||||
<tr>
|
||||
<td class="ps-4"><?= $exp['date'] ?></td>
|
||||
<td><span class="badge bg-secondary bg-opacity-10 text-secondary"><?= htmlspecialchars($exp['category_name']) ?></span></td>
|
||||
<td>
|
||||
<div class="fw-bold"><?= htmlspecialchars($exp['description'] ?: '-') ?></div>
|
||||
<div class="small text-muted"><?= htmlspecialchars($exp['vendor'] ?: '') ?></div>
|
||||
</td>
|
||||
<td class="fw-bold text-danger"><?= number_format($exp['amount'], 2) ?></td>
|
||||
<td><?= htmlspecialchars($exp['payment_method']) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script>
|
||||
// Trend Chart
|
||||
const trendCtx = document.getElementById('trendChart').getContext('2d');
|
||||
new Chart(trendCtx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: <?= json_encode(array_column($trend_data, 'display_label')) ?>,
|
||||
datasets: [{
|
||||
label: 'المصروفات',
|
||||
data: <?= json_encode(array_column($trend_data, 'total')) ?>,
|
||||
backgroundColor: 'rgba(232, 62, 140, 0.6)', // Pinkish
|
||||
borderColor: 'rgba(232, 62, 140, 1)',
|
||||
borderWidth: 1,
|
||||
borderRadius: 4
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: { display: false }
|
||||
},
|
||||
scales: {
|
||||
y: { beginAtZero: true, grid: { color: 'rgba(0,0,0,0.05)' } },
|
||||
x: { grid: { display: false } }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Category Chart
|
||||
const catCtx = document.getElementById('categoryChart').getContext('2d');
|
||||
const catData = <?= json_encode($current_stats['by_category']) ?>;
|
||||
|
||||
new Chart(catCtx, {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
labels: catData.map(d => d.name),
|
||||
datasets: [{
|
||||
data: catData.map(d => d.total),
|
||||
backgroundColor: [
|
||||
'#4e73df', '#1cc88a', '#36b9cc', '#f6c23e', '#e74a3b',
|
||||
'#858796', '#5a5c69', '#fd7e14', '#20c997', '#6f42c1'
|
||||
],
|
||||
borderWidth: 0
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
cutout: '70%',
|
||||
plugins: {
|
||||
legend: { position: 'bottom', labels: { boxWidth: 12 } }
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
@ -130,6 +130,9 @@ $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);
|
||||
|
||||
$expenses_pages = ['expenses.php', 'expense_categories.php', 'expense_reports.php'];
|
||||
$is_expenses_open = in_array($cp, $expenses_pages);
|
||||
|
||||
$report_pages = ['overdue_report.php'];
|
||||
$is_report_open = in_array($cp, $report_pages);
|
||||
|
||||
@ -482,6 +485,43 @@ $is_admin_open = in_array($cp, $admin_pages);
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Expenses Group -->
|
||||
<?php if (canView('expenses') || canView('expense_settings')): ?>
|
||||
<li class="nav-item">
|
||||
<button class="sidebar-group-btn <?= $is_expenses_open ? '' : 'collapsed' ?>" type="button" data-bs-toggle="collapse" data-bs-target="#menu-expenses" aria-expanded="<?= $is_expenses_open ? 'true' : 'false' ?>">
|
||||
<span class="group-content group-expenses">
|
||||
<i class="fas fa-money-bill-wave"></i> المصروفات
|
||||
</span>
|
||||
<i class="fas fa-chevron-down arrow-icon"></i>
|
||||
</button>
|
||||
<div class="collapse <?= $is_expenses_open ? 'show' : '' ?>" id="menu-expenses">
|
||||
<ul class="nav flex-column">
|
||||
<?php if (canView('expenses')): ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= $cp == 'expenses.php' ? 'active' : '' ?>" href="expenses.php">
|
||||
سجل المصروفات
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<?php if (canView('expense_settings')): ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= $cp == 'expense_categories.php' ? 'active' : '' ?>" href="expense_categories.php">
|
||||
تصنيفات المصروفات
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<?php if (canView('expenses')): ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= $cp == 'expense_reports.php' ? 'active' : '' ?>" href="expense_reports.php">
|
||||
التقارير
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Reports Group -->
|
||||
<?php if (canView('reports')): ?>
|
||||
<li class="nav-item">
|
||||
|
||||
@ -28,7 +28,9 @@ $modules = [
|
||||
'stock_out' => 'المخزون - صرف (صادر)',
|
||||
'stock_lending' => 'المخزون - الإعارة',
|
||||
'stock_reports' => 'المخزون - التقارير',
|
||||
'stock_settings' => 'المخزون - الإعدادات'
|
||||
'stock_settings' => 'المخزون - الإعدادات',
|
||||
'expenses' => 'المصروفات',
|
||||
'expense_settings' => 'المصروفات - الإعدادات'
|
||||
];
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
@ -518,4 +520,4 @@ function confirmDelete(id) {
|
||||
}
|
||||
</style>
|
||||
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
<?php require_once __DIR__ . '/includes/footer.php';
|
||||
Loading…
x
Reference in New Issue
Block a user