--- schema: "hl-changelog/v2" ticket: "7158" title: "团期手动成团门槛与提前成团留痕" consumer: "admin" author: "jw(GIT)" change_type: "修改接口" backend_status: "deployed" gateway_status: "verified" frontend_status: "verified" frontend_owner: "mmg" frontend_ref: "2952ab90" target_release: "" verified_at: "2026-09-06" status_note: "后端已部署 TEST 并实测:成团带理由成功、重复成团 589537、不传 body 兼容路径均通过。前端待接:成团弹窗三格读 minToForm、产品排期表单加最低成团户数输入框。" updated_at: "2026-09-06" base: "dev-v3" --- # 团期手动成团:成团门槛 min_to_form 与提前成团留痕 > **影响范围**:管理后台「团期详情 → 整团总览 → 成团」弹窗,以及产品「Step4 班期」维护表单。 > 当前状态:后端已部署 TEST 并实测;前端待接入。 ## ⚠️ 关键变化 1. **成团接口新增可选请求体**。`POST .../group` 原来无请求体,现在可传 `formedNote`(提前成团理由)。 **不传 body 时行为与之前完全一致**,前端不改也不会坏。 2. **成团新增权限校验** `group-batch:manage`。此前该接口无任何权限校验。 3. **重复成团改为语义化错误码 589537**,此前返回笼统的 589501「团期状态不允许当前操作」。 4. **新增成团门槛字段 `minToForm`**(最低成团**户数**,户 = 订单 = 房)。 团期详情与产品班期两侧都新增该字段,供成团弹窗三格渲染。 ## 一、背景 招募中的团期需管理员手动点「成团」才进入成团状态,进入后才能开始需求审核 / 配置资源。 成团弹窗顶部要显示三格「已售 5/9 户 · 成团标准 满 6 户 · 距标准差 1 户」, 但此前**成团标准没有任何数据源**:团期侧的 `minGroupPeople` 是「人数」口径且全局无写入方恒为 null, 产品侧只有按人数的最低成团人数。本次新增按**户数**的 `minToForm` 补齐这条链路。 **门槛只提示不拦截**:未达标也可提前成团,这是业务要求。系统在任何情况下都不自动成团。 ## 二、变更接口清单 | # | 接口 | 方法 | 路径 | 变更类型 | 说明 | | --- | --- | --- | --- | --- | --- | | 1 | 团期手动成团 | POST | `/v3/admin/order/group-batch/{groupBatchId}/group` | 修改接口 | 新增可选请求体 formedNote;新增权限校验;重复成团改 589537 | | 2 | 团期详情 | GET | `/v3/admin/order/group-batch/{groupBatchId}` | 修改接口 | 出参新增 minToForm(成团门槛户数) | | 3 | 产品班期修改 | PUT | `/admin/product/item/{id}/schedule` | 修改接口 | 入参新增 minToForm(最低成团户数) | | 4 | 产品班期列表 | GET | `/admin/product/item/{id}/schedule/list` | 修改接口 | 出参新增 minToForm | ## 三、接口详情 ### 1. 团期手动成团 `POST /v3/admin/order/group-batch/{groupBatchId}/group` **VO**: `GroupBatchFormReqVO` #### 使用场景 管理员在「团期详情 → 整团总览」底部操作条点「成团」,弹窗确认后调用。 仅招募中(`RECRUITING`)的团期可成团;成团后团期进入资源准备中,才能开始需求审核与配置资源。 已售户数未达成团门槛时也可以成团(提前成团),此时建议在弹窗里填写理由。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | | --- | --- | --- | --- | --- | --- | | groupBatchId | path | Long | 是 | 正整数 | 团期 ID | | formedNote | body | String | 否 | 最长 256 字 | 提前成团理由。整个 body 可以不传;不传等同于不带理由,行为与本次改动前一致 | #### 出参 | 字段 | 类型 | 说明 | | --- | --- | --- | | code | Integer | 200 表示成团成功 | | message | String | 提示文案 | | data | Object | 固定为 null,本接口无返回体 | | success | Boolean | true 表示成功 | #### 请求示例 ```json { "formedNote": "客户催促,线下已谈妥另外 1 户" } ``` #### 响应示例 ```json { "code": 200, "message": "成功", "data": null, "traceId": null, "success": true } ``` #### 空数据 / 降级响应 本接口成功时 `data` 恒为 null,不存在空数据形态。 成团流水文案里的「已售 N/M 户」依赖实时聚合,聚合失败时降级为不带户数的文案, **不影响成团本身成功**,前端无需为此做特殊处理。 #### 错误响应 ```json { "code": 589537, "message": "团期已成团,不可重复成团", "data": null, "traceId": null, "success": false } ``` #### 业务边界 - 仅 `RECRUITING` 状态可成团;已成团(含之后各状态)返回 589537,已流团返回 589501。 - 重复成团**零副作用**:不写状态、不写流水、不触发任何下游动作。 - 未达成团门槛**不拦截**,照常成团,仅在成团流水里标注未达标与理由。 - 需要 `group-batch:manage` 权限,无权限返回 589507。 - 系统在任何情况下都不自动成团;达标只代表「允许成团」。 ### 2. 团期详情 `GET /v3/admin/order/group-batch/{groupBatchId}` **VO**: `GroupBatchDetailRespVO` #### 使用场景 打开团期详情页时调用。本次新增的 `minToForm` 与既有 `enrolledRooms` / `maxRooms` 一起,足以渲染成团弹窗顶部三格,前端不需要再调其他接口。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | | --- | --- | --- | --- | --- | --- | | groupBatchId | path | Long | 是 | 正整数 | 团期 ID | #### 出参 | 字段 | 类型 | 说明 | | --- | --- | --- | | minToForm | Integer | **本次新增**。最低成团户数,户 = 订单 = 房。null 或 0 表示未设门槛 | | enrolledRooms | Integer | 已售户数(成团弹窗第一格分子) | | maxRooms | Integer | 满团户数(第一格分母),0 表示不限 | | remainRooms | Integer | 剩余户数,不限时为 null | | minGroupPeople | Integer | 历史字段,最低成团**人数**,全局无写入方恒为 null。成团门槛请改用 minToForm | | batchStatus | String | 团期状态码 | #### 请求示例 ```http GET /v3/admin/order/group-batch/2096412454643802114 Authorization: Bearer ``` #### 响应示例 ```json { "code": 200, "message": "成功", "data": { "groupBatchId": "2096412454643802114", "batchStatus": "RECRUITING", "enrolledRooms": 1, "maxRooms": 8, "remainRooms": 7, "minToForm": 6, "minGroupPeople": null }, "success": true } ``` #### 空数据 / 降级响应 `minToForm` 为 null 或 0 表示该团期未设成团门槛。 此时前端**只显示第一格「已售 X/Y 户」**,不显示「成团标准」与「距标准差」两格。 本次上线前建出的存量团期一律是这种情况,不做数据回填。 #### 错误响应 ```json { "code": 589500, "message": "团期不存在", "data": null, "success": false } ``` #### 业务边界 - 三格渲染口径:已售 `enrolledRooms`/`maxRooms` 户;成团标准 满 `minToForm` 户;距标准差 `max(0, minToForm - enrolledRooms)` 户。 - `minToForm` 是**建团时从产品班期快照**下来的,之后改产品班期不会回改已建出的团期。 - `minGroupPeople` 与 `minToForm` 是两个不同口径的字段,并存且互不换算,不要混用。 ### 3. 产品班期修改 `PUT /admin/product/item/{id}/schedule` **VO**: `ScheduleSaveReqVO` #### 使用场景 产品维护 Step4「班期」表单保存时调用。本次新增 `minToForm`, 即该班期的最低成团户数,与既有 `maxRooms`(满团户数)成对维护。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | | --- | --- | --- | --- | --- | --- | | id | path | Long | 是 | 正整数 | 产品 ID | | batchId | body | Long | 是 | 修改时必传 | 班期 ID | | minToForm | body | Integer | 否 | 非负整数 | **本次新增**。最低成团户数,空或 0 表示不限 | | maxRooms | body | Integer | 否 | 非负整数 | 满团户数,0 表示不限 | #### 出参 | 字段 | 类型 | 说明 | | --- | --- | --- | | data | Long | 班期 ID | | success | Boolean | 是否成功 | #### 请求示例 ```json { "batchId": 2052935476557328386, "departureDate": "2026-10-01", "adultPrice": 2925.00, "childPrice": 2425.00, "singleRoomDiff": 500.00, "maxRooms": 8, "minToForm": 6 } ``` #### 响应示例 ```json { "code": 200, "message": "成功", "data": 2052935476557328386, "success": true } ``` #### 空数据 / 降级响应 `minToForm` 不传时服务端兜底为 0(不限),与 `maxRooms` / `maxParticipants` 的兜底口径一致。 存量班期在本次 DDL 后取默认值 0,不做回填。 #### 错误响应 ```json { "code": 403, "message": "无操作权限", "data": null, "success": false } ``` #### 业务边界 - `minToForm` **只服务团期运营台的成团弹窗与成团留痕**,不参与任何下单与库存判定。 - 库存与满团判定仍然只看 `maxRooms` / `maxParticipants`,本次不受影响。 - 与既有的按人数的最低成团人数字段并存、互不覆盖、互不换算。 - 改这里**不会**回改已经建出的团期,团期侧是建团时的快照。 ### 4. 产品班期列表 `GET /admin/product/item/{id}/schedule/list` **VO**: `ScheduleRespVO` #### 使用场景 产品维护 Step4「班期」列表回显,以及班期编辑弹窗打开时取当前值。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | | --- | --- | --- | --- | --- | --- | | id | path | Long | 是 | 正整数 | 产品 ID | #### 出参 | 字段 | 类型 | 说明 | | --- | --- | --- | | minToForm | Integer | **本次新增**。最低成团户数,0 表示不限 | | maxRooms | Integer | 满团户数,0 表示不限 | | batchId | Long | 班期 ID | | departureDate | String | 出发日期 | #### 请求示例 ```http GET /admin/product/item/2044306857534636034/schedule/list Authorization: Bearer ``` #### 响应示例 ```json { "code": 200, "message": "成功", "data": [ { "batchId": 2052935476557328386, "batchNo": "Q202610012052935476548939777", "departureDate": "2026-10-01", "maxRooms": 8, "minToForm": 0 } ], "success": true } ``` #### 空数据 / 降级响应 产品下无班期时 `data` 为空数组。 存量班期的 `minToForm` 一律回显 0,表示尚未设置成团门槛,属正常值而非异常。 #### 错误响应 ```json { "code": 404, "message": "产品不存在", "data": null, "success": false } ``` #### 业务边界 - 列表回显的 `minToForm` 是产品侧权威值;团期侧显示的是建团时的快照,两者可能不同,属预期。 - 该字段不影响列表排序与筛选。 ## 四、契约约束与正确调用方式 - **成团请求体整体可选**。前端可以继续用无 body 的旧调用,也可以传 `{"formedNote": "..."}`。 建议:已售户数未达 `minToForm` 时在弹窗里出现理由输入框并把值传上来。 - **理由本期不强制必填**。服务端不会因为没填理由而拒绝成团,前端可自行决定是否做前端必填。 - **成团弹窗三格全部来自团期详情接口**,不需要额外接口。`minToForm` 为 null 或 0 时只显示第一格。 - **同一份 `ScheduleSaveReqVO` 也被新建班期 `POST /admin/product/item/{id}/schedule` 使用**, 该接口同样接受 `minToForm`,语义与修改班期完全一致;批量创建 `POST /admin/product/item/{id}/schedule/batch-create` 使用 `ScheduleBatchCreateReqVO`, 同样新增了 `minToForm` 并逐个透传给每个新建班期。这三个入口共用一套口径,前端按同一字段接入即可。 - **权限**:成团需要 `group-batch:manage`。该权限已随本次发布授予 `ADMIN` 与 `SUPER_ADMIN`。 若线上还有其他角色需要点成团,需要单独补授,否则会被拦为 589507。 ## 五、数据库行为 - 成团成功时推进团期状态,并写入一条**成团操作流水**,内容形如 「手动成团 · 已售 1/8 户 · 未达标(满6户) · 理由:客户催促」,含操作人与时间。 未设门槛时省略达标判定段,未填理由时省略理由段。 - 重复成团、无权限、团期不存在三种情况**均不产生任何写入**。 - 成团门槛在产品侧维护、在建团时快照到团期侧,之后两侧独立,不做双向同步。 - 存量数据不回填:本次上线前已存在的班期与团期,成团门槛一律为默认值(0 或空)。 ## 六、边界行为 - 未达成团门槛照常可以成团,门槛只影响提示与流水文案。 - 系统在任何情况下都不自动成团,达标只代表「允许成团」。 - 成团后的既有下游行为不变:派生导游 / 摄影需求标志、免闸置位、尝试推进物料准备、驱动子订单进需求态并派发配房配车待办。 - 成团流水文案里的已售户数与团期详情同源(实时聚合),不读持久计数列,两处显示不会打架。 ## 六.6、修改前后对比 | 项 | 修改前 | 修改后 | | --- | --- | --- | | 成团请求体 | 无请求体 | 可选 body,含 `formedNote`;不传时行为不变 | | 成团权限 | **无任何权限校验** | 需要 `group-batch:manage`,无权限 589507 | | 重复成团 | 589501「团期状态不允许当前操作」 | 589537「团期已成团,不可重复成团」 | | 成团流水 | 固定文案「成团」 | 带已售 / 满团 / 达标情况 / 提前成团理由 | | 团期详情成团门槛 | 只有恒为 null 的 `minGroupPeople`(人数口径) | 新增 `minToForm`(户数口径),弹窗三格可渲染 | | 产品班期成团门槛 | 只有按人数的最低成团人数 | 新增按户数的 `minToForm`,可在班期表单维护 | ## 六.7、影响评估 - **对现有前端调用零破坏**:成团请求体可选,不传 body 的老调用行为与改动前完全一致,已在 TEST 实测确认。 - **权限是行为变更**:`group-batch:manage` 已随发布授予 `ADMIN` / `SUPER_ADMIN`; 若还有其他角色在点成团,上线后会被拦住,需要补授权限。这是本次唯一需要运维配合的点。 - **错误码语义变更**:重复成团由 589501 变为 589537。若前端对 589501 做过特殊处理,需要同步识别 589537。 - **新增字段均为增量**,不改动任何既有字段的类型与含义;`minGroupPeople` 保持原样不动。 - 团期成团的下游链路(子订单推进、待办派发)本次未做任何改动。 ## 七、不影响范围 - 取消成团、流团两个接口本次未改动,仍然没有权限校验,将另行统一处理。 - 下单、库存扣减、满团判定完全不受影响,仍只看 `maxRooms` / `maxParticipants`。 - 小程序端不受影响,本次改动全部在管理后台侧。 - 成团通知(公众号 / 短信)本期不做,弹窗上的通知勾选框暂无后端对应入参,成团后仍需人工通知客户。 ## 八、测试环境已验证 TEST 环境已部署 product-v2、user-service、order-v3 三个服务并实测通过: - 团期详情返回 `minToForm` / `maxRooms` / `enrolledRooms` / `remainRooms`,`minGroupPeople` 保持并存。 - 带 `formedNote` 成团成功,团期由招募中进入资源准备中。 - 重复成团返回 589537「团期已成团,不可重复成团」。 - 不传请求体再次调用同样走到业务校验而非参数错误,确认请求体确实可选。 - 产品班期列表返回 `minToForm`,存量班期回显 0。 未在测试环境覆盖的三项:成团流水文案(团期流水目前无对外查询接口,由单元测试保证)、 产品班期写入 `minToForm`(受产品模块数据权限限制未能实调)、 建团快照(需在设置门槛后新建团期订单才能观察)。 ## 十、相关文档 - 工单:HL#7158 - 合并:HL PR#7168(已合入 dev-v3) - 前置工单:HL#7104(成团驱动子订单流程推进),本次改成团入口,与其下游改动互不冲突 ## 关联 / 联系人 - 后端:jw - 前端待接两项:成团弹窗三格读取 `minToForm` 并在未达标时出现提前成团理由输入框; 产品 Step4 班期表单新增「最低成团户数」输入框。