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:
parent
d832559027
commit
7e9306de01
@ -43,6 +43,8 @@ export class AuthService {
|
|||||||
private readonly usersRepo: Repository<Users>,
|
private readonly usersRepo: Repository<Users>,
|
||||||
@InjectRepository(Children)
|
@InjectRepository(Children)
|
||||||
private readonly childrenRepo: Repository<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.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);
|
const coParentExiste = await this.usersService.findByEmailOrNull(dto.co_parent_email);
|
||||||
if (coParentExiste) {
|
if (coParentExiste) {
|
||||||
throw new ConflictException('L\'email du co-parent est déjà utilisé');
|
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à');
|
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>(
|
const joursExpirationToken = await this.appConfigService.get<number>(
|
||||||
'password_reset_token_expiry_days',
|
'password_reset_token_expiry_days',
|
||||||
7,
|
7,
|
||||||
|
|||||||
@ -79,7 +79,9 @@ 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[]> {
|
||||||
const raw = await this.parentsRepository.query(`
|
let raw: { libelle: string; parentIds: unknown; numero_dossier: string | null }[];
|
||||||
|
try {
|
||||||
|
raw = await this.parentsRepository.query(`
|
||||||
WITH RECURSIVE
|
WITH RECURSIVE
|
||||||
links AS (
|
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
|
SELECT p.id_utilisateur AS p1, p.id_co_parent AS p2 FROM parents p WHERE p.id_co_parent IS NOT NULL
|
||||||
@ -113,13 +115,27 @@ export class ParentsService {
|
|||||||
GROUP BY fr.rep
|
GROUP BY fr.rep
|
||||||
ORDER BY libelle
|
ORDER BY libelle
|
||||||
`);
|
`);
|
||||||
return raw.map((r: { libelle: string; parentIds: unknown; numero_dossier: string | null }) => ({
|
} catch (err) {
|
||||||
libelle: r.libelle,
|
throw err;
|
||||||
parentIds: Array.isArray(r.parentIds) ? r.parentIds.map(String) : [],
|
}
|
||||||
|
if (!Array.isArray(raw)) return [];
|
||||||
|
return raw.map((r) => ({
|
||||||
|
libelle: r.libelle ?? '',
|
||||||
|
parentIds: this.normalizeParentIds(r.parentIds),
|
||||||
numero_dossier: r.numero_dossier ?? null,
|
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).
|
* 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
|
||||||
|
|||||||
@ -33,13 +33,13 @@
|
|||||||
| 20 | [Backend] API Inscription Parent (étape 3 - Enfants) | ✅ Terminé |
|
| 20 | [Backend] API Inscription Parent (étape 3 - Enfants) | ✅ Terminé |
|
||||||
| 21 | [Backend] API Inscription Parent (étape 4-6 - Finalisation) | ✅ Terminé |
|
| 21 | [Backend] API Inscription Parent (étape 4-6 - Finalisation) | ✅ Terminé |
|
||||||
| 24 | [Backend] API Création mot de passe | Ouvert |
|
| 24 | [Backend] API Création mot de passe | Ouvert |
|
||||||
| 25 | [Backend] API Liste comptes en attente | Ouvert |
|
| 25 | [Backend] API Liste comptes en attente | ✅ Fermé (obsolète, couvert #103-#111) |
|
||||||
| 26 | [Backend] API Validation/Refus comptes | Ouvert |
|
| 26 | [Backend] API Validation/Refus comptes | ✅ Fermé (obsolète, couvert #103-#111) |
|
||||||
| 27 | [Backend] Service Email - Installation Nodemailer | Ouvert |
|
| 27 | [Backend] Service Email - Installation Nodemailer | ✅ Fermé (obsolète, couvert #103-#111) |
|
||||||
| 28 | [Backend] Templates Email - Validation | Ouvert |
|
| 28 | [Backend] Templates Email - Validation | Ouvert |
|
||||||
| 29 | [Backend] Templates Email - Refus | Ouvert |
|
| 29 | [Backend] Templates Email - Refus | ✅ Fermé (obsolète, couvert #103-#111) |
|
||||||
| 30 | [Backend] Connexion - Vérification statut | Ouvert |
|
| 30 | [Backend] Connexion - Vérification statut | ✅ Fermé (obsolète, couvert #103-#111) |
|
||||||
| 31 | [Backend] Changement MDP obligatoire première connexion | Ouvert |
|
| 31 | [Backend] Changement MDP obligatoire première connexion | ✅ Terminé |
|
||||||
| 32 | [Backend] Service Documents Légaux | Ouvert |
|
| 32 | [Backend] Service Documents Légaux | Ouvert |
|
||||||
| 33 | [Backend] API Documents Légaux | Ouvert |
|
| 33 | [Backend] API Documents Légaux | Ouvert |
|
||||||
| 34 | [Backend] Traçabilité acceptations documents | Ouvert |
|
| 34 | [Backend] Traçabilité acceptations documents | Ouvert |
|
||||||
@ -53,8 +53,8 @@
|
|||||||
| 42 | [Frontend] Inscription AM - Finalisation | ✅ Terminé |
|
| 42 | [Frontend] Inscription AM - Finalisation | ✅ Terminé |
|
||||||
| 43 | [Frontend] Écran Création Mot de Passe | Ouvert |
|
| 43 | [Frontend] Écran Création Mot de Passe | Ouvert |
|
||||||
| 44 | [Frontend] Dashboard Gestionnaire - Structure | ✅ Terminé |
|
| 44 | [Frontend] Dashboard Gestionnaire - Structure | ✅ Terminé |
|
||||||
| 45 | [Frontend] Dashboard Gestionnaire - Liste Parents | Ouvert |
|
| 45 | [Frontend] Dashboard Gestionnaire - Liste Parents | ✅ Fermé (obsolète, couvert #103-#111) |
|
||||||
| 46 | [Frontend] Dashboard Gestionnaire - Liste AM | Ouvert |
|
| 46 | [Frontend] Dashboard Gestionnaire - Liste AM | ✅ Fermé (obsolète, couvert #103-#111) |
|
||||||
| 47 | [Frontend] Écran Changement MDP Obligatoire | Ouvert |
|
| 47 | [Frontend] Écran Changement MDP Obligatoire | Ouvert |
|
||||||
| 48 | [Frontend] Gestion Erreurs & Messages | Ouvert |
|
| 48 | [Frontend] Gestion Erreurs & Messages | Ouvert |
|
||||||
| 49 | [Frontend] Écran Gestion Documents Légaux (Admin) | Ouvert |
|
| 49 | [Frontend] Écran Gestion Documents Légaux (Admin) | Ouvert |
|
||||||
@ -641,17 +641,18 @@ Modifier l'endpoint de connexion pour bloquer les comptes en attente ou suspendu
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Ticket #31 : [Backend] Changement MDP obligatoire première connexion
|
### Ticket #31 : [Backend] Changement MDP obligatoire première connexion ✅
|
||||||
**Estimation** : 2h
|
**Estimation** : 2h
|
||||||
**Labels** : `backend`, `p2`, `auth`, `security`
|
**Labels** : `backend`, `p2`, `auth`, `security`
|
||||||
|
**Statut** : ✅ TERMINÉ
|
||||||
|
|
||||||
**Description** :
|
**Description** :
|
||||||
Implémenter le changement de mot de passe obligatoire pour les gestionnaires/admins à la première connexion.
|
Implémenter le changement de mot de passe obligatoire pour les gestionnaires/admins à la première connexion.
|
||||||
|
|
||||||
**Tâches** :
|
**Tâches** :
|
||||||
- [ ] Endpoint `POST /api/v1/auth/change-password-required`
|
- [x] Endpoint `POST /api/v1/auth/change-password-required`
|
||||||
- [ ] Vérification flag `changement_mdp_obligatoire`
|
- [x] Vérification flag `changement_mdp_obligatoire`
|
||||||
- [ ] Mise à jour flag après changement
|
- [x] Mise à jour flag après changement
|
||||||
- [ ] Tests unitaires
|
- [ ] Tests unitaires
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user