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); return { logs, 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); return { global: { total: globalStats?.total || 0, success: globalStats?.success || 0, failed: globalStats?.failed || 0, tauxSucces: globalStats?.total ? Math.round((globalStats.success / globalStats.total) * 100) : 0, }, parType: statsByType, 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", };