团期退单户改为「提交申请 + 管理员审批」,070 破坏性变更 + 072~075 新增(#7100)
changelog-filename-gate / validate (push) Successful in 2s
changelog-filename-gate / validate (push) Successful in 2s
070 由「调用即退款」改为「只建待审申请单」,响应体 Result<Void> → 对象; 新增审批列表/详情/通过/取消退单四个端点,审批限 ADMIN/SUPER_ADMIN 角色。 金额口径写明两个数(已付 / 预计退):早期阶段退的是订金应付额、与已付无关, 后期阶段以已付额按退改政策扣减——这条是 TEST 实测暴露 #7126 缺陷后更正的说法。 已过网关真测(2026-09-05):19 项含提交/驳回后该户仍在团里/再次提交/通过后名额回落, 预估与实退一致;验证产生的两张退款单已作废,测试环境资金零变动。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
这个提交包含在:
@@ -0,0 +1,746 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "7100"
|
||||
title: "团期退单户:提交退单申请 + 管理员审批(070 破坏性变更 + 072~075 新增)"
|
||||
consumer: "admin"
|
||||
author: "jw(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "pending"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: "2026-09-05"
|
||||
status_note: "已过网关真测(2026-09-05,admin 切 ADMIN 角色,5 端点全链路含提交/驳回/再提交/通过);含 #7126 预估口径修复后的复测"
|
||||
updated_at: "2026-09-05"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 团期: 退单户改为「提交申请 + 管理员审批」
|
||||
|
||||
> **服务**: hl-order-service-v3
|
||||
> **PR**: #7106
|
||||
> **Issue**: #7100
|
||||
> **日期**: 2026-09-04
|
||||
> **影响范围**: 管理后台团期详情「退单户」弹窗 + 新增退单审批中心
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
**`POST .../sub-order/{orderId}/withdraw` 是破坏性变更:调用它不再退款,只建一张待审申请单。**
|
||||
|
||||
三条前端必读:
|
||||
|
||||
1. **响应体由 `Result<Void>` 变成对象**。老前端只看 `code` 不读 `data` 的话不会崩,但**提示语必须改**——
|
||||
现在的语义是「已提交审核」,不是「已退款」。
|
||||
2. **金额要显示两个数**:`paidAmount`(已付)和 `estimatedRefundAmount`(预计退)。
|
||||
**两者可以不相等,而且早期阶段常常不等**——招募中 / 资源准备中退的是**订金应付额**,
|
||||
与已付多少无关(TEST 实测:已付 ¥0、订金 ¥2000 的单,预计退与实退都是 ¥2000)。
|
||||
**不要自己调 `/v3/admin/order/{id}/cancel-preview` 算**——那个接口恒按退改政策算,
|
||||
与早期阶段的订金口径对不上。
|
||||
3. **审批中心是新界面**:列表 → 详情 → 通过 / 取消退单(4 个新端点),入口按角色显隐,
|
||||
仅 `ADMIN` / `SUPER_ADMIN` 可见,其余角色调用返 589530。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
原来的退单户是「点一下就退款」:提交那一刻订单即 `CANCELLED`、团期名额当场释放、退款单自动建,
|
||||
招募中/资源准备中甚至免审直退。但业务要的是**可以取消退单,且取消后该户继续留在团期里走原流程**——
|
||||
订单都取消了、名额可能已被新客户占走,这事根本做不到。
|
||||
|
||||
故改为**先挂申请单、批了才执行**:`PENDING` 期间订单 / 名额 / 钱三项零变动,
|
||||
该户照常提需求、排房排车;管理员通过才真正退团,驳回则全程无痕、可再次提交。
|
||||
|
||||
退款口径**没有变**:仍按团期状态选模式(招募中 / 资源准备中全退定金,物资准备中 / 待出发按退改政策阶梯扣)。
|
||||
变的只是「什么时候执行」和「谁点头」。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 退单户·提交退单申请 | POST | `/v3/admin/order/group-batch/:groupBatchId/sub-order/:orderId/withdraw` | 修改 | **破坏性**:由「直接退款」改为「建待审申请单」,响应体 Void → 对象 |
|
||||
| 2 | 退单审批分页列表 | GET | `/v3/admin/order/group-batch/withdraw/page` | 新增 | 审批中心列表,缺省只返待审 |
|
||||
| 3 | 退单申请详情 | GET | `/v3/admin/order/group-batch/withdraw/:approvalId` | 新增 | 含「提交时预估」与「当前预估」两个金额 |
|
||||
| 4 | 退单审核通过 | POST | `/v3/admin/order/group-batch/withdraw/:approvalId/approve` | 新增 | 此刻才执行退团:订单取消 + 名额回落 + 退款 |
|
||||
| 5 | 取消退单(驳回) | POST | `/v3/admin/order/group-batch/withdraw/:approvalId/reject` | 新增 | 该户继续留在团期中走原流程 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 退单户·提交退单申请 `POST /v3/admin/order/group-batch/:groupBatchId/sub-order/:orderId/withdraw`
|
||||
|
||||
**VO**: `WithdrawSubOrderReqVO` / `WithdrawSubOrderRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
团期详情底部操作条点「退单户」→ 弹窗选一户(下拉数据仍用既有的
|
||||
`GET /v3/admin/order/group-batch/:groupBatchId/orders`,后端没有另开候选接口)→ 填退团原因 →
|
||||
点「提交退团审核」时调用。**调完钱不动**,等管理员在审批中心处理。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| groupBatchId | Path | Long | ✅ | - | 团期主订单 ID |
|
||||
| orderId | Path | Long | ✅ | - | 要退的子订单 ID(= 一户) |
|
||||
| reason | Body | String | ❌ | ≤512 字 | 退团原因;不传后端记「团期退团」。整个 body 可省略 |
|
||||
|
||||
#### 出参 `Result<WithdrawSubOrderRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| approvalId | String | 退单审批单 ID(雪花,序列化为字符串),审批中心用它取详情 |
|
||||
| paidAmount | BigDecimal | 该户已付款额,弹窗「已付 ¥5,000」取此列 |
|
||||
| estimatedRefundAmount | BigDecimal | 预计退款额,弹窗「预计退 ¥3,500」取此列。`FULL_DEPOSIT` 时 = **订金应付额**(与已付无关),`POLICY` 时 = 已付额按政策扣减后;**实退以审批通过时重算为准** |
|
||||
| refundMode | String | `FULL_DEPOSIT`=全退定金 / `POLICY`=按退改政策阶梯扣 |
|
||||
| refundPolicy | Object | 退改政策明细,仅 `POLICY` 模式给(用于展示「距出发 6 天,扣 30%」),否则为 null |
|
||||
| consultantName | String | 该户定制师姓名。**信息项,不是审批人**,前端不得暗示他要来点头 |
|
||||
| warnings | String[] | 非阻断提示(如当前已满团,退单通过后将空出 1 户);无则为空数组 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{ "reason": "客户临时有事无法参团" }
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"approvalId": "1955009900112233",
|
||||
"paidAmount": 5000.00,
|
||||
"estimatedRefundAmount": 3500.00,
|
||||
"refundMode": "POLICY",
|
||||
"refundPolicy": { "policyName": "标准退改政策", "policyId": 12 },
|
||||
"consultantName": "李雯",
|
||||
"warnings": []
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
写操作,无空数据形态。`refundPolicy` 在 `FULL_DEPOSIT` 阶段恒为 null(退订金不看政策),
|
||||
此时 `estimatedRefundAmount` 是**订金应付额**,与 `paidAmount` 无关,两者不等属正常:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"approvalId": "1955009900112234",
|
||||
"paidAmount": 5000.00,
|
||||
"estimatedRefundAmount": 2000.00,
|
||||
"refundMode": "FULL_DEPOSIT",
|
||||
"refundPolicy": null,
|
||||
"warnings": []
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 589529,
|
||||
"message": "该户已有退单审核在途,请勿重复提交",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
四类拒绝:
|
||||
|
||||
| code | 含义 |
|
||||
|------|------|
|
||||
| 589500 | 团期不存在 |
|
||||
| 589501 | 团期状态不允许退团(出行中及以后,走售后退款) |
|
||||
| 589512 | 子订单不属于该团期 |
|
||||
| 589529 | 该户已有在途退单申请 |
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **零副作用**:本接口只 INSERT 一行审批单。订单状态、团期已报名人数/户数、退款单**三项分文未动**。
|
||||
- **未付分文也能提交**,但**不等于退 0**:
|
||||
- 招募中 / 资源准备中(`FULL_DEPOSIT`)退的是**订金应付额**,未付款的单照样会退订金并生成退款单——
|
||||
这是订单侧既有口径(D3 决策),不是本次引入;如果这不符合业务预期,需要在订单侧另开工单讨论。
|
||||
- 物资准备中 / 待出发(`POLICY`)以已付额为基数,未付则退 0、**不生成退款单**。
|
||||
- 订金为 0 时 `warnings` 会提示「该户订金为 0…」——这类单提交得进去但**审批会被订单侧拒绝**。
|
||||
- **出行后拒**:出行中 / 核算中 / 已结算 / 已取消一律 589501,且**在建单之前就拒**,不会留下批不掉的单。
|
||||
- **幂等**:同一 `groupBatchId + orderId` 5 秒窗口内重复提交只成功一次;顺序重复由 589529 兜底。
|
||||
- **金额会漂移**:`POLICY` 模式下「距出发天数」每天在变,本接口返回的是**提交时**的预估,仅供展示。
|
||||
- **兼容**:body 整体可省略;老前端不读 `data` 也不会报错,但提示文案必须改。
|
||||
|
||||
---
|
||||
|
||||
### 2. 退单审批分页列表 `GET /v3/admin/order/group-batch/withdraw/page`
|
||||
|
||||
**VO**: `WithdrawApprovalListReqVO` / `WithdrawApprovalItemRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
退单审批中心的列表页。缺省只返待审单,管理员进来就是「待我处理」的视图。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| approvalStatus | Query | String | ❌ | PENDING/APPROVED/REJECTED/ALL | 缺省 `PENDING`;查全部传 `ALL` |
|
||||
| groupBatchId | Query | Long | ❌ | - | 按团期筛选 |
|
||||
| keyword | Query | String | ❌ | - | 客户姓名 / 订单号模糊匹配(两者取并集) |
|
||||
| createdFrom | Query | String | ❌ | yyyy-MM-dd | 提交时间起;格式非法则忽略该条件 |
|
||||
| createdTo | Query | String | ❌ | yyyy-MM-dd | 提交时间止,含当日 |
|
||||
| pageNo | Query | Integer | ❌ | ≥1,默认 1 | 页码 |
|
||||
| pageSize | Query | Integer | ❌ | ≤100,默认 20 | 每页条数,超 100 截断 |
|
||||
|
||||
#### 出参 `Result<PageResult<WithdrawApprovalItemRespVO>>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| approvalId | String | 退单审批单 ID |
|
||||
| groupBatchId | String | 团期 ID |
|
||||
| batchNo | String | 团期号 |
|
||||
| productName | String | 产品名称 |
|
||||
| departDate | String | 出发日期 yyyy-MM-dd |
|
||||
| orderId | String | 子订单 ID |
|
||||
| orderNo | String | 订单号 |
|
||||
| customerName | String | 客户姓名 |
|
||||
| participantCount | Integer | 该户人数(成人+儿童+小童+婴儿) |
|
||||
| paidAmount | BigDecimal | 已付款额(提交时快照) |
|
||||
| estimatedRefundAmount | BigDecimal | 预计退款额(提交时快照) |
|
||||
| refundMode | String | `FULL_DEPOSIT` / `POLICY` |
|
||||
| reason | String | 退团原因 |
|
||||
| applicantName | String | 提交人姓名 |
|
||||
| approvalStatus | String | `PENDING` / `APPROVED` / `REJECTED` |
|
||||
| createdAt | DateTime | 提交时间 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/group-batch/withdraw/page?approvalStatus=PENDING&pageNo=1&pageSize=20
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"records": [
|
||||
{
|
||||
"approvalId": "1955009900112233",
|
||||
"batchNo": "GT-26-0007",
|
||||
"productName": "小蒙马亲子团",
|
||||
"departDate": "2026-10-01",
|
||||
"orderNo": "GT-26-0099",
|
||||
"customerName": "林婉清",
|
||||
"participantCount": 2,
|
||||
"paidAmount": 5000.00,
|
||||
"estimatedRefundAmount": 3500.00,
|
||||
"refundMode": "POLICY",
|
||||
"reason": "临时有事无法参团",
|
||||
"applicantName": "王磊",
|
||||
"approvalStatus": "PENDING",
|
||||
"createdAt": "2026-09-04 17:20:11"
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"page": 1,
|
||||
"pageSize": 20
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
无待审单,或 keyword 未命中任何客户/订单号时返回空页(**不是 null**):
|
||||
|
||||
```json
|
||||
{ "code": 200, "data": { "records": [], "total": 0, "page": 1, "pageSize": 20 }, "success": true }
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 589530,
|
||||
"message": "仅管理员可处理退单审核",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
| code | 含义 |
|
||||
|------|------|
|
||||
| 589530 | 当前登录人不是管理员角色 |
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **鉴权**:仅 `ADMIN` / `SUPER_ADMIN` 放行;定制师 / 财务 / 房务 / 车务一律 589530。前端入口应按角色显隐,不要靠调用失败来判断。
|
||||
- **排序**:按提交时间倒序。
|
||||
- **性能**:整页的团期与子订单各批量取一次,不随行数放大查询。
|
||||
- **日期容错**:`createdFrom` / `createdTo` 格式非法时**忽略该条件**并继续查询,不报错。
|
||||
|
||||
---
|
||||
|
||||
### 3. 退单申请详情 `GET /v3/admin/order/group-batch/withdraw/:approvalId`
|
||||
|
||||
**VO**: `WithdrawApprovalDetailRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
审批中心点进某一单。管理员在这里看清楚「退谁、退多少、现在退多少」再决定通过还是取消。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| approvalId | Path | Long | ✅ | - | 退单审批单 ID |
|
||||
|
||||
#### 出参 `Result<WithdrawApprovalDetailRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| (列表行全部字段) | - | 见接口 2 的出参表 |
|
||||
| currentEstimatedRefundAmount | BigDecimal | **当前**预计退款额(实时重算)。审批通过时按它执行 |
|
||||
| currentRefundMode | String | 当前退款模式;团期状态推进后可能与提交时不同 |
|
||||
| refundPolicy | Object | 当前退改政策明细(`POLICY` 模式给) |
|
||||
| actualRefundAmount | BigDecimal | 实际退款额,审批通过后回填;未通过为 null |
|
||||
| consultantName | String | 该户定制师(信息项,非审批人) |
|
||||
| approvedByName | String | 批复人姓名;未批复为 null |
|
||||
| approvedAt | DateTime | 批复时间;未批复为 null |
|
||||
| approveRemark | String | 批复备注 |
|
||||
| refundApplicationId | String | 通过后生成的退款申请 ID |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/group-batch/withdraw/1955009900112233
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"approvalId": "1955009900112233",
|
||||
"orderNo": "GT-26-0099",
|
||||
"customerName": "林婉清",
|
||||
"participantCount": 2,
|
||||
"paidAmount": 5000.00,
|
||||
"estimatedRefundAmount": 3500.00,
|
||||
"refundMode": "POLICY",
|
||||
"currentEstimatedRefundAmount": 3200.00,
|
||||
"currentRefundMode": "POLICY",
|
||||
"consultantName": "李雯",
|
||||
"approvalStatus": "PENDING",
|
||||
"approvedByName": null,
|
||||
"approvedAt": null,
|
||||
"actualRefundAmount": null,
|
||||
"refundApplicationId": null
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
团期已推进到不可退阶段(出行中及以后)时,**当前预估取不到**,`currentEstimatedRefundAmount` /
|
||||
`currentRefundMode` 降级为 null,单子仍可查看——但点通过会被 589501 拦住:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"approvalId": "1955009900112233",
|
||||
"estimatedRefundAmount": 3500.00,
|
||||
"currentEstimatedRefundAmount": null,
|
||||
"currentRefundMode": null
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
`refundApplicationId` 目前**恒为 null**——退款单由订单取消事件异步建,取消响应里不带该 ID。
|
||||
需要跳退款单请用 `orderId` 去退款工作台查。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 589531,
|
||||
"message": "退单申请不存在",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
| code | 含义 |
|
||||
|------|------|
|
||||
| 589530 | 非管理员角色 |
|
||||
| 589531 | 申请单不存在 / 已删除 |
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **两个预估都要展示**:`estimatedRefundAmount` 是提交时快照,`currentEstimatedRefundAmount` 是当前值。
|
||||
`POLICY` 下距出发天数每天在变,**实退按当前值算**,只给前者会误导审批人。
|
||||
- **鉴权**:同列表,仅管理员。
|
||||
|
||||
---
|
||||
|
||||
### 4. 退单审核通过 `POST /v3/admin/order/group-batch/withdraw/:approvalId/approve`
|
||||
|
||||
**VO**: `ApproveWithdrawReqVO` / `WithdrawApprovalDetailRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
审批中心详情页点「通过」。**这一刻才真正退团**:订单取消、团期名额回落、退款进实退链路。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| approvalId | Path | Long | ✅ | - | 退单审批单 ID |
|
||||
| remark | Body | String | ❌ | ≤512 字 | 批复备注;body 可整体省略 |
|
||||
|
||||
#### 出参 `Result<WithdrawApprovalDetailRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| (详情全部字段) | - | 见接口 3 |
|
||||
| approvalStatus | String | 固定 `APPROVED` |
|
||||
| actualRefundAmount | BigDecimal | **实际**退款额(按批复时团期状态重算,可能与提交时快照不同) |
|
||||
| approvedByName | String | 批复人(当前登录人) |
|
||||
| approvedAt | DateTime | 批复时间 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{ "remark": "已与客户确认,同意退单" }
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"approvalId": "1955009900112233",
|
||||
"approvalStatus": "APPROVED",
|
||||
"refundMode": "POLICY",
|
||||
"estimatedRefundAmount": 3500.00,
|
||||
"actualRefundAmount": 3200.00,
|
||||
"approvedByName": "刘涛",
|
||||
"approvedAt": "2026-09-04 18:02:35"
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
写操作,无空数据形态。团期时间线写失败会降级(记 WARN)但**不影响退团成功**。
|
||||
`paidAmount = 0` 的零元退单:订单照常取消,`actualRefundAmount` 为 0,**不生成退款单**:
|
||||
|
||||
```json
|
||||
{ "code": 200, "data": { "approvalStatus": "APPROVED", "actualRefundAmount": 0.00 }, "success": true }
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 589532,
|
||||
"message": "该退单申请已处理,不可重复操作",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
| code | 含义 |
|
||||
|------|------|
|
||||
| 589501 | 团期已推进到不可退阶段(出行中及以后) |
|
||||
| 589512 | 子订单不属于该团期 |
|
||||
| 589530 | 非管理员角色 |
|
||||
| 589531 | 申请单不存在 |
|
||||
| 589532 | 申请单已是终态(已通过 / 已取消) |
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **钱以批复时为准**:审批通过时**重新**按当时团期状态选模式、重算金额,**不认提交时的快照**。
|
||||
典型场景:提交时团期还在资源准备中(全退 5000),批复时已进物资准备(按政策退 3200)→ 实退 3200。
|
||||
- **一次到位**:团期侧审批完即执行,退款**不再进退款审批中心二次审**。
|
||||
- **名额回落**:1 单 = 1 户 = 1 房,通过后该团期已报名 −1 户 / −N 人。
|
||||
- **并发**:按 `approvalId` 加分布式锁 + 状态 CAS 双保险,两名管理员同时点通过只有一个成功,另一个 589532。
|
||||
- **不可撤销**:钱一旦进实退链路就撤不回来,要撤走售后退款。
|
||||
|
||||
---
|
||||
|
||||
### 5. 取消退单(驳回) `POST /v3/admin/order/group-batch/withdraw/:approvalId/reject`
|
||||
|
||||
**VO**: `RejectWithdrawReqVO` / `WithdrawApprovalDetailRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
审批中心详情页点「取消退单」。**该户继续留在团期中走原流程**,可以继续提需求、住房用车、正常出行。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| approvalId | Path | Long | ✅ | - | 退单审批单 ID |
|
||||
| remark | Body | String | ✅ | 非空,≤512 字 | 驳回原因(合规要求必填),body 不可省略 |
|
||||
|
||||
#### 出参 `Result<WithdrawApprovalDetailRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| (详情全部字段) | - | 见接口 3 |
|
||||
| approvalStatus | String | 固定 `REJECTED` |
|
||||
| approveRemark | String | 驳回原因 |
|
||||
| approvedByName | String | 操作人 |
|
||||
| approvedAt | DateTime | 操作时间 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{ "remark": "客户已改口,继续参团" }
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"approvalId": "1955009900112233",
|
||||
"approvalStatus": "REJECTED",
|
||||
"approveRemark": "客户已改口,继续参团",
|
||||
"approvedByName": "刘涛",
|
||||
"approvedAt": "2026-09-04 18:05:12"
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
写操作,无空数据形态。时间线写失败降级不阻断驳回。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 589532,
|
||||
"message": "该退单申请已处理,不可重复操作",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
| code | 含义 |
|
||||
|------|------|
|
||||
| 589530 | 非管理员角色 |
|
||||
| 589531 | 申请单不存在 |
|
||||
| 589532 | 申请单已是终态 |
|
||||
|
||||
remark 为空时走参数校验,返回校验失败提示(非业务码)。
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **零副作用**:只改申请单状态。订单状态、团期名额、退款单**三项分文未动**。
|
||||
- **可再次提交**:驳回后该户可以再走一遍退单户流程(在途判重只拦 `PENDING` 单)。
|
||||
- **并发**:同 approve,锁 + CAS 双保险。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
### ✅ 正确 / ❌ 错误 payload 对照
|
||||
|
||||
```jsonc
|
||||
// ✅ 提交退单:body 可省略,也可只带 reason
|
||||
{ "reason": "客户临时有事无法参团" }
|
||||
|
||||
// ❌ 不要再期待「调完就退款了」——现在只是建了一张待审单
|
||||
// ❌ 不要传 refundMode:退款模式由后端按团期状态定,前端无权指定
|
||||
|
||||
// ✅ 取消退单:remark 必填
|
||||
{ "remark": "客户已改口,继续参团" }
|
||||
|
||||
// ❌ 取消退单不传 remark → 参数校验失败
|
||||
{}
|
||||
```
|
||||
|
||||
### 金额取哪个数
|
||||
|
||||
- 弹窗展示 → 提交接口返回的 `paidAmount` + `estimatedRefundAmount`
|
||||
- 审批详情展示 → `paidAmount` + `currentEstimatedRefundAmount`(当前值才是实退依据)
|
||||
- **都不要**调 `/v3/admin/order/{id}/cancel-preview` 自己算,它恒按退改政策算,团期早期会少显示
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
- 提交时新增一条退单审批记录(待审),**不改订单、不改团期计数、不建退款单**。
|
||||
- 审批通过时才发生:订单流转为已取消、团期已报名人数/户数回落、按金额生成退款申请并进实退链路
|
||||
(金额为 0 时不生成退款申请)。
|
||||
- 取消退单(驳回)只更新审批记录的状态与批复信息,其余数据零变动。
|
||||
- 提交 / 通过 / 驳回各写一条团期时间线;写失败降级为 WARN,不影响主流程。
|
||||
- 同一子订单同时只允许存在一条待审记录。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 出行中及以后不允许退单,提交与审批两处都拦(589501),走售后退款。
|
||||
- 未付分文可以退单;早期阶段(全退订金)仍会按**订金应付额**退并生成退款单,后期阶段(按政策)才退 0 且不生成退款单。
|
||||
- 已满团的团期提交退单会返回 `warnings` 提示,**不阻断**。
|
||||
- 审批期间该户完全正常:可提需求、可排房排车、可被派资源。
|
||||
- 审批期间若该户又付了尾款,通过时按**当前**已付款额重算退款。
|
||||
|
||||
## 六.5、枚举 / 数据字典
|
||||
|
||||
### 审批状态(`approvalStatus`)
|
||||
|
||||
| 值 | 含义 | 可做的操作 |
|
||||
|----|------|------------|
|
||||
| PENDING | 待审 | 通过 / 取消退单 |
|
||||
| APPROVED | 已通过(已退团) | 无(终态) |
|
||||
| REJECTED | 已取消退单 | 无(终态);该户可再次提交新申请 |
|
||||
|
||||
### 退款模式(`refundMode`)
|
||||
|
||||
| 值 | 含义 | 出现阶段 |
|
||||
|----|------|----------|
|
||||
| FULL_DEPOSIT | 全额退**订金应付额**(与已付款额无关;订金为 0 时订单侧拒绝退款) | 招募中 / 资源准备中 |
|
||||
| POLICY | 以**已付款额**为基数按退改政策阶梯扣减 | 物资准备中 / 待出发 |
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
针对 `POST /v3/admin/order/group-batch/:groupBatchId/sub-order/:orderId/withdraw`:
|
||||
|
||||
| 维度 | 修改前 | 修改后 |
|
||||
|------|--------|--------|
|
||||
| 调用后果 | **立即退款**:订单取消、名额释放、退款单自动建 | **只建待审申请单**,三项零变动 |
|
||||
| 招募中 / 资源准备中 | 免审直退,钱当场退 | 同样要管理员审批 |
|
||||
| 物资准备中 / 待出发 | 退款单挂起等定制师审 | 团期侧审批通过后一次到位,不再二次审 |
|
||||
| 响应体 | `Result<Void>`,`data` 为 null | `Result<WithdrawSubOrderRespVO>`,含两个金额与审批单 ID |
|
||||
| 能否反悔 | 不能,订单已取消 | 能,管理员可「取消退单」,该户继续留在团期 |
|
||||
| 退款金额口径 | 按团期状态选模式 | **不变**,仍按团期状态选模式 |
|
||||
| 谁审批 | 定制师(且早期免审) | **管理员角色**(ADMIN / SUPER_ADMIN) |
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **前端必改**:提交成功后的提示语(「已退款」→「已提交审核,待管理员确认后退款」);
|
||||
弹窗金额改为两行展示;新增审批中心三个页面(列表 / 详情 / 通过与取消)。
|
||||
- **不改也不会崩**:响应体从 null 变对象是**向后兼容**的(老前端不读 `data` 即可),
|
||||
但**业务语义会错**——用户以为钱退了,实际还在等审批。属必须跟进项。
|
||||
- **运营流程变化**:招募期退款不再是秒退,需要管理员点一下;换来的是可撤销。
|
||||
- **其他端**:C 端、小程序、财务侧退款工作台**均不受影响**——退款单的建立与实退链路完全没动。
|
||||
- **回滚**:回滚本次发布即恢复旧行为;已建的待审申请单不会自动执行,需人工处理。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- 转订单 `POST .../transfer-in`(#7095)——两件事,各走各的,本次未动。
|
||||
- 团期成团 / 取消成团 / 流团 / 调整容量 / 预支等其余团期动作端点。
|
||||
- 子订单列表 `GET /v3/admin/order/group-batch/:groupBatchId/orders`——退单弹窗下拉仍用它,出参未变。
|
||||
- 订单侧散客退改政策与 `/v3/admin/order/{id}/cancel-preview`,口径与实现均未动。
|
||||
- 退款工作台的申请、审核、实退链路。
|
||||
- C 端 / 小程序全部接口。
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
**过网关真测已完成**(2026-09-05,`https://api.test.1814.love:9443`,
|
||||
admin 账号经 `POST /admin/auth/login` + `POST /admin/auth/switch-role` 切到 `ADMIN` 角色)。
|
||||
测试数据:在团期 `Q202610312089667212070612994`(资源准备中)代下两单并全程验证后作废。
|
||||
|
||||
| # | 验证项 | 结果 |
|
||||
|---|--------|------|
|
||||
| 1 | 角色门:`ROOM_MANAGER` 调审批列表 | 589530 拒绝 ✅ |
|
||||
| 2 | 072 列表(ADMIN,缺省 PENDING / `ALL`) | 200,分页结构 `records/total/page/pageSize` ✅ |
|
||||
| 3 | 073 详情:不存在的单 | 589531 ✅ |
|
||||
| 4 | 070 提交:不存在的团期 | 589500 ✅ |
|
||||
| 5 | 070 提交:真团期 + 不存在的子订单 | 581007 订单不存在 ✅ |
|
||||
| 6 | 070 提交:真实子订单 | 200,返 approvalId + 两个金额 + 模式 ✅ |
|
||||
| 7 | 提交后订单/名额零变动 | 订单仍 `PENDING_PAY`,enrolled 仍 2/1 ✅ |
|
||||
| 8 | 072 列表出现该待审单 | total=1,字段齐 ✅ |
|
||||
| 9 | 073 详情双预估 | 提交时预估与当前预估均返回 ✅ |
|
||||
| 10 | 070 重复提交 | 589529 ✅ |
|
||||
| 11 | **075 取消退单** | 200 → REJECTED ✅ |
|
||||
| 12 | **驳回后该户仍在团里** | 订单仍 `PENDING_PAY`,enrolled 仍 2/1,三项零变动 ✅ |
|
||||
| 13 | 已处理的单再驳 | 589532 ✅ |
|
||||
| 14 | 驳回后可再次提交 | 200,新 approvalId ✅ |
|
||||
| 15 | **074 审核通过** | 200 → APPROVED,回填批复人/实退额 ✅ |
|
||||
| 16 | 通过后订单取消 + 名额回落 | 订单 `CANCELLED`,enrolled 2/1 → **0/0** ✅ |
|
||||
| 17 | 已通过的单再批 | 589532 ✅ |
|
||||
| 18 | 075 remark 为空 | 400「驳回原因不能为空」✅ |
|
||||
| 19 | **预估 == 实退** | 预估 ¥2000 = 实退 ¥2000 ✅(见下方缺陷) |
|
||||
|
||||
### 实测暴露并已修复的缺陷
|
||||
|
||||
首轮实测发现:已付 ¥0 的单,弹窗显示「预计退 ¥0」,审批通过后**实退 ¥2000**——预估与实退不同源。
|
||||
根因是订单侧 `FULL_DEPOSIT` 退的是**订金应付额**而非已付额,本模块的预估函数错用了已付额。
|
||||
已由 **PR #7126** 修复(预估改用订金、订金为 0 时出 warning),重新部署后复测预估与实退一致。
|
||||
两轮验证产生的退款单(`2096027837650616321`、`2096029516789915650`)均已置 REJECTED,
|
||||
无实退记录,**测试环境资金零变动**。
|
||||
|
||||
### 部署记录
|
||||
|
||||
- 2026-09-04 19:39–19:40 首次部署(PR #7106)
|
||||
- 2026-09-05 08:14–08:15 修复后重新部署(PR #7126)
|
||||
|
||||
两次均从 `dev-v3` 重新构建 hl-order-service-v3 并滚动重启,8186 / 8086 双实例先后健康。
|
||||
|
||||
### 本地测试
|
||||
|
||||
`mvn -pl hl-order-service-v3 -am test` 共 8175 例,Failures: 0、Errors: 7——
|
||||
7 例全部是 Testcontainers 迁移测试因本机无 Docker 报 `IllegalState`,与本次改动无关。
|
||||
`GroupBatchWithdrawApprovalServiceTest` 18 例(含 3 例订金口径回归)、
|
||||
`GroupBatchFinanceServiceTest` 24 例、团期全域 553 例全绿;ArchUnit 架构规则全绿。
|
||||
|
||||
---
|
||||
|
||||
## 九、相关历史 PR
|
||||
|
||||
| PR | Issue | 说明 | 是否仍有效 |
|
||||
|----|-------|------|------------|
|
||||
| **PR #7126** | **#7100** | 实测暴露的预估口径修复(FULL_DEPOSIT 按订金算) | ✅ 最新 |
|
||||
| PR #7106 | #7100 | 退单户改为申请 + 管理员审批,新增 072~075 | ✅ 有效 |
|
||||
| #7103 | #7095 | 团期转订单(与本次并行开发,错误码与迁移号已错开) | ✅ 有效 |
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 关联 Issue: [wx/HL#7100](https://git.1814.love:8443/wx/HL/issues/7100)
|
||||
- 关联 PR: [wx/HL#7106](https://git.1814.love:8443/wx/HL/pulls/7106)
|
||||
- 后续计划: 流团审批复用同一套审批表(本次只做退单);已通过后的撤销走售后,不在本次范围
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#7100](https://git.1814.love:8443/wx/HL/issues/7100)
|
||||
- **PR**: [#7106](https://git.1814.love:8443/wx/HL/pulls/7106) + [#7126](https://git.1814.love:8443/wx/HL/pulls/7126)(预估口径修复)
|
||||
- **Merge commit**: [46c261d84](https://git.1814.love:8443/wx/HL/commit/46c261d84)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @jw
|
||||
在新工单中引用
屏蔽一个用户