feat(acl): deep data access by section for roles and users

Role default + user override with OR rules; covers customers, leads, sales, activities, notes, routines, projects, and tickets.
This commit is contained in:
2026-10-22 11:53:26 +03:30
parent cb5944e767
commit 09a60b3b5a
25 changed files with 1997 additions and 122 deletions
+22 -2
View File
@@ -1,4 +1,6 @@
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';
@@ -38,8 +40,26 @@ export async function resolveRoutineScope(
manageable = [...new Set([actor.userId, ...members.map((m) => m.userId)])];
}
const visible = scopedOwnerIds(actor, 'routines');
const viewable = visible === null ? null : [...new Set([...visible, ...manageable])];
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,