docs(changelog): 团期流团改为申请与审批 #7196
changelog-filename-gate / validate (push) Successful in 3s

发起流团端点语义变更(立即执行 → 提交申请,理由改必填,新增权限与阶段限制),
新增审批中心列表 / 申请详情 / 同意 / 拒绝 4 个端点。

后端已部署 TEST 并实测 R1-R10 全过;前端待接。
如实记录三条限制:批准后无客户通知、抄送定制师只记录不推送、
已付款户流团不会被取消/退款(存量问题,本单未修,另行排期)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
这个提交包含在:
jw
2026-09-07 10:14:31 +08:00
共同撰写人 Claude Opus 5
父节点 de56358ea8
当前提交 da07ac2ebf
@@ -0,0 +1,530 @@
---
schema: "hl-changelog/v2"
ticket: "7196"
title: "团期流团改为申请与审批"
consumer: "admin"
author: "jw(GIT)"
change_type: "修改接口"
backend_status: "deployed"
gateway_status: "verified"
frontend_status: "pending"
frontend_owner: ""
frontend_ref: ""
target_release: ""
verified_at: ""
status_note: "流团由「点完就退款、不可撤销」改为「先申请、批准后才执行」。发起流团端点语义变更且理由改为必填,新增审批中心列表、申请详情、同意、拒绝四个端点。后端已部署 TEST 并实测 R1-R10 全过;前端待接。"
updated_at: "2026-09-07"
base: "dev-v3"
---
# 团期流团:改为「申请 + 审批」,批准后才退定金
> **影响范围**:管理后台「团期详情 → 发起流团」与「订单管理 → 审批中心」。
> 当前状态:后端已部署 TEST 并实测;前端待接入(**发起流团端点语义已变更**)。
## ⚠️ 关键变化
1. **流团不再是点完就执行**。原先点一下就把团期置为已取消、批量退各户定金、释放配车占用,
**不可撤销**;现在只提交一条待审批的申请,**团期、子订单、名额、钱四项零变动**,
**批准后才真正执行**。
2. **发起流团端点语义变更**:`POST .../disband` 由「立即流团」改为「提交流团申请」,
且**流团理由由可空改为必填**(原先允许不带请求体直接流团)。
3. **新增四个端点**:审批中心列表、申请详情、**同意**、**拒绝**(拒绝原因必填)。
4. **新增权限校验**:发起流团需要团期运营写权限(此前该接口无任何权限校验)。
5. **新增阶段限制**:出行后不可发起流团。
## 一、背景
流团意味着整期解散并退还各户定金,是不可逆的重动作,此前却没有任何审批与权限把关,
点一下就生效。原型明确要求走公司管理审批:团期管理员发起 → 抄送定制师知会 →
负责人确认,**批准后**才自动原路退定金。本次把这条链路补齐。
## 二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
| --- | --- | --- | --- | --- | --- |
| 1 | 发起流团申请 | POST | `/v3/admin/order/group-batch/{groupBatchId}/disband` | 修改接口 | 由「立即流团」改为「提交申请」;理由改为必填;新增权限与阶段限制 |
| 2 | 审批中心列表 | GET | `/v3/admin/order/group-batch/approvals/page` | 新增接口 | 流团与退单户两类共用,可按类型筛选 |
| 3 | 流团申请详情 | GET | `/v3/admin/order/group-batch/disband/{approvalId}` | 新增接口 | 审批链、快照、批复信息 |
| 4 | 同意流团 | POST | `/v3/admin/order/group-batch/disband/{approvalId}/approve` | 新增接口 | 批准后才真正执行流团 |
| 5 | 拒绝流团 | POST | `/v3/admin/order/group-batch/disband/{approvalId}/reject` | 新增接口 | 只改申请单状态,团期继续正常招募 |
## 三、接口详情
### 1. 发起流团申请 `POST /v3/admin/order/group-batch/{groupBatchId}/disband`
**VO**: `DisbandSubmitReqVO`
#### 使用场景
团期管理员在团期详情点「发起流团(走审批)」,填写流团理由后提交。
提交只建一条待审批的申请单,**团期状态、子订单、名额、钱一律不动**,等审批结果。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
| --- | --- | --- | --- | --- | --- |
| groupBatchId | path | Long | 是 | 正整数 | 团期 ID |
| reason | body | String | 是 | 非空白,最长 512 字 | 流团理由。**本次由可空改为必填** |
#### 出参
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| approvalId | String | 审批单 ID |
| approvalStatus | String | 固定 PENDING |
| affectedOrderCount | Integer | 影响子订单户数(提交时快照) |
| estimatedRefundAmount | BigDecimal | 退还定金合计(提交时快照,仅供展示) |
| applicantName | String | 发起人姓名 |
| ccUserNames | String[] | 抄送定制师姓名列表(去重) |
#### 请求示例
```json
{
"reason": "临近出团仍未达最低成团数(6 户)"
}
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": {
"approvalId": "2096779558547087362",
"groupBatchId": "2096779382436651009",
"batchNo": "GT-26-0004",
"approvalStatus": "PENDING",
"reason": "临近出团仍未达最低成团数(6 户)",
"affectedOrderCount": 3,
"estimatedRefundAmount": 32900.00,
"applicantName": "卜丽颖",
"ccUserNames": ["李雯", "王浩"]
},
"success": true
}
```
#### 空数据 / 降级响应
本期尚无子订单时也可发起:影响户数为 0、退还定金为 0、抄送名单为空数组,属正常情况。
#### 错误响应
```json
{
"code": 589543,
"message": "该团期已有流团申请在审批中,请勿重复提交",
"data": null,
"success": false
}
```
#### 业务边界
- **提交零副作用**:团期状态、子订单、名额、钱四项均不变动。
- **同期至多一条在途申请**,重复提交返回 589543。
- **出行后不可发起**,返回 589544;可发起的阶段为招募中、资源准备中、物料准备中、待出发。
- 需要团期运营写权限,无权限返回 589507。
- 五秒内重复点击会先被幂等窗口拦下,返回提交中提示,属预期。
### 2. 审批中心列表 `GET /v3/admin/order/group-batch/approvals/page`
**VO**: `GroupBatchApprovalItemRespVO`
#### 使用场景
审批中心页面加载时调用,一次拿到流团与退单户两类申请,可按类型与状态筛选。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
| --- | --- | --- | --- | --- | --- |
| bizType | query | String | 否 | DISBAND / WITHDRAW | 业务类型;不传表示两类都要 |
| approvalStatus | query | String | 否 | PENDING / APPROVED / REJECTED | 审批状态;不传表示全部 |
| groupBatchId | query | Long | 否 | 正整数 | 按团期筛选 |
| pageNum | query | Integer | 否 | 从 1 开始 | 页码,默认 1 |
| pageSize | query | Integer | 否 | 1-100 | 每页条数,默认 20 |
#### 出参
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| bizType | String | DISBAND 流团 / WITHDRAW 退单户 |
| bizTypeText | String | 类型中文名,服务端给出 |
| approvalStatus | String | 审批状态 |
| batchNo | String | 团期号 |
| orderId | String | 被退子订单 ID;流团为 null |
| affectedOrderCount | Integer | 影响户数;退单户为 null |
| estimatedRefundAmount | BigDecimal | 预计退款额 |
| applicantName | String | 发起人 |
| approvedByName | String | 批复人;未批复为 null |
#### 请求示例
```http
GET /v3/admin/order/group-batch/approvals/page?bizType=DISBAND&approvalStatus=PENDING&pageNum=1&pageSize=20
Authorization: Bearer <token>
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": {
"list": [
{
"approvalId": "2096779558547087362",
"bizType": "DISBAND",
"bizTypeText": "流团",
"batchNo": "GT-26-0004",
"approvalStatus": "PENDING",
"orderId": null,
"affectedOrderCount": 3,
"estimatedRefundAmount": 32900.00,
"applicantName": "卜丽颖",
"approvedByName": null
}
],
"total": 4,
"page": 1,
"pageSize": 20
},
"success": true
}
```
#### 空数据 / 降级响应
无待办时返回空列表且 total 为 0,页面展示「暂无审批事项」。
#### 错误响应
```json
{
"code": 589507,
"message": "无操作权限",
"data": null,
"success": false
}
```
#### 业务边界
- 两类申请共用一套列表;差异只在 `orderId`(退单户有值)与 `affectedOrderCount`(流团有值)。
- 按提交时间倒序;每页最多 100 条。
### 3. 流团申请详情 `GET /v3/admin/order/group-batch/disband/{approvalId}`
**VO**: `DisbandApprovalRespVO`
#### 使用场景
审批人在审批中心点开一条流团申请,或团期详情的「流团审批中 / 已被驳回」横幅取详情。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
| --- | --- | --- | --- | --- | --- |
| approvalId | path | Long | 是 | 正整数 | 审批单 ID |
#### 出参
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| approvalStatus | String | PENDING / APPROVED / REJECTED |
| reason | String | 流团理由 |
| affectedOrderCount | Integer | 影响户数(提交时快照) |
| estimatedRefundAmount | BigDecimal | 退还定金合计(提交时快照) |
| actualRefundAmount | BigDecimal | **实际退款合计**,批准执行后按真实取消结果回填 |
| applicantName | String | 发起人 |
| ccUserNames | String[] | 抄送定制师 |
| approvedByName | String | 批复人 |
| approveRemark | String | 批复备注 / 拒绝原因 |
#### 请求示例
```http
GET /v3/admin/order/group-batch/disband/2096779558547087362
Authorization: Bearer <token>
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": {
"approvalId": "2096779558547087362",
"approvalStatus": "APPROVED",
"reason": "临近出团仍未达最低成团数(6 户)",
"affectedOrderCount": 3,
"estimatedRefundAmount": 32900.00,
"actualRefundAmount": 32900.00,
"applicantName": "卜丽颖",
"ccUserNames": ["李雯", "王浩"],
"approvedByName": "刘涛",
"approveRemark": "确认无法成团,同意流团"
},
"success": true
}
```
#### 空数据 / 降级响应
未批复时 `approvedByName` / `approveRemark` / `actualRefundAmount` 均为 null,前端按「审批中」渲染。
#### 错误响应
```json
{
"code": 589545,
"message": "流团申请不存在",
"data": null,
"success": false
}
```
#### 业务边界
- 只认流团类申请;拿退单户的单号来查会返回 589545,两类端点互不串用。
### 4. 同意流团 `POST /v3/admin/order/group-batch/disband/{approvalId}/approve`
**VO**: `ApproveDisbandReqVO`
#### 使用场景
审批人在审批中心点「同意」。**只有走到这一步,流团才真正执行**:
团期置为已取消、按规则退各户定金、释放配车占用。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
| --- | --- | --- | --- | --- | --- |
| approvalId | path | Long | 是 | 正整数 | 审批单 ID |
| remark | body | String | 否 | 最长 512 字 | 批复备注,**可选** |
#### 出参
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| approvalStatus | String | 变为 APPROVED |
| actualRefundAmount | BigDecimal | 实际退款合计,按执行后的真实取消结果回填 |
| approvedByName | String | 批复人姓名 |
| approveRemark | String | 批复备注 |
#### 请求示例
```json
{
"remark": "确认无法成团,同意流团"
}
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": {
"approvalId": "2096779558547087362",
"approvalStatus": "APPROVED",
"actualRefundAmount": 32900.00,
"approvedByName": "刘涛",
"approveRemark": "确认无法成团,同意流团"
},
"success": true
}
```
#### 空数据 / 降级响应
本期无有效子订单时实际退款合计为 0,流团照常完成。
#### 错误响应
```json
{
"code": 589546,
"message": "该流团申请已处理,不可重复操作",
"data": null,
"success": false
}
```
#### 业务边界
- **只有待审批的申请可被处理**;已同意 / 已拒绝的再点返回 589546。
- **并发下只有一人成功**:两名审批人同时点,另一人拿 589546,且**绝不会重复执行流团**。
- 需要管理员角色,否则返回 589547。
- **实际退款合计按执行后的真实取消结果统计**,不等于提交时的预计金额,
两者不一致的常见原因见「不影响范围」的说明。
### 5. 拒绝流团 `POST /v3/admin/order/group-batch/disband/{approvalId}/reject`
**VO**: `RejectDisbandReqVO`
#### 使用场景
审批人认为该期还能继续招募,点「拒绝」并写明原因。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
| --- | --- | --- | --- | --- | --- |
| approvalId | path | Long | 是 | 正整数 | 审批单 ID |
| remark | body | String | 是 | 非空白,最长 512 字 | **拒绝原因必填** |
#### 出参
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| approvalStatus | String | 变为 REJECTED |
| approvedByName | String | 批复人姓名 |
| approveRemark | String | 拒绝原因 |
#### 请求示例
```json
{
"remark": "已有两户确认报名,继续招募"
}
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": {
"approvalId": "2096779558547087362",
"approvalStatus": "REJECTED",
"approvedByName": "刘涛",
"approveRemark": "已有两户确认报名,继续招募"
},
"success": true
}
```
#### 空数据 / 降级响应
拒绝不产生任何业务数据,响应中与执行相关的字段(实际退款合计)保持为空。
#### 错误响应
```json
{
"code": 400,
"message": "拒绝原因不能为空",
"data": null,
"success": false
}
```
#### 业务边界
- **拒绝只改申请单状态**:团期、子订单、名额、钱四项一律不动,团期继续正常招募。
- 拒绝后**可以再次发起**流团申请。
- 拒绝原因会同时写进团期操作记录,在团期详情里可直接看到。
## 四、契约约束与正确调用方式
- **发起流团必须带理由**。原先允许不带请求体直接流团的调用方式**已失效**,会返回参数校验错误。
- **提交 ≠ 已流团**。提交后团期仍在原状态,前端应把按钮切成「流团审批中」并置灰,
同时展示「去审批中心」的入口;不要在提交成功后就把团期显示成已取消。
- **同意与拒绝是两个端点**:同意的备注可选,拒绝的原因必填,前端拒绝弹窗需做必填校验。
- **审批中心一个列表拿两类**,用 `bizType` 区分与筛选,不需要分别请求再合并。
- **五秒幂等窗口**:连点会先返回「提交中,请勿重复提交」,这不是业务错误,前端做防抖即可。
- 展示金额时请区分两个字段:`estimatedRefundAmount` 是提交时的预计值(用于弹窗展示),
`actualRefundAmount` 是批准执行后的实际结果(用于结果页与对账)。
## 五、数据库行为
- 发起流团**只新增一条待审批的申请单**,记录理由、影响户数、退还定金合计、
发起人与抄送定制师名单;**不改团期、不改子订单、不动名额与钱**。
- 同意时**先把申请单落为已通过,再执行流团**;若该单已被他人处理,则整笔拒绝且**不执行流团**。
- 执行流团后**回读实际取消结果**再回填实际退款合计——不拿「预计金额」冒充实退。
若存在已付款但未被取消的户,服务端会打出告警日志,便于人工捞出处理。
- 拒绝**只改申请单状态与批复信息**,另写一条团期操作记录(含拒绝原因)。
- 发起、拒绝、以及流团执行本身各写一条团期操作记录,均可查到操作人与时间。
- 被拒绝或被拒收的请求(重复提交、阶段不允许、已处理、无权限)**一律零写入**。
## 六、边界行为
- 同期至多一条在途申请;驳回后可重新发起,判重只拦在审批中的那条。
- 出行后不可发起流团;已流团的团期再次发起同样被拦。
- 并发审批由串行锁与状态校验双重保证,同一条申请只会被处理一次,流团只会执行一次。
- 本期无有效子订单时也可发起与批准,影响户数与金额均为 0。
## 六.6、修改前后对比
| 项 | 修改前 | 修改后 |
| --- | --- | --- |
| 发起流团的效果 | **点完即执行**:团期取消、批量退定金、释放配车,不可撤销 | **只建申请单**,四项零变动,批准后才执行 |
| 流团理由 | 可空,甚至可以不带请求体 | **必填**,为空拒绝 |
| 权限 | **无任何校验** | 需要团期运营写权限 |
| 阶段限制 | 只要不是已结算 / 已取消都能流 | **出行后不可发起** |
| 审批 | 无 | 同意 / 拒绝两个操作,拒绝原因必填 |
| 审批可见性 | 无 | 审批中心可见,流团与退单户两类共用列表 |
| 返回值 | 空 | 返回申请单快照(含影响户数、退还定金、抄送名单) |
## 六.7、影响评估
- **发起流团端点不兼容**:语义由「立即执行」变为「提交申请」,且理由必填。
前端必须同步改造:提交后不能再当作已流团处理,需展示审批中状态;
看板上旧的「确认流团」二次确认入口(文案写「点完即退款、不可撤销」)**应下线或改走同一条流**,
否则与审批语义冲突。
- **权限是行为变更**:此前任何登录管理员都能流团,现在需要团期运营写权限;
该权限已在既有工单中建立并授予管理员与超级管理员,无需额外配置。
- **阶段限制是行为收紧**:出行后不再允许流团。
- 新增四个端点均为增量,不影响任何既有接口。
- **审批人范围**:本期由管理员审批。原型里写的「部门负责人」角色目前系统中不存在,
待该角色落地后可再收窄。
## 七、不影响范围
- 退单户(单户退团)的申请与审批流程完全不变,仅新增了一个可同时查看两类的列表。
- 团期成团、调整满团名额、需求审核等其他动作不受影响。
- 小程序端不受影响,改动全部在管理后台侧。
- **本次不改动退款的执行口径**。需要特别说明一个既有限制:
流团执行时,**只有尚未付款的户会被取消**,已付定金的户会被跳过、既不取消也不退款,
与「自动原路退定金」的承诺不符。这是本次改造之前就存在的问题,本单未修,
但已让实际退款合计如实反映真实结果、并对被跳过的已付款户打出告警,便于人工兜底。
该问题建议单独排期修复。
- **批准后的客户通知(公众号 / 短信)本期不做**,仍需人工通知客户;抄送定制师目前只记录名单、不推送消息。
## 八、测试环境已验证
TEST 环境已部署订单服务并实测通过:
- 发起流团申请成功,返回影响户数与退还定金快照,**团期状态保持不变**。
- 流团理由为空被拒绝。
- 重复发起被拒绝(同期至多一条在途)。
- 拒绝时原因为空被拒绝;正常拒绝后**团期状态不变**,可再次发起。
- 已处理的申请再次同意或拒绝均被拒绝。
- **同意后团期才变为已取消**,未付款子订单同步取消,实际退款合计按真实结果回填。
- 已流团的团期再次发起被阶段限制拦下。
- 审批中心列表返回两类共 7 条,按类型筛选分别为流团 4 条、退单户 3 条,类型中文名正确。
- 申请详情回读齐全:快照、抄送名单、批复人与批复备注。
## 十、相关文档
- 工单:HL#7196
- 合并:HL PR#7201(已合入 dev-v3)
- 关联:HL#7100(团期退单户审批)——本单复用其审批单模型与审批范式;
HL#7158 / HL#7178——复用其建立的团期运营写权限与阶段限制写法
## 关联 / 联系人
- 后端:jw
- 前端待接:发起流团弹窗(必填理由、审批链展示、提交后切「审批中」)、
团期详情的审批中与已驳回两条横幅、审批中心列表与同意/拒绝操作(拒绝原因必填)、
看板旧「确认流团」入口下线或改走审批流