文件
hl-api-changelog/changelogs-v2/2026-09/30_8491_房务配房接口字段与口径调整-修改接口-管理后台.md
T
API Changelog Bot和Claude Opus 5.5 a60a791132
changelog-filename-gate / validate (push) Failing after 1s
docs(changelog): #8491 房务控制台接口新增、旧列表下线、配房接口口径调整
三份交接件:新增房务控制台接口,下线旧的房务列表接口,调整配房接口的字段与口径。每份的「测试环境已验证」一节填测试服实测读数。

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 06:42:04 +08:00

1727 行
66 KiB
Markdown
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
---
schema: "hl-changelog/v2"
ticket: "8491"
title: "房务配房接口调整:任务类型、只读标识、早餐、改配记录、转单名单校验、读权限对全体房务开放"
consumer: "admin"
author: "wx(GIT)"
change_type: "修改接口"
backend_status: "deployed"
gateway_status: "not_required"
frontend_status: "pending"
frontend_owner: ""
frontend_ref: ""
target_release: ""
verified_at: ""
status_note: ""
updated_at: "2026-09-30"
base: "dev-v3"
---
# 房务配房: 15 个既有接口的字段与口径调整(任务类型 / 只读标识 / 早餐 / 改配记录 / 转单名单校验 / 读权限开放)
> **存放目录**: 二期 → `changelogs-v2/2026-09/`
>
> **服务**: hl-order-service-v3
> **Issue**: #8491
> **日期**: 2026-09-29
> **影响范围**: 管理后台「房务控制台」的常规单 / 团期列表、订单房务详情弹窗、逐晚配房与改配、转单 / 超管指派、房务待办、团期房务看板与订房计划
---
## ⚠️ 关键变化(非必须,本版与上版行为不同 / 纠错 / 撤销时必写)
- **户级转单与超管指派(接口 7)在房务人员名单取不到时改为拒绝**,返回 **808343**「房务人员名单暂不可用,请稍后重试」,需求持有人不变。改前该情况放行。只有超管「整团接管」`POST /v3/admin/order/grab-pool/group-batches/{groupBatchId}/takeover` 在名单取不到时仍放行,接收人姓名显示占位 `user-{id}`。
- **「房务组长」角色取消**:错误码 808091、808092、582204 已删除且不复用。原组长账号访问房务接口返回 808090「未登录或非房务角色,无权操作」。
- **读权限对全体房务开放**:待办 `scope=all/others`、团期看板 `scope=ALL`、团期看板详情 / 房间需求 / 分房总览 / 确认前检查,普通房务都能看他人持有与未认领的数据;这些读接口不再因「不是本人认领」返回 808612 / 808613。写接口仍校验归属,仍返回 808612 / 808613 / 808110。
- 列表与详情新增 `readOnly` / `readOnlyReason`:
- **户级**(常规单行、非团期订单的详情)**超管不豁免**:超管看别人持有的单同样 `readOnly=true`,要改须先「指派」或「转单」给自己;
- **团期级**(团期行、团期子订单的详情、看板详情)**超管豁免**,超管恒为 `readOnly=false`。
- 配房行与团期订房计划新增早餐 `breakfast`(INCLUDED / EXCLUDED / PENDING)与房源 `roomSource`(STOCK / HOTEL);订单房务详情逐晚新增 `nightRoomSource`,配房行新增 `subtotal`。
- 改晚次 / 酒店 / 房型 / 间数(接口 6)在换酒店或减间数时写一条改配记录,出现在订单房务详情的 `changes[]`;原订为非控房且未上传取消凭证时记录为 `HELD`,须在房务控制台做「取消确认」。
---
## 一、背景(选填)
#8491 把房务日常操作收拢到「房务控制台」。本文件只写**既有接口**因此发生的变化:列表按任务类型(新订 / 修改 / 退团)筛选并带未读数与只读标识;配房与团期订房计划记录早餐;改配留痕;转单在名单取不到时不再放行;取消「房务组长」只读监督角色,改为全体房务可读、写仍按归属。控制台新增的 17 个接口见同目录新增接口文件,下线的 4 个旧列表接口见同目录删除接口文件。
---
## 二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|------|------|------|----------|------|
| 1 | 常规单配房列表 | GET | `/v3/admin/order/house-allocation/households` | 修改 | 新增入参 taskKind;行新增 taskKind / unreadCount / readOnly |
| 2 | 团期配房列表 | GET | `/v3/admin/order/house-allocation/group-batches` | 修改 | 新增入参 taskKind;行新增 taskKind / unreadCount / readOnly |
| 3 | 订单房务详情 | GET | `/admin/house/orders/{orderId}` | 修改 | 新增 taskKind / readOnly / changes;逐晚 nightRoomSource;配房行 breakfast / roomSource / subtotal |
| 4 | 逐晚提交配房 | POST | `/v3/admin/order/hotel-requirements/{requirementId}/assignments` | 修改 | items[] 新增 breakfast |
| 5 | 修改配房 | PUT | `/v3/admin/order/assignments/{id}` | 修改 | 新增 breakfast |
| 6 | 改晚次 / 酒店 / 房型 / 间数 | PUT | `/v3/admin/order/hotel-requirements/{requirementId}/assignments/{id}/placement` | 修改 | 新增 breakfast 与取消凭证 / 取消费 / 改配备注;换酒店或减间数写改配记录 |
| 7 | 转单 / 超管指派 | POST | `/v3/admin/order/hotel-requirements/{requirementId}/transfer` | 修改 | 名单取不到返回 808343 |
| 8 | 房务待办列表 | GET | `/v3/admin/order/todos` | 修改 | scope=all/others 对全体房务开放,582204 删除 |
| 9 | 团期房务看板列表 | GET | `/v3/admin/house/group-batches` | 修改 | scope=ALL 对全体房务开放,808092 删除 |
| 10 | 团期房务看板详情 | GET | `/v3/admin/house/group-batches/{groupBatchId}` | 修改 | 全体房务可看;新增 readOnly;计划行带早餐与房源 |
| 11 | 团期房间需求 | GET | `/v3/admin/house/group-batches/{groupBatchId}/room-requirements` | 修改 | 全体房务可看 |
| 12 | 团期分房总览 | GET | `/v3/admin/house/group-batches/{groupBatchId}/allocations` | 修改 | 全体房务可看;计划与分房行带早餐与房源 |
| 13 | 团期计划确认前检查 | GET | `/v3/admin/house/group-batches/{groupBatchId}/room-plans/confirm-check` | 修改 | 全体房务可看 |
| 14 | 团期订房计划保存 | POST | `/v3/admin/house/group-batches/{groupBatchId}/room-plans` | 修改 | items[] 新增 breakfast;响应带早餐与房源 |
| 15 | 团期订房计划修改 | PUT | `/v3/admin/house/group-batches/{groupBatchId}/room-plans/{planId}` | 修改 | 新增 breakfast;响应带早餐与房源 |
---
## 三、接口详情
本节示例 JSON 的字段名、类型、错误码与文案逐一取自源码;ID、日期、金额、姓名等取值为说明用的构造值。所有接口统一返回 `Result` 信封(`code` / `message` / `data` / `traceId` / `success`),示例省略 `traceId`;业务失败与入参校验失败均为 HTTP 200,靠 `code` 区分。ID 与金额字段序列化为字符串。入参 / 出参表只列本次新增或口径变化的字段及路径参数,未列出的字段名、类型与含义均不变。
### 1. 常规单配房列表 `GET /v3/admin/order/house-allocation/households`
**VO**: `HouseAllocationHouseholdPageReqVO → HouseAllocationHouseholdPageRespVO`
#### 使用场景
房务控制台「常规单」页签。新增按任务类型(新订 / 修改 / 退团)筛选;行内显示任务类型标签、房务会话未读数,并按 `readOnly` 置灰操作按钮、用 `readOnlyReason` 提示「由谁处理」。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| taskKind | Query | String | ❌ | NEW / CHANGE / WITHDRAWAL,或不传 | **新增**。任务类型筛选;不传不筛。筛选在分页之前生效,`total` 与 `stats` 七个计数都按筛选后的结果算 |
| scope | Query | String | ❌ | all / mine,默认 all | 不变 |
| status | Query | String | ❌ | pendingClaim / unfinished / claiming / pendingConfirm / confirmed / exception,默认 unfinished | 不变 |
| page | Query | Integer | ❌ | ≥1,默认 1 | 不变 |
| pageSize | Query | Integer | ❌ | 1~100,默认 20 | 不变 |
#### 出参 `Result<HouseAllocationHouseholdPageRespVO>`
| 字段 | 类型 | 说明 |
|------|------|------|
| list | List<HouseAllocationHouseholdRespVO> | 行列表 |
| total | Long | 总数(受 taskKind 筛选) |
| stats | HouseAllocationHouseholdStatsVO | pendingClaim / claiming / pendingConfirm / confirmed / exception / unfinished / all,结构不变,受 taskKind 筛选 |
| list[].taskKind | String | **新增**。NEW / CHANGE / WITHDRAWAL,规则见六.5 |
| list[].taskKindLabel | String | **新增**。新订 / 修改 / 退团 |
| list[].unreadCount | Integer | **新增**。该订单房务会话未读数;取不到按 0 |
| list[].readOnly | Boolean | **新增**。待认领、本人持有为 false;他人持有为 true;**超管看他人持有的单同样为 true** |
| list[].readOnlyReason | String | **新增**。readOnly=true 时为「由 {姓名} 处理」,姓名为空时为「由其他房务处理」;可写时为 null |
#### 请求示例
```http
GET /v3/admin/order/house-allocation/households?scope=all&status=unfinished&taskKind=CHANGE&page=1&pageSize=20
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": {
"list": [
{
"id": "1940000000000000011",
"orderId": "1930000000000000021",
"orderNo": "26-0915",
"teamNo": "26-0920",
"guestName": "李女士一家",
"houseStatus": "CLAIMING",
"houseStatusLabel": "配房中",
"claimerId": "30002",
"claimerName": "王芳",
"isMine": false,
"canStartAllocation": false,
"taskKind": "CHANGE",
"taskKindLabel": "修改",
"unreadCount": 2,
"readOnly": true,
"readOnlyReason": "由 王芳 处理"
}
],
"total": 1,
"stats": {
"pendingClaim": 0,
"claiming": 1,
"pendingConfirm": 0,
"confirmed": 0,
"exception": 0,
"unfinished": 1,
"all": 1
}
},
"success": true
}
```
#### 空数据 / 降级响应
- 筛选无结果:`list=[]`、`total=0`,`stats` 各项为 0。
- 任务类型取数失败时按空集处理,行的 `taskKind` 显示 `NEW`;按 `CHANGE` / `WITHDRAWAL` 筛选返回空页。
- 未读数取不到时 `unreadCount=0`,不影响列表其余字段。
#### 错误响应
```json
{
"code": 400,
"message": "taskKind 只能是 NEW、CHANGE 或 WITHDRAWAL",
"data": null,
"success": false
}
```
其余错误:
| code | message | 触发 |
|------|---------|------|
| 808090 | 未登录或非房务角色,无权操作 | 非房务角色(含原房务组长账号) |
#### 业务边界
- 一行同时满足退团与修改时显示 `WITHDRAWAL`(退团优先)。
- `readOnly` 只决定按钮状态;真正的拒绝由写接口的错误码决定(例如配房写接口的 808110)。
- 超管要改别人持有的常规单,先调接口 7 把需求指派给自己,指派成功后该行 `readOnly=false`。
### 2. 团期配房列表 `GET /v3/admin/order/house-allocation/group-batches`
**VO**: `HouseAllocationGroupPageReqVO → HouseAllocationGroupPageRespVO`
#### 使用场景
房务控制台「团期」页签。与接口 1 相同,新增任务类型筛选、未读数与只读标识;团期的只读判定超管豁免。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| taskKind | Query | String | ❌ | NEW / CHANGE / WITHDRAWAL,或不传 | **新增**。任务类型筛选;不传不筛;筛选在分页之前生效 |
| batchStatus | Query | String | ❌ | RECRUITING / RESOURCE_PREPARING / MATERIAL_PREPARING / PENDING_DEPARTURE / TRAVELLING / TRIP_FINISHED / REVIEWING / SETTLED / CANCELLED | 取值不变(校验常量移入本 VO) |
| scope | Query | String | ❌ | all / mine,默认 all | 不变 |
| status | Query | String | ❌ | pendingClaim / claimed | 不变 |
| page | Query | Integer | ❌ | ≥1,默认 1 | 不变 |
| pageSize | Query | Integer | ❌ | 1~100,默认 20 | 不变 |
#### 出参 `Result<HouseAllocationGroupPageRespVO>`
| 字段 | 类型 | 说明 |
|------|------|------|
| list | List<HouseAllocationGroupRespVO> | 行列表 |
| total | Long | 总数(受 taskKind 筛选) |
| stats | HouseAllocationGroupStatsVO | pendingClaim / claimed / all,结构不变 |
| list[].taskKind | String | **新增**。NEW / CHANGE / WITHDRAWAL |
| list[].taskKindLabel | String | **新增**。新订 / 修改 / 退团 |
| list[].unreadCount | Integer | **新增**。团下活跃子订单房务会话未读数之和;取不到按 0 |
| list[].readOnly | Boolean | **新增**。待认领、本人认领、超管为 false;他人认领为 true |
| list[].readOnlyReason | String | **新增**。readOnly=true 时为「由 {姓名} 处理」或「由其他房务处理」;可写时为 null |
#### 请求示例
```http
GET /v3/admin/order/house-allocation/group-batches?scope=all&status=claimed&taskKind=WITHDRAWAL&page=1&pageSize=20
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": {
"list": [
{
"groupBatchId": "1950000000000000031",
"batchNo": "GB261005",
"batchName": "呼伦贝尔秋色 6 日",
"batchStatus": "PENDING_DEPARTURE",
"batchStatusLabel": "待出发",
"departDate": "2026-10-05",
"houseClaimerId": "30001",
"houseClaimerName": "张敏",
"isMine": true,
"canStartAllocation": true,
"taskKind": "WITHDRAWAL",
"taskKindLabel": "退团",
"unreadCount": 0,
"readOnly": false,
"readOnlyReason": null
}
],
"total": 1,
"stats": {
"pendingClaim": 0,
"claimed": 1,
"all": 1
}
},
"success": true
}
```
#### 空数据 / 降级响应
- 筛选无结果:`list=[]`、`total=0`。
- 任务类型取数失败按空集处理,行显示 `NEW`;未读数取不到按 0。
#### 错误响应
```json
{
"code": 400,
"message": "taskKind 只能是 NEW、CHANGE 或 WITHDRAWAL",
"data": null,
"success": false
}
```
其余错误:
| code | message | 触发 |
|------|---------|------|
| 808090 | 未登录或非房务角色,无权操作 | 非房务角色(含原房务组长账号) |
#### 业务边界
- 团下任一订单为退团 / 修改,该团期即为退团 / 修改;退团另含「以该团期为来源、仍待处理的退团房转房」。
- 团期只读判定超管豁免:超管对任何团期 `readOnly=false`。
### 3. 订单房务详情 `GET /admin/house/orders/{orderId}`
**VO**: `orderId + requirementId → HouseOrderDetailRespVO`
#### 使用场景
房务打开订单房务详情弹窗。新增任务类型、只读标识、改配记录 `changes[]`(控制台「取消确认」的入口数据),逐晚新增整晚房源,配房行新增早餐、房源与小计。注意本接口路径**没有** `/v3` 前缀。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| orderId | Path | Long | ✅ | - | 订单 ID |
| requirementId | Query | Long | ❌ | 须属于该订单 | 不传返回当前生效需求;传入可查看历史作废版本(不变) |
#### 出参 `Result<HouseOrderDetailRespVO>`
| 字段 | 类型 | 说明 |
|------|------|------|
| taskKind | String | **新增**。NEW / CHANGE / WITHDRAWAL;取数异常时为 NEW |
| taskKindLabel | String | **新增**。新订 / 修改 / 退团 |
| readOnly | Boolean | **新增**。团期子订单按团期判定(团期认领人,超管豁免);其余订单按需求持有人判定(**超管不豁免**);团期判定取数失败时回退为按需求持有人判定 |
| readOnlyReason | String | **新增**。「由 {姓名} 处理」或「由其他房务处理」;可写时为 null |
| changes | List<HouseAssignmentChangeRespVO> | **新增**。改配记录,按创建时间倒序;无记录或取数失败时为 `[]` |
| changes[].changeId | Long | 改配记录 ID,调控制台「取消确认」用 |
| changes[].assignmentId | Long | 被改的配房行 ID |
| changes[].changeKind | String | HOTEL / ROOM_COUNT |
| changes[].oldStatus | String | HELD / CANCEL_CONFIRMED |
| changes[].oldStatusLabel | String | 原酒店待取消 / 已确认取消 |
| changes[].cancelFee | BigDecimal | 取消费用(元),可为 null |
| changes[].proofFileIds | List<String> | 取消凭证文件 ID |
| changes[].remark | String | 备注 |
| changes[].operatorName | String | CANCEL_CONFIRMED 时为确认人,否则为改配操作人 |
| changes[].createTime | LocalDateTime | 记录时间 |
| itinerary[].nightRoomSource | String | **新增**。SELF / STOCK / HOTEL / MIXED / UNSET,见六.5 |
| itinerary[].nightRoomSourceLabel | String | **新增**。客人自订 / 控房 / 非控房 / 混合 / 待选择 |
| itinerary[].assignments[].breakfast | String | **新增**。INCLUDED / EXCLUDED / PENDING;库里为空输出 PENDING |
| itinerary[].assignments[].breakfastLabel | String | **新增**。含早餐 / 不含早餐 / 早餐待确认 |
| itinerary[].assignments[].roomSource | String | **新增**。STOCK / HOTEL,由 deductInventory 推导,不单独存储 |
| itinerary[].assignments[].roomSourceLabel | String | **新增**。控房 / 非控房 |
| itinerary[].assignments[].subtotal | BigDecimal | **新增**。roomCount × settlementPrice,2 位小数;任一为 null 时为 null |
#### 请求示例
```http
GET /admin/house/orders/1930000000000000021
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": {
"taskKind": "CHANGE",
"taskKindLabel": "修改",
"readOnly": false,
"readOnlyReason": null,
"changes": [
{
"changeId": "1960000000000000041",
"assignmentId": "1970000000000000051",
"changeKind": "HOTEL",
"oldStatus": "HELD",
"oldStatusLabel": "原酒店待取消",
"cancelFee": null,
"proofFileIds": [],
"remark": "客人要求换到河景房",
"operatorName": "张敏",
"createTime": "2026-09-28 15:20:00"
}
],
"itinerary": [
{
"dayNumber": 1,
"stayDate": "2026-10-05",
"cityName": "海拉尔",
"nightRoomSource": "MIXED",
"nightRoomSourceLabel": "混合",
"assignments": [
{
"assignmentId": "1970000000000000052",
"hotelName": "海拉尔河畔酒店",
"roomTypeName": "高级双床房",
"confirmStatus": "INQUIRING",
"roomCount": 2,
"settlementPrice": "380.00",
"deductInventory": true,
"breakfast": "INCLUDED",
"breakfastLabel": "含早餐",
"roomSource": "STOCK",
"roomSourceLabel": "控房",
"subtotal": "760.00"
},
{
"assignmentId": "1970000000000000053",
"hotelName": "海拉尔雅园宾馆",
"roomTypeName": "标准大床房",
"confirmStatus": "INQUIRING",
"roomCount": 1,
"settlementPrice": null,
"deductInventory": false,
"breakfast": "PENDING",
"breakfastLabel": "早餐待确认",
"roomSource": "HOTEL",
"roomSourceLabel": "非控房",
"subtotal": null
}
]
}
]
},
"success": true
}
```
#### 空数据 / 降级响应
- 无改配记录或改配记录取数失败:`changes=[]`,详情其余部分照常返回。
- 某晚没有配房行且非客人自订:`nightRoomSource="UNSET"`,`assignments=[]`。
- 任务类型取数异常:`taskKind="NEW"`。
#### 错误响应
```json
{
"code": 808141,
"message": "该订单非房务可见",
"data": null,
"success": false
}
```
其余错误:
| code | message | 触发 |
|------|---------|------|
| 808100 | 需求不存在 | 传入的 requirementId 不属于该订单 |
| 808090 | 未登录或非房务角色,无权操作 | 非房务角色(含原房务组长账号) |
#### 业务边界
- `readOnly` 的口径因订单类型而异:团期子订单跟团期认领人走、超管豁免;其他订单跟需求持有人走、**超管不豁免**。
- `changes[]` 里 `oldStatus=HELD` 的记录才可调控制台「取消确认」;`CANCEL_CONFIRMED` 为已办结。
- 客人自订的那一晚 `nightRoomSource="SELF"`,不看配房行。
- `subtotal` 只用结算价计算,不含协议价。
### 4. 逐晚提交配房 `POST /v3/admin/order/hotel-requirements/{requirementId}/assignments`
**VO**: `AssignmentSubmitReqVO → AssignmentSubmitRespVO`
#### 使用场景
房务在订单房务详情里为各晚提交配房。本次 `items[]` 每行新增早餐 `breakfast`。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| requirementId | Path | Long | ✅ | - | 住宿需求 ID |
| items | Body | List<AssignmentItemReqVO> | ✅ | 非空 | 逐晚配房行(不变) |
| items[].breakfast | Body | String | ❌ | INCLUDED / EXCLUDED / PENDING;传空串校验失败 | **新增**。早餐;不传存为空、读出为 PENDING |
| items[].dayNumber | Body | Integer | ✅ | ≥1 | 不变 |
| items[].hotelId | Body | Long | ✅ | - | 不变 |
| items[].roomTypeId | Body | Long | ✅ | - | 不变 |
| items[].roomCount | Body | Integer | ✅ | ≥1 | 不变 |
| items[].deductInventory | Body | Boolean | ❌ | - | 不变;true 即控房(roomSource=STOCK) |
#### 出参 `Result<AssignmentSubmitRespVO>`
| 字段 | 类型 | 说明 |
|------|------|------|
| successCount | Integer | 不变 |
| failCount | Integer | 不变 |
| items | List<Item> | dayNumber / assignmentId / arrange / deductInventory,不变 |
#### 请求示例
```json
{
"items": [
{
"dayNumber": 1,
"hotelId": 100001,
"roomTypeId": 300001,
"roomCount": 2,
"settlementPrice": "380.00",
"deductInventory": true,
"breakfast": "INCLUDED"
}
]
}
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": {
"successCount": 1,
"failCount": 0,
"items": [
{
"dayNumber": 1,
"assignmentId": "1970000000000000052",
"arrange": "pending",
"deductInventory": true
}
]
},
"success": true
}
```
#### 空数据 / 降级响应
本接口为写接口,无降级分支;失败返回非 200 的 `code`,不落库。
#### 错误响应
```json
{
"code": 400,
"message": "breakfast 只能是 INCLUDED / EXCLUDED / PENDING",
"data": null,
"success": false
}
```
其余错误(本次未变,列出便于对照):
| code | message | 触发 |
|------|---------|------|
| 808090 | 未登录或非房务角色,无权操作 | 非房务角色(含原房务组长账号) |
| 808116 | 订单未抢单, 请先抢单再配房 | 需求无人持有 |
| 808110 | 需求不属于当前用户 | 当前登录人不是持有人(超管同样拒绝) |
#### 业务边界
- 同一需求 3 秒内重复提交被防重拦截。
- 提交时保留下来的既有配房行:`breakfast` 传了才改,不传保持原值。
- `breakfast` 传 `null` 等同不传;传空串 `""` 返回 400。
### 5. 修改配房 `PUT /v3/admin/order/assignments/{id}`
**VO**: `AssignmentUpdateReqVO → Result<Void>`
#### 使用场景
房务修改单个配房行的价格、结算方式、备注。本次新增 `breakfast`。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| id | Path | Long | ✅ | - | 配房行 ID |
| breakfast | Body | String | ❌ | INCLUDED / EXCLUDED / PENDING | **新增**。不传保持原值 |
| protoPrice | Body | BigDecimal | ❌ | ≥0 | 不变 |
| settlementPrice | Body | BigDecimal | ❌ | ≥0 | 不变 |
#### 出参 `Result<Void>`
| 字段 | 类型 | 说明 |
|------|------|------|
| data | null | 成功无返回体 |
#### 请求示例
```json
{
"settlementPrice": "360.00",
"breakfast": "EXCLUDED"
}
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": null,
"success": true
}
```
#### 空数据 / 降级响应
写接口,成功时 `data` 恒为 `null`;无降级分支。
#### 错误响应
```json
{
"code": 599602,
"message": "应付款台账行已锁定",
"data": null,
"success": false
}
```
其余错误:
| code | message | 触发 |
|------|---------|------|
| 400 | breakfast 只能是 INCLUDED / EXCLUDED / PENDING | 入参校验 |
| 808090 | 未登录或非房务角色,无权操作 | 非房务角色 |
| 808116 | 订单未抢单, 请先抢单再配房 | 需求无人持有 |
| 808110 | 需求不属于当前用户 | 当前登录人不是持有人(超管同样拒绝) |
#### 业务边界
- 599602(本次未变):已确认(CONFIRMED)的配房行改价格时,若该行对应的应付款台账行已有在途付款申请,拒绝改价,配房行不变。
- 只改 `breakfast` 不涉及价格,不触发 599602。
### 6. 改晚次 / 酒店 / 房型 / 间数 `PUT /v3/admin/order/hotel-requirements/{requirementId}/assignments/{id}/placement`
**VO**: `AssignmentPlacementUpdateReqVO → Result<Void>`
#### 使用场景
房务对已配的某行换晚次、换酒店 / 房型、改间数。本次新增早餐,以及换酒店或减间数时对原订的取消信息(凭证、费用、备注)。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| requirementId | Path | Long | ✅ | - | 当前生效住宿需求 ID |
| id | Path | Long | ✅ | - | 配房行 ID |
| dayNumber | Body | Integer | ✅ | ≥1 | 目标晚次(不变) |
| hotelId | Body | Long | ✅ | - | 目标酒店(不变) |
| roomTypeId | Body | Long | ✅ | - | 目标房型(不变) |
| roomCount | Body | Integer | ✅ | ≥1 | 目标间数(不变) |
| deductInventory | Body | Boolean | ❌ | - | 不变 |
| breakfast | Body | String | ❌ | INCLUDED / EXCLUDED / PENDING | **新增**。不传保持原值 |
| cancelProofFileIds | Body | List<Long> | ❌ | 最多 9 个 | **新增**。原订取消凭证 |
| cancelFee | Body | BigDecimal | ❌ | ≥0,整数最多 10 位、小数最多 2 位 | **新增**。原订取消费用(元) |
| changeRemark | Body | String | ❌ | ≤200 字 | **新增**。改配备注 |
#### 出参 `Result<Void>`
| 字段 | 类型 | 说明 |
|------|------|------|
| data | null | 成功无返回体;改配记录在订单房务详情 `changes[]` 查看 |
#### 请求示例
```json
{
"dayNumber": 1,
"hotelId": 100002,
"roomTypeId": 300005,
"roomCount": 1,
"deductInventory": false,
"breakfast": "INCLUDED",
"cancelProofFileIds": [],
"changeRemark": "客人要求换到河景房"
}
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": null,
"success": true
}
```
#### 空数据 / 降级响应
写接口,成功时 `data` 恒为 `null`;无降级分支。
#### 错误响应
```json
{
"code": 400,
"message": "cancelProofFileIds 最多 9 个",
"data": null,
"success": false
}
```
其余错误:
| code | message | 触发 |
|------|---------|------|
| 400 | cancelFee 不能为负数 / cancelFee 最多 10 位整数、2 位小数 / changeRemark 最长 200 字 / breakfast 只能是 INCLUDED / EXCLUDED / PENDING | 入参校验 |
| 808090 | 未登录或非房务角色,无权操作 | 非房务角色 |
| 808116 | 订单未抢单, 请先抢单再配房 | 需求无人持有 |
| 808110 | 需求不属于当前用户 | 当前登录人不是持有人(超管同样拒绝) |
#### 业务边界
- 何时写改配记录:换了酒店记 `changeKind=HOTEL`(优先);同酒店但间数减少记 `ROOM_COUNT`;间数增加或未变不写记录,此时 `cancelProofFileIds` / `cancelFee` / `changeRemark` 不落库。
- 记录状态:原配房行是控房(`deductInventory=true`),或上传了至少一个取消凭证 → 直接 `CANCEL_CONFIRMED`;原行为非控房且没传凭证 → `HELD`,须在房务控制台对该记录做「取消确认」。
- `HELD` 记录会让该订单在列表中显示 `taskKind=CHANGE`,并出现在房务控制台异常检查的 `HOTEL_CANCEL_PENDING`(原酒店待取消)项里;不生成房务待办。
- 目标行的确认状态重置为 `INQUIRING`,已确认(CONFIRMED)的行同样重置。已知缺口 #8508:对 CONFIRMED 行执行本接口时不校验应付款台账行是否锁定(不返回 599602),也不处理该行的应付款。
### 7. 转单 / 超管指派 `POST /v3/admin/order/hotel-requirements/{requirementId}/transfer`
**VO**: `HouseTransferReqVO → Result<Void>`
#### 使用场景
普通房务把自己持有的常规单需求转给同事;超管把任意常规单需求指派给某个房务(包括指派给自己,用于解除 `readOnly`)。同一路径按登录身份分流。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| requirementId | Path | Long | ✅ | - | 住宿需求 ID |
| toUserId | Body | Long | ✅ | 须在房务人员名单内 | 接收人 adminId |
| reason | Body | String | ❌ | ≤200 字;超管须 ≥10 字 | 原因;普通房务可不填(记为「转单」) |
| skipUpperLimit | Body | Boolean | ❌ | - | 历史字段,不变 |
#### 出参 `Result<Void>`
| 字段 | 类型 | 说明 |
|------|------|------|
| data | null | 成功无返回体,前端刷新列表与详情 |
#### 请求示例
```json
{
"toUserId": 30002,
"reason": "客人改到下周出行,转给负责该线路的同事"
}
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": null,
"success": true
}
```
#### 空数据 / 降级响应
**无降级**:房务人员名单取不到(调用异常、返回空)时直接返回 808343,需求持有人不变。改前该情况放行。
#### 错误响应
```json
{
"code": 808343,
"message": "房务人员名单暂不可用,请稍后重试",
"data": null,
"success": false
}
```
其余错误:
| code | message | 触发 |
|------|---------|------|
| 400 | toUserId 不能为空 / reason 长度不超过 200 字 | 入参校验 |
| 808090 | 未登录或非房务角色,无权操作 | 非房务角色(含原房务组长账号) |
| 808016 | 超管指派原因长度不足 10 字 | 超管 reason 不足 10 字 |
| 808002 | 需求已不存在 | requirementId 不存在 |
| 808650 | 团期订单须整团认领后配房,不支持逐户认领 / 转单 | 团期子订单的需求(普通房务一律;超管在团期已被认领时) |
| 808010 | 需求不属于当前用户,无法转单 | 普通房务转别人持有的需求 |
| 808014 | 接收人就是当前归属人,无需操作 | toUserId 等于当前持有人 |
| 808013 | 一单转单次数达上限(3 次) | 普通房务;超管指派不受限 |
| 808011 | 接收人不存在或已离职 | 名单可用但不含 toUserId |
| 808001 | 该需求已被其他房务认领或状态已变化,请刷新后重试 | 并发改持有人;需求未被认领 |
| 808930 | 配房状态机非法流转:原状态={0},事件={1} | 需求状态不允许转单 |
#### 业务边界
- 808343 同时作用于普通房务转单与超管指派;前端收到后保持弹窗,提示用户重试。
- 名单可用、接收人在名单里但姓名为空时,接收人姓名记为 `user-{id}`。
- 团期层面的换人不走本接口:团期持有人用新增接口「团期转交」,超管用「整团接管」`POST /v3/admin/order/grab-pool/group-batches/{groupBatchId}/takeover`;后者在名单取不到时仍放行并记 `user-{id}`。
- 需求级有写锁,同一需求的并发转单串行执行。
### 8. 房务待办列表 `GET /v3/admin/order/todos`
**VO**: `HouseTodoPageReqVO → HouseTodoListRespVO`
#### 使用场景
房务查看待办。`scope=all`(全部)与 `scope=others`(同事在处理)改为全体房务可用,不再限组长 / 超管。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| scope | Query | String | ❌ | mine / others / all,不传为 mine | **口径变化**:others / all 对全体房务开放 |
| todoType | Query | String | ❌ | 多选逗号分隔 | 不变 |
| 分页参数 | Query | - | ❌ | 继承通用分页参数 | 不变 |
#### 出参 `Result<HouseTodoListRespVO>`
| 字段 | 类型 | 说明 |
|------|------|------|
| list | List<HouseTodoItemVO> | 不变 |
| total | long | 不变 |
| stats | HouseTodoStatsVO | 不变;JSON 键为大写待办类型(SWAP_HOTEL / REFUND / … / UNREAD_CHAT) |
#### 请求示例
```http
GET /v3/admin/order/todos?scope=all
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": {
"list": [
{
"id": "1980000000000000061",
"todoType": "REQUIREMENT_ADJUSTED",
"todoTypeLabel": "需求调整",
"title": "客人调整入住人数",
"status": "OPEN",
"orderId": "1930000000000000021",
"orderNo": "26-0915",
"teamNo": "26-0920",
"guestName": "李女士一家"
}
],
"total": 1,
"stats": {
"SWAP_HOTEL": 0,
"REFUND": 0,
"INVENTORY_CHECK_OVERDUE": 0,
"RETURN_TO_HK": 0,
"REQUIREMENT_ADJUSTED": 1,
"HOTEL_REPLY_TIMEOUT": 0,
"PENDING_ARRANGE": 0,
"PENDING_FINALIZE": 0,
"UNREAD_CHAT": 0
}
},
"success": true
}
```
#### 空数据 / 降级响应
无待办时 `list=[]`、`total=0`,`stats` 各项为 0。
#### 错误响应
```json
{
"code": 582201,
"message": "查询范围取值非法(仅支持我的/他人/全部)",
"data": null,
"success": false
}
```
其余错误:582204 已删除,普通房务传 `scope=all` / `others` 不再报错。
#### 业务边界
- `scope=mine` 含未归属的待办;`others` 为同事在处理的;`all` 为全部。
- 看到同事的待办不代表能处理:处理动作仍按订单归属校验。
### 9. 团期房务看板列表 `GET /v3/admin/house/group-batches`
**VO**: `HouseGroupBatchBoardPageReqVO → PageResult<HouseGroupBatchBoardSimpleRespVO>`
#### 使用场景
团期房务看板列表。`scope=ALL` 改为全体房务可用。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| scope | Query | String | ❌ | MINE / ALL,≤8 字符,默认 MINE | **口径变化**:ALL 对全体房务开放 |
| claimerAdminId | Query | Long | ❌ | - | 按认领人筛选(不变) |
| keyword | Query | String | ❌ | ≤32 字 | 不变 |
| page | Query | Long | ❌ | ≥1,默认 1 | 不变 |
| pageSize | Query | Long | ❌ | 1~50,默认 20 | 不变 |
#### 出参 `Result<PageResult<HouseGroupBatchBoardSimpleRespVO>>`
| 字段 | 类型 | 说明 |
|------|------|------|
| records | List<HouseGroupBatchBoardSimpleRespVO> | 结构不变 |
| total | int | 不变 |
| page | int | 不变 |
| pageSize | int | 不变 |
#### 请求示例
```http
GET /v3/admin/house/group-batches?scope=ALL&page=1&pageSize=20
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": {
"records": [
{
"groupBatchId": "1950000000000000031",
"batchNo": "GB261005",
"batchName": "呼伦贝尔秋色 6 日",
"batchStatus": "PENDING_DEPARTURE",
"departDate": "2026-10-05",
"claimerAdminId": "30002",
"claimerName": "王芳",
"demandDays": 5,
"plannedDays": 5,
"confirmedDays": 3,
"mismatchDays": 0
}
],
"total": 1,
"page": 1,
"pageSize": 20
},
"success": true
}
```
#### 空数据 / 降级响应
无数据时 `records=[]`、`total=0`。
#### 错误响应
```json
{
"code": 808090,
"message": "未登录或非房务角色,无权操作",
"data": null,
"success": false
}
```
其余错误:
| code | message | 触发 |
|------|---------|------|
| 400 | scope 非法 / keyword 最长 32 字 / pageSize 最大 50 | 入参校验 |
808092 已删除,普通房务传 `scope=ALL` 不再报错。
#### 业务边界
- 列表只读,看得见不等于能写;写团期计划仍须本人认领(808612 / 808613)。
### 10. 团期房务看板详情 `GET /v3/admin/house/group-batches/{groupBatchId}`
**VO**: `groupBatchId → HouseGroupBatchBoardRespVO`
#### 使用场景
打开某个团期的房务看板。全体房务可看他人认领与未认领的团期,按新字段 `readOnly` 决定是否可编辑。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| groupBatchId | Path | Long | ✅ | - | 团期 ID |
#### 出参 `Result<HouseGroupBatchBoardRespVO>`
| 字段 | 类型 | 说明 |
|------|------|------|
| readOnly | Boolean | **新增**。未认领、本人认领、超管为 false;他人认领为 true |
| readOnlyReason | String | **新增**。「由 {姓名} 处理」或「由其他房务处理」;可写时为 null |
| days[].plans | List<GroupBatchRoomPlanRespVO> | 计划行,新增 breakfast / breakfastLabel / roomSource / roomSourceLabel(同接口 14 出参) |
| 其余字段 | - | 不变 |
#### 请求示例
```http
GET /v3/admin/house/group-batches/1950000000000000031
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": {
"groupBatchId": "1950000000000000031",
"batchNo": "GB261005",
"claimerAdminId": "30002",
"claimerName": "王芳",
"readOnly": true,
"readOnlyReason": "由 王芳 处理",
"days": [
{
"stayDate": "2026-10-05",
"dayNumber": 1,
"plans": [
{
"planId": "1990000000000000071",
"hotelName": "海拉尔河畔酒店",
"roomTypeName": "高级双床房",
"roomCount": 12,
"deductInventory": true,
"breakfast": "INCLUDED",
"breakfastLabel": "含早餐",
"roomSource": "STOCK",
"roomSourceLabel": "控房"
}
]
}
]
},
"success": true
}
```
#### 空数据 / 降级响应
团期尚无订房计划时 `days[].plans=[]`;未认领团期 `readOnly=false`、`claimerAdminId=null`。
#### 错误响应
```json
{
"code": 808090,
"message": "未登录或非房务角色,无权操作",
"data": null,
"success": false
}
```
其余错误:查看他人认领或未认领的团期不再返回 808612 / 808613。
#### 业务边界
- 本接口的 Swagger 说明仍提到 808612 / 808613,以本文为准:读取不校验认领归属。
- `readOnly=false` 的未认领团期仍须先整团认领才能写计划,否则写接口返回 808612。
### 11. 团期房间需求 `GET /v3/admin/house/group-batches/{groupBatchId}/room-requirements`
**VO**: `groupBatchId → HouseGroupBatchRoomRequirementRespVO`
#### 使用场景
看板里查看团期逐晚、逐户的房间需求。本次只放开读权限,响应结构不变。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| groupBatchId | Path | Long | ✅ | - | 团期 ID |
#### 出参 `Result<HouseGroupBatchRoomRequirementRespVO>`
| 字段 | 类型 | 说明 |
|------|------|------|
| groupBatchId | Long | 不变 |
| days | List<Day> | 不变 |
| householdsWithoutBasis | List<HouseholdWithoutBasis> | 不变 |
| outOfRangeHouseholds | List<OutOfRangeHousehold> | 不变 |
#### 请求示例
```http
GET /v3/admin/house/group-batches/1950000000000000031/room-requirements
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": {
"groupBatchId": "1950000000000000031",
"batchNo": "GB261005",
"departDate": "2026-10-05",
"endDate": "2026-10-10",
"days": [],
"householdsWithoutBasis": [],
"outOfRangeHouseholds": []
},
"success": true
}
```
#### 空数据 / 降级响应
无需求时各列表为 `[]`。
#### 错误响应
```json
{
"code": 808090,
"message": "未登录或非房务角色,无权操作",
"data": null,
"success": false
}
```
其余错误:查看他人认领或未认领的团期不再返回 808612 / 808613。
#### 业务边界
- 只读接口,全体房务可看;不涉及写入。
### 12. 团期分房总览 `GET /v3/admin/house/group-batches/{groupBatchId}/allocations`
**VO**: `groupBatchId → GroupBatchRoomAllocationOverviewRespVO`
#### 使用场景
看板里查看团期逐晚计划与分户分房。本次放开读权限,计划行与分房行新增早餐与房源。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| groupBatchId | Path | Long | ✅ | - | 团期 ID |
#### 出参 `Result<GroupBatchRoomAllocationOverviewRespVO>`
| 字段 | 类型 | 说明 |
|------|------|------|
| days[].plans[].breakfast | String | **新增**。INCLUDED / EXCLUDED / PENDING |
| days[].plans[].breakfastLabel | String | **新增**。含早餐 / 不含早餐 / 早餐待确认 |
| days[].plans[].roomSource | String | **新增**。STOCK / HOTEL |
| days[].plans[].roomSourceLabel | String | **新增**。控房 / 非控房 |
| days[].plans[].allocations[].breakfast | String | **新增**。取自所属计划行 |
| days[].plans[].allocations[].breakfastLabel | String | **新增**。取自所属计划行 |
| days[].plans[].allocations[].roomSource | String | **新增**。取自所属计划行 |
| days[].plans[].allocations[].roomSourceLabel | String | **新增**。取自所属计划行 |
| 其余字段 | - | 不变 |
#### 请求示例
```http
GET /v3/admin/house/group-batches/1950000000000000031/allocations
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": {
"groupBatchId": "1950000000000000031",
"batchNo": "GB261005",
"balanced": true,
"days": [
{
"stayDate": "2026-10-05",
"plannedRooms": 12,
"allocatedRooms": 12,
"plans": [
{
"planId": "1990000000000000071",
"hotelName": "海拉尔河畔酒店",
"roomTypeName": "高级双床房",
"plannedRooms": 12,
"allocatedRooms": 12,
"leftoverRooms": 0,
"breakfast": "INCLUDED",
"breakfastLabel": "含早餐",
"roomSource": "STOCK",
"roomSourceLabel": "控房",
"allocations": [
{
"allocId": "2000000000000000081",
"orderId": "1930000000000000022",
"teamNo": "26-0921",
"orderNo": "26-0916",
"roomCount": 2,
"breakfast": "INCLUDED",
"breakfastLabel": "含早餐",
"roomSource": "STOCK",
"roomSourceLabel": "控房"
}
]
}
],
"households": []
}
],
"blockedHouseholds": []
},
"success": true
}
```
#### 空数据 / 降级响应
无计划时 `days=[]`;计划行没有分房时 `allocations=[]`。
#### 错误响应
```json
{
"code": 808090,
"message": "未登录或非房务角色,无权操作",
"data": null,
"success": false
}
```
其余错误:查看他人认领或未认领的团期不再返回 808612 / 808613。
#### 业务边界
- 分房行的早餐与房源不单独存储,恒等于所属计划行。
- 同一控制器的两个写接口(保存手工分房、重建)仍校验认领归属。
### 13. 团期计划确认前检查 `GET /v3/admin/house/group-batches/{groupBatchId}/room-plans/confirm-check`
**VO**: `groupBatchId → GroupBatchRoomConfirmCheckRespVO`
#### 使用场景
确认团期订房计划前的预检。本次只放开读权限,响应结构不变。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| groupBatchId | Path | Long | ✅ | - | 团期 ID |
#### 出参 `Result<GroupBatchRoomConfirmCheckRespVO>`
| 字段 | 类型 | 说明 |
|------|------|------|
| ready | Boolean | 不变 |
| days | List<DayCheck> | 不变 |
| 其余字段 | - | 不变 |
#### 请求示例
```http
GET /v3/admin/house/group-batches/1950000000000000031/room-plans/confirm-check
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": {
"groupBatchId": "1950000000000000031",
"batchStatus": "PENDING_DEPARTURE",
"stageAllowed": true,
"baselineExists": true,
"hotelReady": true,
"ready": true,
"blockedByOutOfRange": false,
"days": [],
"noBaselineOrders": [],
"outOfRangeOrders": []
},
"success": true
}
```
#### 空数据 / 降级响应
无计划时 `days=[]`,`ready` 按检查结果给出。
#### 错误响应
```json
{
"code": 808090,
"message": "未登录或非房务角色,无权操作",
"data": null,
"success": false
}
```
其余错误:查看他人认领或未认领的团期不再返回 808612 / 808613。
#### 业务边界
- 预检可看,确认计划的写接口仍校验认领归属。
### 14. 团期订房计划保存 `POST /v3/admin/house/group-batches/{groupBatchId}/room-plans`
**VO**: `GroupBatchRoomPlanSaveReqVO → List<GroupBatchRoomPlanRespVO>`
#### 使用场景
团期认领人批量新增订房计划行。本次 `items[]` 新增早餐,响应计划行新增早餐与房源。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| groupBatchId | Path | Long | ✅ | - | 团期 ID |
| items | Body | List<GroupBatchRoomPlanItemReqVO> | ✅ | 1~200 行 | 计划行(不变) |
| items[].breakfast | Body | String | ❌ | INCLUDED / EXCLUDED / PENDING | **新增**。不传存为空、读出为 PENDING |
| items[].stayDate | Body | LocalDate | ✅ | - | 不变 |
| items[].hotelId | Body | Long | ✅ | - | 不变 |
| items[].roomTypeId | Body | Long | ✅ | - | 不变 |
| items[].roomCount | Body | Integer | ✅ | ≥1 | 不变 |
| items[].settleType | Body | String | ❌ | cash / sign / company | 不变 |
| items[].deductInventory | Body | Boolean | ❌ | - | 不变;true 即控房 |
#### 出参 `Result<List<GroupBatchRoomPlanRespVO>>`
| 字段 | 类型 | 说明 |
|------|------|------|
| [].breakfast | String | **新增**。INCLUDED / EXCLUDED / PENDING |
| [].breakfastLabel | String | **新增**。含早餐 / 不含早餐 / 早餐待确认 |
| [].roomSource | String | **新增**。STOCK / HOTEL,由 deductInventory 推导 |
| [].roomSourceLabel | String | **新增**。控房 / 非控房 |
| 其余字段 | - | 不变 |
#### 请求示例
```json
{
"items": [
{
"stayDate": "2026-10-05",
"hotelId": 100001,
"roomTypeId": 300001,
"roomCount": 12,
"settlementPrice": "360.00",
"settleType": "sign",
"deductInventory": true,
"breakfast": "INCLUDED"
}
]
}
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": [
{
"planId": "1990000000000000071",
"groupBatchId": "1950000000000000031",
"stayDate": "2026-10-05",
"hotelName": "海拉尔河畔酒店",
"roomTypeName": "高级双床房",
"roomCount": 12,
"settlementPrice": "360.00",
"settleType": "sign",
"deductInventory": true,
"breakfast": "INCLUDED",
"breakfastLabel": "含早餐",
"roomSource": "STOCK",
"roomSourceLabel": "控房",
"version": 0
}
],
"success": true
}
```
#### 空数据 / 降级响应
写接口,无降级分支;失败返回非 200 的 `code`,不落库。
#### 错误响应
```json
{
"code": 808613,
"message": "该团期由其他房务认领,无权操作",
"data": null,
"success": false
}
```
其余错误:
| code | message | 触发 |
|------|---------|------|
| 400 | items 不能为空 / items 一次最多 200 行 / breakfast 只能是 INCLUDED / EXCLUDED / PENDING | 入参校验 |
| 808090 | 未登录或非房务角色,无权操作 | 非房务角色 |
| 808612 | 该团期尚未被房务整团认领 | 团期未认领 |
#### 业务边界
- 写仍校验团期认领归属;读权限的放开不影响本接口。
- 团期计划行不涉及 599602。
### 15. 团期订房计划修改 `PUT /v3/admin/house/group-batches/{groupBatchId}/room-plans/{planId}`
**VO**: `GroupBatchRoomPlanItemReqVO → GroupBatchRoomPlanRespVO`
#### 使用场景
团期认领人修改单个订房计划行。本次新增早餐。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| groupBatchId | Path | Long | ✅ | - | 团期 ID |
| planId | Path | Long | ✅ | - | 计划行 ID |
| version | Body | Integer | ✅ | - | 乐观锁版本(不变) |
| breakfast | Body | String | ❌ | INCLUDED / EXCLUDED / PENDING | **新增**。不传保持原值 |
| replaceReason | Body | String | ❌ | ≤256 字 | 不变 |
#### 出参 `Result<GroupBatchRoomPlanRespVO>`
| 字段 | 类型 | 说明 |
|------|------|------|
| breakfast | String | **新增** |
| breakfastLabel | String | **新增** |
| roomSource | String | **新增** |
| roomSourceLabel | String | **新增** |
| 其余字段 | - | 不变 |
#### 请求示例
```json
{
"version": 0,
"breakfast": "EXCLUDED"
}
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": {
"planId": "1990000000000000071",
"groupBatchId": "1950000000000000031",
"roomCount": 12,
"deductInventory": true,
"breakfast": "EXCLUDED",
"breakfastLabel": "不含早餐",
"roomSource": "STOCK",
"roomSourceLabel": "控房",
"version": 1
},
"success": true
}
```
#### 空数据 / 降级响应
写接口,无降级分支。
#### 错误响应
```json
{
"code": 808612,
"message": "该团期尚未被房务整团认领",
"data": null,
"success": false
}
```
其余错误:
| code | message | 触发 |
|------|---------|------|
| 400 | version 不能为空 / breakfast 只能是 INCLUDED / EXCLUDED / PENDING | 入参校验 |
| 808090 | 未登录或非房务角色,无权操作 | 非房务角色 |
| 808613 | 该团期由其他房务认领,无权操作 | 他人认领 |
#### 业务边界
- 不传 `breakfast` 时原值保留:原地修改与「删旧建新」两条路径都保留原早餐。
---
## 四、契约约束与正确调用方式(接口类必写)
> 本节只写**后端接受/拒绝 payload 的规则**,不写 UI 渲染建议。
### ✅ 正确 / ❌ 错误 payload 对照
| 场景 | payload |
|------|---------|
| ✅ 早餐未知时不传 | `{ "items": [ { "dayNumber": 1, "hotelId": 100001, "roomTypeId": 300001, "roomCount": 1 } ] }` → 读出 breakfast=PENDING |
| ✅ 显式标待确认 | `{ "breakfast": "PENDING" }` |
| ❌ 早餐传空串 | `{ "breakfast": "" }` → 400 breakfast 只能是 INCLUDED / EXCLUDED / PENDING |
| ❌ 早餐传中文 | `{ "breakfast": "含早" }` → 400 |
| ✅ 换酒店并附原订取消凭证 | `{ "dayNumber": 1, "hotelId": 100002, "roomTypeId": 300005, "roomCount": 1, "cancelProofFileIds": [1930000000000000501] }` → 记录直接 CANCEL_CONFIRMED |
| ❌ 取消凭证超 9 个 | `{ ..., "cancelProofFileIds": [1,2,3,4,5,6,7,8,9,10] }` → 400 cancelProofFileIds 最多 9 个 |
| ❌ 取消费为负 | `{ ..., "cancelFee": "-1" }` → 400 cancelFee 不能为负数 |
| ❌ 超管指派原因过短 | `{ "toUserId": 30002, "reason": "改派" }` → 808016 |
| ❌ 列表 taskKind 小写 | `?taskKind=change` → 400 taskKind 只能是 NEW、CHANGE 或 WITHDRAWAL |
### 切换状态时的必要动作
- 超管要编辑别人持有的常规单:先调接口 7 把需求指派给自己,再调配房写接口;直接写返回 808110。
- 收到 808343:持有人没变,保持当前界面,由用户重试。
- 改晚次 / 酒店 / 房型 / 间数成功后,重新拉取订单房务详情,读取新的 `changes[]` 与 `taskKind`。
- `HELD` 改配记录办结走房务控制台「取消确认」接口,`changeId` 取自 `changes[].changeId`。
---
## 五、数据库行为(涉及写操作时必写)
| 前端提交 | 写入位置 | 行为 |
|----------|----------|------|
| 逐晚提交配房 | 配房行 `breakfast` 列 | 新建行:传则写入,未传写空(读出 PENDING);保留的既有行:传了才改 |
| 修改配房 | 配房行 | 未传 `breakfast` 保持原值;价格变更规则不变 |
| 改晚次 / 酒店 / 房型 / 间数 | 配房行、`house_assignment_change` | 目标行确认状态置 INQUIRING;换酒店或减间数时新增一条改配记录(HELD 或 CANCEL_CONFIRMED) |
| 转单 / 超管指派 | 需求认领字段 | CAS 换持有人;808343 / 808011 时不写 |
| 团期订房计划保存 | 团期订房计划行 `breakfast` 列 | 传则写入,未传写空 |
| 团期订房计划修改 | 团期订房计划行 | 未传 `breakfast` 保持原值,删旧建新时也带过去 |
**显式 SET NULL 说明**: 本次新增字段都不支持「传 null 清空」:`breakfast` 传 null 等同不传(新建写空、修改保持原值),要改回待确认须显式传 `PENDING`。改配记录的 `cancelFee` / `cancelProofFileIds` / `changeRemark` 只在写入新记录时落库,间数增加或未变时忽略。
---
## 六、边界行为
- 已登录但不是房务角色(ROOM_MANAGER / SUPER_ADMIN 以外)→ 808090「未登录或非房务角色,无权操作」;原「房务组长」账号同样返回 808090。
- 读接口不校验认领归属,全体房务可看;写接口仍校验:常规单 808116 / 808110,团期 808612 / 808613。
- `readOnly` 与写接口的拒绝口径一致:户级超管不豁免(对应 808110 超管同样拒绝),团期级超管豁免。
- 常规单列表的 Swagger 字段说明写「超管为 false」,与实际行为不符,以本文为准:户级 `readOnly` 超管不豁免。
- 581045「房务角色无权查看订单详情,房务仅可配房」只改说明文字(去掉组长),码值与文案不变。
- 599602 出现位置(本次均未变):修改配房对已确认行改价格、删除配房行 `DELETE /v3/admin/order/assignments/{id}`、清空需求配房 `DELETE /v3/admin/order/hotel-requirements/{requirementId}/assignments`,以及房务控制台退团房转房(来源为订单时)。含义:该配房行对应的应付款台账行已有在途付款申请而被锁定。团期订房计划不涉及 599602。
---
## 六.5、枚举 / 数据字典(接口出现枚举时必写)
### taskKind(HouseTaskKind)
**所属字段**: `HouseAllocationHouseholdRespVO.taskKind`、`HouseAllocationGroupRespVO.taskKind`、`HouseOrderDetailRespVO.taskKind`,及列表入参 `taskKind` / **类型**: `String`
| 值 | 中文 | 说明 |
|----|------|------|
| `WITHDRAWAL` | 退团 | 订单有未关闭的退款待办,或有以该订单为来源、仍待处理的退团房转房;团期行:团下任一订单为退团,或有以该团期为来源、仍待处理的转房;优先级最高 |
| `CHANGE` | 修改 | 订单有未关闭的需求调整待办(与列表 `isRework` 同一口径),或有 HELD 改配记录;团期行:团下任一订单为修改 |
| `NEW` | 新订 | 以上都不满足;某项判据取数失败时该项按空集处理 |
### breakfast(HouseBreakfast)
**所属字段**: 配房行与团期订房计划行的 `breakfast`(入参与出参) / **类型**: `String`
| 值 | 中文 | 说明 |
|----|------|------|
| `INCLUDED` | 含早餐 | - |
| `EXCLUDED` | 不含早餐 | - |
| `PENDING` | 早餐待确认 | 未填时按此输出 |
### roomSource(HouseRoomSource)
**所属字段**: 配房行与团期订房计划行、团期分房行的 `roomSource` / **类型**: `String`
| 值 | 中文 | 说明 |
|----|------|------|
| `STOCK` | 控房 | deductInventory=true |
| `HOTEL` | 非控房 | deductInventory 为 false 或空 |
### nightRoomSource(订单房务详情逐晚)
**所属字段**: `HouseOrderDetailRespVO.itinerary[].nightRoomSource` / **类型**: `String`
| 值 | 中文 | 说明 |
|----|------|------|
| `SELF` | 客人自订 | 该晚客人自订 |
| `STOCK` | 控房 | 该晚配房行全部为控房 |
| `HOTEL` | 非控房 | 该晚配房行全部为非控房 |
| `MIXED` | 混合 | 该晚同时有控房与非控房 |
| `UNSET` | 待选择 | 该晚没有配房行 |
### changeKind(HouseAssignmentChangeConstants)
**所属字段**: `HouseOrderDetailRespVO.changes[].changeKind` / **类型**: `String`
| 值 | 中文 | 说明 |
|----|------|------|
| `HOTEL` | 换酒店 | 同时减间数也记为此值 |
| `ROOM_COUNT` | 减间数 | 同酒店、间数减少 |
### oldStatus(HouseAssignmentChangeConstants)
**所属字段**: `HouseOrderDetailRespVO.changes[].oldStatus` / **类型**: `String`
| 值 | 中文 | 说明 |
|----|------|------|
| `HELD` | 原酒店待取消 | 可在房务控制台做取消确认 |
| `CANCEL_CONFIRMED` | 已确认取消 | 已办结 |
### 待办 scope
**所属字段**: `HouseTodoPageReqVO.scope` / **类型**: `String`
| 值 | 中文 | 说明 |
|----|------|------|
| `mine` | 我的 | 含未归属;不传时的默认值 |
| `others` | 他人 | 同事在处理的;全体房务可用 |
| `all` | 全部 | 全体房务可用 |
### 看板 scope
**所属字段**: `HouseGroupBatchBoardPageReqVO.scope` / **类型**: `String`
| 值 | 中文 | 说明 |
|----|------|------|
| `MINE` | 我的 | 默认 |
| `ALL` | 全部 | 全体房务可用 |
### 已删除错误码
**所属字段**: `Result.code` / **类型**: `Integer`
| 值 | 中文 | 说明 |
|----|------|------|
| `808091` | 房务组长为只读监督角色,无权执行该操作 | 已删除,不复用 |
| `808092` | 无权查看全部房务订单(仅房务组长或超管可查看) | 已删除,不复用 |
| `582204` | 无权查看全部/他人房务待办(仅房务组长或超管可查看) | 已删除,不复用 |
---
## 六.6、修改前后对比(修改/删除接口必写)
### 字段级对比
| 字段 | 改前 | 改后 |
|------|------|------|
| 常规单 / 团期列表入参 `taskKind` | 无 | NEW / CHANGE / WITHDRAWAL,可选 |
| 常规单 / 团期列表行 `taskKind` / `taskKindLabel` | 无 | 有 |
| 常规单 / 团期列表行 `unreadCount` | 无 | 有,取不到为 0 |
| 列表行、订单房务详情、看板详情 `readOnly` / `readOnlyReason` | 无 | 有 |
| 订单房务详情 `changes[]` | 无 | 改配记录,倒序 |
| 订单房务详情 `itinerary[].nightRoomSource` / `nightRoomSourceLabel` | 无 | 有 |
| 配房行 `breakfast` / `breakfastLabel` / `roomSource` / `roomSourceLabel` / `subtotal` | 无 | 有 |
| 团期计划行、分房行 `breakfast` / `breakfastLabel` / `roomSource` / `roomSourceLabel` | 无 | 有 |
| 配房写入参 `breakfast` | 无 | 可选 |
| 改晚次等入参 `cancelProofFileIds` / `cancelFee` / `changeRemark` | 无 | 可选 |
### 行为级对比
| 行为 | 改前 | 改后 |
|------|------|------|
| 户级转单 / 超管指派,房务人员名单取不到 | 放行 | 返回 808343,不换持有人 |
| 原房务组长账号访问房务接口 | 可读,写返回 808091 | 返回 808090 |
| 普通房务查待办 scope=all / others | 返回 582204 | 放行 |
| 普通房务查看板 scope=ALL | 返回 808092 | 放行 |
| 查看他人认领 / 未认领团期的看板详情、房间需求、分房总览、确认前检查 | 返回 808612 / 808613 | 放行,看板详情给出 readOnly |
| 换酒店或减间数 | 不留记录 | 写改配记录(HELD 或 CANCEL_CONFIRMED) |
---
## 六.7、影响评估
- **是否破坏向后兼容**: 是。户级转单 / 超管指派在名单取不到时由放行改为 808343;808091 / 808092 / 582204 三个码删除,按这三个码映射文案或分支的前端逻辑不再被触发。新增字段均为追加,旧字段含义不变。
- **前端是否必须同步上线**: 否。新增入参均为可选,旧前端不传照常工作;但 808343 的提示需要前端能展示后端 `message`。
- **前端 workaround 清理点**: 组长只读视图、按 808091 / 808092 / 582204 做的分支与文案;按「是否本人认领」自行推导按钮置灰的逻辑,改为直接读 `readOnly` / `readOnlyReason`。
---
## 七、不影响范围(显式声明, 帮前端/QA 缩小排查面)
- **仅影响**: 管理后台房务相关页面(房务控制台常规单 / 团期列表、订单房务详情弹窗、团期房务看板与订房计划、房务待办)。
- **零影响**:
- 小程序与 H5(本文接口均为管理端路由)
- 写接口的归属校验口径:常规单 808116 / 808110、团期 808612 / 808613,码值与文案不变
- 配房行与团期计划行既有字段(价格、结算方式、确认状态)的含义与取值
---
## 八、测试环境已验证
所有接口都经测试服网关调用。HTTP 状态恒为 200,下面的 `code` 指响应 body 里的 `code`,带 ✓ 标记。行尾 `@` 后面是当时测试服 order-v3 的部署提交。`bdde64a3a`、`9c7ac9382`、`ff6863754`、`3ecf38797` 四个提交都包含本单合并提交 `7c21cf0e40`。
```
GET /v3/admin/order/house-allocation/households 房务 A scope=all 查别人持有的单 → code=200,该行 readOnly=true,readOnlyReason「由<持有人姓名>处理」 ✓ @9c7ac9382
GET /v3/admin/order/house-allocation/households 刚认领、未配房的单 → taskKind=NEW ✓ @bdde64a3a
GET /v3/admin/order/house-allocation/households 测试定制师提交需求调整(新增 OPEN 的 REQUIREMENT_ADJUSTED)后 → taskKind=CHANGE、isRework=true、todoCount=1 ✓ @ff6863754
GET /v3/admin/order/house-allocation/households 改酒店产生 HELD 改配记录后 → 该单 taskKind=CHANGE ✓ @bdde64a3a
GET /v3/admin/order/house-allocation/households scope=all&taskKind=WITHDRAWAL → code=200,已配房后取消的单 taskKind=WITHDRAWAL;结果 5 行全是退团单,上面那张 CHANGE 单不在其中 ✓ @9c7ac9382
GET /v3/admin/order/house-allocation/households HOUSE 会话发 2 条未读 → 该行 unreadCount=2;没有会话的行 unreadCount=0 ✓ @bdde64a3a
GET /v3/admin/order/house-allocation/households scope=mine&status=unfinished,需求最终确认后 → code=200,5 行,不含该需求所在单 ✓ @9c7ac9382,带房需求复测 ✓ @ff6863754
GET /v3/admin/order/house-allocation/group-batches 团期转交后 scope=all 查该团 → code=200,houseClaimerName 变为接收人 ✓ @9c7ac9382
GET /admin/house/orders/{orderId} 一晚两行(一行 breakfast=INCLUDED + deductInventory=true,一行两者都不传) → breakfastLabel「含早餐」/「早餐待确认」,roomSource STOCK / HOTEL,该晚 nightRoomSource=MIXED;客人自订晚 SELF,未配房晚 UNSET ✓ @bdde64a3a
GET /admin/house/orders/{orderId} 非控房已确认行改酒店不带凭证后 → changes[] 多一条 oldStatus=HELD;确认取消后该条 CANCEL_CONFIRMED、cancelFee="200.00";控房行改酒店的新 change 直接是 CANCEL_CONFIRMED ✓ @bdde64a3a
GET /admin/house/orders/{orderId} 间数 3→2 后 changes[] 多一条 changeKind=ROOM_COUNT、oldStatus=HELD;2→3 后没有新增 ✓ @bdde64a3a
POST /v3/admin/order/hotel-requirements/{requirementId}/assignments 一晚一行扣控房 + 一行不扣控房 → code=200 ✓ @bdde64a3a
POST /v3/admin/order/hotel-requirements/{requirementId}/assignments 对别人持有的需求提交 → code=808110;持有人经转单转给本人后同一请求 → code=200 ✓ @9c7ac9382
POST /v3/admin/order/hotel-requirements/{requirementId}/assignments 控房表调价后提交同酒店同房型同晚 → code=200,快照价 680.00 / 675.00;调价前已有行仍是 620.00 / 615.00 ✓ @9c7ac9382
PUT /v3/admin/order/assignments/{id} 测试服未单独调用,由 HouseAssignmentServiceTest#update_breakfast_overwritesWhenGivenKeepsWhenOmitted 覆盖
PUT /v3/admin/order/hotel-requirements/{requirementId}/assignments/{id}/placement 非控房已确认行改酒店、不带凭证 → code=200,产生 oldStatus=HELD 改配记录 ✓ @bdde64a3a
PUT /v3/admin/order/hotel-requirements/{requirementId}/assignments/{id}/placement 控房行改酒店、不带凭证 → code=200,改配记录直接是 CANCEL_CONFIRMED ✓ @bdde64a3a
PUT /v3/admin/order/hotel-requirements/{requirementId}/assignments/{id}/placement 间数 3→2 → code=200,多一条 ROOM_COUNT / HELD 改配记录;2→3 → code=200,不新增 ✓ @bdde64a3a
POST /v3/admin/order/hotel-requirements/{requirementId}/transfer 持有人把需求转给房务 A → code=200,之后房务 A 对该需求提交配房成功 ✓ @9c7ac9382
GET /v3/admin/order/todos 房务 A scope=all → code=200 ✓;scope=others → code=200 ✓ @9c7ac9382
GET /v3/admin/house/group-batches 测试服未单独调用,由 HouseGroupBatchBoardManagerTest#page_scopeAllRoomManager_allClaims 覆盖
GET /v3/admin/house/group-batches/{groupBatchId} 房务 A 查看房务 B 持有的团 → code=200,readOnly=true ✓ @9c7ac9382
GET /v3/admin/house/group-batches/{groupBatchId}/room-requirements 团期转交前后查计划 → code=200,计划行数 1→1 ✓ @9c7ac9382;RESOURCE_PREPARING 团期该晚 needRoomCount=4 ✓ @ff6863754
GET /v3/admin/house/group-batches/{groupBatchId}/allocations 团里一户出行前取消后 → code=200,该户 allocatedRooms 2→0,本团 leftoverRooms 0→2,计划行保留 ✓ @3ecf38797
GET /v3/admin/house/group-batches/{groupBatchId}/room-plans/confirm-check 测试服未单独调用,由 HouseGroupBatchClaimGuardTest#assertReadable_others_noThrow、GroupBatchRoomDayConfirmManagerTest#check_staleAllocation_dayNotReadyAndBatchNotReady 覆盖
POST /v3/admin/house/group-batches/{groupBatchId}/room-plans 团期持有人新建一行 2 间计划 → code=200 ✓ @9c7ac9382
PUT /v3/admin/house/group-batches/{groupBatchId}/room-plans/{planId} 测试服未单独调用,由 GroupBatchRoomPlanManagerTest#update_replace_breakfastGiven_overridesOld、#update_breakfastOnly_pendingRow_inPlacePatchCarriesBreakfast 覆盖
```
验证身份:房务 A / B / C、测试定制师、超管测试账号,全部是测试专用账号。落库读数来自测试服只读 SQL。
---
## 十、相关文档
- 关联 Issue: [wx/HL#8491](https://git.1814.love:8443/wx/HL/issues/8491)
- 契约文档: `docs/order-v3/api/API-SPEC-HOUSE-V1.1.html` §1.3 转单 / 超管指派、§11.12 错误码、§12 房务控制台
- 同批变更: 同目录 `30_8491_房务控制台接口-新增接口-管理后台.md`、`30_8491_房务旧列表接口下线-删除接口-管理后台.md`
- 已知缺口: #8508(改晚次 / 酒店 / 房型 / 间数对已确认行的应付款处理)
## 关联 / 联系人
### 链接
- **Issue**: [#8491](https://git.1814.love:8443/wx/HL/issues/8491)
### 联系人
- **后端负责人**: @wx