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 影响范围: 管理后台订单详情「选择酒店」弹窗的候选酒店匹配与快速配房
⚠️ 关键变化
roomCategory入参终于会过滤房型了:改前它只是回显标签,不参与任何匹配;改后matchedRoomTypeId只会落在与roomCategory相同大类的房型上。matchedRoomTypeLabel改为房型真实中文名:改前它被赋值为入参roomCategory本身(一个大写 code,如DELUXE),会把「选中的其实不是所要大类」的错误掩盖掉;改后恒为真实房型名(如「豪华大床房」)。- 新增置灰文案「所选房型今日无房」:与既有「未核房,请先核房」「核房数据过期,请先核房」「今日无房」三支互斥,那三支一字未改。
- 契约零增删字段:本次不新增、不删除任何请求/响应字段,
roomCategory不传或传空白时行为与改前逐字一致。 - 🔴 前端仍需自己动手:第二个房型下拉需要按第一个下拉选定的大类过滤;后端提供的数据(
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 作为定制师指定) |
⚠️ 已于 2026-09-20 修掉(PR #8025,squash 合并 roomCategory 在 Swagger 上的 example 字段写的是 standard_double,那是过期垃圾,既不是字典 code 也不是中文,不要照抄。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现在是真实房型中文名而非大类名,展示文案可能需要微调; - 新增置灰文案「所选房型今日无房」需要正常展示(它与「今日无房」语义不同:前者是换个大类可能有房)。
十、相关文档
- 关联 Issue: wx/HL#8016
- 关联 PR: wx/HL#8022
关联 / 联系人
链接
联系人
- 后端负责人: @wx