文件
hl-api-changelog/changelogs-v2/2026-09/07_7095_团期转订单-新增接口-管理后台.md
T
2026-09-08 13:18:09 +08:00

16 KiB
原始文件 Blame 文件历史

schema, ticket, title, consumer, author, change_type, backend_status, gateway_status, frontend_status, frontend_owner, frontend_ref, target_release, verified_at, status_note, updated_at, base
schema ticket title consumer author change_type backend_status gateway_status frontend_status frontend_owner frontend_ref target_release verified_at status_note updated_at base
hl-changelog/v2 7095 团期转订单:同产品其他期的子订单跨期转入本期 admin jw(GIT) 新增接口 deployed verified verified mmg cc07a40d 2026-09-08 PR #7103 合入 dev-v3(6c1124db5),后续修复 #7270(订单侧 timeline)、#7278(容量守卫 0=不限)均已合入,2026-09-07 部署测试服过网关实测(候选查询/成功转期/容量守卫拒绝全通过)。前端待接:团期详情底部「转订单」弹窗接真——候选下拉替掉本地 mock 改调 GET transfer-candidates(手机号仅整串精确匹配,加密列),转期调 POST transfer-in(body fromGroupBatchId,转期原因选填,勾选通知仅影响 warnings 文案不实发)。决策:入 backlog 排在 #7067 U3-U7 与 #7244 之后统一汇总审派发。本条保持 pending。 2026-09-07 dev-v3

团期: 转订单——同产品其他期的子订单跨期转入本期

服务: hl-order-service-v3 PR: #7103 Issue: #7095 日期: 2026-09-04 影响范围: 管理后台团期详情底部操作条「转订单」弹窗


⚠️ 关键变化

原型上的「转订单」按钮此前没有后端可调(全仓零命中),候选下拉是前端本地 mock。本次两个端点补齐。

三条前端必读:

  1. 手机号搜索只支持整串精确匹配,输前几位搜不到——customer_phone 是 AES 加密列,LIKE 在密文上无意义。
  2. 「转期原因」是选填。原型弹窗没有这个输入框,后端不强制,前端零改动也能上线(不传则记「管理员手动转期」)。
  3. 通知客户本期不实发。勾选「转入后通知客户」只影响响应 warnings 里的提示文案,后端不发公众号/短信(模板 ID 未提供)。

一、背景

转订单 = 把同一产品其他期的一个子订单(= 一户)转到本期:客户无需重新下单, 订金不变、尾款按本期价重算、不可跨产品转。与已上线的「退单户」POST .../withdraw 是两件事:

维度 退单户(已有) 转订单(本次)
退不退钱 退该户定金 不退,钱跟着人走
审批 走(定制师审) 不走
该户去向 离团进退款流程 转入目标期,订单继续有效
团期计数 已报名户/人数回落 源期回落 + 目标期增加,总数守恒

二、变更接口清单

# 接口 方法 路径 变更类型 说明
1 转订单(子订单跨期转入) POST /v3/admin/order/group-batch/:groupBatchId/sub-order/:orderId/transfer-in 新增 把源期一个子订单转入本期,尾款按本期价重算
2 转订单候选子订单列表 GET /v3/admin/order/group-batch/:groupBatchId/transfer-candidates 新增 弹窗搜索下拉的候选池,替掉前端本地 mock

三、接口详情

1. 转订单(子订单跨期转入) POST /v3/admin/order/group-batch/:groupBatchId/sub-order/:orderId/transfer-in

VO: TransferSubOrderReqVO / TransferSubOrderRespVO

使用场景

团期详情底部操作条点「转订单」→ 弹窗里搜到源期的某个子订单 → 点「转入本期并通知」时调用。 path 上的 groupBatchId 是转入期(当前打开的这一期),源期在 body 的 fromGroupBatchId。

入参

字段 位置 类型 必填 约束 说明
groupBatchId Path Long ✅ - 转入期(本期)团期主订单 ID
orderId Path Long ✅ - 待转入的子订单 ID(取自候选列表的 orderId)
fromGroupBatchId Body Long ✅ 非空 源期团期主订单 ID(取自候选列表的 fromGroupBatchId,不是产品侧班期 ID)
reason Body String ❌ ≤512 字 转期原因;不传后端记「管理员手动转期」
notify Body Boolean ❌ 默认 true 对应弹窗复选框;本期只落标志位并在 warnings 提示,不实发通知

出参 Result<TransferSubOrderRespVO>

字段 类型 说明
orderId String 被转子订单 ID(雪花,序列化为字符串)
fromBatchNo String 源期团期号
toBatchNo String 目标期团期号
oldOrderAmount BigDecimal 转期前应收
newOrderAmount BigDecimal 转期后应收(按目标期报价重算)
paidAmount BigDecimal 已付金额(转期不动,原样跟随)
newBalanceDue BigDecimal 转期后待收尾款 = 新应收 − 已付
warnings String[] 非阻断提示,需人工跟进的事项;无则为空数组

请求示例

{ "fromGroupBatchId": 1932847562341, "reason": "客户改期,转到第 7 期", "notify": true }

响应示例

{
  "code": 200,
  "message": "成功",
  "data": {
    "orderId": "1955001234567890",
    "fromBatchNo": "GT-26-0005",
    "toBatchNo": "GT-26-0007",
    "oldOrderAmount": 8000.00,
    "newOrderAmount": 8600.00,
    "paidAmount": 3000.00,
    "newBalanceDue": 5600.00,
    "warnings": ["需人工经公众号 + 短信通知客户团期变更与新行程"]
  },
  "success": true
}

空数据 / 降级响应

本接口是写操作,无空数据形态。产品域报价 / 库存调用降级时不静默成功,一律按错误响应返回并整事务回滚。 warnings 可能为空数组(该户无合同保险、且 notify=false):

{ "code": 200, "data": { "warnings": [] }, "success": true }

错误响应

{
  "code": 589528,
  "message": "目标团期价低于该户已付金额,需先走退款流程",
  "success": false,
  "data": null
}

八类拒绝:

code 含义
589500 团期不存在(源期或目标期)
589501 该订单当前状态不可转(只放行待支付 / 待出行)
589510 源期或目标期状态不满足(须为招募中 / 资源筹备中)
589512 子订单不属于所填源期
589524 转出与转入是同一团期
589525 跨产品转(只能同产品不同期)
589526 目标期名额或房间数不足
589527 获取目标期报价失败
589528 目标期价低于该户已付金额

业务边界

  • 鉴权:管理后台端点,须带网关注入的 X-Admin-Id;与既有团期动作端点(成团 / 流团 / 退单户)同口径。
  • 幂等:同一 orderId 5 秒窗口内重复提交只成功一次(双击防重)。
  • 并发:按团期 ID 数值升序对两期加分布式锁,两个管理员对拉互转不会死锁。
  • 失败零写入:任一守卫不过或产品域调用失败,整事务回滚——两期名额、两期已报名计数、订单本身分文未动。
  • 钱不动:paid_amount 全程不变,本接口不产生任何退款单或收款单;差额体现在待收尾款上。
  • 兼容:不传 reason / notify 均可,老前端零改动可调。

2. 转订单候选子订单列表 GET /v3/admin/order/group-batch/:groupBatchId/transfer-candidates

VO: TransferCandidateVO

使用场景

转订单弹窗里「搜索要转入的订单」输入框的数据源,替掉原型阶段的前端本地 mock 数据。 返回的每一行可直接渲染成原型那种候选行:客户名 + 期号徽标 + 订单号 + 人数。

入参

字段 位置 类型 必填 约束 说明
groupBatchId Path Long ✅ - 转入期(本期)团期主订单 ID
keyword Query String ❌ - 订单号 / 客户名 / 期号 → 模糊;手机号 → 必须整串(11 位),前缀搜不到

出参 Result<List<TransferCandidateVO>>

字段 类型 说明
orderId String 子订单 ID,转入时原样回传为 path 的 orderId
orderNo String 订单号,如 GT-26-0085
customerName String 客户姓名
peopleCount Integer 该户人数(成人+儿童+小童+婴儿)
paidAmount BigDecimal 该户已付金额
fromGroupBatchId String 源期团期主订单 ID,转入时原样回传为 body 的 fromGroupBatchId
batchNo String 源期团期号
batchName String 源期期名,原型徽标「第 5 期」取此列
departDate LocalDate 源期出发日期

请求示例

GET /v3/admin/order/group-batch/1932847562341/transfer-candidates?keyword=%E8%B5%B5%E6%95%8F HTTP/1.1
X-Admin-Id: 1

响应示例

{
  "code": 200,
  "message": "成功",
  "data": [
    {
      "orderId": "1955001234567890",
      "orderNo": "GT-26-0085",
      "customerName": "赵敏",
      "peopleCount": 4,
      "paidAmount": 3000.00,
      "fromGroupBatchId": "1932847562341",
      "batchNo": "GT-26-0005",
      "batchName": "第 5 期",
      "departDate": "2026-10-01"
    }
  ],
  "success": true
}

空数据 / 降级响应

无候选(同产品没有其他可转期、或关键字无命中、或本期自身状态已不可转)时返回空数组,不报错:

{ "code": 200, "data": [], "success": true }

错误响应

{
  "code": 589500,
  "message": "团期不存在",
  "success": false,
  "data": null
}

业务边界

  • 候选池口径:同 productId 的其他期(排除本期自身),且源期状态 ∈ 招募中 / 资源筹备中, 订单状态 ∈ 待支付 / 待出行——与转入接口的守卫完全同源,列表里出现的都是能转的。
  • 本期不可转时返回空:本期已进物资准备及以后,直接返回 [],前端应据此禁用弹窗而不是靠调用结果试探。
  • 不分页:同产品可转期数 × 每期户数量级很小(原型「满 9 户满团」),一次返回全量供前端本地筛。
  • 手机号是加密列:整串走加密等值匹配,非 11 位数字的关键字走订单号 / 客户名 / 期号三路模糊。

四、契约约束与正确调用方式

✅ 正确 / ❌ 错误 payload 对照

场景 payload
✅ 最小请求 { "fromGroupBatchId": 1932847562341 }
✅ 带原因不通知 { "fromGroupBatchId": 1932847562341, "reason": "客户改期", "notify": false }
❌ 缺源期 { "reason": "客户改期" } → 非 200 业务码,message 含「源团期 ID 不能为空」
❌ 传产品侧班期 ID 当源期 { "fromGroupBatchId": 92001 } → 589500 团期不存在
❌ 源期 = 本期 { "fromGroupBatchId": <path 上同一个值> } → 589524

两个 ID 不要混用

fromGroupBatchId 与 path 上的 groupBatchId 同一口径,都是团期主订单 ID (候选列表返回的 fromGroupBatchId 直接回传即可)。不要传产品侧班期 ID。


五、数据库行为

动作 落库效果
转期成功 order_main.product_batch_id 改为目标期班期 ID;order_amount 改为目标期报价;depart_date / return_date / trip_days / trip_nights 同步刷成目标期值
团号 order_main.team_no 不变(订金支付时生成的团号跟订单走)
已付 order_main.paid_amount 不变
流水 group_batch_transfer 新增一行(新表),记录两期 ID、人数房数、新旧应收、已付快照、原因、操作人
计数 源期 enrolled_people/enrolled_rooms 减、目标期加,总数守恒;产品域两期 enrolled_count/booked_rooms 同步
时间线 源期一条「子订单转出」、目标期一条「子订单转入」(order_group_batch_status_log,DATA 类)
失败 任一步失败整事务回滚,以上全部不发生

六、边界行为

  • 未登录 / 缺 X-Admin-Id → 401(网关拦截)
  • 团期或子订单不存在 → 589500 / 订单域 not found,不 500
  • 产品域报价或名额调用失败 → 589527 / 名额失败码 + 整事务回滚,不产生半搬状态
  • 时间线写失败 → 降级 WARN,不影响转期成功
  • 候选列表下游无数据 → 返回 [],不 500 不阻断弹窗

六.5、枚举 / 数据字典

可转的订单状态(com.hulalv.order.core.enums.OrderStatus)

所属字段: 服务端守卫用,不在出参中 | 类型: String

值 中文 说明
PENDING_PAY 待支付 允许转期
PENDING_DEPARTURE 待出行 允许转期
TRAVELLING 出行中 拒绝(589501)
COMPLETED 已完成 拒绝(589501)
CANCELLED 已取消 拒绝(589501)

可转的团期状态(com.hulalv.order.groupbatch.enums.GroupBatchStatus)

所属字段: 服务端守卫用,不在出参中 | 类型: String

值 中文 说明
RECRUITING 招募中 源期 / 目标期均允许
RESOURCE_PREPARING 资源准备中 源期 / 目标期均允许
MATERIAL_PREPARING 及以后 物资准备中 / 待出发 / 出行中 / 核团中 / 已结算 / 已流团 任一方处于该区间即拒(589510)

七、不影响范围

  • 仅影响: 管理后台团期详情的「转订单」弹窗
  • 零影响:
    • 退单户 POST .../sub-order/:orderId/withdraw(本次未改一行)
    • 创单与支付链路(不产生退款单 / 收款单,paid_amount 不动)
    • C 端小程序全部接口
    • 团期人员 / 物资 / 财务等其余团期端点
    • 存量数据(新表为空表,不迁移历史)

八、测试环境已验证

✅ 已验证。 2026-09-07 部署 dev-v3 到测试服并经网关实测(https://api.test.1814.love:9443,真实管理端鉴权)。

产品 2056947670512971778 的两个同产品期(源期 gbId 2096510069465088002 / 目标期 2096495107078328322),转入一户 2 人 1 房:

# 用例 期望 实测
1 候选查询(目标期视角) 列出源期该户 ✅ 返回 orderNo HL20260906160526197、2 人、fromGroupBatchId 源期
2 成功转期 code 200 + 金额三件套 ✅ oldOrderAmount 4000 / newOrderAmount 4000 / paidAmount 0 / newBalanceDue 4000 + warnings 通知提示
3 计数守恒 源期 −2/−1、目标期 +2/+1 ✅ 源期 people/rooms 2/1 → 0/0,目标期 2/1 → 4/2
4 订单归属改到目标期 转走后源期候选不再有它 ✅ 目标期候选查询转后为 0 条(该户已在本期)
5 订单侧 timeline(#7270 补) 一条 GROUP_BATCH_TRANSFER ✅ order status-log 事件序列 [CREATE, GROUP_BATCH_TRANSFER],content「由 {源期号} 转入 {目标期号},应收 4000.00 → 4000.00」
6 容量守卫拒绝路径 目标期满时 589526 ✅ 修容量口径前实测触发 589526(目标期 maxParticipants=0 曾被误判满,已由 #7278 修正「0=不限」)

候选查询四路 keyword(订单号/客户名/期号 LIKE + 手机号加密等值)由单测 GroupBatchTransferServiceTest.phoneKeywordGoesEncryptedEqualsBranch 等覆盖; 八类拒绝路径、时间线降级、库存补偿由该测试类 30 例覆盖。

单测:全量 mvn -o -pl hl-order-service-v3 -am test 8782 例 Failures 0(含 #7270 timeline、#7278 容量修复)。


九、相关历史 PR

PR Issue 说明 是否仍有效
#7103 #7095 首次落地 GB-ADM-071 转订单两个端点 ✅
#7257 #7250/#7095 修 dev-v3 测试编译中断(并发签名冲突) ✅
#7270 #7095 补写订单侧 timeline(验收要点 5 漏实现) ✅
#7278 #7095 容量守卫「0=不限」口径修正(网关实测暴露) ✅ 最新

十、相关文档

  • 关联 Issue: wx/HL#7095
  • 关联 PR: wx/HL#7103
  • 后续计划: 通知实发(模板 ID 到位后)、审批流、差额自动退款——均见工单 #7095 §7「明确不做」

关联 / 联系人

链接

联系人

  • 后端负责人: @jw