docs(changelog): #7344 取消班期活跃订单判据 fail-closed(聚合失败取保守侧 CANCELLING,出入参零变化)
changelog-filename-gate / validate (push) Successful in 2s

管理端取消班期在判「班期还有没有活跃订单」时用的是 fail-open 取数:order-v3
实时聚合一失败就退化为「无活跃订单」,班期被落成终态 CANCELLED 且全仓无路径
可自愈。改为聚合失败取保守侧 CANCELLING + WARN,与订单域内部入口口径对齐。

接口 / 字段 / 路径 / 参数 / 枚举 / 错误码零变化,batchStatus 取值集合与语义
不变,CANCELLING 与 CANCELLED 在下游四处判定中等价 → frontend_status=not_required。

backend_status=deployed / gateway_status=verified:已部署测试服,经真实网关实测
三条路径(无订单→CANCELLED、真实下单→CANCELLING、注入聚合失败→CANCELLING+WARN),
落库均以 SQL 复核。

Refs HL#7344 (PR #7348, 合并提交 2071286a1)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R2LSgt2xhmX2ae9M3rkdZg
这个提交包含在:
jw
2026-09-08 17:24:56 +08:00
共同撰写人 Claude Opus 5
父节点 a32b384cb1
当前提交 9c1290d1c7
@@ -0,0 +1,93 @@
---
schema: "hl-changelog/v2"
ticket: "7344"
title: "管理端取消班期:order-v3 活跃订单聚合失败时不再落终态 CANCELLED,改取保守侧 CANCELLING(无接口、字段、路径、参数、枚举、错误码变化)"
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: "纯后端修复(PR #7348 合入 dev-v3,合并提交 2071286a1),不动接口/字段/路径/参数/枚举/错误码,只在「order-v3 活跃订单聚合失败」这一条失败路径上把落库值由 CANCELLED 改为更保守的 CANCELLING。batchStatus 的取值集合与语义均不变(CANCELLING/CANCELLED 在展示态解析、可报名谓词、选品列表过滤、order-v3 下单闸四处判定中一律同等排除,差异只在展示文案),前端读后端值直接渲染、无兜底反推逻辑,故 not_required。已部署测试服并经真实网关三条路径实测(无订单→CANCELLED / 有真实订单→CANCELLING / 聚合失败→CANCELLING+WARN)。"
updated_at: "2026-09-08"
base: "dev-v3"
---
# 取消班期的活跃订单判据改 fail-closed(修复)
## 一、给前端的一句话
**前端无需任何改动。** 本次修复不动接口、不动字段、不动路径、不动参数、不动枚举、不动错误码。`POST /admin/product/item/:id/schedule/:scheduleId/cancel` 的响应在全部场景下仍是 `code=200` / `message="班期取消成功"` / `data=null`;`batchStatus` 的取值集合(`ENROLLING` / `NEARLY_FULL` / `FULL` / `FINISHED` / `CANCELLING` / `CANCELLED`)与语义也不变。变的只是**订单服务聚合失败时落库取哪一个取消态**。
## 二、症状(改前)
管理端「取消班期」要先问 order-v3「这个班期还有没有活跃订单」,再决定落 `CANCELLING`(还有订单待订单域处理)还是 `CANCELLED`(确实没订单)。
这次取数走的是 fail-open 的降级路径:向 order-v3 的实时聚合一旦失败(Feign 超时 / 5xx / 返回 `code != 200` / 目标班期缺席结果),失败分片的班期就退化为「无活跃订单」,活跃数被当成 0,班期于是被落成**终态 `CANCELLED`**。
后果不是钱退不出去(取消班期本来就不发起任何退款,见第五节),而是三件事:
1. **状态语义错误**:明明还有活跃订单的班期,被记成「已取消、无遗留」。
2. **与订单域口径分裂**:order-v3 流团触发的同语义入口(内部接口)早已收口为 fail-closed,两条路径对同一件事给出相反结论。
3. **写错后不自愈**:全仓没有任何路径会把 `CANCELLED` 改回来——班期状态重算只在 `ENROLLING` ↔ `FULL` 之间流转、明确不碰取消态;编辑班期会保留原状态;订单域再次回调时对已取消态幂等直接返回。一旦写错,只能手工改库。
触发窗口窄(order-v3 抖动期间恰好有人点取消),但一旦命中就是永久性的错误状态。
## 三、修复(改后)
活跃数取数改用严格版本:聚合成功才按真实活跃订单数判定,聚合失败取**保守侧 `CANCELLING`** 并记一条 WARN 日志,与订单域内部入口逐字对齐。**不新增任何接口、不新增任何方法。**
| 场景 | 改前落库 `batchStatus` | 改后落库 `batchStatus` |
|---|---|---|
| 聚合成功且有活跃订单 | `CANCELLING` | `CANCELLING`(不变) |
| 聚合成功且无活跃订单 | `CANCELLED` | `CANCELLED`(不变) |
| **聚合失败**(超时 / 5xx / `code != 200` / 班期缺席结果) | `CANCELLED` | **`CANCELLING`** + 一条后端 WARN |
保守侧的代价是:order-v3 抖动窗口内被取消的空班期会显示「取消中」而不是「已取消」。这是刻意接受的——两个状态在全部下游判定里等价(下一节),差异只是展示文案;而反方向的错误会丢失「这批还有订单要处理」这一运营信号,且同样不自愈。
## 四、受影响接口(全部「复用不改」)
| 端 | 接口 | 变化 |
|---|---|---|
| 管理后台 | `POST /admin/product/item/:id/schedule/:scheduleId/cancel` | method / path / 入参 / 出参 / 错误码均无变化;只有「聚合失败」这一条路径的落库值变了。Swagger `notes` 文案一并修正(原文误称「标记为 CANCELLING 触发退款」,实际不触发任何退款) |
| 管理后台 | `GET /admin/product/item/:id/schedule/list` | 无变化。仅作为取证入口,`batchStatus` 字段读到的就是上面落库的值 |
`CANCELLING` 与 `CANCELLED` 在下游四处判定中**完全等价**,前端已有的渲染逻辑不受影响:
- 班期展示态解析:两者都原样保留持久值,不参与实时库存重算
- 可报名谓词:两者都返回不可报名
- 下单选品列表:两者都被排除
- order-v3 下单闸:可下单状态只含 `ENROLLING` / `NEARLY_FULL`,两者都不在其中
## 五、澄清:取消班期不触发退款
修复顺带纠正了两处误导性文案(服务层注释与 Swagger `notes`)。它们原本写「CANCELLING → 触发退款 / 等退款流程异步处理」,但产品域取消班期的方法体只有一次班期状态落库,不发 MQ、不调 Feign、不发事件;全仓也没有任何代码读产品侧的 `CANCELLING` 去发起退款。团期退款实际由订单域的流团审批链路驱动。前端如果据旧文案做过「取消班期后等退款」的假设,可以去掉。
## 六、影响范围
- 只影响管理端「取消班期」这一个动作,且只在 order-v3 聚合失败时表现不同。
- 后端只滚 `hl-product-service-v2`;order-v3 / 网关 / 小程序端 / 其它服务一律不动。
- 无表结构变更、无 Flyway、无数据迁移;`group_tour_batch.batch_status` 的列定义与取值字典不变。
## 七、测试环境已验证
测试环境(`api.test.1814.love`)经真实网关实测三条路径,落库均以 SQL 复核:
| 路径 | 实测结果 |
|---|---|
| 无活跃订单 → 取消 | `code=200`,`batchStatus=CANCELLED`(不回归) |
| 真实下单后 → 取消 | `code=200`,`batchStatus=CANCELLING`(不回归) |
| 注入聚合失败 → 取消无订单班期 | `code=200`,`batchStatus=CANCELLING`,后端 WARN「取消班期: 活跃订单数聚合失败, 取保守侧 CANCELLING」 |
聚合失败用「只压单个 Feign 客户端超时」的方式构造,未停 order-v3;取证后配置已还原并复核一致。合并进 `dev-v3` 后又在 `dev-v3` 构建上复核过正常路径。本机单测 `BUILD SUCCESS`(1621 例,0 失败 0 错误),含新增的聚合失败用例与架构门禁。
## 关联 / 联系人
- 工单:https://git.1814.love:8443/wx/HL/issues/7344
- PR:https://git.1814.love:8443/wx/HL/pulls/7348 (合并提交 2071286a1 合入 dev-v3)
- 同源三单:#7283(订单域内部入口先行收口)、#7293(状态重算同构降级)、#7263(分片修复同一聚合链路)
- 后端:jw