refactor: 菜单栏角色化降噪(role-based-navigation-simplification)

这个提交包含在:
2026-05-29 21:11:32 +08:00
父节点 325a7950c9
当前提交 541593a5b6
共修改 58 个文件,包含 796 行新增和 366 行删除
@@ -6,13 +6,31 @@ const {
} = require("../../src/services/workspaceSettings");
const { hasPermission, resolvePermissionsForRoles } = require("../../src/constants/permissions");
function buildPrisma() {
function buildPrisma(enabledFlagKeys = []) {
const enabled = new Set(enabledFlagKeys);
return {
workspaceSetting: {
findFirst: vi.fn().mockResolvedValue(null),
},
featureFlag: {
findUnique: vi.fn().mockResolvedValue(null),
findUnique: vi.fn().mockImplementation(async ({ where }) => {
if (!enabled.has(where.flag_key)) {
return null;
}
return {
id: `flag-${where.flag_key}`,
flag_key: where.flag_key,
override_mode: "force_on",
rollout_type: "global",
department_ids: [],
note: "",
version: 1,
created_at: null,
created_by: null,
updated_at: null,
updated_by: null,
};
}),
},
};
}
@@ -37,7 +55,7 @@ function buildConfig() {
}
describe("workspace small-team navigation defaults", () => {
it("keeps only the R66 daily path in default main navigation", () => {
it("keeps only the small-team task path in default main navigation", () => {
const mainModules = Object.entries(DEFAULT_MODULE_VISIBILITY)
.filter(([, placement]) => placement === "main")
.map(([key]) => key);
@@ -47,12 +65,10 @@ describe("workspace small-team navigation defaults", () => {
"team",
"leaveRequests",
"reimbursements",
"assets",
"weeklyReports",
"todos",
"announcements",
"projects",
]);
expect(DEFAULT_MODULE_VISIBILITY.todos).toBe("hidden");
expect(DEFAULT_MODULE_VISIBILITY.assets).toBe("admin");
expect(DEFAULT_MODULE_VISIBILITY.attendance).toBe("hidden");
});
@@ -77,7 +93,7 @@ describe("workspace small-team navigation defaults", () => {
expect(workspace.moduleVisibility.attendance).toBe("hidden");
});
it("keeps P2 and finance-only backup modules out of employee navigation", async () => {
it("keeps low-frequency and finance-only modules out of employee primary navigation", async () => {
const employee = {
id: "employee-1",
department_id: "dept-1",
@@ -86,12 +102,18 @@ describe("workspace small-team navigation defaults", () => {
};
const navigation = await getSystemNavigation({
prisma: buildPrisma(),
prisma: buildPrisma(["presence_board_v1", "project_domain_v1"]),
config: buildConfig(),
employee,
hasPermission: (candidate, permissionCode) => hasPermission(candidate.permissionCodes, permissionCode),
});
const mainLabels = navigation.sections.main.map((item) => item.label);
expect(mainLabels).toEqual(["工作台", "报销", "请假记录"]);
expect(mainLabels).not.toContain("待我处理");
expect(mainLabels).not.toContain("票据");
expect(mainLabels).not.toContain("个人设置");
const backupKeys = navigation.sections.backup.map((item) => item.key);
expect(backupKeys).toContain("purchaseRequests");
expect(backupKeys).not.toContain("travelRequests");
@@ -104,7 +126,30 @@ describe("workspace small-team navigation defaults", () => {
expect(backupKeys).not.toContain("dashboard");
});
it("does not expose workflow task entry to admin-only navigation", async () => {
it("shows待我处理 for business approvers and keeps admin-only users out", async () => {
const manager = {
id: "manager-1",
department_id: "dept-1",
roleCodes: ["manager"],
permissionCodes: resolvePermissionsForRoles(["manager"]),
};
const managerNavigation = await getSystemNavigation({
prisma: buildPrisma(["presence_board_v1", "project_domain_v1"]),
config: { ...buildConfig(), projectDomainEnabled: true, presenceBoardEnabled: true },
employee: manager,
hasPermission: (candidate, permissionCode) => hasPermission(candidate.permissionCodes, permissionCode),
});
expect(managerNavigation.sections.main.map((item) => item.label)).toEqual([
"工作台",
"待我处理",
"报销",
"项目",
"团队",
"请假记录",
]);
const adminOnly = {
id: "admin-1",
department_id: "dept-1",
@@ -113,7 +158,7 @@ describe("workspace small-team navigation defaults", () => {
};
const navigation = await getSystemNavigation({
prisma: buildPrisma(),
prisma: buildPrisma(["payment_batch_v1"]),
config: { ...buildConfig(), workflowDefinitionAdminEnabled: true },
employee: adminOnly,
hasPermission: (candidate, permissionCode) => hasPermission(candidate.permissionCodes, permissionCode),
@@ -125,7 +170,32 @@ describe("workspace small-team navigation defaults", () => {
...navigation.sections.backup,
].map((item) => item.key);
expect(allKeys).toContain("auditLogs");
expect(allKeys).toContain("systemSettings");
expect(allKeys).not.toContain("auditLogs");
expect(allKeys).not.toContain("workflowTasks");
});
it("uses work names for finance navigation", async () => {
const finance = {
id: "finance-1",
department_id: "dept-1",
roleCodes: ["finance"],
permissionCodes: resolvePermissionsForRoles(["finance"]),
};
const navigation = await getSystemNavigation({
prisma: buildPrisma(["payment_batch_v1"]),
config: { ...buildConfig(), paymentBatchEnabled: true },
employee: finance,
hasPermission: (candidate, permissionCode) => hasPermission(candidate.permissionCodes, permissionCode),
});
const mainLabels = navigation.sections.main.map((item) => item.label);
expect(mainLabels).toContain("待我处理");
expect(mainLabels).toContain("付款");
expect(mainLabels).toContain("应付账款");
expect(mainLabels).toContain("票据");
expect(mainLabels).not.toContain("AP 运营");
expect(mainLabels).not.toContain("付款批次");
});
});