--- schema: "hl-changelog/v2" ticket: "7245" title: "团期看板 batchName 去空格 + 不限容量 remain 统一为 null + board 库存同源" 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 #7256 已合入 dev-v3(732889527);2026-09-07 测试环境网关实测 AC-1/2/4/4b/5/6 全部通过。前端 mmg 已实查为 not_required:无任一读取点用后端 remainRooms/remainParticipants 判满员(PeriodRow 用 enrolledRooms>=maxRooms 自算,maxRooms=0/null 不显满团,与 null=不限一致);remainParticipants 读取点全按 !=null;前端无 batchName.trim();board enrolledRooms 仅显示。结构零变化,显示自动改善,无需改码。" updated_at: "2026-09-07" base: "dev-v3" --- # 团期看板: 三处响应值口径修正(结构不变) > **服务**: hl-order-service-v3 | **PR**: #7256 | **Issue**: #7245 | **合并提交**: `732889527` > **影响范围**: 管理后台团期看板分页 / board / 详情三个读端点的响应值 --- ## ⚠️ 关键变化 **`remainRooms` 与 `remainParticipants` 为 `null` 表示「不限」,前端不要按 `=== 0` 判满员。** 改前:容量字段为 `0` 时这两个值返 `0`,而同一列表里未建团行返 `null`,两类行语义打架,运营看到「剩余 0 人」会误判满员。 改后:容量为 `null` 或 `0` 一律返 `null`;容量大于 0 时才返 `max(0, 容量 − 已用)`。 另有两处修正:班期名去除前后空格;board 未建团行的已用房数改与分页、产品侧同源。 **接口路径、入参、响应结构均未变**,前端无需改码。 --- ## 一、背景 1. 产品侧存量脏值(如 `" 没,那你"`,前导空格)经合并层实时透出成看板行标题。product 侧只在保存路径 trim、order 侧只在建团与刷新快照时 trim,**存量列不会自愈**。 2. `remainParticipants` 两路不一致:未建团行走 `normalizeRemainingSlots`(不限返 null),命中行走 `calcRemain`(max=0 返 0)。 3. board 未建团行的已用房数取订单侧计数器(**不含线下占位** `manual_order_count`),而合并分页与产品侧取 `occupiedRooms`(含占位)——**同一个班期两个剩余数**。实测容量 8 / 占位 3 / 线上 0 时,产品侧与分页回「剩 5」,board 回「剩 8」。 --- ## 二、变更接口清单 | # | 接口 | 方法 | 路径 | 变更类型 | 说明 | |---|------|------|------|----------|------| | 1 | 团期看板分页 | GET | `/v3/admin/order/group-batch` | **响应值修正** | `batchName` 去空格;不限容量 remain 返 null | | 2 | 团期合并看板 | GET | `/v3/admin/order/group-batch/board` | **响应值修正** | 同上,另:未建团行已用房数改与分页同源 | | 3 | 团期详情 | GET | `/v3/admin/order/group-batch/:groupBatchId` | **响应值修正** | 同 1 | --- ## 三、接口详情 ### 1. 团期看板分页 `GET /v3/admin/order/group-batch` **VO**: `GroupBatchPageItemRespVO` #### 使用场景 管理后台「团期订单」看板列表。传 `productId` 走合并基底(命中 / 未建团 / 孤儿三类行),不传走订单侧老路径。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | `productId` | Query | Long | ❌ | 字符串传参 | 有值走合并基底 | | `scope` | Query | String | ❌ | `ONGOING`/`FINISHED`/`ALL` | 缺省:有 productId 时 ONGOING,否则 ALL | | `opsStage` | Query | String | ❌ | 八桶之一 | 运营阶段筛选 | | `batchStatus` | Query | String | ❌ | 团期九态 | 状态筛选 | | `month` | Query | String | ❌ | `yyyy-MM` | 出发月 | | `keyword` | Query | String | ❌ | — | 编号或名称模糊 | | `pageNo` / `pageSize` | Query | Integer | ❌ | 默认 1 / 20,上限 100 | 分页 | #### 出参 `Result>` 本次仅三个字段取值变化,结构不变: | 字段 | 类型 | 说明 | |------|------|------| | `batchName` | String | 班期名,**已去除前后空白**;null 仍为 null | | `remainRooms` | Integer | 剩余房数。**`maxRooms` 为 null 或 0 时返 null(不限)**,否则 `max(0, maxRooms − enrolledRooms)` | | `remainParticipants` | Integer | 剩余人数。口径同上,基于 `maxParticipants` 与 `enrolledPeople` | #### 请求示例 ```http GET /v3/admin/order/group-batch?productId=2044306857534636034&scope=ALL&pageSize=50 ``` #### 响应示例 ```json { "code": 200, "message": "成功", "success": true, "data": { "total": 8, "records": [ { "productBatchId": "2052935476557328386", "batchName": "没,那你", "maxRooms": 80, "enrolledRooms": 55, "remainRooms": 25, "maxParticipants": 0, "remainParticipants": null } ] } } ``` #### 空数据 / 降级响应 无匹配班期时 `records` 为空数组、`total` 为 0,不报错。 #### 错误响应 无权限: ```json { "code": 589507, "message": "无权限操作该团期", "success": false, "data": null } ``` #### 业务边界 - 只读接口,不产生任何写入 - `remainRooms` / `remainParticipants` 为 **null 表示不限**,不是「剩 0」 - 命中行容量读订单侧快照,未建团行读产品侧实时值——改产品容量不会立刻影响命中行 - 行集、排序、分页与其余字段值均不变 --- ### 2. 团期合并看板 `GET /v3/admin/order/group-batch/board` **VO**: `GroupBatchBoardItemRespVO` #### 使用场景 按产品维度展示全部班期(含未建团班期)的合并看板。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | `productId` | Query | Long | ✅ | 字符串传参 | 产品 ID | | `scope` | Query | String | ❌ | 缺省 `ALL` | 范围筛选 | #### 出参 `Result>` | 字段 | 类型 | 说明 | |------|------|------| | `batchName` | String | 已去除前后空白 | | `remainRooms` / `remainParticipants` | Integer | 不限时返 null,口径同接口 1 | | `enrolledRooms` | Integer | **未建团行改取产品侧 `occupiedRooms`(含线下占位)**,与分页及产品侧同源;命中行与孤儿行仍为订单侧计数 | | `enrolledPeople` | Integer | 未建团行改取产品侧 `enrolledCount`,同上 | #### 请求示例 ```http GET /v3/admin/order/group-batch/board?productId=2044306857534636034 ``` #### 响应示例 ```json { "code": 200, "message": "成功", "success": true, "data": [ { "productBatchId": "2045498872326717443", "groupBatchId": null, "batchName": "冻干粉发短信给", "maxRooms": 11, "enrolledRooms": 9, "remainRooms": 2 } ] } ``` #### 空数据 / 降级响应 产品下无班期时返回空数组。 #### 错误响应 产品域不可用: ```json { "code": 589515, "message": "拉取产品信息失败", "success": false, "data": null } ``` #### 业务边界 - 只读接口 - **未建团行的已用量含线下占位**,与产品侧 `schedule/list` 剩余库存一致 - 命中行的权威源仍是订单侧计数器,不受产品侧占位影响 --- ### 3. 团期详情 `GET /v3/admin/order/group-batch/:groupBatchId` **VO**: `GroupBatchDetailRespVO` #### 使用场景 团期详情页头部信息。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | `groupBatchId` | Path | Long | ✅ | — | 团期主订单 ID(**不是** productBatchId) | #### 出参 `Result` | 字段 | 类型 | 说明 | |------|------|------| | `batchName` | String | 快照值,已去除前后空白 | | `remainRooms` / `remainParticipants` | Integer | 不限时返 null,口径同接口 1 | #### 请求示例 ```http GET /v3/admin/order/group-batch/2096779382436651009 ``` #### 响应示例 ```json { "code": 200, "message": "成功", "success": true, "data": { "batchName": "#7196复验班期", "maxRooms": 9, "remainRooms": 9, "maxParticipants": 0, "remainParticipants": null } } ``` #### 空数据 / 降级响应 无空数据形态;团期不存在返 589500。 #### 错误响应 ```json { "code": 589500, "message": "团期不存在", "success": false, "data": null } ``` #### 业务边界 - 只读接口 - 容量读订单侧快照,产品侧改容量需等下次该班期下单刷新才反映 --- ## 四、契约约束与正确调用方式 - **`remainRooms` / `remainParticipants` 为 null 一律解释为「不限」**,不要按 `=== 0` 或 `!= null` 判满员;判满员应看 `maxXxx > 0 && remainXxx === 0`。 - **`batchName` 已由服务端去空格**,前端不必再 trim;null 仍为 null(未转空串)。 - **board 与分页的剩余数现已同源**,两处显示不一致即为缺陷,可直接报。 - 命中行与未建团行的容量来源不同(订单侧快照 vs 产品侧实时),验证时不要混用样本。 --- ## 五、数据库行为 三个接口均为**只读**,不产生任何写入。本次**无表变更、无 Flyway、无 H2 schema 变更,不回填存量数据**。 产品侧 `group_tour_batch.batch_name` 的存量脏值**未回填**,由读端 trim 兜住。 --- ## 六、边界行为 - 容量为 null 或 0 → remain 返 null - 容量大于 0 且已用超额 → remain 返 0(不返负数) - `batchName` 为 null → 保持 null - `batchName` 为纯空白 → 转空串 - 未建团行含线下占位 → 已用量计入 - 无权限 → 589507;产品域不可用 → 589515;团期不存在 → 589500 --- ## 六.5、枚举 / 数据字典 本次无新增枚举。 --- ## 六.6、修改前后对比 | 项 | 变更前 | 变更后 | |---|---|---| | `batchName` | 原样透出,可带前后空格 | **trim 后**输出,null 仍 null | | 容量为 0 时 `remainRooms` | 返 `0`(前端显示「剩余 0」误判满员) | 返 **null**(不限) | | 容量为 0 时 `remainParticipants` | 命中行返 `0`、未建团行返 `null`(两路打架) | 三类行统一返 **null** | | board 未建团行 `enrolledRooms` | 订单侧计数,**不含线下占位** | 产品侧 `occupiedRooms`,**含线下占位** | | board 与分页剩余数 | 同班期可能不同(实测 8 vs 5) | **一致** | | 路径 / 入参 / 响应结构 | — | **均未变** | ## 六.7、影响评估 | 维度 | 评估 | |---|---| | 兼容性 | 结构零变化,仅三个字段取值修正;前端无需改码 | | 前端 | 行标题与「剩余」列显示自动改善;若曾用 `remainXxx === 0` 判满员,不限行不再误判 | | 数据 | 无 DDL、无迁移、无回填;纯读端派生 | | 性能 | 无额外查询,未建团行改用入参里已有的产品侧字段,零新增 IO | | 回滚 | 还原 Converter 即可,无数据侧残留 | | 风险 | 低。改动集中在单个静态 Converter,13 例单测 + 6 组网关用例覆盖 | --- ## 七、不影响范围 - **零影响**:三个接口的路径、入参、响应结构、行集、排序、分页 - **零影响**:导出 CSV(GB-ADM-008,读订单快照) - **零影响**:建团与刷新时的快照写入路径 - **零影响**:product-v2、`normalizeRemainingSlots`、Feign 契约的 `remainingSlots` MAX_VALUE 语义 - **零影响**:`GroupBatchMergedRowsService`(含 `matchesKeyword`,用 contains,带空格不影响搜索) --- ## 八、测试环境已验证 ✅ 2026-09-07 于测试环境网关实测,真实鉴权(管理端 admin),部署本单特性分支后验收再合入 dev-v3。 | # | 用例 | 结果 | |---|---|---| | AC-1 | 分页 `batchName` 无前后空白 | ✅ 8 行全通过;目标脏值行 `2052935476557328386` → `"没,那你"` | | AC-2 | 容量 0/null 的行 remain 均为 null | ✅ 违例 0(其中容量 0/null 的行 1 行构成有效样本) | | AC-4 | board 同样成立 | ✅ 8 行,带空白 0、违例 0 | | **AC-4b** | **board 与分页库存同源** | ✅ **8 行 `remainRooms` 全部一致**。样本:未建团行 `2045498872326717443` 已用 9 / 剩 2(含线下占位),改前 board 会回 `0/11` | | AC-5 | 详情端点 | ✅ `batchName` 无空白;同一行 `maxRooms=9→remainRooms=9`(有限容量正常算)、`maxParticipants=0→remainParticipants=null`(不限) | | AC-6 | 缺省 productId 老路径回归 | ✅ 22 行,容量 0/null 的 21 行 remain 均为 null,batchName 无空白 | **本地单测**:`GroupBatchConverterTest` 新增 13 例(trim 四组、max=0 三组、未建团行含线下占位取 `occupiedRooms`、命中行仍用订单侧计数);全量 **8728 例 0 failures**(余 7 个 error 为 Testcontainers 依赖 Docker,纯 dev-v3 同样失败)。 --- ## 九、相关历史 PR - #7256 本次变更 - 前序:#7188(batchLabel 与保存路径 trim)、#7189(合并基底引入,暴露两路 remain 不一致) --- ## 十、相关文档 - 团期需求文档:`docs/group/`(dev-v3 分支) --- ## 关联 / 联系人 - **Issue**: #7245 | **PR**: #7256(合并提交 `732889527`) - **服务**: hl-order-service-v3 - **后端**: jw | **前端**: 无需改码,仅需知悉「null = 不限」