文件
hl-api-changelog/changelogs-v2/2026-09/20_7443_TRANSFER派车行确认改派基线复核修复-修改接口-管理后台.md
Mimingguang ac12dabe20
changelog-filename-gate / validate (push) Failing after 2s
docs(changelog): #7443 C 车务侧+13_7439/18_7443/20_7990 前端已交付 verified(hl-admin v2.1 662310ea/6c091ef24)
18_7443 挂起期回头补落地(派车弹窗 kind 切换+batch/pickup-dropoff-config 显式 kind);
20_7990 requirementIdentities 已消费;13_7439 硬契约点 A+B 已补(809008/显式 kind/reject 走 query);
20_7443 AC-24 维持 not_required 仅补 C 段实证
2026-09-21 17:52:22 +08:00

59 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 7443 TRANSFER 派车行确认/改派基线复核修复(605905/605041 不再误判) admin wx(GIT) 修改接口 deployed verified not_required 【2026-09-20 AC-26 收尾】gateway_status 回填 verified,四个端点(「二、变更接口清单」全部条目)均经网关实测(cw_test_7443 账号,dev-v3 测试服,fleet 部署 dev-v3@311dc92ee,2026-09-20 17:46:56,含 PR #7952/c527e48c3 + #8048 + #8042;取证前后各跑一次 deploy-status.sh,两次 fleet/order-v3 commit 一致,证据有效):(1) POST /admin/fleet/assignments/candidates(requirementId=2101219393722634242):canonicalSnapshot 非 null;(2) POST /admin/fleet/assignments/{id}/change(TRANSFER 派车行 2101174428044824578):code=200;(3) POST /admin/fleet/assignments/{id}/confirm(TRANSFER 派车行 2101174428711686146):code=200/confirmed=true/assignmentStatus=assigned(此前记录的 582093 已由 #8037 修复);(4) POST /admin/fleet/assignments/requirements/{requirementId}/confirm(TRANSFER 需求 2101567456596365313,订单 2101566624467419137):借新上线的 GET /admin/fleet/board/orders/{orderId} 返回的 requirementIdentities(#7990/#8048 一并带来的新字段,含 requirementVersion/requirementSha256/dispatchPlanGeneration 三个本端点必需的真值,此前全仓无接口能给出)取得 expectedRequirementVersion=1/expectedRequirementSha256=814e3f85.../expectedPlanGeneration=359984828583645184,groups[] 取自 candidates 返回的 retainedGroupIds 两个 assignmentGroupId,实测 code=200/confirmed=true/finalPlanPublished=true。四项证据补全,判据满足,回填 verified。 前端实证维持 not_required(mmg 2026-09-20):四端点入参/出参零变化、TRAVEL 逐字不变、TRANSFER 前端不可达(809009 开关仍关,#7443 维持挂起);canonicalSnapshot=null 自验:AssignModal applyCanonicalSnapshotToDraft 首行 `!snapshot` 静默 return,不报错、草稿保持现状,合法降级形态既有处理;#7443 启动条件更新为 产品排期+809009 开关+outbox 修复交接件(AC-24 本件已销),已入前端 memory。【mmg 2026-09-21 C 段实证补充】#7443 C 交付(hl-admin v2.1 662310ea)再次确认本件前端零改动:四端点请求/响应契约逐字未变,改派按 assignmentId 反查服务端自取基线,TRANSFER 行零改动可用;canonicalSnapshot=null 合法降级既有处理(applyCanonicalSnapshotToDraft 首行静默 return)不动。 2026-09-20 dev-v3

fleet/order-v3: TRANSFER 派车行确认/改派基线复核修复(605905/605041 不再误判)

存放目录: changelogs-v2/{YYYY-MM}/

服务: hl-fleet-service、hl-order-service-v3(共享 hl-common-core 的 Feign DTO) PR: 尚未创建(三个提交 abd435c15/694e405fd/15c7cdd0c,本地分支 feature/7443-rest-ac,尚未合入 dev-v3) Issue: #7443 AC-24 日期: 2026-09-18 影响范围: 管理后台车务派单弹窗——按需求原子确认、车务确认执行、修改派单、查询派单候选资源(Step2 canonical 快照)四个既有端点,仅当对象(派车行/需求/候选查询)属于 kind=TRANSFER(接送机)时行为发生变化;另有一处内部作业端点(重发最终方案快照)不面向 admin 前端


⚠️ 关键变化

一句话:kind=TRANSFER 建出来的派车行,以前建得出来但操作不动——现在通了。三个既有端点 (按需求原子确认、车务确认执行、修改派单)的入参、出参零变化,变的只是内部"拿哪条需求当 基线比对"的逻辑。

根因(写给前端知道以前为什么会失败):fleet 侧这三个端点的最终基线复核,原来一律拿 OrderFleetDetailContextDTO.vehicleRequirement 当基线;而 order-v3 提供这个字段时恒为 TRAVEL(OrderFleetProviderService.java:1027-1029 写死取 kind=TRAVEL 那条需求)。于是一条 requirement_id 指向 TRANSFER 需求的派车行,在第一道 requirementId 比对上必然不等,从而被 判定为"需求已过期/已变化"而拒绝——接送机的车派得出来,却一步都确认/改派不了。

修法:OrderFleetDetailContextDTO(hl-common-core)新增可空字段 transferVehicleRequirement;order-v3 一并把订单当前生效的 TRANSFER 需求带回(服务日未回填时 降级为 null,不让整个车务详情上下文报错)。fleet 侧新增私有方法 baselineRequirementFor(requirementId, context):传入的 requirementId 命中 transferVehicleRequirement 就用那条,否则原样回退 vehicleRequirement(TRAVEL)—— TRAVEL 派车行的比对结果与改动前逐字相同。

⚠️ 订正 2026-09-18 早些时候的说法(本目录 17_7443_*.md、18_7443_接送机用车需求分叉… 两份文档的「六、边界行为」都写"确认、改派、基线变更复验三处内部调用……对 TRANSFER 派车行 一律 605041"):这句话对 POST /{assignmentId}/confirm 与 POST /{assignmentId}/change 准确,但对 POST /requirements/{requirementId}/confirm 不准确——经 2026-09-18 源码复核 (AssignmentService.java:3567-3583 的 assertRequirementPreflightVersion),该端点有一道 更早触发的版本预检门禁,直接读 context.getVehicleRequirement()(恒 TRAVEL)与请求体里的 requirementId(TRANSFER)比对,必不等 ⇒ 实际可观测的报错是 605905(需求版本过期), 不是 605041;请求根本走不到后面真正会抛 605041 的 assertFinalConfirmationBaseline (:3974)。两处的根因完全相同(都是"拿 context.getVehicleRequirement() 当基线"), 本次修复对两处一并修,但前端排查历史工单日志时请按实际报错码 605905 去找,不要按 605041 去找。

不影响范围(前端最关心的一句):TRAVEL 派车行的确认/改派/复核行为逐字未变;上游 PUT /v3/admin/order/{id}/vehicle-requirement 传 kind=TRANSFER 的写口目前仍然关着 (809009,见 18_7443_接送机用车需求分叉… 一文),本次修复不改变这一点——没有那个开关, 生产环境仍然造不出真实的 TRANSFER 需求;本次修复解决的是"已经有一条 TRANSFER 需求/派车行时, 这三个端点能不能正常操作它"。

前端行动项:无。本次三个端点的请求体、响应体结构一个字段都没有变,不需要改任何代码。 如果贵方之前按 18_7443_接送机用车需求分叉… 文档的提示"前端本版暂缓接入 TRANSFER 派车行的 确认/改派"搁置了相关联调,这条限制在这三个端点自身的维度已经解除;但由于上游写口 (809009)仍未开放,测试环境目前仍无法通过正常业务流程产出可供联调的 TRANSFER 数据。

🆕 2026-09-18 当日追加两个提交,同一根因的另外两处落点(分支已 rebase 到最新 dev-v3, 现为三个提交):

① refinalizeFinalSnapshot(AssignmentService.java:13092,提交 694e405fd)——一个 内部作业端点(POST /internal/fleet/jobs/vehicle-assignment-snapshot/refinalize, FleetJobInternalController),不面向 admin 前端,供运维/作业系统补发存量订单的最终方案 快照。坏法与前三处同形:读 context.getVehicleRequirement()(恒 TRAVEL),入参 requirementId 指向 TRANSFER 需求时必判"身份错位"而拒绝。⚠️ 源码注释与提交信息把这里的 错误码写成了"605036",经核实是笔误:该方法唯一的身份不一致分支 (AssignmentService.java:13096-13098)实际 throw new BusinessException( AssignmentErrorCode.REQUIREMENT_VERSION_EXPIRED),即 605905("需求版本过期"), 605036 是另一个不相关的码(CROSS_RESIDENT_CONFIRMATION_REQUIRED,"司机与车辆不是常驻组合")。 本文档按实测的 605905 记录,不采信注释里的 605036。这一处不进本文档「二/三」的接口清单 (非 admin 端点),详见下方「六、边界行为」。

② Step2 canonical 快照(Step2CanonicalSnapshotService.java:100,提交 15c7cdd0c)——这一处前端可见,是本次追加里最重要的一处。四步向导第②步选车/选司机页 用的 POST /admin/fleet/assignments/candidates 端点(AssignmentCandidateRespVO .canonicalSnapshot 字段)此前同样恒读 context.getVehicleRequirement()(TRAVEL)。 坏法是静默的:身份比对(identityConsistent)不等时不抛任何错误码,只有一条 log.warn("Step2 快照身份不一致,拒绝建快照/更新"),方法直接 return null——调用方拿到的是 一个空字段,不是一次失败。运营端表现为"四步向导第②步网格出不来",且没有任何错误码指向 成因,排查只能靠日志。已收录为本文档新增的「三、接口详情」第 4 条,详见该节与「六、边界 行为」。


二、变更接口清单

# 接口 方法 路径 变更类型 说明
1 按需求原子确认 POST /admin/fleet/assignments/requirements/{requirementId}/confirm 行为修复(入参/出参契约不变) kind=TRANSFER 需求不再在预检门禁上必现 605905
2 车务确认执行 POST /admin/fleet/assignments/{assignmentId}/confirm 行为修复(入参/出参契约不变) kind=TRANSFER 派车行的 holding 收尾与 ASSIGNED 复核两条内部分支不再必现 605041
3 修改派单 POST /admin/fleet/assignments/{assignmentId}/change 行为修复(入参/出参契约不变) kind=TRANSFER 派车行改派不再必现 605041
4 查询派单候选资源 POST /admin/fleet/assignments/candidates 行为修复(入参/出参契约不变) kind=TRANSFER 需求的 Step2 canonical 快照(canonicalSnapshot 字段)不再静默返回 null

另有一处内部作业端点(不进本表)同根因修复:POST /internal/fleet/jobs/vehicle-assignment-snapshot/refinalize, 详见「⚠️ 关键变化」①与「六、边界行为」。


三、接口详情

1. 按当前派车方案代际原子确认全部执行段 POST /admin/fleet/assignments/requirements/{requirementId}/confirm

VO: ConfirmRequirementReqVO → ConfirmRequirementRespVO

使用场景

车务在派单弹窗对某条用车需求下当前有效的全部执行段做原子最终确认(#5827 起新派车提交即 派定,本端点服务"已派定组的复核"与"存量 holding 组的收尾确认")。本次修复前,若 requirementId 指向一条 kind=TRANSFER(接送机)需求,请求会在最早一道版本预检门禁 (assertRequirementPreflightVersion)上必然判定"需求已过期"而拒绝,即使前端刚从看板拉取的 expectedRequirementVersion/expectedRequirementSha256 完全正确也一样;修复后该门禁按 requirementId 正确择取 TRANSFER 需求做比对,version/sha256 匹配时可正常通过。

入参(本次零新增/零变化,全量 7 个字段逐一核对源码)

字段 位置 类型 必填 约束 说明
requirementId Path Long 是 - 当前用车需求 ID;本次修复后,传一条 kind=TRANSFER 需求的 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 是 - 是否向本执行段司机发送行程短信

出参(本次零新增/零变化,全量字段)

字段 类型 说明
requirementId String(雪花 ID,Long 转字符串序列化) 用车需求 ID
dispatchPlanGeneration String(雪花 ID) 已确认的最终派车方案代际
confirmed Boolean 整组是否原子确认成功
finalPlanPublished Boolean 本次是否发布了最终方案快照;false 表示确认已成功但订单车控仍为处理中
groups[] Array 各执行段确认结果
groups[].assignmentId String(雪花 ID) 代表派单 ID
groups[].assignmentGroupId String(雪花 ID) 派车组 ID
groups[].assignmentStatus String 派单状态
groups[].confirmedAt LocalDateTime 车务最终确认时间
groups[].sendItinerarySms Boolean 是否选择发送本段行程短信
groups[].itinerarySmsEventId String(雪花 ID,未发送为 null) 行程短信 Outbox 事件 ID
groups[].itinerarySmsStatus String 行程短信状态
groups[].itineraryUrl String 本段电子行程单 H5 链接;签发不可用时为 null

请求示例

POST /admin/fleet/assignments/requirements/1934567890123457001/confirm
{
  "orderId": 1934567890123456789,
  "requestId": "confirm-req-20260918-0001",
  "expectedRequirementVersion": 3,
  "expectedRequirementSha256": "a1b2c3d4e5f60718293a4b5c6d7e8f90112233445566778899aabbccddeeff",
  "expectedPlanGeneration": 5,
  "groups": [
    {"assignmentGroupId": 1934567890123457100, "sendItinerarySms": true}
  ]
}

上例 requirementId=1934567890123457001 是一条 kind=TRANSFER 需求;本次修复前该请求必返 605905,version/sha256 是否匹配无关紧要。

响应示例

{
  "code": 200,
  "message": "成功",
  "data": {
    "requirementId": "1934567890123457001",
    "dispatchPlanGeneration": "5",
    "confirmed": true,
    "finalPlanPublished": true,
    "groups": [
      {
        "assignmentId": "1934567890123457100",
        "assignmentGroupId": "1934567890123457100",
        "assignmentStatus": "assigned",
        "confirmedAt": "2026-09-18 15:00:00",
        "sendItinerarySms": true,
        "itinerarySmsEventId": "1934567890123457200",
        "itinerarySmsStatus": "PENDING",
        "itineraryUrl": "https://h5.example.com/#/itinerary/eyJvcmRlcklkIjoi..."
      }
    ]
  },
  "success": true
}

空数据 / 降级响应

N/A。本端点是需求级原子确认动作,要么全部执行段确认成功并返回完整 groups[],要么整体失败 关闭返回错误响应;不存在"部分成功"或 data 为空数组/null 的成功响应形态。

错误响应

{
  "code": 605905,
  "message": "需求版本过期",
  "data": null,
  "success": false
}

⚠️ 响应信封订正:Result 只有 code/message/data/traceId 四个字段,业务失败时 code 本身就是业务错误码,HTTP 状态码固定 200,不存在独立的 errorCode 字段; success 是 Result.isSuccess()(code==200)序列化出的真实字段。此端点未被 Controller 专门 catch,异常经 GlobalExceptionHandler.handleBusiness 兜底,data 固定为 null(不含 dailyDifferences,与下面两个端点不同)。

业务边界

  • 本次修复前,仅因 requirementId 指向 kind=TRANSFER 需求就必然命中上面这条 605905,即便 expectedRequirementVersion/expectedRequirementSha256 与前端刚拉取的完全一致;修复后只在 需求确实已发生变化(如被他人换版/改派)时才会命中
  • 若通过预检门禁后,在需求级原子确认循环内部(assertFinalConfirmationBaseline, AssignmentService.java:3974)仍检出基线不一致,同样按 605041 (FINAL_CONFIRMATION_BASELINE_MISMATCH,"订单行程或用车派单已变化,请按逐日差异处理后 重试")拒绝;但该调用点不经 Controller 的专门 catch,响应 data 固定为 null,不返回 dailyDifferences(与「2. 车务确认执行」「3. 修改派单」两个端点不同,那两处会返回结构化 差异)
  • kind=TRAVEL 需求的确认流程、错误码语义与本次改动前逐字一致

2. 车务确认执行(holding→assigned) POST /admin/fleet/assignments/{assignmentId}/confirm

VO: ConfirmReqVO → ConfirmRespVO

使用场景

两类入口共用本端点:①存量 holding 组的收尾确认;②已派定组的复核——重跑最终确认基线 + 行程 短信决策一致性校验 + 接送机门禁,通过后重发最终方案快照。本次修复覆盖这两条内部分支 (holding 收尾在 AssignmentService.java:4246/4279,ASSIGNED 复核经 revalidateAssignedAfterBaselineChange 于 :4462)对 kind=TRANSFER 派车行的基线选取—— 两处修复前都直接读 context.getVehicleRequirement()(恒 TRAVEL),派车行的 assignmentId 无论对应哪条需求,基线比对都必然落到 TRAVEL 上。

入参(本次零新增/零变化,全量 5 个字段)

字段 位置 类型 必填 约束 说明
assignmentId Path Long 是 - 派单 ID;本次修复后,若该行属于 kind=TRANSFER 需求,不再必然 605041
sendItinerarySms Body Boolean 是 - 是否在最终确认后向该车师傅发送行程短信
requestId Body String 是 ≤64 幂等请求标识
vehicleFeeTotal Body BigDecimal 否 已废弃,传值即被拒绝 历史字段,最终总车费已改为逐日车费只读合计
vehicleFeeAdjustmentReason Body String 否 ≤256,已废弃,传值即被拒绝 历史字段,逐日车费调整原因应在派车保存时提交

出参(本次零新增/零变化,全量字段,含 sideEffects/dailyDifferences 展开)

字段 类型 说明
confirmed Boolean 是否已完成最终确认
assignmentStatus String 派单状态(assigned=已派定)
stageCode String 生命周期阶段码
stageLabel String 生命周期阶段文案
currentStep Integer 当前三阶段步骤(订单详情/排车/确认执行)
assignmentGroupId String(雪花 ID) 派车组 ID(同一辆车连续每日切片共用)
vehicleFeeTotal String(BigDecimal 转字符串) 最终确认后的该车本派车段总车费
vehicleFeeSource String 最终总车费来源:AUTO、MANUAL
vehicleFeeAdjustmentReason String 手工总车费调整原因
confirmedAt LocalDateTime 车务确认派车时间
sendItinerarySms Boolean 车务是否选择向该车师傅发送行程短信
itinerarySmsEventId String(雪花 ID) 行程短信可靠事件 ID;不发送为空
itinerarySmsStatus String 确认响应中的初始短信状态(PENDING,NOT_SENT)
itineraryUrl String 电子行程单 H5 签名链接
sideEffects Object 副作用执行结果,见下方展开
sideEffects.vehicleStatusUpdated String 车辆状态更新结果(idle,busy,maint;不涉及车为 null)
sideEffects.driverStatusUpdated String 司机状态更新结果(busy,idle;不涉及司机为 null)
sideEffects.reconPrepRowsCreated Integer 对账预备单创建条数(M1 降级恒返 0)
sideEffects.reconPrepMarkedCanceled Integer 对账预备单标记取消条数(M1 降级恒返 0)
dailyDifferences Array 确认失败时的逐日基线差异;成功时为空
dailyDifferences[].serviceDate LocalDate 服务日
dailyDifferences[].differenceType String 差异类型枚举,见「六.5」
dailyDifferences[].differenceLabel String 差异中文名称
dailyDifferences[].assignmentId String(雪花 ID) 派单 ID
dailyDifferences[].assignmentSlotId String(雪花 ID) 稳定车辆槽位 ID
dailyDifferences[].passengerCount Integer 订单乘客人数(不含司机)
dailyDifferences[].message String 差异说明

请求示例

POST /admin/fleet/assignments/1934567890123457100/confirm
{
  "sendItinerarySms": true,
  "requestId": "fleet-final-confirm-20260918-0001"
}

上例 assignmentId=1934567890123457100 是一条挂在 kind=TRANSFER 需求下的派车行;本次修复前 该请求必返 605041。

响应示例

{
  "code": 200,
  "message": "成功",
  "data": {
    "confirmed": true,
    "assignmentStatus": "assigned",
    "stageCode": "assigned",
    "stageLabel": "已派车",
    "currentStep": 3,
    "assignmentGroupId": "1934567890123457100",
    "vehicleFeeTotal": "700.00",
    "vehicleFeeSource": "AUTO",
    "vehicleFeeAdjustmentReason": null,
    "confirmedAt": "2026-09-18 15:05:00",
    "sendItinerarySms": true,
    "itinerarySmsEventId": "1934567890123457200",
    "itinerarySmsStatus": "PENDING",
    "itineraryUrl": "https://h5.example.com/#/itinerary/eyJvcmRlcklkIjoi...",
    "sideEffects": {
      "vehicleStatusUpdated": "busy",
      "driverStatusUpdated": "busy",
      "reconPrepRowsCreated": 0,
      "reconPrepMarkedCanceled": 0
    },
    "dailyDifferences": null
  },
  "success": true
}

空数据 / 降级响应

N/A。本端点确认成功时恒返回完整对象(sideEffects 各字段可能为 null,但不是"空数据"形态); 失败时按下方错误响应返回,不存在中间态。

错误响应

{
  "code": 605041,
  "message": "订单行程或用车派单已变化,请按逐日差异处理后重试",
  "data": {
    "confirmed": false,
    "dailyDifferences": [
      {
        "serviceDate": null,
        "differenceType": "REQUIREMENT_VERSION_MISMATCH",
        "differenceLabel": "用车需求已更新",
        "assignmentId": "1934567890123457100",
        "assignmentSlotId": "1934567890123457099",
        "passengerCount": null,
        "message": "用车需求已更新,请刷新后按最新需求重新派车"
      }
    ]
  },
  "success": false
}

业务边界

  • 本次修复前,kind=TRANSFER 派车行走 holding 收尾确认或 ASSIGNED 复核任一分支都必现 605041,与请求体字段是否正确无关(两条分支都不接受调用方传 requirementId,只能按 assignmentId 反查行、再拿上下文里恒为 TRAVEL 的需求比对);修复后按该行自己的 requirementId 正确匹配 TRANSFER 需求
  • 若基线确实发生变化(如换版、需求被取消),仍会返回 605041 及 data.dailyDifferences,与本次 改动前逐字一致;前端展示优先用 differenceLabel/message,differenceType 仅供逻辑判断
  • kind=TRAVEL 派车行的确认流程、sideEffects/dailyDifferences 语义与本次改动前逐字一致
  • 旧 HOLD 通知结果仍在确认中的场景(605042 等)不在本次修复范围内,行为未变

3. 修改派单(按槽位和生效日) POST /admin/fleet/assignments/{assignmentId}/change

VO: ChangeAssignmentReqVO → ChangeAssignmentRespVO

使用场景

按稳定车辆槽位和生效日原子换车、换司机或同时替换;改派恒执行最终基线复核。本次修复覆盖 doChangeInLock 内部对 kind=TRANSFER 派车行的基线选取(AssignmentService.java:5848 附近, 修复前 assertFinalConfirmationBaseline 的 requirementOverride 显式传 null,回退读 context.getVehicleRequirement() 恒为 TRAVEL)。

入参(本次零新增/零变化,全量 14 个字段,含嵌套 dailyVehicleFees[])

字段 位置 类型 必填 约束 说明
assignmentId Path Long 是 - 当前车辆槽位任一派单 ID;本次修复后,若该行属于 kind=TRANSFER 需求,不再必然 605041
effectiveDate Body LocalDate 是 - 改派生效日(含当日)
serviceDates Body Array<LocalDate> 否 - 按天改派限定的服务日期集;空=生效日起连续后缀整体改派
newVehicleId Body Long 否 - 新车辆 ID;不换车可空
newDriverId Body Long 否 - 新司机 ID;不换司机可空
sendItinerarySms Body Boolean 否 不传按 false 是否向改派后的师傅发送行程短信
holdMode Body Integer 否 已废弃,服务端忽略 #5827 起一律一步派定,勿再传
messageTemplateId Body Long 否 已废弃 不再消费
customBody Body String 否 ≤4000,已废弃 不再消费
protocolPrice Body BigDecimal 否 ≥0.00,整数最多10位/小数最多2位 新协议价日单价;可空按价格日历计算
vehicleFeeTotal Body BigDecimal 否 已废弃,传值即被拒绝 历史字段
vehicleFeeAdjustmentReason Body String 否 ≤256 新派车段单日车费与价格日历不一致时的调整原因
dailyVehicleFees[] Body Array 否 - 新派车段逐日车费;未提交的收费日按价格日历取价
dailyVehicleFees[].serviceDate Body LocalDate 是(数组项内) - 车费服务日期
dailyVehicleFees[].price Body BigDecimal 是(数组项内) ≥0.00 本次派车单日车费;只覆盖本次派车,不回写价格日历
retainedVehicleFeeTotal Body BigDecimal 否 已废弃,传值即被拒绝 历史字段
retainedVehicleFeeAdjustmentReason Body String 否 ≤256,已废弃 历史字段
chargeableServiceDates Body Array<LocalDate> 否 - 生效日起收取车费的服务日期;不传沿用原值
vehicleFeeWaiverReason Body String 否 ≤256 免费服务日原因;全部免费时必填
confirmAllServiceDatesFree Body Boolean 否 - 目标日期全部免费二次确认
confirmCrossResident Body Boolean 否 - 跨常驻车显式确认
reason Body String 是 ≤256 修改原因
requestId Body String 是 ≤64 请求幂等 ID

出参(本次零新增/零变化,全量字段,含 otherVehicles[]/dailyVehicleFees[]/dailyDifferences[] 展开)

字段 类型 说明
assignmentId String(雪花 ID) 新派单锚点 ID
assignmentSlotId String(雪花 ID) 稳定车辆槽位 ID;改派前后保持不变
previousAssignmentGroupId String(雪花 ID) 被替换的旧派车组 ID
newAssignmentGroupId String(雪花 ID) 新派车组 ID
assignmentStatus String 新派车组状态
effectiveDate LocalDate 改派实际生效日(含当日)
affectedDays Integer 本次原子替换的逐日切片数量
protocolPrice String(BigDecimal 转字符串) 新协议价日单价快照(元/车天)
vehicleFeeAutoTotal String(BigDecimal) 改派后价格日历自动合计参考
vehicleFeeAutoComplete Boolean 改派后自动合计是否覆盖全部计费服务日
vehicleFeeTotal String(BigDecimal) 改派后新派车段逐日车费只读合计
vehicleFeeSource String 改派后总车费来源:AUTO、MANUAL、INCOMPLETE
vehicleFeeAdjustmentReason String 改派后单日车费调整原因
dailyVehicleFees[] Array 改派后逐日价格日历参考价和本次派车价
dailyVehicleFees[].serviceDate LocalDate 服务日期
dailyVehicleFees[].chargeable Boolean 是否收取车费
dailyVehicleFees[].calendarPrice String(BigDecimal) 车型价格日历参考价;缺价为空
dailyVehicleFees[].assignmentPrice String(BigDecimal) 本次派车单日车费;收费日必有值
dailyVehicleFees[].source String 价格来源:CALENDAR、OVERRIDE、FREE、MISSING
dailyVehicleFees[].calendarPriceMissing Boolean 价格日历是否缺价
otherVehicleCount Integer 同订单其它有效车辆槽位数
warningCode String 强提示代码;无其它车辆时为空
warningMessage String 强提示文案;无其它车辆时为空
otherVehicles[] Array 同订单其它有效车辆摘要
otherVehicles[].assignmentSlotId String(雪花 ID) 其它稳定车辆槽位 ID
otherVehicles[].vehiclePlate String 当前车牌号
otherVehicles[].driverName String 当前司机名称
otherVehicles[].startDate LocalDate 当前有效服务日起
otherVehicles[].endDate LocalDate 当前有效服务日止
sendItinerarySms Boolean 车务本次是否选择向改派后的师傅发送行程短信
itinerarySmsEventId String(雪花 ID) 行程短信可靠事件 ID;未勾选发送为空
itinerarySmsStatus String 本次改派的初始短信状态(PENDING,NOT_SENT)
dailyDifferences[] Array 改派最终派定失败时的逐日基线差异(结构同「2. 车务确认执行」的 dailyDifferences)

请求示例

POST /admin/fleet/assignments/1934567890123457100/change
{
  "effectiveDate": "2026-10-11",
  "newVehicleId": 1934567890123456701,
  "newDriverId": 1934567890123456702,
  "sendItinerarySms": false,
  "reason": "司机临时无法执行,替换车辆和司机",
  "requestId": "change-20260918-0001"
}

上例 assignmentId=1934567890123457100 是一条挂在 kind=TRANSFER 需求下的派车行;本次修复前 该请求必返 605041。

响应示例

{
  "code": 200,
  "message": "成功",
  "data": {
    "assignmentId": "1934567890123457300",
    "assignmentSlotId": "1934567890123457099",
    "previousAssignmentGroupId": "1934567890123457100",
    "newAssignmentGroupId": "1934567890123457300",
    "assignmentStatus": "assigned",
    "effectiveDate": "2026-10-11",
    "affectedDays": 1,
    "protocolPrice": "1688.00",
    "vehicleFeeAutoTotal": "1688.00",
    "vehicleFeeAutoComplete": true,
    "vehicleFeeTotal": "1688.00",
    "vehicleFeeSource": "AUTO",
    "vehicleFeeAdjustmentReason": null,
    "dailyVehicleFees": [
      {
        "serviceDate": "2026-10-11",
        "chargeable": true,
        "calendarPrice": "1688.00",
        "assignmentPrice": "1688.00",
        "source": "CALENDAR",
        "calendarPriceMissing": false
      }
    ],
    "otherVehicleCount": 0,
    "warningCode": null,
    "warningMessage": null,
    "otherVehicles": [],
    "sendItinerarySms": false,
    "itinerarySmsEventId": null,
    "itinerarySmsStatus": "NOT_SENT",
    "dailyDifferences": null
  },
  "success": true
}

空数据 / 降级响应

N/A。本端点改派成功时恒返回完整对象(otherVehicles 无其它车辆时为空数组而非缺失字段); 失败时按下方错误响应返回。

错误响应

{
  "code": 605041,
  "message": "订单行程或用车派单已变化,请按逐日差异处理后重试",
  "data": {
    "dailyDifferences": [
      {
        "serviceDate": null,
        "differenceType": "REQUIREMENT_VERSION_MISMATCH",
        "differenceLabel": "用车需求已更新",
        "assignmentId": "1934567890123457100",
        "assignmentSlotId": "1934567890123457099",
        "passengerCount": null,
        "message": "用车需求已更新,请刷新后按最新需求重新派车"
      }
    ]
  },
  "success": false
}

业务边界

  • 本次修复前,kind=TRANSFER 派车行改派必现 605041,与请求体字段是否正确无关(change 端点不接受调用方传 requirementId,只能按 assignmentId 反查所属行,再拿上下文里恒为 TRAVEL 的需求比对);修复后按该行自己的 requirementId 正确匹配 TRANSFER 需求
  • 若基线确实发生变化,仍会返回 605041 及 data.dailyDifferences,与本次改动前逐字一致
  • 同订单存在其它车辆槽位时仍返回 warningCode=ORDER_HAS_OTHER_VEHICLES 及摘要,前端仍须强提示 (行为未变)
  • kind=TRAVEL 派车行的改派流程、错误码语义与本次改动前逐字一致

4. 查询派单候选资源 POST /admin/fleet/assignments/candidates

VO: AssignmentCandidateReqVO → AssignmentCandidateRespVO

⚠️ 本节范围声明:本端点是派单弹窗车辆/司机候选查询的既有端点,入参 24 个字段、出参 涵盖车辆候选、司机候选、常驻关系、动态筛选项等一整套与本次修复无关的结构。本次修复只影响 出参里的 canonicalSnapshot 一个字段(及其内部 resolveCanonicalSnapshot 的基线选取逻辑), 下方入参/出参表只列与本次修复直接相关的字段,其余约 20 个入参字段与 vehicles[]/ drivers[]/fleetTeamFacets[]/vehicleTypeFacets[]/selectedDriverResidentVehicle 等 出参结构本次零改动,未逐一列出。

使用场景

四步向导第②步(选车/选司机)加载页面时调用。本次修复前,若 requirementId 指向一条 kind=TRANSFER(接送机)需求,响应里 vehicles[]/drivers[] 等候选数据正常返回,但 canonicalSnapshot 字段恒为 null——Step2CanonicalSnapshotService.resolveCanonicalSnapshot 内部的 identityConsistent 身份比对拿 context.getVehicleRequirement()(恒 TRAVEL)与请求的 requirementId(TRANSFER)比对,必不等,不抛错误码,只打一条 log.warn 后直接返回 null。 运营端表现为"网格出不来",没有任何错误提示能指向成因。修复后 Step2CanonicalSnapshotService 与 AssignmentService 共用新抽出的 BaselineRequirementSelector.select(requirementId, context) 按请求自己的 requirementId 正确择取 TRANSFER 需求。

可验证的效果句(工单 #7443 AC-24 给定判据):对一条 kind=TRANSFER 的活跃需求调 resolveCanonicalSnapshot(该 requirementId),返回非 null,且 editableServiceDates 恰为该需求的两条航班日、cells 覆盖这两日、其中已派车那一日的 cell used = "USED" 且 assignmentId 指向该派车行。

入参(本节只列与本次修复相关的字段;其余约 20 个字段本次零改动,未列出)

字段 位置 类型 必填 约束 说明
orderId Body Long 改派排除自身时必填 - 当前订单 ID
requirementId Body Long 改派排除自身时必填 - 当前用车需求 ID;本次修复后,传一条 kind=TRANSFER 需求的 ID 时 canonicalSnapshot 不再必然为 null

出参(本节只列 canonicalSnapshot 极其内部结构;vehicles[]/drivers[]/其余顶层字段本次零改动,未列出)

字段 类型 说明
canonicalSnapshot Object Step2 canonical full snapshot;无 requirementId 或需求上下文不可用时为 null(本次修复前,kind=TRANSFER 需求也会落入这个 null,是本次修复的对象)
canonicalSnapshot.planGeneration String(雪花 ID,required=true) 当前计划代际(不透明令牌)
canonicalSnapshot.snapshotVersion String(雪花 ID,required=true) 当前快照版本;同代内容修订递增
canonicalSnapshot.retainedGroupIds Array<String>(required=true) 有序 RetainedGroupSet:稳定派车组 ID
canonicalSnapshot.editableServiceDates Array<LocalDate>(required=true) 完整有序可编辑服务日集合
canonicalSnapshot.cells Array(required=true) 每个派车组×服务日笛卡尔积位置唯一 cell
canonicalSnapshot.cells[].groupId String(雪花 ID) 稳定派车组 ID
canonicalSnapshot.cells[].serviceDate LocalDate 服务日期
canonicalSnapshot.cells[].assignmentId String(雪花 ID) 当天逐日派车行 ID;无行为 null
canonicalSnapshot.cells[].used String USED=实际用车/UNUSED=显式不用车/null=无切片行
canonicalSnapshot.cells[].readOnly Boolean 该 cell 是否只读
canonicalSnapshot.cells[].vehicleId String(雪花 ID) 当天车辆 ID
canonicalSnapshot.cells[].driverId String(雪花 ID) 当天司机 ID
canonicalSnapshot.selectedVehicle Object 已选车辆最小脱敏展示快照;未选为 null(结构未动)
canonicalSnapshot.selectedDriver Object 已选司机最小脱敏展示快照;未选为 null(结构未动)

CanonicalSnapshotVO 五个 required=true 字段(planGeneration/snapshotVersion/ retainedGroupIds/editableServiceDates/cells)是 Swagger 注解表达的契约意图,不是 运行时观测——它们只在 canonicalSnapshot 本身非 null 时必然存在;canonicalSnapshot 整体为 null 时这五个字段自然也拿不到。Swagger required 描述的是"存在时必填",不是 "恒存在"。

请求示例

POST /admin/fleet/assignments/candidates
{
  "orderId": 1934567890123456789,
  "requirementId": 1934567890123457001,
  "startDate": "2026-10-11",
  "endDate": "2026-10-17",
  "vehiclePage": 1,
  "vehiclePageSize": 20,
  "driverPage": 1,
  "driverPageSize": 20
}

上例 requirementId=1934567890123457001 是一条 kind=TRANSFER 需求;本次修复前 data.canonicalSnapshot 必为 null(vehicles/drivers 等其余字段不受影响,正常返回)。

响应示例

{
  "code": 200,
  "message": "成功",
  "data": {
    "canonicalSnapshot": {
      "planGeneration": "1934567890123457500",
      "snapshotVersion": "1",
      "retainedGroupIds": ["1934567890123457100"],
      "editableServiceDates": ["2026-10-11", "2026-10-17"],
      "cells": [
        {
          "groupId": "1934567890123457100",
          "serviceDate": "2026-10-11",
          "assignmentId": "1934567890123457100",
          "used": "USED",
          "readOnly": false,
          "vehicleId": "2064998142394183681",
          "driverId": "2065272150960357378"
        },
        {
          "groupId": "1934567890123457100",
          "serviceDate": "2026-10-17",
          "assignmentId": null,
          "used": null,
          "readOnly": false,
          "vehicleId": null,
          "driverId": null
        }
      ],
      "selectedVehicle": null,
      "selectedDriver": null
    }
  },
  "success": true
}

(省略号:本响应实际还含 vehicles/drivers/fleetTeamFacets 等与本次修复无关的字段, 上例只展示 canonicalSnapshot。)

空数据 / 降级响应

canonicalSnapshot 为 null 是本端点合法的既有降级形态,不是本次修复要消灭的东西—— 未传 requirementId、或需求上下文确实不可达(如 809007 服务日未回填导致 transferVehicleRequirement 也取不到)时,canonicalSnapshot 仍然合法为 null。本次修复只 消灭了"requirementId 合法指向一条存在的 TRANSFER 需求,却因基线选错而被误判身份不一致" 这一种 null 成因;其余合法 null 成因不受影响。

{
  "code": 200,
  "message": "成功",
  "data": {
    "canonicalSnapshot": null
  },
  "success": true
}

错误响应

本端点的 canonicalSnapshot 解析路径不抛错误码(这正是「使用场景」里描述的"坏法是静默的"); resolveCanonicalSnapshot 内部锁获取失败时会抛 CommonErrorCode.RESOURCE_LOCKED (hl-common-core/.../CommonErrorCode.java:67-68),但这是既有的锁竞争兜底,与本次修复的身份 比对分支无关,本次未改动:

{
  "code": 100503,
  "message": "资源被占用,请稍后重试",
  "data": null,
  "success": false
}

业务边界

  • 🔴 本条【假设】未验证,请 mmg 自验:canonicalSnapshot 为 null 时四步向导第②步前端 会表现出什么效果(空白网格/报错提示/直接不可操作),本单没有测试前端行为,不替前端下 结论。上文"运营看到网格出不来"是问题背景陈述,不是对当前前端代码行为的实测断言
  • canonicalSnapshot 为 null 与"请求本身失败"是两回事:响应 code 仍是 200、success 仍是 true,vehicles[]/drivers[] 等其余候选数据不受影响,只有这一个字段拿不到
  • kind=TRAVEL 需求的 canonicalSnapshot 解析行为逐字不变
  • 本条修复与本文档前三条同根因、同修法(BaselineRequirementSelector),但触发路径不同 (前三条走 HTTP 错误响应,这一条走静默降级字段),前端排查时不能用同一套"看错误码"的思路

四、契约约束与正确调用方式

本节只写后端接受/拒绝 payload 的规则,不写 UI 渲染建议。

场景 做法
requirementId/assignmentId 指向一条 kind=TRANSFER 需求/派车行 三个端点的请求体格式不需要任何调整——与 kind=TRAVEL 完全一样传参即可,fleet 内部会自动按行/需求自己的 ID 择基线
POST /requirements/{id}/confirm 拿到 605905 与本次修复前不同:现在只代表需求版本确实过期(被他人换版/改派),需要重新拉取最新的 expectedRequirementVersion/expectedRequirementSha256 后重试,不再是"TRANSFER 需求天生用不了这个端点"的信号
POST /{assignmentId}/confirm、POST /{assignmentId}/change 拿到 605041 同上,现在只代表基线确实变化,按 data.dailyDifferences[].differenceLabel/message 提示用户刷新重试
需要创建/查询一条真实的 kind=TRANSFER 需求用于联调 目前仍需 SQL 直接构造(上游写口 809009 未开放),不在本次修复范围内

五、数据库行为

操作 影响
kind=TRANSFER 派车行调用上述三接口,基线校验通过 与 kind=TRAVEL 完全相同的既有落库逻辑(fleet_assignment 状态流转、confirmed_at/assignment_status 等列更新),不因 kind 不同而有任何额外列或额外写入
kind=TRANSFER 派车行调用上述三接口,基线校验未通过(需求确实已变化) 与 kind=TRAVEL 相同:整体拒绝,不落库,不改变已有行状态
kind=TRAVEL 派车行调用上述三接口 落库行为与本次改动前逐字一致
OrderFleetDetailContextDTO 新增字段 transferVehicleRequirement 纯内存态 Feign 响应字段,不对应任何新增数据库列

六、边界行为

  • transferVehicleRequirement 为 null 的三种合法情形(源码 javadoc 逐字, OrderFleetProviderService.java:336-354):①该订单没有接送机需求(绝大多数订单); ②接送机需求存在但服务日尚未回填(809007 降级为 null,不让整个订单车务详情报错); ③订单已终态。三种情形下 baselineRequirementFor 都会回退到 vehicleRequirement(TRAVEL), 行为与改动前一致
  • 服务日尚未回填的 kind=TRANSFER 需求:本次修复不改变这类需求仍无法被正常操作的事实 (transferVehicleRequirement 取不到即为 null,三端点仍按原口径拒绝);服务日回填属 #7443 AC-6~9 范畴,已于今天早些时候的 PR #7911/#7912/#7913 交付(见 18_7443_接送机用车需求分叉… 文档)
  • POST /requirements/{requirementId}/confirm 对 kind=TRANSFER 的实际可观测报错是 605905(assertRequirementPreflightVersion 更早触发的预检门禁),不是 605041——本条 订正了 2026-09-18 早些时候 17_7443_*.md/18_7443_接送机用车需求分叉… 两份文档"三处均 605041"的表述,理由与出处见上方「⚠️ 关键变化」
  • POST /{assignmentId}/confirm、POST /{assignmentId}/change 对 kind=TRANSFER 的实际可 观测报错才是 605041
  • 本次修复还覆盖了一处非 HTTP 端点的内部判据:isRequirementEligibleForReopen (AssignmentService.java:9135,commit 内注释标注"第 4 个落点")。它被两条异步补偿链路调用 (AssignmentLifecycleOutboxEffectService.java:136 撤销"整段不用车"声明后的需求重开、:264 取消派车后的补偿重开),修复前对 kind=TRANSFER 需求恒返回 false,两条补偿链路对接送机 需求整条失效(需求会卡在 DONE/PROCESSING 不自愈,且不报任何错误)。前端无法直接感知 这一点——它不产生任何同步 HTTP 响应,只在"撤销不用车声明"或"取消已派车后需求应否被重开"的 排查场景可能相关,记录于此供后续问题定位参考
  • refinalizeFinalSnapshot(内部作业端点,提交 694e405fd,AssignmentService.java:13092): 与 isRequirementEligibleForReopen 同形,入参 requirementId 由 FleetJobInternalController(POST /internal/fleet/jobs/vehicle-assignment-snapshot/refinalize) 原样传入、不带 kind,修复前恒读 context.getVehicleRequirement()(TRAVEL),TRANSFER 需求 进来在 :13096-13098 的身份比对上必然不等,整条重发最终方案快照的补偿链路对 TRANSFER 不可用。抛出的错误码是 605905(REQUIREMENT_VERSION_EXPIRED,"需求版本过期")—— ⚠️ 该方法的源码注释与提交信息把这里写成"报 605036",经对照 :13097-13098 的实际 throw 语句核实为笔误(605036 是 CROSS_RESIDENT_CONFIRMATION_REQUIRED,"司机与车辆 不是常驻组合",与本方法无关),本文档按源码实测的 605905 记录。本端点不面向 admin 前端, 不进「二、变更接口清单」
  • Step2 canonical 快照(提交 15c7cdd0c,Step2CanonicalSnapshotService.java:100): 详见「三、接口详情」第 4 条。补充一点边界行为——identityConsistent 的身份比对除 requirementId 外还比对 orderId(Step2CanonicalSnapshotService.java:136-154),本次 修复只解决了 requirementId 因恒读 TRAVEL 而误判的那部分;若 orderId 本身传错, 修复前后同样会被判定"身份不一致"并静默返回 null,这不是本次修复要处理的场景
  • BaselineRequirementSelector(提交 15c7cdd0c,assignment/support/ 新增类):原来 fleet 内部只有 AssignmentService 私有方法 baselineRequirementFor 一份择基线逻辑,本次 抽成独立静态工具类 BaselineRequirementSelector.select(requirementId, context), AssignmentService(确认/改派/复验/重开/重发快照五处)与 Step2CanonicalSnapshotService (候选查询一处)共用同一份实现(CODE_RULES §15.7 对称子域禁镜像重复)。这是内部重构,不 改变任何对外行为或字段,此处只作记录,不影响上面任何一条边界行为
  • kind=TRAVEL 派车行三个端点、isRequirementEligibleForReopen、refinalizeFinalSnapshot、 Step2 canonical 快照的全部行为逐字不变(baselineRequirementFor/BaselineRequirementSelector 对非 TRANSFER 命中的 requirementId 一律回退到原 context.getVehicleRequirement())
  • 本次修复零 DB 迁移,不改变 fleet_assignment/vehicle_requirement 任一张表结构
  • 上游 PUT /v3/admin/order/{id}/vehicle-requirement 传 kind=TRANSFER 的写口仍然关着 (809009,transfer-kind-submit-enabled 默认 false),本次修复不改变这一点——没有这个 开关,生产环境依然造不出真实的 TRANSFER 需求;本次修复只在"已经存在一条 TRANSFER 需求/ 派车行(如通过 SQL 构造,或未来开关打开后)"的前提下起作用

六.5 枚举(本次不新增错误码,以下为本次行为直接相关的既有错误码)

错误码 常量名 文案 触发位置
605905 REQUIREMENT_VERSION_EXPIRED 需求版本过期 assertRequirementPreflightVersion(AssignmentService.java:3567-3583),POST /requirements/{id}/confirm 专属预检门禁;同码也是内部作业端点 refinalizeFinalSnapshot(:13096-13098)的身份不一致分支,见「六、边界行为」
605041 FINAL_CONFIRMATION_BASELINE_MISMATCH 订单行程或用车派单已变化,请按逐日差异处理后重试 assertFinalConfirmationBaseline(AssignmentService.java:4644 起),POST /{assignmentId}/confirm、POST /{assignmentId}/change 及 POST /requirements/{id}/confirm 内部循环共用

⚠️ Step2 canonical 快照(POST /admin/fleet/assignments/candidates 的 canonicalSnapshot 字段)没有对应错误码——它的身份不一致分支(identityConsistent 判 false)走的是 return null + log.warn,不抛任何 IErrorCode。上表只覆盖会抛错误码的场景,canonicalSnapshot 为 null 的排查请走「三、接口详情」第 4 条,不要在这张表里找它的错误码——它没有。

dailyDifferences[].differenceType 允许值(既有枚举,本次未新增,仅在 605041 场景可能 出现):REQUIREMENT_VERSION_MISMATCH / ORDER_DATE_MISMATCH / ITINERARY_DATE_MISMATCH / ASSIGNMENT_DATE_MISSING / ASSIGNMENT_DATE_EXTRA / HEADCOUNT_BASELINE_MISMATCH。


六.6、修改前后对比

字段级对比

字段 改前 改后
OrderFleetDetailContextDTO.transferVehicleRequirement(hl-common-core,fleet↔order-v3 内部 Feign 契约,前端不可见) 不存在 新增可空字段,携带订单当前生效的 kind=TRANSFER 需求
四个端点(confirmRequirement/confirm/change/candidates)的请求体、响应体字段 — 零变化,本次不涉及任何 ReqVO/RespVO 字段增删(canonicalSnapshot 及其内部字段结构不变,只是不再必然为 null)
AssignmentService.baselineRequirementFor 的实现 内联逻辑 委托给新抽出的 BaselineRequirementSelector.select(内部重构,行为等价)

行为级对比

行为 改前 改后
POST /requirements/{id}/confirm 对 kind=TRANSFER 需求 必现 605905(assertRequirementPreflightVersion 恒判需求已过期) version/sha256 匹配时正常通过
POST /{assignmentId}/confirm 对 kind=TRANSFER 派车行(holding 收尾 + ASSIGNED 复核两条分支) 必现 605041 基线一致时正常确认为 assigned
POST /{assignmentId}/change 对 kind=TRANSFER 派车行 必现 605041 基线一致时正常改派
isRequirementEligibleForReopen(内部,两条异步补偿链路) 对 kind=TRANSFER 需求恒返 false,重开补偿失效且无报错 按该需求自己的 requirementId 正确判定
refinalizeFinalSnapshot(内部作业端点) 对 kind=TRANSFER 需求必现 605905,重发快照补偿失效 按入参 requirementId 正确判定
POST /admin/fleet/assignments/candidates 的 canonicalSnapshot 字段(对 kind=TRANSFER 需求) 静默恒为 null,不抛任何错误码 requirementId/orderId 身份一致时返回正常快照
kind=TRAVEL 上述全部行为 — 逐字不变

六.7、影响评估

  • 是否破坏向后兼容: 否。三个端点入参/出参零变化,OrderFleetDetailContextDTO 只新增 可空字段(纯增量),TRAVEL 行为逐字不变
  • 前端是否必须同步上线: 否。本次不涉及任何前端可见的请求/响应契约变更,不需要改代码。 仅供知悉:18_7443_接送机用车需求分叉… 文档里"前端本版暂缓接入 TRANSFER 派车行确认/改派" 的限制,在这三个端点自身维度已解除;但上游 809009 写口未开放,测试环境目前仍无法通过正常 业务流程产出可联调的 TRANSFER 数据(见「七、不影响范围」)
  • 前端 workaround 清理点: 无(此前未要求前端做任何 workaround,只是要求暂缓接入)
  • 数据: 无需迁移,本次零 DB 迁移

七、不影响范围

  • 仅影响: 管理后台车务派单弹窗——POST /requirements/{id}/confirm、 POST /{assignmentId}/confirm、POST /{assignmentId}/change、 POST /admin/fleet/assignments/candidates 四个既有端点对 kind=TRANSFER 对象(派车行/需求/候选查询)的内部基线选取逻辑;以及一处不面向 admin 前端的内部作业端点 POST /internal/fleet/jobs/vehicle-assignment-snapshot/refinalize
  • 零影响:
    • 四个端点自身的请求体、响应体字段结构(零新增/零删除/零改名)
    • kind=TRAVEL 派车行/需求在这四个端点的全部行为
    • POST /admin/fleet/assignments/batch(批量创建)、PUT /pickup-dropoff-config (接送机配置)——两者的 kind=TRANSFER 基线复核缺口已由今天早些时候的 PR #7913 单独修复(见 18_7443_接送机用车需求分叉… 文档),与本次修复是同一根因的两个不同修复点
    • 单笔创建派单 POST /admin/fleet/assignments(该端点无本次改动涉及的基线复核缺口)
    • 取消派单 DELETE /{assignmentId}、司机拒接 POST /{assignmentId}/driver-reject、软清 POST /{assignmentId}/soft-clear-assignment、撤销取消 POST /{assignmentId}/restore-cancel、 提前完结 POST /{assignmentId}/early-complete、行程短信查询/重发、一键重派推荐等其余端点
    • fleet_assignment/vehicle_requirement 数据库表结构(零迁移)
    • 上游 PUT /v3/admin/order/{id}/vehicle-requirement 传 kind=TRANSFER 的写口现状 (809009 仍未开放,不受本次修复影响)
    • 看板列表、矩阵日订单等只读查询接口

部署清单(本单改了 hl-common-core,CODE_RULES §16.6)

本单在 hl-common-core 新增了字段(OrderFleetDetailContextDTO.transferVehicleRequirement)。 按 CODE_RULES §16.6,共享 jar 变了行为就变了,部署时全部消费方必须一起滚,不能只滚 hl-fleet-service/hl-order-service-v3 这两个直接改代码的服务。

⚠️ 消费方清单不能按 pom.xml 直接依赖关系查(2026-09-18 实测教训): grep -rl 'hl-common-core' */pom.xml 的结果里 order-v3、fleet、user、resource、 product-v2、mp 一个都没有——它们全部经 hl-common-web(本身声明 hl-common-core 依赖) 传递引入,直接查 pom.xml 会把本单真正改动、也真正需要重启的这两个服务漏掉。

部署单位 与 hl-common-core 的依赖关系 是否需要本次一起滚
hl-gateway 直接依赖(pom.xml 实测命中) 是(§16.6 全消费方一起滚)
hl-user-service 经 hl-common-web 传递依赖 是
hl-resource-service 经 hl-common-web 传递依赖 是
hl-product-service-v2 经 hl-common-web 传递依赖 是
hl-order-service-v3 经 hl-common-web 传递依赖 是(本单直接改了这个服务的生产代码)
hl-mp-service 经 hl-common-web 传递依赖 是
hl-fleet-service 经 hl-common-web 传递依赖 是(本单直接改了这个服务的生产代码)
hl-finance 直接依赖(pom.xml 实测命中) 不独立部署——packaging=jar,无 spring-boot-maven-plugin,是 order-v3 的库依赖,随 order-v3 一起滚即可,不是第 8 个部署单位

⇒ 实际部署单位共 7 个:gateway / user / resource / product-v2 / order-v3 / mp / fleet。 部署顺序、回滚预案等具体操作步骤本文档不展开(属发布流程,不是本 changelog 的职责范围); 这里只交付"漏了会出什么问题"的判据——hl-common-core 里的类是所有 7 个服务共享的同一份 class 定义,只滚一部分服务会导致该部分服务与其余服务对 OrderFleetDetailContextDTO 的 序列化/反序列化预期不一致(新滚的服务发出/接收带 transferVehicleRequirement 字段的 JSON,未滚的服务用旧版本类反序列化——Jackson 对未知字段默认静默丢弃,不会报错,但会让 这次修复在未滚的服务视角里"看起来没生效")。


八、测试环境已验证

⚠️ 本节如实说明:本次修复尚未通过网关/端到端验证。

  • 三个提交 abd435c15/694e405fd/15c7cdd0c 位于本地分支 feature/7443-rest-ac(已 rebase 到最新 dev-v3),尚未合入 origin/dev-v3(2026-09-18 复核:分支落在 origin/dev-v3@701898e2f 之上 3 个提交,git log origin/dev-v3..HEAD --oneline 精确列出 这三个;origin/dev-v3:hl-common/hl-common-core/.../OrderFleetDetailContextDTO.java 实测 确认无 transferVehicleRequirement 字段)
  • 2026-09-18 现场 deploy-status.sh 复测(合入前最后一次核对):测试环境 hl-fleet-service 跑在 dev-v3 分支的 cdf763de6、hl-order-service-v3 跑在 dev-v3 分支的 701898e2f(0 behind,即当前 origin/dev-v3 的真实 HEAD)——均不含本次三个提交
  • 因此本节没有网关请求/响应实测数据;上方「三、接口详情」的请求/响应示例是依据源码 VO 字段结构构造的示意,不是网关捕获的原始报文,取值仅供理解结构,不代表真实业务数据
  • 代码层佐证(随三个提交一并提交,本次撰写过程中未在本会话重新执行,通过与否以合并前的 CI/单测结果为准,行数据/测试数字均取自各提交的 git show --stat 与提交信息实测):
    • abd435c15(第一批,前三个端点):AssignmentServiceTest.java +471 行、 AssignmentServiceNoVehicleDeclarationTest.java +25 行、 OrderFleetProviderServiceTest.java +81 行、RequirementServiceTest.java +73 行
    • 694e405fd(refinalizeFinalSnapshot):AssignmentServiceTest.java +119 行;提交信息称 fleet 定向 626/0/0/0
    • 15c7cdd0c(Step2 canonical 快照 + BaselineRequirementSelector): Step2CanonicalSnapshotServiceTest.java +59 行(新增 BaselineRequirementSelector.java +62 行为非测试代码);提交信息称 Step2CanonicalSnapshotServiceTest 23/0/0/0(+1), fleet 定向合计 714/0/0/0
  • 发布前置条件:需先将 feature/7443-rest-ac 合入 dev-v3 并部署到测试环境(含 hl-common-core 触及的全部 7 个部署单位,见上方「部署清单」),完成四个端点对 kind=TRANSFER 的网关实测后,方可将本文档 backend_status 改为 deployed、 gateway_status 改为 verified 并推送

十、相关文档

  • Issue: #7443 AC-24
  • PR: 尚未创建(代码见本地分支 feature/7443-rest-ac,三个提交 abd435c15(前三个端点,rebase 后哈希; 原 09e9d7e50 内容相同,rebase 到 dev-v3@701898e2f 后哈希改变)、 694e405fd(refinalizeFinalSnapshot)、 15c7cdd0c(Step2 canonical 快照 + BaselineRequirementSelector)——这些链接在 PR 合入前可能 404,仅作提交号留档)
  • 前序文档(本文订正其中一处表述,详见「⚠️ 关键变化」): changelogs-v2/2026-09/17_7443_团期身份失败关闭与看板矩阵按团筛选-修改接口-管理后台.md、 changelogs-v2/2026-09/18_7443_接送机用车需求分叉PR1派车入参新增需求类别kind-修改接口-管理后台.md

关联 / 联系人

链接

  • Issue: #7443 AC-24
  • PR: 尚未创建;三个提交 abd435c15/ 694e405fd/ 15c7cdd0c(本地分支 feature/7443-rest-ac,已 rebase 到 dev-v3@701898e2f,尚未合入)

联系人