Files
siop2/apps/api/src/teams/teams.service.ts
pr-daaif 266ffaaf1b feat(r1.1): socle backend du référentiel — modèle, contrat, API, seed, tests
- migration r1_referentiel : Category (EQUIPMENT/COMPONENT_TYPE), Location
  (site → zone, lat/lng + colonne PostGIS générée geography(Point,4326)
  + index GIST), Asset (statut d'équipement), AssetComponent (organe sans
  emplacement PAR CONSTRUCTION), Team, invitation sur User ; migration
  autosuffisante (CREATE EXTENSION IF NOT EXISTS postgis)
- contrat : 21 nouvelles opérations (26 total), générateur OpenAPI étendu
  aux paramètres de chemin ; spec + client web régénérés dans ce commit
- API : modules categories/locations/assets/teams + gestion des personnes
  (liste, rôles, invitation lien 7 j à usage unique, activation publique
  qui connecte directement, mise à jour rôle/équipes) — tout sous
  @RequirePermission ; invariants en service (profondeur 2, kinds,
  catégorie jamais supprimée)
- seed : parc de la maquette validée (5 sites + 8 zones, 8 appareils,
  organes A1/B2, 9 catégories, 2 équipes) — idempotent
- 36 tests verts (couverture 96 % stmts / 85 % branches) : recette
  site→zone→appareil→organes, matrice vivante, invitation→activation ;
  smoke test sur build de prod
- CI : postgres → postgis/postgis:18-3.6 (la migration R1 l'exige)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 12:27:48 +01:00

93 lines
2.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {
ConflictException,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { Prisma } from '@prisma/client';
import type { Team, TeamCreate, TeamsResponse, TeamUpdate } from '@siop/shared';
import { PrismaService } from '../prisma/prisma.service';
const teamInclude = {
members: { include: { role: true }, orderBy: { displayName: 'asc' } },
} satisfies Prisma.TeamInclude;
type TeamRow = Prisma.TeamGetPayload<{ include: typeof teamInclude }>;
@Injectable()
export class TeamsService {
constructor(private readonly prisma: PrismaService) {}
async list(): Promise<TeamsResponse> {
const rows = await this.prisma.team.findMany({
include: teamInclude,
orderBy: { name: 'asc' },
});
return { teams: rows.map((t) => this.toDto(t)) };
}
async create(dto: TeamCreate): Promise<Team> {
try {
const created = await this.prisma.team.create({
data: {
name: dto.name,
description: dto.description,
members: dto.memberIds?.length
? { connect: dto.memberIds.map((id) => ({ id })) }
: undefined,
},
include: teamInclude,
});
return this.toDto(created);
} catch (e) {
if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === 'P2002') {
throw new ConflictException('Ce nom déquipe existe déjà');
}
throw e;
}
}
async update(id: string, dto: TeamUpdate): Promise<Team> {
try {
const updated = await this.prisma.team.update({
where: { id },
data: {
name: dto.name,
description: dto.description,
members: dto.memberIds
? { set: dto.memberIds.map((memberId) => ({ id: memberId })) }
: undefined,
},
include: teamInclude,
});
return this.toDto(updated);
} catch (e) {
if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === 'P2025') {
throw new NotFoundException('Équipe inconnue');
}
if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === 'P2002') {
throw new ConflictException('Ce nom déquipe existe déjà');
}
throw e;
}
}
private toDto(row: TeamRow): Team {
return {
id: row.id,
name: row.name,
description: row.description,
members: row.members.map((m) => ({
id: m.id,
displayName: m.displayName,
roleName: m.role.name,
initials: m.displayName
.split(/\s+/)
.filter(Boolean)
.slice(0, 2)
.map((w) => w[0]!.toUpperCase())
.join(''),
})),
};
}
}