From 1e141e445cf4c21dfac348f3bde2eed99424df4b Mon Sep 17 00:00:00 2001 From: API Changelog Bot Date: Tue, 19 May 2026 16:16:23 +0800 Subject: [PATCH] =?UTF-8?q?docs(changelog):=20#2586=20=E5=90=88=E5=90=8C/?= =?UTF-8?q?=E4=BF=9D=E9=99=A9=E6=A8=A1=E5=9D=97=20TODO=20=E6=8E=A5?= =?UTF-8?q?=E9=80=9A=20=E2=80=94=20=E4=BB=8E=E5=8D=A0=E4=BD=8D=20=E2=86=92?= =?UTF-8?q?=20=E5=AE=8C=E6=95=B4=E5=8F=AF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .../19_#2586_contract-insurance-fillin.md | 209 ++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 changelogs-v2/2026-05/19_#2586_contract-insurance-fillin.md diff --git a/changelogs-v2/2026-05/19_#2586_contract-insurance-fillin.md b/changelogs-v2/2026-05/19_#2586_contract-insurance-fillin.md new file mode 100644 index 0000000..21ab1a1 --- /dev/null +++ b/changelogs-v2/2026-05/19_#2586_contract-insurance-fillin.md @@ -0,0 +1,209 @@ +# 合同/保险模块 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` + 调宝游 API +- `POST /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` +- **发布点**:`TravelerService` 5 个写方法末尾 +- **守卫**:仅在订单 `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) + +--- + +## 八、已知限制(非缺陷) + +1. **测试服当前 active contract_scheme 都没填 `contract_template_code`**,所以 transition CONFIRM 异步自动签约会 short-circuit「无可用合同方案」并发紧急待办。需要在 admin 后台进 `/v3/admin/contract/scheme/*` 表单配置 `contract_template_code` 后才能完整测端到端。 +2. **测试服当前没有 active `insurance_scheme`**,同上。 +3. 自动签约时按 v3 当前 list 第一个 ACTIVE 方案挑选(暂无 mchId/productId 路由),后续多公司多产品场景需要在 `ContractScheme` 加 `mchId` / `productId` 字段后过滤。 + +--- + +## 九、关联 + +- 主 PR:#2590 (https://git.1814.love:8443/wx/HL/pulls/2590) +- CR 跟进:#2591 +- 最终零遗留:#2603 +- 工单:#2586 + +--- + +**前端 mmg**:请同步检查投保 / 合同管理 / 出行人编辑 / 订单确认 4 个页面的 UI 提示逻辑,重点是「投保按钮置灰条件」+「订单确认后异步合同/保险生成提示」+「已签约后编辑出行人的告知 toast」。如有疑问随时找。