docs(changelog): 补 #8597 退团房期限清空与退团房待办交接件,并订正 #8491 里「三列一并置空」的错述
changelog-filename-gate / validate (push) Failing after 2s

新增 30_8597 覆盖两个端点:
- PUT /v3/admin/order/house-console/room-transfers/{id}/deadline
- GET /v3/admin/order/house-console/audit

同时就地订正已交付的 30_8491(4 行 / 3 处):原文写「cancelDays 传 null 时
cancelCutoff / remindDays 一并清空」,与源码相反。HouseRoomTransferManager#doUpdateDeadline
对 cancelDays == null 的分支是「cutoff / remind 沿用行上原值」——两列 NOT NULL,
传 null 会被 Mapper 守卫当失败返 0、进而被误报成并发修改(正是 #8597 的根因)。
30_8491 自己第 1847 行记录的实测读数也是「cancelCutoff 保留」,即该文件内部自相矛盾。

订正不是措辞问题:前端若按原文写 `cancelCutoff === null` 去判「有没有设期限」,
那个判断恒为 false。订正后同时写明正确判据是看 cancelDays 或 risk === "NO_DEADLINE"。

两文件 validate-changelog-frontmatter.mjs --files 全绿(校验 2 个对象,EXIT=0)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
这个提交包含在:
API Changelog Bot
2026-09-30 07:46:23 +08:00
共同撰写人 Claude Opus 5
父节点 a60a791132
当前提交 c2f04d7b32
共修改 2 个文件,包含 455 行新增和 4 行删除
@@ -743,8 +743,8 @@ GET /v3/admin/order/house-console/room-transfers?status=PENDING&cityCode=海拉
|------|------|------|------|------|------|
| id | Path | Long | ✅ | - | 转房行 ID |
| cancelDays | Body | Integer | ❌ | 0~60;null 表示清除期限 | 入住前几天免费取消 |
| cancelCutoff | Body | String | ❌ | HH:mm;cancelDays 非空而本字段空时取 18:00;cancelDays 为 null 时本字段被忽略、一并清空 | 截止当天的时刻 |
| remindDays | Body | Integer | ❌ | 0~30;cancelDays 非空而本字段空时取 1;cancelDays 为 null 时本字段被忽略、一并清空 | 提前几天提醒 |
| cancelCutoff | Body | String | ❌ | HH:mm;cancelDays 非空而本字段空时取 18:00;cancelDays 为 null 时本字段被忽略,**该行原值保留、不置空**(#8597 订正) | 截止当天的时刻 |
| remindDays | Body | Integer | ❌ | 0~30;cancelDays 非空而本字段空时取 1;cancelDays 为 null 时本字段被忽略,**该行原值保留、不置空**(#8597 订正) | 提前几天提醒 |
#### 出参 `Result<HouseRoomTransferRespVO>`
@@ -784,7 +784,7 @@ GET /v3/admin/order/house-console/room-transfers?status=PENDING&cityCode=海拉
#### 空数据 / 降级响应
`cancelDays` 传 null 表示清除期限:`cancelDays` / `cancelCutoff` / `remindDays` 三列一并置空,返回行的 `deadlineAt=null`、`risk=NO_DEADLINE`、`riskLabel="未设免费取消期"`。
`cancelDays` 传 null 表示清除期限:**只有 `cancelDays` 变成 null**,`cancelCutoff` / `remindDays` 保留该行原值(从未设过期限时是默认的 `"18:00"` / `1`);返回行的 `deadlineAt=null`、`risk=NO_DEADLINE`、`riskLabel="未设免费取消期"`。判「有没有设期限」只看 `cancelDays === null` 或 `risk === "NO_DEADLINE"`,不要看 `cancelCutoff` / `remindDays` 是否为 null(#8597 订正)。
#### 错误响应
@@ -1620,7 +1620,7 @@ DELETE /v3/admin/order/house-console/stay-templates/1839000000000000001
| 模板保存 / 删除 | `house_stay_template` | 新建 / 整份覆盖;删除为软删 |
| 团期转交 | 团期认领字段、团期时间线 | CAS 换持有人,写时间线;计划与分房不动 |
**显式 SET NULL 说明**: 设置期限时 `cancelDays` 传 null 会把 cancel_days / cancel_cutoff / remind_days 三列一并清空,`deadlineAt` 随之为 null;其余写接口未传的可选字段保持原值,不会被写成 null。
**显式 SET NULL 说明**: 设置期限时 `cancelDays` 传 null **只把免费退改天数清空**,截止时刻与提醒天数保留该行原值(两列非空),`deadlineAt` 随之为 null(#8597 订正);其余写接口未传的可选字段保持原值,不会被写成 null。
---
@@ -0,0 +1,451 @@
---
schema: "hl-changelog/v2"
ticket: "8597"
title: "退团房清空免费退改期限:截止时刻与提醒天数保留原值(订正 #8491 交接件三处表述),异常检查补回源单已取消的退团房待办"
consumer: "admin"
author: "wx(GIT)"
change_type: "修改接口"
backend_status: "deployed"
gateway_status: "not_required"
frontend_status: "pending"
frontend_owner: ""
frontend_ref: ""
target_release: ""
verified_at: ""
status_note: "PR #8599(提交 ff68637542)已合入 dev-v3;测试服 hl-order-service-v3 运行提交 d57498d38、BEHIND 0/N、STATE ok、2026-09-30 05:32:57,ff68637542 是 d57498d381 的祖先。两个端点的路径与方法均未变,网关 /v3/admin/** 路由块已通配,零网关改动。"
updated_at: "2026-09-30"
base: "dev-v3"
---
# 房务控制台: 退团房期限清空口径订正 + 异常检查退团房待办补全
> **存放目录**: 二期 → `changelogs-v2/2026-09/`
>
> **服务**: hl-order-service-v3
> **Issue**: #8597
> **PR**: #8599
> **日期**: 2026-09-30
> **影响范围**: 管理后台「房务控制台」的「退团房」页签(设期限弹窗)与「异常检查」页签(待办列表)
---
## ⚠️ 关键变化(非必须,本版与上版行为不同 / 纠错 / 撤销时必写)
- 🔴 **订正 #8491 交接件的三处表述**:`PUT .../room-transfers/{id}/deadline` 传 `cancelDays: null` 清除期限时,**只有 `cancelDays` 变成 `null`,`cancelCutoff` 与 `remindDays` 保留该行原值,不会被置空**。`changelogs-v2/2026-09/30_8491_房务控制台接口-新增接口-管理后台.md` 的第 746、747 行(入参表)、第 787 行(空数据 / 降级响应)、第 1623 行(显式 SET NULL 说明)写的「三列一并清空 / 本字段被忽略、一并清空」与实际行为不符,以本条为准;该文件第 1847 行的测试环境读数(`cancel_cutoff / remind_days 保留原值`)才是正确的那一条。
- 🔴 **判「这一行有没有设免费退改期限」只能看 `cancelDays === null` 或 `risk === "NO_DEADLINE"`**,不能看 `cancelCutoff` / `remindDays` 是否为 `null`——清空后这两个字段仍有值(该行从未设过期限时是建表默认的 `"18:00"` 与 `1`)。
- **清除期限从「必定失败」改为成功**:本次改动前,`cancelDays: null` 的请求 100% 返回 **808932**「房务状态已被并发修改,请刷新后重试」(并非真的并发冲突),现在返回 `code=200` 并给出更新后的整行。
- **异常检查 `tasks[]` 的 `TRANSFER_PENDING` 条目不再漏行**:待处理退团房行的窗口归属改为只看源团期 / 源订单的**出发日**,**不看源单状态**;源单查不到、或源单出发日为空时同样列出。取消订单恰恰是产生退团房最常见的原因,订正前这些待办在控制台唯一的待办载体上看不到。**同一窗口下本端点返回的 `tasks` 行数会比订正前多。**
---
## 一、背景(选填)
退团房行有两处独立缺陷,都发生在「房务控制台」已交付的端点上:
1. 清期限走的乐观锁写口,其入参守卫要求截止时刻与提醒天数非空(两列在库中是 NOT NULL);旧代码在 `cancelDays` 为 `null` 时把这两项也一起传 `null`,守卫直接返回「影响行数 0」,上层把 0 当成版本冲突抛 808932。表现是「清除期限」按钮永远失败,而错误文案指向刷新重试,看不出是入参问题。
2. 异常检查的退团房待办原先用「窗口内的团期集合 / 散单集合」判归属,这两个集合按占用口径排除了已取消的单,于是「订单取消 → 释放房间 → 待转出」这条最常见的链路产出的待办从不出现。
---
## 二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|------|------|------|----------|------|
| 1 | 设置 / 清除退团房免费退改期限 | PUT | `/v3/admin/order/house-console/room-transfers/{id}/deadline` | 修改 | 清除期限由必定失败改为成功;`cancelCutoff` / `remindDays` 保留原值(订正交接件表述) |
| 2 | 房务异常检查 | GET | `/v3/admin/order/house-console/audit` | 修改 | `tasks[]` 的 `TRANSFER_PENDING` 按源单出发日归属、不看源单状态,补回源单已取消的行 |
---
## 三、接口详情
### 1. 设置 / 清除退团房免费退改期限 `PUT /v3/admin/order/house-console/room-transfers/{id}/deadline`
**VO**: `HouseRoomTransferDeadlineSaveReqVO → HouseRoomTransferRespVO`
#### 使用场景
「退团房」页签某一行点「设期限」,录入酒店给的免费取消规则(入住前几天、当天几点前)与提前几天提醒;也用于把已设的期限清掉。清掉后该行的风险分档变为 `NO_DEADLINE`,前端应按 `cancelDays` 是否为 `null` 渲染「未设免费取消期」,而不是按 `cancelCutoff` / `remindDays` 是否有值判断。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| id | Path | Long | ✅ | 必须是退团房**父行**(子行是转出明细,不可改期限);不存在或已删返 808320 | 退团房行 ID |
| cancelDays | Body | Integer | ❌ | 0~60,越界返 808328;传 `null` 表示清除期限 | 入住前几天可免费取消 |
| cancelCutoff | Body | String | ❌ | `HH:mm` 24 小时制(`00:00`~`23:59`),格式不符返 808328;`cancelDays` 非空而本字段为空 / 空白时取 `18:00`;**`cancelDays` 为 `null` 时本字段被忽略,该行原值保留** | 截止当天的时刻 |
| remindDays | Body | Integer | ❌ | 0~30,越界返 808328;`cancelDays` 非空而本字段为空时取 `1`;**`cancelDays` 为 `null` 时本字段被忽略,该行原值保留** | 提前几天提醒 |
#### 出参 `Result<HouseRoomTransferRespVO>`
| 字段 | 类型 | 说明 |
|------|------|------|
| (整行) | HouseRoomTransferRespVO | 更新后的该退团房父行,字段与退团房分页 `records[]` 同构 |
| cancelDays | Integer | 入住前几天免费取消;**清除期限后为 `null`** |
| cancelCutoff | String | 截止当天的时刻 `HH:mm`;**清除期限后仍是该行原值(从未设过则是 `"18:00"`),不会变成 `null`** |
| remindDays | Integer | 提前几天提醒;**清除期限后仍是该行原值(从未设过则是 `1`),不会变成 `null`** |
| deadlineAt | LocalDateTime | 免费取消截止时刻 = 入住晚 − `cancelDays` 天的 `cancelCutoff`;`cancelDays` 为 `null` 或缺入住晚时为 `null` |
| risk | String | 风险码 `OVERDUE` / `NEAR` / `NO_DEADLINE` / `NORMAL`;仅 `PENDING` 行有值,其余为 `null` |
| riskLabel | String | 风险中文;`NO_DEADLINE` 对应「未设免费取消期」 |
| status / statusLabel | String | 行状态 `PENDING` / `TRANSFERRED` / `CANCELLED` 与中文;本端点只对 `PENDING` 行成功 |
| id / sourceOrderId / sourceGroupBatchId / hotelId / roomTypeId | Long(String) | 雪花 ID,JSON 中为字符串 |
| teamNo | String | 源订单团号(批量读订单主表团号);取不到为 `null` |
| stayDate | LocalDate | 入住晚 |
| roomCount / remainingCount | Integer | 原始间数 / 剩余待处理间数 |
| readOnly / readOnlyReason | Boolean / String | 对当前操作人是否只读(源单由他人处理)与理由 |
#### 请求示例
```json
PUT /v3/admin/order/house-console/room-transfers/1950000000000000001/deadline
{
"cancelDays": null
}
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": {
"id": "1950000000000000001",
"sourceType": "ORDER",
"sourceOrderId": "1930000000000000001",
"teamNo": "HL20261001A",
"sourceGroupBatchId": null,
"sourceBatchNo": null,
"stayDate": "2026-10-02",
"cityName": "海拉尔",
"hotelId": "100001",
"hotelName": "海拉尔草原酒店",
"roomTypeId": "300001",
"roomTypeName": "豪华双床房",
"roomCount": 2,
"remainingCount": 2,
"status": "PENDING",
"statusLabel": "待处理",
"cancelDays": null,
"cancelCutoff": "18:00",
"remindDays": 1,
"deadlineAt": null,
"risk": "NO_DEADLINE",
"riskLabel": "未设免费取消期",
"readOnly": false,
"readOnlyReason": null
},
"success": true
}
```
#### 空数据 / 降级响应
- 本端点恒返回整行,没有空响应形态。
- 清除期限(`cancelDays: null`)是正常成功路径:`data.cancelDays = null`、`data.deadlineAt = null`、`data.risk = "NO_DEADLINE"`、`data.riskLabel = "未设免费取消期"`,而 `data.cancelCutoff` 与 `data.remindDays` 仍是该行原值。
- 源单(订单需求 / 团期)读不到时,只影响「谁是源单处理人」的判定,不影响本端点的写入结果;非源单处理人且非超管一律返 808326。
#### 错误响应
```json
{
"code": 808328,
"message": "退改期限参数不合法",
"data": null,
"success": false
}
```
| code | message | 触发 |
|------|---------|------|
| 808090 | 未登录或非房务角色,无权操作 | 非房务角色 |
| 808320 | 转房记录不存在 | `id` 不存在、已删、或不是父行 |
| 808321 | 该房间已处理 | 行已不是 `PENDING`(已转出 / 已取消) |
| 808326 | 只有原单处理人可以处理退团房间 | 非源单处理人且非超管 |
| 808328 | 退改期限参数不合法 | `cancelDays` 超 0~60、`remindDays` 超 0~30、`cancelCutoff` 不是 `HH:mm` |
| 808932 | 房务状态已被并发修改,请刷新后重试 | 真并发写冲突(行版本在锁定读与写之间被改)。**订正前 `cancelDays: null` 会恒定命中这一条,订正后不再出现这种假冲突** |
| 100502 | 修改处理中,请勿重复提交 | 3 秒幂等窗口内重复提交同一请求 |
#### 业务边界
- 入参不走 Bean Validation,范围与格式错误统一以 **808328** 返回,HTTP 状态仍是 200,不是 400。
- 清除期限只清 `cancelDays` 这一项语义;`cancelCutoff` / `remindDays` 是「下次设期限时的默认值」,保留它们不影响「有没有期限」的判定,因为 `deadlineAt` 与 `risk` 都只在 `cancelDays` 非空时才成立。
- 该行处于 `PENDING` 才可改期限;`TRANSFERRED` / `CANCELLED` 返 808321。
- 只有源单处理人或超管可改;源订单来源看该需求的持有人,团期来源看该团期的房务认领人。
- 同一行的改期限、转房、向酒店取消共用一把行级锁,前端不必自己串行化。
### 2. 房务异常检查 `GET /v3/admin/order/house-console/audit`
**VO**: `HouseConsoleAuditReqVO → HouseConsoleAuditRespVO`
#### 使用场景
「异常检查」页签:按出发日期区间一次列出数据对不上的问题(`issues`)和还没办完的事(`tasks`)。本次订正只影响 `tasks` 里 `TRANSFER_PENDING`(退团房未结清)这一类条目的**取行范围**,字段结构未变;前端按 `refId` 跳转退团房处理页的逻辑不变。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| scope | Query | String | ❌ | `mine` / `all`,默认 `mine`;其他取值返 400 | 只作用于 `tasks`:`mine` 只留当前登录人是处理人的条目;`issues` 不受它影响 |
| departDateFrom | Query | LocalDate | ❌ | `yyyy-MM-dd`,默认今天 | 出发日期区间起(含) |
| departDateTo | Query | LocalDate | ❌ | `yyyy-MM-dd`,默认起始日 +30 天;早于起始日或跨度超 92 天返 808313 | 出发日期区间止(含) |
#### 出参 `Result<HouseConsoleAuditRespVO>`
| 字段 | 类型 | 说明 |
|------|------|------|
| issues | Array | 数据不一致问题,不归属处理人,不受 `scope` 影响;无则空数组 |
| tasks | Array | 待处理事项,受 `scope` 过滤;无则空数组 |
| issues[].code / tasks[].code | String | 问题码 / 待办码,取值见「六.5、枚举」 |
| tasks[].taskCode | String | 待办码(仅 `tasks` 有值),与 `code` 同值 |
| issues[].codeLabel / tasks[].codeLabel | String | 中文标签,后端给出直接展示;`TRANSFER_PENDING` 为「退团房未结清」 |
| tasks[].orderId | Long(String) | 源订单 ID;退团房条目取该行的源订单 |
| tasks[].teamNo | String | 源订单团号;取不到为 `null` |
| tasks[].groupBatchId / tasks[].batchNo | Long(String) / String | 源团期 ID 与批次号;散单来源为 `null` |
| tasks[].stayDate | LocalDate | 入住晚 |
| tasks[].hotelId / hotelName / roomTypeId / roomTypeName | Long(String) / String | 酒店与房型 |
| tasks[].refId | Long(String) | 关联单据 ID,`TRANSFER_PENDING` 为退团房父行 ID,前端据此跳转 |
| tasks[].detail | String | 说明文案,`TRANSFER_PENDING` 为「剩余 N 间待转出或向酒店取消」 |
#### 请求示例
```http
GET /v3/admin/order/house-console/audit?scope=mine&departDateFrom=2026-10-01&departDateTo=2026-10-31
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": {
"issues": [],
"tasks": [
{
"code": "TRANSFER_PENDING",
"codeLabel": "退团房未结清",
"taskCode": "TRANSFER_PENDING",
"orderId": "1930000000000000001",
"teamNo": "HL20261001A",
"groupBatchId": null,
"batchNo": null,
"stayDate": "2026-10-02",
"hotelId": "100001",
"hotelName": "海拉尔草原酒店",
"roomTypeId": "300001",
"roomTypeName": "豪华双床房",
"refId": "1950000000000000001",
"detail": "剩余 2 间待转出或向酒店取消"
}
]
},
"success": true
}
```
#### 空数据 / 降级响应
```json
{ "code": 200, "message": "成功", "data": { "issues": [], "tasks": [] }, "success": true }
```
- `issues` 与 `tasks` 恒为数组,不会是 `null`。
- 退团房条目的源单读不到(源订单或源团期查不到、或出发日为空)时,该条目**仍然列出**,`teamNo` / `batchNo` 可能为 `null`;口径是「宁可多报一条,也不让待办从唯一载体上消失」。
- `STOCK_LEDGER_MISMATCH`(库存账不平)只在资源侧全局库存追踪开关打开时检查,开关关闭时不产出该问题码。
#### 错误响应
```json
{
"code": 808313,
"message": "日期跨度不能超过 92 天",
"data": null,
"success": false
}
```
| code | message | 触发 |
|------|---------|------|
| 400 | scope 取值非法 | `scope` 不是 `mine` / `all` |
| 808090 | 未登录或非房务角色,无权操作 | 非房务角色 |
| 808313 | 日期跨度不能超过 {0} 天 | 出发日期区间跨度超 92 天,或止日早于起日 |
#### 业务边界
- `TRANSFER_PENDING` 的归属判据是**源团期 / 源订单的出发日落在窗口内**,与源单当前状态(含已取消)无关;这是本次订正的点。
- 只列 `PENDING` 的退团房父行;已转出、已取消的行不是待办。
- `scope=mine` 的「我的」按源单处理人判:散单来源看该需求持有人,团期来源看该团期房务认领人;源单读不到时该条目在 `mine` 下不会出现(无法判定处理人)。
- 窗口跨度上限 92 天,与控房表查询的 62 天不是同一个上限,别复用。
- 纯读接口,不写数据。
---
## 四、契约约束与正确调用方式(接口类必写)
### ✅ 正确 / ❌ 错误 payload 对照
| 场景 | payload / 判断 |
|------|----------------|
| ✅ 设期限 | `{ "cancelDays": 3, "cancelCutoff": "18:00", "remindDays": 1 }` |
| ✅ 设期限只给天数 | `{ "cancelDays": 3 }` → 截止时刻取 `18:00`、提醒天数取 `1` |
| ✅ 清除期限 | `{ "cancelDays": null }`(或整个 body 只有 `{}`)→ 200,`cancelCutoff` / `remindDays` 保留原值 |
| ✅ 判「未设期限」 | `data.cancelDays === null`,或 `data.risk === "NO_DEADLINE"` |
| ❌ 判「未设期限」 | `data.cancelCutoff === null && data.remindDays === null`——清除期限后这两项仍有值,该判断恒为 false |
| ❌ 清除期限时显式传空 | `{ "cancelDays": null, "cancelCutoff": "", "remindDays": null }` 能成功,但 `cancelCutoff` / `remindDays` 一样被忽略,不要指望用它们清值 |
| ❌ 期限天数越界 | `{ "cancelDays": 61 }` → 808328(不是 400) |
### 切换状态时的必要动作
- 清除期限成功后,前端应以返回的整行直接替换列表行,不要只把 `cancelDays` 置空——`risk` / `riskLabel` / `deadlineAt` 都由后端重算,本地推算会与「退团房分页」的汇总读数对不上。
- 「异常检查」页签的待办条数在订正后可能增加;若页面上有与之对照的徽标计数,改为直接用本端点返回的 `tasks.length`,不要沿用按订单状态自行过滤后的口径。
---
## 五、数据库行为(涉及写操作时必写)
| 写操作 | 外部可观察行为 |
|--------|----------------|
| 设期限(`cancelDays` 非空) | 该行的免费退改天数、截止时刻、提醒天数三项按入参(含默认值)整体更新;行版本 +1;写一条订单级操作日志「退团房 {入住晚} {酒店名} 免费退改期限改为入住前 N 天 HH:mm」 |
| 清除期限(`cancelDays` 为 `null`) | **只有免费退改天数被清空**;截止时刻与提醒天数保持该行原值;行版本 +1;写一条订单级操作日志「…免费退改期限改为未设」 |
| 并发保护 | 行级分布式锁 + 行版本比对;版本在锁定读与写之间被改则整笔回滚并返 808932 |
- 两个写路径都不产生跨服务调用,不发消息。
- 「异常检查」端点是纯读,不写任何数据。
---
## 六、边界行为
- 清除期限后再次设期限,若只传 `cancelDays`,截止时刻与提醒天数会被**重新按默认值 `18:00` / `1` 覆盖**(不是沿用清除前保留下来的那两个值);要沿用旧值必须显式回传。
- 从未设过期限的新行,其截止时刻与提醒天数是建表默认的 `"18:00"` 与 `1`,所以「一行从没设过期限」与「设过又被清掉」在这两个字段上不可区分;唯一区分点是操作日志。
- 风险分档 `NEAR` 按「日」比较(今天 ≥ 截止日 − 提醒天数),同一天上午和下午不会给出不同分档。
- 退团房待办的取行只设窗口下限(入住晚不早于窗口起日),不设上限:待处理池量级小,多读回的行由源单出发日过滤掉。
- `scope=all` 时任何房务都能看到全部待办条目,但看得见不等于能写;改期限仍按源单处理人校验(808326)。
---
## 六.5、枚举 / 数据字典(接口出现枚举时必写)
### risk(退团房风险分档)
| 值 | 中文(`riskLabel`) | 判据 |
|----|--------------------|------|
| `OVERDUE` | 已过免费取消期 | 现在已过截止时刻 |
| `NEAR` | 临近免费取消期 | 今天 ≥ 截止日 − 提醒天数 |
| `NO_DEADLINE` | 未设免费取消期 | `cancelDays` 为空(或该行缺入住晚) |
| `NORMAL` | 正常 | 其余 |
> 仅 `status = PENDING` 的行有值,其余行 `risk` / `riskLabel` 均为 `null`。
### status(退团房行状态)
| 值 | 中文(`statusLabel`) | 说明 |
|----|----------------------|------|
| `PENDING` | 待处理 | 还有剩余间数待转出或向酒店取消;只有这一状态能改期限 |
| `TRANSFERRED` | 已转出 | 全部间数已转给别的订单 / 团期 |
| `CANCELLED` | 已取消 | 已向酒店取消 |
### taskCode / code(异常检查待办码,`tasks[]`)
| 值 | 中文(`codeLabel`) | 说明 |
|----|--------------------|------|
| `TRANSFER_PENDING` | 退团房未结清 | 本次订正影响的就是这一类的取行范围 |
| `HOTEL_CANCEL_PENDING` | 原酒店待取消 | 改配留下的原订尚未确认取消 |
| `INQUIRY_PENDING` | 新订 / 变更待确认 | — |
| `STAY_UNARRANGED` | 住宿待落实 | — |
> `issues[]` 的 `code` 取值域是另一组(`STOCK_OVERBOOKED` / `STOCK_LEDGER_MISMATCH` / `STOCK_ROW_MISSING` / `TRANSFER_TARGET_GONE` / `PLAN_COUNT_MISMATCH`),本次未变。
---
## 六.6、修改前后对比(修改/删除类接口必写,新增跳过)
### 字段级对比
| 字段 | 之前交接件写的 | 现在的实际行为 |
|------|----------------|----------------|
| `data.cancelCutoff`(清除期限后) | 被一并置空为 `null` | **保留该行原值**(从未设过则为 `"18:00"`) |
| `data.remindDays`(清除期限后) | 被一并置空为 `null` | **保留该行原值**(从未设过则为 `1`) |
| `data.cancelDays`(清除期限后) | `null` | `null`(不变) |
| `data.deadlineAt`(清除期限后) | `null` | `null`(不变) |
| `data.risk` / `riskLabel`(清除期限后) | `NO_DEADLINE` / 「未设免费取消期」 | 同(不变) |
| 异常检查 `tasks[]` 结构 | — | 字段与类型均未变 |
### 行为级对比
| 行为 | 之前 | 现在 |
|------|------|------|
| `PUT .../deadline` 传 `cancelDays: null` | 恒定返回 808932「房务状态已被并发修改,请刷新后重试」,期限清不掉 | 返回 200 并给出更新后的整行 |
| `PUT .../deadline` 传 `cancelDays` 非空 | 成功 | 成功(口径不变) |
| `GET .../audit` 的 `TRANSFER_PENDING` | 源订单 / 源团期已取消的退团房行不出现在 `tasks` 里 | 按源单出发日归属、不看源单状态,这些行会出现 |
| `GET .../audit` 的 `TRANSFER_PENDING`(源单查不到 / 出发日为空) | 不出现 | 出现(宁可多报一条) |
| `GET .../audit` 的 `issues[]` | — | 口径不变 |
---
## 六.7、影响评估(修改/删除类必写)
| 项 | 评估 |
|----|------|
| 需要前端改代码 | **是(1 处必改)**:凡按 `cancelCutoff` / `remindDays` 是否为 `null` 判「有没有设期限」的地方,改为按 `cancelDays === null` 或 `risk === "NO_DEADLINE"` 判。 |
| 需要前端改代码 | **可能(1 处)**:「异常检查」页签若自行按订单状态过滤过待办条目,去掉该过滤,直接用后端返回的 `tasks`。 |
| 兼容性 | 无字段增删、无类型变化、无路径与方法变化;只有取值与取行范围变化。 |
| 「清除期限」功能 | 从不可用变为可用,前端原有的 808932 报错提示分支在该场景不再触发(真并发冲突仍会返回它,不要删该分支)。 |
| 读数变化 | 同一出发日窗口下「异常检查」的待办行数只会增加或不变,不会减少。 |
| 其他消费方 | 退团房分页、转房、向酒店取消三个端点的契约未变;本次不涉及小程序端。 |
---
## 七、不影响范围(显式声明, 帮前端/QA 缩小排查面)
- 退团房分页 `GET /v3/admin/order/house-console/room-transfers`、转入候选 `GET .../room-transfers/{id}/candidates`、转房 `POST .../room-transfers/{id}/transfer`、向酒店取消 `POST .../room-transfers/{id}/cancel-hotel`:字段与口径均未变。
- 控房表(查询 / 调房量 / 调价 / 导出)、住宿模板、批量认领、改配取消确认、团期转交:未触及。
- `issues[]` 的五类问题码及其判据未变。
- 房务只读标识 `readOnly` / `readOnlyReason` 的口径未变(其口径见 #8491 的两份交接件)。
- 通知、消息模板、跳转链接未变。
- 无数据库结构变更,无新增 Flyway 脚本,无网关路由改动。
---
## 八、测试环境已验证
| 项 | 读数 | 依据 |
|----|------|------|
| hl-order-service-v3 运行的提交 | `d57498d38`,BEHIND `0/N`,STATE `ok`,时间 2026-09-30 05:32:57 | 测试服部署登记脚本 `/opt/hulalv/scripts/deploy-status.sh`,2026-09-30 本次读取 |
| 本次改动在运行的字节里 | 是 | `git merge-base --is-ancestor ff68637542 origin/dev-v3` 返回 0;`origin/dev-v3` 头为 `d57498d381` |
| 清除期限 | `cancelDays=null` → `code=200`;库里免费退改天数为 NULL,截止时刻 / 提醒天数保留原值;`risk=NO_DEADLINE` | 取证提交 `ff6863754`,读数原文记在 `changelogs-v2/2026-09/30_8491_房务控制台接口-新增接口-管理后台.md` 第 1847 行 |
| 设期限(回归) | `cancelDays=3` → `code=200`、该行 `risk=OVERDUE`;`cancelDays=0` + `remindDays=1` → `code=200`、`risk=NEAR` | 同上文件第 1845、1846 行,复测提交 `ff6863754` |
| 退团房待办含源单已取消的行 | 源订单已取消 + 退团房行 `PENDING` + 窗口覆盖出发日 → `code=200`,`tasks` 含 `TRANSFER_PENDING`「退团房未结清」 | 同上文件第 1861 行,取证提交 `ff6863754` |
| 用例覆盖 | `HouseRoomTransferManagerTest#updateDeadline_nullCancelDays_keepsRowCutoffAndRemind`;`HouseRoomTransferMapperMysqlTest#casUpdateDeadline_nullCancelDaysWithRowValues_writesNullAndKeepsCutoffRemind`;`HouseConsoleAuditManagerTest#audit_pendingTransferSourceOrderCancelledInWindow_listed` / `#audit_pendingTransferSourceBatchCancelledInWindow_listed` / `#audit_pendingTransferSourceOrderMissing_listed` / `#audit_pendingTransferSourceOrderDepartOutsideWindow_notListed` | 随 `ff68637542` 新增 |
---
## 九、相关历史 PR(纠错 / 功能演进时必写)
| PR / 提交 | 内容 | 与本条的关系 |
|-----------|------|--------------|
| PR #8564(`7c21cf0e40`) | 房务控制台整套(#8491),首次引入本文两个端点 | 被订正的表述出自它的交接件 |
| PR #8599(`ff68637542`) | 本条的两处修复(#8597) | 本条正文描述的就是它合入后的行为 |
---
## 十、相关文档
- 本条 Issue:#8597(PR #8599)
- 被订正的交接件:`changelogs-v2/2026-09/30_8491_房务控制台接口-新增接口-管理后台.md`(接口 8 与接口 12)
- 只读标识口径:`changelogs-v2/2026-09/30_8491_房务配房接口字段与口径调整-修改接口-管理后台.md`
---
## 关联 / 联系人
### 链接
- Issue: #8597
- PR: #8599
- 分支基线: `dev-v3`
### 联系人
- 后端: wx
- 前端: mmg(管理后台)