权限管理:新增医学审核项目角色
新增 MEDICAL_REVIEW 项目角色,界面显示为医学审核,并通过 Alembic 迁移扩展 user_role 枚举。 为医学审核配置独立默认权限:项目总览、参与者、风险问题、监查稽查、FAQ、共享库按医学审核职责开放,不再复制 PV 默认权限。 同步前后端类型、项目成员角色下拉、权限矩阵展示、文件分发角色兜底和用户协议文案,并补充权限矩阵回归测试。
This commit is contained in:
@@ -7,7 +7,9 @@ const items = [
|
||||
{ value: "PM", label: TEXT.enums.userRole.PM, order: 2 },
|
||||
{ value: "CRA", label: TEXT.enums.userRole.CRA, order: 3 },
|
||||
{ value: "PV", label: TEXT.enums.userRole.PV, order: 4 },
|
||||
{ value: "IMP", label: TEXT.enums.userRole.IMP, order: 5 },
|
||||
{ value: "MEDICAL_REVIEW", label: TEXT.enums.userRole.MEDICAL_REVIEW, order: 5 },
|
||||
{ value: "IMP", label: TEXT.enums.userRole.IMP, order: 6 },
|
||||
{ value: "QA", label: TEXT.enums.userRole.QA, order: 7 },
|
||||
];
|
||||
|
||||
export const roleDict: Dict = createDict(items);
|
||||
|
||||
@@ -989,6 +989,7 @@ export const TEXT = {
|
||||
PM: "项目负责人",
|
||||
CRA: "CRA",
|
||||
PV: "PV",
|
||||
MEDICAL_REVIEW: "医学审核",
|
||||
IMP: "药品管理员",
|
||||
QA: "QA",
|
||||
},
|
||||
|
||||
@@ -32,7 +32,7 @@ export interface LoginKeyResponse {
|
||||
expires_at: string;
|
||||
}
|
||||
|
||||
export type UserRole = "PM" | "CRA" | "PV" | "IMP" | "QA" | "ADMIN";
|
||||
export type UserRole = "PM" | "CRA" | "PV" | "MEDICAL_REVIEW" | "IMP" | "QA" | "ADMIN";
|
||||
export type UserStatus = "PENDING" | "ACTIVE" | "REJECTED" | "DISABLED";
|
||||
|
||||
export interface UserInfo {
|
||||
|
||||
@@ -167,7 +167,7 @@ const protocolSections = [
|
||||
title: "1. 服务条款",
|
||||
paragraphs: [
|
||||
"本系统为 CTMS 临床试验管理系统,用于支持临床试验项目的账号治理、项目与中心管理、项目里程碑、立项与伦理、启动会与培训授权、受试者管理、访视、AE/SAE、PD、风险问题、费用、药品物资、文件版本、知识库与审计日志等业务协同。",
|
||||
"用户应在所属机构授权范围内使用本系统。系统中的流程提醒、状态跟踪和数据汇总用于辅助项目管理,不替代申办方、研究中心、CRA、PM、PV、IMP 或管理员按照法规、方案、SOP 和合同约定应履行的专业判断与职责。",
|
||||
"用户应在所属机构授权范围内使用本系统。系统中的流程提醒、状态跟踪和数据汇总用于辅助项目管理,不替代申办方、研究中心、CRA、PM、PV、医学审核、IMP 或管理员按照法规、方案、SOP 和合同约定应履行的专业判断与职责。",
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -187,7 +187,7 @@ const protocolSections = [
|
||||
{
|
||||
title: "4. 权限与审计",
|
||||
paragraphs: [
|
||||
"系统按照管理员、PM、CRA、PV、IMP 及普通成员等角色提供不同操作能力。同一账号在不同项目中的角色可能不同,用户仅可在被授权项目和中心范围内执行查看、新增、编辑、审批、关闭、导出等操作。",
|
||||
"系统按照管理员、PM、CRA、PV、医学审核、IMP 及普通成员等角色提供不同操作能力。同一账号在不同项目中的角色可能不同,用户仅可在被授权项目和中心范围内执行查看、新增、编辑、审批、关闭、导出等操作。",
|
||||
"系统会记录关键业务操作和管理操作,包括但不限于账号、项目、成员、中心、AE、费用、文件、审计导出等事件。用户理解并同意这些日志可用于安全追踪、合规核查、问题复盘和内部管理。",
|
||||
],
|
||||
},
|
||||
|
||||
@@ -44,7 +44,9 @@
|
||||
<el-option :label="TEXT.enums.userRole.PM" value="PM" />
|
||||
<el-option :label="TEXT.enums.userRole.CRA" value="CRA" />
|
||||
<el-option :label="TEXT.enums.userRole.PV" value="PV" />
|
||||
<el-option :label="TEXT.enums.userRole.MEDICAL_REVIEW" value="MEDICAL_REVIEW" />
|
||||
<el-option :label="TEXT.enums.userRole.IMP" value="IMP" />
|
||||
<el-option :label="TEXT.enums.userRole.QA" value="QA" />
|
||||
<el-option :label="TEXT.enums.userRole.ADMIN" value="ADMIN" />
|
||||
</el-select>
|
||||
<span v-if="scope.row.effectiveStatus === 'DISABLED_GLOBAL'" class="hint">{{ TEXT.modules.adminProjectMembers.disabledGlobalDesc }}</span>
|
||||
@@ -82,7 +84,9 @@
|
||||
<el-option :label="TEXT.enums.userRole.PM" value="PM" />
|
||||
<el-option :label="TEXT.enums.userRole.CRA" value="CRA" />
|
||||
<el-option :label="TEXT.enums.userRole.PV" value="PV" />
|
||||
<el-option :label="TEXT.enums.userRole.MEDICAL_REVIEW" value="MEDICAL_REVIEW" />
|
||||
<el-option :label="TEXT.enums.userRole.IMP" value="IMP" />
|
||||
<el-option :label="TEXT.enums.userRole.QA" value="QA" />
|
||||
<el-option :label="TEXT.enums.userRole.ADMIN" value="ADMIN" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
@@ -10,6 +10,7 @@ describe("project permissions matrix", () => {
|
||||
|
||||
expect(source).toContain('new Set(["project_members", "sites", "audit_export"])');
|
||||
expect(source).toContain('role === "PM" || role === "ADMIN"');
|
||||
expect(source).toContain('"MEDICAL_REVIEW"');
|
||||
expect(source).toContain("!canConfigureAdminModule(role, mod.key)");
|
||||
expect(source).toContain("!canConfigureAdminModule(role, moduleKey)");
|
||||
});
|
||||
|
||||
@@ -86,7 +86,7 @@ import { TEXT } from "../../locales";
|
||||
|
||||
const route = useRoute();
|
||||
const projectId = computed(() => route.params.projectId as string);
|
||||
const roleOrder: UserRole[] = ["ADMIN", "PM", "CRA", "PV", "IMP", "QA"];
|
||||
const roleOrder: UserRole[] = ["ADMIN", "PM", "CRA", "PV", "MEDICAL_REVIEW", "IMP", "QA"];
|
||||
|
||||
const loading = ref(false);
|
||||
const saving = ref(false);
|
||||
@@ -388,6 +388,11 @@ onMounted(load);
|
||||
background: #ffe4e6;
|
||||
}
|
||||
|
||||
.role-badge--medical_review {
|
||||
color: #0f766e;
|
||||
background: #ccfbf1;
|
||||
}
|
||||
|
||||
.role-badge--imp {
|
||||
color: #b45309;
|
||||
background: #fef3c7;
|
||||
|
||||
@@ -360,7 +360,7 @@ const uploadRules: FormRules = {
|
||||
};
|
||||
|
||||
const roleOptions = computed(() => {
|
||||
const fallbackRoles = ["CRA", "PM", "IMP", "PV"];
|
||||
const fallbackRoles = ["CRA", "PM", "IMP", "PV", "MEDICAL_REVIEW"];
|
||||
const roles = new Set(members.value.map((m) => m.role_in_study).filter(Boolean));
|
||||
const values = roles.size ? Array.from(roles) : fallbackRoles;
|
||||
return values.map((value) => ({
|
||||
|
||||
Reference in New Issue
Block a user