diff --git a/changelogs-v2/2026-09/18_7939_团期看板统计条回带生效班期范围与被过滤条数-修改接口-管理后台.md b/changelogs-v2/2026-09/18_7939_团期看板统计条回带生效班期范围与被过滤条数-修改接口-管理后台.md new file mode 100644 index 00000000..f7172bab --- /dev/null +++ b/changelogs-v2/2026-09/18_7939_团期看板统计条回带生效班期范围与被过滤条数-修改接口-管理后台.md @@ -0,0 +1,302 @@ +--- +schema: "hl-changelog/v2" +ticket: "7939" +title: "团期看板统计条回带生效班期范围 effectiveScope 与被过滤条数 filteredOutCount" +consumer: "admin" +author: "jw(GIT)" +change_type: "修改接口" +backend_status: "deployed" +gateway_status: "verified" +frontend_status: "pending" +frontend_owner: "mmg" +frontend_ref: "" +target_release: "" +verified_at: "" +status_note: "团期看板统计条 GET /v3/admin/order/group-batch/summary 出参纯新增 effectiveScope(本次生效的班期范围 ONGOING/FINISHED/ALL)与 filteredOutCount(被范围过滤掉的班期数,与 total 同一套 productId/month/keyword 筛选,ALL 时恒 0);入参、既有 5 个字段、错误码、scope 缺省值均不变。解决「产品卡片 10 期、统计条共 8 期」同屏打架且无解释的问题。后端已合并 dev-v3(129de0292)并部署 TEST,网关实测 29/29 通过。前端待办:effectiveScope≠ALL 且 filteredOutCount>0 时,在统计条旁提示「仅显示未结束班期(另有 N 期已结束)」,见第四节。" +updated_at: "2026-09-18" +base: "dev-v3" +--- + +# order-v3: 团期看板统计条回带生效班期范围与被过滤条数 + +> **存放目录**: `changelogs-v2/{YYYY-MM}/`(管理后台,二期 order-v3) +> +> **服务**: hl-order-service-v3 (端口 8086) +> **PR**: #7941 +> **Issue**: #7939 +> **日期**: 2026-09-18 +> **影响范围**: 管理后台「团期订单」页面的统计条(班期总览 / 状态页签计数) + +--- + +## ⚠️ 关键变化 + +1. **统计条接口多返回两个字段**:`effectiveScope`(本次实际按哪个班期范围统计)和 `filteredOutCount`(被这个范围过滤掉了几期)。 +2. **只加字段,不改原有字段**,也不改 `scope` 的缺省规则:带 `productId` 不传 `scope` 时仍只统计**未结束**班期。 +3. **前端待办**:结果集被时间过滤时给出提示,避免运营把「共 8 期」当成「这个产品只有 8 期」,见第四节。 + +--- + +## 一、背景 + +「团期订单」页面同一屏出现两个互相矛盾的班期数:左侧产品卡片写「王骁测试团期产品 **10 期**」,右侧班期总览写「共 **8 期**」、状态页签「全部期 **8**」,界面上没有任何解释。 + +原因是统计条接口在 `productId` 有值且不传 `scope` 时,缺省只统计未结束班期(`end_date >= 今天` 或未填返团日),已结束的 2 期被排除;而响应里没有任何字段说明「结果被按时间过滤过」,前端无从提示。2026-09-18 测试环境造数时当事人第一反应是「数据丢了」。 + +本次**只让过滤变得可见**,缺省只看未结束班期是刻意设计,保持不变。 + +--- + +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|------|------|------|----------|------| +| 1 | 团期看板统计条 | GET | `/v3/admin/order/group-batch/summary` | 修改 | 出参新增 `effectiveScope`、`filteredOutCount` | + +--- + +## 三、接口详情 + +### 1. 团期看板统计条 `GET /v3/admin/order/group-batch/summary` + +**VO**: `Result`(新增 `effectiveScope`、`filteredOutCount`) + +#### 使用场景 + +管理后台「团期订单」页面顶部统计条:班期总数、8 个运营阶段页签计数、子订单数、成团/满团门槛副标题。本次新增的两个字段用于在结果集被班期范围过滤时给出提示。 + +#### 入参 + +本次入参**不变**,原样列出供核对。 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| productId | Query | Long | 否 | 产品 ID | 缺省不限。**有值时 `scope` 缺省为 `ONGOING`** | +| month | Query | String | 否 | `yyyy-MM`,非法值忽略 | 按出团日落月 | +| keyword | Query | String | 否 | trim 后包含匹配 | 团期编号 / 名称模糊 | +| scope | Query | String | 否 | `ONGOING` / `FINISHED` / `ALL`,非法值按缺省 | 缺省:`productId` 有值→`ONGOING`,否则→`ALL` | + +#### 出参 + +| 字段 | 类型 | 说明 | +|------|------|------| +| total | Integer | 不变。当前范围内的班期总数(= 8 桶之和) | +| buckets | Map | 不变。8 桶固定全键 | +| subOrderCount | Integer | 不变。活跃子订单合计 | +| minToForm | Integer | 不变。成团门槛(唯一才给,否则 null) | +| maxRooms | Integer | 不变。满团门槛(唯一才给,否则 null) | +| effectiveScope | String | **新增**。本次实际生效的班期范围:`ONGOING` / `FINISHED` / `ALL`。不传 `scope` 时由 `productId` 决定;传了非法值时是回落后的缺省值 | +| filteredOutCount | Integer | **新增**。被本次范围过滤掉的班期数。与 `total` 使用**同一套** `productId` / `month` / `keyword` 条件,所以恒有 `total + filteredOutCount = 同条件 scope=ALL 的 total`;`effectiveScope=ALL` 时恒为 0;永不为 null | + +#### 请求示例 + +```http +GET /v3/admin/order/group-batch/summary?productId=2100839045562077186 +Authorization: Bearer +``` + +#### 响应示例 + +(测试服真实响应,2026-09-18;该产品 10 个班期,其中 08-20~08-22、09-03~09-05 两期已结束) + +```json +{ + "code": 200, + "message": "成功", + "data": { + "total": 8, + "buckets": { + "RECRUIT": 8, + "FORMED": 0, + "PENDING_TRIP": 0, + "TRAVELLING": 0, + "TRIP_FINISHED": 0, + "AUDITING": 0, + "CHECKED": 0, + "DISBANDED": 0 + }, + "subOrderCount": 0, + "minToForm": 0, + "maxRooms": 0, + "effectiveScope": "ONGOING", + "filteredOutCount": 2 + }, + "success": true +} +``` + +同一产品显式传 `scope=ALL`: + +```json +{ + "code": 200, + "message": "成功", + "data": { + "total": 10, + "buckets": { "RECRUIT": 10, "FORMED": 0, "PENDING_TRIP": 0, "TRAVELLING": 0, "TRIP_FINISHED": 0, "AUDITING": 0, "CHECKED": 0, "DISBANDED": 0 }, + "subOrderCount": 0, + "minToForm": 0, + "maxRooms": 0, + "effectiveScope": "ALL", + "filteredOutCount": 0 + }, + "success": true +} +``` + +#### 空数据 / 降级响应 + +产品名下没有任何班期时,两个新字段照常返回,不为 null(测试服真实响应,产品 `2045486260364996609`): + +```json +{ + "code": 200, + "message": "成功", + "data": { + "total": 0, + "buckets": { "RECRUIT": 0, "FORMED": 0, "PENDING_TRIP": 0, "TRAVELLING": 0, "TRIP_FINISHED": 0, "AUDITING": 0, "CHECKED": 0, "DISBANDED": 0 }, + "subOrderCount": 0, + "minToForm": null, + "maxRooms": null, + "effectiveScope": "ONGOING", + "filteredOutCount": 0 + }, + "success": true +} +``` + +#### 错误响应 + +```json +{ "code": 589507, "message": "无操作权限(非团期管理员 / 非本定制师名下)", "data": null } +``` + +| code | 触发条件 | +|------|----------| +| 589507 | 没有团期列表查看权限(`group-batch:list`) | +| 401 | 未登录(网关拦截) | + +与改动前相同,没有新增错误码。 + +#### 业务边界 + +- 只读接口,无事务、无写库;判权与改动前一致。 +- `filteredOutCount` 只统计**时间范围**这一个维度过滤掉的条数;`month` / `keyword` 过滤掉的不算在内。 +- 状态未知的脏数据行既不计入 `total`,也不计入 `filteredOutCount`,两者可直接相加。 +- `productId` 有值时第二次统计是纯内存计算(产品班期只拉一次);`productId` 缺省且 `scope≠ALL` 时多一次数据库查询。 +- 同一请求内「今天」只取一次,两次统计的范围判定口径一致。 + +--- + +## 四、契约约束与正确调用方式 + +### 前端展示建议 + +| 条件 | 建议提示 | +|------|----------| +| `effectiveScope === 'ONGOING' && filteredOutCount > 0` | 统计条旁:「仅显示未结束班期(另有 {filteredOutCount} 期已结束)」,可带「查看全部」切到 `scope=ALL` | +| `effectiveScope === 'FINISHED' && filteredOutCount > 0` | 「仅显示已结束班期(另有 {filteredOutCount} 期未结束)」 | +| `effectiveScope === 'ALL'` 或 `filteredOutCount === 0` | 不提示 | + +### ✅ 正确 / ❌ 错误 用法 + +| 场景 | 做法 | +|------|------| +| ✅ 判断统计结果有没有被时间过滤 | 看 `effectiveScope` 与 `filteredOutCount`,不要自己按「是否传了 scope」推断 | +| ✅ 得到该产品在当前筛选下的全部期数 | `total + filteredOutCount` | +| ❌ 把 `total` 当成「这个产品一共有多少期」 | 缺省只统计未结束班期,要么提示,要么显式传 `scope=ALL` | +| ❌ 用 `filteredOutCount` 解释 `month` / `keyword` 的差额 | 它只含时间范围过滤掉的条数 | + +--- + +## 六、边界行为 + +- 产品有已结束班期、不传 `scope` → `effectiveScope=ONGOING`,`filteredOutCount` = 已结束期数。 +- 显式 `scope=ALL` → `filteredOutCount=0`。 +- 显式 `scope=FINISHED` → `filteredOutCount` = 未结束期数。 +- 产品无班期 → `total=0`、`filteredOutCount=0`,均不为 null。 +- 全部班期都已结束且不传 `scope` → `total=0`,`filteredOutCount` = 全部期数。 +- 返团日是今天的班期算「未结束」;没填返团日的班期也算「未结束」。 +- `scope` 传非法值 → 按缺省处理(原行为),`effectiveScope` 返回回落后的值。 +- 无权限 → 589507;未登录 → 401(与改动前相同)。 + +--- + +## 六.6、修改前后对比 + +### 字段级对比 + +| 字段 | 改前 | 改后 | +|------|------|------| +| `effectiveScope` | 无 | 新增,String | +| `filteredOutCount` | 无 | 新增,Integer,永不为 null | +| `total` / `buckets` / `subOrderCount` / `minToForm` / `maxRooms` | — | 不变(TEST 18 组请求改前改后逐字比对一致) | +| 入参 4 个 | — | 不变 | + +### 行为级对比 + +| 行为 | 改前 | 改后 | +|------|------|------| +| 结果被按时间过滤时 | 响应无任何标识,前端无法提示 | 响应声明生效范围与被过滤条数 | +| `scope` 缺省规则 | `productId` 有值→ONGOING,否则→ALL | 不变 | +| 查询次数 | 1 次 | `scope=ALL` 时仍 1 次;否则 `productId` 场景多一次内存筛选,`productId` 缺省场景多一次 DB 查询 | + +## 六.7、影响评估 + +- **是否破坏向后兼容**: 否,只新增字段。 +- **前端是否必须同步上线**: 否,不接入也不会报错;但需要前端接入后,页面才会出现提示,「10 期 / 8 期」的困惑才会消失。 +- **回滚**: 回滚 PR #7941 即可,无数据变更。 + +--- + +## 七、不影响范围 + +- **仅影响**: `GET /v3/admin/order/group-batch/summary` 的响应(新增 2 个字段)。 +- **零影响**: + - `GET /v3/admin/order/group-batch/board`(缺省仍为 `ALL`,与 summary 缺省不一致的问题另行立项) + - `GET /v3/admin/order/group-batch/export`(与 summary 共用缺省规则,导出同样只含未结束班期且无说明,另行立项) + - GB-ADM-001 团期分页列表 + - 数据库:无表结构变更,只读 + +--- + +## 八、测试环境已验证 + +被测版本:hl-order-service-v3 = dev-v3 `129de0292`(2026-09-18 15:41 部署,双实例滚动完成)。取证前确认面板检出 `dev-v3 @ 129de0292`、`hl-order-service-v3` 与 `hl-gateway` 均 running,且新字段在线。网关 `https://api.test.1814.love:9443` 实测 **29/29** 通过。 + +``` +AC-1 productId=2100839045562077186 不传 scope → effectiveScope=ONGOING total=8 filteredOutCount=2;8+2 = ALL 的 10 ✓ +AC-2 同产品 scope=ALL → effectiveScope=ALL filteredOutCount=0 ✓;scope=FINISHED → total=2 filteredOutCount=8 ✓ +AC-3 productId + month=2026-09 → 0+1 = ALL 的 1 ✓ + productId + keyword=Q20260 → 0+2 = ALL 的 2 ✓ + productId + month=2026-10 → 4+0 = ALL 的 4 ✓ + productId + month=2026-08 + keyword → 0+1 = ALL 的 1 ✓ + 无 productId month=2026-09 ONGOING → 9+8 = ALL 的 17 ✓ + 无 productId keyword=GB ONGOING → 7+0 = ALL 的 7 ✓ + 无 productId month=2026-09 FINISHED → 8+9 = ALL 的 17 ✓ +AC-4 0 班期产品 2045486260364996609 → total=0 filteredOutCount=0 effectiveScope=ONGOING ✓ +AC-5 18 组请求(各 scope × 有无 productId × month/keyword)改前改后既有 5 字段逐字一致 ✓ +``` + +本地:`GroupBatchBoardStatsServiceTest` 33/0/0(新增 9 例);order-v3 全量 Half A 7849/0/0、Half B 3964/F1(`MapperBoundaryArchTest.non_refund_not_depend_on_refund_mapper`,干净 dev-v3 `846998c4b` 上同样失败,既存,与本次无关)。 + +--- + +## 十、相关文档 + +- 关联 Issue: [wx/HL#7939](https://git.1814.love:8443/wx/HL/issues/7939) +- 关联 PR: [wx/HL#7941](https://git.1814.love:8443/wx/HL/pulls/7941) +- 缺省范围单源:`GroupBatchScopeResolver.defaultFor`(#7189) + +## 关联 / 联系人 + +### 链接 + +- **Issue**: [#7939](https://git.1814.love:8443/wx/HL/issues/7939) +- **PR**: [#7941](https://git.1814.love:8443/wx/HL/pulls/7941) +- **Merge commit**: [129de0292](https://git.1814.love:8443/wx/HL/commit/129de0292) + +### 联系人 + +- **后端负责人**: @jw +- **前端负责人**: @mmg