---
schema: "hl-changelog/v2"
ticket: "5257"
title: "车务看板按用车需求聚合多车型槽位"
consumer: "admin"
change_type: "修改接口"
backend_status: "deployed"
gateway_status: "verified"
frontend_status: "implemented"
frontend_owner: "hl-ui-codex"
frontend_ref: "mmg/hl-ui@594dfa44b497b40a1e75c81b3e60abba8deb6137"
target_release: ""
verified_at: "2026-07-26T11:13:51+08:00"
status_note: "后端 PR #5259 已合并为 dev-v3@01bf9c627，并重新部署测试环境及通过网关复验；本契约明确替代 #5216 的按槽位卡片维度。前端尚未认领，需按需求卡消费 assignmentSlots。"
updated_at: "2026-07-26T03:40:29.152Z"
base: "dev-v3"
---

# 车务看板按用车需求聚合多车型槽位

## 关联

- Issue: [wx/HL#5257](https://git.1814.love:8443/wx/HL/issues/5257)
- 服务: `hl-fleet-service`
- 前端仓库/分支: `mmg/hl-ui` / `v2.1`
- 影响范围: 车务管理 → 派车看板列表、需求卡和需求详情
- 被本契约替代的旧口径:
  [#5216 派车看板补充槽位接送路线与就绪摘要](./24_5216_派车看板补充槽位接送路线与就绪摘要-修改接口-管理后台.md)
  中“每张卡对应一个稳定车辆槽位”的维度说明

## 关键变化

同一订单的一条当前有效用车需求可能同时需要多种车型、多个车辆槽位。例如
`SUV×1 + 商务车×1` 是 **1 条用车需求、合计 2 辆车**，不能显示成 2 条“用车需求”。

`GET /admin/fleet/board/orders` 的 `data.records[]` 从派车槽位粒度调整为当前
active `requirementId` 粒度：

- 同一 `requirementId` 只返回一条 record。
- `requiredVehicles[]` 展示全部车型组及数量。
- `assignmentProgress.totalSlots` 展示合计需要的车辆数。
- 新增 `assignmentSlots[]`，完整保留逐辆派车身份、状态、车辆和司机。
- 原顶层单槽位字段兼容保留，统一表示当前最需要处理的代表槽位。
- 状态、车型或司机筛选命中需求内任一槽位时，需求只返回一次。
- 后端先按需求聚合，再排序和分页；`total` 与汇总接口不再按槽位重复计数。

请求参数、路径、HTTP method、响应包络、错误码、数据库和派车执行语义均不变。

## 变更接口

```http
GET /admin/fleet/board/orders
```

### 响应示例

```json
{
  "code": 200,
  "message": "成功",
  "data": {
    "records": [
      {
        "id": "26-5256",
        "orderId": "2080200000000000000",
        "requirementId": "2080200000000000900",
        "requiredVehicles": [
          {
            "vehicleType": "suv",
            "categoryLabel": "SUV",
            "seats": 5,
            "count": 1
          },
          {
            "vehicleType": "mpv",
            "categoryLabel": "商务车",
            "seats": 7,
            "count": 1
          }
        ],
        "assignmentProgress": {
          "totalSlots": 2,
          "unassignedSlots": 2,
          "holdingSlots": 0,
          "assignedSlots": 0,
          "completedSlots": 0,
          "canceledSlots": 0
        },
        "assignmentStatus": "unassigned",
        "assignmentId": "2080200000000000001",
        "assignmentSlotId": "2080200000000000101",
        "fleetItemIndex": 0,
        "canAssign": true,
        "canRejectRequirement": true,
        "assignmentSlots": [
          {
            "assignmentId": "2080200000000000001",
            "assignmentGroupId": "2080200000000000201",
            "assignmentSlotId": "2080200000000000101",
            "fleetItemIndex": 0,
            "slotSummary": {
              "requiredVehicleType": "suv",
              "requiredVehicleTypeLabel": "SUV",
              "requiredSeats": 5
            },
            "baseAssignmentStatus": "unassigned",
            "assignmentStatus": "unassigned",
            "assignmentStatusLabel": "待派车",
            "availableActionCodes": ["ASSIGN"],
            "vehiclePlate": null,
            "vehicleModel": null,
            "vehicleSeats": null,
            "driverName": null,
            "driverPhone": null,
            "urgentBadge": null,
            "canAssign": true
          },
          {
            "assignmentId": "2080200000000000002",
            "assignmentGroupId": "2080200000000000202",
            "assignmentSlotId": "2080200000000000102",
            "fleetItemIndex": 1,
            "slotSummary": {
              "requiredVehicleType": "mpv",
              "requiredVehicleTypeLabel": "商务车",
              "requiredSeats": 7
            },
            "baseAssignmentStatus": "unassigned",
            "assignmentStatus": "unassigned",
            "assignmentStatusLabel": "待派车",
            "availableActionCodes": ["ASSIGN"],
            "vehiclePlate": null,
            "vehicleModel": null,
            "vehicleSeats": null,
            "driverName": null,
            "driverPhone": null,
            "urgentBadge": null,
            "canAssign": true
          }
        ]
      }
    ],
    "total": 1,
    "page": 1,
    "pageSize": 20
  },
  "success": true
}
```

## 新增字段 `assignmentSlots[]`

所有雪花 ID 必须按字符串消费。

| 字段 | 类型 | 空值 | 说明 |
|---|---|---|---|
| `assignmentId` | `String` | 否 | 逐槽位派车/改派操作使用的派单 ID |
| `assignmentGroupId` | `String` | 否 | 同一派车组 ID |
| `assignmentSlotId` | `String` | 否 | 跨逐日切片稳定的车辆槽位 ID |
| `fleetItemIndex` | `Integer` | 否 | 当前需求 `fleet[]` 展开后的槽位序号，0 起 |
| `slotSummary` | `Object` | 否 | 该槽位车型、座位、服务范围和整单槽位数 |
| `baseAssignmentStatus` | `String` | 否 | 基础落库态 |
| `assignmentStatus` | `String` | 否 | 当前有效状态，可含紧急派生态 |
| `assignmentStatusLabel` | `String` | 否 | 后端中文状态标签 |
| `lifecycleStageCode/lifecycleStageLabel` | `String` | 否 | 该槽位生命周期阶段 |
| `currentStep` | `Integer` | 否 | 该槽位当前步骤 |
| `availableActionCodes` | `String[]` | 否 | 该槽位可用动作；需求级驳回不在此数组 |
| `vehiclePlate/vehicleModel` | `String` | 是 | 已派车辆信息 |
| `vehicleSeats` | `Integer` | 是 | 已派车辆座位数 |
| `vehicleFleetTeamId` | `String` | 是 | 已派车辆所属车队 ID |
| `vehicleFleetTeamName/vehicleFleetTeamType/vehicleFleetTeamSettleType` | `String` | 是 | 已派车辆所属车队信息 |
| `driverName` | `String` | 是 | 已派司机姓名 |
| `driverPhone` | `String` | 是 | 已脱敏司机手机号 |
| `urgentBadge` | `String` | 是 | `T-N` 或 `Nh`；非紧急为 `null` |
| `canAssign` | `Boolean` | 否 | 该槽位是否可进入派车/改派流程 |

## 顶层兼容字段

以下既有字段没有删除，旧前端继续读取不会报错：

- `assignmentId/assignmentGroupId/assignmentSlotId/fleetItemIndex`
- `slotSummary`
- `assignmentStatus/assignmentStatusLabel`
- `lifecycleStageCode/lifecycleStageLabel/currentStep/availableActionCodes`
- `currentVehicle*`
- `currentDriver*`
- `urgentBadge/canAssign/canRejectRequirement`

它们统一指向代表槽位，选择优先级为：

```text
未派 → 排车中 → 已派 → 已完成 → 已取消
```

同级按 `fleetItemIndex/assignmentSlotId/assignmentId` 稳定排序。部分已派时，顶层仍指向
未派槽位，保证原“派车派人”入口可继续派下一辆；所有槽位的真值以
`assignmentSlots[]` 和 `assignmentProgress` 为准。

`canRejectRequirement` 与需求级驳回动作只读外层 record。任一槽位已进入
`holding/assigned` 时，外层不会错误开放驳回；槽位级 `availableActionCodes` 不包含需求级驳回。

## 筛选、分页和汇总

- 状态多选：任一槽位命中即返回该需求一次。
- 车型多选：任一需求槽位命中即返回该需求一次，`requiredVehicles[]` 仍保留全部车型。
- 司机筛选/关键词：任一槽位司机命中即返回该需求一次。
- 混合状态：顶层状态取代表槽位状态；完整状态分布读取 `assignmentProgress` 和
  `assignmentSlots[]`。
- 空态：无当前有效需求或无 fleet 看板候选时返回 `records=[]/total=0`，不按人数合成虚假槽位。
- 顺序：先聚合为唯一 requirement record，再确定性排序和分页。
- `data.total`：查询范围内唯一 active `requirementId` 数，不是车辆槽位数。
- `GET /admin/fleet/board/summary`：状态和今日出团数使用相同需求粒度，不重复计数。

守恒关系：

```text
assignmentProgress.totalSlots
  == unassignedSlots
   + holdingSlots
   + assignedSlots
   + completedSlots
   + canceledSlots

assignmentProgress.totalSlots
  == sum(requiredVehicles[].count)
```

## 前端处理清单

- [ ] 看板列表对每个 `records[]` 只渲染一张需求卡，不再按
      `fleetItemIndex/assignmentSlotId` 拆卡，也不得展开 `assignmentSlots[]` 生成额外卡片。
- [ ] 列表 row key 优先使用 `requirementId`；仅兼容历史空值时回退 `id/orderId`。
- [ ] 需求卡展示 `requiredVehicles[]` 的全部车型组，并显示
      `assignmentProgress.totalSlots` 为“需要 N 辆车”。
- [ ] 需求详情遍历 `assignmentSlots[]` 展示全部车辆槽位、各自状态和已派车辆/司机。
- [ ] 顶层按钮可继续使用代表槽位字段；逐辆操作必须使用所选
      `assignmentSlots[i].assignmentId/fleetItemIndex/assignmentSlotId`，不得复用其他槽位 ID。
- [ ] 需求级驳回只使用外层 `canRejectRequirement/availableActionCodes`，不从槽位数组推断。
- [ ] 混合状态显示以 `assignmentProgress` 为准，不用顶层单一状态覆盖所有槽位。
- [ ] 继续按既有稳定状态 token 映射颜色，不按中文文案判断色值。
- [ ] 覆盖单车型×1、多车型各×1、同车型×2、部分已派、全已派、完结/取消和空列表场景。

## 不影响范围

- 不修改用车需求 `fleet[]` 的业务语义。
- 不合并、删除或改写真实 `fleet_assignment`；派车仍逐辆、逐槽位执行。
- 不修改派车、确认、取消、改派、需求驳回接口的请求结构。
- 不修改 §7 矩阵派单的车辆槽位维度。
- 不修改数据库、网关路由、错误码、车辆/司机占用、保险或费用。
- 本交接不代表已修改、发布或验证 `mmg/hl-ui`。

## 后端验证

- 精确复现测试：同一 `requirementId` 下 `SUV×1 + 商务车×1` 返回
  `total=1/records=1`、`requiredVehicles=2` 组、`assignmentSlots=2`、
  `assignmentProgress.totalSlots=2`。
- 同车型×2、部分已派、全已派、混合完结/取消、状态多选、聚合后分页和汇总去重均有自动化覆盖。
- 相关定向测试 64 项通过，0 failures/errors。
- OpenAPI diff 状态为 `not_configured`：当前环境没有 `oasdiff`，仓库也没有可复现的
  Swagger 2 → OpenAPI 3 导出链；契约审查保存了 Controller/VO 字段对比和自动化测试
  作为人工 fallback 证据。

## 验证证据

- 后端 commit：`fd641651144ab429ec1b371e1902a70bf3e7f025`
- 后端 PR：[wx/HL#5259](https://git.1814.love:8443/wx/HL/pulls/5259)
- 合并 commit：`dev-v3@01bf9c6274d5c40e00e65b84c5224189b0fd06b8`
- 合并后测试部署：现有部署 API 已滚动发布 `dev-v3`，
  `hl-fleet-service:8087/8187` 均健康；构建、发布成功，部署日志尾部的
  `ERROR/FATAL/Exception` 命中数为 0。
- 网关复现订单 `26-5256`：HTTP/业务码均为 200，`records=1`、`total=1`、
  `requiredVehicles=SUV×1+MPV×1`、`assignmentSlots=2` 且逐槽位 ID 唯一、
  `assignmentProgress.totalSlots=2`、状态数量之和为 2；脱敏证据 SHA-256 为
  `7ba2b05d8bdf335207771c59e089357dbe25d1fe0ac4aa0db1c91f910da563f5`。
- fleet 定向测试 64 项通过；`mvn -pl hl-fleet-service spotless:check`
  611 个文件通过；`mvn -pl hl-fleet-service -am verify` 中 fleet 2386 项测试
  0 failures/errors、skipped 1，完整 reactor `BUILD SUCCESS`。
- OpenAPI diff 为 `not_configured`，已保存 Controller/VO 字段对比、兼容语义、
  自动化测试和脱敏网关结构作为人工 fallback 证据。
- `frontend_status=pending`：本次未修改、部署或验证 `mmg/hl-ui`；
  页面仍需按“1 条需求卡 + 2 个槽位详情”的新契约完成消费。
