Add named teams and per-user data visibility (own/team/org) with settings UI and org module.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import type { UserRole } from '@mento/shared';
|
||||
import type { DataVisibility, UserRole } from '@mento/shared';
|
||||
import { canViewAllRecords } from '@mento/shared';
|
||||
import type { Prisma } from '@prisma/client';
|
||||
|
||||
@@ -6,39 +6,73 @@ export type Actor = {
|
||||
userId: string;
|
||||
role: string;
|
||||
organizationId: string;
|
||||
/** own | team | org — از Membership */
|
||||
dataVisibility?: DataVisibility;
|
||||
/**
|
||||
* شناسه کاربرانی که پروندهشان برای این Actor قابلمشاهده است.
|
||||
* اگر undefined باشد فقط خود کاربر؛ اگر null یعنی همه (elevated/org).
|
||||
*/
|
||||
visibleUserIds?: string[] | null;
|
||||
};
|
||||
|
||||
export function elevated(role: string | null | undefined): boolean {
|
||||
return canViewAllRecords(role);
|
||||
}
|
||||
|
||||
export function seesAllRecords(
|
||||
actor: Pick<Actor, 'role' | 'dataVisibility' | 'visibleUserIds'>,
|
||||
): boolean {
|
||||
if (elevated(actor.role)) return true;
|
||||
if (actor.dataVisibility === 'org') return true;
|
||||
if (actor.visibleUserIds === null) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function scopedOwnerIds(
|
||||
actor: Pick<Actor, 'userId' | 'role' | 'dataVisibility' | 'visibleUserIds'>,
|
||||
): string[] | null {
|
||||
if (seesAllRecords(actor)) return null;
|
||||
const ids = actor.visibleUserIds?.length
|
||||
? actor.visibleUserIds
|
||||
: [actor.userId];
|
||||
return [...new Set(ids.filter(Boolean))];
|
||||
}
|
||||
|
||||
/** فیلتر مالکیت برای Deal / Quote / Invoice / Person / Company */
|
||||
export function ownerWhere(
|
||||
actor: Pick<Actor, 'userId' | 'role'>,
|
||||
): { ownerId: string } | Record<string, never> {
|
||||
if (elevated(actor.role)) return {};
|
||||
return { ownerId: actor.userId };
|
||||
actor: Pick<Actor, 'userId' | 'role' | 'dataVisibility' | 'visibleUserIds'>,
|
||||
): { ownerId: string } | { ownerId: { in: string[] } } | Record<string, never> {
|
||||
const ids = scopedOwnerIds(actor);
|
||||
if (!ids) return {};
|
||||
if (ids.length === 1) return { ownerId: ids[0]! };
|
||||
return { ownerId: { in: ids } };
|
||||
}
|
||||
|
||||
/** فعالیتها بر اساس مسئول */
|
||||
export function assigneeWhere(
|
||||
actor: Pick<Actor, 'userId' | 'role'>,
|
||||
): { assigneeId: string } | Record<string, never> {
|
||||
if (elevated(actor.role)) return {};
|
||||
return { assigneeId: actor.userId };
|
||||
actor: Pick<Actor, 'userId' | 'role' | 'dataVisibility' | 'visibleUserIds'>,
|
||||
):
|
||||
| { assigneeId: string }
|
||||
| { assigneeId: { in: string[] } }
|
||||
| Record<string, never> {
|
||||
const ids = scopedOwnerIds(actor);
|
||||
if (!ids) return {};
|
||||
if (ids.length === 1) return { assigneeId: ids[0]! };
|
||||
return { assigneeId: { in: ids } };
|
||||
}
|
||||
|
||||
/** یادداشت: نویسنده یا پروندههای تحت مالکیت */
|
||||
/** یادداشت: نویسنده یا پروندههای تحت مالکیت در محدوده دید */
|
||||
export function noteAccessWhere(
|
||||
actor: Pick<Actor, 'userId' | 'role'>,
|
||||
actor: Pick<Actor, 'userId' | 'role' | 'dataVisibility' | 'visibleUserIds'>,
|
||||
): Prisma.NoteWhereInput {
|
||||
if (elevated(actor.role)) return {};
|
||||
const ids = scopedOwnerIds(actor);
|
||||
if (!ids) return {};
|
||||
return {
|
||||
OR: [
|
||||
{ authorId: actor.userId },
|
||||
{ deal: { ownerId: actor.userId } },
|
||||
{ person: { ownerId: actor.userId } },
|
||||
{ company: { ownerId: actor.userId } },
|
||||
{ authorId: { in: ids } },
|
||||
{ deal: { ownerId: { in: ids } } },
|
||||
{ person: { ownerId: { in: ids } } },
|
||||
{ company: { ownerId: { in: ids } } },
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -53,13 +87,14 @@ export function resolveOwnerId(
|
||||
return actor.userId;
|
||||
}
|
||||
|
||||
/** تیکت: مسئول یا ثبتکننده */
|
||||
/** تیکت: مسئول یا ثبتکننده در محدوده دید */
|
||||
export function ticketAccessWhere(
|
||||
actor: Pick<Actor, 'userId' | 'role'>,
|
||||
actor: Pick<Actor, 'userId' | 'role' | 'dataVisibility' | 'visibleUserIds'>,
|
||||
): Prisma.TicketWhereInput {
|
||||
if (elevated(actor.role)) return {};
|
||||
const ids = scopedOwnerIds(actor);
|
||||
if (!ids) return {};
|
||||
return {
|
||||
OR: [{ assigneeId: actor.userId }, { reporterId: actor.userId }],
|
||||
OR: [{ assigneeId: { in: ids } }, { reporterId: { in: ids } }],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -68,3 +103,10 @@ export function canReassignOwner(role: string | null | undefined): boolean {
|
||||
}
|
||||
|
||||
export type OrgRole = UserRole;
|
||||
|
||||
export function normalizeDataVisibility(
|
||||
value: string | null | undefined,
|
||||
): DataVisibility {
|
||||
if (value === 'team' || value === 'org' || value === 'own') return value;
|
||||
return 'own';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user