# 成品流转对账系统 · 已实施方案 v0.3 更新:2026-09-11。已实施独立采集入口、MQTT 接收与落库确认、SQLite 持久化及实时 Web 对账。正式库从空库启动,旧原型与 MQTT 模拟联调独立保留。真实 MES/码垛/WMS/OQC 接口和参数尚待现场提供,未进行生产联调;摄像头仍未接入。启动、配置和限制以 [README.md](README.md) 为准。 ## 1. 已确认的业务边界 - **整机下线 = 成品下线:同一岗位、同一成功起点**,内部节点统一为 OFFLINE,不重复计数。 - 多条产线没有合流;SN 不复用。SN 按字符串保留,不删除前导零、不自行变更大小写;同 SN 的重复事件不是新产品。 - **工单号 work_order_no、产品编码 product_code 是两个独立字段**,另有唯一产品条码 sn。页面、筛选、导出及报文均分开;型号如需展示另设 model,不借用产品编码。 - 范围为成功下线 → 码垛扫码 → WMS 上抛 → WMS 确认入库(收货报表回采),OQC 入库前借机/还机为旁路。销售出库与库存收发存平衡不在当前原型内。 - MES、码垛、WMS 各有子采集模块;OQC 借还另设子采集模块,不要求由 WMS 产生借机单。 - 摄像头本期仅给查询位置和时间建议,后期接入。 ## 2. 独立采集模块与 Web 模块 MES 子采集 码垛子采集 WMS 子采集 OQC 子采集 └─────────────── MQTT Broker ────────────────┘ ↓ Web 模块的 MQTT 接收服务 ↓ 原始事件库 → 节点投影 / 对账 / 时间规则 ↓ Web API + SSE/SignalR → 浏览器页面 采集模块和 Web 模块独立运行、独立部署。本机试运行版沿用已验证的 JavaScript 规则,采用 Node.js 24 + SQLite + 原生 JavaScript/SSE;不依赖单独安装数据库服务。原先的 ASP.NET Core/PostgreSQL 为建议方案,未作为本轮实际实现;生产容量和运维要求明确后再决定数据库升级。 | 模块 | 职责 | | --- | --- | | MES 采集 | 成功/失败下线记录,SN、工单号、产品编码、产线顺序与业务时间 | | 码垛采集 | 正常扫码、校验结果、确认漏扫、补扫成功/失败及补扫关联;不把反复查询的累计值当新增量 | | WMS 采集 | 上抛回执(成功、明确拒绝、结果未知、撤销)与收货报表回采明细;发送成功不能当入库成功 | | OQC 采集 | 入库前借机/还机,SN、loan_id、借还时刻及来源凭证 | | 采集公共能力 | 独立游标、只读查询、重叠回补、本地持久化待发送队列、稳定事件编号、采集完整水位和心跳 | | Web 服务端 | MQTT 订阅、校验、去重持久化、节点/路线定义、状态重建、超时推测、日结快照、查询与导出 | | Web 浏览器 | 展示服务端投影、证据、筛选、明细及告警;刷新/关闭页面不影响后台持续接收 | “Web 用 MQTT 接收”落在 Web 服务端,服务持续运行。若后续要求浏览器直接订阅,可用 MQTT over WebSocket 读取展示主题,但业务接收、持久化和对账仍由服务端完成。Topic 和完整报文见 [mqtt-contract.md](mqtt-contract.md)。 ## 3. 动态节点与路线 - 业务事件自带 node_id、node_name;遇到新节点自动注册展示列,不需要为每个节点增加数据库物理列。 - **节点号和名称不能说明节点先后。** 通过 route.definition 一次发布完整路线:node_id、node_name、predecessor_ids、required、branch、route_version、effective_at。 - 已知节点按路线关系展示;仅提供名称的新节点列为“待配置链路”,先存证据,不猜边、不触发新节点缺失告警。 - 新增“入库交接”时,同时声明 PALLET → TRANSFER、TRANSFER → WMS;必须一起更新 WMS 的前置关系,不能只发送一个孤立节点。 - 正式版保留全部路线版本;产品成功下线绑定生效路线,旧产品不因新增节点被追溯误报。节点撤下保留旧历史。 - 普通新增节点通过 node.result 的统一状态模型展示;新业务语义(例如新的借还规则)仍需注册规则,名称本身不能生成业务算法。 - 正式后台已按产品下线所带路线版本重建节点及区段超时;新版本不追溯改变旧产品。通过 scripts/publish-routes.js 发布完整路线并等待落库确认。离线原型仍保留演示按钮。 - **明细列语义(上抛 ≠ 入库)**:投影层把路线里的单个 WMS 节点在展示上拆成两列,不新增 MQTT 报文、不改路线定义、不动历史事件的 `node_id`—— - **【WMS上抛】** = `warehouse.result`(中央 MySQL WmsUploadTable 上抛接口回执):回执成功/失败/撤销照实呈现;上抛成功但收货报表超宽限期仍无该条码时显示 `WMS接受异常`;有收货明细只是缺上抛回执时显示 `无上抛回执`(黄,不判异常,避免把「上抛侧没采到」当成现场问题)。 - **【WMS确认入库】** = `receipt.result`(WMS 收货报表回采)或 `oqc.return`(借还机手工入库),是「真的入到 WMS」的唯一判据,与汇总指标「WMS 确认入库」、页签「尚未入库」同口径。入库方式(`成功` / `人工入库` / `还机入库`)标在这一列,不再标在上抛列。取证未覆盖时显示 `收货报表未覆盖期间` / `市场无收货报表覆盖`(黄),明确是取证范围问题而不是现场异常。 - **【码垛扫码】** = PALLET 扫码记录本身(可与 PALLET 导出的 WDS 日志逐条对照);现场大量条码只有 wms-node(中央 MySQL 汇总)侧记录,这类单元格附注 `源:WMS节点`,表示该扫码证据不是现场码垛采集器直采而是汇总补齐。 ## 4. 漏扫、补扫与 A/B/C 定位 统计口径固定为**所选成功下线批次内、按唯一 SN 计台数**: | 指标 | 规则 | | --- | --- | | 累计确认漏扫 | 至少有一条有效码垛 scan.missed 确认记录的 SN 数;补扫后不删除历史 | | 已补扫 | 上述漏扫 SN 中,所有有效漏扫记录都已关联成功 scan.rescan 的 SN 数 | | 待补扫 | 尚有未解决漏扫记录的 SN 数 = 累计确认漏扫 − 已补扫 | | 疑似漏扫 | 从顺序缺口推测出的 SN;与确认漏扫分开展示,不相加 | | 源系统汇总漏扫/补扫 | 按源指定产线、班次、统计窗口展示的累计快照,不能直接加到逐台确认统计里 | 正常扫码事件不冒充补扫;补扫必须明确关联 miss_event_id。失败补扫不减少待补扫;补扫成功也不等于入库。重复补扫消息不重复计数。详细操作次数另按事件保留,不混入台数。 A、B、C 示例:从 MES 同产线、同序号作用域、同路线版本的真实顺序,获得三个实际 SN。码垛已记录 A、C 成功扫码,B 没有扫码时,提示“疑似漏扫 B”;有码垛 B 的漏扫记录后展示“确认漏扫 B”。不能用条码数字加一来编造 B。 推测前先确认数据采集完整,排除 B 的有效借机,等待乱序宽限(原型 2 分钟,仅示例)。不跨线夹取邻机;工单切换若序号连续可跨工单,序号重置必须变更 sequence_scope。前后顺序反转、端点缺失、缓存回流时降级待核查。先用完整同线数据找邻机,再筛选展示,避免搜索 B 时把 A/C 丢掉。 只有汇总漏扫台数、没有具体 SN 时,保留“未定位漏扫”汇总。候选条码只能标疑似,不能把汇总数量强行分配到 B。 ## 5. 来源证据与推测分开 节点完成、失败、漏扫、补扫、借还等事实均由采集记录驱动。Web 生成的是推测或对账结论,附 evidence_level、引用事件、规则版本及有效等待时长,不伪造成采集结果。 | 现有证据 | 页面结论 | | --- | --- | | 下线成功;无码垛扫码/漏扫;有效等待未超时 | 输送中 | | 无扫码/漏扫;采集完整;无有效借机;已超时 | 超时 / 疑似人为拉走,待现场核实 | | A/C 已扫,B 无记录 | B 顺序缺口 / 疑似漏扫;可同时超时 | | 码垛确认漏扫 | 确认漏扫 / 待补扫,不按无记录超时归因 | | 已关联成功补扫,未入库 | 已补扫 / 待入库;累计漏扫历史保留 | | OQC 借出未还 | 借机中,单独跟踪借期;不抵消入库差异 | | OQC 已归还 | 仅归还状态恢复;入库仍等 WMS 成功凭证 | | WMS 明确拒绝 | 业务失败,展示源单据/失败原因 | | 上抛回执成功、宽限期内收货报表没有该条码 | 已正确上抛WMS,但WMS接受异常;不计入已入库,进异常工作台 | | 上抛回执成功、但取证范围未覆盖该条码时段/市场 | 已上抛 · 收货报表无可对照;属取证范围问题,不判异常 | | WMS 已入库但缺扫码 | 账务一致、扫码证据缺失;不自动认定漏扫 | | 采集断连或回补未完成 | 采集不完整,暂缓新的无记录推测;已有确认异常保留 | | 成功下线缺失但有下游记录 | 起点外异常,反向核对,不计成功下线分母 | 原因确认、账务恢复、扫码证据修复分别记录。后台保留首次发现、规则依据、恢复及现场核实的历史,不能因消息迟到悄悄抹掉历史异常。 ## 6. 班次与超时 工厂时区 Asia/Shanghai: - 白班:当日 **07:30:00 ≤ t < 19:30:00**。 - 夜班:当日 **19:30:00 ≤ t < 次日 07:30:00**,业务日归夜班起始日期。 - 例:9 月 12 日 07:29:59 归 9 月 11 日夜班;07:30:00 起归 9 月 12 日白班。 - 入库、补扫跨班时仍回到原成功下线批次;同时可按事件发生班次查操作量,两套口径分别标识。 - 换班不清零在途等待。班次本身不自动减 12 小时,不把夜间视为停线。 有效等待 = 当前时刻 − 上一有效节点时刻 − 与该等待区间重叠的**适用停线/就餐/有效借机区间并集**。 按产线、区段配置固定就餐表;临时停线由指定采集模块上报 line.pause/line.resume,使用 pause_id 配对。尚未恢复的停线扣至当前时刻;停线源失联时标数据不完整,不能永久静默。重叠区间仅扣一次;补到历史停线后重算并记录订正依据,不删除原告警历史。 后台按路线中的 timeout_seconds 和产线/区段计算等待;未配置阈值的节点不产生超时推测。离线与联调样例使用 20 分钟。例:11:55 下线,12:00–13:00 停线,13:10 仍无扫码,75−60=15 分钟,不超时。不能每天给所有产品一律减 60 分钟。 数据源恢复后,先补齐到可靠水位,再按有效时间重算;宽限结束才生成新告警。就餐具体时段尚待现场配置;12:00–13:00 不是已确认的现场时间。 ## 7. 数据存储与追溯 下表为逻辑模型。v0.3 实际使用 messages 存储不同类型的不可变来源事件/路线/心跳/汇总,rejected 隔离错误,cases/case_history 保存异常及核实履历,snapshots 冻结班次;采集进程各自使用 outbox/state 保存队列和游标。节点与业务状态由持久化证据重建,不为每个节点新增物理列。 | 表 | 关键字段 / 用途 | | --- | --- | | product | sn、line_id、work_order_no、product_code、offline_event_id、business_date、shift、route_id/version | | raw_event | source+event_id 唯一、原始载荷、业务/采集/接收时间、source_record_id、node_id、引用关系;只追加 | | node_definition / route_definition | 稳定节点号、名称、来源授权、前置关系、必经/旁路、版本与有效期 | | node_projection | sn+node_id、来源结果、证据引用、计算规则版本;可重建 | | scan_miss / scan_rescan | SN、漏扫事件、补扫关联、成功/失败、来源记录;支持未配对回补 | | loan_transaction | loan_id、SN、借还事件;孤立还机单或矛盾顺序单列核查 | | pause_interval / shift_calendar | 产线/区段、时区、停线起止、来源/配置版本 | | connector_checkpoint / outbox | 源游标、本地待发送消息、完整覆盖水位、心跳、失败与重试 | | discrepancy_case / reconciliation_snapshot | 推测/确认、发现/恢复/核实履历、固定日结快照与最新回溯结果 | 同批次成功下线 SN 数 = 有效入库 SN 数 + 尚未入库 SN 数。工单计划量、扫码操作次数和当日全库入库流水不能混算。消息幂等、撤销、迟到、字段冲突和未知版本处理见 MQTT 约定。 ## 8. 摄像头查询建议(后期接入) 1. 下线证据时刻前后 2 分钟查起点画面,确认实际产品外观/条码方向。 2. 沿该条产线查输送、积放区、人工取机点及码垛入口;用 A、C 通过时刻缩小 B 的候选窗口。 3. 有 OQC 单据时优先核对借还区域和借还时刻,避免把正常抽检误认为拉走。 4. 前后邻机不足时,从最后已知位置查到下一已知节点或告警发现时刻,不能仅查下线摄像头。 5. 后期再维护产线/区域 → camera_id、时间偏差、录像保存期及回放入口。当前页面不宣称已经回放或识别人。 ## 9. 验收与接入准备 已提供:工单/产品编码分列、真实 MQTT 接收及独立采集、漏扫/补扫计台、同线缺口 SN、OQC 旁路、班次筛选、停线扣时、采集健康展示、动态版本节点、来源汇总、异常核实、班次冻结及 CSV 导出。正式入口与 16 台模拟联调入口使用不同数据库。 自动检查:运行 npm test;覆盖原有规则及持久化重启、HTTP 映射、真实 MQTT 落库确认、Web 中断补传、来源校验、载荷冲突、历史路线、单据撤销、更正引用与班次快照时区。浏览器另验证实时台账、字段筛选、详情、核实说明保存及动态展示。 上线前还需:四个来源的真实字段/查询接口与样本、正常输送阈值、白夜班实际就餐时段、停线证据来源、MQTT Broker 地址/账号/证书以及现场联调。已确认的岗位、无合流、SN 不复用与班次边界无需再次确认。 当前边界:已完成本机试运行基础实现;真实源系统专用查询适配、企业登录/角色权限、生产容量与长期稳定性、主备部署尚未现场验证。本机内置 Broker 仅监听回环地址,跨机器使用现场 TLS Broker。不得将模拟联调通过等同生产验收完成。 ## 10. WMS 补上传(已上抛但未成功入库) 对账判定「已正确上抛 WMS,但 WMS 接受异常」的机器(`warehouse.result` 成功、`receipt.result` 缺失且已超宽限期),可按下发接口重新补传,不必再动产线。 - 接口(见《中山TCL项目PLC接口清单表 V1.0》):`PLCCODE` 码垛机条码下发(补上传走这条);`PLCCHECK` 条码核对,用于下发前后确认 WMS 到底有没有收到。均为 POST https + json,地址 `https://api-gw.tcl.com/datahubjson/FluxWmsJsonPlcApi?client_customerid=TCLPLC&messageid=...`,成功判定为 `Response.return.returnFlag == '1'`。 - 报文含 `plcCode / qty / unFlag / udf01~03 / factory_code / system_code / eq_code / snlist / type`。`plcCode` = 虚拟ID/托盘码,清单表写 varchar2(20),但上抛软件实测发的是 **46 位**(时间戳+随机串),`plcCodeMaxLength` 可放宽。 - 默认按「工单 + 产线」分组,一组一个虚拟ID(`plcCodeMode:'order'`,实测整包 `qty>1` 可用)。 - **每批最多 12 台**(`maxBatchQty:12`):同一工单超过 12 台就切成 12/12/… 多批,每批单独发一次 PLCCODE —— 整包台数多时网关会超时。切出来的第 2 批起虚拟ID末尾加批序号(`WO-A-2`、`WO-A-3`),让每批在 WMS 侧是一个独立的新托盘号;要共用同一个号就把 `multiBatchNewPlcCode` 设成 false。 - 另有 `plcCodeMode:'sn'`(1 台 1 托,配 `maxBatchQty:1`、`plcCodePrefix:'B'`)与 `'uid'`(按上抛软件同款 46 位形态现场生成)备用。条码超长时**截头部保尾部**(区分度在流水号),相邻条码不会撞号。 - `unFlag` 非整版标记:补单按 `Y`;若改成 `N` 需先与 WMS 确认(已实测 `Y`/`N` 对本批结果无差别)。 - **`type` 与 `udf01~03` 不在接口清单里**,来自上抛软件(DataCollect)真实报文:`type` 是上抛类型(**2 = 补录**,补上传用它;实测 0~5 与不传该字段,接口层全部返回成功,说明它不改变接口层判定,但按语义应取 2);`udf01~03` 未配时按 **null** 下发而不是空串。 - 选择维度:一台(勾选单个 SN)、多台(勾选多个)、本工单全部补(工单卡片上的独立按钮)、全部(顶部按钮)。 - **勾选范围严格生效**:只下发「当前可见 ∩ 可补上传 ∩ 已勾选」的交集,同工单未勾的台不会跟着下发;下发后结果面板会把本次实际下发的 SN 逐条列出,便于核对。工单卡片上的工单号可点击复制(供 WMS/IT 沟通用),"只看本工单"与"本工单全部补"是两个独立按钮,避免误点整块卡片就下发整工单。 - **只有一道闸**:本地规则拦截(**埋拉机(WMS 已锁定,需在 WMS 另一个系统解锁,补上传无效)、无码垛扫码且未核对过 MES 采集记录(不能排除埋拉)、OQC 借机未还、归属冲突、无成功下线**)在服务端生效,拦下的台不会下发。页面**一次点击即下发**,不再"先校验→再确认→再下发"三段式。 - 页面已去掉「试运行」开关与「包含未超宽限期」开关:候选固定为已超宽限期的接受异常台;试运行只剩后端 `/api/reupload/preview` 与配置 `wmsReupload.dryRun` 两条路。 - 「仅核对(PLCCHECK)」按钮已移除——该接口在 WMS 侧不可用(见下),留着只会误导。 - **下发后要能回答"还要等多久、到底收下没有"**: - `GET /api/reupload/receipt?batch=` → `receiptStatus()`,按批次逐台给出「WMS 收货报表有没有回采到」,并算出采集节拍(`oqc.pollMilliseconds`,默认 30 分钟)、最近一轮时间、下一轮预计时间与剩余秒数。 - 页面据此刻画倒计时进度条,到点自动只读校验一次;也可点「立即采集并校验」先触发 `/api/oqc/collect` 再校验。 - **`returnCode=0000 消息处理成功` 只是网关收单,不等于 WMS 已收货**,所以"下发成功"与"收货确认"必须分开表述。 - **性能红线(踩过)**:`messages` 表已达 **156 万行**。任何 `WHERE json_extract(payload,'$.source')=...` 的聚合都是全表扫描——实测 `SELECT max(received_at) FROM messages WHERE json_extract(payload,'$.source')='oqc'` 单次 **28 秒**;`project()` 冷启动一次 **32 秒**(热态 7~8 秒)。所以: - 收货校验默认**直接读投影缓存**(页面在轮询,缓存基本是热的,实测 0.7 秒),只有 `?fresh=1`(刚触发过采集)才重算; - "最近一轮采集时间"改取 `latestHealth()`(走 `latest_health` 索引,0.2 秒),不再查 `messages`; - 曾经因为连着 4 次触发全量重投影(每次 76 秒)把 Web 服务压到重启,别再这样调。 - **已补上传要进「恢复记录」**:投影层读 `wms_reupload`(只取真实下发,试运行不算),对有成功下发记录的台追加 `recoveries` 项 `code='wms_reupload'`,使其进入「恢复记录」筛选;状态由 `已正确上抛WMS,但WMS接受异常`(bad) 改为 `已补上传 · 待收货确认`(warn),不再挂在异常工作台。`acceptAbnormal` 仍保留为账务事实(WMS 确实还没收货),收货报表回采到该条码后自动转为 `已恢复`/`已入库`。 - **现场实测的两个坑(2026-09-17)**: 1. `factory_code` 虽然文档里没标必填(只有 plcCode/qty/unFlag/sn 标 Y,这三个是"SIT已加"的追溯字段),但 WMS **实际校验非空**:缺了会返回 `ED04802 JSON头信息数据检验异常{factory_code Field cannot be empty}`。`resultInfo` 里才有真正的逐条错误,不能只看 `returnDesc`。 **取值必须来自上抛软件真实报文,不能照猜**:`factory_code="5540"`(= MES/WMS 的组织编码,与 `mes-probe-out.json` 的 `orgCode`、收货报表 `lotatt05` 同源)、`system_code="1"`。之前按 `WHWHJY` / `DataCollect` 下发,WMS 一律判 `#该托条码存在错板` —— **错板的真正原因是这两个码不对,不是旧托问题**。 2. `PLCCHECK` 在 WMS 侧不可用:无论按文档示例传 4 个字段、还是补上设备三件套共 7 个、`barcode` 作键名、`header` 包裹,一律返回 `ORA-06550 PLS-00306 wrong number or types of arguments in call to 'SPEDI_PLCCHECK'` → 是 WMS 存储过程没跟上 SIT 追加字段,**只能等 WMS 侧修**。因此当前 `checkRemote/verifyBeforeUpload/verifyAfterUpload` 均设为 false,重复入库靠账台侧"无收货明细"判断兜底。 - 业务层拒绝要单独识别:接口通了不代表能进库。模块会把 `#该托条码存在错板,不能自动交收。需重新组托,人工交收!` 这类返回翻译成处置提示。 - **2026-09-17 决定性实验**(见 `scripts/probe-type.js`):同一台 SN、同一报文,只换 `factory_code`/`system_code` —— `5540`/`1` 返回 `returnFlag=1 returnCode=0000 消息处理成功 {Success}`,`WHWHJY`/`DataCollect` 仍返回错板。换 3 台**从未下发过**的新 SN 复现:2 台成功、1 台(F017963)即使用正确三码也错板 → 说明大多数是三码配错,**个别台是真错板**。整包 `qty=4` 也成功。 - `type` 不是关键:0/1/2/3/4/5 与省略该字段,接口层返回一致。按语义补上传取 `type=2`(补录)。 - **`returnCode=0000 消息处理成功` 只是网关收单**,不等于 WMS 已收货。同一台下发成功后再次下发会被拒,可作为"已进 WMS"的旁证;最终仍以收货报表回采闭环。 - PLCCHECK 仍不可用,无法下发前后核对;账台侧也没有混产品依据(缺失台与已正常收货台产品编码相同,见 `scripts/probe-order-sku.js`)。 - 默认 `enabled=false` 且 `dryRun=true`:未显式开启时不允许真实下发;预览恒为试运行,只组装报文不发送请求。 - 每次补上传按批次(`RU-` 前缀)落库留痕,`wms_reupload` / `wms_reupload_item` 记录每台的动作、前后核对结果与完整报文,可按批次号或 SN 回读。 - 补上传成功不等于已入库:仍以 WMS 收货报表回采到的 `receipt.result` 闭环。 - **现场实测的第三个坑(2026-09-18):「超时」大多是我们自己把在途请求饿死的** - 现象:连续 4 次点击补上传,前 3 次(14+27+14 台次)全部 `The operation was aborted due to timeout`(`returnCode=null`),第 4 次同样的报文一次成功;单独进程直连同一接口实测 **227~793 ms** 就返回 —— WMS 侧一直是好的。 - 根因:`project()` **同步**执行(冷启动实测 **65.7 秒**、热态约 **8 秒**),而服务 1 秒定时器每 `web.refreshMilliseconds`(默认 5 秒)就调一次 `refresh()`;补上传在途时若被它插一脚,Node 事件循环被整段堵住,发给 WMS 的请求连着十几秒拿不到响应 → 15 秒超时被打满 → 误报"WMS 超时"。**这不是 WMS 挂了,也不是报文问题。** - 处置(已落地): 1. 补上传在途期间(`reuploadBusy>0`)**暂停一切非强制重投影**:定时器直接跳过该轮,`refresh()` 只吃缓存; 2. `/api/reupload/run` 与 `/verify` **不再 `refresh(true)`**(执行前那次全量重投影会把随后所有 WMS 请求堵死),改为 `cache||refresh()`,缓存最多差一个刷新周期,对"哪些台缺收货"的判断无影响; 3. 超时按 `retryOnTimeout`/`retryTimes`(默认额外重试 2 次)自动重试;**只有超时才重试**,WMS 已应答的业务失败(如 999 错板)绝不重试,避免重复下发。 - **错板是 WMS 侧的业务性拒绝,补多少次都一样(2026-09-18 对照实验)** - 工单 `003102032521`(238 台)+ `003102045628`(13 台)共 251 台,返回 `999 #该托条码存在错板,不能自动交收。需重新组托,人工交收!`。 - 三组对照(`scripts/probe-plccode.js`、`scripts/probe-solo.js`):**沿用原报文 / 换成 46 位全新托盘号(与上抛软件同形态)/ 改成一台一托**,三种都返回同一个 999 → 与本机报文形态无关,是这些条码所属托盘在 WMS 里已被判错板,**只能重新组托后人工交收**,接口补不进去。 - 因此新增 `pallet_mix` 拦截:`reuploadLastFailures()` 汇总每台最后一次真实下发被拒的原因,`candidates()` 默认把"错板"台排除(页面给出「N 台错板」的原因明细),不再每轮「全部补」都打一屏红;需要强制再试时由调用方传 `includeWmsBlocked`。 - 对照:同批 `003102047129`(14 台)下发成功,WMS 在 **22 秒后**(14:03:55 下发 → 14:04:17 收货)回采到收货明细,状态自动转 `已恢复` —— 证明配置与报文本身没问题。 ## 文件 - index.html、style.css:页面与样式。 - wms-reupload-ui.js:补上传页面(候选清单、工单一键、校验、试运行与执行、批次历史)。 - flow-model.js:离线证据投影、漏扫/补扫、班次与有效等待计算。 - demo-data.js:16 台虚构采集事件、午休及健康状态。 - app.js:实时 API/SSE 台账、筛选、详情、动态展示、核实说明、快照与 CSV;兼容独立演示模式。 - mqtt-contract.md:建议采用的 v1.0 接入约定,待源系统样本联调。 - collectors/、server/、lib/:独立采集、持久化服务、协议校验与后台投影。 - scripts/、config/*.example.json、examples/、README.md:启动、路线发布、联调与接入配置说明。 - tests/:规则、持久化、HTTP 和 MQTT 端到端验证。