--- 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: "pending" frontend_owner: "mmg" frontend_ref: "" target_release: "" verified_at: "2026-08-11" status_note: "" 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 项)。 > 补充:自订晚的端到端写操作(提交带标记的需求)未在真实订单上执行——提交会改动定制师的真实数据。相关行为由单测与变异验证覆盖。