文件
hl-api-changelog/changelogs-v2/2026-09/08_7294_流团对已付款户也取消并发起退款-修改接口-管理后台.md
T
jw和Claude Opus 5 b22345d167
changelog-filename-gate / validate (push) Successful in 2s
docs(changelog): #7294 补充 actualRefundAmount 的暂定值/定稿值口径(PR #7354)
退款单在流团事务【提交之后】才建(异步),所以 approve 响应体里的
actualRefundAmount 是暂定值=退款诉求合计;随后服务端逐户回读实际建单结果,
把没建出退款单的户剔掉,改写成定稿值。要展示准确金额请重新拉一次详情。

本文原本已写「actualRefundAmount 只统计成功发起退款的户」,PR #7354 之前
实现其实是 Σ诉求、有户建单失败就对不上;现在实现与本文口径一致了。

顺带修两处当前校验器不再接受的 frontmatter:
consumer hl-ui → admin;not_required 不得保留 frontend_owner / verified_at。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 18:58:20 +08:00

384 行
17 KiB
Markdown
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
---
schema: "hl-changelog/v2"
ticket: "7294"
title: "批准流团后已付款户会被自动取消并发起退款;审批单实退金额不再恒为 0;新增 589554 出行中户硬闸"
consumer: "admin"
author: "jw(GIT)"
change_type: "修改接口"
backend_status: "deployed"
gateway_status: "verified"
frontend_status: "not_required"
frontend_owner: ""
frontend_ref: ""
target_release: ""
verified_at: ""
status_note: "前端不改:接口出入参零变化;actualRefundAmount 前端本就直显后端权威值(group-batch-approval/index.vue 与 withdraw/index.vue formatPrice 直读),口径修正由后端算;589554 出行中户硬闸由拦截器透后端 message(无中央错误码字典)。补充(PR #7354):approve 响应里的 actualRefundAmount 是暂定值,定稿在提交后异步改写(约 1 秒内);上述两个页面读的是列表/详情接口不是 approve 响应,故仍不需要改,但请勿改成直接渲染 approve 响应的该字段。"
updated_at: "2026-09-08"
base: "dev-v3"
---
# 团期: 流团对已付款户也取消并发起退款 + 出行中户硬闸 589554
> **服务**: hl-order-service-v3(只需部署这一个)
> **PR**: #7333
> **Issue**: #7294
> **日期**: 2026-09-08
> **影响范围**: 团期流团申请与审批(GB-ADM-060 / GB-ADM-062)
---
## ⚠️ 关键变化
**出入参结构一个字段都没改。** 前端不改不报错,但有三件事必须知道:
1. **批准流团后,已付款的户现在会被自动取消并发起退款**。改前只有「待支付」的户被取消,
付过定金或全款的户**既不取消也不退款**——团已解散,活跃子订单和客户的钱都还留着。
2. **审批单的「实退金额」不再恒为 0**。改前 `actualRefundAmount` 是系统性的 0,
与「预估退款」形成无人负责的落差;现在它等于**本次实际发起的退款诉求合计**。
⚠️ 这是**退款诉求**不是**实际到账**,界面文案建议写「已发起退款金额」。
3. **新增错误码 589554**「团期下有出行中的子订单,不可流团」,前端错误码字典需补录。
发起流团与批复流团两个入口都会返这个码。
改前实测(AC-4,在部署本次改动**之前**于测试服复现):三户(未付款 / 付订金 2000 / 已补尾款 6000)
批准流团后,只有未付款那户被取消,另两户仍是「制作中」,审批单 `estimated=8000 / actual=0`,
团期已「已取消」而底下**残留 2 张活跃单**。
---
## 一、背景
流团在 #7196 之后是一条完整审批链:发起申请 → 管理员审批 → 通过后系统自动置团期取消、
批量退各户定金、释放配车占用。设计目标是「批准后系统一次做完,不留手工尾巴」。
但「批量退各户定金」这一步从未真正实现:执行时逐户调的是「系统自动取消」,
而它**只处理待支付订单**,其余状态一律跳过。而待支付订单的已付金额恒为 0
(付款成功即离开待支付态),所以实退恒为 0。
**这不是低频边界,是每次流团都会发生**:只要团里有一户付过钱,这一户就会被跳过。
---
## 二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|------|------|------|----------|------|
| 1 | 同意流团申请 | POST | `/v3/admin/order/group-batch/disband/:approvalId/approve` | 行为变化 | 已付款户一并取消并发起退款;`actualRefundAmount` 口径修正;新增 589544 / 589554 两条拒绝路径 |
| 2 | 提交流团申请 | POST | `/v3/admin/order/group-batch/:groupBatchId/disband` | 行为变化 | 新增 589554 前置拒绝;出入参与响应结构不变 |
---
## 三、接口详情
### 1. 同意流团申请 `POST /v3/admin/order/group-batch/disband/:approvalId/approve`
**VO**: `DisbandApprovalRespVO`
#### 使用场景
管理后台团期「流团审批」中同意一条待审流团申请。调用方 hl-ui 团期审批页。
权限:仅管理员角色。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| approvalId | Path | Long | ✅ | — | 流团审批单 ID;**入参一个都没变** |
| remark | Body | String | ❌ | ≤256 | 批复备注;请求体整体可不传 |
#### 出参 `Result<DisbandApprovalRespVO>`
| 字段 | 类型 | 说明 |
|------|------|------|
| approvalId | Long | 审批单 ID;**响应结构完全不变** |
| approvalStatus | String | PENDING / APPROVED / REJECTED |
| affectedOrderCount | Integer | 影响子订单户数 |
| estimatedRefundAmount | BigDecimal | 提交时算的预估退款(Σ已付),口径未变 |
| actualRefundAmount | BigDecimal | **口径修正**:本次实际发起的退款诉求合计。改前系统性恒为 0 |
| 其余全部字段 | — | **完全不变** |
#### 请求示例
```http
POST /v3/admin/order/group-batch/disband/2097220075659415553/approve HTTP/1.1
Authorization: Bearer <admin-token>
Content-Type: application/json
{"remark":"确认无法成团"}
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"approvalId": "2097220075659415553",
"approvalStatus": "APPROVED",
"affectedOrderCount": 3,
"estimatedRefundAmount": 8000.0,
"actualRefundAmount": 8000.0,
"approvedByName": "admin",
"approvedAt": "2026-09-08 15:06:46"
}
}
```
上面这单里,三户分别是「未付款 / 付订金 2000 / 已补尾款 6000」,
执行后三户**全部取消**,退款单 2000 + 6000 与 `actualRefundAmount` 逐笔对上。
#### 空数据 / 降级响应
**单户失败不中断整批**:某户因状态不允许取消、或用车需求正处于最终确认中而被跳过时,
接口仍返 200、其余户照常取消、审批单与团期状态**不回滚**;
`actualRefundAmount` 只统计成功发起退款的户,因此**可能小于 `estimatedRefundAmount`**,
差额由服务端日志里的「未处理清单」解释。
⚠️ **`actualRefundAmount` 会在批准之后被改写一次,前端不要缓存 approve 响应里的那个值。**
退款单是在流团事务**提交之后**才建的(异步),所以 approve 响应体里带回的
`actualRefundAmount` 是**暂定值**=本次发出的退款诉求合计;随后服务端逐户回读实际建单结果,
把没建出退款单的户剔掉,改写成**定稿值**。两者在「全部建单成功」时相同,
有户建单失败时定稿值更小。
定稿通常在 1 秒内完成。**要展示准确金额,请在 approve 返回后重新拉一次
`GET /v3/admin/order/group-batch/disband/:approvalId`**,
或直接以列表 / 详情接口的值为准,不要直接渲染 approve 响应里的这个字段。
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"approvalId": "2097216263326466050",
"approvalStatus": "APPROVED",
"affectedOrderCount": 3,
"estimatedRefundAmount": 6000.0,
"actualRefundAmount": 4000.0
}
}
```
#### 错误响应
```json
{
"code": 589554,
"message": "团期下有出行中的子订单,不可流团;请等其出行完毕,或先对该户单独终止行程",
"success": false,
"data": null
}
```
**错误码新增一条、新增两条触发路径**:
| 码 | 含义 | 变化 |
|----|------|------|
| 589554 | 团期下有出行中的子订单,不可流团 | **本次新增**。发起与批复两个入口都会返 |
| 589544 | 团期已出行,不可发起或批复流团 | **新增触发路径**:此前只在发起入口出现,现在批复时也会按当时的团期状态重判 |
| 589545 / 589546 / 589547 / 589500 / 589501 | 申请不存在 / 已处理 / 非管理员 / 团期不存在 / 团期已终态 | 均未变 |
#### 业务边界
- **退款按「已付全额」算**,不扣违约金:流团是平台方单方面解散,客人无过错。
金额 = 该户已付款 − 已退款。**不是**按应付订金退——已补尾款的户按订金退会少退,
全款户更会因为没有订金而直接失败。
- **已付款为 0 的户只取消不建退款单**,属正常情形,不是失败。
- **退款单是「诉求」不是「到账」**:由取消事件的异步监听器创建、由渠道异步打款,
都可能后续失败。界面文案请写「已发起退款金额」。
- **退款不再挂第二道审批**:流团本身已经过一道管理员审批,退款单直接放行。
- **有出行中户就整笔拒绝**:拒的时候什么都不改——团期状态、子订单、审批单全部零变动,
审批单仍留在「待审批」,运营处理完那一户后可以直接再批一次。
### 2. 提交流团申请 `POST /v3/admin/order/group-batch/:groupBatchId/disband`
**VO**: `DisbandApprovalRespVO`
#### 使用场景
管理后台团期详情页发起流团申请,只建待审批单、不执行。调用方 hl-ui 团期详情页。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| groupBatchId | Path | Long | ✅ | — | 团期主订单 ID;**入参一个都没变** |
| reason | Body | String | ✅ | 非空 | 流团理由 |
#### 出参 `Result<DisbandApprovalRespVO>`
| 字段 | 类型 | 说明 |
|------|------|------|
| approvalId | Long | 新建的审批单 ID;**响应结构完全不变** |
| approvalStatus | String | 恒为 PENDING |
| affectedOrderCount | Integer | 影响子订单户数 |
| estimatedRefundAmount | BigDecimal | 预估退款(Σ已付),口径未变 |
| 其余全部字段 | — | **完全不变** |
#### 请求示例
```http
POST /v3/admin/order/group-batch/2097220073544073217/disband HTTP/1.1
Authorization: Bearer <admin-token>
Content-Type: application/json
{"reason":"临近出团仍未达最低成团数"}
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"approvalId": "2097220075659415553",
"approvalStatus": "PENDING",
"affectedOrderCount": 3,
"estimatedRefundAmount": 8000.0
}
}
```
#### 空数据 / 降级响应
被 589554 拒时**不会建出审批单**,团期审批列表里不会多出一条待处理项,
运营处理完出行中那户后重新发起即可。
```json
{
"code": 589554,
"message": "团期下有出行中的子订单,不可流团;请等其出行完毕,或先对该户单独终止行程",
"success": false,
"data": null
}
```
#### 错误响应
```json
{
"code": 589543,
"message": "该团期已有在途的流团申请",
"success": false,
"data": null
}
```
错误码:589543(已有在途申请)、589544(团期已出行)、589500(团期不存在)、
**589554(有出行中的子订单,本次新增)**。
#### 业务边界
- 本入口的 589554 是**前置提示**,让运营在提单那一刻就知道拦在哪;
权威的那道在批复侧——提单到批复之间订单还可能被出发任务推成出行中。
- 提交阶段的「预估退款」口径未变,仍是全部活跃户的已付金额之和。
---
## 四、契约约束与正确调用方式
- **`actualRefundAmount` 请按「已发起退款金额」展示**,不要写成「实退合计」。
它可能小于「预估退款」——差额来自被跳过的户,属正常情形而非数据错误。
- **589554 是可恢复的拒绝**:提示运营「该团有客人正在行程中,请等其出行完毕,
或先单独为该户办理终止行程」,而不是提示「系统繁忙,稍后重试」。
- **589544 与 589554 是两件事**:前者是「这个团期本身已经出发了」,
后者是「团期还没出发,但底下有客人已经在行程中」。两个提示语不要合并。
---
## 五、数据库行为
- 本次**无表结构变更、无数据迁移**。
- 批准流团后:已付款户的订单状态推进为「已取消」并写取消时间;审批单回填「已发起退款金额」;
退款申请由取消事件异步创建。
- 被 589544 / 589554 拒时**零写入**:团期、子订单、审批单全部不变。
---
## 六、边界行为
- **只含待支付户的团期**:行为与改前逐字节一致——订单取消、无退款单、实退金额 0。
- **已被全额退过的户**:可退额为 0,只取消不建退款单。
- **有历史部分退款的户**:可退额按「已付 − 已退」算,不会撞退款复核的上限校验。
- **用车需求正在最终确认中的户**:本轮跳过(该冲突有 10 分钟租约会自动过期),
其余户照常处理,审批单与团期不回滚。
## 六.6、修改前后对比
| 场景 | 改前 | 改后 |
|------|------|------|
| 批准流团,团里有付过定金 / 全款的户 | 那些户**既不取消也不退款**,团期已「已取消」而底下残留活跃单 | **全部取消并发起退款**,团期下无残留 |
| 审批单「实退金额」 | 系统性恒为 **0** | 等于本次**已发起退款金额合计**,与退款单逐笔对得上 |
| 已补尾款的户(已付 6000 / 订金 2000) | 不取消不退款 | 取消并退 **6000**(按已付款,不是按订金) |
| 全款支付户 | 不取消不退款 | 取消并退全部已付款 |
| 只含待支付户的团期 | 取消、无退款单、实退 0 | **完全一致**,无回归 |
| 批复时团期已出行 | **放行**,团被流掉 | **589544 拒绝**,零变动 |
| 团期下有出行中的子订单 | 放行,团被流掉而该户残留 | **589554 拒绝**,零变动 |
| 单户因状态或用车确认冲突被跳过 | — | 其余户照常成功,审批单与团期**不回滚**,接口仍 200 |
## 六.7、影响评估
- **前端零改动**:两个接口的出入参与响应结构完全不变。
- **需要前端做的一件事**:错误码字典补录 **589554**。不补也不会报错,只是拿不到友好文案。
- **建议改一处文案**:审批单上的 `actualRefundAmount` 若显示为「实退金额」,
建议改为「已发起退款金额」——它是退款诉求,不是到账确认。
- **对客户的净效果**:团解散后钱会自动退,不用等客户来问。
- **对运营的净效果**:审批单上的金额第一次有了真实含义;
「团期已取消却还挂着活跃单」这种需要人工善后的现场大幅减少。
- **风险**:户数多时批复会串行处理各户退款,接口耗时随户数上升;正确性有 CAS 兜底,
不会双执行。
---
## 七、不影响范围
- 拒绝流团(reject)未改。
- 退单户审批(withdraw)未改。
- C 端自主取消、管理后台单笔取消订单未改——新增的「已付全额退」模式**不进后台取消下拉**,
只由流团执行链路内部使用。
- 团期名额计数、对账 Job 未改。
- 定制师提交房 / 车需求的权限未收紧。
---
## 八、测试环境已验证
只部署 `hl-order-service-v3`。验收后已滚回主干,造数已清理。
| 验证项 | 结果 |
|--------|------|
| 三户(未付款 / 付订金 / 已补尾款)批准流团 | **三户全部「已取消」**,`cancelled_at` 非空 |
| 退款单落库 | 付订金户 2000、已补尾款户 **6000**(按已付款不是订金)、未付款户**无退款单** |
| 审批单金额 | `estimated=8000 / actual=8000`,与两张退款单逐笔对上 |
| 改前对照 | 同样三户 → 只取消未付款那户,另两户仍「制作中」,`actual=0`,残留 2 张活跃单 |
| 单户不可取消时 | 接口 200、其余户全成功、审批单与团期**未回滚**、`actual` 只算成功户(4000 / 预估 6000) |
| 用车最终确认在途的户 | 该户跳过、其余成功、审批单与团期**未回滚**、接口 200 |
| 团期下无残留 | 三户全可取消的场景下,活跃单数 = **0** |
| 只含待支付户 | 取消、无退款单、实退 0——与改前一致 |
| 全款户(无订金) | 取消并退全部已付款 6000 |
| 批复时团期已出行 | **589544**,团期 / 子订单 / 审批单零变动 |
| 团期下有出行中子订单(提单侧 / 批复侧) | 均 **589554**,且提单侧不建审批单、批复侧审批单仍「待审批」 |
| 待办同步真实失败时 | 流团照常提交(真数据库 + 真事务代理的集成测试坐实) |
| 单元测试 | order-v3 9060 例,0 失败 0 错误 |
---
## 十、相关文档
- Issue #7294、PR #7333
- 关联工单:#7196(流团申请与审批)、#7283(流团后班期置为不可售)
- 未覆盖项:退款建单失败注入、进程中断恢复、批复与出发任务并发——
三条需要 kill 进程或精确并发构造,未在共享测试环境执行
---
## 关联 / 联系人
- 后端:jw
- 前端:mmg(hl-ui)
- 前端待办:错误码字典补录 **589554**;建议把审批单的「实退金额」文案改为「已发起退款金额」。
**接口结构零变化,不改也不会报错。**