新增团期调整满团名额同步产品域库存接口说明(#7178)
changelog-filename-gate / validate (push) Failing after 2s

修的是「调了不生效」:调名额此前只写订单域,而库存权威在产品域,
导致加名额放不出空位、减名额停不了售,但看板数字会变。

请求体不兼容变更:{maxParticipants,maxRooms} 两个绝对值 →
{capacityDelta,reason} 净增量且只调户数;新增阶段门(仅招募中、
资源准备中可调)与 group-batch:manage 权限校验;响应改为返回
调整前后值、已报名与余量。

后端已部署 TEST 并实测:加减名额驱动满团即停/放开继续招募、
低于已报名拒绝且零写入、物料准备起阶段门、已成团加名额仍保持成团。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
这个提交包含在:
jw
2026-09-06 16:21:40 +08:00
共同撰写人 Claude Opus 5
父节点 a7c44c0caa
当前提交 70c1f44650
@@ -0,0 +1,225 @@
---
schema: "hl-changelog/v2"
ticket: "7178"
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 并实测:加减名额驱动产品域满团即停/放开继续招募、低于已报名拒绝且零写入、物料准备起阶段门。请求体形状已变更(净增量),前端待接。"
updated_at: "2026-09-06"
base: "dev-v3"
---
# 团期调整满团名额:同步产品域库存 + 阶段门 + 净增量入参
> **影响范围**:管理后台「团期详情 → 整团总览 → 调整满团名额」弹窗。
> 当前状态:后端已部署 TEST 并实测;前端待接入(**请求体形状已变更**)。
## ⚠️ 关键变化
1. **修复「调了不生效」**。此前调名额只写订单域,而下单侧的剩余名额由**产品域**算出,
于是加名额放不出空位、减名额停不了售,但看板数字会变。现在调整会真正驱动售卖状态。
2. **请求体不兼容变更**:由 `{maxParticipants, maxRooms}` 两个绝对值,
改为 `{capacityDelta, reason}` —— **净增量、只调户数**。
3. **新增阶段门**:仅「招募中」与「资源准备中」可调,物料准备中及之后返回 589538。
4. **新增权限校验** `group-batch:manage`(此前该接口无任何权限校验)。
5. **响应由空改为返回结果对象**,直接给出弹窗三格所需数据。
## 一、背景
调整满团名额是给运营临时增减本期可报名户数用的。此前的实现只更新了团期侧的展示值,
没有同步到真正决定「还能不能报名」的那一侧,导致这个功能实际上不起作用——
**页面上数字变了,但客户仍按老上限被卡住**。本次修复把调整落到库存权威侧,
并按新库存重算班期是否已满额。
## 二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
| --- | --- | --- | --- | --- | --- |
| 1 | 调整团期满团名额 | PUT | `/v3/admin/order/group-batch/{groupBatchId}/capacity` | 修改接口 | 入参改为净增量并只调户数;新增阶段门与权限校验;同步产品域库存;响应改为结果对象 |
## 三、接口详情
### 1. 调整团期满团名额 `PUT /v3/admin/order/group-batch/{groupBatchId}/capacity`
**VO**: `AdjustCapacityReqVO`
#### 使用场景
管理员在「团期详情 → 整团总览」底部操作条点「调整满团名额」,
弹窗用 −/+ 步进器改满团户数,点「保存名额」时把**净变化量**提交给本接口。
典型用法:某期已满员但还有客户想报,加 2 个名额把空位放出来继续招募;
或临近出团减名额提前收口。**已成团的团期加名额后仍保持成团,不会退回招募中。**
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
| --- | --- | --- | --- | --- | --- |
| groupBatchId | path | Long | 是 | 正整数 | 团期 ID |
| capacityDelta | body | Integer | 是 | 不能为 0 | 满团名额净增量(户),可正可负。新满团户数 = 当前满团户数 + 该值 |
| reason | body | String | 否 | 最长 256 字 | 调整原因,填了写进团期操作记录。**不强制必填** |
#### 出参
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| batchId | String | 团期 ID |
| beforeMaxRooms | Integer | 调整前满团户数 |
| maxRooms | Integer | 调整后满团户数,0 表示不限 |
| capacityDelta | Integer | 本次净增量 |
| enrolledRooms | Integer | 已报名户数(户 = 订单 = 房) |
| remainRooms | Integer | 调整后余量 = max(0, maxRooms − enrolledRooms);满团户数为 0(不限)时为 null |
#### 请求示例
```json
{
"capacityDelta": 3,
"reason": "客户加订,放三个空位继续招募"
}
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": {
"batchId": "2096510069465088002",
"beforeMaxRooms": 2,
"maxRooms": 5,
"capacityDelta": 3,
"enrolledRooms": 1,
"remainRooms": 4
},
"traceId": null,
"success": true
}
```
#### 空数据 / 降级响应
调整后满团户数为 0 时表示**不限名额**,此时 `remainRooms` 返回 null,
前端不显示余量。这是正常语义而非异常。
本接口没有空列表形态;任何失败都以错误码返回,且**失败一律零写入**——
产品侧与团期侧都不会留下半改状态。
#### 错误响应
```json
{
"code": 589538,
"message": "物料准备开始后不可再调整满团名额(仅招募中、资源准备中可调)",
"data": null,
"traceId": null,
"success": false
}
```
#### 业务边界
- **仅「招募中」与「资源准备中」可调**;物料准备中及之后返回 589538。
- **调整后不得低于已报名户数**,否则返回 589509;新值也不得为负。
- **新值为 0 表示不限名额**,此时不受已报名数约束。
- `capacityDelta` 为 0 返回 589539(等于没调)。
- 需要 `group-batch:manage` 权限,无权限返回 589507。
- **调整不改团期状态**:已成团的仍保持成团,不会退回招募中。
- 产品侧同步失败时整笔失败并返回 589540,团期侧零写入。
## 四、契约约束与正确调用方式
- **提交净增量,不要提交总数**。前端步进器算出的总数只用于展示,
提交时给差值即可。这样两人同时调也不会互相覆盖成对方的总数。
- **不再接收人数容量**。此前的 `maxParticipants` 字段已移除,人数容量由产品侧维护。
- **调整前先看能不能调**:团期进入物料准备后按钮应置灰,避免用户点了才报错。
- **弹窗三格数据**:调整前可从团期详情取「当前名额 / 已报名」;
调整成功后直接用本接口返回的 `maxRooms` / `enrolledRooms` / `remainRooms` 刷新,
不必再拉一次详情。
- **重试是安全的**。若前端超时后重试同一请求,服务端以团期侧当前值为基数重算,
不会把同一个增量叠加两次。
## 五、数据库行为
- 调整会**同时更新产品侧与团期侧的满团户数**,并按新库存重算班期是否已满额:
售罄时停止继续接单,库存恢复时重新开放报名。
- **先写产品侧,成功后才写团期侧**;产品侧写失败即整笔失败、团期侧不留任何痕迹。
- 每次成功调整写一条团期操作记录,内容形如
「满团名额:9→10(+1) · 理由:客户加订一间」,含操作人与时间;未填理由时省略后半段。
- 拒绝的三种情况(阶段不允许 / 低于已报名 / 增量为 0)**均不产生任何写入**。
- 历史上两侧数值已经不一致的团期,本次不做批量订正;下一次调整会自动把两侧拉齐。
## 六、边界行为
- 已成团的团期加名额后**仍保持成团**,只是把空位放出来继续招募,满团即停。
- 减名额减到正好等于已报名户数是允许的(余量为 0),再减一户即被拒绝。
- 满团户数为 0 表示不限,此时无论已报名多少都不触发下限校验。
- 团期未绑定产品班期时返回 589540(无处可写,不静默放过)。
## 六.6、修改前后对比
| 项 | 修改前 | 修改后 |
| --- | --- | --- |
| 是否真正影响售卖 | ❌ 只改团期侧展示值,**加名额放不出空位、减名额停不了售** | ✅ 同步到库存权威侧,满团即停 / 放开继续招募都生效 |
| 请求体 | `{maxParticipants, maxRooms}` 两个绝对值,均必填 | `{capacityDelta, reason}`,净增量、只调户数 |
| 响应 | 空(调完要再拉一次详情) | 返回调整前后值、已报名与余量 |
| 阶段限制 | **无**,任何状态都能调 | 仅招募中与资源准备中,其余 589538 |
| 权限 | **无任何校验** | `group-batch:manage` |
| 调整原因 | 无此入参 | `reason` 选填,写进操作记录 |
| 操作记录 | 只有「最大人数:20→24,最大房间:8→9」 | 「满团名额:9→10(+1) · 理由:…」 |
## 六.7、影响评估
- **请求体不兼容**:字段整体更换。上线前已核对管理后台前端仓库,
**没有任何页面在调用本接口**(原型侧本就是提交净增量,只是此前只在前端本地累加),
因此未设兼容期。若有未知调用方,需同步改造。
- **阶段门是行为变更**:此前任何状态都能调,现在物料准备后会被拒。
这是有意收紧——那之后房车已按户数配好,改名额会让资源计划失真。
- **权限是行为变更**:`group-batch:manage` 已随上一单建好并授予管理员与超级管理员,
本次直接复用,无需额外配置。
- **资源准备中调整的已知风险**:该阶段房务/车务可能已按当前户数派单订房订车,
此时加减名额**不会回头改动已派资源**,需人工复核资源计划。
- 新增字段与新响应均为增量,不改动任何既有字段的类型与含义。
## 七、不影响范围
- 下单与库存扣减的判定口径不变,仍按满团户数与人数上限;本次只是让调整真正作用到它。
- 人数容量不受影响,仍由产品侧维护。
- 团期状态机不变:调整不推进也不回退任何状态。
- 成团、取消成团、流团三个动作本次未改动。
- 小程序端不受影响,改动全部在管理后台侧。
## 八、测试环境已验证
TEST 环境已部署产品服务与订单服务并实测通过:
- 加名额 +1:产品侧满团户数随之变化,团期侧同步,返回三格数据。
- 减名额 −2:两侧同步。
- 减到正好等于已报名户数:班期转为**已满额、停止接单**。
- 再加 1 户:班期**恢复报名中**,空位重新放出。
- 减到低于已报名户数:拒绝且**零写入**(产品侧数值未变)。
- 增量为 0:拒绝。
- 物料准备中及之后调整:拒绝且零写入。
- **已成团(资源准备中)加名额:成功,班期继续招募,团期仍保持成团不回退**。
## 十、相关文档
- 工单:HL#7178
- 合并:HL PR#7181(已合入 dev-v3)
- 关联工单:HL#7158(团期手动成团),本单复用其建立的 `group-batch:manage` 权限码;
弹窗顶部「满 6 户成团 · 满 9 户满团」的成团标准数据亦由该单交付
## 关联 / 联系人
- 后端:jw
- 前端待接:弹窗步进器改为提交净增量、接收新的响应结构刷新三格;
团期进入物料准备后按钮置灰;未达标提示与「不得低于已报名数」的前端拦截保持不变