d4000cd292
2. 文档的基本信息修改改用接口。 3. 重新完善角色权限管理的页面逻辑。 4.将评查点列表中的返回逻辑改用浏览器的记忆返回。
1223 lines
32 KiB
Markdown
1223 lines
32 KiB
Markdown
# 交叉评查接口文档
|
||
|
||
> 本文档描述交叉评查模块的所有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` |
|
||
| `GET` | `/tasks/{task_id}/can-confirm` | 检查用户是否有权确认完成 | 无(仅需登录) |
|
||
|
||
---
|
||
|
||
## 接口详细说明
|
||
|
||
---
|
||
|
||
### 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. **权限验证**(重要!)
|
||
- **只有以下用户有权确认完成**:
|
||
- 任务创建者(`assigner_id`)
|
||
- 主要负责人(`principal_user_ids` 数组中的用户)
|
||
- 普通任务参与者(仅在 `user_ids` 中)无权确认完成
|
||
- 建议前端先调用 `GET /tasks/{task_id}/can-confirm` 判断是否显示按钮
|
||
|
||
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` | 用户不是任务创建者或主要负责人,无权确认完成 | 权限不足 |
|
||
|
||
---
|
||
|
||
### 11. 检查用户是否有权确认完成
|
||
|
||
**GET** `/api/v2/cross_review/tasks/{task_id}/can-confirm`
|
||
|
||
检查当前登录用户是否有权点击"确认文档评查完成"按钮。
|
||
|
||
**权限**: 无特殊权限要求,仅需登录
|
||
|
||
#### 路径参数
|
||
|
||
| 参数 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `task_id` | int | 任务ID(必须大于0) |
|
||
|
||
#### 请求体
|
||
|
||
无需请求体。
|
||
|
||
#### 业务逻辑
|
||
|
||
1. **任务查询**
|
||
- 查询任务的 `assigner_id`(创建者)和 `principal_user_ids`(主要负责人列表)
|
||
|
||
2. **权限判断**
|
||
- 当前用户是任务创建者 → 有权限
|
||
- 当前用户在主要负责人列表中 → 有权限
|
||
- 其他情况 → 无权限
|
||
|
||
**有权限的用户**:
|
||
- 任务创建者(`assigner_id`)
|
||
- 主要负责人(`principal_user_ids` 数组中的用户)
|
||
|
||
#### 响应示例
|
||
|
||
**有权限**:
|
||
```json
|
||
{
|
||
"can_confirm": true,
|
||
"reason": "您是任务创建者,有权确认完成"
|
||
}
|
||
```
|
||
|
||
或
|
||
|
||
```json
|
||
{
|
||
"can_confirm": true,
|
||
"reason": "您是任务主要负责人,有权确认完成"
|
||
}
|
||
```
|
||
|
||
**无权限**:
|
||
```json
|
||
{
|
||
"can_confirm": false,
|
||
"reason": "您不是任务创建者或主要负责人,无权确认完成"
|
||
}
|
||
```
|
||
|
||
**任务不存在**:
|
||
```json
|
||
{
|
||
"can_confirm": false,
|
||
"reason": "任务 123 不存在"
|
||
}
|
||
```
|
||
|
||
#### 返回字段说明
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `can_confirm` | boolean | 是否有权确认完成 |
|
||
| `reason` | string | 原因说明 |
|
||
|
||
#### 前端使用示例
|
||
|
||
```javascript
|
||
// 进入任务详情页时调用,判断是否显示"确认完成"按钮
|
||
async function checkConfirmPermission(taskId) {
|
||
const response = await fetch(`/api/v2/cross_review/tasks/${taskId}/can-confirm`, {
|
||
headers: {
|
||
'Authorization': `Bearer ${token}`
|
||
}
|
||
});
|
||
const { can_confirm, reason } = await response.json();
|
||
|
||
if (can_confirm) {
|
||
// 显示/启用"确认完成"按钮
|
||
showConfirmButton();
|
||
} else {
|
||
// 隐藏按钮或显示提示信息
|
||
hideConfirmButton();
|
||
// 可选:显示无权限原因
|
||
console.log(reason);
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 核心业务规则
|
||
|
||
### 提案投票机制
|
||
|
||
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数组
|
||
├── principal_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 字段**: 前端应根据此字段决定是否显示投票按钮
|
||
|
||
9. **主要负责人(principal_user_ids)**:
|
||
- 创建任务时可指定主要负责人列表
|
||
- 主要负责人和任务创建者都有权确认文档评查完成
|
||
- 通过 `GET /tasks/{task_id}/can-confirm` 接口判断当前用户是否有确认权限
|
||
- 前端应在显示"确认完成"按钮前调用此接口判断权限
|
||
|
||
10. **创建任务时传递主要负责人**:
|
||
- 接口:`POST /api/v2/documents/cross_review/documents/upload_and_assign`
|
||
- 使用单独的 FormData 字段 `principal_user_ids` 传递
|
||
- FormData 参数:
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------|------|------|------|
|
||
| `file` | File | 是 | 上传的文件 |
|
||
| `upload_info` | string | 是 | 上传信息JSON |
|
||
| `assign_user_ids` | string | 是 | 任务参与者用户ID列表JSON,如 `[1,2,3,4,5]` |
|
||
| `principal_user_ids` | string | 否 | 主要负责人用户ID列表JSON,如 `[1,2]` |
|
||
|
||
- 示例:
|
||
```javascript
|
||
const formData = new FormData();
|
||
formData.append('file', file);
|
||
formData.append('upload_info', JSON.stringify({
|
||
task_name: "2024年度交叉评查",
|
||
doc_type: "XZCF",
|
||
task_type: "CITY"
|
||
}));
|
||
formData.append('assign_user_ids', JSON.stringify([1, 2, 3, 4, 5]));
|
||
formData.append('principal_user_ids', JSON.stringify([1, 2])); // 主要负责人
|
||
|
||
await fetch('/api/v2/documents/cross_review/documents/upload_and_assign', {
|
||
method: 'POST',
|
||
headers: { 'Authorization': `Bearer ${token}` },
|
||
body: formData
|
||
});
|
||
```
|