--- schema: "hl-changelog/v2" ticket: "7188" title: "团期看板/列表/详情透出「第N期」班期序号 batchLabel" consumer: "admin" author: "wx(GIT)" change_type: "修改接口" backend_status: "deployed" gateway_status: "verified" frontend_status: "verified" frontend_owner: "mmg" frontend_ref: "2eb27845" target_release: "" verified_at: "2026-09-08" updated_at: "2026-09-08" base: "dev-v3" status_note: "前端已交付并验证(commit 2eb27845, 汇总工单项1):PeriodRow 行标题/BatchHero 详情头/detail toolbar 三处 batchLabel 数字字符串纯拼接为「第{batchLabel}期 {名称}」,null 兜底只显名称,禁算术/排序;新增 spec 锁三处拼接与 null 兜底。" --- # 团期模块:看板 / 列表 / 详情透出「第N期」班期序号 ## ⚠️ 关键变化 - 三个团期接口新增 / 修正字段 **`batchLabel`**(String,班期序号,如 `"7"`):product 侧按同产品全部班期**出发日期升序位置 = 下标 + 1** 实时重算(不信存储列,删班期后自愈;同日期同排序值按 batchId 兜底),经内部接口透传到 order-v3。 - **前端行标题改为「第{batchLabel}期 {batchName}」**(hl-ui `src/views/order-v2/batch/components/PeriodRow.vue:164-171` `title` computed 目前只显 `batchName`),`batchLabel` 为空时退回只显 `batchName`——存量团期在下一次该班期下单前分页/详情里是 null,必须兜底。 - 班期名口径:product 保存与 order 快照两侧 **trim**;新增/编辑班期名为空白 → 回退班期编号 `batchNo`;编辑不传 `batchName` → 保留旧名。现场「 没,那你」(前导空格)这类脏名下单后自愈。 - 分页项(GB-ADM-001)本单取快照值;#7189 合入后分页改以产品全班期为基底、`batchLabel` 取 product 实时值,**字段名不变**。 --- ## 一、背景 ### 现象 wx 2026-09-06 看测试服「团期订单」看板:行标题显示班期名「没,那你」,期望「第7期 没,那你」(该班期是产品「冻干粉发短信给」按出发日的第 7 期)。序号只存在于 product 管理端班期列表,Feign 契约与团期三个响应 VO 都没有,`order_group_batch.batch_label` 列从未被写入。 ### 调用链 1. hl-ui `src/stores/orderV2Batch.js:93-97` `getGroupBatchPage()` → `GET /v3/admin/order/group-batch`(GB-ADM-001)→ `records[].batchLabel`(快照) 2. 看板合并层 `GET /v3/admin/order/group-batch/board` → order-v3 Feign `GET /internal/product/group/{productId}/detail` → product `GroupTourBatchService.getBatchCalendarByProductId` 逐项 `batchLabel = 位置 + 1` → 命中/未建团行实时值、孤儿行快照 3. 团期详情 `GET /v3/admin/order/group-batch/{groupBatchId}`(GB-ADM-002)→ `batchLabel` 快照 4. 快照写入:GROUP 创单建团时 `GroupBatchService.buildGroupBatchDO` 从 `GET /internal/product/batch/{batchId}/info` 的 `batchLabel` 写 `batch_label`;此后每次该班期下单 `refreshSnapshot` 同步刷新序号 / 班期名 / 产品名 / 出发日 / 返团日 / 报名截止日六列 ### 地面真相(测试服 dev-v3 a5eacefd,2026-09-06 21:2x,产品 2044306857534636034,班期 2026-10-01 productBatchId=2052935476557328386,团期 2096412454643802114) | 接口 | 结果 | |---|---| | board | 7 行按出发日升序 `batchLabel` = "1"(06-04) "2"(07-03) "3"(07-10) "4"(07-17) "5"(07-24) "6"(07-31) "7"(10-01);06-04 期存储列是 "6" 却回 "1",10-01 期回 "7" ✓ 按位置实时重算 | | 分页 / 详情(刷新前) | `batchLabel` 字段存在、值 null(存量团期) | | 分页 / 详情(在该班期新下一单后,测完已取消) | 详情 `batchLabel="7"`、分页项 `batchLabel="7"`、`batchName="没,那你"`(前导空格已 trim) | --- ## 二、变更接口清单 | # | 接口 | 方法 | 路径 | 变更类型 | 说明 | |---|------|------|------|----------|------| | 1 | 团期看板分页(GB-ADM-001) | GET | `/v3/admin/order/group-batch` | 响应新增字段 | `records[].batchLabel`(快照;存量团期下单前为 null) | | 2 | 团期看板合并层列表 | GET | `/v3/admin/order/group-batch/board` | 响应新增字段 | 每项 `batchLabel`:命中/未建团行取 product 实时值,孤儿行取快照 | | 3 | 团期详情(GB-ADM-002) | GET | `/v3/admin/order/group-batch/{groupBatchId}` | 响应字段语义修正 | 既有字段 `batchLabel` 由恒 null 改为班期序号快照 | --- ## 三、接口详情 ### 1. 团期看板分页 `GET /v3/admin/order/group-batch` **VO**: `GroupBatchListReqVO → Result>` #### 使用场景 团期看板列表;行标题用 `batchLabel` + `batchName` 拼「第N期 名称」。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | productId | query | Long(字符串) | 否 | 雪花 ID | 按产品筛选 | | opsStage | query | String | 否 | RECRUIT / FORMED / PENDING_TRIP / TRAVELLING / TRIP_FINISHED / AUDITING / CHECKED / DISBANDED | 桶筛选(不变) | | month | query | String | 否 | yyyy-MM | 出发月份 | | keyword | query | String | 否 | 已转义 | 班期编号 / 名称模糊 | | batchStatus | query | String | 否 | 团期状态码 | 可选 | | deadlineFrom / deadlineTo | query | String | 否 | yyyy-MM-dd | 报名截止日区间 | | pageNo | query | Integer | 否 | 默认 1 | 页码 | | pageSize | query | Integer | 否 | 默认 20,最大 100 | 每页条数 | #### 出参 `Result>` | 字段 | 类型 | 说明 | |------|------|------| | data.records[] | Array | 团期行(分页容器 `records / total / page / pageSize`) | | data.records[].batchLabel | String | **新增**。班期序号快照(`order_group_batch.batch_label`):建团写入、每次该班期下单刷新;存量团期在下一次下单前为 null;#7189 起改取 product 实时值 | | data.records[].batchName | String | 班期名(快照,下单刷新时已 trim) | | data.records[].batchNo | String | 班期编号 | | 其余字段 | — | 不变(groupBatchId / productBatchId / productId / productName / batchStatus / batchStatusName / minGroupPeople / maxRooms / maxParticipants / enrolledPeople / enrolledRooms / remainRooms / remainParticipants / orderCount / enrollDeadline / departDate / endDate / receivableAmount / receivedAmount / chips) | #### 请求示例 ```http GET /v3/admin/order/group-batch?productId=2044306857534636034&pageNo=1&pageSize=50 HTTP/1.1 Host: api.test.1814.love:9443 Authorization: Bearer ``` #### 响应示例 在该班期下单刷新快照后实测(2026-09-06 21:2x,非相关字段略): ```json { "code": 200, "message": "成功", "data": { "records": [ { "groupBatchId": "2096412454643802114", "productBatchId": "2052935476557328386", "productId": "2044306857534636034", "batchNo": "Q202610012052935476548939777", "batchName": "没,那你", "batchLabel": "7", "batchStatus": "RESOURCE_PREPARING", "batchStatusName": "资源准备中", "departDate": "2026-10-01", "endDate": "2026-10-03" } ], "total": 1, "page": 1, "pageSize": 50 }, "success": true } ``` #### 空数据 / 降级响应 存量团期在下一次该班期下单前 `batchLabel` 为 null(不是缺字段),前端退回只显 `batchName`;无命中团期时 `records` 为空数组。 ```json { "code": 200, "message": "成功", "data": { "records": [ { "groupBatchId": "2096412454643802114", "batchName": "没,那你", "batchLabel": null } ], "total": 1, "page": 1, "pageSize": 50 }, "success": true } ``` #### 错误响应 ```json { "code": 589507, "message": "无操作权限(非团期管理员 / 非本定制师名下)", "data": null, "success": false } ``` HTTP 始终 200,按 `code` 判断。 #### 业务边界 - 权限 `group-batch:view` - `batchLabel` 是数字字符串(`"7"`),后端不拼「第N期」文案,前端拼 - 序号会随 product 侧增删班期漂移,快照只在下单时刷新;需要绝对实时以 board / #7189 后的分页为准 ### 2. 团期看板合并层列表 `GET /v3/admin/order/group-batch/board` **VO**: `Result>`(查询参数 productId,无请求 VO) #### 使用场景 以产品全部班期为基底的看板(命中 / 未建团 / 孤儿三类行);hl-ui 当前未调用,#7189 后分页也走同一合并基底。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | productId | query | Long(字符串) | 是 | 雪花 ID | 产品 ID | #### 出参 `Result>` | 字段 | 类型 | 说明 | |------|------|------| | data[].batchLabel | String | **新增**。命中行 / 未建团行 = product 实时序号;孤儿行(product 已删该班期)= 快照,可能 null | | data[].productBatchId / productId / batchNo / batchName | String | 不变 | | data[].departureDate / endDate / enrollmentDeadline | String(yyyy-MM-dd) | 不变(product 实时) | | data[].groupBatchId | String(Long) | 未建团行为 null(不变) | | 其余字段 | — | 不变(maxRooms / maxParticipants / enrolledRooms / enrolledPeople / remainRooms / remainParticipants / batchStatus / batchStatusLabel / hotelReady / vehicleReady / guideReady / photographerReady / needsGuide / needsPhotographer / orderCount / productBatchRemoved / contractSignedCount / insuranceInsuredCount) | #### 请求示例 ```http GET /v3/admin/order/group-batch/board?productId=2044306857534636034 HTTP/1.1 Host: api.test.1814.love:9443 Authorization: Bearer ``` #### 响应示例 实测(2026-09-06 21:2x,只列序号相关字段,共 7 行): ```json { "code": 200, "message": "成功", "data": [ { "productBatchId": "2045498872326717442", "departureDate": "2026-06-04", "batchName": "fadege", "batchLabel": "1", "groupBatchId": null }, { "productBatchId": "2045401225930690562", "departureDate": "2026-07-03", "batchLabel": "2", "groupBatchId": null }, { "productBatchId": "2045401225960050689", "departureDate": "2026-07-10", "batchLabel": "3", "groupBatchId": null }, { "productBatchId": "2045401225964244993", "departureDate": "2026-07-17", "batchLabel": "4", "groupBatchId": null }, { "productBatchId": "2045401225972633601", "departureDate": "2026-07-24", "batchLabel": "5", "groupBatchId": null }, { "productBatchId": "2045401225976827906", "departureDate": "2026-07-31", "batchLabel": "6", "groupBatchId": null }, { "productBatchId": "2052935476557328386", "departureDate": "2026-10-01", "batchName": " 没,那你", "batchLabel": "7", "groupBatchId": "2096412454643802114" } ], "success": true } ``` #### 空数据 / 降级响应 产品无班期时 `data` 为空数组;孤儿行 `batchLabel` 可能为 null。 ```json { "code": 200, "message": "成功", "data": [], "success": true } ``` #### 错误响应 ```json { "code": 589515, "message": "获取团期产品列表失败,请稍后重试", "data": null, "success": false } ``` 另:`589507` 无操作权限。 #### 业务边界 - 序号按 product 当前全部未删班期的出发日升序位置计算(同日期按 sortOrder、再按 batchId);软删的班期不占位 - 未建团行的 `batchName` 取 product 侧当前值(可能未 trim,如示例第 7 行前导空格),命中行分页/详情取快照(已 trim) ### 3. 团期详情 `GET /v3/admin/order/group-batch/{groupBatchId}` **VO**: `Result`(路径参数,无请求 VO) #### 使用场景 团期详情页头部;`batchLabel` 语义修正为班期序号快照。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | groupBatchId | path | Long(字符串) | 是 | 团期主订单 ID | 不是 productBatchId | #### 出参 `Result` | 字段 | 类型 | 说明 | |------|------|------| | data.batchLabel | String | **语义修正**:改前恒 null(列从未写入,文案「班期标签快照 example=主推」);改后 = 班期序号快照(建团写入、下单刷新),文案「班期序号(第N期)快照 example=7」 | | data.batchName | String | 班期名快照(下单刷新时已 trim) | | 其余字段 | — | 不变 | #### 请求示例 ```http GET /v3/admin/order/group-batch/2096412454643802114 HTTP/1.1 Host: api.test.1814.love:9443 Authorization: Bearer ``` #### 响应示例 刷新快照后实测(非相关字段略): ```json { "code": 200, "message": "成功", "data": { "groupBatchId": "2096412454643802114", "productBatchId": "2052935476557328386", "batchNo": "Q202610012052935476548939777", "batchName": "没,那你", "batchLabel": "7", "batchStatus": "RESOURCE_PREPARING", "departDate": "2026-10-01", "endDate": "2026-10-03" }, "success": true } ``` #### 空数据 / 降级响应 存量团期下单刷新前 `batchLabel` 为 null;前端退回只显 `batchName`。 ```json { "code": 200, "message": "成功", "data": { "groupBatchId": "2096412454643802114", "batchName": "没,那你", "batchLabel": null }, "success": true } ``` #### 错误响应 ```json { "code": 589500, "message": "团期不存在", "data": null, "success": false } ``` #### 业务边界 - 同 GB-ADM-001:数字字符串、快照语义、null 兜底 --- ## 四、契约约束与正确调用方式 > 三个接口均为只读 GET;本节写的是**前端消费 `batchLabel` 的规则**。 ### ✅ 正确 / ❌ 错误 payload 对照 | 场景 | payload / 处理 | |------|---------| | ✅ 行标题 | `row.batchLabel ? \`第${row.batchLabel}期 ${row.batchName}\` : row.batchName` | | ✅ 徽标 / 导出用序号 | 直接用 `batchLabel` 字符串,不要自己按行下标算 | | ❌ 把 `batchLabel` 当数字做算术或排序键 | 它是字符串,且随 product 增删班期漂移;排序用 `departDate` | | ❌ 不兜底 null | 存量团期分页/详情在下单刷新前为 null,会渲染成「第undefined期」 | ### 切换状态时的必要动作 无写接口。序号变化来自 product 侧班期增删/改期,看板刷新即可。 --- ## 五、数据库行为 无表变更、无 Flyway。`order_group_batch.batch_label VARCHAR(64)` 列早已存在,本单开始写入: | 时点 | 写入 | |------|------| | GROUP 首单建团 | `batch_label` = product `GET /internal/product/batch/{batchId}/info` 的 `batchLabel`;`batch_name` trim | | 该班期每次下单 | `refreshSnapshot` 非 null 且变化才更新:`product_name / batch_name / batch_label / depart_date / end_date / enroll_deadline` | product 侧 `group_tour_batch.batch_label` 存储列不再作为读源(脏值如 06-04 期存 "6" 不影响输出);`batch_name` 保存时 trim,空白回退 `batch_no`。 --- ## 六、边界行为 - 未登录 → 网关 401;无权限 → `589507` - 团期不存在 → `589500`;board 拉产品失败 → `589515` - product 定位不到该班期(理论不会)→ `batchLabel` null 并记 WARN,不报错 - 产品下只有一期 → `batchLabel="1"` - 下单时 product 回传日期串非法 → 刷新跳过该列并记 WARN,不影响下单 ## 六.5、枚举 / 数据字典 无新增枚举;`batchLabel` 为自由数字字符串。 ## 六.6、修改前后对比 ### 字段级对比 | 字段 | 修改前 | 修改后 | |------|--------|--------| | GB-ADM-001 `records[].batchLabel` | 无 | 新增(快照) | | board `data[].batchLabel` | 无 | 新增(实时 / 孤儿行快照) | | GB-ADM-002 `data.batchLabel` | 存在但恒 null | 班期序号快照 | | product `GET /internal/product/batch/{batchId}/info` `batchLabel` | 无 | 新增(内部接口,前端不直接调) | ### 行为级对比 | 场景 | 修改前 | 修改后 | |------|--------|--------| | 看板行标题 | 只有班期名 | 「第7期 没,那你」(前端拼) | | 班期名带前后空格 | 原样落库、原样显示 | 保存 / 快照两侧 trim | | 新增或编辑班期名为空白 | 新增回退 batchNo,编辑可能落 null | 两条路径都回退 `batchNo` | | 编辑不传 batchName | 保留旧名 | 保留旧名(不变) | ## 六.7、影响评估 - 向后兼容:只加字段 / 修正语义,老前端不改不报错 - 前端是否必须同步上线:否;但不改则标题仍无序号 - 数据:无表变更;存量 `batch_label` 不回填,随下单自愈 --- ## 七、不影响范围 - **仅影响**: 团期看板行标题、团期详情头部、board 合并层 - **零影响**: - 创单 / 报价接口契约(`POST /v3/admin/order` 入参出参不变) - product 管理端班期列表(已是同一序号口径) - 芯片、桶、状态机 - 小程序 --- ## 八、测试环境已验证 真实接口输出(测试服 api.test.1814.love,2026-09-06 21:2x,登录后切 ADMIN 角色): ``` GET /v3/admin/order/group-batch/board?productId=2044306857534636034 → 200, 7 行 batchLabel 1..7 按出发日升序,10-01 期 "7" ✓ GET /v3/admin/order/group-batch?productId=2044306857534636034&pageSize=50 → 200, 存量行 batchLabel 字段存在=null ✓ GET /v3/admin/order/group-batch/2096412454643802114 → 200, batchLabel 字段存在=null ✓ POST /v3/admin/order(该班期新下一单,测完取消) → 200, 触发 refreshSnapshot ✓ GET /v3/admin/order/group-batch/2096412454643802114 → batchLabel="7", batchName="没,那你" ✓ GET /v3/admin/order/group-batch?productId=2044306857534636034&pageSize=50 → 该行 batchLabel="7" ✓ ``` 验证产品: `productId=2044306857534636034`(冻干粉发短信给),班期 `productBatchId=2052935476557328386`(2026-10-01),团期 `groupBatchId=2096412454643802114`。单测:product `GroupTourBatchServiceTest` 3/0、`GroupTourBatchMapperTest` 2/0(ORDER BY 三键,变异验证)、`GroupBatchLabelResolverTest` 6/0、`InternalProductServiceTest` 7/0、`ProductPricingServiceTest` 97/0;order `GroupBatchServiceTest` 73/0、`GroupBatchConverterTest` 37/0、`BatchInfoVODeserializationTest` 4/0、ArchTest 41/0;合入后 dev-v3 HEAD 全量 product-v2 1550/0、order-v3 8726/0。部署:Deploy Panel 21:19-21:22 product-v2 与 order-v3 双实例滚动完成,分支 dev-v3。 --- ## 十、相关文档 - 关联 Issue: [wx/HL#7188](https://git.1814.love:8443/wx/HL/issues/7188) - 关联 PR: [wx/HL#7209](https://git.1814.love:8443/wx/HL/pulls/7209) - 同现场: #7189(看板以产品全班期为基底 + 班期范围筛选,分页 batchLabel 改实时值)、#7190(「出行完毕」状态与八桶)、#7204(导/摄芯片零指派口径) - 前端缺陷 changelog: `06_frontend_团期看板展开行子订单列表恒空-前端缺陷-管理后台.md` ## 关联 / 联系人 ### 链接 - **Issue**: [#7188](https://git.1814.love:8443/wx/HL/issues/7188) - **PR**: [#7209](https://git.1814.love:8443/wx/HL/pulls/7209) - **Merge commit**: [a5eacefd](https://git.1814.love:8443/wx/HL/commit/a5eacefdb270b2f783db95afc790c9d0eb65a8da) ### 联系人 - **后端负责人**: @wx