feat: update audit platform workspace

This commit is contained in:
wren
2026-05-25 09:50:01 +08:00
parent ba8e93c0d3
commit 68d0b4c878
73 changed files with 12196 additions and 367 deletions
@@ -200,7 +200,7 @@ class CrossReviewServiceImpl(ICrossReviewService):
await self._ensure_tables_ready(session)
memberUserIds = self._unique_int_list(Body.memberUserIds + [CurrentUserId])
principalUserIds = self._unique_int_list(Body.principalUserIds)
principalUserIds = self._unique_int_list(Body.principalUserIds + [CurrentUserId])
documentIds = self._unique_int_list(Body.documentIds)
await self._assert_task_scope_inputs(session, CurrentUserId, memberUserIds, documentIds)
@@ -289,7 +289,12 @@ class CrossReviewServiceImpl(ICrossReviewService):
documentCount=len(documentIds),
)
async def GetUserTasks(self, CurrentUserId: int, Body: CrossReviewTaskQueryDTO) -> CrossReviewTaskPageVO:
async def GetUserTasks(
self,
CurrentUserId: int,
Body: CrossReviewTaskQueryDTO,
CanViewProgress: bool = True,
) -> CrossReviewTaskPageVO:
"""查询当前用户参与的交叉评查任务。"""
async with GetAsyncSession() as session:
await self._ensure_tables_ready(session)
@@ -401,6 +406,15 @@ class CrossReviewServiceImpl(ICrossReviewService):
t.doc_type_code,
t.status,
t.create_time,
CASE
WHEN t.assigner_id = :current_user_id THEN 'assigner'
ELSE COALESCE(MAX(tm.member_role), 'participant')
END AS current_user_role,
CASE
WHEN t.assigner_id = :current_user_id THEN TRUE
WHEN COALESCE(MAX(CASE WHEN tm.member_role = 'principal' THEN 1 ELSE 0 END), 0) = 1 THEN TRUE
ELSE FALSE
END AS current_user_can_confirm,
COALESCE(ds.total_documents, 0) AS total_documents,
COALESCE(ds.completed_documents, 0) AS completed_documents,
COALESCE(tt.evaluation_tenants, '[]'::jsonb) AS evaluation_tenants,
@@ -416,7 +430,7 @@ class CrossReviewServiceImpl(ICrossReviewService):
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.id, t.task_name, t.task_type, t.doc_type_id, t.doc_type_code, t.assigner_id,
t.status, t.create_time, ds.total_documents, ds.completed_documents, tt.evaluation_tenants,
tr.evaluation_regions
ORDER BY t.create_time DESC, t.id DESC
@@ -429,35 +443,7 @@ class CrossReviewServiceImpl(ICrossReviewService):
items = []
for row in rows:
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)
evaluationTenants = self._parse_task_tenants(row.get("evaluation_tenants"))
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)]
if not evaluationRegion:
evaluationRegion = [tenant.tenantName for tenant in evaluationTenants if tenant.tenantName]
items.append(
CrossReviewTaskItemVO(
taskId=int(row["task_id"]),
taskName=str(row["task_name"]),
taskType=str(row["task_type"]),
docTypeId=self._to_int(row.get("doc_type_id")),
docTypeCode=row.get("doc_type_code"),
status=str(row["status"]),
progress=progress,
totalDocuments=totalDocuments,
completedDocuments=completedDocuments,
createdAt=row.get("create_time"),
evaluationTenants=evaluationTenants,
evaluationRegion=evaluationRegion,
)
)
items.append(self._build_task_item_vo(row=row, CanViewProgress=CanViewProgress))
return CrossReviewTaskPageVO(total=total, page=Body.page, pageSize=Body.pageSize, items=items)
@@ -518,7 +504,32 @@ class CrossReviewServiceImpl(ICrossReviewService):
baseWhereClauses.append("(d.normalized_name ILIKE :keyword OR CAST(d.biz_document_id AS TEXT) ILIKE :keyword)")
params["keyword"] = f"%{Body.keyword.strip()}%"
baseWhereSql = " AND ".join(baseWhereClauses)
latestWhereSql = f"{baseWhereSql} AND COALESCE(d.is_latest_version, false) = true"
taskDocumentsSql = f"""
SELECT
td.task_id,
td.document_id,
td.audit_status,
d.id,
d.normalized_name,
d.biz_document_id,
d.type_id,
d.processing_status,
d.version_no,
d.is_latest_version,
d.version_group_key,
d.root_version_id,
d.current_run_id,
d.created_at,
COALESCE(NULLIF(d.version_group_key, ''), CONCAT('root:', COALESCE(d.root_version_id, d.id)::text)) AS task_version_group_key,
ROW_NUMBER() OVER (
PARTITION BY COALESCE(NULLIF(d.version_group_key, ''), CONCAT('root:', COALESCE(d.root_version_id, d.id)::text))
ORDER BY d.version_no DESC, d.id DESC
) AS task_version_rank
FROM leaudit_cross_review_task_documents td
JOIN leaudit_documents d
ON d.id = td.document_id
WHERE {baseWhereSql}
"""
total = int(
(
@@ -526,10 +537,8 @@ class CrossReviewServiceImpl(ICrossReviewService):
text(
f"""
SELECT COUNT(*)
FROM leaudit_cross_review_task_documents td
JOIN leaudit_documents d
ON d.id = td.document_id
WHERE {latestWhereSql}
FROM ({taskDocumentsSql}) task_docs
WHERE task_docs.task_version_rank = 1
"""
),
params,
@@ -558,10 +567,10 @@ class CrossReviewServiceImpl(ICrossReviewService):
) AS processing_status,
d.version_no,
d.is_latest_version,
COALESCE(NULLIF(d.version_group_key, ''), CONCAT('root:', COALESCE(d.root_version_id, d.id)::text)) AS version_group_key,
d.task_version_group_key AS version_group_key,
COALESCE(vc.total_versions, 1)::int AS total_versions,
d.created_at,
td.audit_status,
d.audit_status,
COALESCE(dt.name, '') AS type_name,
COALESCE(df.file_size, 0) AS file_size,
COALESCE(df.file_path, '') AS path,
@@ -581,9 +590,7 @@ class CrossReviewServiceImpl(ICrossReviewService):
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
FROM ({taskDocumentsSql}) d
LEFT JOIN leaudit_audit_runs ar
ON ar.id = d.current_run_id
LEFT JOIN leaudit_document_types dt
@@ -608,7 +615,7 @@ class CrossReviewServiceImpl(ICrossReviewService):
WHERE d2.deleted_at IS NULL
GROUP BY d2.version_group_key
, COALESCE(d2.root_version_id, d2.id)
) vc ON vc.version_group_key = COALESCE(NULLIF(d.version_group_key, ''), CONCAT('root:', COALESCE(d.root_version_id, d.id)::text))
) vc ON vc.version_group_key = d.task_version_group_key
LEFT JOIN LATERAL (
SELECT
COUNT(*)::int AS total_evaluation_points,
@@ -661,7 +668,7 @@ class CrossReviewServiceImpl(ICrossReviewService):
AND p.status = 'approved'
AND p.delete_time IS NULL
) pd ON TRUE
WHERE {latestWhereSql}
WHERE d.task_version_rank = 1
ORDER BY d.created_at DESC, d.id DESC
LIMIT :limit OFFSET :offset
"""
@@ -2099,13 +2106,15 @@ class CrossReviewServiceImpl(ICrossReviewService):
if not member_tenant_code and not member_tenant_name:
raise LeauditException(StatusCodeEnum.HTTP_400_BAD_REQUEST, f"成员用户未绑定有效租户: {user_id}")
resolved_scopes.append({"tenant_code": member_tenant_code, "tenant_name": member_tenant_name})
if is_global:
continue
if current_tenant_code:
if member_tenant_code:
if member_tenant_code != current_tenant_code:
raise LeauditException(StatusCodeEnum.HTTP_403_FORBIDDEN, f"不能将其他租户用户加入交叉评查任务: {user_id}")
elif member_tenant_name != current_tenant_name:
raise LeauditException(StatusCodeEnum.HTTP_403_FORBIDDEN, f"不能将其他租户用户加入交叉评查任务: {user_id}")
elif not is_global and member_tenant_name != current_tenant_name:
elif member_tenant_name != current_tenant_name:
raise LeauditException(StatusCodeEnum.HTTP_403_FORBIDDEN, f"不能将其他租户用户加入交叉评查任务: {user_id}")
if document_ids:
@@ -2141,13 +2150,15 @@ class CrossReviewServiceImpl(ICrossReviewService):
if not document_tenant_code and not document_tenant_name:
raise LeauditException(StatusCodeEnum.HTTP_400_BAD_REQUEST, f"任务文档未绑定有效租户: {document_id}")
resolved_scopes.append({"tenant_code": document_tenant_code, "tenant_name": document_tenant_name})
if is_global:
continue
if current_tenant_code:
if document_tenant_code:
if document_tenant_code != current_tenant_code:
raise LeauditException(StatusCodeEnum.HTTP_403_FORBIDDEN, f"不能将其他租户文档加入交叉评查任务: {document_id}")
elif document_tenant_name != current_tenant_name:
raise LeauditException(StatusCodeEnum.HTTP_403_FORBIDDEN, f"不能将其他租户文档加入交叉评查任务: {document_id}")
elif not is_global and document_tenant_name != current_tenant_name:
elif document_tenant_name != current_tenant_name:
raise LeauditException(StatusCodeEnum.HTTP_403_FORBIDDEN, f"不能将其他租户文档加入交叉评查任务: {document_id}")
self._assert_single_task_scope(resolved_scopes)
@@ -2283,13 +2294,15 @@ class CrossReviewServiceImpl(ICrossReviewService):
result.append(intValue)
return result
def _to_int(self, value) -> int | None:
@staticmethod
def _to_int(value) -> int | None:
"""安全转 int。"""
if value is None:
return None
return int(value)
def _parse_text_array(self, value) -> list[str]:
@staticmethod
def _parse_text_array(value) -> list[str]:
"""安全解析 PostgreSQL text[] 为字符串列表。"""
if value is None:
return []
@@ -2297,7 +2310,8 @@ class CrossReviewServiceImpl(ICrossReviewService):
return [str(v) for v in value]
return [str(value)]
def _parse_task_tenants(self, value) -> list[CrossReviewTaskTenantVO]:
@staticmethod
def _parse_task_tenants(value) -> list[CrossReviewTaskTenantVO]:
"""安全解析任务租户 JSON 聚合结果。"""
if value is None:
return []
@@ -2319,6 +2333,40 @@ class CrossReviewServiceImpl(ICrossReviewService):
result.append(CrossReviewTaskTenantVO(tenantCode=tenant_code, tenantName=tenant_name or tenant_code))
return result
@classmethod
def _build_task_item_vo(cls, row, CanViewProgress: bool = True) -> CrossReviewTaskItemVO:
"""根据进度权限组装任务列表项。"""
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)
evaluationTenants = cls._parse_task_tenants(row.get("evaluation_tenants"))
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)]
if not evaluationRegion:
evaluationRegion = [tenant.tenantName for tenant in evaluationTenants if tenant.tenantName]
return CrossReviewTaskItemVO(
taskId=int(row["task_id"]),
taskName=str(row["task_name"]),
taskType=str(row["task_type"]),
docTypeId=cls._to_int(row.get("doc_type_id")),
docTypeCode=row.get("doc_type_code"),
status=str(row["status"]),
progress=progress if CanViewProgress else None,
totalDocuments=totalDocuments if CanViewProgress else None,
completedDocuments=completedDocuments if CanViewProgress else None,
createdAt=row.get("create_time"),
evaluationTenants=evaluationTenants,
evaluationRegion=evaluationRegion,
currentUserRole=str(row.get("current_user_role") or "participant"),
currentUserCanConfirm=bool(row.get("current_user_can_confirm")),
)
def _build_score_summary(self, finalScore: float, fullScore: float) -> str:
if fullScore <= 0:
return "0/0"