diff --git a/changelogs-v2/2026-09/05_7100_团期退单户改为提交申请与管理员审批-修改接口-管理后台.md b/changelogs-v2/2026-09/05_7100_团期退单户改为提交申请与管理员审批-修改接口-管理后台.md new file mode 100644 index 00000000..31b3a27e --- /dev/null +++ b/changelogs-v2/2026-09/05_7100_团期退单户改为提交申请与管理员审批-修改接口-管理后台.md @@ -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` 变成对象**。老前端只看 `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` + +| 字段 | 类型 | 说明 | +|------|------|------| +| 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>` + +| 字段 | 类型 | 说明 | +|------|------|------| +| 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` + +| 字段 | 类型 | 说明 | +|------|------|------| +| (列表行全部字段) | - | 见接口 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` + +| 字段 | 类型 | 说明 | +|------|------|------| +| (详情全部字段) | - | 见接口 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` + +| 字段 | 类型 | 说明 | +|------|------|------| +| (详情全部字段) | - | 见接口 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`,`data` 为 null | `Result`,含两个金额与审批单 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