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 { 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 { 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 { 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 { await this.ensureTeam(organizationId, id); await this.prisma.team.delete({ where: { id } }); } async setMembers( organizationId: string, id: string, dto: SetTeamMembersRequest, ): Promise { 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(); 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 { 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(), }; } }