Checkpoint: Correction du bug d'émargement formateur bloqué sur la saisie d'email

- Ajout de la détection automatique formateur/apprenant dans EmargementScan.tsx
- Nouvelle procédure tRPC getSequenceByToken pour récupérer les infos de séquence
- Interface adaptée selon le type d'utilisateur (apprenant ou formateur)
- Déploiement réussi sur le VPS de production

Le système détecte maintenant automatiquement si l'email correspond à un apprenant ou un formateur, et affiche l'interface appropriée pour chaque cas.
This commit is contained in:
Manus
2026-02-02 14:18:46 -05:00
parent e8410230a9
commit f9983adada
81 changed files with 3734 additions and 27745 deletions

View File

@@ -1,202 +0,0 @@
import { eq, and, desc, sql } from "drizzle-orm";
import { getDb } from "./db";
import { formations, attestations, inscriptions, apprenants, sequences, datesFormation } from "../drizzle/schema";
/**
* Récupérer la configuration d'une formation
*/
export async function getFormationConfig(formationId: number) {
const db = await getDb();
if (!db) return null;
const results = await db
.select({
id: formations.id,
nom: formations.nom,
modeAttestation: formations.modeAttestation,
modeEnvoi: formations.modeEnvoi,
})
.from(formations)
.where(eq(formations.id, formationId))
.limit(1);
return results.length > 0 ? results[0] : null;
}
/**
* Mettre à jour la configuration d'une formation
*/
export async function updateFormationConfig(
formationId: number,
modeAttestation: "auto" | "manuel",
modeEnvoi: "auto" | "manuel"
) {
const db = await getDb();
if (!db) throw new Error("Database not available");
await db
.update(formations)
.set({
modeAttestation,
modeEnvoi,
updatedAt: new Date(),
})
.where(eq(formations.id, formationId));
return true;
}
/**
* Récupérer toutes les attestations d'une formation avec les informations des apprenants
*/
export async function getAttestationsByFormation(formationId: number) {
const db = await getDb();
if (!db) return [];
const results = await db
.select({
attestation: attestations,
apprenant: apprenants,
sequence: sequences,
})
.from(attestations)
.innerJoin(inscriptions, eq(attestations.inscriptionId, inscriptions.id))
.innerJoin(apprenants, eq(inscriptions.apprenantId, apprenants.id))
.innerJoin(sequences, eq(inscriptions.sequenceId, sequences.id))
.where(eq(sequences.formationId, formationId))
.orderBy(desc(attestations.createdAt));
return results;
}
/**
* Uploader un document d'attestation pour un apprenant
*/
export async function uploadAttestationDocument(
inscriptionId: number,
documentUrl: string,
documentS3Key: string,
uploadedBy: number
) {
const db = await getDb();
if (!db) throw new Error("Database not available");
// Vérifier si une attestation existe déjà pour cette inscription
const existing = await db
.select()
.from(attestations)
.where(eq(attestations.inscriptionId, inscriptionId))
.limit(1);
if (existing.length > 0) {
// Mettre à jour l'attestation existante
await db
.update(attestations)
.set({
documentUrl,
documentS3Key,
uploadedBy,
uploadedAt: new Date(),
})
.where(eq(attestations.inscriptionId, inscriptionId));
return existing[0].id;
} else {
// Créer une nouvelle attestation
const result = await db
.insert(attestations)
.values({
inscriptionId,
documentUrl,
documentS3Key,
uploadedBy,
uploadedAt: new Date(),
})
.$returningId();
return result[0].id;
}
}
/**
* Envoyer une attestation par email
*/
export async function markAttestationAsSent(attestationId: number) {
const db = await getDb();
if (!db) throw new Error("Database not available");
await db
.update(attestations)
.set({
emailEnvoye: true,
dateEnvoiEmail: new Date(),
})
.where(eq(attestations.id, attestationId));
return true;
}
/**
* Récupérer les apprenants d'une formation avec leur statut d'attestation
*/
export async function getApprenantsWithAttestationStatus(formationId: number) {
const db = await getDb();
if (!db) return [];
const results = await db
.select({
apprenant: {
id: apprenants.id,
nom: apprenants.nom,
prenom: apprenants.prenom,
email: apprenants.email,
},
inscription: {
id: inscriptions.id,
sequenceId: inscriptions.sequenceId,
},
sequence: {
id: sequences.id,
nom: sequences.nom,
},
attestation: {
id: attestations.id,
documentUrl: attestations.documentUrl,
emailEnvoye: attestations.emailEnvoye,
dateEnvoiEmail: attestations.dateEnvoiEmail,
},
})
.from(inscriptions)
.innerJoin(apprenants, eq(inscriptions.apprenantId, apprenants.id))
.innerJoin(sequences, eq(inscriptions.sequenceId, sequences.id))
.leftJoin(attestations, eq(attestations.inscriptionId, inscriptions.id))
.where(
and(
eq(sequences.formationId, formationId),
eq(inscriptions.statut, "confirmee")
)
)
.orderBy(apprenants.nom, apprenants.prenom);
return results;
}
/**
* Supprimer un document d'attestation
*/
export async function deleteAttestationDocument(attestationId: number) {
const db = await getDb();
if (!db) throw new Error("Database not available");
await db
.update(attestations)
.set({
documentUrl: null,
documentS3Key: null,
uploadedBy: null,
uploadedAt: null,
})
.where(eq(attestations.id, attestationId));
return true;
}