Role default + user override with OR rules; covers customers, leads, sales, activities, notes, routines, projects, and tickets.
100 lines
3.6 KiB
TypeScript
100 lines
3.6 KiB
TypeScript
import { ForbiddenException } from '@nestjs/common';
|
|
import { sectionHasDataAccessLimits } from '@mento/shared';
|
|
import { sectionOwnerIdUnion } from '../common/data-access';
|
|
import { type Actor, elevated, scopedOwnerIds } from '../common/record-scope';
|
|
import type { PrismaService } from '../prisma/prisma.service';
|
|
|
|
export type RoutineScope = {
|
|
userId: string;
|
|
organizationId: string;
|
|
/** مالک/مدیر سازمان یا مدیر حداقل یک تیم */
|
|
isManager: boolean;
|
|
/** کاربرانی که میتواند برایشان روتین/Task تعریف کند؛ null = همه */
|
|
manageableUserIds: string[] | null;
|
|
/** کاربرانی که Taskهایشان را میبیند؛ null = همه */
|
|
viewableUserIds: string[] | null;
|
|
};
|
|
|
|
export async function resolveRoutineScope(
|
|
prisma: PrismaService,
|
|
actor: Actor,
|
|
): Promise<RoutineScope> {
|
|
const base = {
|
|
userId: actor.userId,
|
|
organizationId: actor.organizationId,
|
|
};
|
|
if (elevated(actor.role)) {
|
|
return { ...base, isManager: true, manageableUserIds: null, viewableUserIds: null };
|
|
}
|
|
|
|
const managed = await prisma.teamMember.findMany({
|
|
where: { userId: actor.userId, role: 'manager', team: { organizationId: actor.organizationId } },
|
|
select: { teamId: true },
|
|
});
|
|
let manageable: string[] = [actor.userId];
|
|
if (managed.length) {
|
|
const members = await prisma.teamMember.findMany({
|
|
where: { teamId: { in: managed.map((m) => m.teamId) } },
|
|
select: { userId: true },
|
|
});
|
|
manageable = [...new Set([actor.userId, ...members.map((m) => m.userId)])];
|
|
}
|
|
|
|
let viewable: string[] | null;
|
|
if (actor.routinesAccess !== undefined) {
|
|
if (!sectionHasDataAccessLimits(actor.routinesAccess)) {
|
|
viewable = null;
|
|
} else {
|
|
const owners = sectionOwnerIdUnion(actor.routinesAccess, {
|
|
userId: actor.userId,
|
|
teamPeerIds: actor.teamPeerIds ?? null,
|
|
});
|
|
// فقط-مالک: محدود به owners؛ اگر قانون بدون owners (مثلاً دسته) باشد null = همه + فیلتر deepWhere
|
|
viewable = owners === null ? null : owners.length ? owners : [actor.userId];
|
|
}
|
|
} else {
|
|
const visible = scopedOwnerIds(actor, 'routines');
|
|
viewable = visible === null ? null : [...new Set([...visible, ...manageable])];
|
|
}
|
|
|
|
if (viewable !== null) {
|
|
viewable = [...new Set([...viewable, ...manageable])];
|
|
}
|
|
|
|
return {
|
|
...base,
|
|
isManager: managed.length > 0,
|
|
manageableUserIds: manageable,
|
|
viewableUserIds: viewable,
|
|
};
|
|
}
|
|
|
|
export function canView(scope: RoutineScope, userId: string): boolean {
|
|
return scope.viewableUserIds === null || scope.viewableUserIds.includes(userId);
|
|
}
|
|
|
|
export function canManage(scope: RoutineScope, userId: string): boolean {
|
|
return scope.manageableUserIds === null || scope.manageableUserIds.includes(userId);
|
|
}
|
|
|
|
export function assertManager(scope: RoutineScope): void {
|
|
if (!scope.isManager) {
|
|
throw new ForbiddenException('فقط مدیران به این بخش دسترسی دارند');
|
|
}
|
|
}
|
|
|
|
/** فیلتر Prisma روی assigneeId بر اساس دید کاربر و فیلتر کاربر درخواستی */
|
|
export function assigneeFilter(
|
|
scope: RoutineScope,
|
|
requestedUserId?: string | null,
|
|
): { assigneeId?: string | { in: string[] } } {
|
|
if (requestedUserId) {
|
|
if (!canView(scope, requestedUserId)) {
|
|
throw new ForbiddenException('دسترسی به کارهای این کاربر ندارید');
|
|
}
|
|
return { assigneeId: requestedUserId };
|
|
}
|
|
if (scope.viewableUserIds === null) return {};
|
|
return { assigneeId: { in: scope.viewableUserIds } };
|
|
}
|