docs(changelog): #7946 团期转订单收紧为只允许招募中团期之间转(修改接口)
changelog-filename-gate / validate (push) Failing after 2s

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
这个提交包含在:
jw
2026-09-18 17:11:44 +08:00
共同撰写人 Claude Opus 5
父节点 d7fbbdeb1d
当前提交 547035c39e
@@ -0,0 +1,369 @@
---
schema: "hl-changelog/v2"
ticket: "7946"
title: "团期转订单收紧为只允许招募中(未成团)团期之间转"
consumer: "admin"
author: "jw(GIT)"
change_type: "修改接口"
backend_status: "deployed"
gateway_status: "verified"
frontend_status: "pending"
frontend_owner: "mmg"
frontend_ref: ""
target_release: ""
verified_at: ""
status_note: "团期转订单 POST .../sub-order/{orderId}/transfer-in 与候选列表 GET .../transfer-candidates 的可转团期状态由「招募中 + 资源准备中」收紧为只允许招募中(未成团),源期与目标期都适用;同产品限制不变。入参、出参、路径均不变,589510 文案改为「须为招募中」。后端已合并 dev-v3(843e81712)并部署 TEST,网关实测 7/7 通过。前端待办(一行):团期详情「更多操作 → 转订单」入口白名单 canTransferOrder 由 ['RECRUITING','RESOURCE_PREPARING'] 改为 ['RECRUITING'],见第四节。"
updated_at: "2026-09-18"
base: "dev-v3"
---
# order-v3: 团期转订单收紧为只允许招募中(未成团)团期之间转
> **存放目录**: `changelogs-v2/{YYYY-MM}/`(管理后台,二期 order-v3)
>
> **服务**: hl-order-service-v3 (端口 8086)
> **PR**: #7948
> **Issue**: #7946
> **日期**: 2026-09-18
> **影响范围**: 团期详情「更多操作 → 转订单」弹窗
---
## ⚠️ 关键变化
1. **只有招募中(未成团)的团期之间才能转订单**:订单所在的原团期、要转入的本团期,两个都必须是招募中。
2. 已成团的团期(资源准备中及之后)**既不能转出,也不能转入**:候选列表里不再出现这些团期的订单;本团期已成团时候选列表直接为空。
3. 同一产品的限制不变;入参、出参、路径都不变。
4. **前端待办(一行)**:转订单入口的显示条件改为只在招募中显示,见第四节。
---
## 一、背景
转订单(#7095)原来允许在「招募中」和「资源准备中」两个状态之间转。资源准备中其实就是已成团(看板「已成团」这一栏 = 资源准备中 + 物料准备中),成团后该户已进入资源采购与配房,业务要求不再跨团期挪动。jw 2026-09-18 定案:只允许招募中。
---
## 二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|------|------|------|----------|------|
| 1 | 转订单(子订单跨期转入) | POST | `/v3/admin/order/group-batch/{groupBatchId}/sub-order/{orderId}/transfer-in` | 修改 | 源期、目标期都须为招募中,否则 589510 |
| 2 | 转订单候选子订单列表 | GET | `/v3/admin/order/group-batch/{groupBatchId}/transfer-candidates` | 修改 | 本期非招募中返回 `[]`;候选只来自同产品其他招募中团期 |
---
## 三、接口详情
### 1. 转订单(子订单跨期转入) `POST /v3/admin/order/group-batch/{groupBatchId}/sub-order/{orderId}/transfer-in`
**VO**: `Result<TransferSubOrderRespVO>`(不变)
#### 使用场景
团期详情「更多操作 → 转订单」弹窗,选中候选订单后确认:把同一产品另一个团期的一户转到本团期。不退钱、订金跟人走、尾款按本团期价格重算。
#### 入参
本次入参**不变**。
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| groupBatchId | Path | Long | ✅ | 转入期(本期)团期 ID | **须为招募中** |
| orderId | Path | Long | ✅ | 待转入的子订单 ID | 订单状态须为待支付 / 待出行 |
| fromGroupBatchId | Body | Long | ✅ | 源期团期 ID | **须为招募中**,且与本期同一产品 |
| reason | Body | String | 否 | ≤512 字 | 不传记「管理员手动转期」 |
| notify | Body | Boolean | 否 | 缺省 true | 是否通知客户 |
#### 出参
本次出参**不变**。
| 字段 | 类型 | 说明 |
|------|------|------|
| orderId | String | 子订单 ID |
| fromBatchNo | String | 源期期号 |
| toBatchNo | String | 目标期期号 |
| oldOrderAmount | Number | 转期前应收 |
| newOrderAmount | Number | 转期后应收(按目标期价格重算) |
| paidAmount | Number | 已付(转期不动钱) |
| newBalanceDue | Number | 转期后待收尾款 = 新应收 − 已付 |
| warnings | List<String> | 非阻断提示 |
#### 请求示例
```http
POST /v3/admin/order/group-batch/2100866001561128962/sub-order/2100866000617414658/transfer-in
Authorization: Bearer <token>
Content-Type: application/json
{ "fromGroupBatchId": "2100866000667746306", "reason": "#7946 验收", "notify": false }
```
#### 响应示例
(测试服真实响应,2026-09-18,两个团期都是招募中)
```json
{
"code": 200,
"message": "成功",
"data": {
"orderId": "2100866000617414658",
"fromBatchNo": "Q202705102100866000156069890",
"toBatchNo": "Q202705172100866000994930690",
"oldOrderAmount": 6000.0,
"newOrderAmount": 6000.0,
"paidAmount": 0.0,
"newBalanceDue": 6000.0,
"warnings": []
},
"success": true
}
```
#### 空数据 / 降级响应
写接口,无空数据形态。被拒时整笔回滚:订单归属、两个团期的报名人数、产品库存、转期流水都不变。
```json
{ "code": 589510, "message": "转订单时源期或目标期状态不满足条件(须为招募中)", "data": null, "success": false }
```
#### 错误响应
(测试服真实响应:源期已成团)
```json
{ "code": 589510, "message": "转订单时源期或目标期状态不满足条件(须为招募中)", "data": null, "success": false }
```
| code | 触发条件 |
|------|----------|
| 589510 | 源期或目标期不是招募中(**本次收紧**:资源准备中也会触发;文案改为「须为招募中」) |
| 589525 | 源期与目标期不是同一产品(不变) |
| 589524 | 源期与目标期是同一团期(不变) |
| 589512 | 子订单不属于源期(不变) |
| 589501 | 订单状态不可转(不变) |
| 589560 | 该户已在源期分到房(不变) |
#### 业务边界
- 状态判断在读订单之前完成(事务里只先对两个团期各取一次行锁);被拒时整笔回滚,净写入为零。
- 同产品、订单状态、容量、价格不低于已付等其余守卫全部不变。
- 已分房守卫(589560)保留:用于「取消成团回到招募中」但源期仍残留分房行的情形。
---
### 2. 转订单候选子订单列表 `GET /v3/admin/order/group-batch/{groupBatchId}/transfer-candidates`
**VO**: `Result<List<TransferCandidateVO>>`(不变)
#### 使用场景
转订单弹窗的搜索下拉:列出可以转入本团期的订单。
#### 入参
本次入参**不变**。
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| groupBatchId | Path | Long | ✅ | 转入期(本期)团期 ID | 非招募中时直接返回 `[]` |
| keyword | Query | String | 否 | 手机号须整串 | 订单号 / 客户名 / 期号 / 手机号 |
#### 出参
本次出参**不变**。
| 字段 | 类型 | 说明 |
|------|------|------|
| orderId | String | 子订单 ID |
| orderNo | String | 订单号 |
| customerName | String | 客户名 |
| peopleCount | Integer | 人数 |
| paidAmount | Number | 已付 |
| fromGroupBatchId | String | 所在源期团期 ID(**只会是招募中的团期**) |
| batchNo | String | 源期期号 |
| batchName | String | 源期名称 |
| departDate | String | 源期出发日 |
#### 请求示例
```http
GET /v3/admin/order/group-batch/2100866001561128962/transfer-candidates
Authorization: Bearer <token>
```
#### 响应示例
(按测试服验收时的真实值整理,只列本单造数那一条;同产品已成团团期 C 的订单已不在列表中)
```json
{
"code": 200,
"message": "成功",
"data": [
{
"orderId": "2100866000617414658",
"orderNo": "HL20260918163421351",
"customerName": "测试七九四六A",
"peopleCount": 2,
"paidAmount": 0.0,
"fromGroupBatchId": "2100866000667746306",
"batchNo": "Q202705102100866000156069890",
"batchName": "#7946-A",
"departDate": "2027-05-10"
}
],
"success": true
}
```
#### 空数据 / 降级响应
本团期已成团(资源准备中及之后)时,候选直接为空(测试服真实响应,改前同一团期返回 9 条):
```json
{ "code": 200, "message": "成功", "data": [], "success": true }
```
#### 错误响应
```json
{ "code": 589500, "message": "团期不存在", "data": null, "success": false }
```
| code | 触发条件 |
|------|----------|
| 589500 | 本期团期不存在(不变) |
| 401 | 未登录(网关拦截) |
#### 业务边界
- 本期非招募中 → `[]`,不查订单。
- 源期只取同产品、招募中、非本期的团期;已成团团期的订单不再出现。
- 关键词匹配规则不变。
---
## 四、契约约束与正确调用方式
### 前端需要改的一行
hl-ui `v2.1`:`src/views/order-v2/batch/detail/index.vue` 约第 657 行
```js
// 改前
const canTransferOrder = computed(() =>
['RECRUITING', 'RESOURCE_PREPARING'].includes(detail.value?.batchStatus))
// 改后
const canTransferOrder = computed(() => detail.value?.batchStatus === 'RECRUITING')
```
不改的后果:资源准备中的团期仍会显示「转订单」入口,点开后候选列表永远是空的,运营会以为「搜不到单」。不会转错数据(后端已拦)。
### ✅ 正确 / ❌ 错误 用法
| 场景 | 做法 |
|------|------|
| ✅ 决定是否显示转订单入口 | 只在 `batchStatus === 'RECRUITING'` 时显示 |
| ✅ 589510 的提示 | 直接展示后端 message「须为招募中」 |
| ❌ 在资源准备中团期显示转订单入口 | 候选恒为空,点了没用 |
| ❌ 前端自己拼「须为招募中/资源准备中」的提示文案 | 旧口径,已作废 |
---
## 五、数据库行为
- **无表结构变更,无 Flyway。**
- 转入成功时的写入与改前完全相同:订单改归属到目标团期并刷新出发 / 返程日期,两个团期报名人数一增一减,记一条转期记录,两个团期时间线各记一条。
- 本次只是把一部分请求提前拒掉:被拒时事务在状态守卫处整笔回滚,**零写入**(测试服已核对订单、两期报名人数、转期流水均未变化)。
---
## 六、边界行为
- 源期、目标期都招募中且同产品 → 成功。
- 源期资源准备中(已成团)→ 589510,零写入。
- 目标期资源准备中(已成团)→ 589510,零写入。
- 物料准备中及之后 → 589510(与改前相同)。
- 跨产品 → 589525(与改前相同,先于状态判断)。
- 候选列表:本期非招募中 → `[]`;本期招募中 → 只含同产品其他招募中团期的订单。
---
## 六.6、修改前后对比
### 字段级对比
| 项 | 改前 | 改后 |
|------|------|------|
| 入参 / 出参 / 路径 | — | 不变 |
| 589510 文案 | 须为招募中/资源筹备中 | 须为招募中 |
### 行为级对比
| 行为 | 改前 | 改后 |
|------|------|------|
| 可转团期状态 | 招募中、资源准备中 | 只有招募中 |
| 本期资源准备中的候选列表 | 同产品这两种状态其他团期的订单 | `[]` |
| 源期资源准备中的订单 | 出现在候选中,可转 | 不出现,转入被 589510 拒 |
## 六.7、影响评估
- **是否破坏向后兼容**: 结构上兼容(字段不变);行为上收紧,原来能在资源准备中团期之间转的现在会被拒。
- **前端是否必须同步上线**: 否,不改也不会出错;但需要前端改那一行,入口才不会出现在已成团的团期。
- **回滚**: 回滚 PR #7948 即可,无数据变更。
---
## 七、不影响范围
- **仅影响**: 上述两个接口的可转状态判断与 589510 文案。
- **零影响**: 退单户(GB-ADM-070)、成团 / 取消成团、调整满团名额、发起流团;转订单的金额计算、库存、配房联动、行程与用车重排;数据库结构。
---
## 八、测试环境已验证
被测版本:hl-order-service-v3 = dev-v3 `843e81712`(2026-09-18 17:09 部署,双实例滚动完成),网关实测 **7/7** 通过。
造数:同一产品建三个班期 A / B(招募中)、C(经成团接口推到资源准备中),各下一张真实订单。
```
改前(部署前,同一批数据)
候选 本期=C(资源准备中) → 9 条(含 A、B 的单)
候选 本期=B(招募中) → 含 C(资源准备中) 的单
改后
AC-4 候选 本期=C → [];候选 本期=B → 含 A 单、不含 C 单 ✓
AC-2 C(资源准备中)→B → 589510「须为招募中」,订单 / 两期报名人数 / 转期流水零变化 ✓
AC-3 A→C(资源准备中) → 589510,零变化 ✓
AC-5 跨产品源期→B → 589525,零变化 ✓
AC-1 A→B(均招募中) → 200;订单归属与出发日改到 B;报名人数 A 2→0、B 2→4;流水 1 条 ✓
```
本地:`GroupBatchTransferServiceTest` 50/0/0(新增 5 例)、`GroupBatchTransferDayConfirmConcurrencyIT` 3/0/0(真实 MySQL);全量 Half A 7955/0/0、Half B 3967/F1(`MapperBoundaryArchTest` dev-v3 既存,与本次无关)。
验收后已清理:三张订单取消,三个班期在产品侧取消(CANCELLED);订单侧团期行不随产品侧取消同步,停在原状态。
---
## 十、相关文档
- 关联 Issue: [wx/HL#7946](https://git.1814.love:8443/wx/HL/issues/7946)
- 关联 PR: [wx/HL#7948](https://git.1814.love:8443/wx/HL/pulls/7948)
- 接口文档:`docs/group/团期模块接口文档-v2.0.html` GB-ADM-071 小节与错误码表已同步
## 关联 / 联系人
### 链接
- **Issue**: [#7946](https://git.1814.love:8443/wx/HL/issues/7946)
- **PR**: [#7948](https://git.1814.love:8443/wx/HL/pulls/7948)
- **Merge commit**: [843e81712](https://git.1814.love:8443/wx/HL/commit/843e81712)
### 联系人
- **后端负责人**: @jw
- **前端负责人**: @mmg