Checkpoint: Phase 1 : Système d'attestations de formation automatiques
✅ Infrastructure backend complète : - Tables attestations et configAttestation en base de données - Service de génération PDF (attestationService.ts) avec PDFKit - Procédures tRPC pour générer, récupérer et configurer les attestations - Stockage S3 des PDF générés ✅ Interface d'administration : - Page de configuration des attestations (/admin/attestations) - Personnalisation du texte avec variables dynamiques - Champs pour signataire (nom et fonction) - Placeholders pour logo et signature (à implémenter en phase 2) 📋 À faire en Phase 2 : - Upload des images (logo/signature) vers S3 - Génération automatique après validation de présence - Envoi automatique par email aux apprenants - Espace apprenant pour télécharger les attestations
This commit is contained in:
@@ -1736,6 +1736,105 @@ export const appRouter = router({
|
||||
});
|
||||
}),
|
||||
}),
|
||||
|
||||
// ===== ATTESTATIONS =====
|
||||
attestations: router({
|
||||
// Générer une attestation pour une inscription
|
||||
generer: adminProcedure
|
||||
.input(z.object({ inscriptionId: z.number() }))
|
||||
.mutation(async ({ input }) => {
|
||||
const attestationService = await import("./attestationService");
|
||||
const { getDb } = await import("./db");
|
||||
const { inscriptions, apprenants, sequences } = await import("../drizzle/schema");
|
||||
const { eq } = await import("drizzle-orm");
|
||||
|
||||
const db = await getDb();
|
||||
if (!db) throw new Error("Base de données non disponible");
|
||||
|
||||
// Récupérer les infos de l'inscription
|
||||
const [inscriptionData] = await db
|
||||
.select({
|
||||
inscription: inscriptions,
|
||||
apprenant: apprenants,
|
||||
sequence: sequences,
|
||||
})
|
||||
.from(inscriptions)
|
||||
.innerJoin(apprenants, eq(inscriptions.apprenantId, apprenants.id))
|
||||
.innerJoin(sequences, eq(inscriptions.sequenceId, sequences.id))
|
||||
.where(eq(inscriptions.id, input.inscriptionId))
|
||||
.limit(1);
|
||||
|
||||
if (!inscriptionData) {
|
||||
throw new Error("Inscription introuvable");
|
||||
}
|
||||
|
||||
// Vérifier si une attestation existe déjà
|
||||
const existingAttestation = await attestationService.getAttestation(
|
||||
inscriptionData.apprenant.id,
|
||||
inscriptionData.sequence.id
|
||||
);
|
||||
|
||||
if (existingAttestation) {
|
||||
return existingAttestation;
|
||||
}
|
||||
|
||||
// Générer le PDF
|
||||
const { s3Key, pdfUrl } = await attestationService.genererAttestationPDF(input.inscriptionId);
|
||||
|
||||
// Enregistrer dans la base
|
||||
await attestationService.enregistrerAttestation(
|
||||
input.inscriptionId,
|
||||
inscriptionData.apprenant.id,
|
||||
inscriptionData.sequence.id,
|
||||
s3Key,
|
||||
pdfUrl
|
||||
);
|
||||
|
||||
return { s3Key, pdfUrl };
|
||||
}),
|
||||
|
||||
// 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 attestationService = await import("./attestationService");
|
||||
return attestationService.getAttestation(input.apprenantId, input.sequenceId);
|
||||
}),
|
||||
|
||||
// Récupérer toutes les attestations d'un apprenant
|
||||
listByApprenant: publicProcedure
|
||||
.input(z.object({ apprenantId: z.number() }))
|
||||
.query(async ({ input }) => {
|
||||
const attestationService = await import("./attestationService");
|
||||
return attestationService.getAttestationsApprenant(input.apprenantId);
|
||||
}),
|
||||
|
||||
// Configuration des attestations
|
||||
getConfig: adminProcedure
|
||||
.query(async () => {
|
||||
const attestationService = await import("./attestationService");
|
||||
return attestationService.getOrCreateConfigAttestation();
|
||||
}),
|
||||
|
||||
updateConfig: adminProcedure
|
||||
.input(z.object({
|
||||
logoS3Key: z.string().optional(),
|
||||
logoUrl: z.string().optional(),
|
||||
signatureS3Key: z.string().optional(),
|
||||
signatureUrl: z.string().optional(),
|
||||
nomSignataire: z.string().optional(),
|
||||
fonctionSignataire: z.string().optional(),
|
||||
texteAttestation: z.string().optional(),
|
||||
}))
|
||||
.mutation(async ({ input }) => {
|
||||
const attestationService = await import("./attestationService");
|
||||
await attestationService.updateConfigAttestation(input);
|
||||
return { success: true };
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export type AppRouter = typeof appRouter;
|
||||
|
||||
Reference in New Issue
Block a user