Files
leaudit-platform-frontend/auth_doc/交叉评查接口文档(1).md
T
LiangShiyong d8bba607fc fix: 1. 重新对齐交叉评查的接口。
2. 确认评查结果的接口对接。 3. 新增评查点适配省级创建的响应数据和其他用户创建的单条响应数据。  4. 文档列表的文档类型通过通用的查询接口查询文档类型。优化加载状态的时机。
2025-12-11 11:16:50 +08:00

1081 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 交叉评查接口文档
> 本文档描述交叉评查模块的所有API接口,供前端直接对接使用。
## 基础信息
- **基础路径**: `/api/v2/cross_review` (推荐)
- **备用路径**: `/admin/v2/cross_review` (兼容,功能相同)
- **认证方式**: JWT Token (Header: `Authorization: Bearer <token>`)
- **Content-Type**: `application/json`
> **说明**: `/api/v2` 和 `/admin/v2` 两个前缀指向同一套接口,功能完全相同。建议前端统一使用 `/api/v2/cross_review`。
---
## 接口总览
### 提案管理
| 方法 | 路径 | 接口名称 | 权限 |
|------|------|----------|------|
| `POST` | `/proposals` | 发起评分提案 | `cross_review:proposal:create` |
| `DELETE` | `/proposals/{proposal_id}` | 撤销评分提案 | `cross_review:proposal:delete` |
| `POST` | `/proposals/{proposal_id}/votes` | 对提案投票 | `cross_review:proposal:vote` |
| `POST` | `/proposals/details` | 获取提案列表及详情 | `cross_review:proposal:read` |
| `POST` | `/proposals/document` | 获取指定文档的提案列表 | `cross_review:proposal:read` |
| `POST` | `/proposals/document/check_pending_votes` | 检查未投票用户 | `cross_review:task:read` |
### 任务管理
| 方法 | 路径 | 接口名称 | 权限 |
|------|------|----------|------|
| `POST` | `/tasks/user_tasks` | 获取用户参与的任务列表 | `cross_review:task:read` |
| `GET` | `/tasks/{task_id}/progress` | 获取评查任务进度 | `cross_review:progress:view` |
| `POST` | `/tasks/{task_id}/documents` | 获取任务下文档列表 | `cross_review:task:read` |
| `POST` | `/tasks/{task_id}/documents/{document_id}/complete` | 确认完成文档评查 | `cross_review:document:complete` |
---
## 接口详细说明
---
### 1. 发起评分提案
**POST** `/api/v2/cross_review/proposals`
为某个评查结果创建一个新的评分提案(加分或扣分)。
**权限**: `cross_review:proposal:create`
#### 请求参数
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `document_id` | int | 是 | 文档ID |
| `evaluation_point_id` | int | 是 | 评查点ID |
| `proposed_score` | float | 是 | 建议加/减分数(正数加分,负数扣分,不能为0) |
| `reason` | string | 是 | 理由说明(不能为空) |
| `proposer_id` | int | 是 | 提案人ID |
| `evaluation_result_id` | int | 是 | 评查结果ID(必填!) |
#### 请求示例
```json
{
"document_id": 123,
"evaluation_point_id": 456,
"proposed_score": -5,
"reason": "该评查点存在明显问题,应扣5分",
"proposer_id": 1,
"evaluation_result_id": 789
}
```
#### 业务逻辑
1. **权限验证**
- 验证用户是否有权限访问该文档
- 验证 `evaluation_result_id` 是否真正属于 `document_id`(防止IDOR攻击)
2. **任务参与者验证**
- 查找文档关联的最新交叉评查任务
- 验证提案人是否是任务的参与者(在 `user_ids` 中)
3. **重复提案检查**
- 同一用户不能对同一评查点重复创建提案
4. **分数校验**
- 不能创建 0 分的提案
- 当前分数为 0 时,不能发起扣分提案(`proposed_score < 0`
- 当前分数已满分时,不能发起加分提案(`proposed_score > 0`
5. **自动投票**
- 创建提案后,系统自动为提案人创建一条"同意"的投票记录
6. **状态检查**
- 检查是否达到通过/否决条件,自动更新提案状态
#### 响应示例
**成功 (201)**:
```json
{
"code": 0,
"success": true,
"message": "评分提案创建成功",
"proposal": {
"id": 1,
"document_id": 123,
"evaluation_point_id": 456,
"proposed_score": -5,
"reason": "该评查点存在明显问题,应扣5分",
"proposer_id": 1,
"status": "pending",
"evaluation_result_id": 789,
"created_at": "2024-01-01T10:00:00"
}
}
```
#### 错误码
| 状态码 | 错误信息 | 说明 |
|--------|----------|------|
| `400` | 评查结果ID不能为空 | `evaluation_result_id` 未提供 |
| `400` | 不能创建0分的提案 | `proposed_score` 为 0 |
| `400` | 当前分数为0,不能再发起扣分提案 | 当前评查结果分数已经是0 |
| `400` | 当前已满分,不能再加分 | 当前评查结果已达到满分 |
| `400` | 您已经为该评查点创建过提案 | 重复创建提案 |
| `400` | 文档未分配任何评查任务 | 文档不在交叉评查任务中 |
| `400` | 用户无权为该文档创建提案 | 用户不是任务参与者 |
| `403` | 无权访问此文档 | 文档权限验证失败 |
| `403` | 评查结果与文档不匹配 | IDOR攻击防护触发 |
| `404` | 评查结果不存在 | `evaluation_result_id` 无效 |
---
### 2. 对提案投票
**POST** `/api/v2/cross_review/proposals/{proposal_id}/votes`
对指定的评分提案进行投票(同意/反对/取消)。
**权限**: `cross_review:proposal:vote`
#### 路径参数
| 参数 | 类型 | 说明 |
|------|------|------|
| `proposal_id` | int | 提案ID(必须大于0 |
#### 请求参数
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `vote_type` | string | 是 | 投票类型:`agree`(同意) / `disagree`(反对) / `cancel`(取消投票) |
| `voter_id` | int | 是 | 投票人的用户ID |
#### 请求示例
```json
{
"vote_type": "agree",
"voter_id": 2
}
```
#### 业务逻辑
1. **提案状态检查**
- 提案必须存在且未被删除
- 只能对 `pending` 状态的提案投票
-`approved``rejected` 的提案无法投票
2. **任务参与者验证**
- 根据提案关联的文档找到对应任务
- 验证投票人是否是任务参与者
3. **分数校验**(投票前再次校验)
- 不能投 0 分的提案
- 当前分数为 0 时,不能投赞成扣分提案
- 当前已满分时,不能投赞成加分提案
4. **投票处理**
- **新投票**: 创建新的投票记录
- **更新投票**: 如果已投票,更新投票类型
- **恢复投票**: 如果之前取消过投票,恢复并更新
- **取消投票**: 软删除投票记录(设置 `deleted_at`
5. **自动状态更新**
- 投票后自动检查提案是否达到通过/否决条件
- **通过条件**: 同意票数 >= (参与人数/2 + 1)
- **否决条件**: 反对票数 >= (参与人数/2 + 1) 或 剩余票数不足以达到通过条件
- 提案通过后自动更新评查结果的 `final_score`
6. **文档完成检查**
- 提案状态变更后,检查文档下所有提案是否都已完成
- 如果都完成,自动将文档标记为已完成(`audit_status=1`
#### 响应示例
**成功 (201)**:
```json
{
"code": 0,
"success": true,
"message": "投票成功",
"proposal_status": "approved"
}
```
**取消投票成功**:
```json
{
"code": 0,
"success": true,
"message": "投票已撤销",
"proposal_status": "pending"
}
```
#### 错误码
| 状态码 | 错误信息 | 说明 |
|--------|----------|------|
| `400` | 提案不存在或已被删除 | 无效的提案ID |
| `400` | 提案状态为 approved/rejected,无法投票 | 提案已结束 |
| `400` | 不能投0分的提案 | 提案分数为0 |
| `400` | 当前分数为0,不能再扣分 | 评查结果分数已是0 |
| `400` | 当前已满分,不能再加分 | 评查结果已满分 |
| `400` | 用户无权对该提案投票 | 用户不是任务参与者 |
---
### 3. 撤销评分提案
**DELETE** `/api/v2/cross_review/proposals/{proposal_id}`
撤销一个评分提案。仅提案人本人可以撤销。
**权限**: `cross_review:proposal:delete`
#### 路径参数
| 参数 | 类型 | 说明 |
|------|------|------|
| `proposal_id` | int | 提案ID(必须大于0 |
#### 请求体
无需请求体。
#### 业务逻辑
1. **提案存在性验证**
- 验证提案存在且未被删除
2. **权限验证**
- 只有提案人本人才能撤销自己的提案
3. **状态验证**
- 只能撤销 `pending` 状态的提案
-`approved``rejected` 的提案无法撤销
4. **软删除**
- 将提案的 `deleted_at` 设置为当前时间
- 同时软删除所有关联的投票记录
#### 响应示例
**成功 (200)**:
```json
{
"code": 0,
"success": true,
"message": "提案已成功撤销"
}
```
#### 错误码
| 状态码 | 错误信息 | 说明 |
|--------|----------|------|
| `400` | 提案不存在 | 无效的提案ID |
| `400` | 提案状态为 approved/rejected,无法撤销 | 提案已结束 |
| `403` | 只有提案人才能撤销自己的提案 | 权限不足 |
---
### 4. 获取提案列表及详情
**POST** `/api/v2/cross_review/proposals/details`
获取当前用户需要处理的所有待投票提案列表(排除自己创建的提案和已投票的提案)。
**权限**: `cross_review:proposal:read`
#### 请求参数
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| `document_id` | int | 否 | null | 按文档ID过滤(不传则查所有) |
| `page` | int | 否 | 1 | 页码(从1开始) |
| `page_size` | int | 否 | 20 | 每页数量 |
#### 请求示例
```json
{
"document_id": 123,
"page": 1,
"page_size": 20
}
```
#### 业务逻辑
1. **任务范围确定**
- 查找当前用户参与的所有交叉评查任务
- 获取这些任务下的所有文档ID
2. **提案过滤**
- 只返回 `pending` 状态的提案
- 排除当前用户自己创建的提案
- 如果指定了 `document_id`,只返回该文档的提案
3. **关联信息查询**
- 评查点名称
- 提案人昵称
- 所有投票人及投票类型
- 同意者列表、反对者列表
- 待投票者列表
- 发现问题(evaluation_result 的 message
4. **投票状态判断**
- 判断当前用户是否已对该提案投票
- 返回 `can_vote` 字段
#### 响应示例
```json
{
"data": [
{
"proposal_id": 1,
"evaluation_point_name": "当事人签名检查",
"proposed_score": -5,
"reason": "缺少当事人签名",
"proposer": "张三",
"votes": [
{"voter": "张三", "vote_type": "agree"},
{"voter": "李四", "vote_type": "disagree"}
],
"agree_voters": ["张三"],
"disagree_voters": ["李四"],
"can_vote": true,
"problem_message": "文档中未找到当事人签名",
"pending_voters": ["王五", "赵六"],
"status": "pending"
}
],
"pagination": {
"page": 1,
"page_size": 20,
"total": 5,
"total_pages": 1
}
}
```
#### 返回字段说明
| 字段 | 类型 | 说明 |
|------|------|------|
| `proposal_id` | int | 提案ID |
| `evaluation_point_name` | string | 评查点名称 |
| `proposed_score` | float | 提议的加/减分数 |
| `reason` | string | 提案理由 |
| `proposer` | string | 提案人昵称 |
| `votes` | array | 所有投票记录(含投票人和投票类型) |
| `agree_voters` | array | 同意者昵称列表 |
| `disagree_voters` | array | 反对者昵称列表 |
| `can_vote` | boolean | 当前用户是否可以投票 |
| `problem_message` | string | 评查结果发现的问题描述 |
| `pending_voters` | array | 待投票者昵称列表 |
| `status` | string | 提案状态(pending/approved/rejected |
---
### 5. 获取指定文档的提案列表
**POST** `/api/v2/cross_review/proposals/document`
获取指定文档下的所有评分提案及其详细信息(包含所有状态的提案)。
**权限**: `cross_review:proposal:read`
#### 请求参数
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| `document_id` | int | 是 | - | 文档ID |
| `page` | int | 否 | 1 | 页码(从1开始) |
| `page_size` | int | 否 | 10 | 每页数量 |
#### 请求示例
```json
{
"document_id": 123,
"page": 1,
"page_size": 10
}
```
#### 业务逻辑
1. **权限验证**
- 验证当前用户是否有权限访问该文档
2. **提案查询**
- 获取该文档下所有未删除的提案(包括 pending/approved/rejected
3. **关联信息查询**
- 评查点名称
- 提案人昵称和ID
- 所有投票人及投票类型
- 同意者列表、反对者列表
- 待投票者列表
- 发现问题
- 创建时间
4. **投票状态判断**
- 判断当前用户是否可以投票
- 条件:未投票 + 不是提案人 + 是任务参与者
#### 响应示例
```json
{
"data": [
{
"proposal_id": 1,
"evaluation_point_name": "当事人签名检查",
"proposed_score": -5,
"reason": "缺少当事人签名",
"proposer": "张三",
"proposer_id": 1,
"votes": [
{"voter": "张三", "vote_type": "agree"},
{"voter": "李四", "vote_type": "agree"},
{"voter": "王五", "vote_type": "agree"}
],
"agree_voters": ["张三", "李四", "王五"],
"disagree_voters": [],
"problem_message": "文档中未找到当事人签名",
"pending_voters": [],
"created_at": "2024-01-01 10:00:00",
"can_vote": false,
"status": "approved"
}
],
"pagination": {
"page": 1,
"page_size": 10,
"total": 3,
"total_pages": 1
}
}
```
#### 返回字段说明
| 字段 | 类型 | 说明 |
|------|------|------|
| `proposal_id` | int | 提案ID |
| `evaluation_point_name` | string | 评查点名称 |
| `proposed_score` | float | 提议的加/减分数 |
| `reason` | string | 提案理由 |
| `proposer` | string | 提案人昵称 |
| `proposer_id` | int | 提案人ID |
| `votes` | array | 所有投票记录 |
| `agree_voters` | array | 同意者昵称列表 |
| `disagree_voters` | array | 反对者昵称列表 |
| `problem_message` | string | 评查结果发现的问题描述 |
| `pending_voters` | array | 待投票者昵称列表 |
| `created_at` | string | 创建时间(格式:yyyy-MM-dd HH:mm:ss |
| `can_vote` | boolean | 当前用户是否可以投票 |
| `status` | string | 提案状态 |
#### 错误码
| 状态码 | 错误信息 | 说明 |
|--------|----------|------|
| `403` | 无权访问此文档 | 用户无权限访问该文档 |
---
### 6. 检查未投票用户
**POST** `/api/v2/cross_review/proposals/document/check_pending_votes`
检查指定文档下所有提案是否存在未投票的用户。**仅任务创建人可调用**。
**权限**: `cross_review:task:read`
#### 请求参数
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `document_id` | int | 是 | 文档ID |
#### 请求示例
```json
{
"document_id": 123
}
```
#### 业务逻辑
1. **任务查找**
- 根据文档ID查找关联的最新任务
2. **权限验证**
- 验证当前用户是否是任务的创建人(`assigner_id`
- 只有任务创建人才能调用此接口
3. **投票统计**
- 查找文档下所有提案
- 统计每个提案的已投票用户
- 计算待投票用户(任务参与者 - 已投票用户 - 提案人)
#### 响应示例
**有未投票用户**:
```json
{
"has_pending_votes": true,
"pending_proposals": [
{
"proposal_id": 1,
"pending_voters_num": 2,
"pending_voters": ["王五", "赵六"],
"evaluation_point_name": "当事人签名检查"
},
{
"proposal_id": 2,
"pending_voters_num": 1,
"pending_voters": ["赵六"],
"evaluation_point_name": "日期格式检查"
}
]
}
```
**无未投票用户**:
```json
{
"has_pending_votes": false,
"pending_proposals": []
}
```
#### 错误码
| 状态码 | 错误信息 | 说明 |
|--------|----------|------|
| `400` | 未找到该文档的任务 | 文档不在任何任务中 |
| `403` | 只有任务创建人可以执行此操作 | 当前用户不是任务创建人 |
---
### 7. 获取用户参与的任务列表
**POST** `/api/v2/cross_review/tasks/user_tasks`
获取当前用户参与的所有交叉评查任务列表(分页)。
**权限**: `cross_review:task:read`
#### 请求参数
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| `page` | int | 否 | 1 | 页码(从1开始) |
| `page_size` | int | 否 | 10 | 每页数量 |
#### 请求示例
```json
{
"page": 1,
"page_size": 10
}
```
#### 业务逻辑
1. **任务查询**
- 查询当前用户参与的所有任务(用户ID在任务的 `user_ids` 数组中)
2. **进度计算**
- 统计每个任务下的文档总数
- 统计已完成文档数(`cross_task_document_mapping.audit_status = 1`
- 计算完成百分比
3. **文档类型解析**
-`doc_type` 代码转换为可读名称
4. **地区信息聚合**
- 获取任务参与者的地区列表
#### 响应示例
```json
{
"total": 5,
"page": 1,
"page_size": 10,
"items": [
{
"task_id": 1,
"task_name": "2024年度交叉评查任务",
"task_status": "in_progress",
"doc_type": "行政处罚",
"task_type": "CITY",
"task_created_at": "2024-01-01T10:00:00",
"progress": 75,
"total_documents": 20,
"evaluation_region": ["梅州", "云浮"]
},
{
"task_id": 2,
"task_name": "2024年Q1评查",
"task_status": "completed",
"doc_type": "行政许可",
"task_type": "DISTRICT",
"task_created_at": "2024-02-01T10:00:00",
"progress": 100,
"total_documents": 15,
"evaluation_region": ["揭阳"]
}
]
}
```
#### 返回字段说明
| 字段 | 类型 | 说明 |
|------|------|------|
| `task_id` | int | 任务ID |
| `task_name` | string | 任务名称 |
| `task_status` | string | 任务状态(in_progress/completed |
| `doc_type` | string | 文档类型名称 |
| `task_type` | string | 任务类型(CITY/DISTRICT等) |
| `task_created_at` | datetime | 任务创建时间 |
| `progress` | int | 完成进度百分比 (0-100) |
| `total_documents` | int | 任务包含的文档总数 |
| `evaluation_region` | array | 参与评查的地区列表 |
---
### 8. 获取评查任务进度
**GET** `/api/v2/cross_review/tasks/{task_id}/progress`
根据任务ID获取评查进度详情。**仅任务参与者可访问**。
**权限**: `cross_review:progress:view`
#### 路径参数
| 参数 | 类型 | 说明 |
|------|------|------|
| `task_id` | int | 任务ID(必须大于0 |
#### 请求体
无需请求体。
#### 业务逻辑
1. **权限验证**
- 验证当前用户是否是任务的参与者(在 `user_ids` 中)
2. **进度计算**
- 获取任务关联的所有文档映射(排除已删除的)
- 统计已完成文档数(`audit_status = 1`
- 计算完成百分比
#### 响应示例
```json
{
"code": 0,
"task_id": 1,
"total_documents": 20,
"completed_documents": 15,
"progress": 75.0
}
```
#### 返回字段说明
| 字段 | 类型 | 说明 |
|------|------|------|
| `code` | int | 状态码(0表示成功) |
| `task_id` | int | 任务ID |
| `total_documents` | int | 文档总数 |
| `completed_documents` | int | 已完成文档数 |
| `progress` | float | 完成百分比(保留2位小数) |
#### 错误码
| 状态码 | 错误信息 | 说明 |
|--------|----------|------|
| `403` | 无权访问任务:您不是该任务的参与者 | 用户不是任务参与者 |
| `404` | 任务不存在 | 无效的任务ID |
---
### 9. 获取任务下文档列表
**POST** `/api/v2/cross_review/tasks/{task_id}/documents`
获取指定任务下的文档列表,支持多种筛选条件和排序。**仅任务参与者可访问**。
**权限**: `cross_review:task:read`
#### 路径参数
| 参数 | 类型 | 说明 |
|------|------|------|
| `task_id` | int | 任务ID(必须大于0 |
#### 请求参数
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| `page` | int | 否 | 1 | 页码(从1开始) |
| `page_size` | int | 否 | 10 | 每页数量 |
| `file_type_ids` | array[int] | 否 | null | 文件类型ID列表(不传查所有类型) |
| `date_from` | string | 否 | null | 起始日期 (yyyy-MM-dd) |
| `date_to` | string | 否 | null | 结束日期 (yyyy-MM-dd) |
| `keyword` | string | 否 | null | 搜索关键字(匹配文件名或文书号) |
| `order` | string | 否 | `upload_time_desc` | 排序方式 |
#### 排序方式
| 值 | 说明 |
|----|------|
| `upload_time_desc` | 上传时间降序(默认,最新的在前) |
| `upload_time_asc` | 上传时间升序(最早的在前) |
#### 请求示例
```json
{
"page": 1,
"page_size": 10,
"file_type_ids": [1, 2],
"date_from": "2024-01-01",
"date_to": "2024-12-31",
"keyword": "处罚",
"order": "upload_time_desc"
}
```
#### 业务逻辑
1. **权限验证**
- 验证当前用户是否是任务的参与者
2. **文档查询**
-`cross_task_document_mapping` 获取任务关联的文档
- 应用各种筛选条件
- 关联 `documents``document_types`
3. **评查统计**
- 统计每个文档的评查结果
- 计算通过/警告/失败/人工审核数量
- 计算最终得分和满分
- 获取问题摘要(最多5条)
#### 响应示例
```json
{
"total": 100,
"page": 1,
"page_size": 10,
"items": [
{
"document_id": 123,
"file_name": "行政处罚决定书_001.pdf",
"status": "completed",
"path": "/documents/2024/01/123.pdf",
"file_code": "穗市监罚字[2024]001号",
"file_type_name": "行政处罚",
"file_type_id": 1,
"file_size": 1024000,
"upload_time": "2024-01-01T10:00:00",
"created_at": "2024-01-01T10:00:00",
"evaluations_status": "completed",
"audit_status": 1,
"created_by_user_id": 1,
"final_score": 85.5,
"full_score": 100,
"score_summary": "85.5/100",
"score_percent": 85.5,
"pass_count": 18,
"warning_count": 2,
"fail_count": 1,
"manual_count": 3,
"issues": [
{
"severity": "error",
"message": "缺少当事人签名"
},
{
"severity": "warning",
"message": "日期格式不规范"
}
]
}
]
}
```
#### 返回字段说明
| 字段 | 类型 | 说明 |
|------|------|------|
| `document_id` | int | 文档ID |
| `file_name` | string | 文件名 |
| `status` | string | 文档处理状态 |
| `path` | string | 文件存储路径 |
| `file_code` | string | 文书号 |
| `file_type_name` | string | 文件类型名称 |
| `file_type_id` | int | 文件类型ID |
| `file_size` | int | 文件大小(字节) |
| `upload_time` | datetime | 上传时间 |
| `created_at` | datetime | 创建时间 |
| `evaluations_status` | string | 评查状态 |
| `audit_status` | int | 审核状态 (0:待审核, 1:已完成) |
| `created_by_user_id` | int | 创建用户ID |
| `final_score` | float | 最终得分 |
| `full_score` | float | 满分 |
| `score_summary` | string | 得分摘要(如 "85.5/100" |
| `score_percent` | float | 得分百分比 |
| `pass_count` | int | 通过的评查点数量 |
| `warning_count` | int | 警告的评查点数量(severity=warning或info |
| `fail_count` | int | 失败的评查点数量(severity=error |
| `manual_count` | int | 需人工审核的评查点数量(post_action=manual |
| `issues` | array | 问题列表(最多5条,包含severity和message |
#### 错误码
| 状态码 | 错误信息 | 说明 |
|--------|----------|------|
| `403` | 无权访问任务:您不是该任务的参与者 | 用户不是任务参与者 |
---
### 10. 确认完成文档评查
**POST** `/api/v2/cross_review/tasks/{task_id}/documents/{document_id}/complete`
确认完成对指定文档的评查,更新任务进度。
**权限**: `cross_review:document:complete`
#### 路径参数
| 参数 | 类型 | 说明 |
|------|------|------|
| `task_id` | int | 任务ID(必须大于0 |
| `document_id` | int | 文档ID(必须大于0 |
#### 请求体
无需请求体。
#### 业务逻辑
1. **任务存在性验证**
- 验证任务存在
2. **权限验证**
- 验证当前用户是否是任务的参与者
3. **文档归属验证**
- 验证文档确实属于该任务(在 `cross_task_document_mapping` 中)
4. **状态更新**
-`cross_task_document_mapping.audit_status` 设置为 `1`(已完成)
#### 响应示例
```json
{
"code": 0,
"success": true,
"message": "文档评查已完成",
"task_id": 1,
"document_id": 123
}
```
#### 错误码
| 状态码 | 错误信息 | 说明 |
|--------|----------|------|
| `400` | 任务不存在 | 无效的任务ID |
| `400` | 文档不在任务中 | 文档不属于该任务 |
| `403` | 用户不是任务的参与者 | 权限不足 |
---
## 核心业务规则
### 提案投票机制
1. **投票通过条件**: 同意票数 >= ⌊参与人数/2⌋ + 1
2. **投票否决条件**:
- 反对票数 >= ⌊参与人数/2⌋ + 1
- 或剩余可投票数不足以达到通过条件
3. **提案人自动投票**: 创建提案时,系统自动为提案人投一票"同意"
4. **分数自动更新**: 提案通过后,自动将 `proposed_score` 累加到评查结果的 `final_score`
### 分数校验规则
| 场景 | 限制 |
|------|------|
| 创建0分提案 | 禁止 |
| 当前分数为0时扣分 | 禁止 |
| 当前分数已满分时加分 | 禁止 |
| 分数计算后超过满分 | 自动截断为满分 |
| 分数计算后小于0 | 自动截断为0 |
### 文档完成自动标记
当文档下所有提案都达到终态(approved 或 rejected)时,系统自动将该文档在任务中的状态标记为已完成(`audit_status=1`)。
---
## 权限列表
| 权限 Key | 说明 |
|----------|------|
| `cross_review:proposal:create` | 创建提案 |
| `cross_review:proposal:read` | 查看提案 |
| `cross_review:proposal:delete` | 撤销提案 |
| `cross_review:proposal:vote` | 提案投票 |
| `cross_review:task:read` | 查看任务 |
| `cross_review:progress:view` | 查看进度 |
| `cross_review:document:complete` | 完成文档评查 |
---
## 枚举值说明
### VoteType 投票类型
| 值 | 说明 |
|----|------|
| `agree` | 同意 |
| `disagree` | 反对 |
| `cancel` | 取消投票 |
### ProposalStatus 提案状态
| 值 | 说明 |
|----|------|
| `pending` | 待处理(投票中) |
| `approved` | 已通过(分数已更新) |
| `rejected` | 已否决 |
### DocType 文档类型
| 值 | 说明 |
|----|------|
| `XZCF` | 行政处罚 |
| `XZXK` | 行政许可 |
### audit_status 任务内文档审核状态
| 值 | 说明 |
|----|------|
| `0` | 待审核/进行中 |
| `1` | 已完成 |
### task_status 任务状态
| 值 | 说明 |
|----|------|
| `in_progress` | 进行中 |
| `completed` | 已完成 |
---
## 数据库表关系
```
cross_examination_tasks (交叉评查任务)
├── id: 任务ID
├── assigner_id: 创建人ID
├── user_ids: 参与者ID数组
├── task_name: 任务名称
├── task_status: 任务状态
├── doc_type: 文档类型
└── task_type: 任务类型
cross_task_document_mapping (任务-文档映射)
├── task_id: 任务ID
├── document_id: 文档ID
├── audit_status: 文档审核状态
└── deleted_at: 软删除标记
cross_scoring_proposals (评分提案)
├── id: 提案ID
├── document_id: 文档ID
├── evaluation_point_id: 评查点ID
├── evaluation_result_id: 评查结果ID
├── proposed_score: 提议分数
├── reason: 理由
├── proposer_id: 提案人ID
├── status: 提案状态
└── deleted_at: 软删除标记
cross_opinion_votes (投票记录)
├── proposal_id: 提案ID
├── voter_id: 投票人ID
├── vote_type: 投票类型
└── deleted_at: 软删除标记
```
---
## 通用错误响应格式
```json
{
"detail": "错误信息描述"
}
```
**HTTP 状态码**:
- `400 Bad Request`: 请求参数错误/业务规则校验失败
- `403 Forbidden`: 权限不足
- `404 Not Found`: 资源不存在
- `500 Internal Server Error`: 服务器内部错误
---
## 前端对接注意事项
1. **所有接口都需要认证**: 必须在 Header 中携带有效的 JWT Token
2. **evaluation_result_id 必填**: 创建提案时必须提供评查结果ID
3. **任务权限检查**: 大部分任务相关接口会校验当前用户是否为任务参与者
4. **分页参数**: 分页从第1页开始,不是第0页
5. **日期格式**: 使用 `yyyy-MM-dd` 格式(如 `2024-01-01`
6. **投票后刷新**: 投票后应刷新提案列表,因为提案状态可能已自动更新
7. **proposed_score 含义**:
- 正数表示加分
- 负数表示扣分
- 0 不允许
8. **can_vote 字段**: 前端应根据此字段决定是否显示投票按钮