@@ -0,0 +1,331 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8818"
|
||||
title: "团期核单酒店候选查询(无 orderId,住宿行选酒店带协议价/结算价/库存)"
|
||||
consumer: "admin"
|
||||
author: "yst"
|
||||
change_type: "新增接口"
|
||||
backend_status: "merged"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "pending"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: "v2.1"
|
||||
verified_at: ""
|
||||
status_note: "后端 PR #8819 已合并 dev-v3(squash 09f9dc8c4c),并已部署测试服、网关实调验证新端点生效(401 鉴权,非 404)。无 DDL、不动现有 /v3/admin/hotel-candidates 配房接口。"
|
||||
updated_at: "2026-10-10"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 团期核单酒店候选查询(无 orderId)
|
||||
|
||||
> 面向:管理后台前端(hl-admin)
|
||||
> 日期:2026-10-10 | 后端:hl-order-service-v3(端口 8086)
|
||||
> 范围:**纯新增**一个酒店候选查询接口,供团期核单「住宿(HOTEL)」明细行选酒店/房型下拉使用。
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
团期核单录入住宿明细行时,前端需要「选酒店 / 选房型的下拉候选」,选中后带出酒店名、房型名、协议价(作为单价)等快照值填进核单行。
|
||||
|
||||
现有的酒店候选接口 `GET /v3/admin/hotel-candidates` **必须传 orderId**,它服务的是「给某个订单的某一天配房」场景,返回里带产品池徽章、定制师点名、已配回显、快速配房等配房专属逻辑,且日期可由订单推算。核单站在**团 / 资源视角**选房,**没有也不该伪造 orderId**,无法复用该接口。
|
||||
|
||||
因此新增一个**无需 orderId** 的核单酒店候选接口:只按「入住日期 + 城市/关键词 + 需要房数」查在售酒店及当日房型价格库存,数据与配房候选同源(同一资源价格日历),但**不含任何配房个性化字段**。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 团期核单酒店候选查询 | GET | `/v3/admin/order/group-batch/hotel-candidates` | 新增 | 无 orderId,按入住日查在售酒店 + 当日房型价格库存 |
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 团期核单酒店候选查询
|
||||
|
||||
- **使用场景**:团期核单录入住宿(HOTEL)明细行时,前端弹层/下拉加载可选酒店列表,选中某酒店某房型后取快照值(酒店名、房型名、协议价作单价)填行。
|
||||
- **认证**:需要管理后台 JWT(`/v3/admin/*` 走网关鉴权),需具备团期查看权限(`group-batch:view`)。
|
||||
- **幂等性**:是(只读查询,无副作用)。
|
||||
- **限流**:沿用网关统一限流,无单独 QPS 限制。
|
||||
- **HTTP 方法 / 路径**:`GET /v3/admin/order/group-batch/hotel-candidates`
|
||||
- **请求方式**:Query 参数,无请求体。
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 Query 参数
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `stayDate` | String(yyyy-MM-dd) | ✅ 是 | 入住日期。**价格 / 库存都按这一天取**(当天酒店价格日历)。缺参返回 400。 |
|
||||
| `city` | String | 否 | 城市名(前缀模糊,如「呼伦贝尔」命中「呼伦贝尔市」)。**空 = 查全部在售酒店,不限城市**。 |
|
||||
| `keyword` | String | 否 | 关键词搜索。**非空时突破单城限制**,跨城/省按关键词匹配酒店名/城市/省份/地址,此时 `city` 被忽略可不传。 |
|
||||
| `hotelIds` | Long[] | 否 | 酒店 ID 白名单(逗号分隔多个)。传入则只查这批酒店(如限定某个池子内的酒店);不传 = 不限定。 |
|
||||
| `roomCount` | Integer | 否 | 需要的房间数(≥1,默认 1)。用于过滤「够房」的房型;不传按 1 间处理。 |
|
||||
| `roomCategory` | String | 否 | 房型大类(字典 `room_category` 的 value,如 STANDARD/TWIN/KING/SUITE/FAMILY)。传入后只在同大类房型内选取匹配项;不传不限大类。 |
|
||||
| `page` | Integer | 否 | 页码,从 1 开始,默认 1。 |
|
||||
| `pageSize` | Integer | 否 | 每页条数,默认 50,**最大 200**(超过按 200 计)。 |
|
||||
|
||||
> `city` 与 `keyword` 二选一生效:`keyword` 非空时走跨地区关键词搜(忽略 `city`);`keyword` 为空时按 `city` 筛(`city` 也空 = 全部在售)。
|
||||
|
||||
## 5. 出参(响应)
|
||||
|
||||
响应为统一分页包装 `PageResult`,结构:`{ code, message, success, data: { list: [...], total, pageNo, pageSize } }`。
|
||||
`data.list` 每个元素为一家酒店候选,字段如下。
|
||||
|
||||
### 5.1 酒店候选字段(`data.list[]`)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `hotelId` | String | 酒店 ID(Long 序列化为 String,防 JS 精度丢失) |
|
||||
| `hotelName` | String | 酒店名 |
|
||||
| `starLevel` | String | 星级(如 FIVE_STAR/FOUR_STAR,字典值) |
|
||||
| `city` | String | 城市 |
|
||||
| `district` | String | 区/县 |
|
||||
| `address` | String | 详细地址 |
|
||||
| `hotelType` | String | 住宿形态(字典 `hotel_type`,见 §6.1) |
|
||||
| `tags` | String[] | 运营标签名列表(如 ["协议酒店","近景区"];无标签为空数组) |
|
||||
| `contactPerson` | String | 联系人姓名 |
|
||||
| `contactWechat` | String | 联系人微信号 |
|
||||
| `settleType` | String | 结算类型(字典 `resource_settle_type`,见 §6.2) |
|
||||
| `coverMaterialId` | String | 封面素材 ID(前端可取封面图;无则为 null) |
|
||||
| `protoPrice` | String | 酒店代表价(协议价,元/间·晚)= 当日**可订**房型里最低协议价;当天无可订协议价为 null。金额 BigDecimal 序列化为 String。 |
|
||||
| `protoPriceRoomTypeId` | String | 产生 `protoPrice` 的那个房型 ID;无有效可售协议价为 null |
|
||||
| `settlementPrice` | String | 与 `protoPriceRoomTypeId` 同一房型、同一天的结算价;未维护为 null |
|
||||
| `roomTypes` | Object[] | 该酒店**当日房型列表**(含每房型库存/价格,见 §5.2);无房型数据为空数组 |
|
||||
|
||||
### 5.2 房型字段(`data.list[].roomTypes[]`)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `roomTypeId` | String | 房型 ID |
|
||||
| `name` | String | 房型名称(如「豪华大床房」)——**核单行 `roomTypeName` 快照取这里** |
|
||||
| `description` | String | 房型描述 |
|
||||
| `maxOccupancy` | Integer | 最大入住人数 |
|
||||
| `bedType` | String | 床型(如 KING/TWIN) |
|
||||
| `roomCategory` | String | 房型大类(字典 `room_category` 的 value,如 TWIN/KING) |
|
||||
| `stock` | Integer | 当前可用房(库存);无当日价格日历记录为 null |
|
||||
| `stockUsed` | Integer | 已占用数;无记录为 null(仅记录,不参与 available 计算) |
|
||||
| `available` | Integer | 当日可订房数;`unlimited=true`(不限库存)时为 null(语义「不限」而非具体数);无记录为 null |
|
||||
| `unlimited` | Boolean | 是否不限库存(资源侧库存列留空 = 永远可售);true 时 `available=null`、`inventoryStatus=AVAILABLE`,前端展示「不限」 |
|
||||
| `protocolPrice` | String | 该房型当日**协议价**(元/间·晚)——**核单行 `unitPrice`(单价)建议取这里**;无当日记录为 null |
|
||||
| `settlementPrice` | String | 该房型当日结算价;无记录为 null |
|
||||
| `basePrice` | String | 标价/挂牌价;当日无记录时回退到房型基础价 |
|
||||
| `inventoryStatus` | String | 库存状态,见 §6.3 |
|
||||
|
||||
> **核单行快照取值建议**:选中某酒店某房型后,取 `hotelId` + `hotelName` + `roomTypes[].roomTypeId` + `roomTypes[].name`(房型名)+ `roomTypes[].protocolPrice`(单价)填入核单住宿行对应快照字段。
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### 6.1 `hotelType`(字典 `hotel_type`,酒店住宿形态)
|
||||
|
||||
**所属字段**:`data.list[].hotelType` | **类型**:`String` | **必填**:❌(可空)
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `HOTEL` | 酒店 | 标准酒店 |
|
||||
| `GUESTHOUSE` | 客栈/宾馆 | — |
|
||||
| `RESORT` | 度假村 | — |
|
||||
| `CAMP` | 营地 | — |
|
||||
| `HOMESTAY` | 民宿 | — |
|
||||
|
||||
### 6.2 `settleType`(字典 `resource_settle_type`,结算类型)
|
||||
|
||||
**所属字段**:`data.list[].settleType` | **类型**:`String` | **必填**:❌(可空)
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `cash` | 现结 | 现场现金/即时结算 |
|
||||
| `sign` | 签单 | 挂账签单,周期结算 |
|
||||
| `company` | 对公 | 对公转账结算 |
|
||||
|
||||
### 6.3 `inventoryStatus`(库存状态,资源价格日历派生)
|
||||
|
||||
**所属字段**:`data.list[].roomTypes[].inventoryStatus` | **类型**:`String` | **必填**:✅
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `AVAILABLE` | 可订 | 当日价格日历可售且有余量,或 `unlimited=true` 不限库存 |
|
||||
| `FULL` | 满房 | 当日可售但余量为 0,或当日已售罄 |
|
||||
| `CLOSED` | 关闭 | 未配置当日价格日历(漏配)、当日已关闭、或状态异常 |
|
||||
|
||||
> FULL / CLOSED 时 `available=0`、`unlimited=false`,但**价格字段照常返回**(前端可展示「满房」「未开放」并 still 显示参考价)。
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| code | 含义 | 触发场景 |
|
||||
|------|------|----------|
|
||||
| `400` | 参数校验失败 | `stayDate` 缺失(必填)、`pageSize`/`roomCount`/`page` 超出范围(如 pageSize>200 之外非法值、roomCount<1) |
|
||||
| `401` | 未登录 / 缺少 Authorization 头 | 未带管理后台 JWT 或 token 过期 |
|
||||
| `200`(空列表) | 查询成功但无候选 | 当日该城市/关键词下无在售可订酒店,或资源侧暂无数据 —— **资源服务不可用时本接口降级返回空列表(不报错)**,前端据此展示「暂无可选酒店」 |
|
||||
|
||||
> 本接口是查询类,资源服务故障时**降级返回空列表**(HTTP 200 + 空 `data.list`),不会抛 5xx 阻断核单页面。前端需能区分「真的没酒店」和「服务暂不可用」(两者都返回空列表,后者可稍候重试)。
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功(按城市 + 入住日查)
|
||||
|
||||
**请求**:
|
||||
```http
|
||||
GET /v3/admin/order/group-batch/hotel-candidates?stayDate=2026-10-27&city=呼伦贝尔&roomCount=2&page=1&pageSize=20
|
||||
Authorization: Bearer {admin-token}
|
||||
(无请求体)
|
||||
```
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "ok",
|
||||
"success": true,
|
||||
"data": {
|
||||
"total": 3,
|
||||
"pageNo": 1,
|
||||
"pageSize": 20,
|
||||
"list": [
|
||||
{
|
||||
"hotelId": "70123",
|
||||
"hotelName": "海拉尔金鼎大酒店",
|
||||
"starLevel": "FOUR_STAR",
|
||||
"city": "呼伦贝尔市",
|
||||
"district": "海拉尔区",
|
||||
"address": "海拉尔区中央大街123号",
|
||||
"hotelType": "HOTEL",
|
||||
"tags": ["协议酒店", "近景区"],
|
||||
"contactPerson": "张经理",
|
||||
"contactWechat": "zhang_mgr",
|
||||
"settleType": "sign",
|
||||
"coverMaterialId": "90001",
|
||||
"protoPrice": "328.00",
|
||||
"protoPriceRoomTypeId": "80125",
|
||||
"settlementPrice": "300.00",
|
||||
"roomTypes": [
|
||||
{
|
||||
"roomTypeId": "80125",
|
||||
"name": "豪华大床房",
|
||||
"description": "含双早",
|
||||
"maxOccupancy": 2,
|
||||
"bedType": "KING",
|
||||
"roomCategory": "KING",
|
||||
"stock": 10,
|
||||
"stockUsed": 3,
|
||||
"available": 7,
|
||||
"unlimited": false,
|
||||
"protocolPrice": "328.00",
|
||||
"settlementPrice": "300.00",
|
||||
"basePrice": "568.00",
|
||||
"inventoryStatus": "AVAILABLE"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界情况(关键词跨地区搜 + 不限库存房型)
|
||||
|
||||
**场景说明**:用 `keyword` 跨城搜索;命中酒店含「不限库存」房型(`unlimited=true`、`available=null`)。
|
||||
|
||||
**请求**:
|
||||
```http
|
||||
GET /v3/admin/order/group-batch/hotel-candidates?stayDate=2026-10-27&keyword=蒙古包&roomCount=1
|
||||
Authorization: Bearer {admin-token}
|
||||
(无请求体)
|
||||
```
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "ok",
|
||||
"success": true,
|
||||
"data": {
|
||||
"total": 1,
|
||||
"pageNo": 1,
|
||||
"pageSize": 50,
|
||||
"list": [
|
||||
{
|
||||
"hotelId": "70200",
|
||||
"hotelName": "图嘎营地",
|
||||
"starLevel": null,
|
||||
"city": "陈巴尔虎旗",
|
||||
"district": null,
|
||||
"address": "呼和诺尔湖畔",
|
||||
"hotelType": "CAMP",
|
||||
"tags": [],
|
||||
"contactPerson": null,
|
||||
"contactWechat": null,
|
||||
"settleType": "cash",
|
||||
"coverMaterialId": null,
|
||||
"protoPrice": "380.00",
|
||||
"protoPriceRoomTypeId": "80210",
|
||||
"settlementPrice": "350.00",
|
||||
"roomTypes": [
|
||||
{
|
||||
"roomTypeId": "80210",
|
||||
"name": "蒙古包",
|
||||
"description": null,
|
||||
"maxOccupancy": 4,
|
||||
"bedType": null,
|
||||
"roomCategory": "YURT",
|
||||
"stock": null,
|
||||
"stockUsed": null,
|
||||
"available": null,
|
||||
"unlimited": true,
|
||||
"protocolPrice": "380.00",
|
||||
"settlementPrice": "350.00",
|
||||
"basePrice": null,
|
||||
"inventoryStatus": "AVAILABLE"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 业务失败(缺 stayDate)
|
||||
|
||||
**场景说明**:必填参数 `stayDate` 未传。
|
||||
|
||||
**请求**:
|
||||
```http
|
||||
GET /v3/admin/order/group-batch/hotel-candidates?city=呼伦贝尔
|
||||
Authorization: Bearer {admin-token}
|
||||
(无请求体)
|
||||
```
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"message": "stayDate 不能为空",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- ✅ **适用场景**:团期核单住宿行选酒店;任何「无需订单上下文、按入住日查在售酒店价格库存」的管理后台场景。
|
||||
- ✅ **价格口径**:所有价格(`protoPrice`/`settlementPrice`/`protocolPrice`/`basePrice`)都按 `stayDate` 当天的价格日历取值,**换一天价格可能不同**——必须传准确的入住日期。
|
||||
- ⚠️ **降级**:资源服务不可用时返回空列表(HTTP 200),不抛错——前端展示「暂无可选酒店」并可提示稍候重试。
|
||||
- ⚠️ **不限库存**:`unlimited=true` 的房型 `available=null`(表示「不限」),前端不要用 `available==null` 判满房,应看 `inventoryStatus`。
|
||||
- ❌ **不适用**:需要订单个性化推荐(产品池/定制师点名/已配回显/快速配房)的配房场景 → 请用 `GET /v3/admin/hotel-candidates`(需 orderId)。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- **纯新增接口**,不影响任何现有接口;`/v3/admin/hotel-candidates`(配房候选)保持原样。
|
||||
- **已部署测试服**(order-v3,squash commit `09f9dc8c4c`),前端可直接联调。
|
||||
- 所有 `hotelId`/`roomTypeId`/`protoPriceRoomTypeId`/`coverMaterialId` 为 String(Long 序列化),所有金额字段为 String(BigDecimal 序列化),前端按字符串接收即可。
|
||||
- 本接口只读、幂等,可在下拉搜索框高频调用(建议前端做防抖)。
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
### 13.1 链接
|
||||
|
||||
- **Issue**: [#8818](https://git.1814.love/wx/HL/issues/8818)
|
||||
- **PR**: [#8819](https://git.1814.love/wx/HL/pulls/8819)
|
||||
- **Merge commit**: [09f9dc8c4c](https://git.1814.love/wx/HL/commit/09f9dc8c4c)
|
||||
|
||||
### 13.2 联系人
|
||||
|
||||
- **后端负责人**: @yst
|
||||
在新工单中引用
屏蔽一个用户