feat: enrichissement GET pending-families (date_soumission, nombre_enfants, emails)
Made-with: Cursor
This commit is contained in:
parent
76ba28a7ce
commit
cf9cd495bf
@ -1,4 +1,4 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
|
||||||
export class PendingFamilyDto {
|
export class PendingFamilyDto {
|
||||||
@ApiProperty({ example: 'Famille Dupont', description: 'Libellé affiché pour la famille' })
|
@ApiProperty({ example: 'Famille Dupont', description: 'Libellé affiché pour la famille' })
|
||||||
@ -17,4 +17,24 @@ export class PendingFamilyDto {
|
|||||||
description: 'Numéro de dossier famille (format AAAA-NNNNNN)',
|
description: 'Numéro de dossier famille (format AAAA-NNNNNN)',
|
||||||
})
|
})
|
||||||
numero_dossier: string | null;
|
numero_dossier: string | null;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
nullable: true,
|
||||||
|
example: '2026-01-12T10:00:00.000Z',
|
||||||
|
description: 'Date de référence dossier soumis / en attente : MIN(cree_le) des parents en_attente du groupe (ISO 8601)',
|
||||||
|
})
|
||||||
|
date_soumission: string | null;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
example: 3,
|
||||||
|
description: 'Nombre d’enfants distincts liés aux parents de la famille (enfants_parents)',
|
||||||
|
})
|
||||||
|
nombre_enfants: number;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
type: [String],
|
||||||
|
example: ['parent1@example.com', 'parent2@example.com'],
|
||||||
|
description: 'Emails des parents du groupe (ordre stable : nom, prénom)',
|
||||||
|
})
|
||||||
|
emails?: string[];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,7 +34,7 @@ export class ParentsController {
|
|||||||
@Get('pending-families')
|
@Get('pending-families')
|
||||||
@Roles(RoleType.SUPER_ADMIN, RoleType.ADMINISTRATEUR, RoleType.GESTIONNAIRE)
|
@Roles(RoleType.SUPER_ADMIN, RoleType.ADMINISTRATEUR, RoleType.GESTIONNAIRE)
|
||||||
@ApiOperation({ summary: 'Liste des familles en attente (une entrée par famille)' })
|
@ApiOperation({ summary: 'Liste des familles en attente (une entrée par famille)' })
|
||||||
@ApiResponse({ status: 200, description: 'Liste des familles (libellé, parentIds, numero_dossier)', type: [PendingFamilyDto] })
|
@ApiResponse({ status: 200, description: 'Liste des familles (libellé, parentIds, numero_dossier, date_soumission, nombre_enfants, emails)', type: [PendingFamilyDto] })
|
||||||
@ApiResponse({ status: 403, description: 'Accès refusé' })
|
@ApiResponse({ status: 403, description: 'Accès refusé' })
|
||||||
getPendingFamilies(): Promise<PendingFamilyDto[]> {
|
getPendingFamilies(): Promise<PendingFamilyDto[]> {
|
||||||
return this.parentsService.getPendingFamilies();
|
return this.parentsService.getPendingFamilies();
|
||||||
|
|||||||
@ -87,7 +87,14 @@ export class ParentsService {
|
|||||||
* Uniquement les parents dont l'utilisateur a statut = en_attente.
|
* Uniquement les parents dont l'utilisateur a statut = en_attente.
|
||||||
*/
|
*/
|
||||||
async getPendingFamilies(): Promise<PendingFamilyDto[]> {
|
async getPendingFamilies(): Promise<PendingFamilyDto[]> {
|
||||||
let raw: { libelle: string; parentIds: unknown; numero_dossier: string | null }[];
|
let raw: {
|
||||||
|
libelle: string;
|
||||||
|
parentIds: unknown;
|
||||||
|
numero_dossier: string | null;
|
||||||
|
date_soumission: Date | string | null;
|
||||||
|
nombre_enfants: string | number | null;
|
||||||
|
emails: unknown;
|
||||||
|
}[];
|
||||||
try {
|
try {
|
||||||
raw = await this.parentsRepository.query(`
|
raw = await this.parentsRepository.query(`
|
||||||
WITH RECURSIVE
|
WITH RECURSIVE
|
||||||
@ -115,7 +122,16 @@ export class ParentsService {
|
|||||||
SELECT
|
SELECT
|
||||||
'Famille ' || string_agg(u.nom, ' - ' ORDER BY u.nom, u.prenom) AS libelle,
|
'Famille ' || string_agg(u.nom, ' - ' ORDER BY u.nom, u.prenom) AS libelle,
|
||||||
array_agg(DISTINCT p.id_utilisateur ORDER BY p.id_utilisateur) AS "parentIds",
|
array_agg(DISTINCT p.id_utilisateur ORDER BY p.id_utilisateur) AS "parentIds",
|
||||||
(array_agg(p.numero_dossier))[1] AS numero_dossier
|
(array_agg(p.numero_dossier))[1] AS numero_dossier,
|
||||||
|
MIN(u.cree_le) AS date_soumission,
|
||||||
|
COALESCE((
|
||||||
|
SELECT COUNT(DISTINCT ep.id_enfant)::int
|
||||||
|
FROM enfants_parents ep
|
||||||
|
WHERE ep.id_parent IN (
|
||||||
|
SELECT frx.id FROM family_rep frx WHERE frx.rep = fr.rep
|
||||||
|
)
|
||||||
|
), 0) AS nombre_enfants,
|
||||||
|
array_agg(u.email ORDER BY u.nom, u.prenom) AS emails
|
||||||
FROM family_rep fr
|
FROM family_rep fr
|
||||||
JOIN parents p ON p.id_utilisateur = fr.id
|
JOIN parents p ON p.id_utilisateur = fr.id
|
||||||
JOIN utilisateurs u ON u.id = p.id_utilisateur
|
JOIN utilisateurs u ON u.id = p.id_utilisateur
|
||||||
@ -131,9 +147,34 @@ export class ParentsService {
|
|||||||
libelle: r.libelle ?? '',
|
libelle: r.libelle ?? '',
|
||||||
parentIds: this.normalizeParentIds(r.parentIds),
|
parentIds: this.normalizeParentIds(r.parentIds),
|
||||||
numero_dossier: r.numero_dossier ?? null,
|
numero_dossier: r.numero_dossier ?? null,
|
||||||
|
date_soumission: this.toIsoDateTimeOrNull(r.date_soumission),
|
||||||
|
nombre_enfants: this.normalizeNombreEnfants(r.nombre_enfants),
|
||||||
|
emails: this.normalizeEmails(r.emails),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private toIsoDateTimeOrNull(value: Date | string | null | undefined): string | null {
|
||||||
|
if (value == null) return null;
|
||||||
|
if (value instanceof Date) return value.toISOString();
|
||||||
|
const d = new Date(value);
|
||||||
|
return Number.isNaN(d.getTime()) ? null : d.toISOString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private normalizeNombreEnfants(v: string | number | null | undefined): number {
|
||||||
|
if (v == null) return 0;
|
||||||
|
const n = typeof v === 'number' ? v : parseInt(String(v), 10);
|
||||||
|
return Number.isFinite(n) && n >= 0 ? n : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private normalizeEmails(emails: unknown): string[] {
|
||||||
|
if (Array.isArray(emails)) return emails.map(String);
|
||||||
|
if (typeof emails === 'string') {
|
||||||
|
const s = emails.replace(/^\{|\}$/g, '').trim();
|
||||||
|
return s ? s.split(',').map((x) => x.trim()) : [];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
/** Convertit parentIds (array ou chaîne PG) en string[] pour éviter 500 si le driver renvoie une chaîne. */
|
/** Convertit parentIds (array ou chaîne PG) en string[] pour éviter 500 si le driver renvoie une chaîne. */
|
||||||
private normalizeParentIds(parentIds: unknown): string[] {
|
private normalizeParentIds(parentIds: unknown): string[] {
|
||||||
if (Array.isArray(parentIds)) return parentIds.map(String);
|
if (Array.isArray(parentIds)) return parentIds.map(String);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user