diff --git a/changelogs-v2/2026-09/27_8413_团期看板导出与统计条补齐列表新筛选-修改接口-管理后台.md b/changelogs-v2/2026-09/27_8413_团期看板导出与统计条补齐列表新筛选-修改接口-管理后台.md new file mode 100644 index 00000000..d35992b2 --- /dev/null +++ b/changelogs-v2/2026-09/27_8413_团期看板导出与统计条补齐列表新筛选-修改接口-管理后台.md @@ -0,0 +1,381 @@ +--- +schema: "hl-changelog/v2" +ticket: "8413" +title: "团期看板导出与统计条补齐列表新筛选:导出接收团期状态 / 截止日 / 出发区间 / 排序(看到什么导什么),统计条接收截止日 / 出发区间;三接口截止日非法值改为忽略" +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: "jw 2026-09-27 定:统计条只跟截止日、出发区间(不跟团期状态 / 看板桶 / 排序);导出看到什么导什么(传排序同列表,不传与列表各自默认一致:按产品 = 出发日升序、不按产品 = 建团时间倒序);导出不带分页、保持 2000 行上限。PR #8417(主体)+ #8432(截止日宽容解析)已合 dev-v3(dada3a8f4)并部署 TEST。2026-09-27 11:54~15:12 经真实网关实测:按产品与全局两种基底、团期状态 / 截止日 / 出发区间组合与三个排序字段 × 升降序、不传排序,导出行集与顺序均与列表已建团行逐一致;产品侧改期(快照 12-10 / 实时 12-15)后按实时日期筛,列表与导出同时命中、按快照日期同时不命中;统计条 total = 列表 total = 七桶之和,filteredOutCount 非零场景(8 / 15)与「ALL − ONGOING」一致;统计条多传团期状态 / 看板桶 / 排序结果不变;非法截止日期三接口均 200 且等同不传该条件(改前列表与导出 500)。TEST 全库团期仅 298 条,2000 行上限由单测覆盖。前端需把列表当前筛选同步传给统计条与导出,故 frontend_status 记 pending。" +updated_at: "2026-09-27" +base: "dev-v3" +--- + +# 团期看板:导出与统计条跟上列表筛选(管理后台) + +> **服务**: hl-order-service-v3(端口 8086/8186) +> **PR**: #8417、#8432 +> **Issue**: #8413 +> **日期**: 2026-09-27 +> **影响范围**: 管理后台「团期订单」看板的统计条与「导出」按钮;列表接口仅截止日非法值的处理变化 + +--- + +## ⚠️ 关键变化 + +1. **导出新增入参**:`batchStatus`、`deadlineFrom` / `deadlineTo`、`departFrom` / `departTo`、`sortBy` / `sortOrder`,与列表同名同义。前端把列表**当前的全部筛选和排序**原样传给导出,导出的行和顺序就与列表一致(导出不含未建团行,这一点不变)。 +2. **导出不传排序时的默认顺序变了(仅不按产品时)**:以前一律按出发日升序;现在与列表一致——**按产品看 = 出发日升序,不按产品看 = 建团时间倒序**。 +3. **统计条新增入参**:`deadlineFrom` / `deadlineTo`、`departFrom` / `departTo`。**不接收**团期状态、看板桶、排序——统计条是按桶分组的计数,前端传了也会被忽略。 +4. **报名截止日非法值不再报 500**:列表、导出、统计条传非法截止日期(如 `bad-date`、`2026-02-30`)时忽略该条件,与出发区间一致。以前列表和导出会整页返回 500。 + +--- + +## 一、背景 + +列表先后加了团期状态、报名截止日、出发区间(#7770)和排序(#7770),导出和统计条没跟上:运营在列表上筛完,顶部统计条数字对不上,导出拿到的也是没筛的全量、顺序也不同。 + +--- + +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|---|---|---|---|---| +| 1 | 导出团期列表 | GET | `/v3/admin/order/group-batch/export` | 修改 | 新增团期状态 / 截止日 / 出发区间 / 排序入参;不按产品时默认顺序改为建团时间倒序 | +| 2 | 团期看板统计条 | GET | `/v3/admin/order/group-batch/summary` | 修改 | 新增截止日 / 出发区间入参 | +| 3 | 团期分页列表 | GET | `/v3/admin/order/group-batch` | 修改 | 截止日非法值由 500 改为忽略该条件 | + +--- + +## 三、接口详情 + +### 1. 导出团期列表 `GET /v3/admin/order/group-batch/export` + +**VO**: `GroupBatchListReqVO`(入参,与列表同一个)→ CSV 附件(固定 10 列,不变) + +#### 使用场景 + +看板点「导出」,导出当前筛选下的全部已建团团期(不分页,单次上限 2000 行)。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|---|---|---|---|---|---| +| productId | query | Long | 否 | — | 原有 | +| month | query | String | 否 | yyyy-MM | 原有,决定文件名 | +| keyword | query | String | 否 | — | 原有 | +| opsStage | query | String | 否 | 七桶 code | 原有 | +| scope | query | String | 否 | ONGOING / FINISHED / ALL | 原有 | +| batchStatus | query | String | 否 | 九态 code | **新增**,精确匹配 | +| deadlineFrom / deadlineTo | query | String | 否 | yyyy-MM-dd,非法值忽略 | **新增**,报名截止日区间 | +| departFrom / departTo | query | String | 否 | yyyy-MM-dd,非法值忽略 | **新增**,出团日期区间 | +| sortBy | query | String | 否 | departDate / enrollDeadline / createTime,非法值忽略 | **新增** | +| sortOrder | query | String | 否 | asc / desc,缺省或非法 asc | **新增** | + +`pageNo` / `pageSize` 即使传了也不生效(导出不分页)。 + +#### 出参 + +| 字段 | 类型 | 说明 | +|---|---|---| +| (CSV 附件) | text/csv | 列:团期号、期号、日期、出团日、满团名额、已售、剩余、状态、子订单数、整团应收;不变 | + +#### 请求示例 + +```http +GET /v3/admin/order/group-batch/export?productId=2056944943066132481&scope=ALL&batchStatus=RECRUITING&deadlineFrom=2026-10-01&deadlineTo=2026-12-31&departFrom=2026-10-01&departTo=2027-01-31&sortBy=departDate&sortOrder=desc +Authorization: Bearer +``` + +#### 响应示例 + +成功时直接返回 CSV 文件流(`Content-Disposition: attachment`),不套 Result 信封。 + +```json +{ + "说明": "成功响应为 CSV 附件,此处仅示意首行表头", + "header": "团期号,期号,日期,出团日,满团名额,已售,剩余,状态,子订单数,整团应收" +} +``` + +#### 空数据 / 降级响应 + +无命中时返回只含表头的 CSV。按产品导出时要调产品服务取实时日期,产品服务不可用返回 589515(与列表、统计条一致): + +```json +{ + "code": 589515, + "message": "获取团期产品列表失败,请稍后重试", + "data": null, + "success": false +} +``` + +#### 错误响应 + +| code | 触发条件 | +|---|---| +| `589517` | 全部筛选后命中超过 2000 行 | +| `589515` | 按产品导出时产品服务不可用 | +| `589507` | 无导出权限(`group-batch:export`,未改) | + +```json +{ + "code": 589517, + "message": "导出数据量超过单次上限,请缩小筛选范围后重试", + "data": null, + "success": false +} +``` + +#### 业务边界 + +- **看到什么导什么**:同一组参数下,导出行 = 列表里已建团的行,顺序一致(未建团行本来就不导出)。 +- **不传排序**:按产品看 = 出发日升序;不按产品看 = 建团时间倒序(**以前一律出发日升序**)。 +- **按产品导出**时,班期范围、截止日、出发区间按**产品侧实时日期**筛选和排序,与列表一致;产品改期后不会再出现「列表里有、导出里没有」。 +- 2000 行上限在全部筛选之后判断。 + +### 2. 团期看板统计条 `GET /v3/admin/order/group-batch/summary` + +**VO**: `GroupBatchSummaryVO`(不变) + +#### 使用场景 + +看板顶部统计条(总数 + 七桶计数 + 被过滤条数),随列表的截止日、出发区间一起刷新。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|---|---|---|---|---|---| +| productId / month / keyword / scope | query | — | 否 | — | 原有 | +| deadlineFrom / deadlineTo | query | String | 否 | yyyy-MM-dd,非法值忽略 | **新增** | +| departFrom / departTo | query | String | 否 | yyyy-MM-dd,非法值忽略 | **新增** | + +#### 出参 + +| 字段 | 类型 | 说明 | +|---|---|---| +| total | Integer | 命中团期数(与列表同条件、不筛团期状态和看板桶时的 total 相等) | +| buckets | Map | 七桶计数(之和 = total)+ 旧八桶别名键(#8271 过渡期兼容,不变) | +| subOrderCount / minToForm / maxRooms | Integer | 不变 | +| effectiveScope | String | 生效的班期范围,不变 | +| filteredOutCount | Integer | 因班期范围被过滤掉的条数,**现在带上截止日与出发区间计算** | + +#### 请求示例 + +```http +GET /v3/admin/order/group-batch/summary?productId=2056944943066132481&scope=ONGOING&departFrom=2026-05-01&departTo=2026-12-31 +Authorization: Bearer +``` + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "data": { + "total": 22, + "buckets": { + "RECRUIT": 4, "CONFIGURE": 7, "CONFIRM": 0, "TRIP": 0, "REVIEW": 7, "SETTLE": 1, "DISBANDED": 3, + "FORMED": 7, "PENDING_TRIP": 0, "TRAVELLING": 0, "TRIP_FINISHED": 0, "AUDITING": 7, "CHECKED": 1 + }, + "subOrderCount": 42, + "minToForm": null, + "maxRooms": null, + "effectiveScope": "ONGOING", + "filteredOutCount": 8 + }, + "success": true +} +``` + +#### 空数据 / 降级响应 + +无命中时 `total=0`、各桶为 0: + +```json +{ + "code": 200, + "message": "成功", + "data": { "total": 0, "buckets": { "RECRUIT": 0 }, "filteredOutCount": 0 }, + "success": true +} +``` + +#### 错误响应 + +判权未改(`group-batch:list`)。 + +```json +{ + "code": 589507, + "message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)", + "data": null, + "success": false +} +``` + +#### 业务边界 + +- 不接收 `batchStatus`、`opsStage`、`sortBy`、`sortOrder`,传了也忽略(统计条要给出全部桶的计数)。 +- 不传新参数时结果与改前一致。 + +### 3. 团期分页列表 `GET /v3/admin/order/group-batch` + +**VO**: `GroupBatchListReqVO` → `PageResult`(均不变) + +#### 使用场景 + +看板列表。本次只改报名截止日非法值的处理。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|---|---|---|---|---|---| +| deadlineFrom / deadlineTo | query | String | 否 | yyyy-MM-dd | **非法值改为忽略该条件**(以前 500) | +| 其余 | query | — | 否 | — | 不变 | + +#### 出参 + +| 字段 | 类型 | 说明 | +|---|---|---| +| (全部) | — | 不变 | + +#### 请求示例 + +```http +GET /v3/admin/order/group-batch?deadlineFrom=bad-date&pageNo=1&pageSize=20 +Authorization: Bearer +``` + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "data": { "records": [], "total": 298, "page": 1, "pageSize": 20 }, + "success": true +} +``` + +#### 空数据 / 降级响应 + +不变。 + +```json +{ + "code": 200, + "message": "成功", + "data": { "records": [], "total": 0, "page": 1, "pageSize": 20 }, + "success": true +} +``` + +#### 错误响应 + +判权未改。 + +```json +{ + "code": 589507, + "message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)", + "data": null, + "success": false +} +``` + +#### 业务边界 + +- 非法截止日期等同不传该条件,服务端记 warn 日志。 + +--- + +## 四、契约约束与正确调用方式 + +- 前端把列表当前的筛选参数**同一份**传给统计条和导出: + - 导出:`productId / month / keyword / opsStage / scope / batchStatus / deadlineFrom / deadlineTo / departFrom / departTo / sortBy / sortOrder`; + - 统计条:`productId / month / keyword / scope / deadlineFrom / deadlineTo / departFrom / departTo`。 +- 统计条不要传团期状态和看板桶;传了会被忽略,但不要依赖这一点做业务判断。 + +--- + +## 五、数据库行为 + +- 零表结构变更、零迁移脚本;三个接口均只读(导出成功后照旧写导出留痕,逻辑未改)。 +- 三个接口的筛选条件由同一段代码生成,不按产品时下推数据库,按产品时日期类条件按产品侧实时日期在内存筛选。 + +--- + +## 六、边界行为 + +- 导出单次上限 2000 行不变,判断在全部筛选之后。 +- 按产品导出时,`month` 与 `keyword` 按订单侧快照匹配、CSV「日期 / 出团日」列打印订单侧快照:产品侧改过期或改过名的班期,这两处可能与列表显示不同。 + +--- + +## 六.6、修改前后对比 + +| 项 | 改前 | 改后 | +|---|---|---| +| 导出可用筛选 | productId / month / keyword / opsStage / scope | 另加 batchStatus / 截止日 / 出发区间 / 排序 | +| 导出默认顺序(不按产品) | 出发日升序 | 建团时间倒序(与列表一致) | +| 导出默认顺序(按产品) | 出发日升序(订单快照) | 出发日升序(产品侧实时) | +| 统计条可用筛选 | productId / month / keyword / scope | 另加截止日 / 出发区间 | +| 非法截止日期 | 列表、导出 500 | 三接口忽略该条件 | + +--- + +## 六.7、影响评估 + +- 前端:统计条与导出要补传参数才生效;不补传时统计条结果不变,导出仅「不按产品」时默认顺序变化。 +- 列表:只有非法截止日期的处理变化,合法请求不受影响。 + +--- + +## 七、不影响范围 + +- 导出 CSV 的列与格式、导出留痕、判权码。 +- 看板列表 `GET /v3/admin/order/group-batch/board`、产品选择器、团期详情。 + +--- + +## 八、测试环境已验证 + +部署 dev-v3 @ `7c69e8227`(主体)与 `dada3a8f4`(截止日宽容解析)后,经真实网关 `https://api.test.1814.love` 实测: + +| 用例 | 期望 | 实测 | +|---|---|---| +| 构建身份:统计条 departFrom=2099-01-01 | total 由 33 变 0 | 通过 | +| 按产品:scope=ALL 不传排序 / 团期状态+截止日+出发区间 / 3 排序字段 × 升降序 | 导出行集与顺序 = 列表已建团行 | 8 组全部一致(列表 34 条含 5 条未建团,导出 29 行) | +| 全局:出发区间不传排序 / 团期状态+截止日 / 3 排序字段 × 升降序 | 同上 | 8 组全部一致(169 / 165 / 260 行) | +| 产品侧改期(快照 12-10、实时 12-15) | 按实时日期命中、按快照不命中,列表与导出一致 | 出发区间与截止日各两组,通过 | +| 统计条 截止日+出发区间(按产品 / 全局,ALL / ONGOING) | total = 列表 total = 七桶之和 | 通过(21 / 23 / 229 / 260) | +| 统计条 filteredOutCount 非零场景 | = ALL − ONGOING | 8 / 15,通过 | +| 统计条多传团期状态 / 看板桶 / 排序 | 结果不变 | 通过 | +| 非法截止日期 `bad-date` / `2026-02-30` | 三接口 200,等同不传 | 通过(改前列表与导出 500) | +| 非法出发日期 / 非法排序 | 忽略 | 通过 | +| 不带 token | 401 | 通过 | + +TEST 全库团期 298 条,2000 行上限(589517)由单测覆盖。造数已清理(产品侧日期已改回、班期已取消)。 + +--- + +## 十、相关文档 + +- Issue #8413、PR #8417、PR #8432 +- 列表出团区间与排序:Issue #7770 +- 统计条被过滤条数:Issue #7939 + +--- + +## 关联 / 联系人 + +- 后端:jw +- 前端:mmg