import { eq, desc, and, gte, lte, sql, like } from "drizzle-orm"; import { getDb } from "./db"; import { logsNotifications, sequences, apprenants, formateurs, formations } from "../drizzle/schema"; export type NotificationType = | "remerciement" | "notification_formateur_inscription" | "notification_formateur_annulation" | "alerte_capacite" | "notification_liste_attente"; export interface LogNotificationInput { type: NotificationType; sequenceId?: number; apprenantId?: number; formateurId?: number; emailDestinataire: string; sujet: string; statut: "success" | "failed"; messageErreur?: string; metadata?: Record; } /** * Enregistre un log de notification */ export async function logNotification(input: LogNotificationInput) { const db = await getDb(); if (!db) { console.warn("[NotificationLogs] Database not available"); return; } try { await db.insert(logsNotifications).values({ type: input.type, sequenceId: input.sequenceId || null, apprenantId: input.apprenantId || null, formateurId: input.formateurId || null, emailDestinataire: input.emailDestinataire, sujet: input.sujet, statut: input.statut, messageErreur: input.messageErreur || null, metadata: input.metadata ? JSON.stringify(input.metadata) : null, }); console.log(`[NotificationLogs] Log créé: ${input.type} -> ${input.emailDestinataire} (${input.statut})`); } catch (error) { console.error("[NotificationLogs] Erreur lors de la création du log:", error); } } /** * Récupère l'historique des notifications avec filtres */ export async function getNotificationLogs(filters?: { type?: NotificationType; dateDebut?: Date; dateFin?: Date; statut?: "success" | "failed"; email?: string; sequenceId?: number; limit?: number; offset?: number; }) { const db = await getDb(); if (!db) return { logs: [], total: 0 }; const conditions = []; if (filters?.type) { conditions.push(eq(logsNotifications.type, filters.type)); } if (filters?.dateDebut) { conditions.push(gte(logsNotifications.dateEnvoi, filters.dateDebut)); } if (filters?.dateFin) { conditions.push(lte(logsNotifications.dateEnvoi, filters.dateFin)); } if (filters?.statut) { conditions.push(eq(logsNotifications.statut, filters.statut)); } if (filters?.email) { conditions.push(like(logsNotifications.emailDestinataire, `%${filters.email}%`)); } if (filters?.sequenceId) { conditions.push(eq(logsNotifications.sequenceId, filters.sequenceId)); } const whereClause = conditions.length > 0 ? and(...conditions) : undefined; // Récupérer les logs avec les informations associées const logs = await db .select({ id: logsNotifications.id, type: logsNotifications.type, sequenceId: logsNotifications.sequenceId, apprenantId: logsNotifications.apprenantId, formateurId: logsNotifications.formateurId, emailDestinataire: logsNotifications.emailDestinataire, sujet: logsNotifications.sujet, dateEnvoi: logsNotifications.dateEnvoi, statut: logsNotifications.statut, messageErreur: logsNotifications.messageErreur, metadata: logsNotifications.metadata, sequenceNom: sequences.nom, formationNom: formations.nom, apprenantNom: apprenants.nom, apprenantPrenom: apprenants.prenom, formateurNom: formateurs.nom, }) .from(logsNotifications) .leftJoin(sequences, eq(logsNotifications.sequenceId, sequences.id)) .leftJoin(formations, eq(sequences.formationId, formations.id)) .leftJoin(apprenants, eq(logsNotifications.apprenantId, apprenants.id)) .leftJoin(formateurs, eq(logsNotifications.formateurId, formateurs.id)) .where(whereClause) .orderBy(desc(logsNotifications.dateEnvoi)) .limit(filters?.limit || 50) .offset(filters?.offset || 0); // Compter le total const [countResult] = await db .select({ count: sql`COUNT(*)` }) .from(logsNotifications) .where(whereClause); // S'assurer que les logs sont bien définis et que tous les champs sont valides const safeLogs = (logs || []).map(log => ({ ...log, sequenceNom: log.sequenceNom || null, formationNom: log.formationNom || null, apprenantNom: log.apprenantNom || null, apprenantPrenom: log.apprenantPrenom || null, formateurNom: log.formateurNom || null, messageErreur: log.messageErreur || null, metadata: log.metadata || null, })); return { logs: safeLogs, total: countResult?.count || 0, }; } /** * Récupère les statistiques des notifications */ export async function getNotificationStats(filters?: { dateDebut?: Date; dateFin?: Date; }) { const db = await getDb(); if (!db) return null; const conditions = []; if (filters?.dateDebut) { conditions.push(gte(logsNotifications.dateEnvoi, filters.dateDebut)); } if (filters?.dateFin) { conditions.push(lte(logsNotifications.dateEnvoi, filters.dateFin)); } const whereClause = conditions.length > 0 ? and(...conditions) : undefined; // Statistiques globales const [globalStats] = await db .select({ total: sql`COUNT(*)`, success: sql`SUM(CASE WHEN statut = 'success' THEN 1 ELSE 0 END)`, failed: sql`SUM(CASE WHEN statut = 'failed' THEN 1 ELSE 0 END)`, }) .from(logsNotifications) .where(whereClause); // Statistiques par type const statsByType = await db .select({ type: logsNotifications.type, total: sql`COUNT(*)`, success: sql`SUM(CASE WHEN statut = 'success' THEN 1 ELSE 0 END)`, failed: sql`SUM(CASE WHEN statut = 'failed' THEN 1 ELSE 0 END)`, }) .from(logsNotifications) .where(whereClause) .groupBy(logsNotifications.type); // Évolution par jour (7 derniers jours) const evolutionParJour = await db .select({ date: sql`DATE(dateEnvoi)`, total: sql`COUNT(*)`, success: sql`SUM(CASE WHEN statut = 'success' THEN 1 ELSE 0 END)`, failed: sql`SUM(CASE WHEN statut = 'failed' THEN 1 ELSE 0 END)`, }) .from(logsNotifications) .where(whereClause) .groupBy(sql`DATE(dateEnvoi)`) .orderBy(sql`DATE(dateEnvoi)`) .limit(30); // Vérifier que globalStats existe et a des valeurs valides const total = globalStats?.total || 0; const success = globalStats?.success || 0; const failed = globalStats?.failed || 0; const tauxSucces = total > 0 ? Math.round((success / total) * 100) : 0; return { global: { total, success, failed, tauxSucces, }, parType: statsByType || [], evolutionParJour: evolutionParJour || [], }; } /** * Labels pour les types de notifications */ export const notificationTypeLabels: Record = { remerciement: "Remerciement post-formation", notification_formateur_inscription: "Notification formateur (inscription)", notification_formateur_annulation: "Notification formateur (annulation)", alerte_capacite: "Alerte capacité atteinte", notification_liste_attente: "Notification liste d'attente", };