文件
hl-api-changelog/changelogs-v2/2026-09/22_8056_TRANSFER-only订单用车数据静默丢失修复-修复-管理后台.md
API Changelog Bot和Claude Opus 5 57a9e068dd
changelog-filename-gate / validate (push) Failing after 2s
docs(changelog): #8056 TRANSFER-only 订单用车数据静默丢失修复交接件
行程详情 / 确认前置 Checklist 两个只读端点的字段现在反映真实数据:
修复前 TRANSFER-only 订单在这两处返回 HTTP 200、无异常、无错误码、
字段静默为空/false,与「这个订单本来就没安排车」在返回结构上完全无法区分。

backend_status=deployed(hl-order-service-v3@d30cd9561,测试环境)、
gateway_status=verified(两个只读端点已网关实测)、
frontend_status=not_required(前端侧为纯透传渲染,无需改代码)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 02:25:08 +08:00

39 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 8056 TRANSFER-only 订单用车数据静默丢失修复(行程详情 / 确认前置 Checklist) admin wx(GIT) 修复 deployed verified not_required #8056 记录的用车数据丢失共 10 个落点,均已在同一提交(commit 62c28d502,PR #8118)中一并修复并合入 dev-v3,测试环境已部署(hl-order-service-v3@d30cd9561,2026-09-21 21:55:58)。本文逐字段交付其中 3 处:已通过网关实测的两个只读端点(行程详情、确认前置 Checklist),以及经源码核查、本次未做独立网关实测的派单看板列表端点(三、接口详情第 3 节,行为完全由已验证部署的 hl-order-service-v3 驱动,hl-fleet-service 侧代码未改动);其余落点未在本文展开。前端侧已核实 mmg/hl-ui 对本文相关字段为纯透传渲染,无需改动代码,见六.7。POST /v3/admin/order/{id}/settlement/finalize 的车务车费一致性校验不在本次交接范围内,见七、不影响范围。 2026-09-22 dev-v3

订单核心服务: TRANSFER-only 订单用车数据静默丢失修复(行程详情 / 确认前置 Checklist)

存放目录: 二期(v3) → changelogs-v2/2026-09/

服务: hl-order-service-v3(主体,第 1/2 节)+ hl-fleet-service(第 3 节派单看板列表;字段结构未变、代码未改动,行为完全由 hl-order-service-v3 侧修复驱动) PR: #8118 Issue: #8056 日期: 2026-09-22 影响范围: TRANSFER-only 订单(只提交了接送机用车需求、没有提交行程用车需求)在「订单详情-行程安排 Tab」与「确认订单前置 Checklist」两个只读端点上的用车相关字段;另涉及混合订单(同一订单同时有有效 TRAVEL 与 TRANSFER 需求)在「派单看板列表」端点上的兜底展示行为(见三、接口详情第 3 节)


⚠️ 关键变化

  • 本次变了什么:GET /v3/admin/order/{id}/itinerary 与 GET /v3/admin/order/{id}/confirm-checklist 这两个只读端点在 TRANSFER-only 订单(只有接送机用车需求、没有行程用车需求)上的用车数据读取逻辑被修复。此前这两个端点把「该读哪一类用车需求」硬编码成了 TRAVEL,TRANSFER-only 订单在这两个口子上查到的用车相关字段恒为空。
  • 前端/调用方以前以为的是什么:vehicleGroup: null + canContactFleet: false + contactFleetDisabledReason: "请先提交有效用车需求后再联系车务",或 confirm-checklist 里 VEHICLE_DONE 项恒 false、failReason 固定为「未提交用车需求」/「用车需求未完成」——这组返回值此前是唯一信号,而且和「这个订单本来就没安排车」在返回结构上完全无法区分:HTTP 200,无异常,无错误码,字段静默为空/false/固定文案。
  • 实际现在是什么:对已提交有效接送机用车需求的 TRANSFER-only 订单,这两个端点现在会返回真实数据(车辆/司机/座位数等),canContactFleet 会按实际情况变为 true,confirm-checklist 的 allPassed 在其余 4 项通过时也能正确变为 true。过去在这两个端点上读到的「空」,不代表订单真的没有安排车辆,需要按本次修复后的语义重新核对,不要沿用旧的「空即无车」假设。
  • 另需知悉(看板新增展示,非新引入缺陷):派单看板列表 GET /admin/fleet/board/orders(hl-fleet-service,见「三、接口详情」第 3 节)对混合订单(同一订单同时存在有效 TRAVEL 与 TRANSFER 需求)新增了一种兜底展示:此前若 TRAVEL 需求处于不可联络状态,整单会从看板消失;本次修复后由 TRANSFER 需求兜底展示一行,订单不再整单消失。「整单消失」本身就是 #8056 静默丢数问题的又一种表现,此项是同一次修复顺带解决的,不是新引入的行为回归。
  • #8056 记录的用车数据丢失共 10 个落点,均已在同一提交(62c28d502)中一并修复并合入 dev-v3;本文逐字段交付其中 3 处——已在测试环境网关实测的两个只读端点(行程详情、确认前置 Checklist),以及经源码核查、本次未做独立网关实测的派单看板列表端点(见「三、接口详情」第 3 节);其余落点未在本文展开。

一、背景

VehicleRequirement 按 kind 分为 TRAVEL(行程用车)与 TRANSFER(接送机用车)两类(#7439 引入)。#8056 修复前,行程详情/确认预览等单值消费点各自把 kind 参数硬编码为 TRAVEL,本次收口到 RequirementService#resolveSingleValueVehicleKind(TRAVEL 优先,没有 TRAVEL 时回落 TRANSFER)统一解析。

维度 改前 改后
用车需求类别解析方式 调用点各自硬编码 VehicleRequirementKind.TRAVEL 收口到 resolveSingleValueVehicleKind:TRAVEL 优先,无 TRAVEL 时回落 TRANSFER
TRANSFER-only 订单命中查询的结果 恒为空(按 TRAVEL 类别查,0 条命中) 命中回落解析出的 TRANSFER 记录

二、变更接口清单

# 接口 方法 路径 变更类型 说明
1 订单详情行程安排 Tab GET /v3/admin/order/{id}/itinerary 数据修复 TRANSFER-only 订单的 vehicleGroup/vehicleHistory/canContactFleet/contactFleetDisabledReason 不再恒为空/false/固定文案
2 确认订单前置 Checklist GET /v3/admin/order/{id}/confirm-checklist 数据修复 TRANSFER-only 订单的 VEHICLE_DONE 判定与 preview 司机栏不再恒判未完成/留空
3 派单看板列表 GET /admin/fleet/board/orders 行为增强(结构未变) 混合订单(同时有效 TRAVEL 与 TRANSFER 需求)中,若 TRAVEL 需求不可联络,改前整单从看板消失,改后由 TRANSFER 需求兜底展示一行(服务:hl-fleet-service)

三、接口详情

1. 订单详情行程安排 Tab GET /v3/admin/order/{id}/itinerary

VO: 无 ReqVO(路径参数 id)→ ItineraryVO

使用场景

管理后台订单详情页「行程安排」Tab 加载时调用,展示配房组/配车组的需求摘要+实配记录、已失活的配车需求历史,以及「联系房务/车务/团期管理员」按钮的未读消息角标与可用状态。

入参

字段 位置 类型 必填 约束 说明
id Path Long ✅ 订单雪花 ID 订单 ID

出参 Result<ItineraryVO>

顶层字段:

字段 类型 说明
hotelGroup HotelGroupVO 配房需求+实配;本次未改动,结构与既有行为一致
vehicleGroup VehicleGroupVO 配车需求+实配;本次修复的核心字段,见下方子表
vehicleHistory List<VehicleRequirementBriefVO> 已失活的配车需求历史(版本倒序);与 vehicleGroup 在同一次请求内按同一个解析出的类别查询,见「业务边界」
unreadMessageCount Integer 「联系房务」按钮未读消息数角标
fleetUnreadMessageCount Integer 「联系车务」按钮未读消息数角标
groupUnreadMessageCount Integer 「联系团期管理员」按钮未读消息数角标
groupBatchId String(雪花 ID,字符串序列化) 运营团期 ID;非团期子订单为 null
canContactFleet Boolean 是否允许联系车务;本次修复后,TRANSFER-only 订单在存在有效用车需求时为 true(此前恒为 false)
contactFleetDisabledReason String 不可联系车务原因;canContactFleet=false 时有值

vehicleGroup(VehicleGroupVO):

字段 类型 说明
requirement VehicleRequirementBriefVO 配车需求摘要,见下表
assignments List<VehicleAssignmentVO> 实际配车列表,见下表

vehicleGroup.requirement / vehicleHistory[](VehicleRequirementBriefVO):

字段 类型 说明
requirementId String(雪花 ID) 需求 ID
version Integer 版本号
status String PENDING/PROCESSING/DONE
isActive Boolean 是否当前生效版本
manualUrgent Boolean 是否由定制师手动加急
submittedAt String(yyyy-MM-dd HH:mm:ss) 提交时间
returnedAt String(yyyy-MM-dd HH:mm:ss) 驳回时间;非驳回历史版本为 null
returnRemark String 驳回原因;非驳回历史版本为 null
vehicleTypeSummary String 车型摘要,如「商务车×2 / SUV×1」
passengerCount Integer 订单乘车人数
vehicleCount Integer 车辆总数
totalSeatCount Integer 车辆座位总数(含司机座)
driverSeatCount Integer 司机占用座位数
passengerSeatCapacity Integer 可载客座位数
remainingPassengerSeats Integer 剩余可载客座位数
specialTags List<String> 特殊诉求标签列表
pickupRequired Boolean 兼容回显字段;接机/接站以大交通信息为准
dropoffRequired Boolean 兼容回显字段;送机/送站以大交通信息为准
remark String 备注

vehicleGroup.assignments[](VehicleAssignmentVO):

字段 类型 说明
assignmentId String(雪花 ID) 配车记录 ID
vehicleType String 车型(冻结快照)
vehicleCount Integer 车辆数(本段)
licensePlate String 车牌(冻结快照)
brand String 品牌(冻结快照)
seats Integer 座位数(冻结快照)
fleetTeamId String(雪花 ID) 车辆所属车队 ID
fleetTeamName String 车辆所属车队名称
startDate String(yyyy-MM-dd) 连续服务开始日期
endDate String(yyyy-MM-dd) 连续服务结束日期
serviceDays Integer 连续服务天数(首尾日期均计入)
plannedDailyFee String(金额,字符串序列化) 计划日单价
driverName String 司机姓名(冻结快照)
driverPhoneMasked String 司机手机(脱敏,格式 138****1111)
remark String 备注

请求示例

GET /v3/admin/order/3401829901234567890/itinerary
Authorization: Bearer {token}

无请求体。

响应示例

TRANSFER-only 订单,已提交并完成一条接送机用车需求(修复后):

{
  "code": 200,
  "message": "成功",
  "data": {
    "hotelGroup": null,
    "vehicleGroup": {
      "requirement": {
        "requirementId": "3401829901234567890",
        "version": 1,
        "status": "DONE",
        "isActive": true,
        "manualUrgent": false,
        "submittedAt": "2026-09-10 09:15:00",
        "returnedAt": null,
        "returnRemark": null,
        "vehicleTypeSummary": "商务车7座×1",
        "passengerCount": 4,
        "vehicleCount": 1,
        "totalSeatCount": 7,
        "driverSeatCount": 1,
        "passengerSeatCapacity": 6,
        "remainingPassengerSeats": 2,
        "specialTags": [],
        "pickupRequired": true,
        "dropoffRequired": true,
        "remark": null
      },
      "assignments": [
        {
          "assignmentId": "3401830011122334455",
          "vehicleType": "商务车7座",
          "vehicleCount": 1,
          "licensePlate": "京A12345",
          "brand": "别克GL8",
          "seats": 7,
          "fleetTeamId": "40001",
          "fleetTeamName": "自有车队",
          "startDate": "2026-09-20",
          "endDate": "2026-09-20",
          "serviceDays": 1,
          "plannedDailyFee": "800.00",
          "driverName": "王师傅",
          "driverPhoneMasked": "138****1111",
          "remark": null
        }
      ]
    },
    "vehicleHistory": [],
    "unreadMessageCount": 0,
    "fleetUnreadMessageCount": 0,
    "groupUnreadMessageCount": 0,
    "groupBatchId": null,
    "canContactFleet": true,
    "contactFleetDisabledReason": null
  },
  "traceId": "a1b2c3d4-e5f6-7890",
  "success": true
}

字段名/类型/嵌套结构逐一核对自 ItineraryVO 源码;业务数值为示意构造,非测试服原始抓包逐字节留存。

空数据 / 降级响应

订单确实没有提交任何用车需求(TRAVEL 与 TRANSFER 均无)时,vehicleGroup 仍合法为 null——这是真实的「没有配车」状态,不是本次修复要处理的缺陷:

{
  "code": 200,
  "data": {
    "hotelGroup": null,
    "vehicleGroup": null,
    "vehicleHistory": [],
    "unreadMessageCount": 0,
    "fleetUnreadMessageCount": 0,
    "groupUnreadMessageCount": 0,
    "groupBatchId": null,
    "canContactFleet": false,
    "contactFleetDisabledReason": "请先提交有效用车需求后再联系车务"
  },
  "success": true
}

错误响应

{
  "code": 581007,
  "message": "订单不存在",
  "success": false,
  "data": null
}

其余可能的错误码:581008「无权查看此订单」(非本单定制师且非超管/管理员/车务管理员)、581045「房务角色无权查看订单详情,房务仅可配房」(Controller 层 OrderViewGuard.assertNotHouseRole() 反向门禁)。

业务边界

  • 鉴权顺序:未登录 → 401(网关拦截);订单不存在 → 581007;越权查看 → 581008;房务/房务组长角色 → 581045。
  • TRANSFER-only 订单在没有有效用车需求时,vehicleGroup 仍为 null、canContactFleet 仍为 false——合法状态,见「空数据/降级响应」。
  • 两类需求都存在(既有 TRAVEL 又有 TRANSFER)的订单:vehicleGroup/vehicleHistory 仍只反映 TRAVEL 一类,TRANSFER 数据不会出现在这两个字段里,这不是遗留缺陷,是既定的单值契约(见「四、契约约束」)。
  • vehicleHistory 与 vehicleGroup 在同一次请求内使用同一个解析出的类别,不会出现「当前需求是接送机、变更历史却按行程用车查」的类别错位。
  • 响应体所有 VO 均不暴露 kind/requirementKind 字段,前端不能从返回值判断当前数据属于 TRAVEL 还是 TRANSFER。

2. 确认订单前置 Checklist GET /v3/admin/order/{id}/confirm-checklist

VO: 无 ReqVO(路径参数 id)→ ConfirmChecklistRespVO

使用场景

管理后台订单详情页点击「确认订单」按钮前调用,用于校验 5 项前置条件(款项/出行人/房型/用车/合同方案)是否全部满足;全部满足时同时返回确认弹框的预览数据。

入参

字段 位置 类型 必填 约束 说明
id Path Long ✅ 订单雪花 ID 订单 ID

出参 Result<ConfirmChecklistRespVO>

顶层字段(allPassed 为唯一开关,items/preview 互斥):

字段 类型 说明
allPassed Boolean 是否全部通过;true 时 items=null、preview 有值,false 时 items 有值、preview=null
items List<ChecklistItemVO> 5 项详细结果,仅 allPassed=false 时返回
preview PreviewVO 确认弹框预览数据,仅 allPassed=true 时返回

items[](ChecklistItemVO):

字段 类型 说明
code String 检查项代码:PAYMENT_OK/TRAVELER_COMPLETE/HOTEL_DONE/VEHICLE_DONE/CONTRACT_TEMPLATE_OK
checkName String 检查项中文名称
passed Boolean 是否通过
failReason String 未通过原因;通过时为 null

preview(PreviewVO):

字段 类型 说明
departureDate String(yyyy-MM-dd) 出发日期
totalPeopleCount Integer 总出行人数
driverName String 司机姓名;本次修复后 TRANSFER-only 订单在有对应配车记录时正确回填(此前恒为 null)
driverPhoneMasked String 司机手机(脱敏);同上
hotels List<HotelSummaryVO> 酒店列表(按行程城市分组)
staffs List<StaffItemVO> 本单配置人员列表(报账人 PRIMARY 排首)
contractAutoAction ContractAutoActionVO 确认后自动生成合同副作用
insuranceAutoAction InsuranceAutoActionVO 确认后自动投保副作用

preview.hotels[](HotelSummaryVO):cityName(String,城市名)、hotelName(String,酒店名)。

preview.staffs[](StaffItemVO):assignmentId(String 雪花 ID)、staffId(String 雪花 ID)、staffName(String)、staffPhone(String,脱敏)、staffRole(String,DRIVER/LEADER/GUIDE/PHOTOGRAPHER/OTHER)、staffRoleName(String)、isPrimaryReporter(Boolean)。

preview.contractAutoAction(ContractAutoActionVO):planName(String)、autoSign(Boolean)。

preview.insuranceAutoAction(InsuranceAutoActionVO):planName(String)、peopleCount(Integer)、effectiveDescription(String)。

请求示例

GET /v3/admin/order/3401829901234567890/confirm-checklist
Authorization: Bearer {token}

无请求体。

响应示例

TRANSFER-only 订单,接送机用车需求已完成、其余 4 项也已满足(修复后 allPassed 可正确为 true):

{
  "code": 200,
  "message": "成功",
  "data": {
    "allPassed": true,
    "items": null,
    "preview": {
      "departureDate": "2026-09-20",
      "totalPeopleCount": 4,
      "driverName": "王师傅",
      "driverPhoneMasked": "138****1111",
      "hotels": [],
      "staffs": [
        {
          "assignmentId": "1234567890123456789",
          "staffId": "9876543210987654321",
          "staffName": "王师傅",
          "staffPhone": "138****1111",
          "staffRole": "DRIVER",
          "staffRoleName": "司机",
          "isPrimaryReporter": false
        }
      ],
      "contractAutoAction": {
        "planName": "标准接送机方案 v1.0",
        "autoSign": true
      },
      "insuranceAutoAction": {
        "planName": "安联境内旅行险 · 尊享版",
        "peopleCount": 4,
        "effectiveDescription": "出发前 24h 内生效"
      }
    }
  },
  "traceId": "a1b2c3d4-e5f6-7890",
  "success": true
}

字段名/类型/互斥结构逐一核对自 ConfirmChecklistRespVO 源码;业务数值为示意构造,非测试服原始抓包逐字节留存。

空数据 / 降级响应

同一批 TRANSFER-only 订单,用车已判定完成但其余项尚未满足(例如合同方案未配置)——修复只纠正用车判定本身,不代表订单必然可确认:

{
  "code": 200,
  "data": {
    "allPassed": false,
    "items": [
      { "code": "PAYMENT_OK", "checkName": "款项校验", "passed": true, "failReason": null },
      { "code": "TRAVELER_COMPLETE", "checkName": "出行人信息", "passed": true, "failReason": null },
      { "code": "HOTEL_DONE", "checkName": "房型安排", "passed": true, "failReason": null },
      { "code": "VEHICLE_DONE", "checkName": "用车安排", "passed": true, "failReason": null },
      { "code": "CONTRACT_TEMPLATE_OK", "checkName": "合同方案配置", "passed": false, "failReason": "未配置合同方案" }
    ],
    "preview": null
  },
  "success": true
}

错误响应

{
  "code": 581007,
  "message": "订单不存在",
  "success": false,
  "data": null
}

其余可能的错误码:581008「无权查看此订单」、581045「房务角色无权查看订单详情,房务仅可配房」。

业务边界

  • 鉴权同「订单详情行程安排 Tab」:581007/581008/581045。
  • allPassed/items/preview 三者互斥,前端渲染前先判 allPassed,不要同时依赖 items 与 preview 都非空。
  • TRANSFER-only 订单不需要用车(needsVehicle=false)或所在团整团免车时,VEHICLE_DONE 项直接通过,不受本次修复影响。
  • preview.driverName/driverPhoneMasked 只在能定位到「当前 active 用车需求绑定的配车记录」时才回填;团车合法缺席场景下该两个字段合法为 null,不是异常。
  • POST /v3/admin/order/{id}/settlement/finalize 的车务车费一致性校验不在本次修复范围内(见七、不影响范围),confirm-checklist 返回 allPassed=true 不代表 settlement/finalize 一定会成功,前端仍需按其可能返回业务失败码处理。

3. 派单看板列表 GET /admin/fleet/board/orders

VO: BoardOrderPageReqVO → BoardOrderPageRespVO

本端点属于 hl-fleet-service(不是 hl-order-service-v3),经网关 Path=/admin/fleet/** 路由(hl-gateway/application.yml:230-233,无 StripPrefix),前端可直接调用;路径本身早于 #8056 修复即已存在(既有派单看板契约)。本次收录的行为变化完全来自其上游依赖 hl-order-service-v3 的 OrderFleetProviderService#batchFleetBoardContexts(同一修复提交 62c28d502),hl-fleet-service 自身代码本次未改动(全仓 grep 8056 零命中),不需要为此单独重新部署 hl-fleet-service。

使用场景

车务派单看板列表页首次加载/翻页/筛选时调用,按当前有效用车需求展示订单及其派车进度。

入参字段表

本次修复未修改该端点任何入参字段(BoardOrderPageReqVO 源码本轮未改动),以下为现状字段,供核对结构未变:

字段 位置 类型 必填 说明
statuses Query String[] 否 多状态筛选(含派生态,后端翻译),空=不过滤
status Query String 否 状态筛选别名(单值/逗号分隔),与 statuses 合并
startDayFrom / startDayTo Query LocalDate(yyyy-MM-dd) 否 行程区间 [start_date,end_date] 重叠筛选
startDate / endDate Query LocalDate(yyyy-MM-dd) 否 startDayFrom/startDayTo 别名,未传前者时生效
vehicleTypeKeys / typeKeys Query String[] 否 车型大类多选:suv/mpv/bus/sedan
driverName Query String 否 司机姓名模糊搜索
keyword Query String 否 统一文字搜索(司机/联系人/团号/订单号/定制师显示名任一包含)
contactName / contactKeyword Query String 否 联系人/客户名模糊搜索
teamNo Query String 否 团号模糊搜索(仅真实团号,不匹配订单号)
groupBatchId Query Long 否 运营团期精确筛选
consultantId Query Long 否 定制师精确筛选(下拉值)
plannerName / consultantName Query String 否 定制师姓名模糊搜索(兼容旧前端)
variant Query String 否 list(默认)/grid,其余值返 100001
page Query Integer 否 页码,默认 1,最小 1
pageSize Query Integer 否 每页条数,默认 20,最大 100

出参字段表

BoardOrderPageRespVO(结构未变,records/total/page/pageSize 平级):

字段 类型 说明
records List<BoardOrderRecordVO> 当前页记录(确定性排序:紧急组置顶→常规→终态沉底→id 兜底)
total Long 总条数(筛选后全量)
page Integer 当前页码
pageSize Integer 每页条数

records[](BoardOrderRecordVO,结构未变;完整字段清单见既有契约 FLEET v1.5 §6.1,本次不重复列出,仅摘录与本次行为变化直接相关的字段):

字段 类型 说明
orderId String(雪花,字符串序列化) 订单 ID
requirementId String(雪花) 当前代表本行的用车需求 ID;混合订单在 TRAVEL 不可联络时,本次修复后该字段可能改为指向 TRANSFER 需求,见「业务边界」
dailySummary BoardDailySummaryVO 代表日行摘要,随 requirementId 所属需求联动

请求示例

GET /admin/fleet/board/orders?page=1&pageSize=20&statuses=unassigned
Authorization: Bearer {token}

无请求体。

响应示例

混合订单(同时有 TRAVEL 与 TRANSFER 需求,TRAVEL 处于不可联络状态、TRANSFER 可联络)兜底展示出的一行:

{
  "code": 200,
  "message": "成功",
  "data": {
    "records": [
      {
        "id": "HL202609200001",
        "orderNo": "HL202609200001",
        "orderId": "3401829901234567890",
        "requirementId": "3401829901234599102"
      }
    ],
    "total": 1,
    "page": 1,
    "pageSize": 20
  },
  "traceId": "a1b2c3d4-e5f6-7890",
  "success": true
}

字段名/类型逐一核对自 BoardOrderRecordVO 源码;为避免与既有契约重复,本示例只展示与本次行为变化直接相关的字段,其余字段结构未变、照旧下发,未在本例中重复列出;业务数值为示意构造,非测试服原始抓包逐字节留存(本端点本次未做独立网关实测,见「八、测试环境已验证」后的说明)。

空数据 / 降级响应

订单没有任何满足 isFleetBoardRequirement 条件的活跃需求时,该订单不进入 records(既有行为,本次未改动):

{
  "code": 200,
  "data": { "records": [], "total": 0, "page": 1, "pageSize": 20 },
  "success": true
}

order-v3 整体不可达时,OrderQueryFacade#getFleetBoardContextsOrNull 返回 null,服务按既有降级路径回退到 fleet 本地派单快照渲染(该降级路径本次未改动)。

错误响应

{
  "code": 100001,
  "message": "参数非法",
  "success": false,
  "data": null
}

variant 传非 list/grid 时触发上述 100001;未登录 → 401(网关拦截)。

业务边界

  • 定性为改进,不是回归:改前,混合订单若 TRAVEL 需求处于不可联络状态(RequirementStatus.isFleetContactable 判 false,仅 PENDING/PROCESSING/DONE 判 true),整单从看板消失——车务完全看不到该订单,且没有任何错误信号;这属于 #8056 静默丢数问题的又一种表现。改后由 TRANSFER 需求兜底展示一行,混合订单不再整单消失。
  • 可达性如实说明:当前数据下,触发该兜底所需的两个条件(TRAVEL 不可联络 ∧ TRANSFER 可联络)在同一订单上的交集为 0 条;但两个子条件各自都有样本(TRAVEL 处于 PENDING_REVIEW 态:30 条;TRANSFER 可联络:88 条),交集为空是当前数据的巧合,不是结构性约束——没有任何机制阻止同一订单同时满足这两个条件。当前同时存在有效 TRAVEL 与 TRANSFER 需求的混合订单共 25 单。
  • 两类需求各自独立查询、独立判歧义(OrderFleetProviderService.java:242-276),不是合并成一次查询——这是刻意保留的既有行为:合并查询会让两类并存的订单拿到 2 条、被 size()==1 判为歧义而整单掉出看板,那会是对行程用车(TRAVEL)看板行为的回归。
  • records[] 出参结构未变,字段来自 TRAVEL 还是 TRANSFER 需求对前端不可见(响应体不暴露 kind/requirementKind 字段,与「三、接口详情」第 1/2 节一致)。
  • 前端渲染该行为完全数据驱动;对 mmg/hl-ui 的 src/views/fleet/ 目录的穷举 grep(证据见「六.7、影响评估」)未发现任何按用车需求种类分支的代码,本行为变化不要求前端改动。
  • 同一订单同一类别(TRAVEL 或 TRANSFER)内存在多条活跃需求时,该类别整体被判定为歧义并跳过(记 warn 日志),不会猜测采用哪一条;这与「四、契约约束」中单值端点 resolveSingleValueVehicleKind 的歧义处理是两套独立实现,互不影响。

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

本节只写后端在「该读哪一类用车需求」上的实际行为,不写 UI 渲染建议。

✅ 正确 / ⚠️ 需注意 理解对照

场景 说明
✅ TRANSFER-only 订单(只有接送机用车需求) 这两个端点现在会读到该订单的 TRANSFER 需求数据
✅ TRAVEL-only 订单(只有行程用车需求) 行为不变,仍读 TRAVEL 数据
⚠️ 两类需求都存在的订单(既有行程用车又有接送机用车) 这两个端点仍然只返回 TRAVEL 数据,不会合并展示 TRANSFER;这不是本次修复遗留的缺陷,是既定的单值契约,不要据此误判为「接送机数据又丢了」
❌ 试图从响应中读取 kind/requirementKind 字段区分当前数据属于哪一类 两个端点的响应 VO 都不暴露该字段(见「三、接口详情」出参表),无法从返回值本身判断
⚠️ 派单看板列表(GET /admin/fleet/board/orders,见「三、接口详情」第 3 节)的 TRAVEL 优先/TRANSFER 兜底 与本节两个单值端点所用的 resolveSingleValueVehicleKind 是两套独立实现(看板按 TRAVEL/TRANSFER 两类分别查询、分别判歧义,见 OrderFleetProviderService.java:242-276),不要假设两者共用同一份解析逻辑或行为完全对称

为什么是「优先」而不是「合并」

这两个端点的响应契约是单值的(一条需求、一个 requirementId、一组司机车辆),容不下两类数据同时返回。两类需求都存在时,返回值逐字段与修复前一致(仍是 TRAVEL);只有「TRAVEL 那类根本不存在」的订单(即 TRANSFER-only 订单)行为发生变化。

请求侧无需改动

两个端点均为 GET + 路径参数 id,请求方式、Header、鉴权方式本次均未改动,无需修改请求代码;本次改动只影响响应体里用车相关字段的取值。


五、数据库行为

两个端点均为只读 GET,不接受请求体,不写库。本次修复只改变了读取用车需求时选择的类别(TRAVEL/TRANSFER),不引入、不修改任何写入行为。


六、边界行为

  • 未登录 → 401(网关拦截)
  • 订单不存在 → 581007
  • 越权查看(非本单定制师且非超管/管理员/车务管理员)→ 581008
  • 房务/房务组长角色查看这两个只读端点 → 581045(OrderViewGuard.assertNotHouseRole())
  • 订单确实没有任何用车需求(TRAVEL 与 TRANSFER 均无)→ 两个端点均按「没有配车」的合法状态返回(见「三、接口详情」空数据/降级响应),不是异常
  • 团车合法缺席(非 DAILY_V3 契约 + 团级配车完成)→ confirm-checklist 的 VEHICLE_DONE 项照常通过,但 preview.driverName/driverPhoneMasked 合法留空

六.5、枚举 / 数据字典

VehicleRequirementKind(TRAVEL/TRANSFER)是本次修复涉及的分类依据,但不是任何请求/响应字段的显式取值——ItineraryVO/ConfirmChecklistRespVO 及其全部嵌套 VO 均不暴露 kind/requirementKind 字段(见「四、契约约束」)。因此本节不适用于字段级枚举值表;TRAVEL/TRANSFER 的选择规则见「四、契约约束」。


六.6、修改前后对比

字段级对比(均限定为 TRANSFER-only 订单场景)

字段 改前 改后
itinerary.vehicleGroup 存在有效接送机用车需求时仍恒为 null 存在有效需求时返回真实的 requirement+assignments
itinerary.canContactFleet 恒为 false 存在有效需求时为 true
itinerary.contactFleetDisabledReason 恒为「请先提交有效用车需求后再联系车务」(误导:需求已提交,只是类别没读到) 存在有效需求时为 null
itinerary.vehicleHistory 按 TRAVEL 类别查询,恒为空数组(即使 TRANSFER 侧有历史驳回记录) 按订单实际单值类别(TRAVEL 优先/TRANSFER 回落)查询
confirm-checklist.items[code=VEHICLE_DONE].passed 用车已实际完成时仍为 false 正确反映实际完成状态
confirm-checklist.items[code=VEHICLE_DONE].failReason 恒为「未提交用车需求」(误导) 用车确已完成时该 item 不再出现在 items(因 allPassed 可能变为 true)
confirm-checklist.allPassed 即使其余 4 项都通过也恒为 false(被 VEHICLE_DONE 拖累) 5 项均满足时可正确为 true
confirm-checklist.preview.driverName / driverPhoneMasked 恒为 null 有对应配车记录时正确回填

行为级对比

行为 改前 改后
TRANSFER-only 订单在行程 Tab 展示配车信息 页面表现为「没有安排车辆」(实际已安排) 正确展示已安排的车辆/司机信息
TRANSFER-only 订单发起「确认订单」 恒被 VEHICLE_DONE 项拦截,无法确认 用车确已完成且其余项满足时可正常确认
失败可见性 无异常、无错误码,HTTP 200,字段静默为空/false,与「真的没安排车」无法区分 同样 HTTP 200,但字段现在反映真实数据
混合订单(同时有效 TRAVEL 与 TRANSFER 需求)在派单看板列表,TRAVEL 需求处于不可联络状态时 整单从看板消失,车务完全看不到该订单(#8056 静默丢数的又一种表现,无任何错误信号) 由 TRANSFER 需求兜底展示一行,字段来自 TRANSFER 需求;订单不再消失(见「三、接口详情」第 3 节)

六.7、影响评估

  • 是否破坏向后兼容: 否——响应结构(字段名、类型、层级)未变,只是同一批字段在 TRANSFER-only 订单上的取值范围从「恒为空/false/固定文案」变为「反映真实数据」。TRAVEL-only 订单与两类都没有活跃需求的订单,这两个端点的返回值逐字段不变。派单看板列表端点(第 3 节)同理:BoardOrderPageRespVO/BoardOrderRecordVO 字段结构未变,只是混合订单在特定条件下代表行从「整单消失」变为「TRANSFER 需求兜底展示一行」。
  • 前端是否必须同步上线: 否——已核实管理后台前端对本文相关字段是纯透传渲染,无需任何代码改动即可直接受益于修复后的数据,核实依据见下方「前端 workaround 清理点」。
  • 前端 workaround 清理点(管理者已对 mmg/hl-ui 代为核实,转录核实结果供核对):查证对象为 origin ref(非本地树;本地树落后 520 个提交,若沿用会得出过时/错误结论),基线 origin/v2.1 @ 56dc9455(2026-09-22 提交,2026-09-22 读取)。核实结果:
    • src/views/order-v2/detail/_shared/v3Adapter.js:1007-1008——canContactFleet/contactFleetDisabledReason 是直接透传映射,无分支逻辑。
    • src/views/order-v2/detail/components/VehicleArrangeCard.vue:434,437——canContactFleet === false 时禁用按钮并展示 contactFleetDisabledReason || '请先提交有效用车需求后再联系车务',纯数据驱动。
    • src/views/order-v2/detail/modals/FunItemAdjustModal.vue:2559-2560——同样的透传模式。
    • src/views/order-v2/detail/_shared/confirmChecklistActions.js:5——VEHICLE_DONE 只映射到 {label:'查看配车', tab:'arrange'},不参与通过/未通过的判定逻辑。
    • 负控:对该仓库 src/views/order-v2/detail/ 与 src/views/fleet/ 目录穷举 grep TRANSFER|接送机,全部命中均与本缺陷无关(BANK_TRANSFER 支付渠道、CONSULTANT_TRANSFER/HOUSE_TRANSFER 时间线事件类型、transport.transferTimeHint)——零命中专门针对 VehicleRequirementKind.TRANSFER 的分支代码;src/views/fleet/ 属于派单看板前端所在目录,该负控同时覆盖「三、接口详情」第 3 节的前端影响判断。
    • 结论:管理后台前端对这一批字段(含派单看板列表相关字段)是纯透传渲染,不存在针对本缺陷写过的 workaround/特判代码,无需任何前端改动。

七、不影响范围

  • 仅影响: GET /v3/admin/order/{id}/itinerary 与 GET /v3/admin/order/{id}/confirm-checklist 两个只读端点在 TRANSFER-only 订单上的用车相关字段;以及 GET /admin/fleet/board/orders(hl-fleet-service)在混合订单(同时有效 TRAVEL 与 TRANSFER 需求)上的代表行兜底展示行为(见「三、接口详情」第 3 节)。
  • 零影响:
    • 纯 TRAVEL(行程用车)订单,或两类需求都没有的订单:这两个端点的返回值逐字段不变。
    • 两类需求都存在(既有 TRAVEL 又有 TRANSFER)的订单:仍只返回 TRAVEL 数据(见「四、契约约束」),本次修复不改变这类订单在这两个端点上的表现。
    • POST /v3/admin/order/{id}/settlement/finalize 的车务车费一致性校验:不在本次修复范围内,前端仍需按其可能返回业务失败码(如 584100「车务车辆总车费暂时不可用,请稍后重试」)处理,不能假设该接口现在必然成功。
    • #8056 记录的用车数据丢失共 10 个落点,均已在同一提交(62c28d502)中一并修复并合入 dev-v3;本文逐字段交付其中 3 处(两个网关实测的只读端点 + 一个源码核查的派单看板列表端点,见「三、接口详情」),其余落点未在本文展开。

八、测试环境已验证

部署版本:hl-order-service-v3 @ d30cd9561(2026-09-21 21:55:58 部署,经 git merge-base --is-ancestor 确认包含修复提交 62c28d502)

GET https://api.test.1814.love:9443/v3/admin/order/{id}/itinerary
  TRANSFER-only 订单(仅接送机用车需求、无行程用车需求):
  vehicleGroup.assignments 返回真实配车记录(含 assignmentId/车型/车牌/座位数/司机姓名/脱敏手机号/服务天数),
  此前该字段为空数组 ✓

GET https://api.test.1814.love:9443/v3/admin/order/{id}/confirm-checklist
  同一批 TRANSFER-only 订单:allPassed 可正确为 true;
  此前恒被 VEHICLE_DONE 项拦截,failReason 固定为「未提交用车需求」/「用车需求未完成」✓

「三、接口详情」第 3 节(派单看板列表 GET /admin/fleet/board/orders)本次未做独立网关实测,未列入上表。该行为变化完全依赖已验证部署的 hl-order-service-v3@d30cd9561(含同一提交 62c28d502);hl-fleet-service 侧代码本身未改动(全仓 grep 8056 零命中),故不需要 hl-fleet-service 单独部署即可生效,见第 3 节开头说明。


十、相关文档

关联 / 联系人

链接

联系人

  • 后端负责人: @wx