const { DEFAULT_MODULE_VISIBILITY, buildDefaultWorkspaceSetting, getSystemNavigation, normalizeModuleVisibilityJson, } = require("../../src/services/workspaceSettings"); const { hasPermission, resolvePermissionsForRoles } = require("../../src/constants/permissions"); function buildPrisma(enabledFlagKeys = []) { const enabled = new Set(enabledFlagKeys); return { workspaceSetting: { findFirst: vi.fn().mockResolvedValue(null), }, featureFlag: { 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, }; }), }, }; } function buildConfig() { return { announcementsEnabled: false, attendanceEnabled: false, budgetPrecheckEnabled: false, cutoverConsoleEnabled: false, employeeLifecycleEnabled: false, globalSearchEnabled: false, paymentBatchEnabled: false, portalHomeEnabled: false, presenceBoardEnabled: false, projectDomainEnabled: false, projectLinkingEnabled: false, todoBoardEnabled: false, weeklyReportEnabled: false, workflowDefinitionAdminEnabled: false, }; } describe("workspace small-team navigation defaults", () => { 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); expect(mainModules).toEqual([ "portal", "team", "leaveRequests", "reimbursements", "assets", "projects", "contractsRegistry", "purchaseRequests", ]); expect(DEFAULT_MODULE_VISIBILITY.todos).toBe("hidden"); expect(DEFAULT_MODULE_VISIBILITY.assets).toBe("main"); expect(DEFAULT_MODULE_VISIBILITY.contractsRegistry).toBe("main"); expect(DEFAULT_MODULE_VISIBILITY.purchaseRequests).toBe("main"); expect(DEFAULT_MODULE_VISIBILITY.attendance).toBe("hidden"); }); it("normalizes unknown or invalid module visibility back to the R66 defaults", () => { const normalized = normalizeModuleVisibilityJson({ attendance: "main", purchaseRequests: "hidden", unknownModule: "main", budgets: "invalid", }); expect(normalized.attendance).toBe("main"); expect(normalized.purchaseRequests).toBe("hidden"); expect(normalized.budgets).toBe(DEFAULT_MODULE_VISIBILITY.budgets); expect(normalized.unknownModule).toBeUndefined(); }); it("builds a small-team internal workspace by default", () => { const workspace = buildDefaultWorkspaceSetting(); expect(workspace.profile).toBe("small_team_internal"); expect(workspace.moduleVisibility.attendance).toBe("hidden"); }); it("keeps low-frequency and finance-only modules out of employee primary navigation", async () => { const employee = { id: "employee-1", department_id: "dept-1", roleCodes: ["employee"], permissionCodes: resolvePermissionsForRoles(["employee"]), }; const navigation = await getSystemNavigation({ prisma: buildPrisma(["presence_board_v1", "project_domain_v1"]), config: { ...buildConfig(), projectDomainEnabled: true }, 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).not.toContain("purchaseRequests"); expect(backupKeys).not.toContain("travelRequests"); expect(backupKeys).not.toContain("invoicePool"); expect(backupKeys).not.toContain("contractsRegistry"); expect(backupKeys).not.toContain("budgets"); expect(backupKeys).not.toContain("paymentBatches"); expect(backupKeys).not.toContain("apOperations"); expect(backupKeys).not.toContain("erpIntegrations"); expect(backupKeys).not.toContain("dashboard"); }); 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", "project_linking_v1"]), config: { ...buildConfig(), projectDomainEnabled: true, projectLinkingEnabled: 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", roleCodes: ["admin"], permissionCodes: resolvePermissionsForRoles(["admin"]), }; const navigation = await getSystemNavigation({ prisma: buildPrisma(["payment_batch_v1"]), config: { ...buildConfig(), workflowDefinitionAdminEnabled: true }, employee: adminOnly, hasPermission: (candidate, permissionCode) => hasPermission(candidate.permissionCodes, permissionCode), }); const allKeys = [ ...navigation.sections.main, ...navigation.sections.admin, ...navigation.sections.backup, ].map((item) => item.key); 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("付款批次"); }); it("shows payment review navigation to manager reviewers without granting finance-only invoice tools", async () => { const manager = { id: "manager-reviewer-1", department_id: "dept-1", roleCodes: ["manager"], permissionCodes: resolvePermissionsForRoles(["manager"]), }; const navigation = await getSystemNavigation({ prisma: buildPrisma(["payment_batch_v1"]), config: { ...buildConfig(), paymentBatchEnabled: true }, employee: manager, hasPermission: (candidate, permissionCode) => hasPermission(candidate.permissionCodes, permissionCode), }); const mainLabels = navigation.sections.main.map((item) => item.label); expect(mainLabels).toContain("付款"); expect(mainLabels).toContain("应付账款"); expect(mainLabels).not.toContain("票据"); }); });