feat(#119): GET /parents/dossier-famille/:numeroDossier - dossier famille complet (admin/gestionnaire)

Made-with: Cursor
This commit is contained in:
MARTIN Julien 2026-03-17 22:40:48 +01:00
parent 5390276ecd
commit f6fabc521e
3 changed files with 162 additions and 1 deletions

View File

@ -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[];
}

View File

@ -20,6 +20,7 @@ import { AuthGuard } from 'src/common/guards/auth.guard';
import { RolesGuard } from 'src/common/guards/roles.guard'; import { RolesGuard } from 'src/common/guards/roles.guard';
import { User } from 'src/common/decorators/user.decorator'; import { User } from 'src/common/decorators/user.decorator';
import { PendingFamilyDto } from './dto/pending-family.dto'; import { PendingFamilyDto } from './dto/pending-family.dto';
import { DossierFamilleCompletDto } from './dto/dossier-famille-complet.dto';
@ApiTags('Parents') @ApiTags('Parents')
@Controller('parents') @Controller('parents')
@ -39,6 +40,17 @@ export class ParentsController {
return this.parentsService.getPendingFamilies(); 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<DossierFamilleCompletDto> {
return this.parentsService.getDossierFamilleByNumero(numeroDossier);
}
@Post(':parentId/valider-dossier') @Post(':parentId/valider-dossier')
@Roles(RoleType.SUPER_ADMIN, RoleType.ADMINISTRATEUR, RoleType.GESTIONNAIRE) @Roles(RoleType.SUPER_ADMIN, RoleType.ADMINISTRATEUR, RoleType.GESTIONNAIRE)
@ApiOperation({ summary: 'Valider tout le dossier famille (les 2 parents en une fois)' }) @ApiOperation({ summary: 'Valider tout le dossier famille (les 2 parents en une fois)' })

View File

@ -5,12 +5,18 @@ import {
NotFoundException, NotFoundException,
} from '@nestjs/common'; } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm'; import { In, Repository } from 'typeorm';
import { Parents } from 'src/entities/parents.entity'; import { Parents } from 'src/entities/parents.entity';
import { RoleType, Users } from 'src/entities/users.entity'; import { RoleType, Users } from 'src/entities/users.entity';
import { CreateParentDto } from '../user/dto/create_parent.dto'; import { CreateParentDto } from '../user/dto/create_parent.dto';
import { UpdateParentsDto } from '../user/dto/update_parent.dto'; import { UpdateParentsDto } from '../user/dto/update_parent.dto';
import { PendingFamilyDto } from './dto/pending-family.dto'; import { PendingFamilyDto } from './dto/pending-family.dto';
import {
DossierFamilleCompletDto,
DossierFamilleParentDto,
DossierFamilleEnfantDto,
DossierFamillePresentationDto,
} from './dto/dossier-famille-complet.dto';
@Injectable() @Injectable()
export class ParentsService { export class ParentsService {
@ -136,6 +142,79 @@ export class ParentsService {
return []; 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<DossierFamilleCompletDto> {
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<string, DossierFamilleEnfantDto>();
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). * 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 * @throws NotFoundException si parentId n'est pas un parent