比较提交

...

1 次代码提交

作者 SHA1 备注 提交日期
API Changelog Bot
bf465d93e0 docs(api): hand off fleet requirement aggregation (#5257)
所有检测均成功
changelog-filename-gate / validate (pull_request) Successful in 2s
2026-07-26 11:10:35 +08:00

查看文件

@ -0,0 +1,279 @@
---
schema: "hl-changelog/v2"
ticket: "5257"
title: "车务看板按用车需求聚合多车型槽位"
consumer: "admin"
change_type: "修改接口"
backend_status: "deployed"
gateway_status: "verified"
frontend_status: "pending"
frontend_owner: ""
frontend_ref: ""
target_release: ""
verified_at: "2026-07-26T11:08:17+08:00"
status_note: "后端候选 commit fd641651 已部署测试环境并经网关验证;本契约明确替代 #5216 的按槽位卡片维度。前端尚未认领,需按需求卡消费 assignmentSlots。"
updated_at: "2026-07-26"
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)
- 候选分支测试部署:现有部署 API 已滚动发布
`fix/5257-fleet-requirement-card``hl-fleet-service:8087/8187` 均健康;
构建、发布成功,日志尾部未见 `ERROR/FATAL/Exception`
- 网关复现订单 `26-5256`HTTP/业务码均为 200,`records=1``total=1`
`requiredVehicles=SUV×1+MPV×1``assignmentSlots=2` 且逐槽位 ID 唯一、
`assignmentProgress.totalSlots=2`、状态数量之和为 2。
- 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 个槽位详情”的新契约完成消费。