215 lines
12 KiB
PHP
215 lines
12 KiB
PHP
<?php
|
|
session_start();
|
|
require_once __DIR__ . '/db/config.php';
|
|
require_once __DIR__ . '/includes/payroll_engine.php';
|
|
|
|
if (!isset($_SESSION['user_id']) || !isset($_SESSION['company_id']) || $_SESSION['role'] !== 'admin') {
|
|
header('Location: /login.php');
|
|
exit;
|
|
}
|
|
|
|
$company_id = $_SESSION['company_id'];
|
|
$error_message = '';
|
|
$success_message = '';
|
|
$payroll_results = [];
|
|
|
|
// Default to current month and year
|
|
$pay_period_month = $_POST['pay_period_month'] ?? date('m');
|
|
$pay_period_year = $_POST['pay_period_year'] ?? date('Y');
|
|
|
|
// Handle Run Payroll
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['run_payroll'])) {
|
|
try {
|
|
$pdo = db();
|
|
|
|
// 1. Fetch settings
|
|
$stmt = $pdo->prepare("SELECT setting_key, setting_value FROM settings WHERE company_id = ?");
|
|
$stmt->execute([$company_id]);
|
|
$settings_raw = $stmt->fetchAll();
|
|
$settings = [];
|
|
foreach ($settings_raw as $row) {
|
|
$settings[$row['setting_key']] = $row['setting_value'];
|
|
}
|
|
|
|
// 2. Fetch employees
|
|
$stmt = $pdo->prepare("SELECT * FROM employees WHERE company_id = ?");
|
|
$stmt->execute([$company_id]);
|
|
$employees = $stmt->fetchAll();
|
|
|
|
if (empty($employees)) {
|
|
throw new Exception("No employees found for this company.");
|
|
}
|
|
|
|
// 3. Calculate payroll for each employee
|
|
foreach ($employees as $employee) {
|
|
$payroll_data = calculate_payroll((float)$employee['basic_salary'], $settings);
|
|
$payroll_results[] = [
|
|
'employee' => $employee,
|
|
'payroll' => $payroll_data,
|
|
];
|
|
}
|
|
$success_message = "Payroll calculated for " . count($employees) . " employees.";
|
|
|
|
} catch (Exception $e) {
|
|
$error_message = "Error calculating payroll: " . $e->getMessage();
|
|
}
|
|
}
|
|
|
|
// Handle Save Payroll
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['save_payroll'])) {
|
|
$results_json = $_POST['payroll_results_json'] ?? '[]';
|
|
$results_to_save = json_decode($results_json, true);
|
|
|
|
if (empty($results_to_save)) {
|
|
$error_message = "No payroll data to save.";
|
|
} else {
|
|
try {
|
|
$pdo = db();
|
|
$pdo->beginTransaction();
|
|
|
|
// 1. Create payroll run record
|
|
$stmt = $pdo->prepare("INSERT INTO payroll_runs (company_id, pay_period_month, pay_period_year, status) VALUES (?, ?, ?, 'completed') ON DUPLICATE KEY UPDATE status='completed'");
|
|
$stmt->execute([$company_id, $pay_period_month, $pay_period_year]);
|
|
$run_id = $pdo->lastInsertId();
|
|
// If the run already existed, get its ID
|
|
if ($run_id == 0) {
|
|
$stmt = $pdo->prepare("SELECT id FROM payroll_runs WHERE company_id = ? AND pay_period_year = ? AND pay_period_month = ?");
|
|
$stmt->execute([$company_id, $pay_period_year, $pay_period_month]);
|
|
$run_id = $stmt->fetchColumn();
|
|
}
|
|
|
|
// 2. Save each payslip
|
|
foreach ($results_to_save as $result) {
|
|
$employee_id = $result['employee']['id'];
|
|
$payroll = $result['payroll'];
|
|
|
|
$stmt = $pdo->prepare("INSERT INTO payslips (payroll_run_id, employee_id, gross_pay, total_deductions, net_pay) VALUES (?, ?, ?, ?, ?)");
|
|
$stmt->execute([$run_id, $employee_id, $payroll['gross_pay'], $payroll['total_deductions'], $payroll['net_pay']]);
|
|
$payslip_id = $pdo->lastInsertId();
|
|
|
|
// 3. Save payslip items
|
|
foreach ($payroll['items'] as $item) {
|
|
$stmt = $pdo->prepare("INSERT INTO payslip_items (payslip_id, type, description, amount) VALUES (?, ?, ?, ?)");
|
|
$stmt->execute([$payslip_id, $item['type'], $item['description'], $item['amount']]);
|
|
}
|
|
}
|
|
|
|
$pdo->commit();
|
|
$success_message = "Payroll run for {$pay_period_month}/{$pay_period_year} has been saved successfully!";
|
|
$payroll_results = []; // Clear results from view after saving
|
|
|
|
} catch (Exception $e) {
|
|
if ($pdo->inTransaction()) $pdo->rollBack();
|
|
$error_message = "Failed to save payroll run: " . $e->getMessage();
|
|
}
|
|
}
|
|
}
|
|
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Run Payroll - GPTPayroll</title>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
</head>
|
|
<body class="bg-gray-100 font-sans leading-normal tracking-normal">
|
|
<div class="flex md:flex-row-reverse flex-wrap">
|
|
|
|
<!-- Main Content -->
|
|
<div class="w-full md:w-4/5 bg-gray-100">
|
|
<div class="container bg-gray-100 pt-16 px-6 mx-auto">
|
|
|
|
<?php if ($error_message): ?>
|
|
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative mb-4" role="alert"><span class="block sm:inline"><?= htmlspecialchars($error_message) ?></span></div>
|
|
<?php endif; ?>
|
|
<?php if ($success_message): ?>
|
|
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded relative mb-4" role="alert"><span class="block sm:inline"><?= htmlspecialchars($success_message) ?></span></div>
|
|
<?php endif; ?>
|
|
|
|
<!-- Payroll Run Form -->
|
|
<div class="bg-white shadow-md rounded p-8 mb-8">
|
|
<h2 class="text-2xl font-bold text-gray-800 mb-4">Run Payroll</h2>
|
|
<form action="/payroll.php" method="POST" class="flex items-end space-x-4">
|
|
<div>
|
|
<label for="pay_period_month" class="block text-gray-700 text-sm font-bold mb-2">Pay Period</label>
|
|
<select name="pay_period_month" id="pay_period_month" class="shadow border rounded py-2 px-3 text-gray-700">
|
|
<?php for ($i = 1; $i <= 12; $i++): ?>
|
|
<option value="<?= str_pad((string)$i, 2, '0', STR_PAD_LEFT) ?>" <?= ($pay_period_month == $i) ? 'selected' : '' ?>><?= date('F', mktime(0, 0, 0, $i, 10)) ?></option>
|
|
<?php endfor; ?>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<select name="pay_period_year" id="pay_period_year" class="shadow border rounded py-2 px-3 text-gray-700">
|
|
<?php for ($i = date('Y'); $i >= date('Y') - 5; $i--): ?>
|
|
<option value="<?= $i ?>" <?= ($pay_period_year == $i) ? 'selected' : '' ?>><?= $i ?></option>
|
|
<?php endfor; ?>
|
|
</select>
|
|
</div>
|
|
<button type="submit" name="run_payroll" class="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded">Calculate Payroll</button>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Payroll Results -->
|
|
<?php if (!empty($payroll_results)): ?>
|
|
<div class="bg-white shadow-md rounded p-8">
|
|
<h2 class="text-2xl font-bold text-gray-800 mb-4">Payroll Preview for <?= htmlspecialchars(date('F Y', mktime(0,0,0,(int)$pay_period_month,1,(int)$pay_period_year))) ?></h2>
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full divide-y divide-gray-200">
|
|
<thead class="bg-gray-50">
|
|
<tr>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Employee</th>
|
|
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase">Gross Pay</th>
|
|
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase">NAPSA</th>
|
|
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase">NHIMA</th>
|
|
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase">PAYE</th>
|
|
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase">Total Deductions</th>
|
|
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase">Net Pay</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="bg-white divide-y divide-gray-200">
|
|
<?php foreach ($payroll_results as $result): ?>
|
|
<tr>
|
|
<td class="px-6 py-4 text-sm font-medium text-gray-900"><?= htmlspecialchars($result['employee']['first_name'] . ' ' . $result['employee']['last_name']) ?></td>
|
|
<td class="px-6 py-4 text-sm text-gray-500 text-right">ZMW <?= number_format($result['payroll']['gross_pay'], 2) ?></td>
|
|
<td class="px-6 py-4 text-sm text-gray-500 text-right">ZMW <?= number_format($result['payroll']['deductions']['napsa'] ?? 0, 2) ?></td>
|
|
<td class="px-6 py-4 text-sm text-gray-500 text-right">ZMW <?= number_format($result['payroll']['deductions']['nhima'] ?? 0, 2) ?></td>
|
|
<td class="px-6 py-4 text-sm text-gray-500 text-right">ZMW <?= number_format($result['payroll']['taxes']['paye'] ?? 0, 2) ?></td>
|
|
<td class="px-6 py-4 text-sm text-gray-500 text-right font-bold">ZMW <?= number_format($result['payroll']['total_deductions'], 2) ?></td>
|
|
<td class="px-6 py-4 text-sm text-gray-500 text-right font-bold">ZMW <?= number_format($result['payroll']['net_pay'], 2) ?></td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<form action="/payroll.php" method="POST" class="mt-6">
|
|
<input type="hidden" name="pay_period_month" value="<?= htmlspecialchars($pay_period_month) ?>">
|
|
<input type="hidden" name="pay_period_year" value="<?= htmlspecialchars($pay_period_year) ?>">
|
|
<input type="hidden" name="payroll_results_json" value='<?= htmlspecialchars(json_encode($payroll_results)) ?>'>
|
|
<button type="submit" name="save_payroll" class="bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-4 rounded">Finalize & Save Payroll Run</button>
|
|
</form>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sidebar -->
|
|
<div class="w-full md:w-1/5 bg-gray-800 md:min-h-screen">
|
|
<div class="md:relative mx-auto lg:float-right lg:px-6">
|
|
<ul class="list-reset flex flex-row md:flex-col text-center md:text-left">
|
|
<li class="mr-3 flex-1"><a href="/dashboard.php" class="block py-4 px-4 text-gray-400 hover:text-white no-underline">Dashboard</a></li>
|
|
<li class="mr-3 flex-1"><a href="/employees.php" class="block py-4 px-4 text-gray-400 hover:text-white no-underline">Employees</a></li>
|
|
<li class="mr-3 flex-1"><a href="/payroll.php" class="block py-4 px-4 text-white font-bold no-underline">Payroll</a></li>
|
|
<li class="mr-3 flex-1"><a href="/payroll_history.php" class="block py-4 px-4 text-gray-400 hover:text-white no-underline">Payroll History</a></li>
|
|
<li class="mr-3 flex-1"><a href="/settings.php" class="block py-4 px-4 text-gray-400 hover:text-white no-underline">Settings</a></li>
|
|
<li class="mr-3 flex-1"><a href="/logout.php" class="block py-4 px-4 text-gray-400 hover:text-white no-underline">Logout</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</body>
|
|
</html>
|