---
schema: "hl-changelog/v2"
ticket: "5846"
title: "配房候选「定制师推荐」拆分为主选/副选：新增 consultantChoiceRank + consultantChoiceLabel"
consumer: "admin"
change_type: "修改接口"
author: "wx(GIT)"
backend_status: "deployed"
gateway_status: "not_required"
frontend_status: "implemented"
frontend_owner: "mmg"
frontend_ref: "8f33923a"
target_release: ""
verified_at: "2026-08-11"
status_note: "前端已实现(2026-08-11,mmg,8f33923a)：PickHotelModal 候选归一化透传 consultantChoiceRank(1主/2副/null)+consultantChoiceLabel,酒店列标签改按 label 渲染(rank=1 warning 醒目/rank=2 default 次级/非点名不显),外层标签容器条件补 consultantChoiceLabel;兼容期旧候选未下发 label 但 isConsultantRecommended=true 时回退显「定制师推荐」。既有 isConsultantRecommended/recommendSource 行为不变。另一消费点 FunItemAdjustModal.vue 不消费推荐字段无需改。测试:新建 pick-hotel-modal.spec 3 用例(主/副选标签 type+文案、非点名不显、兼容期回退),全绿,checkpoint 精确文件集全过。"
updated_at: "2026-08-11"
base: "dev-v3"
---

# 配房候选：「定制师推荐」拆分为主选 / 副选（#5846）

> **服务**: hl-order-service-v3
> **PR**: #5863（已合并 dev-v3 并部署测试服）
> **日期**: 2026-08-11
> **背景**: 房务「配置酒店」弹窗里，定制师点名的酒店统一显示「定制师推荐」，分不出优先级。wx 转述徐莱口径：**「一个主选，其他都是副选」**——定制师需求里排第一位的候选是主选，其余全部副选。

---

## 变更接口

`GET /v3/admin/hotel-candidates` —— 候选项**新增 2 个字段**：

| 字段 | 类型 | 取值 | 说明 |
|---|---|---|---|
| `consultantChoiceRank` | `Integer` | `1` / `2` / `null` | 1=主选，2=副选，null=非定制师点名候选 |
| `consultantChoiceLabel` | `String` | `定制师主选` / `定制师副选` / `null` | 可直接渲染的中文标签 |

**既有字段 `isConsultantRecommended` / `recommended` / `recommendSource` 行为完全不变**（兼容期保留，前端切换完成后再议下线）。

### 排序保证

**主选一定排在副选之前**。后端在排序后做了专门的收口重排，确保标识与顺序不会自相矛盾（不会出现「副选排在主选上面」）。普通候选的相对位置不受影响。

### 判定规则（后端已实现，前端直接用即可）

- 主选 = 定制师需求 `days[].segments[].candidates[]` 里**每段的第一个候选**
- 一晚有多段（同晚住多家）时，**每段各有一个主选**；候选列表是按天扁平返回的，同一酒店若既是 A 段主选又是 B 段副选，按**最高优先级**标为主选
- 查询参数 `preferredHotelId` **不影响**主/副判定（它只参与置顶展示），所以房务带不同参数进来看到的主选不会变
- 定制师只提房数没选酒店的候选（无 `hotelId`）不参与标记，两个字段为 `null`

---

## 前端要做

把候选行的「定制师推荐」标签改为按 `consultantChoiceLabel` 渲染：

| `consultantChoiceRank` | 标签 | 建议样式 |
|---|---|---|
| `1` | 定制师主选 | 强调色（比现「定制师推荐」更醒目） |
| `2` | 定制师副选 | 次级色 |
| `null` | 不显示标签 | — |

直接用 `consultantChoiceLabel` 的文案即可，不需要自己根据 rank 拼中文。弹窗顶部「定制师指定酒店已置顶，仍可选择其他酒店」的提示语由前端维护，后端未改。

---

## 验证证据

2026-08-11 测试服（网关 `https://api.test.1814.love:9443`，房务管理员 token），订单 26-7666：

| 天 | 需求 days 里的候选顺序 | 候选查询实测 |
|---|---|---|
| day1 | 阿尔山成悦 → 阿尔善国际维景 | `[0]` 阿尔山成悦 `rank=1 定制师主选`、`[1]` 阿尔善国际维景 `rank=2 定制师副选` |
| day2 | 额尔古纳豪星 → 额尔古纳白桦 | `[0]` 额尔古纳豪星 `rank=1 定制师主选`、`[1]` 额尔古纳白桦 `rank=2 定制师副选` |

主选均在副选之前，其余 19 家候选 `rank=null` 未误标；标识顺序与定制师提交顺序完全一致。

单测：核心 95 全绿（含新增 `HotelCandidateConsultantChoiceTest` 9 例、`CandidateConsultantChoiceRankTest`），双向变异验证通过。全量 6515 tests / Failures 0；5 个 Errors 全在 `AdjustmentRecordMapperIT`（Testcontainers 集成测试，与本改动零调用关系），**单跑 5/5 绿**，系本机并行构建的容器资源竞争。
