文件
hl-api-changelog/changelogs-v2/2026-09/20_7988_团期配车刷新状态只读观测字段-修改接口-管理后台.md
T

28 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 7988 团期配车刷新状态补只读观测口(读团期正式用车需求新增七个只读字段) admin wx(GIT) 修改接口 deployed verified verified mmg 3df2c6f8df7902b31886e7ca0fc0ec88807d7032 2026-09-21 gateway_status=verified 的依据:2026-09-20 18:22-18:46 经网关实测(账号 cw_test_7444)GET /v3/admin/order/group-batch/2101524283048263681/vehicle-requirement,code=200,本篇登记的七个只读观测字段全部出现且字段名/类型与正文一字对应:planRefreshState=null / planRefreshReplayCount=0 / blockedStage=RESOURCE_PREPARING / planRefreshStalled=false / planRefreshStalledReason=null / planRefreshTimeoutAt=null / planRefreshReplayExhausted=false。逐字段对照无出入。⚠️ 正文示例值原来取自单元测试常量,现已有这组真实取值作为实测错开对照。 【上一轮原注】backend_status=deployed 的判据(2026-09-20 18:35 复核):hl-order-service-v3 测试服部署点 e179e09bd(2026-09-20 17:55:02 发布),`git merge-base --is-ancestor 587af48cd e179e09bd` = true,故本篇端点的代码确已在测试服运行的字节里。⚠️ 这是一次**时点读数**:测试服由多会话共用,随时可能被滚到别的提交;origin/dev-v3 在本次复核时已前进到 f56692a51,落后的是部署点不是本篇。 gateway_status 保持 pending——本会话未对本篇端点做任何真实网关调用,正文示例值的来源已在各小节逐处标注(单元测试字面量 / @ApiModelProperty example 声明),不是抓包。待网关复验后再置 verified,**不得为了让门禁变绿改这个位**。 【本轮之前的原注,保留供追溯口径变化】代码已合入 dev-v3(PR #8033,squash 提交 587af48cd,经 git log origin/dev-v3 --oneline | grep 7988 核实存在于 origin/dev-v3)。backend_status 刻意保持 pending——hl-order-service-v3 尚未部署测试服到该提交,未做任何真实网关调用;gateway_status 同样保持 pending。正文请求/响应示例的具体数值来自随 PR 一起合入的单元测试常量(GroupVehicleRequirementPlanRefreshObservationTest:groupBatchId=7201、requirementId=92001、blockedStage=PENDING_DEPARTURE、windowMinutes=120 等)与源码 @ApiModelProperty(example=...) 声明,逐一对源码核实过字段名/类型,但不是测试服网关抓包,具体取值以复验后实测为准。待管理者安排部署 + 网关复验后再把 backend_status/gateway_status 置 deployed/verified 并推送本文件;发布前不许为了过校验改状态位。 前端实证维持 pending(mmg 2026-09-20):planRefresh*/blockedStage 全仓零命中,前端从未为读刷新状态调 confirm(无 workaround 可撤),纯新增只读字段不接入零影响;停滞告警渲染(planRefreshStalled 判据/ReplayExhausted 文案分叉)属团期配车编辑页 #7442+#7444 挂起域,随配车排期一并接入。前端 2026-09-21 已交付:GroupVehicleRequirementSection 渲染观测块——行动判据只看 planRefreshStalled(stalled 未耗尽 warning 引导去上方「整团确认需求」重投、replayExhausted error「请联系后台排查」、stalled=false 且 PENDING info「刷新中,预计 planRefreshTimeoutAt 前出结果」+6s×5 短轮询、null/DONE 不显提示条);PUT/withdraw/waive 后本组件本就重新 GET,同源覆盖;从未为读 FAILED 调 confirm,无 workaround 可撤。定向 spec 覆盖三分支+轮询。 2026-09-20 dev-v3

order-v3: 团期配车刷新状态补只读观测口(工单 #7988)

存放目录: 二期(order-v3 标签工单)→ changelogs-v2/2026-09/

服务: hl-order-service-v3 PR: #8033 | Issue: #7988 | 合并提交: 587af48cd 日期: 2026-09-20 影响范围: 管理后台「团期配车」编辑页,读团期正式用车需求响应体新增七个只读字段(配车刷新状态观测块)


⚠️ 关键变化

改动前,"配车刷新到底死没死"这件事只能通过写口的响应看到——requirement/confirm 与 vehicle-requirement/reopen 两个写口的响应体里带着 planRefreshState 等状态列,而只读的 GET .../vehicle-requirement 没有。于是运营/车务要看一眼"刷新是不是卡住了",唯一手段就是去调 confirm;而 confirm 一旦走到受控重投分支,会顺手把状态从 FAILED 改回 PENDING——查看这个动作本身把现场改掉了,FAILED 因此在管理后台侧根本读不到。

本次在只读的 GET .../vehicle-requirement 响应体上补了七个只读字段,全程不产生任何写(已有单测 get_stalledRequirement_performsNoWrite 与反射断言 get_declaredShape_carriesNoLockOrTransaction 钉住这一点)。

🔴 前端渲染口径必须看 planRefreshStalled,不能看 planRefreshState:planRefreshState=PENDING 对应三种互不相同的现实(正常在途 / 命令已终态失败但状态列没回写 / 已超过时限卡死),三者在 planRefreshState 这一个字段上逐字相同,无法用它自己区分。详见「四、契约约束与正确调用方式」。


一、背景

团期正式用车需求上挂着一条"照这份需求刷新 fleet 配车计划"的异步链路(#7442 PR-C2 引入),状态机三态 NONE(列值 NULL)/PENDING/DONE/FAILED,配套 blocked_stage(阻断阶段快照)、plan_refresh_replay_count(人工受控重投次数,上限 5)等库列。这些库列此前只在两个写口的响应里露出:

  • POST .../requirement/confirm(确认整份需求,会顺手触发一次受控重投)
  • POST .../vehicle-requirement/reopen(重新开窗)

运营想知道"这个团的配车刷新到底怎么样了",只能调 confirm——而 confirm 会把 FAILED 重新入队成 PENDING。这意味着:只要有人查看过一次,FAILED 这个最需要被看到的终态就从管理后台侧消失了。本单在唯一的无副作用观测口——读团期正式用车需求——上补齐这七个字段,让"查一下"这个动作不再改变现场。

维度 改前 改后
看配车刷新状态的入口 只能调写口 confirm/reopen 新增只读入口 GET .../vehicle-requirement(以及共用同一响应体的 PUT/withdraw/waive)
查看动作是否会改变现场 会(confirm 顺手重投,FAILED → PENDING) 不会(读口全程零写,含反射断言钉住方法上无 @Lock4j/@Transactional/@Idempotent)
PENDING 的三种现实是否可区分 无对应字段,读原值也分不开 planRefreshStalled/planRefreshStalledReason 把三种现实拆开

二、变更接口清单

# 接口 方法 路径 变更类型 说明
1 读团期正式用车需求 GET /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement 修改接口 响应体新增 7 个只读字段(配车刷新观测块)

⚠️ 该响应体 GroupVehicleRequirementRespVO 同时被 PUT/withdraw/waive 三个写口共用(源码 javadoc 明确标注"四个端点共用这一个响应体"),所以这 7 个字段同样会出现在:

  • PUT /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement(保存草稿)
  • POST /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement/withdraw(整份撤回)
  • POST /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement/waive(声明整团免车)

这三个写口的字段值语义与 GET 完全相同(都是"该次写入后的现场"只读投影),本次未改动这三个端点自身的请求契约或业务逻辑,故不在上表单独列出,只在这里明确提示;三节共用「接口 1」的「六.5 枚举」「六.6 对比」解释。

POST .../requirement/confirm 与 POST .../vehicle-requirement/reopen 两个写口一字未改——它们的响应体分别是 GroupBatchRequirementConfirmRespVO 与 GroupVehicleReopenRespVO,是两个独立的 VO,本次未触碰。


三、接口详情

1. 读团期正式用车需求 GET /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement

VO: (PathVariable groupBatchId, 无请求体) → GroupVehicleRequirementRespVO

(源码核对:GroupBatchRequirementController.java:180-192、GroupVehicleRequirementRespVO.java、GroupVehicleRequirementService.java:2480-2545 fillPlanRefreshObservation/resolvePlanRefreshStalledReason、GroupVehiclePlanRefreshStalledReason.java)

使用场景

团期配车管理员编辑页打开时调用,回填该团当前正式用车需求(分组/逐日行/整份状态)。本次改动后,它同时是"配车刷新死没死"在 admin 侧唯一的无副作用观测口:页面可以在不触发任何写操作的前提下,展示"这个团的配车刷新是否需要人工介入、卡在哪一步、还能不能自己点确认重试"。该团期尚未形成正式需求时,data 整体为 null(这不是新行为,改动前就是如此),此时七个新字段自然也不存在。

入参字段表

字段 位置 类型 必填 约束 说明
groupBatchId Path Long ✅ - 团期聚合主键

(本端点无请求体、无 Query 参数;本次改动未新增任何入参)

出参字段表

字段 类型 说明
requirementId String 正式需求主键(雪花)
groupBatchId String 团期聚合主键
status String 需求状态:DRAFT/CONFIRMED/DISPATCHED/DONE/PENDING_RECONFIRM/CANCELLED;本次未改动
version Integer 乐观锁版本号
remark String 整份备注
confirmedBy String 整份确认人(DRAFT 时为 null)
confirmedAt LocalDateTime 整份确认时间(DRAFT 时为 null)
planRefreshState String 【本次新增】 配车计划刷新状态原值:null=从未登记过刷新(多数团期的正常态,不要当异常画)/PENDING=命令已发出等 fleet 回来/DONE=本轮刷新已闭环/FAILED=刷新已被判死。⚠️ 单看本字段判不了"要不要处置",看 planRefreshStalled
planRefreshReplayCount Integer 【本次新增】 人工受控重投累计次数(管理员点确认触发的,不含自动重试);上限 5;从未重投过为 0 或 null
blockedStage String 【本次新增】 团期阻断阶段快照:null=未阻断;非空(RESOURCE_PREPARING/MATERIAL_PREPARING/PENDING_DEPARTURE)表示该团正卡在这一步等重新配车。展示口径,不是门禁输入——真正挡住团期推进的是 order_group_batch.vehicle_ready=false
planRefreshStalled Boolean 【本次新增,本块的行动判据,恒非 null】 true=刷新不会自愈、必须有人处置(该团出不了团);false=无需动作(含从未登记刷新/正常在途/已闭环)。判"要不要现在找人"看本字段,不要看 planRefreshState
planRefreshStalledReason String 【本次新增】 停滞归因:STATE_FAILED=刷新已被判死(自动重试耗尽或被 fleet 判定性终结)/COMMAND_FAILED=命令行已失败但状态列没回写(同样是死了)/TIMEOUT=超过本轮窗口时限仍无结果。planRefreshStalled=false 时恒为 null
planRefreshTimeoutAt LocalDateTime 【本次新增】 本轮刷新的"再等就没意义"时刻 = 发起时刻 + 本轮窗口时长(管理员重开时自定,缺省 120 分钟)。仅 planRefreshState=PENDING 且已登记发起时刻时有值,其余为 null
planRefreshReplayExhausted Boolean 【本次新增,恒非 null】 人工重投额度是否已耗尽:true=已达 5 次上限,再点确认只会收到 809210,提示应改为"联系后台排查";false=停滞时仍可由管理员重新确认触发一次重投。只在 planRefreshStalled=true 时才有行动意义
groups[] Array 全部乘车分组(整团免车态为空数组);本次未改动
groups[].groupId / groupCode / vehicleType / serviceStartDate / serviceEndDate / days[] - 分组回显字段,本次未改动

请求示例

GET /v3/admin/order/group-batch/7201/vehicle-requirement

响应示例

数值取自单元测试常量(GroupVehicleRequirementPlanRefreshObservationTest,非网关抓包,见 frontmatter status_note),展示 FAILED 停滞态(get_planRefreshFailed_projectsStateFailedStalled 用例覆盖的场景):

{
  "code": 200,
  "message": "成功",
  "data": {
    "requirementId": "92001",
    "groupBatchId": "7201",
    "status": "CONFIRMED",
    "version": 3,
    "remark": null,
    "confirmedBy": "10086",
    "confirmedAt": "2026-09-18 09:00:00",
    "planRefreshState": "FAILED",
    "planRefreshReplayCount": 1,
    "blockedStage": "PENDING_DEPARTURE",
    "planRefreshStalled": true,
    "planRefreshStalledReason": "STATE_FAILED",
    "planRefreshTimeoutAt": null,
    "planRefreshReplayExhausted": false,
    "groups": []
  },
  "success": true
}

补充第二种取值组合——PENDING 且未超时(get_pendingWithinWindow_projectsNotStalled 用例),只摘录七个新字段:

{
  "planRefreshState": "PENDING",
  "planRefreshReplayCount": 0,
  "blockedStage": "PENDING_DEPARTURE",
  "planRefreshStalled": false,
  "planRefreshStalledReason": null,
  "planRefreshTimeoutAt": "2026-09-20 13:20:00",
  "planRefreshReplayExhausted": false
}

⚠️ 与上一个响应对比:planRefreshState 都可能是 PENDING(本例)或 FAILED(上例),但同为 PENDING 时 planRefreshStalled 还可能是 true(命令已终态失败但状态列没回写、或已超时)——这正是本次改动要解决的"单看原值分不清三种现实"的问题,务必配合下面「四、契约约束」一起读。

空数据 / 降级响应

该团期尚未形成正式需求时,data 整体为 null(改动前既有行为,本次未改动)。从未登记过任何刷新的团期(多数团期的正常态)响应形如:

{
  "code": 200,
  "data": {
    "requirementId": "92001",
    "groupBatchId": "7201",
    "planRefreshState": null,
    "planRefreshReplayCount": null,
    "blockedStage": null,
    "planRefreshStalled": false,
    "planRefreshStalledReason": null,
    "planRefreshTimeoutAt": null,
    "planRefreshReplayExhausted": false
  },
  "success": true
}

⚠️ planRefreshState=null 不是异常、不是数据缺失,是状态机里的 NONE(列值 NULL),没走过受控重开的团期恒为此态,前端不应画成异常/报错样式。本端点无下游依赖,不存在降级路径(全程只读本地 DB,planRefreshOutboxId 非空时最多额外读一次 outbox 命令状态,见「业务边界」)。

错误响应

{
  "code": 589500,
  "message": "团期不存在",
  "success": false,
  "data": null
}

业务边界

  • 鉴权:permissionGuard.require(..., PERMISSION_DEMAND_CONFIRM),与 PUT/withdraw/waive 同一权限码,本次未改动。
  • 全程只读,零写:本次改动新增的装配逻辑 fillPlanRefreshObservation 只做 select(团期存在性 + 需求主表 + 分组/逐日行 + 至多一次按 planRefreshOutboxId 读命令状态),不写任何表、不入队、不触发重投、不取锁、不开事务——已有单测 get_stalledRequirement_performsNoWrite(verifyNoMoreInteractions 钉死"该发生的读之外一次交互都不许有")与反射断言 get_declaredShape_carriesNoLockOrTransaction(get 方法上没有 @Lock4j/@Transactional/@Idempotent)双重覆盖。
  • 命令状态查询是有条件的:只有 planRefreshOutboxId != null 且当前不是已知终态(非 NONE/DONE)时才会去查一次 order_fleet_command_outbox 的命令状态;从未登记过刷新(NONE)或已闭环(DONE)时不会多发这次查询——verifyNoInteractions(orderFleetCommandOutboxService) 在两种场景下都被单测钉住,避免在存量团期上放大成全表的额外查询。
  • NONE/DONE 一律不判停滞,哪怕 planRefreshOutboxId 指向一行已失败的旧命令:reopen 把状态归零时并不清 planRefreshOutboxId 这一列,如果直接套用"命令终态失败即停滞"的判据,会把一个刚刚重开、还没登记新刷新的团期误报成"已死",而那正是最不该报警的时刻。
  • planRefreshTimeoutAt 与受控重开守卫(809209)共用同一份阈值计算:两处各算一遍会出现"页面说已超时、重开却报 809209 不让开"的自相矛盾,本次改动把阈值计算抽成同一个私有方法复用。
  • reconfigureScope 解析失败时兜底 120 分钟,不会因为一条脏 JSON 让团期在页面上永远显示"还在跑"。

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

本节只写响应字段的正确解读方式,不写 UI 渲染建议之外的后端行为规则(本端点无写操作,没有 payload 校验规则可写)。

🔴 判"要不要现在找人"只看 planRefreshStalled,不要看 planRefreshState

planRefreshState=PENDING 对应三种互不相同的现实,在这一个字段上逐字相同:

现实 planRefreshState planRefreshStalled planRefreshStalledReason
正常在途,等 fleet 回调 PENDING false null
命令已终态失败,但状态列没回写(回写钩子没落成) PENDING true COMMAND_FAILED
超过本轮窗口时限仍无结果 PENDING true TIMEOUT
已被判死(自动重试耗尽或被 fleet 终结,状态列已回写) FAILED true STATE_FAILED

只读 planRefreshState 拿到的 PENDING 无法区分前三行——这正是本单要解决的问题。前端渲染逻辑必须以 planRefreshStalled 作为唯一的"要不要报警"判据。

渲染口径

  • planRefreshStalled=true 必须是显式告警态(不是静默):
    • planRefreshReplayExhausted=false → 引导文案「重新确认」
    • planRefreshReplayExhausted=true → 文案改为「联系后台排查」,别让运营继续点确认(再点只会收到 809210)
  • planRefreshStalled=false 且 planRefreshState=PENDING → 展示「刷新中,预计 planRefreshTimeoutAt 前出结果」
  • planRefreshState=null → 不展示刷新相关的任何提示条(这是多数团期的正常态,不是"缺数据")
  • planRefreshState=DONE → 不展示告警,可选展示"已完成"的静态标记

无新增错误码

本端点是纯读口,本次改动不引入任何新错误码;809210/809209 是既有错误码(#7442/#7442 PR-C2 已定义),仅在前端引导用户点击其他写口(confirm/reopen)时才可能命中,本端点自身不会抛出它们。


六、边界行为

  • 未登录/网关未透传角色 → 401(网关拦截),既有行为
  • 团期不存在 → 589500,既有行为
  • 该团期尚未形成正式需求 → data=null,既有行为,本次未改动
  • 老数据兼容:改动前落库的存量需求行 plan_refresh_state/plan_refresh_replay_count/blocked_stage 三列均可能是 NULL(未登记过刷新),响应对应投影为 planRefreshState=null/planRefreshReplayCount=null/blockedStage=null、planRefreshStalled=false、planRefreshReplayExhausted=false,不是异常
  • reconfigure_scope 解析失败(脏 JSON)时,超时窗口按默认 120 分钟兜底计算,不影响响应本身返回成功

六.5、枚举 / 数据字典

planRefreshState(GroupVehicleRequirementRespVO.planRefreshState,源码 GroupVehiclePlanRefreshState 状态机枚举,列值 NULL 对应 NONE)

所属字段: planRefreshState | 类型: String(可空)

值 中文 说明
null 未登记 状态机 NONE,尚未登记过任何刷新;多数团期的正常态
PENDING 刷新中 刷新命令已登记,等 fleet 刷完并把就绪意图投回来;需结合 planRefreshStalled 判断是否正常
DONE 已闭环 就绪回调已通过两级判定并应用,本轮刷新完成
FAILED 已判死 刷新已耗尽重试预算或被 fleet 判定性终结;不放行团期,出团门②持续拒绝

planRefreshStalledReason(GroupVehicleRequirementRespVO.planRefreshStalledReason,源码 GroupVehiclePlanRefreshStalledReason 枚举,仅在读口投影中使用,不落库)

所属字段: planRefreshStalledReason | 类型: String(可空,planRefreshStalled=false 时恒为 null)

值 中文 说明
STATE_FAILED 状态已判死 planRefreshState=FAILED;去查 fleet 为什么刷不动
COMMAND_FAILED 命令已死未回写 状态列仍 PENDING,但命令行已终态失败;去查回写状态的终态钩子为什么没落成
TIMEOUT 已超时 状态列仍 PENDING,命令未终态失败,但已超过本轮窗口时限;去查命令是否卡在队列里

⚠️ 本枚举只进响应,不进库、不进 WHERE——它是三条既有判据(状态列/命令行状态/超时)在读的那一刻算出来的结论,不是持久化状态。

blockedStage(GroupVehicleRequirementRespVO.blockedStage,值域同团期状态机的阶段码)

所属字段: blockedStage | 类型: String(可空)

值 说明
null 未阻断
RESOURCE_PREPARING 团期阶段:资源准备中
MATERIAL_PREPARING 团期阶段:物资准备中
PENDING_DEPARTURE 团期阶段:待出团

⚠️ 本字段是展示口径,不是门禁输入:真正挡住团期推进的是 order_group_batch.vehicle_ready=false,本字段只是"卡在哪一步"的留痕快照。


六.6、修改前后对比

字段级对比

字段 改前 改后
GET .../vehicle-requirement 响应 planRefreshState 不存在(只在 confirm/reopen 两个写口的响应里有) 新增只读投影
同响应 planRefreshReplayCount 不存在 新增
同响应 blockedStage 不存在 新增
同响应 planRefreshStalled 不存在(任何端点都没有这个派生结论) 新增,全站唯一来源
同响应 planRefreshStalledReason 不存在 新增,全站唯一来源
同响应 planRefreshTimeoutAt 不存在 新增,全站唯一来源
同响应 planRefreshReplayExhausted 不存在 新增,全站唯一来源
PUT/withdraw/waive 三个写口的响应(共用同一 VO) 同样没有这七个字段 同样新增(值 = 该次写入后的现场)

行为级对比

行为 改前 改后
查看配车刷新是否卡住 只能调 confirm,而它会顺手做一次受控重投,把 FAILED 改回 PENDING——查看动作本身会改变现场 调只读的 GET 即可看到,零副作用
PENDING 的三种现实(在途/命令已死未回写/超时)是否可区分 不可区分(原值三者相同) 可区分(planRefreshStalled/planRefreshStalledReason)
FAILED 终态在 admin 侧是否可读到 事实上读不到(查看会把它改成 PENDING) 可以,因为查看不再产生写

六.7、影响评估

  • 是否破坏向后兼容: 否——本次只在既有响应体上新增字段,不删除、不改名、不改变既有字段(requirementId/status/groups[] 等)的类型或取值规则;旧前端忽略这七个新字段,行为与改动前逐字节一致
  • 前端是否必须同步上线: 否,是纯新增只读字段,前端可延后接入;接入前不影响现有编辑页回填逻辑
  • 前端 workaround 清理点: 若前端此前为了看一眼 FAILED 状态而在编辑页刻意调用过 confirm(哪怕不点击真正的确认按钮,只为了读响应里的状态字段),这类 workaround 必须撤除——继续这么用会重新触发受控重投,产生非预期的写副作用(把 FAILED 悄悄改回 PENDING,还会消耗一次人工重投额度)

七、不影响范围

  • 仅影响: GET /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement 响应体新增的七个只读字段(以及共用同一 VO 的 PUT/withdraw/waive 三个写口的响应)
  • 零影响:
    • POST .../requirement/confirm、POST .../vehicle-requirement/reopen 两个写口的请求/响应契约一字未改(各自独立的 VO,本次未触碰)
    • GET/PUT/withdraw/waive 四端点原有字段(requirementId/status/version/remark/confirmedBy/confirmedAt/groups[] 等)零变更
    • 无新增错误码,既有错误码(589500/809209/809210 等)行为不变
    • 无新增 DB 列/表/索引:plan_refresh_state/plan_refresh_replay_count/blocked_stage/plan_refresh_requested_at/plan_refresh_outbox_id 五列均由 #7442 PR-C2 建好,本次只是把已有列在只读口上多投影一次,外加一个只存在于响应层、不落库的派生枚举 GroupVehiclePlanRefreshStalledReason
    • 受控重开守卫(809209/809210)的判定逻辑本身未改,本次只是把它复用的阈值计算函数抽出来给只读投影共用
    • 团期配车其余端点(就绪判定、共用关系、派单预校验/候选等,见 #7444/#8013 changelog)零影响

八、测试环境已验证

⚠️ 本篇尚无测试服/网关读数(backend_status=pending、gateway_status=pending,hl-order-service-v3 尚未部署到该提交),如实标注为待部署后补,不把单测读数伪装成测试环境验证。

以下是随 PR #8033 一并合入的单元测试证据(GroupVehicleRequirementPlanRefreshObservationTest,纯 Mockito,交接时未重新执行,仅列出用例名供核实其覆盖范围):

get_planRefreshFailed_projectsStateFailedStalled          — FAILED 终态可见且判为停滞(STATE_FAILED)
get_noPlanRefreshRegistered_projectsQuiet                 — 从未登记过刷新一律不报警,且不多查 outbox
get_planRefreshDoneWithStaleFailedCommand_projectsQuiet   — DONE 不因残留失败命令行被误报,且不多查 outbox
get_pendingWithinWindow_projectsNotStalled                — PENDING 且未超时 = 不停滞,给出 timeoutAt
get_pendingWithFailedCommand_projectsCommandFailed         — PENDING 但命令已死 = 停滞(COMMAND_FAILED)
get_pendingBeyondWindow_projectsTimeout                    — PENDING 且超时 = 停滞(TIMEOUT)
get_pendingWithUnparsableScope_fallsBackToDefaultWindow    — 脏 JSON 兜底默认 120 分钟窗口
get_replayCount_marksExhaustedAtLimit(参数化 4 组)        — replayCount≥5 时 planRefreshReplayExhausted=true
get_stalledRequirement_performsNoWrite                      — 读链路零写(verifyNoMoreInteractions 收口)
get_declaredShape_carriesNoLockOrTransaction                — 方法签名上无 @Lock4j/@Transactional/@Idempotent

待办:管理者安排 hl-order-service-v3 部署测试服到 587af48cd 之后,需补一次真实网关调用(至少覆盖"从未登记"「PENDING 未超时」「FAILED」三种形态),核实七个字段的真实返回值,再把 backend_status/gateway_status 置 deployed/verified 并推送本文件。


十、相关文档

  • 关联 Issue: wx/HL#7988
  • 关联 PR: wx/HL#8033(squash 合并至 dev-v3 @587af48cd)
  • 配车刷新状态机与受控重开守卫的背景见 #7442 PR-C2(GroupVehiclePlanRefreshState/GroupVehiclePlanRefreshStateMachineConfig 源文件头注释)

关联 / 联系人

链接

联系人

  • 后端负责人: wx(GIT)
  • 前端负责人: mmg