Checkpoint: Système d'attestations de formation implémenté

 Infrastructure backend :
- Tables attestations et configAttestation en base de données
- Service de génération PDF avec PDFKit (attestationService.ts)
- Génération automatique de PDF avec template personnalisable
- Upload automatique vers S3 avec URLs publiques
- Procédures tRPC pour générer, récupérer et configurer

 Interface utilisateur :
- Page de configuration des attestations (/admin/attestations)
- Personnalisation du texte avec variables dynamiques (nomComplet, nomFormation, dateDebut, dateFin)
- Configuration du signataire (nom et fonction)
- Bouton "Générer" dans la page de validation des présences formateur
- Génération automatique avec ouverture du PDF dans un nouvel onglet

 Fonctionnalités :
- Vérification des doublons (une seule attestation par apprenant/séquence)
- Génération uniquement pour les apprenants marqués comme présents
- PDF professionnel avec détails de la formation et dates
- Stockage permanent sur S3

📋 Améliorations futures possibles :
- Upload de logo et signature personnalisés
- Envoi automatique par email aux apprenants
- Espace apprenant pour télécharger les attestations
This commit is contained in:
Manus
2026-01-07 10:06:29 -05:00
parent cc3badffa3
commit 023711df92
15 changed files with 6691 additions and 2 deletions

View File

@@ -1736,6 +1736,98 @@ export const appRouter = router({
});
}),
}),
// Attestations de formation
attestations: router({
// Générer une attestation pour une inscription
generer: adminProcedure
.input(z.object({
inscriptionId: z.number(),
}))
.mutation(async ({ input }) => {
const { genererAttestationPDF, enregistrerAttestation, getAttestation } = await import("./attestationService");
const db = await import("./db").then(m => m.getDb());
if (!db) throw new Error("Base de données non disponible");
// Vérifier si l'attestation existe déjà
const { inscriptions } = await import("../drizzle/schema");
const { eq } = await import("drizzle-orm");
const [inscription] = await db.select().from(inscriptions).where(eq(inscriptions.id, input.inscriptionId)).limit(1);
if (!inscription) {
throw new Error("Inscription introuvable");
}
const attestationExistante = await getAttestation(inscription.apprenantId, inscription.sequenceId);
if (attestationExistante) {
return {
success: true,
attestation: attestationExistante,
message: "Attestation déjà générée",
};
}
// Générer le PDF
const { s3Key, pdfUrl } = await genererAttestationPDF(input.inscriptionId);
// Enregistrer dans la base
const attestationId = await enregistrerAttestation(
input.inscriptionId,
inscription.apprenantId,
inscription.sequenceId,
s3Key,
pdfUrl
);
const attestation = await getAttestation(inscription.apprenantId, inscription.sequenceId);
return {
success: true,
attestation,
message: "Attestation générée avec succès",
};
}),
// Récupérer l'attestation d'un apprenant pour une séquence
get: publicProcedure
.input(z.object({
apprenantId: z.number(),
sequenceId: z.number(),
}))
.query(async ({ input }) => {
const { getAttestation } = await import("./attestationService");
return getAttestation(input.apprenantId, input.sequenceId);
}),
// Récupérer toutes les attestations d'un apprenant
listApprenant: publicProcedure
.input(z.object({
apprenantId: z.number(),
}))
.query(async ({ input }) => {
const { getAttestationsApprenant } = await import("./attestationService");
return getAttestationsApprenant(input.apprenantId);
}),
// Récupérer la configuration des attestations
getConfig: adminProcedure
.query(async () => {
const { getOrCreateConfigAttestation } = await import("./attestationService");
return getOrCreateConfigAttestation();
}),
// Mettre à jour la configuration des attestations
updateConfig: adminProcedure
.input(z.object({
nomSignataire: z.string().optional(),
fonctionSignataire: z.string().optional(),
texteAttestation: z.string().optional(),
}))
.mutation(async ({ input }) => {
const { updateConfigAttestation } = await import("./attestationService");
await updateConfigAttestation(input);
return { success: true };
}),
}),
});
export type AppRouter = typeof appRouter;