文件
hl-api-changelog/changelogs-v2/2026-09/10_7347_finalize团期住宿户级闸门-修改接口-管理后台.md
T
Mimingguang 4281aa8aca
changelog-filename-gate / validate (push) Failing after 1s
docs(changelog): #7390/#7347 frontmatter 回写 not_required
2026-09-10 15:09:21 +08:00

275 行
16 KiB
Markdown
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
---
schema: "hl-changelog/v2"
ticket: "7347"
title: "整单核单 finalize 新增团期住宿户级闸门,未安排好住宿不允许结算(新增错误码 584130)"
consumer: "admin"
author: "wx(GIT)"
change_type: "修改接口"
backend_status: "deployed"
gateway_status: "not_required"
frontend_status: "not_required"
frontend_owner: "mmg"
frontend_ref: ""
target_release: ""
verified_at: ""
status_note: "not_required 2026-09-10 mmg: 仅 finalize 错误码集合扩大新增 584130,HTTP 恒 200 判 code,请求/成功响应/方法/路径一字未改;后端自答前端唯一事=给 584130 直接展示后端 message(不二次包装)。实证该诉求已被既有通用业务码分支覆盖:request.js:512-528 未知码取 data.message 原样弹(errorBus message.error),finalize 经 settlementService.js 调用无 silentError、catch 不改写 message,后端 message 自动原样弹出,新增映射反违「不二次包装」。旧口径清理点实证无(财务核单无按住宿派生行推断能否结算逻辑)。"
updated_at: "2026-09-10"
base: "dev-v3"
---
# 整单核单 finalize 新增团期住宿户级闸门(修改接口)
> **服务**: hl-order-service-v3
> **Issue**: [#7347](https://git.1814.love:8443/wx/HL/issues/7347)
> **日期**: 2026-09-10
> **影响范围**: 既有端点 `POST /v3/admin/order/{orderId}/settlement/finalize`(完成核单)的**错误码集合**扩大;不新增/不删除端点,请求体、成功响应体结构、方法、路径、网关路由**一字未改**
---
## ⚠️ 关键变化
1. **团期子订单完成核单(finalize)新增一道硬阻断**:该订单需要配房(`needsHotel=true`)但其住宿需求还没被房务推到 `DONE` 时,finalize 直接拒绝,新增业务错误码 **`584130`**。改前这种情况能直接结算通过(尤其是"一条住宿派生行都没有"的团期子订单,改前完全不受阻拦)。
2. **HTTP 状态码恒为 200**,闸门以 `Result.code = 584130` 的业务错误体返回,前端必须判 `code`,不能按 HTTP 状态识别。
3. 免房户(`needsHotel=false`)与非团期订单(`productBatchId` 为空)**完全不受影响,行为与改前逐字节一致**。
4. 管理者已对测试库执行存量统计 SQL,结果为 **0**(团期子订单里没有一单会被新闸拦住),因此**全量生效,不做灰度**,没有需要提前处理的存量数据。
5. 前端需要做的唯一事情:给 `584130` 加提示文案,见下方"前端提示文案建议"。不涉及任何请求/响应字段改动。
---
## 一、背景
团期房务批次(`#7322`–`#7328`)把住宿的**行级**确认接进了核单:某户某晚没分平,那一行就不能被确认(`#7327` 的 584129)。但**整单核单完成(finalize)此前对住宿没有任何硬性闸门**——一个团期子订单哪怕住宿完全没安排好,只要没有已录入的住宿行,照样能走完 finalize 结算掉(既有的住宿检查落在 `SettlementCategoryCheckService`,条件是 `rowCount > 0`,0 行时天然放行)。
本单在 `performSubmitBlockingChecks`(既有 finalize 阻断检查方法)里新增一道**户级**闸门:只要该团期子订单还需要配房,就必须等房务把户级住宿需求推到 `DONE` 才允许结算。闸门用户级谓词而不是团级 `order_group_batch.hotel_ready`——团级标志只要有一户没推平就是 false,会把整团所有户的结算一起冻住,违反"不让一户卡整团"的既有原则;户级谓词与房务侧完成判定 `finalizeHotelRequirementDone` 写的是同一个字段,两侧口径天然对齐。
---
## 二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 完成核单(finalize) | POST | `/v3/admin/order/{orderId}/settlement/finalize` | 错误码集合扩大 | 新增可能返回 `584130`;请求体(无)、成功响应体结构、方法、路径均未改;网关路由零改动 |
---
## 三、接口详情
### 1. 完成核单 `POST /v3/admin/order/{orderId}/settlement/finalize`
**VO**: `无请求体(Path 参数 orderId)→ Result<SettlementSubmitRespVO>`(成功响应结构本单未改,字段集合不重复列出)
#### 使用场景
核单员在核单页点击"完成核单",前端调用本端点。团期子订单在此新增一道住宿完成校验;本节判定入口与触发条件表见下方"业务边界"前的说明表。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| `orderId` | Path | Long | 是 | 大于 0 | 订单 ID;本单未改 |
**判定入口与触发条件**(前端可直接照此做提示判定表):新闸位于 `SettlementService.assertGroupHotelReadyForFinalize(OrderInfo)`,在既有的司机结算完成检查之后、`CASH_PAID` 缺凭证软预警之前触发(硬阻断,非软预警):
| 条件 | 判定结果 |
|---|---|
| `productBatchId == null`(非团期订单) | **不受本单影响**,行为与改前完全一致 |
| `productBatchId != null` 且 `needsHotel != true`(团期免房户) | **放行**,行为与改前一致 |
| `productBatchId != null` 且 `needsHotel == true`,该户**最新一条住宿需求 `status == DONE`** | **放行** |
| `productBatchId != null` 且 `needsHotel == true`,住宿需求**不存在**(一条派生行都没有,改前的漏洞场景) | 抛 **`584130`** |
| `productBatchId != null` 且 `needsHotel == true`,住宿需求存在但 `status` 为 `PENDING` / `PROCESSING` / `PENDING_REVIEW` / `REJECTED_TO_CONSULTANT` / `REJECTED_TO_ADMIN`(即非 `DONE`) | 抛 **`584130`** |
| 该户住宿派生行已被撤销转为 `GROUP_BATCH_PLAN_REVOKED`,但住宿需求本身仍非 `DONE` | 抛 **`584130`**(`REVOKED` 行不构成"已安排好"的证据) |
判定只读户级住宿需求的 `status` 字段,**不看有没有派生行、也不解析日期判断"是否自助订房"**——这些口径统一由房务侧的完成判定负责写 `DONE`,本闸只读结果。
#### 出参
成功响应结构本单**未改**,`Result<SettlementSubmitRespVO>` 字段集合与改前完全一致(不重复列出全部字段,仅摘录与本单相关的部分):
| 字段 | 类型 | 说明 |
|---|---|---|
| `data.orderId` | Long | 订单 ID;本单未改 |
| `data.finalSnapshotStatus` | String | 核单终态快照状态;本单未改 |
| `data.warnings` | `List<WarningItemVO>` | 软预警列表(如 `CASH_PAID` 缺凭证);本单未改,`584130` 不进这个列表,而是走错误响应 |
#### 请求示例
```http
POST /v3/admin/order/1000123/settlement/finalize
```
(无请求体,仅 Path 参数 `orderId`;本单未改请求形态)
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"orderId": 1000123,
"finalSnapshotStatus": "CONFIRMED",
"warnings": []
}
}
```
(成功路径响应结构本单未改,仅摘录关键字段)
#### 空数据 / 降级响应
不适用:本闸不产生空数据/降级语义,判定只有"放行"或"抛 584130"两种结果。
#### 错误响应
```json
{
"code": 584130,
"message": "团期子订单 GB202609090001 的住宿尚未安排完成(住宿需求当前状态:PROCESSING),请等房务配房完成后再提交核单",
"success": false,
"data": null
}
```
message 模板(`SettlementErrorCode.SETTLEMENT_GROUP_HOTEL_NOT_READY`,`SettlementErrorCode.java:339-342`)为:
```
团期子订单 {0} 的住宿尚未安排完成(住宿需求当前状态:{1}),请等房务配房完成后再提交核单
```
两个占位符:`{0}` = 订单号(`orderNo`),`{1}` = 住宿需求当前状态。**住宿需求行完全不存在时**,`{1}` 填固定文案 **`未提交住宿需求`**(常量 `SettlementService.GROUP_HOTEL_REQUIREMENT_ABSENT`,逐字抄自源码,不是状态枚举值),而不是某个状态码。
#### 业务边界
- 闸门只对 `productBatchId != null`(团期子订单)生效,核心订单的房控闸是另一套逻辑(`OrderService.needsHotel != true → 视为房控通过`),本单不动它。
- 闸门读取的是"该户最新一条住宿需求"(`RequirementService.getLatestHotelRequirementByOrderId`),走既有 Service 公共只读方法,未直连 Mapper。
- 与既有"已录入住宿行全部 CONFIRMED"的行级检查(`SettlementCategoryCheckService`)并列不重叠:旧检查看的是已录入行的确认状态(行级),新闸看的是住宿需求 `status`(户级),两者判定顺序上新闸更早触发(`performSubmitBlockingChecks` 早于 `SettlementReportFlowService.prepareFinalizationInCurrentTransaction`)。
---
## 四、契约约束与正确调用方式
- **必须按响应体 `code == 584130` 识别,不能按 HTTP 状态码识别**——HTTP 恒为 200。
- 建议直接展示后端 `message`,其中已包含订单号与住宿需求当前状态,无需前端自行拼接。
- "该团期子订单是否可以完成核单"以 finalize 实际返回结果为准,前端**不应该**通过"是否存在住宿派生行"自行推断能否结算——这正是改前的漏洞场景(0 行时误以为可以结算)。
### 切换状态时的必要动作
无。本单不涉及任何需要调用方额外置空/切换的字段,闸门完全由后端根据现有数据判定。
---
## 五、数据库行为
- `584130` 在阻断检查阶段抛出,属于既有 finalize 阻断检查的一部分,抛出后**不产生任何写入**(不写核单快照、不写车辆冻结、不写日志表)。
- 判定只读 `order_main`(`productBatchId`/`needsHotel`)与住宿需求表(`status`),**无表结构变更、无 Flyway**。
---
## 六、边界行为
- 团期免房户(`needsHotel=false`):放行,不查住宿需求。
- 团期需房户,住宿需求 `status=DONE`:放行。
- 团期需房户,住宿需求缺失(零派生行):拒绝,`584130`,`message` 中状态位为"未提交住宿需求"。
- 团期需房户,住宿需求非 DONE(`PENDING`/`PROCESSING`/`PENDING_REVIEW`/`REJECTED_TO_CONSULTANT`/`REJECTED_TO_ADMIN`):拒绝,`584130`,`message` 中状态位为该实际状态值。
- 该户住宿派生行已转 `GROUP_BATCH_PLAN_REVOKED`,但需求 `status` 未到 `DONE`:仍拒绝,`584130`(`REVOKED` 行不算完成证据)。
- 非团期订单:完全不受影响,无论住宿情况如何都走改前逻辑。
- **全自订户**(每一晚都自助订房)的 `needsHotel` 依然为 `true`,其住宿需求需先由房务侧完成判定推到 `DONE` 才能通过本闸;这条口径由另一单负责写 `DONE`,本单只读结果、不重复判定。
---
## 六.5、枚举 / 数据字典
### 新增错误码(`SettlementErrorCode`,`hl-order-service-v3/src/main/java/com/hulalv/order/settlement/errorcode/SettlementErrorCode.java:339-342`)
| code | 常量名 | message 模板 | 触发条件 |
|---|---|---|---|
| `584130` | `SETTLEMENT_GROUP_HOTEL_NOT_READY` | `团期子订单 {0} 的住宿尚未安排完成(住宿需求当前状态:{1}),请等房务配房完成后再提交核单` | 团期子订单需要配房,且住宿需求当前状态不是 `DONE`(含需求缺失) |
### 住宿需求状态枚举(`RequirementStatus`,本单只读不改,供理解 `{1}` 占位取值)
| value | label | 是否放行本闸 |
|---|---|---|
| `PENDING` | 待房务配 | 否 |
| `PROCESSING` | 配房中 | 否 |
| `DONE` | 配房完成 | 是(唯一放行值) |
| `PENDING_REVIEW` | 待审核 | 否 |
| `REJECTED_TO_CONSULTANT` | 驳回 | 否 |
| `REJECTED_TO_ADMIN` | 驳回 | 否 |
| (需求行不存在) | —— | 否,`message` 状态位显示"未提交住宿需求" |
### 前端提示文案建议
- 直接展示 `message` 原文即可(已含订单号 + 当前状态),无需二次包装。
- 若需要更醒目的引导,可在 `message` 之外追加一句操作指引,例如:"请前往房务模块查看该团期订单的住宿安排进度,配房完成后再回来提交核单。"
- 不建议把 `584130` 与其他 40xxxx/58xxxx 段错误码合并成同一个通用提示——该码语义明确(住宿未完成),合并展示会丢失"该去催房务"这条关键信息。
---
## 六.6、修改前后对比
### 行为级对比
| 场景 | 改前 | 改后 |
|---|---|---|
| 团期需房户,一条住宿派生行都没有 | **可以直接结算通过**(改前存在的漏洞) | 拒绝,`584130` |
| 团期需房户,住宿需求非 `DONE`(有派生行但未分平) | 可能继续核单(仅受行级 `CONFIRMED` 检查约束,0 行时不受约束) | 拒绝,`584130` |
| 团期需房户,住宿需求 `DONE` | 可核单 | 行为不变,仍可核单 |
| 团期免房户 | 可核单 | 行为不变 |
| 非团期订单 | 可核单(受核心订单自己的房控闸约束) | 行为不变,核心房控闸逻辑本单未动 |
| 该户住宿派生行已被撤销为 `GROUP_BATCH_PLAN_REVOKED` | 若无行级检查约束可能通过 | 拒绝,`584130`(除非需求 `status` 已是 `DONE`) |
### 字段级对比
无字段变化。请求体(无)、成功响应体 `SettlementSubmitRespVO` 的字段集合均未改动,仅错误响应的 `code` 取值范围新增了 `584130`。
---
## 六.7、影响评估
- **是否破坏向后兼容**:否。端点方法/路径/请求体/成功响应体结构均未变化;免房户、`DONE` 户、非团期订单的行为与改前逐字节一致。
- **前端是否必须同步上线**:需要新增 `584130` 的提示文案,否则该错误会被前端当成未知错误码兜底展示(用户体验较差,但不会导致请求失败或数据错误)。
- **前端 workaround 清理点**:如果前端此前依赖"住宿派生行是否存在"来判断该团期子订单是否可结算,需要清理——这个判断口径本身就是改前的漏洞,不应再使用;一律以 finalize 实际返回结果为准。
---
## 七、不影响范围
- **仅影响**:`POST /v3/admin/order/{orderId}/settlement/finalize` 端点在团期子订单(`productBatchId != null`)且需要配房(`needsHotel=true`)时的错误码集合。
- **零影响**:
- 无新增/修改/删除任何其他端点;网关路由零改动。
- 免房户(`needsHotel=false`)与非团期订单(`productBatchId` 为空)的 finalize 行为与改前完全一致。
- 成功响应体 `SettlementSubmitRespVO` 字段集合未变。
- 不读取团级 `order_group_batch.hotel_ready` 字段,不会出现"一户卡整团"的情况。
- 不涉及表结构变更、Flyway、数据迁移。
- 既有的住宿"已录入行全部 CONFIRMED"行级检查(`SettlementCategoryCheckService`)逻辑未改,新闸与它并列而非替代。
---
## 八、测试环境已验证
- **存量影响评估**:管理者已对测试库执行存量统计 SQL(统计"团期子订单中会被新闸拦住"的数量),**结果为 0**——没有任何存量团期子订单处于会被新闸拦截的状态。因此本单**全量生效,不做灰度**,无需推平存量数据。
- **部署状态**:测试服已部署 HEAD `18c1df126`,该 HEAD 即本单的实现提交本身(PR #7433)。
- **网关验证**:端点路径/方法未变,无需新增网关路由配置;沿用既有 `/v3/admin/order/**` 路由规则。
- **兼容性结论**:端点结构不变,仅扩展业务错误码集合,前端只需新增对 `584130` 的分支处理。
---
## 十、相关文档
- 关联 Issue: [wx/HL#7347](https://git.1814.love:8443/wx/HL/issues/7347)
- 前置/关联依赖:`#7327`(团期住宿行级确认,584129 与本单 584130 相邻但语义不同)、`#7325`(户级住宿完成判定,本单读取其写入的 `status` 字段)
## 关联 / 联系人
### 链接
- **Issue**: [#7347](https://git.1814.love:8443/wx/HL/issues/7347)
- **PR**: 尚未创建(合并后回填 [#N](https://git.1814.love:8443/wx/HL/pulls/N))
- **Merge commit**: 尚未产生(合并后回填 [https://git.1814.love:8443/wx/HL/commit/sha](https://git.1814.love:8443/wx/HL/commit/sha))
### 联系人
- **后端负责人**: @wx
- **前端负责人**: @mmg