--- schema: "hl-changelog/v2" ticket: "7939" title: "团期看板统计条回带生效班期范围 effectiveScope 与被过滤条数 filteredOutCount" consumer: "admin" author: "jw(GIT)" change_type: "修改接口" backend_status: "deployed" gateway_status: "verified" frontend_status: "verified" frontend_owner: "mmg" frontend_ref: "8360287a44191921a5abd7110b1ecd4f2b8f98ae" target_release: "" verified_at: "2026-09-18" 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 期已结束)」,见第四节。[mmg 2026-09-18 已实现并验证] batch/index.vue 总览条加 scopeFilterHint:ONGOING 且 filteredOutCount>0 显「仅显示未结束班期(另有 N 期已结束)」、FINISHED 显「仅显示已结束班期(另有 N 期未结束)」、ALL 或 0 不提示;scope 已随 9/16 前台条目摘除,经用户确认只提示不加「查看全部」切换;store 原样存响应新字段自动流入,零 API/store 改动;spec 加 #7939 专项 3 例,定向 9/9+scoped checkpoint 全绿。" 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