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:
wren
2026-05-15 14:15:29 +08:00
parent 397cbb111a
commit adc1e0b8dc
7 changed files with 393 additions and 11 deletions
@@ -303,6 +303,17 @@ class CrossReviewServiceImpl(ICrossReviewService):
FROM leaudit_cross_review_task_documents td
WHERE td.delete_time IS NULL
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
t.id AS task_id,
@@ -313,16 +324,20 @@ class CrossReviewServiceImpl(ICrossReviewService):
t.status,
t.create_time,
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
JOIN leaudit_cross_review_task_members tm
ON tm.task_id = t.id
LEFT JOIN doc_stats ds
ON ds.task_id = t.id
LEFT JOIN task_regions tr
ON tr.task_id = t.id
WHERE {whereSql}
GROUP BY
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
LIMIT :limit OFFSET :offset
"""
@@ -336,6 +351,13 @@ class CrossReviewServiceImpl(ICrossReviewService):
totalDocuments = int(row["total_documents"] or 0)
completedDocuments = int(row["completed_documents"] or 0)
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(
CrossReviewTaskItemVO(
taskId=int(row["task_id"]),
@@ -348,6 +370,7 @@ class CrossReviewServiceImpl(ICrossReviewService):
totalDocuments=totalDocuments,
completedDocuments=completedDocuments,
createdAt=row.get("create_time"),
evaluationRegion=evaluationRegion,
)
)
@@ -453,7 +476,24 @@ class CrossReviewServiceImpl(ICrossReviewService):
d.created_at,
td.audit_status,
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
JOIN leaudit_documents d
ON d.id = td.document_id
@@ -462,7 +502,8 @@ class CrossReviewServiceImpl(ICrossReviewService):
LEFT JOIN leaudit_document_types dt
ON dt.id = d.type_id
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
WHERE document_id = d.id
ORDER BY id ASC
@@ -478,6 +519,51 @@ class CrossReviewServiceImpl(ICrossReviewService):
WHERE d2.deleted_at IS NULL
GROUP BY d2.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}
ORDER BY d.created_at DESC, d.id DESC
LIMIT :limit OFFSET :offset
@@ -502,6 +588,23 @@ class CrossReviewServiceImpl(ICrossReviewService):
auditStatus=int(row.get("audit_status") or 0),
createdAt=row.get("created_at"),
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
]
@@ -1411,3 +1514,11 @@ class CrossReviewServiceImpl(ICrossReviewService):
if value is None:
return None
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.edit_audit_status,
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
LEFT JOIN leaudit_review_point_audits a
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
AND rr.document_id = :document_id
ORDER BY id ASC
@@ -2429,6 +2438,10 @@ class DocumentServiceImpl(IDocumentService):
score=score,
finalScore=score if resultFlag is True else (0.0 if resultFlag is False else None),
machineScore=score,
currentScore=(
(score if resultFlag is True else 0.0)
+ float(row["approved_delta"] or 0)
),
result=resultFlag,
failMessage=str(row["fail_message"] or ""),
passMessage=str(row["pass_message"] or ""),