This commit is contained in:
Manus
2026-02-02 17:32:58 -05:00
parent 04001e7bcd
commit 70f9055073
94 changed files with 25859 additions and 5260 deletions

View File

@@ -0,0 +1,402 @@
import { eq, sql, and, gte, lte, desc } from "drizzle-orm";
import { getDb } from "./db";
import {
sequences,
formations,
formateurs,
datesFormation,
inscriptions,
apprenants,
supportsFormation
} from "../drizzle/schema";
/**
* Récupère le calendrier des interventions d'un formateur
* @param formateurId - ID du formateur
* @param dateDebut - Date de début optionnelle
* @param dateFin - Date de fin optionnelle
*/
export async function getCalendrierFormateur(formateurId: number, dateDebut?: Date, dateFin?: Date) {
const db = await getDb();
if (!db) return [];
const conditions = [eq(sequences.formateurId, formateurId)];
if (dateDebut) {
conditions.push(gte(datesFormation.dateDebut, dateDebut));
}
if (dateFin) {
conditions.push(lte(datesFormation.dateFin, dateFin));
}
const results = await db
.select({
sequenceId: sequences.id,
sequenceNom: sequences.nom,
formationId: formations.id,
formationNom: formations.nom,
dateId: datesFormation.id,
dateDebut: datesFormation.dateDebut,
dateFin: datesFormation.dateFin,
ordre: datesFormation.ordre,
lieu: sequences.lieu,
nbInscrits: sql<number>`(
SELECT COUNT(*)
FROM inscriptions
WHERE inscriptions.sequenceId = ${sequences.id}
AND inscriptions.statut IN ('confirmee', 'liste_attente')
)`,
})
.from(sequences)
.innerJoin(formations, eq(sequences.formationId, formations.id))
.innerJoin(datesFormation, eq(datesFormation.sequenceId, sequences.id))
.where(conditions.length > 0 ? and(...conditions) : undefined)
.orderBy(datesFormation.dateDebut);
return results;
}
/**
* Récupère la liste des apprenants inscrits à une séquence
* @param sequenceId - ID de la séquence
*/
export async function getApprenantsSequence(sequenceId: number) {
const db = await getDb();
if (!db) return [];
const results = await db
.select({
inscriptionId: inscriptions.id,
apprenantId: apprenants.id,
nom: apprenants.nom,
prenom: apprenants.prenom,
email: apprenants.email,
fonction: apprenants.fonction,
codeEtablissement: apprenants.codeEtablissement,
statut: inscriptions.statut,
dateInscription: inscriptions.dateInscription,
// presenceValidee n'existe pas dans le schéma actuel
})
.from(inscriptions)
.innerJoin(apprenants, eq(inscriptions.apprenantId, apprenants.id))
.where(eq(inscriptions.sequenceId, sequenceId))
.orderBy(apprenants.nom, apprenants.prenom);
return results;
}
/**
* Récupère les supports de formation d'une séquence
* @param sequenceId - ID de la séquence
*/
export async function getSupportsSequence(sequenceId: number) {
const db = await getDb();
if (!db) return [];
const results = await db
.select({
id: supportsFormation.id,
nomFichier: supportsFormation.nomFichier,
typeFichier: supportsFormation.typeFichier,
tailleFichier: supportsFormation.tailleFichier,
urlFichier: supportsFormation.urlFichier,
description: supportsFormation.description,
formateurNom: formateurs.nom,
createdAt: supportsFormation.createdAt,
})
.from(supportsFormation)
.innerJoin(formateurs, eq(supportsFormation.formateurId, formateurs.id))
.where(eq(supportsFormation.sequenceId, sequenceId))
.orderBy(desc(supportsFormation.createdAt));
return results;
}
/**
* Ajoute un support de formation
* @param support - Données du support à ajouter
*/
export async function ajouterSupport(support: {
sequenceId: number;
formateurId: number;
nomFichier: string;
typeFichier: string;
tailleFichier: number;
urlFichier: string;
s3Key: string;
description?: string;
}) {
const db = await getDb();
if (!db) throw new Error("Database not available");
const [result] = await db.insert(supportsFormation).values(support);
return result;
}
/**
* Supprime un support de formation
* @param supportId - ID du support à supprimer
* @param formateurId - ID du formateur (pour vérification)
*/
export async function supprimerSupport(supportId: number, formateurId: number) {
const db = await getDb();
if (!db) throw new Error("Database not available");
// Vérifier que le support appartient bien au formateur
const [support] = await db
.select()
.from(supportsFormation)
.where(
and(
eq(supportsFormation.id, supportId),
eq(supportsFormation.formateurId, formateurId)
)
)
.limit(1);
if (!support) {
throw new Error("Support non trouvé ou non autorisé");
}
await db.delete(supportsFormation).where(eq(supportsFormation.id, supportId));
return support; // Retourner le support pour récupérer la clé S3
}
/**
* Valide la présence d'un apprenant
* @param inscriptionId - ID de l'inscription
* @param present - true si présent, false sinon
*/
export async function validerPresence(inscriptionId: number, present: boolean) {
const db = await getDb();
if (!db) throw new Error("Database not available");
await db
.update(inscriptions)
.set({ statut: present ? 'confirmee' : 'annulee' })
.where(eq(inscriptions.id, inscriptionId));
}
/**
* Récupère l'historique des formations d'un formateur
* @param formateurId - ID du formateur
* @param limit - Nombre de résultats à retourner (par défaut 50)
*/
export async function getHistoriqueFormateur(formateurId: number, limit: number = 50) {
const db = await getDb();
if (!db) return [];
const results = await db
.select({
sequenceId: sequences.id,
sequenceNom: sequences.nom,
formationNom: formations.nom,
dateDebut: sql<Date>`MIN(${datesFormation.dateDebut})`,
dateFin: sql<Date>`MAX(${datesFormation.dateFin})`,
lieu: sequences.lieu,
nbInscrits: sql<number>`(
SELECT COUNT(*)
FROM inscriptions
WHERE inscriptions.sequenceId = ${sequences.id}
AND inscriptions.statut IN ('confirmee', 'liste_attente')
)`,
nbPresents: sql<number>`(
SELECT COUNT(*)
FROM inscriptions
WHERE inscriptions.sequenceId = ${sequences.id}
AND inscriptions.statut = 'confirmee'
)`,
})
.from(sequences)
.innerJoin(formations, eq(sequences.formationId, formations.id))
.innerJoin(datesFormation, eq(datesFormation.sequenceId, sequences.id))
.where(eq(sequences.formateurId, formateurId))
.groupBy(sequences.id, sequences.nom, formations.nom, sequences.lieu)
.orderBy(desc(sql`MIN(${datesFormation.dateDebut})`))
.limit(limit);
return results;
}
/**
* Récupère les détails d'une séquence pour un formateur
* @param sequenceId - ID de la séquence
* @param formateurId - ID du formateur (pour vérification)
*/
export async function getDetailSequence(sequenceId: number, formateurId: number) {
const db = await getDb();
if (!db) return null;
const [result] = await db
.select({
id: sequences.id,
nom: sequences.nom,
formationNom: formations.nom,
lieu: sequences.lieu,
capaciteMax: sequences.capaciteMax,
formateurNom: formateurs.nom,
})
.from(sequences)
.innerJoin(formations, eq(sequences.formationId, formations.id))
.innerJoin(formateurs, eq(sequences.formateurId, formateurs.id))
.where(
and(
eq(sequences.id, sequenceId),
eq(sequences.formateurId, formateurId)
)
)
.limit(1);
if (!result) return null;
// Récupérer les dates de formation
const dates = await db
.select({
id: datesFormation.id,
dateDebut: datesFormation.dateDebut,
dateFin: datesFormation.dateFin,
ordre: datesFormation.ordre,
})
.from(datesFormation)
.where(eq(datesFormation.sequenceId, sequenceId))
.orderBy(datesFormation.dateDebut);
return {
...result,
dates,
};
}
/**
* Récupérer les statistiques du tableau de bord pour un formateur
*/
export async function getFormateurDashboardStats(formateurId: number) {
const db = await getDb();
if (!db) throw new Error("Database not available");
// Nombre total de séquences du formateur
const totalSequences = await db
.select({ count: sql<number>`COUNT(*)` })
.from(sequences)
.where(eq(sequences.formateurId, formateurId));
// Nombre total d'inscrits confirmés à toutes les séquences du formateur
const totalInscrits = await db
.select({ count: sql<number>`COUNT(*)` })
.from(inscriptions)
.innerJoin(sequences, eq(inscriptions.sequenceId, sequences.id))
.where(
and(
eq(sequences.formateurId, formateurId),
eq(inscriptions.statut, 'confirmee')
)
);
// Nombre de séquences à venir (ayant au moins une date future)
const today = new Date();
const sequencesAvenir = await db
.select({ sequenceId: datesFormation.sequenceId })
.from(datesFormation)
.innerJoin(sequences, eq(datesFormation.sequenceId, sequences.id))
.where(
and(
eq(sequences.formateurId, formateurId),
gte(datesFormation.dateDebut, today)
)
)
.groupBy(datesFormation.sequenceId);
return {
totalSequences: totalSequences[0]?.count || 0,
totalInscrits: totalInscrits[0]?.count || 0,
sequencesAvenir: sequencesAvenir.length,
};
}
/**
* Récupérer les prochaines séquences du formateur avec leurs détails
*/
export async function getFormateurProchainesSequences(formateurId: number, limit: number = 10) {
const db = await getDb();
if (!db) return [];
const today = new Date();
// Récupérer les séquences du formateur ayant au moins une date future
const seqs = await db
.select()
.from(sequences)
.where(eq(sequences.formateurId, formateurId));
// Pour chaque séquence, récupérer les détails
const sequencesAvecDetails = await Promise.all(
seqs.map(async (seq) => {
// Récupérer toutes les dates de la séquence
const dates = await db
.select()
.from(datesFormation)
.where(eq(datesFormation.sequenceId, seq.id))
.orderBy(datesFormation.dateDebut);
// Vérifier si au moins une date est future
const hasFutureDate = dates.some(d => new Date(d.dateDebut) >= today);
if (!hasFutureDate) return null;
// Récupérer la formation
const formation = await db
.select()
.from(formations)
.where(eq(formations.id, seq.formationId))
.limit(1);
// Compter les inscrits confirmés
const inscritsCount = await db
.select({ count: sql<number>`COUNT(*)` })
.from(inscriptions)
.where(
and(
eq(inscriptions.sequenceId, seq.id),
eq(inscriptions.statut, 'confirmee')
)
);
return {
...seq,
formation: formation[0] || null,
dates,
nbInscrits: inscritsCount[0]?.count || 0,
prochaineDateDebut: dates.find(d => new Date(d.dateDebut) >= today)?.dateDebut || dates[0]?.dateDebut,
};
})
);
// Filtrer les séquences nulles et trier par prochaine date
const sequencesFiltrees = sequencesAvecDetails
.filter(s => s !== null)
.sort((a, b) => {
const dateA = new Date(a!.prochaineDateDebut);
const dateB = new Date(b!.prochaineDateDebut);
return dateA.getTime() - dateB.getTime();
})
.slice(0, limit);
return sequencesFiltrees;
}
/**
* Récupérer l'email du formateur à partir de son ID
*/
export async function getFormateurEmailById(formateurId: number): Promise<string | null> {
const db = await getDb();
if (!db) return null;
const result = await db
.select({ email: formateurs.email })
.from(formateurs)
.where(eq(formateurs.id, formateurId))
.limit(1);
return result[0]?.email || null;
}