本文目录导读:

我来为您提供一个完整的PHP调休管理系统实现方案,这个系统包括员工调休申请、审批流程、余额管理等核心功能。
数据库设计
创建数据表
-- 员工表
CREATE TABLE `employees` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`employee_no` VARCHAR(20) NOT NULL UNIQUE,
`name` VARCHAR(50) NOT NULL,
`department` VARCHAR(50),
`position` VARCHAR(50),
`email` VARCHAR(100),
`phone` VARCHAR(20),
`status` TINYINT DEFAULT 1, -- 1:在职 0:离职
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 调休余额表
CREATE TABLE `leave_balances` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`employee_id` INT NOT NULL,
`year` YEAR NOT NULL,
`total_hours` DECIMAL(6,1) DEFAULT 0, -- 总调休小时数
`used_hours` DECIMAL(6,1) DEFAULT 0, -- 已使用小时数
`remaining_hours` DECIMAL(6,1) AS (total_hours - used_hours) STORED,
FOREIGN KEY (employee_id) REFERENCES employees(id),
UNIQUE KEY `employee_year` (employee_id, year)
);
-- 调休申请记录表
CREATE TABLE `overtime_records` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`employee_id` INT NOT NULL,
`overtime_date` DATE NOT NULL,
`start_time` TIME NOT NULL,
`end_time` TIME NOT NULL,
`hours` DECIMAL(4,1) NOT NULL,
`reason` TEXT,
`status` ENUM('pending', 'approved', 'rejected') DEFAULT 'pending',
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`approved_by` INT,
`approved_at` TIMESTAMP,
FOREIGN KEY (employee_id) REFERENCES employees(id)
);
-- 调休申请表
CREATE TABLE `leave_requests` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`employee_id` INT NOT NULL,
`start_date` DATE NOT NULL,
`end_date` DATE NOT NULL,
`total_hours` DECIMAL(4,1) NOT NULL,
`reason` TEXT,
`status` ENUM('pending', 'approved', 'rejected', 'cancelled') DEFAULT 'pending',
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`approved_by` INT,
`approved_at` TIMESTAMP,
`remarks` TEXT,
FOREIGN KEY (employee_id) REFERENCES employees(id)
);
-- 审批流程表
CREATE TABLE `approval_flow` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`request_type` ENUM('overtime', 'leave') NOT NULL,
`request_id` INT NOT NULL,
`approver_id` INT NOT NULL,
`level` INT DEFAULT 1, -- 审批层级
`status` ENUM('pending', 'approved', 'rejected') DEFAULT 'pending',
`comment` TEXT,
`processed_at` TIMESTAMP
);
核心PHP类实现
数据库连接配置 (config/database.php)
<?php
class Database {
private $host = 'localhost';
private $db_name = 'leave_management';
private $username = 'root';
private $password = '';
private $conn;
public function getConnection() {
$this->conn = null;
try {
$this->conn = new PDO(
"mysql:host={$this->host};dbname={$this->db_name}",
$this->username,
$this->password
);
$this->conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException $e) {
echo "Connection error: " . $e->getMessage();
}
return $this->conn;
}
}
调休管理类 (classes/LeaveManagement.php)
<?php
require_once 'config/database.php';
class LeaveManagement {
private $db;
private $conn;
public function __construct() {
$this->db = new Database();
$this->conn = $this->db->getConnection();
}
// 获取员工调休余额
public function getLeaveBalance($employeeId, $year = null) {
$year = $year ?? date('Y');
$query = "SELECT total_hours, used_hours, remaining_hours
FROM leave_balances
WHERE employee_id = :employee_id AND year = :year";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':employee_id', $employeeId);
$stmt->bindParam(':year', $year);
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$result) {
return ['total_hours' => 0, 'used_hours' => 0, 'remaining_hours' => 0];
}
return $result;
}
// 添加加班记录(增加调休余额)
public function addOvertime($employeeId, $date, $startTime, $endTime, $reason) {
try {
$this->conn->beginTransaction();
// 计算加班小时数
$hours = $this->calculateHours($startTime, $endTime);
// 插入加班记录
$query = "INSERT INTO overtime_records
(employee_id, overtime_date, start_time, end_time, hours, reason, status)
VALUES (:employee_id, :overtime_date, :start_time, :end_time, :hours, :reason, 'pending')";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':employee_id', $employeeId);
$stmt->bindParam(':overtime_date', $date);
$stmt->bindParam(':start_time', $startTime);
$stmt->bindParam(':end_time', $endTime);
$stmt->bindParam(':hours', $hours);
$stmt->bindParam(':reason', $reason);
$stmt->execute();
$overtimeId = $this->conn->lastInsertId();
$this->conn->commit();
return ['success' => true, 'message' => '加班记录提交成功', 'id' => $overtimeId];
} catch (Exception $e) {
$this->conn->rollBack();
return ['success' => false, 'message' => $e->getMessage()];
}
}
// 提交调休申请
public function submitLeaveRequest($employeeId, $startDate, $endDate, $hours, $reason) {
try {
// 检查调休余额
$balance = $this->getLeaveBalance($employeeId);
if ($balance['remaining_hours'] < $hours) {
return ['success' => false, 'message' => '调休余额不足'];
}
$this->conn->beginTransaction();
// 插入调休申请
$query = "INSERT INTO leave_requests
(employee_id, start_date, end_date, total_hours, reason, status)
VALUES (:employee_id, :start_date, :end_date, :total_hours, :reason, 'pending')";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':employee_id', $employeeId);
$stmt->bindParam(':start_date', $startDate);
$stmt->bindParam(':end_date', $endDate);
$stmt->bindParam(':total_hours', $hours);
$stmt->bindParam(':reason', $reason);
$stmt->execute();
$requestId = $this->conn->lastInsertId();
$this->conn->commit();
return ['success' => true, 'message' => '调休申请提交成功', 'id' => $requestId];
} catch (Exception $e) {
$this->conn->rollBack();
return ['success' => false, 'message' => $e->getMessage()];
}
}
// 审批调休申请
public function approveLeaveRequest($requestId, $approverId, $status, $comment = '') {
try {
$this->conn->beginTransaction();
// 获取申请详情
$request = $this->getLeaveRequest($requestId);
if (!$request) {
return ['success' => false, 'message' => '申请记录不存在'];
}
// 更新申请状态
$query = "UPDATE leave_requests
SET status = :status,
approved_by = :approved_by,
approved_at = NOW(),
remarks = :remarks
WHERE id = :id";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':status', $status);
$stmt->bindParam(':approved_by', $approverId);
$stmt->bindParam(':remarks', $comment);
$stmt->bindParam(':id', $requestId);
$stmt->execute();
// 如果审批通过,更新调休余额
if ($status === 'approved') {
$this->updateLeaveBalance($request['employee_id'], $request['total_hours'], 'deduct');
}
$this->conn->commit();
return ['success' => true, 'message' => '审批完成'];
} catch (Exception $e) {
$this->conn->rollBack();
return ['success' => false, 'message' => $e->getMessage()];
}
}
// 更新调休余额
private function updateLeaveBalance($employeeId, $hours, $type) {
$year = date('Y');
// 检查是否有余额记录
$query = "SELECT id FROM leave_balances
WHERE employee_id = :employee_id AND year = :year";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':employee_id', $employeeId);
$stmt->bindParam(':year', $year);
$stmt->execute();
if ($stmt->rowCount() == 0) {
// 创建新记录
$query = "INSERT INTO leave_balances (employee_id, year, total_hours, used_hours)
VALUES (:employee_id, :year, 0, 0)";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':employee_id', $employeeId);
$stmt->bindParam(':year', $year);
$stmt->execute();
}
// 更新余额
if ($type === 'add') {
$query = "UPDATE leave_balances
SET total_hours = total_hours + :hours
WHERE employee_id = :employee_id AND year = :year";
} else if ($type === 'deduct') {
$query = "UPDATE leave_balances
SET used_hours = used_hours + :hours
WHERE employee_id = :employee_id AND year = :year";
}
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':hours', $hours);
$stmt->bindParam(':employee_id', $employeeId);
$stmt->bindParam(':year', $year);
$stmt->execute();
}
// 计算时间段小时数
private function calculateHours($startTime, $endTime) {
$start = strtotime($startTime);
$end = strtotime($endTime);
$diff = $end - $start;
return round($diff / 3600, 1);
}
// 获取调休申请详情
public function getLeaveRequest($requestId) {
$query = "SELECT * FROM leave_requests WHERE id = :id";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':id', $requestId);
$stmt->execute();
return $stmt->fetch(PDO::FETCH_ASSOC);
}
// 获取员工的调休申请列表
public function getEmployeeLeaveRequests($employeeId) {
$query = "SELECT * FROM leave_requests WHERE employee_id = :employee_id
ORDER BY created_at DESC";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':employee_id', $employeeId);
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// 获取待审批列表
public function getPendingApprovals($approverId = null) {
$query = "SELECT lr.*, e.name as employee_name, e.department
FROM leave_requests lr
JOIN employees e ON lr.employee_id = e.id
WHERE lr.status = 'pending'";
if ($approverId) {
$query .= " AND lr.approved_by = :approver_id";
}
$query .= " ORDER BY lr.created_at DESC";
$stmt = $this->conn->prepare($query);
if ($approverId) {
$stmt->bindParam(':approver_id', $approverId);
}
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// 取消调休申请
public function cancelLeaveRequest($requestId, $employeeId) {
try {
$request = $this->getLeaveRequest($requestId);
if (!$request || $request['employee_id'] != $employeeId) {
return ['success' => false, 'message' => '无权限取消此申请'];
}
if ($request['status'] !== 'pending') {
return ['success' => false, 'message' => '只能取消待审批的申请'];
}
$query = "UPDATE leave_requests SET status = 'cancelled' WHERE id = :id";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':id', $requestId);
$stmt->execute();
return ['success' => true, 'message' => '申请已取消'];
} catch (Exception $e) {
return ['success' => false, 'message' => $e->getMessage()];
}
}
// 获取部门调休统计
public function getDepartmentLeaveStats($department, $year = null) {
$year = $year ?? date('Y');
$query = "SELECT
e.department,
COUNT(DISTINCT e.id) as employee_count,
SUM(lb.total_hours) as total_overtime_hours,
SUM(lb.used_hours) as total_used_hours,
SUM(lb.remaining_hours) as total_remaining_hours
FROM employees e
LEFT JOIN leave_balances lb ON e.id = lb.employee_id AND lb.year = :year
WHERE e.department = :department AND e.status = 1
GROUP BY e.department";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':department', $department);
$stmt->bindParam(':year', $year);
$stmt->execute();
return $stmt->fetch(PDO::FETCH_ASSOC);
}
}
前端页面示例
员工调休申请页面 (apply_leave.php)
<?php
require_once 'classes/LeaveManagement.php';
session_start();
// 假设已登录员工的ID
$employeeId = $_SESSION['employee_id'];
$leaveMgmt = new LeaveManagement();
// 获取调休余额
$balance = $leaveMgmt->getLeaveBalance($employeeId);
// 处理表单提交
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$result = $leaveMgmt->submitLeaveRequest(
$employeeId,
$_POST['start_date'],
$_POST['end_date'],
$_POST['total_hours'],
$_POST['reason']
);
if ($result['success']) {
echo "<script>alert('申请提交成功!');</script>";
} else {
echo "<script>alert('{$result['message']}');</script>";
}
}
?>
<!DOCTYPE html>
<html>
<head>调休申请</title>
<meta charset="utf-8">
<style>
.container {
max-width: 800px;
margin: 20px auto;
padding: 20px;
}
.balance-info {
background: #f5f5f5;
padding: 15px;
border-radius: 5px;
margin-bottom: 20px;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input, select, textarea {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
button {
background: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.history-table {
width: 100%;
border-collapse: collapse;
margin-top: 30px;
}
.history-table th, .history-table td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
.status-pending {
color: #ff9800;
}
.status-approved {
color: #4CAF50;
}
.status-rejected {
color: #f44336;
}
</style>
</head>
<body>
<div class="container">
<h1>调休申请</h1>
<!-- 调休余额显示 -->
<div class="balance-info">
<h3>我的调休余额</h3>
<p>总调休时长:<?php echo $balance['total_hours']; ?> 小时</p>
<p>已使用:<?php echo $balance['used_hours']; ?> 小时</p>
<p><strong>可用余额:<?php echo $balance['remaining_hours']; ?> 小时</strong></p>
</div>
<!-- 申请表单 -->
<form method="POST" onsubmit="return validateForm()">
<div class="form-group">
<label>开始日期:</label>
<input type="date" name="start_date" required>
</div>
<div class="form-group">
<label>结束日期:</label>
<input type="date" name="end_date" required>
</div>
<div class="form-group">
<label>调休小时数:</label>
<input type="number" name="total_hours" step="0.5"
min="0.5" max="<?php echo $balance['remaining_hours']; ?>" required>
<small>可用余额:<?php echo $balance['remaining_hours']; ?> 小时</small>
</div>
<div class="form-group">
<label>申请原因:</label>
<textarea name="reason" rows="4" required></textarea>
</div>
<button type="submit">提交申请</button>
</form>
<!-- 历史申请记录 -->
<h2>我的申请记录</h2>
<table class="history-table">
<thead>
<tr>
<th>申请日期</th>
<th>调休日期</th>
<th>小时数</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php
$requests = $leaveMgmt->getEmployeeLeaveRequests($employeeId);
foreach ($requests as $request):
?>
<tr>
<td><?php echo date('Y-m-d', strtotime($request['created_at'])); ?></td>
<td><?php echo $request['start_date'] . ' ~ ' . $request['end_date']; ?></td>
<td><?php echo $request['total_hours']; ?>小时</td>
<td class="status-<?php echo $request['status']; ?>">
<?php
$statusMap = [
'pending' => '待审批',
'approved' => '已批准',
'rejected' => '已拒绝',
'cancelled' => '已取消'
];
echo $statusMap[$request['status']];
?>
</td>
<td>
<?php if ($request['status'] === 'pending'): ?>
<button onclick="cancelRequest(<?php echo $request['id']; ?>)">取消</button>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<script>
function validateForm() {
var startDate = document.querySelector('[name="start_date"]').value;
var endDate = document.querySelector('[name="end_date"]').value;
var hours = parseFloat(document.querySelector('[name="total_hours"]').value);
var maxHours = parseFloat('<?php echo $balance['remaining_hours']; ?>');
if (startDate > endDate) {
alert('开始日期不能晚于结束日期');
return false;
}
if (hours > maxHours) {
alert('调休小时数不能超过可用余额');
return false;
}
return confirm('确认提交调休申请?');
}
function cancelRequest(requestId) {
if (confirm('确定要取消此申请吗?')) {
window.location.href = 'cancel_leave.php?id=' + requestId;
}
}
</script>
</body>
</html>
API接口示例 (api/leaves.php)
<?php
header('Content-Type: application/json');
require_once '../classes/LeaveManagement.php';
$leaveMgmt = new LeaveManagement();
$method = $_SERVER['REQUEST_METHOD'];
switch ($method) {
case 'GET':
// 获取调休信息
if (isset($_GET['action'])) {
switch ($_GET['action']) {
case 'balance':
$result = $leaveMgmt->getLeaveBalance(
$_GET['employee_id'],
$_GET['year'] ?? date('Y')
);
break;
case 'requests':
$result = $leaveMgmt->getEmployeeLeaveRequests(
$_GET['employee_id']
);
break;
case 'pending':
$result = $leaveMgmt->getPendingApprovals(
$_GET['approver_id'] ?? null
);
break;
case 'stats':
$result = $leaveMgmt->getDepartmentLeaveStats(
$_GET['department'],
$_GET['year'] ?? date('Y')
);
break;
default:
$result = ['success' => false, 'message' => '无效的操作'];
}
}
break;
case 'POST':
// 提交调休申请或加班记录
$data = json_decode(file_get_contents('php://input'), true);
if ($data['type'] === 'overtime') {
$result = $leaveMgmt->addOvertime(
$data['employee_id'],
$data['date'],
$data['start_time'],
$data['end_time'],
$data['reason']
);
} elseif ($data['type'] === 'leave') {
$result = $leaveMgmt->submitLeaveRequest(
$data['employee_id'],
$data['start_date'],
$data['end_date'],
$data['total_hours'],
$data['reason']
);
}
break;
case 'PUT':
// 审批调休申请
$data = json_decode(file_get_contents('php://input'), true);
$result = $leaveMgmt->approveLeaveRequest(
$data['request_id'],
$data['approver_id'],
$data['status'],
$data['comment'] ?? ''
);
break;
case 'DELETE':
// 取消调休申请
$result = $leaveMgmt->cancelLeaveRequest(
$_GET['id'],
$_GET['employee_id']
);
break;
default:
$result = ['success' => false, 'message' => '不支持的请求方法'];
}
echo json_encode($result);
使用示例
// 初始化调休管理系统
$leaveMgmt = new LeaveManagement();
// 员工提交调休申请
$result = $leaveMgmt->submitLeaveRequest(
1, // 员工ID
'2024-01-15', // 开始日期
'2024-01-15', // 结束日期
8, // 调休小时数
'调休补休加班'
);
// 审批调休申请
$result = $leaveMgmt->approveLeaveRequest(
1, // 申请ID
2, // 审批人ID
'approved', // 状态
'同意调休' // 备注
);
// 获取员工调休余额
$balance = $leaveMgmt->getLeaveBalance(1);
// 获取待审批列表
$pendingList = $leaveMgmt->getPendingApprovals();
安全建议
// 添加CSRF保护
session_start();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) {
die('CSRF token validation failed');
}
}
// SQL注入防护(已使用PDO预处理语句)
// XSS防护
function sanitizeOutput($data) {
return htmlspecialchars($data, ENT_QUOTES, 'UTF-8');
}
// 权限验证
function checkPermission($employeeId, $requiredRole) {
// 实现权限检查逻辑
}
这个系统提供了完整的调休管理功能,包括:
- 员工端:查看余额、提交申请、查看记录
- 管理端:审批申请、统计报表、余额管理
- 核心功能:加班记录管理、调休申请、审批流程、余额自动更新
您可以根据实际需求进一步扩展功能,如添加邮件通知、批量导入导出、更复杂的审批流程等。