优化文档管理与导航体验

1、为文档增加编辑接口和前端编辑抽屉,文档列表与详情页统一按创建、更新、删除权限展示操作。

2、将文档版本上传和分发改为抽屉交互,并在详情页补充版本删除权限检查。

3、增强全局面包屑上下文和详情页标题同步,避免详情页重复或缺失导航信息。

4、按项目权限过滤快捷入口、首页费用指标和 eTMF 新建入口,并补充对应前端测试。
This commit is contained in:
Cheng Zhou
2026-06-04 11:11:28 +08:00
parent 6e8494abd5
commit 90ca061db8
21 changed files with 1016 additions and 189 deletions
+52 -1
View File
@@ -32,7 +32,7 @@ from app.models.document import Document, DocumentScopeType, DocumentStatus
from app.models.document_version import DocumentVersion, DocumentVersionStatus
from app.schemas.acknowledgement import AcknowledgementCreate
from app.schemas.distribution import DistributionCreate, DistributionRead, DistributionStats
from app.schemas.document import DocumentCreate, DocumentDetail, DocumentSummary
from app.schemas.document import DocumentCreate, DocumentDetail, DocumentSummary, DocumentUpdate
from app.schemas.document_version import DocumentVersionRead, DocumentVersionSummary
from app.schemas.notification import NotificationItem
from app.schemas.user import UserDisplay
@@ -43,6 +43,7 @@ DOCUMENT_ACTION_PERMISSIONS = {
"view": "documents:read",
"ack": "documents:read",
"create_document": "documents:create",
"update_document": "documents:update",
"create_version": "documents:update",
"distribute": "documents:update",
"delete_document": "documents:delete",
@@ -261,6 +262,56 @@ async def get_document_detail(
)
async def update_document(
db: AsyncSession,
document_id: uuid.UUID,
doc_in: DocumentUpdate,
current_user,
) -> Document:
doc = await document_crud.get(db, document_id)
if not doc:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="文档不存在")
await _ensure_study_access(db, doc.trial_id, current_user, action="update_document")
values = doc_in.model_dump(exclude_unset=True)
if not values:
return doc
next_scope = values.get("scope_type", doc.scope_type)
next_scope_value = getattr(next_scope, "value", next_scope)
next_site_id = values.get("site_id", doc.site_id)
if next_scope_value == DocumentScopeType.SITE.value:
if not next_site_id:
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="分中心文件需指定分中心")
site = await site_crud.get_site(db, next_site_id)
if not site or site.study_id != doc.trial_id:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="分中心不属于当前项目")
if not site.is_active:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="中心已停用")
else:
values["site_id"] = None
before = _doc_snapshot(doc)
await document_crud.update(db, document_id, values, commit=False)
updated = await document_crud.get(db, document_id)
if not updated:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="文档不存在")
db.add(
AuditLog(
study_id=updated.trial_id,
entity_type="DOCUMENT",
entity_id=updated.id,
action="DOCUMENT_UPDATED",
detail=_audit_detail(before, _doc_snapshot(updated)),
operator_id=current_user.id,
operator_role=await get_operator_role_label(db, updated.trial_id, current_user),
)
)
await db.commit()
await db.refresh(updated)
return updated
async def create_version(
db: AsyncSession,
document_id: uuid.UUID,