Files
formation-manager-itinova/server/presenceDb.ts
Manus 5cf4313f10 Checkpoint: Correction de l'affichage des présences formateur dans la fenêtre des émargements
## Problème identifié
Les présences formateur validées n'apparaissaient pas dans la fenêtre des émargements. La fonction `getSignaturesFormateursBySequence()` lisait la table `presences` avec un filtre `typeUtilisateur = "formateur"`, alors que les présences formateur sont enregistrées dans une table séparée `presences_formateur`.

## Solution implémentée
- Modification de `getSignaturesFormateursBySequence()` dans `presenceDb.ts` pour lire directement la table `presences_formateur`
- Suppression du filtre `typeUtilisateur` devenu inutile
- Correction définitive et robuste pour éviter toute récurrence

## Déploiement
 Serveur de production (98.71.216.234) - Compilé et redémarré
 Toutes les présences formateur sont maintenant visibles dans l'interface
2026-02-10 11:30:23 -05:00

317 lines
9.8 KiB
TypeScript

import { eq, and, sql } from "drizzle-orm";
import { presences, inscriptions, datesFormation, sequences, apprenants } from "../drizzle/schema";
import { getDb } from "./db";
/**
* Valider la présence d'un apprenant (par QR code ou manuellement)
*/
export async function validerPresence(params: {
inscriptionId: number;
dateFormationId: number;
periode: "matin" | "apres_midi";
modeValidation: "qrcode" | "manuel";
validateurId?: number;
commentaire?: string;
signatureUrl?: string;
signatureS3Key?: string;
}) {
const db = await getDb();
if (!db) throw new Error("Database not available");
// Récupérer la date de formation
const dateFormation = await db
.select()
.from(datesFormation)
.where(eq(datesFormation.id, params.dateFormationId))
.limit(1);
if (dateFormation.length === 0) {
throw new Error("Date de formation introuvable");
}
// Vérifier que la date actuelle est le jour de la formation ou après
const now = new Date();
const dateDebut = new Date(dateFormation[0].dateDebut);
// Réinitialiser les heures pour comparer uniquement les dates
now.setHours(0, 0, 0, 0);
dateDebut.setHours(0, 0, 0, 0);
if (now < dateDebut) {
throw new Error("Vous ne pouvez pas émarger avant la date de formation");
}
// Vérifier si la présence existe déjà pour cette période
const presenceExistante = await db
.select()
.from(presences)
.where(
and(
eq(presences.inscriptionId, params.inscriptionId),
eq(presences.dateFormationId, params.dateFormationId),
sql`${presences.periode} = ${params.periode}`
)
);
if (presenceExistante.length > 0) {
throw new Error(`Présence déjà validée pour ${params.periode === 'matin' ? 'le matin' : 'l\'après-midi'}`);
}
// Créer la présence
await db.insert(presences).values({
inscriptionId: params.inscriptionId,
dateFormationId: params.dateFormationId,
periode: params.periode,
heurePresence: new Date(),
modeValidation: params.modeValidation,
validateurId: params.validateurId,
commentaire: params.commentaire,
signatureUrl: params.signatureUrl,
signatureS3Key: params.signatureS3Key,
});
return { success: true };
}
/**
* Récupérer toutes les présences pour une séquence
*/
export async function getPresencesBySequence(sequenceId: number) {
const db = await getDb();
if (!db) throw new Error("Database not available");
const result = await db
.select({
presenceId: presences.id,
inscriptionId: presences.inscriptionId,
dateFormationId: presences.dateFormationId,
periode: presences.periode,
heurePresence: presences.heurePresence,
modeValidation: presences.modeValidation,
validateurId: presences.validateurId,
commentaire: presences.commentaire,
signatureUrl: sql<string | null>`${presences}.signatureUrl`,
signatureS3Key: sql<string | null>`${presences}.signatureS3Key`,
apprenantId: apprenants.id,
apprenantNom: apprenants.nom,
apprenantPrenom: apprenants.prenom,
apprenantEmail: apprenants.email,
dateDebut: datesFormation.dateDebut,
dateFin: datesFormation.dateFin,
})
.from(presences)
.innerJoin(inscriptions, eq(presences.inscriptionId, inscriptions.id))
.innerJoin(apprenants, eq(inscriptions.apprenantId, apprenants.id))
.innerJoin(datesFormation, eq(presences.dateFormationId, datesFormation.id))
.where(eq(datesFormation.sequenceId, sequenceId));
return result;
}
/**
* Récupérer les signatures formateurs pour une séquence
*/
export async function getSignaturesFormateursBySequence(sequenceId: number) {
const db = await getDb();
if (!db) throw new Error("Database not available");
const { presencesFormateur } = await import("../drizzle/schema");
const result = await db
.select({
presenceId: presencesFormateur.id,
formateurId: presencesFormateur.formateurId,
dateFormationId: presencesFormateur.dateFormationId,
periode: presencesFormateur.periode,
heurePresence: presencesFormateur.heurePresence,
modeValidation: presencesFormateur.modeValidation,
signatureUrl: presencesFormateur.signatureUrl,
signatureS3Key: presencesFormateur.signatureS3Key,
dateDebut: datesFormation.dateDebut,
dateFin: datesFormation.dateFin,
})
.from(presencesFormateur)
.innerJoin(datesFormation, eq(presencesFormateur.dateFormationId, datesFormation.id))
.where(eq(datesFormation.sequenceId, sequenceId));
return result;
}
/**
* Récupérer les présences pour une inscription spécifique
*/
export async function getPresencesByInscription(inscriptionId: number) {
const db = await getDb();
if (!db) throw new Error("Database not available");
const result = await db
.select({
presenceId: presences.id,
dateFormationId: presences.dateFormationId,
periode: presences.periode,
heurePresence: presences.heurePresence,
modeValidation: presences.modeValidation,
validateurId: presences.validateurId,
commentaire: presences.commentaire,
signatureUrl: sql<string | null>`${presences}.signatureUrl`,
signatureS3Key: sql<string | null>`${presences}.signatureS3Key`,
dateDebut: datesFormation.dateDebut,
dateFin: datesFormation.dateFin,
})
.from(presences)
.innerJoin(datesFormation, eq(presences.dateFormationId, datesFormation.id))
.where(eq(presences.inscriptionId, inscriptionId));
return result;
}
/**
* Vérifier si toutes les présences sont validées pour une inscription
*/
export async function checkAllPresencesValidated(inscriptionId: number): Promise<boolean> {
const db = await getDb();
if (!db) throw new Error("Database not available");
// Récupérer l'inscription avec la séquence
const inscription = await db
.select({
sequenceId: inscriptions.sequenceId,
})
.from(inscriptions)
.where(eq(inscriptions.id, inscriptionId))
.limit(1);
if (inscription.length === 0) {
throw new Error("Inscription not found");
}
// Compter le nombre de dates de formation pour cette séquence
const datesCount = await db
.select({ count: datesFormation.id })
.from(datesFormation)
.where(eq(datesFormation.sequenceId, inscription[0].sequenceId));
// Compter le nombre de présences validées pour cette inscription
const presencesCount = await db
.select({ count: presences.id })
.from(presences)
.where(eq(presences.inscriptionId, inscriptionId));
return presencesCount.length === datesCount.length;
}
/**
* Supprimer une présence
*/
export async function supprimerPresence(presenceId: number) {
const db = await getDb();
if (!db) throw new Error("Database not available");
await db.delete(presences).where(eq(presences.id, presenceId));
return { success: true };
}
/**
* Valider la présence d'un formateur (par QR code ou manuellement)
*/
export async function validerPresenceFormateur(params: {
formateurId: number;
sequenceId: number;
dateFormationId: number;
periode: "matin" | "apres_midi";
modeValidation: "qrcode" | "manuel";
commentaire?: string;
signatureUrl?: string;
signatureS3Key?: string;
}) {
const db = await getDb();
if (!db) throw new Error("Database not available");
// Récupérer la date de formation
const dateFormation = await db
.select()
.from(datesFormation)
.where(eq(datesFormation.id, params.dateFormationId))
.limit(1);
if (dateFormation.length === 0) {
throw new Error("Date de formation introuvable");
}
// Vérifier que la date actuelle est le jour de la formation ou après
const now = new Date();
const dateDebut = new Date(dateFormation[0].dateDebut);
// Réinitialiser les heures pour comparer uniquement les dates
now.setHours(0, 0, 0, 0);
dateDebut.setHours(0, 0, 0, 0);
if (now < dateDebut) {
throw new Error("Vous ne pouvez pas émarger avant la date de formation");
}
// Vérifier si la présence existe déjà pour cette période
const presenceExistante = await db
.select()
.from(presences)
.where(
and(
eq(presences.formateurId, params.formateurId),
eq(presences.dateFormationId, params.dateFormationId),
sql`${presences.periode} = ${params.periode}`,
sql`${presences.typeUtilisateur} = 'formateur'`
)
);
if (presenceExistante.length > 0) {
throw new Error(`Présence déjà validée pour ${params.periode === 'matin' ? 'le matin' : 'l\'après-midi'}`);
}
// Créer la présence formateur
await db.insert(presences).values({
typeUtilisateur: "formateur",
formateurId: params.formateurId,
dateFormationId: params.dateFormationId,
periode: params.periode,
heurePresence: new Date(),
modeValidation: params.modeValidation,
commentaire: params.commentaire,
signatureUrl: params.signatureUrl,
signatureS3Key: params.signatureS3Key,
});
return { success: true };
}
/**
* Récupérer les présences du formateur pour une séquence
*/
export async function getPresencesFormateurBySequence(sequenceId: number) {
const db = await getDb();
if (!db) throw new Error("Database not available");
const result = await db
.select({
presenceId: presences.id,
formateurId: presences.formateurId,
dateFormationId: presences.dateFormationId,
periode: presences.periode,
heurePresence: presences.heurePresence,
modeValidation: presences.modeValidation,
commentaire: presences.commentaire,
signatureUrl: sql<string | null>`${presences}.signatureUrl`,
signatureS3Key: sql<string | null>`${presences}.signatureS3Key`,
})
.from(presences)
.innerJoin(datesFormation, eq(presences.dateFormationId, datesFormation.id))
.where(
and(
eq(datesFormation.sequenceId, sequenceId),
sql`${presences}.typeUtilisateur = 'formateur'`
)
);
return result;
}