403 lines
12 KiB
TypeScript
403 lines
12 KiB
TypeScript
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;
|
|
}
|