feat: update audit platform workspace
This commit is contained in:
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user