文件
hl-api-changelog/changelogs-v2/2026-09/20_8016_团期候选房型大类过滤-修改接口-管理后台.md

19 KiB

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 8016 团期候选酒店:roomCategory 入参从「仅展示标签」到「真正参与房型过滤」(1 个接口,契约零增删字段) admin wx(GIT) 修改接口 deployed verified verified mmg bb1c4f8afd94136c303269e2033d3ea05967aae5 2026-09-20 酒店候选查询 GET /v3/admin/hotel-candidates 契约零增删字段,只改行为:roomCategory 入参此前完全不参与房型过滤,只当展示标签回显(matchedRoomTypeLabel 甚至直接赋成入参 code 本身,掩盖错配);现在 roomCategory 非空白时 matchedRoomTypeId 只能落在同大类房型上(忽略大小写去空白),matchedRoomTypeLabel 恒为房型真实中文名,新增置灰文案「所选房型今日无房」(与既有三支互斥,仅在 availFreshness=fresh 且 todayAvailable>0 时出现)。todayAvailable 口径不变仍是全房型合计。roomCategory 不传/空白时行为逐字一致。PR #8022 squash 合并 dev-v3,合并提交 7b713305e;测试服 hl-order-service-v3 已部署该提交(deploy-status 核验 BEHIND 0,is-ancestor 退出码 0),gateway 未重新部署因未涉及路由改动。测试服网关实测订单 2100856430239121409,四组(不传/DELUXE/STANDARD/PARENT_CHILD)各 21 候选,回显缺陷计数 0,todayAvailable 逐 hotelId 比对 A vs B/C 均 0/21 不一致,D 组 15 个 todayAvailable>0 候选置灰原因逐一核对均为所选房型今日无房。🔴 前端行动项:第二个房型下拉需按第一个下拉选定大类过滤,数据早已在 roomTypes[].roomCategory 里(工单 #4204 加的),前端用现有字段即可实现,本次未新增任何字段,无需等后端。前端已交付(hl-ui v2.1 @ bb1c4f8a):调整订单 FunItemAdjustModal 第二个房型下拉按第一个下拉大类过滤(roomTypes[].roomCategory,忽略大小写去空白,大类空房型不列入,回显合成项保留),换大类时已选房型不属新大类清房型/协议价,placeholder 区分过滤后空态;grep 实证 quickPick*/matchedRoomTypeLabel 前端无消费面(仅 PickHotelModal pickFirst 回退显示自动受益,无英文 code 清洗残留可撤);spec +5 例 29/29,scoped checkpoint 全绿。 2026-09-20 dev-v3

order-v3: 酒店候选 roomCategory 入参从「仅展示标签」到「真正参与房型过滤」

存放目录: changelogs-v2/{YYYY-MM}/(管理后台,二期 order-v3)

服务: hl-order-service-v3 (端口 8086) PR: #8022 Issue: #8016 日期: 2026-09-20 影响范围: 管理后台订单详情「选择酒店」弹窗的候选酒店匹配与快速配房


⚠️ 关键变化

  1. roomCategory 入参终于会过滤房型了:改前它只是回显标签,不参与任何匹配;改后 matchedRoomTypeId 只会落在与 roomCategory 相同大类的房型上。
  2. matchedRoomTypeLabel 改为房型真实中文名:改前它被赋值为入参 roomCategory 本身(一个大写 code,如 DELUXE),会把「选中的其实不是所要大类」的错误掩盖掉;改后恒为真实房型名(如「豪华大床房」)。
  3. 新增置灰文案「所选房型今日无房」:与既有「未核房,请先核房」「核房数据过期,请先核房」「今日无房」三支互斥,那三支一字未改。
  4. 契约零增删字段:本次不新增、不删除任何请求/响应字段,roomCategory 不传或传空白时行为与改前逐字一致。
  5. 🔴 前端仍需自己动手:第二个房型下拉需要按第一个下拉选定的大类过滤;后端提供的数据(roomTypes[].roomCategory)在工单 #4204 就已经给了,本次没有新增字段,前端可以直接用现有响应字段实现,无需后端再改接口。详见「九、前端需要做的事」。

一、背景

wx 2026-09-20 反馈:「选择酒店」弹窗里第一个下拉选了「豪华房」,第二个房型下拉仍然列出大床房、标间。经核查,问题分两部分:

  • 后端部分(本文档范围):roomCategory 入参此前完全不参与 matched 房型的选取,只是被当成展示标签回显,导致 matchedRoomTypeId 可能指向错误大类的房型、quickPickEnabled 对错房型开了「快速配房」绿灯,且 matchedRoomTypeLabel 回显的是入参 code 而非真实房型名,掩盖了这个错配。
  • 前端部分(不在本文档范围,需 mmg 自行处理):第二个房型下拉本身没有按第一个下拉的选择做过滤,详见「九、前端需要做的事」。

二、变更接口清单

# 接口 方法 路径 变更类型 说明
1 酒店候选查询 GET /v3/admin/hotel-candidates 修改 roomCategory 入参真正参与 matched 房型过滤;matchedRoomTypeLabel 改为回显真实房型中文名;新增置灰文案「所选房型今日无房」;契约零增删字段

三、接口详情

1. 酒店候选查询 GET /v3/admin/hotel-candidates

VO:HotelCandidateQueryReqVO → HotelCandidateRespVO

使用场景

管理后台订单详情页「选择酒店」弹窗:定制师/房务在弹窗里选定「房型大类」(如豪华房、标间)后,弹窗按此大类过滤候选酒店,并展示每个候选是否可「快速配房」(即该酒店是否有该大类的够数房型)。

入参字段表

字段 位置 类型 必填 约束 说明
orderId Query Long ✅ - 订单 ID
dayNumber Query Integer - ≥1 第几天(从 1 开始,用于推算 stayDate = departDate + dayNumber - 1)
stayDate Query LocalDate(yyyy-MM-dd) - - 入住日期(直接指定优先于 dayNumber 推算)
city Query String - - 城市代码
keyword Query String - - 关键词,非空时突破单城限定,跨城/省按关键词搜酒店
limit Query Integer - 1~50,默认 30 返回候选条数上限
roomCategory Query String - 字典 room_category 的 value(大写 code) 本次生效:非空白时 matched 只匹配同大类房型
roomCount Query Integer - ≥1 需要的房间数,不传退化为 available>0 的旧行为
preferredHotelId Query Long - - 定制师指定的优先酒店 ID
requirementId Query Long - - 用房需求 ID(传入后 days JSON 里所有 hotelId 作为定制师指定)

⚠️ roomCategory 在 Swagger 上的 example 字段写的是 standard_double,那是过期垃圾,既不是字典 code 也不是中文,不要照抄。 已于 2026-09-20 修掉(PR #8025,squash 合并 dev-v3 = 819a147a2):Swagger 上该入参的 example 现为 DELUXE,value 文案也补上了取值集合与过滤语义,可以直接照 Swagger 传。保留 standard_double 字面量在此,供仍搜到旧值的人落到这条订正上。真实取值见「六.5、枚举 / 数据字典」。

出参字段表

candidates[] 元素(仅列本次涉及/相关字段,完整字段清单见源码 HotelCandidateRespVO.Candidate):

字段 类型 说明
matchedRoomTypeId Long(字符串) 匹配的房型 ID;本次生效:roomCategory 非空白时只能落在同大类房型上,为空则为 null
matchedRoomTypeAvailable Integer 匹配房型的今日可用数
matchedRoomTypeLabel String 本次改变取值口径:恒为房型真实中文名,不再回显入参 roomCategory
quickPickEnabled Boolean 是否支持快速配房
quickPickDisabledReason String 置灰原因;本次新增一支「所选房型今日无房」
todayAvailable Integer 今日可用房数(全房型合计,不按 roomCategory 过滤,口径不变)
roomTypes[].roomCategory String 该房型的真实大类 code(字典 room_category 的 value);早于本次已存在(工单 #4204)

请求示例

GET /v3/admin/hotel-candidates?orderId=2100856430239121409&stayDate=2026-10-08&dayNumber=1&roomCategory=DELUXE

响应示例

{
  "code": 200,
  "message": "成功",
  "data": {
    "stayDate": "2026-10-08",
    "city": "hailar",
    "productType": "GROUP",
    "candidates": [
      {
        "hotelId": "200001",
        "hotelName": "海拉尔假日酒店",
        "roomTypes": [
          {
            "roomTypeId": "300001",
            "name": "豪华大床房",
            "roomCategory": "DELUXE",
            "available": 7
          }
        ],
        "todayAvailable": 12,
        "availFreshness": "fresh",
        "matchedRoomTypeId": "300001",
        "matchedRoomTypeAvailable": 7,
        "matchedRoomTypeLabel": "豪华大床房",
        "quickPickEnabled": true,
        "quickPickDisabledReason": null
      }
    ]
  },
  "success": true
}

空数据 / 降级响应

所选大类没有够数房型时(实测组 D:roomCategory=PARENT_CHILD,库内无此大类房型):

{
  "code": 200,
  "message": "成功",
  "data": {
    "candidates": [
      {
        "hotelId": "200002",
        "todayAvailable": 15,
        "availFreshness": "fresh",
        "matchedRoomTypeId": null,
        "matchedRoomTypeAvailable": null,
        "matchedRoomTypeLabel": null,
        "quickPickEnabled": false,
        "quickPickDisabledReason": "所选房型今日无房"
      }
    ]
  },
  "success": true
}

错误响应

本接口无专属错误码;业务失败(下游降级、无候选等)一律 HTTP 200 返回空 candidates 或置灰字段,不抛错误码。orderId 缺失时走参数校验的全局异常处理:

{
  "code": 400,
  "message": "orderId 不能为空",
  "success": false,
  "data": null
}

业务边界

  • roomCategory 比较忽略大小写、去首尾空白(容忍存量脏数据);房型自身 roomCategory 为 null(resource 侧漏配分类)时该房型不参与 matched,宁可置灰让人工核房,也不拿分类未知的房型冒充所选大类。
  • roomCategory 不传或传空白字符串时,matched 选取逻辑与改前逐字一致(不限大类)。
  • todayAvailable 恒为全房型合计,不受 roomCategory 过滤影响;前端可用「matchedRoomTypeId 为空但 todayAvailable>0」判断「这家酒店有房、只是所选大类没有」,据此提示换大类而非换酒店。
  • 「所选房型今日无房」仅在 availFreshness=fresh 且 todayAvailable>0 时出现,与「未核房,请先核房」「核房数据过期,请先核房」「今日无房」三支互斥,不会误报未核房的候选。
  • CORE / CUSTOM 产品类型行为无变化:过滤逻辑对三种产品类型统一生效,但只有传了 roomCategory 时才会产生差异。

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

本节只写后端接受/拒绝 payload 的规则,不写 UI 渲染建议。

✅ 正确 / ❌ 错误取值对照

场景 roomCategory 取值 结果
✅ 过滤豪华房 DELUXE matched 只从大类=DELUXE 的房型中选
✅ 过滤标间 STANDARD matched 只从大类=STANDARD 的房型中选
✅ 不限大类 不传 / 空字符串 行为与改前一致,不过滤
❌ 传中文 豪华房 不会匹配到任何房型(房型侧存的是大写 code,不做中文翻译),退化为「所选房型今日无房」或「今日无房」
❌ 抄 Swagger example standard_double 同上,不会匹配到任何房型;该 example 已过期,不能照抄

切换大类时的必要动作

前端切换第一个下拉(房型大类)后,需重新发起 GET /v3/admin/hotel-candidates 请求并带上新的 roomCategory;后端不做「记忆上次大类」之类的会话状态,每次请求都是独立判定。


五、数据库行为

本接口是纯查询接口,不写库。roomCategory 参与的是内存里对 resource 端批量返回的 roomTypes 做过滤/匹配,不落任何表。


六、边界行为

  • 未登录 → 401(网关拦截)
  • orderId 缺失/非法 → 400(参数校验)
  • 下游 resource 服务降级 → 候选 todayAvailable/matchedRoomTypeAvailable 为 null,quickPickEnabled=false,不 500 不阻断弹窗
  • 老数据兼容 → 无影响,本次不改表结构,存量候选数据无关

六.5、枚举 / 数据字典

roomCategory(字典 room_category)

所属字段: HotelCandidateQueryReqVO.roomCategory(入参)/ HotelCandidateRespVO.Candidate.RoomTypeOption.roomCategory(出参) | 类型: String

值 中文 说明
STANDARD 标间
SINGLE 单人间
TWIN 双床房
QUEEN 大床房
DELUXE 豪华房
KING 豪华大床
SUITE 套房
FAMILY 家庭房
YURT 蒙古包
SPECIAL 特色房
PARENT_CHILD 亲子房

quickPickDisabledReason(文案枚举,非字典表)

所属字段: HotelCandidateRespVO.Candidate.quickPickDisabledReason | 类型: String

值 触发条件
未核房,请先核房 matchedRoomTypeAvailable == null 或 availFreshness=never_checked
核房数据过期,请先核房 availFreshness=stale
今日无房 已核房但全房型都无余量
所选房型今日无房(本次新增) roomCategory 非空白 + matchedRoomTypeAvailable==null + availFreshness=fresh + todayAvailable>0,即整家酒店有房、只是所选大类没有

六.6、修改前后对比

字段级对比

字段 改前 改后
matchedRoomTypeId 可能落在与 roomCategory 不同大类的房型上 只能落在与 roomCategory 相同大类的房型上(忽略大小写、去空白比较)
matchedRoomTypeAvailable 同上(可能对应错房型) 同上(改为对应正确大类的房型,或为 null)
matchedRoomTypeLabel 恒为入参 roomCategory 本身(如 DELUXE) 恒为房型真实中文名(如「豪华大床房」)
quickPickEnabled 可能对错大类房型开放「快速配房」 只对匹配大类的够数房型开放
quickPickDisabledReason 三支枚举 四支枚举,新增「所选房型今日无房」
todayAvailable 全房型合计 不变,仍是全房型合计

行为级对比

行为 改前 改后
传 roomCategory=DELUXE 但该酒店只有标间够数 仍可能 matched 到标间,quickPickEnabled=true,label 显示 DELUXE matched 为 null,quickPickEnabled=false,置灰原因「所选房型今日无房」
不传 roomCategory 不限大类匹配 不限大类匹配(逐字一致)

六.7、影响评估

  • 是否破坏向后兼容: 是(局部)——只影响「传了 roomCategory 且该酒店没有对应大类够数房型」这一种此前被错误 matched 的场景;不传 roomCategory 的调用方零影响。
  • 前端是否必须同步上线: 否(后端这次修的是数据准确性,不改字段,前端不改代码也不会报错)/ 是(前端另需改造第二个房型下拉的过滤逻辑,见「九、前端需要做的事」,但那是独立于本次后端契约的前端本地问题)。
  • 前端 workaround 清理点: 若前端此前对 matchedRoomTypeLabel 里出现英文大写 code(而非中文)做过特殊兼容/正则清洗,现在可以撤掉——该字段恒为真实中文名。

七、不影响范围

  • 仅影响: 传了非空白 roomCategory 参数的候选查询请求
  • 零影响:
    • roomCategory 不传或传空白的调用(行为逐字一致)
    • todayAvailable 的取值口径(仍是全房型合计)
    • 请求/响应字段的增删(本次零增删)
    • 错误码(本接口本无专属错误码,本次未新增)
    • 「未核房,请先核房」「核房数据过期,请先核房」「今日无房」三支既有置灰文案的触发条件

八、测试环境已验证

测试服网关实测,订单 2100856430239121409(团期),stayDate=2026-10-08,dayNumber=1,每组 21 个候选,四组响应 body code 均为 200:

组 入参 结果
A 不带 roomCategory(基线) 15 个候选有 matched
B roomCategory=DELUXE 2 个候选有 matched,两者 matched 房型在自己 roomTypes[] 中的 roomCategory 均为 DELUXE,label 为「豪华大床房」
C roomCategory=STANDARD 12 个候选有 matched,全部 roomCategory=STANDARD,label 为中文房型名(普通标间/标准间/商务标间/精品标间等)
D roomCategory=PARENT_CHILD(库内无此大类房型) 0 个候选有 matched;15 个 todayAvailable>0 的候选 quickPickDisabledReason 逐一核对均恰为「所选房型今日无房」

三条对照:

  • matchedRoomTypeLabel == 入参 code 的计数在 B、C 两组均为 0(回显缺陷确认已修);
  • todayAvailable 逐 hotelId 比对 A vs B、A vs C 均 0/21 不一致(大类过滤未影响全房型合计);
  • 阳性对照成立(B、C 两组 matched 非 null 的候选数分别为 2 和 12,非全 null,所以「无错误大类混入」不是空断言)。如实说明:B 组样本量偏薄(2/21),C 组(12/21)是更强的主证据。

部署核实:deploy-status 报 hl-order-service-v3 跑 dev-v3 @ 7b713305e,BEHIND 0/N,git merge-base --is-ancestor 退出码 0。hl-gateway 未重新部署——本改动不涉及 gateway 路由。


九、前端需要做的事(🔴 mmg 行动项)

wx 2026-09-20 反馈:「选择酒店」弹窗里第一个下拉选了「豪华房」,第二个房型下拉仍然列出大床房、标间。

这一条后端改不了,需要前端改:第二个房型下拉应当按第一个下拉选定的大类过滤。

所需数据后端早已提供且本次未变——响应里每个候选的 roomTypes[] 逐项都带 roomCategory(真实大类 code,工单 #4204 加的,原意就是「供前端从候选真实房型生成房型下拉」)。前端用现有数据即可完成过滤,无需后端再改接口、也无需新增任何字段。

前端还应同步的两点:

  • 若界面上有回显「匹配房型」,注意 matchedRoomTypeLabel 现在是真实房型中文名而非大类名,展示文案可能需要微调;
  • 新增置灰文案「所选房型今日无房」需要正常展示(它与「今日无房」语义不同:前者是换个大类可能有房)。

十、相关文档

关联 / 联系人

链接

联系人

  • 后端负责人: @wx