# 交叉评查接口文档 > 本文档描述交叉评查模块的所有API接口,供前端直接对接使用。 ## 基础信息 - **基础路径**: `/api/v2/cross_review` (推荐) - **备用路径**: `/admin/v2/cross_review` (兼容,功能相同) - **认证方式**: JWT Token (Header: `Authorization: Bearer `) - **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 字段**: 前端应根据此字段决定是否显示投票按钮