--- 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` | 字段 | 类型 | 说明 | |------|------|------| | 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 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` | 字段 | 类型 | 说明 | |------|------|------| | approvalId | Long | 新建的审批单 ID;**响应结构完全不变** | | approvalStatus | String | 恒为 PENDING | | affectedOrderCount | Integer | 影响子订单户数 | | estimatedRefundAmount | BigDecimal | 预估退款(Σ已付),口径未变 | | 其余全部字段 | — | **完全不变** | #### 请求示例 ```http POST /v3/admin/order/group-batch/2097220073544073217/disband HTTP/1.1 Authorization: Bearer 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**;建议把审批单的「实退金额」文案改为「已发起退款金额」。 **接口结构零变化,不改也不会报错。**