文件
hl-api-changelog/changelogs-v2/2026-09/15_7326_团期分房重算人工微调把重判不平已完成户退回处理中-修改接口-管理后台.md
T

36 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 7326 团期分房重算/人工微调:重判后不平的已完成户退回处理中,时间线新增 reopenedOrderIds admin wx(GIT) 修改接口 deployed not_required verified mmg 917b1848abed2657ac4aea5a3f88c87b6b36e4d3 2026-09-15 PR #7689(Issue #7326 AC-8)已 squash 合并 dev-v3(合并提交 b57413ab8)。2026-09-15 代码已确认部署在测试服(deploy-status.sh 实测),并对 GET status-logs 做了真实网关取证,证实 extra.reopenedOrderIds 字段真实存在(恒为数组)。backend_status 记 deployed:代码已部署且字段契约已实测。⚠️ 如实标注未覆盖范围:取证样本恰好都是空数组,未采到非空(真的退回)场景——该场景目前只有单测覆盖,非测试服端到端验证,详见正文「八」节。gateway_status=not_required:本次涉及的三个端点均为已有路由(/v3/admin/house/group-batches/**、/v3/admin/order/group-batch/**),未新增/修改任何路径或方法。frontend_status=pending:待前端确认「人工微调/重算分房提交后,之前已标记完成的户可能被静默退回处理中」这件事是否需要在结果弹窗提示,或在待配房列表/订单详情页对这类回退做特殊展示,故不定为 not_required。 前端已闭环(917b1848):BoardDetailModal onAllocSaved 在 hotelReady=false 时补查 status-logs 取最新 H10/H11 记录 extra.reopenedOrderIds,非空则结果弹窗点名退回户数并升 warning,补查失败不阻塞;StatusLogsPanel extraText 补「退回处理中 N 户」展示(空数组/历史缺键兜住);spec 共 +4 例。 2026-09-15 dev-v3

团期房务: 分房重算/人工微调把重判不平的已完成户退回处理中

存放目录:

  • 一期(v2,无 order-v3 标签的工单)→ changelogs/{YYYY-MM}/
  • 二期(v3,order-v3 标签的工单)→ changelogs-v2/{YYYY-MM}/

服务: hl-order-service-v3 (端口 8086) PR: #7689(Issue #7326 AC-8) Issue: #7326 日期: 2026-09-15 影响范围: 管理后台团期「人工微调分房」「重算分房」两个写口新增的副作用(已完成户可能被退回处理中);团期状态流水(时间线)接口的 extra 新增 reopenedOrderIds 字段


⚠️ 关键变化(非必须,本版与上版行为不同 / 纠错 / 撤销时必写)

  • 本次变了什么:H10(人工微调分房,POST /v3/admin/house/group-batches/{groupBatchId}/allocations)与 H11(重算分房,POST .../allocations/rebuild)在判定「团级配房完成标志判不出就绪、需要置回 false」这一分支时(resetWhenNotReady=true),新增了一个户级动作:把「此前已经是住宿已完成(house_status=CONFIRMED 且需求 status=DONE),但按最新分房重判后又不平」的户,CAS 退回处理中——需求 status 从 DONE 回退到 PROCESSING,house_status 改写为 PENDING_CLAIM,订单 flow_status 尝试从 PENDING_CONFIRM CAS 到 RESOURCE_PREPARING,并重新同步一次该订单的房务待办(GroupBatchRoomDayConfirmManager.java:707-744、:833-843;GroupBatchRoomLifecycleManager.java:547-559)。
  • 前端/调用方以前以为的是什么:H10/H11 只会影响「团级」的 hotelReady 之类聚合标志;某个户一旦被标记为「住宿已完成」,除非房务在这户自己的详情页手动操作,否则它的状态不会被别的团级操作(人工微调另一天、或重算另一晚)连带改动。
  • 实际现在是什么:只要本次 H10/H11 调用判不出团级就绪(resetWhenNotReady=true 分支被触发),凡是重判后不平的已完成户都会被一并静默退回处理中;H10/H11 自身的直接响应体(GroupBatchRoomAllocationRebuildRespVO)不携带这批被退回的户名单——调用方必须另外读团期状态流水接口(GET /v3/admin/order/group-batch/{groupBatchId}/status-logs),在 eventType=BATCH_ROOM_ALLOC_MANUAL/BATCH_ROOM_ALLOC_REBUILD 的记录里读 extra.reopenedOrderIds 才能知道具体是哪些子订单(GroupBatchRoomAllocationManager.java:250-258、:329-338)。不读时间线,前端只能看到该户重新出现在待配房列表/订单详情的房务进度条回退到「待配房」,却不知道原因。

一、背景(选填)

Issue #7326 AC-8:H10/H11 共用的 GroupBatchRoomDayConfirmManager#settleHouseholdsAndHotelReady 此前只做团级 resetHotelReady(把团级 hotelReady 置回 false),户级的 DONE + house_status=CONFIRMED 原样留着不动(GroupBatchRoomDayConfirmManager.java:728,2026-09-15 对照 origin/dev-v3 核实更新,2026-09-14 起草时的 708-712 因中间插入 #7700/#7459 等提交已漂移约 +20 行;javadoc 原话:「同一个假信号的户级版本」)。这会导致:核单侧依赖 house_status=CONFIRMED/需求 status=DONE 的户级闸门继续放行,待办也已经从房务列表里消失,但这户实际上已经因为重算/人工微调而少了房,两边状态不一致。

本次改动让 resetWhenNotReady=true 时,团级 resetHotelReady 与户级 reopenUnbalancedHouseholds 两个反向动作成对执行(同一个开关,不新增第二个开关;理由见 GroupBatchRoomDayConfirmManager.java:713-716 javadoc:拆成两个参数就是同一条判据两份实现,容易两条链路各改各的、对「这个团安排好了没有」给出矛盾答案)。

H7/H8(另外两个会触发 settleHouseholdsAndHotelReady 的入口,见 GroupBatchRoomDayConfirmManager.java:233、:346)传的固定是 resetWhenNotReady=false——它们只会让事实变好(确认计划行、扣库存),不会触发户级回退,本次改动不影响 H7/H8 这两个入口的行为。


二、变更接口清单

# 接口 方法 路径 变更类型 说明
1 人工微调分房 POST /v3/admin/house/group-batches/{groupBatchId}/allocations 副作用扩大 判不出团级就绪时,重判后不平的已完成户被 CAS 退回处理中;时间线 extra 新增 reopenedOrderIds
2 重算分房 POST /v3/admin/house/group-batches/{groupBatchId}/allocations/rebuild 副作用扩大 同上
3 团期状态流水(时间线) GET /v3/admin/order/group-batch/{groupBatchId}/status-logs 响应内容新增字段 eventType=BATCH_ROOM_ALLOC_MANUAL/BATCH_ROOM_ALLOC_REBUILD 两类记录的 extra 新增 reopenedOrderIds(数组,恒不为 null,可为空数组)

三、接口详情

三个端点的请求参数、错误码均未变化;H10/H11 的直接响应体字段也未变化,只是多了一个不反映在响应体里的副作用;端点 3 的响应体新增一个 extra 子字段。以下逐接口自包含描述。

1. 人工微调分房 POST /v3/admin/house/group-batches/{groupBatchId}/allocations

VO: GroupBatchRoomAllocationSaveReqVO → Result<GroupBatchRoomAllocationRebuildRespVO>

使用场景

房务在「团期分房」页面对某几条已确认计划行提交人工分房覆盖时调用(H10)。本次改动后,若提交这次微调导致判不出团级就绪,且团内存在此前已标记「住宿已完成」但按新分房结果不平的户,这些户会被自动退回处理中——这是本接口触发的副作用,不在直接响应体里体现,前端需要另外查询时间线(见本文件端点 3)或该户的订单详情才能感知。

入参

字段 位置 类型 必填 约束 说明
items Body Array<Object> ❌ 与 clearPlanIds 至少一个非空;最多 500 行 覆盖后的人工分房行集合(本次未改)
items[].planId Body String(雪花 ID) ✅ 须属本团、未软删且已确认 订房计划行 ID
items[].orderId Body String(雪花 ID) ✅ 须为本团在团子订单 分给哪一户
items[].roomCount Body Integer ✅ 1-99 占用间数
items[].roomGroupNo Body String ❌ 形如 F1~F999,不填按 F1 家庭分组号
items[].travelerCount Body Integer ❌ 1-9 该房入住人数
items[].bedType Body String ❌ single/double/twin/family,非法 808131 床型 code
items[].remark Body String ❌ ≤256 字 备注
clearPlanIds Body Array<String>(雪花 ID) ❌ 与 items 至少一个非空;最多 200 条 这些计划行下的人工分房全部软删(回到纯自动分房)

(以上入参字段结构本次未改,见 GroupBatchRoomAllocationSaveReqVO.java、GroupBatchRoomAllocationItemReqVO.java)

出参 Result<GroupBatchRoomAllocationRebuildRespVO>

字段 类型 说明
data.groupBatchId String(雪花 ID) 团期主订单 ID
data.force Boolean 本次是否重置了人工分房(H10 恒 false)
data.balanced Boolean 本次范围内所有日都对平、且无过时户、无阻塞户
data.hotelReady Boolean 后置判定之后团期的配房完成标志;本次起:若有已完成户被退回处理中,通常伴随此值为 false
data.days[] Array<Object> 本次处理过的入住日结果(leftover/shortage/manualConflicts/outOfRange 等,字段结构本次未改)
data.skippedDays[] Array<Object> 未处理的入住日(计划行未确认)
data.staleOrderIdsBefore[] Array<String> 本次开始前判定为分房过时的户
data.replacedAllocIds[] Array<String> 本次被软删的分房行 ID
data.warnings[] Array<Object> 户完成联动与团级判定过程中产生的告警

⚠️ 本次改动不在这里新增字段:被退回处理中的户 ID 列表不在 GroupBatchRoomAllocationRebuildRespVO 里,只能从本文件端点 3(时间线)的 extra.reopenedOrderIds 读到。

请求示例

{
  "items": [
    { "planId": "880101", "orderId": "70001", "roomCount": 1, "roomGroupNo": "F1" }
  ],
  "clearPlanIds": []
}

响应示例

以下取自 GroupBatchRoomAllocationManagerTest#saveManual_householdsReopened_writesThemIntoTimeline 测试夹具(团期 groupBatchId=9001,提交人工分房时触发户 orderId=70002 被判不平并退回),不是测试服抓包报文;响应体本身的字段结构与本次改动前逐字节相同:

{
  "code": 200,
  "message": "成功",
  "data": {
    "groupBatchId": "9001",
    "force": false,
    "balanced": false,
    "hotelReady": false,
    "days": [],
    "skippedDays": [],
    "staleOrderIdsBefore": [],
    "replacedAllocIds": [],
    "warnings": []
  },
  "success": true
}

空数据 / 降级响应

items/clearPlanIds 均传空数组时按 100001 拒绝(既有校验,本次未改,见「四、契约约束」);本接口不存在「有效提交但返回空数据」的形态——只要通过校验,days 至少包含受影响的入住日。

错误响应

计划行未确认或不属本团(既有错误码,本次未改):

{
  "code": 808601,
  "message": "计划行不存在或未确认",
  "data": null,
  "success": false
}

受影响户存在过时分房时整单拒绝(既有错误码,本次未改):

{
  "code": 808643,
  "message": "存在分房过时的户,请先重算",
  "data": null,
  "success": false
}

业务边界

  • 鉴权:@HouseWriteGuarded,非房务角色在幂等/锁窗口之前即被拦截;团期须处于可配房阶段,否则 assertConfirmableStage 拒绝(既有逻辑,本次未改)。
  • 团期级锁:@Lock4j(house-groupbatch-lock),同团期并发提交串行化,本次未改。
  • 新增副作用只在 resetWhenNotReady=true 分支触发:本端点内部固定传 true(GroupBatchRoomAllocationManager.java:392 起的 settleAndFinish 私有方法),即 H10 每次调用都参与户级回退判定,不是可选行为,前端不能假设「只微调了没关系的一天就不会影响别的已完成户」。
  • 只退真的从 DONE 被 CAS 掉的户:若某户虽然「不平」但此前并不是完成态(status≠DONE),CAS 直接返回 0 行,reopenedOrderIds 不计入它,也不会有任何写操作发生在它身上(GroupBatchRoomLifecycleManager.java:601-613 reopenHousehold 方法体,2026-09-15 核实更新,2026-09-14 起草时的 551-553 已漂移约 +50 行)。
  • 全自订户不受影响:全自订户走的是 completeSelfBookedHouseholds 独立判定路径,不进入 evaluateHouseholds 的返回集合,因而结构性地碰不到本次新增的退回逻辑(GroupBatchRoomDayConfirmManager.java:826-829)。

2. 重算分房 POST /v3/admin/house/group-batches/{groupBatchId}/allocations/rebuild

VO: GroupBatchRoomAllocationRebuildReqVO → Result<GroupBatchRoomAllocationRebuildRespVO>

使用场景

团期管理员重新确认需求后,房务在「团期分房」页面点击「重算分房」调用(H11)。可指定 stayDate 只重算某一天,或不传重算整团全部已确认日;force=true 会先把范围内的人工分房整片软删。本次改动后,重算若导致某个已完成户不再平,同样会被自动退回处理中。

入参

字段 位置 类型 必填 约束 说明
force Body Boolean ❌ 默认 false 是否先重置全部人工分房
stayDate Body String(日期,yyyy-MM-dd) ❌ 不传 = 整团全部已确认日 只重算某一入住日
reason Body String force=true 时 ✅ ≤256 字 重置原因,写进团级时间线

(以上入参字段结构本次未改,见 GroupBatchRoomAllocationRebuildReqVO.java)

出参 Result<GroupBatchRoomAllocationRebuildRespVO>

字段结构与端点 1 完全相同(两端点共用同一个 GroupBatchRoomAllocationRebuildRespVO),差异只在于 force/days 的取值来源不同;reopenedOrderIds 同样不出现在这里。

字段 类型 说明
data.groupBatchId String(雪花 ID) 团期主订单 ID
data.force Boolean 本次是否重置了人工分房(随请求 force 回显)
data.balanced Boolean 本次范围内所有日都对平、且无过时户、无阻塞户
data.hotelReady Boolean 后置判定之后团期的配房完成标志;本次起:若有已完成户被退回处理中,通常伴随此值为 false
data.days[] Array<Object> 本次处理过的入住日结果(字段结构本次未改)
data.skippedDays[] Array<Object> 未处理的入住日(计划行未确认)
data.staleOrderIdsBefore[] Array<String> 本次开始前判定为分房过时的户
data.replacedAllocIds[] Array<String> 本次被软删的分房行 ID
data.warnings[] Array<Object> 户完成联动与团级判定过程中产生的告警

请求示例

{
  "force": false,
  "stayDate": "2026-06-12"
}

响应示例

以下取自 GroupBatchRoomAllocationManagerTest#rebuild_householdsReopened_writesThemIntoTimeline 测试夹具(团期 groupBatchId=9001,重算导致户 orderId=70002 判不平并退回),不是测试服抓包报文:

{
  "code": 200,
  "message": "成功",
  "data": {
    "groupBatchId": "9001",
    "force": false,
    "balanced": false,
    "hotelReady": false,
    "days": [],
    "skippedDays": [],
    "staleOrderIdsBefore": [],
    "replacedAllocIds": [],
    "warnings": []
  },
  "success": true
}

空数据 / 降级响应

范围内无已确认计划行时拒绝(既有错误码 808644,本次未改);未确认的日进 skippedDays,不算错误。

错误响应

范围内无已确认计划行(既有错误码,本次未改):

{
  "code": 808644,
  "message": "范围内无已确认计划行,无法重算",
  "data": null,
  "success": false
}

force=true 但未填 reason(既有校验,本次未改):

{
  "code": 100001,
  "message": "reason 不能为空",
  "data": null,
  "success": false
}

业务边界

  • 鉴权/锁:同端点 1。
  • 新增副作用同样固定触发:本端点内部同样固定传 resetWhenNotReady=true(GroupBatchRoomAllocationManager.java:329 起),force=false(默认档,只重跑自动分房、保留人工分房)与 force=true(先软删人工分房再重来)两档都会触发户级回退判定,不因 force 取值而豁免。
  • stayDate 只影响本次重算的范围,不影响户级回退的判定范围:reopenUnbalancedHouseholds 对全团重新判定平衡(evaluateHouseholds 判的是每户全部基线晚,不按 stayDate 收窄),因此即使只重算了某一天,如果这户在另一天早就不平,本次也会一并把它退回处理中(GroupBatchRoomDayConfirmManager.java:821-823 javadoc)。
  • 只退真的从 DONE 被 CAS 掉的户:同端点 1。

3. 团期状态流水(时间线) GET /v3/admin/order/group-batch/{groupBatchId}/status-logs

VO: Result<List<GroupBatchStatusLogItemVO>>(无请求体,仅路径参数)

使用场景

管理后台团期详情页查看操作历史/时间线时调用,按 changedAt 升序返回该团期全部流水,不分页。本次起,H10/H11 触发的 BATCH_ROOM_ALLOC_MANUAL/BATCH_ROOM_ALLOC_REBUILD 两类记录的 extra 多一个 reopenedOrderIds 字段,前端可用它在时间线条目上提示「本次操作把 N 户退回了处理中」。

入参

字段 位置 类型 必填 约束 说明
groupBatchId Path Long ✅ - 团期主订单 ID

出参 Result<List<GroupBatchStatusLogItemVO>>

字段 类型 说明
data[].logId String(雪花 ID) 流水 ID
data[].groupBatchId String(雪花 ID) 团期 ID
data[].changeType String STATUS/DATA;本次涉及的两类事件恒为 DATA
data[].eventType String 事件类型值,本次涉及 BATCH_ROOM_ALLOC_MANUAL/BATCH_ROOM_ALLOC_REBUILD
data[].eventTypeName String 事件类型中文标签,如「房务人工微调分房」「房务重算分房」
data[].content String 展示文本,如「房务人工微调分房(1 行,涉及 1 天)」
data[].extra Object/null 附加快照(已从 JSON 字符串解析为对象);本次起,BATCH_ROOM_ALLOC_MANUAL/BATCH_ROOM_ALLOC_REBUILD 两类记录新增 extra.reopenedOrderIds 字段
data[].extra.reopenedOrderIds Array<Number> 新增字段。本次操作真被 CAS 退回处理中的子订单 ID 数组;恒为数组(不会是 null),CAS 未命中任何户时为 []
data[].changedAt String(时间) 变更时间

(operatorType/operatorId/operatorName/reason 等既有字段本次未改,未逐一列出)

请求示例

GET /v3/admin/order/group-batch/9001/status-logs
Authorization: Bearer <token>

响应示例

以下取自 GroupBatchRoomAllocationManagerTest#rebuild_householdsReopened_writesThemIntoTimeline 测试夹具中对 extra 的断言(该用例只断言 extra map 的内容,未经过完整的 GroupBatchStatusLogItemVO 序列化链路;logId/changedAt 等字段为示意值,不是取自该用例):

{
  "code": 200,
  "message": "成功",
  "data": [
    {
      "logId": "90501",
      "groupBatchId": "9001",
      "changeType": "DATA",
      "eventType": "BATCH_ROOM_ALLOC_REBUILD",
      "eventTypeName": "房务重算分房",
      "fromStatus": "RESOURCE_PREPARING",
      "fromStatusName": "资源准备中",
      "toStatus": "RESOURCE_PREPARING",
      "toStatusName": "资源准备中",
      "content": "房务重算分房(1 天,force=false)",
      "reason": null,
      "operatorType": "ADMIN",
      "operatorId": "1001",
      "operatorName": "小呼",
      "extra": {
        "force": false,
        "reason": null,
        "stayDate": "2026-06-12",
        "unbalancedDays": ["2026-06-12"],
        "manualReset": 0,
        "reopenedOrderIds": [70002]
      },
      "changedAt": "2026-09-14 10:21:33"
    }
  ],
  "success": true
}

未触发任何户级回退时,同一 extra 里 reopenedOrderIds 为空数组(取自 rebuild_manualExceedsNewDemand_keepsManualAndReportsConflict 等既有用例默认桩 SettleResult(List.of(), List.of(), false)):

{ "extra": { "reopenedOrderIds": [] } }

空数据 / 降级响应

该团期从未发生过任何生命周期事件(理论场景,团期一旦创建即有 BATCH_GROUP 建团记录)时返回空数组:

{ "code": 200, "message": "成功", "data": [], "success": true }

extra 解析失败或该条记录本身无附加数据时,extra 为 null,不是空对象(既有降级规则,本次未改,见 GroupBatchStatusLogItemVO.java:17-19 javadoc)。

错误响应

groupBatchId 指向不存在的团期(既有错误码,本次未改):

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

业务边界

  • 鉴权:走网关统一鉴权,未登录 401;本端点不带额外角色限制(与改动前一致)。
  • reopenedOrderIds 只对 BATCH_ROOM_ALLOC_MANUAL/BATCH_ROOM_ALLOC_REBUILD 两类事件出现:其余事件类型(如 BATCH_GROUP、BATCH_HOUSE_CLAIM)的 extra 结构不受本次改动影响。
  • 该字段只反映「CAS 真命中」的户:调用方不应据此反推「团内还有多少户不平」——不平但本来就不是完成态的户不会出现在这里(不属于「退回」,因为它们从未处于「已完成」态)。
  • 写入是尽力而为、不阻断主链路:时间线写入走 recordHouseTimelineQuietly 降级包装,DB 类异常之外的失败只记日志不抛出;但注意它与主事务共用一个事务边界,DB 类异常仍会导致整个 H10/H11 调用连带回滚(GroupBatchService.java:346-360(origin 行号)javadoc 明确写了这条边界)。
  • 只读:本端点不提供任何写口。

四、契约约束与正确调用方式(接口类必写)

本节只写后端接受/拒绝 payload 的规则与调用后必须做的动作,不写 UI 渲染建议。

✅ 正确 / ❌ 错误 调用方式对照

场景 说明
✅ 提交 H10/H11 后,若 hotelReady=false,前端另外查一次时间线确认是否有户被退回 reopenedOrderIds 只在时间线里,响应体的 hotelReady=false 只说明团级判不出就绪,不说明是不是有已完成户被动了状态
✅ 展示某户的房务进度条前,以 GET /admin/house/orders/{orderId} 返回的 progress.houseStatus 为准 该户是否被退回处理中,最终权威落在这个字段上,不要用前端本地缓存的「已完成」状态继续渲染
❌ 认为「H10/H11 返回 200 且响应体没有报错字段」就等于「除了本次显式提交的行,其他户状态不受影响」 已完成户被退回是静默副作用,不在响应体里报告,200 不代表没有连带影响
❌ 把 extra.reopenedOrderIds 缺失当成「没有户被退回」 该字段本次才新增;改动前产生的历史时间线记录(changedAt 早于本次上线)不会有这个字段,前端按字段缺失兜底为「未知」而不是「一定为空」

切换状态时的必要动作

被退回处理中的户,其需求会重新出现在房务「待配房」相关列表/抢单池、待办也会重新生成(orderTodoSyncContract.syncForOrder(orderId, SYNC_REASON_REOPEN),实现在 GroupBatchRoomLifecycleManager.java:611,2026-09-15 核实更新;⚠️ 2026-09-14 起草时把这个内部原因常量的字面值错写成了 "REOPEN",源码实际值是 "group room plan revised"——该值只用于待办同步的内部 reason 参数,不出现在任何对外响应字段里,故本节此前的错误不影响契约本身,只更正引用准确性)。前端若维护了「已完成」本地状态缓存或已勾选/已处理的 UI 状态,收到时间线里的 reopenedOrderIds 非空后应主动刷新这些户的展示,不要依赖用户手动刷新页面。


五、数据库行为(涉及写操作时必写)

前置条件 order_hotel_requirement.status 列 order_hotel_requirement.house_status 列 子订单 order_main.flow_status 列
该户当前 status=DONE,重判后不平,CAS 命中 DONE → PROCESSING(CAS,仅当前恰为 DONE 时生效) → PENDING_CLAIM 仅当前恰为 PENDING_CONFIRM 时 CAS → RESOURCE_PREPARING,否则原样不动(CAS 结果不影响 reopenedOrderIds 是否计入该户)
该户当前 status≠DONE(已经不是完成态) 不变(CAS 0 行) 不变 不变
该户重判后仍平(balanced=true) 不变 不变 不变

幂等/并发:reopenHousehold 的第一条语句就是 requirement.status 的 DONE-only CAS,非完成态或已被并发操作改动的户会在这一步返回 0 行,后续 house_status/flow_status/待办同步三个写操作都不会执行(RequirementService.java:3560-3566(origin 行号,未在本轮复核);GroupBatchRoomLifecycleManager.java:601-613,2026-09-15 核实更新)。


六、边界行为

  • 未登录 → 401(网关拦截)
  • 团期不存在 → 808001(端点 3)/ 已有错误码(端点 1/2 建团期不存在时的行为本次未改)
  • 计划行未确认/不属本团 → 808601(既有,未改)
  • 存在过时分房 → 808643(既有,未改)
  • 范围内无已确认计划行 → 808644(既有,未改)
  • 户重判后不平但本来就不是完成态 → 不产生任何写操作,不出现在 reopenedOrderIds 里,不是错误
  • 全自订户 → 结构性不参与本次新增的判定,不受影响
  • 时间线写入失败(非 DB 类异常)→ 降级为只记日志,H10/H11 主链路不受影响;DB 类异常仍会导致整次调用回滚

六.5、枚举 / 数据字典(接口出现枚举时必写)

以下枚举不是本文件端点 1/2/3 直接返回的字段,但是理解「已完成户被退回处理中」这一行为对页面展示影响的必要背景——它们出现在另一个未被本次改动的既有端点 GET /admin/house/orders/{orderId}(房务侧订单详情聚合,§2.0)的 data.itinerary.progress.houseStatus 字段上;本次改动只是让部分订单的这个字段值出现回退,字段本身与端点契约均未变。

houseStatus(com.hulalv.house.statemachine.HouseStateEnum)

所属字段: HouseOrderDetailRespVO.Itinerary.Progress.houseStatus(GET /admin/house/orders/{orderId},非本次变更接口) | 类型: String

值 中文 说明
PENDING_CLAIM 待配房 本次改动后,被退回处理中的户会落到这个值(HouseStateEnum.java:34)
CLAIMING 抢单中 本次未涉及
PENDING_FINALIZE 待核实 本次未涉及
CONFIRMED 已完成 被退回前的原值(HouseStateEnum.java:48)
EXCEPTION 异常 本次未涉及

flow_status(com.hulalv.order.core.enums.OrderFlowStatus,节选)

所属字段: 子订单 order_main.flow_status(内部字段,无对外只读端点直接暴露该原始枚举值;本次列出仅为解释 CAS 的判定条件) | 类型: String

值 中文 说明
PENDING_CONFIRM 待确认 被退回时 CAS 的判定条件:仅当子订单当前恰为此值,回退才会把它 CAS 成 RESOURCE_PREPARING(OrderFlowStatus.java:28)
RESOURCE_PREPARING 资源准备 CAS 命中后的目标值(OrderFlowStatus.java:27)

六.6、修改前后对比(修改/删除类接口必写,新增跳过)

字段级对比

字段 改前 改后
H10/H11 直接响应体(GroupBatchRoomAllocationRebuildRespVO) 字段结构不变 字段结构完全不变——本次不改响应体任何字段
status-logs 响应 extra(BATCH_ROOM_ALLOC_MANUAL/BATCH_ROOM_ALLOC_REBUILD) 无 reopenedOrderIds 字段 新增 reopenedOrderIds: Array<Number>,恒为数组(不为 null)
已完成户的 house_status(经 GET /admin/house/orders/{orderId}) 一旦 CONFIRMED,不会因为别的户的分房调整而改变 团级 H10/H11 判不出团级就绪时,重判不平的已完成户可能被静默改回 PENDING_CLAIM

行为级对比

行为 改前 改后
H10/H11 判不出团级就绪时(resetWhenNotReady=true 分支) 只置回团级 hotelReady;户级 DONE/CONFIRMED 原样保留 团级置回 + 重判不平的已完成户 CAS 退回处理中(需求 status/house_status/子订单 flow_status/待办同步四件事)
房务待配房列表/抢单池 已完成户不会出现 被退回处理中的户会重新出现(orderTodoSyncContract.syncForOrder 触发待办重同步)
时间线可追溯性 H10/H11 的时间线记录只报告受影响日/受影响计划行,不报告户级状态回退 新增 extra.reopenedOrderIds,可精确定位是哪些子订单被退回
H7/H8(resetWhenNotReady=false 的两个入口) 不触发户级回退 仍不触发,本次改动对它们零影响

六.7、影响评估(修改/删除类必写)

  • 是否破坏向后兼容:否。三个端点的路径、方法、请求参数、错误码均未变化;H10/H11 响应体字段结构未变;端点 3 只新增一个此前不存在的可选字段(extra.reopenedOrderIds),旧前端忽略未知字段不受影响。
  • 前端是否必须同步上线:不同步上线不会导致接口调用失败,但存在一个真实的展示风险——已完成户可能被静默退回,若前端在人工微调/重算分房的结果弹窗、以及待配房相关列表页对此毫无提示,运营/房务可能不知道「为什么这户又出现在待办里了」。建议前端读取端点 3 的 extra.reopenedOrderIds 并在结果里提示。
  • 前端 workaround 清理点:无(此前没有相关字段可供 workaround)。

七、不影响范围(显式声明, 帮前端/QA 缩小排查面)

  • 仅影响:H10(人工微调)、H11(重算分房)判不出团级就绪分支下的已完成户状态;团期状态流水接口 BATCH_ROOM_ALLOC_MANUAL/BATCH_ROOM_ALLOC_REBUILD 两类记录的 extra。
  • 零影响:
    • H9 分房总览(只读,GET /v3/admin/house/group-batches/{groupBatchId}/allocations)——零副作用,未调用 settleHouseholdsAndHotelReady
    • H7/H8(resetWhenNotReady=false 的两个既有入口)
    • 全自订户的完成态判定
    • 团级 hotelReady/resetHotelReady 本身的既有语义与既有判定逻辑
    • 月度对账、核单结算等其他房务只读端点(另见 changelogs-v2/2026-09/14_7327_月度对账并入团期计划与分房行-修改接口-管理后台.md)
    • 团期车务(fleet)域

八、测试环境已验证

部署与实测状态:修复本身(PR #7689,合并提交 b57413ab8)已合入 dev-v3 并是测试服当前部署基线(hl-order-service-v3 2026-09-15 13:12 部署于 6a7b43a17,经 deploy-status.sh 实测确认)的祖先,代码已在测试服跑着。2026-09-15 对 GET /v3/admin/order/group-batch/{groupBatchId}/status-logs 做了真实网关取证(账号 1001,只读 GET),证实了 extra.reopenedOrderIds 字段真实存在于线上响应,但取证用的团期(groupBatchId=2099692512066105346,另一会话当天为工单 #7327 AC-24/25 造的真实夹具)当时只有单户、且该户处于单户团自动回填场景,本轮 7 条 BATCH_ROOM_ALLOC_MANUAL 记录的 extra.reopenedOrderIds 均为空数组——证实了字段契约(恒为数组、不为 null),但没有证实非空场景(即真的有已完成户被退回处理中的那条主线)。这不是负面证据,单测已覆盖非空场景,backend_status 仍记 deployed(代码已部署、字段契约已实测),但「真的有户被退回处理中」这一核心场景请前端联调时按「单测覆盖、未端到端验证」对待,待专项造数(多户团、其中一户先置 DONE 再改分房让它不平)后补测。以下先列本机自动化单测断言,不是测试服抓包:

GroupBatchRoomDayConfirmManagerTest(origin/dev-v3,新增 4 例):
  settleHouseholdsAndHotelReady_unbalancedDoneHousehold_reopenedWithItsOwnBasis
    → 重判不平的已完成户被退回,reopenedOrderIds=[ORDER_ID],doneOrderIds 不含该户 ✓
  settleHouseholdsAndHotelReady_unbalancedNotDoneHousehold_notListedAsReopened
    → 不平但原非完成态的户,CAS 落空,不进回退名单(verify(reopenHousehold) 正向对照) ✓
  settleHouseholdsAndHotelReady_mixedBalance_reopensOnlyTheUnbalancedHousehold
    → 同一次调用,已平户走完成、不平户走回退,两条路互斥 ✓
  settleHouseholdsAndHotelReady_unbalancedDoneHouseholdButResetNotRequested_neverReopens
    → resetWhenNotReady=false(H7/H8 语义)时,同样的不平事实不触发任何回退 ✓

GroupBatchRoomAllocationManagerTest(origin/dev-v3,新增 2 例):
  rebuild_householdsReopened_writesThemIntoTimeline
    → H11 时间线 extra.reopenedOrderIds=[OTHER_ORDER_ID=70002] ✓
  saveManual_householdsReopened_writesThemIntoTimeline
    → H10 时间线同样落 extra.reopenedOrderIds=[OTHER_ORDER_ID=70002] ✓

全量(PR 描述自报):Tests run: 81, Failures: 0, Errors: 0(GroupBatchRoomAllocationManagerTest=49 + GroupBatchRoomDayConfirmManagerTest=32)

2026-09-15 测试服真实取证(账号 1001,只读 GET,证实字段契约,未证实非空场景):

GET /v3/admin/order/group-batch/2099692512066105346/status-logs → 200
  → 共 16 条流水,其中 7 条 eventType=BATCH_ROOM_ALLOC_MANUAL
  → 逐条 extra 均含 reopenedOrderIds 字段,7 条全部为空数组 []
  → 例:{"stayDates": ["2027-01-25"], "planIds": ["2099692682136743937"], "itemCount": 2,
        "clearedPlanIds": [], "reopenedOrderIds": []}
  → 该团期是当天另一会话为工单 #7327 AC-24/25 造的真实夹具(单户团),字段存在但本次未观察到
    非空样本;非空场景(真的有已完成户被退回)目前仍只有单测覆盖

网关:本次无新增/修改路径,三个端点沿用既有路由。测试服部署已确认(见上),extra.reopenedOrderIds 字段的存在性已实测,但「非空、真的退回了户」这一核心场景仍待专项造数验证,建议按此专项补测。


十、相关文档

  • 关联 Issue: wx/HL#7326
  • 关联 PR: wx/HL#7689
  • 同一 Issue 下与本次相关的既有背景:H10/H11 的完整契约、错误码见工单 #7326 正文
  • 团期状态流水端点的通用契约背景: changelogs-v2/2026-09/14_7327_月度对账并入团期计划与分房行-修改接口-管理后台.md(引用了同一个 GroupBatchStatusLogItemVO)
  • 同一 Issue 下 #7325 的另一改动(confirm-check 预检行粒度变化 + 旧 finalize 拒绝越界户,PR #7700,已合并): changelogs-v2/2026-09/15_7325_越界户预检列出末晚与旧finalize拒绝越界户-修改接口-管理后台.md

关联 / 联系人

链接

联系人

  • 后端负责人: @wx