文件
hl-api-changelog/changelogs-v2/2026-09/20_7990_车务看板订单详情按需求各返一组身份三元组-修改接口-管理后台.md
T
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

20 KiB
原始文件 Blame 文件历史

schema, ticket, title, consumer, author, change_type, backend_status, gateway_status, frontend_status, frontend_owner, frontend_ref, target_release, verified_at, status_note, updated_at, base
schema ticket title consumer author change_type backend_status gateway_status frontend_status frontend_owner frontend_ref target_release verified_at status_note updated_at base
hl-changelog/v2 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 同步交接)