文件
hl-api-changelog/changelogs-v2/2026-09/15_7327_团单取消终止REFUND待办owner回落团级认领人-修改接口-管理后台.md
T

400 行
29 KiB
Markdown
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
---
schema: "hl-changelog/v2"
ticket: "7327"
title: "团单取消/终止产生的 REFUND 待办:owner 回落到团级认领人,不再恒为广播态"
consumer: "admin"
author: "wx(GIT)"
change_type: "修改接口"
backend_status: "deployed"
gateway_status: "not_required"
frontend_status: "not_required"
frontend_owner: ""
frontend_ref: ""
target_release: ""
verified_at: "2026-09-15"
status_note: "PR #7679(Issue #7327 AC-17)已 squash 合并 dev-v3(合并提交 6169a612a)。2026-09-15 代码已确认部署在测试服(deploy-status.sh 实测),并对 GET /v3/admin/order/todos 做了真实网关取证,证实了字段契约本身(含 2026-09-14 起草时写错的 todoTypeName/groupBatchId 已更正为 todoTypeLabel/teamNo,并补充了此前遗漏的 todoTypes[] 聚合数组)。backend_status 记 deployed:代码已部署且响应字段契约已实测。⚠️ 如实标注未覆盖范围:本单核心修复点(团单户级为空、回落读团级认领人这一分支)本轮未能采到「团单+户级为空+团期已认领+OPEN」四条件同时成立的真实样本,抓到的 REFUND 样本都是户级直接抢单的散客单——该分支目前只有单测覆盖,非测试服端到端验证,详见「八、测试环境已验证」。另需注意 #7459(PR #7731)已把本条讨论的取消触发路径改为约 1 秒的异步 outbox,详见正文「切换状态时的必要动作」更正。PR 正文已记录 2026-09-14 一次测试服实测(取消订单 2099318713927778306 产出 owner_user_id=NULL 的缺陷现象),那是修复前的缺陷复现证据,不是修复后的验证。gateway_status=not_required:受影响的 GET /v3/admin/order/todos 是已有路由,本次未新增/修改任何路径。frontend_status=pending:待前端确认房务待办列表页是否需要对「owner 从广播态变为团级认领人」这类变化做任何界面提示,故不定为 not_required。 前端 not_required(grep 实证):todos/index.vue owner 展示为 ownerName || \"团队\" 通用渲染,字段用 teamNo/todoTypeLabel 与真实契约一致;owner 从 null 变真实 ID 纯取值变化、scope 换桶为服务端行为,前端零改动。"
updated_at: "2026-09-15"
base: "dev-v3"
---
# 房务待办: 团单取消/终止产生的 REFUND 待办 owner 回落到团级认领人
> **存放目录**:
> - 一期(v2,无 `order-v3` 标签的工单)→ `changelogs/{YYYY-MM}/`
> - 二期(v3,`order-v3` 标签的工单)→ `changelogs-v2/{YYYY-MM}/`
>
> **服务**: hl-order-service-v3 (端口 8086)
> **PR**: [#7679](https://git.1814.love:8443/wx/HL/pulls/7679)(Issue #7327 AC-17)
> **Issue**: #7327
> **日期**: 2026-09-15
> **影响范围**: 管理后台房务待办列表 `GET /v3/admin/order/todos` 中,团单取消/终止产生的 `todoType=REFUND` 记录的 `ownerUserId`/`ownerName` 取值,以及该记录归属的 `scope`(我的/同事在跟)
---
## ⚠️ 关键变化(非必须,本版与上版行为不同 / 纠错 / 撤销时必写)
- **本次变了什么**:`HouseAssignmentService#findClaimerIdByOrder`(`HouseAssignmentService.java:767-777`,2026-09-15 对照 origin/dev-v3 核实,因中间插入 #7459 outbox 改造行号较 2026-09-14 起草时的 752-762 漂移约 +15 行)在户级 `order_hotel_requirement.claimer_id` 为空时,新增回落逻辑——查该订单所属团期,读团级认领人(`GroupBatchService#listHouseClaimersIncludeDeleted`)作为兜底;`HouseTodoService.handleOrderCancelled`/`handleOrderTerminated`(`HouseTodoService.java:1785`、`:1886`,同日一并核实更新)取 owner 时调的就是这个方法,两处均无需改动即自动受益。⚠️ `handleOrderCancelled` 的**触发方式**已在 #7459(PR #7731,见另一份 changelog)改为异步 outbox 命令,详见本节末尾追加说明。
- **前端/调用方以前以为的是什么**:团单(`order_main` 挂了团期)取消或终止行程时产生的 `REFUND` 待办,`ownerUserId` 会像散客单一样,取自该订单用房需求的户级抢单人(`order_hotel_requirement.claimer_id`);抢单人存在就应该有值。
- **实际现在是什么,以及此前的真实缺陷**:**团单的户级 `claimer_id` 结构性恒为 NULL**——团单房务归属走的是团级认领(认领入口是抢单池,而抢单池的基础过滤 `HotelRequirementMapper.java:718` `w.isNull(OrderInfo::getProductBatchId)` 把团单排除在外,团单根本进不了户级抢单流程)。**修复前**:这导致团单取消/终止产生的 `REFUND` 待办 `ownerUserId` 恒为 `NULL`,全部落入广播桶(PR 正文记录了 2026-09-14 的一次测试服实测:取消订单 `2099318713927778306` 产出 `house_todo id=2099341634310201345, todoType=REFUND, owner_user_id=NULL`,同团 `house_claimer_id=1001` 却没有被用上)。**修复后**:户级取不到时会回落读团级认领人,团单产生的 `REFUND` 待办会有真实 owner;两级都无归属时仍保留广播态(`ownerUserId=null`),不是失败,是设计内语义。
---
## 一、背景(选填)
Issue #7327 AC-17:口径 8 把 `HouseAssignmentService#hasActiveAssignment` 扩成两段判定后,团单取消/终止也会走进 `HouseTodoService` 的「已配房」分支产 `REFUND` 待办(`HouseTodoService.java:1750`、原 handleOrderCancelled 分支 2a)。但取 owner 的那条链路当时没有跟着扩——两处都只调 `findClaimerIdByOrder`,而它此前只读户级 `claimer_id`。
| 维度 | 证据 |
|------|------|
| 户级 `claimer_id` 写口 | 全仓只有两处:`HotelRequirementMapper.casClaimByRequirementId`(抢单,WHERE `claimer_id IS NULL`)、`casTransferByRequirementId`(转单,WHERE `claimer_id` 已非空) |
| 抢单入口的过滤 | `HotelRequirementMapper.java:718`:`w.isNull(OrderInfo::getProductBatchId)`,团单(`product_batch_id` 非空)结构性进不去 |
| 结论 | 团单户级 `claimer_id` 永远是 NULL,不是偶发脏数据 |
(以上引用自 PR #7679 正文「🔴 户级恒 NULL 是结构性的,不是『数据没跑到』」一节)
修复只动了 owner 取值这一条链路,不改「是否产生 REFUND 待办」的既有判定(`hasActiveAssignment`/口径 8 是先于本次改动就存在的逻辑,不在本次变更范围内)。
---
## 二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|------|------|------|----------|------|
| 1 | 房务待办列表 | GET | `/v3/admin/order/todos` | 响应内容修正 | 团单取消/终止产生的 `todoType=REFUND` 记录,`ownerUserId`/`ownerName` 从恒为 `null` 改为回落团级认领人 |
---
## 三、接口详情
响应字段结构、请求参数、错误码均未变化,本次只修正了特定条件下返回内容的**取值**(一个此前恒为 `null` 的字段,在团单场景下会有真实值)。
### 1. 房务待办列表 `GET /v3/admin/order/todos`
**VO**: `HouseTodoPageReqVO → Result<HouseTodoListRespVO>`
#### 使用场景
房务在「待办」页面查看自己的/同事的/全部待办时调用,13 个查询参数支持按归属范围、类型、状态、紧急度等筛选。本次起,团单(挂了团期的子订单)取消或终止后产生的 `todoType=REFUND` 待办,若该团有房务认领人,会带着真实 `ownerUserId` 出现——这决定了它出现在**谁的** `scope=mine` 列表里,以及是否出现在别人的 `scope=others` 列表里。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| scope | Query | String | ❌ | mine(默认)/others/all | mine=owner=当前用户 或 owner=NULL;others=owner≠当前用户 且 非 NULL;all=不过滤(仅房务组长/超管可用)。本次未改语义,但受影响记录会因 owner 从 NULL 变为团级认领人 ID 而换到不同 scope 桶 |
| todoType | Query | String | ❌ | 逗号分隔多选,如 `REFUND,SWAP_HOTEL` | 待办类型过滤,空=全部 |
| status | Query | String | ❌ | OPEN/RESOLVED | 状态过滤,空=全部 |
| urgency | Query | String | ❌ | danger/warn/normal | 紧急度过滤(运行时推导值) |
| keyword | Query | String | ❌ | ≤32 字 | 模糊搜 title/reason/团号 |
| orderId | Query | Long | ❌ | - | 订单 ID 过滤 |
| ownerUserId | Query | Long | ❌ | 配合 scope=others/all | 归属房务 ID 过滤 |
| hotelId | Query | Long | ❌ | - | 酒店 ID 过滤 |
| createTimeFrom / createTimeTo | Query | String(ISO 8601) | ❌ | - | 创建时间范围 |
| overdueMinutes | Query | Integer | ❌ | - | 仅看超时 N 分钟以上 |
| sortBy | Query | String | ❌ | 默认 `urgency,desc,createTime,desc` | 排序 |
| pageNo / pageSize | Query | Integer | ❌ | 继承自 `PageParam`,本次未改 | 分页 |
(以上入参字段结构本次未改,见 `HouseTodoPageReqVO.java`)
#### 出参 `Result<HouseTodoListRespVO>`
| 字段 | 类型 | 说明 |
|------|------|------|
| data.list[] | Array\<Object\> | ⚠️ 2026-09-15 核实更正:这是**订单聚合行**(一个订单一行,同订单多类型待办聚合进 `todoTypes[]`),不是「待办列表(已按默认排序)」这种一条待办一行的旧描述——2026-09-14 起草时未对照源码,按字面猜测成了扁平列表,实际结构见 `HouseTodoItemVO.java`。本条 changelog 讨论的 `REFUND` 类型走顶层「主标签」兼容字段,读顶层字段即可拿到 owner,不强制读 `todoTypes[]` |
| data.list[].id | Long | 该行「主标签」(本单最高紧急度类型)对应的待办 ID |
| data.list[].todoType | String | 该行「主标签」类型 code,本次涉及 `REFUND` |
| data.list[].todoTypeLabel | String | 待办类型中文标签。⚠️ 字段名更正:字段是 `todoTypeLabel`,不是 2026-09-14 起草时写的 `todoTypeName`(源码 `HouseTodoItemVO.java` 无 `todoTypeName` 字段) |
| data.list[].title | String | 标题,如「客人取消订单 · 请处理酒店退订」/「客人终止行程 · 请处理酒店退订」 |
| data.list[].orderId | Long | 订单 ID |
| data.list[].orderNo | String | 订单号 |
| data.list[].teamNo | String/null | 团号(未生成时为 null)。⚠️ 字段名更正:字段是 `teamNo`,不是 2026-09-14 起草时写的 `groupBatchId`(该 VO 里不存在 `groupBatchId` 字段,写成这个名字是未对照源码的臆造)。本次涉及的记录该字段应非空(团单才会走到本次修复的回落逻辑) |
| data.list[].todoTypes[] | Array\<Object\> | 本单待办类型标签数组(同订单多类型聚合,一类型一标签),2026-09-14 起草时遗漏未记录。子字段含 `typeCode`/`typeLabel`/`urgency`/`count`/`derived`/`todoId`/`requirementId`/`unreadCount`,见 `HouseTodoItemVO.TodoTypeTag` |
| data.list[].**ownerUserId** | Long/null | **本次修正取值来源**。归属房务 ID,`null`=广播态。团单场景下:户级抢单人为空时,本次起改为读团级认领人;两级都空仍为 `null`(设计内广播态) |
| data.list[].**ownerName** | String/null | **随 ownerUserId 联动**。归属房务姓名;`ownerUserId` 非空时应有对应姓名 |
| data.total | Long | 总条数(过滤后,分页前) |
| data.stats | Object | 按 `todoType` 分类的统计(9 类),本次未改结构 |
(`fromValue`/`toValue`/`reason`/`urgency`/`status`/`productType`/`orderTodoCount`/`guestName`/`personsDesc`/`hotelId`/`assignmentId`/`createTime`/`elapsedMinutes`/`requirementSummary`/`requirementVersion`/`hasUnresolvedReturn`/`departDate`/`derived`/`unreadCount`/`requirementId` 等其余字段本次未改,且非本条 changelog 讨论重点,完整定义见 `HouseTodoItemVO.java`)
(`reason`/`urgency`/`status`/`hotelId`/`assignmentId`/`travelerCount`/`departDate` 等其余字段本次未改,未逐一列出,见 `HouseTodoItemVO.java`)
#### 请求示例
```http
GET /v3/admin/order/todos?scope=mine&todoType=REFUND&status=OPEN
Authorization: Bearer <token>
```
#### 响应示例
以下字段名称/结构 2026-09-15 已对照源码 `HouseTodoItemVO.java` 更正(2026-09-14 起草版把字段名写成了 `todoTypeName`/`groupBatchId`,源码实际是 `todoTypeLabel`/`teamNo`,且遗漏了 `todoTypes[]` 聚合数组,见「出参」节说明);`orderId=30456`/`teamNo=90001` 取自 `HouseAssignmentServiceTest#findClaimerIdByOrder_householdNullAndGroupClaimed_returnsGroupClaimerId` 用例夹具(团级认领人 `houseClaimerId=2002`),其余展示性字段(`id`/`title`/`orderNo`/时间等)为示意值,不是测试服抓包报文:
```json
{
"code": 200,
"message": "成功",
"data": {
"list": [
{
"id": "70500",
"todoType": "REFUND",
"todoTypeLabel": "退订",
"title": "客人取消订单 · 请处理酒店退订",
"reason": "客人临时取消行程",
"fromValue": null,
"toValue": null,
"urgency": "danger",
"status": "OPEN",
"orderId": "30456",
"orderNo": "26-0518",
"teamNo": "90001",
"productType": "GROUP",
"orderTodoCount": 1,
"todoTypes": [
{ "typeCode": "REFUND", "typeLabel": "退订", "urgency": "danger", "count": 1, "derived": false, "todoId": "70500", "requirementId": null, "unreadCount": null }
],
"guestName": "赵先生",
"hotelId": 11,
"ownerUserId": "2002",
"ownerName": "小呼",
"createTime": "2026-09-14T10:13:00",
"elapsedMinutes": 30
}
],
"total": 1,
"stats": { }
},
"success": true
}
```
2026-09-15 测试服真实抓包对照(账号 1001,只读 GET,见「八、测试环境已验证」)——字段名与上方更正后的一致,证明 `todoTypeLabel`/`teamNo`/`todoTypes[]` 是真实线上契约而不是本次更正时的另一次猜测:
```json
{
"id": "2099707745790779394",
"todoType": "REFUND",
"todoTypeLabel": "退订",
"title": "客人取消订单 · 请处理酒店退订",
"status": "RESOLVED",
"orderId": "2099707703508054018",
"orderNo": "HL20260915115141720",
"teamNo": "26-6171",
"productType": "CORE",
"ownerUserId": "1001",
"ownerName": "admin",
"createTime": "2026-09-15 11:51:52"
}
```
⚠️ 上面这条真实抓包是**散客单**(`productType=CORE`,户级直接抢单,不经过本条 changelog 的团级回落分支),只用来坐实字段名与 `todoTypes[]` 结构;它不是团单回落场景的证据,团单回落场景(户级为空、读团级认领人)目前仍只有单测覆盖,见「八、测试环境已验证」的说明。
两级都无归属(团期未认领)时的广播态形态(取自 `HouseAssignmentServiceTest#findClaimerIdByOrder_householdNullAndGroupUnclaimed_returnsNullForBroadcast` 用例:户级为空、`listHouseClaimersIncludeDeleted` 返回空列表):
```json
{ "ownerUserId": null, "ownerName": null }
```
#### 空数据 / 降级响应
筛选条件下无匹配待办:
```json
{
"code": 200,
"message": "成功",
"data": { "list": [], "total": 0, "stats": { } },
"success": true
}
```
#### 错误响应
`keyword` 超长(既有校验,本次未改):
```json
{
"code": 100001,
"message": "keyword 最长 32 字",
"data": null,
"success": false
}
```
#### 业务边界
- **鉴权**:走网关统一鉴权,未登录 401;`scope=all` 仅房务组长/超管可用(既有逻辑,本次未改)。
- **本次只改变 `REFUND` 类型记录、且仅限满足两个条件同时成立时**:①该订单是团单(`orderService.resolveGroupBatchLinks` 能解析出 `groupBatchId`);②户级 `order_hotel_requirement.claimer_id` 为空。散客单(无团期归属)的 `REFUND` 待办行为**逐字节不变**——`findClaimerIdByOrder_householdClaimerPresent_returnsHouseholdIdWithoutGroupLookup` 用例断言了户级有值时不会额外查团期(`HouseAssignmentServiceTest.java` 新增用例)。
- **归团判定走 `OrderService#resolveGroupBatchLinks` 门面,不直读 `order_main.group_batch_id`**:工单 #7083 遗留的、`group_batch_id` 未回填的老团单,只有经这个门面才能被正确识别为团单;直读该列会把它们误判为散客单,本次修复对它们**不生效**(`HouseAssignmentService.java:788-` 起 `findGroupBatchClaimerIdByOrder` 私有方法 javadoc,2026-09-15 核实更新)。
- **团级认领人只经 `GroupBatchService#listHouseClaimersIncludeDeleted` 读**:与既有 `HouseGroupBatchClaimGuard`、`HouseGroupBatchBoardManager` 等同一单源,不引入第二个认领人数据来源。
- **两级都空仍返回广播态 `null`,不是缺陷**:这是既有设计语义(未认领时任一房务可接),本次未改这条语义,只是改了「取不到户级值时该不该再查一层」。
---
## 四、契约约束与正确调用方式(接口类必写)
> 本节只写**后端接受/拒绝查询参数的规则与调用后必须知道的取值规则**,不写 UI 渲染建议。
### ✅ 正确 / ❌ 错误 理解对照
| 场景 | 说明 |
|------|------|
| ✅ 团单 `REFUND` 待办 `ownerUserId` 非空时,展示为「该团认领房务在跟」 | 这个 owner 来自团级认领人,不是户级抢单人;前端不应把它误标为「已抢单」(散客单语义),应标「团期认领人」或直接沿用既有的房务姓名展示(字段名不变,语义按订单是否团单区分即可,无需新增字段) |
| ✅ 团单 `REFUND` 待办 `ownerUserId` 为 `null` 时,视为广播态(任一房务可处理) | 与散客单未抢单时的语义完全一致,不是新语义 |
| ❌ 认为团单 `REFUND` 待办的 `ownerUserId` 一定等于团级认领人 | 只有「户级 `claimer_id` 为空」这一条件成立时才回落团级;理论上若某天户级抢单口径变化导致团单也能写户级 `claimer_id`(当前无此写口),则仍以户级值优先 |
| ❌ 用 `groupBatchId` 字段是否非空来判断「这条待办的 owner 一定不是广播态」 | 团期未认领(`listHouseClaimersIncludeDeleted` 返回空)时,团单待办的 `ownerUserId` 依然是 `null` |
### 切换状态时的必要动作
无状态切换(本端点为只读查询)。⚠️ 2026-09-15 更正:2026-09-14 起草时写的「立即可查到」已不成立——同日晚些时候合并的 #7459(PR #7731,另一份 changelog)把**订单取消**(`handleOrderCancelled`)触发的房务处置改成了异步 outbox 命令,实测约 1 秒内完成;**订单终止**(`handleOrderTerminated`)未受 #7459 影响,仍走原 `HouseTodoEventListener` 同步事件路径。也就是说:团单**终止**触发的带真实 owner 的 `REFUND` 待办目前仍是同步可查;团单**取消**触发的这类待办现在需要等约 1 秒(详见 changelogs-v2/2026-09/15_7459_订单取消房务处置改走outbox耐久命令流团逐户REFUND-修改接口-管理后台.md),前端如果在取消成功后立即查询本端点校验 owner 是否正确,需要预留这个延迟窗口。
---
## 五、数据库行为
本次改动位于**读路径**(`findClaimerIdByOrder` 只读团级认领人,不写任何列),但它的调用方 `createRefundTodo`(现分两个重载:3 参业务入口 `HouseTodoService.java:2043` 起,4 参实现 `:2066` 起,2026-09-15 核实更新)会把取到的值写进 `house_todo.owner_user_id` 列。**本次不新增任何写口/写路径**,只是让既有写口(团单取消/终止触发的 REFUND 待办创建)在写这一列时,从「恒写 NULL」变成「先查户级、再查团级、最后才写 NULL」。
| 场景 | `house_todo.owner_user_id`(新建 REFUND 待办时) |
|------|----------------------------------------------------|
| 散客单,户级已抢单 | 户级 `claimer_id`(本次未改) |
| 散客单,未抢单 | `NULL`(本次未改) |
| 团单,团期已认领 | **本次起**:团级 `house_claimer_id` |
| 团单,团期未认领 | `NULL`(不变,广播态) |
该待办创建走 `@Transactional(rollbackFor = Exception.class)`(3 参入口的注解在 `HouseTodoService.java:2042`,2026-09-15 核实更新),`dedupKey="REFUND-{orderId}"` 幂等复用,本次未改。
---
## 六、边界行为
- 未登录 → 401(网关拦截)
- `keyword` 超长 → 100001(既有,未改)
- 无匹配待办 → 200 + 空列表 + `total=0`
- 订单查不到(理论场景,触发方是已存在的订单事件)→ `findClaimerIdByOrder` 返回 `null`,不抛错(`HouseAssignmentServiceTest#findClaimerIdByOrder_orderMissing_returnsNullWithoutGroupResolve` 覆盖)
- 非团单 → 不查团级认领人,直接返回户级值或 `null`(`findClaimerIdByOrder_nonGroupOrder_returnsNullWithoutClaimerLookup` 覆盖)
- 团期未认领 → owner 为 `null`,广播态,不是错误
---
## 六.5、枚举 / 数据字典(接口出现枚举时必写)
本次改动**不引入任何新枚举值**。受影响记录的 `todoType` 恒为既有值 `REFUND`,未新增/修改该枚举的取值域,仅作为定位记录的上下文列出:
### todoType(`com.hulalv.house.enums.HouseTodoType`,节选)
**所属字段**: `HouseTodoItemVO.todoType`(`GET /v3/admin/order/todos`,既有字段) | **类型**: `String`
| 值 | 中文 | 说明 |
|----|------|------|
| `REFUND` | 退订 | 本次唯一涉及的类型;订单取消/终止且已配房时产生,本次只改其 `ownerUserId` 取值来源,不改该枚举值本身 |
---
## 六.6、修改前后对比(修改/删除类接口必写,新增跳过)
### 字段级对比
| 字段 | 改前 | 改后 |
|------|------|------|
| `list[].ownerUserId`(团单 `REFUND` 待办,团期已认领) | 恒为 `null` | 团级认领人 ID(如 `2002`) |
| `list[].ownerName`(同上条件) | 恒为 `null` | 团级认领人姓名快照 |
| `list[].ownerUserId`(散客单 `REFUND` 待办) | 户级抢单人或 `null` | **不变** |
| `list[].ownerUserId`(团单 `REFUND` 待办,团期未认领) | `null` | **不变**(仍为 `null`,广播态) |
### 行为级对比
| 行为 | 改前 | 改后 |
|------|------|------|
| 团单取消/终止产 `REFUND` 待办的 owner 归属 | 恒落广播桶,全体房务在自己的 `scope=mine` 都能看到 | 团期已认领时,归属到该团认领人的 `scope=mine`;其他房务改为在 `scope=others`/`all` 里看到(归属他人) |
| `findClaimerIdByOrder` 内部查询次数(团单,户级为空场景) | 只查一次(户级) | 新增两次库查(`orderService.getById` + `resolveGroupBatchLinks`),仅发生在取消/终止产 REFUND 时,非热路径 |
| 散客单 `REFUND` 待办 owner 归属 | 不变 | **不变** |
---
## 六.7、影响评估(修改/删除类必写)
- **是否破坏向后兼容**:否。请求参数、响应字段结构、错误码均未变化;改动完全体现在**特定条件下**(团单 + 户级为空 + 团期已认领)的 `ownerUserId`/`ownerName` 取值上。
- **前端是否必须同步上线**:否,不同步上线不会导致接口调用失败或报错。但会有真实的行为差异:此前所有房务都能在自己的「我的待办」里看到团单 `REFUND` 待办(广播态),本次上线后,团期已认领的这类待办会**收窄**到认领人一人的「我的待办」,其他房务需要切到「同事在跟」或「全部」才能看到。若前端/运营对「REFUND 待办为什么从我的列表消失了」没有心理准备,可能产生疑问,建议知会一线房务。
- **前端 workaround 清理点**:无(此前没有相关字段可供 workaround)。
---
## 七、不影响范围(显式声明, 帮前端/QA 缩小排查面)
- **仅影响**:`GET /v3/admin/order/todos` 中,团单取消/终止产生的 `todoType=REFUND` 记录的 `ownerUserId`/`ownerName` 取值及其 `scope` 归属。
- **零影响**:
- 散客单的 `REFUND` 待办 owner 取值(逐字节不变)
- `REFUND` 待办是否产生的判定(`hasActiveAssignment`/口径 8,先于本次改动即已存在)
- `REFUND` 待办的 RESOLVE 闭环(由主 API V5.49 退款成功触发,本次未改)
- 其余 8 类待办类型(`SWAP_HOTEL`/`HOTEL_REPLY_TIMEOUT`/`PENDING_ARRANGE`/`PENDING_FINALIZE`/`UNREAD_CHAT` 等)
- 户级抢单/转单流程(`casClaimByRequirementId`/`casTransferByRequirementId`)
- 团级认领/释放流程本身(`GroupBatchService#listHouseClaimersIncludeDeleted` 只读,未新增写口)
- `#7326`(分房重算/人工微调)相关端点,见 `changelogs-v2/2026-09/15_7326_团期分房重算人工微调把重判不平已完成户退回处理中-修改接口-管理后台.md`
---
## 八、测试环境已验证
**部署与实测状态**:修复本身(PR #7679,合并提交 `6169a612a`)已合入 dev-v3 并是测试服当前部署基线(`hl-order-service-v3` 2026-09-15 13:12 部署于 `6a7b43a17`,经 `deploy-status.sh` 实测确认)的祖先,代码已在测试服跑着。2026-09-15 对 `GET /v3/admin/order/todos` 做了真实网关取证(账号 1001,只读 GET),**证实了字段契约本身**(见下方「2026-09-15 测试服真实取证」)。⚠️ **如实标注未覆盖范围**:本单核心修复点(团单户级为空、回落读团级认领人这一分支)本轮**没有证实**——当天在测试服上没能定位到一个满足「团单 + 户级 `claimer_id` 为空 + 团期已认领 + REFUND 待办处于 OPEN 状态」四个条件同时成立的真实样本,抓到的 REFUND 样本都是户级直接抢单的散客单。这不是负面证据(不代表修复无效,单测已覆盖该分支),backend_status 仍记 `deployed`(代码已部署、字段契约已实测),但团单回落分支这一核心场景请前端联调时按「单测覆盖、未端到端验证」对待。以下分三部分:
**修复前的缺陷复现(PR 正文自报,2026-09-14 测试服实测,验证的是问题存在,不是修复生效)**:
```
取消订单 2099318713927778306
→ house_todo id=2099341634310201345, todo_type=REFUND, owner_user_id=NULL
同团 order_group_batch 2099318712929566721: house_claimer_id=1001, house_claimed_at=10:13:13
该户 order_hotel_requirement.claimer_id 取消前快照即为 NULL(非本次取消动作清掉)
```
**修复后的本机自动化单测(origin/dev-v3,`HouseAssignmentServiceTest.java` 新增 5 例)**:
```
findClaimerIdByOrder_householdClaimerPresent_returnsHouseholdIdWithoutGroupLookup
→ 户级有值时直接返回,不查团期(verify never resolveGroupBatchLinks/listHouseClaimersIncludeDeleted) ✓
findClaimerIdByOrder_householdNullAndGroupClaimed_returnsGroupClaimerId
→ 户级为空、团期已认领(houseClaimerId=2002) → 返回 2002(本单修复点:修复前此处恒为 null) ✓
findClaimerIdByOrder_householdNullAndGroupUnclaimed_returnsNullForBroadcast
→ 户级为空、团期未认领 → 返回 null,广播态(verify 确实查过团级,不是没问就落空) ✓
findClaimerIdByOrder_nonGroupOrder_returnsNullWithoutClaimerLookup
→ 非团单(resolveGroupBatchLinks 返回空 Map)→ 返回 null,不查团级认领人 ✓
findClaimerIdByOrder_orderMissing_returnsNullWithoutGroupResolve
→ 订单查不到 → 返回 null,不判团、不抛错 ✓
```
PR 自报定向轮:`Tests run: 179, Failures: 0, Errors: 0, Skipped: 6`(`Skipped` 为既有 `@Disabled`,非本次引入)。
**2026-09-15 测试服真实取证(账号 1001,只读 GET,覆盖字段契约,未覆盖团单回落分支)**:
```
GET /v3/admin/order/todos?scope=mine&orderId=2099707703508054018&status=RESOLVED&pageNo=1&pageSize=10 → 200
→ 命中 1 条 REFUND,字段名与「出参」节更正后的一致:
todoTypeLabel="退订"(不是 todoTypeName)、teamNo="26-6171"(不是 groupBatchId)、
存在 todoTypes[] 聚合数组、ownerUserId="1001" 正确回显
→ 该订单 productType=CORE,是散客单直接抢单场景,不是本单要修的团单回落场景
GET /v3/admin/order/todos?scope=mine&todoType=REFUND&status=OPEN → 200,list=[],stats.REFUND=9
→ 说明当前测试库里存在 9 条 OPEN 状态的 REFUND(按 owner 过滤后),但受「订单聚合改造」
的聚合/排序规则影响,未能在这次会话里筛出具体是哪些订单、是否含团单回落样本,
本节不据此下任何结论,如实记录为未查明
```
网关:本次未新增/修改路径,沿用既有路由。测试服部署已确认(见上),字段契约已实测,但团单回落分支这一核心修复点仍待专项造数验证(造一个团单:团期已认领 + 该户从未走户级抢单 + 触发取消或终止),验证方式建议按此专项补测。
---
## 十、相关文档
- 关联 Issue: [wx/HL#7327](https://git.1814.love:8443/wx/HL/issues/7327)
- 关联 PR: [wx/HL#7679](https://git.1814.love:8443/wx/HL/pulls/7679)
- 同一 Issue 相关的既有背景(住宿核单/月度对账): `changelogs-v2/2026-09/13_7327_团期配房行合流核单结算-修改接口-管理后台.md`、`changelogs-v2/2026-09/14_7327_月度对账并入团期计划与分房行-修改接口-管理后台.md`
- 关联改动(订单取消房务处置改走 outbox,改变本条讨论的取消路径时序,工单 #7459,已部署已实测): `changelogs-v2/2026-09/15_7459_订单取消房务处置改走outbox耐久命令流团逐户REFUND-修改接口-管理后台.md`
## 关联 / 联系人
### 链接
- **Issue**: [#7327](https://git.1814.love:8443/wx/HL/issues/7327)
- **PR**: [#7679](https://git.1814.love:8443/wx/HL/pulls/7679)
- **Merge commit**: [6169a612a](https://git.1814.love:8443/wx/HL/commit/6169a612ad3ade8fa2a2a0fcfb404f25e27833e0)
### 联系人
- **后端负责人**: @wx