fix: GET /parents/pending-families 500 + #113 doublons inscription
- parents.service: normaliser parentIds (array ou string PG) pour éviter 500 - auth.service: doublons à l'inscription (#113) - parent/co-parent même email, NIR et numéro agrément AM - docs: mise à jour statuts tickets Made-with: Cursor
This commit is contained in:
@@ -43,6 +43,8 @@ export class AuthService {
|
||||
private readonly usersRepo: Repository<Users>,
|
||||
@InjectRepository(Children)
|
||||
private readonly childrenRepo: Repository<Children>,
|
||||
@InjectRepository(AssistanteMaternelle)
|
||||
private readonly assistantesMaternellesRepo: Repository<AssistanteMaternelle>,
|
||||
) { }
|
||||
|
||||
/**
|
||||
@@ -189,6 +191,11 @@ export class AuthService {
|
||||
}
|
||||
|
||||
if (dto.co_parent_email) {
|
||||
if (dto.email.trim().toLowerCase() === dto.co_parent_email.trim().toLowerCase()) {
|
||||
throw new BadRequestException(
|
||||
'L\'email du parent et du co-parent doivent être différents.',
|
||||
);
|
||||
}
|
||||
const coParentExiste = await this.usersService.findByEmailOrNull(dto.co_parent_email);
|
||||
if (coParentExiste) {
|
||||
throw new ConflictException('L\'email du co-parent est déjà utilisé');
|
||||
@@ -360,6 +367,27 @@ export class AuthService {
|
||||
throw new ConflictException('Un compte avec cet email existe déjà');
|
||||
}
|
||||
|
||||
const nirDejaUtilise = await this.assistantesMaternellesRepo.findOne({
|
||||
where: { nir: nirNormalized },
|
||||
});
|
||||
if (nirDejaUtilise) {
|
||||
throw new ConflictException(
|
||||
'Un compte assistante maternelle avec ce numéro NIR existe déjà.',
|
||||
);
|
||||
}
|
||||
|
||||
const numeroAgrement = (dto.numero_agrement || '').trim();
|
||||
if (numeroAgrement) {
|
||||
const agrementDejaUtilise = await this.assistantesMaternellesRepo.findOne({
|
||||
where: { approval_number: numeroAgrement },
|
||||
});
|
||||
if (agrementDejaUtilise) {
|
||||
throw new ConflictException(
|
||||
'Un compte assistante maternelle avec ce numéro d\'agrément existe déjà.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const joursExpirationToken = await this.appConfigService.get<number>(
|
||||
'password_reset_token_expiry_days',
|
||||
7,
|
||||
|
||||
@@ -79,47 +79,63 @@ export class ParentsService {
|
||||
* Uniquement les parents dont l'utilisateur a statut = en_attente.
|
||||
*/
|
||||
async getPendingFamilies(): Promise<PendingFamilyDto[]> {
|
||||
const raw = await this.parentsRepository.query(`
|
||||
WITH RECURSIVE
|
||||
links AS (
|
||||
SELECT p.id_utilisateur AS p1, p.id_co_parent AS p2 FROM parents p WHERE p.id_co_parent IS NOT NULL
|
||||
UNION ALL
|
||||
SELECT p.id_co_parent AS p1, p.id_utilisateur AS p2 FROM parents p WHERE p.id_co_parent IS NOT NULL
|
||||
UNION ALL
|
||||
SELECT ep1.id_parent AS p1, ep2.id_parent AS p2
|
||||
FROM enfants_parents ep1
|
||||
JOIN enfants_parents ep2 ON ep2.id_enfant = ep1.id_enfant AND ep1.id_parent < ep2.id_parent
|
||||
UNION ALL
|
||||
SELECT ep2.id_parent AS p1, ep1.id_parent AS p2
|
||||
FROM enfants_parents ep1
|
||||
JOIN enfants_parents ep2 ON ep2.id_enfant = ep1.id_enfant AND ep1.id_parent < ep2.id_parent
|
||||
),
|
||||
rec AS (
|
||||
SELECT id_utilisateur AS id, id_utilisateur AS rep FROM parents
|
||||
UNION
|
||||
SELECT l.p2 AS id, LEAST(rec_alias.rep, l.p2) AS rep FROM links l JOIN rec rec_alias ON rec_alias.id = l.p1
|
||||
),
|
||||
family_rep AS (
|
||||
SELECT id, (MIN(rep::text))::uuid AS rep FROM rec GROUP BY id
|
||||
)
|
||||
SELECT
|
||||
'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(p.numero_dossier))[1] AS numero_dossier
|
||||
FROM family_rep fr
|
||||
JOIN parents p ON p.id_utilisateur = fr.id
|
||||
JOIN utilisateurs u ON u.id = p.id_utilisateur
|
||||
WHERE u.role = 'parent' AND u.statut = 'en_attente'
|
||||
GROUP BY fr.rep
|
||||
ORDER BY libelle
|
||||
`);
|
||||
return raw.map((r: { libelle: string; parentIds: unknown; numero_dossier: string | null }) => ({
|
||||
libelle: r.libelle,
|
||||
parentIds: Array.isArray(r.parentIds) ? r.parentIds.map(String) : [],
|
||||
let raw: { libelle: string; parentIds: unknown; numero_dossier: string | null }[];
|
||||
try {
|
||||
raw = await this.parentsRepository.query(`
|
||||
WITH RECURSIVE
|
||||
links AS (
|
||||
SELECT p.id_utilisateur AS p1, p.id_co_parent AS p2 FROM parents p WHERE p.id_co_parent IS NOT NULL
|
||||
UNION ALL
|
||||
SELECT p.id_co_parent AS p1, p.id_utilisateur AS p2 FROM parents p WHERE p.id_co_parent IS NOT NULL
|
||||
UNION ALL
|
||||
SELECT ep1.id_parent AS p1, ep2.id_parent AS p2
|
||||
FROM enfants_parents ep1
|
||||
JOIN enfants_parents ep2 ON ep2.id_enfant = ep1.id_enfant AND ep1.id_parent < ep2.id_parent
|
||||
UNION ALL
|
||||
SELECT ep2.id_parent AS p1, ep1.id_parent AS p2
|
||||
FROM enfants_parents ep1
|
||||
JOIN enfants_parents ep2 ON ep2.id_enfant = ep1.id_enfant AND ep1.id_parent < ep2.id_parent
|
||||
),
|
||||
rec AS (
|
||||
SELECT id_utilisateur AS id, id_utilisateur AS rep FROM parents
|
||||
UNION
|
||||
SELECT l.p2 AS id, LEAST(rec_alias.rep, l.p2) AS rep FROM links l JOIN rec rec_alias ON rec_alias.id = l.p1
|
||||
),
|
||||
family_rep AS (
|
||||
SELECT id, (MIN(rep::text))::uuid AS rep FROM rec GROUP BY id
|
||||
)
|
||||
SELECT
|
||||
'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(p.numero_dossier))[1] AS numero_dossier
|
||||
FROM family_rep fr
|
||||
JOIN parents p ON p.id_utilisateur = fr.id
|
||||
JOIN utilisateurs u ON u.id = p.id_utilisateur
|
||||
WHERE u.role = 'parent' AND u.statut = 'en_attente'
|
||||
GROUP BY fr.rep
|
||||
ORDER BY libelle
|
||||
`);
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
if (!Array.isArray(raw)) return [];
|
||||
return raw.map((r) => ({
|
||||
libelle: r.libelle ?? '',
|
||||
parentIds: this.normalizeParentIds(r.parentIds),
|
||||
numero_dossier: r.numero_dossier ?? null,
|
||||
}));
|
||||
}
|
||||
|
||||
/** Convertit parentIds (array ou chaîne PG) en string[] pour éviter 500 si le driver renvoie une chaîne. */
|
||||
private normalizeParentIds(parentIds: unknown): string[] {
|
||||
if (Array.isArray(parentIds)) return parentIds.map(String);
|
||||
if (typeof parentIds === 'string') {
|
||||
const s = parentIds.replace(/^\{|\}$/g, '').trim();
|
||||
return s ? s.split(',').map((x) => x.trim()) : [];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
Reference in New Issue
Block a user