---
schema: "hl-changelog/v2"
ticket: "5838"
title: "用房需求每晚支持「客户自订」：days[].customerSelfBooked + 错误码 582097/582098"
consumer: "admin"
change_type: "修改接口"
author: "wx(GIT)"
backend_status: "deployed"
gateway_status: "not_required"
frontend_status: "implemented"
frontend_owner: "mmg"
frontend_ref: "f3b08c5d"
target_release: ""
verified_at: "2026-08-11"
status_note: "前端已实现(2026-08-11,mmg,f3b08c5d),跨定制师/房务两端:①提交侧 FunItemAdjustModal 每晚加「客户自订」开关(打开隐藏候选区改自订说明+备注,关闭恢复候选),serialize 自订晚下发 customerSelfBooked:true+segments 空(有备注保留 1 仅备注段),buildRequirementDays/v3Adapter 双向透传标记与备注;②校验自订晚跳过指定酒店房数校验,非自订晚「至少 1 候选」拦截不变;③错误码 582097/582098 实证零额外编码(submitAdjustment silentError 仍 reject 且 error.message=后端文案,catch 透传);④房务端 orderDetailAdapter 透传标记,ItineraryPanel 空态分支渲染「客户自订」warning 色(机读 arrange 仍 pending 零改动)。进度分母/日历排除自订晚为后端口径,前端只读零改动。hotelRequirement.spec +4、FunItemAdjustModal.spec +2、orderDetailAdapter.spec +1、ItineraryPanel.spec +2,318 全绿,checkpoint 含生产构建全过。"
updated_at: "2026-08-11"
base: "dev-v3"
---

# 用房需求每晚支持「客户自订」（#5838）

> **服务**: hl-order-service-v3
> **PR**: #5872（已合并 dev-v3 并部署测试服）
> **日期**: 2026-08-11
> **背景**: 调整订单弹窗「酒店安排」里，某晚不选酒店只显示「未指定酒店」，语义是「留空由房控安排」。客人自己订了酒店的那晚没法表达，房务分不清「等我配」和「不用我配」。

---

## 变更接口

### 1. 提交侧：`days[]` 新增 `customerSelfBooked`

涉及 **提交用房需求** 与 **调整订单（hotelRequirement）** 两个入口（共用同一 `DayHotelReq` 结构）：

```json
"days": [
  { "dayNumber": 1, "segments": [ ... ] },
  { "dayNumber": 2, "customerSelfBooked": true, "segments": [] },   // 客人自己订，房务不用配
  { "dayNumber": 3, "segments": [ ... ] }
]
```

| 字段 | 类型 | 说明 |
|---|---|---|
| `customerSelfBooked` | `Boolean` | 整晚级。`true`=该晚客人自己订酒店、房务无需配房；`false`/不传=原有语义（要配） |

**校验变化**：
- `customerSelfBooked=true` 的晚 **允许 `segments` 为空**（也可只填备注，如「客人自订汉庭，勿重复订」）
- 非自订晚 **仍必须至少 1 段**，否则返回 **`582098`**「第{N}晚缺少用房需求，请填写房间需求或标记为客户自订」

> 注：该校验原是 bean 层 `@NotEmpty`（HTTP 422），因 bean 校验读不到同级 `customerSelfBooked` 无法条件化，已下沉 Service 改为业务错误码（项目 HTTP 恒 200 + 错误码口径）。**前端原本处理 422 的分支要改为处理 582098。**

### 2. 新增错误码 `582097`：已配房的晚不能改成客户自订

```
582097  第{N}晚已配房，不能改为客户自订，请先删除该晚配房
```

**判定含「询房中」**（不只已确认）：询房中同样已占用房务人力、可能已向酒店发出询问。

**为什么 fail closed**：该晚已产生配房行，可能已扣库存、已与酒店确认并产生成本。放行会让配房行变成没有需求对应的孤儿（房务侧看不到、也无从追责），库存也无人释放。要求定制师先显式删配房再改标记，让「谁在什么时候放弃了这间房」留下痕迹。

### 3. 房务侧：`GET /admin/house/orders/{orderId}` 配房行程透出标记

`itinerary[]`（配房行程 Tab）每晚新增：

| 字段 | 类型 | 说明 |
|---|---|---|
| `customerSelfBooked` | `Boolean` | 该晚是否客户自订 |

同时该晚的 `arrangeLabel` 由「待配房」改为 **「客户自订」**。

> 🔴 **机读字段 `arrange` 仍然是 `pending`，没有新增枚举值**。前端所有按 `arrange` 取值的既有分支**零改动**；要专门呈现自订晚，请读 `customerSelfBooked` 布尔字段。这是刻意为之，避免新增枚举值打挂既有渲染。

### 4. 自订晚被排除出三处房务口径

| 口径 | 变化 |
|---|---|
| 配房进度分母 `progress.totalCount` | 排除自订晚。3 晚含 1 晚自订 → 显示 **2/2** 而非 2/3（否则房务永远看到「差一晚」） |
| `progress.unarrangedDayNumbers`（#5842 新增） | 排除自订晚。否则最终确认的二次确认弹窗会提示「第 2 晚未配房」——而那晚根本不用配 |
| 配房日历「待配房」桶 | 排除自订晚。否则日历上会挂一个永远做不完的格子 |

`unconfirmedDayNumbers` 由同一集合派生，一并受益。

**抢单池与待办不受影响**（抢单池按需求维度、不迭代 days；带 dayNumber 的待办是「酒店回复超时」，由真实配房行驱动，自订晚没有配房行自然不产生）。

---

## 前端要做

1. **调整订单弹窗「酒店安排」每晚增加「客户自订」开关/按钮**（wx 截图指定位置：该晚「+ 加候选酒店」旁）。打开后该晚的酒店/房型/预算区域可隐藏或置灰，备注仍可填。
2. 提交时对自订晚传 `customerSelfBooked: true`，`segments` 可为空数组。
3. **错误码处理**：
   - `582098` → 提示该晚缺用房需求（原 422 分支改这里）
   - `582097` → 提示"该晚已配房，请先删除配房再改为客户自订"
4. **房务侧配房行程 Tab**：`customerSelfBooked=true` 的晚渲染为「客户自订」样式（后端 `arrangeLabel` 已给文案），与「待配房」在视觉上区分开——这是本需求的核心目的。

---

## 验证证据

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

```
itinerary[0].customerSelfBooked = false        ← 字段已贯通
itinerary[0].arrange            = pending      ← 机读值未变
itinerary[0].arrangeLabel       = 待配房        ← 非自订晚文案不变
progress.unarrangedDayNumbers   = [1, 2]       ← 与实际未配房晚一致
```

单测：**双向变异验证**通过（①进度分母排除退回 `days.size()` → 对应用例失败；②读回侧 carry-through 置 null → 换版存活用例失败）。全量 6383 tests / 0 failures / 0 errors；BUILD FAILURE 系长跑尾部 fork 起不来（内存耗尽）导致 134 个类未跑、**其中含合并门禁 ArchTest**，已按包补跑 **1223 项全绿**（ArchTest 门禁 51 项）。

> 补充：自订晚的端到端写操作（提交带标记的需求）未在真实订单上执行——提交会改动定制师的真实数据。相关行为由单测与变异验证覆盖。
