diff --git a/backend/src/modules/absences-garde/absences-garde.controller.spec.ts b/backend/src/modules/absences-garde/absences-garde.controller.spec.ts deleted file mode 100644 index 84844d7..0000000 --- a/backend/src/modules/absences-garde/absences-garde.controller.spec.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { AbsencesGardeController } from './absences-garde.controller'; -import { AbsencesGardeService } from './absences-garde.service'; -import { AuthGuard } from 'src/common/guards/auth.guard'; -import { RolesGuard } from 'src/common/guards/roles.guard'; -import { RoleType } from 'src/entities/users.entity'; -import { TypeAbsenceGardeType } from 'src/entities/absences_garde.entity'; - -describe('AbsencesGardeController (#172)', () => { - let controller: AbsencesGardeController; - const serviceMock = { - lister: jest.fn(), - creer: jest.fn(), - maj: jest.fn(), - supprimer: jest.fn(), - }; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [AbsencesGardeController], - providers: [{ provide: AbsencesGardeService, useValue: serviceMock }], - }) - .overrideGuard(AuthGuard) - .useValue({ canActivate: () => true }) - .overrideGuard(RolesGuard) - .useValue({ canActivate: () => true }) - .compile(); - - controller = module.get(AbsencesGardeController); - jest.clearAllMocks(); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); - - it('lister délègue au service', async () => { - serviceMock.lister.mockResolvedValue({ items: [] }); - const res = await controller.lister( - 'u1', - RoleType.PARENT, - 'pl-1', - undefined, - undefined, - '2026-01-01', - '2026-12-31', - ); - expect(serviceMock.lister).toHaveBeenCalledWith('u1', RoleType.PARENT, { - placementId: 'pl-1', - type: undefined, - statut: undefined, - from: '2026-01-01', - to: '2026-12-31', - }); - expect(res.items).toEqual([]); - }); - - it('creer délègue au service', async () => { - serviceMock.creer.mockResolvedValue({ id: 'a1' }); - const dto = { - id_placement: 'pl-1', - type: TypeAbsenceGardeType.ABSENCE_ENFANT, - date_debut: '2026-10-01', - date_fin: '2026-10-02', - }; - await controller.creer('u1', RoleType.PARENT, dto); - expect(serviceMock.creer).toHaveBeenCalledWith( - 'u1', - RoleType.PARENT, - dto, - ); - }); -}); diff --git a/backend/src/modules/absences-garde/absences-garde.controller.ts b/backend/src/modules/absences-garde/absences-garde.controller.ts deleted file mode 100644 index 02163ca..0000000 --- a/backend/src/modules/absences-garde/absences-garde.controller.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { - Body, - Controller, - Delete, - Get, - HttpCode, - HttpStatus, - Param, - ParseUUIDPipe, - Patch, - Post, - Query, - UseGuards, -} from '@nestjs/common'; -import { - ApiBearerAuth, - ApiBody, - ApiOperation, - ApiQuery, - ApiResponse, - ApiTags, -} from '@nestjs/swagger'; -import { AuthGuard } from 'src/common/guards/auth.guard'; -import { RolesGuard } from 'src/common/guards/roles.guard'; -import { Roles } from 'src/common/decorators/roles.decorator'; -import { User } from 'src/common/decorators/user.decorator'; -import { - StatutAbsenceGardeType, - TypeAbsenceGardeType, -} from 'src/entities/absences_garde.entity'; -import { RoleType } from 'src/entities/users.entity'; -import { AbsencesGardeService } from './absences-garde.service'; -import { - AbsenceGardeDto, - CreerAbsenceGardeDto, - ListeAbsencesGardeDto, - MajAbsenceGardeDto, -} from './dto/absences-garde.dto'; - -@ApiTags('Absences garde') -@ApiBearerAuth('access-token') -@Controller('absences-garde') -@UseGuards(AuthGuard, RolesGuard) -export class AbsencesGardeController { - constructor(private readonly absencesGardeService: AbsencesGardeService) {} - - @Get() - @Roles(RoleType.PARENT, RoleType.ASSISTANTE_MATERNELLE) - @ApiOperation({ - summary: 'Lister les absences / congés / arrêts — ticket #172', - description: - 'Sans placementId : tous les placements du user (parent = famille multi-AM ; AM = tous accueils). ' + - 'Avec placementId : un couple enfant–AM.', - }) - @ApiQuery({ name: 'placementId', required: false }) - @ApiQuery({ name: 'type', required: false, enum: TypeAbsenceGardeType }) - @ApiQuery({ name: 'statut', required: false, enum: StatutAbsenceGardeType }) - @ApiQuery({ name: 'from', required: false, description: 'YYYY-MM-DD' }) - @ApiQuery({ name: 'to', required: false, description: 'YYYY-MM-DD' }) - @ApiResponse({ status: 200, type: ListeAbsencesGardeDto }) - lister( - @User('id') userId: string, - @User('role') role: RoleType, - @Query('placementId') placementId?: string, - @Query('type') type?: TypeAbsenceGardeType, - @Query('statut') statut?: StatutAbsenceGardeType, - @Query('from') from?: string, - @Query('to') to?: string, - ): Promise { - return this.absencesGardeService.lister(userId, role, { - placementId, - type, - statut, - from, - to, - }); - } - - @Post() - @Roles(RoleType.PARENT, RoleType.ASSISTANTE_MATERNELLE) - @ApiOperation({ summary: 'Créer une période d’absence / congé / arrêt — #172' }) - @ApiBody({ type: CreerAbsenceGardeDto }) - @ApiResponse({ status: 201, type: AbsenceGardeDto }) - @HttpCode(HttpStatus.CREATED) - creer( - @User('id') userId: string, - @User('role') role: RoleType, - @Body() dto: CreerAbsenceGardeDto, - ): Promise { - return this.absencesGardeService.creer(userId, role, dto); - } - - @Patch(':id') - @Roles(RoleType.PARENT, RoleType.ASSISTANTE_MATERNELLE) - @ApiOperation({ - summary: 'Mettre à jour dates / statut / motif — #172', - description: - 'Parent : accept/refuse congé (motivation si refus), ack arrêt. ' + - 'AM : modifier dates (en attente / refuse / accepté), republication après refus.', - }) - @ApiBody({ type: MajAbsenceGardeDto }) - @ApiResponse({ status: 200, type: AbsenceGardeDto }) - maj( - @User('id') userId: string, - @User('role') role: RoleType, - @Param('id', ParseUUIDPipe) id: string, - @Body() dto: MajAbsenceGardeDto, - ): Promise { - return this.absencesGardeService.maj(userId, role, id, dto); - } - - @Delete(':id') - @Roles(RoleType.PARENT, RoleType.ASSISTANTE_MATERNELLE) - @HttpCode(HttpStatus.NO_CONTENT) - @ApiOperation({ summary: 'Supprimer une absence (hard delete) — #172' }) - @ApiResponse({ status: 204 }) - async supprimer( - @User('id') userId: string, - @User('role') role: RoleType, - @Param('id', ParseUUIDPipe) id: string, - ): Promise { - await this.absencesGardeService.supprimer(userId, role, id); - } -} diff --git a/backend/src/modules/absences-garde/absences-garde.module.ts b/backend/src/modules/absences-garde/absences-garde.module.ts deleted file mode 100644 index 174fa37..0000000 --- a/backend/src/modules/absences-garde/absences-garde.module.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { AbsencesGarde } from 'src/entities/absences_garde.entity'; -import { AmChildren } from 'src/entities/am_children.entity'; -import { ParentsChildren } from 'src/entities/parents_children.entity'; -import { AbsencesGardeController } from './absences-garde.controller'; -import { AbsencesGardeService } from './absences-garde.service'; - -@Module({ - imports: [ - TypeOrmModule.forFeature([AbsencesGarde, AmChildren, ParentsChildren]), - ], - controllers: [AbsencesGardeController], - providers: [AbsencesGardeService], - exports: [AbsencesGardeService], -}) -export class AbsencesGardeModule {} diff --git a/backend/src/modules/absences-garde/absences-garde.service.spec.ts b/backend/src/modules/absences-garde/absences-garde.service.spec.ts deleted file mode 100644 index 1c40cd7..0000000 --- a/backend/src/modules/absences-garde/absences-garde.service.spec.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { getRepositoryToken } from '@nestjs/typeorm'; -import { ForbiddenException, BadRequestException } from '@nestjs/common'; -import { AbsencesGardeService } from './absences-garde.service'; -import { - AbsencesGarde, - StatutAbsenceGardeType, - TypeAbsenceGardeType, -} from 'src/entities/absences_garde.entity'; -import { AmChildren } from 'src/entities/am_children.entity'; -import { ParentsChildren } from 'src/entities/parents_children.entity'; -import { RoleType } from 'src/entities/users.entity'; - -describe('AbsencesGardeService (#172)', () => { - let service: AbsencesGardeService; - const absencesRepo = { - create: jest.fn((x) => x), - save: jest.fn(async (x) => ({ - ...x, - id: x.id ?? 'abs-1', - cree_le: new Date(), - modifie_le: new Date(), - })), - findOne: jest.fn(), - delete: jest.fn(), - createQueryBuilder: jest.fn(), - }; - const amChildrenRepo = { - find: jest.fn(), - findOne: jest.fn(), - }; - const parentsChildrenRepo = { - find: jest.fn(), - findOne: jest.fn(), - }; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ - AbsencesGardeService, - { provide: getRepositoryToken(AbsencesGarde), useValue: absencesRepo }, - { provide: getRepositoryToken(AmChildren), useValue: amChildrenRepo }, - { - provide: getRepositoryToken(ParentsChildren), - useValue: parentsChildrenRepo, - }, - ], - }).compile(); - service = module.get(AbsencesGardeService); - jest.clearAllMocks(); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); - - it('parent crée absence_enfant → statut accepte', async () => { - amChildrenRepo.findOne.mockResolvedValue({ - id: 'pl-1', - amId: 'am-1', - enfantId: 'e-1', - date_fin: null, - }); - parentsChildrenRepo.findOne.mockResolvedValue({ - parentId: 'p-1', - enfantId: 'e-1', - }); - absencesRepo.findOne.mockResolvedValue({ - id: 'abs-1', - id_placement: 'pl-1', - type: TypeAbsenceGardeType.ABSENCE_ENFANT, - date_debut: '2026-10-01', - date_fin: '2026-10-02', - statut: StatutAbsenceGardeType.ACCEPTE, - expire_at: new Date('9999-12-31'), - cree_le: new Date(), - modifie_le: new Date(), - placement: { - enfantId: 'e-1', - amId: 'am-1', - child: { id: 'e-1', first_name: 'Léo' }, - am: { user: { prenom: 'Marie', nom: 'AM' } }, - }, - }); - - const res = await service.creer('p-1', RoleType.PARENT, { - id_placement: 'pl-1', - type: TypeAbsenceGardeType.ABSENCE_ENFANT, - date_debut: '2026-10-01', - date_fin: '2026-10-02', - }); - - expect(absencesRepo.save).toHaveBeenCalled(); - const savedArg = absencesRepo.save.mock.calls[0][0]; - expect(savedArg.statut).toBe(StatutAbsenceGardeType.ACCEPTE); - expect(res.type).toBe(TypeAbsenceGardeType.ABSENCE_ENFANT); - }); - - it('parent ne peut pas créer un congé AM', async () => { - amChildrenRepo.findOne.mockResolvedValue({ - id: 'pl-1', - amId: 'am-1', - enfantId: 'e-1', - date_fin: null, - }); - parentsChildrenRepo.findOne.mockResolvedValue({ - parentId: 'p-1', - enfantId: 'e-1', - }); - await expect( - service.creer('p-1', RoleType.PARENT, { - id_placement: 'pl-1', - type: TypeAbsenceGardeType.CONGE_AM, - date_debut: '2026-10-01', - date_fin: '2026-10-05', - }), - ).rejects.toBeInstanceOf(ForbiddenException); - }); - - it('refus congé sans motif → BadRequest', async () => { - absencesRepo.findOne.mockResolvedValue({ - id: 'abs-1', - id_placement: 'pl-1', - type: TypeAbsenceGardeType.CONGE_AM, - date_debut: '2026-10-01', - date_fin: '2026-10-05', - statut: StatutAbsenceGardeType.EN_ATTENTE, - expire_at: new Date(), - }); - amChildrenRepo.findOne.mockResolvedValue({ - id: 'pl-1', - amId: 'am-1', - enfantId: 'e-1', - date_fin: null, - }); - parentsChildrenRepo.findOne.mockResolvedValue({ - parentId: 'p-1', - enfantId: 'e-1', - }); - - await expect( - service.maj('p-1', RoleType.PARENT, 'abs-1', { - statut: StatutAbsenceGardeType.REFUSE, - }), - ).rejects.toBeInstanceOf(BadRequestException); - }); - - it('dates invalides → BadRequest', async () => { - amChildrenRepo.findOne.mockResolvedValue({ - id: 'pl-1', - amId: 'am-1', - enfantId: 'e-1', - date_fin: null, - }); - parentsChildrenRepo.findOne.mockResolvedValue({ - parentId: 'p-1', - enfantId: 'e-1', - }); - await expect( - service.creer('p-1', RoleType.PARENT, { - id_placement: 'pl-1', - type: TypeAbsenceGardeType.ABSENCE_ENFANT, - date_debut: '2026-10-10', - date_fin: '2026-10-01', - }), - ).rejects.toBeInstanceOf(BadRequestException); - }); -}); diff --git a/backend/src/modules/absences-garde/absences-garde.service.ts b/backend/src/modules/absences-garde/absences-garde.service.ts deleted file mode 100644 index a3bf9f5..0000000 --- a/backend/src/modules/absences-garde/absences-garde.service.ts +++ /dev/null @@ -1,420 +0,0 @@ -import { - BadRequestException, - ForbiddenException, - Injectable, - NotFoundException, -} from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { In, IsNull, Repository } from 'typeorm'; -import { - AbsencesGarde, - StatutAbsenceGardeType, - TypeAbsenceGardeType, -} from 'src/entities/absences_garde.entity'; -import { AmChildren } from 'src/entities/am_children.entity'; -import { ParentsChildren } from 'src/entities/parents_children.entity'; -import { RoleType } from 'src/entities/users.entity'; -import { - AbsenceGardeDto, - CreerAbsenceGardeDto, - ListeAbsencesGardeDto, - MajAbsenceGardeDto, -} from './dto/absences-garde.dto'; - -const TTL_EN_ATTENTE_MS = 15 * 24 * 60 * 60 * 1000; -const TTL_REFUSE_MS = 7 * 24 * 60 * 60 * 1000; -/** Sentinel « pas de purge » pour les périodes acceptées */ -const EXPIRE_ACCEPTE = new Date('9999-12-31T23:59:59.999Z'); - -@Injectable() -export class AbsencesGardeService { - constructor( - @InjectRepository(AbsencesGarde) - private readonly absencesRepo: Repository, - @InjectRepository(AmChildren) - private readonly amChildrenRepo: Repository, - @InjectRepository(ParentsChildren) - private readonly parentsChildrenRepo: Repository, - ) {} - - async lister( - userId: string, - role: RoleType, - opts: { - placementId?: string; - type?: TypeAbsenceGardeType; - statut?: StatutAbsenceGardeType; - from?: string; - to?: string; - }, - ): Promise { - const placementIds = await this.resolvePlacementIds( - userId, - role, - opts.placementId, - ); - if (placementIds.length === 0) { - return { items: [] }; - } - - const qb = this.absencesRepo - .createQueryBuilder('ag') - .leftJoinAndSelect('ag.placement', 'placement') - .leftJoinAndSelect('placement.child', 'child') - .leftJoinAndSelect('placement.am', 'am') - .leftJoinAndSelect('am.user', 'amUser') - .where('ag.id_placement IN (:...placementIds)', { placementIds }) - .orderBy('ag.date_debut', 'DESC'); - - if (opts.type) { - qb.andWhere('ag.type = :type', { type: opts.type }); - } - if (opts.statut) { - qb.andWhere('ag.statut = :statut', { statut: opts.statut }); - } - if (opts.from) { - qb.andWhere('ag.date_fin >= :from', { from: opts.from }); - } - if (opts.to) { - qb.andWhere('ag.date_debut <= :to', { to: opts.to }); - } - - const rows = await qb.getMany(); - return { items: rows.map((r) => this.toDto(r)) }; - } - - async creer( - userId: string, - role: RoleType, - dto: CreerAbsenceGardeDto, - ): Promise { - this.assertDates(dto.date_debut, dto.date_fin); - await this.assertCanAccessPlacement(userId, role, dto.id_placement); - this.assertCanCreateType(role, dto.type); - - const statut = this.statutInitial(dto.type); - const entity = this.absencesRepo.create({ - id_placement: dto.id_placement, - type: dto.type, - date_debut: dto.date_debut, - date_fin: dto.date_fin, - statut, - expire_at: this.expireAtFor(statut), - cree_par: userId, - motif: dto.motif?.trim() || undefined, - }); - const saved = await this.absencesRepo.save(entity); - return this.getByIdForUser(saved.id, userId, role); - } - - async maj( - userId: string, - role: RoleType, - id: string, - dto: MajAbsenceGardeDto, - ): Promise { - const row = await this.absencesRepo.findOne({ where: { id } }); - if (!row) { - throw new NotFoundException('Absence introuvable'); - } - await this.assertCanAccessPlacement(userId, role, row.id_placement); - - if (dto.date_debut !== undefined || dto.date_fin !== undefined) { - const debut = dto.date_debut ?? row.date_debut; - const fin = dto.date_fin ?? row.date_fin; - this.assertDates(debut, fin); - // Parent : peut modifier ses absences enfant - // AM : peut modifier congé/arrêt en_attente (avant accept) ou dates si créateur - this.assertCanEditDates(role, row); - row.date_debut = debut; - row.date_fin = fin; - if (row.statut === StatutAbsenceGardeType.EN_ATTENTE) { - row.expire_at = this.expireAtFor(StatutAbsenceGardeType.EN_ATTENTE); - } - } - - if (dto.statut !== undefined && dto.statut !== row.statut) { - this.assertCanChangeStatut(role, row, dto.statut, dto.motif); - if ( - dto.statut === StatutAbsenceGardeType.REFUSE && - (!dto.motif || !dto.motif.trim()) - ) { - throw new BadRequestException( - 'Une motivation est obligatoire en cas de refus', - ); - } - row.statut = dto.statut; - row.expire_at = this.expireAtFor(dto.statut); - if (dto.motif?.trim()) { - row.motif = dto.motif.trim(); - } - } else if (dto.motif !== undefined) { - row.motif = dto.motif.trim() || undefined; - } - - await this.absencesRepo.save(row); - return this.getByIdForUser(id, userId, role); - } - - async supprimer( - userId: string, - role: RoleType, - id: string, - ): Promise { - const row = await this.absencesRepo.findOne({ where: { id } }); - if (!row) { - throw new NotFoundException('Absence introuvable'); - } - await this.assertCanAccessPlacement(userId, role, row.id_placement); - - if ( - role === RoleType.PARENT && - row.type !== TypeAbsenceGardeType.ABSENCE_ENFANT - ) { - throw new ForbiddenException( - 'Un parent ne peut supprimer que les absences enfant', - ); - } - - await this.absencesRepo.delete({ id }); - } - - private async getByIdForUser( - id: string, - userId: string, - role: RoleType, - ): Promise { - const row = await this.absencesRepo.findOne({ - where: { id }, - relations: ['placement', 'placement.child', 'placement.am', 'placement.am.user'], - }); - if (!row) { - throw new NotFoundException('Absence introuvable'); - } - await this.assertCanAccessPlacement(userId, role, row.id_placement); - return this.toDto(row); - } - - private statutInitial(type: TypeAbsenceGardeType): StatutAbsenceGardeType { - if (type === TypeAbsenceGardeType.ABSENCE_ENFANT) { - return StatutAbsenceGardeType.ACCEPTE; - } - return StatutAbsenceGardeType.EN_ATTENTE; - } - - private expireAtFor(statut: StatutAbsenceGardeType): Date { - const now = Date.now(); - if (statut === StatutAbsenceGardeType.ACCEPTE) { - return EXPIRE_ACCEPTE; - } - if (statut === StatutAbsenceGardeType.REFUSE) { - return new Date(now + TTL_REFUSE_MS); - } - return new Date(now + TTL_EN_ATTENTE_MS); - } - - private assertDates(debut: string, fin: string): void { - if (fin < debut) { - throw new BadRequestException( - 'date_fin doit être supérieure ou égale à date_debut', - ); - } - } - - private assertCanCreateType(role: RoleType, type: TypeAbsenceGardeType): void { - if (role === RoleType.PARENT) { - if (type !== TypeAbsenceGardeType.ABSENCE_ENFANT) { - throw new ForbiddenException( - 'Un parent ne peut créer que des absences enfant', - ); - } - return; - } - if (role === RoleType.ASSISTANTE_MATERNELLE) { - if ( - type !== TypeAbsenceGardeType.CONGE_AM && - type !== TypeAbsenceGardeType.ARRET_MALADIE_AM - ) { - throw new ForbiddenException( - 'Une AM ne peut créer que congé ou arrêt maladie', - ); - } - return; - } - throw new ForbiddenException('Rôle non autorisé à créer une absence'); - } - - private assertCanEditDates( - role: RoleType, - row: AbsencesGarde, - ): void { - if (role === RoleType.PARENT) { - if (row.type !== TypeAbsenceGardeType.ABSENCE_ENFANT) { - throw new ForbiddenException( - 'Un parent ne peut modifier que les absences enfant', - ); - } - return; - } - if (role === RoleType.ASSISTANTE_MATERNELLE) { - if ( - row.type === TypeAbsenceGardeType.CONGE_AM || - row.type === TypeAbsenceGardeType.ARRET_MALADIE_AM - ) { - if ( - row.statut !== StatutAbsenceGardeType.EN_ATTENTE && - row.statut !== StatutAbsenceGardeType.REFUSE && - row.statut !== StatutAbsenceGardeType.ACCEPTE - ) { - throw new ForbiddenException('Statut incompatible avec une modification'); - } - // Accepté : autorisé (S2b — re-validation via cartes plus tard ; API permet update dates) - return; - } - throw new ForbiddenException('Type non modifiable par l’AM'); - } - throw new ForbiddenException('Modification non autorisée'); - } - - private assertCanChangeStatut( - role: RoleType, - row: AbsencesGarde, - next: StatutAbsenceGardeType, - _motif?: string, - ): void { - if (role === RoleType.PARENT) { - // Accept / refuse congé ; ack arrêt (accepte) - if ( - row.type === TypeAbsenceGardeType.CONGE_AM || - row.type === TypeAbsenceGardeType.ARRET_MALADIE_AM - ) { - if ( - next !== StatutAbsenceGardeType.ACCEPTE && - next !== StatutAbsenceGardeType.REFUSE - ) { - throw new BadRequestException('Transition de statut invalide'); - } - if ( - row.type === TypeAbsenceGardeType.ARRET_MALADIE_AM && - next === StatutAbsenceGardeType.REFUSE - ) { - throw new BadRequestException( - 'Un arrêt maladie ne se refuse pas (accusé seulement)', - ); - } - if (row.statut !== StatutAbsenceGardeType.EN_ATTENTE) { - throw new BadRequestException('Cette demande n’est plus en attente'); - } - return; - } - throw new ForbiddenException( - 'Pas de changement de statut sur ce type pour un parent', - ); - } - if (role === RoleType.ASSISTANTE_MATERNELLE) { - // Remise en attente après refus (republication) - if ( - row.statut === StatutAbsenceGardeType.REFUSE && - next === StatutAbsenceGardeType.EN_ATTENTE - ) { - return; - } - throw new ForbiddenException( - 'L’AM ne valide pas elle-même (sauf republication après refus)', - ); - } - throw new ForbiddenException('Changement de statut non autorisé'); - } - - private async resolvePlacementIds( - userId: string, - role: RoleType, - placementId?: string, - ): Promise { - if (placementId) { - await this.assertCanAccessPlacement(userId, role, placementId); - return [placementId]; - } - - if (role === RoleType.PARENT) { - const liens = await this.parentsChildrenRepo.find({ - where: { parentId: userId }, - select: ['enfantId'], - }); - const enfantIds = liens.map((l) => l.enfantId); - if (enfantIds.length === 0) return []; - const placements = await this.amChildrenRepo.find({ - where: { enfantId: In(enfantIds), date_fin: IsNull() }, - select: ['id'], - }); - return placements.map((p) => p.id); - } - - if (role === RoleType.ASSISTANTE_MATERNELLE) { - const placements = await this.amChildrenRepo.find({ - where: { amId: userId, date_fin: IsNull() }, - select: ['id'], - }); - return placements.map((p) => p.id); - } - - throw new ForbiddenException('Rôle non autorisé'); - } - - private async assertCanAccessPlacement( - userId: string, - role: RoleType, - placementId: string, - ): Promise { - const placement = await this.amChildrenRepo.findOne({ - where: { id: placementId, date_fin: IsNull() }, - }); - if (!placement) { - throw new NotFoundException('Placement / couple introuvable ou inactif'); - } - - if (role === RoleType.ASSISTANTE_MATERNELLE) { - if (placement.amId !== userId) { - throw new ForbiddenException('Ce placement ne vous appartient pas'); - } - return placement; - } - - if (role === RoleType.PARENT) { - const lien = await this.parentsChildrenRepo.findOne({ - where: { parentId: userId, enfantId: placement.enfantId }, - }); - if (!lien) { - throw new ForbiddenException( - 'Ce placement ne concerne pas un de vos enfants', - ); - } - return placement; - } - - throw new ForbiddenException('Rôle non autorisé'); - } - - private toDto(row: AbsencesGarde): AbsenceGardeDto { - const child = row.placement?.child; - const amUser = row.placement?.am?.user; - return { - id: row.id, - id_placement: row.id_placement, - type: row.type, - date_debut: row.date_debut, - date_fin: row.date_fin, - statut: row.statut, - expire_at: row.expire_at?.toISOString?.() ?? String(row.expire_at), - cree_par: row.cree_par ?? null, - motif: row.motif ?? null, - id_enfant: child?.id ?? row.placement?.enfantId ?? null, - prenom_enfant: child?.first_name ?? null, - id_am: row.placement?.amId ?? null, - prenom_am: amUser?.prenom ?? null, - nom_am: amUser?.nom ?? null, - cree_le: row.cree_le?.toISOString?.() ?? String(row.cree_le), - modifie_le: row.modifie_le?.toISOString?.() ?? String(row.modifie_le), - }; - } -} diff --git a/backend/src/modules/absences-garde/dto/absences-garde.dto.ts b/backend/src/modules/absences-garde/dto/absences-garde.dto.ts deleted file mode 100644 index 326be46..0000000 --- a/backend/src/modules/absences-garde/dto/absences-garde.dto.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { - IsDateString, - IsEnum, - IsOptional, - IsString, - IsUUID, - MaxLength, - MinLength, -} from 'class-validator'; -import { - StatutAbsenceGardeType, - TypeAbsenceGardeType, -} from 'src/entities/absences_garde.entity'; - -export class CreerAbsenceGardeDto { - @ApiProperty({ description: 'Placement AM↔enfant (couple)' }) - @IsUUID() - id_placement: string; - - @ApiProperty({ enum: TypeAbsenceGardeType }) - @IsEnum(TypeAbsenceGardeType) - type: TypeAbsenceGardeType; - - @ApiProperty({ example: '2026-10-01' }) - @IsDateString() - date_debut: string; - - @ApiProperty({ example: '2026-10-05' }) - @IsDateString() - date_fin: string; - - @ApiPropertyOptional() - @IsOptional() - @IsString() - @MaxLength(2000) - motif?: string; -} - -export class MajAbsenceGardeDto { - @ApiPropertyOptional({ example: '2026-10-02' }) - @IsOptional() - @IsDateString() - date_debut?: string; - - @ApiPropertyOptional({ example: '2026-10-06' }) - @IsOptional() - @IsDateString() - date_fin?: string; - - @ApiPropertyOptional({ enum: StatutAbsenceGardeType }) - @IsOptional() - @IsEnum(StatutAbsenceGardeType) - statut?: StatutAbsenceGardeType; - - @ApiPropertyOptional({ - description: 'Motivation de refus (parent) ou motif créateur', - }) - @IsOptional() - @IsString() - @MinLength(1) - @MaxLength(2000) - motif?: string; -} - -export class AbsenceGardeDto { - @ApiProperty() - id: string; - - @ApiProperty() - id_placement: string; - - @ApiProperty({ enum: TypeAbsenceGardeType }) - type: TypeAbsenceGardeType; - - @ApiProperty() - date_debut: string; - - @ApiProperty() - date_fin: string; - - @ApiProperty({ enum: StatutAbsenceGardeType }) - statut: StatutAbsenceGardeType; - - @ApiProperty() - expire_at: string; - - @ApiPropertyOptional() - cree_par?: string | null; - - @ApiPropertyOptional() - motif?: string | null; - - @ApiPropertyOptional() - id_enfant?: string | null; - - @ApiPropertyOptional() - prenom_enfant?: string | null; - - @ApiPropertyOptional() - id_am?: string | null; - - @ApiPropertyOptional() - prenom_am?: string | null; - - @ApiPropertyOptional() - nom_am?: string | null; - - @ApiProperty() - cree_le: string; - - @ApiProperty() - modifie_le: string; -} - -export class ListeAbsencesGardeDto { - @ApiProperty({ type: [AbsenceGardeDto] }) - items: AbsenceGardeDto[]; -} diff --git a/backend/src/modules/absences-garde/index.ts b/backend/src/modules/absences-garde/index.ts deleted file mode 100644 index 11a9a04..0000000 --- a/backend/src/modules/absences-garde/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { AbsencesGardeModule } from './absences-garde.module'; -export { AbsencesGardeService } from './absences-garde.service';