新增面板读、手动开、作废重开、批量催签四个端点。 如实标注三条: 1. 出具依赖房/车/导/摄四项 ready,而 hotel_ready 线上无任何写入方(#4132), 故面板 issuable 恒 false、三个写动作恒返 589548 —— 前端可先接, 但 #4132 落地前看不到出具效果;面板读与催签不受限制,已实测可用。 2. 出具成功的正向路径未在网关侧实测(该状态在 TEST 造不出来), 仅单测覆盖;589549 同样未在真实环境命中。 3. 上线必须先部署 hl-user-service 再部署 order-v3,否则写动作全返 589507。 门的口径以 #7248/PR#7249 修正后为准(判四项 ready,不判团期状态)。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
这个提交包含在:
@@ -0,0 +1,471 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "7105"
|
||||
title: "团期合同保险面板与逐户出具"
|
||||
consumer: "admin"
|
||||
author: "jw(GIT)"
|
||||
change_type: "新增接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "pending"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "新增团期合同保险面板与逐户手动开、作废重开、批量催签四个端点。⚠️ 出具门依赖房/车/导/摄四项 ready,而其中 hotel_ready 目前线上无任何写入方(#4132 未完成),因此面板 issuable 恒为 false、三个写动作恒返 589548——前端可以先接,但在 #4132 落地前看不到出具效果。面板读与催签不受影响,已实测可用。"
|
||||
updated_at: "2026-09-07"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 团期「合同保险」面板:逐户出具、作废重开、批量催签
|
||||
|
||||
> **影响范围**:管理后台「团期详情 → 合同保险」Tab。
|
||||
> 当前状态:后端已部署 TEST 并实测;前端待接入。
|
||||
> **请先读下面的「当前不可用的部分」再排期。**
|
||||
|
||||
## ⚠️ 当前不可用的部分(重要)
|
||||
|
||||
出具的开闸条件是**房 / 车 / 导 / 摄四项资源全部就绪**。这四项里:
|
||||
|
||||
| 项 | 线上有没有写入方 |
|
||||
| --- | --- |
|
||||
| 导游 ready | ✅ 成团时若该团不需要导游即自动置位,需要时由人员配置置位 |
|
||||
| 摄影 ready | ✅ 同上 |
|
||||
| 车辆 ready | ✅ 车队服务整团配车完成后回填 |
|
||||
| **酒店 ready** | ❌ **没有**。回填它的那条链路挂在一个从未被发出的事件上,见 HL#4132 |
|
||||
|
||||
结果:**现网任何团期的四项都集不齐**,所以
|
||||
|
||||
- 面板的 `issuable` **恒为 false**
|
||||
- 手动开 / 作废重开 **恒返 589548**
|
||||
- 团期单的合同与保险**不会自动出具**
|
||||
|
||||
**前端可以照本文档接入,但在 HL#4132 落地前,出具相关的按钮点下去只会拿到 589548。**
|
||||
面板读取与批量催签**不受此限制**,现在就能用。
|
||||
|
||||
这一条是产品链路的既有缺口,不是本次交付的缺陷,但它决定了本功能何时真正可用,故写在最前面。
|
||||
|
||||
## 一、背景
|
||||
|
||||
此前团期没有合同保险的集中视图,管理员要逐户点进订单才能看到谁的合同出了、谁签了、谁的保险还没投;
|
||||
开错了也只能逐户处理,催签只有单张接口。本次补齐面板与三个批量动作。
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 1 | 合同保险面板 | GET | `/v3/admin/order/group-batch/{groupBatchId}/contracts` | 新增接口 | 汇总计数 + 逐户三态明细;权限码 `group-batch:view` |
|
||||
| 2 | 手动开合同/保险 | POST | `/v3/admin/order/group-batch/{groupBatchId}/contracts/issue` | 新增接口 | 逐户补开;权限码 `group-batch:contract:issue` |
|
||||
| 3 | 作废重开 | POST | `/v3/admin/order/group-batch/{groupBatchId}/contracts/reissue` | 新增接口 | 先作废后重开;权限码 `group-batch:contract:issue` |
|
||||
| 4 | 批量催签合同 | POST | `/v3/admin/order/group-batch/{groupBatchId}/contracts/remind-sign` | 新增接口 | 只对已出未签的户发短信;权限码 `group-batch:contract:issue` |
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 合同保险面板 `GET /v3/admin/order/group-batch/{groupBatchId}/contracts`
|
||||
|
||||
**VO**: `GroupBatchContractBoardVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
进入团期详情的「合同保险」Tab 时调用,一次拿到整团的汇总计数与逐户明细。
|
||||
整页只发 3 次底层查询,不随户数放大。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| groupBatchId | path | Long | 是 | 正整数 | 团期 ID |
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| issuable | Boolean | 当前是否允许出具(四项已配齐)。**现网恒为 false,见文首说明** |
|
||||
| batchStatus | String | 团期状态 |
|
||||
| totalCount | Integer | 活跃子订单户数(已取消的户不计) |
|
||||
| contractIssuedCount | Integer | 合同已出户数 |
|
||||
| contractSignedCount | Integer | 合同已签户数 |
|
||||
| insuranceIssuedCount | Integer | 保险已出户数 |
|
||||
| contractWrongCount | Integer | 合同开错户数 |
|
||||
| insuranceWrongCount | Integer | 保险开错户数 |
|
||||
| items[].contractState | String | 三态:NOT_ISSUED / ISSUED / WRONG |
|
||||
| items[].contractStateText | String | 三态中文名,服务端给出,前端直接显示 |
|
||||
| items[].awaitingSign | Boolean | 已出未签,即催签的目标 |
|
||||
| items[].contractUrl | String | 合同文件地址,未出为 null |
|
||||
| items[].policyNo | String | 保单号,未出为 null |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/group-batch/2096412454643802114/contracts
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"batchId": "2096412454643802114",
|
||||
"issuable": false,
|
||||
"batchStatus": "RESOURCE_PREPARING",
|
||||
"totalCount": 55,
|
||||
"contractIssuedCount": 0,
|
||||
"contractSignedCount": 0,
|
||||
"insuranceIssuedCount": 0,
|
||||
"contractWrongCount": 0,
|
||||
"insuranceWrongCount": 0,
|
||||
"items": [
|
||||
{
|
||||
"orderId": "2096633951715033089",
|
||||
"orderNo": "HL20260907001741975",
|
||||
"contactName": "蒋岚妍",
|
||||
"contractState": "NOT_ISSUED",
|
||||
"contractStateText": "未出",
|
||||
"contractStatus": null,
|
||||
"contractPlanName": null,
|
||||
"contractSignedAt": null,
|
||||
"contractUrl": null,
|
||||
"awaitingSign": false,
|
||||
"insuranceState": "NOT_ISSUED",
|
||||
"insuranceStateText": "未出",
|
||||
"insuranceStatus": null,
|
||||
"insuranceProductName": null,
|
||||
"policyNo": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
团期尚无子订单时 `totalCount` 为 0、`items` 为空数组,各计数器均为 0,页面展示空态。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 589507,
|
||||
"message": "无操作权限(非团期管理员 / 非本定制师名下)",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **已取消的户不出现在面板里**,也不计入任何计数器。
|
||||
- 三态不落库,全部由既有的合同状态与保险状态派生;后端给出中文名,前端不要自行映射。
|
||||
- 读面板走只读权限,不需要出具权限。
|
||||
|
||||
### 2. 手动开合同/保险 `POST /v3/admin/order/group-batch/{groupBatchId}/contracts/issue`
|
||||
|
||||
**VO**: `GroupBatchContractIssueReqVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
四项资源配齐后,为漏开的户补开合同与旅行意外险。正常情况下系统会自动出具,
|
||||
本接口是补救通路。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| groupBatchId | path | Long | 是 | 正整数 | 团期 ID |
|
||||
| target | body | String | 是 | CONTRACT / INSURANCE / BOTH | 本次要开什么 |
|
||||
| orderIds | body | String[] | 否 | 子订单 ID 列表 | 不传表示整团所有活跃户 |
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| totalCount | Integer | 本次处理户数 |
|
||||
| successCount | Integer | 成功户数 |
|
||||
| failCount | Integer | 失败户数 |
|
||||
| skipCount | Integer | 跳过户数(已出具无需重复开) |
|
||||
| results[].outcome | String | SUCCESS / FAILED / SKIPPED |
|
||||
| results[].message | String | 失败或跳过的原因,成功为 null |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"target": "BOTH",
|
||||
"orderIds": ["2096633951715033089", "2096633958908264449"]
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"totalCount": 2,
|
||||
"successCount": 1,
|
||||
"failCount": 1,
|
||||
"skipCount": 0,
|
||||
"results": [
|
||||
{ "orderId": "2096633951715033089", "target": "CONTRACT", "outcome": "SUCCESS", "message": null },
|
||||
{ "orderId": "2096633958908264449", "target": "INSURANCE", "outcome": "FAILED", "message": "保险下单失败,请稍后重试" }
|
||||
]
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
指定的户都已出具时全部记 SKIPPED,`successCount` 为 0,属正常情况不是错误。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 589548,
|
||||
"message": "房/车/导/摄四项配齐后才能出具合同与保险",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **逐户独立事务**:单户失败不回滚已成功的户,也不中断后续户,前端按 `results` 逐行展示。
|
||||
- **四项未配齐整单拒绝**,返回 589548 且零写入——不是部分成功。
|
||||
- 五秒内重复提交会被幂等窗口拦下,返回「出具处理中,请勿重复提交」。
|
||||
- 同一团期的出具动作串行执行,并发点击不会重复开。
|
||||
|
||||
### 3. 作废重开 `POST /v3/admin/order/group-batch/{groupBatchId}/contracts/reissue`
|
||||
|
||||
**VO**: `GroupBatchContractIssueReqVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
合同方案选错、保险投保信息有误等「开错」情形:先作废原件,再按同一条链路重新开具。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| groupBatchId | path | Long | 是 | 正整数 | 团期 ID |
|
||||
| target | body | String | 是 | CONTRACT / INSURANCE / BOTH | 本次要重开什么 |
|
||||
| orderIds | body | String[] | 否 | 子订单 ID 列表 | 不传表示整团所有活跃户 |
|
||||
| reason | body | String | 否 | 最长 512 字 | 作废原因,写入操作记录 |
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| totalCount | Integer | 本次处理户数 |
|
||||
| successCount | Integer | 作废并重开成功的户数 |
|
||||
| failCount | Integer | 失败户数(含作废失败与重开失败,`message` 区分) |
|
||||
| skipCount | Integer | 跳过户数(该户没有可作废的单据) |
|
||||
| results[].orderId | String | 子订单 ID |
|
||||
| results[].target | String | CONTRACT / INSURANCE |
|
||||
| results[].outcome | String | SUCCESS / FAILED / SKIPPED |
|
||||
| results[].message | String | 失败或跳过的原因,成功为 null |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"target": "CONTRACT",
|
||||
"orderIds": ["2096633951715033089"],
|
||||
"reason": "合同方案选错"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"totalCount": 1,
|
||||
"successCount": 1,
|
||||
"failCount": 0,
|
||||
"skipCount": 0,
|
||||
"results": [
|
||||
{ "orderId": "2096633951715033089", "target": "CONTRACT", "outcome": "SUCCESS", "message": null }
|
||||
]
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
指定的户没有可作废的单据时记 SKIPPED,不产生任何写入。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 589550,
|
||||
"message": "无可用合同方案,无法出具合同",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **作废失败不进重开**:该户直接记 FAILED,绝不会出现「原件作废了但新件没开出来」的空档。
|
||||
- 与「手动开」共用同一道四项配齐门,未配齐同样返 589548。
|
||||
- 幂等窗口与串行锁同「手动开」。
|
||||
|
||||
### 4. 批量催签合同 `POST /v3/admin/order/group-batch/{groupBatchId}/contracts/remind-sign`
|
||||
|
||||
**VO**: `GroupBatchContractRemindReqVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
合同已出但客户迟迟不签,管理员一次性给这些户重发签署短信。
|
||||
**本接口不受四项配齐门限制,现在就能用。**
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| groupBatchId | path | Long | 是 | 正整数 | 团期 ID |
|
||||
| orderIds | body | String[] | 否 | 子订单 ID 列表 | 不传表示整团所有活跃户 |
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| totalCount | Integer | 本次处理户数 |
|
||||
| successCount | Integer | 实际发出短信的户数 |
|
||||
| skipCount | Integer | 跳过户数 |
|
||||
| results[].message | String | 跳过原因 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"orderIds": ["2096633951715033089", "2096633958908264449"]
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"totalCount": 2,
|
||||
"successCount": 0,
|
||||
"failCount": 0,
|
||||
"skipCount": 2,
|
||||
"results": [
|
||||
{ "orderId": "2096633951715033089", "target": "CONTRACT", "outcome": "SKIPPED", "message": "该户合同未出具或已签署,无需催签" },
|
||||
{ "orderId": "2096633958908264449", "target": "CONTRACT", "outcome": "SKIPPED", "message": "该户合同未出具或已签署,无需催签" }
|
||||
]
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
无「已出未签」的户时全部 SKIPPED,`successCount` 为 0,不发任何短信。这是正常结果。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 589507,
|
||||
"message": "无操作权限(非团期管理员 / 非本定制师名下)",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **只对「已出未签」的户发**:未出具的、已签署的一律跳过并说明原因。
|
||||
- 单户发送失败不影响其他户,逐户记 FAILED。
|
||||
- 只要真发出了至少一条,就写一条团期操作记录。
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
- **出具按钮的可用性以面板返回的 `issuable` 为准**,不要前端自行按团期状态推断。
|
||||
- **三个写动作共用一个权限码** `group-batch:contract:issue`,读面板走 `group-batch:view`。
|
||||
- `orderIds` 不传即整团,传了就只处理这几户;两种用法都返回逐户结果。
|
||||
- **逐户结果必须逐行展示**:这三个接口都可能部分成功,只看 `code=200` 会误导操作者。
|
||||
- 三态文案用后端返回的 `contractStateText` / `insuranceStateText`,不要前端自己映射枚举。
|
||||
- 出具与重开有五秒幂等窗口,前端做防抖即可。
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
- 面板读取**零写入**。
|
||||
- 手动开 / 重开**逐户独立事务**:每户单独提交,失败的户不回滚成功的户。
|
||||
- 四项未配齐时在任何写操作之前拒绝,**零写入**。
|
||||
- 重开时先作废后开具,作废失败即停在该户,不会留下「已作废未重开」的中间态。
|
||||
- 出具、重开、催签各写一条团期操作记录;催签仅在真发出短信时才写。
|
||||
- 无权限、幂等拦截、参数非法一律零写入。
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 已取消的户不参与任何动作,也不出现在面板。
|
||||
- 指定的户不属于本团期时按跳过处理,不整单失败。
|
||||
- 同团期的出具动作串行,避免并发重复开具。
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **自动出具链路新增了同一道门**:团期单在四项配齐前不再自动出具合同与保险,
|
||||
未配齐时静默跳过(不建待办、不发失败通知)。散客单不受影响。
|
||||
- **上线依赖部署顺序**:`group-batch:contract:issue` 权限码由用户服务的迁移注册并授予
|
||||
管理员与超级管理员,**必须先部署用户服务再部署订单服务**,否则三个写动作除超级管理员外全返 589507。
|
||||
- **本功能当前实际不可用**,原因见文首:酒店就绪标志线上无写入方(HL#4132)。
|
||||
- 面板读与批量催签不受上述限制。
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- 散客单(非团期单)的合同保险出具完全不变。
|
||||
- 单张合同的出具、签署、作废接口不变,本次只加团期级的批量入口。
|
||||
- 小程序端不受影响。
|
||||
- 团期成团、名额调整、流团审批等其他动作不受影响。
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
TEST 环境已部署用户服务与订单服务并实测:
|
||||
|
||||
- 面板读取:55 户的团期返回 55 行逐户明细,计数器与 `issuable` 齐全。
|
||||
- 空团期读面板:户数 0、明细空数组,不报错。
|
||||
- 四项未配齐时手动开 → 589548,零写入。
|
||||
- 四项未配齐时作废重开 → 589548,与手动开同一道门。
|
||||
- 出具目标非法时同样先被四项配齐门拦下(门先于目标校验)。
|
||||
- 批量催签指定两户 → 均跳过并给出「该户合同未出具或已签署,无需催签」,零副作用。
|
||||
- 权限码已生效:读面板通过、三个写动作走到业务校验而非 589507,
|
||||
说明权限已注册并授予管理员。
|
||||
|
||||
### 尚未验证
|
||||
|
||||
- **出具成功的正向路径未在网关侧实测**:需要四项配齐的团期,而酒店就绪标志线上无写入方(HL#4132),
|
||||
该状态在测试环境造不出来。出具成功、逐户独立事务、作废失败不进重开等行为由单元测试覆盖(60 例全绿),
|
||||
但**未经真实环境验证**,前端联调时请以实际返回为准。
|
||||
- 出具目标非法的错误码 589549 因被四项配齐门先拦,同样未在真实环境命中。
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 工单:HL#7105
|
||||
- 合并:HL PR#7243
|
||||
- 后续修正:HL#7248 / PR#7249——出具门原先按团期状态判定,与「合同全签才能进入物资准备」
|
||||
这道既有门形成死锁,已改为直接判四项就绪标志。本文档描述的是修正后的口径。
|
||||
- 阻塞项:HL#4132——团期基准变更事件未发出,导致酒店就绪标志无写入方
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
- 后端:jw
|
||||
- 前端待接:合同保险 Tab(汇总计数 + 逐户表格 + 三态徽标)、
|
||||
手动开与作废重开弹窗(目标选择、可选择户、逐户结果展示)、批量催签、
|
||||
出具类按钮按 `issuable` 置灰并提示原因
|
||||
在新工单中引用
屏蔽一个用户