如何用PHP项目实现调休管理?

wen java案例 5

本文目录导读:

如何用PHP项目实现调休管理?

  1. 数据库设计
  2. 核心PHP类实现
  3. 前端页面示例
  4. API接口示例 (api/leaves.php)
  5. 使用示例
  6. 安全建议

我来为您提供一个完整的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) {
    // 实现权限检查逻辑
}

这个系统提供了完整的调休管理功能,包括:

  1. 员工端:查看余额、提交申请、查看记录
  2. 管理端:审批申请、统计报表、余额管理
  3. 核心功能:加班记录管理、调休申请、审批流程、余额自动更新

您可以根据实际需求进一步扩展功能,如添加邮件通知、批量导入导出、更复杂的审批流程等。

抱歉,评论功能暂时关闭!