A7 收口权限导航与备用模块护栏
这个提交包含在:
@@ -35,11 +35,11 @@ const NAVIGATION_REGISTRY = Object.freeze({
|
||||
workflowTasks: { key: "workflowTasks", label: "审批待办", icon: "fact_check", href: "/workflow/tasks", anyOf: ["expense.approve", "leave.approve", "procurement.approve", "travel.approve"] },
|
||||
dashboard: { key: "dashboard", label: "仪表盘", icon: "dashboard", href: "/dashboard", anyOf: ["report.dashboard.view"] },
|
||||
purchaseRequests: { key: "purchaseRequests", label: "采购申请", icon: "shopping_cart", href: "/purchase-requests", anyOf: ["procurement.create", "procurement.view_own", "procurement.view_all", "procurement.manage"] },
|
||||
travelRequests: { key: "travelRequests", label: "差旅申请", icon: "flight", href: "/travel-requests", anyOf: ["travel.create", "travel.view_own", "travel.view_all", "travel.manage", "travel.payment.manage", "integration.manage"] },
|
||||
travelRequests: { key: "travelRequests", label: "差旅申请", icon: "flight", href: "/travel-requests", anyOf: ["travel.view_all", "travel.manage", "travel.payment.manage", "integration.manage"] },
|
||||
budgets: { key: "budgets", label: "预算管理", icon: "account_balance", href: "/budgets", anyOf: ["budget.view", "budget.manage"] },
|
||||
paymentBatches: { key: "paymentBatches", label: "支付批次", icon: "payments", href: "/payment-batches", anyOf: ["payment.batch.manage", "risk.review.manage"], featureFlagKey: FEATURE_FLAG_KEYS.paymentBatch },
|
||||
apOperations: { key: "apOperations", label: "AP 运营", icon: "account_balance_wallet", href: "/ap-operations", anyOf: ["payment.batch.manage", "risk.review.manage"], featureFlagKey: FEATURE_FLAG_KEYS.paymentBatch },
|
||||
invoicePool: { key: "invoicePool", label: "票据中心", icon: "receipt_long", href: "/invoice-pool", anyOf: ["invoice.upload", "invoice.view_own", "invoice.manage"] },
|
||||
invoicePool: { key: "invoicePool", label: "票据中心", icon: "receipt_long", href: "/invoice-pool", anyOf: ["invoice.manage"] },
|
||||
inventoryChecks: { key: "inventoryChecks", label: "资产盘点", icon: "inventory", href: "/inventory-checks", anyOf: ["asset.inventory"] },
|
||||
cutover: { key: "cutover", label: "切换控制台", icon: "swap_calls", href: "/system/cutover", anyOf: ["system.settings"], featureFlagKey: FEATURE_FLAG_KEYS.cutoverConsole },
|
||||
erpIntegrations: { key: "erpIntegrations", label: "ERP 集成", icon: "hub", href: "/system/integrations/erp", anyOf: ["integration.manage"] },
|
||||
|
||||
@@ -1,8 +1,40 @@
|
||||
const {
|
||||
DEFAULT_MODULE_VISIBILITY,
|
||||
buildDefaultWorkspaceSetting,
|
||||
getSystemNavigation,
|
||||
normalizeModuleVisibilityJson,
|
||||
} = require("../../src/services/workspaceSettings");
|
||||
const { hasPermission, resolvePermissionsForRoles } = require("../../src/constants/permissions");
|
||||
|
||||
function buildPrisma() {
|
||||
return {
|
||||
workspaceSetting: {
|
||||
findFirst: vi.fn().mockResolvedValue(null),
|
||||
},
|
||||
featureFlag: {
|
||||
findUnique: vi.fn().mockResolvedValue(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 R66 daily path in default main navigation", () => {
|
||||
@@ -44,4 +76,30 @@ describe("workspace small-team navigation defaults", () => {
|
||||
expect(workspace.profile).toBe("small_team_internal");
|
||||
expect(workspace.moduleVisibility.attendance).toBe("hidden");
|
||||
});
|
||||
|
||||
it("keeps P2 and finance-only backup modules out of employee navigation", async () => {
|
||||
const employee = {
|
||||
id: "employee-1",
|
||||
department_id: "dept-1",
|
||||
permissionCodes: resolvePermissionsForRoles(["employee"]),
|
||||
};
|
||||
|
||||
const navigation = await getSystemNavigation({
|
||||
prisma: buildPrisma(),
|
||||
config: buildConfig(),
|
||||
employee,
|
||||
hasPermission: (candidate, permissionCode) => hasPermission(candidate.permissionCodes, permissionCode),
|
||||
});
|
||||
|
||||
const backupKeys = navigation.sections.backup.map((item) => item.key);
|
||||
expect(backupKeys).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");
|
||||
});
|
||||
});
|
||||
|
||||
在新工单中引用
屏蔽一个用户