Role default + user override with OR rules; covers customers, leads, sales, activities, notes, routines, projects, and tickets.
174 lines
4.6 KiB
TypeScript
174 lines
4.6 KiB
TypeScript
import {
|
|
ForbiddenException,
|
|
Injectable,
|
|
NotFoundException,
|
|
UnauthorizedException,
|
|
} from '@nestjs/common';
|
|
import type { OrganizationMembershipDto, UserRole } from '@mento/shared';
|
|
import { Prisma } from '@prisma/client';
|
|
import { PrismaService } from '../prisma/prisma.service';
|
|
|
|
export type MembershipActor = {
|
|
userId: string;
|
|
organizationId: string;
|
|
role: UserRole;
|
|
};
|
|
|
|
@Injectable()
|
|
export class MembershipsService {
|
|
constructor(private readonly prisma: PrismaService) {}
|
|
|
|
async listActive(userId: string) {
|
|
return this.prisma.membership.findMany({
|
|
where: { userId, isActive: true, organization: { isActive: true } },
|
|
include: {
|
|
organization: { select: { id: true, name: true } },
|
|
orgRole: { select: { name: true } },
|
|
},
|
|
orderBy: { createdAt: 'asc' },
|
|
});
|
|
}
|
|
|
|
async requireActive(
|
|
userId: string,
|
|
organizationId: string,
|
|
): Promise<MembershipActor> {
|
|
const row = await this.prisma.membership.findFirst({
|
|
where: { userId, organizationId, isActive: true },
|
|
select: {
|
|
userId: true,
|
|
organizationId: true,
|
|
role: true,
|
|
organization: { select: { isActive: true } },
|
|
},
|
|
});
|
|
if (!row) {
|
|
throw new ForbiddenException('به این محیط دسترسی ندارید');
|
|
}
|
|
if (row.organization.isActive === false) {
|
|
throw new UnauthorizedException('این محیط غیرفعال است');
|
|
}
|
|
return {
|
|
userId: row.userId,
|
|
organizationId: row.organizationId,
|
|
role: row.role,
|
|
};
|
|
}
|
|
|
|
toChoices(
|
|
rows: Array<{
|
|
organization: { id: string; name: string };
|
|
role: UserRole;
|
|
orgRole: { name: string } | null;
|
|
}>,
|
|
): OrganizationMembershipDto[] {
|
|
return rows.map((row) => ({
|
|
id: row.organization.id,
|
|
name: row.organization.name,
|
|
role: row.role,
|
|
orgRoleName: row.orgRole?.name,
|
|
}));
|
|
}
|
|
|
|
async createMembership(data: {
|
|
userId: string;
|
|
organizationId: string;
|
|
role: UserRole;
|
|
orgRoleId?: string | null;
|
|
commissionPercent?: number;
|
|
isActive?: boolean;
|
|
}) {
|
|
return this.prisma.membership.create({
|
|
data: {
|
|
userId: data.userId,
|
|
organizationId: data.organizationId,
|
|
role: data.role,
|
|
orgRoleId: data.orgRoleId ?? null,
|
|
isActive: data.isActive ?? true,
|
|
commissionPercent:
|
|
data.commissionPercent == null
|
|
? undefined
|
|
: new Prisma.Decimal(data.commissionPercent),
|
|
},
|
|
});
|
|
}
|
|
|
|
async findInOrg(organizationId: string, userId: string) {
|
|
return this.prisma.membership.findFirst({
|
|
where: { organizationId, userId },
|
|
include: {
|
|
user: true,
|
|
orgRole: { select: { id: true, name: true, color: true, dataAccessPolicy: true } },
|
|
},
|
|
});
|
|
}
|
|
|
|
async listInOrg(organizationId: string) {
|
|
return this.prisma.membership.findMany({
|
|
where: { organizationId },
|
|
include: {
|
|
user: true,
|
|
orgRole: { select: { id: true, name: true, color: true, dataAccessPolicy: true } },
|
|
},
|
|
orderBy: [{ user: { lastLoginAt: 'desc' } }, { user: { fullName: 'asc' } }],
|
|
});
|
|
}
|
|
|
|
async countActiveOwners(organizationId: string, exceptUserId?: string) {
|
|
return this.prisma.membership.count({
|
|
where: {
|
|
organizationId,
|
|
role: 'owner',
|
|
isActive: true,
|
|
...(exceptUserId ? { userId: { not: exceptUserId } } : {}),
|
|
},
|
|
});
|
|
}
|
|
|
|
async syncUserLastOrg(
|
|
userId: string,
|
|
organizationId: string,
|
|
role: UserRole,
|
|
orgRoleId?: string | null,
|
|
) {
|
|
await this.prisma.user.update({
|
|
where: { id: userId },
|
|
data: {
|
|
organizationId,
|
|
role,
|
|
...(orgRoleId !== undefined ? { orgRoleId } : {}),
|
|
},
|
|
});
|
|
}
|
|
|
|
async findUserInOrgById(organizationId: string, userId: string) {
|
|
const row = await this.findInOrg(organizationId, userId);
|
|
if (!row) throw new NotFoundException('کاربر پیدا نشد');
|
|
return row;
|
|
}
|
|
|
|
async findUserInOrgByIdentity(
|
|
organizationId: string,
|
|
where: { id?: string; email?: string; mobile?: string },
|
|
) {
|
|
const user = await this.prisma.user.findFirst({
|
|
where: {
|
|
...(where.id ? { id: where.id } : {}),
|
|
...(where.email ? { email: where.email } : {}),
|
|
...(where.mobile ? { mobile: where.mobile } : {}),
|
|
},
|
|
select: { id: true },
|
|
});
|
|
if (!user) return null;
|
|
return this.prisma.membership.findFirst({
|
|
where: { organizationId, userId: user.id },
|
|
include: { user: true },
|
|
});
|
|
}
|
|
}
|
|
|
|
export function unauthorizedIfMissing<T>(value: T | null | undefined): T {
|
|
if (!value) throw new UnauthorizedException();
|
|
return value;
|
|
}
|