diff --git a/changelogs-v2/2026-09/12_7513_团期转期同步回写归属两列-修改接口-管理后台.md b/changelogs-v2/2026-09/12_7513_团期转期同步回写归属两列-修改接口-管理后台.md new file mode 100644 index 00000000..45a64aca --- /dev/null +++ b/changelogs-v2/2026-09/12_7513_团期转期同步回写归属两列-修改接口-管理后台.md @@ -0,0 +1,302 @@ +--- +schema: "hl-changelog/v2" +ticket: "7513" +title: "团期转期同步回写归属两列(group_batch_id 不再停留源期)+ 在途退单转期守卫 589589" +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: "2026-09-12" +status_note: "转订单端点的请求/响应结构一行未动,前端无需改造即可继续运行。两处需要知道:①转期后订单详情的团期编号/名称会正确显示为【目标期】(此前错显源期,属修复非回归);②新增拒绝码 589589——该户有在途退单审核时转期会被拒,前端需按普通业务错误弹提示,文案后端已给全。" +updated_at: "2026-09-12" +base: "dev-v3" +--- + +# order-v3: 团期转期同步回写归属两列 + +**服务**: hl-order-service-v3 +**PR**: #7589 +**Issue**: #7513 + +--- + +## ⚠️ 关键变化 + +🟢 **请求与响应结构零改动。** `transfer-in` 的 path、body、`Result` 的字段名/类型/null 语义一行没动,前端**不改任何代码**即可继续工作。 + +🔴 **转期后订单的团期归属显示会变**:此前转期只改了 `order_main.product_batch_id`、没改 `order_main.group_batch_id`,导致同一订单两列指向两个不同的期。凡按 `group_batch_id` 取团期编号/名称的展示位(订单详情、聊天会话归属等)此前**显示的是源期**。本次修复后显示**目标期**。**这是纠错,不是回归**——如果 QA 对比历史截图发现团号变了,是本次修好了。 + +🔴 **新增拒绝码 `589589`**:该户存在**在途(PENDING)退单审核**时,转期会被直接拒绝。 + +``` +{"code": 589589, "message": "该户有退单审核在途,请先在原团期驳回或完成审核后再转期", "success": false} +``` + +**前端按普通业务错误弹 `message` 即可**,文案已含出口指引,无需自造提示。 +**为什么加**:此前「先提退单 → 被转走 → 审批通过」这条路能跑通,但跑出来是**错账**——按源期阶段算退款、时间线记源期,而人数扣减按 `product_batch_id` 扣到目标期,钱与账落在两个团,接口却返回成功。修好归属两列后,这种审批会在归属校验处抛 `589512`,那张申请**再也批不掉**;故把冲突提前到转期入口,给运营看得懂的提示与出口。 + +🔴 **历史脏数据本次不修**:本次只保证**新发生的转期**两列一致。修复上线**之前**已转过期的订单,两列仍分叉,需 **#7586** 单独处理(不能与本次同版发迁移,理由见该单)。 + +--- + +## 一、背景 + +转订单功能(#7095)合入于 2026-09-04 17:32,而 `order_main.group_batch_id` 这一「一跳直连」列由 #7083 于**同日 18:09** 才加上——相差 37 分钟,两个工单并行落地,此后无人回头补转期链路的回写。 + +后果比「两列不一致」更重:团期在团名册走的是「一跳列 ∪ 两跳列(且一跳为 NULL)」的双通道并集读,回退通道带 `group_batch_id IS NULL` 硬闸。转期户该列非空被排除,于是**在目标期的在团名册里整体消失**(房务需求、整团需求确认、团期核单、用车派单全看不到),同时仍留在源期;而人数计数走 `product_batch_id` 已正确搬走——「人数」与「成员名单」互相矛盾。 + +--- + +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|---|---|---|---|---| +| 1 | 转订单(子订单跨期转入) | POST | `/v3/admin/order/group-batch/:groupBatchId/sub-order/:orderId/transfer-in` | 修改接口 | 请求/响应结构不变;写库时同步回写归属第二列,并新增拒绝码 589589 | + +--- + +## 三、接口详情 + +### 1. 转订单(子订单跨期转入) `POST /v3/admin/order/group-batch/:groupBatchId/sub-order/:orderId/transfer-in` + +**VO**: `TransferSubOrderReqVO` → `Result` + +#### 使用场景 + +团期管理员把同一产品其他期的一个子订单转入本期:不退钱、订金跟人走、尾款按目标期价重算、不可跨产品转。path 上的 `:groupBatchId` 是**转入**期,源期在 body 的 `fromGroupBatchId`。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|---|---|---|---|---|---| +| `groupBatchId` | path | Long | 是 | 团期主订单 ID,非产品侧班期 ID | **转入**期 | +| `orderId` | path | Long | 是 | 须为源期下的在团子订单 | 待转入的子订单 | +| `fromGroupBatchId` | body | Long | 是 | 团期主订单 ID;须与订单当前排期一致 | 源期 | +| `reason` | body | String | 否 | ≤512 字 | 转期原因;不传记「管理员手动转期」 | +| `notify` | body | Boolean | 否 | — | 转入后是否通知客户;只落标志位,**不实发** | + +**本次未改动任何入参。** + +#### 出参 `Result` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `orderId` | String | 子订单 ID | +| `fromBatchNo` / `toBatchNo` | String | 源期 / 目标期期号 | +| `oldOrderAmount` / `newOrderAmount` | BigDecimal | 转期前 / 后应收 | +| `paidAmount` | BigDecimal | 已付(转期不动钱) | +| `newBalanceDue` | BigDecimal | 新待收尾款 | +| `warnings` | String[] | 非阻断提示 | + +**本次未改动任何出参字段。** + +#### 请求示例 + +```http +POST /v3/admin/order/group-batch/2096412454643802114/sub-order/2097498673159028737/transfer-in +Content-Type: application/json + +{ + "fromGroupBatchId": 2097498512387104770, + "reason": "客户改期,转到第 7 期", + "notify": false +} +``` + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "data": { + "orderId": "2097498673159028737", + "fromBatchNo": "Q202611202097498511120478209", + "toBatchNo": "Q202610012052935476548939777", + "oldOrderAmount": 1000.0, + "newOrderAmount": 3425.0, + "paidAmount": 0.0, + "newBalanceDue": 3425.0, + "warnings": [] + }, + "success": true +} +``` + +#### 空数据 / 降级响应 + +- 目标期班期天数取不到(产品域 Feign 失败)时**不阻断**:订单沿用原天数,`warnings` 追加「目标团期行程天数未取到,订单沿用原天数,请人工核对」,`code` 仍为 200。 +- 本接口无「空数据」形态:要么整体成功,要么抛业务错误码,不返回半截数据。 + +#### 错误响应 + +| code | message | 何时出现 | 本次变化 | +|---|---|---|---| +| **589589** | 该户有退单审核在途,请先在原团期驳回或完成审核后再转期 | 该子订单存在 PENDING 退单审批 | **🆕 本次新增** | +| 589510 | 团期状态不允许转期 | 源期或目标期不在「招募中 / 资源准备中」 | 不变 | +| 589512 | 该订单不属于本团期 | 源期与订单当前排期不一致 | 不变 | +| 589560 | 该户在源期已分房,不可转期({0} 条) | 源期已有生效分房行 | 不变 | +| 589527 | 获取目标团期报价失败,请稍后重试 | 目标期报价取不到或非正 | 不变 | +| 589528 | 目标团期价低于该户已付金额,需先走退款流程 | 目标期价 < 已付 | 不变 | + +本次新增的 `589589` 实际响应体(TEST 网关实测原样): + +```json +{ + "code": 589589, + "message": "该户有退单审核在途,请先在原团期驳回或完成审核后再转期", + "data": null, + "success": false +} +``` + +#### 业务边界 + +- **单单语义**:一次调用只转一个子订单,请求体内没有订单列表。 +- **幂等**:按 `orderId` 5 秒防重;同时按两期 ID 升序各下一次行栅栏,避免两期互转死锁。 +- 可转订单状态白名单:`PENDING_PAY` / `PENDING_DEPARTURE`。 +- 可转团期状态白名单:`RECRUITING` / `RESOURCE_PREPARING`。因此**已出账 / 已配房 / 已出行的团在守卫层就转不了**。 + +--- + +## 四、契约约束与正确调用方式 + +### ✅ 正确 / ❌ 错误 payload 对照 + +❌ **错误——把源期写在 path 上**(这是最容易搞反的一处): + +```http +POST /v3/admin/order/group-batch/{源期ID}/sub-order/{orderId}/transfer-in +{"fromGroupBatchId": 目标期ID} +``` + +✅ **正确——path 是转入期,body 是源期**: + +```http +POST /v3/admin/order/group-batch/{目标期ID}/sub-order/{orderId}/transfer-in +{"fromGroupBatchId": 源期ID} +``` + +两个 ID 都是**团期主订单 ID(`groupBatchId`)**,不是产品侧班期 ID(`productBatchId`)。一个接口不混用两套口径。 + +### 切换状态时的必要动作 + +前端收到 `589589` 时的正确引导:**先在源期把该户的退单申请驳回**(驳回路径不校验归属,任何时候都可用),再重新发起转期;或等该退单审批走完(走完即离团,也就不需要转期了)。**不要**引导用户去目标期重新提交退单——会被 `589529`「该户已有退单审核在途」拦。 + +--- + +## 五、数据库行为 + +| 表 | 列 | 变更 | +|---|---|---| +| `order_main` | `product_batch_id` | 转期时改写为目标期排期 ID(**行为不变**) | +| `order_main` | `group_batch_id` | **🆕 本次起同步改写为目标期团期 ID**;此前不写,停留源期 | + +两列在**同一条 UPDATE、同一个事务**内落库(转期方法整体 `@Transactional(rollbackFor = Exception.class)`),不存在「只更新了一列」的中间态。**未新增 SQL 语句、未加表、未加列、无 DDL。** + +`group_batch_transfer` 转期流水表行为不变,仍逐次留痕。 + +--- + +## 六、边界行为 + +- **多次转期**:每次转期两列同步搬到当次目标期,流水表逐条累积,不做合并。 +- **目标期 ID 不可能为空**:目标期对象按主键查出,查不到直接抛「团期不存在」,因此不存在「把归属清成 null」的路径。 +- **归属守卫仍比 `product_batch_id`**(不比 `group_batch_id`):这样 `group_batch_id` 尚未回填的历史订单仍能正常转期,不会被挡死。 +- **人数计数(enrolled)口径不变**:仍按显式 ±N 搬移,本次未改。 +- **在途退单守卫只拦 `PENDING`**:已通过 / 已驳回 / 已撤销的审批行不影响转期。 + +## 六.6、修改前后对比 + +### 字段级对比 + +| 数据位 | 修复前 | 修复后 | +|---|---|---| +| `order_main.product_batch_id` | 目标期排期 | 目标期排期(不变) | +| `order_main.group_batch_id` | **停留源期** ❌ | **目标期** ✅ | + +### 行为级对比 + +| 场景 | 修复前 | 修复后 | +|---|---|---| +| 目标期在团名册是否含该户 | **否**(被两跳回退的空值闸排除) | **是** | +| 源期在团名册是否仍含该户 | **是**(错误滞留) | **否** | +| 订单详情展示的团期编号 | **源期**(错) | **目标期** | +| 该户有在途退单时转期 | 允许,且后续审批产生错账且返回成功 | **拒绝 589589**,给出口 | +| 每日对账 `inconsistent` 指标 | 每转一次 +1,永不归零 | 新转期不再增加 | + +## 六.7、影响评估 + +| 受影响面 | 影响 | 需要前端动作 | +|---|---|---| +| `transfer-in` 请求/响应结构 | 无变化 | **无** | +| 转期后团期编号/名称展示 | 由源期改为目标期(纠错) | **无**,但 QA 对比历史截图时需知情 | +| 转期错误码集合 | 新增 589589 | **有**:按普通业务错误弹 `message` | +| 房务需求 / 整团需求 / 团期核单 / 用车派单的在团名册 | 转期户从此正确出现在目标期 | 无(后端口径修正) | + +--- + +## 七、不影响范围 + +- **不影响**任何接口的请求参数、响应字段名/类型/null 语义。 +- **不影响**判团读写法:本次一行未动各处「这张订单属于哪个团」的判据写法,只保证两列数据一致。 +- **不影响**退款金额计算、退改政策、订金/尾款口径。 +- **不影响**人数计数(enrolled)的搬移口径。 +- **不影响**小程序端:本次改动不涉及 mp 链路。 +- **不影响**历史已转期订单的存量数据(见 #7586)。 +- **无 DDL、无新增表/列、无网关路由变更、无 Feign 契约变更、无 MQ 变更。** + +--- + +## 八、测试环境已验证 + +部署:`hl-order-service-v3` ← 分支 `fix/7513-transfer-groupbatchid`,双实例滚动,2026-09-12 17:01 在线。取证经真实网关 `api.test.1814.love` + TEST 库直查。 + +| # | 用例 | 结果 | +|---|---|---| +| 1 | 真实转期 `transfer-in` | `code 200`,金额 1000.0 → 3425.0 | +| 2 | 转期后两列归属 | `group_batch_id` 与 `product_batch_id` **同指目标期**(SQL 判定 CONSISTENT) | +| 3 | 全局两列分叉计数 | 转期前 1 → 转期后 1(**新转期未制造新分叉**;那 1 条是历史脏行,归 #7586) | +| 4 | 在团名册(双通道并集口径,经 `requirement-summary` 的 `activeOrderCount`) | 目标期 56→55、源期 3→4,**名册随转期正确移动** | +| 5 | 五种判团形态一致性 | A / B / C / D / E 五种口径**全部指向同一个团** | +| 6 | 人数计数 | 源期 6→5、目标期 110→111 | +| 7 | **589589 负向** | 先提交退单(200)→ 转期得 **589589**,文案一致 | +| 8 | **589589 逃生口** | 驳回退单(200)→ 再转期得 **200**,确认是卡壳不是死锁 | +| 9 | 全量单测 + ArchTest | 基线 10132/0/13/49 → 本分支 10135/0/13/49,**仅 +3(本次新增用例),零新增失败**;13 条 Errors 两边逐类一致,均为本机无 Docker 的 Testcontainers 类 | + +取证用的既有订单已**转出后转回**,两列、金额、两侧人数、分叉计数全部回到基线值。 + +--- + +## 九、相关历史 PR + +- #7095 转订单功能初版(`V20260904_002`,2026-09-04 17:32)——设计时 `order_main.group_batch_id` 尚不存在 +- #7083 新增 `order_main.group_batch_id` 一跳直连列(`V20260904_004`,同日 18:09)——回填了当时存量、接管创单写入,但未回头改转期链路 + +--- + +## 十、相关文档 + +- 工单 #7513(缺陷与口径定案) +- 工单 #7586(存量数据修复,代码全量滚完后执行) +- `docs/group/团期模块详细设计-v3.0.html` — 归属列写入时机与一致性约束的冻结口径 + +--- + +## 关联 / 联系人 + +### 链接 + +- Issue: #7513 +- PR: #7589 +- 后续单: #7586 + +### 联系人 + +- 后端: jw +- 前端: mmg