行程详情 / 确认前置 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>
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 自身代码本次未改动(全仓 grep8056零命中),不需要为此单独重新部署 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/目录穷举 grepTRANSFER|接送机,全部命中均与本缺陷无关(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 侧代码本身未改动(全仓 grep8056零命中),故不需要 hl-fleet-service 单独部署即可生效,见第 3 节开头说明。
十、相关文档
- 关联 Issue: wx/HL#8056
- 关联 PR: wx/HL#8118
关联 / 联系人
链接
联系人
- 后端负责人: @wx