Files
CRM_Montzar/apps/api/src/teams/teams.service.ts
T

245 lines
6.5 KiB
TypeScript

import {
BadRequestException,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { Prisma } from '@prisma/client';
import type {
CreateTeamRequest,
SetTeamMembersRequest,
TeamDto,
TeamMemberDto,
TeamMemberRole,
UpdateTeamRequest,
} from '@mento/shared';
import { PrismaService } from '../prisma/prisma.service';
@Injectable()
export class TeamsService {
constructor(private readonly prisma: PrismaService) {}
async list(organizationId: string): Promise<TeamDto[]> {
const rows = await this.prisma.team.findMany({
where: { organizationId },
include: {
members: {
include: {
user: {
select: {
id: true,
fullName: true,
avatarStorageKey: true,
},
},
},
orderBy: [{ role: 'asc' }, { user: { fullName: 'asc' } }],
},
},
orderBy: { name: 'asc' },
});
return rows.map((row) => this.toDto(row));
}
async create(
organizationId: string,
dto: CreateTeamRequest,
): Promise<TeamDto> {
const name = dto.name?.trim();
if (!name) throw new BadRequestException('نام تیم الزامی است');
const managerIds = new Set((dto.managerIds ?? []).filter(Boolean));
const memberIds = [
...new Set([
...(dto.memberIds ?? []).filter(Boolean),
...managerIds,
]),
];
if (memberIds.length) {
await this.assertMembersInOrg(organizationId, memberIds);
}
try {
const row = await this.prisma.team.create({
data: {
organizationId,
name,
description: dto.description?.trim() ?? '',
members: memberIds.length
? {
create: memberIds.map((userId) => ({
userId,
role: managerIds.has(userId) ? 'manager' : 'member',
})),
}
: undefined,
},
include: {
members: {
include: {
user: {
select: {
id: true,
fullName: true,
avatarStorageKey: true,
},
},
},
},
},
});
return this.toDto(row);
} catch (e) {
if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === 'P2002') {
throw new BadRequestException('تیمی با این نام از قبل وجود دارد');
}
throw e;
}
}
async update(
organizationId: string,
id: string,
dto: UpdateTeamRequest,
): Promise<TeamDto> {
await this.ensureTeam(organizationId, id);
const data: Prisma.TeamUpdateInput = {};
if (dto.name !== undefined) {
const name = dto.name.trim();
if (!name) throw new BadRequestException('نام تیم الزامی است');
data.name = name;
}
if (dto.description !== undefined) {
data.description = dto.description.trim();
}
try {
await this.prisma.team.update({ where: { id }, data });
} catch (e) {
if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === 'P2002') {
throw new BadRequestException('تیمی با این نام از قبل وجود دارد');
}
throw e;
}
return this.get(organizationId, id);
}
async remove(organizationId: string, id: string): Promise<void> {
await this.ensureTeam(organizationId, id);
await this.prisma.team.delete({ where: { id } });
}
async setMembers(
organizationId: string,
id: string,
dto: SetTeamMembersRequest,
): Promise<TeamDto> {
await this.ensureTeam(organizationId, id);
const members = dto.members ?? [];
const userIds = [...new Set(members.map((m) => m.userId).filter(Boolean))];
if (userIds.length) {
await this.assertMembersInOrg(organizationId, userIds);
}
const byUser = new Map<string, TeamMemberRole>();
for (const m of members) {
if (!m.userId) continue;
byUser.set(m.userId, m.role === 'manager' ? 'manager' : 'member');
}
await this.prisma.$transaction([
this.prisma.teamMember.deleteMany({ where: { teamId: id } }),
...(byUser.size
? [
this.prisma.teamMember.createMany({
data: [...byUser.entries()].map(([userId, role]) => ({
teamId: id,
userId,
role,
})),
}),
]
: []),
]);
return this.get(organizationId, id);
}
private async get(organizationId: string, id: string): Promise<TeamDto> {
const row = await this.prisma.team.findFirst({
where: { id, organizationId },
include: {
members: {
include: {
user: {
select: {
id: true,
fullName: true,
avatarStorageKey: true,
},
},
},
orderBy: [{ role: 'asc' }, { user: { fullName: 'asc' } }],
},
},
});
if (!row) throw new NotFoundException('تیم پیدا نشد');
return this.toDto(row);
}
private async ensureTeam(organizationId: string, id: string) {
const row = await this.prisma.team.findFirst({
where: { id, organizationId },
select: { id: true },
});
if (!row) throw new NotFoundException('تیم پیدا نشد');
}
private async assertMembersInOrg(
organizationId: string,
userIds: string[],
) {
const count = await this.prisma.membership.count({
where: {
organizationId,
userId: { in: userIds },
isActive: true,
},
});
if (count !== userIds.length) {
throw new BadRequestException('برخی کاربران عضو فعال این محیط نیستند');
}
}
private toDto(row: {
id: string;
name: string;
description: string;
createdAt: Date;
updatedAt: Date;
members: Array<{
role: string;
user: {
id: string;
fullName: string;
avatarStorageKey: string | null;
};
}>;
}): TeamDto {
const members: TeamMemberDto[] = row.members.map((m) => ({
userId: m.user.id,
fullName: m.user.fullName,
role: m.role === 'manager' ? 'manager' : 'member',
hasAvatar: Boolean(m.user.avatarStorageKey),
}));
return {
id: row.id,
name: row.name,
description: row.description,
memberCount: members.length,
members,
createdAt: row.createdAt.toISOString(),
updatedAt: row.updatedAt.toISOString(),
};
}
}