- 新增入库登记、入库作废、通用出库、退库确认等仓储管理场景 - 添加来料检验、过程检验、完工检验、库存盘点等质量管理场景 - 实现半成品入库出库、库存查询、工单备料台账等库存控制功能 - 集成AGV配送、区域维护、物料档案等基础数据管理场景 - 完善账号角色管理、操作日志、附件中心等系统管理功能 - 补充登录工作台、库存大屏等用户界面交互场景 - 增加MES工单状态流转、BOM维护、数量不符处理等生产管理场景 - 实现工位组合下发、拧紧查询、绩效报表等制造执行功能 - 添加巡检终端、质量检验、生产流程卡等现场作业场景 - 完善预警中心、看板管理等监控告警机制
147 KiB
任务:通读 bj_power 代码,生成业务契约文档
【你要读的代码】
- bj_power_mes、bj_power_wms、bj_power_workstation 三个 Go 项目的 handler、logic、schema
- 各项目 frontend/src/pages/*.vue
- 根目录所有 .md 文档(只作背景,代码是唯一事实)
【文档定位】 这是给业务方和开发看的业务场景流水账,不是代码审计报告。 用业务语言写,人能看懂。不要贴代码,不要标行号,不要贴函数名。
【铁律】
- 读代码,但用业务语言描述,不暴露代码细节。
- 业务逻辑如果跑不通,记录下来,停止后续操作。
- 如何判断逻辑跑不通? 每一步,都有 前置条件、执行过程、产生的数据。如果 本步骤需要的数据条件, 前面步骤无法提供,就是业务流程不通。
- 不写"应该怎样"、"建议改成"、"后续优化"。
- 不合并场景,不擅自拆分。
- 英文枚举名转中文。
【第一步:列场景清单】 通读代码后,列出系统实际支持的全部业务场景。 每个场景写:场景名、从哪个菜单进入、干什么事。 清单只写代码里真实存在的。列完清单后,直接进入第二步,不要停下来等确认。
【第二步:在此文档 写三份 大标题】
文档1:业务流程 顶层地图。主链路 + 异常链路,每个节点指向“案例”的场景编号。 一个节点一句话。不重复场景内容。
文档2:案例 场景流水,禁止写分支,从头到尾只有一条路径,禁止写 "如果...就..."、"否则..."、"另一种情况"。每个场景独立成章,格式:
场景N:场景名
前置条件
操作步骤(角色 + 系统 + 菜单 + 填什么字段(关键字段即可) + 点什么按钮)
系统反应(业务语言,如"库存扣减204颗,锁定量清零")
数据变化(哪个表、哪个字段、变成什么值)
验证点(页面能看到什么、数据库断言什么、数量等式是否成立)
对不上的地方(标红,只标真正对不上的,不标"代码在哪")
文档3:回归测试 可断言用例。按场景分组,格式:
测试场景N:场景名
前置数据准备(SQL 或 API)
执行步骤(API 路径 + body)
预期结果(表名 + 字段 + 期望值)
数量等式断言
清理步骤
断言必须是具体数值/状态/条数,不能写"页面正常"。
【执行顺序】
- 列场景清单
- 写业务流程
- 写案例(一次一个场景,写完继续下一个,不要停)
- 写回归测试
【禁止】
- 禁止凭想象写业务逻辑
- 禁止把设计文档当事实
- 禁止贴代码、标行号、贴函数名
- 禁止写"应该"、"建议"、"后续优化"
- 禁止模糊断言
- 禁止英文枚举直接写文档
- 禁止在案例里写代码细节
下面是执行结果输出文本:
第一步:业务场景清单
清单只列代码里真实存在的菜单与功能。分三套系统:WMS 库房管理(端口 8890)、MES 产线控制(端口 8888)、工位终端(端口 8892)。
一、WMS 库房管理
| 编号 | 场景名 | 入口菜单 | 干什么事 |
|---|---|---|---|
| W1 | 登录 | 登录页 | 库管员/管理员用账号密码登录,取得访问令牌 |
| W2 | 工作台 | 工作台 | 查看库存总览、预警等首页概览 |
| W3 | 入库登记 | 入库管理 | 结构件按批次、电气件按序列号登记入库,支持 Excel 批量导入,入库后库存增加 |
| W4 | 入库作废 | 入库管理 | 对已登记入库单填原因作废,库存按批次/序列号精确回滚 |
| W5 | 备料出库 | 出库管理 | 按工单备料出库,装箱、指定目标工位,库存扣减 |
| W6 | 通用出库 | 出库管理 | 非工单的一般出库,库存扣减 |
| W7 | 退库确认 | 退库确认 | 工位退回物料,库管确认收货后库存加回 |
| W8 | 库存查询 | 库存查询 | 按物料汇总/区域汇总/明细三种口径查库存 |
| W9 | 来料检验 | 来料检验 | 登记来料检验结论,合格/不合格翻转与处置 |
| W10 | 过程检验 | 过程检验 | 登记生产过程检验结论与处置 |
| W11 | 完工检验 | 完工检验 | 登记完工检验结论与处置 |
| W12 | 库存盘点 | 库存盘点 | 发起盘点、扫码录入实盘、完成写回差异、取消、导出明细 |
| W13 | 半成品入库/出库 | 半成品管理 | 半成品入库存/出库存与查询 |
| W14 | 工单备料台账 | 工单备料台账 | 查看某工单各物料的锁定量/在途量/线边量/已接料量四段台账 |
| W15 | AGV 配送 | AGV配送 | 向海康调度系统下发搬运任务、查看任务与接驳台状态、取消 |
| W16 | 区域维护 | 区域维护 | 维护区域/货架/层/位置号四级库位结构,支持批量生成 |
| W17 | 物料档案维护 | 物料档案 | 新增/修改/查询物料档案,支持导入导出 |
| W18 | 接驳台维护 | 接驳台维护 | 查看系统预置的接驳台只读主数据 |
| W19 | 账号管理 | 账号管理 | 增删改查登录账号、分配角色 |
| W20 | 角色管理 | 角色管理 | 增删改查角色、配置权限 |
| W21 | 操作日志 | 操作日志 | 查询系统操作留痕 |
| W22 | 附件中心 | 附件中心 | 集中查看/归档/清理各业务上传的附件 |
| W23 | 修改密码 | 修改密码 | 当前用户修改自己的登录密码 |
| W24 | 库存大屏 | 库存大屏(免登录) | 全屏展示库存概览 |
二、MES 产线控制
| 编号 | 场景名 | 入口菜单 | 干什么事 |
|---|---|---|---|
| M1 | 工单管理 | 工单管理 | 新增/修改/查询工单、切换工单状态、自动排产 |
| M2 | 日排产 | 日排产 | 为工单编排每日生产计划、推荐接驳台 |
| M3 | 产品物料清单 | 产品物料清单 | 维护产品型号的 BOM,支持导入、删除条目 |
| M4 | 备料单 | 备料单 | 按 BOM 生成备料单、领料、触发 WMS 自动出库、查看配送进度 |
| M5 | 数量不符处理 | 数量不符处理 | 处理工位上报的物料数量不符,查看统计 |
| M6 | 工位组合下发 | 工位组合下发 | 把工艺下发到 PLC,查看下发日志 |
| M7 | 拧紧查询 | 拧紧查询 | 查询拧紧记录、按组统计、审计修改 |
| M8 | 工艺流程 | 工艺流程 | 维护产品型号的工艺流程与工序步骤、上传工艺文件 |
| M9 | 关联工位 | 关联工位 | 维护工位、配置工位与工序的派工关系 |
| M10 | 绩效报表 | 绩效报表 | 按人/按工位/明细三口径统计工作量并导出 |
| M11 | 巡检终端 | 巡检终端 | 登记巡检记录、上传照片、一键生成工序间检验记录 |
| M12 | 质量检验 | 质量检验 | 查 BOM 物料、登记检验处置、生成不合格单 |
| M13 | 在制品 | 在制品 | 查看线上在制工件 |
| M14 | 手动报工 | 手动报工 | 工件上线、工序报工、完工 |
| M15 | 工件追溯 | 工件追溯 | 按工件追溯全流程、面板绑定/解绑 |
| M16 | 生产流程卡 | 生产流程卡 | 打印工单流程卡、查看型号全景流程卡 |
| M17 | 物料档案 | 物料档案 | 维护产品型号/物料档案 |
| M18 | 预警中心 | 预警中心 | 配置预警规则、查看预警、标记已读、应急呼叫 |
| M19 | 附件中心 | 附件中心 | 查看/下载/归档/删除附件 |
| M20 | 操作日志 | 操作日志 | 查询操作留痕 |
| M21 | 账号管理 | 账号管理 | 增删改查账号、分配角色 |
| M22 | 角色管理 | 角色管理 | 增删改查角色与权限 |
| M23 | 修改密码 | 修改密码 | 当前用户修改密码 |
三、工位终端(产线现场触摸屏)
| 编号 | 场景名 | 入口 | 干什么事 |
|---|---|---|---|
| S1 | 工位登录/登出 | 主界面 | 工位操作员登录、登出 |
| S2 | 当前任务 | 主界面 | 显示本工位当前应生产的任务 |
| S3 | 工件上线 | 上线面板 | 扫码工件上线建档 |
| S4 | 报工/拧紧 | 工作面板 | 按工序采集数据、记录拧紧、工序完工报工、暂存 |
| S5 | 工件下线/完工 | 下线面板 | 工件末道完工下线 |
| S6 | 面板绑定 | 工作面板 | 工件与面板绑定/解绑 |
| S7 | 移料/叫料 | 移动面板 | 产线点位移料、叫料、占用点位 |
| S8 | 领料 | 物料面板 | 查看备料单、确认接料 |
| S9 | 巡检 | 巡检面板 | 现场提交巡检、上传照片 |
| S10 | 数量不符上报 | 物料面板 | 上报物料数量不符 |
| S11 | 退料 | 物料面板 | 工位退回物料,生成 WMS 退库单 |
| S12 | 应急呼叫 | 主界面 | 现场发起应急呼叫 |
| S13 | 工艺文件查看 | 主界面 | 查看工序对应的工艺文件(PDF) |
| S14 | 工作量查询 | 主界面 | 查看本工位工作量 |
| S15 | 离线同步 | 主界面 | 断网时本地暂存、恢复后回传,查看同步状态 |
场景清单完毕。以下进入第二步:写三份文档(业务流程 / 案例 / 回归测试)。
文档1:业务流程
顶层地图。一个节点一句话,括号内指向「文档2:案例」的场景编号。
主链路(从合同到成品入库)
- 建产品型号与 BOM,定义一台产品由哪些物料、多少数量构成(M17、M3)。
- 维护工艺流程与工位派工,定义型号要过哪些工序、每道工序在哪个工位干(M8、M9)。
- 按合同建工单,一个合同对应一个工单,定下产品型号与数量(M1)。
- 对工单做日排产,把工单排到具体生产日并推荐接驳台(M2)。
- 按排产生成备料单,算出每个工位、每道工序需要哪些物料、多少量(M4)。
- WMS 备料出库,按备料单从库存扣料、装箱、指定目标工位,台账记「已发出」(W5)。
- AGV 把料从库房搬到接驳台(W15)。
- 工位扫码接料,备料单记「已接料」,在途量清零(S8)。
- 工件上线建档,一件产品进入产线(S3、M14)。
- 逐道工序报工,采集过程数据、记录拧紧、推进工件工序状态(S4、M14)。
- 过程巡检与质检,记录工序间检验结论(S9、M11、M12)。
- 末道完工下线,成品经内部接口自动回流 WMS 入库为成品,并按整份 BOM 反冲核销台账「已消耗」(S5)。
- 工件追溯与绩效统计,回看全流程、按人/工位汇总工作量(M15、M10)。
异常链路
- 来料不合格:来料检验判不合格后做退货/让步处置(W9)。
- 数量不符:工位上报物料数量不符(S10)→ MES 处理并生成补发备料单(M5、M4)。
- 退料:工位退回物料(S11)→ WMS 退库确认收货、库存加回、台账记「已退回」(W7)。
- 入库作废:入库单填原因作废,库存按批次/序列号回滚(W4)。
- 质检不合格:质量检验判不合格并生成不合格单(M12)。
- 盘点差异:盘点扫码录入实盘,完成时写回盘盈盘亏(W12)。
- 拧紧超差:拧紧记录超差被审计修改并留痕(M7)。
- 应急呼叫:工位现场发起应急呼叫,进预警中心(S12、M18)。
- 断网离线:工位断网本地暂存,恢复后回传(S15)。
支撑域(不在主链路上,但全局依赖)
- 主数据:区域库位四级结构(W16)、物料档案(W17/M17)、接驳台(W18)。
- 库存查询与台账:库存三口径查询(W8)、工单备料四段量台账(W14)。
- 权限与留痕:账号(W19/M21)、角色(W20/M22)、操作日志(W21/M20)、修改密码(W23/M23)。
- 附件与展示:附件中心(W22/M19)、库存大屏(W24)、生产流程卡(M16)、在制品看板(M13)、预警中心(M18)。
- 下发:工位组合下发到 PLC(M6)。
文档2:案例
每个场景独立成章,从头到尾只一条路径。数据变化写到表/字段/值。本批为生产主链路(工单→排产→派工→备料→接料→上线→报工→完工→追溯)。
场景 M1:建工单
前置条件
- 已存在产品型号(物料档案 M17),型号编码记为 CP001。
操作步骤
- 计划员登录 MES,进「工单管理」,点「新增工单」,填:工单号 WO20260920、产品型号 CP001、数量 10、合同编号 HT001、工程编号 GC001、产品序号 01、完成日期 2026-09-30,点「保存」。
系统反应
- 工单创建成功,状态为「已创建」,完成数量 0、NG 数量 0,创建人记为当前登录用户。
数据变化
- work_order 表新增一行:workOrderNo=WO20260920、productCode=CP001、quantity=10、finishedNum=0、failNum=0、contractNo=HT001、projectNo=GC001、productSerial=01、status=CREATED。
验证点
- 工单管理列表出现 WO20260920,状态「已创建」,数量 10,完成 0。
- 数据库断言:work_order 中 workOrderNo=WO20260920 的行 status='CREATED' 且 quantity=10。
对不上的地方
- 无,链路打通。
场景 M2:日排产
前置条件
- 工单 WO20260920 存在(M1)。
操作步骤
- 计划员在 MES「日排产」,选工单 WO20260920、排产日期 2026-09-20、计划数量 10,点「保存」。
系统反应
- 生成当日排产计划,状态「待生产」,系统给出推荐接驳台。
数据变化
- daily_plan 表新增一行:orderNo=WO20260920、planDate=2026-09-20、planQty=10、completedQty=0、status=PENDING。
验证点
- 日排产列表出现 2026-09-20 / WO20260920 / 计划 10。
- 数据库断言:daily_plan 中 (planDate=2026-09-20, orderNo=WO20260920) 唯一一行,planQty=10。
对不上的地方
- 无。
场景 M9:工位派工
前置条件
- 型号 CP001 的工艺流程含工序 1、工序 2(M8);工位 1、工位 2 已维护(M9 关联工位)。
操作步骤
- 工艺员在 MES「关联工位」,选生效日期 2026-09-20,把工位 1 派给工序 1、工位 2 派给工序 2,点「保存」。
系统反应
- 当日派工生效,同工位同天同班次覆盖式写入。
数据变化
- station_process 写入行:(stationNo=1, processCode=1, effectDate=2026-09-20)、(stationNo=2, processCode=2, effectDate=2026-09-20)。
验证点
- 数据库断言:station_process 中 effectDate=2026-09-20 存在工位1→工序1、工位2→工序2;(stationNo,effectDate,shift) 唯一。
对不上的地方
- 无。
场景 M4:生成备料单并自动出库
前置条件
- 工单 WO20260920 已排产到 2026-09-20(M2);今日已配工位派工(M9)。
- 型号 CP001 已配 BOM(M3):物料 M-A(结构件、单台用量 2、损耗率 0、装配工序 1)、物料 M-B(电气件、单台用量 4、装配工序 2)。
- WMS 物料档案存在 M-A、M-B(W17);WMS 有 M-A、M-B 的合格在库库存。
操作步骤
- 计划员在 MES「备料单」,选排产日期 2026-09-20,点「生成备料单」。
系统反应
- 系统把 BOM 展开到叶子,按工位拆料:工位 1 需 M-A = 10×2 = 20,工位 2 需 M-B = 10×4 = 40。
- 生成前校验 M-A、M-B 在 WMS 物料档案存在(缺失则不生成并回缺料清单)。
- 生成后把工单需求总量同步到 WMS 台账:M-A total_qty=20、M-B total_qty=40。
- 随即自动出库:按 FIFO 从合格在库库存扣料,补足未发数量;缺料则记「备料未齐套」预警。
数据变化
- material_request 新增行:(orderNo=WO20260920, planDate=2026-09-20, materialCode=M-A, stationNo=1, processCode=1, reqQty=20, sentQty=20, status=LOCKED);M-B 同理 reqQty=40、stationNo=2、sentQty=40。
- WMS order_material_ledger:M-A total_qty=20、out_qty=20;M-B total_qty=40、out_qty=40。
- WMS outbound_order:新增出库单 outbound_type=workorder、status=已出库、order_no=WO20260920;outbound_detail 记批次/SN 与数量。
- WMS inventory:M-A 对应批次 quantity 减 20;M-B 对应 40 个 SN status 由「在库」→「出库」。
验证点
- 备料单列表出现 M-A(工位1,需20)、M-B(工位2,需40),已发出=需求。
- 数量等式:material_request.sentQty ≤ reqQty;台账 net_out = out_qty − returned_qty ≤ total_qty;库存可用 = quantity − locked_qty ≥ 0。
- WMS 工单备料台账(W14)显示 M-A 已发出 20 / 需求 20。
对不上的地方
- 无。
场景 S8:工位扫码接料
前置条件
- 备料单 M-A(工位1) 已发出 sentQty=20(M4),料已送到工位。
操作步骤
- 工位 1 操作员在工位终端「物料面板」,扫备料单/物料码,点「接料」。
系统反应
- 系统登记已接料,接料量不超过已发出量;接满且已发齐需求时备料单完结。
数据变化
- material_request(M-A,工位1):receivedQty 0→20、receivedAt=当前时间、receiveBy=操作员、status LOCKED→DONE。
验证点
- 备料单状态「已完成」,已接料 20。
- 数量等式:在途 = sentQty − receivedQty = 0;receivedQty ≤ sentQty。
对不上的地方
- 无。
场景 S3:工件上线
前置条件
- 工单 WO20260920 存在。
操作步骤
- 上线位操作员在工位终端「上线面板」,扫工件 SN0001、选工单 WO20260920,点「上线」。
系统反应
- 工件建档进线,状态「已进线」,记进线时间。
数据变化
- workpiece 新增行:sn=SN0001、orderNo=WO20260920、workOrderId=工单ID、status=ONLINE、currentProcess=0、onlineAt=当前时间。
验证点
- 在制品(M13)出现 SN0001,状态在制,当前工位=上线位。
- 数据库断言:workpiece sn=SN0001 status='ONLINE' currentProcess=0。
对不上的地方
- 无。
场景 S4:工序报工
前置条件
- 工件 SN0001 已上线(S3),currentProcess=0。
- 工位 1 今日派工为工序 1(M9)并绑定工艺流程;本工序 BOM 物料 M-A 已在工位接料(S8)。
操作步骤
- 工位 1 操作员在「工作面板」,上料时系统记开始时间,装配后录入各步骤实测值、勾选需检测确认的步骤、绑定本工序装配物料(M-A 批次号),点「报工」。
系统反应
- 系统强校验:工件在制、本工位是下一道工序(工序号=已完成工序+1)、装配物料齐套、需检测确认的步骤已勾选。
- 校验通过后写报工实绩与步骤考核,按考核标准判定 OK/NG,推进工件工序状态。
数据变化
- workpiece_process 新增行:sn=SN0001、processCode=1、stationNo=1、status=DONE、result=OK(全部步骤合格)或 NG、operator、startedAt/endedAt/durationSec。
- step_data 新增各步骤实测行:value、is_ok 按考核标准判定。
- workpiece_bind:本工序绑定的 M-A 批次落库。
- workpiece(SN0001):currentProcess 0→1,status ONLINE→PROCESSING。
验证点
- 报工记录(M14)出现 SN0001 工序1 记录,结果 OK。
- 数量等式:workpiece.currentProcess = 已报工工序数;durationSec = endedAt − startedAt(≥0)。
- 数据库断言:workpiece_process(sn=SN0001,processCode=1) 存在且 status='DONE'。
对不上的地方
- 无。(跳站/漏站/工件非在制/物料未绑齐/需检测步骤未勾选时,报工被拒——属前置校验,非链路断裂。)
场景 S5:完工下线并成品回流入库
前置条件
- 工件 SN0001 已报完全部工序(S4),当前工序=末道工序;全部工序装配物料已绑齐。
操作步骤
- 下线位操作员在「下线面板」,扫工件 SN0001,录入结构件批次清单/电气件 SN 清单,点「完工」。
系统反应
- 系统兜底校验全部工序物料齐套,登记完工,生成成品关联追溯。
- 成品经内部接口自动回流 WMS,入库为成品;并按整份 BOM 反冲核销工单台账「已消耗」。
- 工单完成数量 +1,日排产完成数量 +1。
数据变化
- workpiece(SN0001):status PROCESSING→DONE,doneAt=当前时间。
- association_trace 新增/更新:finishedSn=SN0001、orderNo=WO20260920、batchItems=[结构件批次]、serialItems=[电气件SN]、operator。
- work_order(WO20260920):finishedNum +1;daily_plan:completedQty +1。
- WMS inventory:新增成品行 category=3、material_code=CP001、sn_code=SN0001、manage_mode=2、quantity=1、order_no=WO20260920、status=在库。
- WMS order_material_ledger:M-A、M-B 的 consumed_qty 按整份 BOM 单件消耗累加(核销上限=net_out−已消耗)。
验证点
- 在制品不再显示 SN0001;工件追溯(M15)可查到全流程。
- WMS 库存查询(W8)成品口径出现 CP001/SN0001。
- 数量等式:consumed_qty ≤ net_out_qty = out_qty − returned_qty;工单 finishedNum ≤ quantity。
对不上的地方
- 反冲只核销台账 consumed_qty,不扣库存、不生成出库单(WMS 实际行为);但 MES 侧完工反冲的实现注释写「生成 backflush 出库单」,与 WMS 实际行为对不上。业务链路本身打通(库存已在备料出库时扣过,不重复扣),仅注释误导。
场景 M15:工件追溯
前置条件
- 工件 SN0001 已完工(S5)。
操作步骤
- 质量员在 MES「工件追溯」,输入 SN0001,点「查询」。
系统反应
- 系统按 SN 汇总工序时间线、操作人、步骤数据、装配物料批次/SN、拧紧数据、成品关联追溯。
数据变化
- 只读,无写入。
验证点
- 页面显示 SN0001 全部工序记录、绑定的结构件批次与电气件 SN、拧紧记录。
- 数据库断言:association_trace finishedSn=SN0001 的 batchItems/serialItems 与页面一致。
对不上的地方
- 无。
场景 W3:入库登记
前置条件
- WMS 物料档案已存在结构件物料 M-A(按批次管理)、电气件物料 M-B(按序列号管理)(W17)。
- 已存在一张来料检验单号(用于入库时填写),结论为合格。
操作步骤
- 库管员登录 WMS,进「入库管理」,点「新增入库」,填:图号 M-A、入库类型「采购入库」、区域 Z1、检测单号 INSP-001、数量 100、批次号 BA001、归属类型「工单编号」、归属单号 WO20260920、操作人「张三」,点「保存」。
系统反应
- 系统先校验图号必填、区域必填、检测单号必填,再查物料档案确认 M-A 存在。
- 质量门禁:若该批次此前被判「不合格」,拒绝入库。本例批次无不合格记录,放行。
- 按检测单号带出质量状态(合格/不合格/部分/未关联)。
- 结构件按批次建库存行、写入库明细,回写入库单数量。
数据变化
- inbound_order 新增一行:inbound_no=IBxxxx、material_code=M-A、manage_mode=1、batch_no=BA001、zone_code=Z1、quantity=100、inspection_no=INSP-001、ownership_type=workorder、ownership_no=WO20260920、quality_status=合格、voided=false。
- inventories 新增一行:manage_mode=1、category=1、material_code=M-A、batch_no=BA001、quantity=100、locked_qty=0、quality_status=未检、status=在库、zone_code=Z1、inbound_no=IBxxxx。
- inbound_detail 新增一行:inbound_no=IBxxxx、batch_no=BA001、material_code=M-A、quantity=100。
验证点
- 入库管理列表出现该单,数量 100,质量状态「合格」,未作废。
- 库存查询(W8)批次 BA001 在库 100、可用 100。
- 数量等式:inventory.quantity = inbound_order.quantity = inbound_detail.quantity = 100;可用 = quantity − locked_qty = 100。
对不上的地方
- 无。(同批次再次入库为「追加」,quantity 累加;盘点锁定中的批次禁止追加入库——属前置校验。)
场景 W4:入库作废
前置条件
- 入库单 IBxxxx(M-A 批次 BA001 数量 100)已登记且未作废(W3);该批次未被出库锁定、未被消耗。
操作步骤
- 库管员在「入库管理」找到该单,点「作废」,填作废原因「录入重复」,点「确认作废」。
系统反应
- 系统校验作废原因必填、单据未重复作废。
- 结构件按本单明细批次号扣减对应批次库存,扣到 0 删除该库存行;电气件则按本单 SN 精确删除库存行。
- 安全门禁:目标库存已被锁定或数量不足,拒绝作废。
- 单据标记作废,保留记录供追溯。
数据变化
- inventories:批次 BA001 行 quantity 100→0,行被删除。
- inbound_order(IBxxxx):voided=false→true、void_reason=录入重复、voided_by=当前登录用户、voided_at=当前时间。
验证点
- 入库管理按「已作废」筛选可查到该单,库存查询不再有 BA001。
- 数据库断言:inbound_order inbound_no=IBxxxx 的 voided=true;inventories 中 batch_no=BA001 的行数=0。
- 数量等式:作废回滚量 rolledQty = 本单明细数量合计 = 100。
对不上的地方
- 无。(批次已被出库锁定 locked_qty>0、或在库量已小于本单数量时,作废被拒——属前置校验。)
场景 W6:通用出库
前置条件
- 结构件物料 M-A 批次 BA002 合格在库、quantity=80、locked_qty=0(W3 入库后并经检验合格)。
操作步骤
- 库管员在「出库管理」选「通用出库」,填:图号 M-A、批次号 BA002、数量 30、出库类别「发货」、操作人「李四」,点「确认出库」。
系统反应
- 系统校验图号必填、批次或 SN 至少一个、出库类别必选(类别为「其他」时原因必填)。
- 盘点冻结守卫:批次被进行中盘点锁定则拒绝。
- 质量门禁:发货/样品/其他类出库仅允许「合格」品;退货/报废/退料类豁免。
- 校验可用量充足后扣减批次库存、写出库明细、回写出库单实际数量。出库单创建即「已出库」。
数据变化
- outbound_order 新增一行:outbound_no=OBxxxx、outbound_type=general、status=已出库、material_code=M-A、batch_no=BA002、manage_mode=1、outbound_category=发货、quantity=30。
- outbound_detail 新增一行:outbound_no=OBxxxx、batch_no=BA002、material_code=M-A、quantity=30。
- inventories:批次 BA002 quantity 80→50。
验证点
- 出库管理列表出现该单,类型「通用出库」、状态「已出库」、数量 30。
- 数据库断言:inventories batch_no=BA002 的 quantity=50;outbound_detail 该单 quantity=30。
- 数量等式:出库前 quantity − 出库数量 = 出库后 quantity(80−30=50);可用 = quantity − locked_qty ≥ 0。
对不上的地方
- 无。(批次质量状态非「合格」且出库类别为发货/样品/其他时,出库被拒;可用量不足时被拒——属前置校验。)
场景 W7:退库确认
前置条件
- 工位已发起退库,WMS 存在一张待确认退库单 THxxxx:order_no=WO20260920、station_no=1、material_code=M-A、qty=20、status=PENDING(由工位终端 S11 经内部接口预建)。
- 工单台账 order_material_ledger(WO20260920, M-A) 存在:total_qty=20、out_qty=20、returned_qty=0、status=领料完结。
操作步骤
- 库管员在「退库确认」找到退库单 THxxxx,点「确认收货」,选退库类型「剩余物料」,选择退回库位区域 Z1,结构件填批次号 BA001,点「确认」。
系统反应
- 系统校验退库单未重复收货,按物料档案确定管理粒度。
- 退库类型决定质量状态:剩余物料置「未检」,问题物料置「不合格」。
- 结构件按数量加回(匹配同料同质量状态同货位的在库行追加,否则新建行);电气件按 SN 逐个扫码加回,扫码数须与退库数一致。
- 盘点冻结守卫:目标批次/SN 被盘点锁定则拒绝加回。
- 回写台账退回量,并把「领料完结」按净额拉回「进行中」。
数据变化
- return_order(THxxxx):status PENDING→RECEIVED、receive_by=当前登录用户、receive_at=当前时间。
- inventories:M-A 批次 BA001(未检、Z1)quantity 加回 20(若无匹配行则新建 quantity=20 的行,inbound_no=RET:THxxxx)。
- order_material_ledger(WO20260920, M-A):returned_qty 0→20;因 out_qty−returned_qty=0 < total_qty=20,status 领料完结→进行中。
验证点
- 退库确认列表该单状态「已收货」;库存查询 M-A 未检在库 +20。
- 数量等式:net_out = out_qty − returned_qty = 20 − 20 = 0,且 net_out ≤ total_qty;电气件退库扫码数 = 退库数 ro.qty。
对不上的地方
- 无。(电气件扫码 SN 数与退库数量不符、目标被盘点锁定时,收货被拒——属前置校验。)
场景 W9:来料检验
前置条件
- 结构件物料 M-A 批次 BA003 已入库、在库、quality_status=未检(W3)。
操作步骤
- 检验员在「来料检验」,填:检验类型「来料检验」、批次号 BA003、结论「不合格」、检验数 100、合格数 0、记录人「王五」,点「保存」。
系统反应
- 系统校验结论只能为合格/不合格,按批次号定位唯一库存行。
- 写检验记录,并按结论+处置同步库存质量状态:合格→合格;让步接收→合格;退货→待退;其余不合格→不合格。
数据变化
- inspection_record 新增一行:target_type=BATCH、target_id=BA003、material_code=M-A、inspection_type=来料检验、status=不合格、inspect_qty=100、pass_qty=0、disposal_type=NONE。
- inventories:批次 BA003 quality_status 未检→不合格(隔离,禁止出库上线)。
验证点
- 来料检验列表出现 BA003 不合格记录;库存查询 BA003 质量状态「不合格」。
- 数据库断言:inventories batch_no=BA003 的 quality_status='不合格'。
- 数量等式:pass_qty ≤ inspect_qty(0 ≤ 100)。
对不上的地方
- 无。
场景 W9-处置:不合格品处置(退货)
前置条件
- 检验记录 BA003 结论为「不合格」(W9),记录 id 记为 :recId。
操作步骤
- 检验员在「来料检验」对 BA003 记录点「处置」,选处置方式「退货」,填货运单号 SF123456,点「确认处置」。
系统反应
- 系统校验仅「不合格」记录可处置、退货处置必须填货运单号。
- 按处置方式翻转库存质量状态:退货→待退(保持数量、隔离);报废→不合格且生命周期报废;部分入库→按合格数拆行(仅批次管理);返修→不合格;让步接收→合格。
数据变化
- inspection_record(:recId):disposal_type NONE→RETURN、disposal_remark、return_tracking_no=SF123456。
- inventories:批次 BA003 quality_status 不合格→待退。
验证点
- 检验记录处置方式显示「退货」,货运单号 SF123456;库存查询 BA003 质量状态「待退」。
- 数据库断言:inventories batch_no=BA003 的 quality_status='待退';quantity 不变。
对不上的地方
- 无。
场景 W10:过程检验
前置条件
- 电气件物料 M-B 某 SN(记为 SNB0001)已入库、在库、quality_status=未检(W3)。
操作步骤
- 检验员在「过程检验」,填:检验类型「过程检验」、SN SNB0001、结论「合格」、检验数 1、合格数 1、记录人「王五」,点「保存」。
系统反应
- 系统按 SN 定位库存行(manage_mode=2),写检验记录并把库存质量状态同步为合格。
数据变化
- inspection_record 新增一行:target_type=SN、target_id=SNB0001、material_code=M-B、inspection_type=过程检验、status=合格、inspect_qty=1、pass_qty=1。
- inventories:SN SNB0001 quality_status 未检→合格。
验证点
- 过程检验列表出现 SNB0001 合格记录;库存查询 SNB0001 质量状态「合格」。
- 数据库断言:inventories sn_code=SNB0001 的 quality_status='合格'。
对不上的地方
- 无。
场景 W11:完工检验
前置条件
- 成品 SN(记为 SN0001,category=3)已由 MES 完工回流入 WMS、在库、quality_status=未检(S5)。
操作步骤
- 检验员在「完工检验」,填:检验类型「完工检验」、SN SN0001、结论「合格」、检验数 1、合格数 1、记录人「王五」,点「保存」。
系统反应
- 系统按 SN 定位成品库存行,写检验记录并把质量状态同步为合格。
数据变化
- inspection_record 新增一行:target_type=SN、target_id=SN0001、inspection_type=完工检验、status=合格、inspect_qty=1、pass_qty=1。
- inventories:成品 SN0001 quality_status 未检→合格。
验证点
- 完工检验列表出现 SN0001 合格记录;库存查询成品口径 SN0001 质量状态「合格」。
- 数据库断言:inventories sn_code=SN0001 且 category=3 的 quality_status='合格'。
对不上的地方
- 无。
场景 W12:库存盘点
前置条件
- 结构件物料 M-A 批次 BA004 在库、quantity=50、locked_qty=0、未被其他盘点单锁定。
操作步骤
- 库管员在「库存盘点」点「发起盘点」,限定物料 M-A(区域可空),点「确认发起」;进扫码区扫批次 BA004,填实盘数 48,点「录入」;点「完成盘点」,勾选「差异写回」、未盘项处理选「按账面保留」,点「确认完成」。
系统反应
- 发起:冻结当前库存快照并锁定范围内库存(打盘点单号),算出目标数量,状态「进行中」。
- 扫码录入:电气件实盘数恒为 1,结构件按填入数量;记差异=实盘−账面。
- 完成写回:仅按差异增减库存量、锁定量不动、可用量重算;写回后校验库存量≥锁定量,否则整体回滚;解锁库存,生成盘点调整单号,状态「已写回」。
数据变化
- stocktake_order 新增一行:stocktake_no=STxxxx、status 进行中→已写回、total_targets=覆盖批次/SN 数、diff_count=1、adjust_no=ADJxxxx、adjusted=true。
- stocktake_item 新增/更新:target_id=BA004、target_type=BATCH、book_qty=50、scanned_qty=48、diff_qty=-2、counted=true。
- inventories:发起时 BA004 stocktake_no=STxxxx(锁定),完成写回后 quantity 50→48、stocktake_no 清空(解锁)。
验证点
- 盘点单状态「已写回」,差异项数 1;库存查询 BA004 在库 48。
- 数量等式:库存量 = 可用量 + 锁定量(48 = 48 + 0);diff_qty = scanned_qty − book_qty = 48 − 50 = -2;写回后 quantity ≥ locked_qty。
- 数据库断言:inventories batch_no=BA004 的 quantity=48 且 stocktake_no=''。
对不上的地方
- 无。(盘亏后库存量将小于锁定量时写回被拒并整体回滚;仅「进行中」可取消——属前置校验/状态机约束。)
场景 W13:半成品入库/出库
前置条件
- 半成品 SN(记为 SEMI0001)尚未入 WMS 半成品库。
操作步骤
- 库管员在「半成品管理」点「半成品入库」,填:SN SEMI0001、图号 M-A、品类「半成品」、已完成工序「1,2」、区域 Z1、工单号 WO20260920,点「保存」;再对该 SN 点「半成品出库」,选出库原因「继续生产」,点「确认」。
系统反应
- 入库:SN 防重(同品类同 SN 只允许一条),建统一库存行(category=2 半成品),状态在库、质量未检。
- 出库:出库原因必选,校验在库且未被盘点锁定,置状态「出库」并记出库原因。
数据变化
- inventories 新增一行:manage_mode=2、category=2、sn_code=SEMI0001、material_code=M-A、quantity=1、quality_status=未检、status=在库、order_no=WO20260920、completed_process=1,2。
- 出库后:该行 status 在库→出库、outbound_reason=继续生产。
验证点
- 半成品管理列表出现 SEMI0001,入库后状态「在库」,出库后状态「出库」、原因「继续生产」。
- 数据库断言:inventories sn_code=SEMI0001 且 category=2 的行 status='出库'、quantity=1。
- 数量等式:半成品按 SN 单件管理,quantity 恒为 1。
对不上的地方
- 无。(SN 重复入库、非在库出库、盘点锁定中出库均被拒——属前置校验。)
场景 W8:库存查询
前置条件
- 已有若干在库库存(结构件批次、电气件 SN)。
操作步骤
- 库管员在「库存查询」,选口径「按物料汇总」,可填图号/名称/区域/质量状态筛选,点「查询」。
系统反应
- 系统只统计在库/锁定状态的库存(出库/使用/报废不计),结构件隐藏数量为 0 的行,按物料聚合出在库量/锁定量/可用量。
- 另两个口径:按区域汇总、明细(逐批次/逐 SN)。
数据变化
- 只读,无写入。
验证点
- 页面按物料展示在库量/锁定量/可用量。
- 数量等式:可用量 = 在库量 − 锁定量;仅合格品计入可用(未检/不合格/待退不计)。
对不上的地方
- 无。
场景 W14:工单备料台账
前置条件
- 工单 WO20260920 已经备料同步与出库(M4),order_material_ledger 存在 M-A、M-B 行。
操作步骤
- 库管员在「工单备料台账」,填工单号 WO20260920,点「查询」。
系统反应
- 系统按工单+物料展示四段量(需求总量/已出库/已退回/已消耗),并实时算出可用库存与备料四态。
- 四态优先级:未到货(可用=0 且已出库=0)> 预警(可用<未来5天需求)> 未齐套(0<可用<需求)> 齐套(可用≥需求)。
数据变化
- 只读,无写入。
验证点
- 页面展示 M-A:需求 20 / 已出库 20 / 已退回 0 / 已消耗(完工后)。
- 数量等式:net_out = out_qty − returned_qty ≤ total_qty;缺口 short_qty = max(0, total_qty − 可用量)。
对不上的地方
- 无。
场景 W15:AGV 配送
前置条件
- MES 已有待配送备料单行(含目标接驳台 target_dock);接驳台主数据已预置。
操作步骤
- 仓管员在「AGV配送」,拉取待发料行,勾选一批(同一目标接驳台),点「下发」。
系统反应
- 系统按目标接驳台聚合生成搬运任务,先落库为待下发,再调海康调度下发;下发成功置「已下发」并回写 MES 备料单为「配送中」;下发失败置「失败」。
- 轮询刷新:到位后置「已到位」,关联出库单置「已送达」,回写 MES 备料单「已完成」。
- 取消:已下发的先通知调度取消,本地置「已取消」,关联备料单退回待发料。
数据变化
- agv_task 新增一行:task_no=AGVxxxx、source_dock=DOCK21、target_dock=目标接驳台、materials_json=料行清单、status PENDING→DISPATCHED、operator、dispatched_at、hik_task_code。
- 到位后:status DISPATCHED→DONE、arrived_at=当前时间;关联 outbound_order status→已送达。
验证点
- AGV任务列表出现该任务,状态「已下发」(模拟模式下刷新后「已到位」)。
- 数据库断言:agv_task task_no=AGVxxxx 的 status 与海康回写一致;取消后 status='CANCELLED'。
对不上的地方
- 无。(模拟模式(未接真实海康 RCS)下任务会立即标记为已到位,属环境配置差异,非业务链路断裂。)
场景 W16:区域维护
前置条件
- 已存在区域 Z1(level1)与其下货架 S1(level2)。
操作步骤
- 库管员在「区域维护」选「批量生成」,选区域 Z1、货架 S1,层起 1、层止 2,位起 1、位止 3,先点「预览」再点「提交生成」。
系统反应
- 系统按层×位范围生成位置节点(并自动确保层节点存在),已存在的节点跳过不重复建;一次最多 2000 个。
数据变化
- zone 新增多行:level=4、zone_code=Z1、shelf_no=S1、layer_no=1或2、position_no=1~3、code=位号、name=“S1架X层Y位”、status=启用;同时确保 level=3 层节点存在。
验证点
- 区域维护位置号 Tab 出现 6 个新位置(2层×3位)。
- 数量等式:created + skipped = 层数 × 位数(6);重复提交时 created=0、skipped=6。
对不上的地方
- 无。
场景 W17:物料档案维护
前置条件
- 登录用户拥有物料新增权限。
操作步骤
- 库管员在「物料档案」点「新增」,填:图号 M-C、名称“结构件C”、规格型号“C-100”、单位“个”、品类“原材料”、类型“结构件”、安全库存 10,点「保存」。
系统反应
- 系统校验除「说明」外全部必填,图号唯一;其他类物料强制按数量管理(不做 SN)。
数据变化
- material 新增一行:code=M-C、name=结构件C、spec=C-100、unit=个、item_type=1、manage_mode=1、is_batch_managed=true、is_serial_managed=false、safety_stock=10。
验证点
- 物料档案列表出现 M-C,类型「结构件」、品类「原材料」。
- 数据库断言:material code='M-C' 的 manage_mode=1、is_batch_managed=true。
- 数量等式:manage_mode=1 时 is_batch_managed=true 且 is_serial_managed=false(互斥)。
对不上的地方
- 无。(图号重复新增被拒;除说明外缺任一必填项被拒——属前置校验。)
场景 W18:接驳台维护
前置条件
- 系统已预置接驳台主数据(产线/库房/其他)。
操作步骤
- 库管员在「接驳台维护」,按类型/编码/名称/状态筛选,点「查询」。
系统反应
- 接驳台为系统预置只读主数据,仅展示不提供新增/修改/删除;产线类型与工位 1:1 绑定。
数据变化
- 只读,无写入。
验证点
- 页面展示接驳台编码/名称/类型/绑定工位号/状态/是否有托盘。
- 数据库断言:dock 表中 dock_type='line' 的行 station_no 与工位一一对应。
对不上的地方
- 无。
场景 W19:账号管理
前置条件
- 登录用户拥有账号新建权限;角色已存在(W20)。
操作步骤
- 管理员在「账号管理」点「新建账号」,填:用户名 wms01、密码 123456、姓名“库管一”、选角色(roleId)、部门“库房”,点「保存」。
系统反应
- 系统校验用户名/密码必填、密码至少 6 位、用户名唯一;密码加密存储;角色优先按角色编号关联并回填角色编码。
数据变化
- user 新增一行:username=wms01、password=bcrypt 密文、real_name=库管一、role=角色编码、role_id=角色ID、dept=库房。
验证点
- 账号列表出现 wms01,可用该账号登录。
- 数据库断言:user username='wms01' 存在且 password 为密文(非明文 123456)。
对不上的地方
- 无。(用户名重复、密码不足 6 位均被拒——属前置校验。)
场景 W20:角色管理
前置条件
- 登录用户拥有角色新建权限。
操作步骤
- 管理员在「角色管理」点「新建角色」,填:角色名称“库管”、编码 warehouse、勾选权限(如 inbound:create、stocktake:start),点「保存」。
系统反应
- 系统校验名称与编码必填、编码唯一;权限以编码数组存储。
数据变化
- role 新增一行:name=库管、code=warehouse、remark、permission_codes=["inbound:create","stocktake:start"]。
验证点
- 角色列表出现 warehouse,权限数=勾选数。
- 数据库断言:role code='warehouse' 的 permission_codes 含已勾选编码。
对不上的地方
- 无。(角色编码重复被拒;内置管理员角色权限不允许修改、内置三角色不允许删除——属前置校验。)
场景 W21:操作日志
前置条件
- 系统已产生若干业务操作(入库/出库/盘点等均写日志)。
操作步骤
- 管理员在「操作日志」,按时间/操作人/业务类型筛选,点「查询」。
系统反应
- 系统分页返回操作留痕(谁、何时、对哪个对象、做了什么)。
数据变化
- 只读,无写入。
验证点
- 页面展示日志条目,含操作人/时间/业务对象/描述。
- 数据库断言:event_log 中每条业务写入均有对应日志行。
对不上的地方
- 无。
场景 W22:附件中心
前置条件
- 存在一个业务对象(如某入库单),登录用户拥有附件查看权限。
操作步骤
- 库管员在业务页上传一份附件(选文件),或在「附件中心」集中查看/归档/清理各业务附件。
系统反应
- 上传校验业务类型/业务对象必填、文件类型白名单、大小上限;流式落盘并算 MD5,同业务对象同 MD5 秒传不重复落盘。
数据变化
- attachment 新增一行:biz_type、biz_id、file_name、file_path、file_type、file_ext、file_md5、file_size、uploaded_by、deleted=false。
验证点
- 附件中心可查到该附件并可预览/下载。
- 数据库断言:attachment biz_id=业务对象 的行 deleted=false;重复上传同文件不新增行(file_md5 相同)。
对不上的地方
- 无。
场景 W23:修改密码
前置条件
- 当前用户已登录。
操作步骤
- 用户在「修改密码」,填旧密码、新密码(至少 6 位)、确认新密码,点「保存」。
系统反应
- 系统校验旧密码正确、新密码至少 6 位,重新加密存储。
数据变化
- user(当前用户):password 更新为新密码的 bcrypt 密文。
验证点
- 用新密码可登录,旧密码失效。
- 数据库断言:user 该行 password 已变更且为密文。
对不上的地方
- 无。(旧密码不正确、新密码不足 6 位均被拒——属前置校验。)
场景 W24:库存大屏
前置条件
- 库存/入出库已有数据。
操作步骤
- 展示屏免登录打开库存大屏页面,自动拉取概览数据全屏展示。
系统反应
- 系统汇总结构件批次总量/锁定量、电气件在库/出库 SN 数、半成品/成品在库数、装箱数、当日入/出库单数、按区域+质量状态明细。
数据变化
- 只读,无写入。
验证点
- 大屏展示批次数/总量/锁定量/SN在库数/半成品在库数/成品在库数/今日入库单数/今日出库单数。
- 数据库断言:大屏各计数与 inventories/inbound_order/outbound_order 对应条件计数一致(如 totalQty = 结构件 quantity 求和)。
对不上的地方
- 无。
场景 W1:登录
前置条件
- 账号已在库房系统创建并启用、密码已加密存储。
操作步骤
- 库管员/管理员在登录页输入用户名、密码,点登录。
系统反应
- 校验用户名密码必填→查用户→账号停用被拒→比对密码→更新最后登录时间→签发访问令牌→记登录操作日志。返回令牌、过期时间、用户信息(含权限码列表)、弱密码标记(纯数字密码仅提示不拦截)。
数据变化
- user.last_login_at 更新为当前时间戳;event_log 增一条登录日志(auth.login、含 IP)。
验证点
- 用户名或密码错误提示「用户名或密码错误」;账号停用提示「账号已停用」;用户名/密码为空提示「用户名和密码必填」;纯数字密码登录成功但弱密码标记为真;返回权限码为该用户角色权限。
对不上的地方
- 无。
场景 W2:工作台
前置条件
- 用户已登录。
操作步骤
- 用户登录后进入工作台首页。
系统反应
- 首页自动拉取库存概览与缺货预警:概览汇总在库总量、锁定量、SN在库数、半成品在库数、装箱出库数、批次数、今日入库单数、今日出库单数;缺货预警列出可用量低于安全库存的物料(图号、名称、规格、安全库存、可用量、缺口);并提供入库/备料出库/库存查询/质量检验/盘点/半成品/台账/区域/物料档案/大屏等快捷入口。概览接口免登录。
数据变化
- 只读,无写入。
验证点
- 概览各计数与库存/入出库表对应条件计数一致;缺货预警仅列可用量小于安全库存的物料;缺货接口异常时工作台不报错、预警区留空。
对不上的地方
- 无。
场景 W5:备料出库
前置条件
- 工单 BOM 已同步到库房形成工单物料台账;目标批次库存充足且质量状态为合格;未在盘点冻结期。
操作步骤
- 库管在备料出库页选工单号、物料、批次号(或序列号列表)、数量、目标工位/接驳台,可填装箱与库位信息,点出库。
系统反应
- 强校验工单物料台账存在(否则提示先同步 BOM 或改用通用出库)、台账非「领料完结」、批次或序列号至少提供一个、盘点期间冻结的批次禁止出库;在一个事务内创建出库单(类型备料出库、状态已出库、按需叫 AGV)+按批次原子扣减库存(库存量不小于出库量才扣、回收锁定量)+写出库明细+更新工单台账,任一环节失败整体回滚;批次质量状态非合格被拦截(仅合格品可领料上线);累计出库超 BOM 需求时需填超领原因否则拦截。
数据变化
- outbound_order 增出库单(出库单号 OB+uuid、类型备料出库、状态已出库、是否需 AGV、工单号、物料、批次号、库位、目标接驳台、操作人);inventories 对应批次 quantity 减少、locked_qty 回收;outbound_detail 增明细;order_material_ledger 累计出库量增加(达需求量置「领料完结」)。
验证点
- 台账不存在被拒;台账「领料完结」后再出库被拒;批次可用量不足提示「批次库存不足」;批次非合格提示仅合格品可领料上线;缺批次号与序列号提示至少提供一个;盘点冻结批次禁止出库;扣减为原子条件更新不产生负库存。
对不上的地方
- 无。
场景 M1-状态:工单状态流转
前置条件
- 工单 WO20260920 已创建(状态「已创建」);产线上无其他「执行中/已暂停」的在产工单。
操作步骤
- 计划员在 MES「工单管理」页找到 WO20260920,点「下发」(可填变更原因);之后在列表对该工单依次点「开工」「暂停」「恢复」「完工」或「取消」。
系统反应
- 系统校验流转是否合法:已创建→已下发/已取消;已下发→执行中/已取消;执行中→已暂停/已完成;已暂停→执行中/已取消;已完成、已取消为终态不再流转。下发或开工前校验产线互斥:已有其他在产工单则拒绝。完工时写入完成时间。每次变更记操作日志(原状态/新状态/原因),并联动排产:完工或取消时把该工单未完成排产批量取消,开工时排产置执行中。
数据变化
- work_order.status 变为目标状态(已下发/执行中/已暂停/已完成/已取消);置「已完成」时 completed_at=当前时间;event_log 增一条 event_type=work.order.status(payload 含 from/to/reason);daily_plan 随工单状态联动。
验证点
- 非法流转(如 已创建→已完成)被拒并提示「不允许从「已创建」变更为「已完成」」;产线已有在产工单时下发第二张被拒;相同状态重复提交为幂等成功不报错。
对不上的地方
- 无。
场景 M3:产品物料清单(BOM 维护)
前置条件
- 产品型号 CP001 已存在;准备 BOM 条目(M-A 结构件 单台用量2 工序1;M-B 电气件 单台用量4 工序2)。
操作步骤
- 计划员在 MES「产品物料清单」页选产品编码 CP001、填 BOM 名称(留空即「默认」),逐行录入物料编码/名称/规格/单位/单台用量/损耗率/装配工序/相关标准,点「保存」;也可点「导入」上传 Excel(列序:产品编号、清单名称、图号、物料名称、规格、单位、单台用量、损耗率、装配工序、相关标准);对多余条目点「移除」。
系统反应
- 保存为覆盖式写入,唯一键=产品编码+BOM名称+物料编码,存在则更新、不存在则新建;管理方式留空默认按序列号。导入按「产品编号+清单名称」分组逐组保存,跳过表头行,缺产品编号或图号的行忽略,全为空则报「未解析到有效物料清单行」。移除条目时若该型号已被任一工单选用则拒绝。
数据变化
- bom_item 新增/更新行:product_code=CP001、bom_name=默认、material_code、unit_qty、loss_rate、process_code、related_standard、manage_mode(默认2);移除则删该 bom_item 行;event_log 记 bom.save / bom.item.delete。
验证点
- 同一(型号+BOM名+物料)重复保存不产生重复行;导入返回 count=有效行数;已被工单选用的型号移除条目被拒。
对不上的地方
- 无。
场景 M5:数量不符处理
前置条件
- 工位已上报一条待处理数量不符记录(应发>实收为少发/差异为正;应发<实收为多发/差异为负)。
操作步骤
- 库管/计划员在 MES「数量不符处理」页按状态/工位/物料/工单查询,选中一条,选处理方式(补发/退库/调整)并填说明,点「处理」。
系统反应
- 仅「待处理/处理中」可处理,已处理拒绝重复。补发仅少发可用,生成来源=补料、需 AGV、关联差异单号的备料单,后续走自动出库→AGV→接料;退库仅多发可用,调 WMS 建退库单,WMS 确认收货后库存加回;调整任意方向、必须填说明、直接关闭不补不退。处理后把对应「数量不符」未读预警置已读并记日志。
数据变化
- material_qty_report.status 变为 已补发/已退库/已关闭,写 handle_type/handle_doc_no/handle_note/handled_by/handled_at;补发生成 material_request(source=REFILL,req_qty=差异量,status=PENDING);退库在 WMS 生成退库单;alert(type=qty_diff) 对应行 status=已读;event_log 记 qty_report.handle。
验证点
- 少发选退库被拒「本单为「少发」,不能走退库」;多发选补发被拒;调整不填说明被拒;已处理记录再处理被拒;处理后统计概览待处理数减一。
对不上的地方
- 无。
场景 M6:工位组合下发(PLC)
前置条件
- 今日已配置工位派工(工位↔工序);工单 WO20260920 未完工未取消;工件 SN0001 已进线登记且未完工;无未完成的上一条下发或上一条已收到完成信号。
操作步骤
- 计划员在 MES「工位组合下发」页选工单号、填/扫工件 SN、选工位号,点「下发」。
系统反应
- 路线由今日工位派工派生(未配置则拒「今日未配置工位派工,无法下发 PLC 工序码」)。校验 SN 属于该工单且已进线、工件未完工、工单未完工/取消。若存在上一条状态=已下发的记录,先查完成信号,未收到则拒「上一条工序尚未收到PLC完成信号,不能下发下一条」,收到则把上一条置完成再下发。下发写日志并模拟下行,数秒后由系统模拟回传完成信号把本条置完成。
数据变化
- plc_send_log 新增行:order_no/sn/station_no/process_combination/process_codes/status=已下发(SENT)/send_time/operator;握手完成后 status=已完成(DONE)/done_time/ack=true;event_log 记 plc.send。
验证点
- SN 不属于工单被拒;工件已完工被拒;上一条未收到完成信号时下发下一条被拒;下发日志可按工单号/状态查询。
对不上的地方
- 完成信号为系统内模拟(下发后延时数秒自动置完成),非真实 PLC 握手;对接真实 PLC 时该握手以现场信号为准。
场景 M7:拧紧查询
前置条件
- 已有拧紧记录(含 SN/工单号/工位/螺丝编号/扭矩/角度/结果);对应工位绑定的工艺流程含「扭矩」考核标准与拧紧步骤。
操作步骤
- 查询员在 MES「拧紧查询」页按 SN/工单号/工位/日期区间查明细;切「按组汇总」看应拧vs实拧;对某条记录点「审核签字」填备注;点「修改留痕」看审计历史。
系统反应
- 明细四条件取交集(大小写不敏感),封顶500条。组汇总按 工单号+SN+工位 分组,应拧数=该工位工艺流程拧紧步骤数,满足判定=应拧>0且实拧>=应拧且无不合格;组内任一条未审核则整组未审核。审核签字仅未审核记录可审,重复审核被拒,写审核人/时间并记留痕。上报时扭矩超出「扭矩」范围考核标准判不合格,写步骤数据并触发拧紧不合格预警。
数据变化
- torque_record(strain/torque/angle/result/audit_by/audit_at);step_data(按范围判定 is_ok);torque_audit_log 新增 action=审核 行;不合格时 alert(type=torque_ng)。
验证点
- 组汇总 satisfied 与 应拧/实拧/不合格数一致;已审核记录再审被拒「该记录已审核,不可重复审核」;修改留痕按记录倒序可查。
对不上的地方
- 无。
场景 M10:绩效报表
前置条件
- 已有报工记录(含操作人/工位/工序/结果/作业时长/日期)。
操作步骤
- 管理员在 MES「绩效报表」页按操作人/工位/工单/日期区间查询,切「按人/按工位/明细」三口径查看;点「导出」下载 Excel(可指定起止日期)。
系统反应
- 以报工记录为源,按 操作人+工位+日期+工序 聚合完成数、合格数、不合格数、累计与平均作业时长、效率(件/小时)。三口径分别按人、按工位、明细汇总。导出把三口径写成 Excel 多表;日期区间非法时报错。
数据变化
- 只读统计,无写入(导出仅生成文件)。
验证点
- 完成数=报工记录条数;合格+不合格=完成数;按人/按工位汇总数与明细一致。
对不上的地方
- 无。
场景 M11:巡检终端
前置条件
- 登录用户持对应巡检按钮权限(签到/点检/过程/完工/异常各一权限);过程巡检需工单与工序步骤。
操作步骤
- 巡检员在 MES「巡检终端」页选记录类型(签到/点检/过程巡检/完工确认/异常),填工位/班次/工单/SN/结果/点检项/实测值/文字说明,可「上传照片」,点「提交」;对某工单点「一键生成工序间检验记录」。
系统反应
- 记录类型必填且须合法(签到/点检/过程/完工/异常,及质量检验三类)。按类型校验按钮权限(质量检验三类持质检录入权限放行)。结果留空默认合格。异常类型同时推看板报警。工序间检验记录汇总该工单全部过程巡检(按步骤维度),工单不存在则拒。
数据变化
- inspection_record 新增行:category/station_no/shift/order_no/sn/result/items/photo/remark/operator/process_code/step_id/step_name/measured_value;event_log 记 inspection.<类型>;异常时看板报警缓存失效。
验证点
- 非法类型被拒;无权限类型被拒(无操作权限);工序间检验记录条数=该工单过程巡检条数。
对不上的地方
- 无。
场景 M12:质量检验
前置条件
- 已有检验记录(过程检/完工检/来料检);BOM 中有所需物料。
操作步骤
- 质检员在 MES「质量检验」页按物料编码/名称/图号关键字检索 BOM 料并带出;登记检验结论;对不合格记录选处置类型(退货/返修/退换)填处置说明点「处置」;点「生成不合格单」下载预填 Excel。
系统反应
- 按关键字模糊检索 BOM 料(编码或名称,封顶200)。处置类型仅限 退货/返修/退换,非法被拒,写处置说明并记日志。不合格单按记录取数生成 Excel(报检单号/图号/物料名称/生产厂家/检验单号/结论/处置方式/处置说明/记录时间),缺记录标识被拒。
数据变化
- inspection_record.disposal_type、disposal_remark 更新;event_log 记 inspection.disposal;不合格单为导出文件不写库。
验证点
- 处置类型非 退货/返修/退换 被拒「处置类型仅支持 退货/返修/退换」;不合格单缺 id 被拒。
对不上的地方
- 无。
场景 M16:生产流程卡
前置条件
- 工件 SN0001 已上线并有报工/拧紧/关联物料数据。
操作步骤
- 计划员在 MES「生产流程卡」页填/扫 SN,点「打印前预检」看缺失项,点「打印」出卡;或打开「型号全景流程卡」按型号查看聚合视图。
系统反应
- 按 SN 联查工件→工单(产品名称/数量)、工序时间线(操作人/完成时间)、步骤采集参数、拧紧数据(该路径含拧紧步骤才要求)、关联物料批次/SN,计算缺失项(工单号/产品名称/数量/工序报工/拧紧数据等)。带预检参数时只返回缺失项不渲染卡片。SN 无追溯数据被拒。
数据变化
- 只读,无写入(渲染打印)。
验证点
- 预检返回的缺失项与实际数据缺口一致;无任何报工时缺失项含「工序报工记录」;需拧紧但无拧紧数据时缺失项含「拧紧数据」。
对不上的地方
- 无。
场景 M18:预警中心
前置条件
- 管理员登录 MES。
操作步骤
- 管理员在「预警中心」页新增预警规则(名称/类型/阈值/接收人/启用),可改可删;在预警列表按状态/类型/工单/时间查询,对某条点「标记已读」;现场或前端点「应急呼叫」填工位/工单/说明发起。
系统反应
- 规则名称与类型必填。预警列表支持分页与筛选。标记已读把该预警置已读。业务事件(如拧紧不合格、数量不符)按启用规则评估命中阈值则生成预警(库存偏低为小于等于阈值命中,其余为大于等于)。应急呼叫落一条类型=应急呼叫的未读预警,缺工位号被拒。
数据变化
- alert_rule 增/改/删;alert 新增(type/title/content/ref_type/ref_id/order_no/receiver/status=未读)或 status=已读;应急呼叫 alert.type=emergency_call、ref_type=station、ref_id=工位号;event_log 记 station.emergency。
验证点
- 规则名称/类型为空被拒;标记已读后未读数减一;应急呼叫缺工位号被拒「缺少工位号」。
对不上的地方
- 无。
场景 M8:工艺流程
前置条件
- 工位主数据已存在(工位1、工位2);准备工序步骤与考核标准。
操作步骤
- 工艺员在 MES「工艺流程」页新建流程,填流程名称、绑定工位(可多选)、上传工艺 PDF、设状态(启用/停用)、逐条录入工序步骤(序号/名称/采集方式/是否拧紧/是否需检测确认/备注/附件)与每步考核标准(名称/单位/判定逻辑/目标值/上下限),点「保存」;对已有流程可「启用/停用」「删除」。
系统反应
- 流程名称必填,状态仅启用/停用。绑定工位必须已存在(不自动创建),一个工位同时只绑一个流程,保存时所选工位整体改绑到本流程、原绑该流程但不在列表内的工位解绑。工序步骤与考核标准覆盖式重建(先删后建),考核逻辑为空或无的标准跳过。删除流程先解绑所有工位再删步骤与流程。
数据变化
- process_flow 增/改(name/pdf_file/status/remark);station.flow_id 指向该流程或置0;process_step 覆盖重建(flow_id/seq/name/collect_type/is_torque/need_check/remark/attachment);step_criterion 覆盖重建(step_id/name/unit/logic/target/min/max);event_log 记 process.flow.save / process.flow.delete。
验证点
- 流程名为空被拒;绑定不存在工位报「工位 X 不存在」;非法状态被拒;保存后该流程步骤数=提交步骤数;删除后工位 flow_id 归0。
对不上的地方
- 无。
场景 M13:在制品
前置条件
- 已有工件进线未完工(状态 已进线/在工位/返修)。
操作步骤
- 管理员在 MES「在制品」页查看线上在制工件,可按工位号与工单号过滤。
系统反应
- 列出停在各工位尚未完工的工件,按进线时间倒序。当前工位推算:已进线在上线位(0),在工位/返修按已完成工序推算,已到末端且在工位状态显示下线位(13)。展示产品编码/名称、当前工位名、状态、进线时间、停留时长、已完成工序数/总工序数(总工序由今日工位派工派生)。工位号>0只返回停在该工位的;工单号非空按模糊过滤。
数据变化
- 只读,无写入。
验证点
- 仅返回状态为 已进线/在工位/返修 的工件;完工工件不出现;已完成工序数=该SN状态为已完成的报工行数。
对不上的地方
- 无。
场景 M14:手动报工
前置条件
- 工单 WO20260920 存在;工件 SN0001 需手动上线/补报;对应工位绑定启用工艺流程(含考核标准,可能有需检测确认步骤与拧紧步骤)。
操作步骤
- 操作员在 MES「手动报工」页选工位带出工序步骤,扫/填工件 SN 点「上线」建档;按工序填采集数据(含检测确认勾选)、装机绑定物料,点「报工」;末道点「完工」填结构件批次/电气件SN。
系统反应
- 上线以 SN 建工件档案状态=已进线。报工强校验:仅「已进线/在工位」可报工,且必须按工序顺序(本工位=已完成工序+1),禁止跳站/漏站/重复;先应用装机绑定并校验本工序物料齐套(电气件按单台用量逐颗SN、结构件至少一个批次),缺料拒绝并返回缺料清单;标记需检测确认的步骤必须逐条勾选,否则拒绝;据考核标准判每步合格与否,任一步不合格则本工序结果=不合格。报工为整体事务,任一步失败全回滚。完工兜底校验全部工序物料齐套后置工件=已完工,生成/更新成品关联追溯,联动工单进度与日排产完成数,成品回流 WMS 库存并按整份 BOM 反冲扣料(WMS 不可达仅记日志不阻塞)。
数据变化
- workpiece 新增/更新(sn/order_no/status 已进线ONLINE→在工位PROCESSING→已完工DONE/current_process/online_at/done_at);workpiece_process 新增报工行(process_code/station_no/result/started_at/ended_at/duration_sec);step_data 逐步判定 is_ok;association_trace 成品关联(finished_sn/batch_items/serial_items);work_order 进度、daily_plan 完成数联动;event_log 记 workpiece.online/workpiece.process.report/workpiece.done。
验证点
- 跳站报工被拒「工序顺序不符」;非在制状态报工被拒;缺料报工被拒并返回缺料清单;需检测确认未勾选被拒;完工后 workpiece.status=DONE 且 association_trace 有该 SN 行。
对不上的地方
- 完工反冲的 WMS 侧只累加工单备料台账已耗量、不实际扣减库存也不生成出库单,与此处「按整份 BOM 反冲扣料、生成反冲出库单」的描述不一致(业务链路可跑通,仅反冲扣账口径与预期不符;同 S5 已标注问题的 MES 手动报工入口复现)。
场景 M17:物料档案(MES)
前置条件
- WMS 可达(编码主数据单一来源=WMS 成品档案)。
操作步骤
- 计划员在 MES「物料档案」页按编码/名称/类别/启用状态分页查询产品型号;新增/修改填编码/名称/规格/单位/类别/启用;删除某型号;可导出 Excel。
系统反应
- 本页实时代理 WMS(成品档案),WMS 不可达时降级读本地只读缓存(前端以 wmsOnline 标识提示),写操作必须 WMS 可用。新增/修改名称、规格、单位必填,写请求转发 WMS。删除保护:被工单或 BOM 引用过的型号禁删(提示改用停用/下架),否则转发 WMS 删除。导出转发 WMS 生成 xlsx。
数据变化
- 主数据落在 WMS(materials item_type=3);MES 本地 product_types 仅作降级只读缓存;删除/新增/修改经 WMS 内部接口。
验证点
- 名称/规格/单位为空被拒;被工单引用的型号删除被拒「该物料档案已被 N 张工单引用」;WMS 不可达时列表降级(wmsOnline=false)且写操作被拒。
对不上的地方
- 无。
场景 M19:附件中心(MES)
前置条件
- 已有各业务上传的附件。
操作步骤
- 管理员在 MES「附件中心」按文件类型/业务类型/业务号/文件名/日期/是否含已删除分页查询;查看磁盘状态;对某附件下载/预览、逻辑删除;点「归档」(可先试运行)把超期附件移到归档目录。
系统反应
- 上传时业务类型与业务号必填,流式落盘算 MD5,同业务对象同 MD5 秒传(删刚落盘文件返回既有记录)。中心返回分页列表+磁盘状态。下载按记录标识提供(支持断点续传、dl=1 强制下载、中文名 RFC5987)。删除为逻辑删除(库内标记,物理文件交归档任务)。归档把早于本地保留年数的附件移到归档目录并标记已归档(归档目录为空只标记不移动,试运行只返回将归档数量)。
数据变化
- attachment 增/改(biz_type/biz_id/file_type/file_name/file_path/file_md5/file_size/uploader/archived/deleted/deleted_by);event_log 记 attachment.upload/attachment.delete/attachment.archive。
验证点
- 业务类型或业务号为空上传被拒;同业务同 MD5 重复上传不新增行;逻辑删除后 deleted=true 且默认列表不含;归档试运行返回待归档数不移动文件。
对不上的地方
- 无。
场景 M20:操作日志(MES)
前置条件
- 系统已有操作留痕;当前用户已登录。
操作步骤
- 用户在 MES「操作日志」页按工单号/事件类型/操作人/时间范围分页查询操作留痕。
系统反应
- 普通用户仅能查看自己的操作日志(用户名+中文名任一命中,忽略显式操作人筛选);管理员(SUPER_ADMIN)可查全部并按工单号/事件类型/操作人/时间范围筛选。返回事件类型/工单号/操作人(映射中文名,缺档显原用户名)/实体类型/实体标识/描述/时间。
数据变化
- 只读,无写入。
验证点
- 普通用户查询只返回自己名下日志;isAdmin 标识与当前用户角色一致;每条含非空创建时间与事件类型。
对不上的地方
- 无。
场景 M21:账号管理(MES)
前置条件
- 管理员登录;已有角色。
操作步骤
- 管理员在 MES「账号管理」页查账号列表;新增账号填用户名/密码/姓名/角色/状态/是否可登录工位终端(及工位终端密码、允许操作工位);修改;删除。
系统反应
- 密码强度校验(至少6位且不能纯数字),bcrypt 存储;状态默认启用。可登录工位终端时设工位终端密码(留空默认复用登录密码,同样强度校验)。内置管理员账号(admin 或 SUPER_ADMIN 角色)不允许修改/删除。新增后按所选工位重建用户-工位关联。
数据变化
- user 增/改(username/password/name/role_id/status/can_login_workstation/workstation_password);user_station 关联重建。
验证点
- 纯数字或不足6位密码被拒;删除 admin 被拒「系统内置管理员账号不允许删除」;修改 admin 被拒。
对不上的地方
- 无。
场景 M22:角色管理(MES)
前置条件
- 管理员登录;已有权限项。
操作步骤
- 管理员在 MES「角色管理」页查角色;新增角色填名称/编码/备注/权限勾选;修改;删除。
系统反应
- 新增写名称/编码/备注/权限码集合。修改时内置管理员角色(SUPER_ADMIN)不允许改权限,角色编码创建后不可修改。删除保护:内置角色(SUPER_ADMIN/OPERATOR/INSPECTOR)不可删;仍有关联用户的角色不可删。
数据变化
- role 增/改/删(name/code/remark/permission_codes)。
验证点
- 改内置管理员角色权限被拒;改角色编码被拒「角色编码创建后不可修改」;删内置角色被拒;删有用户的角色被拒「该角色下仍有用户」。
对不上的地方
- MES 新增角色未显式校验编码重复(直接落库),与 WMS 侧新增角色「编码已存在」的显式拦截不一致;若数据库无唯一约束则可能产生重复编码。
场景 M23:修改密码(MES)
前置条件
- 当前用户已登录。
操作步骤
- 用户在 MES「修改密码」页填旧密码、新密码,点提交。
系统反应
- 未登录被拒;旧密码与新密码必填;新密码至少6位且不能纯数字;校验旧密码正确(bcrypt 比对),错误被拒;通过后更新为新密文。
数据变化
- user.password 更新为新 bcrypt 密文。
验证点
- 旧密码错误被拒「旧密码不正确」;新密码不足6位或纯数字被拒;改后新密码可登录、旧密码失效。
对不上的地方
- 无。
场景 S1:工位登录/登出
前置条件
- 账号已在产线服务开通工位终端登录权限、设置了工位终端密码、并分配了可操作工位;工位终端已配置固定工位号。
操作步骤
- 操作员在工位终端主界面输入用户名、工位终端密码(工位号由终端配置固定带入),点「登录」;下班点「登出」。
系统反应
- 终端把登录请求转给产线服务校验:账号处于启用、已开通工位终端登录权限、工位终端密码正确、且该账号拥有此工位的操作权限(未单独配工位权限时兜底授权全部物理工位,不含虚拟上下线位),通过后签发登录令牌并返回可操作工位清单。
- 登出转产线服务补记一条「工位终端退出」操作日志;产线服务不可达时静默忽略,不阻塞本地退出。
数据变化
- 登录成功返回令牌、用户信息、可操作工位号列表;登出在产线服务 event_log 增一条工位退出日志。
验证点
- 密码错误、账号禁用、未开通工位登录权限、无此工位权限分别被拒并给中文提示;工位号由终端配置固定,终端不可修改。
对不上的地方
- 无。
场景 S2:当前任务
前置条件
- 工位已在产线服务配置并绑定生效(ACTIVE)的工艺流程;存在处于已创建/已下发/执行中的工单。
操作步骤
- 操作员登录后,终端主界面自动加载「当前任务」。
系统反应
- 终端按固定工位号向产线服务取本工位任务:返回工位名、绑定的工艺流程及其步骤清单、当前可生产的工单号列表(取状态为已创建/已下发/执行中的工单,最多10条)。本工位要做哪些步骤完全由绑定的流程图决定,工单只决定走哪些工位。
数据变化
- 只读,无写入。
验证点
- 工位不存在提示「工位不存在或未配置」;绑定流程非生效状态时步骤清单为空。
对不上的地方
- 无。
场景 S6:面板绑定
前置条件
- 工件已上线并关联工单;工单锁定的产品清单在本工序配置了装配物料。
操作步骤
- 操作员在工作面板扫工件序列号拉取「应装/已装」清单,逐件扫物料绑定(结构件扫批次号、电气件扫序列号);误绑可在报工前撤销。
系统反应
- 拉面板返回本工序应装物料(含需绑数量、已绑列表、齐套状态)。
- 绑定时校验:物料属于该产品清单且装配工序为本工序;电气件序列号全局防重(已被别的工件绑定则拒);同工件同料同值重复扫码静默去重。撤销仅允许该工序未报工前,已报工不可撤销。
数据变化
- workpiece_bind 增绑定记录(工件序列号、工单号、工序号、工位号、物料编码/名称/规格/单位、管理方式、绑定值、绑定类型:结构件为批次、电气件为序列号、操作人);撤销删除对应记录;event_log 记装机绑定/撤销绑定。
验证点
- 料不在清单被拒;料配在别的工序被拒;电气件序列号重复被拒并提示已绑到哪个工件;已完工/报废工件不能绑;已报工工序撤销被拒。
对不上的地方
- 无。
场景 S7:移料/叫料
前置条件
- 产线点位台账已初始化(上料位、工位、缓存位、末端下料检测位、接驳台);移料时起点位当前占用为该工件、目标点位空闲;叫料时所选接驳台属于本工位。
操作步骤
- 操作员在移动面板:① 点位上料/下料记账(选点位、扫工件序列号、选上料或下料);② 下发移料指令(选起点位、目标点位、工件序列号);③ 工件到位确认(指令号);④ 叫料(选本工位接驳台、填叫料明细的物料与数量)。
系统反应
- 点位记账:上料把工件记到点位(点位被别的工件占用则拒、同工件幂等放行),下料清空点位(传工件时校验点位当前就是该工件)。
- 移料:校验起点位存在且当前是该工件、目标点位存在且空闲、起终点不同,生成移料指令(状态已下发);到位时回写台账(起点清空、目标写入该工件)并闭环指令。
- 叫料:校验工位号有效、所选接驳台属于本工位、有明细,逐条生成备料需求(来源为补发、需 AGV 配送、目标为所选接驳台、状态待处理、写入本工位号)。
数据变化
- line_point 更新在位工件与占用标记;plc_move_cmd 增移料指令(指令号、工件、工单、起点位、目标点位、状态:已下发→到位→闭环、操作人);material_request 增补料单(备料单号、工单号、计划日期为今日、物料、需求数量、状态待处理、工位号、来源补发、需 AGV、目标接驳台、操作人);event_log 记移料下发/到位、点位上料/下料、叫料。
验证点
- 起点位非该工件、目标点位被占用、起终点相同均被拒;接驳台不属于本工位被拒「该工位无此接驳台」;叫料明细物料编码或数量缺失的行被忽略,全部无效时被拒。
对不上的地方
- 无。
场景 S9:巡检
前置条件
- 操作员已登录工位终端;巡检记录类型合法(签到/点检/过程巡检/完工确认/异常等)。
操作步骤
- 操作员在巡检面板选记录类型、填工位号/工单号/工件序列号/班次/结果/检验项/实测值/备注,可拍照上传,点提交。
系统反应
- 拍照把图片传产线服务解码落盘到上传目录的日期子目录,返回系统文件名。提交巡检复用与巡检终端相同的落库逻辑:记录类型必填且合法,结果留空默认合格。工位终端无按钮级权限概念,仅按记录类型合法性校验。
数据变化
- inspection_record 增记录(记录类型、工位号、工单号、工件序列号、班次、结果、检验项、照片、备注、工序号、步骤、实测值、操作人);照片落盘到上传目录日期子目录。
验证点
- 记录类型为空提示「记录类型 category 必填」;非法类型被拒;照片数据为空提示「图片数据为空」、解码失败报错;提交成功返回「巡检记录已提交」。
对不上的地方
- 无。
场景 S10:数量不符上报
前置条件
- 操作员已登录;工位收到的物料数量与下发数量不一致。
操作步骤
- 操作员在物料面板填工位号/工单号/工件序列号/物料编码/物料名称/应发数/实收数/原因,点提交。
系统反应
- 校验工位号与物料编码必填;落数量不符单(差异=应发-实收,状态待处理),同时生成预警中心消息(类型数量不符、关联号为差异单业务号、状态未读)并推大屏。后续处理在产线服务侧(见场景 M7)。
数据变化
- material_qty_report 增记录(工位号、工单号、工件序列号、物料编码/名称、应发数、实收数、差异数、原因、状态待处理、操作人);alert 增预警(类型数量不符、标题「数量不符预警」、关联差异单业务号、状态未读);event_log 记数量不符上报。
验证点
- 缺工位号或物料编码被拒;差异数=应发数-实收数;上报后预警中心出现未读的数量不符预警。
对不上的地方
- 无。
场景 S11:退料
前置条件
- 操作员已登录;工位有余料需退回库房。
操作步骤
- 操作员在物料面板填工单号/工位号/工件序列号/物料编码/名称/规格/退库数量/单位/原因,点提交。
系统反应
- 校验工位号、物料编码、退库数量(大于0)必填;落一条事件日志,并调库房服务预建「待确认退库单」(库管确认收货后库存才加回);通知库房服务失败则报错。
数据变化
- MES event_log 记工位退料;库房侧 return_order 预建一条待确认退库单(含物料、数量、来源工位、原因)。
验证点
- 退库数量小于等于0被拒「退库数量必须大于 0」;物料编码为空被拒;提交成功返回「退库单已提交」;退库单在库房确认收货前库存不变。
对不上的地方
- 无。
场景 S12:应急呼叫
前置条件
- 操作员已登录工位终端。
操作步骤
- 操作员在主界面点「应急呼叫」,可填工单号、备注,确认发送。
系统反应
- 校验工位号;落预警中心消息(类型应急呼叫、标题「应急呼叫」、内容为「工位N发起应急呼叫(工单X):备注」、状态未读),推大屏,巡检终端顶部轮询提示。纯软件方案,不依赖安灯硬件。
数据变化
- alert 增记录(类型应急呼叫、关联类型工位、关联号为工位号、状态未读);event_log 记工位应急呼叫。
验证点
- 缺工位号被拒「缺少工位号」;呼叫成功返回「应急呼叫已发送」;预警中心出现未读的应急呼叫。
对不上的地方
- 无。
场景 S13:工艺文件查看
前置条件
- 工序对应的工艺文件(PDF)已上传到产线服务文件库。
操作步骤
- 操作员在主界面或工序步骤点「查看工艺文件」,终端内嵌或新窗口打开 PDF。
系统反应
- 内嵌/新窗口打开无法携带登录令牌,终端提供免鉴权直链,内部仍走产线服务文件接口(内部令牌保护)。启用本地预缓存后首次访问回源下载并落盘,后续直接读本地缓存(断网也能查看);文件流原样转发,并校验文件名防路径穿越(禁上级目录、反斜杠、绝对路径、非法字符,段数有上限)。
数据变化
- 本地 PDF 缓存目录首次回源时新增缓存文件;无数据库写入。
验证点
- 文件名为空提示「缺少文件名」;非法文件名提示「非法文件名」;命中缓存与回源分别标记;产线服务不可达提示「工艺文件获取失败」。
对不上的地方
- 无。
场景 S14:工作量查询
前置条件
- 操作员已登录;有历史报工记录(工件工序实绩)。
操作步骤
- 操作员在主界面点「我的工作量」,可选日期区间查询。
系统反应
- 终端按当前登录人(可带工位号、日期区间)向产线服务取工作量:以工件工序实绩为源,按 操作人+工位+日期+工序 聚合完成数/合格数/不合格数/累计作业时长/平均作业时长/效率(件每小时);工位号留空表示统计该操作人全部工位。
数据变化
- 只读,无写入。
验证点
- 聚合口径为工件工序实绩按操作人+工位+日期+工序;平均作业时长=累计时长/完成数;效率=完成数/(累计时长/3600);结果按操作人、工位排序。
对不上的地方
- 无。
场景 S15:离线同步
前置条件
- 工位终端本地库可用;产线服务临时不可达(断网)。
操作步骤
- 断网时操作员照常报工/暂存/上线/完工/拧紧,终端本地暂存;网络恢复后终端每15秒自动回传,或操作员手动点「立即同步」;可在主界面查看同步状态(待同步条数)。
系统反应
- 报工/暂存/上线/完工:先尽力冲刷一轮历史积压,再实时上报产线服务;产线服务不可达则写入本地队列(工序完成/暂存/上线建档/下线完工四类)并返回「已排队」稍后自动重传;产线服务业务拒绝(如装配未绑齐、电气件序列号重复)直接失败回前端,绝不入离线队。
- 拧紧结果单独存本地表,同步时把接驳台编码换算成工位号后上报。
- 后台每15秒冲刷一轮(先队列后拧紧),成功标记已同步、失败保持待同步并累计重试次数;手动同步立即执行一轮并返回本次成功条数;同步状态返回待同步拧紧数与待同步队列数。
数据变化
- 本地 report_queue(类型、载荷、是否已同步、重试次数、创建时间)、torque_results(拧紧结果、是否已同步)、event_log(本地操作留痕);同步成功后对应记录标记为已同步。
验证点
- 断网报工返回已排队且待同步队列数加1;恢复后自动或手动同步成功则标记已同步、待同步数归0;产线服务业务拒绝不入队直接失败;手动同步返回本轮成功条数。
对不上的地方
- 无。
文档3:回归测试
可断言用例,按场景分组。断言为具体数值/状态/条数。MES 接口前缀 /api/v1(需 JWT);WMS 接口前缀 /api(需 JWT);内部接口带 X-API-TOKEN。 公共前置:用管理员账号 POST /api/v1/login 取 token,后续 MES 请求带 Authorization: Bearer ;WMS 同理 POST /api/auth/login。
测试场景 M1:建工单
前置数据准备(SQL)
- 确保产品型号存在:INSERT INTO product_type(code,name) VALUES('CP001','测试产品') ON CONFLICT DO NOTHING;(取回其 id 记为 :ptId)
执行步骤(API)
- POST /api/v1/work-orders,body:{"workOrderNo":"WO20260920","productTypeId":,"productCode":"CP001","productName":"测试产品","quantity":10,"contractNo":"HT001","projectNo":"GC001","productSerial":"01","dueDate":"2026-09-30T00:00:00Z"}
预期结果
- work_order:workOrderNo='WO20260920' 新增一行,status='CREATED'、quantity=10、finishedNum=0、failNum=0、contractNo='HT001'。
数量等式断言
- SELECT count(*) FROM work_order WHERE workOrderNo='WO20260920' → 1;finishedNum=0 且 finishedNum ≤ quantity。
清理步骤
- DELETE FROM work_order WHERE workOrderNo='WO20260920';
测试场景 M2:日排产
前置数据准备
- 工单 WO20260920 存在(M1)。
执行步骤(API)
- POST /api/v1/daily-plans,body:{"orderNo":"WO20260920","planDate":"2026-09-20","planQty":10}
预期结果
- daily_plan:(orderNo='WO20260920',planDate='2026-09-20') 新增一行,planQty=10、completedQty=0、status='PENDING'。
数量等式断言
- SELECT count(*) FROM daily_plan WHERE orderNo='WO20260920' AND planDate='2026-09-20' → 1(planDate+orderNo 唯一)。
清理步骤
- DELETE FROM daily_plan WHERE orderNo='WO20260920' AND planDate='2026-09-20';
测试场景 M9:工位派工
前置数据准备
- 工位 1、2 存在(station 表)。
执行步骤(API)
- POST /api/v1/station-process/save,body:{"effectDate":"2026-09-20","items":[{"stationNo":1,"processCode":1},{"stationNo":2,"processCode":2}]}
预期结果
- station_process:effectDate='2026-09-20' 存在 (stationNo=1,processCode=1) 与 (stationNo=2,processCode=2)。
数量等式断言
- SELECT count(*) FROM station_process WHERE effectDate='2026-09-20' AND stationNo IN(1,2) → 2;(stationNo,effectDate,shift) 无重复。
清理步骤
- DELETE FROM station_process WHERE effectDate='2026-09-20';
测试场景 M4:生成备料单并自动出库
前置数据准备(SQL/API)
- BOM(MES work_order_bom):INSERT 两行:(productCode='CP001',bomName='默认',materialCode='M-A',manageMode='1',processCode=1,unitQty=2,lossRate=0)、(productCode='CP001',bomName='默认',materialCode='M-B',manageMode='2',processCode=2,unitQty=4,lossRate=0)。
- WMS 物料档案:POST /api/material/create 建 M-A(结构件)、M-B(电气件)。
- WMS 合格在库库存:
- INSERT INTO inventories(manage_mode,category,material_code,batch_no,quantity,locked_qty,quality_status,status) VALUES(1,1,'M-A','B001',100,0,'合格','在库');
- 电气件 M-B 插 40 行 SN:INSERT INTO inventories(manage_mode,category,material_code,sn_code,quantity,locked_qty,quality_status,status) VALUES(2,1,'M-B','SNB0001',1,0,'合格','在库'); … 至 'SNB0040'。
- 已存在 M1/M2/M9 的前置数据。
执行步骤(API)
- POST /api/v1/material-requests/generate,body:{"planDate":"2026-09-20"}
预期结果
- material_request:新增 (orderNo='WO20260920',materialCode='M-A',stationNo=1,processCode=1,reqQty=20,sentQty=20,status='LOCKED') 与 (materialCode='M-B',stationNo=2,reqQty=40,sentQty=40,status='LOCKED')。
- WMS order_material_ledger:(WO20260920,'M-A') total_qty=20、out_qty=20;(WO20260920,'M-B') total_qty=40、out_qty=40。
- WMS outbound_order:新增 outbound_type='workorder'、status='已出库'、order_no='WO20260920' 的出库单。
- WMS inventories:M-A 批次 B001 quantity 100→80;M-B 的 40 个 SN status '在库'→'出库'。
数量等式断言
- material_request.sentQty ≤ reqQty(M-A:20≤20,M-B:40≤40)。
- ledger net_out = out_qty − returned_qty ≤ total_qty(20≤20,40≤40)。
- SELECT quantity FROM inventories WHERE batch_no='B001' → 80;SELECT count(*) FROM inventories WHERE material_code='M-B' AND status='出库' → 40。
清理步骤
- DELETE FROM material_request WHERE orderNo='WO20260920';
- WMS:DELETE FROM outbound_detail WHERE order_no='WO20260920'; DELETE FROM outbound_order WHERE order_no='WO20260920'; DELETE FROM order_material_ledger WHERE order_no='WO20260920';
- 回滚库存:UPDATE inventories SET quantity=100 WHERE batch_no='B001'; UPDATE inventories SET status='在库' WHERE material_code='M-B';
测试场景 S8:工位接料
前置数据准备
- 备料单 M-A(工位1) sentQty=20、status='LOCKED'(M4)。
执行步骤(API)
- POST /api/v1/material-requests/receive,body:{"requestNo":"<M-A备料单号>","qty":0}(qty=0 表示接本批全部剩余)
预期结果
- material_request(M-A):receivedQty=20、status='DONE'、receiveBy 非空、receivedAt>0。
数量等式断言
- 在途 = sentQty − receivedQty = 0;receivedQty ≤ sentQty(20≤20)。
清理步骤
- 随 M4 清理一并删除 material_request。
测试场景 S3:工件上线
前置数据准备
- 工单 WO20260920 存在。
执行步骤(API)
- POST /api/v1/workpiece/online,body:{"sn":"SN0001","orderNo":"WO20260920"}
预期结果
- workpiece:sn='SN0001' 新增一行,status='ONLINE'、currentProcess=0、onlineAt 非空。
数量等式断言
- SELECT count(*) FROM workpiece WHERE sn='SN0001' → 1;sn 全局唯一。
清理步骤
- DELETE FROM workpiece WHERE sn='SN0001';
测试场景 S4:工序报工
前置数据准备
- 工件 SN0001 已上线(S3),currentProcess=0。
- 工位 1 已绑定工艺流程(station.flowId>0),流程含工序 1 的步骤与考核标准;BOM 中 M-A processCode=1。
执行步骤(API)
- POST /api/v1/workpiece/process/report,body:{"sn":"SN0001","processCode":1,"stationNo":1,"binds":[{"materialCode":"M-A","batchNo":"B001"}],"steps":[{"stepId":<步骤id>,"name":"步骤1","value":10,"checked":true}],"startedAt":"2026-09-20T09:00:00Z","endedAt":"2026-09-20T09:05:00Z"}
预期结果
- workpiece_process:(sn='SN0001',processCode=1) 新增一行,status='DONE'、result='OK'(步骤均合格)、durationSec=300。
- step_data:对应步骤行 is_ok=true。
- workpiece(SN0001):currentProcess=1、status='PROCESSING'。
数量等式断言
- workpiece.currentProcess = SELECT count(*) FROM workpiece_process WHERE sn='SN0001' AND status='DONE' → 1。
- durationSec = endedAt−startedAt = 300(≥0)。
- 负用例:再次以 processCode=1 报工 → 拒绝(工序顺序不符,应报工序 2);以 processCode=3 报工 → 拒绝。
清理步骤
- DELETE FROM step_data WHERE sn='SN0001'; DELETE FROM workpiece_process WHERE sn='SN0001'; DELETE FROM workpiece_bind WHERE sn='SN0001';
测试场景 S5:完工下线并成品回流
前置数据准备
- 工件 SN0001 已报完全部工序(本例工序总数=2,先完成工序 2 报工),全部工序物料已绑齐。
执行步骤(API)
- POST /api/v1/workpiece/done,body:{"sn":"SN0001","batchItems":["B001"],"serialItems":["SNB0001"]}
预期结果
- workpiece(SN0001):status='DONE'、doneAt 非空。
- association_trace:finishedSn='SN0001' 新增/更新,batchItems=['B001']、serialItems=['SNB0001']。
- work_order(WO20260920):finishedNum 由 0→1;daily_plan(2026-09-20):completedQty 由 0→1。
- WMS inventories:新增成品行 category=3、material_code='CP001'、sn_code='SN0001'、quantity=1、status='在库'、order_no='WO20260920'。
- WMS order_material_ledger:M-A consumed_qty=2(单台用量2×完工1件)、M-B consumed_qty=4。
数量等式断言
- work_order.finishedNum=1 且 ≤ quantity(10)。
- ledger consumed_qty ≤ net_out_qty(M-A:2≤20,M-B:4≤40)。
- SELECT count(*) FROM inventories WHERE sn_code='SN0001' AND category=3 → 1。
清理步骤
- DELETE FROM association_trace WHERE finishedSn='SN0001';
- WMS:DELETE FROM inventories WHERE sn_code='SN0001' AND category=3; UPDATE order_material_ledger SET consumed_qty=0 WHERE order_no='WO20260920';
测试场景 M15:工件追溯
前置数据准备
- 工件 SN0001 已完工(S5)。
执行步骤(API)
- GET /api/v1/trace?sn=SN0001
预期结果
- 返回体含 workpiece、processTimeline(工序记录条数=已报工工序数)、associationTrace(batchItems/serialItems)、torqueRecords。
数量等式断言
- processTimeline 条数 = SELECT count(*) FROM workpiece_process WHERE sn='SN0001'。
- associationTrace.finishedSn='SN0001' 与入参一致。
清理步骤
- 无写入,无需清理。
测试场景 W3:入库登记
前置数据准备(SQL/API)
- WMS 物料档案:POST /api/material/create 建 M-A(manage_mode=1 结构件)、M-B(manage_mode=2 电气件)。
- 先建一张合格来料检验单号 INSP-001(可先走 W9 合格检验,或直接 INSERT inspection_record(inspection_no='INSP-001',status='合格'))。
执行步骤(API)
- POST /api/inbound/create,body:{"inboundType":"purchase","materialCode":"M-A","batchNo":"BA001","quantity":100,"zoneCode":"Z1","inspectionNo":"INSP-001","ownershipType":"workorder","ownershipNo":"WO20260920","operator":"张三"}
预期结果
- inbound_order:新增一行 material_code='M-A'、batch_no='BA001'、quantity=100、inspection_no='INSP-001'、voided=false(inbound_no 由系统生成,从响应体取)。
- inventories:新增 batch_no='BA001'、manage_mode=1、category=1、quantity=100、locked_qty=0、quality_status='未检'、status='在库'。
- inbound_detail:新增 batch_no='BA001'、quantity=100。
数量等式断言
- SELECT quantity FROM inventories WHERE batch_no='BA001' → 100;SELECT sum(quantity) FROM inbound_detail WHERE inbound_no='<返回单号>' → 100;两者相等。
- 可用量 = quantity − locked_qty = 100 − 0 = 100。
清理步骤
- DELETE FROM inbound_detail WHERE inbound_no='<返回单号>'; DELETE FROM inbound_order WHERE inbound_no='<返回单号>'; DELETE FROM inventories WHERE batch_no='BA001';
测试场景 W4:入库作废
前置数据准备
- 入库单 IBxxxx(M-A 批次 BA001 数量 100)已登记(W3),且该批次 locked_qty=0、quantity=100。
执行步骤(API)
- POST /api/inbound/void,body:{"inboundNo":"","reason":"录入重复"}
预期结果
- inbound_order(IBxxxx):voided=true、void_reason='录入重复'、voided_by 非空、voided_at>0。
- inventories:batch_no='BA001' 的行被删除(quantity 100→0 后删行)。
- 响应体 rolledQty=100。
数量等式断言
- SELECT count(*) FROM inventories WHERE batch_no='BA001' → 0。
- 负用例:对已作废单再次 POST /api/inbound/void → 409(重复作废)。
清理步骤
- 随 W3 清理(本用例已删除 BA001 库存行);DELETE FROM inbound_order WHERE inbound_no=''。
测试场景 W6:通用出库
前置数据准备(SQL)
- INSERT INTO inventories(manage_mode,category,material_code,batch_no,quantity,locked_qty,quality_status,status,zone_code) VALUES(1,1,'M-A','BA002',80,0,'合格','在库','Z1');
执行步骤(API)
- POST /api/outbound/general,body:{"materialCode":"M-A","batchNo":"BA002","qty":30,"outboundCategory":"delivery","operator":"李四"}
预期结果
- outbound_order:新增 outbound_type='general'、status='已出库'、batch_no='BA002'、outbound_category='delivery'、quantity=30。
- outbound_detail:新增 batch_no='BA002'、quantity=30。
- inventories:batch_no='BA002' quantity 80→50。
数量等式断言
- SELECT quantity FROM inventories WHERE batch_no='BA002' → 50(= 80 − 30)。
- 负用例:对 quality_status='未检' 的批次以 delivery 出库 → 409(仅合格品);qty 大于可用量 → 409。
清理步骤
- DELETE FROM outbound_detail WHERE batch_no='BA002'; DELETE FROM outbound_order WHERE outbound_no='<返回单号>'; DELETE FROM inventories WHERE batch_no='BA002';
测试场景 W7:退库确认
前置数据准备(SQL/API)
- 预建退库单:POST /api/internal/return-order(带 X-API-TOKEN),body:{"orderNo":"WO20260920","materialCode":"M-A","qty":20,"stationNo":1,"reason":"用剩退回","operator":"工位1"},取回 returnNo 记为 THxxxx。
- 台账:INSERT INTO order_material_ledger(order_no,material_code,total_qty,out_qty,returned_qty,status) VALUES('WO20260920','M-A',20,20,0,'领料完结') ON CONFLICT 更新为同值。
- 库存:INSERT INTO inventories(manage_mode,category,material_code,batch_no,quantity,locked_qty,quality_status,status,zone_code) VALUES(1,1,'M-A','BA001',0,0,'未检','在库','Z1');
执行步骤(API)
- POST /api/return-order/confirm,body:{"returnNo":"","returnType":"surplus","zoneCode":"Z1","batchNo":"BA001"}
预期结果
- return_order(THxxxx):status='RECEIVED'、receive_by 非空、receive_at>0。
- inventories:M-A 批次 BA001(未检、Z1)quantity 加回 20。
- order_material_ledger(WO20260920,M-A):returned_qty=20、status='进行中'。
数量等式断言
- net_out = out_qty − returned_qty = 20 − 20 = 0,且 0 ≤ total_qty(20)。
- SELECT quantity FROM inventories WHERE batch_no='BA001' AND quality_status='未检' → 含加回的 20。
- 负用例:对已 RECEIVED 单再次 confirm → 409。
清理步骤
- DELETE FROM return_order WHERE return_no=''; DELETE FROM order_material_ledger WHERE order_no='WO20260920' AND material_code='M-A'; DELETE FROM inventories WHERE batch_no='BA001';
测试场景 W9:来料检验
前置数据准备(SQL)
- INSERT INTO inventories(manage_mode,category,material_code,batch_no,quantity,locked_qty,quality_status,status,zone_code) VALUES(1,1,'M-A','BA003',100,0,'未检','在库','Z1');
执行步骤(API)
- POST /api/inspection/create,body:{"inspectionType":"来料检验","targetId":"BA003","status":"不合格","inspectQty":100,"passQty":0,"inspector":"王五"}(取回记录 id 记为 :recId)
预期结果
- inspection_record(:recId):inspection_type='来料检验'、status='不合格'、inspect_qty=100、pass_qty=0。
- inventories:batch_no='BA003' quality_status 由'未检'置'不合格';quantity=100。
数量等式断言
- pass_qty ≤ inspect_qty(0 ≤ 100);SELECT quality_status FROM inventories WHERE batch_no='BA003' → '不合格'。
- 负用例:status 传非合格/不合格值 → 400;targetId 无对应库存行 → 400。
清理步骤
- DELETE FROM inspection_record WHERE target_id='BA003'; DELETE FROM inventories WHERE batch_no='BA003';
测试场景 W9-处置:不合格品处置(退货)
前置数据准备(SQL)
- 已有 W9 产生的不合格来料检验记录 inspection_record(:recId)(status='不合格';批次 BA003 quality_status='不合格'、quantity=100)。
执行步骤(API)
- POST /api/inspection/dispose,body:{"recordId":,"disposalType":"RETURN","returnTrackingNo":"SF123456","operator":"王五"}
预期结果
- inspection_record(:recId):disposal_type='RETURN'、return_tracking_no='SF123456'。
- inventories:batch_no='BA003' quality_status 由'不合格'置'待退';quantity 仍=100。
数量等式断言
- SELECT quality_status FROM inventories WHERE batch_no='BA003' → '待退';SELECT quantity FROM inventories WHERE batch_no='BA003' → 100(退货处置不减数量)。
- 负用例:对 status='合格' 的记录调 dispose → 400(仅不合格可处置);disposalType='RETURN' 不填 returnTrackingNo → 400。
清理步骤
- DELETE FROM inspection_record WHERE target_id='BA003'; DELETE FROM inventories WHERE batch_no='BA003';
测试场景 W10:过程检验
前置数据准备(SQL)
- INSERT INTO inventories(manage_mode,category,material_code,sn_code,quantity,locked_qty,quality_status,status) VALUES(2,1,'M-B','SNB0001',1,0,'未检','在库');
执行步骤(API)
- POST /api/inspection/create,body:{"inspectionType":"过程检验","targetId":"SNB0001","status":"合格","inspectQty":1,"passQty":1,"inspector":"王五"}
预期结果
- inspection_record:新增 target_id='SNB0001'、inspection_type='过程检验'、status='合格'。
- inventories:sn_code='SNB0001' quality_status 由'未检'置'合格'。
数量等式断言
- SELECT quality_status FROM inventories WHERE sn_code='SNB0001' → '合格';负用例:status 传非合格/不合格值 → 400;targetId 无对应库存行 → 400。
清理步骤
- DELETE FROM inspection_record WHERE target_id='SNB0001'; DELETE FROM inventories WHERE sn_code='SNB0001';
测试场景 W11:完工检验
前置数据准备(SQL)
- INSERT INTO inventories(manage_mode,category,material_code,sn_code,quantity,locked_qty,quality_status,status,order_no) VALUES(2,3,'CP001','SN0001',1,0,'未检','在库','WO20260920');
执行步骤(API)
- POST /api/inspection/create,body:{"inspectionType":"完工检验","targetId":"SN0001","status":"合格","inspectQty":1,"passQty":1,"inspector":"王五"}
预期结果
- inspection_record:新增 target_id='SN0001'、inspection_type='完工检验'、status='合格'。
- inventories:sn_code='SN0001' 且 category=3 的 quality_status 由'未检'置'合格'。
数量等式断言
- SELECT quality_status FROM inventories WHERE sn_code='SN0001' AND category=3 → '合格';负用例:status 传非合格/不合格值 → 400;targetId 无对应库存行 → 400。
清理步骤
- DELETE FROM inspection_record WHERE target_id='SN0001'; DELETE FROM inventories WHERE sn_code='SN0001';
测试场景 W12:库存盘点
前置数据准备(SQL)
- INSERT INTO inventories(manage_mode,category,material_code,batch_no,quantity,locked_qty,quality_status,status,zone_code,stocktake_no) VALUES(1,1,'M-A','BA004',50,0,'合格','在库','Z1','');
执行步骤(API)
- POST /api/stocktake/start,body:{"operator":"张三","materialCodes":["M-A"]}(取回 stocktakeNo 记为 STxxxx)
- POST /api/stocktake/record,body:{"stocktakeNo":"","targetId":"BA004","scanQty":48}
- POST /api/stocktake/finish(需 stocktake:writeback 权限),body:{"stocktakeNo":"","adjust":true,"uncountedMode":"keep"}
预期结果
- stocktake_order(STxxxx):start 后 status='进行中'、total_targets≥1;finish 后 status='已写回'、adjusted=true、adjust_no 非空、diff_count=1。
- stocktake_item(BA004):book_qty=50、scanned_qty=48、diff_qty=-2、counted=true。
- inventories(BA004):start 后 stocktake_no=STxxxx;finish 后 quantity=48、stocktake_no=''。
数量等式断言
- diff_qty = scanned_qty − book_qty = 48 − 50 = -2。
- 库存量 = 可用量 + 锁定量(48 = 48 + 0);写回后 quantity(48) ≥ locked_qty(0)。
- SELECT quantity,stocktake_no FROM inventories WHERE batch_no='BA004' → 48, ''。
清理步骤
- DELETE FROM stocktake_item WHERE stocktake_no=''; DELETE FROM stocktake_order WHERE stocktake_no=''; DELETE FROM inventories WHERE batch_no='BA004';
测试场景 W13:半成品入库/出库
前置数据准备
- 确保 SEMI0001 在 category=2 下无库存行。
执行步骤(API)
- POST /api/semi/inbound,body:{"sn":"SEMI0001","materialCode":"M-A","category":2,"doneProcessCodes":[1,2],"zoneCode":"Z1","orderNo":"WO20260920"}
- POST /api/semi/outbound,body:{"sn":"SEMI0001","category":2,"reason":"继续生产"}
预期结果
- inventories:新增 sn_code='SEMI0001'、manage_mode=2、category=2、quantity=1、quality_status='未检'、status 先'在库'、completed_process='1,2';出库后 status='出库'、outbound_reason='继续生产'。
数量等式断言
- SELECT quantity FROM inventories WHERE sn_code='SEMI0001' AND category=2 → 1(SN 单件恒为1)。
- SELECT status FROM inventories WHERE sn_code='SEMI0001' AND category=2 → '出库'。
- 负用例:重复入库同 SN → 409;reason 空出库 → 400;非在库出库 → 409。
清理步骤
- DELETE FROM inventories WHERE sn_code='SEMI0001' AND category=2;
测试场景 W8:库存查询
前置数据准备(SQL)
- INSERT INTO inventories(manage_mode,category,material_code,batch_no,quantity,locked_qty,quality_status,status,zone_code) VALUES(1,1,'M-A','BA010',60,10,'合格','在库','Z1');
执行步骤(API)
- GET /api/stock/query?materialCode=M-A
- GET /api/stock/material-summary?materialCode=M-A
- GET /api/stock/zone-summary?zoneCode=Z1
预期结果
- 返回体含 BA010 在库行;按物料汇总 M-A 在库 60、锁定 10、可用 50(仅统计在库/锁定)。
数量等式断言
- 可用 = 在库 − 锁定 = 60 − 10 = 50。
- 负用例:将 quality_status 置'不合格'后,可用量汇总不计入该批次。
清理步骤
- DELETE FROM inventories WHERE batch_no='BA010';
测试场景 W14:工单备料台账
前置数据准备(SQL)
- INSERT INTO order_material_ledger(order_no,material_code,total_qty,out_qty,returned_qty,consumed_qty,status) VALUES('WO20260920','M-A',20,20,0,0,'领料完结');
- INSERT INTO inventories(manage_mode,category,material_code,batch_no,quantity,locked_qty,quality_status,status) VALUES(1,1,'M-A','BA011',5,0,'合格','在库');
执行步骤(API)
- GET /api/ledger/query?orderNo=WO20260920
预期结果
- 返回 M-A 行:total_qty=20、out_qty=20、returned_qty=0、availableQty=5、shortQty=15、state='未齐套'(0<5<20)。
数量等式断言
- net_out = out_qty − returned_qty = 20 ≤ total_qty=20;short_qty = max(0, 20 − 5) = 15。
清理步骤
- DELETE FROM order_material_ledger WHERE order_no='WO20260920' AND material_code='M-A'; DELETE FROM inventories WHERE batch_no='BA011';
测试场景 W15:AGV 配送
前置数据准备
- MES 存在待配送备料单行(requestNo 记为 REQ001,targetDock=DOCK01);接驳台 DOCK21、DOCK01 已预置。
执行步骤(API)
- POST /api/agv/submit,body:{"sourceDock":"DOCK21","rows":[{"requestNo":"REQ001","materialCode":"M-A","qty":20,"targetDock":"DOCK01"}]}
- POST /api/agv/refresh(模拟模式下拉取到位)
- POST /api/agv/cancel,body:{"taskNo":"<返回任务号>","reason":"测试取消"}
预期结果
- agv_task:新增 task_no='AGVxxxx'、source_dock='DOCK21'、target_dock='DOCK01'、status 先'DISPATCHED';refresh 后'DONE'、arrived_at>0;cancel 后'CANCELLED'。
- 回写:MES 备料单 REQ001 submit 后'DELIVERING'、到位后'DONE'、取消后'WAIT_SEND'。
数量等式断言
- SELECT count(*) FROM agv_task WHERE target_dock='DOCK01' → ≥1;响应体 created=1。
清理步骤
- DELETE FROM agv_task WHERE target_dock='DOCK01';
测试场景 W16:区域维护(批量生成)
前置数据准备(SQL)
- INSERT INTO zones(level,code,name,parent_code,zone_code,zone_name,status) VALUES(1,'Z1','区域1','','Z1','区域1','启用');
- INSERT INTO zones(level,code,name,parent_code,zone_code,shelf_no,status) VALUES(2,'S1','货架1','Z1','Z1','S1','启用');
执行步骤(API)
- POST /api/zone/batch-generate,body:{"zoneCode":"Z1","shelfNo":"S1","layerStart":1,"layerEnd":2,"posStart":1,"posEnd":3,"level":4,"preview":false}
预期结果
- zones:新增 6 行 level=4、zone_code='Z1'、shelf_no='S1'、layer_no∈{1,2}、position_no∈{1,2,3}、status='启用';并确保 level=3 层节点 2 行。
- 响应体 created=6、skipped=0、total=6。
数量等式断言
- SELECT count(*) FROM zones WHERE zone_code='Z1' AND shelf_no='S1' AND level=4 → 6;重复提交后 created=0、skipped=6。
清理步骤
- DELETE FROM zones WHERE zone_code='Z1';
测试场景 W17:物料档案维护
前置数据准备
- 确保 material 中无 code='M-C'。
执行步骤(API)
- POST /api/material/create,body:{"code":"M-C","name":"结构件C","spec":"C-100","unit":"个","itemType":1,"manageMode":1,"safetyStock":10}
预期结果
- material:新增 code='M-C'、name='结构件C'、spec='C-100'、unit='个'、item_type=1、manage_mode=1、is_batch_managed=true、is_serial_managed=false、safety_stock=10。
数量等式断言
- SELECT count(*) FROM material WHERE code='M-C' → 1;manage_mode=1 时 is_batch_managed=true 且 is_serial_managed=false。
- 负用例:重复 code 新增 → 409;缺 name/spec/unit 任一 → 400。
清理步骤
- DELETE FROM material WHERE code='M-C';
测试场景 W18:接驳台维护(只读)
前置数据准备
- dock 表已由种子预置(含 dock_type='line' 行)。
执行步骤(API)
- GET /api/docks?dockType=line
预期结果
- 返回 dock_type='line' 的接驳台列表,每行含 dock_code/name/station_no/status/has_pallet。
数量等式断言
- 返回条数 = SELECT count(*) FROM docks WHERE dock_type='line';无新增/修改/删除接口(只读)。
清理步骤
- 无写入,无需清理。
测试场景 W19:账号管理
前置数据准备
- 角色已存在(W20,取其 id 记为 :roleId);确保 user 中无 username='wms01'。
执行步骤(API)
- POST /api/user/create,body:{"username":"wms01","password":"123456","realName":"库管一","roleId":,"dept":"库房"}
预期结果
- user:新增 username='wms01'、password 为 bcrypt 密文(非'123456')、real_name='库管一'、role_id=、dept='库房'。
数量等式断言
- SELECT count(*) FROM users WHERE username='wms01' → 1;SELECT password FROM users WHERE username='wms01' ≠ '123456'。
- 负用例:重复 username → 409;password 不足 6 位 → 400。
清理步骤
- DELETE FROM users WHERE username='wms01';
测试场景 W20:角色管理
前置数据准备
- 确保 role 中无 code='warehouse'。
执行步骤(API)
- POST /api/roles,body:{"name":"库管","code":"warehouse","permissionCodes":["inbound:create","stocktake:start"]}
预期结果
- role:新增 name='库管'、code='warehouse'、permission_codes=["inbound:create","stocktake:start"]。
数量等式断言
- SELECT count(*) FROM roles WHERE code='warehouse' → 1;permission_codes 数组长度=2。
- 负用例:重复 code → 409;对 code='admin' 调 /api/roles/update 改权限 → 400。
清理步骤
- DELETE FROM roles WHERE code='warehouse';
测试场景 W21:操作日志(只读)
前置数据准备
- 先执行一次写入操作(如 W17 新增物料)以产生日志。
执行步骤(API)
- GET /api/event/logs?page=1&pageSize=20
预期结果
- 返回分页日志列表,含刚产生的 material.create 条目(操作人/时间/对象/描述)。
数量等式断言
- 返回 total ≥ 1;列表每条含非空 created_at 与 action。
清理步骤
- 无写入,无需清理(日志为审计留痕,不随业务数据回滚)。
测试场景 W22:附件上传
前置数据准备
- 准备一份小于上限的 PDF/图片文件;bizType='inbound'、bizId='<某入库单号>'。
执行步骤(API)
- POST /api/attachments/upload(multipart/form-data),字段:bizType=inbound、bizId=<入库单号>、file=<文件>
- GET /api/attachments?bizType=inbound&bizId=<入库单号>
预期结果
- attachment:新增 biz_type='inbound'、biz_id=<入库单号>、file_md5 非空、file_size>0、deleted=false。
- 重复上传同一文件(同 MD5)不新增行,返回已有记录。
数量等式断言
- SELECT count(*) FROM attachments WHERE biz_id=<入库单号> AND file_md5=<该文件MD5> AND deleted=false → 1(传两次仍为1)。
清理步骤
- DELETE FROM attachments WHERE biz_id=<入库单号>(并清理落盘文件)。
测试场景 W23:修改密码
前置数据准备
- 用户 wms01 已存在(W19),密码 123456;以 wms01 登录取 token。
执行步骤(API)
- POST /api/user/change-password(带 wms01 的 Authorization),body:{"oldPassword":"123456","newPassword":"654321"}
预期结果
- user(wms01):password 更新为新密文;用 654321 可登录,123456 失效。
数量等式断言
- 负用例:oldPassword 错误 → 400(旧密码不正确);newPassword 不足 6 位 → 400。
清理步骤
- 随 W19 清理删除 wms01。
测试场景 W24:库存大屏(只读)
前置数据准备(SQL)
- 确保 inventories 有若干结构件在库行(如 BA010 quantity=60)。
执行步骤(API)
- GET /api/display/overview(免登录)
预期结果
- 返回体含 batchCount、totalQty、lockedQty、snInStock、semiInStock、finishedInStock、inToday、outToday、zoneDetail。
数量等式断言
- totalQty = SELECT sum(quantity) FROM inventories WHERE manage_mode=1;batchCount = SELECT count(*) FROM inventories WHERE manage_mode=1。
- semiInStock = SELECT count(*) FROM inventories WHERE category=2 AND status='在库'。
清理步骤
- 无写入,无需清理。
测试场景 W1:登录(WMS)
前置数据准备(SQL)
- 确保用户 wms01:INSERT INTO "user"(username,password,real_name,role,is_active) VALUES('wms01', bcrypt('wms123456'), '库管员', 'OPERATOR', true) ON CONFLICT DO NOTHING。
执行步骤(API)
- POST /api/auth/login body {"username":"wms01","password":"wms123456"}
预期结果
- code=0;data.token 非空;data.expireAt>当前;data.user.username='wms01';data.user.permissionCodes 为数组;data.weakPassword=false。
数量等式断言
- 负用例:password 错 → 401「用户名或密码错误」;is_active=false → 403「账号已停用」;username 或 password 空 → 400「用户名和密码必填」;纯数字密码(如 '123456')登录成功但 weakPassword=true。
- 登录后 user.last_login_at 被更新;event_log 新增1条 event_type='auth.login'。
清理步骤
- DELETE FROM "user" WHERE username='wms01'; DELETE FROM event_log WHERE event_type='auth.login' AND operator='wms01';
测试场景 W2:工作台(只读)
前置数据准备(SQL)
- 确保 inventories 有若干结构件在库行(如 BA010 quantity=60);至少一条物料可用量低于安全库存(触发缺货预警)。
执行步骤(API)
- GET /api/display/overview(免登录);GET /api/stock/shortage(带 token)
预期结果
- overview 返回体含 totalQty、lockedQty、snInStock、semiInStock、packageCount、batchCount、inToday、outToday;shortage 返回 data.list,每项含 materialCode、materialName、spec、safetyStock、availQty、shortQty。
数量等式断言
- totalQty = SELECT sum(quantity) FROM inventories WHERE manage_mode=1;shortage 仅列 availQty<safetyStock 的物料(每项 shortQty=safetyStock-availQty);shortage 接口异常时前端不报错、预警区留空。
清理步骤
- 无写入,无需清理。
测试场景 W5:备料出库(WMS)
前置数据准备(SQL)
- 工单 WO20260920 的 BOM 已同步:order_material_ledger 存 (order_no='WO20260920', material_code='M-A', 需求量=20, status 非'领料完结');inventories 存合格批次 BA001(manage_mode=1, quantity=30, locked_qty=0, quality_status='合格');无盘点冻结。
执行步骤(API)
- POST /api/outbound/create body {"orderNo":"WO20260920","materialCode":"M-A","batchNo":"BA001","qty":10,"operator":"wms01","targetDock":"1-R1","needAgv":true}
预期结果
- code=0;outbound_order 新增1条 outbound_type='workorder'、status='已出库'、need_agv=true、material_code='M-A'、batch_no='BA001';inventories(BA001).quantity 由30→20;outbound_detail 新增1条 quantity=10;order_material_ledger 累计出库量 +10。
数量等式断言
- 出库前后:inventories(BA001).quantity = 30 - 10 = 20;outbound_detail SUM(quantity WHERE outbound_no=:obNo)=10。
- 负用例:orderNo/materialCode 空 → 400「orderNo 和 materialCode 必填」;台账不存在 → 400「工单物料台账不存在…」;台账 status='领料完结' → 409「工单领料已完结,禁止再出库」;batchNo 与 snList 都空 → 400「batchNo 或 snList 至少提供一个」;批次可用量不足(qty>available)→ 409「批次库存不足」;批次 quality_status非'合格' → 409「仅合格品可领料上线」;盘点冻结批次 → 被拦截。
清理步骤
- DELETE FROM outbound_detail WHERE outbound_no=:obNo; DELETE FROM outbound_order WHERE outbound_no=:obNo; UPDATE inventories SET quantity=30, locked_qty=0 WHERE batch_no='BA001';
测试场景 M1-状态:工单状态流转
前置数据准备(SQL)
- INSERT INTO work_order(work_order_no,product_type_id,product_code,quantity,status) VALUES('WO20260920',:ptId,'CP001',10,'CREATED');(取回 id=:woId)
- 清场:UPDATE work_order SET status='DONE' WHERE status IN('IN_PROGRESS','PAUSED') AND work_order_no<>'WO20260920';
执行步骤(API)
- POST /api/v1/work-orders/status body {"id":":woId","status":"RELEASED","reason":"下发"}
- POST /api/v1/work-orders/status body {"id":":woId","status":"IN_PROGRESS"}
- POST /api/v1/work-orders/status body {"id":":woId","status":"DONE"}
预期结果
- work_order(:woId).status 依次 RELEASED→IN_PROGRESS→DONE;DONE 后 completed_at 非空。
- 负用例:新建一张 CREATED 工单直接 POST status=DONE → 错误「不允许从「已创建」变更为「已完成」」。
数量等式断言
- SELECT status FROM work_order WHERE id=:woId → 'DONE'。
- SELECT count(*) FROM event_log WHERE event_type='work.order.status' AND work_order_no='WO20260920' → >=3。
清理步骤
- DELETE FROM work_order WHERE work_order_no='WO20260920'; DELETE FROM event_log WHERE work_order_no='WO20260920';
测试场景 M3:产品物料清单(BOM 维护)
前置数据准备(SQL)
- 确保产品型号 CP001 存在;清场 DELETE FROM bom_item WHERE product_code='CP001';
执行步骤(API)
- PUT /api/v1/bom body {"productCode":"CP001","bomName":"","items":[{"materialCode":"M-A","materialName":"结构件","unit":"件","unitQty":2,"processCode":1},{"materialCode":"M-B","materialName":"电气件","unit":"件","unitQty":4,"processCode":2}]}
- 重复 PUT 同一 body(验证 upsert 不重行)
预期结果
- bom_item 中 CP001/默认 共 2 行;M-A unit_qty=2、process_code=1;M-B unit_qty=4、process_code=2;manage_mode 默认 '2'。
- 重复保存后行数仍为 2(不新增)。
数量等式断言
- SELECT count(*) FROM bom_item WHERE product_code='CP001' AND bom_name='默认' → 2(传两次仍 2)。
- 负用例:对已被工单选用的型号 POST /api/v1/bom/item/delete {"id":":bomItemId"} → 错误(已被工单选用)。
清理步骤
- DELETE FROM bom_item WHERE product_code='CP001';
测试场景 M5:数量不符处理
前置数据准备(API)
- 工位上报少发:POST /api/internal/station/qty-report(X-API-TOKEN)body {"stationNo":1,"orderNo":"WO20260920","materialCode":"M-A","planQty":10,"actualQty":7,"cause":"少发"}(差异=3,取回记录 id=:qrId)
执行步骤(API)
- POST /api/v1/qty-reports/handle body {"id":":qrId","handleType":"REPLENISH","note":"补发"}
预期结果
- material_qty_report(:qrId).status='REPLENISHED'、handle_type='REPLENISH'、handle_doc_no 非空、handled_at>0。
- 新增 material_request:source='REFILL'、req_qty=3、status='PENDING'、order_no='WO20260920'。
- alert(type='qty_diff',ref_id='QR:qrId') 由 UNREAD 置 READ。
数量等式断言
- 负用例:对少发单 handleType='RETURN' → 错误「本单为「少发」,不能走退库」;handleType='ADJUST' 不填 note → 错误「调整关闭必须填写处理说明」。
- 幂等:已 REPLENISHED 后再 handle → 错误「该差异已处理」。
- SELECT req_qty FROM material_request WHERE source='REFILL' AND order_no='WO20260920' → 3。
清理步骤
- DELETE FROM material_qty_report WHERE id=:qrId; DELETE FROM material_request WHERE source='REFILL' AND order_no='WO20260920'; DELETE FROM alert WHERE type='qty_diff' AND ref_id='QR:qrId';
测试场景 M6:工位组合下发(PLC)
前置数据准备(SQL/API)
- 今日已配工位派工;工单 WO20260920 status 非 DONE/CANCELLED;工件 SN0001 已进线(workpiece 存 sn='SN0001',order_no='WO20260920',status<>'DONE')。
执行步骤(API)
- POST /api/v1/plc/send-process body {"orderNo":"WO20260920","sn":"SN0001","stationNo":1}
- GET /api/v1/plc/send-logs?orderNo=WO20260920
预期结果
- plc_send_log 新增一行:order_no='WO20260920'、sn='SN0001'、status='SENT'、send_time 非空、process_combination 非空。
- 负用例:sn='NOT_EXIST' → 错误「该 SN 不属于当前工单或未进线登记」;今日无工位派工时 → 错误「今日未配置工位派工」。
数量等式断言
- SELECT count(*) FROM plc_send_log WHERE order_no='WO20260920' AND sn='SN0001' → >=1。
- 等待数秒后该行 status 由 SENT 转 DONE、ack=true(模拟完成信号)。
清理步骤
- DELETE FROM plc_send_log WHERE order_no='WO20260920';
测试场景 M7:拧紧查询
前置数据准备(API)
- 上报拧紧:POST /api/v1/torque/report body {"sn":"SN0001","workOrderNo":"WO20260920","stationNo":"1","screwNo":"S1","strain":12.5,"angle":90,"result":"OK"}(重复上报至应拧数,取回某行 id=:tqId)
执行步骤(API)
- GET /api/v1/torque/records?sn=SN0001&stationNo=1
- GET /api/v1/torque/groups?sn=SN0001
- POST /api/v1/torque/audit body {"recordId":":tqId","remark":"已核"}
预期结果
- torque_record 写入 strain=torque=12.5、angle=90、result='OK'。
- groups 返回该组 required=工位拧紧步骤数、actual=记录数、satisfied 与(required>0且actual>=required且ngCount=0)一致。
- 审核后 torque_record(:tqId).audit_by 非空、audit_at 非空;torque_audit_log 新增 action='审核' 行。
数量等式断言
- 负用例:对已审核行再次 POST /torque/audit → 错误「该记录已审核,不可重复审核」。
- SELECT count(*) FROM torque_audit_log WHERE torque_record_id=:tqId → 1。
清理步骤
- DELETE FROM torque_audit_log WHERE torque_record_id=:tqId; DELETE FROM torque_record WHERE sn='SN0001'; DELETE FROM step_data WHERE sn='SN0001';
测试场景 M10:绩效报表
前置数据准备(SQL)
- 确保 workpiece_process 有若干行(operator='张三',station_no='1',result='OK'/'NG',duration_sec,created_at)。
执行步骤(API)
- GET /api/v1/performance?operator=张三&from=2026-09-01&to=2026-09-30
- GET /api/v1/performance/export?startDate=2026-09-01&endDate=2026-09-30
预期结果
- 返回 byOperator/byStation/detail 三口径;导出返回 xlsx 附件流。
数量等式断言
- 任一口径行:doneCount = okCount + ngCount。
- byOperator 某操作人 doneCount = SELECT count(*) FROM workpiece_process WHERE operator='张三' AND created_at 在区间内。
清理步骤
- 无写入,无需清理。
测试场景 M11:巡检终端
前置数据准备(SQL)
- 工单 WO20260920 存在;登录用户持 sys.inspect:process 权限。
执行步骤(API)
- POST /api/v1/inspections body {"category":"PROCESS","stationNo":"1","orderNo":"WO20260920","sn":"SN0001","result":"OK","stepId":1,"stepName":"工序1","measuredValue":"10.2"}
- POST /api/v1/inspections/generate-inter-process body {"orderNo":"WO20260920"}
预期结果
- inspection_record 新增:category='PROCESS'、order_no='WO20260920'、result='OK'、step_name='工序1'、measured_value='10.2'。
- generate-inter-process 返回列表条数 = 该工单 category='PROCESS' 记录数。
数量等式断言
- 负用例:category='XXX' → 错误「记录类型不合法」;无权限用户提交 PROCESS → 403 无操作权限。
- 返回条数 = SELECT count(*) FROM inspection_record WHERE category='PROCESS' AND order_no='WO20260920'。
清理步骤
- DELETE FROM inspection_record WHERE order_no='WO20260920' AND category='PROCESS';
测试场景 M12:质量检验
前置数据准备(SQL)
- 已有一条检验记录 inspection_record(id=:insId,含 report_no/material_code/result='NG')。
执行步骤(API)
- GET /api/v1/quality/bom-material?keyword=M-A
- POST /api/v1/quality/disposal body {"id":":insId","disposalType":"退货","disposalRemark":"来料不良"}
- GET /api/v1/quality/unqualified-report?id=:insId
预期结果
- bom-material 返回 material_code 或 name 命中 M-A 的行(<=200)。
- inspection_record(:insId).disposal_type='退货'、disposal_remark='来料不良'。
- unqualified-report 返回 xlsx 附件流(表头含报检单号/图号/结论/处置方式)。
数量等式断言
- 负用例:disposalType='报废' → 错误「处置类型仅支持 退货/返修/退换」;unqualified-report 缺 id → 错误「缺少 id」。
清理步骤
- UPDATE inspection_record SET disposal_type='',disposal_remark='' WHERE id=:insId;(或随检验数据一并清理)
测试场景 M16:生产流程卡
前置数据准备(SQL)
- 工件 SN0001 已上线,workpiece_process 有报工行;(可选)torque_record 有拧紧行。
执行步骤(API)
- GET /api/v1/process-card?sn=SN0001&check=1
- GET /api/v1/process-card?sn=SN0001
预期结果
- check=1 返回 JSON {sn,missing:[...]};无 check 返回 text/html 流程卡。
- SN 无追溯数据时返回错误「该SN无追溯数据」。
数量等式断言
- 无任何报工时 missing 含「工序报工记录」;路径需拧紧但无拧紧数据时 missing 含「拧紧数据」。
清理步骤
- 无写入,无需清理。
测试场景 M18:预警中心
前置数据准备(SQL)
- 无(直接建规则)。
执行步骤(API)
- POST /api/v1/alert/rule body {"name":"拧紧不合格","type":"torque_ng","threshold":1,"receiver":"班组长","enabled":true}(取回 id=:ruleId)
- POST /api/v1/alert/emergency body {"stationNo":1,"orderNo":"WO20260920","remark":"设备异常"}
- GET /api/v1/alerts?status=UNREAD(取回应急呼叫预警 id=:alertId)
- POST /api/v1/alert/read body {"id":":alertId"}
预期结果
- alert_rule 新增一行 name='拧紧不合格'、type='torque_ng'、enabled=true。
- alert 新增一行 type='emergency_call'、ref_type='station'、ref_id='1'、status='UNREAD'。
- 标记已读后 alert(:alertId).status='READ'。
数量等式断言
- 负用例:POST /alert/rule {"name":"","type":""} → 错误「规则名称与类型必填」;POST /alert/emergency {"stationNo":0} → 错误「缺少工位号」。
- SELECT count(*) FROM alert WHERE type='emergency_call' AND ref_id='1' AND status='UNREAD' → 标记已读前 1、后 0。
清理步骤
- DELETE FROM alert WHERE type='emergency_call' AND ref_id='1'; DELETE FROM alert_rule WHERE id=:ruleId;
测试场景 M8:工艺流程
前置数据准备(SQL)
- 确保工位存在:INSERT INTO station(station_no,name,status,is_builtin,has_dock) VALUES(1,'工位1','ENABLED',true,true) ON CONFLICT DO NOTHING;
执行步骤(API)
- POST /api/v1/process-flows body {"name":"测试流程","stations":[1],"status":"ACTIVE","steps":[{"seq":1,"name":"工序1","collectType":"MANUAL","isTorque":true,"criteria":[{"name":"扭矩","unit":"N·m","logic":"RANGE","min":10,"max":15}]}]}
- GET /api/v1/process-flows?stationNo=1(取回 flowId=:flowId)
预期结果
- process_flow 新增 name='测试流程'、status='ACTIVE';station(station_no=1).flow_id=:flowId。
- process_step 新增 1 行 flow_id=:flowId、is_torque=true;step_criterion 新增 1 行 logic='RANGE'、min=10、max=15。
数量等式断言
- SELECT count(*) FROM process_step WHERE flow_id=:flowId → 1(与提交步骤数一致)。
- 负用例:POST /process-flows {"name":"","stations":[]} → 错误「流程名称必填」;stations:[9999](不存在)→ 错误「工位 9999 不存在」。
清理步骤
- DELETE FROM step_criterion WHERE step_id IN(SELECT id FROM process_step WHERE flow_id=:flowId); DELETE FROM process_step WHERE flow_id=:flowId; UPDATE station SET flow_id=0 WHERE station_no=1; DELETE FROM process_flow WHERE id=:flowId;
测试场景 M13:在制品
前置数据准备(SQL)
- INSERT INTO workpiece(sn,order_no,status,online_at) VALUES('SN0001','WO20260920','ONLINE',now());
执行步骤(API)
- GET /api/v1/workpieces/in-process?orderNo=WO20260920
预期结果
- 返回列表含 sn='SN0001'、status='ONLINE'、currentStation=0、currentStationName='上线位(虚拟)'。
数量等式断言
- 返回条数 = SELECT count(*) FROM workpiece WHERE order_no='WO20260920' AND status IN('ONLINE','PROCESSING','REPAIR')。
- 完工(status='DONE')后该 SN 不再出现在列表。
清理步骤
- DELETE FROM workpiece WHERE sn='SN0001';
测试场景 M14:手动报工
前置数据准备(SQL/API)
- 工单 WO20260920 存在;工位1绑定启用流程(含工序1考核标准);清场 DELETE FROM workpiece WHERE sn='SN0001';
执行步骤(API)
- POST /api/v1/workpiece/online body {"sn":"SN0001","orderNo":"WO20260920"}
- POST /api/v1/workpiece/process/report body {"sn":"SN0001","processCode":1,"stationNo":1,"steps":[],"binds":[本工序应绑物料]}
- POST /api/v1/workpiece/done body {"sn":"SN0001","batchItems":["BA001"],"serialItems":["SNB0001"]}
预期结果
- 上线后 workpiece(sn='SN0001').status='ONLINE'、online_at 非空。
- 报工后 workpiece_process 新增 process_code=1、result 根据考核判定;workpiece.status='PROCESSING'、current_process=1。
- 完工后 workpiece.status='DONE'、done_at 非空;association_trace 新增 finished_sn='SN0001'。
数量等式断言
- 负用例:未完工序1直接报 processCode=2 → 错误「工序顺序不符」;缺料报工 → 错误含缺料清单。
- SELECT status FROM workpiece WHERE sn='SN0001' → 'DONE';SELECT count(*) FROM association_trace WHERE finished_sn='SN0001' → 1。
清理步骤
- DELETE FROM association_trace WHERE finished_sn='SN0001'; DELETE FROM step_data WHERE sn='SN0001'; DELETE FROM workpiece_process WHERE sn='SN0001'; DELETE FROM workpiece WHERE sn='SN0001';
测试场景 M17:物料档案(MES)
前置数据准备
- WMS 可达;管理员 token。
执行步骤(API)
- POST /api/v1/product-types body {"code":"CP002","name":"测试产品2","spec":"S","unit":"台","category":"成品","isActive":true}
- GET /api/v1/product-types/page?code=CP002
预期结果
- WMS materials 新增 item_type=3、code='CP002' 行(经内部接口);分页查询返回该型号且 wmsOnline=true。
数量等式断言
- 负用例:POST /product-types {"name":"","spec":"","unit":""} → 错误「名称为必填项」;DELETE 被工单引用的型号 → 错误「该物料档案已被 N 张工单引用」。
清理步骤
- 经 WMS 删除 CP002(或 DELETE FROM materials WHERE code='CP002')。
测试场景 M19:附件中心(MES)
前置数据准备
- 准备一份小于上限的文件;bizType='work_order'、bizId='WO20260920'。
执行步骤(API)
- POST /api/v1/attachment/upload(multipart)字段 bizType=work_order、bizId=WO20260920、file=<文件>
- GET /api/v1/attachment/all?bizType=work_order&bizId=WO20260920
- POST /api/v1/attachment/delete body {"id":":attId"}
预期结果
- attachment 新增 biz_type='work_order'、biz_id='WO20260920'、file_md5 非空、deleted=false;中心返回含 disk 磁盘状态。
- 逻辑删除后 attachment(:attId).deleted=true。
数量等式断言
- 同业务同 MD5 重复上传:SELECT count(*) FROM attachment WHERE biz_id='WO20260920' AND file_md5= AND deleted=false → 1(传两次仍1)。
- 负用例:bizType 或 bizId 为空上传 → 错误「bizType 与 bizId 必填」。
清理步骤
- DELETE FROM attachment WHERE biz_id='WO20260920'(并清理落盘文件)。
测试场景 M20:操作日志(MES)
前置数据准备
- 以普通用户(非 SUPER_ADMIN)登录取 token;该用户名下有至少一条 event_log。
执行步骤(API)
- GET /api/v1/event-logs?page=1&pageSize=20(带普通用户 token)
预期结果
- 返回 list/total/isAdmin;isAdmin=false 时 list 仅含当前用户(operator 为本人用户名或中文名)的日志。
数量等式断言
- 非管理员时:list 中每条 operator ∈ {当前用户 username, name};显式传 operator=他人 仍只返回本人日志。
清理步骤
- 无写入,无需清理。
测试场景 M21:账号管理(MES)
前置数据准备(SQL)
- 取一非内置角色 id=:roleId;清场 DELETE FROM user WHERE username='mes01';
执行步骤(API)
- POST /api/v1/users body {"username":"mes01","password":"abc123","name":"测试员","roleId":":roleId","status":"ENABLED"}
预期结果
- user 新增 username='mes01'、password 为 bcrypt 密文(非明文)、role_id=:roleId、status='ENABLED'。
数量等式断言
- 负用例:password='123456'(纯数字)→ 错误「密码不能是纯数字」;password='abc'(<6)→ 错误「密码长度至少 6 位」。
- 内置保护:DELETE /api/v1/users/:adminId(admin)→ 错误「系统内置管理员账号不允许删除」。
清理步骤
- DELETE FROM user_station WHERE user_id=(SELECT id FROM user WHERE username='mes01'); DELETE FROM user WHERE username='mes01';
测试场景 M22:角色管理(MES)
前置数据准备(SQL)
- 清场 DELETE FROM role WHERE code='TEST_ROLE';
执行步骤(API)
- POST /api/v1/roles body {"name":"测试角色","code":"TEST_ROLE","remark":"","permissionCodes":["workorder:view"]}(取回 id=:roleId)
预期结果
- role 新增 name='测试角色'、code='TEST_ROLE'、permission_codes 含 'workorder:view'。
数量等式断言
- 负用例:PUT /api/v1/roles 改 SUPER_ADMIN 权限 → 错误「内置管理员角色不允许修改其权限」;改 code → 错误「角色编码创建后不可修改」。
- DELETE 内置角色(SUPER_ADMIN/OPERATOR/INSPECTOR)→ 错误「内置角色不允许删除」;DELETE 有用户的角色 → 错误「该角色下仍有用户」。
清理步骤
- DELETE FROM role WHERE id=:roleId;
测试场景 M23:修改密码(MES)
前置数据准备(SQL)
- 用户 mes01 已存在(M21),密码 abc123;以 mes01 登录取 token。
执行步骤(API)
- POST /api/v1/user/change-password(带 mes01 的 Authorization)body {"oldPassword":"abc123","newPassword":"xyz789"}
预期结果
- user(mes01).password 更新为新密文;用 xyz789 可登录,abc123 失效。
数量等式断言
- 负用例:oldPassword 错误 → 错误「旧密码不正确」;newPassword='123456'(纯数字)→ 错误「密码不能是纯数字」;newPassword 不足6位 → 错误「新密码长度至少 6 位」。
清理步骤
- 随 M21 清理删除 mes01。
工位终端(前缀 /api):登录免鉴权 POST /api/auth/login 取 token,其余 /api/* 带 Authorization: Bearer 。终端多为代理,断言落在 MES(PostgreSQL)与终端本地库(SQLite)两侧。
测试场景 S1:工位登录/登出(工位终端)
前置数据准备(SQL)
- MES 侧确保用户 ws01:INSERT/UPDATE "user" SET status='ENABLED', can_login_workstation=true, workstation_password=bcrypt('ws123456') WHERE username='ws01'(取回其 id 记为 :uid);user_station 授权工位1。
执行步骤(API)
- POST /api/auth/login body {"username":"ws01","password":"ws123456","stationNo":1}
预期结果
- code=0;data.accessToken 非空;data.user.username='ws01';data.stations 含 1;data.stationNo=1。
数量等式断言
- 负用例:password 错 → 「用户名或密码错误」;status='DISABLED' → 「账号已禁用」;can_login_workstation=false → 「该账号未开通工位终端登录权限」;stationNo=99(未授权)→ 「该账号无此工位的操作权限」。
- POST /api/station/logout(带 token)body {"username":"ws01","stationNo":1} → code=0;MES event_log 新增1条工位退出日志。
清理步骤
- DELETE FROM user_station WHERE user_id=:uid; DELETE FROM "user" WHERE username='ws01';
测试场景 S2:当前任务(工位终端)
前置数据准备(SQL)
- 工位1存在且 station.flow_id 指向一个 status='ACTIVE' 的 process_flow;工单 WO20260920.status ∈ {CREATED,RELEASED,IN_PROGRESS}。
执行步骤(API)
- GET /api/task/current?stationNo=1(带 ws01 token)
预期结果
- code=0;data.stationNo=1;data.stationName 非空;data.flowActive=true;data.steps 为该流程步骤数组;data.orderNos 含 'WO20260920'。
数量等式断言
- GET /api/task/current?stationNo=999 → 错误「工位不存在或未配置」;stationNo 缺失 → 400「缺少 stationNo 参数」;工单全部为 DONE/CANCELLED 时 data.orderNos 长度=0;data.orderNos 长度≤10。
清理步骤
- 无(只读)。
测试场景 S6:面板绑定(工位终端)
前置数据准备(SQL)
- 工件 SN0001 已上线且关联 WO20260920(workpiece.work_order_id>0, status='ONLINE');bom_item(product_code='CP001', bom_name='默认') 工序1配 M-A(manage_mode='1', unit_qty=2)、工序2配 M-B(manage_mode='2', unit_qty=4)。
执行步骤(API)
- GET /api/bind/panel?sn=SN0001&processCode=1(带 token)
- POST /api/bind/record body {"sn":"SN0001","orderNo":"WO20260920","processCode":1,"stationNo":"1","operator":"ws01","items":[{"materialCode":"M-A","bindValue":"BA001"}]}
预期结果
- panel:data.enabled=true,required 含 M-A(need=2、manageMode='1');record:code=0,data.bound=1,data.panel.required[M-A].boundCount=1、complete=true(结构件至少绑1个批次即齐套)。
数量等式断言
- COUNT(workpiece_bind WHERE sn='SN0001' AND process_code=1 AND material_code='M-A')=1。
- 电气件工序2:绑 M-B 四颗不同SN(SNB0001..SNB0004)后 panel M-B boundCount=4、complete=true;只绑3颗 complete=false、missing 含「需绑定4颗SN,已绑3颗」。
- 负用例:绑 M-C(不在BOM) → 「物料 M-C 不在该产品的物料清单(BOM)中,不能绑定」;工序1绑 M-B(配在工序2) → 「物料 M-B 配置在工序2装配,本工位(工序1)不可绑定」;M-B SN 已被别的工件绑 → 「电气件SN xxx 已绑定到工件 yyy」;同工件同料同值重绑 → 静默去重 data.bound=0。
- 撤销:POST /api/bind/remove body {"sn":"SN0001","processCode":1,"materialCode":"M-A","bindValue":"BA001","operator":"ws01"} → code=0,COUNT(workpiece_bind …M-A…)=0;该工序已报工后撤销 → 「该工序已报工,不能撤销装配绑定」。
清理步骤
- DELETE FROM workpiece_bind WHERE sn='SN0001';
测试场景 S7:移料/叫料(工位终端)
前置数据准备(SQL)
- 点位台账已初始化(line_point 含 point_no 'IN'/'1'/'2'/'1-R1' 等);UPDATE line_point SET occupied=true, current_sn='SN0001', order_no='WO20260920' WHERE point_no='1';UPDATE line_point SET occupied=false, current_sn='' WHERE point_no='2'。
执行步骤(API)
- POST /api/line/occupy body {"pointNo":"IN","sn":"SN0001","orderNo":"WO20260920","action":"IN"}
- POST /api/line/move body {"sn":"SN0001","fromPoint":"1","toPoint":"2","orderNo":"WO20260920"}(记返回 data.cmdNo=:cmdNo)
- POST /api/line/move/arrive body {"cmdNo":":cmdNo"}
- POST /api/line/call-material body {"stationNo":1,"dock":"1-R1","orderNo":"WO20260920","items":[{"materialCode":"M-A","materialName":"结构件","unit":"件","manageMode":"1","qty":5}]}
预期结果
- move:data.cmdNo 以 'MV' 开头、data.status='ISSUED';arrive 后 line_point '1' occupied=false current_sn=''、'2' occupied=true current_sn='SN0001';plc_move_cmd(:cmdNo).status='CLOSED';call-material:data=1(创建条数),material_request 新增1条 source='REFILL'、need_agv=true、target_dock='1-R1'、station_no=1、status='PENDING'、req_qty=5。
数量等式断言
- COUNT(material_request WHERE source='REFILL' AND station_no=1 AND target_dock='1-R1')=1。
- 负用例:move fromPoint=toPoint → 「起点位与目标点位相同」;起点位非该工件 → 「起点位当前不是该工件,无法移出」;目标被占用 → 「目标点位被占用:X」;occupy 点位被别的工件占用 → 「点位 X 已被工件 Y 占用」;call-material dock='2-R1'(属工位2)→ 「该工位无此接驳台:2-R1」;items 空 → 「请添加叫料明细」;明细 materialCode 空或 qty<=0 被忽略,全无效 → 「没有有效的叫料明细(物料编码与数量必填)」。
清理步骤
- DELETE FROM plc_move_cmd WHERE cmd_no=:cmdNo; DELETE FROM material_request WHERE request_no=:reqNo; UPDATE line_point SET occupied=false, current_sn='', order_no='' WHERE point_no IN ('1','2','IN');
测试场景 S9:巡检(工位终端)
前置数据准备(SQL)
- ws01 已登录取 token。
执行步骤(API)
- POST /api/inspection/upload body {"filename":"chk.png","data":""}(记 data.filename=:photo)
- POST /api/inspection/submit body {"category":"POINT","stationNo":1,"orderNo":"WO20260920","sn":"SN0001","shift":"白班","result":"OK","remark":"点检正常","photo":":photo","operator":"ws01"}
预期结果
- upload:code=0,data.filename 形如 '日期/chk.png',data.url='/api/v1/files/日期/chk.png';submit:code=0,message='巡检记录已提交';inspection_record 新增1条 category='POINT'、station_no='1'、order_no='WO20260920'、result='OK'。
数量等式断言
- COUNT(inspection_record WHERE order_no='WO20260920' AND category='POINT')=1。
- 负用例:category 空 → 400「记录类型 category 必填」;非法 category(如 'XXX')→ 被拒;upload data 空 → 「图片数据为空」;result 留空 → 默认 'OK'。
清理步骤
- DELETE FROM inspection_record WHERE order_no='WO20260920' AND category='POINT';
测试场景 S10:数量不符上报(工位终端)
前置数据准备(SQL)
- ws01 已登录;工位1收到 M-A 应发10实收8。
执行步骤(API)
- POST /api/qty-report body {"stationNo":1,"orderNo":"WO20260920","sn":"SN0001","materialCode":"M-A","materialName":"结构件","planQty":10,"actualQty":8,"cause":"运输损耗","operator":"ws01"}
预期结果
- code=0,message='数量不符已上报';material_qty_report 新增1条 plan_qty=10、actual_qty=8、diff_qty=2、status='PENDING';alert 新增1条 type='qty_diff'、ref_id='QR{新单id}'、status='UNREAD'。
数量等式断言
- diff_qty = plan_qty - actual_qty = 10 - 8 = 2;COUNT(alert WHERE type='qty_diff' AND ref_id='QR{id}')=1。
- 负用例:materialCode 空 → 400「物料编码必填」;stationNo<=0 或 materialCode 空(MES侧)→ 「工位号与物料编码必填」。
清理步骤
- DELETE FROM material_qty_report WHERE id=:id; DELETE FROM alert WHERE type='qty_diff' AND ref_id='QR{id}';
测试场景 S11:退料(工位终端)
前置数据准备(SQL)
- ws01 已登录;工位1有 M-A 余料3件退库;WMS 服务可达。
执行步骤(API)
- POST /api/return-material body {"orderNo":"WO20260920","stationNo":1,"sn":"SN0001","materialCode":"M-A","materialName":"结构件","spec":"S1","qty":3,"unit":"件","reason":"工序余料","operator":"ws01"}
预期结果
- code=0,message='退库单已提交';MES event_log 新增1条 event_type='station.return_material';WMS return_order 预建1条待确认退库单(material_code='M-A'、qty=3、待确认状态)。
数量等式断言
- 负用例:qty<=0 → 400「退库数量必须大于 0」;materialCode 空 → 400「物料编码必填」;MES侧 stationNo<=0 或 qty<=0 或 materialCode 空 → 「工位号、物料编码、退库数量必填」;退库单确认收货前 WMS 库存不变(仅预建待确认)。
清理步骤
- WMS:删除预建退库单(return_order WHERE …);MES:DELETE FROM event_log WHERE event_type='station.return_material' AND entity_id='M-A';
测试场景 S12:应急呼叫(工位终端)
前置数据准备(SQL)
- ws01 已登录。
执行步骤(API)
- POST /api/emergency/call body {"stationNo":1,"orderNo":"WO20260920","remark":"设备异响","operator":"ws01"}
预期结果
- code=0,message='应急呼叫已发送';alert 新增1条 type='emergency_call'、title='应急呼叫'、ref_type='station'、ref_id='1'、status='UNREAD'、content 含「工位 1 发起应急呼叫(工单 WO20260920):设备异响」;event_log 新增1条 event_type='station.emergency'。
数量等式断言
- COUNT(alert WHERE type='emergency_call' AND ref_id='1' AND status='UNREAD')=1;负用例:stationNo<=0 → 「缺少工位号」。
清理步骤
- DELETE FROM alert WHERE type='emergency_call' AND ref_id='1';
测试场景 S13:工艺文件查看(工位终端)
前置数据准备(SQL)
- 工艺 PDF 已上传产线服务文件库(如 2026-09-16/xxx.pdf);ws01 已登录。
执行步骤(API)
- GET /api/pdf?name=2026-09-16/xxx.pdf(带 token);或免鉴权 GET /pdfview?name=2026-09-16/xxx.pdf
预期结果
- HTTP 200,Content-Type=application/pdf,响应头 X-Original-Filename 为文件名;首次 X-PDF-Cache=miss(回源),二次 X-PDF-Cache=hit(本地缓存)。
数量等式断言
- 负用例:name 空 → 400「缺少文件名」;name='../../etc/passwd' 或含反斜杠 → 400「非法文件名」;name 段数>8 → 「非法文件名」;产线服务不可达 → 502「工艺文件获取失败:…」。
清理步骤
- 删除本地 PDF 缓存文件(可选)。
测试场景 S14:工作量查询(工位终端)
前置数据准备(SQL)
- workpiece_process 插入 ws01 在工位1 于 2026-09-20 的报工实绩:工序1 完成5件(result OK 4条/NG 1条)、duration_sec 合计3600。
执行步骤(API)
- GET /api/workload?operator=ws01&stationNo=1&from=2026-09-20&to=2026-09-20(带 token)
预期结果
- code=0;聚合行(operator=ws01,stationNo=1,date=2026-09-20,processCode=1):doneCount=5、okCount=4、ngCount=1、totalDurationSec=3600、avgDurationSec=720、efficiency=5.0。
数量等式断言
- avgDurationSec = totalDurationSec/doneCount = 3600/5 = 720;efficiency = doneCount/(totalDurationSec/3600) = 5/(3600/3600) = 5.0;stationNo 空 = 统计该操作人全部工位;from/to 按 created_at 过滤(to 含当日)。
清理步骤
- DELETE FROM workpiece_process WHERE operator='ws01' AND order_no='WO20260920';
测试场景 S15:离线同步(工位终端)
前置数据准备(SQL)
- 工位终端本地库(SQLite)可用;模拟产线服务不可达(关停 MES 或断网)。
执行步骤(API)
- 断网时 POST /api/report/process-done body {"sn":"SN0001","orderNo":"WO20260920","processCode":1,"stationNo":1,"operator":"ws01","steps":[]}
- GET /api/sync/stats
- 恢复产线服务后 POST /api/sync/flush
预期结果
- 断网报工:code=0,data.queued=true;本地 report_queue 新增1条 kind='process_done'、synced=0;sync/stats:data.pendingQueue≥1。恢复后 sync/flush:code=0,data.count=本次成功条数(≥1),report_queue 该条 synced=1,sync/stats pendingQueue 归0。
数量等式断言
- MES 不可达 → report_queue 待同步数 +1、queued=true;MES 业务拒绝(HTTP200+code!=0,如装配未绑齐)→ HTTP409 直接失败、report_queue 不新增(不入队)。
- 拧紧离线:torque_results synced=0 计数=pendingTorque;同步时 dock_code 'DOCK01' 换算 station_no='1' 上报 /api/internal/torque/report 成功后 synced=1。
- flush 后全部成功时 pendingQueue=0 且 pendingTorque=0;失败条 retry_count +1 且 synced 保持0。
清理步骤
- 本地 SQLite:DELETE FROM report_queue WHERE kind='process_done'; DELETE FROM torque_results WHERE sn='SN0001';