--- 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: "verified" frontend_owner: "mmg" frontend_ref: "4bc8dd4d" 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