完善审计日志展示与导出权限

This commit is contained in:
Cheng Zhou
2026-06-10 10:20:04 +08:00
parent 1fef12cd86
commit ea3f19e241
33 changed files with 1862 additions and 240 deletions
+4 -2
View File
@@ -56,23 +56,25 @@ def _audit_detail(before: dict | None, after: dict | None) -> str:
def _doc_snapshot(doc: Document) -> dict:
status = doc.status.value if hasattr(doc.status, "value") else str(doc.status)
return {
"id": str(doc.id),
"trial_id": str(doc.trial_id),
"site_id": str(doc.site_id) if doc.site_id else None,
"etmf_node_id": str(doc.etmf_node_id) if doc.etmf_node_id else None,
"doc_no": doc.doc_no,
"status": str(doc.status),
"status": status,
"current_effective_version_id": str(doc.current_effective_version_id) if doc.current_effective_version_id else None,
}
def _version_snapshot(version: DocumentVersion) -> dict:
status = version.status.value if hasattr(version.status, "value") else str(version.status)
return {
"id": str(version.id),
"document_id": str(version.document_id),
"version_no": version.version_no,
"status": str(version.status),
"status": status,
"file_hash": version.file_hash,
}
+23 -2
View File
@@ -1,6 +1,7 @@
from __future__ import annotations
import uuid
import json
from collections import defaultdict
from typing import Iterable
@@ -18,6 +19,17 @@ from app.schemas.document import DocumentCreate, DocumentSummary
from app.schemas.etmf import EtmfNodeCreate, EtmfNodeRead, EtmfNodeStatus, EtmfTreeNode, EtmfNodeUpdate
def _node_audit_snapshot(node: EtmfNode) -> dict[str, object | None]:
return {
"code": node.code,
"name": node.name,
"parent_id": str(node.parent_id) if node.parent_id else None,
"scope_type": node.scope_type,
"required": node.required,
"is_active": node.is_active,
}
def calculate_node_status(node: EtmfNode, documents: Iterable[Document]) -> EtmfNodeStatus:
docs = list(documents)
if not node.is_active:
@@ -102,7 +114,10 @@ async def create_node(db: AsyncSession, payload: EtmfNodeCreate, current_user) -
entity_type="ETMF_NODE",
entity_id=node.id,
action="ETMF_NODE_CREATED",
detail=f'{{"code":"{node.code}","name":"{node.name}"}}',
detail=json.dumps(
{"targetName": node.name, "after": _node_audit_snapshot(node)},
ensure_ascii=False,
),
operator_id=current_user.id,
operator_role=await get_operator_role_label(db, payload.study_id, current_user),
)
@@ -124,14 +139,20 @@ async def update_node(db: AsyncSession, node_id: uuid.UUID, payload: EtmfNodeUpd
parent = await etmf_crud.get_node(db, values["parent_id"])
if not parent or parent.study_id != node.study_id or parent.id == node.id:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="父级eTMF目录不合法")
before = _node_audit_snapshot(node)
await etmf_crud.update_node(db, node_id, values, commit=False)
updated_snapshot = {**before, **{key: value for key, value in values.items() if key in before}}
db.add(
AuditLog(
study_id=node.study_id,
entity_type="ETMF_NODE",
entity_id=node.id,
action="ETMF_NODE_UPDATED",
detail="{}",
detail=json.dumps(
{"targetName": updated_snapshot.get("name") or before.get("name"), "before": before, "after": updated_snapshot},
ensure_ascii=False,
default=str,
),
operator_id=current_user.id,
operator_role=await get_operator_role_label(db, node.study_id, current_user),
)