SpringBoot+Vue3 企业人事调动全流程设计:调动申请+BPM审批+生效日回写档案——从“纸面调岗”到“审批即调动”
深度拆解企业人事调动管理系统的完整设计方案。1张调动申请单承载员工原/新部门、公司、职位、职务等关键变更信息,BPM审批通过后支持立即生效或按生效日定时回写员工档案,配合XXL-Job处理延迟生效场景,实现从申请、审批到档案更新的完整闭环。
SpringBoot+Vue3 企业人事调动全流程设计:调动申请+BPM审批+生效日回写档案——从“纸面调岗”到“审批即调动”
🌐 文档地址:http://ruoyioffice.com | 📦 源码1:https://gitcode.com/zhouzhongyan/ruoyi-office-vben.git | 📦 源码2:https://gitcode.com/zhouzhongyan/ruoyi-office.git | 📦 源码3:https://github.com/yuqing2026/ruoyi-office.git | 💬 微信:17156169080(备注「RuoYi Office」)
人事调动是企业组织活力最直接的体现——员工从测试部转到产品部、从深圳公司调到北京公司、从普通岗位转到管理岗位,看起来只是改几个字段,真正做成系统却必须回答很多问题:调动是否立即生效?审批通过前能不能改档案?跨公司调动谁来审批?如果生效日是下个月 1 号,今天审批通过要不要立刻更新员工信息?RuoYi Office 用 1 张调动申请单 + BPM 审批 + 生效日回写策略 + XXL-Job 定时补写机制,把“调动申请”和“档案更新”彻底打通。
▲ 人事调动业务流转全景:申请单记录原/新组织与岗位信息,BPM 审批驱动状态流转,通过后可立即回写档案,也可按生效日期由 XXL-Job 定时生效
引言:人事调动到底难在哪?
“不就是把部门改一下吗?”很多人第一次做调动管理时都会低估它的复杂度。事实上,它是 HRM 里非常典型的“审批数据”和“正式档案”双轨问题。
调动信息不是简单覆盖:员工当前属于哪个部门、哪家公司、什么职位、什么职务,都必须在审批前完整保留,供审批人对照“原状态”和“目标状态”。
生效时间不一定等于审批时间:今天 4 月 15 日审批通过,不代表调动一定今天生效。很多企业要求“次月 1 日统一生效”,这就需要把“审批通过”和“档案变更”拆成两步。
调动范围是复合变更:一次调动可能同时变更公司、部门、职位、职务,也可能只调部门不调岗位。系统必须允许“按字段局部回写”,而不是全量覆盖员工档案。
组织信息要级联联动:选择“变更为部门”后,往往能推导出“变更为公司”。如果前端不能自动带出,HR 就得重复选择两遍,还容易选错。
审批和档案更新必须强一致:如果档案先改了、流程后走,审批被拒绝就会出现严重脏数据;如果流程通过了但档案没有更新,也会导致员工信息滞后。
| 痛点 | 传统做法 | 后果 |
|---|---|---|
| 原/新岗位信息不留痕 | 直接修改员工档案 | 事后无法审计“调前是什么、调后是什么” |
| 审批和生效时间混淆 | 审批通过立即人工改档案 | 无法支持“下月生效”等真实场景 |
| 调动范围不完整 | 只改部门,不改公司/岗位 | 档案字段彼此矛盾 |
| 部门/公司联动靠人记 | HR 手动二次选择 | 选错公司或部门的概率很高 |
| 流程与档案脱节 | 审批通过后再人工同步 | 忙忘了就出现数据滞后 |
本文就以 RuoYi Office 的人事调动模块为例,拆解它如何通过调动申请单 + BPM 审批 + 立即生效/定时生效双策略,把组织异动这件事做成真正可靠的系统能力。
一、业务设计:审批通过不一定立刻生效
1.1 业务全景
一次完整的人事调动涉及 4 个阶段:
| 阶段 | 角色 | 操作 | 系统行为 |
|---|---|---|---|
| 发起 | HR / 直属上级 | 选择员工,填写异动类型、原因、新组织与岗位 | 创建调动申请单 |
| 审批 | HR / 主管 / 领导 | 审核调动合理性 | BPM 流程推进 |
| 生效 | 系统自动 | 根据生效策略执行 | 立即回写或按日期定时回写 |
| 归档 | HR | 查看申请单与档案结果 | 保留完整调动留痕 |
1.2 调动单为什么要冗余“原信息”和“新信息”?
人事调动单不是简单地引用一份员工档案,而是必须沉淀一份审批快照:
| 字段组 | 代表字段 | 作用 |
|---|---|---|
| 原信息 | 原公司、原部门、原职位、原职务 | 审批对照、留痕审计 |
| 新信息 | 新公司、新部门、新职位、新职务 | 审批目标、回写来源 |
| 控制信息 | 是否立即生效、生效日期 | 决定档案何时更新 |
如果只保存“新值”,审批人永远看不到“原来在哪里”;如果直接读取当前员工档案,当员工在流程审批期间又发生其他变动,单据就会失真。
1.3 两种生效策略
这是本方案最重要的业务抽象:审批通过和档案生效可以相同,也可以不同。
| 策略 | 字段组合 | 适用场景 |
|---|---|---|
| 立即生效 | effectiveImmediately = true |
小团队快速调岗,当天审批当天生效 |
| 按生效日生效 | effectiveImmediately = false + effectiveDate |
集团统一调岗、月初组织调整 |
业务语义非常清楚:
- BPM 只负责判断“这次调动是否被批准”。
- 员工档案何时更新,由生效策略决定。
1.4 立即生效和定时生效如何共存?
RuoYi Office 采用“双通道”实现:
审批通过
├── 立即生效 = true
│ └── 立刻调用 updateEmployeeFromTransferBill()
└── 立即生效 = false
└── 由 employeeTransferByEffectiveDateJob
在 effectiveDate 当天补写员工档案
这比“审批通过后先写入档案,再用计划任务覆盖”干净得多,因为系统只会在真正应该生效的时刻更新正式档案。
二、系统设计:申请单、档案、定时任务的三方协同
2.1 模块定位
人事调动位于 人力 → 人事管理 → 人事调动 目录下,是员工全生命周期中的重要流程单据之一。
| 模块 | 功能定位 | 面向角色 |
|---|---|---|
| 调动申请单 | 承载审批前的异动信息 | HR / 直属上级 |
| 员工档案 | 承载正式组织与岗位信息 | HR 管理员 |
| XXL-Job 生效任务 | 处理延迟生效单据 | 系统后台 |
2.2 核心设计决策
| 决策点 | 方案 | 理由 |
|---|---|---|
| 申请模型 | 单表承载原/新信息 | 调动信息结构相对稳定,无需子表 |
| 生效策略 | 立即生效 + 生效日补写 | 同时满足灵活调岗与月度统一生效 |
| 审批回调 | FlowBillService.updateProcessStatus |
BPM 统一驱动单据状态 |
| 档案回写 | updateEmployeeFromTransferBill |
只回写新部门/公司/职位/职务 |
| 定时任务 | employeeTransferByEffectiveDateJob |
处理审批通过但未到生效日的单据 |
| 前端联动 | 选员工带出原信息、选部门带出新公司 | 减少人工重复录入 |
2.3 数据结构
| 表名 | 职责 | 关键字段 |
|---|---|---|
hrm_employee_transfer_bill |
调动申请单 | employee_id, old_dept_id, new_dept_id, effective_immediately, effective_date, process_status |
hrm_employee |
员工档案 | dept_id, company_id, job_post, job_position |
单据和档案的关系不是“谁替代谁”,而是“申请数据驱动正式数据更新”。
三、PC 端功能实现:调前、调后信息一眼看清
3.1 调动申请列表页

▲ 人事调动列表页:直接展示员工工号、姓名、所属部门、异动类型、原部门与变更后部门,方便 HR 快速回顾调动轨迹
这个列表页最大的价值,不是简单罗列申请单,而是把“调前 → 调后”的核心变化直接摊开给 HR 看:
- 员工工号、姓名保证快速定位人。
- 原部门、变更后部门帮助判断调动方向。
- 单据状态显示流程进度。
- 默认只查当前创建人的申请,天然符合“我的申请”视角。
3.2 调动申请详情页

▲ 人事调动表单页:上半部分是员工当前档案快照,下半部分是调动信息与生效策略,既能看清“从哪里来”,也能明确“要到哪里去”
这个页面明显不是普通表单,而是“主表 + 调动信息子表单”的组合式单据:
- 选择员工后,自动带出工号、性别、手机号、原部门、原公司、原职位、原职务。
- 下方“调动信息”区域只填写变更项,不会强迫用户重复录入所有字段。
- “是否立即生效”与“生效日期”联动,符合真实 HR 业务习惯。
3.3 交互设计亮点
| 交互 | 设计方式 | 价值 |
|---|---|---|
| 选员工自动带出原信息 | EmployeeSelectModal |
省去 HR 手动填写当前岗位信息 |
| 选新部门自动带出新公司 | 部门弹窗联动公司 | 避免跨公司/跨部门录错 |
| 保存和提交分离 | 草稿可不完整,提交才强校验 | 符合单据类产品习惯 |
| 审批只读控制 | computeBusinessFormReadonly |
审批态禁止任意篡改单据 |
四、流程设计:调动通过后,什么时候改档案?
4.1 流程定义 Key 与业务键
人事调动流程定义 Key 为 hr_employee_transfer_bill,业务键是调动申请单主键 ID。这意味着 BPM 只需要保存“流程实例 ↔ 业务单据”的映射,不需要知道员工档案细节。
4.2 审批通过后的两条路径
流程通过后,系统不会一刀切直接改员工档案,而是分两种情况:
- 立即生效:审批通过的当下就更新员工档案。
- 延迟生效:仅把单据状态置为通过,等生效日任务执行。
这样可以完美覆盖“月底审批、月初统一生效”的组织调整场景。
4.3 为什么要引入 XXL-Job?
因为“生效日期当天自动更新档案”不是 BPM 自身要负责的能力,它更适合交给定时任务:
- BPM 负责审批路径。
- XXL-Job 负责时间到点执行。
- 员工档案服务只负责真正的更新动作。
这个分工非常清晰,也更容易排障。
五、后端核心实现
5.1 提交调动申请:生成单号、发起流程、保存附件
submitEmployeeTransferBill() 的职责和入职、转正等单据保持一致:编号生成、校验、流程发起、附件保存。
@Transactional(rollbackFor = Exception.class)
public Long submitEmployeeTransferBill(EmployeeTransferBillSaveReqVO saveReqVO) {
if (StringUtils.isBlank(saveReqVO.getBillCode())) {
saveReqVO.setBillCode(BillCodeUtils.generateBillCode(
SystemEnum.HRM, HrmBillTypeEnum.HRM_EMPLOYEE_TRANSFER_BILL));
}
validateEmployeeExists(saveReqVO.getEmployeeId());
EmployeeTransferBillDO transferBill = BeanUtils.toBean(saveReqVO, EmployeeTransferBillDO.class)
.setProcessStatus(BpmTaskStatusEnum.RUNNING.getStatus());
employeeTransferBillMapper.insertOrUpdate(transferBill);
Map<String, Object> processInstanceVariables = BpmProcessVariableUtils.buildBillVariables(saveReqVO);
processInstanceVariables.put(BpmProcessVariableConstants.CAUSE, transferBill.getName() + "人事调动申请");
String processInstanceId = processInstanceApi.submitProcessInstance(
Long.valueOf(saveReqVO.getCreator()),
new BpmProcessInstanceCreateReqDTO()
.setProcessDefinitionKey(HrmBillTypeEnum.HRM_EMPLOYEE_TRANSFER_BILL.getProcessDefinitionKey())
.setVariables(processInstanceVariables)
.setBusinessKey(String.valueOf(transferBill.getId()))
).getCheckedData();
employeeTransferBillMapper.updateById(new EmployeeTransferBillDO()
.setId(transferBill.getId())
.setProcessInstanceId(processInstanceId));
return transferBill.getId();
}
这段代码体现了调动单作为典型“业务单据”的标准模式:先形成申请事实,再把审批系统挂上去。
5.2 审批回调:立即生效的单据直接改档案
updateProcessStatus() 是整个模块最关键的“分流点”:
@Override
@Transactional(rollbackFor = Exception.class)
public void updateProcessStatus(String businessKey, Integer status) {
Long id = Long.parseLong(businessKey);
EmployeeTransferBillDO bill = validateEmployeeTransferBillExists(id);
EmployeeTransferBillDO updateObj = new EmployeeTransferBillDO();
updateObj.setId(id);
updateObj.setProcessStatus(status);
if (APPROVE.getStatus().equals(status)) {
if (Boolean.TRUE.equals(bill.getEffectiveImmediately())) {
updateEmployeeFromTransferBill(id);
}
}
employeeTransferBillMapper.updateById(updateObj);
}
这段逻辑非常值得借鉴:审批通过并不等于无条件立刻写档案,而是先判断业务策略。
5.3 档案回写:只更新确实发生变化的字段
updateEmployeeFromTransferBill() 不是粗暴地全量覆盖,而是按字段有条件更新:
public void updateEmployeeFromTransferBill(Long transferBillId) {
EmployeeTransferBillRespVO transferBillRespVO = getEmployeeTransferBillInfo(transferBillId);
EmployeeDO employee = employeeMapper.selectById(transferBillRespVO.getEmployeeId());
EmployeeDO updateEmployee = new EmployeeDO();
updateEmployee.setId(employee.getId());
if (transferBillRespVO.getNewDeptId() != null) {
updateEmployee.setDeptId(transferBillRespVO.getNewDeptId());
updateEmployee.setDeptName(transferBillRespVO.getNewDeptName());
}
if (transferBillRespVO.getNewCompanyId() != null) {
updateEmployee.setCompanyId(transferBillRespVO.getNewCompanyId());
updateEmployee.setCompanyName(transferBillRespVO.getNewCompanyName());
}
if (StringUtils.isNotBlank(transferBillRespVO.getNewJobPost())) {
updateEmployee.setJobPost(transferBillRespVO.getNewJobPost());
}
if (StringUtils.isNotBlank(transferBillRespVO.getNewJobPosition())) {
updateEmployee.setJobPosition(transferBillRespVO.getNewJobPosition());
}
employeeMapper.updateById(updateEmployee);
}
它的价值在于:一次调动只改需要改的部分,不会误伤员工档案里的其他信息。
5.4 延迟生效:用 XXL-Job 到点补写档案
这段任务代码就是“月初统一生效”策略真正落地的地方:
@XxlJob("employeeTransferByEffectiveDateJob")
@TenantJob
public String execute() {
LocalDate today = LocalDate.now();
List<EmployeeTransferBillDO> bills = employeeTransferBillMapper.selectList(
new LambdaQueryWrapperX<EmployeeTransferBillDO>()
.eq(EmployeeTransferBillDO::getProcessStatus, APPROVE.getStatus())
.eq(EmployeeTransferBillDO::getEffectiveImmediately, false)
.eq(EmployeeTransferBillDO::getEffectiveDate, today)
);
for (EmployeeTransferBillDO bill : bills) {
employeeTransferBillService.updateEmployeeFromTransferBill(bill.getId());
}
return "处理完成";
}
这里的关键点有两个:
- 只处理“已审批通过 + 非立即生效 + 生效日期 = 今天”的单据。
- 使用
@TenantJob保证多租户场景下按租户安全执行。
5.5 前端表单:主表和调动信息表单组合保存
前端不是一个单纯的 BasicForm,而是“员工基础信息表单 + 调动信息子表单”双表单合并提交:
async function handleSaveAndSubmit(isSubmit: boolean) {
const formValues = isSubmit
? ((await basicFormRef.value.getFormValues()) as EmployeeTransferBillApi.EmployeeTransferBill)
: ((await basicFormRef.value.getFormValues(false)) as EmployeeTransferBillApi.EmployeeTransferBill);
const transferValues = await transferFormApi.getValues();
const data = {
...formData.value,
...formValues,
...transferValues,
};
id = await (isSubmit
? submitEmployeeTransferBill(data)
: saveEmployeeTransferBill(data));
}
这段实现很好地说明了为什么人事调动前端会拆成上下两块:员工现状是一组字段,调动目标又是一组字段,但提交时仍然应该是同一张业务单据。
六、RuoYi Office 的创新设计
6.1 审批通过与档案生效解耦
这是整个模块最有价值的设计。很多系统把“审批通过”直接等同于“立刻改档案”,RuoYi Office 则允许企业按管理制度选择是否延迟生效。
6.2 调动单保留完整原/新快照
原部门、原公司、原职位、原职务全部保留,使调动单天然具备审计价值,而不是一张只记录“结果”的表。
6.3 前端联动减少 HR 重复录入
员工选择带出原信息、部门选择带出公司,这些看似小细节,实际上决定了 HR 是否愿意长期使用系统。
6.4 XXL-Job 补齐“流程之外的时间逻辑”
审批系统擅长处理“谁来批”,定时任务擅长处理“什么时候生效”。把两者拆开,是非常成熟的架构分工。
七、数据结构
7.1 hrm_employee_transfer_bill
| 字段 | 含义 | 设计要点 |
|---|---|---|
employee_id |
员工ID | 关联正式员工档案 |
old_dept_id / new_dept_id |
原/新部门 | 保留调动前后对照 |
old_company_id / new_company_id |
原/新公司 | 支撑跨公司调动 |
new_job_post / new_job_position |
新职位/新职务 | 部分字段允许局部更新 |
effective_immediately |
是否立即生效 | 区分两种生效策略 |
effective_date |
生效日期 | 定时任务筛选条件 |
process_status |
流程状态 | BPM 回调驱动 |
7.2 hrm_employee
| 字段 | 含义 | 调动时是否回写 |
|---|---|---|
dept_id / dept_name |
所属部门 | 是 |
company_id / company_name |
所属公司 | 是 |
job_post |
职位 | 是 |
job_position |
职务 | 是 |
| 其他个人信息 | 手机、身份证、学历等 | 否 |
八、技术亮点总结
| 设计要点 | 实现方式 | 价值 |
|---|---|---|
| 调动留痕 | 单据冗余原/新组织与岗位 | 审批与审计更清晰 |
| 即时生效 | updateProcessStatus() 中直接回写档案 |
适合快速调岗场景 |
| 延迟生效 | employeeTransferByEffectiveDateJob |
支持月初统一生效 |
| 安全回写 | updateEmployeeFromTransferBill() 按字段更新 |
避免误覆盖其他档案字段 |
| 前端联动 | 员工/部门选择弹窗自动带值 | 降低 HR 录入成本 |
| 多租户任务 | @TenantJob |
保证跨租户安全执行 |
九、快速体验
9.1 操作路径
- 人事调动列表:
人力 → 人事管理 → 人事调动 - 调动申请详情:
/hrm/employee-relation/transfer-info
9.2 推荐体验流程
- 进入人事调动列表,新建调动申请。
- 选择员工,确认系统自动带出的原公司、原部门、原职位信息。
- 填写异动类型、异动原因、变更后部门和岗位。
- 选择“立即生效”或“按生效日期生效”。
- 提交流程并完成审批。
- 如果选择立即生效,回到员工档案确认字段已更新。
- 如果选择延迟生效,等待生效日期后由任务自动更新。
9.3 源码仓库
| 类型 | 路径 |
|---|---|
| 前端 | ruoyi-office-vben/apps/web-antd/src/views/hrm/employee-relation/transfer |
| 后端 | ruoyi-office/yudao-module-hrm/.../service/employee/EmployeeTransferBillServiceImpl.java |
| 定时任务 | ruoyi-office/yudao-module-hrm/.../job/employee/EmployeeTransferByEffectiveDateJob.java |
结语
人事调动最容易做错的地方,不是表单字段不全,而是审批通过、何时生效、如何更新正式档案三者之间的关系处理不清。
RuoYi Office 这套“调动单承载审批事实、员工档案承载正式结果、XXL-Job 兜住延迟生效”的设计,实际上为很多 HRM 流程都提供了通用范式。凡是“审批通过后未必立刻更新主数据”的场景,比如岗位调整、编制变更、薪资调档,都可以复用这套思路。
💡 想要体验 RuoYi Office 的强大功能?
🌐 在线演示:http://ruoyioffice.com/web/(账号 admin / admin123)
💬 技术咨询:添加💬 17156169080,备注「RuoYi Office」
⭐ 如果觉得不错,请给个 Star 支持一下!
更多推荐

所有评论(0)