退款单在流团事务【提交之后】才建(异步),所以 approve 响应体里的 actualRefundAmount 是暂定值=退款诉求合计;随后服务端逐户回读实际建单结果, 把没建出退款单的户剔掉,改写成定稿值。要展示准确金额请重新拉一次详情。 本文原本已写「actualRefundAmount 只统计成功发起退款的户」,PR #7354 之前 实现其实是 Σ诉求、有户建单失败就对不上;现在实现与本文口径一致了。 顺带修两处当前校验器不再接受的 frontmatter: consumer hl-ui → admin;not_required 不得保留 frontend_owner / verified_at。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
17 KiB
schema, ticket, title, consumer, author, change_type, backend_status, gateway_status, frontend_status, frontend_owner, frontend_ref, target_release, verified_at, status_note, updated_at, base
| schema | ticket | title | consumer | author | change_type | backend_status | gateway_status | frontend_status | frontend_owner | frontend_ref | target_release | verified_at | status_note | updated_at | base |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| hl-changelog/v2 | 7294 | 批准流团后已付款户会被自动取消并发起退款;审批单实退金额不再恒为 0;新增 589554 出行中户硬闸 | admin | jw(GIT) | 修改接口 | deployed | verified | not_required | 前端不改:接口出入参零变化;actualRefundAmount 前端本就直显后端权威值(group-batch-approval/index.vue 与 withdraw/index.vue formatPrice 直读),口径修正由后端算;589554 出行中户硬闸由拦截器透后端 message(无中央错误码字典)。补充(PR #7354):approve 响应里的 actualRefundAmount 是暂定值,定稿在提交后异步改写(约 1 秒内);上述两个页面读的是列表/详情接口不是 approve 响应,故仍不需要改,但请勿改成直接渲染 approve 响应的该字段。 | 2026-09-08 | dev-v3 |
团期: 流团对已付款户也取消并发起退款 + 出行中户硬闸 589554
服务: hl-order-service-v3(只需部署这一个) PR: #7333 Issue: #7294 日期: 2026-09-08 影响范围: 团期流团申请与审批(GB-ADM-060 / GB-ADM-062)
⚠️ 关键变化
出入参结构一个字段都没改。 前端不改不报错,但有三件事必须知道:
- 批准流团后,已付款的户现在会被自动取消并发起退款。改前只有「待支付」的户被取消, 付过定金或全款的户既不取消也不退款——团已解散,活跃子订单和客户的钱都还留着。
- 审批单的「实退金额」不再恒为 0。改前
actualRefundAmount是系统性的 0, 与「预估退款」形成无人负责的落差;现在它等于本次实际发起的退款诉求合计。 ⚠️ 这是退款诉求不是实际到账,界面文案建议写「已发起退款金额」。 - 新增错误码 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<DisbandApprovalRespVO>
| 字段 | 类型 | 说明 |
|---|---|---|
| approvalId | Long | 审批单 ID;响应结构完全不变 |
| approvalStatus | String | PENDING / APPROVED / REJECTED |
| affectedOrderCount | Integer | 影响子订单户数 |
| estimatedRefundAmount | BigDecimal | 提交时算的预估退款(Σ已付),口径未变 |
| actualRefundAmount | BigDecimal | 口径修正:本次实际发起的退款诉求合计。改前系统性恒为 0 |
| 其余全部字段 | — | 完全不变 |
请求示例
POST /v3/admin/order/group-batch/disband/2097220075659415553/approve HTTP/1.1
Authorization: Bearer <admin-token>
Content-Type: application/json
{"remark":"确认无法成团"}
响应示例
{
"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 响应里的这个字段。
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"approvalId": "2097216263326466050",
"approvalStatus": "APPROVED",
"affectedOrderCount": 3,
"estimatedRefundAmount": 6000.0,
"actualRefundAmount": 4000.0
}
}
错误响应
{
"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<DisbandApprovalRespVO>
| 字段 | 类型 | 说明 |
|---|---|---|
| approvalId | Long | 新建的审批单 ID;响应结构完全不变 |
| approvalStatus | String | 恒为 PENDING |
| affectedOrderCount | Integer | 影响子订单户数 |
| estimatedRefundAmount | BigDecimal | 预估退款(Σ已付),口径未变 |
| 其余全部字段 | — | 完全不变 |
请求示例
POST /v3/admin/order/group-batch/2097220073544073217/disband HTTP/1.1
Authorization: Bearer <admin-token>
Content-Type: application/json
{"reason":"临近出团仍未达最低成团数"}
响应示例
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"approvalId": "2097220075659415553",
"approvalStatus": "PENDING",
"affectedOrderCount": 3,
"estimatedRefundAmount": 8000.0
}
}
空数据 / 降级响应
被 589554 拒时不会建出审批单,团期审批列表里不会多出一条待处理项, 运营处理完出行中那户后重新发起即可。
{
"code": 589554,
"message": "团期下有出行中的子订单,不可流团;请等其出行完毕,或先对该户单独终止行程",
"success": false,
"data": null
}
错误响应
{
"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;建议把审批单的「实退金额」文案改为「已发起退款金额」。 接口结构零变化,不改也不会报错。