docs(changelog): 团期预支防双花——团单子订单禁走订单级预支入口(589558)+团期财务在途口径补PAID (#8384)
changelog-filename-gate / validate (push) Failing after 2s

这个提交包含在:
yaosutu
2026-09-28 17:26:29 +08:00
父节点 52aa4a0fd0
当前提交 117a6815c7
@@ -0,0 +1,314 @@
---
schema: "hl-changelog/v2"
ticket: "8384"
title: "团期预支防双花:团单子订单禁走订单级预支入口(新错误码 589558)+ 团期财务在途预支口径补 PAID"
consumer: "admin"
author: "yst"
change_type: "修改接口"
backend_status: "deployed"
gateway_status: "verified"
frontend_status: "pending"
frontend_owner: ""
frontend_ref: ""
target_release: ""
verified_at: ""
status_note: ""
updated_at: "2026-09-28"
base: "dev-v3"
---
# 【修改接口·管理后台】团期预支防双花 (#8384)
> **PR**: #8479 | **服务**: hl-order-service-v3 | **更新时间**: 2026-09-28 16:30
## 1. 接口背景
团期预支走「统一池」口径:同一团期下所有预支(团期级 + 各子订单级)共用一个尾款池、共享一条额度上限。本次修复该模型下的两个「双花」缺口:
1. **入口双花**:团期产品的子订单(即订单挂在某个团期下的单)此前仍可以从「订单级预支」入口申请预支,同一笔尾款可能在订单级、团期级两个入口被重复支取。业务拍板:**团期子订单只允许走团期级预支入口,订单级入口一律拒绝**。
2. **口径双花**:团期财务 Tab 的「可支取余额」在计算扣减项时,漏算了「已付款但核单尚未扣回」的预支(状态 PAID),导致这笔钱在核单完成前被重复放出额度。本次把 PAID 补入扣减口径,与订单级预支上限的既有口径对齐。
## 2. 变更清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|------|------|------|----------|------|
| 1 | 创建预支(订单级) | POST | `/v3/admin/order/{orderId}/advance` | 行为变更 | 团期子订单调用直接返回新错误码 **589558**,不再创建预支;普通散客订单行为不变 |
| 2 | 团期财务总览(财务 Tab) | GET | `/v3/admin/order/group-batch/{groupBatchId}/finance` | 行为变更 | 「可支取余额」的统计口径补入 PAID 状态预支;字段结构零变化,返回值数值可能变小 |
## 3. 接口详情
### 3.1 创建预支(订单级)
- **使用场景**:订单详情页为单个订单的报账人申请预支借款(创建即进待审批)
- **认证**:需管理后台 JWT
- **幂等性**:否(同单并发创建由后端串行化)
- **限流**:无
### 3.2 团期财务总览(财务 Tab)
- **使用场景**:团期详情页「财务」Tab,展示整团应收/已收/待收/预支与逐户明细
- **认证**:需管理后台 JWT + `group-batch:finance:view` 权限
- **幂等性**:是(只读查询)
- **限流**:无
## 4. 接口入参
### 4.1 创建预支(POST `/v3/admin/order/{orderId}/advance`)
#### 路径参数
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| orderId | Long | 必填 | 订单 ID |
#### 请求体字段
| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|------|------|------|------|----------|
| payeeStaffId | Long | 必填 | 借款对象(取借款对象候选下拉接口 `GET /v3/admin/order/{orderId}/advance/payee-candidates` 返回的 id) | 必须为本订单的在职人员 |
| advanceType | String | 必填 | 预支借款类型(取数据字典 `advance_type` 的 dictValue) | 非空 |
| amount | Number | 必填 | 预支金额 | 必须 > 0,且不超过可支取余额上限 |
| purpose | String | 可选 | 用途说明 | — |
| voucherUrl | String | 可选 | 凭证文件 URL | — |
### 4.2 团期财务总览(GET `/v3/admin/order/group-batch/{groupBatchId}/finance`)
#### 路径参数
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| groupBatchId | Long | 必填 | 团期 ID |
无请求体、无 Query 参数。
## 5. 出参(响应)
### 5.1 创建预支 → `OrderAdvanceRespVO`
| 字段 | 类型 | 说明 |
|------|------|------|
| id | String(Long) | 预支 ID |
| orderId | String(Long) | 订单 ID |
| teamNo | String | 团号 |
| payeeStaffId | String(Long) | 报账人 staff 分配 ID(快照) |
| payeeName | String | 报账人姓名(快照) |
| payeeRole | String | 报账人角色代码 |
| payeeRoleText | String | 报账人角色中文文案 |
| advanceType | String | 预支类型 |
| amount | Number | 预支金额 |
| purpose | String | 用途说明 |
| voucherUrl | String | 凭证文件 URL |
| status | String | 状态代码(枚举见 §6) |
| statusText | String | 状态中文文案 |
| rejectReason | String | 驳回原因(仅 REJECTED 有值) |
| createdByName | String | 创建人姓名 |
| createTime | String(日期时间) | 创建时间 |
| submittedAt | String(日期时间) | 提交审批时间 |
| approvedAt | String(日期时间) | 审批时间 |
| approvedBy | String | 审批人姓名 |
### 5.2 团期财务总览 → `GroupBatchFinanceRespVO`
| 字段 | 类型 | 说明 |
|------|------|------|
| receivableAmount | String(金额) | 整团应收 |
| receivedAmount | String(金额) | 整团已收 |
| unpaidAmount | String(金额) | 整团待收(本期总尾款,预支上限计算基数) |
| advanceApproved | String(金额) | 已预支 = 本团期 APPROVED 状态预支合计(团期级 + 各子订单级) |
| advancePending | String(金额) | 待审批预支 = 本团期 SUBMITTED 状态预支合计 |
| advanceAvailable | String(金额) | **可支取余额** = 团期尾款池 −(待审批 + 已通过 + 已付款未核单),下限 0。**本次口径变化字段** |
| items | Array | 逐户付款明细(一户一行,全量返回不分页) |
| totals | Object | 整团合计行(totalPrice / paidAmount / unpaidAmount) |
| withdrawnCount | Integer | 已退团户数 |
| primaryPayeeName | String | 主报账人姓名(未设置为 null) |
| secondaryPayeeName | String | 次报账人姓名(未设置为 null) |
> 字段结构**零变化**;变化的是 `advanceAvailable` 的计算口径(见 §10)。
## 6. 枚举 / 数据字典
### 6.1 预支状态 status(`AdvanceStatus`)
**所属字段**:`OrderAdvanceRespVO.status` | **类型**:`String`
| 值 | 中文 | 说明 |
|----|------|------|
| `SUBMITTED` | 待审批 | 创建即进此态,占额度 |
| `APPROVED` | 已通过 | 财务审批通过,占额度 |
| `PAID` | 已支付 | 出纳已付款、核单未扣回,**仍占额度**(核单完成才释放) |
| `REJECTED` | 已驳回 | 终态,不占额度 |
## 7. 错误码
| code | 含义 | 触发场景 |
|------|------|----------|
| **589558**(新增) | 团期订单请通过团期预支入口申请 | 对**团期子订单**(挂在团期下的订单)调用订单级创建预支接口 `POST /v3/admin/order/{orderId}/advance` 时直接返回,不创建预支 |
| 585003 | 当前订单状态不允许预支 | 订单状态不在 {待出发, 行程中} 或已结算完成(既有行为不变;团单判断优先于此码) |
| 585004 | 预支金额超过可用余额上限 | 申请金额 > 可支取余额上限(既有行为不变) |
> 团单判断**先于**状态判断:团期子订单即使状态满足,也固定返回 589558,不会返回 585003。
## 8. 示例(3 组:典型 / 边界 / 异常)
### 8.1 业务失败(新增场景):团期子订单调订单级预支入口
**场景说明**:订单是团期产品的子订单,前端仍调了订单级入口 → 返回 589558。
**请求**:
```
POST /v3/admin/order/1234567890123456789/advance
Authorization: Bearer <token>
Content-Type: application/json
```
```json
{
"payeeStaffId": 9876543210987654321,
"advanceType": "TRAVEL_EXPENSE",
"amount": 500.00,
"purpose": "门票垫付"
}
```
**响应**:
```json
{
"code": 589558,
"message": "团期订单请通过团期预支入口申请",
"data": null
}
```
### 8.2 典型成功:普通散客订单正常创建预支
**场景说明**:订单不挂在任何团期下,状态为待出发/行程中、未结算完成 → 行为与改前完全一致。
**请求**:
```
POST /v3/admin/order/1111222233334444555/advance
Authorization: Bearer <token>
Content-Type: application/json
```
```json
{
"payeeStaffId": 9876543210987654321,
"advanceType": "TRAVEL_EXPENSE",
"amount": 500.00,
"purpose": "门票垫付"
}
```
**响应**:
```json
{
"code": 200,
"message": "success",
"data": {
"id": "7777888899990000111",
"orderId": "1111222233334444555",
"teamNo": "26-0480",
"payeeStaffId": "9876543210987654321",
"payeeName": "张领队",
"payeeRole": "GUIDE",
"payeeRoleText": "导游",
"advanceType": "TRAVEL_EXPENSE",
"amount": 500.00,
"purpose": "门票垫付",
"voucherUrl": null,
"status": "SUBMITTED",
"statusText": "待审批",
"rejectReason": null,
"createdByName": "李财务",
"createTime": "2026-09-28T16:00:00",
"submittedAt": "2026-09-28T16:00:00",
"approvedAt": null,
"approvedBy": null
}
}
```
### 8.3 边界情况:团期财务 Tab 可支取余额口径
**场景说明**:某团期尾款池(unpaidAmount)= 7300.00;本团期有 1 笔预支已审批通过并已付款(PAID,金额 2000.00),核单尚未扣回。
**请求**:
```
GET /v3/admin/order/group-batch/5555666677778888999/finance
Authorization: Bearer <token>
(无请求体)
```
**响应(改后)**:
```json
{
"code": 200,
"message": "success",
"data": {
"receivableAmount": "40200.00",
"receivedAmount": "32900.00",
"unpaidAmount": "7300.00",
"advanceApproved": "0.00",
"advancePending": "0.00",
"advanceAvailable": "5300.00",
"items": [],
"totals": { "totalPrice": "40200.00", "paidAmount": "32900.00", "unpaidAmount": "7300.00" },
"withdrawnCount": 0,
"primaryPayeeName": "张领队",
"secondaryPayeeName": null
}
}
```
> 对比:改前同一数据下 `advanceAvailable` 返回 `"7300.00"`(PAID 的 2000.00 未计入扣减);改后返回 `"5300.00"`。该笔 PAID 预支在核单完成扣回后才会释放额度。
## 9. 业务边界
- 适用场景(订单级预支):不挂在任何团期下的普通散客订单,状态为待出发/行程中、未结算完成 → 行为与改前**完全一致**
- 不适用场景(订单级预支):团期产品的子订单 → 固定返回 589558,须改用团期级预支入口(`POST /v3/admin/order/group-batch/{groupBatchId}/advance`,该接口逻辑本次不变)
- 特殊边界(财务 Tab):团期下存在 PAID(已付款未核单)预支时,`advanceAvailable` 比改前变小;核单完成后该笔释放,`advanceAvailable` 回升。这是口径修正,不是数据异常
## 10. 修改前后对比
### 10.1 字段级对比
无字段增删 / 改名 / 类型变化。
### 10.2 行为级对比
| 行为 | 改前 | 改后 |
|------|------|------|
| 团期子订单调订单级预支入口 | 放行(只看本单状态/上限),可能与团期级重复支取同一笔尾款 | 直接返回 **589558**,不创建预支 |
| 团期财务 Tab「可支取余额」扣减口径 | 只扣 SUBMITTED + APPROVED | 扣 SUBMITTED + APPROVED + **PAID**(已付款未核单也占额度) |
| 普通散客订单订单级预支 | 原有校验链 | **不变** |
| 团期级预支入口 | 原有逻辑 | **不变** |
## 11. 影响评估 / 回滚
### 11.1 影响评估
- **是否破坏向后兼容**:行为收紧型变更。① 团期子订单此前能调通的订单级预支入口现在固定失败(589558)——若前端在团期子订单详情页暴露了订单级预支入口,需要处理该错误码;② 财务 Tab `advanceAvailable` 数值在存在 PAID 预支时变小,属口径修正
- **前端是否必须同步上线**:否。接口字段结构零变化;但建议尽快处理 589558 的提示与入口引导(把团期子订单的预支操作引导到团期级入口)
- **影响已有数据**:无数据迁移;既有 PAID 预支自动按新口径计入扣减
### 11.2 回滚方案
- **回滚方式**:revert merge commit `7bbc98c335`,重新部署 hl-order-service-v3 即恢复原行为
- **回滚后清理**:无需清理数据 / 缓存
- **回滚耗时**:约等于一次常规服务部署
## 12. 注意事项
- 若前端此前对「团期子订单调订单级预支失败」有任何本地兜底 / workaround(如自行判断团单后隐藏入口但保留了调用),本次后端已硬拦,相关兜底不会再触发到创建成功分支
- 财务 Tab 的 `advanceAvailable` 请继续直接读接口返回值,不要本地自算(本次口径调整只发生在服务端,本地自算会与卡片对不上)
- 需要重启的服务:**hl-order-service-v3**(测试服部署走 Deploy Panel 操作)
## 13. 关联 / 联系人
### 13.1 链接
- **Issue**: [#8384](https://git.1814.love/wx/HL/issues/8384)
- **PR**: [#8479](https://git.1814.love/wx/HL/pulls/8479)
- **Merge commit**: [7bbc98c335](https://git.1814.love/wx/HL/commit/7bbc98c3359ef2fe31cd69e9c098a29d625caca4)
### 13.2 联系人
- **后端负责人**: @yst