diff --git a/changelogs-v2/2026-07/26_5257_车务看板按用车需求聚合多车型槽位-修改接口-管理后台.md b/changelogs-v2/2026-07/26_5257_车务看板按用车需求聚合多车型槽位-修改接口-管理后台.md new file mode 100644 index 0000000..c70ee8a --- /dev/null +++ b/changelogs-v2/2026-07/26_5257_车务看板按用车需求聚合多车型槽位-修改接口-管理后台.md @@ -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 个槽位详情”的新契约完成消费。