--- schema: "hl-changelog/v2" ticket: "8818" title: "团期核单酒店候选查询(无 orderId,住宿行选酒店带协议价/结算价/库存)" consumer: "admin" author: "yst" change_type: "新增接口" backend_status: "merged" gateway_status: "not_required" frontend_status: "implemented" frontend_owner: "hl-admin(claude)" frontend_ref: "aaae21f86a012844b41e36141044243628a57bb1" target_release: "v2.1" verified_at: "2026-10-10" status_note: "后端 PR #8819 已合并 dev-v3(squash 09f9dc8c4c),并已部署测试服、网关实调验证新端点生效(401 鉴权,非 404)。无 DDL、不动现有 /v3/admin/hotel-candidates 配房接口。 / 住宿 tab 酒店/房型下拉有 stayDate 走 #8818 候选口,选房型按当日协议价带出单价,满房/未开放选项标状态;无日期回退管理域读口;日期后补单价为空白愈回填" 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 序列化),前端按字符串接收即可。 - 本接口只读、幂等,可在下拉搜索框高频调用(建议前端做防抖)。 ### 12.1 核单行「付款方式」取值约定(关联提示) 选中酒店后,核单住宿行保存时需填「付款方式」`paymentMethod`(保存走核单明细接口,不在本候选接口范围)。该字段约定如下,**单体核单与团期核单完全同一套**: - **走数据字典取值**:字典类型 `settlement_payment_method`,前端调字典接口取下拉选项(**不要前端硬编码三个值**)。 - **提交**:把字典的 `value` 填进 `paymentMethod`。 - **展示**:列表/详情用后端带回的 `paymentMethodName`(中文名后端已拼好,前端不必再翻译)。 字典 `settlement_payment_method` 标准值(3 个): | value | 中文 | 说明 | |-------|------|------| | `CASH_PAID` | 现金已付 | 已现金/即时付掉 | | `COMPANY_PAID` | 公司支付 | 已对公转账付掉 | | `SIGNED` | 签单 | 挂账,周期与酒店/供应商结算 | **⚠️ 与候选接口 `settleType` 的区别(勿混用)**:本候选接口带回的 `settleType`(值域 `cash`/`sign`/`company`,字典 `resource_settle_type`)是**酒店主数据上的习惯结算方式**,与核单行 `paymentMethod`(本次实际付款动作)是**两个字典、两套值**。前端可用 `settleType` 给 `paymentMethod` 做**默认推荐**(如酒店 `settleType=sign` 默认选中 `SIGNED`),但**必须把值做映射后再填,不能直接拿 `settleType` 的值塞进 `paymentMethod`**(`cash`≠`CASH_PAID`,值域不同)。 ## 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