Modifications apportées : - Backend : Ajout de la fonction getSignaturesFormateursBySequence dans presenceDb.ts - Backend : Ajout de la procédure tRPC presences.listSignaturesFormateurs dans routers.ts - Frontend : Ajout de l'encart visuel des signatures formateurs dans FormateurEmargement.tsx - L'encart affiche les signatures avec date, période (matin/après-midi), heure et image de signature - Rafraîchissement automatique toutes les 30 secondes - Design distinct avec bordure bleue pour différencier des signatures apprenants - Déployé sur recette (108.143.67.56) et production (98.71.216.234)
320 lines
9.8 KiB
TypeScript
320 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 result = await db
|
|
.select({
|
|
presenceId: presences.id,
|
|
formateurId: presences.formateurId,
|
|
dateFormationId: presences.dateFormationId,
|
|
periode: presences.periode,
|
|
heurePresence: presences.heurePresence,
|
|
modeValidation: presences.modeValidation,
|
|
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(
|
|
and(
|
|
eq(datesFormation.sequenceId, sequenceId),
|
|
eq(presences.typeUtilisateur, "formateur")
|
|
)
|
|
);
|
|
|
|
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;
|
|
}
|