diff --git a/backend/src/routes/parents/dto/dossier-famille-complet.dto.ts b/backend/src/routes/parents/dto/dossier-famille-complet.dto.ts new file mode 100644 index 0000000..c1de22c --- /dev/null +++ b/backend/src/routes/parents/dto/dossier-famille-complet.dto.ts @@ -0,0 +1,70 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { StatutUtilisateurType } from 'src/entities/users.entity'; +import { StatutDossierType } from 'src/entities/dossiers.entity'; +import { StatutEnfantType } from 'src/entities/children.entity'; + +/** Parent dans le dossier famille (infos utilisateur + parent) */ +export class DossierFamilleParentDto { + @ApiProperty() + user_id: string; + @ApiProperty() + email: string; + @ApiProperty({ required: false }) + prenom?: string; + @ApiProperty({ required: false }) + nom?: string; + @ApiProperty({ required: false }) + telephone?: string; + @ApiProperty({ enum: StatutUtilisateurType }) + statut: StatutUtilisateurType; + @ApiProperty({ required: false, description: 'Id du co-parent si couple' }) + co_parent_id?: string; +} + +/** Enfant dans le dossier famille */ +export class DossierFamilleEnfantDto { + @ApiProperty() + id: string; + @ApiProperty({ required: false }) + first_name?: string; + @ApiProperty({ required: false }) + last_name?: string; + @ApiProperty({ required: false }) + birth_date?: Date; + @ApiProperty({ required: false }) + due_date?: Date; + @ApiProperty({ enum: StatutEnfantType }) + status: StatutEnfantType; +} + +/** Dossier (parent+enfant) avec presentation */ +export class DossierFamillePresentationDto { + @ApiProperty() + id: string; + @ApiProperty() + id_parent: string; + @ApiProperty() + id_enfant: string; + @ApiProperty({ required: false, description: 'Texte de présentation' }) + presentation?: string; + @ApiProperty({ required: false }) + type_contrat?: string; + @ApiProperty() + repas: boolean; + @ApiProperty({ required: false }) + budget?: number; + @ApiProperty({ enum: StatutDossierType }) + statut: StatutDossierType; +} + +/** Réponse GET /parents/dossier-famille/:numeroDossier – dossier famille complet. Ticket #119 */ +export class DossierFamilleCompletDto { + @ApiProperty({ example: '2026-000001', description: 'Numéro de dossier famille' }) + numero_dossier: string; + @ApiProperty({ type: [DossierFamilleParentDto] }) + parents: DossierFamilleParentDto[]; + @ApiProperty({ type: [DossierFamilleEnfantDto], description: 'Enfants de la famille' }) + enfants: DossierFamilleEnfantDto[]; + @ApiProperty({ type: [DossierFamillePresentationDto], description: 'Dossiers (présentation par parent/enfant)' }) + presentation: DossierFamillePresentationDto[]; +} diff --git a/backend/src/routes/parents/parents.controller.ts b/backend/src/routes/parents/parents.controller.ts index edadf2b..d9c7546 100644 --- a/backend/src/routes/parents/parents.controller.ts +++ b/backend/src/routes/parents/parents.controller.ts @@ -20,6 +20,7 @@ import { AuthGuard } from 'src/common/guards/auth.guard'; import { RolesGuard } from 'src/common/guards/roles.guard'; import { User } from 'src/common/decorators/user.decorator'; import { PendingFamilyDto } from './dto/pending-family.dto'; +import { DossierFamilleCompletDto } from './dto/dossier-famille-complet.dto'; @ApiTags('Parents') @Controller('parents') @@ -39,6 +40,17 @@ export class ParentsController { return this.parentsService.getPendingFamilies(); } + @Get('dossier-famille/:numeroDossier') + @Roles(RoleType.SUPER_ADMIN, RoleType.ADMINISTRATEUR, RoleType.GESTIONNAIRE) + @ApiOperation({ summary: 'Dossier famille complet par numéro de dossier (Ticket #119)' }) + @ApiParam({ name: 'numeroDossier', description: 'Numéro de dossier (ex: 2026-000001)' }) + @ApiResponse({ status: 200, description: 'Dossier famille (numero_dossier, parents, enfants, presentation)', type: DossierFamilleCompletDto }) + @ApiResponse({ status: 404, description: 'Aucun dossier pour ce numéro' }) + @ApiResponse({ status: 403, description: 'Accès refusé' }) + getDossierFamille(@Param('numeroDossier') numeroDossier: string): Promise { + return this.parentsService.getDossierFamilleByNumero(numeroDossier); + } + @Post(':parentId/valider-dossier') @Roles(RoleType.SUPER_ADMIN, RoleType.ADMINISTRATEUR, RoleType.GESTIONNAIRE) @ApiOperation({ summary: 'Valider tout le dossier famille (les 2 parents en une fois)' }) diff --git a/backend/src/routes/parents/parents.service.ts b/backend/src/routes/parents/parents.service.ts index 006af47..6362b13 100644 --- a/backend/src/routes/parents/parents.service.ts +++ b/backend/src/routes/parents/parents.service.ts @@ -5,12 +5,18 @@ import { NotFoundException, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; +import { In, Repository } from 'typeorm'; import { Parents } from 'src/entities/parents.entity'; import { RoleType, Users } from 'src/entities/users.entity'; import { CreateParentDto } from '../user/dto/create_parent.dto'; import { UpdateParentsDto } from '../user/dto/update_parent.dto'; import { PendingFamilyDto } from './dto/pending-family.dto'; +import { + DossierFamilleCompletDto, + DossierFamilleParentDto, + DossierFamilleEnfantDto, + DossierFamillePresentationDto, +} from './dto/dossier-famille-complet.dto'; @Injectable() export class ParentsService { @@ -136,6 +142,79 @@ export class ParentsService { return []; } + /** + * Dossier famille complet par numéro de dossier. Ticket #119. + * Rôles : admin, gestionnaire. + * @throws NotFoundException si aucun parent avec ce numéro de dossier + */ + async getDossierFamilleByNumero(numeroDossier: string): Promise { + const num = numeroDossier?.trim(); + if (!num) { + throw new NotFoundException('Numéro de dossier requis.'); + } + const firstParent = await this.parentsRepository.findOne({ + where: { numero_dossier: num }, + relations: ['user'], + }); + if (!firstParent || !firstParent.user) { + throw new NotFoundException('Aucun dossier famille trouvé pour ce numéro.'); + } + const familyUserIds = await this.getFamilyUserIds(firstParent.user_id); + const parents = await this.parentsRepository.find({ + where: { user_id: In(familyUserIds) }, + relations: ['user', 'co_parent', 'parentChildren', 'parentChildren.child', 'dossiers', 'dossiers.child'], + }); + const enfantsMap = new Map(); + const presentationList: DossierFamillePresentationDto[] = []; + for (const p of parents) { + // Enfants via parentChildren + if (p.parentChildren) { + for (const pc of p.parentChildren) { + if (pc.child && !enfantsMap.has(pc.child.id)) { + enfantsMap.set(pc.child.id, { + id: pc.child.id, + first_name: pc.child.first_name, + last_name: pc.child.last_name, + birth_date: pc.child.birth_date, + due_date: pc.child.due_date, + status: pc.child.status, + }); + } + } + } + // Dossiers (présentation) + if (p.dossiers) { + for (const d of p.dossiers) { + presentationList.push({ + id: d.id, + id_parent: p.user_id, + id_enfant: d.child?.id ?? '', + presentation: d.presentation, + type_contrat: d.type_contrat, + repas: d.meals, + budget: d.budget != null ? Number(d.budget) : undefined, + statut: d.status, + }); + } + } + } + const parentsDto: DossierFamilleParentDto[] = parents.map((p) => ({ + user_id: p.user_id, + email: p.user.email, + prenom: p.user.prenom, + nom: p.user.nom, + telephone: p.user.telephone, + statut: p.user.statut, + co_parent_id: p.co_parent?.id, + })); + return { + numero_dossier: num, + parents: parentsDto, + enfants: Array.from(enfantsMap.values()), + presentation: presentationList, + }; + } + /** * Retourne les user_id de tous les parents de la même famille (co_parent ou enfants partagés). * @throws NotFoundException si parentId n'est pas un parent