264 lines
12 KiB
PHP
264 lines
12 KiB
PHP
<?php
|
|
require_once 'includes/header.php';
|
|
|
|
if (!canView('hr_payroll')) {
|
|
echo "<div class='alert alert-danger'>ليس لديك صلاحية للوصول إلى هذه الصفحة.</div>";
|
|
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'];
|
|
|
|
?>
|
|
|
|
<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">
|
|
<form class="d-flex gap-2 align-items-center" method="get">
|
|
<select name="month" class="form-select form-select-sm">
|
|
<?php for($m=1; $m<=12; $m++): ?>
|
|
<option value="<?= $m ?>" <?= $m == $month ? 'selected' : '' ?>><?= date('F', mktime(0, 0, 0, $m, 1)) ?></option>
|
|
<?php endfor; ?>
|
|
</select>
|
|
<select name="year" class="form-select form-select-sm">
|
|
<?php for($y=date('Y')-1; $y<=date('Y')+1; $y++): ?>
|
|
<option value="<?= $y ?>" <?= $y == $year ? 'selected' : '' ?>><?= $y ?></option>
|
|
<?php endfor; ?>
|
|
</select>
|
|
<button type="submit" class="btn btn-sm btn-outline-secondary">عرض</button>
|
|
</form>
|
|
|
|
<?php if (canAdd('hr_payroll')): ?>
|
|
<button type="button" class="btn btn-sm btn-primary ms-2" data-bs-toggle="modal" data-bs-target="#generateModal">
|
|
<i class="fas fa-cog"></i> توليد الرواتب
|
|
</button>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<?php if ($error): ?>
|
|
<div class="alert alert-danger"><?= htmlspecialchars($error) ?></div>
|
|
<?php endif; ?>
|
|
<?php if ($success): ?>
|
|
<div class="alert alert-success"><?= htmlspecialchars($success) ?></div>
|
|
<?php endif; ?>
|
|
|
|
<div class="row mb-3">
|
|
<div class="col-md-4">
|
|
<div class="card bg-light">
|
|
<div class="card-body py-2 text-center">
|
|
<h6 class="mb-0 text-muted">إجمالي الرواتب للشهر</h6>
|
|
<h4 class="mb-0 text-primary fw-bold"><?= number_format($total_salaries, 2) ?></h4>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card shadow-sm">
|
|
<div class="card-body">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover align-middle">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>الموظف</th>
|
|
<th>الراتب الأساسي</th>
|
|
<th>إضافي</th>
|
|
<th>خصومات</th>
|
|
<th>الصافي</th>
|
|
<th>الحالة</th>
|
|
<th>إجراءات</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php if (empty($payrolls)): ?>
|
|
<tr><td colspan="7" class="text-center py-4 text-muted">لا توجد بيانات لهذا الشهر. اضغط على "توليد الرواتب" للبدء.</td></tr>
|
|
<?php else: ?>
|
|
<?php foreach ($payrolls as $row): ?>
|
|
<tr>
|
|
<td>
|
|
<div class="fw-bold"><?= htmlspecialchars($row['first_name'] . ' ' . $row['last_name']) ?></div>
|
|
<div class="small text-muted"><?= htmlspecialchars($row['job_title']) ?></div>
|
|
</td>
|
|
<td><?= number_format($row['basic_salary'], 2) ?></td>
|
|
<td class="text-success"><?= number_format($row['bonuses'], 2) ?></td>
|
|
<td class="text-danger"><?= number_format($row['deductions'], 2) ?></td>
|
|
<td class="fw-bold"><?= number_format($row['net_salary'], 2) ?></td>
|
|
<td>
|
|
<span class="badge bg-<?= $row['status'] == 'paid' ? 'success' : 'warning' ?>">
|
|
<?= $row['status'] == 'paid' ? 'مدفوع' : 'معلق' ?>
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<?php if (canEdit('hr_payroll')): ?>
|
|
<button class="btn btn-sm btn-outline-primary"
|
|
data-bs-toggle="modal"
|
|
data-bs-target="#editPayModal"
|
|
data-id="<?= $row['id'] ?>"
|
|
data-name="<?= htmlspecialchars($row['first_name']) ?>"
|
|
data-bonus="<?= $row['bonuses'] ?>"
|
|
data-deduct="<?= $row['deductions'] ?>"
|
|
data-status="<?= $row['status'] ?>">
|
|
<i class="fas fa-edit"></i>
|
|
</button>
|
|
<?php endif; ?>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
<?php endif; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Generate Modal -->
|
|
<div class="modal fade" id="generateModal" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">توليد الرواتب</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<form method="post">
|
|
<div class="modal-body">
|
|
<p>سيتم حساب الرواتب لجميع الموظفين النشطين لشهر: <strong><?= $month ?> / <?= $year ?></strong></p>
|
|
<p class="text-muted small">سيتم احتساب الخصومات تلقائياً بناءً على أيام الغياب المسجلة.</p>
|
|
<input type="hidden" name="month" value="<?= $month ?>">
|
|
<input type="hidden" name="year" value="<?= $year ?>">
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">إلغاء</button>
|
|
<button type="submit" name="generate_payroll" class="btn btn-primary">تأكيد التوليد</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Edit Payroll Modal -->
|
|
<div class="modal fade" id="editPayModal" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">تعديل الراتب: <span id="payName"></span></h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<form method="post">
|
|
<div class="modal-body">
|
|
<input type="hidden" name="id" id="payId">
|
|
<div class="mb-3">
|
|
<label class="form-label">مكافآت / إضافي</label>
|
|
<input type="number" step="0.01" name="bonuses" id="payBonus" class="form-control">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">خصومات</label>
|
|
<input type="number" step="0.01" name="deductions" id="payDeduct" class="form-control">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">الحالة</label>
|
|
<select name="status" id="payStatus" class="form-select">
|
|
<option value="pending">معلق</option>
|
|
<option value="paid">مدفوع</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">إلغاء</button>
|
|
<button type="submit" name="update_payroll" class="btn btn-primary">حفظ التغييرات</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const editPayModal = document.getElementById('editPayModal');
|
|
editPayModal.addEventListener('show.bs.modal', event => {
|
|
const button = event.relatedTarget;
|
|
document.getElementById('payId').value = button.getAttribute('data-id');
|
|
document.getElementById('payName').textContent = button.getAttribute('data-name');
|
|
document.getElementById('payBonus').value = button.getAttribute('data-bonus');
|
|
document.getElementById('payDeduct').value = button.getAttribute('data-deduct');
|
|
document.getElementById('payStatus').value = button.getAttribute('data-status');
|
|
});
|
|
</script>
|
|
|
|
<?php require_once 'includes/footer.php'; ?>
|