--- schema: "hl-changelog/v2" ticket: "7143" title: "团期看板分页项/详情/看板 VO 补 productId(供新增子订单深链预填产品)" consumer: "admin" author: "wx(GIT)" change_type: "修改接口" backend_status: "deployed" gateway_status: "verified" frontend_status: "verified" frontend_owner: "mmg" frontend_ref: "5d0344ad" target_release: "" verified_at: "2026-09-06" status_note: "" updated_at: "2026-09-06" base: "dev-v3" --- # 团期模块:看板 VO 补 productId 三个团期看板读接口响应新增 `productId` 字段,供前端「新增子订单」深链到订单创建向导时预填产品和班期 ID,锁定出发日期。前端逻辑:**仅 GROUP 产品创单时才在 payload 中带 productBatchId**;非 GROUP 产品忽略 productBatchId。 ## ⚠️ 关键变化 - 新增 `productId` 是产品主键,用于深链向导 `/order-v2/new?productId={productId}&productBatchId={productBatchId}&departureDate={date}` 的预填参数。 - 向导跳转后,**仅 GROUP 产品**应将 productBatchId 放入订单创建 POST payload;其他产品类型忽略该参数。 - 前端缺陷单已发,说明旧建单向导漏传 productBatchId,新版本补全(见关联链接)。 --- ## 二、变更接口清单 | # | 接口 | 方法 | 路径 | 变更类型 | 说明 | |---|------|------|------|----------|------| | 1 | 团期分页列表 | GET | `/v3/admin/order/group-batch` | 响应新增字段 | data.list[] 各项新增 productId | | 2 | 团期详情 | GET | `/v3/admin/order/group-batch/{groupBatchId}` | 响应新增字段 | data 新增 productId | | 3 | 团期看板列表 | GET | `/v3/admin/order/group-batch/board?productId=` | 响应新增字段 | data[] 各项新增 productId | --- ## 三、接口详情 ### 1. 团期分页列表 `GET /v3/admin/order/group-batch` **VO**: `GroupBatchPageItemRespVO`(响应位置:`data.list[]`) #### 使用场景 加载团期分页列表时,每一行包含产品 ID,前端点击「新增子订单」时取该行的 productId / productBatchId / departureDate 拼接深链,跳转到订单创建向导。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | `productId` | Query | String | 否 | 正整数 ID | 按产品筛选 | | `batchStatus` | Query | String | 否 | 枚举值 | 按团期状态码筛选(RECRUITING/RESOURCE_PREPARING/...) | | `deadlineFrom` | Query | String | 否 | yyyy-MM-dd | 报名截止日起 | | `deadlineTo` | Query | String | 否 | yyyy-MM-dd | 报名截止日止 | | `opsStage` | Query | String | 否 | 枚举值 | 按运营阶段筛选(RECRUIT/FORMED/PENDING_TRIP/TRAVELLING/AUDITING/CHECKED/DISBANDED) | | `month` | Query | String | 否 | yyyy-MM | 按出发月份筛选 | | `keyword` | Query | String | 否 | - | 班期编号/班期名称模糊关键词 | | `pageNo` | Query | Integer | 是 | ≥1 | 页码,从 1 开始 | | `pageSize` | Query | Integer | 是 | ≤100 | 每页条数,默认 20 | #### 出参 `Result>` | 字段 | 类型 | 说明 | |------|------|------| | `data.list[].groupBatchId` | String | 团期聚合主键 | | `data.list[].productBatchId` | String | product 侧班期 ID | | `data.list[].productId` | String | **【新增】** 产品 ID,供深链预填 | | `data.list[].productName` | String | 产品名称 | | `data.list[].batchNo` | String | 班期编号 | | `data.list[].batchName` | String | 班期名称 | | `data.list[].batchStatus` | String | 团期状态码 | | `data.list[].batchStatusName` | String | 状态中文名 | | `data.list[].minGroupPeople` | Integer | 最低成团人数 | | `data.list[].maxRooms` | Integer | 房间容量 | | `data.list[].maxParticipants` | Integer | 人数容量 | | `data.list[].enrolledPeople` | Integer | 已报名人数 | | `data.list[].enrolledRooms` | Integer | 已用房间数 | | `data.list[].remainRooms` | Integer | 剩余房间数 | | `data.list[].remainParticipants` | Integer | 剩余人数 | | `data.list[].orderCount` | Integer | 子订单数 | | `data.list[].enrollDeadline` | String | 报名截止日 | | `data.list[].departDate` | String | 出发日期 | | `data.list[].endDate` | String | 结束日期 | | `data.list[].receivableAmount` | String | 整团应收合计 | | `data.list[].receivedAmount` | String | 整团已收合计 | | `data.list[].chips` | Object | 六芯片整团聚合态(hotel/vehicle/guide/photo/contract/insurance,各为字符串状态:全部完成为 `DONE`,存在未完成项为待办态;完整取值见六芯片文档 GB-ADM-090~095) | #### 请求示例 ```http GET /v3/admin/order/group-batch?pageNo=1&pageSize=20 Authorization: Bearer ``` 无请求体。 #### 响应示例 ```json { "code": 200, "message": "成功", "success": true, "data": { "total": 5, "list": [ { "groupBatchId": "2096412454643802114", "productBatchId": "2052935476557328386", "productId": "2044306857534636034", "productName": "冻干粉发短信给", "batchNo": "Q202610012052935476548939777", "batchName": "10月1日长白山亲子团", "batchStatus": "RECRUITING", "batchStatusName": "招募中", "minGroupPeople": 6, "maxRooms": 4, "maxParticipants": 10, "enrolledPeople": 4, "enrolledRooms": 2, "remainRooms": 2, "remainParticipants": 6, "orderCount": 2, "enrollDeadline": "2026-09-25", "departDate": "2026-10-01", "endDate": "2026-10-03", "receivableAmount": "48000.00", "receivedAmount": "36000.00", "chips": { "hotel": "DONE", "vehicle": "DONE", "guide": "DONE", "photo": "DONE", "contract": "DONE", "insurance": "DONE" } } ] } } ``` #### 空数据 / 降级响应 ```json { "code": 200, "message": "成功", "success": true, "data": { "total": 0, "list": [] } } ``` #### 错误响应 ```json { "code": 589507, "message": "无操作权限", "success": false, "data": null } ``` #### 业务边界 - 沿用团期列表权限校验(团期管理员/运营/定制师等);无权返回 589507。 - `productId` 与其他字段同时生效,始终非空(命中行/未命中行/孤儿行均返回)。 - 分页参数超界时返回空列表。 - 业务失败仍为 HTTP 200,需检查 code。 --- ### 2. 团期详情 `GET /v3/admin/order/group-batch/{groupBatchId}` **VO**: `GroupBatchDetailRespVO`(响应位置:`data`) #### 使用场景 打开团期详情页时,读取产品 ID,供「新增子订单」按钮拼接深链,跳转订单创建向导并预填产品及班期。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | `groupBatchId` | Path | String | 是 | 正整数 ID | 团期主键 | #### 出参 `Result` | 字段 | 类型 | 说明 | |------|------|------| | `data.groupBatchId` | String | 团期聚合主键 | | `data.productBatchId` | String | product 侧班期 ID | | `data.productId` | String | **【新增】** 产品 ID,供深链预填 | | `data.productName` | String | 产品名称 | | `data.batchNo` | String | 班期编号 | | `data.batchName` | String | 班期名称 | | `data.batchLabel` | String/null | 班期标签快照 | | `data.batchStatus` | String | 团期状态码 | | `data.batchStatusName` | String | 状态中文名 | | `data.minGroupPeople` | Integer | 最低成团人数 | | `data.maxRooms` | Integer | 房间容量 | | `data.maxParticipants` | Integer | 人数容量 | | `data.enrolledPeople` | Integer | 已报名人数 | | `data.enrolledRooms` | Integer | 已用房间数 | | `data.remainRooms` | Integer | 剩余房间数 | | `data.remainParticipants` | Integer | 剩余人数 | | `data.hotelReady` | Boolean | 配房完成标志 | | `data.vehicleReady` | Boolean | 配车完成标志 | | `data.guideReady` | Boolean | 导游完成标志 | | `data.photographerReady` | Boolean | 摄影完成标志 | | `data.materialConfirmed` | Boolean | 物资确认标志 | | `data.requirementConfirmed` | Boolean | 需求整体确认标志 | | `data.departDate` | String | 出发日期 | | `data.endDate` | String | 结束日期 | | `data.enrollDeadline` | String | 报名截止日 | | `data.totalReceivable` | String | 整团应收合计 | | `data.totalReceived` | String | 整团已收合计 | | `data.remark` | String/null | 备注 | #### 请求示例 ```http GET /v3/admin/order/group-batch/2096412454643802114 Authorization: Bearer ``` 无请求体。 #### 响应示例 ```json { "code": 200, "message": "成功", "success": true, "data": { "groupBatchId": "2096412454643802114", "productBatchId": "2052935476557328386", "productId": "2044306857534636034", "productName": "冻干粉发短信给", "batchNo": "Q202610012052935476548939777", "batchName": "10月1日长白山亲子团", "batchLabel": null, "batchStatus": "RESOURCE_PREPARING", "batchStatusName": "资源准备中", "minGroupPeople": 6, "maxRooms": 4, "maxParticipants": 10, "enrolledPeople": 10, "enrolledRooms": 4, "remainRooms": 0, "remainParticipants": 0, "hotelReady": false, "vehicleReady": false, "guideReady": false, "photographerReady": false, "materialConfirmed": false, "requirementConfirmed": false, "departDate": "2026-10-01", "endDate": "2026-10-03", "enrollDeadline": "2026-09-25", "totalReceivable": "48000.00", "totalReceived": "36000.00", "remark": null } } ``` #### 空数据 / 降级响应 详情接口不存在空数据响应。 #### 错误响应 ```json { "code": 589501, "message": "团期不存在", "success": false, "data": null } ``` 业务失败错误码沿用 GroupBatchErrorCode(权限/不存在等)。 #### 业务边界 - 沿用团期详情权限校验;无权返回 589507。 - `productId` 与其他字段同时返回,始终非空。 - 团期不存在返回业务码 589501(HTTP 200)。 --- ### 3. 团期看板列表 `GET /v3/admin/order/group-batch/board?productId=` **VO**: `GroupBatchBoardItemRespVO`(响应位置:`data[]`) #### 使用场景 打开团期看板时,按产品维度加载该产品下的所有班期(产品侧班期为基底,左连运营侧团期数据),每一行携带 productId,供「新增子订单」深链按行数据拼接预填参数。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | `productId` | Query | String | 是 | 正整数 ID | 按产品筛选(必填;缺参返回 400) | #### 出参 `Result>` | 字段 | 类型 | 说明 | |------|------|------| | `data[].productBatchId` | String | product 侧班期 ID | | `data[].productId` | String | **【新增】** 产品 ID(= 请求入参;命中/未命中/孤儿行均非空) | | `data[].batchNo` | String | 班期编号 | | `data[].batchName` | String | 班期名称 | | `data[].departureDate` | String | 出发日期 | | `data[].endDate` | String | 结束日期 | | `data[].enrollmentDeadline` | String | 报名截止日 | | `data[].maxRooms` | Integer | 房间容量 | | `data[].maxParticipants` | Integer | 人数容量 | | `data[].enrolledRooms` | Integer | 已报名房数 | | `data[].enrolledPeople` | Integer | 已报名人数 | | `data[].remainRooms` | Integer | 剩余房间数 | | `data[].remainParticipants` | Integer | 剩余人数 | | `data[].batchStatus` | String | 团期状态码 | | `data[].batchStatusLabel` | String | 状态中文名 | | `data[].hotelReady` | Boolean | 配房完成标志 | | `data[].vehicleReady` | Boolean | 配车完成标志 | | `data[].guideReady` | Boolean | 导游完成标志 | | `data[].photographerReady` | Boolean | 摄影完成标志 | | `data[].needsGuide` | Boolean | 是否需领队 | | `data[].needsPhotographer` | Boolean | 是否需摄影 | | `data[].orderCount` | Integer | 活跃子订单数 | | `data[].groupBatchId` | String/null | 团期聚合主键(未成团时为 null) | | `data[].productBatchRemoved` | Boolean | 是否孤儿行(product 侧已删除该班期) | | `data[].contractSignedCount` | Integer | 合同已签子订单数 | | `data[].insuranceInsuredCount` | Integer | 保险已出子订单数 | #### 请求示例 ```http GET /v3/admin/order/group-batch/board?productId=2044306857534636034 Authorization: Bearer ``` 无请求体。 #### 响应示例 ```json { "code": 200, "message": "成功", "success": true, "data": [ { "productBatchId": "2052935476557328386", "productId": "2044306857534636034", "batchNo": "Q202610012052935476548939777", "batchName": "10月1日长白山亲子团", "departureDate": "2026-10-01", "endDate": "2026-10-03", "enrollmentDeadline": "2026-09-25", "maxRooms": 4, "maxParticipants": 10, "enrolledRooms": 2, "enrolledPeople": 4, "remainRooms": 2, "remainParticipants": 6, "batchStatus": "RECRUITING", "batchStatusLabel": "招募中", "hotelReady": false, "vehicleReady": false, "guideReady": false, "photographerReady": false, "needsGuide": true, "needsPhotographer": false, "orderCount": 2, "groupBatchId": "2096412454643802114", "productBatchRemoved": false, "contractSignedCount": 1, "insuranceInsuredCount": 1 }, { "productBatchId": "2052935476557328387", "productId": "2044306857534636034", "batchNo": "Q202610022052935476548939778", "batchName": "10月2日长白山亲子团", "departureDate": "2026-10-02", "endDate": "2026-10-04", "enrollmentDeadline": "2026-09-26", "maxRooms": 4, "maxParticipants": 10, "enrolledRooms": 0, "enrolledPeople": 0, "remainRooms": 4, "remainParticipants": 10, "batchStatus": "RECRUITING", "batchStatusLabel": "招募中", "hotelReady": false, "vehicleReady": false, "guideReady": false, "photographerReady": false, "needsGuide": true, "needsPhotographer": false, "orderCount": 0, "groupBatchId": null, "productBatchRemoved": false, "contractSignedCount": 0, "insuranceInsuredCount": 0 } ] } ``` #### 空数据 / 降级响应 ```json { "code": 200, "message": "成功", "success": true, "data": [] } ``` #### 错误响应 缺参 productId: ```json { "code": 400, "message": "productId 参数缺失", "success": false, "data": null } ``` --- #### 业务边界 - `productId` 必填,缺参返回 HTTP 200 + code 400;不要用 GET 参数默认值。 - 返回结构按产品侧班期为基底(左连团期数据): - **命中行**(班期有对应团期):团期状态/容量/ready 取订单侧值;batchStatus ≠ RECRUITING - **未命中行**(班期无团期):batchStatus 固定 RECRUITING;groupBatchId = null;容量取产品侧 maxRooms/maxParticipants - **孤儿行**(团期但班期已删):productBatchRemoved = true;仅 orderCount > 0 时出现 - `productId` 在命中行、未命中行、孤儿行中均等于请求入参,始终非空。 - 表合并按 productBatchId 左连 order_group_batch;无团期记录时新增一行(未命中)。 --- ## 四、契约约束与正确调用方式 | 场景 | 正确做法 | |------|---------| | 新增子订单深链 | 按行数据拼接 `/order-v2/new?productId={productId}&productBatchId={productBatchId}&departureDate={departureDate}` | | 向导内 GROUP 产品创单 | 检查产品类型,仅 GROUP 类型在 POST payload 中带 productBatchId;其他类型忽略 | | 非 GROUP 产品创单 | 忽略 productBatchId 参数,后端根据 productId 与 departureDate 自行逻辑 | | 孤儿行处理 | 若 productBatchRemoved = true,需向用户提示"班期已下架,不可创建子订单"或禁用按钮 | | 未成团行处理 | groupBatchId = null 时无团期记录;前端可选择隐藏"团期信息"列或显示"待成团" | --- ## 五、数据库行为 | 班期状态 | productBatchId | groupBatchId | batchStatus | 数据来源 | |---------|----------------|-------------|------------|---------| | 命中(有团期) | 产品侧 | 订单侧 | 订单侧 | order_group_batch 存在 | | 未命中(无团期) | 产品侧 | null | RECRUITING | 无 order_group_batch 行 | | 孤儿(班期删)| 产品侧 | 订单侧 | 订单侧 | order_group_batch 存在但班期无 | 新增 productId 字段在 productBatchId 后(响应顺序一致)。 --- ## 六、边界行为 - 业务失败可能仍为 HTTP 200,必须同时检查 `code`、`success` 和 `message`。 - 看板接口 `productId` 缺参返回 400(HTTP 200);不要依赖前端参数校验。 - 分页/列表接口分页参数超界时返回空列表(无 5XX)。 - `productId` 在所有三个接口的所有行中均非空,无特殊情况返回 null。 - Long 型 ID 在 JSON 字符串化后,前端若需数值运算应保持字符串存储。 --- ## 六.6、修改前后对比 ### 字段级对比 | 字段 | 改前 | 改后 | |------|------|------| | `productId`(分页项) | 无此字段 | 新增;产品 ID | | `productId`(详情) | 无此字段 | 新增;产品 ID | | `productId`(看板项) | 无此字段 | 新增;产品 ID | ### 行为级对比 | 行为 | 改前 | 改后 | |------|------|------| | 新增子订单跳转 | 无法从响应直接拼深链参数 | 新增 productId,与 productBatchId/departureDate 配合直接拼链 | | 向导内产品预填 | 依赖约定俗成或外部 context | 直接从深链 query string 传入 | | GROUP 产品识别 | 向导内自行判别 | 向导根据产品类型自动识别是否需 productBatchId | --- ## 六.7、影响评估 - **是否破坏向后兼容**: 否。新增字段对旧客户端透明。 - **前端是否必须同步上线**: 否(从旧口径讲);但为完整支持团期看板「新增子订单」功能,前端应同步接入新增 productId 于深链拼接(仅一处改动)。 - **前端 workaround 清理点**: - 新增子订单按钮处,改用响应中的 productId 拼深链,而非硬编码产品 ID - 向导内创建 GROUP 订单时,检查产品类型再决定是否传 productBatchId(无需前端重构,只需补一个类型判断) --- ## 七、不影响范围 - **仅影响**: 管理后台团期看板的「新增子订单」功能入口 - **零影响**: - 团期创建、编辑、审批、资源配置等写接口 - 团期与订单间的关联关系和业务流程 - 产品侧班期相关接口和定义 --- ## 八、测试环境已验证 - **单测**: 79 条测试用例绿✓(新增 productId 相关的 UT 已覆盖命中/未命中/孤儿行三路径) - **ArchTest**: 45 条架构测试绿✓ - **测试服网关实测**: 单测 + CR 通过;测试服网关实测见管理者补充 实测产品: `productId=2044306857534636034`(冻干粉发短信给),班期 `2026-10-01`(productBatchId=2052935476557328386),团期主键 `groupBatchId=2096412454643802114`,团期名 `batchName=10月1日长白山亲子团`。 --- ## 九、相关历史 PR(功能演进) | PR | Issue | 说明 | 是否仍有效 | |----|-------|------|------------| | #7135 | - | GROUP 产品创单校验与重整(定义 productBatchId 入参) | ✅ 有效 | | #7142 | - | 订单详情/列表/创单响应透出判团字段(groupBatchId/productBatchId/groupOrder)| ✅ 有效 | | **本 PR #7157** | **#7143** | **团期看板 VO 补 productId(配合深链预填)** | ✅ 最新 | --- ## 十、相关文档 - 关联 Issue: [wx/HL#7143](https://git.1814.love:8443/wx/HL/issues/7143) - 关联 PR: [wx/HL#7157](https://git.1814.love:8443/wx/HL/pulls/7157) - 相关工单: #7142(订单判团字段)、#7135(GROUP 产品规范) - 前端缺陷: 新建订单向导漏传 productBatchId(另发前端 changelog) ## 关联 / 联系人 ### 链接 - **Issue**: [#7143](https://git.1814.love:8443/wx/HL/issues/7143) - **PR**: [#7157](https://git.1814.love:8443/wx/HL/pulls/7157) - **Merge commit**: [ce2809c15](https://git.1814.love:8443/wx/HL/commit/ce2809c15) ### 联系人 - **后端负责人**: @wx