From aeb5106d907397f948a2b807829cf53c2490f48d Mon Sep 17 00:00:00 2001 From: yaosutu <770858045@qq.com> Date: Sat, 10 Oct 2026 09:45:47 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=9B=A2=E6=9C=9F=E6=A0=B8?= =?UTF-8?q?=E5=8D=95=E9=85=92=E5=BA=97=E5=80=99=E9=80=89=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=EF=BC=88#8818=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ..._团期核单酒店候选查询-新增接口-管理后台.md | 331 ++++++++++++++++++ 1 file changed, 331 insertions(+) create mode 100644 changelogs-v2/2026-10/10_8818_团期核单酒店候选查询-新增接口-管理后台.md diff --git a/changelogs-v2/2026-10/10_8818_团期核单酒店候选查询-新增接口-管理后台.md b/changelogs-v2/2026-10/10_8818_团期核单酒店候选查询-新增接口-管理后台.md new file mode 100644 index 00000000..024d9127 --- /dev/null +++ b/changelogs-v2/2026-10/10_8818_团期核单酒店候选查询-新增接口-管理后台.md @@ -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