feat(#135): POST /parents/:id/co-parent — ajout co-parent foyer.
API staff pour foyer mono-parent : compte actif, liens + enfants, mail création MDP. Mini-specs front/back dans docs/tmp. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -36,6 +36,7 @@ import { MailService } from 'src/modules/mail/mail.service';
|
||||
import { ParentsService } from '../parents/parents.service';
|
||||
import { DossiersService } from '../dossiers/dossiers.service';
|
||||
import { DossierAmCompletDto } from '../dossiers/dto/dossier-am-complet.dto';
|
||||
import { StaffAddCoParentDto } from '../parents/dto/staff-add-co-parent.dto';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
@@ -718,6 +719,167 @@ export class AuthService {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajoute un co-parent à un foyer existant (mono-parent) — ticket #135.
|
||||
* Compte actif + mail création MDP + liens Parents bidirectionnels + enfants du foyer.
|
||||
*/
|
||||
async addCoParentStaff(pivotUserId: string, dto: StaffAddCoParentDto) {
|
||||
const pivotParent = await this.parentsRepo.findOne({
|
||||
where: { user_id: pivotUserId },
|
||||
relations: ['user', 'co_parent', 'parentChildren'],
|
||||
});
|
||||
if (!pivotParent?.user) {
|
||||
throw new NotFoundException('Parent introuvable');
|
||||
}
|
||||
|
||||
if (pivotParent.co_parent) {
|
||||
throw new BadRequestException('Ce foyer a déjà un co-parent.');
|
||||
}
|
||||
|
||||
const numeroDossier = pivotParent.numero_dossier?.trim() || pivotParent.user.numero_dossier?.trim();
|
||||
if (!numeroDossier) {
|
||||
throw new BadRequestException("Ce parent n'a pas de numéro de dossier.");
|
||||
}
|
||||
|
||||
const sameDossierCount = await this.parentsRepo.count({
|
||||
where: { numero_dossier: numeroDossier },
|
||||
});
|
||||
if (sameDossierCount >= 2) {
|
||||
throw new BadRequestException('Ce dossier a déjà deux responsables.');
|
||||
}
|
||||
|
||||
const email = dto.email.trim().toLowerCase();
|
||||
if (pivotParent.user.email.trim().toLowerCase() === email) {
|
||||
throw new BadRequestException(
|
||||
"L'email du co-parent doit être différent de celui du parent principal.",
|
||||
);
|
||||
}
|
||||
|
||||
const emailExiste = await this.usersService.findByEmailOrNull(dto.email);
|
||||
if (emailExiste) {
|
||||
throw new ConflictException("L'email du co-parent est déjà utilisé");
|
||||
}
|
||||
|
||||
const memeAdresse = dto.meme_adresse ?? true;
|
||||
if (!memeAdresse) {
|
||||
if (!dto.adresse?.trim() || !dto.ville?.trim() || !dto.code_postal?.trim()) {
|
||||
throw new BadRequestException(
|
||||
"Adresse, code postal et ville du co-parent sont requis si meme_adresse est faux.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const joursExpirationToken = await this.appConfigService.get<number>(
|
||||
'password_reset_token_expiry_days',
|
||||
7,
|
||||
);
|
||||
const tokenCreationMdp = crypto.randomUUID();
|
||||
const dateExpiration = new Date();
|
||||
dateExpiration.setDate(dateExpiration.getDate() + joursExpirationToken);
|
||||
|
||||
let coParent: Users;
|
||||
|
||||
try {
|
||||
coParent = await this.usersRepo.manager.transaction(async (manager) => {
|
||||
const pivotUser = await manager.findOne(Users, {
|
||||
where: { id: pivotUserId },
|
||||
});
|
||||
if (!pivotUser) {
|
||||
throw new NotFoundException('Parent introuvable');
|
||||
}
|
||||
|
||||
const pivotEntite = await manager.findOne(Parents, {
|
||||
where: { user_id: pivotUserId },
|
||||
relations: ['parentChildren'],
|
||||
});
|
||||
if (!pivotEntite) {
|
||||
throw new NotFoundException('Parent introuvable');
|
||||
}
|
||||
|
||||
const coUser = manager.create(Users, {
|
||||
email: dto.email.trim(),
|
||||
prenom: dto.prenom,
|
||||
nom: dto.nom,
|
||||
role: RoleType.PARENT,
|
||||
statut: StatutUtilisateurType.ACTIF,
|
||||
telephone: dto.telephone,
|
||||
adresse: memeAdresse ? pivotUser.adresse : dto.adresse,
|
||||
code_postal: memeAdresse ? pivotUser.code_postal : dto.code_postal,
|
||||
ville: memeAdresse ? pivotUser.ville : dto.ville,
|
||||
token_creation_mdp: tokenCreationMdp,
|
||||
token_creation_mdp_expire_le: dateExpiration,
|
||||
numero_dossier: numeroDossier,
|
||||
});
|
||||
const coUserSaved = await manager.save(Users, coUser);
|
||||
|
||||
pivotEntite.co_parent = coUserSaved;
|
||||
pivotEntite.numero_dossier = numeroDossier;
|
||||
await manager.save(Parents, pivotEntite);
|
||||
|
||||
const coEntite = manager.create(Parents, {
|
||||
user_id: coUserSaved.id,
|
||||
numero_dossier: numeroDossier,
|
||||
});
|
||||
coEntite.user = coUserSaved;
|
||||
coEntite.co_parent = pivotUser;
|
||||
await manager.save(Parents, coEntite);
|
||||
|
||||
const enfantIds = (pivotEntite.parentChildren ?? [])
|
||||
.map((pc) => pc.enfantId)
|
||||
.filter(Boolean);
|
||||
for (const enfantId of enfantIds) {
|
||||
const existing = await manager.findOne(ParentsChildren, {
|
||||
where: { parentId: coUserSaved.id, enfantId },
|
||||
});
|
||||
if (existing) continue;
|
||||
await manager.save(
|
||||
ParentsChildren,
|
||||
manager.create(ParentsChildren, {
|
||||
parentId: coUserSaved.id,
|
||||
enfantId,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return coUserSaved;
|
||||
});
|
||||
} catch (err) {
|
||||
if (this.isPostgresUniqueViolation(err)) {
|
||||
throw new ConflictException(
|
||||
'Un compte avec cet email existe déjà (contrainte unique en base).',
|
||||
);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.mailService.sendValidatedAccountPasswordSetupEmail(
|
||||
{
|
||||
email: coParent.email,
|
||||
prenom: coParent.prenom ?? '',
|
||||
nom: coParent.nom ?? '',
|
||||
token: tokenCreationMdp,
|
||||
numeroDossier,
|
||||
},
|
||||
'parent',
|
||||
);
|
||||
} catch (err) {
|
||||
this.logger.error(
|
||||
'[addCoParentStaff] Échec envoi email création MDP (co-parent conservé)',
|
||||
err instanceof Error ? err.stack : String(err),
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
message:
|
||||
'Co-parent ajouté au foyer. Un e-mail de création de mot de passe a été envoyé.',
|
||||
numero_dossier: numeroDossier,
|
||||
parent_user_id: pivotUserId,
|
||||
co_parent_user_id: coParent.id,
|
||||
statut: StatutUtilisateurType.ACTIF,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Cœur partagé création dossier AM (#156).
|
||||
* - public : statut en_attente + mail pending
|
||||
|
||||
Reference in New Issue
Block a user