---
schema: "hl-changelog/v2"
ticket: "5810"
title: "换版槽位人工制：新增需求语句 requirementFleetText + 派车日期门禁 605062 + 槽位统一原样渲染（取代 #5788 全部旧数据形态）"
consumer: "admin"
author: "wx(GIT)"
change_type: "修改接口"
backend_status: "deployed"
gateway_status: "not_required"
frontend_status: "implemented"
frontend_owner: "mmg"
frontend_ref: "b6f9c44a"
target_release: ""
verified_at: "2026-08-11"
status_note: "前端已实现(b6f9c44a):删 RequirementSupersededCard 组件+utils/requirementSuperseded.js+两 spec,AssignModal/OrderDrawer 摘除标旧块/supersededSlots computed/删除守卫/样式;batchAssignmentSlots 删 superseded 合并/收集/返回(保留 canDelete/deleteBlockReason 合并),槽位统一以 vehicleSlots 为源原样渲染能力不降级;槽位头两处「建议{车型}」chip 删除(改派卡头+派车卡头,派车卡头保留「全程」标记),删 slotChangeRequirementLabel/slotRequiredVehicleLabel;新增 requirementFleetText 常驻需求语句(后端权威文案,null 不渲染),挂原 SupersededCard 位(弹窗级+详情级)。605062 实证:confirm 走全局拦截器透传 msg、batch 走 silentError+catch message.error(error.message),msg 已含引导语,统一透传即满足,前端零改动。保持 housekeeper requirementSuperseded 独立契约/BoardSlotSummary/司机建议/OrderDrawer「需求:」行。fleet/board 35 spec 427 全过,checkpoint 8 文件全绿。注:配套 11_5810b(格式改车队组成写法)与 11_frontend(Step2 需求展示区布局+统一选车弹窗空白缺陷)为独立待办,本字段原样渲染不解析、对格式变更零改动即受益。"
updated_at: "2026-08-11"
base: "dev-v3"
---

# 车务：换版槽位人工制 —— 需求语句 + 派车日期门禁 + 槽位统一原样渲染（#5810）

> **服务**: hl-fleet-service (8087/8187)
> **PR**: #5815
> **Issue**: #5810（取代 #5788 的前端契约）
> **日期**: 2026-08-10 后端部署完成
> **影响范围**: 派单看板订单详情 `GET /admin/fleet/board/orders/{orderId}`（新增 1 字段 + 3 个字段语义退化为恒空）、派车推进类接口新增错误码 605062
> **一句话**: 换版后端不再自动增删槽位、不再区分新旧行，前端也不要再区分——**槽位有什么就原样画什么**；需求变成一句提示语；唯一会拦住车务的是「派车日期不在需求日期窗内」。

---

## 0. 先读这段：为什么推翻 #5788 的做法

#5788（后端 #5784/#5796/#5798/#5802）的老机制是：定制师改用车需求 → 后端按「车型+座位」签名匹配，**匹配上的旧派车行沿用、匹配不上的标 `superseded`（旧数据）、缺的自动补未派占位、多的自动取消**。于是页面上出现了「新槽位 + 旧数据行」两类东西，前端为了表达它反复返工三轮（窄条 → 卡片 → 单独区块），wx 每一轮都不满意。

**2026-08-10 wx 最终口径（本单实现的就是这个）**：

> 「槽位的数据配置啥就是啥，不自动清除数据，不自动增加/减少槽位。只在改出发日期的时候做日期判断，日期不对没法下一步。」

所以后端把整套「签名匹配 / 自动补位 / 自动取消 / superseded 标记」全部删掉，换成**整槽平移**：换版时把该订单下所有未删派车行（含已完成 completed，只排除 canceled）原封不动地重新绑定到新需求 ID 上——槽位数、车辆、司机、日期、价格、状态全不动，只是"户口"迁到新需求名下。存量带 `superseded` 标记的历史行也已由 Flyway 一次性迁移重绑完毕。

**给前端的直接含义**：**再也没有"旧数据行"这个概念了。** 所有槽位都是当前需求下的普通槽位，一视同仁地渲染成普通槽位卡。

---

## 1. 新增字段：`requirementFleetText`（需求语句）

### 接口

`GET /admin/fleet/board/orders/{orderId}` —— **根级新增**（不在 `vehicleSlots` 里）：

| 字段 | 类型 | 可空 | 说明 |
|------|------|------|------|
| `requirementFleetText` | string | 是（需求不可达时 `null`） | 当前有效用车需求的车型语句 |

### 取值规则（后端已实现，前端直接展示即可，**不要自己再拼**）

- 格式：车型项按 `{车型中文标签}×{数量}({座位数}座)` 生成，多项用中文顿号 `、` 连接
- 实例（测试服真实返回）：
  - 26-9313 改需求后 → `"商务车×1(7座)、SUV×1(5座)"`
  - 单车型单辆 → `"商务车×1(7座)"`
- 车型标签走字典 normalize，字典查不到时回退车型原始编码；数量 ≤0 按 1 计；座位数缺失时省略 `(N座)` 后缀

### 前端怎么用

1. **改版提示横幅**：文案 `需求已改版，请对照新需求人工调整` + 紧跟这句 `requirementFleetText`。
   ⚠️ 注意：横幅的**触发条件不再是** `requirementChangePendingCount > 0`（该字段现在恒 0，见 §2）。是否显示横幅由前端自行决定（例如：本次进入派车页时对比上次看到的 `requirementVersion`，或干脆常驻显示"当前需求：xxx"）。**推荐做法**：不做横幅态，直接在派车页/订单概览常驻一行「当前需求：商务车×1(7座)、SUV×1(5座)」，这样任何时候车务都能对照，不依赖任何"变更"状态。
2. **订单概览**：同一句展示，位置见 wx 截图指向的区域（需求信息块）。
3. `null` 时该行整体不渲染（不要显示"当前需求：null"或空冒号）。

---

## 2. ⚠️ 必须删除的旧渲染分支（#5788 遗留）

以下三个字段**契约保留不删**（兼容期，避免前端 500），但**语义已死**，值恒为空：

| 字段 | 位置 | #5810 后的值 | 前端处理 |
|------|------|-------------|---------|
| `requirementChangePendingCount` | 根级 | **恒 `0`** | 删掉依赖它的徽章/横幅触发逻辑 |
| `supersededAssignments` | 根级 | **恒 `[]`** | 删掉整个"旧数据区块/列表"渲染 |
| `vehicleSlots[].superseded` | 槽位 | **恒 `false`** | 删掉 `superseded ? 旧数据卡 : 普通卡` 的三元分支 |
| `vehicleSlots[].supersededByRequirementId` | 槽位 | **恒 `null`** | 无用 |
| `vehicleSlots[].requirementId` | 槽位 | 恒 == 根级 `requirementId` | 无需比对 |

**具体到 mmg 已写的代码**（按 #5788 三轮返工的产物）：

- `SupersededSlotCard`（064d0be2 的旧数据槽位卡组件）→ **整个组件删除**
- "标旧窄条"（a75efc05/b31ac7c3 的窄条渲染）→ **删除**
- 任何 `slot.superseded === true` 的判断、任何把 `supersededAssignments` 单独成区/成行的代码 → **删除**
- 槽位列表数据源：**只用 `vehicleSlots`**，按后端返回顺序原样渲染，每一项都是普通槽位卡（可派车/可换车/可取消/可删除，能力与普通槽位完全一致）

**验收标准**：改需求前后，页面槽位卡数量与内容**完全不变**（除非车务自己动手加/删/改）。26-9313 实测返回 3 个槽位（原商务车 E5555 行、原商务车 S6666 行、新 SUV 空槽），三个都是普通卡，没有任何"旧"标记。

---

## 3. ⚠️ 槽位头去掉「建议 {车型}」标签

槽位头当前显示的「建议 商务车」「建议 SUV」这类标签 —— **去掉**。

原因：槽位人工制下车务爱派什么车派什么车，后端不再按车型校验（车型/数量/人数不符**不拦截**，见 §4），"建议"标签只会让车务误以为必须匹配。车型诉求统一由 §1 的 `requirementFleetText` 在横幅/概览表达一次即可。

字段层面：`vehicleSlots[].requiredVehicleType` / `requiredVehicleTypeLabel` / `requiredSeats` **契约保留**（后端仍返回真实值，其它场景可能用到），但**不要再渲染成槽位头的"建议 xx"标签**。

---

## 4. 新增错误码 605062：派车日期与需求不符（唯一硬门禁）

### 触发场景

定制师**改了出发日期/行程天数**后，需求的日期窗变了，但车务原来配的车还挂在旧日期上。此时车务想推进（发送给司机 / 确认执行 / 提交最终方案），后端拦截：

```json
{ "code": 605062, "msg": "存在派车日期与当前用车需求不符的槽位，请先调整或取消后再继续", "data": null }
```

### 哪些接口会返回

| 接口 | 说明 |
|------|------|
| `POST /admin/fleet/assignments/requirements/{requirementId}/confirm` | 需求级确认执行（preflight + 锁内双重校验） |
| `POST /admin/fleet/assignments/batch` | 批量提交最终实派方案（提交入参日期 + 既有在途行双向校验） |
| 逐日方案提交（`batch` 走 `dailyPlan` 入参） | 同上 |

### 前端处理

1. `msg` 已是**完整可直接展示的中文**，直接弹给用户即可，不要自己另写文案。
2. 建议在提示后附操作引导：`请在槽位列表中删除或改期日期不符的槽位后重试`。
3. **不需要**前端预先算日期做本地拦截（后端是权威，且前端算容易与后端口径不一致）；如果想给视觉提示，可用 `vehicleSlots[].serviceStartDate/serviceEndDate` 与订单 `departDate/endDate` 比对给个黄色角标，但**不要**据此禁用按钮。

### 不拦截的情况（明确告知，避免前端"帮倒忙"加校验）

- **加天数**（需求日期窗**扩大**）：旧派车行日期仍落在扩大后的窗内 → **完全放行**，且**不强制车务把新增的天派满**
- **车型/数量/人数与需求不符** → **不拦截**（降级为提示语句，即 §1 的 `requirementFleetText`）
- 已完成（`completed`）的历史行日期越窗 → **不拦截**（终态行无法调整，不能卡死推进）

---

## 5. 行为变化速查（后端换版语义，供前端理解页面为什么这样）

| 场景 | #5788 老行为 | #5810 新行为 |
|------|-------------|-------------|
| 定制师改车型（2商务 → 1商务+1SUV） | 匹配上的沿用，匹配不上的标旧，缺的自动补占位 | **全部行原样平移**，槽位数不变；SUV 需求只体现在语句里 |
| 定制师改日期 | 同上 + 旧日期行标旧 | 全部行原样平移（日期不动）；推进时 **605062** 拦截，车务手动改期/取消后放行 |
| 定制师加天数 | 自动补新天占位 | 不自动补；车务可自行补派新增天（不补也能推进） |
| 定制师减车辆数 | 自动取消多余未派占位 | **不自动取消**；多出来的槽位由车务自己删（删除能力 #5572 已全放开） |
| 旧存量 superseded 行 | 特殊形态 | Flyway 已一次性迁移重绑为普通行 |

---

## 6. 联调数据（测试服可直接复现）

| 场景 | 订单 | 说明 |
|------|------|------|
| 改车型后的三槽位原样 | 团号 `26-9313`（orderId `2086270153414103041`） | 详情返回 `requirementFleetText="商务车×1(7座)、SUV×1(5座)"`，`vehicleSlots` 3 项、`superseded` 全 false、`supersededAssignments=[]` |
| 605062 拦截 / 放行 | 订单 `2086697882311680002` | 已被本次实测改到 08-28~31 且已确认执行完毕；如需重现 605062，请改该单出发日期后再点确认执行 |

后端实测记录：改车型场景槽位零变动；改日期场景 confirm 返 605062、取消越窗行并窗内重派后 confirm 返 200；加天数场景不拦截，且新增天用他单占用车被 605001 正确拒绝、precheck 如实返回车+司机双 conflicts。

---

## 7. 不变的部分（无需改动）

- `vehicleSlots` 其余字段（`assignmentSlotId`/`fleetItemIndex`/`assignmentGroupId`/`slotStatus`/`canDelete` 等）语义全不变
- 候选查询 `POST /admin/fleet/assignments/candidates` 的 `excludeAssignmentId` 语义不变（#5802 宽容口径保留：同订单不命中的排除项后端忽略并记 WARN，不再报"参数非法"）
- 派车/改派/取消/增删槽位所有既有接口契约不变
- 网关路由无新增

---

## 8. 前端 checklist（做完请把 frontmatter 的 `frontend_status` 改为 `implemented` 并填 `frontend_ref`）

- [ ] 详情响应新增消费 `requirementFleetText`，在派车页/订单概览展示为一行需求语句（null 不渲染）
- [ ] 删除 `SupersededSlotCard` 组件及所有"旧数据"分支渲染（窄条/区块/角标）
- [ ] 删除对 `supersededAssignments`、`requirementChangePendingCount`、`slot.superseded` 的一切依赖
- [ ] 槽位列表数据源统一为 `vehicleSlots`，按序原样渲染为普通槽位卡，能力（派车/换车/取消/删除）不因来源不同而降级
- [ ] 槽位头去掉「建议 {车型}」标签
- [ ] 派车推进类接口（confirm / batch）接住 `605062`，直接展示后端 `msg` + 操作引导
- [ ] 不新增任何前端侧日期硬拦截（不 disable 按钮）
