SpringBoot+Vue3 企业公车管理全流程设计:用车申请+还车申请双单联动、时间冲突检测、审批驱动还车状态闭环

张开发
2026/4/19 7:53:15 15 分钟阅读

分享文章

SpringBoot+Vue3 企业公车管理全流程设计:用车申请+还车申请双单联动、时间冲突检测、审批驱动还车状态闭环
SpringBootVue3 企业公车管理全流程设计用车申请还车申请双单联动、时间冲突检测、审批驱动还车状态闭环文档地址http://ruoyioffice.com | 源码1https://gitcode.com/zhouzhongyan/ruoyi-office-vben.git | 源码2https://gitcode.com/zhouzhongyan/ruoyi-office.git | 源码3https://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,statusoa_car_apply_bill用车申请单bill_code,car_id,go_time,return_time,process_status,return_statusoa_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);CarApplyBillDOcarApplyBillBeanUtils.toBean(saveReqVO,CarApplyBillDO.class).setProcessStatus(BpmTaskStatusEnum.RUNNING.getStatus());carApplyBillMapper.insertOrUpdate(carApplyBill);MapString,ObjectprocessInstanceVariablesBpmProcessVariableUtils.buildBillVariables(saveReqVO);StringprocessInstanceIdprocessInstanceApi.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 流程通过后把申请单推进到“待还车”用车申请单实现了FlowBillServiceBPM 回调时统一更新流程状态。OverridepublicvoidupdateProcessStatus(StringbusinessKey,Integerstatus){LongidLong.parseLong(businessKey);validateCarApplyBillExists(id);CarApplyBillDOupdateObjnewCarApplyBillDO();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);}ListCarApplyBillDOconflictBillscarApplyBillMapper.selectList(newLambdaQueryWrapperXCarApplyBillDO().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){CarReturnBillDOreturnBillgetCarReturnBill(returnBillId);if(returnBillnull||returnBill.getApplyBill()null){return;}CarApplyBillDOapplyBillcarApplyBillService.getCarApplyBillByCode(returnBill.getApplyBill());if(applyBillnull){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单据编号统一走BillCodeUtilscar_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_billoa_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 支持一下

更多文章