feat: 交叉评查后端优化 — 评查地区、文档评查统计、currentScore、错误提示
- GetUserTasks: 新增 task_regions CTE,从任务成员 sso_users.area 去重收集 evaluationRegion - GetTaskDocuments: 新增 es LATERAL 子查询聚合 leaudit_rule_results 的 pass_count/warning_count/error_count/score_percent;path/uploadTime 改为从 leaudit_document_files 获取;新增 fileExt - ReviewPointResultVO: 新增 currentScore 字段 - _loadReviewPointResults: SQL 新增 approved_delta LATERAL 子查询,currentScore = base_score + SUM(approved_deltas) - CrossReviewTaskItemVO: 新增 evaluationRegion - CrossReviewTaskDocumentVO: 新增 18 个评查统计字段 + path/uploadTime/fileExt - 文档更新:交叉评查核心模块业务逻辑文档补充评查地区、评查统计、版本号本地化等章节
This commit is contained in:
@@ -0,0 +1,136 @@
|
|||||||
|
# Project Structure (Auto-generated)
|
||||||
|
|
||||||
|
> This file was automatically generated by DeepSeek TUI.
|
||||||
|
> You can edit or delete it at any time.
|
||||||
|
|
||||||
|
**Summary:** A JavaScript/Node.js project
|
||||||
|
|
||||||
|
**Tree:**
|
||||||
|
```
|
||||||
|
DIR: .claude
|
||||||
|
DIR: skills
|
||||||
|
DIR: .git
|
||||||
|
FILE: COMMIT_EDITMSG
|
||||||
|
FILE: FETCH_HEAD
|
||||||
|
FILE: HEAD
|
||||||
|
FILE: ORIG_HEAD
|
||||||
|
DIR: branches
|
||||||
|
FILE: config
|
||||||
|
FILE: description
|
||||||
|
DIR: gk
|
||||||
|
DIR: hooks
|
||||||
|
FILE: index
|
||||||
|
DIR: info
|
||||||
|
DIR: objects
|
||||||
|
DIR: refs
|
||||||
|
DIR: worktrees
|
||||||
|
FILE: .gitignore
|
||||||
|
DIR: deploy
|
||||||
|
DIR: collabora-proxy
|
||||||
|
DIR: docs
|
||||||
|
DIR: Collabora
|
||||||
|
FILE: HANDOFF.md
|
||||||
|
DIR: RAG
|
||||||
|
FILE: README.md
|
||||||
|
DIR: leaudit
|
||||||
|
DIR: 交叉评查
|
||||||
|
DIR: 入口模块
|
||||||
|
DIR: 内部公文模块
|
||||||
|
DIR: 文档管理
|
||||||
|
DIR: 权限与地区隔离
|
||||||
|
DIR: 系统使用统计
|
||||||
|
DIR: 规则编辑
|
||||||
|
DIR: 评查点分组
|
||||||
|
DIR: 迁移与兼容
|
||||||
|
DIR: 项目总览
|
||||||
|
DIR: fastapi_admin
|
||||||
|
FILE: __init__.py
|
||||||
|
FILE: app.py
|
||||||
|
DIR: bootstrap_parts
|
||||||
|
FILE: celery_app.py
|
||||||
|
DIR: config
|
||||||
|
DIR: fastapi_common
|
||||||
|
FILE: __init__.py
|
||||||
|
DIR: fastapi_common_logger
|
||||||
|
DIR: fastapi_common_security
|
||||||
|
DIR: fastapi_common_sqlalchemy
|
||||||
|
DIR: fastapi_common_storage
|
||||||
|
DIR: fastapi_common_utils
|
||||||
|
DIR: fastapi_common_web
|
||||||
|
DIR: fastapi_modules
|
||||||
|
FILE: __init__.py
|
||||||
|
DIR: fastapi_leaudit
|
||||||
|
FILE: leaudit.sh
|
||||||
|
DIR: legal-platform-frontend
|
||||||
|
DIR: .codex-run
|
||||||
|
FILE: .env.example
|
||||||
|
DIR: .git
|
||||||
|
FILE: .gitignore
|
||||||
|
FILE: README.md
|
||||||
|
DIR: app
|
||||||
|
FILE: bun.lock
|
||||||
|
DIR: components
|
||||||
|
DIR: config
|
||||||
|
DIR: constants
|
||||||
|
DIR: contexts
|
||||||
|
DIR: docs
|
||||||
|
FILE: ecosystem.config.cjs
|
||||||
|
FILE: eslint.config.mjs
|
||||||
|
DIR: hooks
|
||||||
|
DIR: lib
|
||||||
|
FILE: middleware.ts
|
||||||
|
DIR: models
|
||||||
|
FILE: next.config.ts
|
||||||
|
FILE: orval.config.ts
|
||||||
|
FILE: package-lock.json
|
||||||
|
FILE: package.json
|
||||||
|
FILE: postcss.config.mjs
|
||||||
|
DIR: public
|
||||||
|
DIR: scripts
|
||||||
|
DIR: styles
|
||||||
|
FILE: tailwind.config.ts
|
||||||
|
DIR: tests
|
||||||
|
FILE: tsconfig.json
|
||||||
|
DIR: types
|
||||||
|
DIR: utils
|
||||||
|
DIR: new-rules
|
||||||
|
FILE: contract.lease.yaml
|
||||||
|
FILE: contract.sale.yaml
|
||||||
|
FILE: contract.tech.yaml
|
||||||
|
FILE: package.json
|
||||||
|
FILE: pyproject.toml
|
||||||
|
DIR: resources
|
||||||
|
FILE: .gitkeep
|
||||||
|
DIR: rules
|
||||||
|
DIR: contract_construction
|
||||||
|
DIR: contract_entrust
|
||||||
|
DIR: contract_evaluation
|
||||||
|
DIR: contract_gift_charity
|
||||||
|
DIR: contract_gift_general
|
||||||
|
DIR: contract_lease
|
||||||
|
DIR: contract_loan
|
||||||
|
DIR: contract_purchase
|
||||||
|
DIR: contract_sale
|
||||||
|
FILE: contract_sale.zip
|
||||||
|
DIR: contract_tech
|
||||||
|
DIR: 行政处罚
|
||||||
|
DIR: 行政许可_停业
|
||||||
|
DIR: 行政许可_变更
|
||||||
|
DIR: 行政许可_延续
|
||||||
|
DIR: 行政许可_恢复营业
|
||||||
|
DIR: 行政许可_收回
|
||||||
|
DIR: 行政许可_新办
|
||||||
|
DIR: 行政许可_歇业
|
||||||
|
DIR: 行政许可_注销
|
||||||
|
DIR: 行政许可_补办
|
||||||
|
FILE: run.py
|
||||||
|
DIR: scripts
|
||||||
|
FILE: dev.sh
|
||||||
|
FILE: m4_seed_rules.py
|
||||||
|
FILE: merge_document_version_groups.py
|
||||||
|
FILE: migrate_legacy_users.py
|
||||||
|
FILE: preview_document_version_merge.py
|
||||||
|
FILE: start_beat.sh
|
||||||
|
FILE: start_worker.sh
|
||||||
|
DIR: 创建sql
|
||||||
|
```
|
||||||
@@ -49,11 +49,13 @@
|
|||||||
|----|------|------|
|
|----|------|------|
|
||||||
| `id` | INT PK | |
|
| `id` | INT PK | |
|
||||||
| `task_id` | INT FK → tasks | 所属任务 |
|
| `task_id` | INT FK → tasks | 所属任务 |
|
||||||
| `document_id` | INT | 关联的文档 ID |
|
| `document_id` | INT | 关联的文档 ID(FK → `leaudit_documents.id`) |
|
||||||
| `audit_status` | INT | 评查状态:`0`=未完成,`1`=已完成 |
|
| `audit_status` | INT | 评查状态:`0`=未评查,`1`=已评查(**文档×任务级别**,非按人) |
|
||||||
| `create_time` | TIMESTAMPTZ | |
|
| `create_time` | TIMESTAMPTZ | |
|
||||||
| `delete_time` | TIMESTAMPTZ | |
|
| `delete_time` | TIMESTAMPTZ | |
|
||||||
|
|
||||||
|
> **评查状态逻辑**:`CompleteTaskDocument` 将 `audit_status` 置为 1。当任务内全部文档的 `audit_status=1` 时,任务状态自动更新为 `completed`。
|
||||||
|
|
||||||
### 1.4 `leaudit_cross_review_proposals` — 交叉评查提案(意见)
|
### 1.4 `leaudit_cross_review_proposals` — 交叉评查提案(意见)
|
||||||
|
|
||||||
| 列 | 类型 | 说明 |
|
| 列 | 类型 | 说明 |
|
||||||
@@ -83,6 +85,25 @@
|
|||||||
| `create_time` | TIMESTAMPTZ | |
|
| `create_time` | TIMESTAMPTZ | |
|
||||||
| `delete_time` | TIMESTAMPTZ | |
|
| `delete_time` | TIMESTAMPTZ | |
|
||||||
|
|
||||||
|
### 1.6 依赖的外部表
|
||||||
|
|
||||||
|
任务列表和文档列表的查询依赖以下外部表(非交叉评查专属):
|
||||||
|
|
||||||
|
| 表 | 用途 |
|
||||||
|
|----|------|
|
||||||
|
| `sso_users` | 用户信息;`area` 字段用于收集任务成员的评查地区 |
|
||||||
|
| `leaudit_documents` | 文档主表;含 `version_no`(全局版本号)、`version_group_key`、`current_run_id` |
|
||||||
|
| `leaudit_document_files` | 文档文件;第一个文件的 `local_path` 和 `created_at` 作为文档路径和上传时间 |
|
||||||
|
| `leaudit_rule_results` | 评查规则结果;聚合 `passed`/`risk`/`score`/`fail_message` 得出评查统计 |
|
||||||
|
| `leaudit_audit_runs` | 评查运行记录;通过 `d.current_run_id` 关联 |
|
||||||
|
|
||||||
|
> **评查地区数据来源**:`GetUserTasks` 中 `task_regions` CTE 从 `leaudit_cross_review_task_members` JOIN `sso_users` 收集 `DISTINCT u.area`(排除 NULL 和空串),非从文档表获取。
|
||||||
|
|
||||||
|
> **评查统计数据来源**:`GetTaskDocuments` 中 `es` LATERAL 子查询聚合 `leaudit_rule_results`,按 `risk` 分级:
|
||||||
|
> - `passed IS TRUE` → pass_count
|
||||||
|
> - `passed IS FALSE AND risk = 'high'` → error_count
|
||||||
|
> - `passed IS FALSE AND risk IN ('low','medium')` → warning_count
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. 后端 API 全景
|
## 2. 后端 API 全景
|
||||||
@@ -173,13 +194,45 @@ pendingProposals: list[PendingProposalVO]
|
|||||||
# PendingProposalVO: { evaluationPointName, pendingVotersNum }
|
# PendingProposalVO: { evaluationPointName, pendingVotersNum }
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**`CrossReviewTaskItemVO`** — 任务列表项:
|
||||||
|
```python
|
||||||
|
taskId: int
|
||||||
|
taskName: str
|
||||||
|
taskType: str
|
||||||
|
docTypeId: int | None
|
||||||
|
docTypeCode: str | None
|
||||||
|
status: str
|
||||||
|
progress: float
|
||||||
|
totalDocuments: int
|
||||||
|
completedDocuments: int
|
||||||
|
createdAt: datetime | None
|
||||||
|
evaluationRegion: list[str] # 评查地区 — 从任务成员 sso_users.area 去重收集
|
||||||
|
```
|
||||||
|
|
||||||
**`CrossReviewTaskDocumentVO`** — 任务文档列表项:
|
**`CrossReviewTaskDocumentVO`** — 任务文档列表项:
|
||||||
```python
|
```python
|
||||||
documentId, name, documentNumber, typeId, typeName
|
documentId, name, documentNumber, typeId, typeName
|
||||||
processingStatus, versionNo, isLatestVersion
|
processingStatus, versionNo, isLatestVersion
|
||||||
versionGroupKey, totalVersions
|
versionGroupKey, totalVersions
|
||||||
auditStatus # 0=未完成, 1=已完成
|
auditStatus # 0=未评查, 1=已评查(文档×任务级别)
|
||||||
createdAt, fileSize
|
createdAt, fileSize
|
||||||
|
path # 文件路径 — 来自 leaudit_document_files.local_path
|
||||||
|
uploadTime # 上传时间 — 来自 leaudit_document_files.created_at
|
||||||
|
# 以下为评查统计字段 — 聚合自 leaudit_rule_results
|
||||||
|
totalEvaluationPoints: int # 总评查点数
|
||||||
|
passCount: int # 通过数(passed IS TRUE)
|
||||||
|
warningCount: int # 警告数(passed IS FALSE AND risk IN ('low','medium'))
|
||||||
|
errorCount: int # 错误数(passed IS FALSE AND risk = 'high')
|
||||||
|
manualCount: int # 人工审核数(暂为 0)
|
||||||
|
issueCount: int # 问题总数
|
||||||
|
warningMessages: list[str] # 警告消息
|
||||||
|
errorMessages: list[str] # 错误消息
|
||||||
|
issueMessages: list[str] # 问题消息(综合)
|
||||||
|
manualMessages: list[str] # 人工审核消息(暂为空)
|
||||||
|
finalScore: float # 最终得分(通过规则分数之和)
|
||||||
|
fullScore: float # 满分(所有规则分数之和)
|
||||||
|
scoreSummary: str # 得分摘要(如 "85.0/100.0")
|
||||||
|
scorePercent: float # 得分百分比(0-100)
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -795,7 +848,7 @@ import { CrossCheckingOpinionsPanel } from "@/components/cross-checking/CrossChe
|
|||||||
|
|
||||||
## 7. 前端 API 封装参考
|
## 7. 前端 API 封装参考
|
||||||
|
|
||||||
全部位于 `lib/api/legacy/cross-checking/cross-file-result.ts`:
|
### 7.1 交叉意见相关(`cross-file-result.ts`)
|
||||||
|
|
||||||
| 函数 | HTTP | 路径 | 用途 |
|
| 函数 | HTTP | 路径 | 用途 |
|
||||||
|------|------|------|------|
|
|------|------|------|------|
|
||||||
@@ -807,6 +860,45 @@ import { CrossCheckingOpinionsPanel } from "@/components/cross-checking/CrossChe
|
|||||||
| `updateCrossCheckingReviewResult` | PATCH | `/api/v3/review-points/{id}/audit` | 更新评查结果状态 |
|
| `updateCrossCheckingReviewResult` | PATCH | `/api/v3/review-points/{id}/audit` | 更新评查结果状态 |
|
||||||
| `findIsProposer` | GET | `/api/v3/cross-review/tasks/{taskId}/can-confirm` | 检查是否负责人 |
|
| `findIsProposer` | GET | `/api/v3/cross-review/tasks/{taskId}/can-confirm` | 检查是否负责人 |
|
||||||
|
|
||||||
|
### 7.2 任务列表与文档列表(`cross-files.ts`)
|
||||||
|
|
||||||
|
| 函数 | HTTP | 路径 | 用途 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `getUserTaskDocuments` | POST | `/api/v3/cross-review/tasks/query` | 获取用户任务列表(V3) |
|
||||||
|
| `getTaskDocumentsWithVersions` | GET | `/api/v3/cross-review/tasks/{id}/documents` | 获取任务文档列表(含版本归纳) |
|
||||||
|
| `getCrossCheckingDocumentTypes` | — | — | 获取可选文档类型 |
|
||||||
|
| `getCrossCheckingTasks` | — | — | 任务列表包装(含前端筛选) |
|
||||||
|
|
||||||
|
### 7.3 版本号任务本地化
|
||||||
|
|
||||||
|
`d.version_no` 是文档的**全局**版本号(跨所有任务),前端在 `getTaskDocumentsWithVersions` 中做任务本地重映射:
|
||||||
|
|
||||||
|
```
|
||||||
|
同一 version_group_key 内:
|
||||||
|
1. 按原始 version_number 升序排列(保留全局先后顺序)
|
||||||
|
2. 重新分配 version_number = 1, 2, 3...
|
||||||
|
3. 同时更新 history_versions 中的 version_number
|
||||||
|
```
|
||||||
|
|
||||||
|
**示例**:文档全局版本 v11 → 任务内第 1 次上传 → 显示 V1;v12 → 第 2 次 → 显示 V2。
|
||||||
|
|
||||||
|
### 7.4 评查统计数据映射
|
||||||
|
|
||||||
|
前端 `getTaskDocumentsWithVersions` 将后端 V3 返回的 camelCase 字段映射为内部使用的 snake_case:
|
||||||
|
|
||||||
|
```
|
||||||
|
API 返回 (JSON) → 前端类型 (CrossReviewDocumentWithVersion)
|
||||||
|
────────────────────────────────────────────────────────────
|
||||||
|
passCount → pass_count
|
||||||
|
warningCount → warning_count
|
||||||
|
errorCount → error_count
|
||||||
|
manualCount → manual_count
|
||||||
|
scorePercent → score_percent
|
||||||
|
warningMessages → warning_messages
|
||||||
|
errorMessages → error_messages
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 8. 补充:后端服务实现详解
|
## 8. 补充:后端服务实现详解
|
||||||
@@ -825,7 +917,12 @@ class CrossReviewServiceImpl(ICrossReviewService):
|
|||||||
|
|
||||||
async def GetUserTasks(self, CurrentUserId: int, dto: CrossReviewTaskQueryDTO)
|
async def GetUserTasks(self, CurrentUserId: int, dto: CrossReviewTaskQueryDTO)
|
||||||
-> CrossReviewTaskPageVO:
|
-> CrossReviewTaskPageVO:
|
||||||
"""分页查询当前用户参与的任务"""
|
"""
|
||||||
|
分页查询当前用户参与的任务。
|
||||||
|
SQL 含两个 CTE:
|
||||||
|
- doc_stats: 统计每任务 total_documents / completed_documents
|
||||||
|
- task_regions: 从任务成员 JOIN sso_users 收集去重 area AS evaluationRegion
|
||||||
|
"""
|
||||||
|
|
||||||
async def GetTaskProgress(self, CurrentUserId: int, TaskId: int)
|
async def GetTaskProgress(self, CurrentUserId: int, TaskId: int)
|
||||||
-> CrossReviewTaskProgressVO:
|
-> CrossReviewTaskProgressVO:
|
||||||
@@ -834,7 +931,12 @@ class CrossReviewServiceImpl(ICrossReviewService):
|
|||||||
async def GetTaskDocuments(self, CurrentUserId: int, TaskId: int,
|
async def GetTaskDocuments(self, CurrentUserId: int, TaskId: int,
|
||||||
dto: CrossReviewTaskDocumentQueryDTO)
|
dto: CrossReviewTaskDocumentQueryDTO)
|
||||||
-> CrossReviewTaskDocumentPageVO:
|
-> CrossReviewTaskDocumentPageVO:
|
||||||
"""分页查询任务下的文档列表"""
|
"""
|
||||||
|
分页查询任务下的文档列表。
|
||||||
|
含版本归纳:同一 version_group_key 的文档归为一组,totalVersions 为任务内计数。
|
||||||
|
含评查统计 LATERAL 子查询 (es):聚合 leaudit_rule_results 的
|
||||||
|
pass_count / warning_count / error_count / score_percent 等。
|
||||||
|
"""
|
||||||
|
|
||||||
async def CanConfirmTaskDocument(self, CurrentUserId: int, TaskId: int)
|
async def CanConfirmTaskDocument(self, CurrentUserId: int, TaskId: int)
|
||||||
-> CrossReviewPermissionVO:
|
-> CrossReviewPermissionVO:
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ class CrossReviewTaskItemVO(BaseModel):
|
|||||||
totalDocuments: int = Field(0, description="文档总数")
|
totalDocuments: int = Field(0, description="文档总数")
|
||||||
completedDocuments: int = Field(0, description="已完成文档数")
|
completedDocuments: int = Field(0, description="已完成文档数")
|
||||||
createdAt: datetime | None = Field(None, description="创建时间")
|
createdAt: datetime | None = Field(None, description="创建时间")
|
||||||
|
evaluationRegion: list[str] = Field(default_factory=list, description="评查地区")
|
||||||
|
|
||||||
|
|
||||||
class CrossReviewTaskPageVO(BaseModel):
|
class CrossReviewTaskPageVO(BaseModel):
|
||||||
@@ -56,6 +57,23 @@ class CrossReviewTaskDocumentVO(BaseModel):
|
|||||||
auditStatus: int = Field(0, description="任务内完成状态")
|
auditStatus: int = Field(0, description="任务内完成状态")
|
||||||
createdAt: datetime | None = Field(None, description="创建时间")
|
createdAt: datetime | None = Field(None, description="创建时间")
|
||||||
fileSize: int = Field(0, description="文件大小(字节)")
|
fileSize: int = Field(0, description="文件大小(字节)")
|
||||||
|
path: str | None = Field(None, description="文件存储路径")
|
||||||
|
uploadTime: datetime | None = Field(None, description="上传时间")
|
||||||
|
fileExt: str | None = Field(None, description="文件扩展名")
|
||||||
|
totalEvaluationPoints: int = Field(0, description="总评查点数")
|
||||||
|
passCount: int = Field(0, description="通过数")
|
||||||
|
warningCount: int = Field(0, description="警告数")
|
||||||
|
errorCount: int = Field(0, description="错误数")
|
||||||
|
manualCount: int = Field(0, description="人工审核数")
|
||||||
|
issueCount: int = Field(0, description="问题总数")
|
||||||
|
warningMessages: list[str] = Field(default_factory=list, description="警告消息")
|
||||||
|
errorMessages: list[str] = Field(default_factory=list, description="错误消息")
|
||||||
|
issueMessages: list[str] = Field(default_factory=list, description="问题消息")
|
||||||
|
manualMessages: list[str] = Field(default_factory=list, description="人工审核消息")
|
||||||
|
finalScore: float = Field(0, description="最终得分")
|
||||||
|
fullScore: float = Field(0, description="满分")
|
||||||
|
scoreSummary: str = Field("", description="得分摘要")
|
||||||
|
scorePercent: float = Field(0, description="得分百分比")
|
||||||
|
|
||||||
|
|
||||||
class CrossReviewTaskDocumentPageVO(BaseModel):
|
class CrossReviewTaskDocumentPageVO(BaseModel):
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ class ReviewPointResultVO(BaseModel):
|
|||||||
score: float = Field(0, description="分值")
|
score: float = Field(0, description="分值")
|
||||||
finalScore: float | None = Field(None, description="最终得分")
|
finalScore: float | None = Field(None, description="最终得分")
|
||||||
machineScore: float | None = Field(None, description="机器得分")
|
machineScore: float | None = Field(None, description="机器得分")
|
||||||
|
currentScore: float = Field(0, description="当前得分(含提案扣分/加分)")
|
||||||
result: bool | None = Field(None, description="是否通过")
|
result: bool | None = Field(None, description="是否通过")
|
||||||
failMessage: str = Field("", description="失败提示")
|
failMessage: str = Field("", description="失败提示")
|
||||||
passMessage: str = Field("", description="通过提示")
|
passMessage: str = Field("", description="通过提示")
|
||||||
|
|||||||
@@ -303,6 +303,17 @@ class CrossReviewServiceImpl(ICrossReviewService):
|
|||||||
FROM leaudit_cross_review_task_documents td
|
FROM leaudit_cross_review_task_documents td
|
||||||
WHERE td.delete_time IS NULL
|
WHERE td.delete_time IS NULL
|
||||||
GROUP BY td.task_id
|
GROUP BY td.task_id
|
||||||
|
),
|
||||||
|
task_regions AS (
|
||||||
|
SELECT
|
||||||
|
tm.task_id,
|
||||||
|
ARRAY_AGG(DISTINCT u.area ORDER BY u.area) FILTER (
|
||||||
|
WHERE u.area IS NOT NULL AND u.area != ''
|
||||||
|
) AS evaluation_regions
|
||||||
|
FROM leaudit_cross_review_task_members tm
|
||||||
|
JOIN sso_users u ON u.id = tm.user_id
|
||||||
|
WHERE tm.delete_time IS NULL
|
||||||
|
GROUP BY tm.task_id
|
||||||
)
|
)
|
||||||
SELECT
|
SELECT
|
||||||
t.id AS task_id,
|
t.id AS task_id,
|
||||||
@@ -313,16 +324,20 @@ class CrossReviewServiceImpl(ICrossReviewService):
|
|||||||
t.status,
|
t.status,
|
||||||
t.create_time,
|
t.create_time,
|
||||||
COALESCE(ds.total_documents, 0) AS total_documents,
|
COALESCE(ds.total_documents, 0) AS total_documents,
|
||||||
COALESCE(ds.completed_documents, 0) AS completed_documents
|
COALESCE(ds.completed_documents, 0) AS completed_documents,
|
||||||
|
COALESCE(tr.evaluation_regions, ARRAY[]::varchar[]) AS evaluation_regions
|
||||||
FROM leaudit_cross_review_tasks t
|
FROM leaudit_cross_review_tasks t
|
||||||
JOIN leaudit_cross_review_task_members tm
|
JOIN leaudit_cross_review_task_members tm
|
||||||
ON tm.task_id = t.id
|
ON tm.task_id = t.id
|
||||||
LEFT JOIN doc_stats ds
|
LEFT JOIN doc_stats ds
|
||||||
ON ds.task_id = t.id
|
ON ds.task_id = t.id
|
||||||
|
LEFT JOIN task_regions tr
|
||||||
|
ON tr.task_id = t.id
|
||||||
WHERE {whereSql}
|
WHERE {whereSql}
|
||||||
GROUP BY
|
GROUP BY
|
||||||
t.id, t.task_name, t.task_type, t.doc_type_id, t.doc_type_code,
|
t.id, t.task_name, t.task_type, t.doc_type_id, t.doc_type_code,
|
||||||
t.status, t.create_time, ds.total_documents, ds.completed_documents
|
t.status, t.create_time, ds.total_documents, ds.completed_documents,
|
||||||
|
tr.evaluation_regions
|
||||||
ORDER BY t.create_time DESC, t.id DESC
|
ORDER BY t.create_time DESC, t.id DESC
|
||||||
LIMIT :limit OFFSET :offset
|
LIMIT :limit OFFSET :offset
|
||||||
"""
|
"""
|
||||||
@@ -336,6 +351,13 @@ class CrossReviewServiceImpl(ICrossReviewService):
|
|||||||
totalDocuments = int(row["total_documents"] or 0)
|
totalDocuments = int(row["total_documents"] or 0)
|
||||||
completedDocuments = int(row["completed_documents"] or 0)
|
completedDocuments = int(row["completed_documents"] or 0)
|
||||||
progress = round((completedDocuments / totalDocuments * 100) if totalDocuments > 0 else 0, 2)
|
progress = round((completedDocuments / totalDocuments * 100) if totalDocuments > 0 else 0, 2)
|
||||||
|
rawRegions = row.get("evaluation_regions")
|
||||||
|
if rawRegions is None:
|
||||||
|
evaluationRegion: list[str] = []
|
||||||
|
elif isinstance(rawRegions, list):
|
||||||
|
evaluationRegion = [str(r) for r in rawRegions]
|
||||||
|
else:
|
||||||
|
evaluationRegion = [str(rawRegions)]
|
||||||
items.append(
|
items.append(
|
||||||
CrossReviewTaskItemVO(
|
CrossReviewTaskItemVO(
|
||||||
taskId=int(row["task_id"]),
|
taskId=int(row["task_id"]),
|
||||||
@@ -348,6 +370,7 @@ class CrossReviewServiceImpl(ICrossReviewService):
|
|||||||
totalDocuments=totalDocuments,
|
totalDocuments=totalDocuments,
|
||||||
completedDocuments=completedDocuments,
|
completedDocuments=completedDocuments,
|
||||||
createdAt=row.get("create_time"),
|
createdAt=row.get("create_time"),
|
||||||
|
evaluationRegion=evaluationRegion,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -453,7 +476,24 @@ class CrossReviewServiceImpl(ICrossReviewService):
|
|||||||
d.created_at,
|
d.created_at,
|
||||||
td.audit_status,
|
td.audit_status,
|
||||||
COALESCE(dt.name, '') AS type_name,
|
COALESCE(dt.name, '') AS type_name,
|
||||||
COALESCE(df.file_size, 0) AS file_size
|
COALESCE(df.file_size, 0) AS file_size,
|
||||||
|
COALESCE(df.file_path, '') AS path,
|
||||||
|
df.file_upload_time AS upload_time,
|
||||||
|
COALESCE(df.file_ext, '') AS file_ext,
|
||||||
|
COALESCE(es.total_evaluation_points, 0) AS total_evaluation_points,
|
||||||
|
COALESCE(es.pass_count, 0) AS pass_count,
|
||||||
|
COALESCE(es.warning_count, 0) AS warning_count,
|
||||||
|
COALESCE(es.error_count, 0) AS error_count,
|
||||||
|
COALESCE(es.manual_count, 0) AS manual_count,
|
||||||
|
COALESCE(es.issue_count, 0) AS issue_count,
|
||||||
|
COALESCE(es.warning_messages, ARRAY[]::text[]) AS warning_messages,
|
||||||
|
COALESCE(es.error_messages, ARRAY[]::text[]) AS error_messages,
|
||||||
|
COALESCE(es.issue_messages, ARRAY[]::text[]) AS issue_messages,
|
||||||
|
COALESCE(es.manual_messages, ARRAY[]::text[]) AS manual_messages,
|
||||||
|
COALESCE(es.final_score, 0) AS final_score,
|
||||||
|
COALESCE(es.full_score, 0) AS full_score,
|
||||||
|
COALESCE(es.score_summary, '') AS score_summary,
|
||||||
|
COALESCE(es.score_percent, 0) AS score_percent
|
||||||
FROM leaudit_cross_review_task_documents td
|
FROM leaudit_cross_review_task_documents td
|
||||||
JOIN leaudit_documents d
|
JOIN leaudit_documents d
|
||||||
ON d.id = td.document_id
|
ON d.id = td.document_id
|
||||||
@@ -462,7 +502,8 @@ class CrossReviewServiceImpl(ICrossReviewService):
|
|||||||
LEFT JOIN leaudit_document_types dt
|
LEFT JOIN leaudit_document_types dt
|
||||||
ON dt.id = d.type_id
|
ON dt.id = d.type_id
|
||||||
LEFT JOIN LATERAL (
|
LEFT JOIN LATERAL (
|
||||||
SELECT file_size
|
SELECT file_size, local_path AS file_path, created_at AS file_upload_time,
|
||||||
|
COALESCE(file_ext, '') AS file_ext
|
||||||
FROM leaudit_document_files
|
FROM leaudit_document_files
|
||||||
WHERE document_id = d.id
|
WHERE document_id = d.id
|
||||||
ORDER BY id ASC
|
ORDER BY id ASC
|
||||||
@@ -478,6 +519,51 @@ class CrossReviewServiceImpl(ICrossReviewService):
|
|||||||
WHERE d2.deleted_at IS NULL
|
WHERE d2.deleted_at IS NULL
|
||||||
GROUP BY d2.version_group_key
|
GROUP BY d2.version_group_key
|
||||||
) vc ON vc.version_group_key = d.version_group_key
|
) vc ON vc.version_group_key = d.version_group_key
|
||||||
|
LEFT JOIN LATERAL (
|
||||||
|
SELECT
|
||||||
|
COUNT(*)::int AS total_evaluation_points,
|
||||||
|
COUNT(*) FILTER (WHERE rr.passed IS TRUE)::int AS pass_count,
|
||||||
|
COUNT(*) FILTER (WHERE rr.passed IS FALSE AND rr.risk = 'high')::int AS error_count,
|
||||||
|
COUNT(*) FILTER (WHERE rr.passed IS FALSE AND rr.risk IN ('low', 'medium'))::int AS warning_count,
|
||||||
|
0::int AS manual_count,
|
||||||
|
COUNT(*) FILTER (WHERE rr.passed IS FALSE)::int AS issue_count,
|
||||||
|
ARRAY_AGG(rr.fail_message ORDER BY rr.id) FILTER (
|
||||||
|
WHERE rr.passed IS FALSE AND rr.risk = 'high' AND rr.fail_message IS NOT NULL AND rr.fail_message != ''
|
||||||
|
) AS error_messages,
|
||||||
|
ARRAY_AGG(rr.fail_message ORDER BY rr.id) FILTER (
|
||||||
|
WHERE rr.passed IS FALSE AND rr.risk IN ('low', 'medium') AND rr.fail_message IS NOT NULL AND rr.fail_message != ''
|
||||||
|
) AS warning_messages,
|
||||||
|
ARRAY_AGG(rr.fail_message ORDER BY rr.id) FILTER (
|
||||||
|
WHERE rr.passed IS FALSE AND rr.fail_message IS NOT NULL AND rr.fail_message != ''
|
||||||
|
) AS issue_messages,
|
||||||
|
ARRAY[]::text[] AS manual_messages,
|
||||||
|
COALESCE(SUM(rr.score) FILTER (WHERE rr.passed IS TRUE), 0) AS final_score,
|
||||||
|
COALESCE(SUM(rr.score), 0) AS full_score,
|
||||||
|
CASE
|
||||||
|
WHEN COALESCE(SUM(rr.score), 0) > 0
|
||||||
|
THEN CONCAT(
|
||||||
|
ROUND(
|
||||||
|
COALESCE(SUM(rr.score) FILTER (WHERE rr.passed IS TRUE), 0)::numeric,
|
||||||
|
1
|
||||||
|
)::text,
|
||||||
|
'/',
|
||||||
|
ROUND(COALESCE(SUM(rr.score), 0)::numeric, 1)::text
|
||||||
|
)
|
||||||
|
ELSE '0/0'
|
||||||
|
END AS score_summary,
|
||||||
|
CASE
|
||||||
|
WHEN COALESCE(SUM(rr.score), 0) > 0
|
||||||
|
THEN ROUND(
|
||||||
|
COALESCE(SUM(rr.score) FILTER (WHERE rr.passed IS TRUE), 0)
|
||||||
|
/ SUM(rr.score) * 100,
|
||||||
|
1
|
||||||
|
)
|
||||||
|
ELSE 0
|
||||||
|
END AS score_percent
|
||||||
|
FROM leaudit_rule_results rr
|
||||||
|
WHERE rr.document_id = d.id
|
||||||
|
AND rr.run_id = d.current_run_id
|
||||||
|
) es ON TRUE
|
||||||
WHERE {whereSql}
|
WHERE {whereSql}
|
||||||
ORDER BY d.created_at DESC, d.id DESC
|
ORDER BY d.created_at DESC, d.id DESC
|
||||||
LIMIT :limit OFFSET :offset
|
LIMIT :limit OFFSET :offset
|
||||||
@@ -502,6 +588,23 @@ class CrossReviewServiceImpl(ICrossReviewService):
|
|||||||
auditStatus=int(row.get("audit_status") or 0),
|
auditStatus=int(row.get("audit_status") or 0),
|
||||||
createdAt=row.get("created_at"),
|
createdAt=row.get("created_at"),
|
||||||
fileSize=int(row.get("file_size") or 0),
|
fileSize=int(row.get("file_size") or 0),
|
||||||
|
path=str(row.get("path") or ""),
|
||||||
|
uploadTime=row.get("upload_time"),
|
||||||
|
fileExt=str(row.get("file_ext") or "") or None,
|
||||||
|
totalEvaluationPoints=int(row.get("total_evaluation_points") or 0),
|
||||||
|
passCount=int(row.get("pass_count") or 0),
|
||||||
|
warningCount=int(row.get("warning_count") or 0),
|
||||||
|
errorCount=int(row.get("error_count") or 0),
|
||||||
|
manualCount=int(row.get("manual_count") or 0),
|
||||||
|
issueCount=int(row.get("issue_count") or 0),
|
||||||
|
warningMessages=self._parse_text_array(row.get("warning_messages")),
|
||||||
|
errorMessages=self._parse_text_array(row.get("error_messages")),
|
||||||
|
issueMessages=self._parse_text_array(row.get("issue_messages")),
|
||||||
|
manualMessages=self._parse_text_array(row.get("manual_messages")),
|
||||||
|
finalScore=float(row.get("final_score") or 0),
|
||||||
|
fullScore=float(row.get("full_score") or 0),
|
||||||
|
scoreSummary=str(row.get("score_summary") or ""),
|
||||||
|
scorePercent=float(row.get("score_percent") or 0),
|
||||||
)
|
)
|
||||||
for row in rows
|
for row in rows
|
||||||
]
|
]
|
||||||
@@ -1411,3 +1514,11 @@ class CrossReviewServiceImpl(ICrossReviewService):
|
|||||||
if value is None:
|
if value is None:
|
||||||
return None
|
return None
|
||||||
return int(value)
|
return int(value)
|
||||||
|
|
||||||
|
def _parse_text_array(self, value) -> list[str]:
|
||||||
|
"""安全解析 PostgreSQL text[] 为字符串列表。"""
|
||||||
|
if value is None:
|
||||||
|
return []
|
||||||
|
if isinstance(value, list):
|
||||||
|
return [str(v) for v in value]
|
||||||
|
return [str(value)]
|
||||||
|
|||||||
@@ -2366,10 +2366,19 @@ class DocumentServiceImpl(IDocumentService):
|
|||||||
a.id AS audit_id,
|
a.id AS audit_id,
|
||||||
a.edit_audit_status,
|
a.edit_audit_status,
|
||||||
a.override_result,
|
a.override_result,
|
||||||
a.message AS audit_message
|
a.message AS audit_message,
|
||||||
|
COALESCE(ad.approved_delta, 0) AS approved_delta
|
||||||
FROM leaudit_rule_results rr
|
FROM leaudit_rule_results rr
|
||||||
LEFT JOIN leaudit_review_point_audits a
|
LEFT JOIN leaudit_review_point_audits a
|
||||||
ON a.rule_result_id = rr.id
|
ON a.rule_result_id = rr.id
|
||||||
|
LEFT JOIN LATERAL (
|
||||||
|
SELECT COALESCE(SUM(proposed_score_delta), 0) AS approved_delta
|
||||||
|
FROM leaudit_cross_review_proposals
|
||||||
|
WHERE rule_result_id = rr.id
|
||||||
|
AND document_id = :document_id
|
||||||
|
AND status = 'approved'
|
||||||
|
AND delete_time IS NULL
|
||||||
|
) ad ON TRUE
|
||||||
WHERE rr.run_id = :run_id
|
WHERE rr.run_id = :run_id
|
||||||
AND rr.document_id = :document_id
|
AND rr.document_id = :document_id
|
||||||
ORDER BY id ASC
|
ORDER BY id ASC
|
||||||
@@ -2429,6 +2438,10 @@ class DocumentServiceImpl(IDocumentService):
|
|||||||
score=score,
|
score=score,
|
||||||
finalScore=score if resultFlag is True else (0.0 if resultFlag is False else None),
|
finalScore=score if resultFlag is True else (0.0 if resultFlag is False else None),
|
||||||
machineScore=score,
|
machineScore=score,
|
||||||
|
currentScore=(
|
||||||
|
(score if resultFlag is True else 0.0)
|
||||||
|
+ float(row["approved_delta"] or 0)
|
||||||
|
),
|
||||||
result=resultFlag,
|
result=resultFlag,
|
||||||
failMessage=str(row["fail_message"] or ""),
|
failMessage=str(row["fail_message"] or ""),
|
||||||
passMessage=str(row["pass_message"] or ""),
|
passMessage=str(row["pass_message"] or ""),
|
||||||
|
|||||||
Submodule
+1
Submodule legal-platform-frontend added at f6bb4aa552
Reference in New Issue
Block a user