Checkpoint: Système d'émargement numérique avec QR code et génération automatique d'attestations :

## Base de données
- ✅ Table `presences` créée (inscriptionId, dateFormationId, heurePresence, modeValidation, validateurId)
- ✅ Colonne `qrCodeToken` ajoutée à la table `sequences`

## Backend
- ✅ Génération de QR code unique par séquence (qrCodeGenerator.ts)
- ✅ Procédures tRPC pour l'émargement (presences.generateQRCode, presences.valider, presences.listBySequence)
- ✅ Fonctions de base de données (presenceDb.ts) pour gérer les présences
- ✅ Génération automatique d'attestation après validation de toutes les présences
- ✅ Intégration avec le service d'attestations existant

## Interface formateur (/formateur/emargement)
- ✅ Sélection de la séquence
- ✅ Génération et affichage du QR code en modal
- ✅ Liste des inscrits avec statut de présence par date
- ✅ Validation manuelle de présence possible
- ✅ Actualisation en temps réel des présences
- ✅ Entrée de menu "Émargement" dans DashboardLayout

## Interface publique (/emargement/:token)
- ✅ Page de scan QR code accessible sans authentification
- ✅ Recherche de l'apprenant par email
- ✅ Sélection de la formation et de la date
- ✅ Validation automatique de la présence via QR code
- ✅ Écrans de confirmation (succès/erreur)
- ✅ Gestion des erreurs (QR invalide, déjà émargé, etc.)

## Génération automatique d'attestations
- ✅ Vérification automatique après chaque validation de présence
- ✅ Génération du PDF d'attestation si toutes les présences sont validées
- ✅ Enregistrement en base de données
- ✅ Upload automatique sur S3

## Dépendances installées
- qrcode (génération de QR codes)
- @types/qrcode (types TypeScript)

Le système est entièrement fonctionnel et prêt à être utilisé par les formateurs et apprenants.
This commit is contained in:
Manus
2026-01-11 14:01:22 -05:00
parent f9aa8f7838
commit b64dedc488
16 changed files with 3566 additions and 0 deletions

60
server/qrCodeGenerator.ts Normal file
View File

@@ -0,0 +1,60 @@
import QRCode from "qrcode";
import { randomBytes } from "crypto";
/**
* Générer un token unique pour le QR code
*/
export function generateQRToken(): string {
return randomBytes(32).toString("hex");
}
/**
* Générer un QR code en base64 à partir d'un token
* @param token Le token unique de la séquence
* @returns Une image QR code en base64 (data URL)
*/
export async function generateQRCodeDataURL(token: string): Promise<string> {
// URL complète pour scanner le QR code
const url = `${process.env.VITE_APP_URL || "http://localhost:3000"}/emargement/${token}`;
try {
const qrCodeDataURL = await QRCode.toDataURL(url, {
errorCorrectionLevel: "H",
type: "image/png",
width: 400,
margin: 2,
color: {
dark: "#000000",
light: "#FFFFFF",
},
});
return qrCodeDataURL;
} catch (error) {
console.error("Erreur lors de la génération du QR code:", error);
throw new Error("Impossible de générer le QR code");
}
}
/**
* Générer un QR code en buffer PNG
* @param token Le token unique de la séquence
* @returns Un buffer PNG du QR code
*/
export async function generateQRCodeBuffer(token: string): Promise<Buffer> {
const url = `${process.env.VITE_APP_URL || "http://localhost:3000"}/emargement/${token}`;
try {
const buffer = await QRCode.toBuffer(url, {
errorCorrectionLevel: "H",
type: "png",
width: 400,
margin: 2,
});
return buffer;
} catch (error) {
console.error("Erreur lors de la génération du QR code:", error);
throw new Error("Impossible de générer le QR code");
}
}