211 lines
6.5 KiB
TypeScript
211 lines
6.5 KiB
TypeScript
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<string, unknown>;
|
|
}
|
|
|
|
/**
|
|
* 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<number>`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<number>`COUNT(*)`,
|
|
success: sql<number>`SUM(CASE WHEN statut = 'success' THEN 1 ELSE 0 END)`,
|
|
failed: sql<number>`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<number>`COUNT(*)`,
|
|
success: sql<number>`SUM(CASE WHEN statut = 'success' THEN 1 ELSE 0 END)`,
|
|
failed: sql<number>`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<string>`DATE(dateEnvoi)`,
|
|
total: sql<number>`COUNT(*)`,
|
|
success: sql<number>`SUM(CASE WHEN statut = 'success' THEN 1 ELSE 0 END)`,
|
|
failed: sql<number>`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<NotificationType, string> = {
|
|
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",
|
|
};
|