清理全局角色残留并修复项目权限判断
This commit is contained in:
@@ -3,9 +3,9 @@ import uuid
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.deps import get_db_session, require_roles
|
||||
from app.core.deps import get_db_session, is_system_admin, require_roles
|
||||
from app.crud import user as user_crud
|
||||
from app.models.user import User, UserRole, UserStatus
|
||||
from app.models.user import User, UserStatus
|
||||
from app.schemas.user import AdminUserListResponse, UserAdminReviewRequest, UserResponse
|
||||
|
||||
router = APIRouter(prefix="/users")
|
||||
@@ -15,7 +15,7 @@ router = APIRouter(prefix="/users")
|
||||
async def list_users_for_review(
|
||||
status_filter: UserStatus = Query(default=UserStatus.PENDING, alias="status"),
|
||||
db: AsyncSession = Depends(get_db_session),
|
||||
current_user=Depends(require_roles([UserRole.ADMIN.value])),
|
||||
current_user=Depends(require_roles(["ADMIN"])),
|
||||
) -> AdminUserListResponse:
|
||||
users = await user_crud.list_users_by_status(db, status=status_filter)
|
||||
return AdminUserListResponse(items=list(users), total=len(users))
|
||||
@@ -25,7 +25,7 @@ async def _get_review_user(db: AsyncSession, user_id: uuid.UUID) -> User:
|
||||
user = await user_crud.get_by_id(db, user_id)
|
||||
if not user:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="用户不存在")
|
||||
if user.role == UserRole.ADMIN:
|
||||
if is_system_admin(user):
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="管理员账号不允许审核")
|
||||
return user
|
||||
|
||||
@@ -35,7 +35,7 @@ async def approve_user(
|
||||
user_id: uuid.UUID,
|
||||
review: UserAdminReviewRequest,
|
||||
db: AsyncSession = Depends(get_db_session),
|
||||
current_user=Depends(require_roles([UserRole.ADMIN.value])),
|
||||
current_user=Depends(require_roles(["ADMIN"])),
|
||||
) -> UserResponse:
|
||||
if review.action != "approve":
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="非法操作")
|
||||
@@ -51,7 +51,7 @@ async def reject_user(
|
||||
user_id: uuid.UUID,
|
||||
review: UserAdminReviewRequest,
|
||||
db: AsyncSession = Depends(get_db_session),
|
||||
current_user=Depends(require_roles([UserRole.ADMIN.value])),
|
||||
current_user=Depends(require_roles(["ADMIN"])),
|
||||
) -> UserResponse:
|
||||
if review.action not in ("reject", "approve"):
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="非法操作")
|
||||
|
||||
@@ -5,7 +5,7 @@ from datetime import date
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.deps import get_cra_site_scope, get_current_user, get_db_session, require_study_not_locked, require_api_permission
|
||||
from app.core.deps import get_operator_role_label, get_cra_site_scope, get_current_user, get_db_session, require_study_not_locked, require_api_permission
|
||||
from app.crud import ae as ae_crud
|
||||
from app.crud import audit as audit_crud
|
||||
from app.crud import member as member_crud
|
||||
@@ -79,7 +79,7 @@ async def create_ae(
|
||||
action="CREATE_AE",
|
||||
detail=f"AE {ae.id} 已创建",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
data = AERead.model_validate(ae)
|
||||
data.is_overdue = _is_overdue(data)
|
||||
@@ -249,7 +249,7 @@ async def update_ae(
|
||||
action=action,
|
||||
detail=json.dumps({"before": detail_before, "after": detail_after}, ensure_ascii=False, default=str),
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
data = AERead.model_validate(updated)
|
||||
data.is_overdue = _is_overdue(data)
|
||||
@@ -281,5 +281,5 @@ async def delete_ae(
|
||||
action="DELETE_AE",
|
||||
detail=f"AE {ae_id} 已删除",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
|
||||
@@ -9,7 +9,7 @@ from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.deps import get_current_user, get_db_session, require_study_member, require_study_roles
|
||||
from app.core.deps import get_current_user, get_db_session, is_system_admin, require_study_member, require_system_permission
|
||||
from app.core.project_permissions import (
|
||||
get_api_endpoint_permissions,
|
||||
replace_api_endpoint_permissions,
|
||||
@@ -163,8 +163,7 @@ async def get_my_study_api_permissions(
|
||||
db: Annotated[AsyncSession, Depends(get_db_session)] = None,
|
||||
):
|
||||
"""获取当前用户当前项目角色的有效权限。"""
|
||||
role_value = current_user.role.value if hasattr(current_user.role, "value") else str(current_user.role)
|
||||
if role_value == "ADMIN":
|
||||
if is_system_admin(current_user):
|
||||
return {
|
||||
"ADMIN": {
|
||||
endpoint_key: {"allowed": True}
|
||||
@@ -192,7 +191,7 @@ async def get_my_study_api_permissions(
|
||||
)
|
||||
async def get_study_api_permissions(
|
||||
study_id: uuid.UUID,
|
||||
_=Depends(require_study_roles(["PM"])),
|
||||
_=Depends(require_system_permission("system:permissions:project_config")),
|
||||
db: Annotated[AsyncSession, Depends(get_db_session)] = None,
|
||||
):
|
||||
"""获取项目的接口级权限矩阵
|
||||
@@ -233,7 +232,7 @@ async def get_study_api_permissions(
|
||||
async def update_study_api_permissions(
|
||||
study_id: uuid.UUID,
|
||||
payload: dict[str, dict[str, bool]],
|
||||
_=Depends(require_study_roles(["PM"])),
|
||||
_=Depends(require_system_permission("system:permissions:project_config")),
|
||||
current_user=Depends(get_current_user),
|
||||
db: Annotated[AsyncSession, Depends(get_db_session)] = None,
|
||||
):
|
||||
@@ -248,13 +247,12 @@ async def update_study_api_permissions(
|
||||
"""
|
||||
# 验证输入
|
||||
configurable_roles = set(await _get_configurable_roles(db, study_id))
|
||||
current_role = current_user.role.value if hasattr(current_user.role, "value") else str(current_user.role)
|
||||
for role in payload.keys():
|
||||
if role == "ADMIN":
|
||||
continue
|
||||
if role not in configurable_roles:
|
||||
raise ValueError(f"无效的角色: {role}")
|
||||
if role == "PM" and current_role != "ADMIN":
|
||||
if role == "PM" and not is_system_admin(current_user):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="仅系统管理员可修改项目负责人权限",
|
||||
|
||||
@@ -8,7 +8,7 @@ from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status,
|
||||
from fastapi.responses import FileResponse
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.deps import get_current_user, get_db_session, get_study_member, require_study_not_locked
|
||||
from app.core.deps import get_current_user, get_db_session, get_operator_role_label, get_study_member, is_system_admin, require_study_not_locked
|
||||
from app.core.project_permissions import role_has_api_permission
|
||||
from app.crud import attachment as attachment_crud
|
||||
from app.crud import audit as audit_crud
|
||||
@@ -71,10 +71,6 @@ async def _ensure_study_exists(db: AsyncSession, study_id: uuid.UUID):
|
||||
return study
|
||||
|
||||
|
||||
def _role_value(user) -> str:
|
||||
return user.role.value if hasattr(user.role, "value") else user.role
|
||||
|
||||
|
||||
def _attachment_operation(action: str) -> str:
|
||||
if action in {"create", "read", "delete"}:
|
||||
return action
|
||||
@@ -154,8 +150,7 @@ async def _ensure_attachment_permission(
|
||||
membership=None,
|
||||
) -> None:
|
||||
parent_permission = await _resolve_attachment_parent_permission(db, study_id, entity_type, entity_id, action)
|
||||
role_value = _role_value(current_user)
|
||||
if role_value == "ADMIN":
|
||||
if is_system_admin(current_user):
|
||||
return
|
||||
|
||||
if membership is None:
|
||||
@@ -238,7 +233,7 @@ async def upload_attachment(
|
||||
action="UPLOAD_FILE",
|
||||
detail=f"文件已上传:{file.filename}",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
return AttachmentRead(
|
||||
id=attachment.id,
|
||||
@@ -349,7 +344,7 @@ async def _authorize_global(request: Request, db: AsyncSession, study_id: uuid.U
|
||||
user = await user_crud.get_by_id(db, uuid.UUID(str(payload.get("sub"))))
|
||||
if not user or not user.is_active:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="账号不存在或已停用")
|
||||
if _role_value(user) == "ADMIN":
|
||||
if is_system_admin(user):
|
||||
return user, None
|
||||
membership = await member_crud.get_member(db, study_id, user.id)
|
||||
if not membership or not membership.is_active:
|
||||
@@ -429,7 +424,7 @@ async def global_delete_attachment(
|
||||
db, attachment.study_id, attachment.entity_type, attachment.entity_id, "delete", user, membership
|
||||
)
|
||||
can_delete = (
|
||||
_role_value(user) == "ADMIN"
|
||||
is_system_admin(user)
|
||||
or attachment.uploaded_by == user.id
|
||||
or membership is not None
|
||||
)
|
||||
@@ -444,7 +439,7 @@ async def global_delete_attachment(
|
||||
action="DELETE_ATTACHMENT",
|
||||
detail=f"文件已删除:{attachment.filename}",
|
||||
operator_id=user.id,
|
||||
operator_role=user.role,
|
||||
operator_role=await get_operator_role_label(db, attachment.study_id, user),
|
||||
)
|
||||
|
||||
|
||||
@@ -476,11 +471,11 @@ async def delete_attachment(
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="附件不存在")
|
||||
|
||||
membership = None
|
||||
if _role_value(current_user) != "ADMIN":
|
||||
if not is_system_admin(current_user):
|
||||
membership = await get_study_member(study_id, current_user=current_user, db=db)
|
||||
|
||||
can_delete = (
|
||||
_role_value(current_user) == "ADMIN"
|
||||
is_system_admin(current_user)
|
||||
or attachment.uploaded_by == current_user.id
|
||||
or membership is not None
|
||||
)
|
||||
@@ -496,5 +491,5 @@ async def delete_attachment(
|
||||
action="DELETE_ATTACHMENT",
|
||||
detail=f"文件已删除:{attachment.filename}",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ import uuid
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.deps import get_current_user, get_db_session, require_roles, require_study_member, require_api_permission
|
||||
from app.core.deps import get_operator_role_label, get_current_user, get_db_session, require_roles, require_api_permission
|
||||
from app.crud import audit as audit_crud
|
||||
from app.schemas.audit import AuditEventCreate, AuditLogRead
|
||||
|
||||
@@ -57,11 +57,11 @@ async def delete_audit_log(
|
||||
@router.post(
|
||||
"/events",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
dependencies=[Depends(require_study_member())],
|
||||
)
|
||||
async def create_audit_event(
|
||||
study_id: uuid.UUID,
|
||||
payload: AuditEventCreate,
|
||||
_export_permission=Depends(require_api_permission("audit_logs:export")),
|
||||
db: AsyncSession = Depends(get_db_session),
|
||||
current_user=Depends(get_current_user),
|
||||
):
|
||||
@@ -76,6 +76,6 @@ async def create_audit_event(
|
||||
action=payload.action,
|
||||
detail=payload.detail,
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
return {"ok": True}
|
||||
|
||||
@@ -11,7 +11,7 @@ from app.core.login_crypto import create_login_challenge, decrypt_login_payload,
|
||||
from app.core.security import create_access_token, decode_token_allow_expired, oauth2_scheme, verify_password
|
||||
from app.core.deps import get_current_user, get_db_session
|
||||
from app.crud import user as user_crud
|
||||
from app.models.user import UserRole, UserStatus
|
||||
from app.models.user import UserStatus
|
||||
from app.schemas.user import Token, UserRead, UserRegisterRequest, UserSelfUpdate, UserUpdate
|
||||
from fastapi.responses import FileResponse
|
||||
|
||||
@@ -57,7 +57,6 @@ def issue_user_token(db_user) -> Token:
|
||||
session_start = datetime.now(timezone.utc)
|
||||
access_token = create_access_token(
|
||||
user_id=str(db_user.id),
|
||||
role=db_user.role.value if hasattr(db_user.role, "value") else db_user.role,
|
||||
expires_minutes=None,
|
||||
session_start=session_start,
|
||||
)
|
||||
@@ -191,7 +190,6 @@ async def extend_access_token(
|
||||
session_start = datetime.now(timezone.utc)
|
||||
new_token = create_access_token(
|
||||
user_id=str(db_user.id),
|
||||
role=db_user.role.value if hasattr(db_user.role, "value") else db_user.role,
|
||||
expires_minutes=None,
|
||||
session_start=session_start,
|
||||
)
|
||||
@@ -210,7 +208,6 @@ async def unlock_session(
|
||||
session_start = datetime.now(timezone.utc)
|
||||
access_token = create_access_token(
|
||||
user_id=str(db_user.id),
|
||||
role=db_user.role.value if hasattr(db_user.role, "value") else db_user.role,
|
||||
expires_minutes=None,
|
||||
session_start=session_start,
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ from fastapi import APIRouter, Depends
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.deps import get_cra_site_scope, get_current_user, get_db_session, require_study_member, require_api_permission
|
||||
from app.core.deps import get_cra_site_scope, get_current_user, get_db_session, is_system_admin, require_api_permission
|
||||
from app.core.project_permissions import role_has_api_permission
|
||||
from app.crud import member as member_crud
|
||||
from app.models.milestone import Milestone
|
||||
@@ -16,13 +16,10 @@ from app.crud import overview as overview_crud
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
def _role_value(user) -> str:
|
||||
return user.role.value if hasattr(user.role, "value") else str(user.role)
|
||||
|
||||
|
||||
@router.get("/progress", response_model=StudyProgressRead, dependencies=[Depends(require_study_member())])
|
||||
@router.get("/progress", response_model=StudyProgressRead)
|
||||
async def get_progress(
|
||||
study_id: uuid.UUID,
|
||||
_=Depends(require_api_permission("project_overview:read")),
|
||||
db: AsyncSession = Depends(get_db_session),
|
||||
) -> StudyProgressRead:
|
||||
milestone_total_stmt = select(func.count()).select_from(Milestone).where(Milestone.study_id == study_id)
|
||||
@@ -42,10 +39,11 @@ async def get_progress(
|
||||
)
|
||||
|
||||
|
||||
@router.get("/lost-visits", response_model=list[VisitLostItem], dependencies=[Depends(require_study_member())])
|
||||
@router.get("/lost-visits", response_model=list[VisitLostItem])
|
||||
async def list_lost_visits(
|
||||
study_id: uuid.UUID,
|
||||
limit: int = 20,
|
||||
_=Depends(require_api_permission("project_overview:read")),
|
||||
db: AsyncSession = Depends(get_db_session),
|
||||
current_user=Depends(get_current_user),
|
||||
) -> list[VisitLostItem]:
|
||||
@@ -68,18 +66,15 @@ async def list_lost_visits(
|
||||
return items
|
||||
|
||||
|
||||
@router.get("/center-summary", response_model=list[CenterSummaryItem], dependencies=[
|
||||
Depends(require_study_member()),
|
||||
Depends(require_api_permission("project_overview:read"))
|
||||
])
|
||||
@router.get("/center-summary", response_model=list[CenterSummaryItem])
|
||||
async def get_center_summary(
|
||||
study_id: uuid.UUID,
|
||||
_=Depends(require_api_permission("project_overview:read")),
|
||||
db: AsyncSession = Depends(get_db_session),
|
||||
current_user=Depends(get_current_user),
|
||||
) -> list[CenterSummaryItem]:
|
||||
role_value = _role_value(current_user)
|
||||
membership = None
|
||||
if role_value != "ADMIN":
|
||||
if not is_system_admin(current_user):
|
||||
membership = await member_crud.get_member(db, study_id, current_user.id)
|
||||
if not membership or not membership.is_active:
|
||||
return []
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import uuid
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, File, Form, HTTPException, Query, UploadFile, status
|
||||
from fastapi import APIRouter, Depends, File, Form, Query, UploadFile, status
|
||||
from fastapi.responses import FileResponse
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
@@ -11,7 +11,6 @@ from app.schemas.common import PaginatedResponse
|
||||
from app.schemas.distribution import DistributionCreate, DistributionRead
|
||||
from app.schemas.document import DocumentCreate, DocumentDetail, DocumentSummary
|
||||
from app.schemas.document_version import DocumentVersionRead
|
||||
from app.models.user import UserRole
|
||||
from app.services import document_service
|
||||
from app.utils.pagination import paginate
|
||||
|
||||
@@ -73,9 +72,6 @@ async def delete_document(
|
||||
db: AsyncSession = Depends(get_db_session),
|
||||
current_user=Depends(get_current_user),
|
||||
) -> DocumentSummary:
|
||||
role_value = current_user.role.value if hasattr(current_user.role, "value") else current_user.role
|
||||
if role_value != UserRole.ADMIN.value:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="仅管理员可删除文档")
|
||||
doc = await document_service.delete_document(db, document_id, current_user)
|
||||
return DocumentSummary.model_validate(doc)
|
||||
|
||||
@@ -123,9 +119,6 @@ async def delete_version(
|
||||
db: AsyncSession = Depends(get_db_session),
|
||||
current_user=Depends(get_current_user),
|
||||
) -> None:
|
||||
role_value = current_user.role.value if hasattr(current_user.role, "value") else current_user.role
|
||||
if role_value != UserRole.ADMIN.value:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="仅管理员可删除版本")
|
||||
await document_service.delete_version(db, version_id, current_user)
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import uuid
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.deps import get_cra_site_scope, get_current_user, get_db_session, require_study_not_locked, require_api_permission
|
||||
from app.core.deps import get_operator_role_label, get_cra_site_scope, get_current_user, get_db_session, require_study_not_locked, require_api_permission
|
||||
from app.crud import audit as audit_crud
|
||||
from app.crud import drug_shipment as shipment_crud
|
||||
from app.crud import site as site_crud
|
||||
@@ -56,7 +56,7 @@ async def create_shipment(
|
||||
action="CREATE_DRUG_SHIPMENT",
|
||||
detail=f"药品运输 {shipment.id} 已创建",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
return DrugShipmentRead.model_validate(shipment)
|
||||
|
||||
@@ -152,7 +152,7 @@ async def update_shipment(
|
||||
action="UPDATE_DRUG_SHIPMENT",
|
||||
detail=f"药品运输 {shipment_id} 已更新",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
return DrugShipmentRead.model_validate(shipment)
|
||||
|
||||
@@ -185,5 +185,5 @@ async def delete_shipment(
|
||||
action="DELETE_DRUG_SHIPMENT",
|
||||
detail=f"药品运输 {shipment_id} 已删除",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ import uuid
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.deps import get_current_user, get_db_session, require_study_not_locked, require_api_permission
|
||||
from app.core.deps import get_current_user, get_db_session, get_operator_role_label, is_system_admin, require_study_not_locked, require_api_permission
|
||||
from app.core.project_permissions import role_has_api_permission
|
||||
from app.crud import audit as audit_crud
|
||||
from app.crud import faq_category as category_crud
|
||||
@@ -17,8 +17,7 @@ router = APIRouter()
|
||||
|
||||
|
||||
def _is_system_admin(current_user) -> bool:
|
||||
role = current_user.role.value if hasattr(current_user.role, "value") else current_user.role
|
||||
return role == "ADMIN"
|
||||
return is_system_admin(current_user)
|
||||
|
||||
|
||||
@router.post(
|
||||
@@ -51,7 +50,7 @@ async def create_category(
|
||||
action="CREATE_FAQ_CATEGORY",
|
||||
detail=f"FAQ 分类 {category.name} 已创建",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
return CategoryRead.model_validate(category)
|
||||
|
||||
@@ -113,7 +112,7 @@ async def update_category(
|
||||
action="UPDATE_FAQ_CATEGORY",
|
||||
detail=f"FAQ 分类 {category_id} 已更新",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
return CategoryRead.model_validate(updated)
|
||||
|
||||
@@ -151,5 +150,5 @@ async def delete_category(
|
||||
action="DELETE_FAQ_CATEGORY",
|
||||
detail=f"FAQ 分类 {category_id} 已删除",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ import uuid
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.deps import get_current_user, get_db_session, require_study_not_locked, require_api_permission
|
||||
from app.core.deps import get_current_user, get_db_session, get_operator_role_label, is_system_admin, require_study_not_locked, require_api_permission
|
||||
from app.core.project_permissions import role_has_api_permission
|
||||
from app.crud import audit as audit_crud
|
||||
from app.crud import faq_category as category_crud
|
||||
@@ -27,8 +27,7 @@ router = APIRouter()
|
||||
|
||||
|
||||
def _is_system_admin(current_user) -> bool:
|
||||
role = current_user.role.value if hasattr(current_user.role, "value") else current_user.role
|
||||
return role == "ADMIN"
|
||||
return is_system_admin(current_user)
|
||||
|
||||
|
||||
@router.post(
|
||||
@@ -75,7 +74,7 @@ async def create_faq(
|
||||
action="CREATE_FAQ_ITEM",
|
||||
detail="FAQ 已创建",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
return FaqRead.model_validate(item)
|
||||
|
||||
@@ -108,8 +107,7 @@ async def list_faqs(
|
||||
if not study_id:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="必须提供项目 ID")
|
||||
|
||||
role_value = current_user.role.value if hasattr(current_user.role, "value") else current_user.role
|
||||
if role_value != "ADMIN":
|
||||
if not is_system_admin(current_user):
|
||||
member = await member_crud.get_member(db, study_id, current_user.id)
|
||||
if not member or not member.is_active:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="不是项目成员")
|
||||
@@ -158,8 +156,7 @@ async def get_faq(
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="FAQ 不存在")
|
||||
if not item.study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="FAQ 不存在")
|
||||
role_value = current_user.role.value if hasattr(current_user.role, "value") else current_user.role
|
||||
if not item.is_active and role_value != "ADMIN":
|
||||
if not item.is_active and not is_system_admin(current_user):
|
||||
member = await member_crud.get_member(db, item.study_id, current_user.id)
|
||||
if not member or not member.is_active:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="不是项目成员")
|
||||
@@ -203,7 +200,7 @@ async def update_faq(
|
||||
action=action,
|
||||
detail=detail,
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
return FaqRead.model_validate(updated)
|
||||
|
||||
@@ -359,7 +356,7 @@ async def create_reply(
|
||||
action="CREATE_FAQ_REPLY",
|
||||
detail="FAQ 已回复",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
data = FaqReplyRead.model_validate(reply)
|
||||
if quote:
|
||||
@@ -404,7 +401,7 @@ async def delete_faq(
|
||||
action="DELETE_FAQ_ITEM",
|
||||
detail="FAQ 已删除",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
|
||||
|
||||
@@ -457,5 +454,5 @@ async def delete_reply(
|
||||
action="DELETE_FAQ_REPLY",
|
||||
detail="FAQ 回复已删除",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
|
||||
@@ -6,7 +6,7 @@ from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.core.deps import get_cra_site_scope, get_current_user, get_db_session, require_study_not_locked, require_api_permission
|
||||
from app.core.deps import get_cra_site_scope, get_current_user, get_db_session, get_operator_role_label, is_system_admin, require_study_not_locked, require_api_permission
|
||||
from app.core.project_permissions import role_has_api_permission
|
||||
from app.crud import audit as audit_crud
|
||||
from app.crud import contract_fee as contract_fee_crud
|
||||
@@ -22,7 +22,7 @@ from app.schemas.contract_fee_payment import (
|
||||
ContractFeePaymentUpdate,
|
||||
)
|
||||
from app.schemas.fee_common import FeeApiResponse
|
||||
from app.schemas.fee_attachment import FeeAttachmentRead
|
||||
from app.schemas.attachment import AttachmentRead
|
||||
from app.schemas.user import UserDisplay
|
||||
from app.models.attachment import Attachment
|
||||
|
||||
@@ -33,8 +33,7 @@ async def _ensure_project_access(db: AsyncSession, project_id: uuid.UUID, curren
|
||||
study = await study_crud.get(db, project_id)
|
||||
if not study:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="项目不存在")
|
||||
role_value = current_user.role.value if hasattr(current_user.role, "value") else current_user.role
|
||||
if role_value == "ADMIN":
|
||||
if is_system_admin(current_user):
|
||||
return None
|
||||
membership = await member_crud.get_member(db, project_id, current_user.id)
|
||||
if not membership or not membership.is_active:
|
||||
@@ -163,7 +162,7 @@ async def get_contract_fee(
|
||||
user_ids = {a.uploaded_by for a in attachments if a.uploaded_by}
|
||||
users_map = await user_crud.get_users_by_ids(db, user_ids)
|
||||
|
||||
attachments_map: dict[str, list[FeeAttachmentRead]] = {
|
||||
attachments_map: dict[str, list[AttachmentRead]] = {
|
||||
"contract": [],
|
||||
"voucher": [],
|
||||
"invoice": [],
|
||||
@@ -173,16 +172,11 @@ async def get_contract_fee(
|
||||
attachments_map.setdefault(key, [])
|
||||
user = users_map.get(attachment.uploaded_by)
|
||||
attachments_map[key].append(
|
||||
FeeAttachmentRead(
|
||||
AttachmentRead(
|
||||
id=attachment.id,
|
||||
entity_type="contract_fee",
|
||||
entity_id=attachment.entity_id,
|
||||
file_type=key,
|
||||
filename=attachment.filename,
|
||||
mime_type=attachment.content_type,
|
||||
size=attachment.file_size,
|
||||
storage_key=attachment.file_path,
|
||||
url=None,
|
||||
file_size=attachment.file_size,
|
||||
content_type=attachment.content_type,
|
||||
uploaded_by_id=attachment.uploaded_by,
|
||||
uploaded_by=UserDisplay.model_validate(user) if user else None,
|
||||
uploaded_at=attachment.uploaded_at,
|
||||
@@ -244,7 +238,7 @@ async def create_contract_fee(
|
||||
action="CREATE_CONTRACT_FEE",
|
||||
detail="合同费用已创建",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
return FeeApiResponse(data=ContractFeeRead.model_validate(contract))
|
||||
|
||||
@@ -274,7 +268,7 @@ async def update_contract_fee(
|
||||
action="UPDATE_CONTRACT_FEE",
|
||||
detail="合同费用已更新",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
return FeeApiResponse(data=ContractFeeRead.model_validate(contract))
|
||||
|
||||
@@ -303,7 +297,7 @@ async def delete_contract_fee(
|
||||
action="DELETE_CONTRACT_FEE",
|
||||
detail="合同费用已删除",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
|
||||
|
||||
@@ -333,7 +327,7 @@ async def create_contract_payment(
|
||||
action="CREATE_CONTRACT_FEE_PAYMENT",
|
||||
detail="合同费用分期已创建",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
return FeeApiResponse(data=ContractFeePaymentRead.model_validate(payment))
|
||||
|
||||
@@ -374,7 +368,7 @@ async def update_contract_payment(
|
||||
action="UPDATE_CONTRACT_FEE_PAYMENT",
|
||||
detail="合同费用分期已更新",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
return FeeApiResponse(data=ContractFeePaymentRead.model_validate(payment))
|
||||
|
||||
@@ -406,5 +400,5 @@ async def delete_contract_payment(
|
||||
action="DELETE_CONTRACT_FEE_PAYMENT",
|
||||
detail="合同费用分期已删除",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
|
||||
@@ -4,7 +4,7 @@ from datetime import date
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.deps import get_db_session, require_study_member
|
||||
from app.core.deps import get_db_session, require_api_permission
|
||||
from app.crud import finance as finance_crud
|
||||
from app.crud import study as study_crud
|
||||
from app.schemas.finance import FinanceSummaryRead
|
||||
@@ -12,11 +12,12 @@ from app.schemas.finance import FinanceSummaryRead
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/summary", response_model=FinanceSummaryRead, dependencies=[Depends(require_study_member())])
|
||||
@router.get("/summary", response_model=FinanceSummaryRead)
|
||||
async def finance_summary(
|
||||
study_id: uuid.UUID,
|
||||
date_from: date | None = None,
|
||||
date_to: date | None = None,
|
||||
_=Depends(require_api_permission("fees_contracts:read")),
|
||||
db: AsyncSession = Depends(get_db_session),
|
||||
) -> FinanceSummaryRead:
|
||||
# ensure study exists
|
||||
|
||||
@@ -3,7 +3,7 @@ import uuid
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.deps import get_current_user, get_db_session, require_study_not_locked, require_api_permission
|
||||
from app.core.deps import get_operator_role_label, get_current_user, get_db_session, require_study_not_locked, require_api_permission
|
||||
from app.crud import audit as audit_crud
|
||||
from app.crud import material_equipment as equipment_crud
|
||||
from app.crud import study as study_crud
|
||||
@@ -49,7 +49,7 @@ async def create_equipment(
|
||||
action="CREATE_MATERIAL_EQUIPMENT",
|
||||
detail=f"设备 {item.id} 已创建",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
return MaterialEquipmentRead.model_validate(item)
|
||||
|
||||
@@ -120,7 +120,7 @@ async def update_equipment(
|
||||
action="UPDATE_MATERIAL_EQUIPMENT",
|
||||
detail=f"设备 {equipment_id} 已更新",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
return MaterialEquipmentRead.model_validate(item)
|
||||
|
||||
@@ -149,5 +149,5 @@ async def delete_equipment(
|
||||
action="DELETE_MATERIAL_EQUIPMENT",
|
||||
detail=f"设备 {equipment_id} 已删除",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
|
||||
@@ -4,7 +4,7 @@ import json
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.deps import get_current_user, get_db_session, require_study_member, require_api_permission, require_study_not_locked
|
||||
from app.core.deps import get_current_user, get_db_session, is_system_admin, require_api_permission, require_study_not_locked
|
||||
from app.core.decorators import register_api_endpoint
|
||||
from app.crud import audit as audit_crud
|
||||
from app.crud import member as member_crud
|
||||
@@ -32,8 +32,11 @@ async def _ensure_study_exists(db: AsyncSession, study_id: uuid.UUID):
|
||||
return study
|
||||
|
||||
|
||||
def _role_value(user) -> str:
|
||||
return user.role.value if hasattr(user.role, "value") else str(user.role)
|
||||
async def _operator_project_role(db: AsyncSession, study_id: uuid.UUID, current_user) -> str:
|
||||
if is_system_admin(current_user):
|
||||
return "ADMIN"
|
||||
member = await member_crud.get_member(db, study_id, current_user.id)
|
||||
return member.role_in_study if member and member.is_active else ""
|
||||
|
||||
|
||||
def _role_rank(role: str | None) -> int:
|
||||
@@ -48,7 +51,7 @@ async def _ensure_member_mutation_allowed(
|
||||
target_member=None,
|
||||
target_role: str | None = None,
|
||||
) -> None:
|
||||
if _role_value(current_user) == "ADMIN":
|
||||
if is_system_admin(current_user):
|
||||
return
|
||||
|
||||
actor_member = await member_crud.get_member(db, study_id, current_user.id)
|
||||
@@ -60,7 +63,7 @@ async def _ensure_member_mutation_allowed(
|
||||
|
||||
if target_member:
|
||||
target_user = await user_crud.get_by_id(db, target_member.user_id)
|
||||
if _role_value(target_user) == "ADMIN":
|
||||
if is_system_admin(target_user):
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="不能修改系统管理员账号的项目权限")
|
||||
|
||||
if target_member and _role_rank(target_member.role_in_study) >= actor_rank:
|
||||
@@ -118,7 +121,7 @@ async def add_member(
|
||||
"after": {"is_active": updated.is_active, "role_in_study": updated.role_in_study},
|
||||
}, ensure_ascii=False),
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await _operator_project_role(db, study_id, current_user),
|
||||
)
|
||||
return updated
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="成员已存在")
|
||||
@@ -131,7 +134,7 @@ async def add_member(
|
||||
action="PROJECT_MEMBER_ADDED",
|
||||
detail=json.dumps({"targetName": str(member.user_id), "after": {"role_in_study": member.role_in_study, "is_active": member.is_active}}, ensure_ascii=False),
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await _operator_project_role(db, study_id, current_user),
|
||||
)
|
||||
return member
|
||||
|
||||
@@ -246,7 +249,7 @@ async def update_member(
|
||||
action="PROJECT_MEMBER_UPDATED",
|
||||
detail=json.dumps({"targetName": str(updated.user_id), "before": before_data, "after": after_data}, ensure_ascii=False),
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await _operator_project_role(db, study_id, current_user),
|
||||
)
|
||||
return updated
|
||||
|
||||
@@ -274,7 +277,7 @@ async def remove_member(
|
||||
if not member or member.study_id != study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="成员不存在")
|
||||
target_user = await user_crud.get_by_id(db, member.user_id)
|
||||
if target_user and _role_value(target_user) == "ADMIN":
|
||||
if target_user and is_system_admin(target_user):
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="系统管理员不可从项目中移除")
|
||||
await _ensure_member_mutation_allowed(db, study_id, current_user, target_member=member)
|
||||
before_data = {"role_in_study": member.role_in_study, "is_active": member.is_active}
|
||||
@@ -287,6 +290,6 @@ async def remove_member(
|
||||
action="PROJECT_MEMBER_REMOVED",
|
||||
detail=json.dumps({"targetName": str(removed.user_id), "before": before_data, "after": {"is_active": removed.is_active}}, ensure_ascii=False),
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await _operator_project_role(db, study_id, current_user),
|
||||
)
|
||||
return removed
|
||||
|
||||
@@ -11,7 +11,7 @@ from openpyxl import Workbook, load_workbook
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.deps import get_current_user, get_db_session, require_study_not_locked, require_api_permission
|
||||
from app.core.deps import get_operator_role_label, get_current_user, get_db_session, require_study_not_locked, require_api_permission
|
||||
from app.crud import audit as audit_crud
|
||||
from app.crud import monitoring_visit_issue as issue_crud
|
||||
from app.crud import site as site_crud
|
||||
@@ -413,7 +413,7 @@ async def create_monitoring_visit_issue(
|
||||
action="CREATE_MONITORING_VISIT_ISSUE",
|
||||
detail=f"监查访视问题 {item.issue_no} 已创建",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
return _to_read(item)
|
||||
|
||||
@@ -617,7 +617,7 @@ async def update_monitoring_visit_issue(
|
||||
action="UPDATE_MONITORING_VISIT_ISSUE",
|
||||
detail=json.dumps(detail_payload, ensure_ascii=False),
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
return _to_read(updated)
|
||||
|
||||
@@ -648,7 +648,7 @@ async def delete_monitoring_visit_issue(
|
||||
action="DELETE_MONITORING_VISIT_ISSUE",
|
||||
detail=f"监查访视问题 {issue_no} 已删除",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
|
||||
|
||||
@@ -765,7 +765,7 @@ async def import_monitoring_visit_issues(
|
||||
action="IMPORT_MONITORING_VISIT_ISSUE",
|
||||
detail=f"导入监查访视问题:新增 {created_count} 条,更新 {updated_count} 条,跳过 {len(skipped_rows)} 条",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
|
||||
return MonitoringVisitIssueImportSummary(
|
||||
|
||||
@@ -14,7 +14,7 @@ from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from sqlalchemy import func, select, desc
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.deps import get_current_user, get_db_session, list_active_pm_study_ids
|
||||
from app.core.deps import get_current_user, get_db_session, is_system_admin, list_active_pm_study_ids
|
||||
from app.core.permission_monitor import get_permission_monitor
|
||||
from app.models.permission_access_log import PermissionAccessLog
|
||||
from app.models.permission_metric_snapshot import PermissionMetricSnapshot
|
||||
@@ -36,14 +36,8 @@ class MonitoringScope:
|
||||
return study_id is not None and study_id in self.study_ids
|
||||
|
||||
|
||||
def _role_value(user) -> str:
|
||||
if not hasattr(user, "role"):
|
||||
return "ADMIN"
|
||||
return user.role.value if hasattr(user.role, "value") else str(user.role)
|
||||
|
||||
|
||||
async def resolve_monitoring_scope(db: AsyncSession, current_user) -> MonitoringScope:
|
||||
if _role_value(current_user) == "ADMIN":
|
||||
if is_system_admin(current_user):
|
||||
return MonitoringScope(is_admin=True, study_ids=set())
|
||||
return MonitoringScope(
|
||||
is_admin=False,
|
||||
|
||||
@@ -11,7 +11,6 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.deps import get_current_user, get_db_session, require_roles, require_study_roles
|
||||
from app.models.permission_template import TemplateType
|
||||
from app.models.user import UserRole
|
||||
from app.schemas.permission_template import (
|
||||
ApplyTemplateRequest,
|
||||
ApplyTemplateResponse,
|
||||
@@ -52,7 +51,7 @@ async def list_templates(
|
||||
async def create_template(
|
||||
payload: PermissionTemplateCreate,
|
||||
db: Annotated[AsyncSession, Depends(get_db_session)] = None,
|
||||
current_user=Depends(require_roles([UserRole.ADMIN.value])),
|
||||
current_user=Depends(require_roles(["ADMIN"])),
|
||||
) -> PermissionTemplateRead:
|
||||
"""创建权限模板"""
|
||||
try:
|
||||
@@ -79,7 +78,7 @@ async def update_template(
|
||||
template_id: uuid.UUID,
|
||||
payload: PermissionTemplateUpdate,
|
||||
db: Annotated[AsyncSession, Depends(get_db_session)] = None,
|
||||
current_user=Depends(require_roles([UserRole.ADMIN.value])),
|
||||
current_user=Depends(require_roles(["ADMIN"])),
|
||||
) -> PermissionTemplateRead:
|
||||
"""更新权限模板"""
|
||||
try:
|
||||
@@ -93,7 +92,7 @@ async def update_template(
|
||||
async def delete_template(
|
||||
template_id: uuid.UUID,
|
||||
db: Annotated[AsyncSession, Depends(get_db_session)] = None,
|
||||
current_user=Depends(require_roles([UserRole.ADMIN.value])),
|
||||
current_user=Depends(require_roles(["ADMIN"])),
|
||||
):
|
||||
"""删除权限模板"""
|
||||
try:
|
||||
|
||||
@@ -3,7 +3,7 @@ import uuid
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.deps import get_current_user, get_db_session, require_study_not_locked, require_api_permission
|
||||
from app.core.deps import get_operator_role_label, get_current_user, get_db_session, require_study_not_locked, require_api_permission
|
||||
from app.crud import audit as audit_crud
|
||||
from app.crud import precaution as precaution_crud
|
||||
from app.crud import site as site_crud
|
||||
@@ -51,7 +51,7 @@ async def create_precaution(
|
||||
action="CREATE_PRECAUTION",
|
||||
detail=f"注意事项 {precaution.title} 已创建",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
return PrecautionRead.model_validate(precaution)
|
||||
|
||||
@@ -117,7 +117,7 @@ async def update_precaution(
|
||||
action="UPDATE_PRECAUTION",
|
||||
detail=f"注意事项 {precaution_id} 已更新",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
return PrecautionRead.model_validate(precaution)
|
||||
|
||||
@@ -147,5 +147,5 @@ async def delete_precaution(
|
||||
action="DELETE_PRECAUTION",
|
||||
detail=f"注意事项 {precaution_id} 已删除",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ import uuid
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.deps import get_current_user, get_db_session, require_study_not_locked, require_api_permission
|
||||
from app.core.deps import get_operator_role_label, get_current_user, get_db_session, require_study_not_locked, require_api_permission
|
||||
from app.crud import audit as audit_crud
|
||||
from app.crud import project_milestone as milestone_crud
|
||||
from app.crud import study as study_crud
|
||||
@@ -69,6 +69,6 @@ async def update_project_milestone(
|
||||
action="UPDATE_PROJECT_MILESTONE",
|
||||
detail=f"项目里程碑 {milestone_id} 已更新",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
return ProjectMilestoneRead.model_validate(item)
|
||||
|
||||
@@ -4,7 +4,7 @@ import json
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.deps import get_cra_site_scope, get_current_user, get_db_session, require_study_member, require_api_permission, require_study_not_locked
|
||||
from app.core.deps import get_operator_role_label, get_cra_site_scope, get_current_user, get_db_session, require_study_member, require_api_permission, require_study_not_locked
|
||||
from app.core.decorators import register_api_endpoint
|
||||
from app.crud import audit as audit_crud
|
||||
from app.crud import site as site_crud
|
||||
@@ -64,7 +64,7 @@ async def create_site(
|
||||
action="SITE_CREATED",
|
||||
detail=json.dumps({"targetName": site.name, "after": {"name": site.name, "is_active": site.is_active}}, ensure_ascii=False),
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
return site
|
||||
|
||||
@@ -152,7 +152,7 @@ async def update_site(
|
||||
action=action,
|
||||
detail=json.dumps({"targetName": updated.name, "before": before_data, "after": after_data}, ensure_ascii=False),
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
return updated
|
||||
|
||||
@@ -196,6 +196,6 @@ async def delete_site(
|
||||
action="SITE_DELETED",
|
||||
detail=json.dumps({"targetName": site.name, "before": before_data, "after": {"deleted": True}}, ensure_ascii=False),
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
return None
|
||||
|
||||
@@ -3,7 +3,7 @@ import uuid
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.deps import get_cra_site_scope, get_current_user, get_db_session, require_api_permission
|
||||
from app.core.deps import get_operator_role_label, get_cra_site_scope, get_current_user, get_db_session, require_api_permission
|
||||
from app.crud import audit as audit_crud
|
||||
from app.crud import site as site_crud
|
||||
from app.crud import startup as startup_crud
|
||||
@@ -75,7 +75,7 @@ async def create_feasibility(
|
||||
action="CREATE_STARTUP_FEASIBILITY",
|
||||
detail="立项记录已创建",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
return StartupFeasibilityRead.model_validate(record)
|
||||
|
||||
@@ -149,7 +149,7 @@ async def update_feasibility(
|
||||
action="UPDATE_STARTUP_FEASIBILITY",
|
||||
detail="立项记录已更新",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
return StartupFeasibilityRead.model_validate(record)
|
||||
|
||||
@@ -182,7 +182,7 @@ async def delete_feasibility(
|
||||
action="DELETE_STARTUP_FEASIBILITY",
|
||||
detail="立项记录已删除",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
|
||||
|
||||
@@ -212,7 +212,7 @@ async def create_ethics(
|
||||
action="CREATE_STARTUP_ETHICS",
|
||||
detail="伦理记录已创建",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
return StartupEthicsRead.model_validate(record)
|
||||
|
||||
@@ -286,7 +286,7 @@ async def update_ethics(
|
||||
action="UPDATE_STARTUP_ETHICS",
|
||||
detail="伦理记录已更新",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
return StartupEthicsRead.model_validate(record)
|
||||
|
||||
@@ -319,7 +319,7 @@ async def delete_ethics(
|
||||
action="DELETE_STARTUP_ETHICS",
|
||||
detail="伦理记录已删除",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
|
||||
|
||||
@@ -349,7 +349,7 @@ async def create_kickoff(
|
||||
action="CREATE_KICKOFF_MEETING",
|
||||
detail="启动会记录已创建",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
return KickoffMeetingRead.model_validate(meeting)
|
||||
|
||||
@@ -423,7 +423,7 @@ async def update_kickoff(
|
||||
action="UPDATE_KICKOFF_MEETING",
|
||||
detail="启动会记录已更新",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
return KickoffMeetingRead.model_validate(meeting)
|
||||
|
||||
@@ -454,7 +454,7 @@ async def create_training_authorization(
|
||||
action="CREATE_TRAINING_AUTH",
|
||||
detail="培训授权人员已创建",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
return TrainingAuthorizationRead.model_validate(record)
|
||||
|
||||
@@ -528,7 +528,7 @@ async def update_training_authorization(
|
||||
action="UPDATE_TRAINING_AUTH",
|
||||
detail="培训授权人员已更新",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
return TrainingAuthorizationRead.model_validate(record)
|
||||
|
||||
@@ -561,5 +561,5 @@ async def delete_training_authorization(
|
||||
action="DELETE_TRAINING_AUTH",
|
||||
detail="培训授权人员已删除",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
|
||||
@@ -10,6 +10,8 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.core.deps import (
|
||||
get_current_user,
|
||||
get_db_session,
|
||||
get_operator_role_label,
|
||||
is_system_admin,
|
||||
require_api_permission,
|
||||
require_roles,
|
||||
require_study_member,
|
||||
@@ -50,9 +52,6 @@ def _study_read_with_role(study, role_in_study: str | None = None) -> StudyRead:
|
||||
return data
|
||||
|
||||
|
||||
def _role_value(user) -> str:
|
||||
return user.role.value if hasattr(user.role, "value") else str(user.role)
|
||||
|
||||
def _raise_validation_error(errors: list[dict[str, str]]) -> None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
@@ -769,7 +768,7 @@ async def create_study(
|
||||
action="CREATE_SETUP_CONFIG",
|
||||
detail="初始化立项配置(创建项目时回填基础信息)",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
|
||||
return study
|
||||
@@ -782,7 +781,7 @@ async def list_studies(
|
||||
db: AsyncSession = Depends(get_db_session),
|
||||
current_user=Depends(get_current_user),
|
||||
) -> PaginatedResponse[StudyRead]:
|
||||
if _role_value(current_user) == "ADMIN":
|
||||
if is_system_admin(current_user):
|
||||
studies = await study_crud.list_studies(db, skip=skip, limit=limit)
|
||||
total = await study_crud.list_studies(db, skip=0, limit=10_000_000)
|
||||
items = [_study_read_with_role(study) for study in studies]
|
||||
@@ -805,7 +804,7 @@ async def get_study(
|
||||
study = await study_crud.get(db, study_id)
|
||||
if not study:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="项目不存在")
|
||||
if _role_value(current_user) == "ADMIN":
|
||||
if is_system_admin(current_user):
|
||||
return _study_read_with_role(study)
|
||||
member = await member_crud.get_member(db, study_id, current_user.id)
|
||||
return _study_read_with_role(study, member.role_in_study if member else None)
|
||||
@@ -893,7 +892,7 @@ async def lock_study(
|
||||
action="LOCK_STUDY",
|
||||
detail=f"项目已锁定:{study.name} ({study.code})",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
|
||||
return locked_study
|
||||
@@ -925,7 +924,7 @@ async def unlock_study(
|
||||
action="UNLOCK_STUDY",
|
||||
detail=f"项目已解锁:{study.name} ({study.code})",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
|
||||
return unlocked_study
|
||||
@@ -964,7 +963,7 @@ async def get_study_setup_config(
|
||||
action="CREATE_SETUP_CONFIG",
|
||||
detail="初始化立项配置",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
|
||||
saved_by_name = None
|
||||
@@ -1047,7 +1046,7 @@ async def upsert_study_setup_config(
|
||||
action="UPDATE_SETUP_CONFIG",
|
||||
detail=_top_level_diff_summary(old_config, record.config),
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
|
||||
saved_by_name = current_user.full_name or current_user.username or current_user.email
|
||||
@@ -1159,7 +1158,7 @@ async def publish_study_setup_config(
|
||||
skipped_items=[{"site_id": item.site_id, "reason": item.reason} for item in projection.skipped_items],
|
||||
),
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
auto_commit=False,
|
||||
)
|
||||
published.config = study_setup_config_crud.empty_draft_payload()
|
||||
@@ -1259,7 +1258,7 @@ async def rollback_study_setup_config(
|
||||
action="ROLLBACK_SETUP_CONFIG",
|
||||
detail=f"立项配置已回滚并替换当前发布为 {target_label},草稿分支已切换到对应分支基线",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
operator_name = current_user.full_name or current_user.username or current_user.email
|
||||
published_by_name = None
|
||||
@@ -1318,7 +1317,7 @@ async def checkout_study_setup_config_branch_draft(
|
||||
action="CHECKOUT_SETUP_CONFIG_BRANCH_DRAFT",
|
||||
detail=f"立项配置草稿已切换到 {target_label} 对应分支",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
operator_name = current_user.full_name or current_user.username or current_user.email
|
||||
published_by_name = None
|
||||
@@ -1370,7 +1369,7 @@ async def clear_study_setup_config_draft(
|
||||
action="CLEAR_SETUP_CONFIG_DRAFT",
|
||||
detail="立项配置草稿已清空",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
operator_name = current_user.full_name or current_user.username or current_user.email
|
||||
published_by_name = None
|
||||
@@ -1424,7 +1423,7 @@ async def refill_study_setup_config_draft(
|
||||
action="REFILL_SETUP_CONFIG_DRAFT",
|
||||
detail="立项配置草稿已从当前发布版本一键回填",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
operator_name = current_user.full_name or current_user.username or current_user.email
|
||||
published_by_name = None
|
||||
@@ -1511,7 +1510,7 @@ async def merge_study_setup_config_to_main(
|
||||
action="MERGE_SETUP_CONFIG_TO_MAIN",
|
||||
detail=f"已将发布版本 {source_label} 合并到主分支并生成新主版本",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
auto_commit=False,
|
||||
)
|
||||
merged.config = study_setup_config_crud.empty_draft_payload()
|
||||
@@ -1578,5 +1577,5 @@ async def delete_study_setup_config_version(
|
||||
action="DELETE_SETUP_CONFIG_VERSION",
|
||||
detail=f"删除发布版本快照 v{target_version}",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ import uuid
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.deps import get_current_user, get_db_session, require_study_not_locked, require_api_permission
|
||||
from app.core.deps import get_operator_role_label, get_current_user, get_db_session, require_study_not_locked, require_api_permission
|
||||
from app.crud import audit as audit_crud
|
||||
from app.crud import site as site_crud
|
||||
from app.crud import subject as subject_crud
|
||||
@@ -58,7 +58,7 @@ async def create_history(
|
||||
action="CREATE_SUBJECT_HISTORY",
|
||||
detail="病史记录已创建",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
return SubjectHistoryRead.model_validate(history)
|
||||
|
||||
@@ -133,7 +133,7 @@ async def update_history(
|
||||
action="UPDATE_SUBJECT_HISTORY",
|
||||
detail="病史记录已更新",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
return SubjectHistoryRead.model_validate(history)
|
||||
|
||||
@@ -166,5 +166,5 @@ async def delete_history(
|
||||
action="DELETE_SUBJECT_HISTORY",
|
||||
detail="病史记录已删除",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
|
||||
@@ -6,6 +6,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.core.deps import (
|
||||
get_current_user,
|
||||
get_db_session,
|
||||
get_operator_role_label,
|
||||
require_study_not_locked,
|
||||
require_api_permission,
|
||||
)
|
||||
@@ -120,7 +121,7 @@ async def create_subject_pd(
|
||||
action="CREATE_SUBJECT_PD",
|
||||
detail=f"PD记录 {item.pd_no} 已创建",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
return SubjectPdRead.model_validate(item)
|
||||
|
||||
@@ -167,7 +168,7 @@ async def update_subject_pd(
|
||||
action="UPDATE_SUBJECT_PD",
|
||||
detail=f"PD记录 {updated.pd_no} 已更新",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
return SubjectPdRead.model_validate(updated)
|
||||
|
||||
@@ -201,5 +202,5 @@ async def delete_subject_pd(
|
||||
action="DELETE_SUBJECT_PD",
|
||||
detail=f"PD记录 {item.pd_no} 已删除",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
|
||||
@@ -4,7 +4,7 @@ from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.deps import get_cra_site_scope, get_current_user, get_db_session, require_study_not_locked, require_api_permission
|
||||
from app.core.deps import get_operator_role_label, get_cra_site_scope, get_current_user, get_db_session, require_study_not_locked, require_api_permission
|
||||
from app.crud import audit as audit_crud
|
||||
from app.crud import site as site_crud
|
||||
from app.crud import subject as subject_crud
|
||||
@@ -58,7 +58,7 @@ async def create_subject(
|
||||
action="CREATE_SUBJECT",
|
||||
detail=f"参与者 {subject.subject_no} 已创建",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
return subject
|
||||
|
||||
@@ -188,7 +188,7 @@ async def update_subject(
|
||||
action="SUBJECT_STATUS_CHANGE" if detail else "UPDATE_SUBJECT",
|
||||
detail=detail or "参与者已更新",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
return updated
|
||||
|
||||
@@ -220,5 +220,5 @@ async def delete_subject(
|
||||
action="DELETE_SUBJECT",
|
||||
detail=f"参与者 {subject_id} 已删除",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ import uuid
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.deps import get_db_session, require_roles
|
||||
from app.core.deps import get_db_session, is_system_admin, require_roles
|
||||
from app.schemas.common import PaginatedResponse
|
||||
from app.crud import user as user_crud
|
||||
from app.crud import member as member_crud
|
||||
@@ -59,7 +59,7 @@ async def update_user(
|
||||
requested_status = "ACTIVE" if user_in.is_active else "DISABLED"
|
||||
if requested_status is not None and requested_status != "ACTIVE":
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="系统管理员不允许停用")
|
||||
if db_user.role.value == "ADMIN":
|
||||
if is_system_admin(db_user):
|
||||
requested_status = user_in.status
|
||||
if user_in.is_active is not None:
|
||||
requested_status = "ACTIVE" if user_in.is_active else "DISABLED"
|
||||
@@ -85,7 +85,7 @@ async def delete_user(
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="系统管理员账号不允许删除")
|
||||
if db_user.id == current_user.id:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="不允许删除自己")
|
||||
if db_user.role.value == "ADMIN" and db_user.status.value == "ACTIVE":
|
||||
if is_system_admin(db_user) and db_user.status.value == "ACTIVE":
|
||||
active_admins = await user_crud.count_active_admins(db)
|
||||
if active_admins <= 1:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="至少保留一个管理员账号")
|
||||
|
||||
@@ -3,7 +3,7 @@ import uuid
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.deps import get_current_user, get_db_session, require_study_not_locked, require_api_permission
|
||||
from app.core.deps import get_operator_role_label, get_current_user, get_db_session, require_study_not_locked, require_api_permission
|
||||
from app.crud import audit as audit_crud
|
||||
from app.crud import site as site_crud
|
||||
from app.crud import subject as subject_crud
|
||||
@@ -107,7 +107,7 @@ async def create_visit(
|
||||
action="CREATE_VISIT",
|
||||
detail=f"访视 {visit.visit_code} 已创建",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
return visit
|
||||
|
||||
@@ -160,7 +160,7 @@ async def create_early_termination(
|
||||
action="CREATE_EARLY_TERMINATION",
|
||||
detail=f"参与者 {subject.subject_no} 已提前终止",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
return visit
|
||||
|
||||
@@ -204,7 +204,7 @@ async def update_visit(
|
||||
action="VISIT_STATUS_CHANGE" if detail else "UPDATE_VISIT",
|
||||
detail=detail or "访视已更新",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
return updated
|
||||
|
||||
@@ -236,5 +236,5 @@ async def delete_visit(
|
||||
action="DELETE_VISIT",
|
||||
detail=f"访视 {visit_id} 已删除",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
operator_role=await get_operator_role_label(db, study_id, current_user),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user