changelog-filename-gate / validate (push) Successful in 2s
#7347:整单核单 finalize 新增团期住宿户级硬闸门,新增错误码 584130。 存量统计 SQL 结果为 0,全量生效不做灰度。 #7390:双写扣减孤儿持有行自动收敛,解开重新提交配房恒抛 808902 的死锁。 显著标注「本单不修根因」——submitPersist 仍在 try 块外,孤儿行仍会继续产生。 #7316 订正三处:原文写「本端点无权限码要求……这是既有设计」, 那不是设计而是 #7411 要修的缺陷,已改写为发布时点的事实陈述并指向 #7411。 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
275 行
16 KiB
Markdown
275 行
16 KiB
Markdown
---
|
||
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: "pending"
|
||
frontend_owner: "mmg"
|
||
frontend_ref: ""
|
||
target_release: ""
|
||
verified_at: ""
|
||
status_note: "后端已实现并部署测试服(HEAD 18c1df126,即本单实现提交本身,PR #7433);存量统计SQL结果为0,全量生效不做灰度。前端唯一需要做的事是给 584130 加提示文案,展示后端 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
|