88 KiB
schema, ticket, title, consumer, author, change_type, backend_status, gateway_status, frontend_status, frontend_owner, frontend_ref, target_release, verified_at, status_note, updated_at, base
| schema | ticket | title | consumer | author | change_type | backend_status | gateway_status | frontend_status | frontend_owner | frontend_ref | target_release | verified_at | status_note | updated_at | base |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| hl-changelog/v2 | 7067 | 派单去槽位化:按行程日配车 + 接送机独立配置(管理后台四步向导) | admin | wx(GIT) | 修改接口 | deployed | verified | verified | mmg | af4ad08c | 2026-09-07 | 前端 U1-U7 全部交付并验证。U1(死端点下线+auto-recommend 改路径+updatePickupDropoffConfig)、U2(看板/矩阵读取面切日行粒度,44dafb4d)、U3(canonical 快照 retainedSlotIds→retainedGroupIds,c4e63bf3)、U4(/batch 提交体改稀疏 dailyPlan 去槽位坐标,6cef244a)、U5(向导四步化+第③步接送机,并入 #7203 整段不用车,7d21f35a)、U6(confirm 605914/605915 接送机覆盖门禁分支,透原文+跳第③步,先于幂等回执)、U7(退役字段全量清扫:AssignModal 展示档/基线键去 assignmentSlotId/fleetItemIndex,死函数 validateFleetSlotSelections 修 NaN+误报;保留疑似活契约 singleton create/stale 快照/failedFleetItemIndex 横幅,因 activeAssignments 明示未变更,已加注待后端确认)——U6/U7 合 commit af4ad08c,各步 checkpoint 全绿(含 Vitest 全量+生产构建),收尾 724/724 绿,对抗 review PASS。 | 2026-09-07 | dev-v3 |
车务派单:去槽位化按行程日配车与接送机独立配置(管理后台)
服务: hl-fleet-service (8087) PR: #7194、#7218(返工二轮)、#7223(端到端实测暴露的三处阻断) 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(码位保留不复用,不会再出现在任何响应里)。
- (2026-09-07 返工补充)
progressSteps由 3 步变 4 步,CONFIRM_EXECUTE的 step 号 3 → 4:按下标或按数组长度取值的前端会直接破,改为按code取值。详见「九之二」B1。 - (2026-09-07 返工补充)
POST /batch提交成功不再等于订单车控完成:大交通声明要接/送机而第③步未配齐时,最终方案不发布、订单停在处理中。前端必须读响应新增的finalPlanPublished。详见「九之二」B2。 - (2026-09-07 返工补充)看板详情
pickupDropoffGate为null表示「门禁未知」(order-v3 降级),不是「无接送机声明」。详见「九之二」B3。
一、背景
去槽位化前,"车辆槽位"是排车的固定拓扑单位:一个槽位对应需求声明的一个车辆项,逐日切片必须挂在某个槽位下,且排车与接送机勾选耦合在同一个提交动作里。这带来两个问题:一是同一天需要多辆车时无法表达(一槽一天一行的限制);二是新声明用车需求但车务尚未处理的订单,必须先靠"需求展开"预建 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 | ❌ | - | 跨常驻车显式确认 |
| Body | - | ❌ | 携带非 null 值即 400 | 已移除:旧槽位序号矩阵 | |
| Body | - | ❌ | 携带非 null 值即 400 | 已移除:旧收费日期字段 | |
| Body | - | ❌ | 携带非 null 值即 400 | 已移除 | |
| Body | - | ❌ | 携带非 null 值即 400 | 已移除 | |
| Body | - | ❌ | 携带非 null 值即 400 | 已移除:#5827 起一步派定 | |
| Body | - | ❌ | 携带非 null 值即 400 | 已移除:稳定车辆槽位序号 | |
| Body | - | ❌ | 携带非 null 值即 400 | 已移除:用车开关(不配车=无该项) | |
| Body | - | ❌ | 携带非 null 值即 400 | 已移除:接机标志改由接送机配置步骤写入 |
出参 Result<BatchAssignmentWriteRespVO>
| 字段 | 类型 | 说明 |
|---|---|---|
| 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 | 基线不一致时的逐日差异 |
请求示例
{
"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)
响应示例
{
"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 返回空数组 []。本接口是同步写操作,无下游降级路径——依赖资源不可用时直接抛错,不做静默降级。
错误响应
{
"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独立配置。 - 幂等:同一
requestId300 秒窗口内重复提交直接拒绝「批量派单创建处理中,请勿重复提交」。 - 提交即执行最终基线复核;不一致返回 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<Void>
| 字段 | 类型 | 说明 |
|---|---|---|
| data | null | 本接口无响应体,成功仅 code=200 |
请求示例
{
"orderId": "70123456789",
"requirementId": "70123456790",
"requestId": "pdc-7067-0001",
"items": [
{ "assignmentId": "2086270160359878658", "pickupParticipant": true, "dropoffParticipant": false },
{ "assignmentId": "2086270160359878659", "pickupParticipant": false, "dropoffParticipant": true }
]
}
响应示例
{ "code": 200, "message": "成功", "data": null, "success": true }
空数据 / 降级响应
items 字段只校验非 null(无 @NotEmpty),传空数组 [] 是合法请求,语义为「清空该订单当前需求下全部生效派车行的接送机标志」(两方向都归 0)。本接口无读侧降级路径。
错误响应
{
"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<ConfirmRequirementRespVO>
| 字段 | 类型 | 说明 |
|---|---|---|
| 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 |
请求示例
{
"orderId": "70123456789",
"requestId": "confirm-req-7067-0001",
"expectedRequirementVersion": 3,
"expectedRequirementSha256": "a1b2c3d4e5f6000000000000000000000000000000000000000000000000",
"expectedPlanGeneration": "1934500000000000001",
"groups": [
{ "assignmentGroupId": "2086270160359878658", "sendItinerarySms": true }
]
}
(路径参数 requirementId=70123456790)
响应示例
{
"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,不阻断本次确认。
错误响应
{
"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 做归属校验 |
| 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>
完整字段见源码 AssignmentCandidateRespVO.java,下表为本单变更及核心字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| vehicles | Object | 车辆候选独立分页(CandidatePageVO) |
| drivers | Object | 司机候选独立分页(CandidatePageVO) |
| suggestedDriverId | String | 已选车辆自动代入司机 ID |
| canonicalSnapshot | Object | Step2 canonical 快照;无 requirementId 或需求上下文不可用时为 null |
| canonicalSnapshot.retainedGroupIds | Array<String> | 本单由 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 |
请求示例
{
"requirementId": "70123456790",
"startDate": "2026-08-20",
"endDate": "2026-08-22",
"headcount": 5,
"assignmentGroupId": "2086270160359878658",
"vehiclePage": 1,
"vehiclePageSize": 20,
"driverPage": 1,
"driverPageSize": 20
}
响应示例
{
"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 本身仍存在(笛卡尔积不缺格)。
错误响应
{
"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<BoardOrderPageRespVO>
完整字段见源码 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 | 当前派单状态(含派生态) |
请求示例
GET /admin/fleet/board/orders?statuses=unassigned_urgent&statuses=holding&page=1&pageSize=20
响应示例
{
"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。虚拟待派条目示例:
{ "assignmentId": null, "assignmentGroupId": null, "dailyAssignments": [], "virtualPending": true }
错误响应
{
"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<MatrixUnassignedOrderVO>
使用场景
矩阵派单页右侧"未派订单"面板加载时调用;车务从此处把订单拖拽到左侧甘特图某车某日发起派车。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| year | Query | Integer | ✅ | 1970-9999 | 年份 |
| month | Query | Integer | ✅ | 1-12 | 月份 |
| typeKeys[] | Query | String[] | ❌ | suv/mpv/bus/sedan,空=全部 |
车型大类多选 |
出参 Result<List<MatrixUnassignedOrderVO>>
完整字段见源码 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 | 同订单全部并行派车组;虚拟条目恒空数组 |
请求示例
GET /admin/fleet/matrix/unassigned-orders?year=2026&month=8&typeKeys=suv&typeKeys=mpv
响应示例
{
"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(数据源未建)。
错误响应
{
"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<SlotAutoRecommendRespVO>
| 字段 | 类型 | 说明 |
|---|---|---|
| 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 | 是否无可用推荐 |
请求示例
POST /admin/fleet/assignments/2086270160359878658/auto-recommend
响应示例
{
"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 为空数组,前端提示人工选配;本接口只读不产生写操作,无其他降级路径。
错误响应
{
"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<Void>
| 字段 | 类型 | 说明 |
|---|---|---|
| (无) | - | 本接口已删除,不再返回任何响应体 |
请求示例
{ "_deprecated": "本接口已删除,以下为历史请求体结构,仅供归档参考", "requirementId": "70123456789", "vehicleType": "suv", "seats": 5, "reason": "改派时多派一辆" }
响应示例
{ "_deprecated": "本接口已删除,调用将得到 404,不会再有下列历史响应体", "slotId": "1934567890123456789", "assignmentId": "1934567890123456790", "fleetItemIndex": 1 }
空数据 / 降级响应
无。路径已整体移除,网关/服务对该路径的任何请求返回 404,不做业务层降级。
错误响应
{
"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<Void>
| 字段 | 类型 | 说明 |
|---|---|---|
| (无) | - | 本接口已删除,不再返回任何响应体 |
请求示例
{ "_deprecated": "本接口已删除,以下为历史请求体结构,仅供归档参考", "reason": "定制师傅建议的槽位不需要" }
响应示例
{ "_deprecated": "本接口已删除,调用将得到 404,不会再有下列历史响应体", "slotId": "1934567890123456789", "removedRowCount": 1, "cancelledAssignmentCount": 0 }
空数据 / 降级响应
无。路径已整体移除,网关/服务对该路径的任何请求返回 404,不做业务层降级。
错误响应
{
"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<Void>
| 字段 | 类型 | 说明 |
|---|---|---|
| (无) | - | 本接口已删除,不再返回任何响应体 |
请求示例
{ "_deprecated": "本接口已删除,以下为历史请求体结构,仅供归档参考", "serviceDates": ["2026-08-01", "2026-08-02"], "reason": "订单改期后旧日期残留" }
响应示例
{ "_deprecated": "本接口已删除,调用将得到 404,不会再有下列历史响应体", "clearedServiceDates": ["2026-08-01"], "cancelledRowCount": 1 }
空数据 / 降级响应
无。路径已整体移除,网关/服务对该路径的任何请求返回 404,不做业务层降级。
错误响应
{
"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<Void>
| 字段 | 类型 | 说明 |
|---|---|---|
| (无) | - | 本接口已删除,不再返回任何响应体 |
请求示例
{ "_deprecated": "本接口已删除,以下为历史请求体结构,仅供归档参考", "requestId": "residue-cancel-20260812-001", "cancelReason": "改期残留逐日取消" }
响应示例
{ "_deprecated": "本接口已删除,调用将得到 404,不会再有下列历史响应体(历史响应体为内部结果对象,字段与创建派单响应相同)" }
空数据 / 降级响应
无。路径已整体移除,网关/服务对该路径的任何请求返回 404,不做业务层降级。
错误响应
{
"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>
完整字段见源码 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 | 本单新增,当天是否参与送机 |
| - | 本单删除:稳定车辆槽位 ID | |
| - | 本单删除:需求车辆槽位序号 | |
| dailyVehiclePlan[].dropoffParticipant | Boolean | 本单新增,当天是否参与送机(S3 接送机独立配置) |
| - | 本单删除:稳定车辆槽位序号 | |
| - | 本单删除:稳定车辆槽位 ID |
请求示例
GET /admin/fleet/board/orders/70123456789
响应示例
{
"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 等占位字段显示占位文案,不报错);无行程返回空天列表,不生成伪数据。
错误响应
{
"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>
完整字段见源码 MatrixGridRespVO.java(vehicles[]/statusCounts/fleetTeamCounts 等本单未变更);下表为本单变更字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| vehicles[].assignments[].parallelAssignments[] | Array | 同订单当前全部并行派车组 |
| - | 本单删除:需求车型项序号 | |
| vehicles[].assignments[].assignmentGroupId | String | 派车组 ID;历史行无该值时回退 assignmentId(本单起口径统一) |
请求示例
GET /admin/fleet/matrix/grid?year=2026&month=8
响应示例
{
"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。
错误响应
{
"code": 605010,
"message": "月份超出范围",
"success": false,
"data": null
}
业务边界
parallelAssignments[]不再携带fleetItemIndex;前端若曾用它做并行组去重键,须改用assignmentGroupId。assignmentGroupId历史行无值时统一回退assignmentId,对任何真实行恒非空(本单起在全部矩阵响应中口径一致)。
14. 矩阵当天订单清单 GET /admin/fleet/matrix/day-orders
VO: (RequestParam date) → List<MatrixDayOrderVO>
使用场景
点矩阵日期列头弹出当天所有订单(含已派+未派)时调用。本单变更:assignments[].fleetItemIndex 字段删除,排序键由 fleetItemIndex 改为 assignmentId;本接口明确不并入虚拟待派条目(它是「每辆车一条」的执行视图,虚拟条目无行可列)。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| date | Query | String | ✅ | 格式 YYYY-MM-DD |
日期 |
出参 Result<List<MatrixDayOrderVO>>
完整字段见源码 MatrixDayOrderVO.java;下表为本单变更字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| assignments[] | Array | 该订单展开的派单列表;本单起按 assignmentId 排序(原按 fleetItemIndex 排序) |
| assignments[].assignmentGroupId | String | 派车组 ID;历史行无该值时回退 assignmentId |
| - | 本单删除:同订单内派单序号 |
请求示例
GET /admin/fleet/matrix/day-orders?date=2026-05-04
响应示例
{
"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
}
空数据 / 降级响应
当天无订单覆盖返回空数组 []。本接口不并入虚拟待派条目——它是「每辆车一条」的执行视图,零派车行的订单在这里无行可列,不会出现在本响应中;待派发现请走看板列表或矩阵未派清单。
错误响应
{
"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非空契约;响应 VOFleetUpcomingTripRespVO本轮只更新了注释说明,字段未改名/未删列,不在本文档逐接口详情范围内 - 历史已取消/已完结派车记录的既有数据(不迁移,读侧兼容展示)
- 网关路由:
Path=/admin/fleet/**通配路由已覆盖新增端点PUT /pickup-dropoff-config,无需新增路由配置
八、测试环境已验证
环境:
https://api.test.1814.love:9443(网关)。部署:hl-fleet-service8087+8187、hl-order-service-v38086+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,前端联调必须先切角色。
POST /admin/fleet/assignments/batch携带items[]或holdMode→code=400,报文:请求包含已移除的旧字段(items/chargeableServiceDates/vehicleFeeWaiverReason/confirmAllServiceDatesFree/holdMode),请按新契约仅提交 dailyPlanPUT /admin/fleet/assignments/pickup-dropoff-config端点存在,空items[]→code=200(整批幂等覆盖语义下「全部取消勾选」是合法请求)POST /admin/fleet/assignments/{assignmentId}/auto-recommend(改名后)存在 → 传不存在的 ID 返回业务码605009 派单不存在- 旧路径
POST /admin/fleet/assignments/slots/{slotId}/auto-recommend→code=404 接口不存在 - 已删除端点
DELETE /slots/{slotId}、POST /slots/{slotId}/clear-residue-dates、POST /{assignmentId}/cancel-residue→ 均code=404 接口不存在 - 已删除端点
POST /admin/fleet/assignments/slots→code=405 请求方法不支持: POST(不是 404:slots被DELETE /{assignmentId}的路径变量匹配走,Spring 判方法不支持。端点确已移除,前端按 404/405 都当作「已下线」处理) POST /admin/fleet/assignments/candidates→canonicalSnapshot.retainedGroupIds = ["2087157119630389249"]、cells[].groupId存在;retainedSlotIds/cells[].slotId均已消失;1 组 × 3 个可编辑日 = 3 个 cell(笛卡尔积不缺格),used为三态字符串(实测"USED")GET /admin/fleet/board/orders87 条记录:顶层无assignmentSlots/fleetItemIndex/assignmentSlotId;每条都带dailyAssignments[]与virtualPending;日行携带assignmentGroupId/serviceDate/pickupParticipant/dropoffParticipant,无退役字段;assignmentProgress为日行维度四计数(totalDailyItems/dispatchedDailyItems/completedDailyItems/canceledDailyItems)GET /admin/fleet/board/orders/{orderId}vehicleSlots[]已是日行粒度:新增serviceDate/displayNo/dropoffParticipant,assignmentSlotId/fleetItemIndex/slotDisplayNo均已移除GET /admin/fleet/matrix/unassigned-orders出现虚拟待派条目:virtualPending=true、assignmentId=null、assignmentGroupId=null、parallelAssignments=[]- 不变量:矩阵未派清单条数(1)==
GET /admin/fleet/matrix/grid的statusCounts.unassignedOrders(1) GET /admin/fleet/matrix/gridparallelAssignments[]已无fleetItemIndex,改带assignmentGroupIdGET /admin/fleet/matrix/day-orders?date=YYYY-MM-DDassignments[]已无fleetItemIndex、带assignmentGroupId,且不并入虚拟条目(实测无virtualPending=true记录)- 存量占位行清理:按已批准的
hl-data-cleanup/v1manifest7067-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 —— 需要「大交通声明需接/送机 + 对应日期未配置车辆」的特定数据组合,测试库当前无此样本,未构造(构造需改动他人测试数据)。该门禁由单测覆盖。
九之二、2026-09-07 第二轮返工的契约增量(前端必读)
#7067 于 2026-09-06 被验收打回三项 P1,本节是返工后的契约增量,PR #7218 + #7223(均合 dev-v3)。 前三条是会破坏现有渲染的变化,请优先处理。
⚠️ B1(破坏性)progressSteps 由 3 步变 4 步,CONFIRM_EXECUTE 的 step 号 3 → 4
上一版本节把 progressSteps 列为「本单未变更字段」,这是错的,本次更正。
| step | code | name | 说明 |
|---|---|---|---|
| 1 | ORDER_DETAIL | 订单详情 | 不变 |
| 2 | DISPATCH | 排车 | 不变 |
| 3 | PICKUP_DROPOFF | 接送机 | 本次新增的一步 |
| 4 | CONFIRM_EXECUTE | 确认执行 | step 由 3 变 4 |
按数组下标或按数组长度取值的前端会直接破,请改为按 code 取值。
第③步的状态取自接送机门禁:
- 门禁已满足(无接送机声明,或已配齐)→
DONE,不置active; - 有声明但未配齐 →
PROCESSING且active=true,同时把第④步压回WAITING; - 门禁未知(见 B3)→
WAITING且不置active,第④步同样WAITING。
⚠️ B2(破坏性)POST /admin/fleet/assignments/batch 提交成功不再等于订单车控完成
第②步排车照常落库并推进 assigned,但大交通声明要接/送机而第③步尚未配齐时,DAILY_V3 最终方案被压住不发,order-v3 侧 vehicle_control_status 停在 PROCESSING。
响应 Result<BatchAssignmentWriteRespVO> 新增两个字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| finalPlanPublished | Boolean | 本次是否已发布最终方案。false = 排车已落库但接送机未配齐,必须继续走第③步 |
| pickupDropoffGate | Object | 接送机门禁状态,结构见 B4 |
前端必须读 finalPlanPublished:为 false 时不能提示「派单完成」,应引导用户去第③步接送机;为 true 时才是完成态。第③步配齐后服务端会立即补发最终方案,无需回到第②步重提交。
⚠️ B3(破坏性)pickupDropoffGate 整体为 null 表示「门禁未知」,不是「无声明」
GET /admin/fleet/board/orders/{orderId} 的顶层 pickupDropoffGate 在 order-v3 详情上下文降级(拉取失败)时返回 null。此时大交通拿不到权威值,服务端不给出乐观判定。
前端按「未知」渲染:不展示「接送机已完成」,也不展示缺口日期条;第③④步按 B1 停在 WAITING。
不要把 null 当作「该订单没有接送机要求」——写侧用的是 strict 拉取,真实状态可能是「有声明未配齐、订单仍在处理中」,两侧结论会正好相反。
B4 接送机门禁结构 PickupDropoffGateVO
出现在三处:POST /batch 响应、PUT /pickup-dropoff-config 响应、GET /board/orders/{orderId} 顶层。三处同一份结构、同一套判据。
| 字段 | 类型 | 必返 | 说明 |
|---|---|---|---|
| arrivalRequiredDates | Array<Date> | ✅ | 大交通声明需要接机的服务日(升序) |
| departureRequiredDates | Array<Date> | ✅ | 大交通声明需要送机的服务日(升序) |
| missingPickupDates | Array<Date> | ✅ | 其中尚未配置接机车辆的服务日(升序) |
| missingDropoffDates | Array<Date> | ✅ | 其中尚未配置送机车辆的服务日(升序) |
| declared | Boolean | ✅ | 该订单大交通是否有任一方向的接送机声明 |
| satisfied | Boolean | ✅ | 门禁是否已满足(无声明或已配齐)。false 时最终方案不发布、订单车控停在处理中 |
declared=false 时四个数组均为空、satisfied=true——门禁对无接送机声明的订单整体让路,允许车务手工勾选但不强制。
已完结(completed)的服务日豁免:行程中换版平移后该日事实已随历史派车固化,不会再要求重配。
B5 PUT /admin/fleet/assignments/pickup-dropoff-config 响应类型变更
由原来的空响应改为 Result<PickupDropoffConfigRespVO>:
| 字段 | 类型 | 必返 | 说明 |
|---|---|---|---|
| finalPlanPublished | Boolean | ✅ | 本次保存是否补发了最终方案快照(配齐即发)。只在门禁由「不满足」跨到「满足」的那一次为 true;重复保存同样已满足的状态不会重复发版 |
| requirementReopened | Boolean | ✅ | 本次是否把已完成的订单拉回处理中(见 B6) |
| reopenBlockedReason | String | 否 | 本该拉回处理中却没拉回的原因;null=不适用或已成功拉回。当前唯一取值 REQUIREMENT_LIFECYCLE_DISABLED(服务端需求级生命周期开关未启用),见 B6 |
| pickupDropoffGate | Object | ✅ | 写入后的门禁状态,结构见 B4 |
保存成功后订单状态到底变没变,前端从这两个布尔值当场就能知道,不需要再拉一次看板详情。
B6 在已完成的订单上清掉要求日的接送机勾选 → 订单被拉回处理中
车务改接送机安排是正常业务动作,服务端不拒绝提交;但订单车控已经是完成态时,清掉要求日的勾选会写一条「重开」意图把用车需求拉回 PROCESSING,响应里 requirementReopened=true。前端应据此把订单状态从「已完成」改回「处理中」,并提示需要重新配齐接送机。
例外:服务端配置 fleet.assign.requirement-lifecycle-enabled=false(application.yml 默认值;测试服 Nacos hl-fleet-service-test.yml 已显式置 true,故测试环境走的是正常重开路径)时,重开意图不会被投递。此时接口不拒绝、照常落库,但返回 requirementReopened=false + reopenBlockedReason="REQUIREMENT_LIFECYCLE_DISABLED"——含义是「接送机勾选已保存,但订单状态没能拉回处理中」。
前端在拿到 reopenBlockedReason 时应提示用户「保存成功,但订单仍显示已完成,请联系运维开启需求级生命周期开关后重新保存一次」,不要把它当成失败,也不要把订单画成已回到处理中。
(为什么不整笔拒绝:该开关默认关闭,硬拒等于「改接送机安排」这条正常业务动作在默认配置下永远不可用,还会连带丢弃同一次提交里其它合法的勾选修改。)
B7 逐日 dropoffRequired(读侧新增,与 pickupRequired 对称)
GET /admin/fleet/board/orders/{orderId} 的 dailyVehiclePlan[] 每一项新增 dropoffRequired(Boolean):当天大交通是否要求送机。原先只有 pickupRequired(要求接机),设计文档声称有 dropoffRequired 而代码里没有,本次补齐。
注意区分同名的两组字段:
pickupRequired/dropoffRequired= 大交通要求当天接/送机(只读,来自订单)pickupParticipant/dropoffParticipant= 该派车行实际被勾为当天的接/送机车(可写,第③步配置)
B8 服务端配置(不影响契约,供排障参考)
| Nacos 键 | 默认 | 作用 |
|---|---|---|
fleet.assign.pickup-dropoff-gate-enabled |
true |
接送机门禁总开关。关闭即整体退回返工前行为(无条件发布最终方案、不做 605914/605915 断言),作为线上回滚阀 |
fleet.assign.requirement-lifecycle-enabled |
false |
需求级生命周期 Outbox 开关,见 B6 的例外 |
B9 后端内部修复(无契约变更,但影响可观察行为)
- 改派后订单不再卡在处理中:去槽位化后改派生成的替换行带全新派车组 ID 且不继承槽位,旧实现认不出「墓碑↔替换行」是同一身份,导致方案被永久判为不完整、最终方案不发布、订单卡
PROCESSING且无自动恢复路径。已改为改派时在同一事务里作废墓碑的定稿身份。前端无需改动,但此前观察到的「改派完订单一直显示处理中」现象随之消失。 - 同源修复:改派之后不能再通过「撤销取消」把旧车复活成同日第二辆在途车。
- 「撤销取消」重建最终方案时也会先过接送机门禁,不再绕过。
以下两条是 2026-09-07 测试服端到端实测才暴露的缺陷,均已随本次修复上线;前端无需改动,但此前的异常现象会随之消失:
- 改派必现
400 字段【assignment_slot_id】未填写,且派单操作时间线自 #7067 上线起停止记录。 操作日志表fleet_assignment_operation_log.assignment_slot_id仍是 NOT NULL 且无默认值, 而去槽位化后写入侧是无条件落 NULL(与锚点行是不是存量行无关),MySQL 严格模式直接 打断整笔事务。实测边界:改派 400(会写日志),确认执行 200(不写日志,不受影响);fleet_assignment_operation_log最后一条记录停在 2026-08-31、此后零新增。 已由 Flyway 把该列放开为可空,改派与操作时间线随之恢复。 - 手工调价的订单车控可能永久停在「处理中」。派车行的调价时间列是秒级精度,
MySQL 落库时对亚秒部分四舍五入,可能比同一笔快照的完成时间晚零点几秒,
order-v3 据此判定「调价晚于完成」并整份拒收 DAILY_V3 快照(约一半概率必现,
自动计价的行不受影响)。已两侧修复:写入侧秒级下取整,校验侧按列精度给 1 秒容差。
修复前已被隔离的快照事件需要运维走
POST /internal/fleet/jobs/vehicle-assignment-snapshot-outbox/{eventId}/requeue重排一次。
九之三、2026-09-07 第三轮返工(独立复审缺口)的契约增量
第二轮返工合并后的独立复审发现 2 处逻辑缺口,均属历史数据兼容与「承诺与实现不符」。 本轮对前端没有破坏性变更:没有新增/删除字段,没有改字段名或类型。 只有一个字段的触发面变宽,和一个原因码取值新增——两条都会让前端少踩坑,不用改代码也能跑, 但按下面的口径调提示语会更准确。
C1 requirementReopened 的触发条件从「跃迁」变成「状态补偿」
PUT /admin/fleet/assignments/pickup-dropoff-config 响应里的 requirementReopened:
- 以前:只有「本次保存恰好把门禁从满足变成不满足」才为
true。 - 现在:只要「订单车务仍是完成态、而写入后的门禁不满足」就为
true,不要求本次保存发生跃迁。
为什么改:旧判据有个进不去的死角。当运维把 fleet.assign.requirement-lifecycle-enabled
关着的时候清空接送机勾选,第一次保存会如实返回
reopenBlockedReason=REQUIREMENT_LIFECYCLE_DISABLED(订单没被拉回);
随后运维开启开关、车务重新保存一次时,保存前后门禁都已经是不满足,跃迁不成立,
旧代码永远进不到重开分支——requirementReopened 恒 false、reopenBlockedReason 还退化成 null,
订单永久停在「已完成但门禁不满足」的不一致态。这与九之二 B6 里写的
「开关打开后重存一次即可收敛」直接矛盾。改成补偿判据后那句承诺才真正成立。
对前端的影响:字段名与类型不变,原有「true 就提示订单已回到处理中」的处理逻辑仍然成立,
只是会在更多场景下为 true(例如车务只是改了另一天的勾选、而某个要求日本来就没配齐)。
另外重复保存是幂等的:同一需求已有未消费的重开意图时不会再写第二条,此时仍返回 true
(含义是「已被拉回/重开意图已在途」)。
C2 reopenBlockedReason 新增取值 NO_DISPATCH_ANCHOR
| 取值 | 含义 | 前端提示建议 |
|---|---|---|
null |
不适用,或已成功拉回处理中 | 无需提示 |
REQUIREMENT_LIFECYCLE_DISABLED |
需求级生命周期开关未启用;勾选已落库但订单仍是完成态 | 提示「配置已保存,订单状态需运维开启开关后重存一次收敛」 |
NO_DISPATCH_ANCHOR(新增) |
该需求下已没有可作锚点的派车行(只剩已取消/异常/未派占位行),本该拉回却拉不回 | 提示「配置已保存,但该需求已无有效派车,请回第②步重新排车」 |
以前这种情况会返回 requirementReopened=false + reopenBlockedReason=null,
按契约会被读成「不适用」,而实际是「本该拉回却没拉回」——是个沉默失败。现在显式回报。
C3 「重存一次即可收敛」的前提(B6 的补充说明)
九之二 B6 说「开关打开后重存一次即可收敛」,这句话有个前提:要求日上得有可配置的派车行。
如果某个接送机要求日根本没有排车(该日不配车),门禁在第③步怎么存都满足不了,
重存只会反复把订单拉回处理中——这种情况要回第②步先给该日排车,不是第③步能自愈的。
前端在 pickupDropoffGate.missingPickupDates / missingDropoffDates 命中的日期上,
若该日在排车结果里没有任何车,应引导回第②步而不是让用户在第③步反复点保存。
C4 后端内部修复(无契约变更,但影响可观察行为)
- 存量订单二次改派后,最终方案不再被误判为不完整。 旧模型下已经从 A 改派到 B 的订单(两者共享历史车位血缘),再把 B 改派成新模型的 C 之后, 更早的取消记录 A 会失去同血缘的有效行、重新被计入完成条件,导致改派后的最终方案发不出去、 订单车控卡在「处理中」且没有自动回到「已完成」的路径。 已修复为:改派时把「因本次改派而失去同血缘有效行」的整条历史链路一并退休。 「整槽取消待恢复」的记录不受影响,仍照旧计入完成条件(该防线未被削弱)。
十、相关文档
- 展示口径权威源(HL 仓库路径):
docs/tasks/7067-display-matrix.md(看板/详情/四步向导/矩阵的数据源、状态范围、空态、守恒规则) - 契约审查(HL 仓库路径):
docs/tasks/7067-contract-review.md(变更面、Internal Feign 路由核对、序列化与空值约束、错误码变更清单) - 关联 Issue: wx/HL#7067
关联 / 联系人
链接
- Issue: #7067
- PR: #(待回填)
联系人
- 后端负责人: @wx