feat: 合同管理完整对接方案

19个接口详细定义 + 创建合同请求字段 + 状态流转 + 页面布局 + 数据预填逻辑 + 调用示例

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
这个提交包含在:
API Changelog Bot 2026-03-19 17:53:04 +08:00
父节点 b632390c8b
当前提交 b8a7f4c16c

查看文件

@ -0,0 +1,678 @@
# 合同管理完整对接方案
> 日期: 2026-03-19 | 服务: hl-contract-service
---
## 一、功能说明
订单确认后,定制师需要为订单签订旅游合同。支持两种模式:
- **标准模式STANDARD**:电子签,平台自动发送签署短信给出行人,出行人在线签署
- **同步模式SYNC**线下签,管理员上传已签署的PDF文件
### 合同状态流转
```
标准模式(电子签):
创建 → GENERATED(已生成) → SIGNING(签署中) → SIGNED(已签署)
VOIDED(已作废) ← VOIDING(作废中)
同步模式(线下签):
创建 → REPORTED(已上报) → UPLOADED(已上传PDF)
VOIDED(已作废) ← VOIDING(作废中)
```
### 关联字典
**`contract_status`(合同状态)**
| dict_value | dict_label |
|-----------|-----------|
| PENDING | 待生成 |
| GENERATED | 已生成 |
| SIGNING | 签署中 |
| SIGNED | 已签署 |
| REPORTED | 已上报 |
| UPLOADED | 已上传 |
| VOIDING | 作废中 |
| VOIDED | 已作废 |
**`contract_platform`(签约平台)**
| dict_value | dict_label |
|-----------|-----------|
| 12301 | 全国旅游监管平台 |
| FADADA | 法大大 |
**`contract_type`(合同类型)**
| dict_value | dict_label |
|-----------|-----------|
| TOUR | 旅游合同 |
| INSURANCE | 保险单 |
---
## 二、接口清单
| # | 方法 | 路径 | 说明 | 使用场景 |
|---|------|------|------|---------|
| 1 | POST | `/admin/contract/create` | 创建电子签合同 | 订单详情 → 去签合同 |
| 2 | POST | `/admin/contract/report` | 报备合同(线下签) | 订单详情 → 线下签合同 |
| 3 | GET | `/admin/contract/list` | 合同列表(分页) | 合同管理页 |
| 4 | GET | `/admin/contract/{id}` | 合同详情 | 合同详情页 |
| 5 | GET | `/admin/contract/by-order/{orderId}` | 按订单查合同 | 订单详情 → 合同信息 |
| 6 | GET | `/admin/contract/active-by-order/{orderId}` | 订单有效合同 | 订单详情 → 当前合同 |
| 7 | GET | `/admin/contract/{id}/status` | 刷新合同状态 | 合同详情 → 刷新 |
| 8 | POST | `/admin/contract/{id}/invalidate` | 作废合同 | 合同详情 → 作废 |
| 9 | POST | `/admin/contract/{id}/resend-sms` | 重发签署短信 | 合同详情 → 重发 |
| 10 | POST | `/admin/contract/{id}/upload-pdf` | 上传签署PDF | 同步模式 → 上传 |
| 11 | GET | `/admin/contract/templates` | 合同模板列表 | 创建合同弹窗 → 模板选择 |
| 12 | GET | `/admin/contract/platforms` | 可用平台列表 | 创建合同弹窗 → 平台选择 |
| 13 | GET | `/admin/contract/agencies` | 旅行社列表 | 创建合同弹窗 → 旅行社选择 |
| 14 | GET | `/admin/contract/clause-template/list` | 补充约定模板 | 创建合同弹窗 → 补充约定 |
### 补充约定模板管理
| # | 方法 | 路径 | 说明 |
|---|------|------|------|
| 15 | GET | `/admin/contract/clause-template/list-all` | 全部模板(含停用) |
| 16 | POST | `/admin/contract/clause-template` | 创建模板 |
| 17 | PUT | `/admin/contract/clause-template/{id}` | 更新模板 |
| 18 | PUT | `/admin/contract/clause-template/{id}/toggle-status` | 启用/停用 |
| 19 | DELETE | `/admin/contract/clause-template/{id}` | 删除模板 |
---
## 三、核心接口详细定义
### 1. 创建电子签合同(标准模式)
**接口**`POST /admin/contract/create`
**使用场景**:订单详情页点击「去签合同」,弹窗填写信息后提交
**请求体**
```json
{
"orderId": "2031625428892893186",
"contractType": "TOUR",
"templateCode": "TOURAGE_STANDARD",
"destination": "哈尔滨-亚布力-雪乡",
"routeName": "嗨·冰雪3.0南线6天5晚",
"departureDate": "2026-03-11",
"returnDate": "2026-03-16",
"days": 6,
"nights": 5,
"departureCity": "哈尔滨",
"signatoryName": "张三",
"signatoryPhone": "13800138000",
"signatoryIdNumber": "110101199001011234",
"signatoryMode": 1,
"contactName": "OOPS",
"contactPhone": "17600545225",
"adultCost": 5700.00,
"childCost": 0,
"totalAmount": 5700.00,
"paymentMethod": 3,
"disputeResolution": 2,
"supplementaryClause": "冬季出行补充约定...",
"travelers": [
{
"name": "张三",
"idCardNo": "110101199001011234",
"phone": "13800138000",
"idCardType": 1,
"isSigner": true,
"isChild": false
}
]
}
```
**请求字段说明**
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| orderId | String | 是 | 订单ID |
| contractType | String | 否 | 合同类型,默认 `TOUR`。可选:`TOUR`(旅游合同)/ `INSURANCE`(保险单) |
| templateCode | String | 是 | 模板编码,从模板列表接口获取 |
| platform | String | 否 | 平台,不传则由模板自动确定 |
| agencyCode | String | 否 | 旅行社编号,不传用系统默认值 |
| destination | String | 是 | 目的地 |
| routeName | String | 是 | 线路名称(产品名称) |
| departureDate | String | 是 | 出发日期,格式 `yyyy-MM-dd` |
| returnDate | String | 是 | 返回日期,格式 `yyyy-MM-dd` |
| days | Integer | 否 | 行程天数(不传自动计算) |
| nights | Integer | 否 | 住宿晚数(不传自动计算) |
| departureCity | String | 否 | 出发城市 |
| signatoryName | String | 是 | **签署人姓名**(出行人中 isSigner=true 的那位) |
| signatoryPhone | String | 是 | **签署人电话**(平台发短信用) |
| signatoryIdNumber | String | 是 | **签署人证件号码** |
| signatoryIdType | Integer | 否 | 签署人证件类型1=身份证(默认)|
| signatoryMode | Integer | 否 | 签署模式1=短信签署默认2=现场 3=线下 |
| contactName | String | 是 | 联系人姓名(可与签署人不同) |
| contactPhone | String | 是 | 联系人电话 |
| adultCost | Number | 是 | 成人费用(单价) |
| childCost | Number | 否 | 儿童费用(单价) |
| totalAmount | Number | 是 | 合同总金额 |
| paymentMethod | Integer | 否 | 付款方式1=现金 2=转账 3=在线支付默认2 |
| disputeResolution | Integer | 否 | 争议解决1=仲裁 2=诉讼默认2 |
| leastCustomerNumber | Integer | 否 | 最低成团人数默认1 |
| supplementaryClause | String | 否 | 补充约定内容 |
| travelers | Array | 是 | 出行人列表,至少1人 |
**出行人travelers字段**
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| name | String | 是 | 姓名 |
| idCardNo | String | 是 | 证件号码 |
| phone | String | 签署人必填 | 手机号(签署人需要接收短信) |
| idCardType | Integer | 否 | 证件类型1=身份证默认2=护照 |
| isSigner | Boolean | 否 | 是否签署人默认false,至少1人为true |
| isChild | Boolean | 否 | 是否儿童默认false |
| gender | String | 否 | 性别male/female |
| age | Integer | 否 | 年龄 |
**响应示例**
```json
{
"code": 200,
"message": "成功",
"data": {
"contractId": "2035000000000000001",
"orderId": "2031625428892893186",
"templateCode": "TOURAGE_STANDARD",
"templateName": "12301标准模板",
"contractNumber": "CN20260319001",
"platform": "12301",
"contractType": "TOUR",
"mode": "STANDARD",
"status": "GENERATED",
"statusLabel": "已生成",
"signUrl": "https://dz.12301.cn/sign/xxxxx",
"qrCodeUrl": "https://dz.12301.cn/qr/xxxxx",
"fileUrl": null,
"agencyCode": "L-FJ-100005",
"travelAgencyName": "博华(厦门)国际旅行社有限公司",
"destination": "哈尔滨-亚布力-雪乡",
"departureDate": "2026-03-11",
"returnDate": "2026-03-16",
"totalAmount": 5700.00,
"touristCount": 1,
"contactName": "OOPS",
"contactPhone": "17600545225",
"createTime": "2026-03-19 17:30:00",
"supplementaryClause": "冬季出行补充约定...",
"travelers": [
{
"travelerId": "2035100000000000001",
"name": "张三",
"idCardType": "ID_CARD",
"idCardNo": "1101****1234",
"phone": "138****8000",
"isSigner": true
}
],
"statusLogs": [
{
"logId": "2035200000000000001",
"oldStatus": "PENDING",
"newStatus": "GENERATED",
"source": "MANUAL",
"createTime": "2026-03-19 17:30:00"
}
]
}
}
```
**业务规则**
- 同一订单同一类型TOUR/INSURANCE只能有一个有效合同,创建前会校验
- 如果订单已有有效的 TOUR 合同,需先作废再创建新的
- 创建成功后自动完成订单的 CONTRACT 待办
---
### 2. 报备合同(同步模式 / 线下签)
**接口**`POST /admin/contract/report`
**使用场景**:线下已签署纸质合同,需在系统中报备
**请求体**:与创建合同相同,区别:
- `templateCode` 使用同步模式模板(如 `TOURAGE_SYNC`
- `signatoryMode` 默认为 2现场
- 创建后状态直接为 `REPORTED`(已上报),无需等待签署
**后续步骤**创建后需上传已签署的PDF → 调用上传接口
---
### 3. 合同列表(分页)
**接口**`GET /admin/contract/list`
**使用场景**:合同管理页面
**请求参数**Query
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| orderId | String | 否 | 按订单ID筛选 |
| status | String | 否 | 按状态筛选(如 `SIGNED` |
| platform | String | 否 | 按平台筛选(如 `12301` |
| page | Integer | 否 | 页码默认1 |
| pageSize | Integer | 否 | 每页条数默认20,最大100 |
**响应示例**
```json
{
"code": 200,
"message": "成功",
"data": {
"list": [
{
"contractId": "2035000000000000001",
"orderId": "2031625428892893186",
"contractNumber": "CN20260319001",
"platform": "12301",
"contractType": "TOUR",
"mode": "STANDARD",
"status": "SIGNED",
"statusLabel": "已签署",
"signUrl": "https://...",
"fileUrl": "https://pdf.12301.cn/CN20260319001.pdf",
"destination": "哈尔滨-亚布力-雪乡",
"departureDate": "2026-03-11",
"returnDate": "2026-03-16",
"totalAmount": 5700.00,
"touristCount": 1,
"contactName": "OOPS",
"contactPhone": "17600545225",
"createTime": "2026-03-19 17:30:00"
}
],
"total": 1,
"page": 1,
"pageSize": 20
}
}
```
---
### 4. 合同详情
**接口**`GET /admin/contract/{id}`
**路径参数**`id` = 合同ID
**响应**:返回 `ContractDetailVO`(比列表多 `travelers``statusLogs``supplementaryClause`),结构同创建合同的响应。
---
### 5. 按订单查合同
**接口**`GET /admin/contract/by-order/{orderId}`
**说明**:返回该订单所有合同(含已作废),按创建时间倒序
**响应**`List<ContractVO>`
---
### 6. 获取订单有效合同
**接口**`GET /admin/contract/active-by-order/{orderId}`
**说明**:返回最新的非作废合同,如果没有返回 null
**响应**`ContractVO``null`
---
### 7. 刷新合同状态
**接口**`GET /admin/contract/{id}/status`
**说明**:主动从签约平台同步最新状态(用于回调未及时到达的情况)
**响应**`ContractVO`(包含最新状态)
---
### 8. 作废合同
**接口**`POST /admin/contract/{id}/invalidate`
**请求体**:无
**响应**`ContractVO`(状态变为 `VOIDING``VOIDED`
**注意**:作废不可恢复,需要新签则重新创建
---
### 9. 重发签署短信
**接口**`POST /admin/contract/{id}/resend-sms`
**说明**:仅 `SIGNING` 状态可用,重新发送签署链接短信给签署人
**响应**`Boolean`true=发送成功)
---
### 10. 上传签署PDF
**接口**`POST /admin/contract/{id}/upload-pdf`
**说明**同步模式专用,上传线下签署的合同PDF
**请求**`multipart/form-data`,字段名 `file`
**响应**`ContractVO`(状态变为 `UPLOADED`
---
### 11. 合同模板列表
**接口**`GET /admin/contract/templates`
**响应示例**
```json
{
"code": 200,
"message": "成功",
"data": [
{
"templateId": "1001",
"templateCode": "TOURAGE_STANDARD",
"templateName": "12301标准电子签模板",
"platform": "12301",
"mode": "STANDARD",
"status": "ACTIVE",
"description": "全国旅游监管平台标准电子签"
},
{
"templateId": "1002",
"templateCode": "TOURAGE_SYNC",
"templateName": "12301同步报备模板",
"platform": "12301",
"mode": "SYNC",
"status": "ACTIVE",
"description": "线下签署后同步报备到12301"
}
]
}
```
**模板字段说明**
| 字段 | 类型 | 说明 |
|------|------|------|
| templateId | String | 模板ID |
| templateCode | String | **创建合同时传这个值** |
| templateName | String | 模板名称(下拉框显示) |
| platform | String | 所属平台 |
| mode | String | `STANDARD`=电子签 / `SYNC`=线下签 |
| status | String | `ACTIVE`=启用 / `INACTIVE`=停用 |
---
### 12. 补充约定模板列表
**接口**`GET /admin/contract/clause-template/list`
**说明**:获取启用的补充约定模板,用于创建合同时选择/预填
**响应示例**
```json
{
"code": 200,
"message": "成功",
"data": [
{
"templateId": "2001",
"name": "冬季出行补充约定",
"content": "1. 冬季道路可能存在积雪结冰,行程可能适当调整...\n2. ...",
"sortOrder": 1,
"status": "ACTIVE"
},
{
"templateId": "2002",
"name": "高原出行补充约定",
"content": "1. 高原地区海拔较高,请自备抗高反药物...",
"sortOrder": 2,
"status": "ACTIVE"
}
]
}
```
---
## 四、前端页面布局
### 1. 订单详情 → 签合同入口
在订单待办面板的 CONTRACT 待办项,按钮为「去签合同」:
```
┌──────────────────────────────────────────────┐
│ 待办事项 │
│ │
│ ✅ 确认订单 已完成 │
│ ✅ 配置保险 已完成 │
│ ⏳ 签订合同 [ 去签合同 ] │
│ ... │
└──────────────────────────────────────────────┘
```
点击「去签合同」→ 打开创建合同弹窗/抽屉
### 2. 创建合同弹窗
```
┌─────────────────────────────────────────────────────┐
│ 创建旅游合同 │
│ │
│ 签约模板 [▼ 12301标准电子签模板 ] │
│ 选项来源: GET /admin/contract/templates │
│ │
│ ─── 行程信息(从订单自动填充) ─── │
│ 目的地 [ 哈尔滨-亚布力-雪乡 ] │
│ 线路名称 [ 嗨·冰雪3.0南线6天5晚 ] │
│ 出发日期 [ 2026-03-11 ] │
│ 返回日期 [ 2026-03-16 ] │
│ 出发城市 [ 哈尔滨 ] │
│ │
│ ─── 费用信息(从订单自动填充) ─── │
│ 成人费用 [ 5700.00 ] │
│ 儿童费用 [ 0 ] │
│ 总金额 [ 5700.00 ] │
│ 付款方式 [▼ 在线支付 ] │
│ │
│ ─── 签署人信息(从出行人选择) ─── │
│ 签署人 [▼ 张三 - 110101****1234 ] │
│ 选择后自动填充姓名/电话/证件号 │
│ │
│ ─── 联系人信息(从订单自动填充) ─── │
│ 联系人 [ OOPS ] │
│ 联系电话 [ 17600545225 ] │
│ │
│ ─── 补充约定(可选) ─── │
│ 快捷选择 [▼ 冬季出行补充约定 ] │
│ 选项来源: GET /clause-template/list │
│ 约定内容 [ 多行文本框,选模板后自动填充 ] │
│ │
│ ─── 出行人列表(从订单出行人自动加载) ─── │
│ ☑ 张三 身份证 110101****1234 13800138000 [签署人] │
│ │
│ [ 取消 ] [ 创建合同 ] │
└─────────────────────────────────────────────────────┘
```
### 3. 合同管理列表页
```
┌────────────────────────────────────────────────────────────┐
│ 合同管理 │
│ │
│ 状态 [▼ 全部] 平台 [▼ 全部] [ 搜索 ] [ 重置 ] │
│ │
│ 合同编号 订单号 产品 状态 操作 │
│ CN20260319001 HL2026031114... 冰雪南线 已签署 详情 │
│ CN20260318005 HL2026031800... 海岛亲子 签署中 详情 重发短信 │
│ CN20260317002 HL2026031700... 冰雪北线 已作废 详情 │
└────────────────────────────────────────────────────────────┘
```
### 4. 合同详情页
```
┌──────────────────────────────────────────────────────┐
│ 合同详情 │
│ │
│ 合同编号: CN20260319001 平台: 12301 │
│ 状态: 已签署 ✅ 模式: 电子签 │
│ 目的地: 哈尔滨-亚布力-雪乡 │
│ 出发: 2026-03-11 → 返回: 2026-03-16 │
│ 总金额: ¥5,700.00 出行人数: 1 │
│ │
│ ─── 出行人 ─── │
│ 张三 身份证 110101****1234 ⭐签署人 │
│ │
│ ─── 操作按钮(按状态显示) ─── │
│ [查看签署链接] [重发短信] [刷新状态] [查看PDF] [作废] │
│ │
│ ─── 状态日志 ─── │
│ 2026-03-19 17:30 PENDING → GENERATED (创建) │
│ 2026-03-19 17:35 GENERATED → SIGNING (回调) │
│ 2026-03-19 17:40 SIGNING → SIGNED (回调) │
└──────────────────────────────────────────────────────┘
```
### 5. 操作按钮显示规则
| 状态 | 可用操作 |
|------|---------|
| GENERATED | 查看签署链接、刷新状态、作废 |
| SIGNING | 重发短信、刷新状态、作废 |
| SIGNED | 查看PDF、作废 |
| REPORTED | 上传PDF、作废 |
| UPLOADED | 查看PDF、作废 |
| VOIDING | 刷新状态 |
| VOIDED | 无操作 |
---
## 五、数据预填逻辑
创建合同弹窗打开时,大部分字段可从订单详情自动填充:
| 合同字段 | 来源 | 说明 |
|---------|------|------|
| orderId | 当前订单 | 自动 |
| routeName | order.productName | 产品名称 |
| destination | order.destination 或从行程中提取 | 可能需要手动填 |
| departureDate | order.departureDate | 出发日期 |
| returnDate | 计算departureDate + tripDays - 1 | 自动计算 |
| days | order.tripDays | 行程天数 |
| nights | tripDays - 1 | 自动计算 |
| contactName | order.contactName | 联系人 |
| contactPhone | order.contactPhone | 联系电话 |
| totalAmount | order.totalPrice | 订单总价 |
| adultCost | order.totalPrice / adultCount | 参考值,可修改 |
| travelers | 从 `GET /admin/order/{orderId}/travelers` 获取 | 自动加载 |
| signatoryName/Phone/IdNumber | 从出行人中选择签署人 | 用户选择 |
---
## 六、完整调用示例
```javascript
import { http } from '@/utils/request'
// 1. 获取模板列表(创建弹窗打开时)
const templates = await http.get('/contract/templates')
// templates = [{ templateCode: 'TOURAGE_STANDARD', templateName: '12301标准电子签模板', ... }]
// 2. 获取补充约定模板(可选)
const clauses = await http.get('/contract/clause-template/list')
// clauses = [{ name: '冬季出行补充约定', content: '...', ... }]
// 3. 创建电子签合同
const contract = await http.post('/contract/create', {
orderId: currentOrder.orderId,
templateCode: 'TOURAGE_STANDARD',
destination: '哈尔滨-亚布力-雪乡',
routeName: currentOrder.productName,
departureDate: currentOrder.departureDate,
returnDate: '2026-03-16',
contactName: currentOrder.contactName,
contactPhone: currentOrder.contactPhone,
signatoryName: selectedTraveler.name,
signatoryPhone: selectedTraveler.phone,
signatoryIdNumber: selectedTraveler.idCardNo,
adultCost: 5700.00,
totalAmount: currentOrder.totalPrice,
supplementaryClause: selectedClause?.content || null,
travelers: orderTravelers.map(t => ({
name: t.name,
idCardNo: t.idCardNo,
phone: t.phone,
idCardType: 1,
isSigner: t.travelerId === selectedSignerId
}))
})
// contract.signUrl → 签署链接(可复制/展示二维码给出行人)
// contract.status → 'GENERATED'
// 4. 查看订单的有效合同
const activeContract = await http.get(`/contract/active-by-order/${orderId}`)
// 5. 刷新合同状态(轮询或手动刷新)
const updated = await http.get(`/contract/${contractId}/status`)
// 6. 重发签署短信
await http.post(`/contract/${contractId}/resend-sms`)
// 7. 作废合同
await http.post(`/contract/${contractId}/invalidate`)
// 8. 上传PDF同步模式
const formData = new FormData()
formData.append('file', pdfFile)
await http.post(`/contract/${contractId}/upload-pdf`, formData)
// 9. 合同列表(合同管理页)
const list = await http.get('/contract/list', {
params: { page: 1, pageSize: 20, status: 'SIGNED' }
})
```
---
## 七、与订单待办的关联
1. 合同创建成功后,系统**自动完成**订单的 `CONTRACT` 待办
2. 前端点击订单待办的「去签合同」按钮时:
- 先检查 `GET /admin/contract/active-by-order/{orderId}` 是否已有合同
- 有 → 跳转合同详情
- 无 → 打开创建合同弹窗
3. 合同作废后,如果订单流程需要重签,会重新生成 `CONTRACT` 待办
---
## 八、注意事项
- 同一订单的 TOUR 和 INSURANCE 合同互相独立,可各有一份有效合同
- 出行人变更(增删改)会自动级联作废已有 TOUR 合同
- 签署链接(`signUrl`)有有效期,过期需重发短信(`resend-sms`
- 合同PDF`fileUrl`)签署完成后才有值
- 12301平台通过回调自动推送状态变更,前端也可手动刷新`/{id}/status`