docs(order-v3): #7529 团期转期后重排逐日行程 + 重版用车需求 + 三条 fail-fast 守卫
changelog-filename-gate / validate (push) Failing after 2s

Refs #7529

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
这个提交包含在:
jw
2026-09-14 09:44:47 +08:00
共同撰写人 Claude Opus 5
父节点 96dcb3fffb
当前提交 3860003084
@@ -0,0 +1,194 @@
---
schema: "hl-changelog/v2"
ticket: "7529"
title: "团期转期后按目标期重排逐日行程 + 重版用车需求(住宿不重版)+ 三条 fail-fast 前置守卫"
consumer: "admin"
author: "jw(GIT)"
change_type: "修改接口"
backend_status: "deployed"
gateway_status: "not_required"
frontend_status: "pending"
frontend_owner: "mmg"
frontend_ref: "#7529"
target_release: ""
verified_at: "2026-09-14"
status_note: "转期接口请求体 / 响应体结构与既有 warning 文案一字未改;唯一前端相关变化是 3 个新错误码(589575/589576/589577)的友好提示映射(message 体后端已给全文,不映射也能原样弹出);另转期成功后该户用车需求落 PENDING_REVIEW,车务复核入口沿用既有页面。无新增出参字段、零 DDL、零网关路由改动。"
updated_at: "2026-09-14"
base: "dev-v3"
---
# order-v3: 团期转期后重排逐日行程 + 重版用车需求 + 三条 fail-fast 守卫
**服务**: hl-order-service-v3
**PR**: #7638
**Issue**: #7529
---
## ⚠️ 关键变化
🟡 **行为修复(后端内部数据纠正为主,响应契约不变)**:
1. **转期后逐日行程按目标期出发日整体位移**。改前 `order_itinerary_day.day_date` 停在源期日期(行程单、出团通知书、小程序行程页逐天日期全错);改后 `day_date = 目标期 depart_date + day_number - 1`。
2. **转期后该户用车需求重版**,服务日期三列(`service_start_date`/`service_end_date`/`service_dates`)按位移后的逐日行程冻结出目标期口径;旧行置 `is_active=0`(源期日期原样保留、不被改写),新行状态落 `PENDING_REVIEW`。**住宿需求不重版**(无绝对日期列,入住日由读侧按主单 `depart_date` 反推,自动跟随)。
3. **源期与目标期「整团需求已确认」标记各清 0 一次**,各写一条 `BATCH_REQUIREMENT_REOPENED` 团级时间线(目标期显式重开,不依赖该户有无用车需求)。
4. **三条新增 fail-fast 前置守卫,全部零写入**:目标期缺出发日 → 589575;目标期日期跨度 ≠ 订单行程天数 → 589577;该户用车需求正处于最终确认占用窗口 → 589576。
**请求体、响应体字段与 `warnings` 文案本单一字未改;无新增出参字段;无 DDL;无网关路由改动。**
## 一、背景
团期「转订单(子订单跨期转入)」把子订单从源期迁到目标期时,主单 `depart_date`/`return_date` 已按目标期刷新,但两处**冻结了绝对日期**的数据没跟着走:逐日行程 `day_date` 与用车需求的三列服务日期都还停在源期,导致行程单错日、车务按源期派车(目标期出发日没车)。本单在 `GroupBatchTransferService.transferIn` 已有的同一事务内补齐这两件事,并把源期/目标期「整团需求已确认」标记各重开一次;同时为「重排」补三条 fail-fast 前置守卫,防止在缺基准数据时把订单写成「末天≠返程日」的脏数据。
## 二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|------|------|------|----------|------|
| 1 | 转订单(子订单跨期转入) | POST | `/v3/admin/order/group-batch/:groupBatchId/sub-order/:orderId/transfer-in` | 修改 | 事务内新增:逐日行程位移 + 用车需求重版 + 两团需求确认标记重开 + 三条 fail-fast 守卫(589575/589576/589577)。请求/响应结构不变 |
## 三、接口详情
### 1. 转订单(子订单跨期转入) `POST /v3/admin/order/group-batch/:groupBatchId/sub-order/:orderId/transfer-in`
**VO**: `TransferSubOrderReqVO` → `Result<TransferSubOrderRespVO>`
#### 使用场景
团期管理台「转订单」弹窗,把同产品其他期的一个子订单转入本期。path 上的 `groupBatchId` 是**转入期**(本期),源期在 body 的 `fromGroupBatchId`。沿用既有鉴权与网关路由(`/v3/admin/**` 已覆盖),本单不改。
#### 入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| groupBatchId | path | string(Long) | 是 | 雪花 ID | **转入期**(本期)团期主订单 ID |
| orderId | path | string(Long) | 是 | 雪花 ID | 待转入的子订单 ID |
| fromGroupBatchId | body | string(Long) | 是 | @NotNull | 源期团期主订单 ID |
| reason | body | string | 否 | @Size(max=512) | 转期原因;为空落「管理员手动转期」 |
| notify | body | boolean | 否 | 默认 true | 转入后是否通知客户 |
#### 出参字段表
(结构一字未改,仅列字段以便前端对照)
| 字段 | 类型 | 说明 |
|---|---|---|
| orderId | string(Long) | 被转子订单 ID |
| fromBatchNo | string | 源期期号 |
| toBatchNo | string | 目标期期号 |
| oldOrderAmount | string(decimal) | 转期前应收 |
| newOrderAmount | string(decimal) | 按目标期报价重算后的应收 |
| paidAmount | string(decimal) | 已付 |
| newBalanceDue | string(decimal) | 待收尾款 |
| warnings | array | 非阻断提示(本单不增不减其中任何一条,如「该户合同/保险已出具,转期后需人工重出」) |
#### 请求示例
```http
POST /v3/admin/order/group-batch/2099073597908647938/sub-order/2099073597711515649/transfer-in
Authorization: Bearer <admin token>
Content-Type: application/json
{ "fromGroupBatchId": 2099078774497681409, "reason": "客户改期", "notify": false }
```
#### 响应示例
```json
{
"code": 200,
"msg": "成功",
"data": {
"orderId": "2099073597711515649",
"fromBatchNo": "Q202612192099078773096828929",
"toBatchNo": "Q202612182099073596763615233",
"oldOrderAmount": "2000.00",
"newOrderAmount": "2000.00",
"paidAmount": "0.00",
"newBalanceDue": "2000.00",
"warnings": ["该户合同/保险已出具,转期后需人工重出"]
}
}
```
#### 空数据 / 降级响应
该户没有用车需求时重版链路返 0、不报错,行程仍位移、两团需求确认标记仍各清 0(证明标记重开不依赖用车链路);该户没有逐日行程时接口仍 200,用车新 active 行服务日回退取目标期 `depart_date`/`end_date`。
#### 错误响应
```json
{ "code": 589577, "msg": "目标团期日期跨度(4 天)与该订单行程天数(3 天)不一致,请先核对团期日期或订单行程后再转期", "data": null }
```
三条新增码(均 HTTP 200 + 业务码 + 零写入):589575 目标期未设置出发日期;589576 该户用车需求正在最终确认中;589577 目标期日期跨度与订单行程天数不一致。既有码 589500/589501/589510/589512/589524~589528/589560 原样保留。
#### 业务边界
- 转期只允许源期与目标期均在「招募中 / 资源筹备中」,且子订单状态在「待付款 / 待出行」。
- 三条 fail-fast 守卫与「源期已分房(589560)」「在途退单(589512)」守卫一样,均在两团行栅栏(本事务第一条 DB 语句)之后、任何本地写之前执行;命中即整笔零写入。
- 589577 天数守卫按定案 16 的 null 跳过口径:`order_main.trip_days` 为 null 时放行(守卫非恒真);现行 schema 该列 `tinyint NOT NULL`,该分支以单测取证。
- 顺序固定不可调换:先落库逐日行程位移,再重版用车(服务日先取逐日行程、为空才回退主单日期)。
- 合同/保险不自动作废重出(团期级合同保险能力本就未实现),沿用既有 `warnings` 文案提示人工处理。
## 四、契约约束与正确调用方式
- 金额四字段是 `BigDecimal` 序列化为**字符串**,勿按 number 解析;`orderId` 亦为字符串形态防大整数精度丢失。
- 转期成功后该户用车需求为**新版本**(旧版本 `is_active=0`),车务侧应按新 active 行的目标期服务日派车。
- 589575/589576/589577 的 `message` 由后端给全文,前端可直接弹出;如需友好化映射按码映射即可。
## 五、数据库行为
- 无表变更、无 Flyway、无新增列、无索引调整、无 H2 `*-schema.sql` 改动。
- 写入均落既有表:`order_itinerary_day.day_date`(位移)、`order_vehicle_requirement`(重版新行 + 旧行置 inactive)、`order_group_batch.requirement_confirmed`(CAS 清 0)、`group_batch_status_log`(`BATCH_REQUIREMENT_REOPENED` 时间线,该 event_type 为既有枚举)。
## 六、边界行为
- 转期是管理员侧写口,两团行栅栏按 groupBatchId 数值升序各下一次锁,防两个管理员对拉互转造成死锁。
- 目标期 `hotel_ready` 转期后置 false(既有行为,不变);源期分房必须为空(增补 C8b 不变量断言,转期入口已用 589560 拦住已分房户)。
- 三条 fail-fast 守卫中,用车最终确认占用探针用「不抛异常的探针」(返回 false 而非标脏事务),命中转 589576,不产生 HTTP 500 / `UnexpectedRollbackException`。
## 六.6、修改前后对比
| 维度 | 改前 | 改后 |
|---|---|---|
| `order_main.depart_date`/`return_date` | 改为目标期 | 不变(仍改为目标期) |
| `order_itinerary_day.day_date` | **不动**(停在源期日期) | 按目标期 `depart_date` 整体位移 |
| 该户用车需求 | **不重版**(三列服务日期留在源期) | 重版出新 active 版本,服务日按位移后行程冻结,状态落 `PENDING_REVIEW` |
| 该户住宿需求 | 不重版 | 不重版(入住日随主单 `depart_date` 自动跟随) |
| 源期 / 目标期「整团需求已确认」标记 | 不变 | 各 CAS 清 0 + 各写一条 `BATCH_REQUIREMENT_REOPENED` 时间线 |
| 目标期缺出发日 / 行程天数漂移 / 用车正在最终确认 | 静默成功或 500 | 分别 589575 / 589577 / 589576,零写入 |
| 请求 / 响应结构、warnings、错误码既有项、DDL、网关 | — | **一字未改 / 零改动** |
## 六.7、影响评估
- **兼容性**:请求体、响应体字段与结构完全不变,未新增出参字段、未新增 warning 文案;未消费新错误码的前端页面无需改动即可继续工作。
- **需要前端动的(均非阻断)**:
1. 3 个新错误码 589575/589576/589577 的友好提示映射(message 后端已给全文,不映射也能原样弹出);
2. 知悉转期后该户用车需求为新版本、状态 `PENDING_REVIEW`,车务复核入口沿用既有页面。
- **性能**:位移逐日行程 + 重版一户用车 + 两团各一次 CAS 重开,均在既有转期事务内,成本可忽略。
- **正确性修复**:改前转期后行程单 / 出团通知书逐天日期错、车务按源期派车——这是修复不是回归。
## 七、不影响范围
- 转期请求体 / 响应体字段名称、类型、结构、warnings 文案一字未改。
- 无网关路由改动、无 Feign/MQ 改动、无 DDL、既有错误码一个未改。
- 住宿需求不重版;出行完毕、其它团期端点行为不变。
## 八、测试环境已验证
2026-09-14 TEST 网关(自签 admin token)+ 真 MySQL 造数三路取证,工单 #7529 的 AC-1 ~ AC-16 逐条通过:
- 归属迁移(AC-16)、逐日行程按目标期重排(AC-2)、用车重版且服务日为目标期口径 + 旧行置 inactive 且源期日期保留(AC-3)、住宿不重版(AC-4,前后 requirement_id/version/submitted_at 一致)、房务侧入住日随目标期跟随且引用未重版的住宿 v1(AC-5)、两团 requirement_confirmed 清 0 + 各一条 `BATCH_REQUIREMENT_REOPENED`(AC-6)、无用车户 / 无行程户不报错回退正确(AC-7/AC-8)。
- 三条 fail-fast 均返对应码 + 四表逐字节零写入:589575(AC-9)、589577 正反两向含 trip_days=null 放行的单测取证(AC-10)、589576 非 500(AC-11);既有守卫未削弱:589560 源期已分房 `{0}` 为真实行数、行栅栏 InOrder 单测证明为本事务第一条 DB 语句(AC-12)。
- 号段:新增码恰为 589575/589576/589577 三个,与 dev-v3 基线 diff 无越界无重号(AC-13);CODE_RULES 红线逐条通过(AC-14)。
- 全量单测:`mvn -o -pl hl-order-service-v3 -am test` — order-v3 模块 10584 test / 0 failure / 0 error / 7 skipped,BUILD SUCCESS(AC-15)。
## 十、相关文档
- 工单 #7529
- 全过程记录:HL 仓 `dev-records/records/2026-09-14-local-7529-transfer-reschedule.md`
## 关联 / 联系人
- 后端:jw(已合入 dev-v3 + TEST 实测)
- 前端:mmg(3 个新错误码可选友好映射;知悉转期后用车需求为新版本、状态 PENDING_REVIEW)