文件
hl-api-changelog/changelogs-v2/2026-09/30_8597_清空退团房退改期限保留截止时刻与提醒天数并补回已取消源单的退团房待办-修改接口-管理后台.md
T

25 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 8597 退团房清空免费退改期限:截止时刻与提醒天数保留原值(订正 #8491 交接件三处表述),异常检查补回源单已取消的退团房待办 admin wx(GIT) 修改接口 deployed not_required not_required 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/** 路由块已通配,零网关改动。 2026-09-30 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 对当前操作人是否只读(源单由他人处理)与理由

请求示例

PUT /v3/admin/order/house-console/room-transfers/1950000000000000001/deadline
{
  "cancelDays": null
}

响应示例

{
  "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。

错误响应

{
  "code": 808328,
  "message": "退改期限参数不合法",
  "data": null,
  "success": false
}
code message 触发
808090 未登录或非房务角色,无权操作 非房务角色
808320 转房记录不存在 id 不存在、已删、或不是父行
808321 该房间已处理 行已不是 PENDING(已转出 / 已取消)
808326 只有原单处理人可以处理退团房间 非源单处理人且非超管
808328 退改期限参数不合法 cancelDays 超 060、remindDays 超 030、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 间待转出或向酒店取消」

请求示例

GET /v3/admin/order/house-console/audit?scope=mine&departDateFrom=2026-10-01&departDateTo=2026-10-31

响应示例

{
  "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
}

空数据 / 降级响应

{ "code": 200, "message": "成功", "data": { "issues": [], "tasks": [] }, "success": true }
  • issues 与 tasks 恒为数组,不会是 null。
  • 退团房条目的源单读不到(源订单或源团期查不到、或出发日为空)时,该条目仍然列出,teamNo / batchNo 可能为 null;口径是「宁可多报一条,也不让待办从唯一载体上消失」。
  • STOCK_LEDGER_MISMATCH(库存账不平)只在资源侧全局库存追踪开关打开时检查,开关关闭时不产出该问题码。

错误响应

{
  "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(管理后台)