feat(#156): POST /assistantes-maternelles/dossier staff (actif + mail MDP).

Factorise createAmDossier depuis register/am ; staff crée un dossier
déjà actif et envoie l’e-mail de création de mot de passe.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-22 19:03:37 +02:00
co-authored by Cursor
parent d2172eafdb
commit 59afeb0a8d
7 changed files with 356 additions and 64 deletions
+140 -61
View File
@@ -645,11 +645,28 @@ export class AuthService {
}
/**
* Inscription Assistante Maternelle COMPLÈTE - Un seul endpoint (identité + pro + photo + CGU)
* Crée User (role AM) + entrée assistantes_maternelles, token création MDP
* Cœur partagé création dossier AM (#156).
* - public : statut en_attente + mail pending
* - staff : statut actif + mail création MDP
*/
async inscrireAMComplet(dto: RegisterAMCompletDto) {
if (!dto.acceptation_cgu || !dto.acceptation_privacy) {
async createAmDossier(
dto: RegisterAMCompletDto,
options: {
statut: StatutUtilisateurType;
sendPendingEmail: boolean;
sendPasswordSetupEmail: boolean;
requireCgu: boolean;
logContext?: string;
},
): Promise<{
message: string;
user_id: string;
statut: StatutUtilisateurType;
numero_dossier: string;
}> {
const logCtx = options.logContext ?? 'createAmDossier';
if (options.requireCgu && (!dto.acceptation_cgu || !dto.acceptation_privacy)) {
throw new BadRequestException(
"L'acceptation des CGU et de la politique de confidentialité est obligatoire",
);
@@ -669,8 +686,7 @@ export class AuthService {
throw new BadRequestException(nirValidation.error || 'NIR invalide');
}
if (nirValidation.warning) {
// Warning uniquement : on ne bloque pas (AM souvent étrangères, DOM-TOM, Corse)
console.warn('[inscrireAMComplet] NIR warning:', nirValidation.warning, 'email=', dto.email);
console.warn(`[${logCtx}] NIR warning:`, nirValidation.warning, 'email=', dto.email);
}
const existe = await this.usersService.findByEmailOrNull(dto.email);
@@ -721,79 +737,142 @@ export class AuthService {
let resultat: { user: Users };
try {
resultat = await this.usersRepo.manager.transaction(async (manager) => {
const { numero: numeroDossier } = await this.numeroDossierService.getNextNumeroDossier(manager);
const { numero: numeroDossier } =
await this.numeroDossierService.getNextNumeroDossier(manager);
const user = manager.create(Users, {
email: dto.email,
prenom: dto.prenom,
nom: dto.nom,
role: RoleType.ASSISTANTE_MATERNELLE,
statut: StatutUtilisateurType.EN_ATTENTE,
telephone: dto.telephone,
adresse: dto.adresse,
code_postal: dto.code_postal,
ville: dto.ville,
token_creation_mdp: tokenCreationMdp,
token_creation_mdp_expire_le: dateExpiration,
photo_url: urlPhoto ?? undefined,
consentement_photo: dto.consentement_photo,
date_consentement_photo: dateConsentementPhoto,
date_naissance: dto.date_naissance ? new Date(dto.date_naissance) : undefined,
lieu_naissance_ville: dto.lieu_naissance_ville,
lieu_naissance_pays: dto.lieu_naissance_pays,
numero_dossier: numeroDossier,
const user = manager.create(Users, {
email: dto.email,
prenom: dto.prenom,
nom: dto.nom,
role: RoleType.ASSISTANTE_MATERNELLE,
statut: options.statut,
telephone: dto.telephone,
adresse: dto.adresse,
code_postal: dto.code_postal,
ville: dto.ville,
token_creation_mdp: tokenCreationMdp,
token_creation_mdp_expire_le: dateExpiration,
photo_url: urlPhoto ?? undefined,
consentement_photo: dto.consentement_photo,
date_consentement_photo: dateConsentementPhoto,
date_naissance: dto.date_naissance
? new Date(dto.date_naissance)
: undefined,
lieu_naissance_ville: dto.lieu_naissance_ville,
lieu_naissance_pays: dto.lieu_naissance_pays,
numero_dossier: numeroDossier,
});
const userEnregistre = await manager.save(Users, user);
const amRepo = manager.getRepository(AssistanteMaternelle);
const am = amRepo.create({
user_id: userEnregistre.id,
approval_number: dto.numero_agrement,
nir: nirNormalized,
max_children: dto.capacite_accueil,
places_available: dto.places_disponibles,
biography: dto.biographie,
residence_city: dto.ville ?? undefined,
agreement_date: dto.date_agrement
? new Date(dto.date_agrement)
: undefined,
available: true,
numero_dossier: numeroDossier,
});
await amRepo.save(am);
return { user: userEnregistre };
});
const userEnregistre = await manager.save(Users, user);
const amRepo = manager.getRepository(AssistanteMaternelle);
const am = amRepo.create({
user_id: userEnregistre.id,
approval_number: dto.numero_agrement,
nir: nirNormalized,
max_children: dto.capacite_accueil,
places_available: dto.places_disponibles,
biography: dto.biographie,
residence_city: dto.ville ?? undefined,
agreement_date: dto.date_agrement ? new Date(dto.date_agrement) : undefined,
available: true,
numero_dossier: numeroDossier,
});
await amRepo.save(am);
return { user: userEnregistre };
});
} catch (err) {
if (this.isPostgresUniqueViolation(err)) {
throw new ConflictException('Un compte avec cet email existe déjà (contrainte unique en base).');
throw new ConflictException(
'Un compte avec cet email existe déjà (contrainte unique en base).',
);
}
throw err;
}
const numeroDossier = resultat.user.numero_dossier ?? '';
try {
await this.mailService.sendRegistrationPendingEmail(
resultat.user.email,
resultat.user.prenom ?? '',
resultat.user.nom ?? '',
numeroDossier,
);
} catch (err) {
this.logger.error(
"[inscrireAMComplet] Échec envoi email d'accusé de réception (inscription conservée)",
err instanceof Error ? err.stack : String(err),
);
if (options.sendPendingEmail) {
try {
await this.mailService.sendRegistrationPendingEmail(
resultat.user.email,
resultat.user.prenom ?? '',
resultat.user.nom ?? '',
numeroDossier,
);
} catch (err) {
this.logger.error(
`[${logCtx}] Échec envoi email d'accusé de réception (inscription conservée)`,
err instanceof Error ? err.stack : String(err),
);
}
}
if (options.sendPasswordSetupEmail) {
try {
await this.mailService.sendValidatedAccountPasswordSetupEmail(
{
email: resultat.user.email,
prenom: resultat.user.prenom ?? '',
nom: resultat.user.nom ?? '',
token: tokenCreationMdp,
numeroDossier,
},
'am',
);
} catch (err) {
this.logger.error(
`[${logCtx}] Échec envoi email création MDP (dossier conservé)`,
err instanceof Error ? err.stack : String(err),
);
}
}
const message =
options.statut === StatutUtilisateurType.ACTIF
? 'Dossier AM créé et validé. Un e-mail de création de mot de passe a été envoyé.'
: 'Inscription réussie. Votre dossier est en attente de validation par un gestionnaire.';
return {
message:
'Inscription réussie. Votre dossier est en attente de validation par un gestionnaire.',
message,
user_id: resultat.user.id,
statut: StatutUtilisateurType.EN_ATTENTE,
statut: options.statut,
numero_dossier: numeroDossier,
};
}
/**
* Inscription Assistante Maternelle COMPLÈTE - Un seul endpoint (identité + pro + photo + CGU)
* Crée User (role AM) + entrée assistantes_maternelles, token création MDP
*/
async inscrireAMComplet(dto: RegisterAMCompletDto) {
return this.createAmDossier(dto, {
statut: StatutUtilisateurType.EN_ATTENTE,
sendPendingEmail: true,
sendPasswordSetupEmail: false,
requireCgu: true,
logContext: 'inscrireAMComplet',
});
}
/**
* Création dossier AM par staff (#156) — statut actif + e-mail création MDP.
*/
async createAmDossierStaff(dto: RegisterAMCompletDto) {
return this.createAmDossier(dto, {
statut: StatutUtilisateurType.ACTIF,
sendPendingEmail: false,
sendPasswordSetupEmail: true,
requireCgu: false,
logContext: 'createAmDossierStaff',
});
}
/**
* Sauvegarde une photo depuis base64 vers le système de fichiers
*/
/**
* Sauvegarde une photo depuis base64 vers le système de fichiers
*/