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 段实证
20 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 | 7990 | 看板订单详情按需求各返一组身份三元组 | admin | wx(GIT) | 修改接口 | deployed | verified | verified | mmg | 662310eac02ef5afc84c81b242949ca536c79489 | v2.1 | 2026-09-21 | backend 已部署到 311dc92ee(hl-fleet-service,2026-09-20 17:46:56,STATE=ok;jar 字节口径已验:605311 命中 2 处,阳性对照老码 605015 命中 1 处,两者都非零说明检索方法本身有效)。gateway_status=verified 依据 2026-09-20 18:0x 经测试网关(api.test.1814.love:9443,账号 cw_test_7443)实测 GET /admin/fleet/board/orders/2101566624467419137 返 HTTP 200(该订单同时有 TRAVEL 与 TRANSFER 两条已确认需求,修复前此形态必返 500),requirementIdentities 返两项:TRAVEL(requirementId=2101567456462147586, version=1, sha256=bcfea7cf…c5cb, generation=359969759900602368) 与 TRANSFER(requirementId=2101567456596365313, version=1, sha256=814e3f85…c21c, generation=359984828583645184)。该组值随后被原样用于 POST /admin/fleet/assignments/requirements/2101567456596365313/confirm,返 code=200, confirmed=true, finalPlanPublished=true (两个执行段均 assigned)——即本字段不只是返回了,而是真的能驱动接送机需求确认走通,这是本篇的效果判据。frontend_status=pending:响应结构新增字段,前端取接送机确认参数必须改用 requirementIdentities 里 kind 匹配的那一项,顶层三字段恒指 TRAVEL。 前端实证翻 not_required(mmg 2026-09-20,挂起期判定):requirementIdentities/605311 全仓零命中;需求级确认取参 useAssignFlow.js 用顶层 requirementSha256(恒 TRAVEL),对现行 TRAVEL 流程逐字正确;500 修复与 605311 由拦截器透 message 自动受益。TRANSFER 接入时(#7443 启动)义务已入前端 memory:取参必须 requirementIdentities.find(kind==='TRANSFER')(禁顶层三字段与 driverConfirmationSummary.dispatchPlanGeneration),且接送机天然多段须走需求级确认端点(单车 confirm 恒 605057)。【mmg 2026-09-21 交付,not_required 翻 verified】requirementIdentities 已随 #7443 C 段消费(hl-admin v2.1 662310ea):AssignModal requirementScopedOrder 按 kind 匹配项覆写 requirementId/requirementVersion/requirementSha256/dispatchPlanGeneration(顶层三字段与整单 summary 均禁用,代际 null 透传),kind 切换走 initializationIdentity 统一重置;新派 batch 取参义务已落地。需求级确认端点按 kind 取参目前仅新派链路使用,confirmHold 复核(整单 activeAssignments 无法按 kind 拆 groups)留后续项。 | 2026-09-20 | dev-v3 |
fleet: 看板订单详情按需求各返一组身份三元组
存放目录: 二期(v3) →
changelogs-v2/{YYYY-MM}/服务: hl-fleet-service (8009) PR: #8048 Issue: #7990 日期: 2026-09-20 影响范围: 管理后台派单看板订单详情页接送机需求确认流程
⚠️ 关键变化
本版修复了一个先前必 500 的缺陷:同一订单同时有行程用车与接送机两条已确认需求时,GET /admin/fleet/board/orders/{orderId} 每次都抛 IllegalStateException ⇒ HTTP 500「系统繁忙」。现已正常返回;真出现无法判定的代际冲突时返业务码 605311(HTTP 200,消息「当前需求存在多个不透明派车方案代际,请联系车务核对派车方案后重试」)。
一、背景
同一订单为何会同时有行程用车与接送机?
订单的出行人日程包含接客、行程、送客三段。接客与送客涉及接送机,中间行程使用行程用车。当出行时间长/人数多时,接机日与送机日往往不相邻,落成两个独立服务窗口,系统会生成两条 active 用车需求:
- TRAVEL(行程用车):行程期间实际用车
- TRANSFER(接送机):接机日 + 送机日之间无直接包含关系
这在存量库中确实存在,不是边角情形。
二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 订单详情 | GET | /admin/fleet/board/orders/{orderId} |
响应体新增字段 + 行为修复 | 新增 requirementIdentities 数组;修复两条需求并存时的 500 缺陷 |
三、接口详情
1. 订单详情 GET /admin/fleet/board/orders/{orderId}
VO: BoardOrderDetailVO → Result<BoardOrderDetailVO>
使用场景
管理后台派单看板左侧点开某订单,右侧弹窗 Step 1(订单详情)加载该接口数据。其中新增的 requirementIdentities 数组是接送机需求确认的唯一参数来源——前端在该页面右上方有「需求级确认」按钮,用户点击后需要从该数组中取参数调用 POST /admin/fleet/assignments/requirements/{requirementId}/confirm。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| orderId | Path | Long | ✅ | 雪花 ID | 订单 ID(订单号) |
出参 Result<BoardOrderDetailVO>
顶层结构:
| 字段 | 类型 | 说明 |
|---|---|---|
| code | Integer | 业务状态码;200=成功,605311=需求代际冲突,其他=失败 |
| message | String | 人类可读的状态消息 |
| data | BoardOrderDetailVO | 订单详情对象(详见下表);失败时为 null |
| success | Boolean | 是否成功 |
BoardOrderDetailVO 关键字段(仅列新增 + 涉及接送机的字段):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | String | ✅ | 订单 ID(订单号);如「HL20260708144554930」 |
| orderNo | String | ✅ | 订单号(同 id) |
| teamNo | String | - | 团号;如「26-0095」 |
| requirementId | Long(序列化为 String) | ✅ | 当前有效用车需求 ID;恒指 TRAVEL(行程用车),语义不变 |
| requirementVersion | Integer | ✅ | 当前有效用车需求版本号;恒指 TRAVEL,语义不变 |
| requirementSha256 | String | ✅ | 当前有效用车需求 SHA-256;恒指 TRAVEL,语义不变 |
| requirementIdentities | List<RequirementIdentityVO> |
✅ | 【新增】本订单当前并存的全部用车需求身份;TRAVEL 在前、TRANSFER 在后;无接送机需求时只有一条 |
| customerName | String | ✅ | 客户名 |
| headcount | Integer | ✅ | 人数 |
| startDate | LocalDate | ✅ | 出团日 |
| endDate | LocalDate | ✅ | 结束日 |
| dailyVehiclePlan | List<DailyVehiclePlanVO> |
✅ | 逐日逐车最终计划;【改】接送机待派车行现在可见(修复前被隐藏) |
| activeAssignments | List<CurrentAssignmentVO> |
✅ | 全部有效派车组 |
| driverConfirmationSummary | DriverConfirmationSummaryVO | ✅ | 司机确认汇总;dispatchPlanGeneration 是整单维度(两条需求并存时恒 null),不能用来确认接送机,需改用 requirementIdentities[].dispatchPlanGeneration |
| (其他字段) | - | - | 与本次变更无关,详见 swagger |
RequirementIdentityVO 结构:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| kind | String | ✅ | 需求类别:TRAVEL(行程用车)/ TRANSFER(接送机);enum 值见 §6.5 |
| requirementId | Long | ✅ | 用车需求 ID;雪花序列化为字符串 |
| requirementVersion | Integer | ✅ | 用车需求版本号;需求级确认时原样回传给 expectedRequirementVersion |
| requirementSha256 | String | ✅ | 用车需求 canonical SHA-256(64 位小写十六进制);需求级确认时原样回传给 expectedRequirementSha256 |
| dispatchPlanGeneration | Long | - | 该需求当前最终派车方案代际;该需求下定稿行的代际不唯一(含尚未定稿)时为 null;需求级确认时回传给 expectedPlanGeneration;注意:不要用顶层 driverConfirmationSummary.dispatchPlanGeneration,那个字段是整单维度计算的(两条需求并存时恒 null),本字段才是当前需求的真实代际 |
请求示例
GET /admin/fleet/board/orders/HL20260708144554930
响应示例
{
"code": 200,
"message": "成功",
"data": {
"id": "HL20260708144554930",
"orderNo": "HL20260708144554930",
"teamNo": "26-0095",
"requirementId": "70123456789",
"requirementVersion": 3,
"requirementSha256": "abc123def456abc123def456abc123def456abc123def456abc123def456ab",
"requirementIdentities": [
{
"kind": "TRAVEL",
"requirementId": "70123456789",
"requirementVersion": 3,
"requirementSha256": "abc123def456abc123def456abc123def456abc123def456abc123def456ab",
"dispatchPlanGeneration": "1934567890123456789"
},
{
"kind": "TRANSFER",
"requirementId": "70123456790",
"requirementVersion": 2,
"requirementSha256": "fed654cba987fed654cba987fed654cba987fed654cba987fed654cba987fe",
"dispatchPlanGeneration": "1934567890123456790"
}
],
"customerName": "赵先生",
"headcount": 3,
"startDate": "2026-05-06",
"endDate": "2026-05-11",
"pickupAt": "hailar",
"dropoffAt": "hailar",
"productName": "额吉的故乡 v9",
"productType": "CORE",
"dailyVehiclePlan": [],
"vehicleSlots": [],
"activeAssignments": [],
"driverConfirmationSummary": {
"dispatchPlanGeneration": null,
"requiredSegmentCount": 0,
"confirmedSegmentCount": 0,
"pendingSegmentCount": 0,
"rejectedSegmentCount": 0,
"ambiguousSegmentCount": 0,
"allDriverConfirmed": false,
"allExecutionConfirmed": false
}
},
"success": true
}
空数据 / 降级响应
{
"code": 503001,
"message": "下游服务暂时不可用,请稍后重试",
"success": false,
"data": null
}
错误响应
场景 1:同一条需求存在多个不透明派车方案代际(新增,修复了原来必 500 的情况)
{
"code": 605311,
"message": "当前需求存在多个不透明派车方案代际,请联系车务核对派车方案后重试",
"success": false,
"data": null
}
场景 2:订单不存在
{
"code": 404,
"message": "订单不存在",
"success": false,
"data": null
}
业务边界
- 原有字段不变:
requirementId、requirementVersion、requirementSha256三个平铺字段恒指 TRAVEL(行程用车),语义与行为完全不变;旧前端不读requirementIdentities时逐字不变 - 存在性保证:
requirementIdentities至少包含一条 TRAVEL 类型的需求;若订单有接送机需求,会多一条 TRANSFER 类型 - 顺序:TRAVEL 恒在数组前,TRANSFER 恒在后(若有);便于前端按索引取值
- 接送机需求确认:前端必须从
requirementIdentities中选择kind === 'TRANSFER'的那一条,提取其requirementVersion、requirementSha256、dispatchPlanGeneration作为需求级确认端点POST /admin/fleet/assignments/requirements/{requirementId}/confirm的入参expectedRequirementVersion、expectedRequirementSha256、expectedPlanGeneration;禁止使用顶层的三个平铺字段(那些恒指 TRAVEL) - 代际字段注意:
requirementIdentities[].dispatchPlanGeneration可能为 null(该需求下尚未定稿、或定稿行的代际不唯一);此时前端调用需求级确认端点时仍需携带,服务端会自行处理;不要用driverConfirmationSummary.dispatchPlanGeneration(整单维度,两条需求并存时恒 null) - 新生成订单:该端点 M1 本地快照的
requirementIdentities会按最新需求逐条填充;若 Feign 拉 order-v3 失败降级,仍返 200,不要当作无需求
四、契约约束与正确调用方式
✅ 正确 / ❌ 错误 payload 对照
需求级确认前端取参示例:
| 场景 | 前端取值来源 | payload |
|---|---|---|
| ✅ 行程用车确认 | requirementIdentities.find(x => x.kind === 'TRAVEL') |
{ "orderId": 123, "expectedRequirementVersion": 3, "expectedRequirementSha256": "abc123...", "expectedPlanGeneration": 1934567890123456789, "requestId": "...", "groups": [...] } |
| ✅ 接送机确认 | requirementIdentities.find(x => x.kind === 'TRANSFER') |
{ "orderId": 123, "expectedRequirementVersion": 2, "expectedRequirementSha256": "fed654...", "expectedPlanGeneration": 1934567890123456790, "requestId": "...", "groups": [...] } |
| ❌ 混合取值 | 分别从 requirementIdentities 与顶层字段混选 |
请求虽不会因此拒绝,但会导致版本/摘要不匹配,服务端拒绝整组确认(605054 错误码) |
| ❌ 用顶层字段确认接送机 | orderId: 123, expectedRequirementVersion: detail.requirementVersion 等 |
顶层字段恒指 TRAVEL,接送机版本号无法提供,确认失败(605054 版本不匹配) |
接送机为何不能用单车端点(POST /admin/fleet/assignments/{assignmentId}/confirm)
接送机需求天然拆成接机日 + 送机日两个不相邻的服务段:
- 接机日:2026-05-06,派车组 assignmentGroupId_1
- 行程:2026-05-07 ~ 2026-05-10,派车组 assignmentGroupId_2(行程用车)
- 送机日:2026-05-11,派车组 assignmentGroupId_3
接送机对应两个 assignmentGroupId(接机和送机),每个都有多个 assignmentId(历史改派)。单车端点 confirm(assignmentId) 按单个 ID 操作,会触发校验:
IF 当前方案内该 assignmentId 所属 assignmentGroupId 包含多个 assignmentId(即多个执行段)
THEN 拒绝并返 605057「当前方案包含多个执行段,请刷新并整组确认」
接送机由于天然多段,任何时刻都会触发 605057。因此前端必须引导用户用需求级确认端点 POST /admin/fleet/assignments/requirements/{requirementId}/confirm(按需求原子性整组提交),不要使用单车端点。
五、数据库行为
无写操作;纯读取。响应字段由如下源头构造:
requirementIdentities[].requirementId/requirementVersion/requirementSha256:来自 fleet_assignment 快照requirementIdentities[].dispatchPlanGeneration:来自 fleet_assignment.plan_finalized_generation(若该需求下多条定稿行代际不唯一或不定稿则为 null)requirementIdentities[].kind:由requirement_type枚举判定(TRAVEL / TRANSFER)
六、边界行为
- 订单不存在 → 404
- 下游 order-v3 Feign 降级 → 返 M1 本地快照数据,仍构造
requirementIdentities,不 500 不阻断页面 - 该订单零需求(业务上不应出现,但 M1 快照理论可能)→
requirementIdentities为空数组 - 只有 TRAVEL 需求(无接送机)→
requirementIdentities仅包含一条 TRAVEL 记录 - 两条需求代际冲突(同一条需求下多条定稿行代际不唯一)→ 该条需求的
dispatchPlanGeneration为 null;若同时触发整单级代际无效(§六.1a)则返 605311
六.5、枚举 / 数据字典
kind(需求类别)
所属字段: requirementIdentities[].kind | 类型: String
| 值 | 中文 | 说明 |
|---|---|---|
TRAVEL |
行程用车 | 出行行程期间的用车需求;恒为主需求 |
TRANSFER |
接送机 | 出行接客或送客期间的接送机需求;可能与 TRAVEL 并存(不相邻服务日) |
六.6、修改前后对比
字段级对比
| 字段 | 改前 | 改后 |
|---|---|---|
| requirementIdentities | 无 | 新增数组,每项含 kind/requirementId/requirementVersion/requirementSha256/dispatchPlanGeneration 五元组 |
| requirementId(顶层) | 指当前有效需求 ID | 改后恒指 TRAVEL;若仅有一条需求则仍指该需求;语义无实质变化 |
| requirementVersion(顶层) | 指当前有效需求版本 | 改后恒指 TRAVEL;语义无实质变化 |
| requirementSha256(顶层) | 指当前有效需求摘要 | 改后恒指 TRAVEL;语义无实质变化 |
行为级对比
| 行为 | 改前 | 改后 |
|---|---|---|
| 订单内同时有 TRAVEL 与 TRANSFER 两条定稿需求 | 抛 IllegalStateException → HTTP 500「系统繁忙」;前端分不清是服务挂了还是数据自相矛盾;接送机确认无从下手 |
正常返 200 + 完整 requirementIdentities 数组;前端根据数组各自提取参数调用需求级确认端点;接送机需求可确认 |
| 同一需求下多条定稿行代际不唯一 | 抛 IllegalStateException → HTTP 500 |
返业务码 605311(HTTP 200)+ message 提示「请联系车务核对派车方案」;前端据此提示用户而非重试 |
| 接送机待派车行的可见性 | 被行程用车的代际筛掉,隐藏不可见 | 改后按需求各自筛代际,接送机的待派车行若无定稿锚点则正常显示 |
六.7、影响评估
- 是否破坏向后兼容:否。
requirementIdentities是纯增量(新字段);原有三个平铺字段语义与行为完全不变;旧前端不读新字段时行为逐字不变 - 前端是否必须同步上线:
- 行程用车确认:否,可延续原流程(用顶层字段)
- 接送机需求确认:是,必须从
requirementIdentities提参,否则无法取到接送机版本号/摘要(接送机确认按钮点不动)
- 前端 workaround 清理点:
- 删除「顶层字段用于确认接送机」的代码路由(若有)
- 接送机需求确认时改为
find(x => x.kind === 'TRANSFER')取参 - 单车端点(
POST .../assignments/{assignmentId}/confirm)对接送机返 605057 时,提示「请用需求级确认」而不是重试
七、不影响范围
- 仅影响:管理后台派单看板订单详情页「需求级确认」流程(接送机确认入口)
- 零影响:
- 管理后台订单列表
- 行程用车单车端点确认流程(
POST .../assignments/{assignmentId}/confirm) - C 端算价、行程详情
- 订单创建、编辑接口
- 存量数据(不迁移;接送机需求现存储为业务数据,查询时动态构造)
八、测试环境已验证
存量问题:全库已有 4 个订单同时存在 TRAVEL + TRANSFER 两条已确认需求(2026-09-20 21:00 前后数据快照):
SELECT order_id, COUNT(DISTINCT requirement_type) as cnt
FROM fleet_assignment
WHERE requirement_type IN ('TRAVEL', 'TRANSFER')
GROUP BY order_id HAVING cnt > 1 AND status != 'canceled';
→ 4 rows
这些订单在修复前每次查看都 500;修复后正常返回 requirementIdentities 完整数据。
后端取证(基于测试用例 + 部署验证):
- ✅ GET /admin/fleet/board/orders/10 → 200 +
requirementIdentities含 TRAVEL 和 TRANSFER 两条记录 - ✅ TRAVEL 和 TRANSFER 的 SHA-256 各不相同、各自 64 位小写十六进制
- ✅ dispatchPlanGeneration 字段存在、可为 null(未定稿时)
- ✅ 同一条需求多代际冲突 → 605311(HTTP 200)而非 500
- ✅ 接送机待派车行在 dailyVehiclePlan 中可见(修复前被筛掉)
网关实测:另一代理正在进行,待读数回填(placeholder 状态)。
十、相关文档
关联 / 联系人
链接
联系人
- 后端负责人: @wx
- 前端接收方: mmg(hl-ui 管理后台)
- 相关接口: 需求级确认端点
POST /admin/fleet/assignments/requirements/{requirementId}/confirm(由 #7989 同步交接)