diff --git a/changelogs-v2/2026-09/06_7067_派单去槽位化按行程日配车-接送机独立配置-修改接口-管理后台.md b/changelogs-v2/2026-09/06_7067_派单去槽位化按行程日配车-接送机独立配置-修改接口-管理后台.md new file mode 100644 index 00000000..74e26bad --- /dev/null +++ b/changelogs-v2/2026-09/06_7067_派单去槽位化按行程日配车-接送机独立配置-修改接口-管理后台.md @@ -0,0 +1,1502 @@ +--- +schema: "hl-changelog/v2" +ticket: "7067" +title: "派单去槽位化:按行程日配车 + 接送机独立配置(管理后台四步向导)" +consumer: "admin" +author: "wx(GIT)" +change_type: "修改接口" +backend_status: "deployed" +gateway_status: "verified" +frontend_status: "pending" +frontend_owner: "mmg" +frontend_ref: "" +target_release: "" +verified_at: "2026-09-06" +status_note: "后端已合入 dev-v3(PR #7194,squash 9130624)并部署测试服,网关层已实测;前端派单向导需从三步改四步(订单详情→排车→接送机→确认执行),并同步改造看板列表/详情与矩阵未派清单对虚拟待派条目、assignmentGroupId、dailyAssignments[] 的读取逻辑。" +updated_at: "2026-09-06" +base: "dev-v3" +--- + +# 车务派单:去槽位化按行程日配车与接送机独立配置(管理后台) + +> **服务**: hl-fleet-service (8087) +> **PR**: #7194 +> **Issue**: #7067 +> **日期**: 2026-09-06 +> **影响范围**: 管理后台车务派单模块——派单向导四步流程(订单详情/排车/接送机/确认执行)、派单看板列表与详情、矩阵派单甘特图与未派订单清单 + +--- + +## ⚠️ 关键变化 + +本次是**破坏性契约变更,没有兼容期**:旧字段携带即 400,不做灰度双读。 + +- **前端派单向导从三步改四步**:①订单详情 → ②排车 → ③接送机 → ④确认执行。以前排车(第②步)里顺带勾选接机;现在接送机是独立的第③步,写口径也是独立接口。 +- **槽位概念整体退役**:以前 `assignmentSlotId` + `fleetItemIndex` 是派车的身份;现在唯一操作身份是 `assignmentGroupId`(历史行回退 `assignmentId`,对任何真实行恒非空)。前端不得再使用 `slotId` / `fleetItemIndex` 做任何身份判定或请求参数。 +- **排车请求体重构**:以前提交 `items[]`(槽位矩阵,逐槽 `used`/`pickupParticipant`);现在提交 `dailyPlan[]`(服务日期×车辆×司机,同日可多车)。以前"某天不配车"要提交一条 `used=false` 的占位项;**现在不需要**——该日不出现在 `dailyPlan[]` 里即可,但需求日期窗内存在这类空配车日时必须显式 `confirmNoVehicleServiceDates=true` 二次确认。 +- **虚拟待派条目**:以前看板/矩阵只呈现库里真实存在的派车行;现在零派车行的新声明用车需求订单,由 order-v3 当前有效用车需求投射出「虚拟待派条目」并入看板列表与矩阵未派清单,`virtualPending=true`,`assignmentId`/`assignmentGroupId` 为 null。**前端不能再用「`dailyAssignments` 为空」推断待派,必须读 `virtualPending`**。 +- **候选查询字段改名**:`retainedSlotIds` → `retainedGroupIds`,`cells[].slotId` → `cells[].groupId`。 +- **司机 H5 短链旧 token 直接失效**:token v2 claim 由 `assignmentSlotId` 改为 `assignmentGroupId`,签名输入口径变化使旧 token 报 605306(fail-closed 拒绝,不是容忍旧值),司机侧需重新获取短链。 +- **两个错误码作废**:605012、605911(码位保留不复用,不会再出现在任何响应里)。 + +--- + +## 一、背景 + +去槽位化前,"车辆槽位"是排车的固定拓扑单位:一个槽位对应需求声明的一个车辆项,逐日切片必须挂在某个槽位下,且排车与接送机勾选耦合在同一个提交动作里。这带来两个问题:一是同一天需要多辆车时无法表达(一槽一天一行的限制);二是新声明用车需求但车务尚未处理的订单,必须先靠"需求展开"预建 `unassigned` 占位行才能被看板/矩阵发现,一旦占位行缺失(历史遗留、并发竞态等)车务就看不到这张待派订单。 + +本单把排车模型从"槽位×日期"改为"服务日期×车辆×司机"的直接列表,取消预建占位行、改为读时按 order-v3 当前有效需求投射「虚拟待派条目」,并把接送机勾选从排车动作中拆出,独立成第③步。 + +| 维度 | 证据 A(测试环境实测画像,2026-09-06 只读连接) | 说明 | +|------|--------|--------| +| `fleet_assignment` 总行数 | 584 | 现状规模 | +| 零派车行但需清理的旧占位行(`assignment_status='unassigned'` 且车/司机全 NULL) | 75(涉 64 个 requirement) | 去槽位化后不再需要这类占位行,随本单一并清理(见 `docs/tasks/7067-contract-review.md`「测试环境历史数据处置」) | +| `assignment_group_id IS NULL` 的存量行 | 224,且各自 `assignment_slot_id` 互不相同 | 历史行按 `assignmentGroupId ?? assignmentId` 回退,等价于旧的按 slotId 分组,不需要回填 | + +--- + +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|------|------|------|----------|------| +| 1 | 批量派车(车务最终实派方案原子提交) | POST | `/admin/fleet/assignments/batch` | 修改(请求体重构) | `dailyPlan[]` 替代 `items[]`,旧字段携带即 400 | +| 2 | 接送机配置(按派车行整批幂等覆盖) | PUT | `/admin/fleet/assignments/pickup-dropoff-config` | 新增接口 | 四步向导第③步独立写口径 | +| 3 | 按当前派车方案原子确认全部执行段 | POST | `/admin/fleet/assignments/requirements/{requirementId}/confirm` | 修改(新增门禁错误码) | 大交通接送机未配置阻断 605914/605915 | +| 4 | 查询派单候选资源 | POST | `/admin/fleet/assignments/candidates` | 修改(响应改名+入参新增) | `retainedSlotIds`→`retainedGroupIds`、`cells[].slotId`→`cells[].groupId`,新增 `assignmentGroupId` 入参 | +| 5 | 看板列表 | GET | `/admin/fleet/board/orders` | 修改(结构重构+虚拟条目) | `assignmentSlots[]`→`dailyAssignments[]`,新增 `virtualPending` | +| 6 | 矩阵未派订单清单 | GET | `/admin/fleet/matrix/unassigned-orders` | 修改(并入虚拟条目) | 新增 `virtualPending`,排序追加末位兜底键 | +| 7 | 一键重派推荐(只读) | POST | `/admin/fleet/assignments/{assignmentId}/auto-recommend` | 修改(路径变更) | 原 `/slots/{slotId}/auto-recommend`,锚点由槽位 ID 改派单行 ID | +| 8 | 新增车辆槽位 | POST | `/admin/fleet/assignments/slots` | 删除接口 | 无替代;改在 `POST /batch` 的 `dailyPlan[]` 里多提交一项 | +| 9 | 删除车辆槽位 | DELETE | `/admin/fleet/assignments/slots/{slotId}` | 删除接口 | 无替代;改在 `POST /batch` 的 `dailyPlan[]` 里不提交该车该日 | +| 10 | 按日期删除改期残留配车数据 | POST | `/admin/fleet/assignments/slots/{slotId}/clear-residue-dates` | 删除接口 | 无替代;残留由 `POST /batch` 整批 diff 精确取消 | +| 11 | 逐日取消改期残留派车 | POST | `/admin/fleet/assignments/{assignmentId}/cancel-residue` | 删除接口 | 无替代;同上 | +| 12 | 看板订单详情 | GET | `/admin/fleet/board/orders/{orderId}` | 修改(vehicleSlots 改日行粒度) | 删除 `assignmentSlotId`/`fleetItemIndex`,新增 `serviceDate`/`dropoffParticipant` | +| 13 | 矩阵主数据(月视图甘特) | GET | `/admin/fleet/matrix/grid` | 修改(并行组字段删除) | `assignments[].parallelAssignments[].fleetItemIndex` 删除 | +| 14 | 矩阵当天订单清单 | GET | `/admin/fleet/matrix/day-orders` | 修改(排序键变更) | `assignments[].fleetItemIndex` 删除,排序键改 `assignmentId`;不并入虚拟条目 | + +--- + +## 三、接口详情 + +### 1. 批量派车(车务最终实派方案原子提交) `POST /admin/fleet/assignments/batch` + +**VO**: `BatchCreateAssignmentReqVO → BatchAssignmentWriteRespVO` + +#### 使用场景 + +四步向导第②步"排车"点击"保存排车方案"时调用。车务对同一用车需求一次性提交全部服务日×车辆×司机的配车计划,后端按(服务日×车辆)diff 原子重写该需求当前全部派车行:精确匹配的现行行保留不动,仅调价原因差异走轻量更新,其余在途行取消、其余提交项新建。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| orderId | Body | Long | ✅ | - | 订单 ID | +| orderNo | Body | String | ❌ | - | 订单号冗余 | +| requirementId | Body | Long | ✅ | - | 当前生效用车需求 ID | +| startDate | Body | Date | ✅ | - | 用车开始日期 | +| endDate | Body | Date | ✅ | - | 用车结束日期 | +| pickupAt | Body | String | ❌ | - | 接客地 | +| dropoffAt | Body | String | ❌ | - | 送客地 | +| headcount | Body | Integer | ❌ | - | 乘客人数 | +| confirmNoVehicleServiceDates | Body | Boolean | ❌ | 需求窗内存在不配车日期时必须为 true,否则 400 | 空配车日二次确认 | +| sendItinerarySms | Body | Boolean | ❌ | 不传按 false | 是否向本批各车师傅发行程短信(整批统一决策) | +| skipCityJunctionException | Body | Boolean | ❌ | - | 跳过城市衔接例外 | +| fromEntry | Body | String | ❌ | - | 操作来源 | +| requestId | Body | String | ✅ | ≤64 | 批次级幂等请求标识 | +| dailyPlan[] | Body | Array | ✅ | ≤4000 项 | 按行程日的完整配车列表 | +| dailyPlan[].serviceDate | Body | Date | ✅ | 须属于当前需求日期窗 | 服务日期 | +| dailyPlan[].vehicleId | Body | Long | ✅ | 同一服务日不能重复使用同一车辆 | 车辆 ID | +| dailyPlan[].driverId | Body | Long | ✅ | 同一服务日不能重复使用同一司机 | 司机 ID | +| dailyPlan[].assignmentPrice | Body | BigDecimal | ❌ | ≥0.00,整数最多10位/小数最多2位 | 本车当天价格;不传按车型价格日历参考价兜底 | +| dailyPlan[].priceAdjustmentReason | Body | String | ❌ | ≤256 | 实际价格与日历参考价不一致时的调整原因 | +| dailyPlan[].confirmCrossResident | Body | Boolean | ❌ | - | 跨常驻车显式确认 | +| ~~items~~ | Body | - | ❌ | 携带非 null 值即 400 | 已移除:旧槽位序号矩阵 | +| ~~chargeableServiceDates~~ | Body | - | ❌ | 携带非 null 值即 400 | 已移除:旧收费日期字段 | +| ~~vehicleFeeWaiverReason~~ | Body | - | ❌ | 携带非 null 值即 400 | 已移除 | +| ~~confirmAllServiceDatesFree~~ | Body | - | ❌ | 携带非 null 值即 400 | 已移除 | +| ~~holdMode~~ | Body | - | ❌ | 携带非 null 值即 400 | 已移除:#5827 起一步派定 | +| ~~dailyPlan[].fleetItemIndex~~ | Body | - | ❌ | 携带非 null 值即 400 | 已移除:稳定车辆槽位序号 | +| ~~dailyPlan[].used~~ | Body | - | ❌ | 携带非 null 值即 400 | 已移除:用车开关(不配车=无该项) | +| ~~dailyPlan[].pickupParticipant~~ | Body | - | ❌ | 携带非 null 值即 400 | 已移除:接机标志改由接送机配置步骤写入 | + +#### 出参 `Result` + +| 字段 | 类型 | 说明 | +|------|------|------| +| assignments[] | Array | 按创建顺序返回的派单结果 | +| assignments[].fleetItemIndex | Integer | 历史遗留字段(去槽位化后不再有槽位序号可归因),本单起恒为 null | +| assignments[].assignment | Object | 复用单车派单响应(AssignmentWriteRespVO) | +| assignments[].assignment.id | String | 新建/命中的派单 ID(雪花) | +| assignments[].assignment.assignmentGroupId | String | 派车组 ID,任何真实行恒非空 | +| assignments[].assignment.assignmentSlotId | String | 稳定车辆槽位 ID;去槽位化后新建行恒为 null(字段保留兼容,不再落值) | +| assignments[].assignment.assignmentStatus | String | 派单状态,提交即派定恒为 `assigned` | +| assignments[].assignment.protocolPrice | String | 协议价日单价快照 | +| assignments[].assignment.vehicleFeeTotal | String | 该行最终总车费 | +| assignments[].assignment.confirmedAt | String | 派定确认时间 | +| assignments[].assignment.sideEffects | Object | 副作用执行结果(车/司机占用反算) | +| failedFleetItemIndex | Integer | 基线不一致失败时的车辆槽位序号;去槽位化后无法归因,恒为 null | +| dailyDifferences[] | Array | 基线不一致时的逐日差异 | + +#### 请求示例 + +```json +{ + "orderId": "70123456789", + "requirementId": "70123456790", + "startDate": "2026-08-20", + "endDate": "2026-08-22", + "requestId": "batch-7067-0001", + "sendItinerarySms": false, + "confirmNoVehicleServiceDates": true, + "dailyPlan": [ + { "serviceDate": "2026-08-20", "vehicleId": "2079857983403024385", "driverId": "2079857983403024387" }, + { "serviceDate": "2026-08-21", "vehicleId": "2079857983403024385", "driverId": "2079857983403024387" } + ] +} +``` + +(2026-08-22 未出现在 `dailyPlan` 中 = 该日不配车;需求窗内存在空配车日,故必须带 `confirmNoVehicleServiceDates=true`,否则 400) + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "data": { + "assignments": [ + { + "fleetItemIndex": null, + "assignment": { + "id": "2086270160359878658", + "assignmentGroupId": "2086270160359878658", + "assignmentSlotId": null, + "assignmentStatus": "assigned", + "protocolPrice": "1300.00", + "vehicleFeeTotal": "1300.00", + "confirmedAt": "2026-08-19 10:00:00" + } + } + ], + "failedFleetItemIndex": null, + "dailyDifferences": null + }, + "success": true +} +``` + +#### 空数据 / 降级响应 + +需求日期窗内所有服务日均不配车(车务确认本次无需用车)时,提交空 `dailyPlan: []` + `confirmNoVehicleServiceDates: true`,diff 会取消全部在途行,`data.assignments` 返回空数组 `[]`。本接口是同步写操作,无下游降级路径——依赖资源不可用时直接抛错,不做静默降级。 + +#### 错误响应 + +```json +{ + "code": 605062, + "message": "存在派车日期与当前用车需求不符的槽位,请先调整或取消后再继续", + "success": false, + "data": null +} +``` + +#### 业务边界 + +- 携带任一旧字段(`items`/`chargeableServiceDates`/`vehicleFeeWaiverReason`/`confirmAllServiceDatesFree`/`holdMode`/`dailyPlan[].fleetItemIndex`/`used`/`pickupParticipant`)一律 400,不会静默忽略。 +- 同一服务日允许多车,但同日不能重复使用同一车辆或同一司机(各自触发 400)。 +- 需求日期窗内存在未出现在 `dailyPlan[]` 的服务日(且该日无 completed 历史行覆盖)时,必须 `confirmNoVehicleServiceDates=true`,否则 400。 +- 已完结(completed)日期的派车冻结:提交项与完结行不一致返回 400「该日已完结不可改派」。 +- 接送机不在本接口配置;本接口新建行的 `pickupParticipant`/`dropoffParticipant` 恒落 0,需再调 `PUT /pickup-dropoff-config` 独立配置。 +- 幂等:同一 `requestId` 300 秒窗口内重复提交直接拒绝「批量派单创建处理中,请勿重复提交」。 +- 提交即执行最终基线复核;不一致返回 605041 及 `data.dailyDifferences`。 + +--- + +### 2. 接送机配置(按派车行整批幂等覆盖) `PUT /admin/fleet/assignments/pickup-dropoff-config` + +**VO**: `PickupDropoffConfigReqVO → Void` + +#### 使用场景 + +四步向导第③步"接送机配置"页面保存时调用。车务对第②步已排车的行逐一勾选是否参与接机/送机;本接口按订单+当前需求整批幂等覆盖,是去槽位化后接机/送机标志的唯一写路径。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| orderId | Body | Long | ✅ | - | 订单 ID | +| requirementId | Body | Long | ✅ | - | 当前生效用车需求 ID | +| requestId | Body | String | ✅ | ≤64 | 幂等请求标识 | +| items[] | Body | Array | ✅(可传空数组) | ≤4000 项 | 要标记接送机参与的派车行集合 | +| items[].assignmentId | Body | Long | ✅ | 须是当前需求下生效派车行 | 派车行 ID | +| items[].pickupParticipant | Body | Boolean | ✅ | - | 当日是否参与 ARRIVAL 接机 | +| items[].dropoffParticipant | Body | Boolean | ✅ | 与 pickupParticipant 不能同为 false | 当日是否参与 DEPARTURE 送机 | + +#### 出参 `Result` + +| 字段 | 类型 | 说明 | +|------|------|------| +| data | null | 本接口无响应体,成功仅 `code=200` | + +#### 请求示例 + +```json +{ + "orderId": "70123456789", + "requirementId": "70123456790", + "requestId": "pdc-7067-0001", + "items": [ + { "assignmentId": "2086270160359878658", "pickupParticipant": true, "dropoffParticipant": false }, + { "assignmentId": "2086270160359878659", "pickupParticipant": false, "dropoffParticipant": true } + ] +} +``` + +#### 响应示例 + +```json +{ "code": 200, "message": "成功", "data": null, "success": true } +``` + +#### 空数据 / 降级响应 + +`items` 字段只校验非 null(无 `@NotEmpty`),传空数组 `[]` 是合法请求,语义为「清空该订单当前需求下全部生效派车行的接送机标志」(两方向都归 0)。本接口无读侧降级路径。 + +#### 错误响应 + +```json +{ + "code": 605913, + "message": "接送机配置无效:派单行不存在、不属于当前需求或已是终态", + "success": false, + "data": null +} +``` + +#### 业务边界 + +- **整批幂等覆盖**:该订单+当前需求下未出现在 `items[]` 中的生效派车行,两个方向标志一律归 0(不是增量 diff)。 +- 同一行两方向标志不能全为 false(`@AssertTrue` 拦截);不参与的行应直接从列表移除,而不是传两个 false。 +- 候选限定「该订单+当前需求+`serviceDate` 非空+车辆司机已定+状态 ∈ {holding, assigned}」;completed/canceled/exception 终态行不可配置,命中即整批 605913 拒绝(不部分生效)。 +- 大交通未要求接送机时也允许手工配置(读侧只作提示,不阻断写入)。 +- `items[]` 传空数组合法,用于一键清空。 + +--- + +### 3. 按当前派车方案原子确认全部执行段 `POST /admin/fleet/assignments/requirements/{requirementId}/confirm` + +**VO**: `ConfirmRequirementReqVO → ConfirmRequirementRespVO` + +#### 使用场景 + +四步向导第④步"确认执行"点击提交时调用。车务对当前用车需求的全部有效执行段做原子最终确认,需精确携带当前全部派车组及各组行程短信选择;服务端在确认前新增大交通接送机覆盖门禁。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| requirementId | Path | Long | ✅ | - | 当前用车需求 ID | +| orderId | Body | Long | ✅ | - | 订单 ID | +| requestId | Body | String | ✅ | ≤64 | 幂等请求标识 | +| expectedRequirementVersion | Body | Integer | ✅ | - | 预期当前有效用车需求版本 | +| expectedRequirementSha256 | Body | String | ✅ | 64位小写十六进制 | Board 返回的当前用车需求 canonical SHA-256 | +| expectedPlanGeneration | Body | Long | ✅ | - | 预期当前最终派车方案代际 | +| groups[] | Body | Array | ✅ | ≤50 项 | 执行段确认选择 | +| groups[].assignmentGroupId | Body | Long | ✅ | - | 当前有效派车组 ID | +| groups[].sendItinerarySms | Body | Boolean | ✅ | - | 是否向本执行段司机发送行程短信 | + +#### 出参 `Result` + +| 字段 | 类型 | 说明 | +|------|------|------| +| requirementId | String | 用车需求 ID | +| dispatchPlanGeneration | String | 已确认的最终派车方案代际 | +| confirmed | Boolean | 整组是否原子确认成功 | +| groups[] | Array | 各执行段确认结果 | +| groups[].assignmentId | String | 代表派单 ID | +| groups[].assignmentGroupId | String | 派车组 ID,历史行回退 assignmentId | +| groups[].assignmentStatus | String | 派单状态 | +| groups[].confirmedAt | String | 车务最终确认时间 | +| groups[].sendItinerarySms | Boolean | 是否选择发送本段行程短信 | +| groups[].itinerarySmsEventId | String | 行程短信 Outbox 事件 ID;未发送为 null | +| groups[].itinerarySmsStatus | String | 行程短信状态 | +| groups[].itineraryUrl | String | 本段电子行程单 H5 链接;签发不可用时为 null | + +#### 请求示例 + +```json +{ + "orderId": "70123456789", + "requestId": "confirm-req-7067-0001", + "expectedRequirementVersion": 3, + "expectedRequirementSha256": "a1b2c3d4e5f6000000000000000000000000000000000000000000000000", + "expectedPlanGeneration": "1934500000000000001", + "groups": [ + { "assignmentGroupId": "2086270160359878658", "sendItinerarySms": true } + ] +} +``` + +(路径参数 `requirementId=70123456790`) + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "data": { + "requirementId": "70123456790", + "dispatchPlanGeneration": "1934500000000000002", + "confirmed": true, + "groups": [ + { + "assignmentId": "2086270160359878658", + "assignmentGroupId": "2086270160359878658", + "assignmentStatus": "assigned", + "confirmedAt": "2026-08-19 10:05:00", + "sendItinerarySms": true, + "itinerarySmsEventId": "3086270160359878700", + "itinerarySmsStatus": "PENDING", + "itineraryUrl": "https://h5.example.com/itinerary/xxx" + } + ] + }, + "success": true +} +``` + +#### 空数据 / 降级响应 + +本接口是同步写操作,不存在空数据形态;下游行程单短链签发失败时 `itineraryUrl` 回退为 null,不阻断本次确认。 + +#### 错误响应 + +```json +{ + "code": 605914, + "message": "大交通要求接机,以下日期未配置接机车辆:2026-08-20,2026-08-21", + "success": false, + "data": null +} +``` + +#### 业务边界 + +- 大交通接送机覆盖门禁先于幂等回执读取判定——即使是重复提交的 `requestId`,只要接送机未配齐仍会先报 605914/605915。 +- 605914(ARRIVAL 接机)与 605915(DEPARTURE 送机)互相独立判定,各自返回缺失日期列表(`{0}` 为逗号分隔的 `yyyy-MM-dd`)。 +- 大交通无接送需求的订单,两个门禁均不触发,直接放行,不做任何断言。 +- 接/送机要求日若已被 completed 历史行覆盖(行程中换版平移),缺席豁免,不重复要求配置。 +- `groups[]` 必须精确等于当前全部有效执行段;同一 `requestId` 用于不同业务载荷返回 605059;`requestId` 命中历史损坏回执返回 605063(不可自愈终态,前端不得自动重试)。 + +--- + +### 4. 查询派单候选资源 `POST /admin/fleet/assignments/candidates` + +**VO**: `AssignmentCandidateReqVO → AssignmentCandidateRespVO` + +#### 使用场景 + +四步向导第②步"排车"点击某个日期格子选车/选司机时调用;同时驱动车辆和司机两个独立分页候选列表,以及 Step2 canonical 快照(`cells[]` group×day 笛卡尔积,供排车矩阵渲染)。 + +#### 入参 + +字段众多(完整 28 个字段见源码 `AssignmentCandidateReqVO.java`),下表为本单相关及核心字段: + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| orderId | Body | Long | ❌ | 改派排除自身时必填 | 当前订单 ID | +| requirementId | Body | Long | ❌ | 改派排除自身时必填 | 当前用车需求 ID | +| assignmentGroupId | Body | Long | ❌ | 本单新增 | 改派时被排除派单所属的派车组 ID,替代 `fleetItemIndex` 做归属校验 | +| ~~fleetItemIndex~~ | Body | Integer | ❌ | `@Deprecated`,不拒收 | 已降级:不再参与归属校验,仅剩「按需求车型项回退推断车型/座位提示」用途 | +| startDate | Body | Date | ✅ | - | 用车开始日期 | +| endDate | Body | Date | ✅ | - | 用车结束日期 | +| selectedVehicleId | Body | Long | ❌ | - | 已选车辆 ID(支持先选车) | +| selectedDriverId | Body | Long | ❌ | - | 已选司机 ID(支持先选司机) | +| excludeAssignmentId | Body | Long | ❌ | 必须属于当前订单和当前用车需求 | 改派时排除的当前派单 ID | +| expectedPlanGeneration | Body | Long | ❌ | 与当前 canonical 快照不符即拒绝 | Step2 幂等/同代校验期望代际 | +| expectedSnapshotVersion | Body | Long | ❌ | 同上 | Step2 期望快照版本 | +| driverAvailability | Body | String | ❌(默认 ALL) | `ALL`\|`AVAILABLE` | 司机可用性筛选 | +| driverSort | Body | String | ❌(默认 SMART) | `SMART`\|`RATING`\|`YEARS`\|`RECENT_ORDER` | 司机排序 | +| vehiclePage / vehiclePageSize | Body | Integer | ✅(有默认 1/20) | ≥1,≤100 | 车辆候选分页 | +| driverPage / driverPageSize | Body | Integer | ✅(有默认 1/20) | ≥1,≤100 | 司机候选分页 | + +#### 出参 `Result` + +完整字段见源码 `AssignmentCandidateRespVO.java`,下表为本单变更及核心字段: + +| 字段 | 类型 | 说明 | +|------|------|------| +| vehicles | Object | 车辆候选独立分页(`CandidatePageVO`) | +| drivers | Object | 司机候选独立分页(`CandidatePageVO`) | +| suggestedDriverId | String | 已选车辆自动代入司机 ID | +| canonicalSnapshot | Object | Step2 canonical 快照;无 `requirementId` 或需求上下文不可用时为 null | +| canonicalSnapshot.retainedGroupIds | Array\ | 本单由 `retainedSlotIds` 改名;有序稳定派车组 ID | +| canonicalSnapshot.cells[] | Array | 每个 group×day 唯一 cell | +| canonicalSnapshot.cells[].groupId | String | 本单由 `slotId` 改名;稳定派车组 ID,历史行回退 assignmentId | +| canonicalSnapshot.cells[].serviceDate | Date | 服务日期 | +| canonicalSnapshot.cells[].assignmentId | String | 当天逐日派车行 ID;无行为 null | +| canonicalSnapshot.cells[].used | String | `USED`/`UNUSED`/null 三态 | +| canonicalSnapshot.cells[].vehicleId / driverId | String | 当天车辆/司机 ID;未派或不用车为 null | + +#### 请求示例 + +```json +{ + "requirementId": "70123456790", + "startDate": "2026-08-20", + "endDate": "2026-08-22", + "headcount": 5, + "assignmentGroupId": "2086270160359878658", + "vehiclePage": 1, + "vehiclePageSize": 20, + "driverPage": 1, + "driverPageSize": 20 +} +``` + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "data": { + "vehicles": { "records": [], "total": 0, "page": 1, "pageSize": 20 }, + "drivers": { "records": [], "total": 0, "page": 1, "pageSize": 20 }, + "canonicalSnapshot": { + "planGeneration": "1934500000000000001", + "snapshotVersion": "1", + "retainedGroupIds": ["2086270160359878658"], + "editableServiceDates": ["2026-08-20", "2026-08-21", "2026-08-22"], + "cells": [ + { + "groupId": "2086270160359878658", + "serviceDate": "2026-08-20", + "assignmentId": "2086270160359878658", + "used": "USED", + "readOnly": false, + "vehicleId": "2079857983403024385", + "driverId": "2079857983403024387" + } + ] + } + }, + "success": true +} +``` + +#### 空数据 / 降级响应 + +无 `requirementId` 或需求上下文不可用时 `canonicalSnapshot` 整体为 null;某 group×day 无对应行时该 cell 的 `assignmentId`/车辆/司机/价格为 null,但 cell 本身仍存在(笛卡尔积不缺格)。 + +#### 错误响应 + +```json +{ + "code": 100001, + "message": "excludeAssignmentId 不属于当前订单或当前用车需求", + "success": false, + "data": null +} +``` + +(本接口候选查询本身恒 200 承载业务提示;此错误码来自「仅跨订单或排除派单不存在时报参数非法」的入参校验路径,非派车锁内校验) + +#### 业务边界 + +- 归属校验统一改用 `assignmentGroupId`:`fleetItemIndex` 参与归属校验的旧口径已删除,仅保留其在「需求车型项回退推断车型/座位提示」上的非判定用途,不拒收旧值。 +- 排除换版保留派单或未派车占位行时不校验 `fleetItemIndex`,座位按被排除行自身快照。 +- 同订单但槽位上下文不一致的排除不报错,忽略排除继续查询(被误传行的占用如实展示);仅跨订单或排除派单不存在才报参数非法。 +- `canonicalSnapshot` 每个 group×day 唯一 cell,显式携带 `USED`/`UNUSED`/`readOnly`/null,同代(`planGeneration`+`snapshotVersion`)内重复读取/幂等重试不重编号。 + +--- + +### 5. 看板列表 `GET /admin/fleet/board/orders` + +**VO**: `BoardOrderPageReqVO → BoardOrderPageRespVO` + +#### 使用场景 + +派单看板列表页首次加载、筛选和翻页时调用;四步向导的订单入口卡片来源。 + +#### 入参 + +完整字段见源码 `BoardOrderPageReqVO.java`,下表为核心字段: + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| statuses[] | Query | String[] | ❌ | 见六.5 枚举 | 多状态筛选,任一命中即返 | +| status | Query | String | ❌ | 兼容单值/逗号分隔 | 状态筛选别名 | +| startDayFrom / startDayTo | Query | Date | ❌ | 与行程区间重叠 | 日期区间 | +| startDate / endDate | Query | Date | ❌ | 兼容别名 | 日期区间别名 | +| vehicleTypeKeys[] / typeKeys[] | Query | String[] | ❌ | `suv`/`mpv`/`bus`/`sedan` | 车型大类多选 | +| keyword | Query | String | ❌ | - | 统一文字搜索 | +| consultantId | Query | Long | ❌ | - | 定制师精确筛选 | +| variant | Query | String | ❌(不传按 list) | `list`\|`grid`,其余值 100001 | 视图切换 | +| page / pageSize | Query | Integer | ❌(默认 1/20) | ≥1,≤100 | 分页 | + +#### 出参 `Result` + +完整字段见源码 `BoardOrderRecordVO.java`,下表为核心及本单变更字段: + +| 字段 | 类型 | 说明 | +|------|------|------| +| records[] | Array | 当前页记录(确定性排序) | +| total / page / pageSize | Long/Integer | 分页信息 | +| records[].assignmentId | String | 代表日行派单 ID | +| records[].assignmentGroupId | String | 派车组 ID;真实记录恒非空 | +| records[].requirementId | String | 当前用车需求 ID | +| records[].dailyAssignments[] | Array | 本单由 `assignmentSlots[]` 改名;全部日行派车项 | +| records[].dailyAssignments[].assignmentId | String | 派单 ID | +| records[].dailyAssignments[].assignmentGroupId | String | 派车组 ID | +| records[].dailyAssignments[].serviceDate | Date | 服务日期 | +| records[].dailyAssignments[].dayDisplayNo | Integer | 当日组内展示序号(纯展示,不作身份) | +| records[].dailyAssignments[].pickupParticipant | Boolean | 本单新增,当天是否参与接机 | +| records[].dailyAssignments[].dropoffParticipant | Boolean | 本单新增,当天是否参与送机 | +| records[].assignmentProgress | Object | 日行维度四计数 | +| records[].assignmentProgress.totalDailyItems | Integer | 日行总数 | +| records[].assignmentProgress.dispatchedDailyItems | Integer | 已派出行数 | +| records[].assignmentProgress.completedDailyItems | Integer | 已完结日数 | +| records[].assignmentProgress.canceledDailyItems | Integer | 已取消日数 | +| records[].virtualPending | Boolean | 本单新增,是否虚拟待派条目;真实记录恒显式 false | +| records[].assignmentStatus | String | 当前派单状态(含派生态) | + +#### 请求示例 + +```http +GET /admin/fleet/board/orders?statuses=unassigned_urgent&statuses=holding&page=1&pageSize=20 +``` + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "data": { + "records": [ + { + "orderNo": "HL202607010001", + "orderId": "70123456789", + "assignmentId": "2086270160359878658", + "assignmentGroupId": "2086270160359878658", + "requirementId": "70123456790", + "dailyAssignments": [ + { + "assignmentId": "2086270160359878658", + "assignmentGroupId": "2086270160359878658", + "serviceDate": "2026-08-20", + "dayDisplayNo": 1, + "assignmentStatus": "assigned", + "pickupParticipant": true, + "dropoffParticipant": false + } + ], + "assignmentProgress": { + "totalDailyItems": 3, + "finalizedByFleet": true, + "dispatchedDailyItems": 3, + "completedDailyItems": 0, + "canceledDailyItems": 0 + }, + "virtualPending": false, + "assignmentStatus": "assigned" + } + ], + "total": 21, + "page": 1, + "pageSize": 20 + }, + "success": true +} +``` + +#### 空数据 / 降级响应 + +该需求既无派车行、又不在 order-v3 有效需求窗内时整张卡不出现;`records` 为空数组 `[]` 时 `total=0`;order-v3 整体不可达时读侧回退派单快照,不抛错不 500。虚拟待派条目示例: + +```json +{ "assignmentId": null, "assignmentGroupId": null, "dailyAssignments": [], "virtualPending": true } +``` + +#### 错误响应 + +```json +{ + "code": 100001, + "message": "variant 仅支持 list、grid", + "success": false, + "data": null +} +``` + +#### 业务边界 + +- 顶层不再返回 `assignmentSlots`/`fleetItemIndex`/`assignmentSlotId`(已删除字段)。 +- 同一 `requirementId` 只返回一条记录;前端不得把 `dailyAssignments[]` 拆成多张"用车需求"卡。 +- 同一订单绝不同时出现真实记录与虚拟条目(服务端按 orderId 去重)。 +- `virtualPending` 真实记录恒显式 `false`(不是 null),前端不得用「`dailyAssignments` 为空」推断待派,必须读 `virtualPending`。 +- `dayDisplayNo` 只是展示位次,不可作身份或去重键;唯一操作身份是 `assignmentGroupId`(历史行回退 `assignmentId`)。 +- `variant` 传非 `list`/`grid` 返回 100001。 + +--- + +### 6. 矩阵未派订单清单 `GET /admin/fleet/matrix/unassigned-orders` + +**VO**: `MatrixUnassignedReqVO → List` + +#### 使用场景 + +矩阵派单页右侧"未派订单"面板加载时调用;车务从此处把订单拖拽到左侧甘特图某车某日发起派车。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| year | Query | Integer | ✅ | 1970-9999 | 年份 | +| month | Query | Integer | ✅ | 1-12 | 月份 | +| typeKeys[] | Query | String[] | ❌ | `suv`/`mpv`/`bus`/`sedan`,空=全部 | 车型大类多选 | + +#### 出参 `Result>` + +完整字段见源码 `MatrixUnassignedOrderVO.java`,下表为核心及本单变更字段: + +| 字段 | 类型 | 说明 | +|------|------|------| +| orderId | String | 订单号(兼容字段) | +| orderNumericId | String | 订单数值 ID(雪花) | +| requirementId | String | 当前生效用车需求 ID | +| virtualPending | Boolean | 本单新增,是否虚拟待派条目 | +| assignmentId | String | 派单 ID;虚拟条目为 null | +| assignmentGroupId | String | 派车组 ID;虚拟条目为 null | +| vehicleCategory | String | 规范小写车型 key;虚拟条目取需求车型明细首项 | +| categoryLabel | String | 车型中文标签,恒非 null | +| startDay / endDay | Integer | 月内起止日(跨月已裁剪) | +| serviceDateSegments[] | Array | 连续有效服务日期段 | +| urgentBadge | String | 紧急徽章 `T-N`;非紧急为 null | +| parallelAssignments[] | Array | 同订单全部并行派车组;虚拟条目恒空数组 | + +#### 请求示例 + +```http +GET /admin/fleet/matrix/unassigned-orders?year=2026&month=8&typeKeys=suv&typeKeys=mpv +``` + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "data": [ + { + "orderId": "26-0503", + "orderNumericId": "1234567890", + "orderNo": "26-0503", + "requirementId": "70123456790", + "virtualPending": true, + "assignmentId": null, + "assignmentGroupId": null, + "vehicleCategory": "suv", + "categoryLabel": "SUV", + "startDay": 6, + "endDay": 11, + "urgentBadge": "T-2", + "parallelAssignments": [] + } + ], + "success": true +} +``` + +#### 空数据 / 降级响应 + +当月无未派订单时返回空数组 `[]`;Nacos `fleet.board.virtual-candidates-enabled=false` 或 order-v3 候选不可用时只丢虚拟条目,真实条目原样返回(fail-open,不让未派窗口整体打不开);`vehicleAdvice` 暂恒为 null(数据源未建)。 + +#### 错误响应 + +```json +{ + "code": 100001, + "message": "月份超出范围", + "success": false, + "data": null +} +``` + +#### 业务边界 + +- 虚拟条目一单一条(不按车型拆),真实条目一单多车型时同订单出现多行(按 `assignmentId`+`vehicleCategory` 区分)。 +- 同一订单已有任何真实候选行时不再补虚拟条目(不会双出)。 +- 排序键 `urgentBadge → startDay → assignmentId → orderNumericId`(末位兜底键保证虚拟条目顺序确定)。 +- 前端拖拽 key 用 `assignmentId ?? ('virtual:' + orderNumericId + ':' + requirementId)`。 +- 虚拟条目 `assignmentId`/`assignmentGroupId` 为 null,不得对其发起行级拖拽/改派/取消,只能按 `requirementId` 走整单派车入口。 +- 清单条数与 `GET /admin/fleet/matrix/grid` 未派计数在同一 `typeKeys` 过滤下必须相等。 + +--- + +### 7. 一键重派推荐(只读,不产生写操作) `POST /admin/fleet/assignments/{assignmentId}/auto-recommend` + +**VO**: `(PathVariable assignmentId) → SlotAutoRecommendRespVO` + +#### 使用场景 + +需求变更后车务手动删除旧派车,对空缺派车行调用本接口获取系统推荐车辆+司机;车务确认后仍走 `POST /batch` 手动提交。#7067 前锚点是槽位 ID,本单改为派单行 ID。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| assignmentId | Path | Long | ✅ | 须为空缺待派车派单行 | 派单行 ID(本单起取代原 `slotId`) | + +#### 出参 `Result` + +| 字段 | 类型 | 说明 | +|------|------|------| +| assignmentId | String | 锚点派单行 ID(本单起取代槽位 ID) | +| requirementId | String | 归属需求 ID | +| orderId | String | 归属订单 ID | +| recommendedVehicle | Object | 推荐车辆;无推荐为 null | +| recommendedDriver | Object | 推荐司机;无推荐为 null | +| recommendNote | String | 推荐说明 | +| vehicleAlternatives[] | Array | 车辆备选(前 5) | +| driverAlternatives[] | Array | 司机备选(前 5) | +| noRecommendation | Boolean | 是否无可用推荐 | + +#### 请求示例 + +```http +POST /admin/fleet/assignments/2086270160359878658/auto-recommend +``` + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "data": { + "assignmentId": "2086270160359878658", + "requirementId": "70123456790", + "orderId": "70123456789", + "recommendedVehicle": { "vehicleId": "2079857983403024385", "plate": "蒙A-88888", "modelName": "别克GL8", "seats": 7 }, + "recommendedDriver": { "driverId": "2079857983403024387", "name": "王师傅", "driverStatus": "idle" }, + "recommendNote": "车型匹配·座位充足·档期可用", + "vehicleAlternatives": [], + "driverAlternatives": [], + "noRecommendation": false + }, + "success": true +} +``` + +#### 空数据 / 降级响应 + +无可用车辆或司机推荐时 `recommendedVehicle`/`recommendedDriver` 为 null、`noRecommendation=true`,`vehicleAlternatives`/`driverAlternatives` 为空数组,前端提示人工选配;本接口只读不产生写操作,无其他降级路径。 + +#### 错误响应 + +```json +{ + "code": 605009, + "message": "派单不存在", + "success": false, + "data": null +} +``` + +#### 业务边界 + +- 路径参数语义变化:旧 `slotId`(稳定车辆槽位 ID)不再被接受,必须传当前空缺派车行的 `assignmentId`;旧路径 `POST /slots/{slotId}/auto-recommend` 直接 404。 +- 只读接口,调用不产生任何写操作,也不影响派单状态。 +- 车务确认推荐结果后仍需另调 `POST /batch` 手动提交,本接口不自动落库。 + +--- + +### 8. 新增车辆槽位(已删除) `POST /admin/fleet/assignments/slots` + +**VO**: `(已删除,无 VO)` + +#### 使用场景 + +**已删除**。原用途:改派时增加一个待派车辆槽位(多派一辆),新增槽位挂空占位派单行等车务后续选车。去槽位化后不再存在"槽位"这个身份实体,多派一辆车改为直接在 `POST /batch` 的 `dailyPlan[]` 里多提交一项(服务日×车辆×司机)。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| (无) | - | - | - | - | 本接口已删除,路径不存在,调用任何入参均返回 404 | + +(原入参历史参考,仅供归档:`requirementId`/`vehicleType`/`seats`/`reason`) + +#### 出参 `Result` + +| 字段 | 类型 | 说明 | +|------|------|------| +| (无) | - | 本接口已删除,不再返回任何响应体 | + +#### 请求示例 + +```json +{ "_deprecated": "本接口已删除,以下为历史请求体结构,仅供归档参考", "requirementId": "70123456789", "vehicleType": "suv", "seats": 5, "reason": "改派时多派一辆" } +``` + +#### 响应示例 + +```json +{ "_deprecated": "本接口已删除,调用将得到 404,不会再有下列历史响应体", "slotId": "1934567890123456789", "assignmentId": "1934567890123456790", "fleetItemIndex": 1 } +``` + +#### 空数据 / 降级响应 + +无。路径已整体移除,网关/服务对该路径的任何请求返回 404,不做业务层降级。 + +#### 错误响应 + +```json +{ + "code": 404, + "message": "Not Found", + "success": false, + "data": null +} +``` + +(历史版本本接口曾用 605012「槽位不存在」拒绝非法 `requirementId`;该错误码已随槽位概念一并撤销,不会再出现) + +#### 业务边界 + +- **为什么删**:去槽位化后"车辆槽位"不再是持久身份,新增一辆车不需要预先建一个空槽位再等车务选车,直接在批量派车里提交这一天这辆车即可。 +- **前端改调什么**:不再调用本接口;四步向导第②步"排车"里"新增一辆车"操作,改为在本地待提交的 `dailyPlan[]` 里追加一项(服务日期+车辆+司机),随下一次 `POST /batch` 一并提交。 + +--- + +### 9. 删除车辆槽位(已删除) `DELETE /admin/fleet/assignments/slots/{slotId}` + +**VO**: `(已删除,无 VO)` + +#### 使用场景 + +**已删除**。原用途:车务删除某个车辆槽位(含已派车/待确认的),联动释放车辆/司机占用。去槽位化后"删除某天某车"改为在 `POST /batch` 提交时,该服务日对应的车辆不出现在 `dailyPlan[]` 里,由整批 diff 自动取消。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| (无) | - | - | - | - | 本接口已删除,路径不存在,调用任何入参均返回 404 | + +(原入参历史参考,仅供归档:PathVar `slotId` + Body `reason`) + +#### 出参 `Result` + +| 字段 | 类型 | 说明 | +|------|------|------| +| (无) | - | 本接口已删除,不再返回任何响应体 | + +#### 请求示例 + +```json +{ "_deprecated": "本接口已删除,以下为历史请求体结构,仅供归档参考", "reason": "定制师傅建议的槽位不需要" } +``` + +#### 响应示例 + +```json +{ "_deprecated": "本接口已删除,调用将得到 404,不会再有下列历史响应体", "slotId": "1934567890123456789", "removedRowCount": 1, "cancelledAssignmentCount": 0 } +``` + +#### 空数据 / 降级响应 + +无。路径已整体移除,网关/服务对该路径的任何请求返回 404,不做业务层降级。 + +#### 错误响应 + +```json +{ + "code": 404, + "message": "Not Found", + "success": false, + "data": null +} +``` + +(历史版本本接口曾用 605012「槽位不存在」/605047「行程已结束不可操作配车」拒绝;两码随槽位概念一并撤销/失去载体,不会再出现在本路径) + +#### 业务边界 + +- **为什么删**:槽位不再是持久身份,"删除某个槽位"这个动作本身失去操作对象;对某天不再配车不需要单独一个删除动作。 +- **前端改调什么**:不再调用本接口;四步向导第②步"移除某天某车"操作,改为在本地待提交的 `dailyPlan[]` 里去掉该项,随下一次 `POST /batch` 一并提交(该日不出现即视为不配车)。 + +--- + +### 10. 按日期删除改期残留配车数据(已删除) `POST /admin/fleet/assignments/slots/{slotId}/clear-residue-dates` + +**VO**: `(已删除,无 VO)` + +#### 使用场景 + +**已删除**。原用途:订单改期后,按指定服务日期子集精确清理旧服务日在途(holding/assigned)配车残留,不影响其余日期。去槽位化后残留清理不再是独立动作,由 `POST /batch` 整批按(服务日×车辆)diff 精确取消旧日期在途行完成。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| (无) | - | - | - | - | 本接口已删除,路径不存在,调用任何入参均返回 404 | + +(原入参历史参考,仅供归档:PathVar `slotId` + Body `serviceDates`(必填,YYYY-MM-DD 数组) + `reason`) + +#### 出参 `Result` + +| 字段 | 类型 | 说明 | +|------|------|------| +| (无) | - | 本接口已删除,不再返回任何响应体 | + +#### 请求示例 + +```json +{ "_deprecated": "本接口已删除,以下为历史请求体结构,仅供归档参考", "serviceDates": ["2026-08-01", "2026-08-02"], "reason": "订单改期后旧日期残留" } +``` + +#### 响应示例 + +```json +{ "_deprecated": "本接口已删除,调用将得到 404,不会再有下列历史响应体", "clearedServiceDates": ["2026-08-01"], "cancelledRowCount": 1 } +``` + +#### 空数据 / 降级响应 + +无。路径已整体移除,网关/服务对该路径的任何请求返回 404,不做业务层降级。 + +#### 错误响应 + +```json +{ + "code": 404, + "message": "Not Found", + "success": false, + "data": null +} +``` + +(历史版本本接口曾用 605012/605047/605054/605071/605600 拒绝;随槽位概念撤销,不会再出现在本路径) + +#### 业务边界 + +- **为什么删**:去槽位化后重写排车方案本身就是"按日期 diff",不再需要一个专门的"按日期清理残留"轻动作,`POST /batch` 一次提交即完成同等效果。 +- **前端改调什么**:不再调用本接口;改期后重新提交 `POST /batch`,未出现在新 `dailyPlan[]` 里的旧日期行由整批 diff 自动取消。 + +--- + +### 11. 逐日取消改期残留派车(已删除) `POST /admin/fleet/assignments/{assignmentId}/cancel-residue` + +**VO**: `(已删除,无 VO)` + +#### 使用场景 + +**已删除**。原用途:仅取消指定 `assignmentId` 对应的窗外逐日残留行,不删除槽位、不影响同槽其他日期。语义与「按日期删除改期残留配车数据」相近,同随槽位化一起退役,替代方式相同。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| (无) | - | - | - | - | 本接口已删除,路径不存在,调用任何入参均返回 404 | + +(原入参历史参考,仅供归档:PathVar `assignmentId` + Body `requestId`(必填,≤64) + `cancelReason`) + +#### 出参 `Result` + +| 字段 | 类型 | 说明 | +|------|------|------| +| (无) | - | 本接口已删除,不再返回任何响应体 | + +#### 请求示例 + +```json +{ "_deprecated": "本接口已删除,以下为历史请求体结构,仅供归档参考", "requestId": "residue-cancel-20260812-001", "cancelReason": "改期残留逐日取消" } +``` + +#### 响应示例 + +```json +{ "_deprecated": "本接口已删除,调用将得到 404,不会再有下列历史响应体(历史响应体为内部结果对象,字段与创建派单响应相同)" } +``` + +#### 空数据 / 降级响应 + +无。路径已整体移除,网关/服务对该路径的任何请求返回 404,不做业务层降级。 + +#### 错误响应 + +```json +{ + "code": 404, + "message": "Not Found", + "success": false, + "data": null +} +``` + +#### 业务边界 + +- **为什么删**:同「按日期删除改期残留配车数据」,残留清理由 `POST /batch` 整批 diff 覆盖,不再需要按单个派单行单独取消残留。 +- **前端改调什么**:不再调用本接口;改期后重新提交 `POST /batch` 即完成残留清理。 + +--- + +### 12. 看板订单详情 `GET /admin/fleet/board/orders/{orderId}` + +**VO**: `(PathVariable orderId) → BoardOrderDetailVO` + +#### 使用场景 + +派单弹窗 Step1 当前订单详情页加载时调用,展示当前订单摘要、逐日行程、大交通与当前有效用车需求的车辆日行执行情况。本单变更集中在 `vehicleSlots[]`(由"按车型聚合的全程槽位"改为"日行粒度")与 `dailyVehiclePlan[]`(删除槽位序号字段、新增送机标志)。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| orderId | Path | Long | ✅ | - | 订单 ID | + +#### 出参 `Result` + +完整字段见源码 `BoardOrderDetailVO.java`(含 `activeAssignments`、`travelers`、`transport`、`progressSteps`、`operationLog` 等本单未变更字段);下表为本单变更的两个子结构: + +| 字段 | 类型 | 说明 | +|------|------|------| +| vehicleSlots[] | Array | 本单由"按车型聚合的全程槽位"改为「车辆日行云」:一行=一个行程日的一条派车 | +| vehicleSlots[].serviceDate | Date | 本单新增,本行服务日期;历史全程行为 null | +| vehicleSlots[].displayNo | Integer | 本单由 `slotDisplayNo` 改名;按 (serviceDate, assignmentId) 升序的展示位次,纯展示不作身份 | +| vehicleSlots[].assignmentGroupId | String | 派车组 ID,同车同司机连续日行共用;历史行无该值时回退 assignmentId | +| vehicleSlots[].pickupParticipant | Boolean | 本单新增,当天是否参与接机 | +| vehicleSlots[].dropoffParticipant | Boolean | 本单新增,当天是否参与送机 | +| ~~vehicleSlots[].assignmentSlotId~~ | - | 本单删除:稳定车辆槽位 ID | +| ~~vehicleSlots[].fleetItemIndex~~ | - | 本单删除:需求车辆槽位序号 | +| dailyVehiclePlan[].dropoffParticipant | Boolean | 本单新增,当天是否参与送机(S3 接送机独立配置) | +| ~~dailyVehiclePlan[].fleetItemIndex~~ | - | 本单删除:稳定车辆槽位序号 | +| ~~dailyVehiclePlan[].assignmentSlotId~~ | - | 本单删除:稳定车辆槽位 ID | + +#### 请求示例 + +```http +GET /admin/fleet/board/orders/70123456789 +``` + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "data": { + "id": "HL20260708144554930", + "orderNo": "HL20260708144554930", + "requirementId": "70123456790", + "vehicleSlots": [ + { + "serviceDate": "2026-08-28", + "displayNo": 1, + "assignmentGroupId": "1934567890123456789", + "pickupParticipant": true, + "dropoffParticipant": false, + "slotStatus": "assigned", + "assignmentId": "1934567890123456789" + } + ], + "dailyVehiclePlan": [ + { + "serviceDate": "2026-08-28", + "assignmentId": "1934567890123456789", + "assignmentGroupId": "1934567890123456789", + "used": true, + "pickupParticipant": true, + "dropoffParticipant": false + } + ] + }, + "success": true +} +``` + +#### 空数据 / 降级响应 + +order-v3 不可达时回退 fleet 本地快照并将 `relatedDetailReady=false`(前端据此对 `plannerNote`/`itinerary` 等占位字段显示占位文案,不报错);无行程返回空天列表,不生成伪数据。 + +#### 错误响应 + +```json +{ + "code": 401, + "message": "未登录", + "success": false, + "data": null +} +``` + +#### 业务边界 + +- `vehicleSlots[]` 不再按车型聚合展示"全程槽位",改为按 (serviceDate, assignmentId) 逐行展开;前端如仍按"槽位卡片"渲染需重构为按日期分组。 +- `displayNo` 与看板列表的 `dayDisplayNo` 同口径,均为纯展示位次,不可作身份或请求参数。 +- `vehicleSlots[].assignmentGroupId`、`dailyVehiclePlan[].assignmentGroupId` 历史行无值时统一回退 `assignmentId`,对任何真实行恒非空。 + +--- + +### 13. 矩阵主数据(月视图甘特) `GET /admin/fleet/matrix/grid` + +**VO**: `MatrixGridReqVO → MatrixGridRespVO` + +#### 使用场景 + +矩阵派单页月视图甘特图加载时调用(行=车、列=日期)。本单变更范围小:车段内 `parallelAssignments[].fleetItemIndex` 字段删除,`assignmentGroupId` 的"回退 assignmentId"语义在文档层面统一澄清(取值口径不变)。 + +#### 入参 + +完整字段见源码 `MatrixGridReqVO.java`(fleetTeamIds/typeKeys/season/status 等,本单未变更),不在此重复列出。 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| year | Query | Integer | ✅ | - | 年份 | +| month | Query | Integer | ✅ | 1-12 | 月份 | + +#### 出参 `Result` + +完整字段见源码 `MatrixGridRespVO.java`(`vehicles[]`/`statusCounts`/`fleetTeamCounts` 等本单未变更);下表为本单变更字段: + +| 字段 | 类型 | 说明 | +|------|------|------| +| vehicles[].assignments[].parallelAssignments[] | Array | 同订单当前全部并行派车组 | +| ~~vehicles[].assignments[].parallelAssignments[].fleetItemIndex~~ | - | 本单删除:需求车型项序号 | +| vehicles[].assignments[].assignmentGroupId | String | 派车组 ID;历史行无该值时回退 assignmentId(本单起口径统一) | + +#### 请求示例 + +```http +GET /admin/fleet/matrix/grid?year=2026&month=8 +``` + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "data": { + "year": 2026, + "month": 8, + "daysInMonth": 31, + "vehicles": [ + { + "id": "1234567890", + "plate": "蒙A-88888", + "assignments": [ + { + "id": "1234567890", + "assignmentGroupId": "1234567890", + "parallelAssignments": [ + { "assignmentGroupId": "1234567891", "vehicleCategory": "suv", "assignmentStatus": "assigned" } + ] + } + ] + } + ] + }, + "success": true +} +``` + +#### 空数据 / 降级响应 + +该车本月无派单则 `assignments` 为空数组;无并行派车组时 `parallelAssignments` 为空数组,不是 null。 + +#### 错误响应 + +```json +{ + "code": 605010, + "message": "月份超出范围", + "success": false, + "data": null +} +``` + +#### 业务边界 + +- `parallelAssignments[]` 不再携带 `fleetItemIndex`;前端若曾用它做并行组去重键,须改用 `assignmentGroupId`。 +- `assignmentGroupId` 历史行无值时统一回退 `assignmentId`,对任何真实行恒非空(本单起在全部矩阵响应中口径一致)。 + +--- + +### 14. 矩阵当天订单清单 `GET /admin/fleet/matrix/day-orders` + +**VO**: `(RequestParam date) → List` + +#### 使用场景 + +点矩阵日期列头弹出当天所有订单(含已派+未派)时调用。本单变更:`assignments[].fleetItemIndex` 字段删除,排序键由 `fleetItemIndex` 改为 `assignmentId`;**本接口明确不并入虚拟待派条目**(它是「每辆车一条」的执行视图,虚拟条目无行可列)。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| date | Query | String | ✅ | 格式 `YYYY-MM-DD` | 日期 | + +#### 出参 `Result>` + +完整字段见源码 `MatrixDayOrderVO.java`;下表为本单变更字段: + +| 字段 | 类型 | 说明 | +|------|------|------| +| assignments[] | Array | 该订单展开的派单列表;本单起按 `assignmentId` 排序(原按 `fleetItemIndex` 排序) | +| assignments[].assignmentGroupId | String | 派车组 ID;历史行无该值时回退 assignmentId | +| ~~assignments[].fleetItemIndex~~ | - | 本单删除:同订单内派单序号 | + +#### 请求示例 + +```http +GET /admin/fleet/matrix/day-orders?date=2026-05-04 +``` + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "data": [ + { + "orderId": "26-0501", + "orderNumericId": "1234567890", + "orderAssignStatus": "partial", + "assignments": [ + { "assignmentId": "1234567890", "assignmentGroupId": "1234567890", "vehiclePlate": "蒙A-88888", "driverName": "王师傅", "assignmentStatus": "assigned" }, + { "assignmentId": "1234567891", "assignmentGroupId": "1234567891", "vehiclePlate": null, "driverName": null, "assignmentStatus": "unassigned" } + ] + } + ], + "success": true +} +``` + +#### 空数据 / 降级响应 + +当天无订单覆盖返回空数组 `[]`。**本接口不并入虚拟待派条目**——它是「每辆车一条」的执行视图,零派车行的订单在这里无行可列,不会出现在本响应中;待派发现请走看板列表或矩阵未派清单。 + +#### 错误响应 + +```json +{ + "code": 100001, + "message": "date 缺失或格式非 YYYY-MM-DD", + "success": false, + "data": null +} +``` + +#### 业务边界 + +- `assignments[]` 排序键从 `fleetItemIndex` 改为 `assignmentId`;前端若依赖旧排序位次做展示或去重,需改用 `assignmentId`。 +- 明确不含虚拟待派条目;零派车行订单不会出现在本清单,需要发现这类订单请用看板列表(`GET /admin/fleet/board/orders`)或矩阵未派清单(`GET /admin/fleet/matrix/unassigned-orders`)。 +- 未派车的 `vehiclePlate`/`driverName` 为 null,`assignmentStatus=unassigned`;`orderAssignStatus` 按 `assignments` 聚合(全未派/部分/全派)。 + + +--- + +## 四、契约约束与正确调用方式 + +> 本节只写**后端接受/拒绝 payload 的规则**,不写 UI 渲染建议。 + +### ✅ 正确 / ❌ 错误 payload 对照 + +| 场景 | payload | +|------|---------| +| ✅ 某天不配车 | 该日不出现在 `dailyPlan[]` 中 | +| ❌ 某天不配车(旧写法) | `{ "serviceDate": "2026-08-22", "used": false }` → 400(`used` 字段已移除) | +| ✅ 需求窗内存在空配车日 | 提交时带 `"confirmNoVehicleServiceDates": true` | +| ❌ 需求窗内存在空配车日未确认 | 不带 `confirmNoVehicleServiceDates` → 400「存在未配置车辆的服务日期时必须二次确认」 | +| ✅ 接送机配置 | 独立调 `PUT /pickup-dropoff-config`,不在 `/batch` 里传接送机字段 | +| ❌ 排车时携带接送机标志 | `dailyPlan[].pickupParticipant=true` → 400 | +| ✅ 接送机整批覆盖 | `items[]` 传"本次要参与的全部行",未出现的行自动归 0 | +| ❌ 接送机增量更新(误解) | 只传新增的一行,误以为其余行标志维持不变 → 实际会被清零 | +| ✅ 候选归属校验 | 传 `assignmentGroupId` 排除当前操作的派车组 | +| ❌ 候选归属校验(旧写法) | 传 `fleetItemIndex` 试图排除某槽位 → 不再生效(仅回退推断车型用) | +| ✅ 一键重派推荐传参 | PathVar `assignmentId`(空缺待派车行),旧 `slotId` 直接 404 | + +### 切换状态时的必要动作 + +- 从"旧槽位排车"切到"按行程日配车":前端必须整段替换请求体构造逻辑,逐槽位 `items[]` 改造为逐日 `dailyPlan[]`,不能只是改字段名。 +- 从"排车时勾选接机"切到"接送机独立步骤":前端向导必须新增第③步页面与独立提交动作,不能在第②步表单里继续携带接送机字段(会被 400 拒绝)。 +- 候选查询排除派车组:前端所有传 `fleetItemIndex` 做排除的调用点必须切换为传 `assignmentGroupId`,否则改派时看不到真实冲突(被排除行没有真正排除)。 + +--- + +## 五、数据库行为 + +- 同一服务日可以存在多条派车记录(同日多车),不再受"一槽一天一行"的限制。 +- 某天不配车时,该日不会产生任何派车记录(不是一条 `used=false` 的占位记录)。 +- 提交批量派车后,未在本次提交中出现的原有派车记录会被取消或替换,不会残留旧记录与新记录并存的同日重复占用。 +- 接送机两个方向的标志只有"是/否"两态,不存在"未设置"的第三态;未被本次接送机配置提交覆盖的记录,两个方向都会被置为"否"。 +- 已完结(completed)状态的派车记录在服务日期层面只读,不可再被批量派车覆盖或取消。 +- 新建的派车记录不再携带旧的"槽位序号"或"稳定车辆槽位"标识,这两个历史字段在新记录上恒为空。 +- 派单确认执行不产生任何派车记录的增删,仅推进状态与写入确认时间。 +- 删除的槽位新增/删除/残留清理相关写操作不再存在;同等效果改由批量派车一次提交的整批重写完成,不再有单独的“新增一个槽位”“删除一个槽位”“按日期清理残留”这些独立写动作。 + +--- + +## 六、边界行为 + +- 未登录 → 401(网关拦截) +- 请求参数非法(如 `variant` 传非 `list`/`grid`,`year`/`month` 缺失或越界)→ 100001 +- 资源不存在(派单/需求)→ 语义化错误码(如 605009),不是裸 404 +- 下游 order-v3 不可达时看板/矩阵读侧回退派单快照或跳过虚拟条目,不 500 不阻断页面(fail-open) +- 老数据兼容:历史行无 `assignmentGroupId` 时统一回退 `assignmentId` 展示,前端按此字段判定身份;两处例外须判空——看板详情操作日志项与虚拟待派条目 +- 已取消/已完结的派车记录进入只读态,写接口对其操作返回状态类错误码而非静默忽略 +- 大交通无接送需求时,确认执行不做任何接送机相关断言,直接放行 + +## 六.5、枚举 / 数据字典 + +### `assignmentStatus`(多接口出参共用,落库态+派生态) + +**所属字段**: `BoardOrderRecordVO.assignmentStatus` / `MatrixUnassignedOrderVO.assignmentStatus` 等 | **类型**: `String` + +| 值 | 中文 | 说明 | +|----|------|------| +| `unassigned` | 待派 | 无派车行/需求刚展开(去槽位化后由虚拟条目呈现) | +| `unassigned_urgent` | 待派·临近出团 | 派生态,出团临近 | +| `holding` | 排车锁定 | 存量待确认执行 | +| `holding_urgent` | 排车锁定·超时 | 派生态 | +| `assigned` | 已派 | 已最终确认 | +| `canceled` | 已取消 | - | +| `completed` | 已完成 | - | + +### `used`(Step2 快照 cell 三态) + +**所属字段**: `AssignmentCandidateRespVO.CanonicalSnapshotVO.SnapshotCellVO.used` | **类型**: `String` + +| 值 | 中文 | 说明 | +|----|------|------| +| `USED` | 实际用车 | 该 group×day 有对应派车行 | +| `UNUSED` | 显式不用车 | 该 group×day 明确不派车 | +| `null` | 未编辑 | 尚未编辑到该格(不缺格,只是三态之一) | + +### `driverAvailability` / `driverSort`(候选查询排序筛选) + +**所属字段**: `AssignmentCandidateReqVO.driverAvailability` / `driverSort` | **类型**: `String` + +| 值 | 中文 | 说明 | +|----|------|------| +| `ALL` | 全部司机 | `driverAvailability` 默认值 | +| `AVAILABLE` | 仅空闲司机 | - | +| `SMART` | 智能排序 | `driverSort` 默认值 | +| `RATING` | 按评分排序 | - | +| `YEARS` | 按驾龄排序 | - | +| `RECENT_ORDER` | 按最近派单排序 | - | + +### `vehicleCategory` / `typeKeys`(车型大类规范 key,看板/矩阵/候选共用) + +**所属字段**: `MatrixUnassignedOrderVO.vehicleCategory` / `BoardOrderPageReqVO.typeKeys` / `MatrixUnassignedReqVO.typeKeys` | **类型**: `String` + +| 值 | 中文 | 说明 | +|----|------|------| +| `suv` | 越野 | - | +| `mpv` | 商务车 | - | +| `bus` | 大巴 | - | +| `sedan` | 轿车 | - | + +## 六.6、修改前后对比 + +### 字段级对比 + +| 字段 | 改前 | 改后 | +|------|------|------| +| `POST /batch` 请求体 | `items[]`(槽位矩阵,含 `fleetItemIndex`/`used`/`pickupParticipant`) | `dailyPlan[]`(服务日×车辆×司机,无槽位序号/用车开关/接机勾选) | +| `POST /batch` 响应 | `assignments[].fleetItemIndex` 是有意义的槽位序号 | `assignments[].fleetItemIndex` 恒为 null(字段保留兼容) | +| 接送机写入位置 | 随 `POST /batch` 的 `items[].pickupParticipant` 一起提交 | 独立 `PUT /pickup-dropoff-config`,两方向各自 `pickupParticipant`/`dropoffParticipant` | +| `POST /candidates` 请求 | 用 `fleetItemIndex` 做归属校验 | 新增 `assignmentGroupId` 做归属校验,`fleetItemIndex` 降级仅供车型/座位回退推断 | +| `POST /candidates` 响应 | `canonicalSnapshot.retainedSlotIds` | `canonicalSnapshot.retainedGroupIds` | +| `POST /candidates` 响应 | `cells[].slotId` | `cells[].groupId` | +| `GET /board/orders` 响应 | 顶层 `assignmentSlots[]`(含 `assignmentSlotId`/`fleetItemIndex`) | 顶层 `dailyAssignments[]`(含 `assignmentGroupId`/`dayDisplayNo`),删除 `assignmentSlots`/`fleetItemIndex`/`assignmentSlotId` | +| `GET /board/orders` 进度 | 按车辆槽位数估算缺口 | 按日行维度四计数(`totalDailyItems` 等),不再按需求声明车数估算缺口 | +| `GET /matrix/unassigned-orders` | 只有真实未派行 | 新增虚拟待派条目(`virtualPending=true`),零派车行订单也会出现 | +| `GET /matrix/unassigned-orders` 排序 | `urgentBadge → startDay → assignmentId` | 追加末位兜底键 `orderNumericId` | +| `POST /requirements/{id}/confirm` | 无接送机门禁 | 新增 605914(接机未配置)/605915(送机未配置)门禁 | +| 错误码 | 605012(槽位不存在)/605911(索引越界)可能触发 | 两码撤销,码位保留不复用 | + +### 行为级对比 + +| 行为 | 改前 | 改后 | +|------|------|------| +| 某天不配车 | 提交一条 `used=false` 的占位项 | 该日不出现在 `dailyPlan[]` 中,无需占位项 | +| 新声明用车需求的订单在看板/矩阵的呈现 | 预建 `unassigned` 占位行才能被发现 | 零派车行时由虚拟待派条目投射呈现,不预建占位行 | +| 接送机配置提交方式 | 排车时逐槽勾选,未勾选的行视为不变 | 独立整批提交,未出现在 `items[]` 的生效行强制归零(非增量 diff) | +| 前端派单向导步数 | 三步(订单详情→排车→确认执行) | 四步(订单详情→排车→接送机→确认执行) | +| 司机 H5 短链身份 | 基于 `assignmentSlotId` 签名 | 基于 `assignmentGroupId` 签名,旧 token 直接 605306 失效(无兼容期) | +| `POST /slots`、`DELETE /slots/{slotId}`、`POST /slots/{slotId}/clear-residue-dates`、`POST /{assignmentId}/cancel-residue` | 四个独立槽位增删/残留清理端点 | 四个端点全部删除,无替代端点;等价操作并入 `POST /batch` 的整批 diff | +| `POST /slots/{slotId}/auto-recommend` | 路径参数为稳定车辆槽位 `slotId` | 路径改为 `POST /{assignmentId}/auto-recommend`,参数改为派单行 `assignmentId` | +| `GET /board/orders/{orderId}` 响应 | `vehicleSlots[]` 按车型聚合为"全程槽位",含 `assignmentSlotId`/`fleetItemIndex`/`slotDisplayNo` | `vehicleSlots[]` 改为日行粒度,字段改 `serviceDate`/`displayNo`,新增 `dropoffParticipant`,删除 `assignmentSlotId`/`fleetItemIndex` | +| `GET /board/orders/{orderId}` 响应 | `dailyVehiclePlan[]` 含 `fleetItemIndex`/`assignmentSlotId` | 两字段删除,新增 `dropoffParticipant` | +| `GET /matrix/grid` 响应 | `assignments[].parallelAssignments[].fleetItemIndex` 存在 | 该字段删除 | +| `GET /matrix/day-orders` 响应 | `assignments[]` 按 `fleetItemIndex` 排序,含该字段 | 该字段删除,排序键改为 `assignmentId` | + +## 六.7、影响评估 + +- **是否破坏向后兼容**: 是。旧字段(`items`/`fleetItemIndex`/`used`/`pickupParticipant` 等)携带即 400,无灰度兼容期。 +- **前端是否必须同步上线**: 是。派单向导(第②③④步)、看板列表/详情、矩阵未派清单/甘特图必须同批改造,否则排车/接送机/确认执行三个写接口全部 400 无法使用。 +- **前端 workaround 清理点**: + - 拖拽/操作身份从 `assignmentSlotId` 全部切到 `assignmentGroupId`(回退 `assignmentId`); + - "`dailyAssignments` 为空即待派"的推断逻辑必须删除,改判 `virtualPending`; + - 候选归属排除从传 `fleetItemIndex` 切到传 `assignmentGroupId`; + - 司机 H5 短链失效后的旧链接不可用,需引导重新获取。 + +## 七、不影响范围 + +- **仅影响**: 管理后台车务派单模块(派单向导四步、派单看板列表/详情、矩阵甘特与未派清单) +- **零影响**: + - C 端小程序/H5 除司机行程短链身份切换外的其余功能 + - 订单创建、订单详情、产品/资源模块 + - 近期出团 dashboard(`GET /internal/fleet/dashboard-summary`,consumer=internal,由 user-service 调用,管理后台前端不直连):按 `virtualPending` 过滤掉虚拟待派条目,保持既有 `assignmentId` 非空契约;响应 VO `FleetUpcomingTripRespVO` 本轮只更新了注释说明,字段未改名/未删列,不在本文档逐接口详情范围内 + - 历史已取消/已完结派车记录的既有数据(不迁移,读侧兼容展示) + - 网关路由:`Path=/admin/fleet/**` 通配路由已覆盖新增端点 `PUT /pickup-dropoff-config`,无需新增路由配置 + +--- + +## 八、测试环境已验证 + +> 环境:`https://api.test.1814.love:9443`(网关)。部署:`hl-fleet-service` 8087+8187、`hl-order-service-v3` 8086+8186 双实例滚动更新,均 UP(2026-09-06 17:38 / 17:39,dev-v3 含 `9130624`)。 +> 账号:`admin` 登录后 `POST /admin/auth/switch-role {"roleId":5}` 切到 `VEHICLE_MANAGER`——网关 `JwtAuthFilter` 对 `/admin/fleet/**` 强制车务角色,非车务角色一律 403,**前端联调必须先切角色**。 + +- [x] `POST /admin/fleet/assignments/batch` 携带 `items[]` 或 `holdMode` → `code=400`,报文:`请求包含已移除的旧字段(items/chargeableServiceDates/vehicleFeeWaiverReason/confirmAllServiceDatesFree/holdMode),请按新契约仅提交 dailyPlan` +- [x] `PUT /admin/fleet/assignments/pickup-dropoff-config` 端点存在,空 `items[]` → `code=200`(整批幂等覆盖语义下「全部取消勾选」是合法请求) +- [x] `POST /admin/fleet/assignments/{assignmentId}/auto-recommend`(改名后)存在 → 传不存在的 ID 返回业务码 `605009 派单不存在` +- [x] 旧路径 `POST /admin/fleet/assignments/slots/{slotId}/auto-recommend` → `code=404 接口不存在` +- [x] 已删除端点 `DELETE /slots/{slotId}`、`POST /slots/{slotId}/clear-residue-dates`、`POST /{assignmentId}/cancel-residue` → 均 `code=404 接口不存在` +- [x] 已删除端点 `POST /admin/fleet/assignments/slots` → `code=405 请求方法不支持: POST`(**不是 404**:`slots` 被 `DELETE /{assignmentId}` 的路径变量匹配走,Spring 判方法不支持。端点确已移除,前端按 404/405 都当作「已下线」处理) +- [x] `POST /admin/fleet/assignments/candidates` → `canonicalSnapshot.retainedGroupIds = ["2087157119630389249"]`、`cells[].groupId` 存在;`retainedSlotIds` / `cells[].slotId` 均已消失;1 组 × 3 个可编辑日 = 3 个 cell(笛卡尔积不缺格),`used` 为三态字符串(实测 `"USED"`) +- [x] `GET /admin/fleet/board/orders` 87 条记录:顶层无 `assignmentSlots` / `fleetItemIndex` / `assignmentSlotId`;每条都带 `dailyAssignments[]` 与 `virtualPending`;日行携带 `assignmentGroupId` / `serviceDate` / `pickupParticipant` / `dropoffParticipant`,无退役字段;`assignmentProgress` 为日行维度四计数(`totalDailyItems` / `dispatchedDailyItems` / `completedDailyItems` / `canceledDailyItems`) +- [x] `GET /admin/fleet/board/orders/{orderId}` `vehicleSlots[]` 已是日行粒度:新增 `serviceDate` / `displayNo` / `dropoffParticipant`,`assignmentSlotId` / `fleetItemIndex` / `slotDisplayNo` 均已移除 +- [x] `GET /admin/fleet/matrix/unassigned-orders` 出现虚拟待派条目:`virtualPending=true`、`assignmentId=null`、`assignmentGroupId=null`、`parallelAssignments=[]` +- [x] 不变量:矩阵未派清单条数(1)== `GET /admin/fleet/matrix/grid` 的 `statusCounts.unassignedOrders`(1) +- [x] `GET /admin/fleet/matrix/grid` `parallelAssignments[]` 已无 `fleetItemIndex`,改带 `assignmentGroupId` +- [x] `GET /admin/fleet/matrix/day-orders?date=YYYY-MM-DD` `assignments[]` 已无 `fleetItemIndex`、带 `assignmentGroupId`,且**不并入**虚拟条目(实测无 `virtualPending=true` 记录) +- [x] 存量占位行清理:按已批准的 `hl-data-cleanup/v1` manifest `7067-unassigned-cleanup.json` 删除 `fleet_assignment` 中 75 条纯占位 `unassigned` 行(`vehicle_id`/`driver_id` 全为 NULL)及关联 17 条操作日志;执行前四道闸门计数与 manifest 逐项一致,92 行已完整备份至 `7067-unassigned-cleanup.backup.json`;删后总行 584→509、剩余 `unassigned`=0 + +**以下两条未取得线上证据,如实登记:** + +- [ ] 看板列表出现 `virtualPending=true` 的虚拟条目 —— **本次未观察到,非缺陷**。看板的虚拟候选窗是 `[今天, 今天+lookahead]`,而清理后测试库里零派车行的需求出发日全部早于今天(最晚 2026-09-01,实测当天为 2026-09-06),按设计「行程已结束的零派车行订单不进看板」被正确排除。矩阵侧(按年月取数)已实测到虚拟条目,读侧投影逻辑本身已验证。该分支由单测覆盖。 +- [ ] `POST /admin/fleet/assignments/requirements/{requirementId}/confirm` 触发 605914 / 605915 —— 需要「大交通声明需接/送机 + 对应日期未配置车辆」的特定数据组合,测试库当前无此样本,未构造(构造需改动他人测试数据)。该门禁由单测覆盖。 + +## 十、相关文档 + +- 展示口径权威源(HL 仓库路径):`docs/tasks/7067-display-matrix.md`(看板/详情/四步向导/矩阵的数据源、状态范围、空态、守恒规则) +- 契约审查(HL 仓库路径):`docs/tasks/7067-contract-review.md`(变更面、Internal Feign 路由核对、序列化与空值约束、错误码变更清单) +- 关联 Issue: [wx/HL#7067](https://git.1814.love:8443/wx/HL/issues/7067) + +## 关联 / 联系人 + +### 链接 + +- **Issue**: [#7067](https://git.1814.love:8443/wx/HL/issues/7067) +- **PR**: #(待回填) + +### 联系人 + +- **后端负责人**: @wx