本文目录导读:

在PHP项目中实现请假审批流转并更新排班状态数据,通常需要结合流程引擎设计(或状态机)与排班状态联动更新,下面是一个通用的设计思路和实现步骤,涵盖表结构设计、审批流程流转、以及排班状态的更新逻辑。
核心表结构设计
你需要至少包含以下几张表:
leave_requests(请假申请表):存储请假记录。approval_flow(审批流程表):存储流程节点(如 申请人->直属上级->HR)approval_records(审批记录表):存储每一步的审批意见、时间、状态。schedules(排班表):存储员工每天的排班状态(值班/休息/请假/调休等)。
示例表结构(简化版):
-- 请假申请表
CREATE TABLE leave_requests (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL, -- 申请人ID
leave_type ENUM('annual','sick','personal') NOT NULL,
start_date DATE NOT NULL,
end_date DATE NOT NULL,
total_days DECIMAL(5,1) NOT NULL, -- 请假总天数
status ENUM('draft','pending','approved','rejected','cancelled') DEFAULT 'pending',
current_step INT DEFAULT 1, -- 当前审批步骤编号
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 审批流程配置表(不同公司可自定义)
CREATE TABLE approval_flow (
id INT PRIMARY KEY AUTO_INCREMENT,
step_no INT NOT NULL, -- 步骤序号 (1,2,3...)
approver_role VARCHAR(50) NOT NULL, -- 审批角色 (如 'manager', 'hr', 'ceo')
condition_sql TEXT DEFAULT NULL -- 可选,动态条件跳过步骤
);
-- 审批记录表
CREATE TABLE approval_records (
id INT PRIMARY KEY AUTO_INCREMENT,
request_id INT NOT NULL,
step_no INT NOT NULL,
approver_id INT NOT NULL,
action ENUM('approved','rejected','returned') NOT NULL,
comment TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 排班表 (按天记录)
CREATE TABLE schedules (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
date DATE NOT NULL,
status ENUM('working','off','leave','trip','sick') DEFAULT 'working',
leave_request_id INT DEFAULT NULL, -- 关联的请假单ID(可选)
UNIQUE KEY uk_user_date (user_id, date)
);
审批流转逻辑(核心)
请假审批通常为线性流转(按步骤顺序),每次审批通过后 current_step 加1,直到结束。
(1)提交请假申请
用户提交后,status 由 draft → pending,并设置 current_step = 1。
(2)审批人处理(同意/驳回)
- 后端处理函数示例:
// 审批处理(假设从请求取得当前审批人ID,步骤序号等)
public function approveLeave($leaveId, $approverId, $action, $comment) {
// 1. 验证该审批人是否有权审批当前步骤
$leave = LeaveRequest::find($leaveId);
$currentStep = $leave->current_step;
// 从审批流配置检查该步骤的角色是否符合当前审批人
$flowStep = ApprovalFlow::where('step_no', $currentStep)->first();
if (!$this->userHasRole($approverId, $flowStep->approver_role)) {
throw new \Exception('无权审批此步骤');
}
// 2. 记录审批记录
$record = new ApprovalRecord([
'request_id' => $leaveId,
'step_no' => $currentStep,
'approver_id' => $approverId,
'action' => $action,
'comment' => $comment,
]);
$record->save();
// 3. 根据action更新请假单状态
if ($action === 'approved') {
// 获取下一步骤序号
$nextStep = $currentStep + 1;
$nextFlowStep = ApprovalFlow::where('step_no', $nextStep)->first();
if ($nextFlowStep) {
// 还有下一步 -> 只更新current_step
$leave->current_step = $nextStep;
$leave->save();
} else {
// 所有步骤已通过 -> 审批完成
$leave->status = 'approved';
$leave->current_step = null;
$leave->save();
// *** 核心:审批通过后更新排班状态 ***
$this->updateScheduleForLeave($leave);
}
} elseif ($action === 'rejected') {
$leave->status = 'rejected';
$leave->current_step = null;
$leave->save();
} elseif ($action === 'returned') {
// 回退到上一步(或者回到申请人)可自定义
$leave->status = 'pending'; // 或者 'draft'
$leave->current_step = max(1, $currentStep - 1);
$leave->save();
}
}
(3)更新排班状态(关键函数)
在审批最终通过时(或发起的瞬间?根据业务),批量更新排班表:
private function updateScheduleForLeave(LeaveRequest $leave) {
// 获取请假起止日期
$start = new \DateTime($leave->start_date);
$end = new \DateTime($leave->end_date);
$interval = new \DateInterval('P1D');
$period = new \DatePeriod($start, $interval, $end->modify('+1 day')); // 包含结束日
foreach ($period as $date) {
$dateStr = $date->format('Y-m-d');
// 检查是否已存在当天的排班(避免覆盖其他类型)
$schedule = Schedule::updateOrCreate(
['user_id' => $leave->user_id, 'date' => $dateStr],
[
'status' => 'leave', // 或 mapping 到具体请假类型
'leave_request_id' => $leave->id,
]
);
// 如果原本是 'off' 休息,可选覆盖或不覆盖,根据业务
}
}
注意:如果请假在审批通过前已经影响了排班(比如提交后立即占用名额),则应该在提交时就用“pending_leave”等中间状态标记排班,最终审批通过后变为“leave”,驳回后恢复。
取消/撤回请假时的状态恢复
如果请假单被取消或驳回,需要恢复排班状态(通常改为 off 或 working 取决于原始排班,建议在排班表中额外存一个 previous_status 字段以便恢复)。
public function cancelLeave($leaveId) {
$leave = LeaveRequest::find($leaveId);
// 先恢复排班
Schedule::where('leave_request_id', $leaveId)
->update(['status' => 'working', 'leave_request_id' => null]);
// 如果之前是 'off',则需特殊处理,建议存储原始状态
$leave->status = 'cancelled';
$leave->save();
}
动态审批流(可选增强)
如果审批步骤不是固定的(比如不同部门、不同天数),可将 approval_flow 表设计成可配置的,并绑定到 leave_requests 的特定条件。
// 根据请假类型、天数动态计算审批流
public function getApprovalFlow(LeaveRequest $leave) {
// 规则例子:1-3天经理审批, 3-7天+HR, 7天以上+老板
if ($leave->total_days <= 3) {
return [['step'=>1, 'role'=>'manager']];
}
return [['step'=>1, 'role'=>'manager'], ['step'=>2, 'role'=>'hr']];
}
在提交时动态设置 current_step 及对应的 approval_flow 表。
排班状态与请假状态的联动时机
| 动作 | 排班状态变化 |
|---|---|
| 提交请假(未审批) | 可选:占位(状态变为 pending_leave)或无变化 |
| 审批通过 | 改为 leave(或具体类型) |
| 审批驳回 | 恢复为原状态(需记录原始状态) |
| 用户取消(撤回) | 同上恢复 |
| 假条修改(例如改日期) | 先清除旧排班,再按新日期新增(需事务) |
事务与一致性
保证 请假单状态 与 排班状态 的一致性,建议使用数据库事务 + 锁(如乐观锁 version 字段)。
DB::transaction(function () use ($leaveId, $approverId, $action) {
// 锁住 leave_requests 行
$leave = LeaveRequest::where('id', $leaveId)->lockForUpdate()->first();
$this->approveLeave($leave, $approverId, $action, $comment);
// 排班更新等...
});
- 审批流转:利用
current_step控制流程前进,每一步记录审批记录。 - 排班更新:在最终状态变化(审批通过/取消)时触发批量更新
schedules表。 - 推荐:使用 状态机模式(如
spatie/laravel-states)封装请假状态变化,并在状态转移时通过事件监听自动更新排班,保持代码清晰扩展。
这样,你的请假审批系统既能灵活流转,又能与排班数据实时联动。