--- schema: "hl-changelog/v2" ticket: "7946" title: "团期转订单收紧为只允许招募中(未成团)团期之间转" consumer: "admin" author: "jw(GIT)" change_type: "修改接口" backend_status: "deployed" gateway_status: "verified" frontend_status: "verified" frontend_owner: "mmg" frontend_ref: "e932c18a6bb68ea39c697416007fd8d8a7138620" target_release: "" verified_at: "2026-09-18" status_note: "团期转订单 POST .../sub-order/{orderId}/transfer-in 与候选列表 GET .../transfer-candidates 的可转团期状态由「招募中 + 资源准备中」收紧为只允许招募中(未成团),源期与目标期都适用;同产品限制不变。入参、出参、路径均不变,589510 文案改为「须为招募中」。后端已合并 dev-v3(843e81712)并部署 TEST,网关实测 7/7 通过。前端待办(一行):团期详情「更多操作 → 转订单」入口白名单 canTransferOrder 由 ['RECRUITING','RESOURCE_PREPARING'] 改为 ['RECRUITING'],见第四节。[mmg 2026-09-18 交付] canTransferOrder 白名单已收紧为仅 RECRUITING,589510 透后端 message 不建字典;建详情页首个 spec(moreActionOptions:RECRUITING 含/RESOURCE_PREPARING 不含且调整满团名额仍在对照/MATERIAL_PREPARING 不含),定向 3/3+scoped checkpoint 全绿,ref 为业务仓 v2.1 commit。" 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`(不变) #### 使用场景 团期详情「更多操作 → 转订单」弹窗,选中候选订单后确认:把同一产品另一个团期的一户转到本团期。不退钱、订金跟人走、尾款按本团期价格重算。 #### 入参 本次入参**不变**。 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | 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 | 非阻断提示 | #### 请求示例 ```http POST /v3/admin/order/group-batch/2100866001561128962/sub-order/2100866000617414658/transfer-in Authorization: Bearer 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>`(不变) #### 使用场景 转订单弹窗的搜索下拉:列出可以转入本团期的订单。 #### 入参 本次入参**不变**。 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | 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 ``` #### 响应示例 (按测试服验收时的真实值整理,只列本单造数那一条;同产品已成团团期 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