diff --git a/apps/server/src/modules/data/data-keys.ts b/apps/server/src/modules/data/data-keys.ts index afdd4e4..c5b56b9 100644 --- a/apps/server/src/modules/data/data-keys.ts +++ b/apps/server/src/modules/data/data-keys.ts @@ -9,6 +9,8 @@ export const APP_DATA_KEYS = [ 'task-categories', 'task-worklogs', 'work-activities', + 'xiaobao-risk-insights', + 'xiaobao-risk-snapshots', 'overtime', ] as const; diff --git a/apps/web/lib/permissions.ts b/apps/web/lib/permissions.ts index bcede05..f0e6543 100644 --- a/apps/web/lib/permissions.ts +++ b/apps/web/lib/permissions.ts @@ -43,6 +43,15 @@ export const PERMISSION_GROUPS: PermissionGroup[] = [ { action: 'export', label: '导出', permission: 'overtime:export' }, ], }, + { + module: 'xiaobao.warning', + moduleLabel: '小宝预警', + category: 'main', + actions: [ + { action: 'view', label: '查看', permission: 'xiaobao.warning:view' }, + { action: 'manage', label: '管理', permission: 'xiaobao.warning:manage' }, + ], + }, { module: 'member', moduleLabel: '成员', category: 'main', actions: std4('member') }, { module: 'role', moduleLabel: '角色', category: 'main', actions: std4('role') }, { module: 'version.req', moduleLabel: '需求 Tab', category: 'version_tab', actions: stdTab('version.req') }, @@ -67,6 +76,7 @@ export const DEFAULT_ROLE_PERMISSIONS: Record = { ...std4('requirement').map((a) => a.permission), 'version.req:view', 'version.req:manage', 'version.product_plan:view', 'version.product_plan:manage', + 'xiaobao.warning:view', 'xiaobao.warning:manage', 'overtime:view', 'member:view', 'role:view', 'version.research:view', 'version.ui_plan:view', 'version.devtask:view', 'version.testcase:view', 'version.bug:view', @@ -76,6 +86,7 @@ export const DEFAULT_ROLE_PERMISSIONS: Record = { 'version.devtask:view', 'version.devtask:manage', 'version.bug:view', 'version.bug:edit', 'version.research:view', 'version.product_plan:view', 'version.ui_plan:view', 'version.testcase:view', + 'xiaobao.warning:view', 'overtime:view', 'overtime:create', ], 'role-test': [ @@ -83,6 +94,7 @@ export const DEFAULT_ROLE_PERMISSIONS: Record = { 'version.testcase:view', 'version.testcase:manage', 'version.bug:view', 'version.bug:create', 'version.bug:edit', 'version.bug:delete', 'version.research:view', 'version.product_plan:view', 'version.ui_plan:view', 'version.devtask:view', + 'xiaobao.warning:view', 'overtime:view', 'overtime:create', ], 'role-design': [ @@ -90,6 +102,7 @@ export const DEFAULT_ROLE_PERMISSIONS: Record = { 'version.ui_plan:view', 'version.ui_plan:manage', 'version.research:view', 'version.product_plan:view', 'version.devtask:view', 'version.testcase:view', 'version.bug:view', + 'xiaobao.warning:view', 'overtime:view', 'overtime:create', ], }; diff --git a/apps/web/lib/role-permission-migration.test.ts b/apps/web/lib/role-permission-migration.test.ts new file mode 100644 index 0000000..06e6e3f --- /dev/null +++ b/apps/web/lib/role-permission-migration.test.ts @@ -0,0 +1,38 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import type { RoleItem } from './members'; +import { mergePresetRolePermissions } from './role-permission-migration'; + +test('mergePresetRolePermissions adds new preset permissions to existing product manager role', () => { + const roles: RoleItem[] = [ + { + id: 'role-pm', + name: '产品经理', + createdAt: '2024-01-01', + permissions: ['product:view'], + }, + ]; + + const result = mergePresetRolePermissions(roles); + + assert.equal(result.changed, true); + assert.ok(result.roles[0].permissions.includes('product:view')); + assert.ok(result.roles[0].permissions.includes('xiaobao.warning:view')); + assert.ok(result.roles[0].permissions.includes('xiaobao.warning:manage')); +}); + +test('mergePresetRolePermissions keeps custom role permissions unchanged', () => { + const roles: RoleItem[] = [ + { + id: 'role-custom', + name: '自定义角色', + createdAt: '2024-01-01', + permissions: ['version:view'], + }, + ]; + + const result = mergePresetRolePermissions(roles); + + assert.equal(result.changed, false); + assert.deepEqual(result.roles[0].permissions, ['version:view']); +}); diff --git a/apps/web/lib/role-permission-migration.ts b/apps/web/lib/role-permission-migration.ts new file mode 100644 index 0000000..c769454 --- /dev/null +++ b/apps/web/lib/role-permission-migration.ts @@ -0,0 +1,15 @@ +import type { RoleItem } from './members'; +import { DEFAULT_ROLE_PERMISSIONS } from './permissions'; + +export function mergePresetRolePermissions(roles: RoleItem[]): { roles: RoleItem[]; changed: boolean } { + let changed = false; + const next = roles.map((role) => { + const defaults = DEFAULT_ROLE_PERMISSIONS[role.id]; + if (!defaults || role.permissions.includes('*')) return role; + const merged = Array.from(new Set([...role.permissions, ...defaults])); + if (merged.length === role.permissions.length) return role; + changed = true; + return { ...role, permissions: merged }; + }); + return { roles: next, changed }; +} diff --git a/apps/web/lib/server-data.ts b/apps/web/lib/server-data.ts index 608886f..8972112 100644 --- a/apps/web/lib/server-data.ts +++ b/apps/web/lib/server-data.ts @@ -11,6 +11,8 @@ export type ServerDataKey = | 'task-categories' | 'task-worklogs' | 'work-activities' + | 'xiaobao-risk-insights' + | 'xiaobao-risk-snapshots' | 'overtime'; interface ServerDataResponse { diff --git a/apps/web/stores/useMemberStore.ts b/apps/web/stores/useMemberStore.ts index f9d8a1a..8a847a4 100644 --- a/apps/web/stores/useMemberStore.ts +++ b/apps/web/stores/useMemberStore.ts @@ -10,6 +10,7 @@ import { sanitizeSystemAdminPatch, } from '@/lib/member-system'; import { DEFAULT_ROLE_PERMISSIONS } from '@/lib/permissions'; +import { mergePresetRolePermissions } from '@/lib/role-permission-migration'; import { loadServerData, saveServerData } from '@/lib/server-data'; const PRESET_DEPARTMENTS: Department[] = [ @@ -81,13 +82,16 @@ export const useMemberStore = create((set, get) => ({ } return r; }); + const permissionMerged = mergePresetRolePermissions(migratedRoles); + const rolesWithNewDefaults = permissionMerged.roles; const adminRole = PRESET_ROLES.find((r) => r.id === SYSTEM_ADMIN_ROLE_ID)!; - const roles = migratedRoles.some((r) => r.id === SYSTEM_ADMIN_ROLE_ID) - ? migratedRoles.map((r) => r.id === SYSTEM_ADMIN_ROLE_ID ? { ...adminRole, ...r, isSystem: true, permissions: DEFAULT_ROLE_PERMISSIONS[SYSTEM_ADMIN_ROLE_ID] } : r) - : [adminRole, ...migratedRoles]; + const roles = rolesWithNewDefaults.some((r) => r.id === SYSTEM_ADMIN_ROLE_ID) + ? rolesWithNewDefaults.map((r) => r.id === SYSTEM_ADMIN_ROLE_ID ? { ...adminRole, ...r, isSystem: true, permissions: DEFAULT_ROLE_PERMISSIONS[SYSTEM_ADMIN_ROLE_ID] } : r) + : [adminRole, ...rolesWithNewDefaults]; const rolesChanged = - roles.length !== migratedRoles.length || - roles.some((role, index) => JSON.stringify(role) !== JSON.stringify(migratedRoles[index])); + permissionMerged.changed || + roles.length !== rolesWithNewDefaults.length || + roles.some((role, index) => JSON.stringify(role) !== JSON.stringify(rolesWithNewDefaults[index])); const ensured = ensureSystemAdminMember(cached.members); const next = { departments: cached.departments,