feat(#112): reprise après refus — dossier complet, email resoumission

- GET/PATCH reprise-dossier enrichis (parents, enfants, motivation, fiche AM)
- Front: lien mail, modale identify login, wizards préremplis, PATCH complet
- Email accusé resoumission aux parents avec n° de dossier
- Fixes préremplissage AM (dates, places, ValueKey étape 2)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-16 19:19:55 +02:00
co-authored by Cursor
parent c226c2fcdf
commit b99745e0fe
36 changed files with 2460 additions and 85 deletions
@@ -98,4 +98,21 @@ describe('MailService (ticket #28)', () => {
expect(html).toContain(`https://app.test/reset-password?token=${encodeURIComponent(token)}`);
expect(html).not.toContain('create-password');
});
it('sendResoumissionPendingEmail — accusé resoumission avec n° dossier', async () => {
await service.sendResoumissionPendingEmail(
'parent@test.fr',
'Claire',
'MARTIN',
'2026-000024',
);
expect(sendEmailSpy).toHaveBeenCalledTimes(1);
const [to, subject, html] = sendEmailSpy.mock.calls[0];
expect(to).toBe('parent@test.fr');
expect(subject).toContain('resoumis');
expect(subject).toContain('2026-000024');
expect(html).toContain('Claire');
expect(html).toContain('2026-000024');
expect(html).toContain('en attente de validation');
});
});
+37
View File
@@ -301,6 +301,43 @@ export class MailService {
await this.sendEmail(to, subject, html);
}
/**
* Accusé de resoumission après refus (reprise #112) : dossier à nouveau en attente de validation.
*/
async sendResoumissionPendingEmail(
to: string,
prenom: string,
nom: string,
numeroDossier: string,
): Promise<void> {
const appName = this.configService.get<string>('app_name', "P'titsPas");
const appUrl = this.configService.get<string>('app_url', 'https://app.ptits-pas.fr');
const safe = (s: string) =>
(s || '')
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
const subject = `Votre dossier a été resoumis — ${safe(numeroDossier)}`;
const html = `
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
<h2 style="color: #4CAF50;">Bonjour ${safe(prenom)} ${safe(nom)},</h2>
<p>Nous avons bien reçu la <strong>resoumission</strong> de votre dossier sur <strong>${safe(appName)}</strong>.</p>
<p><strong>Numéro de dossier :</strong> ${safe(numeroDossier)}</p>
<p>Votre dossier est de nouveau <strong>en attente de validation</strong> par notre équipe. Vous recevrez un email lorsqu'il aura été traité.</p>
<div style="text-align: center; margin: 30px 0;">
<a href="${appUrl}" style="background-color: #4CAF50; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px; font-weight: bold;">Accéder au site</a>
</div>
<hr style="border: 1px solid #eee; margin: 20px 0;">
<p style="color: #666; font-size: 12px;">Cet email a été envoyé automatiquement. Merci de ne pas y répondre.</p>
</div>
`;
await this.sendEmail(to, subject, html);
}
/**
* Email de refus de dossier avec lien reprise (token).
* Ticket #110 Refus sans suppression