12-work-order-timeline.md 14 KB

12: 工单七态时间线

What to build: 只读展示工单七态和状态时间线。验收驳回表现为“待验收回退处理中”的动作轨迹,不展示延期状态。

Blocked by: 11

Status: resolved Labels: ready-for-agent

  • 工单状态完整展示待派单、待接单、已接单、处理中、待验收、已结案、已取消。
  • 每张工单有独立时间线。
  • 验收驳回显示为回退动作,不是独立终态。
  • 不提供任何工单写操作。
  • 有工单状态映射单测。

进度:100%

已完成并关闭(2026-09-18)。 逻辑闸门 49/49、真机走查 21/21;回归 15 个既有逻辑闸门全绿, 相邻的两个真机走查(t11 设备详情 29/29、t10 点位详情 28/28)也复跑确认共用详情卡没回退。

交付物

  • domain/workOrderTimeline.js(新):七态动作轨迹的纯函数层。 stepAction()(推进/回退/取消/同态/认不出的码)、trackOf()(按 operatedAt 排序、 记 reachedCode 走到过的最远处、数回退次数)、tracksByOrder()(每张工单一条)、 pathView()(七态路径视图)、pathOnlyTrack()(只有路径、没有轨迹的兜底)。
  • components/WorkOrderTimeline.vue(新):单张工单的七态路径 + 动作流水两段。
  • domain/equipmentDetailView.js:新增 timelineSource()(按工单编码配轨迹 + 轨迹分区状态); DEFERRED_SECTIONS 只剩 alarmWarningRelations(timelines 已由本票接手)。
  • components/EquipmentDetailSections.vue:工单面板内每张工单挂它自己那条时间线。
  • mocks/contractMocks.js:默认轨迹补成完整七态 + 一次回退;新增 work-order-timeline (三张工单、三种形状:一路推进 / 待验收回退处理中 / 已取消)与 work-order-timeline-failed (工单列表成功、只有轨迹分区失败)两个场景;/work-orders/*/timeline 改为按编码回显 (认不出的编码抛错,与真接口 404 语义一致)。
  • verify/t12-work-order-timeline.mjs(新,49 项)、verify/t12-work-order-timeline-walkthrough.mjs(新,21 项)。

四条值得记下的判断

1. 「回退是动作」是一条判定,所以它落在纯函数里。 票面原句「验收驳回显示为回退动作, 不是独立终态」在实现上就是「上一条 5 待验收、这一条 4 处理中 ⇒ rollback」。 TIMELINE_ACTION 只有六个取值(start/forward/rollback/cancel/hold/unknown), 没有「已驳回」「延期」这类伪状态;认不出的码(如后端真给了「已驳回」)落 UNKNOWN 且状态名留空 —— 不猜、也不接受第八态。七态路径永远七个节点,驳回不多出一个。

2. 当前态与"走到过的最远处"必须分开,否则回退看不见。 待验收被驳回后当前态是 4, 但"走到过待验收"是事实。只画当前态的话,回退在界面上会消失(看起来像从没到过 5)。 所以 trackOf() 同时给 stateCode(工单自己的当前态)与 reachedCode(走到过最远), 路径视图按两者分别标 current 与 reached。

3. 轨迹分区失败 ≠ 没有动作记录,而七态路径不该跟着一起丢。 两条独立判断,都是"别对值班人员撒谎":

  • 后端 loadTimelines() 会在时间线读不到时给 SOURCE_SECTION_FAILED,工单列表可能照常成功, 所以「轨迹没取到」(timelineSource().placeholder)与「这张工单没有动作记录」 必须说成两句不同的话,模板上是两条不同分支(F9/F10 两条静态守卫钉住);
  • 但七态路径所需的当前状态码就在工单列表分区里,与轨迹成败无关。 所以轨迹没拿到时界面仍铺七态路径(pathOnlyTrack()),只是没有动作流水 —— 因为"轨迹没取到"而连"这张工单现在在哪一态"也一起不画,那是丢掉本来已经拿到的信息。

4.(本轮最值得记的一条)静态守卫与逻辑闸门都盖不住「组件里调了没 import 的函数」。 真机走查第一轮逮到一个真 bug:EquipmentDetailSections.vue 在脚本体里写了 return full || pathOnlyTrack(order),却忘了 import —— 结果是 pathOnlyTrack is not defined 把整块工单面板打没(C 组 4 条判红)。三处闸门当时全绿:静态守卫只查"有没有出现被禁的东西"、 逻辑闸门测的是 domain 层(压根不加载 .vue)、而模板级引用检查看不见脚本体。 补上 F11(模板表达式引用完整性)+ F12(脚本体调用点完整性) 两条守卫, 并且用变异测试确认有牙:去掉那行 import 后 F12 恰好报出 pathOnlyTrack。 (F11 第一版只扫模板,变异测试证明它对本案完全无效 —— 那次假绿已修; F12 第一版又被注释里的 O(n²) 与 JSDoc 里的 timelineSource() 判成假红,已改为先剥注释再扫。) 教训:能加载/能编译 ≠ 能渲染;模板与脚本体要分别守。

与票 11 的接缝

票 11 票面写明「timelines 归票 12」,本票接手时没有新增第七块面板: 七态轨道是工单状态面板里每张工单自己的一条线(验收第 2 条「每张工单有独立时间线」), 所以它挂在 kind: 'orders' 那一块上。票 11 的 F5 断言与措辞已同步更新 (DEFERRED_SECTIONS 现只剩 alarmWarningRelations),t05 的静态守卫面已把本票新增的 一个 domain 与一个组件补进去。

验证

逻辑闸门 49/49(六段:A 七态字典 / B 动作判定 / C 轨迹组装 / D 七态路径视图 / E 分区独立(走真契约 mock)/ F 静态守卫,含 F11/F12 两条引用完整性守卫)。回归 15 个既有 逻辑闸门全绿,其中 t05 从 178 增至 194(守卫面扩容)、t11 仍 61/61。

 node .scratch/cockpit-shell-panel-layout/verify/t12-work-order-timeline.mjs

真机走查 21/21(yarn dev 已在 8000/8001 运行,Chrome 152 headless):

 node .scratch/cockpit-shell-panel-layout/verify/t12-work-order-timeline-walkthrough.mjs

四组,内容从 DOM 读(证明界面真画出来了,而不是 store 里有值):

  • A 每张工单独立时间线(4 项):工单面板画出 3 张(推进 5 步 / 回退 6 步 / 取消 3 步), 且每张的 data-wo-code 与工单编码一一对应;三张步数各不相同(证明不是同一份轨迹抄三遍); 轨迹分区 success、无占位说明。
  • B 七态路径与回退动作(9 项,本票核心):七态节点七个一个不少地画出来 (待派单→待接单→已接单→处理中→待验收→已结案→已取消);路径里没有「已驳回」这种第八个节点; 回退是一行带可见文字标记的记录(「回退 · 自待验收」,不靠颜色也读得出); 当前态=处理中、待验收标 reached(两者可区分);对照那张一路推进的零回退 (故"恰一次回退"不是恒真);已取消标 offPath 且是当前态(不接在已结案后面冒充第八步); 详情里按钮只有「返回点位 / 关闭」(零写操作);无页面错误。
  • C 轨迹失败(5 项):说「工单动作轨迹:该分区加载失败(其它分区不受影响)」, 没有画成「这张工单没有动作记录」(该措辞出现 0 次);七态路径照常画(7 节点); 工单列表照常 success(1 张工单仍在);无页面错误。
  • D 回归(3 项):整卡可见文本零内部代号、零字段路径;打开详情不重建地图;无页面错误。

(四组共 4+9+5+3 = 21 项。)

另复跑相邻两个真机走查确认共用详情卡未回退:t11 设备详情 29/29、t10 点位详情 28/28。

验收对照

验收项 证据
工单状态完整展示七态 逻辑 A1/A3 + D1;走查 B1(DOM 上七个节点)
每张工单有独立时间线 逻辑 C8/E1/E2;走查 A2/A3(三张各自一条、步数不同)
验收驳回是回退动作、不是独立终态 逻辑 B2/C2/C3/C4/D1/D2;走查 B2/B3/B4/B5
不提供任何工单写操作 逻辑 F6;走查 B8(详情里只有返回/关闭)
有工单状态映射单测 逻辑闸门 A–D 四段(49 项中的 22 项)

Answer

**工单七态与状态时间线只读展示完毕:七态路径(七个节点,驳回不多出第八态)与动作轨迹分成两段, domain/workOrderTimeline.js 是纯函数判定层 —— 「验收驳回」由状态码 5→4 判成 rollback 动作而不是终态;当前态与"走到过的最远处"分开记,否则回退在界面上会消失; 「轨迹没取到」与「这张工单没有动作记录」是两句不同的话(模板上是两条不同分支), 但七态路径不跟着轨迹一起丢(当前状态码来自工单列表分区,走 pathOnlyTrack() 照铺)。 不新增第七块面板:轨道挂在工单面板内每张工单下面。逻辑闸门 49/49、真机走查 21/21。**

本票最值得留给后人的一条:静态守卫与逻辑闸门都盖不住「组件里调了没 import 的函数」—— 真机走查第一轮逮到 pathOnlyTrack is not defined(三处闸门当时全绿,因为守卫只查"有没有 出现被禁的东西"、逻辑闸门测的是 domain 层不加载 .vue、而模板级引用检查看不见脚本体)。 已补 F11/F12 两条引用完整性守卫,并用变异测试确认有牙。能加载 ≠ 能渲染;模板与脚本体要分别守。

Comments

2026-09-18 认领与阶段判定

已认领(Status: claimed)。本票无 needs-triage 标签、Blocked by: 11 且票 11 已 resolved, 故不经过诊断阶段,直接进入实施。

2026-09-18 真机走查与收口

用户同意继续后跑了真机走查。第一轮 17/21:C 组 4 条判红,根因是 EquipmentDetailSections.vue 调了 pathOnlyTrack(order) 却忘了 import (pathOnlyTrack is not defined,整块工单面板打没)。补上 import 并新增 F11(模板表达式引用完整性)+ F12(脚本体调用点完整性)两条守卫、变异测试确认有牙后, 第二轮 21/21 通过,逻辑闸门同时升到 49/49。相邻两个走查复跑无回退 (t11 29/29、t10 28/28)。本票据此 close(100%)。

走查需要 Chrome(puppeteer 以 piped stdio 启动子进程),当前沙箱模式会以 spawn EPERM 拒绝;已按一次沙箱升级重试的路径取得授权后跑通。后续他人复跑若遇同一报错, 那是沙箱边界而不是脚本问题。

重开记录(2026-09-29,由票 20 触发)— 需要维护者复核

本票被重开复核,不是要重做,而是因为票 20 按后端权威枚举改动了本票的交付面。 维护者 2026-09-29 裁定:以后端 WorkOrderStatusEnum(0–7)为唯一真理,删除其他不必要的字典。 该裁定落在票 20 里执行,外溢到本票,故按 Q15=(a) 重开记录并提醒维护者。

为什么必须改(后端的权威口径)

CockpitWorkOrderStateDictionary 的类注释原文:

对外公开的工单状态字典,与运行时 WorkOrderStatusEnum(0–7)对齐。 0 待派单、1 待接单、2 已接单、3 处理中、4 待验收、5 已结案、6 延期中、7 已取消; 终态为 5、7,延期中是处理旁路。

com.zksy.ops.enums.WorkOrderStatusEnum 逐项与之一致。而本票当初按旧的 1–7 七态实现, 于是每一态都错位一格:本票把「已结案」标在码 6 上,而后端的 6 是「延期中」、5 才是「已结案」。

本票验收面因此改了三处

本票原验收原句 现状 改为
「工单状态完整展示待派单…已取消」(七态) 0–7 是 8 个码 0–7 共 8 个状态,「七态」这个说法作废
「不展示延期状态」 6 延期中是后端正式状态(ACTIVE_CODES 含它、FINISHED_CODES 只含 5/7) 延期中要展示,且是处理旁路(路径外节点,与已取消同类)
「验收驳回显示为回退动作,不是独立终态」 仍然成立 一字未改(回退仍是动作;码从 4→3)

ADR 0003 里那句「工单只读展示七态…不展示延期」已同步更正(见该 ADR 的 2026-09-29 修订节)。

票 20 实际改了什么(逐条)

  • domain/workOrder.js:WORK_ORDER_STATES 改为 0–7(新增 bypass 标记), WORK_ORDER_CLOSED_CODE 6 → 5;文件头写明旧口径的坑。
  • domain/workOrderTimeline.js:推进主线 1→6 改为 0→5; 新增 WORK_ORDER_BYPASS_CODE = 6 与 TIMELINE_ACTION.BYPASS; stepAction() 里进出 6 判为旁路(既不冒充主线前进、也不算回退); pathView() 输出 bypass 标记。ROLLBACK 的语义与判定一字未改。
  • components/WorkOrderTimeline.vue:文案「七态路径」→「状态路径」, 新增 data-wo-path-bypass 与「(处理旁路)」提示。
  • mocks/contractMocks.js:时间线样例全部按 0–7 重排(每条记录的码都小了一格); 新增 WO-TL-DELAYED(延期中旁路)场景,使旁路分支可演示而不只写在注释里。
  • verify/t12-work-order-timeline.mjs:49 → 52 项。A1/A2/A3 改按 0–7; B6-no-delay-action-exists(守"没有延期档"这条已被后端推翻的假设)改为 B6-delay-is-bypass-action;新增 A2b(6 是旁路非终态)与 D3b(延期中是路径外旁路)。 本票核心的 B2/C2/C3/C4(回退是动作、走到过最远处)逐字保留、仍然全绿。

需要维护者确认的两点

  1. 本票验收第 1 条的文字要不要按 0–7 重写(本票已按新口径实测通过,但票面原句未改);
  2. 「不展示延期」这条原验收是否确认作废 —— 它现在与后端权威枚举直接冲突。

复核通过后本票可再次 close(当前 Status: resolved 保持不变,本段只是如实记录跨票改动)。