feat: add tenant-scoped rule and permission management
This commit is contained in:
@@ -31,12 +31,16 @@ from fastapi_modules.fastapi_leaudit.govdoc_engine.reporter.html_renderer import
|
||||
from fastapi_modules.fastapi_leaudit.models import LeauditDocument, LeauditDocumentFile
|
||||
from fastapi_modules.fastapi_leaudit.services import IGovdocService, IOssService
|
||||
from fastapi_modules.fastapi_leaudit.services.impl.ossServiceImpl import OssServiceImpl
|
||||
from fastapi_modules.fastapi_leaudit.services.impl.ssoUserCompat import SsoUserCompat
|
||||
from fastapi_modules.fastapi_leaudit.services.impl.tenantResolver import TenantResolver
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class _GovdocDocumentRow:
|
||||
documentId: int
|
||||
region: str
|
||||
tenantCode: str | None
|
||||
tenantName: str | None
|
||||
processingStatus: str
|
||||
currentRunId: int | None
|
||||
versionGroupKey: str | None
|
||||
@@ -72,9 +76,10 @@ class _GovdocDocumentRow:
|
||||
class GovdocServiceImpl(IGovdocService):
|
||||
"""公文处理与格式审查服务实现。"""
|
||||
|
||||
def __init__(self, OssService: IOssService | None = None) -> None:
|
||||
def __init__(self, OssService: IOssService | None = None, TenantResolverService: TenantResolver | None = None) -> None:
|
||||
self.OssService = OssService or OssServiceImpl()
|
||||
self.Storage = StorageAdapter()
|
||||
self.TenantResolver = TenantResolverService or TenantResolver()
|
||||
|
||||
def _parse_date_filter(self, value: str | None, field_name: str) -> date | None:
|
||||
if value is None:
|
||||
@@ -96,7 +101,8 @@ class GovdocServiceImpl(IGovdocService):
|
||||
self,
|
||||
file: UploadFile,
|
||||
typeId: int | None = None,
|
||||
region: str = "default",
|
||||
region: str | None = None,
|
||||
tenantCode: str | None = None,
|
||||
autoRun: bool = True,
|
||||
speed: str = "normal",
|
||||
ruleVersionId: int | None = None,
|
||||
@@ -111,7 +117,12 @@ class GovdocServiceImpl(IGovdocService):
|
||||
if not content:
|
||||
raise LeauditException(StatusCodeEnum.HTTP_400_BAD_REQUEST, "上传文件内容不能为空")
|
||||
|
||||
normalizedRegion = (region or "default").strip() or "default"
|
||||
requested_tenant = await self.TenantResolver.Resolve(
|
||||
RawValue=(region or "").strip() or None,
|
||||
Source="govdoc_upload_request",
|
||||
PreferredTenantCode=str(tenantCode or "").strip() or None,
|
||||
)
|
||||
normalizedRegion = str(requested_tenant.tenant_name or requested_tenant.normalized_value or region or "").strip()
|
||||
fileName = file.filename
|
||||
fileExt = Path(fileName).suffix.lstrip(".").lower() or None
|
||||
if fileExt != "docx":
|
||||
@@ -128,10 +139,11 @@ class GovdocServiceImpl(IGovdocService):
|
||||
await self._ensureGovdocSchema(session)
|
||||
await self._backfill_missing_version_groups(session)
|
||||
currentUser = await self._getCurrentUserContext(createdBy)
|
||||
resolvedRegion = self._resolve_upload_region(currentUser, normalizedRegion)
|
||||
resolvedRegion = self._resolve_upload_region(currentUser, normalizedRegion, tenantCode)
|
||||
latestCandidate = await self._find_latest_version_candidate(
|
||||
session,
|
||||
region=resolvedRegion,
|
||||
tenantCode=requested_tenant.tenant_code or currentUser.get("tenant_code"),
|
||||
normalizedName=normalizedName,
|
||||
fileExt=fileExt,
|
||||
)
|
||||
@@ -139,6 +151,7 @@ class GovdocServiceImpl(IGovdocService):
|
||||
latestCandidate = await self._backfill_legacy_version_chain(
|
||||
session,
|
||||
region=resolvedRegion,
|
||||
tenantCode=requested_tenant.tenant_code or currentUser.get("tenant_code"),
|
||||
normalizedName=normalizedName,
|
||||
fileExt=fileExt,
|
||||
)
|
||||
@@ -163,6 +176,7 @@ class GovdocServiceImpl(IGovdocService):
|
||||
bizDocumentId=time.time_ns(),
|
||||
typeId=typeId,
|
||||
groupId=None,
|
||||
tenantCode=requested_tenant.tenant_code or currentUser.get("tenant_code"),
|
||||
region=resolvedRegion,
|
||||
processingStatus="waiting",
|
||||
currentRunId=None,
|
||||
@@ -242,6 +256,8 @@ class GovdocServiceImpl(IGovdocService):
|
||||
"fileId": documentFile.Id,
|
||||
"fileName": documentFile.fileName,
|
||||
"region": resolvedRegion,
|
||||
"tenantCode": requested_tenant.tenant_code or currentUser.get("tenant_code"),
|
||||
"tenantName": requested_tenant.tenant_name or resolvedRegion,
|
||||
"engineType": "govdoc",
|
||||
"processingStatus": "processing" if runPayload else (document.processingStatus or "waiting"),
|
||||
"autoRunTriggered": shouldAutoRun,
|
||||
@@ -256,6 +272,7 @@ class GovdocServiceImpl(IGovdocService):
|
||||
keyword: str | None = None,
|
||||
fileExt: str | None = None,
|
||||
region: str | None = None,
|
||||
tenantCode: str | None = None,
|
||||
status: str | None = None,
|
||||
resultStatus: str | None = None,
|
||||
createdBy: int | None = None,
|
||||
@@ -267,6 +284,11 @@ class GovdocServiceImpl(IGovdocService):
|
||||
raise LeauditException(StatusCodeEnum.HTTP_401_UNAUTHORIZED, "当前用户未登录")
|
||||
|
||||
currentUser = await self._getCurrentUserContext(userId)
|
||||
requested_tenant = await self.TenantResolver.Resolve(
|
||||
RawValue=(region or "").strip() or None,
|
||||
Source="govdoc_list_scope",
|
||||
PreferredTenantCode=str(tenantCode or "").strip() or None,
|
||||
)
|
||||
page = max(1, int(page))
|
||||
pageSize = max(1, min(int(pageSize), 100))
|
||||
offset = (page - 1) * pageSize
|
||||
@@ -292,7 +314,8 @@ class GovdocServiceImpl(IGovdocService):
|
||||
Params=params,
|
||||
DocumentAlias="d",
|
||||
FileAlias="f",
|
||||
RequestedRegion=region,
|
||||
RequestedRegion=requested_tenant.tenant_name or requested_tenant.normalized_value or region,
|
||||
RequestedTenantCode=requested_tenant.tenant_code or tenantCode,
|
||||
RequestedUserId=createdBy,
|
||||
)
|
||||
)
|
||||
@@ -327,7 +350,9 @@ class GovdocServiceImpl(IGovdocService):
|
||||
WITH effective_docs AS (
|
||||
SELECT
|
||||
d.id AS document_id,
|
||||
COALESCE(d.region, 'default') AS region,
|
||||
COALESCE(NULLIF(d.region, ''), '公共') AS region,
|
||||
COALESCE(NULLIF(BTRIM(d.tenant_code), ''), NULL) AS tenant_code,
|
||||
{self._tenant_name_sql('d')} AS tenant_name,
|
||||
COALESCE(d.processing_status, 'waiting') AS processing_status,
|
||||
d.current_run_id,
|
||||
COALESCE(NULLIF(d.version_group_key, ''), fallback_vc.derived_version_group_key, '') AS version_group_key,
|
||||
@@ -419,28 +444,28 @@ class GovdocServiceImpl(IGovdocService):
|
||||
SELECT
|
||||
d2.id AS document_id,
|
||||
COUNT(*) OVER (
|
||||
PARTITION BY d2.region, COALESCE(d2.normalized_name, ''), COALESCE(f2.file_ext, '')
|
||||
PARTITION BY {self._version_partition_key_sql('d2', 'f2')}
|
||||
) AS total_versions,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY d2.region, COALESCE(d2.normalized_name, ''), COALESCE(f2.file_ext, '')
|
||||
PARTITION BY {self._version_partition_key_sql('d2', 'f2')}
|
||||
ORDER BY d2.created_at ASC, d2.id ASC
|
||||
) AS derived_version_no,
|
||||
LAG(d2.id) OVER (
|
||||
PARTITION BY d2.region, COALESCE(d2.normalized_name, ''), COALESCE(f2.file_ext, '')
|
||||
PARTITION BY {self._version_partition_key_sql('d2', 'f2')}
|
||||
ORDER BY d2.created_at ASC, d2.id ASC
|
||||
) AS derived_previous_version_id,
|
||||
FIRST_VALUE(d2.id) OVER (
|
||||
PARTITION BY d2.region, COALESCE(d2.normalized_name, ''), COALESCE(f2.file_ext, '')
|
||||
PARTITION BY {self._version_partition_key_sql('d2', 'f2')}
|
||||
ORDER BY d2.created_at ASC, d2.id ASC
|
||||
) AS derived_root_version_id,
|
||||
CASE
|
||||
WHEN ROW_NUMBER() OVER (
|
||||
PARTITION BY d2.region, COALESCE(d2.normalized_name, ''), COALESCE(f2.file_ext, '')
|
||||
PARTITION BY {self._version_partition_key_sql('d2', 'f2')}
|
||||
ORDER BY d2.created_at DESC, d2.id DESC
|
||||
) = 1 THEN true
|
||||
ELSE false
|
||||
END AS derived_is_latest_version,
|
||||
md5(CONCAT_WS('|', d2.region, COALESCE(d2.normalized_name, ''), COALESCE(f2.file_ext, ''))) AS derived_version_group_key
|
||||
md5({self._version_partition_key_sql('d2', 'f2')}) AS derived_version_group_key
|
||||
FROM leaudit_documents d2
|
||||
JOIN leaudit_document_files f2
|
||||
ON f2.document_id = d2.id
|
||||
@@ -565,7 +590,9 @@ class GovdocServiceImpl(IGovdocService):
|
||||
f"""
|
||||
SELECT
|
||||
d.id AS document_id,
|
||||
COALESCE(d.region, 'default') AS region,
|
||||
COALESCE(NULLIF(d.region, ''), '公共') AS region,
|
||||
COALESCE(NULLIF(BTRIM(d.tenant_code), ''), NULL) AS tenant_code,
|
||||
{self._tenant_name_sql('d')} AS tenant_name,
|
||||
COALESCE(d.processing_status, 'waiting') AS processing_status,
|
||||
d.current_run_id,
|
||||
COALESCE(NULLIF(d.version_group_key, ''), fallback_vc.derived_version_group_key, '') AS version_group_key,
|
||||
@@ -657,28 +684,28 @@ class GovdocServiceImpl(IGovdocService):
|
||||
SELECT
|
||||
d2.id AS document_id,
|
||||
COUNT(*) OVER (
|
||||
PARTITION BY d2.region, COALESCE(d2.normalized_name, ''), COALESCE(f2.file_ext, '')
|
||||
PARTITION BY {self._version_partition_key_sql('d2', 'f2')}
|
||||
) AS total_versions,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY d2.region, COALESCE(d2.normalized_name, ''), COALESCE(f2.file_ext, '')
|
||||
PARTITION BY {self._version_partition_key_sql('d2', 'f2')}
|
||||
ORDER BY d2.created_at ASC, d2.id ASC
|
||||
) AS derived_version_no,
|
||||
LAG(d2.id) OVER (
|
||||
PARTITION BY d2.region, COALESCE(d2.normalized_name, ''), COALESCE(f2.file_ext, '')
|
||||
PARTITION BY {self._version_partition_key_sql('d2', 'f2')}
|
||||
ORDER BY d2.created_at ASC, d2.id ASC
|
||||
) AS derived_previous_version_id,
|
||||
FIRST_VALUE(d2.id) OVER (
|
||||
PARTITION BY d2.region, COALESCE(d2.normalized_name, ''), COALESCE(f2.file_ext, '')
|
||||
PARTITION BY {self._version_partition_key_sql('d2', 'f2')}
|
||||
ORDER BY d2.created_at ASC, d2.id ASC
|
||||
) AS derived_root_version_id,
|
||||
CASE
|
||||
WHEN ROW_NUMBER() OVER (
|
||||
PARTITION BY d2.region, COALESCE(d2.normalized_name, ''), COALESCE(f2.file_ext, '')
|
||||
PARTITION BY {self._version_partition_key_sql('d2', 'f2')}
|
||||
ORDER BY d2.created_at DESC, d2.id DESC
|
||||
) = 1 THEN true
|
||||
ELSE false
|
||||
END AS derived_is_latest_version,
|
||||
md5(CONCAT_WS('|', d2.region, COALESCE(d2.normalized_name, ''), COALESCE(f2.file_ext, ''))) AS derived_version_group_key
|
||||
md5({self._version_partition_key_sql('d2', 'f2')}) AS derived_version_group_key
|
||||
FROM leaudit_documents d2
|
||||
JOIN leaudit_document_files f2
|
||||
ON f2.document_id = d2.id
|
||||
@@ -767,6 +794,8 @@ class GovdocServiceImpl(IGovdocService):
|
||||
"mimeType": mapped.mimeType,
|
||||
"fileSize": mapped.fileSize,
|
||||
"region": mapped.region,
|
||||
"tenantCode": mapped.tenantCode,
|
||||
"tenantName": mapped.tenantName or mapped.region,
|
||||
"processingStatus": mapped.processingStatus,
|
||||
"versionGroupKey": mapped.versionGroupKey,
|
||||
"versionNo": mapped.versionNo,
|
||||
@@ -853,7 +882,7 @@ class GovdocServiceImpl(IGovdocService):
|
||||
currentUser = await self._getCurrentUserContext(triggerUserId)
|
||||
documentMeta = await self._get_document_for_run(documentId, triggerUserId, currentUser)
|
||||
if documentMeta.currentRunId and not force:
|
||||
currentRun = await self.GetRunStatus(documentMeta.currentRunId)
|
||||
currentRun = await self.GetRunStatus(documentMeta.currentRunId, userId=triggerUserId)
|
||||
if currentRun["status"] in {"pending", "processing"}:
|
||||
return {
|
||||
"runId": documentMeta.currentRunId,
|
||||
@@ -897,77 +926,17 @@ class GovdocServiceImpl(IGovdocService):
|
||||
"taskId": str(getattr(task, "id", "") or ""),
|
||||
}
|
||||
|
||||
async def GetRunStatus(self, runId: int) -> dict[str, Any]:
|
||||
async with GetAsyncSession() as session:
|
||||
await self._ensureGovdocSchema(session)
|
||||
row = (
|
||||
await session.execute(
|
||||
text(
|
||||
"""
|
||||
SELECT
|
||||
id,
|
||||
document_id,
|
||||
status,
|
||||
phase,
|
||||
result_status,
|
||||
total_score,
|
||||
passed_count,
|
||||
failed_count,
|
||||
skipped_count,
|
||||
error_message,
|
||||
task_id,
|
||||
created_at,
|
||||
updated_at,
|
||||
started_at,
|
||||
finished_at
|
||||
FROM govdoc_runs
|
||||
WHERE id = :run_id
|
||||
AND deleted_at IS NULL
|
||||
LIMIT 1
|
||||
"""
|
||||
),
|
||||
{"run_id": runId},
|
||||
)
|
||||
).mappings().first()
|
||||
if not row:
|
||||
raise LeauditException(StatusCodeEnum.HTTP_404_NOT_FOUND, "审查运行不存在")
|
||||
async def GetRunStatus(self, runId: int, userId: int | None = None) -> dict[str, Any]:
|
||||
row = await self._get_scoped_run_row(runId, userId=userId)
|
||||
return self._build_run_summary(row)
|
||||
|
||||
# ── 结果与报告 ────────────────────────────────────────
|
||||
|
||||
async def GetRunResult(self, runId: int) -> dict[str, Any]:
|
||||
async def GetRunResult(self, runId: int, userId: int | None = None) -> dict[str, Any]:
|
||||
"""从 govdoc_runs + govdoc_rule_results 读取审查结果,含 structure/outline。"""
|
||||
runRow = await self._get_scoped_run_row(runId, userId=userId)
|
||||
async with GetAsyncSession() as session:
|
||||
await self._ensureGovdocSchema(session)
|
||||
runRow = (
|
||||
await session.execute(
|
||||
text(
|
||||
"""
|
||||
SELECT
|
||||
id,
|
||||
document_id,
|
||||
status,
|
||||
phase,
|
||||
total_score,
|
||||
passed_count,
|
||||
failed_count,
|
||||
skipped_count,
|
||||
result_status,
|
||||
result_summary_json,
|
||||
started_at,
|
||||
finished_at
|
||||
FROM govdoc_runs
|
||||
WHERE id = :run_id
|
||||
AND deleted_at IS NULL
|
||||
LIMIT 1
|
||||
"""
|
||||
),
|
||||
{"run_id": runId},
|
||||
)
|
||||
).mappings().first()
|
||||
if not runRow:
|
||||
raise LeauditException(StatusCodeEnum.HTTP_404_NOT_FOUND, "审查运行不存在")
|
||||
|
||||
documentRow = (
|
||||
await session.execute(
|
||||
text(
|
||||
@@ -1096,16 +1065,16 @@ class GovdocServiceImpl(IGovdocService):
|
||||
"entities": aux.get("entities", {}),
|
||||
}
|
||||
|
||||
async def GetRunFindings(self, runId: int) -> dict[str, Any]:
|
||||
result = await self.GetRunResult(runId)
|
||||
async def GetRunFindings(self, runId: int, userId: int | None = None) -> dict[str, Any]:
|
||||
result = await self.GetRunResult(runId, userId=userId)
|
||||
return {"runId": runId, "findings": result["findings"]}
|
||||
|
||||
async def GetRunEntities(self, runId: int) -> dict[str, Any]:
|
||||
result = await self.GetRunResult(runId)
|
||||
async def GetRunEntities(self, runId: int, userId: int | None = None) -> dict[str, Any]:
|
||||
result = await self.GetRunResult(runId, userId=userId)
|
||||
return {"runId": runId, "entities": result.get("entities", {})}
|
||||
|
||||
async def GetRunParagraphs(self, runId: int) -> str:
|
||||
runStatus = await self.GetRunStatus(runId)
|
||||
async def GetRunParagraphs(self, runId: int, userId: int | None = None) -> str:
|
||||
runStatus = await self.GetRunStatus(runId, userId=userId)
|
||||
if runStatus["status"] != "completed":
|
||||
raise LeauditException(
|
||||
StatusCodeEnum.HTTP_409_CONFLICT,
|
||||
@@ -1117,7 +1086,10 @@ class GovdocServiceImpl(IGovdocService):
|
||||
content = await self.OssService.DownloadBytes(str(paragraphArtifact["oss_url"]))
|
||||
return content.decode("utf-8")
|
||||
|
||||
documentMeta = await self._get_document_for_read(int(runStatus["documentId"]))
|
||||
if userId is None:
|
||||
raise LeauditException(StatusCodeEnum.HTTP_401_UNAUTHORIZED, "当前用户未登录")
|
||||
currentUser = await self._getCurrentUserContext(userId)
|
||||
documentMeta = await self._get_document_for_run(int(runStatus["documentId"]), userId, currentUser)
|
||||
fileRow = await self._get_active_original_file(documentMeta.documentId)
|
||||
|
||||
ossUrl = getattr(fileRow, "ossUrl", None) or fileRow.get("oss_url")
|
||||
@@ -1138,7 +1110,7 @@ class GovdocServiceImpl(IGovdocService):
|
||||
)
|
||||
try:
|
||||
doc = parse_docx(tempPath)
|
||||
findingsResult = await self.GetRunFindings(runId)
|
||||
findingsResult = await self.GetRunFindings(runId, userId=userId)
|
||||
findingMap: dict[int, list[str]] = {}
|
||||
for finding in findingsResult["findings"]:
|
||||
pi = int(finding.get("location", {}).get("paragraph_index") or 0)
|
||||
@@ -1150,20 +1122,21 @@ class GovdocServiceImpl(IGovdocService):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
async def GetRunStructure(self, runId: int) -> dict[str, Any]:
|
||||
result = await self.GetRunResult(runId)
|
||||
async def GetRunStructure(self, runId: int, userId: int | None = None) -> dict[str, Any]:
|
||||
result = await self.GetRunResult(runId, userId=userId)
|
||||
return {"runId": runId, "structure": result.get("structure", [])}
|
||||
|
||||
async def GetRunOutline(self, runId: int) -> dict[str, Any]:
|
||||
result = await self.GetRunResult(runId)
|
||||
async def GetRunOutline(self, runId: int, userId: int | None = None) -> dict[str, Any]:
|
||||
result = await self.GetRunResult(runId, userId=userId)
|
||||
return {"runId": runId, "outline": result.get("outline", [])}
|
||||
|
||||
async def GetReportHtml(self, runId: int) -> dict[str, Any]:
|
||||
result = await self.GetRunResult(runId)
|
||||
async def GetReportHtml(self, runId: int, userId: int | None = None) -> dict[str, Any]:
|
||||
result = await self.GetRunResult(runId, userId=userId)
|
||||
html = render_html(self._build_audit_result_from_run_result(result))
|
||||
return {"runId": runId, "html": html}
|
||||
|
||||
async def GetReportDocx(self, runId: int) -> dict[str, Any]:
|
||||
async def GetReportDocx(self, runId: int, userId: int | None = None) -> dict[str, Any]:
|
||||
await self._get_scoped_run_row(runId, userId=userId)
|
||||
artifact = await self._get_report_artifact(runId, "annotated_docx")
|
||||
if not artifact:
|
||||
return {"runId": runId, "docxUrl": ""}
|
||||
@@ -1172,7 +1145,11 @@ class GovdocServiceImpl(IGovdocService):
|
||||
"docxUrl": await self.OssService.PresignGetUrl(str(artifact["oss_url"])),
|
||||
}
|
||||
|
||||
async def DownloadOriginal(self, documentId: int) -> dict[str, Any]:
|
||||
async def DownloadOriginal(self, documentId: int, userId: int | None = None) -> dict[str, Any]:
|
||||
if userId is None:
|
||||
raise LeauditException(StatusCodeEnum.HTTP_401_UNAUTHORIZED, "当前用户未登录")
|
||||
currentUser = await self._getCurrentUserContext(userId)
|
||||
await self._get_document_for_run(documentId, userId, currentUser)
|
||||
fileRow = await self._get_active_original_file(documentId)
|
||||
ossUrl = getattr(fileRow, "ossUrl", None) or fileRow.get("oss_url")
|
||||
if not ossUrl:
|
||||
@@ -1253,6 +1230,10 @@ class GovdocServiceImpl(IGovdocService):
|
||||
|
||||
async def _ensureGovdocSchema(self, session) -> None:
|
||||
statements = [
|
||||
"""
|
||||
ALTER TABLE leaudit_documents
|
||||
ADD COLUMN IF NOT EXISTS tenant_code VARCHAR(64)
|
||||
""",
|
||||
"""
|
||||
ALTER TABLE leaudit_documents
|
||||
ADD COLUMN IF NOT EXISTS engine_type VARCHAR(32) NOT NULL DEFAULT 'leaudit'
|
||||
@@ -1329,6 +1310,10 @@ class GovdocServiceImpl(IGovdocService):
|
||||
)
|
||||
""",
|
||||
"""
|
||||
CREATE INDEX IF NOT EXISTS idx_leaudit_documents_tenant_code
|
||||
ON public.leaudit_documents(tenant_code) WHERE deleted_at IS NULL
|
||||
""",
|
||||
"""
|
||||
CREATE INDEX IF NOT EXISTS idx_leaudit_documents_engine_type
|
||||
ON public.leaudit_documents(engine_type) WHERE deleted_at IS NULL
|
||||
""",
|
||||
@@ -1376,13 +1361,28 @@ class GovdocServiceImpl(IGovdocService):
|
||||
async def _getCurrentUserContext(self, CurrentUserId: int) -> dict[str, Any]:
|
||||
async with GetAsyncSession() as session:
|
||||
await self._backfill_missing_version_groups(session)
|
||||
sso_user_columns = await SsoUserCompat.get_columns(session)
|
||||
tenant_code_select = SsoUserCompat.optional_coalesce_as(
|
||||
sso_user_columns,
|
||||
alias="u",
|
||||
column="tenant_code",
|
||||
fallback_sql="''",
|
||||
)
|
||||
tenant_name_select = SsoUserCompat.optional_coalesce_as(
|
||||
sso_user_columns,
|
||||
alias="u",
|
||||
column="tenant_name",
|
||||
fallback_sql="''",
|
||||
)
|
||||
row = (
|
||||
await session.execute(
|
||||
text(
|
||||
"""
|
||||
f"""
|
||||
SELECT
|
||||
u.id,
|
||||
COALESCE(u.area, '') AS area,
|
||||
{tenant_code_select},
|
||||
{tenant_name_select},
|
||||
COALESCE(bool_or(r.role_key IN ('super_admin', 'provincial_admin')), FALSE) AS is_global,
|
||||
COALESCE(bool_or(r.role_key IN ('super_admin', 'provincial_admin', 'admin')), FALSE) AS can_manage,
|
||||
COALESCE(bool_or(r.role_key = 'super_admin'), FALSE) AS is_super_admin
|
||||
@@ -1398,8 +1398,17 @@ class GovdocServiceImpl(IGovdocService):
|
||||
).mappings().first()
|
||||
if not row:
|
||||
raise LeauditException(StatusCodeEnum.HTTP_404_NOT_FOUND, "当前用户不存在")
|
||||
tenant = await self.TenantResolver.ResolveUserContext(
|
||||
Area=str(row["area"] or ""),
|
||||
TenantCode=str(row.get("tenant_code") or "") or None,
|
||||
TenantName=str(row.get("tenant_name") or "") or None,
|
||||
Source="govdoc_user_context",
|
||||
)
|
||||
return {
|
||||
"area": str(row["area"] or ""),
|
||||
"area": tenant.tenant_name or tenant.normalized_value or str(row["area"] or ""),
|
||||
"tenant_code": tenant.tenant_code or (str(row.get("tenant_code") or "") or None),
|
||||
"tenant_name": tenant.tenant_name or (str(row.get("tenant_name") or "") or None),
|
||||
"tenant_scope_value": tenant.tenant_name or tenant.normalized_value or str(row["area"] or ""),
|
||||
"is_global": bool(row["is_global"]),
|
||||
"can_manage": bool(row["can_manage"]),
|
||||
"is_super_admin": bool(row["is_super_admin"]),
|
||||
@@ -1413,14 +1422,26 @@ class GovdocServiceImpl(IGovdocService):
|
||||
DocumentAlias: str,
|
||||
FileAlias: str,
|
||||
RequestedRegion: str | None = None,
|
||||
RequestedTenantCode: str | None = None,
|
||||
RequestedUserId: int | None = None,
|
||||
) -> list[str]:
|
||||
filters: list[str] = []
|
||||
requestedRegion = (RequestedRegion or "").strip()
|
||||
area = str(CurrentUser["area"] or "").strip()
|
||||
requestedTenantCodeNormalized, requestedRegion = self._normalize_scope_value(RequestedRegion, RequestedTenantCode, CurrentUser)
|
||||
currentTenantCode = str(CurrentUser.get("tenant_code") or "").strip()
|
||||
area = str(CurrentUser.get("tenant_scope_value") or CurrentUser["area"] or "").strip()
|
||||
|
||||
if CurrentUser["is_global"]:
|
||||
if requestedRegion:
|
||||
if requestedTenantCodeNormalized:
|
||||
filters.extend(
|
||||
self._document_tenant_filter_sql(
|
||||
Params,
|
||||
DocumentAlias=DocumentAlias,
|
||||
tenantCode=requestedTenantCodeNormalized,
|
||||
region=requestedRegion,
|
||||
prefix="requested",
|
||||
)
|
||||
)
|
||||
elif requestedRegion:
|
||||
filters.append(f"{DocumentAlias}.region = :requested_region")
|
||||
Params["requested_region"] = requestedRegion
|
||||
if RequestedUserId is not None:
|
||||
@@ -1429,14 +1450,23 @@ class GovdocServiceImpl(IGovdocService):
|
||||
return filters
|
||||
|
||||
if CurrentUser["can_manage"]:
|
||||
if not area:
|
||||
filters.append("1 = 0")
|
||||
return filters
|
||||
if requestedRegion and requestedRegion != area:
|
||||
filters.append("1 = 0")
|
||||
return filters
|
||||
filters.append(f"{DocumentAlias}.region = :scope_region")
|
||||
Params["scope_region"] = area
|
||||
if not currentTenantCode and not area:
|
||||
return ["1 = 0"]
|
||||
effectiveTenantCode = currentTenantCode or requestedTenantCodeNormalized
|
||||
effectiveRegion = area or requestedRegion
|
||||
if currentTenantCode and requestedTenantCodeNormalized and requestedTenantCodeNormalized != currentTenantCode:
|
||||
return ["1 = 0"]
|
||||
if requestedRegion and area and requestedRegion != area:
|
||||
return ["1 = 0"]
|
||||
filters.extend(
|
||||
self._document_tenant_filter_sql(
|
||||
Params,
|
||||
DocumentAlias=DocumentAlias,
|
||||
tenantCode=effectiveTenantCode or None,
|
||||
region=effectiveRegion or None,
|
||||
prefix="scope",
|
||||
)
|
||||
)
|
||||
if RequestedUserId is not None:
|
||||
filters.append(f"{FileAlias}.created_by = :requested_user_id")
|
||||
Params["requested_user_id"] = RequestedUserId
|
||||
@@ -1444,29 +1474,142 @@ class GovdocServiceImpl(IGovdocService):
|
||||
|
||||
filters.append(f"{FileAlias}.created_by = :scope_user_id")
|
||||
Params["scope_user_id"] = CurrentUserId
|
||||
if requestedRegion:
|
||||
filters.append(f"{DocumentAlias}.region = :requested_region")
|
||||
Params["requested_region"] = requestedRegion
|
||||
if requestedTenantCodeNormalized:
|
||||
if currentTenantCode and requestedTenantCodeNormalized != currentTenantCode:
|
||||
filters.append("1 = 0")
|
||||
elif not currentTenantCode and area and requestedRegion != area:
|
||||
filters.append("1 = 0")
|
||||
else:
|
||||
filters.extend(
|
||||
self._document_tenant_filter_sql(
|
||||
Params,
|
||||
DocumentAlias=DocumentAlias,
|
||||
tenantCode=(currentTenantCode or requestedTenantCodeNormalized) or None,
|
||||
region=(area or requestedRegion) or None,
|
||||
prefix="requested",
|
||||
)
|
||||
)
|
||||
elif requestedRegion:
|
||||
if area and requestedRegion != area:
|
||||
filters.append("1 = 0")
|
||||
elif currentTenantCode:
|
||||
filters.extend(
|
||||
self._document_tenant_filter_sql(
|
||||
Params,
|
||||
DocumentAlias=DocumentAlias,
|
||||
tenantCode=currentTenantCode or None,
|
||||
region=area or requestedRegion,
|
||||
prefix="requested",
|
||||
)
|
||||
)
|
||||
else:
|
||||
filters.append(f"{DocumentAlias}.region = :requested_region")
|
||||
Params["requested_region"] = requestedRegion
|
||||
if RequestedUserId is not None and RequestedUserId != CurrentUserId:
|
||||
filters.append("1 = 0")
|
||||
return filters
|
||||
|
||||
def _resolve_upload_region(self, currentUser: dict[str, Any], requestedRegion: str) -> str:
|
||||
area = str(currentUser["area"] or "").strip()
|
||||
def _resolve_upload_region(self, currentUser: dict[str, Any], requestedRegion: str | None, requestedTenantCode: str | None = None) -> str:
|
||||
requestedTenantCodeNormalized, requestedRegion = self._normalize_scope_value(requestedRegion, requestedTenantCode, currentUser)
|
||||
currentTenantCode = str(currentUser.get("tenant_code") or "").strip()
|
||||
area = str(currentUser.get("tenant_scope_value") or currentUser["area"] or "").strip()
|
||||
if currentUser["is_global"]:
|
||||
return requestedRegion or area or "default"
|
||||
if currentUser["can_manage"]:
|
||||
if area and requestedRegion and requestedRegion != area:
|
||||
raise LeauditException(StatusCodeEnum.HTTP_403_FORBIDDEN, "不能上传到非本地区")
|
||||
return area or requestedRegion or "default"
|
||||
return requestedRegion or area or "公共"
|
||||
|
||||
if requestedTenantCodeNormalized:
|
||||
if currentTenantCode and requestedTenantCodeNormalized != currentTenantCode:
|
||||
raise LeauditException(StatusCodeEnum.HTTP_403_FORBIDDEN, "不能上传到非本人所属租户")
|
||||
if not currentTenantCode and area and requestedRegion and requestedRegion != area:
|
||||
raise LeauditException(StatusCodeEnum.HTTP_403_FORBIDDEN, "不能上传到非本人所属租户")
|
||||
return area or requestedRegion or "公共"
|
||||
|
||||
if area and requestedRegion and requestedRegion != area:
|
||||
raise LeauditException(StatusCodeEnum.HTTP_403_FORBIDDEN, "不能上传到非本人地区")
|
||||
return area or requestedRegion or "default"
|
||||
raise LeauditException(StatusCodeEnum.HTTP_403_FORBIDDEN, "不能上传到非本人所属租户")
|
||||
return area or requestedRegion or "公共"
|
||||
|
||||
def _normalize_scope_value(
|
||||
self,
|
||||
requestedRegion: str | None,
|
||||
requestedTenantCode: str | None,
|
||||
currentUser: dict[str, Any],
|
||||
) -> tuple[str | None, str]:
|
||||
tenant_code = str(requestedTenantCode or "").strip()
|
||||
if tenant_code:
|
||||
if tenant_code == str(currentUser.get("tenant_code") or "").strip():
|
||||
return (
|
||||
str(currentUser.get("tenant_code") or "").strip() or None,
|
||||
str(currentUser.get("tenant_scope_value") or currentUser.get("tenant_name") or currentUser.get("area") or "").strip(),
|
||||
)
|
||||
if tenant_code == "PUBLIC":
|
||||
return "PUBLIC", "公共"
|
||||
if tenant_code == "PROVINCIAL":
|
||||
return "PROVINCIAL", "省级"
|
||||
return tenant_code or None, str(requestedRegion or "").strip()
|
||||
return None, str(requestedRegion or "").strip()
|
||||
|
||||
def _document_tenant_filter_sql(
|
||||
self,
|
||||
params: dict[str, Any],
|
||||
*,
|
||||
DocumentAlias: str,
|
||||
tenantCode: str | None,
|
||||
region: str | None,
|
||||
prefix: str,
|
||||
) -> list[str]:
|
||||
normalizedRegion = str(region or "").strip()
|
||||
normalizedTenantCode = str(tenantCode or "").strip()
|
||||
if normalizedTenantCode:
|
||||
params[f"{prefix}_tenant_code"] = normalizedTenantCode
|
||||
return [f"{DocumentAlias}.tenant_code = :{prefix}_tenant_code"]
|
||||
if normalizedRegion:
|
||||
params[f"{prefix}_region"] = normalizedRegion
|
||||
return [f"{DocumentAlias}.region = :{prefix}_region"]
|
||||
return ["1 = 0"]
|
||||
|
||||
def _normalize_document_name(self, fileName: str) -> str:
|
||||
suffix = Path(fileName).suffix
|
||||
return fileName[: -len(suffix)] if suffix else fileName
|
||||
|
||||
def _tenant_name_sql(self, alias: str) -> str:
|
||||
return (
|
||||
"CASE "
|
||||
f"WHEN NULLIF(BTRIM({alias}.tenant_code), '') = 'PUBLIC' THEN '公共' "
|
||||
f"WHEN NULLIF(BTRIM({alias}.tenant_code), '') = 'PROVINCIAL' THEN '省级' "
|
||||
f"ELSE COALESCE(NULLIF(BTRIM({alias}.region), ''), '公共') "
|
||||
"END"
|
||||
)
|
||||
|
||||
def _version_partition_key_sql(self, doc_alias: str, file_alias: str) -> str:
|
||||
version_scope_expr = self._version_scope_key_sql(doc_alias)
|
||||
return (
|
||||
"CONCAT_WS('|', "
|
||||
f"{version_scope_expr}, "
|
||||
f"COALESCE({doc_alias}.normalized_name, ''), "
|
||||
f"COALESCE({file_alias}.file_ext, '')"
|
||||
")"
|
||||
)
|
||||
|
||||
def _version_scope_key_sql(self, alias: str) -> str:
|
||||
return (
|
||||
"CASE "
|
||||
f"WHEN NULLIF(BTRIM({alias}.tenant_code), '') IS NOT NULL "
|
||||
f"THEN CONCAT('TENANT:', NULLIF(BTRIM({alias}.tenant_code), '')) "
|
||||
f"ELSE CONCAT('REGION:', COALESCE(NULLIF(BTRIM({alias}.region), ''), '公共')) "
|
||||
"END"
|
||||
)
|
||||
|
||||
def _tenant_version_match_sql(self, doc_alias: str, tenant_param: str, region_param: str) -> str:
|
||||
return (
|
||||
"("
|
||||
f"NULLIF(BTRIM({doc_alias}.tenant_code), '') = NULLIF(BTRIM(:{tenant_param}), '') "
|
||||
"OR ("
|
||||
f"(:{tenant_param} = '') "
|
||||
f"AND ({doc_alias}.tenant_code IS NULL OR BTRIM({doc_alias}.tenant_code) = '') "
|
||||
f"AND {doc_alias}.region = :{region_param}"
|
||||
")"
|
||||
")"
|
||||
)
|
||||
|
||||
async def _get_document_for_run(
|
||||
self,
|
||||
documentId: int,
|
||||
@@ -1500,7 +1643,9 @@ class GovdocServiceImpl(IGovdocService):
|
||||
f"""
|
||||
SELECT
|
||||
d.id AS document_id,
|
||||
COALESCE(d.region, 'default') AS region,
|
||||
COALESCE(NULLIF(d.region, ''), '公共') AS region,
|
||||
COALESCE(NULLIF(BTRIM(d.tenant_code), ''), NULL) AS tenant_code,
|
||||
{self._tenant_name_sql('d')} AS tenant_name,
|
||||
COALESCE(d.processing_status, 'waiting') AS processing_status,
|
||||
d.current_run_id,
|
||||
COALESCE(NULLIF(d.version_group_key, ''), fallback_vc.derived_version_group_key, '') AS version_group_key,
|
||||
@@ -1568,28 +1713,28 @@ class GovdocServiceImpl(IGovdocService):
|
||||
SELECT
|
||||
d2.id AS document_id,
|
||||
COUNT(*) OVER (
|
||||
PARTITION BY d2.region, COALESCE(d2.normalized_name, ''), COALESCE(f2.file_ext, '')
|
||||
PARTITION BY {self._version_partition_key_sql('d2', 'f2')}
|
||||
) AS total_versions,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY d2.region, COALESCE(d2.normalized_name, ''), COALESCE(f2.file_ext, '')
|
||||
PARTITION BY {self._version_partition_key_sql('d2', 'f2')}
|
||||
ORDER BY d2.created_at ASC, d2.id ASC
|
||||
) AS derived_version_no,
|
||||
LAG(d2.id) OVER (
|
||||
PARTITION BY d2.region, COALESCE(d2.normalized_name, ''), COALESCE(f2.file_ext, '')
|
||||
PARTITION BY {self._version_partition_key_sql('d2', 'f2')}
|
||||
ORDER BY d2.created_at ASC, d2.id ASC
|
||||
) AS derived_previous_version_id,
|
||||
FIRST_VALUE(d2.id) OVER (
|
||||
PARTITION BY d2.region, COALESCE(d2.normalized_name, ''), COALESCE(f2.file_ext, '')
|
||||
PARTITION BY {self._version_partition_key_sql('d2', 'f2')}
|
||||
ORDER BY d2.created_at ASC, d2.id ASC
|
||||
) AS derived_root_version_id,
|
||||
CASE
|
||||
WHEN ROW_NUMBER() OVER (
|
||||
PARTITION BY d2.region, COALESCE(d2.normalized_name, ''), COALESCE(f2.file_ext, '')
|
||||
PARTITION BY {self._version_partition_key_sql('d2', 'f2')}
|
||||
ORDER BY d2.created_at DESC, d2.id DESC
|
||||
) = 1 THEN true
|
||||
ELSE false
|
||||
END AS derived_is_latest_version,
|
||||
md5(CONCAT_WS('|', d2.region, COALESCE(d2.normalized_name, ''), COALESCE(f2.file_ext, ''))) AS derived_version_group_key
|
||||
md5({self._version_partition_key_sql('d2', 'f2')}) AS derived_version_group_key
|
||||
FROM leaudit_documents d2
|
||||
JOIN leaudit_document_files f2
|
||||
ON f2.document_id = d2.id
|
||||
@@ -1620,7 +1765,9 @@ class GovdocServiceImpl(IGovdocService):
|
||||
"""
|
||||
SELECT
|
||||
d.id AS document_id,
|
||||
COALESCE(d.region, 'default') AS region,
|
||||
COALESCE(NULLIF(d.region, ''), '公共') AS region,
|
||||
COALESCE(NULLIF(BTRIM(d.tenant_code), ''), NULL) AS tenant_code,
|
||||
{self._tenant_name_sql('d')} AS tenant_name,
|
||||
COALESCE(d.processing_status, 'waiting') AS processing_status,
|
||||
d.current_run_id,
|
||||
COALESCE(NULLIF(d.version_group_key, ''), fallback_vc.derived_version_group_key, '') AS version_group_key,
|
||||
@@ -1688,28 +1835,28 @@ class GovdocServiceImpl(IGovdocService):
|
||||
SELECT
|
||||
d2.id AS document_id,
|
||||
COUNT(*) OVER (
|
||||
PARTITION BY d2.region, COALESCE(d2.normalized_name, ''), COALESCE(f2.file_ext, '')
|
||||
PARTITION BY {self._version_partition_key_sql('d2', 'f2')}
|
||||
) AS total_versions,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY d2.region, COALESCE(d2.normalized_name, ''), COALESCE(f2.file_ext, '')
|
||||
PARTITION BY {self._version_partition_key_sql('d2', 'f2')}
|
||||
ORDER BY d2.created_at ASC, d2.id ASC
|
||||
) AS derived_version_no,
|
||||
LAG(d2.id) OVER (
|
||||
PARTITION BY d2.region, COALESCE(d2.normalized_name, ''), COALESCE(f2.file_ext, '')
|
||||
PARTITION BY {self._version_partition_key_sql('d2', 'f2')}
|
||||
ORDER BY d2.created_at ASC, d2.id ASC
|
||||
) AS derived_previous_version_id,
|
||||
FIRST_VALUE(d2.id) OVER (
|
||||
PARTITION BY d2.region, COALESCE(d2.normalized_name, ''), COALESCE(f2.file_ext, '')
|
||||
PARTITION BY {self._version_partition_key_sql('d2', 'f2')}
|
||||
ORDER BY d2.created_at ASC, d2.id ASC
|
||||
) AS derived_root_version_id,
|
||||
CASE
|
||||
WHEN ROW_NUMBER() OVER (
|
||||
PARTITION BY d2.region, COALESCE(d2.normalized_name, ''), COALESCE(f2.file_ext, '')
|
||||
PARTITION BY {self._version_partition_key_sql('d2', 'f2')}
|
||||
ORDER BY d2.created_at DESC, d2.id DESC
|
||||
) = 1 THEN true
|
||||
ELSE false
|
||||
END AS derived_is_latest_version,
|
||||
md5(CONCAT_WS('|', d2.region, COALESCE(d2.normalized_name, ''), COALESCE(f2.file_ext, ''))) AS derived_version_group_key
|
||||
md5({self._version_partition_key_sql('d2', 'f2')}) AS derived_version_group_key
|
||||
FROM leaudit_documents d2
|
||||
JOIN leaudit_document_files f2
|
||||
ON f2.document_id = d2.id
|
||||
@@ -1734,6 +1881,74 @@ class GovdocServiceImpl(IGovdocService):
|
||||
raise LeauditException(StatusCodeEnum.HTTP_404_NOT_FOUND, "公文文档不存在")
|
||||
return self._map_document_row(row)
|
||||
|
||||
async def _get_scoped_run_row(self, runId: int, *, userId: int | None) -> dict[str, Any]:
|
||||
if userId is None:
|
||||
raise LeauditException(StatusCodeEnum.HTTP_401_UNAUTHORIZED, "当前用户未登录")
|
||||
|
||||
currentUser = await self._getCurrentUserContext(userId)
|
||||
params: dict[str, Any] = {"run_id": runId}
|
||||
filters = [
|
||||
"gr.id = :run_id",
|
||||
"gr.deleted_at IS NULL",
|
||||
"d.deleted_at IS NULL",
|
||||
"f.deleted_at IS NULL",
|
||||
"f.is_active = true",
|
||||
"f.file_role = 'original'",
|
||||
"COALESCE(d.engine_type, 'leaudit') = 'govdoc'",
|
||||
]
|
||||
filters.extend(
|
||||
self._buildDocumentScopeFilters(
|
||||
CurrentUserId=userId,
|
||||
CurrentUser=currentUser,
|
||||
Params=params,
|
||||
DocumentAlias="d",
|
||||
FileAlias="f",
|
||||
)
|
||||
)
|
||||
whereClause = " AND ".join(filters)
|
||||
|
||||
async with GetAsyncSession() as session:
|
||||
await self._ensureGovdocSchema(session)
|
||||
row = (
|
||||
await session.execute(
|
||||
text(
|
||||
f"""
|
||||
SELECT
|
||||
gr.id,
|
||||
gr.document_id,
|
||||
gr.status,
|
||||
gr.phase,
|
||||
gr.result_status,
|
||||
gr.total_score,
|
||||
gr.passed_count,
|
||||
gr.failed_count,
|
||||
gr.skipped_count,
|
||||
gr.error_message,
|
||||
gr.task_id,
|
||||
gr.result_summary_json,
|
||||
gr.created_at,
|
||||
gr.updated_at,
|
||||
gr.started_at,
|
||||
gr.finished_at
|
||||
FROM govdoc_runs gr
|
||||
JOIN leaudit_documents d
|
||||
ON d.id = gr.document_id
|
||||
JOIN leaudit_document_files f
|
||||
ON f.document_id = d.id
|
||||
AND f.is_active = true
|
||||
AND f.file_role = 'original'
|
||||
AND f.deleted_at IS NULL
|
||||
WHERE {whereClause}
|
||||
LIMIT 1
|
||||
"""
|
||||
),
|
||||
params,
|
||||
)
|
||||
).mappings().first()
|
||||
if not row:
|
||||
raise LeauditException(StatusCodeEnum.HTTP_404_NOT_FOUND, "审查运行不存在或无权访问")
|
||||
return dict(row)
|
||||
|
||||
async def _get_active_original_file(self, documentId: int):
|
||||
async with GetAsyncSession() as session:
|
||||
row = (
|
||||
@@ -1866,7 +2081,9 @@ class GovdocServiceImpl(IGovdocService):
|
||||
|
||||
return _GovdocDocumentRow(
|
||||
documentId=int(row["document_id"]),
|
||||
region=str(row["region"] or "default"),
|
||||
region=str(row["region"] or "公共"),
|
||||
tenantCode=str(row["tenant_code"]) if row.get("tenant_code") else None,
|
||||
tenantName=str(row["tenant_name"]) if row.get("tenant_name") else None,
|
||||
processingStatus=str(row["processing_status"] or "waiting"),
|
||||
currentRunId=int(row["current_run_id"]) if row.get("current_run_id") is not None else None,
|
||||
versionGroupKey=str(row["version_group_key"]) if row.get("version_group_key") else None,
|
||||
@@ -1921,6 +2138,8 @@ class GovdocServiceImpl(IGovdocService):
|
||||
"mimeType": mapped.mimeType,
|
||||
"fileSize": mapped.fileSize,
|
||||
"region": mapped.region,
|
||||
"tenantCode": mapped.tenantCode,
|
||||
"tenantName": mapped.tenantName or mapped.region,
|
||||
"processingStatus": mapped.processingStatus,
|
||||
"currentRunId": mapped.currentRunId,
|
||||
"latestRunId": mapped.currentRunId,
|
||||
@@ -1955,12 +2174,20 @@ class GovdocServiceImpl(IGovdocService):
|
||||
session,
|
||||
*,
|
||||
region: str,
|
||||
tenantCode: str | None = None,
|
||||
normalizedName: str,
|
||||
fileExt: str | None,
|
||||
) -> dict[str, Any] | None:
|
||||
resolved_tenant = await self.TenantResolver.Resolve(
|
||||
RawValue=region,
|
||||
Source="govdoc_version_lookup",
|
||||
PreferredTenantCode=str(tenantCode or "").strip() or None,
|
||||
)
|
||||
normalized_region = str(resolved_tenant.tenant_name or resolved_tenant.normalized_value or region or "公共").strip() or "公共"
|
||||
extClause = ""
|
||||
params: dict[str, Any] = {
|
||||
"region": region,
|
||||
"tenant_code": str(resolved_tenant.tenant_code or "").strip(),
|
||||
"region": normalized_region,
|
||||
"normalized_name": normalizedName,
|
||||
}
|
||||
if fileExt:
|
||||
@@ -1985,7 +2212,7 @@ class GovdocServiceImpl(IGovdocService):
|
||||
WHERE d.deleted_at IS NULL
|
||||
AND d.review_scope = 'govdoc'
|
||||
AND COALESCE(d.engine_type, 'leaudit') = 'govdoc'
|
||||
AND d.region = :region
|
||||
AND {self._tenant_version_match_sql('d', 'tenant_code', 'region')}
|
||||
AND COALESCE(d.normalized_name, '') = :normalized_name
|
||||
AND COALESCE(d.is_latest_version, true) = true{extClause}
|
||||
ORDER BY d.version_no DESC, d.id DESC
|
||||
@@ -2002,12 +2229,20 @@ class GovdocServiceImpl(IGovdocService):
|
||||
session,
|
||||
*,
|
||||
region: str,
|
||||
tenantCode: str | None = None,
|
||||
normalizedName: str,
|
||||
fileExt: str | None,
|
||||
) -> dict[str, Any] | None:
|
||||
resolved_tenant = await self.TenantResolver.Resolve(
|
||||
RawValue=region,
|
||||
Source="govdoc_version_backfill",
|
||||
PreferredTenantCode=str(tenantCode or "").strip() or None,
|
||||
)
|
||||
normalized_region = str(resolved_tenant.tenant_name or resolved_tenant.normalized_value or region or "公共").strip() or "公共"
|
||||
extClause = ""
|
||||
params: dict[str, Any] = {
|
||||
"region": region,
|
||||
"tenant_code": str(resolved_tenant.tenant_code or "").strip(),
|
||||
"region": normalized_region,
|
||||
"normalized_name": normalizedName,
|
||||
}
|
||||
if fileExt:
|
||||
@@ -2028,7 +2263,7 @@ class GovdocServiceImpl(IGovdocService):
|
||||
WHERE d.deleted_at IS NULL
|
||||
AND d.review_scope = 'govdoc'
|
||||
AND COALESCE(d.engine_type, 'leaudit') = 'govdoc'
|
||||
AND d.region = :region
|
||||
AND {self._tenant_version_match_sql('d', 'tenant_code', 'region')}
|
||||
AND COALESCE(d.normalized_name, '') = :normalized_name{extClause}
|
||||
ORDER BY d.created_at ASC, d.id ASC
|
||||
"""
|
||||
@@ -2085,7 +2320,9 @@ class GovdocServiceImpl(IGovdocService):
|
||||
text(
|
||||
"""
|
||||
SELECT
|
||||
d.region,
|
||||
COALESCE(NULLIF(BTRIM(d.region), ''), '公共') AS region,
|
||||
COALESCE(NULLIF(BTRIM(d.tenant_code), ''), NULL) AS tenant_code,
|
||||
CASE WHEN NULLIF(BTRIM(d.tenant_code), '') = 'PUBLIC' THEN '公共' WHEN NULLIF(BTRIM(d.tenant_code), '') = 'PROVINCIAL' THEN '省级' ELSE COALESCE(NULLIF(BTRIM(d.region), ''), '公共') END AS tenant_name,
|
||||
COALESCE(d.normalized_name, '') AS normalized_name,
|
||||
COALESCE(f.file_ext, '') AS file_ext,
|
||||
ARRAY_AGG(d.id ORDER BY d.created_at ASC, d.id ASC) AS document_ids,
|
||||
@@ -2099,7 +2336,7 @@ class GovdocServiceImpl(IGovdocService):
|
||||
WHERE d.deleted_at IS NULL
|
||||
AND d.review_scope = 'govdoc'
|
||||
AND COALESCE(d.engine_type, 'leaudit') = 'govdoc'
|
||||
GROUP BY d.region, COALESCE(d.normalized_name, ''), COALESCE(f.file_ext, '')
|
||||
GROUP BY COALESCE(NULLIF(BTRIM(d.region), ''), '公共'), COALESCE(NULLIF(BTRIM(d.tenant_code), ''), NULL), CASE WHEN NULLIF(BTRIM(d.tenant_code), '') = 'PUBLIC' THEN '公共' WHEN NULLIF(BTRIM(d.tenant_code), '') = 'PROVINCIAL' THEN '省级' ELSE COALESCE(NULLIF(BTRIM(d.region), ''), '公共') END, COALESCE(d.normalized_name, ''), COALESCE(f.file_ext, '')
|
||||
HAVING BOOL_OR(COALESCE(d.version_group_key, '') = '')
|
||||
"""
|
||||
)
|
||||
@@ -2110,7 +2347,9 @@ class GovdocServiceImpl(IGovdocService):
|
||||
return
|
||||
|
||||
for group in groups:
|
||||
region = str(group["region"] or "default")
|
||||
region = str(group["region"] or "公共")
|
||||
tenantCode = str(group.get("tenant_code") or "").strip() or None
|
||||
tenantName = str(group.get("tenant_name") or region).strip() or region
|
||||
normalizedName = str(group["normalized_name"] or "")
|
||||
fileExt = str(group["file_ext"] or "")
|
||||
documentIds = [int(value) for value in (group["document_ids"] or [])]
|
||||
@@ -2118,6 +2357,8 @@ class GovdocServiceImpl(IGovdocService):
|
||||
continue
|
||||
|
||||
versionGroupKey = str(group["existing_version_group_key"] or "").strip() or self._derive_version_group_key(
|
||||
tenantCode=tenantCode,
|
||||
tenantName=tenantName,
|
||||
region=region,
|
||||
normalizedName=normalizedName,
|
||||
fileExt=fileExt or None,
|
||||
@@ -2153,8 +2394,21 @@ class GovdocServiceImpl(IGovdocService):
|
||||
|
||||
await session.commit()
|
||||
|
||||
def _derive_version_group_key(self, *, region: str, normalizedName: str, fileExt: str | None) -> str:
|
||||
raw = f"{region}|{normalizedName}|{fileExt or ''}"
|
||||
def _derive_version_group_key(
|
||||
self,
|
||||
*,
|
||||
tenantCode: str | None,
|
||||
tenantName: str | None,
|
||||
region: str,
|
||||
normalizedName: str,
|
||||
fileExt: str | None,
|
||||
) -> str:
|
||||
versionScopeKey = (
|
||||
f"TENANT:{tenantCode.strip()}"
|
||||
if str(tenantCode or "").strip()
|
||||
else f"REGION:{(tenantName or region or '公共').strip() or '公共'}"
|
||||
)
|
||||
raw = f"{versionScopeKey}|{normalizedName}|{fileExt or ''}"
|
||||
return hashlib.md5(raw.encode("utf-8")).hexdigest()
|
||||
|
||||
async def _resolve_ruleset_metadata(self, rulesPath: str | None) -> dict[str, str]:
|
||||
|
||||
Reference in New Issue
Block a user