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:
@@ -1,248 +0,0 @@
|
||||
import { getDb } from "./db";
|
||||
import { eq, and, lte, sql } from "drizzle-orm";
|
||||
import {
|
||||
questionnaires,
|
||||
sequences,
|
||||
inscriptions,
|
||||
apprenants,
|
||||
datesFormation,
|
||||
envoisQuestionnaires,
|
||||
} from "../drizzle/schema";
|
||||
import {
|
||||
getAllQuestionnaires,
|
||||
checkEnvoiExists,
|
||||
createEnvoiQuestionnaire,
|
||||
} from "./questionnaireDb";
|
||||
import { sendEmail } from "./_core/emailSender";
|
||||
import crypto from "crypto";
|
||||
|
||||
/**
|
||||
* Génère un token unique pour accéder au questionnaire
|
||||
*/
|
||||
function generateToken(): string {
|
||||
return crypto.randomBytes(32).toString("hex");
|
||||
}
|
||||
|
||||
/**
|
||||
* Envoie un questionnaire à un apprenant pour une séquence donnée
|
||||
*/
|
||||
async function envoyerQuestionnaire(
|
||||
questionnaireId: number,
|
||||
apprenantId: number,
|
||||
sequenceId: number,
|
||||
apprenantEmail: string,
|
||||
apprenantNom: string,
|
||||
apprenantPrenom: string,
|
||||
questionnairenom: string,
|
||||
sequenceNom: string
|
||||
) {
|
||||
// Vérifier si le questionnaire n'a pas déjà été envoyé
|
||||
const dejaEnvoye = await checkEnvoiExists(questionnaireId, apprenantId, sequenceId);
|
||||
if (dejaEnvoye) {
|
||||
console.log(
|
||||
`[Questionnaires] Questionnaire ${questionnaireId} déjà envoyé à l'apprenant ${apprenantId} pour la séquence ${sequenceId}`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Générer un token unique
|
||||
const token = generateToken();
|
||||
|
||||
// Créer l'envoi
|
||||
await createEnvoiQuestionnaire({
|
||||
questionnaireId,
|
||||
apprenantId,
|
||||
sequenceId,
|
||||
token,
|
||||
dateEnvoi: new Date(),
|
||||
dateReponse: null,
|
||||
});
|
||||
|
||||
// Envoyer l'email
|
||||
const lienQuestionnaire = `${process.env.VITE_FRONTEND_URL || "http://localhost:3000"}/questionnaire/${token}`;
|
||||
|
||||
const emailSent = await sendEmail({
|
||||
to: apprenantEmail,
|
||||
subject: `Questionnaire : ${questionnairenom}`,
|
||||
html: `
|
||||
<h2>Bonjour ${apprenantPrenom} ${apprenantNom},</h2>
|
||||
|
||||
<p>Nous vous remercions d'avoir participé à la formation <strong>${sequenceNom}</strong>.</p>
|
||||
|
||||
<p>Afin d'améliorer continuellement la qualité de nos formations, nous vous invitons à répondre à ce questionnaire :</p>
|
||||
|
||||
<p style="margin: 30px 0;">
|
||||
<a href="${lienQuestionnaire}"
|
||||
style="background-color: #2563eb; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; display: inline-block;">
|
||||
Répondre au questionnaire
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p>Ce questionnaire ne vous prendra que quelques minutes.</p>
|
||||
|
||||
<p>Merci pour votre participation !</p>
|
||||
|
||||
<p style="margin-top: 30px; font-size: 12px; color: #666;">
|
||||
Si le bouton ne fonctionne pas, copiez ce lien dans votre navigateur :<br>
|
||||
<a href="${lienQuestionnaire}">${lienQuestionnaire}</a>
|
||||
</p>
|
||||
`,
|
||||
});
|
||||
|
||||
if (emailSent) {
|
||||
console.log(
|
||||
`[Questionnaires] Questionnaire ${questionnaireId} envoyé à ${apprenantEmail} pour la séquence ${sequenceId}`
|
||||
);
|
||||
return true;
|
||||
} else {
|
||||
console.error(
|
||||
`[Questionnaires] Échec de l'envoi du questionnaire ${questionnaireId} à ${apprenantEmail}`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Traite les envois automatiques de questionnaires post-formation
|
||||
*/
|
||||
export async function processEnvoisAutomatiques() {
|
||||
console.log("[Questionnaires] Démarrage du traitement des envois automatiques...");
|
||||
|
||||
const db = await getDb();
|
||||
if (!db) {
|
||||
console.log("[Questionnaires] Base de données non disponible");
|
||||
return { success: false, message: "Base de données non disponible" };
|
||||
}
|
||||
|
||||
try {
|
||||
// Récupérer tous les questionnaires actifs avec envoi automatique
|
||||
const questionnairesActifs = await getAllQuestionnaires();
|
||||
const questionnairesAEnvoyer = questionnairesActifs.filter(
|
||||
(q) => q.actif && q.envoiAutomatique
|
||||
);
|
||||
|
||||
if (questionnairesAEnvoyer.length === 0) {
|
||||
console.log("[Questionnaires] Aucun questionnaire avec envoi automatique configuré");
|
||||
return { success: true, message: "Aucun questionnaire à envoyer", count: 0 };
|
||||
}
|
||||
|
||||
let totalEnvoyes = 0;
|
||||
|
||||
for (const questionnaire of questionnairesAEnvoyer) {
|
||||
console.log(
|
||||
`[Questionnaires] Traitement du questionnaire "${questionnaire.titre}" (délai: J+${questionnaire.delaiEnvoiJours})`
|
||||
);
|
||||
|
||||
// Calculer la date cible (aujourd'hui - délai)
|
||||
const dateCible = new Date();
|
||||
dateCible.setDate(dateCible.getDate() - questionnaire.delaiEnvoiJours);
|
||||
dateCible.setHours(0, 0, 0, 0);
|
||||
|
||||
// Récupérer les séquences terminées à la date cible
|
||||
const sequencesTerminees = await db
|
||||
.select({
|
||||
sequenceId: sequences.id,
|
||||
sequenceNom: sequences.nom,
|
||||
formationId: sequences.formationId,
|
||||
})
|
||||
.from(sequences)
|
||||
.innerJoin(datesFormation, eq(datesFormation.sequenceId, sequences.id))
|
||||
.where(lte(datesFormation.dateFin, dateCible))
|
||||
.groupBy(sequences.id);
|
||||
|
||||
console.log(
|
||||
`[Questionnaires] ${sequencesTerminees.length} séquence(s) terminée(s) trouvée(s) pour le ${dateCible.toLocaleDateString("fr-FR")}`
|
||||
);
|
||||
|
||||
// Pour chaque séquence terminée
|
||||
for (const seq of sequencesTerminees) {
|
||||
// Vérifier si le questionnaire est lié à une formation spécifique
|
||||
if (questionnaire.formationId && questionnaire.formationId !== seq.formationId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Récupérer les apprenants inscrits et validés
|
||||
const apprenantsInscrits = await db
|
||||
.select({
|
||||
apprenantId: apprenants.id,
|
||||
apprenantEmail: apprenants.email,
|
||||
apprenantNom: apprenants.nom,
|
||||
apprenantPrenom: apprenants.prenom,
|
||||
})
|
||||
.from(inscriptions)
|
||||
.innerJoin(apprenants, eq(apprenants.id, inscriptions.apprenantId))
|
||||
.where(
|
||||
and(
|
||||
eq(inscriptions.sequenceId, seq.sequenceId),
|
||||
eq(inscriptions.statut, "confirmee")
|
||||
)
|
||||
);
|
||||
|
||||
console.log(
|
||||
`[Questionnaires] ${apprenantsInscrits.length} apprenant(s) inscrit(s) à la séquence ${seq.sequenceId}`
|
||||
);
|
||||
|
||||
// Envoyer le questionnaire à chaque apprenant
|
||||
for (const apprenant of apprenantsInscrits) {
|
||||
if (!apprenant.apprenantEmail) {
|
||||
console.log(
|
||||
`[Questionnaires] Apprenant ${apprenant.apprenantId} sans email, envoi ignoré`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const envoye = await envoyerQuestionnaire(
|
||||
questionnaire.id,
|
||||
apprenant.apprenantId,
|
||||
seq.sequenceId,
|
||||
apprenant.apprenantEmail,
|
||||
apprenant.apprenantNom,
|
||||
apprenant.apprenantPrenom,
|
||||
questionnaire.titre,
|
||||
seq.sequenceNom
|
||||
);
|
||||
|
||||
if (envoye) {
|
||||
totalEnvoyes++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[Questionnaires] Traitement terminé - ${totalEnvoyes} questionnaire(s) envoyé(s)`);
|
||||
return {
|
||||
success: true,
|
||||
message: `${totalEnvoyes} questionnaire(s) envoyé(s)`,
|
||||
count: totalEnvoyes,
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.error("[Questionnaires] Erreur lors du traitement :", error);
|
||||
return { success: false, message: error.message, count: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise le scheduler pour les envois automatiques de questionnaires
|
||||
* Exécute le traitement tous les jours à 9h00
|
||||
*/
|
||||
export function initQuestionnaireScheduler() {
|
||||
console.log("[Questionnaires] Initialisation du scheduler d'envoi automatique");
|
||||
|
||||
// Exécuter immédiatement au démarrage
|
||||
processEnvoisAutomatiques();
|
||||
|
||||
// Puis exécuter tous les jours à 9h00
|
||||
const interval = setInterval(
|
||||
() => {
|
||||
const now = new Date();
|
||||
if (now.getHours() === 9 && now.getMinutes() === 0) {
|
||||
processEnvoisAutomatiques();
|
||||
}
|
||||
},
|
||||
60 * 1000 // Vérifier toutes les minutes
|
||||
);
|
||||
|
||||
console.log("[Questionnaires] Scheduler initialisé - vérification quotidienne à 9h00");
|
||||
|
||||
return interval;
|
||||
}
|
||||
Reference in New Issue
Block a user