changelog(#5824): 换版后看板缺口与订单车控修复——测试服实测通过(PR #5843)
所有检测均成功
changelog-filename-gate / validate (push) Successful in 2s

这个提交包含在:
API Changelog Bot 2026-08-11 14:53:48 +08:00
父节点 1c022e7943
当前提交 76c446312b

查看文件

@ -0,0 +1,209 @@
---
schema: "hl-changelog/v2"
ticket: "5824"
title: "换版后看板缺口消失 / 订单车控误标 DONE 修复assignmentProgress 新增 staleFinalizedPlan,finalizedByFleet 语义收窄为「已对当前用车需求定稿」"
consumer: "admin"
change_type: "修改接口"
author: "wx(GIT)"
backend_status: "deployed"
gateway_status: "not_required"
frontend_status: "pending"
frontend_owner: "mmg"
frontend_ref: ""
target_release: ""
verified_at: "2026-08-11"
status_note: "后端 PR #5843 已合并 dev-v3 并部署测试服,26-5067 双出口实测通过;前端待接 staleFinalizedPlan 提示与 CANCELED 槽位区分渲染。"
updated_at: "2026-08-11"
base: "dev-v3"
---
# 车务看板:换版后缺口重新露出 + 订单车控不再误标完成(#5824
> **服务**: hl-fleet-service
> **日期**: 2026-08-11
> **含 Flyway**: `20260811.005``fleet_assignment` 新增 `plan_finalized_requirement_id` 定稿归属列)
> **无新增端点 · 无字段删除 · 无请求参数变化 · 无需改网关路由**
---
## 背景(一句话)
`#5810` 换版整槽平移只改派单行的 `requirement_id`,「已定稿」标记原样保留,于是**上一版需求的定稿被当成当前版需求的定稿**
看板把缺口整体抹平(多要的车看不见),订单侧还会据此回发最终方案把车控标成已完成(实测 26-5067 少 1 台车仍 DONE
本次给定稿加上「归属哪一版需求」的证据,读侧据此区分「当代定稿」与「换版平移来的陈旧定稿」,修两个出口:
①看板缺口重新露出下文「变更接口」;②订单车控不再误标完成下文「订单车控状态order-v3 侧)」)。
---
## 变更接口
### `GET /admin/fleet/board/orders` —— `assignmentProgress` 结构变化
#### 1. 新增字段 `staleFinalizedPlan`Boolean,恒非 null
| 取值 | 含义 |
|---|---|
| `true` | 该需求名下**存在任一非终态派单行携带「上一版需求的定稿」**:换版后旧派单被整槽平移过来,车务尚未对这些行按当前需求重新定稿。判据是**析取**——当代定稿行与陈旧定稿行并存(例如部分槽已按新需求重新确认、部分槽仍是平移过来的旧定稿)时同样为 `true`,不要求「只剩」旧定稿 |
| `false` | 全部定稿行都归属当前需求(当代定稿)/ 未定稿 / 历史存量行无归属证据(新列上线前的行,按兼容口径视同当代)/ **携带陈旧归属的行已是终态**`canceled``completed` 一律豁免,不判 `true`|
> 终态豁免的原因:改派与按天改派只把行置为 `canceled`、不清定稿标记,完结行同理不会再被刷进新方案;
> 不豁免就会让这些永远动不了的历史行把需求**永久**钉成「陈旧」(看板永久缺口提示、订单车控永久卡处理中)。
**它只是提示,不一定改判**:只有 `staleFinalizedPlan=true` **且确有缺口**`suggestedSlots` > 现有槽位数)时才撤销定稿权威。
换版只改车型或人数、车数没变时V1 商务车×1 → V2 SUV×1,本字段仍为 `true`,但计数与卡片状态**一律不动**——
车型/数量不符按 `#5810` 已定口径只做提示、不做门禁。
**前端建议**`staleFinalizedPlan=true` 时在卡片上挂一个「需求已换版,请核对车型与数量」的轻提示,配合已有的
`requirementFleetText`(当前需求车队组成语句)让车管一眼比对。不要用它做任何按钮禁用。
#### 2. `finalizedByFleet` 语义收窄
| | 旧语义 | 新语义 |
|---|---|---|
| `finalizedByFleet=true` | 车务已提交最终实派方案(不区分是哪一版需求的方案) | 车务**已对当前用车需求**提交最终实派方案 |
只有当「陈旧定稿 + 确有缺口」同时成立时,`finalizedByFleet` 才由旧口径的 `true` 变成 `false`
其余场景取值不变(含少派:车务对当前需求定稿后少派也仍是 `true`,缺口按实派守恒不再报)。
#### 3. `staleFinalizedPlan=true` 且有缺口时的具体变化
以「V1 商务车×1 已定稿并派车 → 换版为 V2 SUV×2」为例`suggestedSlots=2`,现有 1 个槽位):
| 字段 | 修复前 | 修复后 |
|---|---|---|
| `finalizedByFleet` | `true`(按上一版定稿) | `false` |
| `staleFinalizedPlan` | 无此字段 | `true` |
| `totalSlots` | `1`(按实派守恒) | `2`(按当前需求估算) |
| `unassignedSlots` | `0`(缺口被抹平) | `1`(缺口露出) |
| `canceledSlots` | `0`(定稿分支恒 0 | 按实际取消槽位数返回(可 > 0 |
| 记录级 `assignmentStatus` | `assigned` | `unassigned`(当天/临近出发则 `unassigned_urgent` |
**卡片会换 tab**:这类订单从「已派车」跳到「待派车」(临近出发进急单组)。这是本次修复的目的——
车管必须在待派车里看到它,否则新需求多要的那台车永远没人补。
**`statusCounts` 与列表条数同源一致**`summary.statusCounts``statusOptions``/orders` 列表用的是同一份记录级派生态,
「tab 上有 N 条 → 点进去就是 N 条」这一保证在本次改动后仍然成立(已加回归用例锁定)。
#### 4. ⚠️ 破坏性提醒:`assignmentSlots` 会包含 `CANCELED` 槽位
`assignmentSlots` 原本在 `finalizedByFleet=true` 时会过滤掉已取消的槽位。
由于本次「陈旧定稿 + 有缺口」会把 `finalizedByFleet` 置为 `false`,**这类卡片的 `assignmentSlots` 里会重新出现
`baseAssignmentStatus=canceled` / `assignmentStatus=canceled` 的槽位**(该需求下整槽被取消的历史槽位;
该需求没有整槽取消历史时列表内容不变)。
**前端必须按槽位状态区分渲染**:不要假设 `assignmentSlots` 只含活跃槽位;已取消槽位应置灰或折叠,
不要计入「已派几台车」的展示口径(改用 `assignmentProgress``assignedSlots`/`totalSlots`)。
这类槽位的 `availableActionCodes` 为空、`canAssign=false`,点不动,但会占版面。
---
## `GET /admin/fleet/board/summary` —— 口径变化(字段结构不变)
| 字段 | 变化 |
|---|---|
| `todayDepartCount` | 「今日出团」只数「今天出发 **且记录级已派车**」。陈旧定稿且缺口的卡片记录级已被改判为待派车,**不再计入本数** |
| `pendingCount` / `pendingUrgentCount` | 上述卡片改由这两个数体现(今天出发必然落在 `unassigned_urgent` |
| `statusCounts.unassigned` / `.assigned` | 随记录级派生态同步变化,与 `/orders` 列表同源 |
口径理由:车还没配齐,算作「已派车出团」会让当天出团数虚高、车管漏配车。
---
## 订单车控状态order-v3 侧,定制师页面可见)
本工单修的是两个出口,上面是看板(出口①),这里是订单车控(出口②)。**无接口结构变化**
fleet 不新增/修改端点,变的是 order-v3 既有字段 `vehicle_control_status` 的推进时机。
**触发条件**(两个条件必须同时成立,与看板同一份「陈旧」行级判据):
1. 该需求下存在任一非终态派单行携带上一版需求的定稿(终态行同样豁免);
2. **当前需求车数 > 现有非取消槽位数**(订单侧刻意排除已取消槽位——取消掉的槽位不是交付出去的车,
与看板按槽位视图计数略有差异,这里统一取更保守的一侧,宁可判未完成也不误报完成)。
**结果对比**
| | 修复前 | 修复后 |
|---|---|---|
| 车务确认后的满派判定 | 按「上一版定稿即权威」判已满派(该分支刻意不比对需求车数) | 退回常规拓扑校验,按当前需求的车数 × 服务日核对,缺车即未满派 |
| 是否回发 `DAILY_V3` 最终方案 | 回发 | **不回发** |
| 订单 `vehicle_control_status` | 被标 `DONE`(定制师页面显示派车已完成,实际少一台车,实测 26-5067 | 停在处理中(`PROCESSING`),定制师页面继续显示派车进行中 |
**恢复方式**:车务在看板对**当前需求**重新提交/确认最终方案即可自动恢复——确认时定稿归属会被盖章成当前需求 ID,
陈旧判据随即失效,满派后照常回发最终方案、车控回到 `DONE`。无需人工改库,也不需要运维介入。
**只陈旧不缺车不改判**:车数没变的换版(只改车型/人数)仍按定稿权威判已满派,车控照常推进到 `DONE`
与看板「只提示不改判」保持同一口径。
---
## 详情端点说明(无变化)
`GET /admin/fleet/board/orders/{orderId}` **不返回** `assignmentProgress`,因此也没有 `staleFinalizedPlan`
详情页已有等价且更可操作的信息:`requirementFleetText`(当前需求车队组成)+ `suggestedVehicleCount`(需求车数)
+ `actualVehicleCount`(实派槽位数)。由于本次改判只在「确有缺口」时发生,凡列表把卡片挪进待派车的订单,
详情页必然满足 `suggestedVehicleCount > actualVehicleCount`,两处不会互相矛盾。
---
---
## 验证证据
测试服(`hl-fleet-service` 部署于 2026-08-11 14:44,含 PR #5843 合并提交 `6789811ea`),实测订单 **26-5067**`HL20260809095538772`)。
该单需求 V1 `mpv×1`(已派 1 台并定稿)→ V2 `suv×2`,属于典型「换版后车数变多」场景。
### 存量回填
Flyway `20260811.005` 执行后,该单派单行 `plan_finalized_requirement_id` 精确回填为 **V1 的 requirement_id**,与行上当前 `requirement_id` 不等 → 判定为陈旧定稿。真库统计17 行活跃定稿中 12 行可由确认回执精确反查,其中仅 1 行为真陈旧(即本单),其余存量归属 = 当前需求或留 NULL兼容档,状态无跳变。
### 出口①:看板缺口重新露出
`GET /admin/fleet/board/orders?keyword=26-5067` 实际返回:
```json
"assignmentProgress": {
"totalSlots": 2, "suggestedSlots": 2,
"finalizedByFleet": false, "staleFinalizedPlan": true,
"unassignedSlots": 1, "holdingSlots": 0, "assignedSlots": 1,
"completedSlots": 0, "canceledSlots": 0
}
```
- 记录级状态 `unassigned` / 「待派车」(修复前为 `assigned`「已派车」)
- tab 计数与列表条数一致:`statuses=unassigned` → 1 条;`statuses=assigned` → 0 条
- 与订单侧「部分回配 (1/2)」口径对齐
### 出口②:订单车控不再误标完成
在测试服对该单再提交一次换版V3 `suv×2`,车数不变仍缺 1 台)实测:
| 项 | 修复前V2 换版时) | 修复后V3 换版时) |
|---|---|---|
| fleet 行 `requirement_id` | 平移到新需求 | 平移到新需求(一致) |
| `plan_finalized_requirement_id` | 无此列 | 仍为 V1 → 陈旧 |
| 是否回发 DAILY_V3 最终方案 | 是 | 否 |
| `order_main.vehicle_control_status` | **DONE**(缺 1 台车却判完成) | **PENDING** ✅ |
### 门禁与测试
- 受影响 6 个测试类 666/666 全绿;`FleetRedLineArchTest` 12/12;`spotless:check` 通过
- 全量 3505 用例,剩余失败均为环境相关且已在干净 `origin/dev-v3` 基线复现Testcontainers 并发起容器失败、`ReleaseE``-Dfleet.mysql.releaseE.finalMigrationSha256` 系统属性),低负载单独重跑 32/32 通过
## 已知取舍
`statuses=[unassigned]`(含 `unassigned_urgent`)筛选时,后端为了不漏掉「落库态是 assigned/holding、
但记录级会被改判成待派车」的卡片,会把 DB 候选集扩到 `unassigned + holding + assigned` 再在内存精筛。
副作用:**该筛选条件下扫描量变大,更容易触发 5000 条安全上限**(触发后走游标扫描,或提示「请增加日期或关键词条件」)。
不带状态筛选的默认首屏不受影响(本来就查全状态)。
---
## 前端 checklist
- [ ] `assignmentProgress.staleFinalizedPlan=true` 时展示换版核对提示(不做按钮禁用)
- [ ] 不再假设 `finalizedByFleet=true` 等于「有过最终方案」,语义已收窄为「对当前需求定稿」
- [ ] `assignmentSlots``assignmentStatus` 区分渲染,容忍 `canceled` 槽位出现
- [ ] 「已派几台车」一律取 `assignmentProgress`,不要自己数 `assignmentSlots` 长度
- [ ] 待派车 tab 会新增这类换版订单,确认列表/角标/急单分组渲染正常