Files
bj_power/案例.md
T
SunYF 0d34bcd6a3 docs(spec): 添加仓库管理系统和制造执行系统业务场景文档
- 新增入库登记、入库作废、通用出库、退库确认等仓储管理场景
- 添加来料检验、过程检验、完工检验、库存盘点等质量管理场景
- 实现半成品入库出库、库存查询、工单备料台账等库存控制功能
- 集成AGV配送、区域维护、物料档案等基础数据管理场景
- 完善账号角色管理、操作日志、附件中心等系统管理功能
- 补充登录工作台、库存大屏等用户界面交互场景
- 增加MES工单状态流转、BOM维护、数量不符处理等生产管理场景
- 实现工位组合下发、拧紧查询、绩效报表等制造执行功能
- 添加巡检终端、质量检验、生产流程卡等现场作业场景
- 完善预警中心、看板管理等监控告警机制
2026-09-21 08:21:39 +08:00

147 KiB
Raw Blame History

任务:通读 bj_power 代码,生成业务契约文档

【你要读的代码】

  • bj_power_mes、bj_power_wms、bj_power_workstation 三个 Go 项目的 handler、logic、schema
  • 各项目 frontend/src/pages/*.vue
  • 根目录所有 .md 文档(只作背景,代码是唯一事实)

【文档定位】 这是给业务方和开发看的业务场景流水账,不是代码审计报告。 用业务语言写,人能看懂。不要贴代码,不要标行号,不要贴函数名。

【铁律】

  1. 读代码,但用业务语言描述,不暴露代码细节。
  2. 业务逻辑如果跑不通,记录下来,停止后续操作。
  3. 如何判断逻辑跑不通? 每一步,都有 前置条件、执行过程、产生的数据。如果 本步骤需要的数据条件, 前面步骤无法提供,就是业务流程不通。
  4. 不写"应该怎样"、"建议改成"、"后续优化"。
  5. 不合并场景,不擅自拆分。
  6. 英文枚举名转中文。

【第一步:列场景清单】 通读代码后,列出系统实际支持的全部业务场景。 每个场景写:场景名、从哪个菜单进入、干什么事。 清单只写代码里真实存在的。列完清单后,直接进入第二步,不要停下来等确认。

【第二步:在此文档 写三份 大标题】

文档1:业务流程 顶层地图。主链路 + 异常链路,每个节点指向“案例”的场景编号。 一个节点一句话。不重复场景内容。

文档2:案例 场景流水,禁止写分支,从头到尾只有一条路径,禁止写 "如果...就..."、"否则..."、"另一种情况"。每个场景独立成章,格式:

场景N:场景名

前置条件

操作步骤(角色 + 系统 + 菜单 + 填什么字段(关键字段即可) + 点什么按钮)

系统反应(业务语言,如"库存扣减204颗,锁定量清零")

数据变化(哪个表、哪个字段、变成什么值)

验证点(页面能看到什么、数据库断言什么、数量等式是否成立)

对不上的地方(标红,只标真正对不上的,不标"代码在哪")

文档3:回归测试 可断言用例。按场景分组,格式:

测试场景N:场景名

前置数据准备(SQL 或 API

执行步骤(API 路径 + body

预期结果(表名 + 字段 + 期望值)

数量等式断言

清理步骤

断言必须是具体数值/状态/条数,不能写"页面正常"。

【执行顺序】

  1. 列场景清单
  2. 写业务流程
  3. 写案例(一次一个场景,写完继续下一个,不要停)
  4. 写回归测试

【禁止】

  1. 禁止凭想象写业务逻辑
  2. 禁止把设计文档当事实
  3. 禁止贴代码、标行号、贴函数名
  4. 禁止写"应该"、"建议"、"后续优化"
  5. 禁止模糊断言
  6. 禁止英文枚举直接写文档
  7. 禁止在案例里写代码细节

下面是执行结果输出文本:

第一步:业务场景清单

清单只列代码里真实存在的菜单与功能。分三套系统: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:案例」的场景编号。

主链路(从合同到成品入库)

  1. 建产品型号与 BOM,定义一台产品由哪些物料、多少数量构成(M17、M3)。
  2. 维护工艺流程与工位派工,定义型号要过哪些工序、每道工序在哪个工位干(M8、M9)。
  3. 按合同建工单,一个合同对应一个工单,定下产品型号与数量(M1)。
  4. 对工单做日排产,把工单排到具体生产日并推荐接驳台(M2)。
  5. 按排产生成备料单,算出每个工位、每道工序需要哪些物料、多少量(M4)。
  6. WMS 备料出库,按备料单从库存扣料、装箱、指定目标工位,台账记「已发出」(W5)。
  7. AGV 把料从库房搬到接驳台(W15)。
  8. 工位扫码接料,备料单记「已接料」,在途量清零(S8)。
  9. 工件上线建档,一件产品进入产线(S3、M14)。
  10. 逐道工序报工,采集过程数据、记录拧紧、推进工件工序状态(S4、M14)。
  11. 过程巡检与质检,记录工序间检验结论(S9、M11、M12)。
  12. 末道完工下线,成品经内部接口自动回流 WMS 入库为成品,并按整份 BOM 反冲核销台账「已消耗」(S5)。
  13. 工件追溯与绩效统计,回看全流程、按人/工位汇总工作量(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)。
  • 下发:工位组合下发到 PLCM6)。

文档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-BW17);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_ledgerM-A total_qty=20、out_qty=20M-B total_qty=40、out_qty=40。
  • WMS outbound_order:新增出库单 outbound_type=workorder、status=已出库、order_no=WO20260920outbound_detail 记批次/SN 与数量。
  • WMS inventoryM-A 对应批次 quantity 减 20M-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 = 0receivedQty ≤ 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→1status 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→DONEdoneAt=当前时间。
  • association_trace 新增/更新:finishedSn=SN0001、orderNo=WO20260920、batchItems=[结构件批次]、serialItems=[电气件SN]、operator。
  • work_order(WO20260920)finishedNum +1daily_plancompletedQty +1。
  • WMS inventory:新增成品行 category=3、material_code=CP001、sn_code=SN0001、manage_mode=2、quantity=1、order_no=WO20260920、status=在库。
  • WMS order_material_ledgerM-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:入库作废

前置条件

  • 入库单 IBxxxxM-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=trueinventories 中 batch_no=BA001 的行数=0。
  • 数量等式:作废回滚量 rolledQty = 本单明细数量合计 = 100。

对不上的地方

  • 无。(批次已被出库锁定 locked_qty>0、或在库量已小于本单数量时,作废被拒——属前置校验。)

场景 W6:通用出库

前置条件

  • 结构件物料 M-A 批次 BA002 合格在库、quantity=80、locked_qty=0W3 入库后并经检验合格)。

操作步骤

  • 库管员在「出库管理」选「通用出库」,填:图号 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=50outbound_detail 该单 quantity=30。
  • 数量等式:出库前 quantity − 出库数量 = 出库后 quantity8030=50);可用 = quantity locked_qty ≥ 0。

对不上的地方

  • 无。(批次质量状态非「合格」且出库类别为发货/样品/其他时,出库被拒;可用量不足时被拒——属前置校验。)

场景 W7:退库确认

前置条件

  • 工位已发起退库,WMS 存在一张待确认退库单 THxxxxorder_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=当前时间。
  • inventoriesM-A 批次 BA001(未检、Z1quantity 加回 20(若无匹配行则新建 quantity=20 的行,inbound_no=RET:THxxxx)。
  • order_material_ledger(WO20260920, M-A)returned_qty 0→20;因 out_qtyreturned_qty=0 < total_qty=20status 领料完结→进行中。

验证点

  • 退库确认列表该单状态「已收货」;库存查询 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_qty0 ≤ 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。
  • inventoriesSN SNB0001 quality_status 未检→合格。

验证点

  • 过程检验列表出现 SNB0001 合格记录;库存查询 SNB0001 质量状态「合格」。
  • 数据库断言:inventories sn_code=SNB0001 的 quality_status='合格'。

对不上的地方

  • 无。

场景 W11:完工检验

前置条件

  • 成品 SN(记为 SN0001category=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 可用量)。

对不上的地方

  • 无。

场景 W15AGV 配送

前置条件

  • 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)与其下货架 S1level2)。

操作步骤

  • 库管员在「区域维护」选「批量生成」,选区域 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.statuspayload 含 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=trueevent_log 记 plc.send。

验证点

  • SN 不属于工单被拒;工件已完工被拒;上一条未收到完成信号时下发下一条被拒;下发日志可按工单号/状态查询。

对不上的地方

  • 完成信号为系统内模拟(下发后延时数秒自动置完成),非真实 PLC 握手;对接真实 PLC 时该握手以现场信号为准。

场景 M7:拧紧查询

前置条件

  • 已有拧紧记录(含 SN/工单号/工位/螺丝编号/扭矩/角度/结果);对应工位绑定的工艺流程含「扭矩」考核标准与拧紧步骤。

操作步骤

  • 查询员在 MES「拧紧查询」页按 SN/工单号/工位/日期区间查明细;切「按组汇总」看应拧vs实拧;对某条记录点「审核签字」填备注;点「修改留痕」看审计历史。

系统反应

  • 明细四条件取交集(大小写不敏感),封顶500条。组汇总按 工单号+SN+工位 分组,应拧数=该工位工艺流程拧紧步骤数,满足判定=应拧>0且实拧>=应拧且无不合格;组内任一条未审核则整组未审核。审核签字仅未审核记录可审,重复审核被拒,写审核人/时间并记留痕。上报时扭矩超出「扭矩」范围考核标准判不合格,写步骤数据并触发拧紧不合格预警。

数据变化

  • torque_recordstrain/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_valueevent_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 指向该流程或置0process_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_okassociation_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。

数据变化

  • 主数据落在 WMSmaterials 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-ordersbody{"workOrderNo":"WO20260920","productTypeId":,"productCode":"CP001","productName":"测试产品","quantity":10,"contractNo":"HT001","projectNo":"GC001","productSerial":"01","dueDate":"2026-09-30T00:00:00Z"}

预期结果

  • work_orderworkOrderNo='WO20260920' 新增一行,status='CREATED'、quantity=10、finishedNum=0、failNum=0、contractNo='HT001'。

数量等式断言

  • SELECT count(*) FROM work_order WHERE workOrderNo='WO20260920' → 1finishedNum=0 且 finishedNum ≤ quantity。

清理步骤

  • DELETE FROM work_order WHERE workOrderNo='WO20260920';

测试场景 M2:日排产

前置数据准备

  • 工单 WO20260920 存在(M1)。

执行步骤(API

  • POST /api/v1/daily-plansbody{"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' → 1planDate+orderNo 唯一)。

清理步骤

  • DELETE FROM daily_plan WHERE orderNo='WO20260920' AND planDate='2026-09-20';

测试场景 M9:工位派工

前置数据准备

  • 工位 1、2 存在(station 表)。

执行步骤(API

  • POST /api/v1/station-process/savebody{"effectDate":"2026-09-20","items":[{"stationNo":1,"processCode":1},{"stationNo":2,"processCode":2}]}

预期结果

  • station_processeffectDate='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

  • BOMMES 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 行 SNINSERT 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/generatebody{"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 inventoriesM-A 批次 B001 quantity 100→80M-B 的 40 个 SN status '在库'→'出库'。

数量等式断言

  • material_request.sentQty ≤ reqQtyM-A:20≤20M-B:40≤40)。
  • ledger net_out = out_qty returned_qty ≤ total_qty20≤2040≤40)。
  • SELECT quantity FROM inventories WHERE batch_no='B001' → 80SELECT count(*) FROM inventories WHERE material_code='M-B' AND status='出库' → 40。

清理步骤

  • DELETE FROM material_request WHERE orderNo='WO20260920';
  • WMSDELETE 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/receivebody{"requestNo":"<M-A备料单号>","qty":0}qty=0 表示接本批全部剩余)

预期结果

  • material_request(M-A)receivedQty=20、status='DONE'、receiveBy 非空、receivedAt>0。

数量等式断言

  • 在途 = sentQty receivedQty = 0receivedQty ≤ sentQty20≤20)。

清理步骤

  • 随 M4 清理一并删除 material_request。

测试场景 S3:工件上线

前置数据准备

  • 工单 WO20260920 存在。

执行步骤(API

  • POST /api/v1/workpiece/onlinebody{"sn":"SN0001","orderNo":"WO20260920"}

预期结果

  • workpiecesn='SN0001' 新增一行,status='ONLINE'、currentProcess=0、onlineAt 非空。

数量等式断言

  • SELECT count(*) FROM workpiece WHERE sn='SN0001' → 1sn 全局唯一。

清理步骤

  • 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/reportbody{"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 = endedAtstartedAt = 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/donebody{"sn":"SN0001","batchItems":["B001"],"serialItems":["SNB0001"]}

预期结果

  • workpiece(SN0001)status='DONE'、doneAt 非空。
  • association_tracefinishedSn='SN0001' 新增/更新,batchItems=['B001']、serialItems=['SNB0001']。
  • work_order(WO20260920)finishedNum 由 0→1daily_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_ledgerM-A consumed_qty=2(单台用量2×完工1件)、M-B consumed_qty=4。

数量等式断言

  • work_order.finishedNum=1 且 ≤ quantity(10)。
  • ledger consumed_qty ≤ net_out_qtyM-A:2≤20M-B:4≤40)。
  • SELECT count(*) FROM inventories WHERE sn_code='SN0001' AND category=3 → 1。

清理步骤

  • DELETE FROM association_trace WHERE finishedSn='SN0001';
  • WMSDELETE 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(工序记录条数=已报工工序数)、associationTracebatchItems/serialItems)、torqueRecords。

数量等式断言

  • processTimeline 条数 = SELECT count(*) FROM workpiece_process WHERE sn='SN0001'。
  • associationTrace.finishedSn='SN0001' 与入参一致。

清理步骤

  • 无写入,无需清理。

测试场景 W3:入库登记

前置数据准备(SQL/API

  • WMS 物料档案:POST /api/material/create 建 M-Amanage_mode=1 结构件)、M-Bmanage_mode=2 电气件)。
  • 先建一张合格来料检验单号 INSP-001(可先走 W9 合格检验,或直接 INSERT inspection_record(inspection_no='INSP-001',status='合格'))。

执行步骤(API

  • POST /api/inbound/createbody{"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=falseinbound_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' → 100SELECT 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:入库作废

前置数据准备

  • 入库单 IBxxxxM-A 批次 BA001 数量 100)已登记(W3),且该批次 locked_qty=0、quantity=100。

执行步骤(API

  • POST /api/inbound/voidbody{"inboundNo":"","reason":"录入重复"}

预期结果

  • inbound_order(IBxxxx)voided=true、void_reason='录入重复'、voided_by 非空、voided_at>0。
  • inventoriesbatch_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/generalbody{"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。
  • inventoriesbatch_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/confirmbody{"returnNo":"","returnType":"surplus","zoneCode":"Z1","batchNo":"BA001"}

预期结果

  • return_order(THxxxx)status='RECEIVED'、receive_by 非空、receive_at>0。
  • inventoriesM-A 批次 BA001(未检、Z1quantity 加回 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/createbody{"inspectionType":"来料检验","targetId":"BA003","status":"不合格","inspectQty":100,"passQty":0,"inspector":"王五"}(取回记录 id 记为 :recId

预期结果

  • inspection_record(:recId)inspection_type='来料检验'、status='不合格'、inspect_qty=100、pass_qty=0。
  • inventoriesbatch_no='BA003' quality_status 由'未检'置'不合格'quantity=100。

数量等式断言

  • pass_qty ≤ inspect_qty0 ≤ 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/disposebody{"recordId":,"disposalType":"RETURN","returnTrackingNo":"SF123456","operator":"王五"}

预期结果

  • inspection_record(:recId)disposal_type='RETURN'、return_tracking_no='SF123456'。
  • inventoriesbatch_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/createbody{"inspectionType":"过程检验","targetId":"SNB0001","status":"合格","inspectQty":1,"passQty":1,"inspector":"王五"}

预期结果

  • inspection_record:新增 target_id='SNB0001'、inspection_type='过程检验'、status='合格'。
  • inventoriessn_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/createbody{"inspectionType":"完工检验","targetId":"SN0001","status":"合格","inspectQty":1,"passQty":1,"inspector":"王五"}

预期结果

  • inspection_record:新增 target_id='SN0001'、inspection_type='完工检验'、status='合格'。
  • inventoriessn_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/startbody{"operator":"张三","materialCodes":["M-A"]}(取回 stocktakeNo 记为 STxxxx
  • POST /api/stocktake/recordbody{"stocktakeNo":"","targetId":"BA004","scanQty":48}
  • POST /api/stocktake/finish(需 stocktake:writeback 权限),body{"stocktakeNo":"","adjust":true,"uncountedMode":"keep"}

预期结果

  • stocktake_order(STxxxx)start 后 status='进行中'、total_targets≥1finish 后 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=STxxxxfinish 后 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/inboundbody{"sn":"SEMI0001","materialCode":"M-A","category":2,"doneProcessCodes":[1,2],"zoneCode":"Z1","orderNo":"WO20260920"}
  • POST /api/semi/outboundbody{"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 → 1SN 单件恒为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=20short_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';

测试场景 W15AGV 配送

前置数据准备

  • MES 存在待配送备料单行(requestNo 记为 REQ001targetDock=DOCK01);接驳台 DOCK21、DOCK01 已预置。

执行步骤(API

  • POST /api/agv/submitbody{"sourceDock":"DOCK21","rows":[{"requestNo":"REQ001","materialCode":"M-A","qty":20,"targetDock":"DOCK01"}]}
  • POST /api/agv/refresh(模拟模式下拉取到位)
  • POST /api/agv/cancelbody{"taskNo":"<返回任务号>","reason":"测试取消"}

预期结果

  • agv_task:新增 task_no='AGVxxxx'、source_dock='DOCK21'、target_dock='DOCK01'、status 先'DISPATCHED'refresh 后'DONE'、arrived_at>0cancel 后'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-generatebody{"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/createbody{"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' → 1manage_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/createbody{"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' → 1SELECT password FROM users WHERE username='wms01' ≠ '123456'。
  • 负用例:重复 username → 409password 不足 6 位 → 400。

清理步骤

  • DELETE FROM users WHERE username='wms01';

测试场景 W20:角色管理

前置数据准备

  • 确保 role 中无 code='warehouse'。

执行步骤(API

  • POST /api/rolesbody{"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' → 1permission_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/uploadmultipart/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=1batchCount = SELECT count(*) FROM inventories WHERE manage_mode=1。
  • semiInStock = SELECT count(*) FROM inventories WHERE category=2 AND status='在库'。

清理步骤

  • 无写入,无需清理。

测试场景 W1:登录(WMS

前置数据准备(SQL

  • 确保用户 wms01INSERT 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=0data.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、outTodayshortage 返回 data.list,每项含 materialCode、materialName、spec、safetyStock、availQty、shortQty。

数量等式断言

  • totalQty = SELECT sum(quantity) FROM inventories WHERE manage_mode=1shortage 仅列 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=0outbound_order 新增1条 outbound_type='workorder'、status='已出库'、need_agv=true、material_code='M-A'、batch_no='BA001'inventories(BA001).quantity 由30→20outbound_detail 新增1条 quantity=10order_material_ledger 累计出库量 +10。

数量等式断言

  • 出库前后:inventories(BA001).quantity = 30 - 10 = 20outbound_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→DONEDONE 后 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=1M-B unit_qty=4、process_code=2manage_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-reportX-API-TOKENbody {"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_requestsource='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_recordid=: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=truestep_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/uploadmultipart)字段 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/isAdminisAdmin=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/:adminIdadmin)→ 错误「系统内置管理员账号不允许删除」。

清理步骤

  • 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 的 Authorizationbody {"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 侧确保用户 ws01INSERT/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=0data.accessToken 非空;data.user.username='ws01'data.stations 含 1data.stationNo=1。

数量等式断言

  • 负用例:password 错 → 「用户名或密码错误」;status='DISABLED' → 「账号已禁用」;can_login_workstation=false → 「该账号未开通工位终端登录权限」;stationNo=99(未授权)→ 「该账号无此工位的操作权限」。
  • POST /api/station/logout(带 tokenbody {"username":"ws01","stationNo":1} → code=0MES 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=0data.stationNo=1data.stationName 非空;data.flowActive=truedata.steps 为该流程步骤数组;data.orderNos 含 'WO20260920'。

数量等式断言

  • GET /api/task/current?stationNo=999 → 错误「工位不存在或未配置」;stationNo 缺失 → 400「缺少 stationNo 参数」;工单全部为 DONE/CANCELLED 时 data.orderNos 长度=0data.orderNos 长度≤10。

清理步骤

  • 无(只读)。

测试场景 S6:面板绑定(工位终端)

前置数据准备(SQL

  • 工件 SN0001 已上线且关联 WO20260920workpiece.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"}]}

预期结果

  • paneldata.enabled=truerequired 含 M-Aneed=2、manageMode='1');recordcode=0data.bound=1data.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=0COUNT(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}]}

预期结果

  • movedata.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-materialdata=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"}

预期结果

  • uploadcode=0data.filename 形如 '日期/chk.png'data.url='/api/v1/files/日期/chk.png'submitcode=0message='巡检记录已提交'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=0message='数量不符已上报'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 = 2COUNT(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=0message='退库单已提交'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 …);MESDELETE 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=0message='应急呼叫已发送'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 200Content-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 = 720efficiency = doneCount/(totalDurationSec/3600) = 5/(3600/3600) = 5.0stationNo 空 = 统计该操作人全部工位;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=0data.queued=true;本地 report_queue 新增1条 kind='process_done'、synced=0sync/statsdata.pendingQueue≥1。恢复后 sync/flushcode=0data.count=本次成功条数(≥1),report_queue 该条 synced=1sync/stats pendingQueue 归0。

数量等式断言

  • MES 不可达 → report_queue 待同步数 +1、queued=trueMES 业务拒绝(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。

清理步骤

  • 本地 SQLiteDELETE FROM report_queue WHERE kind='process_done'; DELETE FROM torque_results WHERE sn='SN0001';