v3 contract / insurance 三 PR 累计变更(#2590 + #2591 + #2603)。 前端关键变化 4 点: 1. 合同/保险全部接口从占位/抛错转为真实可用 2. POST /v3/admin/insurance/purchase 新增订单状态+支付状态双重校验 3. POST /v3/admin/order/{id}/transition CONFIRM 异步触发自动签约/投保 4. 出行人变更触发已签约订单作废重做(状态白名单过滤) 新增字段:ContractScheme.defaultDestination / defaultDepartureCity 新增错误码:540022 ORDER_NOT_CONFIRMED_FOR_INSURANCE / 510206 合同方案未配置模板 新增异步事件:ChecklistConfirmedEvent / TravelerChangedEvent 测试服真测通过:单测 1096 全绿 + 9443 网关 + 真 admin token + P0-1 正反例齐全。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
12 KiB
合同/保险模块 TODO 接通 — 从占位 → 完整可用 (工单 #2586)
存放目录: 二期(v3,
order-v3标签)→changelogs-v2/2026-05/服务: hl-order-v3 (端口 8084 / 测试服 9443 网关 +
/v3/admin/...) PR: #2590 (主体) + #2591 (CR 跟进) + #2603 (P2/P3 + Publisher 接入) Issue: #2586 日期: 2026-05-19 影响范围: 管理后台「合同」「保险」两大模块全部接口 + 「订单状态机 CONFIRM 转换」副作用
⚠️ 关键变化(前端必看 4 点)
1. 合同 / 保险模块从「占位返空 / 抛异常」转为「完整可用」(行为变化,非破坏性)
PR #2590 之前合同 / 保险服务的所有写接口都返 UnsupportedOperationException 占位,查询接口返空列表 + log.warn TODO。本次合并后全部真实可用:
POST /v3/admin/contract/create-by-scheme合同按方案创建 — 真实创建并接入第三方合同平台(12301 / 腾讯电子签)POST /v3/admin/insurance/purchase手动投保 — 真实写insurance_order+ 调宝游 APIPOST /v3/admin/insurance/auto-purchase/auto-purchase-by-scheme自动投保 — 真实链路POST /v3/admin/contract/resend-sign-sms重发签署短信 — 真实做用户归属校验POST /v3/admin/contract/void/{id}作废 — 真实状态流转- 列表 / 详情 / scheme 列表 — 全部返真实数据
前端可解除任何针对这些接口的"待后端实现"占位提示,按真实响应处理。
2. POST /v3/admin/insurance/purchase 新增订单状态校验(业务规则收紧)
只允许以下组合的订单投保:
| 维度 | 允许值 |
|---|---|
orderStatus |
PENDING_DEPARTURE / PENDING_BALANCE / TRAVELLING |
payStatus |
DEPOSIT_PAID / FULLY_PAID |
任一不符 → 返业务码 540022 ORDER_NOT_CONFIRMED_FOR_INSURANCE 文案 "请先确认订单后再配置保险(当前订单状态不允许投保)"。
前端处理建议:
- 投保按钮的可点击态可加前置判断(不必依赖后端报错才提示)
- 若直接拦不到,把 540022 错误码处理成 toast 弹该文案即可
3. POST /v3/admin/order/{id}/transition event=CONFIRM 自动触发合同 / 保险创建(异步)
订单状态机 CUSTOMIZING → PENDING_DEPARTURE 的 CONFIRM 转换会异步触发:
- 自动创建合同(前提:
contract_scheme.status=ACTIVE且contract_template_code配置过) - 自动投保(前提:
insurance_scheme.status=ACTIVE)
接口同步返回 triggeredEvents: ["ASYNC_CONTRACT_GENERATE", "ASYNC_INSURANCE_ISSUE"],真正写库发生在事务 commit 后异步线程(@TransactionalEventListener(AFTER_COMMIT))。
前端表现:
- CONFIRM 转换 200 返回后,不要立刻显示「合同已生成 / 保险已购买」(异步还没跑完)
- 建议 transition 200 后 toast 提示「订单已确认,合同/保险将在 1 分钟内自动生成」,然后等用户主动刷新合同列表 / 保险列表看到记录
- 自动签约失败时合同/保险域有内部紧急待办 + 通知中心推送(通知中心走 NotificationDispatcher,渠道在
notification_event_config配)
4. 出行人变更触发合同 / 保险作废+重做(已签约订单)
TravelerService 的 5 个写方法(batchEdit / add / delete / addByInternal / updateByInternal)末尾按状态白名单异步发布 TravelerChangedEvent:
| 订单 orderStatus | 是否触发作废重做 |
|---|---|
PENDING_DEPARTURE / PENDING_BALANCE / TRAVELLING |
✅ 触发:作废旧合同 + 重新签约;作废旧保单 + 重投 |
CUSTOMIZING / PENDING_PAY / PENDING_COMPLETE 等未签约 |
❌ 不触发(避免事件风暴) |
REVIEWING / SETTLED / CANCELLED |
❌ 不触发 |
前端表现:已签约订单编辑出行人后,合同 / 保险记录会自动作废并重新生成(异步),用户应感知到合同 PDF / 保单号变化。建议在出行人编辑成功后给一个 toast:「已签约订单编辑出行人将触发合同/保险重新生成」。
一、背景
订单 v3 的 contract / insurance 域骨架在 yst 早期 PR 已从 v2 搬运,但接通订单核心域(OrderInfoMapper / OrderTravelerMapper)的位置全部以 TODO(PR-order-core) 占位,方法体抛 UnsupportedOperationException 或返 Collections.emptyList()。
PR #2552-2554 出行人三连完成后,OrderInfoMapper / OrderTravelerMapper 已在 v3 落位,本工单收尾 — 把 contract / insurance 完整对接、激活 publisher、补全单测、修复 CR 三轮深审找出的 3 P0 + 5 P1 + 10 P2/P3 + 3 P3-NEW。
二、变更接口清单
合同模块(admin)
| # | 接口 | 方法 | 路径 | 变更类型 |
|---|---|---|---|---|
| 1 | 合同方案列表(启用) | GET | /v3/admin/contract/scheme/list |
行为变化(返真实数据) |
| 2 | 合同列表 | GET | /v3/admin/contract/list |
行为变化 |
| 3 | 按方案创建合同 | POST | /v3/admin/contract/create-by-scheme |
行为变化(真实写库 + 第三方调用) |
| 4 | 重发签署短信 | POST | /v3/admin/contract/resend-sign-sms |
行为变化(接入用户归属校验) |
| 5 | 作废合同 | POST | /v3/admin/contract/void/{contractId} |
行为变化 |
| 6 | 合同方案 CRUD | POST | /v3/admin/contract/scheme/* |
新增 defaultDestination / defaultDepartureCity 字段 |
合同模块(mp 内部)
| # | 接口 | 方法 | 路径 | 变更类型 |
|---|---|---|---|---|
| 7 | 用户合同列表 | GET | /internal/mp/contract/list |
行为变化(按 userId 反查 orderIds) |
| 8 | 用户合同详情 | GET | /internal/mp/contract/detail/{contractId} |
行为变化(接入归属校验) |
保险模块(admin)
| # | 接口 | 方法 | 路径 | 变更类型 |
|---|---|---|---|---|
| 9 | 保险方案列表(启用) | GET | /v3/admin/insurance/scheme/list |
行为变化 |
| 10 | 保单列表 | GET | /v3/admin/insurance/orders |
行为变化 |
| 11 | 手动投保 | POST | /v3/admin/insurance/purchase |
行为变化 + 状态校验新增 |
| 12 | 自动投保 | POST | /v3/admin/insurance/auto-purchase |
行为变化 |
| 13 | 按方案自动投保 | POST | /v3/admin/insurance/auto-purchase-by-scheme |
行为变化 |
| 14 | 取消保险 | POST | /v3/admin/insurance/cancel |
行为变化 |
| 15 | 投保预览 | POST | /v3/admin/insurance/preview-apply |
行为变化 |
订单状态机副作用
| # | 接口 | 方法 | 路径 | 变更类型 |
|---|---|---|---|---|
| 16 | 订单状态流转 | POST | /v3/admin/order/{id}/transition |
CONFIRM 事件副作用扩展:异步触发自动签约/投保 |
| 17 | 出行人 batchEdit | POST | /v3/admin/order/{id}/traveler/batch-edit |
已签约阶段触发作废重做 |
| 18 | 出行人 add | POST | /v3/admin/order/{id}/traveler/add |
同上 |
| 19 | 出行人 delete | POST | /v3/admin/order/{id}/traveler/delete/{travelerId} |
同上 |
三、字段变更(合同方案)
ContractScheme 实体新增 2 个字段(对应表 contract_scheme):
| 字段名 | 类型 | 含义 | 业务规则 |
|---|---|---|---|
defaultDestination |
varchar(64) NULL | 方案默认目的地 | createByScheme 时取值优先级:scheme.defaultDestination → productName 关键字提取(呼伦贝尔/阿尔山/新疆/内蒙古/青海/西藏等) → 全局兜底「内蒙古呼伦贝尔」 |
defaultDepartureCity |
varchar(64) NULL | 方案默认出发地 | 同上,全局兜底「呼和浩特」 |
前端影响:admin 合同方案管理表单加 2 个文本输入框(选填)。
四、错误码新增
| 业务码 | 文案 | 触发条件 |
|---|---|---|
540022 |
请先确认订单后再配置保险(当前订单状态不允许投保) | 调 /v3/admin/insurance/purchase 时订单状态 / 支付状态不符合白名单 |
510206 |
合同方案未配置模板 | 调 /v3/admin/contract/create-by-scheme 时 scheme.contractTemplateCode = null |
其他既有错误码不变。
五、Publisher 异步链路
ChecklistConfirmedEvent
- 发布点:
OrderService.transition()守卫from=CUSTOMIZING && to=PENDING_DEPARTURE && event=CONFIRM - 携带:
orderId / orderNo / operatorId / operatorType (ADMIN/SYSTEM) - operatorId 来源:
AdminContextUtil.getAdminId()(请求上下文)→ 无则 null + SYSTEM - 监听者:
ContractEventListener+InsuranceEventListener都接@TransactionalEventListener(AFTER_COMMIT, fallbackExecution=true) - 下游行为:listener 走
contract.createByScheme/insurance.autoPurchase,失败有紧急待办 + 通知中心降级通知
TravelerChangedEvent
- 发布点:
TravelerService5 个写方法末尾 - 守卫:仅在订单
orderStatus ∈ {PENDING_DEPARTURE, PENDING_BALANCE, TRAVELLING}时发布 - 携带:
orderId / orderNo / changeType (ADD/MODIFY/REMOVE) / affectedTravelerIds - 下游行为:listener 走
contract.invalidateByOrderId + createByScheme(作废重签)、insurance.cancelByOrderId + autoPurchase(退旧重投)
六、数据库迁移
| 文件 | 内容 |
|---|---|
V20260519_004__create_order_todo.sql |
新增 order_todo 表(订单内部待办,自动签约/投保失败时生成紧急待办) |
V20260519_005__contract_scheme_add_default_destination.sql |
contract_scheme 加 default_destination + default_departure_city 两列 |
七、测试服真测结论
部署
- 测试服 v3 已部署 dev-v3 最新 HEAD
af7dd70双实例 8086+8186 - Flyway 应用 V20260519_004 + V20260519_005 成功
- Nacos test namespace 注册健康,9443 网关路由通
API 真测(test_admin token 通过 trusted device 通路)
| 接口 | 结果 |
|---|---|
| GET 合同 scheme list / 合同 list / 保险 scheme list / 保单 list | ✅ 200 真实数据 |
| POST insurance.purchase CUSTOMIZING+UNPAID | ✅ 抛 540022 |
| POST insurance.purchase PENDING_DEPARTURE+UNPAID | ✅ 抛 540022(证明 pay_status 单独校验起作用) |
| POST insurance.purchase PENDING_DEPARTURE+DEPOSIT_PAID | ✅ 状态校验过,进入 planId 校验阶段 |
| POST order.transition CONFIRM | ✅ 200,triggeredEvents 含 ASYNC_CONTRACT_GENERATE + ASYNC_INSURANCE_ISSUE,listener 真收到(业务数据 scheme 缺所以 fail-soft) |
单测
mvn -pl hl-order-service-v3 test:Tests run 1096 / Failures 0 / Errors 0(基线 1069 → +27)
八、已知限制(非缺陷)
- 测试服当前 active contract_scheme 都没填
contract_template_code,所以 transition CONFIRM 异步自动签约会 short-circuit「无可用合同方案」并发紧急待办。需要在 admin 后台进/v3/admin/contract/scheme/*表单配置contract_template_code后才能完整测端到端。 - 测试服当前没有 active
insurance_scheme,同上。 - 自动签约时按 v3 当前 list 第一个 ACTIVE 方案挑选(暂无 mchId/productId 路由),后续多公司多产品场景需要在
ContractScheme加mchId/productId字段后过滤。
九、关联
- 主 PR:#2590 (wx/HL#2590)
- CR 跟进:#2591
- 最终零遗留:#2603
- 工单:#2586
前端 mmg:请同步检查投保 / 合同管理 / 出行人编辑 / 订单确认 4 个页面的 UI 提示逻辑,重点是「投保按钮置灰条件」+「订单确认后异步合同/保险生成提示」+「已签约后编辑出行人的告知 toast」。如有疑问随时找。