docs(changelog): 团期酒店候选放开池外 + 候选恒空修复(#7991 #8016)
changelog-filename-gate / validate (push) Failing after 1s
changelog-filename-gate / validate (push) Failing after 1s
同一端点 GET /v3/admin/hotel-candidates 上 2026-09-20 先后上线的两次行为变更合写一份: #7991 修复 GROUP 候选恒空(排序器以未接线的 batchRoomRemain 硬过滤,池内每家都被丢)、 #8016 放开池外(产品固定房池由硬门降级为加分项,池内恒排最前并带徽章)。 契约零增删,但 GROUP 的 isPoolMatch / poolMatchBadge / recommended / recommendSource 取值域由「恒真/恒非空」变为「可假/可 null」,前端若对 GROUP 写过恒为池内的假设必须改。 实测读数取自测试服 order-v3 @7d44ca268 经网关实调,候选 1→21。 Refs #7991 #8016 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
这个提交包含在:
@@ -0,0 +1,381 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8016"
|
||||
title: "团期(GROUP)酒店候选:从「恒空」到「同城全量」——产品固定房池由硬门降级为加分项(1 个接口,契约零增删)"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "pending"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: "2026-09-20"
|
||||
status_note: "本文件覆盖同一端点上 2026-09-20 先后上线的两次行为变更:#7991(候选恒空修复,dev-v3 @624777771,12:36 部署)与 #8016(放开池外,dev-v3 @7d44ca268,13:36 部署)。两者相隔一小时、改同一个端点、对前端是一条连贯的行为变化,故合写一份而非拆两份。backend_status=deployed 与 gateway_status=verified 均为实测:13:39 经网关 api.test.1814.love:9443 实调订单 2100856430239121409,候选数 1→21、池内排第一、跨区关键词搜生效(读数见第八节)。frontend_status 标 pending 而非 not_required 是如实标注——前端大概率无需改代码(池内徽章渲染与 CORE 共用),但 GROUP 分支若存在「恒为池内」的历史假设则必须改;我无法读 mmg 仓库代码,不替前端下结论,请 mmg 核完自行改为 verified 或 not_required。"
|
||||
updated_at: "2026-09-20"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 团期酒店候选: 放开池外,产品固定房池由硬门降级为加分项(工单 #7991、#8016)
|
||||
|
||||
> **存放目录**: 二期(order-v3 标签工单)→ `changelogs-v2/2026-09/`
|
||||
>
|
||||
> **服务**: hl-order-service-v3(8086 / 8186)
|
||||
> **PR**: #8014(#7991 候选恒空修复)、#8021(#8016 放开池外)
|
||||
> **Issue**: #7991、#8016
|
||||
> **日期**: 2026-09-20
|
||||
> **影响范围**: 管理后台「订单详情 → 调整订单 → 酒店安排 → 选择酒店」弹窗,仅团期(GROUP)产品订单
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
- 🔴 **GROUP 订单的 `candidates` 此前恒为空数组**(#7991)。根因:排序器以 `batchRoomRemain > 0` 硬过滤候选,而该字段自 2026-05-20 引入起在服务端从未被任何写口赋值、恒为 `null`,于是产品池内每一家酒店都被丢弃。**这不是「没有可用酒店」,是全部被过滤掉了**——若前端据此做过「团期无可选酒店」的兜底文案或降级交互,现在会走到有数据的分支。
|
||||
|
||||
- 🔴 **GROUP 的四个字段取值域变了,这是最容易打穿前端的一条**:
|
||||
|
||||
| 字段 | 改前(GROUP) | 改后(GROUP) |
|
||||
|------|--------------|--------------|
|
||||
| `isPoolMatch` | **恒为 `true`** | `true`(池内)/ `false`(池外) |
|
||||
| `poolMatchBadge` | **恒非 null** | 池内非 null / 池外 **`null`** |
|
||||
| `recommended` | **恒为 `true`** | `true`(池内或定制师点名)/ `false`(池外普通候选) |
|
||||
| `recommendSource` | 恒为 `CONSULTANT` 或 `PRODUCT_POOL` | 两者之一,或**池外普通候选为 `null`** |
|
||||
|
||||
**前端若对 GROUP 分支写过「一定是池内」「徽章一定有」的假设,必须改成按字段判空。** CORE 产品一直是这个取值域,若徽章渲染与 CORE 共用同一段代码则无需改动。
|
||||
|
||||
- **候选条数量级变了**:同一订单同一晚,测试环境实测从 **1 条变为 21 条**(受 `limit` 约束,默认 30、上限 50)。列表若有高度 / 滚动 / 虚拟化假设需复核。
|
||||
|
||||
- **`keyword` 对团期订单真正生效了**。此前团期单无论填什么关键词都返回同一批池内酒店(池子限定分支早于关键词分支 return,关键词被整个吞掉);现在与 CORE 一致跨城 / 省搜。弹窗 placeholder「留空查本城,填写可跨城搜」**前端文案无需改,但它从「假承诺」变成了「真行为」**。
|
||||
|
||||
- ⚠️ **已知取舍:关键词模式下池内酒店不会被强行保留**。填了关键词时,不命中关键词的池内酒店不会出现在结果里(实测填「海拉尔」时池内的「呼伦贝尔香格里拉大酒店」不在列表中)。这是沿用 CORE 的既有形状——改它会同时改变 CORE 行为,故保留。若业务需要「团期任何搜索下都看得到签约房」,需另立工单。
|
||||
|
||||
- **池内恒排池外之前,且是结构性保证而非约定**:`score = (池内 ? 1000 : 0) + min(可用房分, 999)`,可用房分上限严格小于池内加分,因此「池外但房很多」的候选**不可能**挤掉池内候选。前端可按返回顺序直接展示,不需要自己再排一次。
|
||||
|
||||
- **本次推翻了工单 #3217 的「GROUP 仅展示产品池内固定房」约束**,依据是 wx 2026-09-20 的明确定案(团期实际常需临时换到池外酒店:池内满房 / 客人指定 / 行程变更,原先运营只能绕过系统线下处理)。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
团期订单的「选择酒店」是房务配房链路第一步:定制师 / 房控为每晚挑候选酒店,交房控择一。此前该弹窗对**所有**团期订单显示「无数据」,定制师一家也选不了。排查定位为两层问题叠加:
|
||||
|
||||
1. **恒空(#7991)**:排序器把一个从未接线的字段当硬过滤条件,池内每家酒店都被丢弃。
|
||||
2. **池子限定(#8016)**:修好第 1 层后每晚只能看到产品房池里配的那 1 家,而池外酒店连关键词都搜不出来。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 酒店候选查询(统一入口) | GET | `/v3/admin/hotel-candidates` | 行为变更(契约零增删) | GROUP 从「恒空 / 仅池内」改为「同城全量、池内加分置顶」;keyword 对 GROUP 生效 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 酒店候选查询(统一入口) `GET /v3/admin/hotel-candidates`
|
||||
|
||||
**VO**: `HotelCandidateRespVO`(出参;内部类 `Candidate` / `RoomTypeOption` / `Badge`)。入参 VO 为 `HotelCandidateQueryReqVO`。
|
||||
|
||||
#### 使用场景
|
||||
|
||||
管理后台订单详情 →「调整订单」→「酒店安排」Tab → 点某一晚的「选择酒店」弹窗。定制师为行程的每一晚挑若干候选酒店,交房控择一。CORE / GROUP / CUSTOM 三种产品类型共用本端点,服务端按订单的 `productType` 自行分流排序策略,前端不需要区分。
|
||||
|
||||
#### 入参
|
||||
|
||||
本次**未增删任何参数**,列全供对照:
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| `orderId` | query | Long | 是 | — | 订单 ID |
|
||||
| `dayNumber` | query | Integer | 否 | ≥1 | 第几天,用于推算 `stayDate = departDate + dayNumber - 1` |
|
||||
| `stayDate` | query | LocalDate | 否 | `yyyy-MM-dd` | 入住日期,直接指定时优先于 `dayNumber` |
|
||||
| `city` | query | String | 否 | — | 城市代码 |
|
||||
| `keyword` | query | String | 否 | — | 关键词;非空时跨城 / 省搜(**本次起对 GROUP 生效**),匹配酒店名 / 城市 / 省份 / 地址 |
|
||||
| `limit` | query | Integer | 否 | 1–50 | 返回条数上限,默认 30 |
|
||||
| `roomCategory` | query | String | 否 | — | 房型大类字典 code。⚠️ 当前**不参与房型级过滤**,仅作 `matchedRoomTypeLabel` 展示标签 |
|
||||
| `roomCount` | query | Integer | 否 | ≥1 | 需要的房间数,决定 matched 房型的可用数阈值 |
|
||||
| `preferredHotelId` | query | Long | 否 | — | 定制师指定的优先酒店 ID |
|
||||
| `requirementId` | query | Long | 否 | — | 用房需求 ID |
|
||||
|
||||
#### 出参 `Result<HotelCandidateRespVO>`
|
||||
|
||||
本次**未增删任何字段**,仅部分字段取值域变化(见「⚠️ 关键变化」与「六.6」)。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `stayDate` | LocalDate | 入住日期 |
|
||||
| `city` | String | 城市代码,可为 null |
|
||||
| `productType` | String | `CORE` / `GROUP` / `CUSTOM` |
|
||||
| `candidates[]` | Candidate[] | 候选酒店列表,已按 `score` 降序 |
|
||||
| `candidates[].hotelId` | Long(字符串序列化) | 酒店 ID |
|
||||
| `candidates[].hotelName` | String | 酒店名称 |
|
||||
| `candidates[].city` / `.district` | String | 所在城市 / 区县 |
|
||||
| `candidates[].protoPrice` | BigDecimal(字符串) | 协议价,无当日记录返 null |
|
||||
| `candidates[].settlementPrice` | BigDecimal(字符串) | 结算价,未维护返 null |
|
||||
| `candidates[].todayAvailable` | Integer | 当日可用房数(全房型合计),无快照返 null |
|
||||
| `candidates[].roomTypes[]` | RoomTypeOption[] | 当日真实房型列表(含每房型协议价 / 可用数 / 库存状态) |
|
||||
| `candidates[].isPoolMatch` | Boolean | 是否在产品固定房池内。**GROUP 改后可为 false** |
|
||||
| `candidates[].poolMatchBadge` | Badge | 池内徽章。**GROUP 改后池外为 null** |
|
||||
| `candidates[].isConsultantRecommended` | Boolean | 是否被定制师点名 |
|
||||
| `candidates[].recommended` | Boolean | 是否标记为推荐。**GROUP 改后可为 false** |
|
||||
| `candidates[].recommendSource` | String | `CONSULTANT` / `PRODUCT_POOL` / **null(GROUP 池外普通候选)** |
|
||||
| `candidates[].score` | Double | 排序分,仅供调试;前端按数组顺序展示即可 |
|
||||
| `candidates[].recommendation` | String | 推荐语,GROUP 池内为 `跟团池内`、池外为 `池外可售` |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/hotel-candidates?orderId=2100856430239121409&dayNumber=1&stayDate=2026-10-08&limit=50&roomCount=1
|
||||
Host: api.test.1814.love:9443
|
||||
Authorization: Bearer {token}
|
||||
```
|
||||
|
||||
跨城搜(本次起对 GROUP 生效):
|
||||
|
||||
```http
|
||||
GET /v3/admin/hotel-candidates?orderId=2100856430239121409&dayNumber=1&stayDate=2026-10-08&limit=50&keyword=%E6%B5%B7%E6%8B%89%E5%B0%94
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"stayDate": "2026-10-08",
|
||||
"city": null,
|
||||
"productType": "GROUP",
|
||||
"candidates": [
|
||||
{
|
||||
"hotelId": "2023714929877450753",
|
||||
"hotelName": "呼伦贝尔香格里拉大酒店",
|
||||
"city": "呼伦贝尔市",
|
||||
"district": "满洲里市",
|
||||
"protoPrice": "280.00",
|
||||
"settlementPrice": "279.00",
|
||||
"todayAvailable": 70,
|
||||
"isPoolMatch": true,
|
||||
"poolMatchBadge": { "label": "产品池内", "color": "blue", "tooltip": "本酒店在跟团产品固定房池内" },
|
||||
"isConsultantRecommended": false,
|
||||
"recommended": true,
|
||||
"recommendSource": "PRODUCT_POOL",
|
||||
"score": 1070.0,
|
||||
"recommendation": "跟团池内"
|
||||
},
|
||||
{
|
||||
"hotelId": "3001000000000000002",
|
||||
"hotelName": "海拉尔海棠酒店",
|
||||
"city": "呼伦贝尔市",
|
||||
"district": "海拉尔区",
|
||||
"protoPrice": "320.00",
|
||||
"todayAvailable": 15,
|
||||
"isPoolMatch": false,
|
||||
"poolMatchBadge": null,
|
||||
"isConsultantRecommended": false,
|
||||
"recommended": false,
|
||||
"recommendSource": null,
|
||||
"score": 15.0,
|
||||
"recommendation": "池外可售"
|
||||
}
|
||||
]
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
- 仍返回 HTTP 200 + `code=200`,`candidates` 为**空数组**,不是 null。
|
||||
- **产品固定房池为空不再导致返空**(改前 GROUP 池为空时直接返空且不发起查询)。
|
||||
- 未传 `stayDate` 且无法由 `dayNumber` 推算时,`candidates` 为空数组——这是既有行为、本次未改;前端始终传 `dayNumber` 即可。
|
||||
|
||||
```json
|
||||
{ "code": 200, "message": "成功", "data": { "stayDate": null, "city": null, "productType": "GROUP", "candidates": [] }, "success": true }
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
本端点**无新增错误码**,本次也未改动任何错误码。业务性失败一律 HTTP 200 + 空 `candidates`。鉴权失败由网关返回,形如:
|
||||
|
||||
```json
|
||||
{ "code": 401, "message": "用户名或密码错误", "data": null, "success": false }
|
||||
```
|
||||
|
||||
⚠️ 网关对失效 token 返回的是 **HTTP 200 + body `code=401`**,不是 HTTP 401——前端与取证脚本都必须读 body 的 `code`,不能只看状态码。
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 池内酒店**恒排在池外之前**,由评分结构保证(池内加分 1000 > 可用房分上限 999),不受可用房数影响。
|
||||
- `limit` 截断发生在排序**之后**,因此池内酒店不会被截掉。
|
||||
- 关键词模式下池内酒店**不做强制保留**(不命中关键词就不出现)。
|
||||
- `todayAvailable` 为 null 时可用房分记 0,该候选排在同组最后,但仍会展示。
|
||||
- CORE / CUSTOM 产品行为完全不变。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式(接口类必写)
|
||||
|
||||
### ✅ 正确 / ❌ 错误 payload 对照
|
||||
|
||||
| | 调用 | 说明 |
|
||||
|---|------|------|
|
||||
| ✅ | `?orderId=...&dayNumber=1&stayDate=2026-10-08&limit=50` | 推荐:同时给 `dayNumber` 与 `stayDate`,服务端以 `stayDate` 为准 |
|
||||
| ✅ | `?orderId=...&dayNumber=1&stayDate=2026-10-08&keyword=海拉尔` | 跨城搜;本次起对 GROUP 生效 |
|
||||
| ❌ | `?orderId=...`(不带 `dayNumber` 也不带 `stayDate`) | 推不出入住日期,返回空数组,不报错——**容易被误读为「没有酒店」** |
|
||||
| ❌ | `?orderId=...&limit=100` | `limit` 上限 50,超出被参数校验拒绝 |
|
||||
|
||||
### 切换状态时的必要动作
|
||||
|
||||
本端点是**只读查询**,不涉及状态切换,调用方无需在调用前后做任何状态写入。选定候选酒店后的写入走既有的用房需求保存接口,本次未改。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为(涉及写操作时必写)
|
||||
|
||||
**无写操作。** 本端点只读;本次变更**无表结构变更、无 Flyway 迁移、无 H2 schema 变更**。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
| 场景 | 行为 |
|
||||
|------|------|
|
||||
| 产品固定房池为空 | 按城市正常返回候选(改前直接返空) |
|
||||
| 城市取不到 | 不返空,查全部在售酒店 |
|
||||
| 同城可售酒店数 > `limit` | 截断在排序之后取前缀,池内酒店因分数 ≥1000 恒在头部、不会被截掉 |
|
||||
| `todayAvailable` 为 null | 可用房分记 0,候选仍展示 |
|
||||
| 关键词不命中任何池内酒店 | 结果中可以完全没有池内酒店(见「已知取舍」) |
|
||||
| 资源库存在同名不同 ID 的酒店 | 按 `hotelId` 唯一、结果无重复,但界面上会看到两个同名条目(数据侧问题,非本次引入) |
|
||||
|
||||
---
|
||||
|
||||
## 六.5、枚举 / 数据字典(接口出现枚举时必写)
|
||||
|
||||
### `recommendSource`(`com.hulalv.hotelcandidate.ranker.RecommendSource`)
|
||||
|
||||
| 取值 | 含义 | GROUP 改前 | GROUP 改后 |
|
||||
|------|------|-----------|-----------|
|
||||
| `CONSULTANT` | 定制师点名 | 可能 | 可能 |
|
||||
| `PRODUCT_POOL` | 产品固定房池内 | 可能 | 可能(池内) |
|
||||
| `null` | 普通候选,两者都不是 | **不可能** | **可能(池外普通候选)** |
|
||||
|
||||
### `productType`(`com.hulalv.order.core.enums.ProductType`)
|
||||
|
||||
| 取值 | 含义 |
|
||||
|------|------|
|
||||
| `CORE` | 核心订单 |
|
||||
| `GROUP` | 团期(跟团)订单——本次变更仅影响该类型 |
|
||||
| `CUSTOM` | 定制订单 |
|
||||
|
||||
---
|
||||
|
||||
## 六.6、修改前后对比(修改/删除类接口必写,新增跳过)
|
||||
|
||||
### 字段级对比
|
||||
|
||||
| 字段 | 改前(GROUP) | 改后(GROUP) | 是否破坏性 |
|
||||
|------|--------------|--------------|-----------|
|
||||
| `isPoolMatch` | 恒 `true` | `true` / `false` | 取值域扩大,前端若假设恒真则破坏 |
|
||||
| `poolMatchBadge` | 恒非 null | 池内非 null / 池外 `null` | 同上 |
|
||||
| `recommended` | 恒 `true` | `true` / `false` | 同上 |
|
||||
| `recommendSource` | 恒非 null | 池外普通候选为 `null` | 同上 |
|
||||
| `recommendation` | 恒 `跟团池内 · 剩余 N 间` 或 `跟团池内` | 池内 `跟团池内` / 池外 `池外可售` | 文案变化,非结构变化 |
|
||||
| 其余全部字段 | — | — | 无变化 |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 维度 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| GROUP 候选可见性 | 恒空(#7991 前)→ 仅产品固定房池内(#7991 后) | 同城可售全量,池内 + 池外 |
|
||||
| 产品房池的作用 | 硬门,池外一律不展示 | 加分项,池内恒排最前 + 带徽章 |
|
||||
| GROUP 的 `keyword` | 被整个忽略 | 跨城 / 省搜生效,与 CORE 一致 |
|
||||
| 产品房池为空 | 直接返空、不发起查询 | 按城市正常返回候选 |
|
||||
| CORE / CUSTOM | — | 完全不变 |
|
||||
|
||||
---
|
||||
|
||||
## 六.7、影响评估(修改/删除类必写)
|
||||
|
||||
| 消费方 | 影响 | 需要动作 |
|
||||
|--------|------|----------|
|
||||
| 管理后台「选择酒店」弹窗(hl-ui,mmg) | 结果集变大;GROUP 出现 `isPoolMatch=false` 的条目 | **核查**是否对 GROUP 写过「恒为池内」的假设;徽章渲染若与 CORE 共用则无需改 |
|
||||
| 小程序 | 不调用本端点 | 无 |
|
||||
| 其它后端服务 | 本端点无内部调用方(`/v3/admin/` 前缀,仅管理后台) | 无 |
|
||||
| 数据 / 报表 | 本端点只读、不落库 | 无 |
|
||||
|
||||
**回滚方式**:revert PR #8021 即回到「仅池内」;revert PR #8014 会回到「恒空」的缺陷态,不建议单独回滚。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围(显式声明, 帮前端/QA 缩小排查面)
|
||||
|
||||
- **CORE / CUSTOM 产品的候选行为零变化**。本次对 `CoreCandidateRanker` 的唯一改动是把私有方法 `resolveRecommendSource` 上提为父类共用方法,逻辑逐行未改;`CandidateConsultantChoiceRankTest`(覆盖 CORE 与定制师主 / 副选排序)7 条用例全绿可证。
|
||||
- **接口契约零增删**:请求参数、响应字段、错误码均未增删改。
|
||||
- **无表结构变更、无 Flyway、无网关路由改动**。
|
||||
- **不影响选定候选之后的写入链路**(用房需求保存、房控配房、配房审核等均未改动)。
|
||||
- **不影响 `batchRoomRemain` 的语义**:该字段仍未接线、仍恒为 null,本次未新增写口;批次剩余房数的真实接线与售罄过滤是后续工单。
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
**环境**:测试服 `192.168.100.236`,hl-order-service-v3 部署于 `dev-v3 @7d44ca268`(2026-09-20 13:36 滚动部署,双实例 8086 / 8186 均 UP,`deploy-status.sh` 报 `BEHIND 0/N`、`STATE=ok`)。调用经网关 `api.test.1814.love:9443`,使用独立取证车道账号,未使用共用 admin 账号。
|
||||
|
||||
**部署字节核对**:`git merge-base --is-ancestor 7d44ca268 <deploy-status 报的 sha>` 通过——跑的确实是本次改动的字节,而非「已合并但未部署」。
|
||||
|
||||
**实测读数**(订单 `2100856430239121409`,团期,出行 2026-10-08,3 天 2 晚):
|
||||
|
||||
| 入参 | 候选数 | 首条 | body `code` | 说明 |
|
||||
|------|--------|------|------------|------|
|
||||
| `dayNumber=1&stayDate=2026-10-08&limit=50` | **21** | 呼伦贝尔香格里拉大酒店(`isPoolMatch=true`) | 200 | 改前为 1 |
|
||||
| 同上 + `keyword=酒店` | **15** | 呼伦贝尔香格里拉大酒店(`isPoolMatch=true`) | 200 | 改前为 1 |
|
||||
| 同上 + `keyword=海拉尔` | **5** | 海拉尔嘉世豪酒店(`isPoolMatch=false`) | 200 | 改前为 1;池内不命中关键词故不在列表 |
|
||||
|
||||
三次结果内 `hotelId` 均无重复;`recommendation` 中「剩余 null 间」零条目。
|
||||
|
||||
**单元测试**:`mvn -o -pl hl-order-service-v3 -am -Dtest='GroupCandidateRankerTest,CandidateConsultantChoiceRankTest,HotelCandidateLoaderTest,HotelCandidateServiceTest' test` = **Tests run 75 / Failures 0 / Errors 0 / BUILD SUCCESS**。含「池外 5000 间压不过池内 1 间」「limit=3 时 35 家池外不能把唯一池内候选挤掉」两条针对性用例。
|
||||
|
||||
⚠️ **尚未完成**:order-v3 **全量**单测 + ArchTest 门禁(工单 #7991 AC-4 / #8016 AC-8)因本机内存被多会话占满,今日三次尝试均挂在内存(堆溢出 / 无法启动 fork),尚未取得一次完整 BUILD SUCCESS,**故两张工单均未关闭、对应验收项如实未勾**。本节只声明已实测的部分,不以「跑过的部分都是绿的」替代全量结论。
|
||||
|
||||
---
|
||||
|
||||
## 九、相关历史 PR(纠错 / 功能演进时必写)
|
||||
|
||||
| PR | 工单 | 内容 |
|
||||
|----|------|------|
|
||||
| #8014 | #7991 | 修复 GROUP 候选恒空:去掉以未接线字段 `batchRoomRemain` 做的硬过滤 |
|
||||
| #8021 | #8016 | 放开池外:产品固定房池由硬门降级为加分项 |
|
||||
|
||||
被本次推翻 / 修正的既有约定:
|
||||
|
||||
- 工单 **#3217**「GROUP 仅展示产品池内固定房」——本次推翻。
|
||||
- 工单 **#4047** 定义的 `keyword` 跨城语义——此前对 GROUP 不生效,本次兑现。
|
||||
- 工单 **#4364** 的「返回条数上限由 `HotelCandidateService.truncate` 单源管控」——本次沿用,未在排序器内另加截断。
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 工单 #7991(团期选择酒店候选恒空)、#8016(团期候选酒店放开池外)
|
||||
- 接口源码:`hl-order-service-v3/src/main/java/com/hulalv/hotelcandidate/`(`controller/admin/HotelCandidateAdminController`、`service/HotelCandidateLoader`、`ranker/GroupCandidateRanker`)
|
||||
- 既有约束出处:工单 #3217、#4047、#4364
|
||||
|
||||
---
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- Issue #7991: https://git.1814.love:8443/wx/HL/issues/7991
|
||||
- Issue #8016: https://git.1814.love:8443/wx/HL/issues/8016
|
||||
- PR #8014: https://git.1814.love:8443/wx/HL/pulls/8014
|
||||
- PR #8021: https://git.1814.love:8443/wx/HL/pulls/8021
|
||||
|
||||
### 联系人
|
||||
|
||||
- 后端:wx(hl-order-service-v3)
|
||||
- 前端:mmg(hl-ui 管理后台「选择酒店」弹窗)
|
||||
在新工单中引用
屏蔽一个用户