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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user