--- frontend_status: "implemented" frontend_owner: "hl-ui-codex" frontend_ref: "mmg/hl-ui@adff10ea74198f4e89a1488e631463bedbcd4eea" updated_at: "2026-07-25T03:37:10.934Z" --- # 【修改接口·管理后台】酒店候选补齐房型结算价 (#5237) > **PR**: #5240 | **服务**: hl-order-service-v3 | **更新时间**: 2026-07-25 10:58 ## 1. 接口背景 管理后台酒店候选列表原来只在候选酒店顶层返回 `protoPrice`,前端无法确认这个价格来自哪个真实房型,也拿不到同一房型同一天的结算价。配房时如果只看房型列表或自行匹配最低价,容易把协议价和结算价口径拆到不同房型。 本次在候选酒店顶层补齐: - `protoPriceRoomTypeId`:产生顶层 `protoPrice` 的真实房型 ID。 - `settlementPrice`:与 `protoPriceRoomTypeId` 同一房型、同一天的结算价。 顶层 `protoPrice`、`protoPriceRoomTypeId`、`settlementPrice` 是同一代表房型口径。未维护结算价时 `settlementPrice = null`,不会用协议价兜底。 ## 2. 变更清单 | # | 接口 | 方法 | 路径 | 变更类型 | 说明 | |---|------|------|------|----------|------| | 1 | 查询酒店候选(4 场景统一入口) | GET | `/v3/admin/hotel-candidates` | 修改接口 | 候选酒店项新增 `protoPriceRoomTypeId`、`settlementPrice` 两个出参字段;入参不变。 | ## 3. 接口详情 ### 3.1 查询酒店候选(4 场景统一入口) - **使用场景**:管理后台在订单维度查询某一晚的候选酒店,用于配房选酒店、回显当前已配酒店、按产品池/定制师点名/资源库候选排序。 - **认证**:需要管理后台 JWT。 - **幂等性**:只读查询,幂等。 - **限流**:无接口级特殊限流;受网关与服务通用限流策略约束。 ## 4. 接口入参 ### 4.1 路径参数 / Query 参数 | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `orderId` | String | 是 | 订单 ID。后端 Long,JSON/Query 建议按字符串传,避免长 ID 精度问题。 | | `dayNumber` | Integer | 否 | 第几天,从 1 开始;用于推算 `stayDate = departDate + dayNumber - 1`。最小值 1。 | | `stayDate` | String | 否 | 入住日期,格式 `yyyy-MM-dd`;直接指定时优先于 `dayNumber` 推算。 | | `city` | String | 否 | 城市代码或城市名;未传且非关键词模式时默认不按城市限制。 | | `keyword` | String | 否 | 关键词;非空时跨城/省匹配酒店名、城市、省份、地址,此时 `city` 可不传。 | | `limit` | Integer | 否 | 返回候选条数上限,默认 30,最小 1,最大 50。 | | `roomCategory` | String | 否 | 房型字典 code。 | | `roomCount` | Integer | 否 | 需要的房间数;最小 1。 | | `preferredHotelId` | String | 否 | 定制师指定的优先酒店 ID。后端 Long,建议字符串传。 | | `requirementId` | String | 否 | 用房需求 ID;传入后将该需求 days JSON 中当前天的酒店候选作为定制师指定候选。后端 Long,建议字符串传。 | ### 4.2 请求体字段 GET 接口无请求体。 ## 5. 出参字段 统一响应结构: | 字段 | 类型 | 说明 | |------|------|------| | `code` | Integer | 业务状态码,成功为 `200`。 | | `message` | String | 响应消息,成功为 `成功`。 | | `data` | Object | 酒店候选查询出参。 | | `traceId` | String | 链路追踪 ID,可能为空。 | | `success` | Boolean | `code == 200` 时为 `true`。 | `data` 字段: | 字段 | 类型 | 说明 | |------|------|------| | `stayDate` | String | 入住日期,格式 `yyyy-MM-dd`。 | | `city` | String / null | 本次查询使用的城市;关键词模式或默认不限城市时可为 `null`。 | | `productType` | String | 产品类型:`CORE` / `GROUP` / `CUSTOM`。 | | `candidates` | Array | 候选酒店列表,已按产品类型分流排序。 | `data.candidates[]` 字段: | 字段 | 类型 | 说明 | |------|------|------| | `hotelId` | String | 酒店 ID。 | | `hotelName` | String | 酒店名称。 | | `level` | String / null | 酒店等级。 | | `form` | String / null | 住宿形态。 | | `address` | String / null | 地址。 | | `tags` | Array | 运营标签;无标签时为空数组或 `null`。 | | `contactPerson` | String / null | 联系人。 | | `contactWechat` | String / null | 联系微信。 | | `settleType` | String / null | 结算类型,取值见 §6.1。 | | `city` | String / null | 酒店所在城市。 | | `district` | String / null | 酒店所在区/县。 | | `roomTypes` | Array | 该酒店当日真实房型列表;无房型数据时为空数组。 | | `protoPrice` | String / null | 代表房型协议价。与 `protoPriceRoomTypeId`、顶层 `settlementPrice` 同一房型同一天。 | | `protoPriceRoomTypeId` | String / null | 产生顶层 `protoPrice` 的真实房型 ID。无有效可售协议价时为 `null`。 | | `settlementPrice` | String / null | 与 `protoPriceRoomTypeId` 同一房型、同一天的结算价。未维护时为 `null`,不会用 `protoPrice` 兜底。 | | `todayAvailable` | Integer / null | 今日全房型可用房数合计。 | | `availFreshness` | String / null | 可用数数据时效:`fresh` / `stale` / `never_checked`。 | | `lastCheckedAt` | String / null | 最近一次核房时间,格式 `yyyy-MM-dd'T'HH:mm:ss`。 | | `matchedRoomTypeAvailable` | Integer / null | 匹配房型今日可用数。 | | `matchedRoomTypeId` | String / null | 匹配的房型 ID。 | | `matchedRoomTypeLabel` | String / null | 匹配的房型中文。 | | `quickPickEnabled` | Boolean / null | 是否支持快速配房。 | | `quickPickDisabledReason` | String / null | 置灰原因。 | | `isPoolMatch` | Boolean / null | 是否产品池内。 | | `poolMatchBadge` | Object / null | 产品池内徽章。 | | `isConsultantRecommended` | Boolean / null | 是否被定制师点名。 | | `consultantRecommendBadge` | Object / null | 定制师点名徽章。 | | `historyMatchScore` | Number / null | 历史匹配度,范围 0-1。 | | `score` | Number / null | 排序分数。 | | `recommendation` | String / null | 推荐理由。 | | `recommended` | Boolean / null | 是否为推荐候选。 | | `recommendSource` | String / null | 推荐来源,见 §6.4。 | | `historyScoreStub` | Boolean / null | 历史命中分数是否为 stub。 | | `isCurrentlyAssigned` | Boolean / null | 是否为本天当前已配酒店。 | | `assignedRoomTypeId` | String / null | 本天当前已配的房型 ID;`isCurrentlyAssigned=true` 时可用于预填原房型。 | `data.candidates[].roomTypes[]` 字段: | 字段 | 类型 | 说明 | |------|------|------| | `roomTypeId` | String | 房型 ID。 | | `name` | String / null | 房型名称。 | | `roomCategory` | String / null | 房型分类字典值。 | | `bedType` | String / null | 床型,已按字典尽量翻译;字典缺失时可回退为 code。 | | `maxOccupancy` | Integer / null | 最大入住人数。 | | `available` | Integer / null | 今日可用房数;`unlimited=true` 时为 `null`,语义为不限。 | | `unlimited` | Boolean | 是否不限库存。 | | `stock` | Integer / null | 当前可用房;`unlimited=true` 时为 `null`。 | | `protocolPrice` | String / null | 该房型当日协议价。 | | `settlementPrice` | String / null | 该房型当日结算价。 | | `basePrice` | String / null | 标价/挂牌价。 | | `inventoryStatus` | String | 库存状态,见 §6.2。 | 徽章对象字段: | 字段 | 类型 | 说明 | |------|------|------| | `label` | String | 中文徽章文字。 | | `color` | String | 徽章色,见 §6.5。 | | `tooltip` | String | 悬浮提示。 | ## 6. 枚举 / 数据字典 ### 6.1 `settleType` **所属字段**:`data.candidates[].settleType` | **类型**:String | **必填**:否 | 值 | 中文 | 说明 | |----|------|------| | `cash` | 现付 | 到店或线下现金类结算。 | | `sign` | 签单 | 供应商签单结算。 | | `company` | 公司付 | 公司统一付款结算。 | ### 6.2 `inventoryStatus` **所属字段**:`data.candidates[].roomTypes[].inventoryStatus` | **类型**:String | **必填**:是 | 值 | 中文 | 说明 | |----|------|------| | `AVAILABLE` | 可售 | 有余量,或 `unlimited=true` 不限库存。 | | `FULL` | 满房 | 有日历记录,但库存为 0。 | | `CLOSED` | 未开放 | 无该日价格日历记录。 | ### 6.3 `availFreshness` **所属字段**:`data.candidates[].availFreshness` | **类型**:String | **必填**:否 | 值 | 中文 | 说明 | |----|------|------| | `fresh` | 最新 | 可用于快速配房判断。 | | `stale` | 过期 | 核房数据过期。 | | `never_checked` | 从未核房 | 无可用核房数据。 | ### 6.4 `recommendSource` **所属字段**:`data.candidates[].recommendSource` | **类型**:String | **必填**:否 | 值 | 中文 | 说明 | |----|------|------| | `PRODUCT_POOL` | 产品池 | 来自产品池候选。 | | `CONSULTANT` | 定制师点名 | 来自定制师指定候选。 | | `RESOURCE_LIB` | 资源库 | 来自资源库候选。 | ### 6.5 `Badge.color` **所属字段**:`poolMatchBadge.color` / `consultantRecommendBadge.color` | **类型**:String | **必填**:否 | 值 | 中文 | 说明 | |----|------|------| | `blue` | 蓝色 | 普通推荐或池内标识。 | | `gold` | 金色 | 高优先级推荐标识。 | | `gray` | 灰色 | 弱提示标识。 | ## 7. 错误码 | code | 含义 | 触发场景 | |------|------|----------| | `200` | 成功 | 查询成功。 | | `400` | 参数错误 | `orderId` 为空、`dayNumber < 1`、`limit` 超出 1-50、`roomCount < 1`、日期格式不是 `yyyy-MM-dd` 等参数绑定或校验失败。 | | `401` | 未认证 | JWT 缺失或无效。 | | `403` | 无权限 | 当前账号无权访问该管理后台接口或订单数据。 | | `581007` | 订单不存在 | `orderId` 对应订单不存在。 | | `500` | 服务内部错误 | 非预期异常。 | ## 8. 示例(3 组:典型 / 边界 / 异常) ### 8.1 典型成功 **请求**: ```http GET /v3/admin/hotel-candidates?orderId=100001&dayNumber=1&stayDate=2026-07-25&limit=30&roomCount=2 HTTP/1.1 Authorization: Bearer ``` 无请求体。 **响应**: ```json { "code": 200, "message": "成功", "data": { "stayDate": "2026-07-25", "city": null, "productType": "CORE", "candidates": [ { "hotelId": "2023714929877450753", "hotelName": "测试酒店", "level": "舒适型", "form": "HOTEL", "address": "呼伦贝尔市海拉尔区测试路 1 号", "tags": ["协议酒店"], "contactPerson": "张经理", "contactWechat": "hotel_mgr", "settleType": "sign", "city": "呼伦贝尔市", "district": "海拉尔区", "protoPrice": "280.00", "protoPriceRoomTypeId": "2023727403196502017", "settlementPrice": "279.00", "todayAvailable": 7, "availFreshness": "fresh", "lastCheckedAt": null, "matchedRoomTypeAvailable": 7, "matchedRoomTypeId": "2023727403196502017", "matchedRoomTypeLabel": "豪华大床房", "quickPickEnabled": true, "quickPickDisabledReason": null, "isPoolMatch": true, "poolMatchBadge": { "label": "产品池内", "color": "blue", "tooltip": "本酒店在产品池内,优先推荐" }, "isConsultantRecommended": false, "consultantRecommendBadge": null, "historyMatchScore": 0.85, "score": 1185.0, "recommendation": "池内 · 历史合作 8 单成功率 95%", "recommended": true, "recommendSource": "PRODUCT_POOL", "historyScoreStub": true, "isCurrentlyAssigned": false, "assignedRoomTypeId": null, "roomTypes": [ { "roomTypeId": "2023727403196502017", "name": "豪华大床房", "roomCategory": "KING", "bedType": "大床", "maxOccupancy": 2, "available": 7, "unlimited": false, "stock": 7, "protocolPrice": "280.00", "settlementPrice": "279.00", "basePrice": "568.00", "inventoryStatus": "AVAILABLE" } ] } ] }, "traceId": "trace-20260725-0001", "success": true } ``` ### 8.2 边界情况 **场景说明**:代表房型有协议价但未维护结算价,顶层 `settlementPrice` 返回 `null`,不使用 `protoPrice` 兜底。 **请求**: ```http GET /v3/admin/hotel-candidates?orderId=100001&stayDate=2026-07-25&keyword=%E6%B5%B7%E6%8B%89%E5%B0%94&limit=1 HTTP/1.1 Authorization: Bearer ``` 无请求体。 **响应**: ```json { "code": 200, "message": "成功", "data": { "stayDate": "2026-07-25", "city": null, "productType": "CUSTOM", "candidates": [ { "hotelId": "2023714929877450753", "hotelName": "测试酒店", "settleType": "cash", "protoPrice": "280.00", "protoPriceRoomTypeId": "2023727403196502017", "settlementPrice": null, "roomTypes": [ { "roomTypeId": "2023727403196502017", "name": "豪华大床房", "available": 7, "unlimited": false, "protocolPrice": "280.00", "settlementPrice": null, "basePrice": "568.00", "inventoryStatus": "AVAILABLE" } ], "quickPickEnabled": true, "recommended": true, "recommendSource": "RESOURCE_LIB" } ] }, "traceId": "trace-20260725-0002", "success": true } ``` ### 8.3 业务失败(异常) **场景说明**:`orderId` 未传,触发参数校验失败。 **请求**: ```http GET /v3/admin/hotel-candidates?stayDate=2026-07-25 HTTP/1.1 Authorization: Bearer ``` 无请求体。 **响应**: ```json { "code": 400, "message": "orderId 不能为空", "data": null, "traceId": "trace-20260725-0003", "success": false } ``` ## 9. 业务边界 - **适用场景**:管理后台按订单和入住日查询酒店候选;`stayDate` 可直接传,也可通过 `dayNumber` 和订单出发日推算。 - **不适用场景**:不用于前端直接查询内部资源服务;本文只描述管理后台 `/v3/admin/hotel-candidates`。 - **特殊边界**:顶层 `protoPrice`、`protoPriceRoomTypeId`、`settlementPrice` 必须按同一代表房型理解;`settlementPrice = null` 表示该代表房型当天未维护结算价。 - **特殊边界**:`roomTypes[].settlementPrice` 是每个房型自己的当日结算价;顶层 `settlementPrice` 只对应 `protoPriceRoomTypeId` 指向的代表房型。 ## 10. 修改前后对比 ### 10.1 字段级对比 | 字段 | 改前 | 改后 | |------|------|------| | `data.candidates[].protoPriceRoomTypeId` | 不返回 | 返回产生顶层 `protoPrice` 的真实房型 ID;无有效可售协议价为 `null`。 | | `data.candidates[].settlementPrice` | 不返回 | 返回与 `protoPriceRoomTypeId` 同一房型、同一天的结算价;未维护为 `null`。 | | `data.candidates[].protoPrice` | 已返回,但无法判断来自哪个房型 | 仍返回原字段,并与新增的 `protoPriceRoomTypeId`、顶层 `settlementPrice` 组成同一代表房型口径。 | ### 10.2 行为级对比 | 行为 | 改前 | 改后 | |------|------|------| | 候选酒店顶层价格展示 | 只能拿到代表协议价 `protoPrice`。 | 可同时拿到代表协议价、代表房型 ID、该代表房型结算价。 | | 结算价为空 | 顶层没有结算价字段。 | 顶层 `settlementPrice` 返回 `null`;不使用 `protoPrice` 兜底。 | ## 11. 影响评估 / 回滚 ### 11.1 影响评估 - **是否破坏向后兼容**:否。只新增出参字段,已有字段名、类型、入参不变。 - **前端是否必须同步上线**:否。老前端可忽略新增字段;需要展示或回填结算价的页面可读取新增字段。 - **影响已有数据**:无数据迁移要求;历史未维护结算价的房型按 `settlementPrice = null` 返回。 ### 11.2 回滚方案 - **回滚方式**:回滚 PR #5240 后,顶层新增字段不再返回。 - **回滚后清理**:无前端数据清理要求。 - **回滚耗时**:按常规服务回滚流程处理。 ## 12. 注意事项 - 前端读取顶层 `settlementPrice` 时,不要把 `null` 当作 `protoPrice`;`null` 表示未维护结算价。 - 如需定位价格来自哪个房型,使用顶层 `protoPriceRoomTypeId` 去匹配 `roomTypes[].roomTypeId`。 - 金额和长 ID 在响应 JSON 中按字符串处理,例如 `"280.00"`、`"2023727403196502017"`。 ## 13. 关联 / 联系人 ### 13.1 链接 - **Issue**: [#5237](https://git.1814.love:8443/wx/HL/issues/5237) - **PR**: [#5240](https://git.1814.love:8443/wx/HL/pulls/5240) - **Merge commit**: [dc6e2ef](https://git.1814.love:8443/wx/HL/commit/dc6e2ef6c2b49bd503353814f85723566d4413c6) ### 13.2 联系人 - **后端负责人**: @yst