diff --git a/changelogs-v2/2026-09/07_7245_团期看板班期名与不限容量口径修正-修改接口-管理后台.md b/changelogs-v2/2026-09/07_7245_团期看板班期名与不限容量口径修正-修改接口-管理后台.md new file mode 100644 index 00000000..fad09184 --- /dev/null +++ b/changelogs-v2/2026-09/07_7245_团期看板班期名与不限容量口径修正-修改接口-管理后台.md @@ -0,0 +1,389 @@ +--- +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 全部通过" +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 = 不限」