docs(order-v3): #7781 核单分类未确认提示语补分类名 584310 changelog(已部署 dev-v3 d3d4755e9 并实测)
changelog-filename-gate / validate (push) Failing after 1s
changelog-filename-gate / validate (push) Failing after 1s
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
这个提交包含在:
@@ -0,0 +1,393 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "7781"
|
||||
title: "核单分类未确认提示语补分类名(584310 文案含占位符)"
|
||||
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: "已合并 dev-v3(squash 提交 d3d4755e9,PR #7781),测试服 hl-order-service-v3 已部署该提交(2026-09-16 07:10:22,deploy-status.sh 核实 commit=d3d4755e9、BEHIND=0/N)。触发点一(8 分类循环,HOTEL 命中)已用自造测试订单实测:POST /v3/admin/order/8880000000778101001/settlement/finalize 返回 code=584310、message=核单分类「住宿」明细尚未全部确认或数据已变化,占位符已正确替换为具体分类名。触发点二(SettlementVehicleFeeSnapshotWriter 车辆草稿未确认,分类恒填「车辆」)本次未取证:该分支要求先有真实车辆需求+Fleet 派车+车辆草稿快照(reconcile 需要 Fleet Feign 真实回填数据),用直接 SQL 造数无法安全绕过 draftService.loadCandidate/lockReadySnapshot 的事实一致性比对(会被判 REPORT_SOURCE_CHANGED 而非 584310),需要完整走一遍团期车务派车流程才能自然触发,超出本次取证窗口,未编造该路径证据。本次没有新增 admin 路由,gateway_status=not_required。frontend_status=pending:若 hl-ui 存在对旧文案 八个核单分类尚未全部确认或数据已变化 做精确字符串匹配的逻辑,必须改为按 code=584310 判定,新文案恒含动态分类名,旧字面量不会再出现。本单无独立 Gitea Issue,PR #7781 备注 无关联工单(顺带发现的两处问题)。"
|
||||
updated_at: "2026-09-16"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# order-v3: 核单分类未确认提示语补分类名(584310)
|
||||
|
||||
> **存放目录**: `changelogs-v2/{YYYY-MM}/`(管理后台,二期 order-v3)
|
||||
>
|
||||
> **服务**: hl-order-service-v3 (端口 8083)
|
||||
> **PR**: #7781
|
||||
> **Issue**: 无独立 Issue(PR 备注「无关联工单(顺带发现的两处问题)」)
|
||||
> **日期**: 2026-09-16
|
||||
> **影响范围**: 错误码 `584310`(`CATEGORY_CHECK_INCOMPLETE`)的 message 文本;`code`、HTTP 状态(恒 200)、`data`、请求/响应结构均未改
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
🔴 **错误码 `584310` 的 message 文本变了,`code` 和 HTTP 状态都没变(HTTP 恒 200,`code=584310`)。**
|
||||
|
||||
- 改前:`八个核单分类尚未全部确认或数据已变化`(固定文案,不含分类名)
|
||||
- 改后:`核单分类「{0}」明细尚未全部确认或数据已变化`,`{0}` 由后端填成实际未确认的分类名(取值来自 `SettlementCategory` 枚举 label,共 8 个分类,见「六.5、枚举」)
|
||||
|
||||
🟡 **前端如果之前对旧文案「八个核单分类尚未全部确认或数据已变化」做过精确字符串匹配(例如按 message 做特殊提示/埋点),该匹配会永久失效——新文案恒含动态分类名,旧字面量不会再出现。必须改为按 `code === 584310` 判定,见「四、契约约束与正确调用方式」。**
|
||||
|
||||
🟢 **测试环境已验证**(2026-09-16,order-v3 dev-v3 `d3d4755e9`):触发点一(8 分类循环,HOTEL 命中)自造订单实测通过,返回 `code=584310`、`message=核单分类「住宿」明细尚未全部确认或数据已变化`;触发点二(车辆草稿未确认)因需真实 Fleet 派车数据未取证,详见「八、测试环境已验证」。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
`SettlementFinancialErrorCode.CATEGORY_CHECK_INCOMPLETE`(584310)原来的模板不带占位符,但两处实际抛出点早就在传分类名参数——参数被静默丢弃(模板没有 `{0}` 时不经 `MessageFormat.format`,直接原样返回模板本身)。前端只能看到笼统的「八个核单分类尚未全部确认或数据已变化」,无法定位到底是哪个分类没确认。本单只改模板加占位符,并给两处抛出点分别核实/补传参数,不改判定逻辑本身、不改触发条件。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 完成核单 | POST | `/v3/admin/order/{orderId}/settlement/finalize` | 错误码文案修正 | `584310` message 模板补分类占位符;`code`/HTTP 状态/响应结构不变 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 完成核单 `POST /v3/admin/order/{orderId}/settlement/finalize`
|
||||
|
||||
**VO**: `无请求体 → SettlementSubmitRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
管理后台核单页「完成核单」按钮:提交主报账、单团核算双指纹,原子冻结车辆事实并把核单标记为终态。本单不改这个端点的请求/响应结构,只改它在两种失败场景下返回的 584310 message 文本。
|
||||
|
||||
584310 只能通过这一个端点触发——全仓 `CATEGORY_CHECK_INCOMPLETE` 共 2 处生产代码抛出点,两处都在本端点的调用链上(见「四、契约约束与正确调用方式」的调用链说明),没有第三条触发路径。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| orderId | Path | Long | 是 | 大于 0 | 订单 ID;不变 |
|
||||
|
||||
(无请求体,本单未改任何入参。)
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| summaryId | String(雪花 ID) | 新写入的 settlement_summary 主键 |
|
||||
| finalSnapshotId | String(雪花 ID) | 核单终态快照 ID |
|
||||
| finalSnapshotVersionNo | Integer | 核单终态快照版本号 |
|
||||
| finalSnapshotStatus | String | 核单终态快照状态 |
|
||||
| orderId | String(雪花 ID) | 订单 ID |
|
||||
| settledAt | String(LocalDateTime) | 核单完成时间 |
|
||||
| totalAmount | String(金额) | 订单总金额快照 |
|
||||
| paidAmount | String(金额) | 已付金额快照 |
|
||||
| balanceAmount | String(金额) | 尾款金额快照 |
|
||||
| roomCost | String(金额) | 住宿实际成本 |
|
||||
| ticketCost | String(金额) | 门票实际成本 |
|
||||
| staffCost | String(金额) | 人员费用实际成本 |
|
||||
| subsidyCost | String(金额) | 补助实际成本 |
|
||||
| mealCost | String(金额) | 餐食实际成本 |
|
||||
| vehicleCost | String(金额) | 车辆基础服务总车费 |
|
||||
| otherExpenseCost | String(金额) | 其他支出实际成本 |
|
||||
| insurancePremium | String(金额) | 保险实际保费 |
|
||||
| totalActualCost | String(金额) | 总实际成本 |
|
||||
| driverTransferAmount | String(金额) | 给司机/主报账人转回金额 |
|
||||
| profitAmount | String(金额) | 公司毛利 |
|
||||
| profitRate | Number(小数) | 毛利率 |
|
||||
| orderStatusAfter | String | 结算后订单状态 |
|
||||
| mqTriggered | Boolean | 当前版本固定 false |
|
||||
| warnings | Array of WarningItemVO | 软预警列表 |
|
||||
|
||||
以上字段全部来自 `SettlementSubmitRespVO` 既有定义,本单一个字段都没改;列出仅为满足模板「逐接口自包含」要求。
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
POST /v3/admin/order/2099716954674597889/settlement/finalize HTTP/1.1
|
||||
Authorization: Bearer {token}
|
||||
```
|
||||
|
||||
(无请求体,仅 Path 参数 orderId;本单未改。)
|
||||
|
||||
#### 响应示例
|
||||
|
||||
成功响应结构本单未动,字段值取自源码 `@ApiModelProperty(example=...)` 声明(本单只改错误响应文案,成功响应本身未实测,见「八、测试环境已验证」):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"summaryId": "9600000000001",
|
||||
"finalSnapshotId": "9600000000002",
|
||||
"finalSnapshotVersionNo": 1,
|
||||
"finalSnapshotStatus": "FINALIZED",
|
||||
"orderId": "12345678901234",
|
||||
"settledAt": "2026-05-26T10:30:25",
|
||||
"totalAmount": "24800.00",
|
||||
"paidAmount": "24800.00",
|
||||
"balanceAmount": "0.00",
|
||||
"roomCost": "4280.00",
|
||||
"ticketCost": "3680.00",
|
||||
"staffCost": "14260.00",
|
||||
"subsidyCost": "720.00",
|
||||
"mealCost": "860.00",
|
||||
"vehicleCost": "5200.00",
|
||||
"otherExpenseCost": "1260.00",
|
||||
"insurancePremium": "180.00",
|
||||
"totalActualCost": "23120.00",
|
||||
"driverTransferAmount": "22940.00",
|
||||
"profitAmount": "1680.00",
|
||||
"profitRate": 0.0677,
|
||||
"orderStatusAfter": "待财务复核",
|
||||
"mqTriggered": false,
|
||||
"warnings": []
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
无列表/分页语义,不存在空数据形态。warnings 为空数组时代表无软预警,不阻塞提交:
|
||||
|
||||
```json
|
||||
{ "code": 200, "success": true, "data": { "warnings": [] } }
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
584310 可能由两个不同的判定点触发,都在本端点内,都是本单实际改动点:
|
||||
|
||||
| 触发点 | 场景 | `{0}` 取值 |
|
||||
|---|---|---|
|
||||
| `SettlementCategoryCheckService.validateReadyForReport`(`hl-order-service-v3/src/main/java/com/hulalv/order/settlement/service/SettlementCategoryCheckService.java:85-92`) | 按 `SettlementCategory` 枚举声明顺序(住宿/门票游玩项目/餐食/车辆/导游/摄影/其他收入/其他支出)逐个检查「有明细行但未全部确认」,命中第一个就抛出并停止,不会一次性列出全部未确认分类 | 8 个分类中,第一个命中的那个 |
|
||||
| `SettlementVehicleFeeSnapshotWriter.replaceAndFreezeDraftInCurrentTransaction`(`hl-order-service-v3/src/main/java/com/hulalv/order/settlement/service/SettlementVehicleFeeSnapshotWriter.java:250-256`) | 车辆草稿行存在未确认(`settlementConfirmStatus` 不等于 `CONFIRMED`);该方法只处理车辆草稿行,在整个 finalize 调用链里先于上一行的 8 分类循环执行 | 恒为「车辆」(硬编码 `SettlementCategory.VEHICLE.getLabel()`,与草稿行具体是哪一条无关) |
|
||||
|
||||
示例一(车辆草稿未全部确认,命中较早的判定点):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 584310,
|
||||
"message": "核单分类「车辆」明细尚未全部确认或数据已变化",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
示例二(8 分类循环命中「餐食」,其余分类此前已确认或为空):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 584310,
|
||||
"message": "核单分类「餐食」明细尚未全部确认或数据已变化",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
改前(两处触发点,本单修复前的固定文案,不区分分类):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 584310,
|
||||
"message": "八个核单分类尚未全部确认或数据已变化",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **584310 只在本端点触发**:全仓 `CATEGORY_CHECK_INCOMPLETE` 只有上表两处生产代码抛出点,且都只能通过 `POST /v3/admin/order/{orderId}/settlement/finalize` 这一条调用链到达(`SettlementController.finalizeSettlement` → `SettlementFinalizeOrchestrator` → `SettlementFinalizeLockService` → `SettlementFinalizeTxService.finalizeSettlement`,内部依次调用车辆冻结与 8 分类校验)。
|
||||
- **8 分类循环短路**:`validateReadyForReport` 按枚举声明顺序(住宿/门票游玩项目/餐食/车辆/导游/摄影/其他收入/其他支出)遍历,一旦命中未确认分类立即抛出,不会把全部未确认分类一次性列全;前端不能假设 message 里的分类名是唯一一个未确认分类,重试提交、修完这个再确认下一个可能还会拿到另一个分类名的 584310。
|
||||
- **空分类不计入判定**:某分类若没有任何明细行(`rowCount() == 0`),不需要确认,不会触发 584310,即使它看起来没被确认过。
|
||||
- **车辆草稿分类名恒为「车辆」**:即使草稿行本身归属不同需求(如接送机/包车),触发点二的占位符固定填「车辆」,不细分到具体是哪一条草稿行。
|
||||
- **`data` 恒为 `null`**:与改前一致,本单未改 `Result.error(ec, args)` 的调用方式,仍是 `code + message`,不带附加结构化载荷。
|
||||
- **不做兼容双发**:后端不会同时给出旧文案和新文案,也不新增字段承载纯分类名——分类名只存在于 message 字符串里,前端要拿分类名必须自己从 message 里按「」定界解析,或者干脆只按 `code` 判定、把 message 原样展示给用户(推荐做法,见下节)。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
> 本节只写后端接受/拒绝 payload 的规则,不写 UI 渲染建议。
|
||||
|
||||
### 正确 / 错误 判定方式对照
|
||||
|
||||
| 场景 | 判定方式 |
|
||||
|------|---------|
|
||||
| 正确:只用 `code` 判定是否为核单分类未确认错误 | `if (resp.code === 584310) { ... }` |
|
||||
| 正确:需要展示分类名时,直接展示 `message` 原文(后端已经把分类名拼好) | `showToast(resp.message)` |
|
||||
| 错误:对 `message` 做精确字符串匹配 | `if (resp.message === "八个核单分类尚未全部确认或数据已变化")`,本单起该字面量永久不会再出现,匹配会一直失败 |
|
||||
| 错误:假设一次 584310 只会报一个唯一未确认分类,据此更新本地已确认分类清单 | 见业务边界短路说明,可能还有其他未确认分类没被这次报出来 |
|
||||
|
||||
### 调用链(说明 584310 为什么只挂在这一个端点上)
|
||||
|
||||
```
|
||||
SettlementController.finalizeSettlement (POST /v3/admin/order/{orderId}/settlement/finalize)
|
||||
-> SettlementFinalizeOrchestrator.finalizeSettlement
|
||||
-> SettlementFinalizeLockService.finalizeSettlement
|
||||
-> SettlementFinalizeTxService.finalizeSettlement
|
||||
+- SettlementVehicleFreezeService.freezeForFinalizeInCurrentTransaction
|
||||
| -> SettlementVehicleFeeSnapshotWriter.replaceAndFreezeDraftInCurrentTransaction (触发点二,恒「车辆」)
|
||||
+- SettlementService.finalizeInCurrentTransaction
|
||||
-> SettlementReportFlowService.prepareFinalizationInCurrentTransaction
|
||||
-> SettlementCategoryCheckService.validateReadyForReport (触发点一,8 分类循环)
|
||||
```
|
||||
|
||||
前端无需改任何请求参数——本端点入参/路径未变;判定完全由后端按订单当前核单分类确认状态计算。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
本单不改任何数据库读写行为,属于纯文案/参数传递修正:两处判定的读取逻辑(哪个分类算未确认)本身完全不变,只是把早就存在的 `category.getLabel()` 参数真正传进消息格式化。`finalize` 端点既有的核单写入语义(写 settlement_summary/终态快照/状态流水等)不在本单改动范围内,不在此重复。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 未登录 → 401(网关拦截,不变)
|
||||
- `orderId <= 0` → 400(`@Min` 校验,不变)
|
||||
- 8 个分类全部确认(或没有明细行)→ 不触发 584310,走正常 finalize 流程(不变)
|
||||
- 车辆草稿行未全部确认 → 584310,分类名填「车辆」(本单起分类名可见,改前是固定文案)
|
||||
- 非车辆分类(住宿/门票游玩项目/餐食/导游/摄影/其他收入/其他支出)有明细行但未全部确认 → 584310,分类名为 8 分类循环里第一个命中的分类名(本单起分类名可见)
|
||||
- 老数据兼容:本单不改任何判定条件,存量订单的确认状态数据无需回填、无需迁移,行为按新文案原样生效
|
||||
|
||||
---
|
||||
|
||||
## 六.5、枚举 / 数据字典
|
||||
|
||||
**所属字段**:错误响应 `message` 中占位符的取值来源(不是请求/响应 JSON 里的字段,`message` 整体仍是 `String`)| **枚举类**:`com.hulalv.order.settlement.enums.SettlementCategory`
|
||||
|
||||
| 值(枚举名) | label(占位符实际填入的中文) | 说明 |
|
||||
|----|------|------|
|
||||
| `HOTEL` | 住宿 | |
|
||||
| `TICKET` | 门票/游玩项目 | label 本身含斜杠,不是格式错误 |
|
||||
| `MEAL` | 餐食 | |
|
||||
| `VEHICLE` | 车辆 | 触发点二恒填此值;触发点一按遍历顺序命中时也可能填此值 |
|
||||
| `GUIDE` | 导游 | |
|
||||
| `PHOTOGRAPHER` | 摄影 | |
|
||||
| `OTHER_INCOME` | 其他收入 | |
|
||||
| `OTHER_EXPENSE` | 其他支出 | |
|
||||
|
||||
触发点一(`validateReadyForReport`)按上表从上到下的顺序遍历并短路——`VEHICLE` 排第 4 位,只有前 3 个分类都确认完毕,才可能在这个循环里报出车辆分类;正常情况下车辆的未确认状态会先被触发点二拦截。
|
||||
|
||||
---
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
### 字段级对比
|
||||
|
||||
| 字段 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| `code`(584310) | `584310` | `584310`(不变) |
|
||||
| HTTP 状态 | `200` | `200`(不变,恒 `@ResponseStatus(HttpStatus.OK)`) |
|
||||
| `message` 模板(`SettlementFinancialErrorCode.CATEGORY_CHECK_INCOMPLETE`) | `八个核单分类尚未全部确认或数据已变化`(无占位符) | `核单分类「{0}」明细尚未全部确认或数据已变化`(占位符为分类 label) |
|
||||
| `data` | `null` | `null`(不变) |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 场景 | 改前返回的 message | 改后返回的 message |
|
||||
|------|------|------|
|
||||
| 车辆草稿未全部确认 | `八个核单分类尚未全部确认或数据已变化` | `核单分类「车辆」明细尚未全部确认或数据已变化` |
|
||||
| 餐食有明细行但未全部确认(其余分类已确认) | `八个核单分类尚未全部确认或数据已变化` | `核单分类「餐食」明细尚未全部确认或数据已变化` |
|
||||
| 8 个分类全部确认 | 不触发 584310(不变) | 不触发 584310(不变) |
|
||||
|
||||
---
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**:`code`/HTTP 状态/`data` 结构均未改,接口契约的机器可读部分向后兼容;但 `message` 的字面量值变了——若前端按旧文案精确字符串匹配,这类匹配会被破坏(见下)。
|
||||
- **前端是否必须同步上线**:视前端现有实现而定。若 hl-ui 对 584310 的 `message` 做过精确字符串匹配(不管是用于弹窗文案分支还是埋点),必须同步改为按 `code === 584310` 判定;若前端本就是拿到 584310 直接展示 message 原文、不做字符串匹配的口径,则零改动。本单未接触 hl-ui 代码库,无法从后端仓库判定该假设是否成立,需前端自行核实(对应 `frontend_status: pending`)。
|
||||
- **前端 workaround 清理点**:若前端此前因为旧文案笼统、不知道是哪个分类而做过额外的提示语兜底(例如统一显示请检查全部 8 个分类),现在后端已经把具体分类名带出来,这类兜底提示可以清理、直接透传 message。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- **仅影响**:`584310`(`CATEGORY_CHECK_INCOMPLETE`)这一个错误码的 `message` 文本。
|
||||
- **零影响**:
|
||||
- `POST /v3/admin/order/{orderId}/settlement/finalize` 的成功响应结构(`SettlementSubmitRespVO` 全部 24 个字段)——本单一个字段都没碰。
|
||||
- `584310` 的 `code` 值、触发条件、HTTP 状态——判定逻辑本身完全不变,只是把已经存在的参数真正传进消息模板。
|
||||
- 该错误码段(584300-584399)内其余错误码(如 `584311` 主报账表尚未生成、`584318` 空分类必须显式确认等)——本单未动。
|
||||
- 核单页的其余接口(应收总览、报账表、单团核算表、反确认等)——本单只改了 finalize 一个端点能返回的一个错误码文案。
|
||||
- 小程序端(`consumer: mp`)——本端点为管理后台端点,小程序无影响。
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
**部署确认**(2026-09-16,`deploy-status.sh` 经 SSH 在测试服执行):
|
||||
|
||||
```
|
||||
SERVICE BRANCH COMMIT BEHIND DEPLOYED_AT BY STATE
|
||||
hl-gateway dev-v3 6a7b43a17 29/Y 2026-09-15 12:16:26 root@local(python3) ok
|
||||
hl-order-service-v3 dev-v3 d3d4755e9 0/N 2026-09-16 07:10:22 root@192.168.100.168 ok
|
||||
```
|
||||
|
||||
order-v3 当前跑的就是 PR #7781 的 squash 合并提交 `d3d4755e9`(BEHIND=0/N,dev-v3 之后没有再碰本服务的提交);hl-gateway 也在 dev-v3(本单未新增路由,无需网关改动)。
|
||||
|
||||
**触发点一实测(8 分类循环,HOTEL 命中,`SettlementCategoryCheckService.validateReadyForReport`)**:
|
||||
|
||||
自造测试订单 `order_id=8880000000778101001`(`order_no=HL7781FIXTURE01`,`customer_remark=#7781 584310 取证`),造数:`order_main`(`review_status=PENDING`、`paid_amount=0.00`/`deposit_amount=0.00` 满足对账一致性前置)、`order_settlement_hotel` 一条 `settlement_confirm_status=UNCONFIRMED` 的住宿明细行(字段齐全,满足 `readyLine` 数据质量判据)、`order_settlement_category_check` 八个分类各一条 `source_sync_status=READY`(满足 `readyForReport` 前置,仅 HOTEL 有明细行)。
|
||||
|
||||
请求:
|
||||
|
||||
```http
|
||||
POST https://api.test.1814.love:9443/v3/admin/order/8880000000778101001/settlement/finalize HTTP/1.1
|
||||
Authorization: Bearer {token}
|
||||
```
|
||||
|
||||
请求时刻:2026-09-16 07:27:07
|
||||
|
||||
响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 584310,
|
||||
"message": "核单分类「住宿」明细尚未全部确认或数据已变化",
|
||||
"data": null,
|
||||
"traceId": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
`code=584310`、`message` 占位符已正确替换为「住宿」(不含 `{0}` 字面量)、`data=null`,与源码预期一致。
|
||||
|
||||
**触发点二未实测(车辆草稿未确认,`SettlementVehicleFeeSnapshotWriter.replaceAndFreezeDraftInCurrentTransaction`,分类恒填「车辆」)**:
|
||||
|
||||
该分支要求先有真实车辆需求(TRAVEL/TRANSFER)+ Fleet 已完结派车数据,`finalize` 在事务外先调用 `SettlementVehicleFeeService.prepareFreezeCandidateForReport`(经 `SettlementVehicleDraftService.loadCandidate/reconcile` 读 Fleet Feign 真实回填数据生成候选),事务内再由 `SettlementVehicleFreezeService.freezeForFinalizeInCurrentTransaction` 用 `draftService.lockReadySnapshot` 重新锁定一份快照,与候选比对不一致会先报 `REPORT_SOURCE_CHANGED`(而非 584310)。直接 SQL 向草稿表插入「未确认」行绕不过这道事实一致性校验(构造的草稿数据与 Fleet 真实候选对不上),需要完整走一遍团期车务派车(建需求→派车→回填每日车费→草稿 reconcile)才能自然产生「草稿行存在但未全部确认」的状态,超出本次取证窗口,未编造该路径证据。本单代码逻辑已按源码复核(见「三、接口详情」表格与 `SettlementVehicleFeeSnapshotWriter.java:250-256`),仅缺实测响应。
|
||||
|
||||
**本地/CI 编译与单测证据**(非测试环境实测,仅作为代码正确性的辅助信息):
|
||||
|
||||
- `mvn -o -pl hl-order-service-v3 -am test-compile` 通过。
|
||||
- 定向单测合计 165/0/0/0:`SettlementCategoryCheckServiceTest` 16、`SettlementPrototypeFlowContractTest` 11、`SettlementServicePr5Test` 41、`SettlementVehicleFeeSnapshotWriterTest` 54、`LayerEnforcementTest` 5、`MapperBoundaryArchTest` 26、`RedLineArchTest` 12。
|
||||
- 两处触发点各补了一条断言渲染后 message 的用例(含 `doesNotContain` 对占位符原文的断言,防止占位符原样露给前端)。
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 关联 PR: [wx/HL#7781](https://git.1814.love:8443/wx/HL/pulls/7781)
|
||||
- 关联 Issue:无独立 Issue(PR 备注「无关联工单(顺带发现的两处问题)」)
|
||||
- 合并提交:`d3d4755e9331243dad0129c2da04466cafc3bca5`(已在 dev-v3 主线)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: 无独立 Issue(见上)
|
||||
- **PR**: [#7781](https://git.1814.love:8443/wx/HL/pulls/7781)
|
||||
- **Merge commit**: [d3d4755e9](https://git.1814.love:8443/wx/HL/commit/d3d4755e9331243dad0129c2da04466cafc3bca5)(已在 dev-v3 主线)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
在新工单中引用
屏蔽一个用户