权限管理:新增医学审核项目角色

新增 MEDICAL_REVIEW 项目角色,界面显示为医学审核,并通过 Alembic 迁移扩展 user_role 枚举。

为医学审核配置独立默认权限:项目总览、参与者、风险问题、监查稽查、FAQ、共享库按医学审核职责开放,不再复制 PV 默认权限。

同步前后端类型、项目成员角色下拉、权限矩阵展示、文件分发角色兜底和用户协议文案,并补充权限矩阵回归测试。
This commit is contained in:
Cheng Zhou
2026-05-13 09:25:37 +08:00
parent 1049599147
commit 89cecce6b5
15 changed files with 90 additions and 16 deletions
+19 -1
View File
@@ -7,7 +7,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.models.study_role_permission import StudyRolePermission
PROJECT_PERMISSION_ROLES = ("ADMIN", "PM", "CRA", "PV", "IMP", "QA")
PROJECT_PERMISSION_ROLES = ("ADMIN", "PM", "CRA", "PV", "MEDICAL_REVIEW", "IMP", "QA")
PROJECT_PERMISSION_MODULES = (
{"key": "project_members", "label": "项目成员", "description": "维护项目账号、成员角色与启停状态"},
@@ -93,6 +93,24 @@ DEFAULT_PROJECT_ROLE_PERMISSIONS: dict[str, dict[str, dict[str, bool]]] = {
"sites": {"read": False, "write": False},
"audit_export": {"read": False, "write": False},
},
"MEDICAL_REVIEW": {
"project_overview": {"read": True, "write": False},
"project_milestones": {"read": False, "write": False},
"fees": {"read": False, "write": False},
"materials": {"read": False, "write": False},
"file_versions": {"read": False, "write": False},
"startup_ethics": {"read": False, "write": False},
"startup_auth": {"read": False, "write": False},
"subjects": {"read": True, "write": False},
"risk_issues": {"read": True, "write": True},
"monitoring_audit": {"read": True, "write": False},
"etmf": {"read": False, "write": False},
"faq": {"read": True, "write": True},
"shared_library": {"read": True, "write": True},
"project_members": {"read": False, "write": False},
"sites": {"read": False, "write": False},
"audit_export": {"read": False, "write": False},
},
"IMP": {
"project_overview": {"read": True, "write": False},
"project_milestones": {"read": True, "write": False},
+1
View File
@@ -14,6 +14,7 @@ class UserRole(str, enum.Enum):
PM = "PM"
CRA = "CRA"
PV = "PV"
MEDICAL_REVIEW = "MEDICAL_REVIEW"
IMP = "IMP"
QA = "QA"
+1 -1
View File
@@ -5,7 +5,7 @@ from typing import Literal, Optional
from pydantic import BaseModel, ConfigDict, Field
from app.schemas.user import UserDisplay
StudyRole = Literal["PM", "CRA", "PV", "IMP", "QA", "ADMIN"]
StudyRole = Literal["PM", "CRA", "PV", "MEDICAL_REVIEW", "IMP", "QA", "ADMIN"]
class StudyMemberCreate(BaseModel):
+1 -1
View File
@@ -5,7 +5,7 @@ from typing import Literal
from pydantic import BaseModel, Field
ProjectPermissionRole = Literal["ADMIN", "PM", "CRA", "PV", "IMP", "QA"]
ProjectPermissionRole = Literal["ADMIN", "PM", "CRA", "PV", "MEDICAL_REVIEW", "IMP", "QA"]
ProjectPermissionAction = Literal["read", "write"]
+2 -2
View File
@@ -5,8 +5,8 @@ from typing import Literal, Optional
from pydantic import BaseModel, ConfigDict, EmailStr, Field, field_validator
UserRole = Literal["PM", "CRA", "PV", "IMP", "QA", "ADMIN"]
RegisterRole = Literal["PM", "CRA", "PV", "IMP"]
UserRole = Literal["PM", "CRA", "PV", "MEDICAL_REVIEW", "IMP", "QA", "ADMIN"]
RegisterRole = Literal["PM", "CRA", "PV", "MEDICAL_REVIEW", "IMP"]
UserStatus = Literal["PENDING", "ACTIVE", "REJECTED", "DISABLED"]
PASSWORD_REGEX = re.compile(r"^(?=.*[A-Za-z])(?=.*\d).{8,}$")