中文:收口权限与中心/立项配置改造
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
|
||||
const readPermissionSource = () => readFileSync(resolve(__dirname, "./permission.ts"), "utf8");
|
||||
|
||||
describe("permission project role model", () => {
|
||||
it("does not fall back to non-admin global roles for project permissions", () => {
|
||||
const source = readPermissionSource();
|
||||
|
||||
expect(source).toContain("isSystemAdmin");
|
||||
expect(source).toContain("getProjectRole");
|
||||
expect(source).toContain("study.currentStudyRole");
|
||||
expect(source).not.toContain("role_in_study || auth.user?.role");
|
||||
expect(source).not.toContain("role_in_study || userRole.value");
|
||||
});
|
||||
|
||||
it("allows project administrators and PMs to manage project configuration", () => {
|
||||
const source = readPermissionSource();
|
||||
|
||||
expect(source).toContain('"project.members.manage": ["ADMIN", "PM"]');
|
||||
expect(source).toContain('"site.manage": ["ADMIN", "PM"]');
|
||||
expect(source).toContain('"site.cra.bind": ["ADMIN", "PM"]');
|
||||
});
|
||||
});
|
||||
@@ -2,6 +2,7 @@ import { computed } from "vue";
|
||||
import { useAuthStore } from "../store/auth";
|
||||
import { useStudyStore } from "../store/study";
|
||||
import { TEXT } from "../locales";
|
||||
import { getProjectRole, isSystemAdmin } from "./roles";
|
||||
|
||||
const PERMISSIONS: Record<string, string[]> = {
|
||||
"subject.create": ["ADMIN", "PM", "CRA"],
|
||||
@@ -37,13 +38,11 @@ export const usePermission = () => {
|
||||
const auth = useAuthStore();
|
||||
const study = useStudyStore();
|
||||
|
||||
const userRole = computed(() => auth.user?.role || null);
|
||||
const projectRole = computed(
|
||||
() => study.currentStudyRole || (study.currentStudy as any)?.role_in_study || auth.user?.role || null
|
||||
);
|
||||
const systemAdmin = computed(() => isSystemAdmin(auth.user));
|
||||
const projectRole = computed(() => getProjectRole(study.currentStudy, study.currentStudyRole));
|
||||
|
||||
const can = (action: string): boolean => {
|
||||
if (userRole.value === "ADMIN") return true;
|
||||
if (systemAdmin.value) return true;
|
||||
const allowList = PERMISSIONS[action];
|
||||
if (!allowList) return false;
|
||||
if (!projectRole.value) return false;
|
||||
@@ -51,7 +50,7 @@ export const usePermission = () => {
|
||||
};
|
||||
|
||||
const reason = (action: string): string => {
|
||||
if (userRole.value === "ADMIN") return "";
|
||||
if (systemAdmin.value) return "";
|
||||
return REASONS[action] || TEXT.modules.permissions.default;
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getProjectRole, getSystemRole, isProjectAdminOrPm, isSystemAdmin } from "./roles";
|
||||
|
||||
describe("role helpers", () => {
|
||||
it("keeps system admin separate from project admin", () => {
|
||||
expect(isSystemAdmin({ role: "ADMIN" } as any)).toBe(true);
|
||||
expect(getSystemRole({ role: "ADMIN" } as any)).toBe("ADMIN");
|
||||
expect(getProjectRole({ role_in_study: null } as any, null)).toBeNull();
|
||||
});
|
||||
|
||||
it("uses only project role fields for project permissions", () => {
|
||||
expect(getProjectRole({ role_in_study: "PM" } as any, null)).toBe("PM");
|
||||
expect(getProjectRole({ role_in_study: null } as any, "ADMIN")).toBe("ADMIN");
|
||||
expect(isProjectAdminOrPm("ADMIN")).toBe(true);
|
||||
expect(isProjectAdminOrPm("PM")).toBe(true);
|
||||
expect(isProjectAdminOrPm("CRA")).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { Study, UserInfo } from "../types/api";
|
||||
|
||||
export const getSystemRole = (user?: Pick<UserInfo, "role"> | null) => user?.role || null;
|
||||
|
||||
export const isSystemAdmin = (user?: Pick<UserInfo, "role"> | null) => getSystemRole(user) === "ADMIN";
|
||||
|
||||
export const getProjectRole = (study?: Pick<Study, "role_in_study"> | null, currentStudyRole?: string | null) =>
|
||||
currentStudyRole || study?.role_in_study || null;
|
||||
|
||||
export const isProjectAdminOrPm = (role?: string | null) => role === "ADMIN" || role === "PM";
|
||||
@@ -90,14 +90,6 @@ const setupFieldLabelMap: Record<keyof SetupConfigDraft, Record<string, string>>
|
||||
note: "备注",
|
||||
stageBreakdown: "分阶段计划",
|
||||
},
|
||||
monitoringStrategies: {
|
||||
id: "ID",
|
||||
strategyType: "监查类型",
|
||||
detail: "策略详情",
|
||||
frequency: "监查次数",
|
||||
updatedAt: "更新时间",
|
||||
enabled: "是否启用",
|
||||
},
|
||||
centerConfirm: {
|
||||
id: "ID",
|
||||
siteId: "中心ID",
|
||||
@@ -124,7 +116,6 @@ const getArrayRowIdentity = (moduleKey: keyof SetupConfigDraft, value: unknown):
|
||||
if (moduleKey === "projectMilestones") return String(row.name || "").trim();
|
||||
if (moduleKey === "siteMilestones") return String(row.milestone || "").trim();
|
||||
if (moduleKey === "siteEnrollmentPlans") return String(row.siteName || row.siteId || "").trim();
|
||||
if (moduleKey === "monitoringStrategies") return String(row.strategyType || "").trim();
|
||||
if (moduleKey === "centerConfirm") return String(row.siteName || row.siteId || "").trim();
|
||||
return "";
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user