SpringBoot+Vue3 实战 MES :生产批次追溯从原料来源到成品去向,设计双向可查的关系网络
质量人员发现一批原料存在异常,第一反应不是查它的名称,而是确认:它投入了哪些生产任务,形成了哪些成品,哪些还在库,哪些已经发出。换一个方向,客户反馈某批产品异常,又要查回原料、工单和检验记录。一次混批、一次拆分、一次返工,都会让问题超出“按批次号查一张表”的范围。
▲ 关系设计示意:两个原料批次可以进入同一生产过程,产出又可能分为不同批次与去向。正向和逆向查询沿同一组事实运行,不是两套独立维护的数据。
一、追溯的核心不是编号,而是可解释的关联
批次号用于标识一组在业务上共同管理的物料,但它本身不记录转化关系。知道原料批次 A 和成品批次 C 都存在,不等于知道 A 是否参与了 C 的生产,更不等于知道参与了多少。
追溯需要把身份、事件和证据连接起来:身份说明是哪一批;事件说明发生了投料、产出、拆分、合并或转移;证据说明这条关系来自哪张业务单、哪一行和哪个时点。缺少其中一层,查询结果就容易变成“看起来有关联”的列表。
批次管理与序列号管理也不同。批次描述一组对象的共同属性,序列号通常标识单件。一个成品批次可以包含多件序列号产品,关系需要显式记录;不能把一串能扫码的号码都统称为同一种追溯身份。
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
本文把追溯看作业务关系建模问题,而不是某个数据库产品的选型题。先把事实记录清楚,关系型数据库也可以支持有价值的追溯;关系缺失时,换成图数据库也不会自动获得真实的投料证据。
二、先确认现有 MES 能力,再讨论精细化设计
当前工程的 Maven 聚合包含 MES 模块,实际应用也提供批次追溯入口。源码中不仅有批次生成,还存在 getForwardBatchList 与 getBackwardBatchList 两个方向的查询,以及递归防环和深度上限。
因此,不能把现有系统说成“只有批次编号,没有追溯”。更准确的边界是:已有批次与单据关联查询;更精细的投料分摊、完整路径证据和跨域去向展示,需要按业务进一步验证与扩展。
1. 工单描述生产上下文,不应直接等同于精确谱系
生产工单关联物料、任务、计划和 BOM 等信息,为后续报工、消耗和产出提供上下文。BOM 描述计划需要什么,实际消耗记录才描述本次用了什么,两者不能互相代替。
▲ 真实工单详情。工单是追溯的重要连接点,但计划物料不等于实际投料批次,精确追溯仍需消耗记录。
例如,同一工单先后投入两批原料,并分两次报工。按工单关联可以圈定相关范围,但未必能确认第一批原料只进入第一批产出。若现场没有记录到报工、工序或投料事件层级,系统就应说明关系精度,而不是自行分摊出一组精确数量。
2. 产出与质检,让“生产了多少”和“可以放行多少”分开
现有产出生成逻辑会关联报工、工单、工作站与工序,再根据物料批次配置获取或生成批次。需要检验时先生成待检产出行,后续按检验结果处理;不需检验的分支则按合格与不合格数量创建相应行和明细。
▲ 真实过程检验详情。质量结果应与具体业务对象关联,而不是成为追溯页面上一个无法回查的“合格”标签。
这里也要避免一个误解:质量拆行不必然生成新的批次身份。批次、质量状态、库存位置和产出明细是不同维度。到底拆批还是只拆库存明细,取决于物料配置与业务规则,不能看到两行记录就认定已经形成两条独立批次谱系。
3. 双向追溯入口已经存在,结果仍需解释
批次追溯详情显示批次信息,并提供“向前追溯”“向后追溯”两个标签。当前前端分别调用对应查询接口,服务端沿单据关系递归获取关联批次。
▲ 真实批次追溯界面:从原料批次 RAW-BATCH-001 查到两条关联产出批次。它证明现有单据关系可以查询,不代表已采集完整发货去向或精确投料分摊。
在本文中,正向指从原料查询后续产出与去向,逆向指从成品回查原料来源。界面词语与业务团队的习惯应保持一致;如果“向前”在某个组织被理解为向历史查,最好直接显示“查去向”“查来源”,减少歧义。
三、源码里的关联路径,能证明到什么精度
当前批次 Mapper 的正向查询,从原料消耗明细进入消耗单,再关联报工与生产产出记录,最后得到产出行中的批次。逆向查询则从产出明细进入产出单,再经工单关联消耗单和消耗明细。
这是一条有业务依据的查询链,不是按批次名称做模糊匹配。但其中的重要连接使用了工单身份,因此结果应首先解释为工单相关的上下游范围,而不是天然精确到某次投料对某件成品的数量归属。
举个设计示例:工单 W 使用原料 A 和 B,分两次产出 C 和 D。如果只有工单级连接,系统可能得到 A、B 都与 C、D 相关。这个范围对初步质量排查很有价值,却不能据此声称 C 中恰好包含 A 的某个比例。
要提高精度,需要现场采集更细的事件,例如投料批次与报工任务、设备批次、工序段或容器的对应关系。数据没有采到的部分,应标成未知或粗粒度关联。不能为了让追溯图看起来完整,就用平均分摊制造不存在的事实。
批次生成规则也会影响后续追溯粒度
MesWmBatchServiceImpl 会读取物料批次配置,根据启用的属性检查必填项并构造匹配条件。符合已有批次条件时复用,否则生成新批次码。生产日期、供应商、客户、工单、工作站等是否参与区分,取决于配置。
现有产出链路还会把生产日期处理到当天起点,但这并不表示“同一天必定只有一个批次”。其他启用属性仍然参与匹配。批次规则应该表达真实的管理粒度,而不是为了减少编号数量随意合并。
下面摘出已有产出逻辑中构造批次请求的部分,展示批次与生产上下文如何连接,省略后续质量分支。
MesProWorkOrderDO workOrder = workOrderService.getWorkOrder(feedback.getWorkOrderId());MesWmBatchGenerateReqVO batchRequest = new MesWmBatchGenerateReqVO().setItemId(feedback.getItemId()).setProduceDate(LocalDate.now().atStartOfDay()).setExpireDate(feedback.getExpireDate()).setWorkOrderId(feedback.getWorkOrderId()).setClientId(workOrder != null ? workOrder.getClientId() : null).setSalesOrderCode(workOrder != null ? workOrder.getOrderSourceCode() : null).setWorkstationId(feedback.getWorkstationId()).setLotNumber(feedback.getLotNumber());MesWmBatchDO batch = batchService.getOrGenerateBatchCode(batchRequest);Long batchId = batch != null ? batch.getId() : null;String batchCode = batch != null ? batch.getCode() : null;// 后续产出行和产出明细引用 batchId、batchCode。// 是否启用批次以及哪些属性参与匹配,由物料批次配置决定。
如果物料没有启用批次管理,服务可能返回空批次结果。下游不能为了“让图不缺节点”凭空生成一个批次;应如实说明该段链路缺少批次级身份,并决定是否需要从业务采集环节补齐。
四、用事件连接输入与输出,比只维护一张上下游表更稳妥
最简单的关系表只有来源批次和目标批次。它能支持基本查询,但一旦出现混批、拆批、返工、撤销和单位转换,就很难解释为什么存在这条边。
更稳妥的模型,是在输入与输出之间保留转化事件。事件记录业务类型、工单或工序、发生时间和来源单据;参与项记录某个批次作为输入还是输出、数量和单位;查询可以在此基础上形成批次关联。
▲ 精细谱系建议模型。现有消耗、产出和质检单据提供事实来源;统一事件与精度标识是增强设计,不是已新增的数据库表。
以两种原料共同形成一批成品为例,事件可以记录 A 消耗多少、B 消耗多少、C 产出多少。它保留了真实输入输出,却不必虚构“A 对 C 的一对一分摊金额或重量”。若行业需要进一步分摊,分摊方法和依据应另行定义。
拆批同样需要说明语义。把同一批货放到两个库位,可能只需要两条库存记录,不需要新批次;重新包装或质量分级后形成新的管理身份,则可能需要新批次并保留来源关系。物理移动、库存拆行和批次身份变化不能混为一谈。
返工会让关系网络出现更复杂的路径。原成品重新投入加工时,可以记录新的转化事件,而不是覆盖原产出记录。查询需要防止环路,但也不能把合法返工关系简单删除。事件时间和业务身份能帮助区分重复数据与真实重复加工。
五、正向与逆向查询,应该共享同一份事实
两种方向的区别在于沿哪一端扩展邻居,而不是分别维护两套容易不同步的来源和去向清单。每条事实关系只应有一个可信来源,再由查询决定方向。
现有实现用 visited 集合避免重复递归,并设置最大追溯深度 20。下面是正向查询的核心代码,保持实际控制逻辑。
public List<MesWmBatchDO> getForwardBatchList(String code) {return getForwardBatchList(code, new HashSet<>(), 0);}private List<MesWmBatchDO> getForwardBatchList(String code, Set<String> visited, int depth) {if (code == null || !visited.add(code) || depth >= MAX_TRACE_DEPTH) {return new ArrayList<>();}List<MesWmBatchDO> list = batchMapper.selectListByForward(code);if (CollUtil.isEmpty(list)) {return new ArrayList<>();}List<MesWmBatchDO> results = new ArrayList<>(list);for (MesWmBatchDO batch : list) {results.addAll(getForwardBatchList(batch.getCode(), visited, depth + 1));}return results;}
防止重复递归与保证最终结果去重,是两个不同目标。上面的 visited 约束继续扩展,但结果由各层列表合并而成;多条路径可能触达同一批次,展示层仍需要考虑节点唯一性和路径保留。不能只因为出现了集合,就宣称所有输出已经去重。
另外,达到深度上限时返回空扩展,可以保护系统,但如果界面没有提示,用户可能误以为已经查到全部。用于正式质量排查时,建议返回查询是否截断、使用的范围和限制,以及哪些关系因权限或缺失数据未能展开。
▲ 增强查询时序:结果不仅展示批次,还应展示路径依据和查询边界;深度限制、节点上限与权限范围都属于结果解释的一部分。
大范围查询要保留边,而不是只返回扁平批次列表
如果结果只是十个批次号,用户不知道每个批次通过哪张单据与起点相关。可以把节点与关系分开返回:节点按身份去重,关系保留来源、目标、事件和精度。已经展开过的节点不必再次查询,但第二条合法路径不能因此丢失。
下面是增强查询的设计伪代码,展示有限范围的图遍历。实际实现还需加入租户与数据权限、批量查询、超时和结果大小限制。
public TraceGraph trace(BatchId start, Direction direction, Scope scope) {Deque<Visit> queue = new ArrayDeque<>();Set<BatchId> expanded = new HashSet<>();TraceGraph graph = new TraceGraph();queue.add(new Visit(start, 0));while (!queue.isEmpty()) {Visit visit = queue.removeFirst();if (!expanded.add(visit.id())) continue;if (visit.depth() >= limits.maxDepth()) {graph.markTruncated(visit.id());continue;}for (TraceEdge edge : relations.authorizedNeighbors(visit.id(), direction, scope)) {graph.addEdgeIfAbsent(edge);BatchId next = edge.neighbor(direction);if (graph.reachesNodeLimit()) return graph.asTruncated();queue.addLast(new Visit(next, visit.depth() + 1));}}return graph;}
这个示例故意把关系的授权检查放在查询接口里。企业内不同公司、工厂和客户资料可能有不同可见范围,追溯不能成为跨组织枚举数据的后门。无权展开时应遵守安全策略,不应为了让图连通而返回受限批次名称。
六、数量、单位和质量状态,比关系线更容易出错
一条关系可能代表 100 千克原料、20 件半成品或 8 箱成品。不同物料和单位不能直接相加。需要建立统一计量或明确转换依据,保留转换时的规则和精度;无法转换时就分别展示,不要生成一个没有物理意义的总量。
即使单位相同,也不能简单要求所有制造过程输入数量等于输出数量。加工损耗、辅料、联产品和返工都会影响数量关系。守恒校验应围绕具体工艺和可比较的量定义,而不是对所有事件套一条通用等式。
质量状态还可能在生产之后发生变化。一个批次先处于待检,后来部分放行、部分隔离;历史发货与当前库存需要分别关联当时事实。仅查看批次当前状态,可能无法解释此前发生的流转。
因此,质量记录应保留检验对象、时间、结果和处理依据。将质检记录挂在关系网络上,是为了能够回到具体证据,不是让一枚绿色标签覆盖整个生命周期。
查询影响范围也不是责任认定。某原料批次与某成品批次有关联,只能说明它值得进一步核查;是否存在质量问题、需要采取何种处置,应结合检验、工艺和企业管理要求。系统不能把关系存在直接翻译成“全部不合格”。
七、从成品继续查到库存与发货,需要明确跨域边界
生产关系说明来源,库存和发货记录说明去向。要把链路延伸到仓库、客户或订单,必须核对跨模块使用的是同一个批次身份,或者存在可追溯的映射。
如果 MES 与 WMS 各自保存批次码,同一个字符串未必就是同一对象;如果 ERP 的发货记录只保留物料而没有批次,链路会在这里中断。界面应显示“该环节缺少批次明细”,而不是把所有同物料发货都当成该批次的去向。
本次已经核对的批次查询主要沿消耗与产出关系展开。主图中的客户和仓库去向,是完整目标链路的一部分,不能据此宣称当前查询页面已经完成所有跨模块发货追踪。正式扩展时,需要继续核对出库行、库存明细与批次字段的绑定。
对外部供应商批次,还要区分企业内部批次 ID 与供应商原始批号。供应商批号可能在不同供应商之间重复,内部身份不能只用一段外部字符串作为全局主键。
八、数据修正与撤销,要保持追溯事实可解释
现场录入可能选错批次,也可能在报工后发现数量有误。直接修改历史关系会让此前查询结果失去依据。更稳妥的方式是按业务规则作废错误记录、生成纠正或反向记录,并保留原单据与修正原因。
不是所有场景都需要完整事件溯源架构,但至少应知道一条关系是正常、已撤销还是由后续记录修正。查询默认应使用有效事实,同时允许有权限的人员查看修正过程。
导入历史数据时,尤其不能为了让链路完整而批量猜测关系。可以标记为“人工补录”“工单级推定”或“原始明细确认”,让不同证据强度在结果中可区分。真正的高质量追溯,不是没有未知,而是未知有明确边界。
九、怎样验收双向追溯是否真的可靠
应准备可以人工画清楚的小型样本网络,而不是只找一个只有单一路径的批次演示。至少覆盖两个输入、两个输出、共享中间批次、返工和缺失一段关系的情况。
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
还应测试正逆向的一致性。在同一授权范围、同一数据截点和相同深度条件下,如果查询确认 A 通过某事件关联 C,逆向查询应能够解释这条同一事件关系。条件不同引起的差异可以存在,但必须可解释。
性能测试则关注层数、分支数、返回节点与单次查询成本。深度只有几层但每层分支很多,仍然可能形成大量结果。可以批量查询相邻关系、按证据分页、限制展示规模,但不能静默截断后继续宣称结果完整。
十、从已有追溯能力,走向更精细的制造证据
第一步,核对物料的批次配置与业务采集,确保批次身份足够稳定;第二步,检查实际消耗和产出是否保留了所需关联;第三步,让查询返回关系依据和范围说明;第四步,再扩展到更细的投料事件及库存、发货去向。
RuoYi Office 的批次服务、生产产出和过程检验提供了可调研的现有链路。可以从生产工单、过程检验、批次追溯三个页面开始,再沿 MesWmBatchServiceImpl、MesWmBatchMapper.xml 和 MesWmProductProduceServiceImpl 对照实现。
本文没有向业务库补造追溯记录,也没有把目标网络伪装成已有完整产品。对制造系统来说,承认当前采集精度,往往比画出一张没有依据的全链路图更有工程价值。
常见问题
有批次号和二维码,是否就有追溯能力?
只有身份基础。还需要采集输入、输出、移动和检验等关系,并能回到业务证据。扫码只是访问入口,不会自动产生缺失的业务链。
按工单查询上下游,算不算追溯?
算,但应说明是工单相关范围。若同一工单存在多次投料和多次产出,没有更细关联时,不能把结果解释为精确消耗分配。
是否必须使用图数据库?
不必须。关系型数据库加明确的事件和参与项可以支撑不少场景。先确认采集粒度、查询规模和证据要求,再决定存储技术。
查不到下游,能否认定没有影响?
不能直接认定。还要区分确实没有业务、权限限制、数据缺失、状态过滤和查询截断。系统应把这些情况区分展示。
结语
生产追溯真正要保存的,不只是一串批次号,而是物料如何参与业务、通过什么事件发生转化、最终流向哪里,以及每一条关联的证据精度。来源与去向使用同一组事实,正向与逆向查询才可能相互解释。
从现有批次与单据查询出发,逐步补齐精细关系、数量依据和查询边界,SpringBoot3+Vue3 的制造系统才能让追溯从“查到一些相关记录”,走向“知道这些记录为什么相关”。

