文件
hl-api-changelog/changelogs-v2/2026-09/06_7067_派单去槽位化按行程日配车-接送机独立配置-修改接口-管理后台.md
T
2026-09-07 21:29:21 +08:00

88 KiB
原始文件 Blame 文件历史

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 ❌ - 跨常驻车显式确认
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<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 独立配置。
  • 幂等:同一 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<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 做归属校验
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 本单新增,当天是否参与送机
vehicleSlots[].assignmentSlotId - 本单删除:稳定车辆槽位 ID
vehicleSlots[].fleetItemIndex - 本单删除:需求车辆槽位序号
dailyVehiclePlan[].dropoffParticipant Boolean 本单新增,当天是否参与送机(S3 接送机独立配置)
dailyVehiclePlan[].fleetItemIndex - 本单删除:稳定车辆槽位序号
dailyVehiclePlan[].assignmentSlotId - 本单删除:稳定车辆槽位 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[].parallelAssignments[].fleetItemIndex - 本单删除:需求车型项序号
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
assignments[].fleetItemIndex - 本单删除:同订单内派单序号

请求示例

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 非空契约;响应 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,前端联调必须先切角色。

  • POST /admin/fleet/assignments/batch 携带 items[] 或 holdMode → code=400,报文:请求包含已移除的旧字段(items/chargeableServiceDates/vehicleFeeWaiverReason/confirmAllServiceDatesFree/holdMode),请按新契约仅提交 dailyPlan
  • PUT /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/orders 87 条记录:顶层无 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/grid parallelAssignments[] 已无 fleetItemIndex,改带 assignmentGroupId
  • GET /admin/fleet/matrix/day-orders?date=YYYY-MM-DD assignments[] 已无 fleetItemIndex、带 assignmentGroupId,且不并入虚拟条目(实测无 virtualPending=true 记录)
  • 存量占位行清理:按已批准的 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 —— 需要「大交通声明需接/送机 + 对应日期未配置车辆」的特定数据组合,测试库当前无此样本,未构造(构造需改动他人测试数据)。该门禁由单测覆盖。

九之二、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