docs(order-v3): 团期配房行合流进住宿核单 Step1 changelog(#7327 PR-1)
changelog-filename-gate / validate (push) Successful in 2s

面向 hl-ui:核单 Step1 的 sourceType 扩取值域,团期订单首次出现团期配房派生行
GROUP_BATCH_PLAN;新增行级确认闸业务错误码 584129。
接口路径、HTTP 方法、VO 字段集合均未变化,网关无新增路由。

Refs #7327

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
这个提交包含在:
API Changelog Bot
2026-09-13 08:53:04 +08:00
共同撰写人 Claude Fable 5.1
父节点 d32665518f
当前提交 31d9cb8b23
@@ -0,0 +1,523 @@
---
schema: "hl-changelog/v2"
ticket: "7327"
title: "团期配房行合流进住宿核单 Step1:sourceType 新增取值 GROUP_BATCH_PLAN、新增行级确认闸 584129"
consumer: "admin"
author: "wx(GIT)"
change_type: "修改接口"
backend_status: "deployed"
gateway_status: "verified"
frontend_status: "pending"
frontend_owner: ""
frontend_ref: ""
target_release: ""
verified_at: ""
status_note: "Issue #7327 PR-1 已合并 dev-v3(PR #7600,终态提交 49ce99cc8),服务 hl-order-service-v3。住宿核单 Step1 的团期订单首次出现团期配房派生行;sourceType/sourceTypeName 是既有字段,本次只扩取值域,新增业务错误码 584129。接口路径、HTTP 方法、VO 字段集合均未变化,网关无新增路由。"
updated_at: "2026-09-12"
base: "dev-v3"
---
# 订单模块: 团期配房行合流进住宿核单 Step1(sourceType 新增取值 + 行级确认闸 584129)
> **存放目录**:
> - 一期(v2,无 `order-v3` 标签的工单)→ `changelogs/{YYYY-MM}/`
> - 二期(v3,`order-v3` 标签的工单)→ `changelogs-v2/{YYYY-MM}/`
>
> **服务**: hl-order-service-v3 (端口 8086)
> **PR**: #7600
> **Issue**: #7327
> **日期**: 2026-09-12
> **影响范围**: 管理后台核单 Step1 住宿明细(团期订单);订单详情 / 行程单 / 小程序行程的住宿段行数
---
## ⚠️ 关键变化(非必须,本版与上版行为不同 / 纠错 / 撤销时必写)
一行红字说清:
- **`sourceType` / `sourceTypeName` 不是新增字段,是既有字段**。这两个字段在本次改动之前就存在于 `HotelItemVO` 上,前端按「新增字段」去找会找不到。**本次变的是取值域**:`sourceType` 多出 `GROUP_BATCH_PLAN`(`sourceTypeName` = 团期配房)与 `GROUP_BATCH_PLAN_REVOKED`(`sourceTypeName` = 团期配房(已失效))两个取值。
- 前端/调用方以前以为的是:核单 Step1 住宿明细只有两类行——配房派生行(`HOUSE_ASSIGNMENT`)与手工行(`MANUAL`);团期订单的住宿明细是空的。
- 实际现在是:**团期订单的住宿明细第一次出现团期配房派生行**,`sourceType=GROUP_BATCH_PLAN`。这类行按「住宿事实」聚合成一行(同订单 + 同入住日 + 同酒店 + 同房型的多条分房记录合并为一行,间数求和、计划成本按合计间数重算)。
- `GROUP_BATCH_PLAN_REVOKED` 本期产生不出来(后端本期既不产出也不认领该取值),下一期才会出现;前端做取值域映射时一并纳入即可,不必为它设计交互。
- 新增业务错误码 **584129**:把一条「团期配房未分平」的住宿行置为已确认时被拒绝。
---
## 一、背景(选填)
团期业务的房是「账面上按团订房、核算时还原到户」。住宿成本必须逐户进核单,否则团期订单在核单页看不到任何住宿行、应付算不出来。本次由房务侧的只读契约把团期分房行合流进既有的住宿读取链路,核单 Step1 因此第一次看得到团期住宿。
| 维度 | 改前(团期订单) | 改后(团期订单) |
|------|------------------|------------------|
| Step1 住宿行来源 | 仅 `HOUSE_ASSIGNMENT` 派生行 + `MANUAL` 手工行 | 增加 `GROUP_BATCH_PLAN` 派生行 |
| 团期订单住宿行数 | 0 行(团期分房不进核单) | 按住宿事实分组,一组一行 |
| 行级确认前置条件 | 无团期相关前置 | 团期行须「该户该日已按房型分平」才允许置已确认 |
---
## 二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|------|------|------|----------|------|
| 1 | Step 1 查询住宿核单明细 | GET | `/v3/admin/order/{orderId}/settlement/step1` | 响应取值域扩展 | 团期订单新增 sourceType=GROUP_BATCH_PLAN 的聚合行 |
| 2 | Step 1 录住宿核单明细 | PUT | `/v3/admin/order/{orderId}/settlement/step1` | 入参白名单扩展 + 新错误码 | items 内 sourceType 放行两个新取值;未分平的团期行置已确认返 584129 |
---
## 三、接口详情
### 1. Step 1 查询住宿核单明细 `GET /v3/admin/order/{orderId}/settlement/step1`
**VO**: `无 ReqVO(仅路径参数 orderId) → List<HotelItemVO>`
#### 使用场景
管理后台「核单结算 - Step1 住宿」页面进入或刷新时调用,拿到该订单当前应展示的全部住宿明细行(派生行 + 手工行)。返回的每一行都带 `id`,前端后续 `PUT` 回写时必须原样带回该 `id`,否则会被当成新增行。
本接口是**读写混合**的:服务端在返回前会把库里的核单草稿与房务权威对平(缺的补、变的改、权威消失的软删),所以刷新页面本身会改变库里的行集合与确认状态。前端不需要额外调「同步」动作。
团期订单从本次起会在结果里出现 `sourceType=GROUP_BATCH_PLAN` 的行。出现条件:该户存在有效的团期分房记录、其所属的团期订房计划已确认、且该户不在团期历史旧户冻结名单内;三者任一不满足则该户仍只有原来的行。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| orderId | Path | Long | ✅ | 路径变量,非数字将被框架判为参数错误 | 订单 ID(团期场景下是子订单/户的订单 ID,不是团 ID) |
#### 出参 `Result<List<HotelItemVO>>`
| 字段 | 类型 | 说明 |
|------|------|------|
| data[].id | String | 核单住宿行 ID(雪花 ID,强制序列化为字符串)。**行身份只认它**,回写必须原样带回 |
| data[].hotelAssignmentId | String | 派生行的权威锚点 ID;手工行为 null。团期行取该组内最小的分房记录 ID,**会随房务拆/合行漂移,前端不得用它做行身份** |
| data[].hotelId | String | 酒店 ID |
| data[].roomTypeId | String | 房型 ID |
| data[].stayDate | String | 入住日期,yyyy-MM-dd |
| data[].hotelName | String | 酒店名快照 |
| data[].roomType | String | 房型字典 code(团期行取团期订房计划行快照上的房型大类) |
| data[].roomTypeName | String | 房型名称快照 |
| data[].roomCount | Integer | 间数。团期行等于该组内各分房记录间数之和 |
| data[].unitPrice | Number | 核算单价(元/间夜)。团期行取团期订房计划行的结算价快照 |
| data[].plannedCost | Number | 计划成本(元)。团期行等于 unitPrice 乘 roomCount(按合计间数重算) |
| data[].actualCost | Number | 实际成本(元)。新建行初始等于 plannedCost,之后由核单员维护 |
| data[].paymentMethod | String | 付款方式:`SIGNED` / `COMPANY_PAID` / `CASH_PAID`;结算类型未知时为 null |
| data[].paymentMethodName | String | 付款方式中文名:签单 / 公司付款 / 现付 |
| data[].settleType | String | **本接口恒为 null**(读取路径不回填该字段,它只用于写入时代替 paymentMethod) |
| data[].sourceType | String | 来源类型。既有字段,本次新增取值 `GROUP_BATCH_PLAN` / `GROUP_BATCH_PLAN_REVOKED`(详见六.5) |
| data[].sourceTypeName | String | 来源类型中文名,与 sourceType 一一对应 |
| data[].sourceId | String | 来源业务 ID;团期行等于 hotelAssignmentId(组内最小分房记录 ID),同样会漂移 |
| data[].settlementConfirmStatus | String | 核单确认状态:`UNCONFIRMED` / `CONFIRMED` |
| data[].settlementConfirmStatusName | String | 确认状态中文名:未确认 / 已确认 |
| data[].remark | String | 备注。团期行间数变化时,系统会在原备注前加上提示前缀(见业务边界) |
| data[].voucherUrls | String[] | 凭证图片 URL 数组;无凭证时为空数组 |
#### 请求示例
```http
GET /v3/admin/order/71001/settlement/step1
Authorization: Bearer <token>
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": [
{
"id": "93001",
"hotelAssignmentId": "99001",
"hotelId": "201",
"roomTypeId": "202",
"stayDate": "2026-08-01",
"hotelName": "布达拉宫酒店",
"roomType": "TWIN",
"roomTypeName": "标准双床房",
"roomCount": 2,
"unitPrice": 400.00,
"plannedCost": 800.00,
"actualCost": 800.00,
"paymentMethod": "COMPANY_PAID",
"paymentMethodName": "公司付款",
"settleType": null,
"sourceType": "GROUP_BATCH_PLAN",
"sourceTypeName": "团期配房",
"sourceId": "99001",
"settlementConfirmStatus": "UNCONFIRMED",
"settlementConfirmStatusName": "未确认",
"remark": null,
"voucherUrls": []
}
],
"success": true
}
```
#### 空数据 / 降级响应
- 该订单没有任何住宿行(散客单未配房、团期订房计划未确认、该户在历史旧户冻结名单内)→ `data` 为空数组,不是 null,不报错。
- 房务只读契约返回 null(下游读取失败)→ **降级返回库里已有的核单草稿行,不做对平**,同时把该订单的住宿类目标记为「来源同步失败」;页面照常渲染,团期行不会凭空消失,也不会在这一次刷新里被删。
```json
{ "code": 200, "message": "成功", "data": [], "success": true }
```
#### 错误响应
```json
{
"code": 500,
"message": "系统繁忙,请稍后重试",
"data": null,
"success": false
}
```
#### 业务边界
- **鉴权**:走网关统一鉴权,未登录 401;本端点本身不带角色禁入判断(与同控制器其他只读端点一致)。
- **团期行聚合**:分组键是「订单 + 入住日 + 酒店 + 房型」,一组只返回一行。房务把一条分房记录拆成两条、或把两条合成一条,只要酒店/房型/日期/总间数不变,返回的行 `id` 不变,实际成本、凭证、备注、确认状态四项也不变,只有 `hotelAssignmentId` 与 `sourceId` 这两个锚点会变。
- **事实变更即打回未确认**:团期行的酒店、房型、入住日、酒店名、房型名、间数、单价、计划成本、付款方式任一与权威不一致时,该行 `settlementConfirmStatus` 被重置为 `UNCONFIRMED`,实际成本与凭证**保留不清空**。
- **间数变化加备注前缀**:间数变化时 `remark` 前面被系统加上 `[住宿事实变更 间数 {旧}→{新},请复核实付] `(末尾含一个空格)。连续变化只保留最新一层前缀,不叠加。该前缀仅供人读,前端不要用它做任何判断。
- **权威消失即软删**:团期订房计划行被删除或改成了别的酒店/房型时,对应的核单行在本次刷新中被软删,返回结果里不再出现;手工行与 `GROUP_BATCH_PLAN_REVOKED` 行不受影响、永远原样带回。
- **失败零写入**:对平过程在一个事务内,中途异常整体回滚,同时把住宿类目标记为来源同步失败。
- **兼容**:`sourceType` 的历史取值 `CUSTOM_ASSIGNMENT` / `TEMPLATE` 会被归一化为 `MANUAL` / `SYSTEM` 后再返回,前端不会读到这两个旧值。
---
### 2. Step 1 录住宿核单明细 `PUT /v3/admin/order/{orderId}/settlement/step1`
**VO**: `SettlementHotelSaveReqVO(items 为 HotelItemVO 数组) → SettlementHotelSaveRespVO`
#### 使用场景
核单员在 Step1 住宿页编辑实际成本、付款方式、凭证、备注、确认状态后点保存时调用。**全量替换语义**:请求里没带的现库行视为删除,所以必须把页面上的全部行(含未改动的派生行)一起回传。
本次起,请求里可以出现 `sourceType=GROUP_BATCH_PLAN` 的行;把这类行置为已确认时会经过一道新的闸门(584129)。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| orderId | Path | Long | ✅ | - | 订单 ID |
| items | Body | HotelItemVO[] | ✅ | 不能为 null;逐元素校验 | 住宿明细行全量数组。**请求体也可以直接是数组**,裸数组与对象包裹两种形态都接受 |
| items[].id | Body | String/Long | ❌ | - | 现库行 ID;不传或为 null 表示新增手工行 |
| items[].stayDate | Body | String | ✅ | yyyy-MM-dd,不得早于出发日 | 入住日期 |
| items[].hotelName | Body | String | ✅ | 非空白,长度 ≤200 | 酒店名 |
| items[].roomType | Body | String | ❌ | 长度 ≤64 | 房型字典 code |
| items[].roomTypeName | Body | String | ❌ | 长度 ≤128 | 房型名称 |
| items[].roomCount | Body | Integer | ✅ | - | 间数 |
| items[].unitPrice | Body | Number | ❌ | ≥ 0 | 核算单价 |
| items[].plannedCost | Body | Number | ✅ | ≥ 0 | 计划成本 |
| items[].actualCost | Body | Number | ✅ | ≥ 0 | 实际成本 |
| items[].paymentMethod | Body | String | ❌ | 枚举 `SIGNED` / `COMPANY_PAID` / `CASH_PAID` | 与 settleType 二选一;手工行必填其一 |
| items[].settleType | Body | String | ❌ | 枚举 `cash` / `sign` / `company` | 派生行可用它让后端映射 paymentMethod |
| items[].sourceType | Body | String | ❌ | 枚举 `HOUSE_ASSIGNMENT` / `MANUAL` / `SYSTEM` / `TEMPLATE` / `GROUP_BATCH_PLAN` / `GROUP_BATCH_PLAN_REVOKED`,长度 ≤32 | **本次新增放行后两个取值**。派生行落库的来源以库里既有行为准,入参该字段不改变行的来源归属 |
| items[].sourceId | Body | String/Long | ❌ | - | 派生行落库时取库里既有值,入参值被忽略 |
| items[].settlementConfirmStatus | Body | String | ❌ | 枚举 `UNCONFIRMED` / `CONFIRMED`,长度 ≤32 | 确认状态。新增行只能是未确认 |
| items[].remark | Body | String | ❌ | 长度 ≤500 | 备注 |
| items[].voucherUrls | Body | String[] | ❌ | - | 凭证图片 URL 数组 |
| items[].hotelId | Body | String/Long | ❌ | - | 酒店 ID,回传即可 |
| items[].roomTypeId | Body | String/Long | ❌ | - | 房型 ID,回传即可 |
| items[].hotelAssignmentId | Body | String/Long | ❌ | - | 派生行锚点;落库时以库里既有值为准 |
#### 出参 `Result<SettlementHotelSaveRespVO>`
| 字段 | 类型 | 说明 |
|------|------|------|
| data.addedIds | String[] | 本次落库后全部行的新 ID(全量替换实现:每次保存所有行都会重新写入并拿到新 ID)。雪花 ID 超出 JS 安全整数范围,按全局规则序列化为字符串 |
| data.updatedIds | String[] | 恒为空数组(全量替换语义下不区分更新) |
| data.deletedIds | String[] | 恒为空数组(被删除的行不单独列出,未回传即删除) |
| data.totalActualCost | String | 本次提交的 actualCost 之和,强制序列化为字符串 |
#### 请求示例
```json
{
"items": [
{
"id": "93001",
"hotelAssignmentId": "99001",
"hotelId": "201",
"roomTypeId": "202",
"stayDate": "2026-08-01",
"hotelName": "布达拉宫酒店",
"roomType": "TWIN",
"roomTypeName": "标准双床房",
"roomCount": 2,
"unitPrice": 400.00,
"plannedCost": 800.00,
"actualCost": 650.00,
"paymentMethod": "COMPANY_PAID",
"sourceType": "GROUP_BATCH_PLAN",
"settlementConfirmStatus": "CONFIRMED",
"remark": "财务备注",
"voucherUrls": ["https://oss.example.com/a.jpg"]
}
]
}
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": {
"addedIds": ["93011"],
"updatedIds": [],
"deletedIds": [],
"totalActualCost": "650.00"
},
"success": true
}
```
#### 空数据 / 降级响应
- 提交空的 items 数组是合法的,语义是「清空该订单全部住宿核单行」,返回空的 addedIds 与合计 0。派生行会在下一次 `GET step1` 对平时按权威重新生成,届时实际成本与凭证已丢失,**前端不要用空数组做「取消编辑」**。
- 本端点不提供下游降级:确认团期行时若房务只读契约不可用,保存整体失败并回滚,不会写入半个结果。
```json
{
"code": 200,
"message": "成功",
"data": { "addedIds": [], "updatedIds": [], "deletedIds": [], "totalActualCost": "0" },
"success": true
}
```
#### 错误响应
```json
{
"code": 584129,
"message": "团期配房未分平,该住宿行暂不能确认",
"data": null,
"success": false
}
```
sourceType 传了白名单以外的值时(HTTP 仍为 200):
```json
{
"code": 400,
"message": "sourceType 必须是 HOUSE_ASSIGNMENT / MANUAL / SYSTEM / GROUP_BATCH_PLAN / GROUP_BATCH_PLAN_REVOKED 之一",
"data": null,
"success": false
}
```
#### 业务边界
- **鉴权**:走网关统一鉴权;另有核单财务写权限校验,无权限时按既有权限错误码返回。
- **订单状态门禁**:订单的核单审核状态必须是 PENDING 或 IN_PROGRESS 才允许保存,否则返回既有的「订单状态不可结算」错误码(本次未改)。
- **并发**:按 orderId 加分布式锁(30 秒),同一订单的并发保存串行执行。
- **584129 触发条件(三个条件同时成立)**:① 该行带 `id`(是现库行);② 本次提交把它置为 `CONFIRMED`;③ 库里该行的来源是 `GROUP_BATCH_PLAN`,且房务侧该户该入住日**未按房型大类分平**,或该行对应的团期分房记录已经不存在了。
- **判来源只信库、不信入参**:把 `sourceType` 改成 `MANUAL` 再提交**绕不开**这道闸门。
- **`GROUP_BATCH_PLAN_REVOKED` 行不受该闸门约束**:它已经没有权威可比,成本由核单员自己维护,允许确认。
- **未分平的团期行仍可保存**:只要不把它置成 `CONFIRMED`,实际成本、凭证、备注照常可以录入并保存。
- **新增行必须从未确认起步**:`id` 为空的行提交 `CONFIRMED` 会被既有错误码拒绝(与本次改动无关,该校验在闸门之前执行)。
- **派生行事实变更再打回**:即便 584129 放行,若提交内容与库里该派生行的系统事实不一致,落库后的确认状态仍会被强制写成 `UNCONFIRMED`;前端保存后应以下一次 `GET step1` 的返回为准渲染状态。
- **前端处置建议(584129)**:提示「该团期住宿尚未分房完成,请等房务分平后再确认」,并引导用户刷新 Step1 重新拉取,而不是原样重试。
---
## 四、契约约束与正确调用方式(接口类必写)
> 本节只写**后端接受/拒绝 payload 的规则**,不写 UI 渲染建议。
### ✅ 正确 / ❌ 错误 payload 对照
| 场景 | payload |
|------|---------|
| ✅ 团期行只录实付、不确认 | `{ "id": "93001", "sourceType": "GROUP_BATCH_PLAN", "actualCost": 650.00, "settlementConfirmStatus": "UNCONFIRMED" }` |
| ✅ 团期行已分平后确认 | `{ "id": "93001", "sourceType": "GROUP_BATCH_PLAN", "settlementConfirmStatus": "CONFIRMED" }` → 200 |
| ✅ 已失效团期行确认 | `{ "id": "93002", "sourceType": "GROUP_BATCH_PLAN_REVOKED", "settlementConfirmStatus": "CONFIRMED" }` → 200,且不查房务契约 |
| ✅ 裸数组请求体 | `[ { "id": "93001" }, { "id": "93002" } ]`,与对象包裹形态等价 |
| ❌ 未分平的团期行置确认 | `{ "id": "93001", "sourceType": "GROUP_BATCH_PLAN", "settlementConfirmStatus": "CONFIRMED" }` → 584129 |
| ❌ 改来源绕闸门 | `{ "id": "93001", "sourceType": "MANUAL", "settlementConfirmStatus": "CONFIRMED" }` → 仍 584129(来源以库里为准) |
| ❌ 未知来源取值 | `{ "sourceType": "GROUP_BATCH" }` → code 400,sourceType 校验文案 |
| ❌ 只回传被改的那一行 | `{ "items": [ 单行 ] }` → 200,但**其余行全部被删除**(全量替换) |
### 切换状态时的必要动作
- 把一行从未确认切到已确认,请求里必须带上该行的 `id`;不带 `id` 会被当成新增行,直接被「新增行必须未确认」规则拒绝。
- 团期行确认失败(584129)后,正确动作是重新 `GET step1` 拉取最新事实再确认,而不是把 `sourceType` 改掉或把 `id` 去掉重试——这两条路都会破坏该行已录的实付与凭证。
- 付款方式二选一:派生行可只传 `settleType`(cash / sign / company)让后端映射,手工行请直接传 `paymentMethod`;两者都不传时该行付款方式为空。
---
## 五、数据库行为(涉及写操作时必写)
| 前端提交 | 落库后的来源 | 落库后的确认状态 | 落库后的实付与凭证 |
|----------|--------------|------------------|---------------------|
| 团期派生行,事实与库内一致,置 `CONFIRMED`,且已分平 | `GROUP_BATCH_PLAN`(取库内值,忽略入参) | `CONFIRMED` | 按入参写入 |
| 团期派生行,事实与库内不一致,置 `CONFIRMED` | `GROUP_BATCH_PLAN` | 强制 `UNCONFIRMED` | 按入参写入 |
| 团期派生行,未分平,置 `CONFIRMED` | 不落库(整个请求回滚) | 不落库 | 不落库 |
| 已失效团期行置 `CONFIRMED` | `GROUP_BATCH_PLAN_REVOKED` | `CONFIRMED` | 按入参写入 |
| 新增手工行 | `MANUAL` | 强制 `UNCONFIRMED` | 按入参写入 |
| 现库行未在 items 中回传 | 该行被软删 | — | — |
**派生行字段以库为准说明**: 派生行的来源、来源 ID 与配房锚点三项一律取库内既有值,入参里携带的对应字段被忽略;只有手工行这三项才落 null 与 `MANUAL`。
**读接口也会写库说明**: `GET step1` 在对平阶段会新建、更新或软删住宿行——间数或单价变化会写回计划成本并把确认状态打回未确认,锚点漂移会写回新的锚点 ID。对平只在「锚点变了」或「事实变了」时才发生写入,两者都没变时读接口一行库也不写。
---
## 六、边界行为
- 未登录 → 401 (网关拦截)
- 订单不存在 / 无权访问 → 按既有订单访问错误码返回,HTTP 仍为 200
- 房务只读契约不可用 → `GET step1` 降级返回库内草稿行并标记来源同步失败;`PUT step1` 在需要判分平时整体失败回滚
- 老数据兼容 → 历史 `TEMPLATE` / `CUSTOM_ASSIGNMENT` 来源归一化为 `SYSTEM` / `MANUAL` 后返回
- 非团期订单 → 行为与改动前完全一致,不会出现 `GROUP_BATCH_PLAN` 行
- 团期历史旧户(在团期模型启用前已按旧模式办完住宿的户)→ 不产生团期行,住宿段仍是原来的配房派生行
---
## 六.5、枚举 / 数据字典(接口出现枚举时必写)
每个枚举单独一个子节,不混表。字段+枚举类对应关系写在子节开头。
### sourceType(com.hulalv.order.settlement.enums.SettlementDetailSourceType)
**所属字段**: `HotelItemVO.sourceType`(请求与响应同名同义) | **类型**: `String`
| 值 | 中文 | 说明 |
|----|------|------|
| `HOUSE_ASSIGNMENT` | 配房结果 | 逐户配房派生行,与配房记录一一对应 |
| `MANUAL` | 手工 | 核单员手工新增行,无权威源 |
| `SYSTEM` | 系统 | 系统生成行(历史 TEMPLATE 归一化到此值) |
| `GROUP_BATCH_PLAN` | 团期配房 | **本次新增取值**。团期订房计划派生行,按住宿事实分组聚合,一组一行 |
| `GROUP_BATCH_PLAN_REVOKED` | 团期配房(已失效) | **本次新增取值**。团期来源已失效但成本仍保留的行;**本期产生不出来**,下一期才会出现 |
> 该枚举的其余取值(景区 / 游玩项目 / 餐饮安排 / 车务 / 人员安排 / 订单增费)用于其他核单步骤,住宿 Step1 不会返回,住宿入参白名单也不接受它们。
### settlementConfirmStatus(核单确认状态)
**所属字段**: `HotelItemVO.settlementConfirmStatus` | **类型**: `String`
| 值 | 中文 | 说明 |
|----|------|------|
| `UNCONFIRMED` | 未确认 | 新建行的初始值;事实变更会被打回该值 |
| `CONFIRMED` | 已确认 | 团期行置该值需过 584129 闸门 |
### paymentMethod(付款方式)
**所属字段**: `HotelItemVO.paymentMethod` | **类型**: `String`
| 值 | 中文 | 说明 |
|----|------|------|
| `SIGNED` | 签单 | 对应结算类型 sign |
| `COMPANY_PAID` | 公司付款 | 对应结算类型 company |
| `CASH_PAID` | 现付 | 对应结算类型 cash;该类行可上传凭证 |
## 六.6、修改前后对比(修改/删除类接口必写,新增跳过)
### 字段级对比
| 字段 | 改前 | 改后 |
|------|------|------|
| `sourceType`(响应) | 取值域 `HOUSE_ASSIGNMENT` / `MANUAL` / `SYSTEM` | 增加 `GROUP_BATCH_PLAN`、`GROUP_BATCH_PLAN_REVOKED`(字段本身早已存在,不是新增字段) |
| `sourceTypeName`(响应) | 配房结果 / 手工 / 系统 | 增加「团期配房」「团期配房(已失效)」 |
| `sourceType`(入参白名单) | `HOUSE_ASSIGNMENT` / `MANUAL` / `SYSTEM` / `TEMPLATE` | 再加 `GROUP_BATCH_PLAN` / `GROUP_BATCH_PLAN_REVOKED` |
| `hotelAssignmentId` 与 `sourceId`(团期行) | 团期订单无此类行 | 取该组内最小分房记录 ID,随房务拆/合行漂移,不可作为行身份 |
| `remark`(团期行) | 仅核单员自填内容 | 间数变化时被系统加上 `[住宿事实变更 间数 {旧}→{新},请复核实付] ` 前缀 |
| 其余字段 | — | 无增删、无类型变化 |
### 行为级对比
| 行为 | 改前 | 改后 |
|------|------|------|
| 团期订单 `GET step1` 住宿行 | 0 行 | 按「订单+入住日+酒店+房型」分组各一行,间数求和、计划成本按合计重算 |
| 团期订单行确认 | 无限制(因为没有团期行) | 未分平或权威已消失时返回 584129 |
| 团期行锚点漂移(房务拆/合分房) | — | 只换锚点,行 ID、实付、凭证、备注、确认状态五项不变 |
| 团期行事实变更 | — | 确认状态打回 `UNCONFIRMED`,实付与凭证保留 |
| 团期权威消失 | — | 该行在下一次 `GET step1` 中被软删 |
| 逐户配房派生行与手工行 | 现有行为 | 完全不变 |
## 六.7、影响评估(修改/删除类必写)
- **是否破坏向后兼容**: 否。路径、方法、VO 字段集合、既有取值语义均未变化;老前端把 `GROUP_BATCH_PLAN` 当未知来源忽略时,仍能正常读写散客单。
- **前端是否必须同步上线**: 否(不同步上线不会报错),但**团期订单的核单页在同步前会有两个问题**:来源列渲染成空白或未知值;核单员点确认时收到未映射的 584129 错误码。建议同期处理取值域映射与 584129 文案。
- **前端 workaround 清理点**: 若管理后台此前对团期订单隐藏了 Step1 住宿页、或写死了「团期无住宿」的提示,本次可撤除。
## 七、不影响范围(显式声明, 帮前端/QA 缩小排查面)
- **仅影响**: 管理后台核单结算 Step1 住宿明细的读写。
- **连带变化(字段结构不变,行数变多)**: 订单详情、行程单、小程序行程的住宿段与核单读同一份房务配房契约,因此**团期订单在这三处同样会多出团期配房行**。字段名、类型、层级一个都没变,只是数组元素变多了;散客单这三处零变化。
- **零影响**:
- 核单 Step2 门票、Step3 车辆及其余核单步骤
- 团期整单核单闸(584130 住宿 / 584131 用车)的语义,本次未改
- C 端算价、下单、支付链路
- 逐户配房(非团期)派生行的对平逻辑
- 历史数据:存量核单行不迁移,下一次打开 Step1 对平时才按新规则处理
---
## 八、测试环境已验证
接口行为以自动化用例断言实证,带 ✓ 标记(用例夹具: orderId=71001、departDate=2026-08-01、hotelId=201、roomTypeId=202、分房记录 99001 与 99002、单价 400.00):
```
GET step1 团期权威存在 → 新建行 sourceType=GROUP_BATCH_PLAN、间数 2、计划与实际成本 800.00、
UNCONFIRMED ✓ listHotel_groupAuthority_insertsDerivedRowWithGroupSourceAndAggregatedCost
GET step1 同事实两条分房行 → 只返回一行、间数求和、锚点取最小 allocId 99001 ✓
listHotel_splitAllocationsInSameFactGroup_aggregatesIntoSingleRow
GET step1 仅锚点漂移 → 重绑锚点,实付/凭证/备注/确认状态四项零改动 ✓
listHotel_allocIdDriftedOnly_persistsNewAnchorAndKeepsFinancialColumns
GET step1 事实与锚点均未变 → 读路径一行库也不写 ✓
listHotel_anchorAndFactsUnchanged_doesNotWriteOnReadPath
GET step1 间数 2 变 1 → 计划成本 400.00、实付 650.00 与凭证保留、状态回 UNCONFIRMED、
remark = "[住宿事实变更 间数 2→1,请复核实付] 财务备注" ✓
listHotel_roomCountChanged_keepsActualCostResetsStatusAndPrefixesRemark
GET step1 权威消失 → 派生行软删,已失效行与手工行保留 ✓
listHotel_groupAuthorityGone_softDeletesDerivedRowAndKeepsRevokedAndManual
PUT step1 未分平置确认 → 584129 ✓ saveHotel_groupRowUnbalanced_confirmRejectedWith584129
PUT step1 已分平置确认 → 放行 ✓ saveHotel_groupRowBalanced_confirmAllowed
PUT step1 权威已消失置确认 → 584129 ✓ saveHotel_groupRowSourceIdMissingFromContract_rejectedWith584129
PUT step1 未分平但保持未确认 → 放行 ✓ saveHotel_groupRowUnbalancedButStaysUnconfirmed_allowed
PUT step1 已失效行置确认 → 放行且不查房务契约 ✓
saveHotel_groupBatchPlanRevokedRow_confirmAllowedWithoutContractLookup
PUT step1 逐户配房行置确认 → 不被团期闸门拦 ✓ saveHotel_houseAssignmentRow_confirmNotGatedByGroupBalance
PUT step1 团期行事实变更 → 落库强制 UNCONFIRMED ✓ saveHotel_groupRowFactChanged_resetToUnconfirmed
sourceType 入参白名单 → 两个新取值通过、未知取值被拒 ✓ SettlementHotelSourceTypeValidationTest
```
网关:本次无新增路径,两个端点沿用既有路由 `/v3/admin/**` 到 `hl-order-service-v3`(`hl-gateway/src/main/resources/application.yml` 中的 `order-service-v3` 路由),无需网关改动。
上表为自动化用例断言原文;示例 JSON 中的 ID 与金额取自同一批用例夹具,不是测试服抓包报文。测试服真实网关的验收取证留在工单 #7327 的验收项里。
---
## 九、相关历史 PR(纠错 / 功能演进时必写)
| PR | Issue | 说明 | 是否仍有效 |
|----|-------|------|------------|
| **本 PR #7600** | **#7327** | PR-1:团期配房行合流进住宿核单 Step1 + 584129 行级确认闸 | ✅ 最新 |
---
## 十、相关文档
- 关联 Issue: [wx/HL#7327](https://git.1814.love:8443/wx/HL/issues/7327)
- 关联 PR: [wx/HL#7600](https://git.1814.love:8443/wx/HL/pulls/7600)
- 团期整单住宿闸(584130): 见 `changelogs-v2/2026-09/11_7446_住宿结算闸判团口径-修改接口-管理后台.md`
- 团期住宿户级 finalize 闸门: 见 `changelogs-v2/2026-09/10_7347_finalize团期住宿户级闸门-修改接口-管理后台.md`
- 后续计划: `GROUP_BATCH_PLAN_REVOKED` 的产出与「失效行不计入金额」在下一期一起落地,届时另发条目
## 关联 / 联系人
### 链接
- **Issue**: [#7327](https://git.1814.love:8443/wx/HL/issues/7327)
- **PR**: [#7600](https://git.1814.love:8443/wx/HL/pulls/7600)
- **Merge commit**: [49ce99cc8](https://git.1814.love:8443/wx/HL/commit/49ce99cc8)
### 联系人
- **后端负责人**: @wx