feat: update audit platform workspace
This commit is contained in:
@@ -88,6 +88,7 @@ class DocumentServiceImpl(IDocumentService):
|
||||
TypeId: int | None = None,
|
||||
TypeCode: str | None = None,
|
||||
GroupId: int | None = None,
|
||||
EntryModuleId: int | None = None,
|
||||
Region: str | None = None,
|
||||
FileRole: str = "primary",
|
||||
CreatedBy: int | None = None,
|
||||
@@ -136,6 +137,7 @@ class DocumentServiceImpl(IDocumentService):
|
||||
|
||||
async with GetAsyncSession() as Session:
|
||||
await self._ensureDocumentGroupColumn(Session)
|
||||
normalizedEntryModuleId = int(EntryModuleId) if EntryModuleId is not None and int(EntryModuleId) > 0 else None
|
||||
if TypeId is not None and TypeCode is not None:
|
||||
typeResult = await Session.execute(
|
||||
text(
|
||||
@@ -182,7 +184,13 @@ class DocumentServiceImpl(IDocumentService):
|
||||
|
||||
resolvedTypeId = int(typeRow["id"])
|
||||
resolvedTypeCode = str(typeRow["code"])
|
||||
resolvedGroupId = await self._resolveDocumentGroupId(Session, resolvedTypeId, GroupId)
|
||||
await self._assertDocumentTypeEntryModule(Session, resolvedTypeId, normalizedEntryModuleId)
|
||||
resolvedGroupId = await self._resolveDocumentGroupId(
|
||||
Session,
|
||||
resolvedTypeId,
|
||||
GroupId,
|
||||
normalizedEntryModuleId,
|
||||
)
|
||||
resolvedRootGroupId = await self._resolveDocumentRootGroupId(Session, resolvedTypeId, resolvedGroupId)
|
||||
duplicateUpload = False
|
||||
previousVersionId: int | None = None
|
||||
@@ -236,6 +244,21 @@ class DocumentServiceImpl(IDocumentService):
|
||||
else:
|
||||
rootVersionId = document.rootVersionId
|
||||
|
||||
if normalizedEntryModuleId is not None:
|
||||
await Session.execute(
|
||||
text(
|
||||
"""
|
||||
UPDATE leaudit_documents
|
||||
SET entry_module_id = :entry_module_id
|
||||
WHERE id = :document_id
|
||||
"""
|
||||
),
|
||||
{
|
||||
"entry_module_id": normalizedEntryModuleId,
|
||||
"document_id": document.Id,
|
||||
},
|
||||
)
|
||||
|
||||
versionLabel = f"v{document.versionNo}"
|
||||
objectKey = OssPathUtils.BuildBusinessDocKey(
|
||||
Region=normalizedRegion,
|
||||
@@ -324,6 +347,7 @@ class DocumentServiceImpl(IDocumentService):
|
||||
typeId=resolvedTypeId,
|
||||
typeCode=resolvedTypeCode,
|
||||
groupId=resolvedGroupId,
|
||||
entryModuleId=normalizedEntryModuleId,
|
||||
region=normalizedRegion,
|
||||
tenantCode=resolvedTenant.tenant_code,
|
||||
tenantName=resolvedTenant.tenant_name or normalizedRegion,
|
||||
@@ -463,6 +487,7 @@ class DocumentServiceImpl(IDocumentService):
|
||||
|
||||
optionalSelects = [
|
||||
f"{resolvedGroupIdExpr} AS group_id",
|
||||
"d.entry_module_id AS entry_module_id" if "entry_module_id" in documentColumns else "NULL::bigint AS entry_module_id",
|
||||
"d.document_number AS document_number" if "document_number" in documentColumns else "NULL::text AS document_number",
|
||||
f"{persistedOrDerivedAuditStatusExpr} AS audit_status",
|
||||
"d.is_test_document AS is_test_document" if "is_test_document" in documentColumns else "FALSE AS is_test_document",
|
||||
@@ -662,6 +687,7 @@ class DocumentServiceImpl(IDocumentService):
|
||||
typeName=row["type_name"],
|
||||
groupId=int(row["group_id"]) if row["group_id"] is not None else None,
|
||||
groupName=row["group_name"],
|
||||
entryModuleId=int(row["entry_module_id"]) if row["entry_module_id"] is not None else None,
|
||||
region=row["region"],
|
||||
tenantCode=row["tenant_code"],
|
||||
tenantName=row["tenant_name"],
|
||||
@@ -762,6 +788,26 @@ class DocumentServiceImpl(IDocumentService):
|
||||
scoring_proposals=scoringProposals,
|
||||
)
|
||||
|
||||
async def IsCrossReviewDocument(self, DocumentId: int) -> bool:
|
||||
"""判断文档是否属于交叉评查范围。"""
|
||||
async with GetAsyncSession() as Session:
|
||||
row = (
|
||||
await Session.execute(
|
||||
text(
|
||||
"""
|
||||
SELECT 1
|
||||
FROM leaudit_documents d
|
||||
WHERE d.id = :document_id
|
||||
AND d.deleted_at IS NULL
|
||||
AND COALESCE(d.review_scope, 'standard') = 'cross_review'
|
||||
LIMIT 1
|
||||
"""
|
||||
),
|
||||
{"document_id": DocumentId},
|
||||
)
|
||||
).first()
|
||||
return bool(row)
|
||||
|
||||
async def AuditReviewPoint(
|
||||
self,
|
||||
CurrentUserId: int,
|
||||
@@ -2705,6 +2751,15 @@ class DocumentServiceImpl(IDocumentService):
|
||||
"""
|
||||
)
|
||||
)
|
||||
await Session.execute(
|
||||
text(
|
||||
"""
|
||||
ALTER TABLE leaudit_documents
|
||||
ADD COLUMN IF NOT EXISTS entry_module_id BIGINT NULL
|
||||
REFERENCES leaudit_entry_modules(id)
|
||||
"""
|
||||
)
|
||||
)
|
||||
await Session.execute(
|
||||
text(
|
||||
"""
|
||||
@@ -2717,23 +2772,71 @@ class DocumentServiceImpl(IDocumentService):
|
||||
await Session.execute(
|
||||
text("CREATE INDEX IF NOT EXISTS idx_leaudit_documents_group_id ON leaudit_documents(group_id)")
|
||||
)
|
||||
await Session.execute(
|
||||
text("CREATE INDEX IF NOT EXISTS idx_leaudit_documents_entry_module_id ON leaudit_documents(entry_module_id)")
|
||||
)
|
||||
await Session.execute(
|
||||
text("CREATE INDEX IF NOT EXISTS idx_leaudit_documents_tenant_code ON leaudit_documents(tenant_code)")
|
||||
)
|
||||
|
||||
async def _resolveDocumentGroupId(self, Session, TypeId: int, GroupId: int | None) -> int | None:
|
||||
"""校验上传时选择的二级分组是否属于当前文档类型。"""
|
||||
if GroupId is None:
|
||||
return None
|
||||
async def _assertDocumentTypeEntryModule(
|
||||
self,
|
||||
Session,
|
||||
TypeId: int,
|
||||
EntryModuleId: int | None,
|
||||
) -> None:
|
||||
if EntryModuleId is None:
|
||||
return
|
||||
row = (
|
||||
await Session.execute(
|
||||
text(
|
||||
"""
|
||||
SELECT id, document_type_id, name
|
||||
FROM leaudit_evaluation_point_groups
|
||||
WHERE id = :group_id
|
||||
SELECT id, name, entry_module_id
|
||||
FROM leaudit_document_types
|
||||
WHERE id = :type_id
|
||||
AND deleted_at IS NULL
|
||||
AND COALESCE(pid, 0) <> 0
|
||||
LIMIT 1
|
||||
"""
|
||||
),
|
||||
{"type_id": TypeId},
|
||||
)
|
||||
).mappings().first()
|
||||
if not row:
|
||||
raise LeauditException(StatusCodeEnum.HTTP_404_NOT_FOUND, "文档类型不存在或已停用")
|
||||
if int(row.get("entry_module_id") or 0) != EntryModuleId:
|
||||
raise LeauditException(
|
||||
StatusCodeEnum.HTTP_400_BAD_REQUEST,
|
||||
f"文档类型「{row['name']}」不属于当前入口模块,无法上传",
|
||||
)
|
||||
|
||||
async def _resolveDocumentGroupId(
|
||||
self,
|
||||
Session,
|
||||
TypeId: int,
|
||||
GroupId: int | None,
|
||||
EntryModuleId: int | None = None,
|
||||
) -> int | None:
|
||||
"""校验上传时选择的二级分组是否属于当前文档类型。"""
|
||||
if GroupId is None:
|
||||
return await self._resolveUniqueDocumentGroupId(Session, TypeId, EntryModuleId)
|
||||
row = (
|
||||
await Session.execute(
|
||||
text(
|
||||
"""
|
||||
SELECT
|
||||
child.id,
|
||||
child.document_type_id,
|
||||
child.name,
|
||||
COALESCE(child.entry_module_id, parent.entry_module_id, dt.entry_module_id) AS entry_module_id
|
||||
FROM leaudit_evaluation_point_groups child
|
||||
LEFT JOIN leaudit_evaluation_point_groups parent
|
||||
ON parent.id = child.pid
|
||||
LEFT JOIN leaudit_document_types dt
|
||||
ON dt.id = child.document_type_id
|
||||
WHERE child.id = :group_id
|
||||
AND child.deleted_at IS NULL
|
||||
AND COALESCE(child.pid, 0) <> 0
|
||||
AND child.is_enabled = true
|
||||
LIMIT 1
|
||||
"""
|
||||
),
|
||||
@@ -2748,8 +2851,63 @@ class DocumentServiceImpl(IDocumentService):
|
||||
TypeId, GroupId, row["document_type_id"], row["name"],
|
||||
)
|
||||
raise LeauditException(StatusCodeEnum.HTTP_400_BAD_REQUEST, f"当前子类型「{row['name']}」(id={GroupId}) 属于文档类型 {row['document_type_id']},与所选文档类型 {TypeId} 不匹配,无法上传")
|
||||
if EntryModuleId is not None and int(row.get("entry_module_id") or 0) != EntryModuleId:
|
||||
raise LeauditException(
|
||||
StatusCodeEnum.HTTP_400_BAD_REQUEST,
|
||||
f"当前子类型「{row['name']}」不属于当前入口模块,无法上传",
|
||||
)
|
||||
return int(row["id"])
|
||||
|
||||
async def _resolveUniqueDocumentGroupId(
|
||||
self,
|
||||
Session,
|
||||
TypeId: int,
|
||||
EntryModuleId: int | None = None,
|
||||
) -> int | None:
|
||||
params: dict[str, int] = {"type_id": TypeId}
|
||||
entry_module_filter = ""
|
||||
if EntryModuleId is not None:
|
||||
entry_module_filter = """
|
||||
AND COALESCE(child.entry_module_id, parent.entry_module_id, dt.entry_module_id) = :entry_module_id
|
||||
"""
|
||||
params["entry_module_id"] = int(EntryModuleId)
|
||||
rows = (
|
||||
await Session.execute(
|
||||
text(
|
||||
f"""
|
||||
SELECT
|
||||
child.id,
|
||||
child.name,
|
||||
COALESCE(child.entry_module_id, parent.entry_module_id, dt.entry_module_id) AS entry_module_id
|
||||
FROM leaudit_evaluation_point_groups child
|
||||
LEFT JOIN leaudit_evaluation_point_groups parent
|
||||
ON parent.id = child.pid
|
||||
LEFT JOIN leaudit_document_types dt
|
||||
ON dt.id = child.document_type_id
|
||||
WHERE child.document_type_id = :type_id
|
||||
AND child.deleted_at IS NULL
|
||||
AND COALESCE(child.pid, 0) <> 0
|
||||
AND child.is_enabled = true
|
||||
{entry_module_filter}
|
||||
ORDER BY COALESCE(child.sort_order, 0) ASC, child.id ASC
|
||||
LIMIT 2
|
||||
"""
|
||||
),
|
||||
params,
|
||||
)
|
||||
).mappings().all()
|
||||
if len(rows) == 1:
|
||||
return int(rows[0]["id"])
|
||||
if len(rows) > 1:
|
||||
raise LeauditException(
|
||||
StatusCodeEnum.HTTP_400_BAD_REQUEST,
|
||||
"当前文档类型存在多个子类型,请选择具体子类型后再上传",
|
||||
)
|
||||
raise LeauditException(
|
||||
StatusCodeEnum.HTTP_400_BAD_REQUEST,
|
||||
"当前文档类型未配置可用子类型,请先在评查点分组管理中配置二级分组",
|
||||
)
|
||||
|
||||
async def _resolveDocumentRootGroupId(self, Session, TypeId: int, GroupId: int | None) -> int | None:
|
||||
"""解析上传命中的一级分组,用于跨二级类型做版本归档。"""
|
||||
if GroupId is not None:
|
||||
@@ -2942,6 +3100,7 @@ class DocumentServiceImpl(IDocumentService):
|
||||
|
||||
optionalSelects = [
|
||||
f"{resolvedGroupIdExpr} AS group_id",
|
||||
"d.entry_module_id AS entry_module_id" if "entry_module_id" in DocumentColumns else "NULL::bigint AS entry_module_id",
|
||||
"d.document_number AS document_number" if "document_number" in DocumentColumns else "NULL::text AS document_number",
|
||||
"d.remark AS remark" if "remark" in DocumentColumns else "NULL::text AS remark",
|
||||
"d.is_test_document AS is_test_document" if "is_test_document" in DocumentColumns else "FALSE AS is_test_document",
|
||||
@@ -3149,6 +3308,7 @@ class DocumentServiceImpl(IDocumentService):
|
||||
typeName=detailRow["type_name"],
|
||||
groupId=int(detailRow["group_id"]) if detailRow["group_id"] is not None else None,
|
||||
groupName=detailRow["group_name"],
|
||||
entryModuleId=int(detailRow["entry_module_id"]) if detailRow["entry_module_id"] is not None else None,
|
||||
region=str(detailRow["region"] or ""),
|
||||
tenantCode=detailRow["tenant_code"],
|
||||
tenantName=detailRow["tenant_name"],
|
||||
@@ -3562,6 +3722,9 @@ class DocumentServiceImpl(IDocumentService):
|
||||
pageCount = int(metricRow["page_count"]) if metricRow and metricRow["page_count"] is not None else 0
|
||||
|
||||
ocrResultPayload = await self._buildReviewOcrPayload(Session, Detail.documentId, RunRow, pageCount)
|
||||
pageQualityResults = []
|
||||
if Detail.pageQualityRunId is not None:
|
||||
pageQualityResults = await self._loadPageQualityIssueResults(Session, int(Detail.pageQualityRunId))
|
||||
|
||||
return {
|
||||
"id": Detail.documentId,
|
||||
@@ -3593,8 +3756,66 @@ class DocumentServiceImpl(IDocumentService):
|
||||
"page_count": pageCount,
|
||||
"ocrResult": ocrResultPayload,
|
||||
"attachments": [item.model_dump() for item in Detail.attachments],
|
||||
**self._buildReviewPageQualityPayload(Detail, pageQualityResults),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _buildReviewPageQualityPayload(
|
||||
Detail: DocumentDetailVO,
|
||||
PageQualityResults: list[dict[str, Any]] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""构建评查详情页需要的图片质量摘要字段。"""
|
||||
page_quality_summary = Detail.pageQualitySummary.model_dump() if Detail.pageQualitySummary else None
|
||||
status_order = {"reject": 0, "review": 1}
|
||||
page_quality_results = sorted(
|
||||
PageQualityResults or [],
|
||||
key=lambda item: (
|
||||
status_order.get(str(item.get("qualityStatus") or ""), 9),
|
||||
int(item.get("pageNum") or 0),
|
||||
),
|
||||
)
|
||||
return {
|
||||
"pageQualityRunId": Detail.pageQualityRunId,
|
||||
"pageQualityRunStatus": Detail.pageQualityRunStatus,
|
||||
"pageQualitySummaryStatus": Detail.pageQualitySummaryStatus,
|
||||
"pageQualityIssueCount": Detail.pageQualityIssueCount,
|
||||
"pageQualityWarningText": Detail.pageQualityWarningText,
|
||||
"pageQualitySummary": page_quality_summary,
|
||||
"pageQualityResults": page_quality_results,
|
||||
}
|
||||
|
||||
async def _loadPageQualityIssueResults(self, Session, RunId: int) -> list[dict[str, Any]]:
|
||||
"""加载页级图片质量问题明细。"""
|
||||
if not await self._tableExists(Session, "leaudit_page_quality_results"):
|
||||
return []
|
||||
rows = (
|
||||
await Session.execute(
|
||||
text(
|
||||
"""
|
||||
SELECT page_num, quality_status, quality_score, reason_text
|
||||
FROM leaudit_page_quality_results
|
||||
WHERE run_id = :run_id
|
||||
AND quality_status IN ('review', 'reject')
|
||||
ORDER BY
|
||||
CASE quality_status WHEN 'reject' THEN 0 WHEN 'review' THEN 1 ELSE 9 END,
|
||||
page_num ASC,
|
||||
id ASC
|
||||
"""
|
||||
),
|
||||
{"run_id": RunId},
|
||||
)
|
||||
).mappings().all()
|
||||
return [
|
||||
{
|
||||
"pageNum": int(row["page_num"]),
|
||||
"qualityStatus": str(row["quality_status"] or "review"),
|
||||
"qualityScore": float(row["quality_score"]) if row["quality_score"] is not None else None,
|
||||
"reasonText": str(row["reason_text"] or "") or None,
|
||||
}
|
||||
for row in rows
|
||||
if row["page_num"] is not None
|
||||
]
|
||||
|
||||
async def _buildReviewOcrPayload(
|
||||
self,
|
||||
Session,
|
||||
|
||||
Reference in New Issue
Block a user