Checkpoint: Améliorations de la gestion des attestations de formation :
- Ajout du paramètre d'envoi automatique des attestations dans la page Paramètres - Ajout de la prévisualisation du modèle d'attestation avec génération PDF - Création de la page Historique des attestations dans la section Traçabilité - Création de la table historiqueAttestations pour tracer les envois - Ajout des procédures tRPC pour l'historique et la prévisualisation
This commit is contained in:
@@ -294,3 +294,137 @@ export async function updateConfigAttestation(data: {
|
||||
await db.insert(configAttestation).values(data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère une prévisualisation du modèle d'attestation avec des données fictives
|
||||
*/
|
||||
export async function genererPreviewAttestation(): Promise<{ pdfUrl: string }> {
|
||||
const db = await getDb();
|
||||
if (!db) {
|
||||
throw new Error("Base de données non disponible");
|
||||
}
|
||||
|
||||
// Récupérer la configuration de l'attestation
|
||||
const config = await getOrCreateConfigAttestation();
|
||||
if (!config) {
|
||||
throw new Error("Configuration d'attestation introuvable");
|
||||
}
|
||||
|
||||
// Données fictives pour la prévisualisation
|
||||
const donneesFictives = {
|
||||
apprenant: {
|
||||
nom: "Dupont",
|
||||
prenom: "Jean",
|
||||
email: "jean.dupont@example.com",
|
||||
},
|
||||
formation: {
|
||||
nom: "Formation Manager Itinova",
|
||||
nbJours: 5,
|
||||
},
|
||||
sequence: {
|
||||
nom: "Groupe A",
|
||||
lieu: "Paris",
|
||||
},
|
||||
dates: [
|
||||
{ date: new Date("2026-02-10") },
|
||||
{ date: new Date("2026-02-11") },
|
||||
{ date: new Date("2026-02-12") },
|
||||
{ date: new Date("2026-02-13") },
|
||||
{ date: new Date("2026-02-14") },
|
||||
],
|
||||
};
|
||||
|
||||
// Créer le PDF
|
||||
const doc = new PDFDocument({ size: "A4", margin: 50 });
|
||||
const chunks: Buffer[] = [];
|
||||
|
||||
doc.on("data", (chunk) => chunks.push(chunk));
|
||||
|
||||
// En-tête avec logo si disponible
|
||||
if (config.logoUrl) {
|
||||
try {
|
||||
const response = await fetch(config.logoUrl);
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
const buffer = Buffer.from(arrayBuffer);
|
||||
doc.image(buffer, 50, 45, { width: 100 });
|
||||
} catch (error) {
|
||||
console.error("Erreur lors du chargement du logo:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Titre
|
||||
doc
|
||||
.fontSize(24)
|
||||
.font("Helvetica-Bold")
|
||||
.text("ATTESTATION DE FORMATION", 0, 150, { align: "center" });
|
||||
|
||||
doc.moveDown(2);
|
||||
|
||||
// Texte de l'attestation avec remplacement des variables
|
||||
let texte = config.texteAttestation || "Nous attestons que {{nomComplet}} a suivi avec assiduité la formation \"{{nomFormation}}\" organisée du {{dateDebut}} au {{dateFin}}.";
|
||||
|
||||
const dateDebut = format(donneesFictives.dates[0].date, "d MMMM yyyy", { locale: fr });
|
||||
const dateFin = format(donneesFictives.dates[donneesFictives.dates.length - 1].date, "d MMMM yyyy", { locale: fr });
|
||||
|
||||
texte = texte
|
||||
.replace(/\{\{nomComplet\}\}/g, `${donneesFictives.apprenant.prenom} ${donneesFictives.apprenant.nom}`)
|
||||
.replace(/\{\{nomFormation\}\}/g, donneesFictives.formation.nom)
|
||||
.replace(/\{\{dateDebut\}\}/g, dateDebut)
|
||||
.replace(/\{\{dateFin\}\}/g, dateFin)
|
||||
.replace(/\{\{lieu\}\}/g, donneesFictives.sequence.lieu)
|
||||
.replace(/\{\{nbJours\}\}/g, donneesFictives.formation.nbJours.toString());
|
||||
|
||||
doc
|
||||
.fontSize(12)
|
||||
.font("Helvetica")
|
||||
.text(texte, { align: "justify", lineGap: 5 });
|
||||
|
||||
doc.moveDown(2);
|
||||
|
||||
// Dates de formation
|
||||
doc.fontSize(10).font("Helvetica-Bold").text("Dates de formation :");
|
||||
doc.font("Helvetica");
|
||||
donneesFictives.dates.forEach((d) => {
|
||||
doc.text(`• ${format(d.date, "EEEE d MMMM yyyy", { locale: fr })}`, { indent: 20 });
|
||||
});
|
||||
|
||||
doc.moveDown(2);
|
||||
|
||||
// Signature
|
||||
doc.fontSize(10).font("Helvetica").text(`Fait à ${donneesFictives.sequence.lieu}, le ${format(new Date(), "d MMMM yyyy", { locale: fr })}`);
|
||||
|
||||
doc.moveDown(1);
|
||||
|
||||
if (config.signatureUrl) {
|
||||
try {
|
||||
const response = await fetch(config.signatureUrl);
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
const buffer = Buffer.from(arrayBuffer);
|
||||
doc.image(buffer, doc.x, doc.y, { width: 150 });
|
||||
doc.moveDown(3);
|
||||
} catch (error) {
|
||||
console.error("Erreur lors du chargement de la signature:", error);
|
||||
}
|
||||
}
|
||||
|
||||
if (config.nomSignataire) {
|
||||
doc.font("Helvetica-Bold").text(config.nomSignataire);
|
||||
}
|
||||
if (config.fonctionSignataire) {
|
||||
doc.font("Helvetica").text(config.fonctionSignataire);
|
||||
}
|
||||
|
||||
// Finaliser le PDF
|
||||
doc.end();
|
||||
|
||||
await new Promise((resolve) => doc.on("end", resolve));
|
||||
|
||||
const pdfBuffer = Buffer.concat(chunks);
|
||||
|
||||
// Uploader sur S3
|
||||
const randomSuffix = crypto.randomBytes(8).toString("hex");
|
||||
const s3Key = `attestations/preview/preview-${randomSuffix}.pdf`;
|
||||
const { url: pdfUrl } = await storagePut(s3Key, pdfBuffer, "application/pdf");
|
||||
|
||||
return { pdfUrl };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user