SpringBoot3+Vue3 资产维修闭环:报修、审批、办结后怎样恢复资产状态
▲ 报修、审批、办结与状态恢复共同构成资产维修闭环。
一台已经分配给员工的笔记本开不了机,员工报修,负责人同意送修。接下来,系统至少还要回答两个问题:这台电脑现在能不能再次领用?修好以后,它应该回到“闲置”还是“使用中”?
如果维修单只记录一个“审批通过”,这两个问题都没有答案。审批说明这次维修得到同意,维修结果说明故障已经处理,资产卡片则要告诉其他业务这台设备当前能做什么。
我们在RuoYi Office中把这三件事分开处理。下面沿着真实维修页面,拆解Spring Boot3、Vue3和Flowable如何接起报修、审批、办结与台账恢复这条链。
▲ 提交时记住资产原状态,审批通过才进入维修,办结后再恢复。审批链与实物处理链在这里衔接。
一、维修单要管过程,资产卡片要管可用性
报修人关心故障是否被受理,审批人关心维修原因和费用,资产管理人员关心设备什么时候恢复使用。同一张维修单承接这些信息,但不能用一个状态替所有角色下结论。
当前实现支持选择一项资产,填写故障描述,区分内部维修和外部送修,记录厂商、费用与附件。提交后进入审批;通过后,维修单进入“维修中”,资产卡片同步进入维修态;处理完成后,由有权限的人员登记完成日期和维修结果。
这里的费用是维修业务记录,不能仅凭这个字段推导出已经付款或已经完成财务入账。厂商支持手录或从供应商主数据选择,但本单保存的是厂商名称,也不宜把它理解成完整的应付账款关联。
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
维修状态的1、2、3只是维修单自己的枚举值,不能直接拿去更新资产卡片。两张表表达不同含义,即使界面文案都叫“维修中”,也应该使用各自的状态定义。
▲ 列表把“维修状态”和“审批状态”分列展示:审批通过的单据仍可能处于维修中。系统图均保留完整框架,手机上可点开放大;阅读时先看这两列的对应关系。
这也解释了为什么不能在审批结束时直接把维修单设为“已完成”。审批可能当天结束,送修却需要几天。提前结束维修,会让业务人员失去跟踪未完工设备的依据。
二、从报修到办结,页面围绕同一份业务事实展开
选资产时筛一次,提交时再校验一次
报修从资产选择开始。页面采用单选,候选资产限制为闲置或使用中,并排除被在途单据占用的资产。编辑当前维修单时,还要排除它自己,避免重新打开单据就把原资产判为冲突。
选择器只能改善操作体验,不能代替提交校验。用户打开页面后,另一位管理员可能已经办理调拨或处置;因此后端提交时会重新读取资产卡片,检查状态,并查询是否存在冲突单据。冲突提示包含资产编号和占用单据,业务人员才有办法找到原因。
保存草稿和提交审批也有区别。保存负责留下故障、资产快照与附件;提交负责再次检查可用性,记录维修前状态并创建流程实例。当前在途查询把“未提交”和“审批中”都纳入占用范围,因此一张已有草稿也可能挡住另一项业务。它是业务选择,意味着长期搁置的草稿需要有人管理。
审批通过后,保留申请事实,进入实际处理
在修详情中可以同时看到故障、维修类型、厂商、费用和报修资产。已进入审批后的申请字段不应被随意改写,否则审批人当时同意的内容与后续执行依据就会分离。
▲ 这张既有演示单的故障为“电脑开不了机”,选择外部送修,登记费用4000元。顶部已审批通过,底部仍有“办结维修”,表明审批与完工是两个动作;金额只是该演示记录的值。
审批通过后,服务端把维修单设为维修中,并把资产卡片设为维修态。后续领用、借用等业务会检查卡片状态;领用要求闲置资产,维修中的设备因此不能继续按闲置资产领出。
这不是把维修单删掉或藏起来,而是让资产台账成为其他业务判断可用性的共同入口。维修详情仍保留过程信息,便于继续跟进。
办结需要结果,也需要一个恢复目标
页面的办结入口只在审批通过、维修状态为维修中时出现。打开弹窗后,需要填写维修结果;完成日期可以留空,由后端取当天日期。前端会对结果做去空白后的非空检查,后端则继续校验单据所处阶段。
▲ 办结是一个独立动作。此图仅打开已有在修单的弹窗,未填写、未提交;它说明输入要求,不作为完成结果的证明。
▲ 另一张既有测试单已经显示完成日期和“auto-verify 已修复”的结果。它与上图不是同一笔连续操作,测试字样保留原貌;本图证明结果字段的呈现方式。
这些画面来自现有演示、测试记录。页面状态帮助我们看清功能分工,下面的源码则解释状态恢复规则:如果报修前是使用中,修好后仍回到使用中;如果原来是闲置,就回到闲置。办结只恢复资产状态,不顺便改使用人、部门或存放位置。设备修复与资产交接需要各自的业务动作。
三、关键取舍:把审批、实物和历史分开保存
单纯在完成时写一个“闲置”,代码最短,却会把正在员工名下使用的资产变成可再次分配的候选。另一种办法是在完工时根据当前使用人反推状态,但这又把历史事实交给了可能变化的字段。
当前方案是在提交时保存beforeAssetStatus。此时系统已经重新检查资产可用性,保存的是本次送审的起点,比用户首次打开表单时的状态更有意义。审批通过后,卡片会被改为维修中,届时再读卡片就无法知道原先是闲置还是使用中。
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
一张维修单,连接卡片、流程与履历
▲ 一张资产卡片可对应多次维修。维修单关联流程实例;履历通过业务类型与业务ID指向来源单据。图示为逻辑关系,不表示数据库已声明外键。
asset_repair保存这一次维修的事实,asset_card保存资产当前状态。单据同时保留资产编号、名称快照,便于在单据上识别对象;真正更新卡片时仍使用assetId。
|
|
|
|---|---|
assetId
billCode
|
|
processStatus
repairStatus
|
|
beforeAssetStatus |
|
completeDate
repairResult
|
|
processInstanceId |
|
|
|
|
进入维修时,当前实现会写资产履历,记录前后状态与来源维修单。办结方法则更新维修结果和卡片状态,没有在该方法中追加一条完工履历。如果业务需要逐次追溯每个状态变化,这里还需要补齐事件记录,不能把“有履历表”理解为所有动作已经完整留痕。
一条业务链,分成不同时间点的事务
▲ 提交先校验并留快照,流程结束通知先回写审批状态,再进入通过钩子;实际维修完成后,人员另行发起办结。三段动作并不处在一个跨天数据库事务里。
流程定义标识为asset_repair,创建流程时以维修单ID作为businessKey,再把流程实例ID回写单据。这样审批中心和资产业务可以沿同一业务标识找到对方,而不必把资产字段塞进流程引擎的内部表。
四、让代码对应三个业务节点
以下为当前实现的核心摘录,省略处明确标出,保留各自的业务判断。它们依赖所在工程的Mapper、枚举和流程API,不是脱离工程即可运行的独立示例。
提交时重新判断可用性,再记住原状态
这一段对应“提交报修”:先保存本次表单,再读卡片并执行占用检查。传入当前单据类型和ID,是为了让检查跳过本单,而不是跳过其他维修单。
public Long submit(AssetRepairSaveReqVO saveReqVO) {Long id = save(saveReqVO);AssetRepairDO row = repairMapper.selectById(id);// ... 省略资产非空、重复送审检查AssetCardDO card = assetCardMapper.selectById(row.getAssetId());if (card == null) {throw exception(ASSET_CARD_NOT_EXISTS);}billLockService.assertAssetsAvailable(List.of(row.getAssetId()),List.of(AssetStatusEnum.IDLE.getStatus(),AssetStatusEnum.IN_USE.getStatus()),AssetBillLockService.BILL_REPAIR, id);row.setBeforeAssetStatus(card.getAssetStatus());row.setProcessStatus(BpmTaskStatusEnum.RUNNING.getStatus());// ... 省略组织变量、流程创建及实例ID回写repairMapper.updateById(row);return id;}
这里没有把卡片改为维修中。此时的限制主要来自在途单据检查;申请被拒绝或取消,也就不需要因为“提前进入维修”再额外反向恢复卡片。
审批通过,维修单和卡片一起进入维修阶段
流程通知回写审批状态后,通过钩子推进实际业务。维修单与资产卡片各写自己的状态,避免把审批枚举直接当成资产枚举。
public void onProcessApproved(String businessKey) {Long id = Long.parseLong(businessKey);AssetRepairDO row = validateRepairExists(id);AssetRepairDO u = new AssetRepairDO();u.setId(id);u.setRepairStatus(REPAIR_STATUS_REPAIRING);repairMapper.updateById(u);AssetCardDO card = assetCardMapper.selectById(row.getAssetId());Integer beforeStatus = card != null? card.getAssetStatus() : row.getBeforeAssetStatus();Integer afterStatus = AssetStatusEnum.REPAIRING.getStatus();AssetCardDO cardUpd = new AssetCardDO();cardUpd.setId(row.getAssetId());cardUpd.setAssetStatus(afterStatus);assetCardMapper.updateById(cardUpd);// ... 省略日志文本拼装及维修履历写入}
审批状态回写与业务钩子是两个调用,不能仅看“审批通过”标签就推断后续回写一定成功。尤其是重试、异常和重复通知场景,需要关注钩子的执行结果;本方法没有显式的重复回调拦截,不能据此宣称回调已具备完整幂等保障。
办结先检查阶段,再恢复提交时的状态
完成操作同时维护单据结果与资产卡片。它拒绝未审批通过、已经完成以及不处于维修中的单据;顺序重复点击不会重新推进一次业务。
public void complete(Long id, String repairResult, LocalDate completeDate) {AssetRepairDO row = validateRepairExists(id);if (!BpmTaskStatusEnum.APPROVE.getStatus().equals(row.getProcessStatus())) {throw exception(ASSET_REPAIR_NOT_APPROVED);}if (REPAIR_STATUS_DONE == row.getRepairStatus()) {throw exception(ASSET_REPAIR_ALREADY_COMPLETED);}if (!Integer.valueOf(REPAIR_STATUS_REPAIRING).equals(row.getRepairStatus())) {throw exception(ASSET_REPAIR_STATUS_FORBIDDEN);}LocalDate doneDate = completeDate != null ? completeDate : LocalDate.now();AssetRepairDO upd = new AssetRepairDO();upd.setId(id);upd.setCompleteDate(doneDate);upd.setRepairResult(repairResult);upd.setRepairStatus(REPAIR_STATUS_DONE);repairMapper.updateById(upd);Integer targetStatus = row.getBeforeAssetStatus();if (!AssetStatusEnum.IDLE.getStatus().equals(targetStatus)&& !AssetStatusEnum.IN_USE.getStatus().equals(targetStatus)) {targetStatus = AssetStatusEnum.IDLE.getStatus();}AssetCardDO cardUpd = new AssetCardDO();cardUpd.setId(row.getAssetId());cardUpd.setAssetStatus(targetStatus);assetCardMapper.updateById(cardUpd);}
当快照不是闲置或使用中时,当前代码默认回到闲置。这是兼容异常、历史快照的策略,并不等于能够自动判断资产真实归属。对必须严格保留责任人的场景,更稳妥的业务选择可能是阻止办结并要求核对,但那属于进一步设计,不能当作当前已实现能力。
五、把业务门禁与并发保障分清楚
上述链路解决的是“该不该推进”,还需要注意“两个请求同时推进会怎样”。在途服务虽然名为AssetBillLockService,当前实现核心是查询卡片与关联单据,没有因为名字里带Lock就自动获得数据库行锁或分布式锁。
它能识别已有的占用:处置、领用借用归还、调拨、维修等单据会参与判断。但两个提交若同时查到可用,仅靠先查后写不能保证原子互斥。要支撑严格的并发竞争,还需要结合部署形态选择行锁、条件更新或唯一占用记录,并确保所有入口使用相同约束。
办结前检查“已完成”也有相同边界:它可以拦住后到的顺序请求,不等于已经解决两个并发请求同时读取旧状态的问题。审批回调若可能重复投递,还应结合业务状态、事件标识或更新条件设计幂等,而不能只依赖前端隐藏按钮。
另一个容易遗漏的边界是结果校验。当前页面要求维修结果非空,办结接口在所核对实现中没有同等的非空注解或服务内空白校验。页面约束与接口约束不能混为一谈;如果增加其他调用端,应补齐一致的输入规则。
资产维修的闭环,最终落在几个明确的时间点上:提交时保留起点,审批通过时改变可用性,办结时记录结果并恢复原态。把这几个事实分清,维修单才既能服务处理人员,也能让领用、调拨等后续业务读到有意义的资产状态。

