当前位置: 首页 > news >正文

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 的规则是:

  1. 同一辆车,出车时间和回车时间不能反向。
  2. 编辑时排除自己当前单据。
  3. 冲突检测不仅看“已审批”的单据,也看“审批中的单据”。
  4. 还车状态处于待还车还车中的申请单,视为仍然占用车辆。

这意味着一张还在审批中的用车单,也足以阻止别人在同一时段重复申请同一辆车。


二、系统设计:三张表的职责分工

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,核心职责只有两件事:

  1. 决定这次用车申请是否审批通过。
  2. 审批通过后把申请单推进到“待还车”。

4.2 还车流程

还车单流程定义 Key 为oa_car_return_bill,核心职责是:

  1. 确认归还动作是否真实发生。
  2. 审批通过时把原用车单推进到“已还车”。
  3. 审批拒绝或撤回时把原用车单回滚。

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 推荐体验流程

  1. 先在车辆台账中确认已有可用车辆。
  2. 进入“用车申请单管理”,点击新增。
  3. 填写车辆、出车/回车时间、地点、用车事由并提交。
  4. 在流程中心完成审批。
  5. 返回列表观察用车单returnStatus是否变为“待还车”。
  6. 新建还车申请单,关联原用车单。
  7. 提交流程并审批通过。
  8. 返回用车列表确认状态已回写为“已还车”。

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 支持一下!

http://www.jsqmd.com/news/655953/

相关文章:

  • 2026杭州浙江门窗改造与系统门窗隔音节能全屋换窗方案(含官方直达专线) - 精选优质企业推荐官
  • 济南考研集训营红黑榜:避坑指南与高性价比推荐 - 新闻快传
  • 《现代密码学理论与实践》中英文版:深入理解与实践应用
  • m4s-converter终极指南:3分钟解锁B站缓存视频的完整教程
  • 从沙漏到数字:Hourglass如何用极简设计重塑Windows时间管理效率工具
  • 告别Adobe插件安装烦恼:ZXPInstaller跨平台安装指南
  • 别再乱选电源了!5分钟搞懂DC-DC和LDO到底怎么选(附效率对比图)
  • 如何用Python轻松下载B站视频:从零开始到4K大会员画质完整指南
  • 【博客园使用技巧】Markdown 符号速查表及模板
  • 别再死记硬背了!用Vivado/Quartus做FPGA时序约束,这3个实战案例帮你彻底搞懂
  • 光伏并网逆变器资料:原理图、PCB、源码及元器件明细表大全
  • 告别命令行GDB!用CLion远程调试Linux C++程序,像本地开发一样丝滑
  • 收藏!AI大模型自学路线(小白+程序员专属),从入门到实战少走90%弯路
  • ChineseOCR文字方向检测终极指南:智能校正0°、90°、180°、270°旋转文字
  • Coze插件开发实战:5分钟搞定API调用(附完整代码示例)
  • 2026年 光亮剂厂家推荐:水性、油性、轮胎、塑料等多种光亮剂优质品牌之选! - 速递信息
  • Gogs数据迁移进阶:如何只迁移数据库,或把MySQL换成PostgreSQL?
  • 跨系统无缝协同实战:用Synergy+FileZilla打通Windows与Linux的办公壁垒
  • Smithbox游戏创作平台:打造专属魂系游戏体验的终极工具箱
  • 开箱即用的语音情感识别:Emotion2Vec+ Large镜像快速体验
  • Python 内存优化实战:**slots** 的优势、限制与百万级风控系统应用指南
  • 中兴光猫配置解密工具:三步解锁你的网络隐藏功能
  • 别再乱用全局变量了!用FreeRTOS的xQueueSend/xQueueReceive实现安全高效的数据传递
  • Qwen3-ASR-1.7B模型在算法竞赛中的语音指令识别应用
  • 振弦传感器从原理到实践:如何用Python快速计算频模变化(附代码)
  • PostgreSQL 表结构解析与权限管理实战指南
  • 2026年杭州、浙江门窗改造全屋静音节能系统方案(含官方直联渠道) - 精选优质企业推荐官
  • 3个实战技巧:如何用Fluent.Ribbon让你的WPF应用拥有专业Office界面
  • 从单向广播到双向对话:DMX512与RDM协议在智能舞台灯光中的协同演进
  • 别再死记硬背了!用Python(SymPy库)5分钟搞定泰勒公式展开与验证