import { ForbiddenException } from '@nestjs/common'; import type { Prisma } from '@prisma/client'; import type { AuthUser } from './decorators/current-user.decorator'; import { PrismaService } from '../prisma/prisma.service'; export type DataScope = 'ALL' | 'DEPT' | 'SELF'; const RANK: Record = { SELF: 0, DEPT: 1, ALL: 2 }; export function mergeDataScope(scopes: string[]): DataScope { let best: DataScope = 'SELF'; for (const s of scopes) { const v = s as DataScope; if (RANK[v] > RANK[best]) best = v; } return best; } export function effectiveScope(user: AuthUser): DataScope { if (user.roles.includes('admin') || user.roles.includes('owner')) return 'ALL'; return user.dataScope || 'SELF'; } export async function descendantDeptIds(prisma: PrismaService, rootId?: string | null) { if (!rootId) return [] as string[]; const all = await prisma.department.findMany({ where: { tenantId: '1' }, select: { id: true, parentId: true }, }); const ids = new Set([rootId]); let grew = true; while (grew) { grew = false; for (const d of all) { if (d.parentId && ids.has(d.parentId) && !ids.has(d.id)) { ids.add(d.id); grew = true; } } } return [...ids]; } function empInScope(user: AuthUser, deptIds: string[]): Prisma.EmployeeWhereInput { return { OR: [ { departmentId: { in: deptIds } }, ...(user.employeeId ? [{ managerId: user.employeeId }, { managerIds: { contains: user.employeeId } }] : []), ], }; } /** 本人 / 本部门及下级 / 全公司 */ export async function ownerScope( prisma: PrismaService, user: AuthUser, owner: 'createdById' | 'applicantId', ): Promise> { const scope = effectiveScope(user); if (scope === 'ALL') return {}; if (scope === 'SELF' || !user.departmentId) return { [owner]: user.id }; const deptIds = await descendantDeptIds(prisma, user.departmentId); const rel = owner === 'createdById' ? 'createdBy' : 'applicant'; return { OR: [{ [owner]: user.id }, { [rel]: { employee: empInScope(user, deptIds) } }], }; } export function withAnd(where: T, extra: Record): T { if (!extra || !Object.keys(extra).length) return where; const prev = where.AND; const list = Array.isArray(prev) ? [...prev] : prev ? [prev] : []; list.push(extra); where.AND = list; return where; } export async function canSeeOwner(prisma: PrismaService, user: AuthUser, ownerId?: string | null) { if (!ownerId) return true; const scope = effectiveScope(user); if (scope === 'ALL') return true; if (ownerId === user.id) return true; if (scope !== 'DEPT' || !user.departmentId) return false; const owner = await prisma.user.findUnique({ where: { id: ownerId }, include: { employee: true }, }); if (!owner?.employee) return false; const deptIds = await descendantDeptIds(prisma, user.departmentId); if (owner.employee.departmentId && deptIds.includes(owner.employee.departmentId)) return true; const extra = String(owner.employee.managerIds || '') .split(/[,,、\s]+/) .filter(Boolean); return Boolean( user.employeeId && (owner.employee.managerId === user.employeeId || extra.includes(user.employeeId)), ); } export async function assertCanSeeOwner(prisma: PrismaService, user: AuthUser, ownerId?: string | null) { if (await canSeeOwner(prisma, user, ownerId)) return; throw new ForbiddenException('超出数据范围,无权查看'); }