changelog-filename-gate / validate (push) Successful in 2s
退款单在流团事务【提交之后】才建(异步),所以 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>
384 行
17 KiB
Markdown
384 行
17 KiB
Markdown
---
|
||
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**;建议把审批单的「实退金额」文案改为「已发起退款金额」。
|
||
**接口结构零变化,不改也不会报错。**
|