SpringBoot+Vue3 企业公车管理全流程设计:用车申请+还车申请双单联动、时间冲突检测、审批驱动还车状态闭环
SpringBoot+Vue3 企业公车管理全流程设计:用车申请+还车申请双单联动、时间冲突检测、审批驱动还车状态闭环
🌐文档地址: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」)
公车管理看起来只是“借车、还车”两件小事,真正落到系统里却会迅速复杂起来:一辆车同一时段不能被两个人同时借走,审批通过后才能真正出车,还车申请又必须反向驱动原用车单状态回写。RuoYi Office 用 1 张车辆台账 + 2 张流程单据 + 1 套 BPM 审批回调,构建了从用车申请到还车归档的双单闭环,让“谁在用车、什么时候归还、有没有冲突”全部可追溯。
▲ 企业公车管理业务流转全景:车辆台账提供主数据,用车申请审批通过后进入待还车,还车单提交后驱动“还车中→已还车/未还车”状态回写
引言:公车管理到底难在哪?
“不就是填个用车申请吗?”很多开发者第一次做公车管理时都会这么想。但只要业务真正跑起来,就会发现它至少有 5 个高频难点:
车辆资源天然互斥:同一辆车 4 月 10 日 9:00-11:00 只能服务一张申请单。系统如果只校验“审批通过的单据”,却忽略“审批中的单据”,就会出现两个人同时等待同一辆车的冲突。
用车和还车不是一张单据能解决的:用车是“借出动作”,还车是“归还动作”。如果强行塞进一张表,流程状态会迅速失控;拆成两张单又必须解决“还车单如何回写原申请单”的联动问题。
审批和状态必须强绑定:用车单审批通过前,车辆不能真正进入“待还车”;还车单审批通过后,原用车单必须自动变成“已还车”。如果靠人工改状态,迟早出现漏改。
还车异常要有回滚路径:还车单被拒绝、撤回或退回时,原申请单不能继续保持“还车中”,必须回滚为“未还车”或“待处理”状态。
车辆台账和业务单据是两套模型:oa_car负责描述“有哪些车”,oa_car_apply_bill/oa_car_return_bill负责描述“谁在什么时候用了哪辆车”。主数据与交易数据职责必须清晰分离。
| 痛点 | 传统做法 | 后果 |
|---|---|---|
| 用车时间撞车 | 靠行政人工记忆 | 到了出车时间才发现车辆已被占用 |
| 用车/还车混在一张单 | 一张表多状态硬撑 | 状态字段越来越乱,维护困难 |
| 审批与状态脱节 | 审批通过后人工改状态 | 忘改就会出现“流程通过但系统仍显示草稿” |
| 还车失败无回滚 | 拒绝后不处理原单 | 原申请单长期停留在错误状态 |
| 车辆主数据散乱 | Excel 维护车辆档案 | 车牌、车型、保险、所属公司难统一管理 |
本文以 RuoYi Office 的公车管理模块为例,完整拆解它如何通过车辆台账 + 用车申请单 + 还车申请单 + BPM 流程回调构建一套真正能落地的企业公车管理方案。
一、业务设计:双单据驱动的车辆生命周期
1.1 业务全景
一次完整的公车使用涉及 4 个阶段:
| 阶段 | 角色 | 操作 | 系统行为 |
|---|---|---|---|
| 建档 | 行政管理员 | 维护车辆信息 | 创建oa_car车辆台账 |
| 申请 | 普通员工 | 填写用车事由、出发/回车时间、地点 | 创建oa_car_apply_bill并发起 BPM |
| 用车 | 审批人 / 车管员 | 审批通过后安排出车 | 用车单returnStatus置为“待还车” |
| 还车 | 用车人 / 行政 | 发起还车单、确认归还 | 通过oa_car_return_bill回写原用车单状态 |
1.2 为什么要拆成“用车单 + 还车单”?
这是本方案最重要的业务抽象:
| 方案 | 看起来的优点 | 实际问题 |
|---|---|---|
| 一张单据到底 | 表少、字段集中 | 同时承载借出、审批、归还、异常回滚,状态爆炸 |
| 两张单据联动 | 模型更清晰 | 需要设计原单回写与业务键关联 |
RuoYi Office 选择第二种方案:
车辆台账 oa_car ├── 用车申请单 oa_car_apply_bill │ 负责:申请、审批、占用时段、待还车状态 └── 还车申请单 oa_car_return_bill 负责:归还动作、归还审批、回写原申请单状态这种拆分的核心价值是:每张单据只负责一个动作,流程和状态都更稳定。
1.3 四态还车状态机
公车管理真正复杂的不是 BPM 流程状态,而是“车辆是否已经归还”的业务状态。RuoYi Office 在用车申请单维度定义了returnStatus:
| 状态码 | 状态名 | 触发时机 | 说明 |
|---|---|---|---|
0 | 未生效 | 草稿/退回/拒绝后的回滚态 | 还车流程未正式成立 |
1 | 待还车 | 用车申请审批通过 | 车辆已借出,等待发起还车 |
2 | 还车中 | 提交还车单 | 还车流程已开始 |
3 | 已还车 | 还车单审批通过 | 业务闭环结束 |
这个状态机不是独立存在的,而是由两张单据共同驱动:
- 用车单审批通过:
待还车 - 还车单提交:
还车中 - 还车单审批通过:
已还车 - 还车单被拒绝/撤回/退回:回滚为
未生效
1.4 时间冲突检测的边界
公车管理最容易被忽略的,是冲突检测范围到底该算哪些单据。
RuoYi Office 的规则是:
- 同一辆车,出车时间和回车时间不能反向。
- 编辑时排除自己当前单据。
- 冲突检测不仅看“已审批”的单据,也看“审批中的单据”。
- 还车状态处于
待还车或还车中的申请单,视为仍然占用车辆。
这意味着一张还在审批中的用车单,也足以阻止别人在同一时段重复申请同一辆车。
二、系统设计:三张表的职责分工
2.1 模块组成
公车管理位于OA → 车辆管理目录下,由三个子模块组成:
| 子模块 | 菜单 | 功能定位 | 面向角色 |
|---|---|---|---|
| 车辆信息 | 车辆管理 | 维护车辆台账、车牌、车型、所属公司 | 行政管理员 |
| 用车申请单 | 用车申请单管理 | 发起用车流程、占用时段、跟踪还车状态 | 普通员工 / 行政 |
| 还车申请单 | 还车申请单管理 | 发起归还流程、回写用车状态 | 用车人 / 行政 |
2.2 核心设计决策
| 决策点 | 方案 | 理由 |
|---|---|---|
| 单据建模 | 用车/还车双单据 | 借出与归还是两次独立业务动作 |
| 状态中心 | 申请单维护returnStatus | 统一反映“这次用车是否已闭环” |
| 业务关联 | 还车单保存applyBill单据编号 | 用业务单号建立领域关联,便于对账和人工排查 |
| 流程回调 | 两张单据都实现FlowBillService | 统一由 BPM 回调更新业务状态 |
| 冲突检测 | 提交前校验 | 防止重复占用车辆资源 |
| 附件策略 | 用车单/还车单分别保存附件 | 出车资料和还车资料分离留痕 |
2.3 数据结构关系
| 表名 | 职责 | 关键字段 |
|---|---|---|
oa_car | 车辆主数据 | car_no,car_type,company_id,status |
oa_car_apply_bill | 用车申请单 | bill_code,car_id,go_time,return_time,process_status,return_status |
oa_car_return_bill | 还车申请单 | bill_code,apply_bill,car_id,return_time,remark,process_status |
这里最值得注意的是
oa_car_return_bill.apply_bill。它不是一个普通备注字段,而是还车单反向定位原用车单的核心业务键。
三、PC 端功能实现:从申请到归还的前端体验
3.1 用车申请列表页
▲ 用车申请列表页:支持按单据编号、单据状态、车辆筛选,并直接展示还车状态,方便行政统一跟踪当前车辆占用情况
列表页直接把“流程状态”和“还车状态”并列展示,这一点非常关键。很多系统只能看到审批状态,却看不到车辆实际上有没有归还,导致车管员还要点进详情逐条确认。
当前页面重点字段包括:
| 字段 | 作用 |
|---|---|
| 单据编号 | 用于业务检索与对外沟通 |
| 单据状态 | 展示 BPM 当前状态 |
| 车辆 | 对应台账中的具体车辆 |
| 出车/回车时间 | 用于时段冲突判断 |
| 出发/回车地点 | 记录行程范围 |
| 还车状态 | 反映业务闭环进度 |
3.2 用车申请详情页
▲ 用车申请详情页:车辆、时间、地点、事由、随行人、附件全部集中管理,保存和提交流程分离,既支持草稿也支持一次性提交审批
表单页不是简单的 CRUD 表单,而是典型的“业务单据页”:
- 新建时自动带出当前登录人的公司、部门、创建人等默认信息。
保存允许作为草稿保留,不触发严校验。提交会触发完整表单校验,并立即发起 BPM 流程。- 附件区域和单据主体分离,便于补充行车说明、审批附件等材料。
3.3 还车申请列表页
▲ 还车申请列表页:单独跟踪归还流程,显示关联用车申请单、流程实例编号、回车时间和申请部门,便于车管员集中处理还车场景
还车页面单独存在的价值,在这里一目了然:
- 它不再关心“为什么借车”,而是只关心“这辆车是否按计划归还”。
- 表格中明确展示关联用车申请单号,便于追溯原始借用记录。
- 审批通过、审批中、未提交等状态一眼可见,特别适合行政集中处理归还。
四、流程设计:双流程如何协同不打架?
4.1 用车流程
用车单流程定义 Key 为oa_car_apply_bill,核心职责只有两件事:
- 决定这次用车申请是否审批通过。
- 审批通过后把申请单推进到“待还车”。
4.2 还车流程
还车单流程定义 Key 为oa_car_return_bill,核心职责是:
- 确认归还动作是否真实发生。
- 审批通过时把原用车单推进到“已还车”。
- 审批拒绝或撤回时把原用车单回滚。
4.3 为什么还车流程不直接改车辆台账?
因为业务真正需要追踪的是“这一次用车申请是否闭环”,而不是“这辆车是否空闲”。
换句话说:
- 车辆台账是主数据。
- 用车申请单才是业务上下文。
- 还车单应该回写原申请单,而不是越级直接篡改车辆主数据。
这就是典型的“交易单据驱动业务状态”的设计方法。
五、后端核心实现
5.1 提交用车单:先校验冲突,再发起流程
CarApplyBillServiceImpl.submitCarApplyBill()的职责非常集中:生成单号、校验时间冲突、保存申请单、发起 BPM、保存附件。
publicLongsubmitCarApplyBill(CarApplyBillSaveReqVOsaveReqVO){if(StringUtils.isBlank(saveReqVO.getBillCode())){saveReqVO.setBillCode(BillCodeUtils.generateBillCode(SystemEnum.OA,OaBillTypeEnum.OA_CAR_APPLY_BILL));}validateTimeConflict(saveReqVO);CarApplyBillDOcarApplyBill=BeanUtils.toBean(saveReqVO,CarApplyBillDO.class).setProcessStatus(BpmTaskStatusEnum.RUNNING.getStatus());carApplyBillMapper.insertOrUpdate(carApplyBill);Map<String,Object>processInstanceVariables=BpmProcessVariableUtils.buildBillVariables(saveReqVO);StringprocessInstanceId=processInstanceApi.submitProcessInstance(Long.valueOf(saveReqVO.getCreator()),newBpmProcessInstanceCreateReqDTO().setProcessDefinitionKey(OaBillTypeEnum.OA_CAR_APPLY_BILL.getProcessDefinitionKey()).setVariables(processInstanceVariables).setBusinessKey(String.valueOf(carApplyBill.getId()))).getCheckedData();carApplyBillMapper.updateById(newCarApplyBillDO().setId(carApplyBill.getId()).setProcessInstanceId(processInstanceId));returncarApplyBill.getId();}这段代码的关键点在于:冲突检测发生在提交流程之前,而不是审批通过之后。这样才能真正挡住重复占车。
5.2 流程通过后,把申请单推进到“待还车”
用车申请单实现了FlowBillService,BPM 回调时统一更新流程状态。
@OverridepublicvoidupdateProcessStatus(StringbusinessKey,Integerstatus){Longid=Long.parseLong(businessKey);validateCarApplyBillExists(id);CarApplyBillDOupdateObj=newCarApplyBillDO();updateObj.setId(id);updateObj.setProcessStatus(status);if(APPROVE.getStatus().equals(status)){updateObj.setReturnStatus(CarReturnStatusEnum.PENDING_RETURN.getStatus());}carApplyBillMapper.updateById(updateObj);}这段逻辑体现了本文最核心的一个设计思想:审批状态是 BPM 的事,但业务状态的推进必须由业务服务自己掌控。
5.3 时间冲突检测:审批中和待还车都算占用
下面这段代码体现了车辆资源调度最容易被忽略的边界条件:
privatevoidvalidateTimeConflict(CarApplyBillSaveReqVOsaveReqVO){if(saveReqVO.getCarId()==null||saveReqVO.getGoTime()==null||saveReqVO.getReturnTime()==null){return;}if(saveReqVO.getGoTime().isAfter(saveReqVO.getReturnTime())){throwexception(CAR_TIME_CONFLICT);}List<CarApplyBillDO>conflictBills=carApplyBillMapper.selectList(newLambdaQueryWrapperX<CarApplyBillDO>().eq(CarApplyBillDO::getCarId,saveReqVO.getCarId()).ne(saveReqVO.getId()!=null,CarApplyBillDO::getId,saveReqVO.getId()).and(wrapper->wrapper.eq(CarApplyBillDO::getProcessStatus,BpmTaskStatusEnum.RUNNING.getStatus()).or().eq(CarApplyBillDO::getReturnStatus,CarReturnStatusEnum.PENDING_RETURN.getStatus()).or().eq(CarApplyBillDO::getReturnStatus,CarReturnStatusEnum.RETURNING.getStatus())));}它并不是单纯查“已审批通过”的单子,而是把审批中、待还车、还车中都视为占用状态,这样才不会出现“流程还没结束,但车已经被下一张单盯上”的问题。
5.4 还车提交:先把原申请单标记为“还车中”
CarReturnBillServiceImpl.submitCarReturnBill()在提交还车单后,立刻把原用车单推进到中间态。
// 保存附件信息if(saveReqVO.getAttachments()!=null){attachmentService.saveAttachmentList(OaBillTypeEnum.OA_CAR_RETURN_BILL.getTypeCode(),carReturnBill.getId(),saveReqVO.getAttachments());}// 标记对应用车申请单为还车中carApplyBillService.markAsReturning(applyBillId);returncarReturnBill.getId();这一步非常关键。因为“提交了还车单”和“车辆已经彻底归还”不是一回事,中间必须存在一个RETURNING过渡态。
5.5 还车审批完成后,反向回写原申请单
真正完成闭环的是handleApplyBillReturnStatus():
privatevoidhandleApplyBillReturnStatus(LongreturnBillId,Integerstatus){CarReturnBillDOreturnBill=getCarReturnBill(returnBillId);if(returnBill==null||returnBill.getApplyBill()==null){return;}CarApplyBillDOapplyBill=carApplyBillService.getCarApplyBillByCode(returnBill.getApplyBill());if(applyBill==null){return;}if(BpmTaskStatusEnum.APPROVE.getStatus().equals(status)){carApplyBillService.markAsReturned(applyBill.getId());}elseif(BpmTaskStatusEnum.REJECT.getStatus().equals(status)||BpmTaskStatusEnum.CANCEL.getStatus().equals(status)||BpmTaskStatusEnum.RETURN.getStatus().equals(status)){carApplyBillService.markAsNotReturned(applyBill.getId());}}它把“还车流程”的最终结果映射成“原用车单”的业务状态,这就是双单据模型真正成立的关键。
六、RuoYi Office 的创新设计
6.1 双单据建模,而不是一张万能单
很多系统喜欢把“借车”和“还车”塞进一个单据里,字段很多,但语义极不清晰。RuoYi Office 反其道而行之,用两张单据明确拆开两个动作,使得每张单据都更稳定。
6.2 流程状态和业务状态解耦
processStatus反映 BPM 走到了哪里,returnStatus反映车辆业务闭环走到了哪里。两套状态各司其职,避免“审批状态硬充当业务状态”的混乱。
6.3 用业务单号关联还车单
还车单通过applyBill关联原申请单编号,不依赖纯数据库主键。这样在排查、对账、人工沟通时更直观,业务人员也更容易理解。
6.4 冲突检测覆盖“审批中”单据
这是很多产品容易漏掉的边界。RuoYi Office 把审批中单据也纳入冲突范围,避免车辆资源在审批阶段被重复分配。
七、数据结构
7.1oa_car
| 字段 | 含义 | 设计要点 |
|---|---|---|
id | 主键 | 车辆主数据唯一标识 |
car_no | 车牌号 | 对外展示最常用字段 |
company_id | 所属公司 | 支持按公司维度管理车辆 |
status | 车辆状态 | 用于台账可用性控制 |
7.2oa_car_apply_bill
| 字段 | 含义 | 设计要点 |
|---|---|---|
bill_code | 单据编号 | 统一走BillCodeUtils |
car_id | 车辆ID | 关联车辆台账 |
go_time | 出车时间 | 与回车时间一起做冲突校验 |
return_time | 回车时间 | 资源占用边界 |
process_status | 流程状态 | BPM 回调更新 |
return_status | 还车状态 | 业务闭环核心字段 |
7.3oa_car_return_bill
| 字段 | 含义 | 设计要点 |
|---|---|---|
bill_code | 还车单号 | 独立编号 |
apply_bill | 原用车单号 | 反向定位原申请单 |
car_id | 车辆ID | 冗余存储便于查询 |
remark | 还车说明 | 记录归还异常与备注 |
process_status | 流程状态 | 决定是否回写成功 |
八、技术亮点总结
| 设计要点 | 实现方式 | 价值 |
|---|---|---|
| 双单据闭环 | oa_car_apply_bill+oa_car_return_bill | 模型清晰,动作解耦 |
| 审批通过即待还车 | updateProcessStatus() | 业务状态和流程节点对齐 |
| 提交还车即“还车中” | markAsReturning() | 引入中间态,表达更准确 |
| 还车失败自动回滚 | handleApplyBillReturnStatus() | 防止业务状态污染 |
| 时间冲突检测 | validateTimeConflict() | 杜绝重复占用车辆 |
| 前端单据表单 | 保存/提交分离 | 草稿和正式提交体验更自然 |
九、快速体验
9.1 操作路径
- 车辆台账:
OA → 车辆管理 - 用车申请:
OA → 用车申请单管理 - 还车申请:
OA → 还车申请单管理
9.2 推荐体验流程
- 先在车辆台账中确认已有可用车辆。
- 进入“用车申请单管理”,点击新增。
- 填写车辆、出车/回车时间、地点、用车事由并提交。
- 在流程中心完成审批。
- 返回列表观察用车单
returnStatus是否变为“待还车”。 - 新建还车申请单,关联原用车单。
- 提交流程并审批通过。
- 返回用车列表确认状态已回写为“已还车”。
9.3 源码仓库
| 类型 | 仓库 |
|---|---|
| 前端 | ruoyi-office-vben/apps/web-antd/src/views/oa/car |
| 后端 | ruoyi-office/yudao-module-oa/yudao-module-oa-server/src/main/java/.../service/car |
| 数据库 | ruoyi-office-db |
结语
公车管理真正有价值的地方,不在于“能填一张申请单”,而在于把车辆这种天然互斥的共享资源,变成一套可审批、可回写、可追溯的闭环系统。
RuoYi Office 这套“主数据台账 + 双业务单据 + BPM 回调”的设计,并不只适用于公车。凡是涉及“借出/归还”“领用/回收”“申请/归档”的企业资源管理场景,都可以直接复用这套建模思路。
💡想要体验 RuoYi Office 的强大功能?
🌐在线演示:http://ruoyioffice.com/web/(账号 admin / admin123)
💬技术咨询:添加💬17156169080,备注「RuoYi Office」
⭐如果觉得不错,请给个 Star 支持一下!
