新增团期手动成团门槛与提前成团留痕接口说明(#7158)
changelog-filename-gate / validate (push) Successful in 3s

成团接口新增可选请求体 formedNote、新增 group-batch:manage 权限校验、
重复成团改为语义化 589537;团期详情与产品班期两侧新增最低成团户数 minToForm。

后端已部署 TEST 并实测:带理由成团成功、重复成团 589537、
不传 body 兼容路径均通过;网关链路已验证。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
这个提交包含在:
jw
2026-09-06 14:56:28 +08:00
共同撰写人 Claude Opus 5
父节点 713b15a640
当前提交 62c52b8024
@@ -0,0 +1,424 @@
---
schema: "hl-changelog/v2"
ticket: "7158"
title: "团期手动成团门槛与提前成团留痕"
consumer: "admin"
author: "jw(GIT)"
change_type: "修改接口"
backend_status: "deployed"
gateway_status: "verified"
frontend_status: "pending"
frontend_owner: ""
frontend_ref: ""
target_release: ""
verified_at: ""
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 <token>
```
#### 响应示例
```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 <token>
```
#### 响应示例
```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 班期表单新增「最低成团户数」输入框。