38808-vm/expenses.php
2026-03-27 04:07:31 +00:00

431 lines
20 KiB
PHP

<?php
require_once __DIR__ . '/includes/header.php';
require_once __DIR__ . '/includes/accounting_functions.php'; // Include accounting helpers
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();
$journal_id = null;
// --- Accounting Logic: Prepare Data ---
// 1. Get Category Account
$stmt_cat = $db->prepare("SELECT a.name as account_name FROM expense_categories c LEFT JOIN accounting_accounts a ON c.account_id = a.id WHERE c.id = ?");
$stmt_cat->execute([$category_id]);
$cat_account = $stmt_cat->fetchColumn();
// 2. Get Payment Account (Default: Cash / النقدية)
// Ideally, map Payment Method to Account. For now, defaulting to 'النقدية'.
$pay_account = 'النقدية';
// Could be improved: if ($payment_method == 'Bank Transfer') $pay_account = 'Bank'; etc.
if ($action === 'add') {
// Create Expense
$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']]);
$expense_id = $db->lastInsertId();
// Create Journal Entry
if ($cat_account) {
$entries = [
['account' => $cat_account, 'debit' => $amount, 'credit' => 0],
['account' => $pay_account, 'debit' => 0, 'credit' => $amount]
];
$journal_desc = "Expense #$expense_id: " . ($vendor ? "$vendor - " : "") . $description;
$jid = add_journal_entry($date, $journal_desc, $reference, $entries);
if ($jid) {
$db->prepare("UPDATE expenses SET journal_id = ? WHERE id = ?")->execute([$jid, $expense_id]);
}
}
$_SESSION['success'] = 'تم إضافة المصروف بنجاح' . ($cat_account ? ' وتم إنشاء قيد محاسبي.' : '');
} else {
// Update Expense
// Get existing journal_id and file
$stmt = $db->prepare("SELECT journal_id, receipt_file FROM expenses WHERE id = ?");
$stmt->execute([$id]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
$journal_id = $row['journal_id'] ?? null;
$old_file = $row['receipt_file'] ?? null;
if (!$receipt_path) $receipt_path = $old_file;
$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]);
// Update Journal Entry
if ($cat_account) {
$entries = [
['account' => $cat_account, 'debit' => $amount, 'credit' => 0],
['account' => $pay_account, 'debit' => 0, 'credit' => $amount]
];
$journal_desc = "Expense #$id: " . ($vendor ? "$vendor - " : "") . $description;
if ($journal_id) {
edit_journal_entry($journal_id, $date, $journal_desc, $reference, $entries);
} else {
// Create new if missing
$jid = add_journal_entry($date, $journal_desc, $reference, $entries);
if ($jid) {
$db->prepare("UPDATE expenses SET journal_id = ? WHERE id = ?")->execute([$jid, $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 and journal_id
$stmt = $db->prepare("SELECT receipt_file, journal_id FROM expenses WHERE id = ?");
$stmt->execute([$id]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
// Delete file
if ($row && $row['receipt_file'] && file_exists($row['receipt_file'])) unlink($row['receipt_file']);
// Delete Journal Entry
if ($row && $row['journal_id']) {
delete_journal_entry($row['journal_id']);
}
// Delete Expense
$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'; ?>