✅ **1. Indicateur visuel de remplissage** - Composant CapacityProgressBar avec barre de progression colorée - Affichage "X / Y" avec pourcentage - Couleurs automatiques : vert (<70%), orange (70-90%), rouge (>90%) - Intégré dans la colonne capacité du tableau des séquences ✅ **2. Vue calendrier mensuel** - Nouvelle page AdminCalendrier accessible depuis le menu - Calendrier mensuel avec navigation (mois précédent/suivant, aujourd'hui) - Affichage des séquences sur leurs dates respectives - Dialog de détails au clic sur une séquence - Vue complète avec toutes les informations (lieu, formateur, capacité, dates) ✅ **3. Système de rappels automatiques personnalisables** - Tables rappels et rappelsEnvoyes créées - Procédures tRPC complètes (list, create, update, delete) - Page AdminRappels avec interface de gestion - Configuration de rappels multiples (J-15, J-7, J-1, etc.) - Templates d'emails personnalisables avec variables dynamiques - Activation/désactivation des rappels - Infrastructure prête pour l'envoi automatique (nécessite service de planification externe) Ces améliorations facilitent grandement la planification et le suivi des formations.
780 lines
21 KiB
TypeScript
780 lines
21 KiB
TypeScript
import { eq, and, sql } from "drizzle-orm";
|
|
import { drizzle } from "drizzle-orm/mysql2";
|
|
import {
|
|
InsertUser,
|
|
users,
|
|
formations,
|
|
apprenants,
|
|
formateurs,
|
|
sequences,
|
|
datesFormation,
|
|
inscriptions,
|
|
passwordResetTokens,
|
|
InsertPasswordResetToken,
|
|
emailTemplates,
|
|
emailConfig,
|
|
InsertEmailConfig,
|
|
InsertSequence,
|
|
InsertDateFormation,
|
|
InsertInscription,
|
|
InsertFormation,
|
|
InsertApprenant,
|
|
InsertFormateur,
|
|
InsertRappel,
|
|
Sequence,
|
|
DateFormation,
|
|
Apprenant,
|
|
Formation,
|
|
Formateur,
|
|
EmailTemplate,
|
|
InsertEmailTemplate
|
|
} from "../drizzle/schema";
|
|
import { ENV } from './_core/env';
|
|
|
|
let _db: ReturnType<typeof drizzle> | null = null;
|
|
|
|
// Lazily create the drizzle instance so local tooling can run without a DB.
|
|
export async function getDb() {
|
|
if (!_db && process.env.DATABASE_URL) {
|
|
try {
|
|
_db = drizzle(process.env.DATABASE_URL);
|
|
} catch (error) {
|
|
console.warn("[Database] Failed to connect:", error);
|
|
_db = null;
|
|
}
|
|
}
|
|
return _db;
|
|
}
|
|
|
|
export async function upsertUser(user: InsertUser): Promise<void> {
|
|
if (!user.openId) {
|
|
throw new Error("User openId is required for upsert");
|
|
}
|
|
|
|
const db = await getDb();
|
|
if (!db) {
|
|
console.warn("[Database] Cannot upsert user: database not available");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const values: InsertUser = {
|
|
openId: user.openId,
|
|
};
|
|
const updateSet: Record<string, unknown> = {};
|
|
|
|
const textFields = ["name", "email", "loginMethod"] as const;
|
|
type TextField = (typeof textFields)[number];
|
|
|
|
const assignNullable = (field: TextField) => {
|
|
const value = user[field];
|
|
if (value === undefined) return;
|
|
const normalized = value ?? null;
|
|
values[field] = normalized;
|
|
updateSet[field] = normalized;
|
|
};
|
|
|
|
textFields.forEach(assignNullable);
|
|
|
|
if (user.lastSignedIn !== undefined) {
|
|
values.lastSignedIn = user.lastSignedIn;
|
|
updateSet.lastSignedIn = user.lastSignedIn;
|
|
}
|
|
if (user.role !== undefined) {
|
|
values.role = user.role;
|
|
updateSet.role = user.role;
|
|
} else if (user.openId === ENV.ownerOpenId) {
|
|
values.role = 'admin';
|
|
updateSet.role = 'admin';
|
|
}
|
|
|
|
if (!values.lastSignedIn) {
|
|
values.lastSignedIn = new Date();
|
|
}
|
|
|
|
if (Object.keys(updateSet).length === 0) {
|
|
updateSet.lastSignedIn = new Date();
|
|
}
|
|
|
|
await db.insert(users).values(values).onDuplicateKeyUpdate({
|
|
set: updateSet,
|
|
});
|
|
} catch (error) {
|
|
console.error("[Database] Failed to upsert user:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
export async function getUserByOpenId(openId: string) {
|
|
const db = await getDb();
|
|
if (!db) {
|
|
console.warn("[Database] Cannot get user: database not available");
|
|
return undefined;
|
|
}
|
|
|
|
const result = await db.select().from(users).where(eq(users.openId, openId)).limit(1);
|
|
|
|
return result.length > 0 ? result[0] : undefined;
|
|
}
|
|
|
|
// ==================== FORMATIONS ====================
|
|
|
|
export async function createFormation(data: InsertFormation) {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
|
|
const result = await db.insert(formations).values(data);
|
|
return result;
|
|
}
|
|
|
|
export async function getFormations() {
|
|
const db = await getDb();
|
|
if (!db) return [];
|
|
|
|
return await db.select().from(formations);
|
|
}
|
|
|
|
export async function getFormationById(id: number) {
|
|
const db = await getDb();
|
|
if (!db) return undefined;
|
|
|
|
const result = await db.select().from(formations).where(eq(formations.id, id)).limit(1);
|
|
return result.length > 0 ? result[0] : undefined;
|
|
}
|
|
|
|
export async function getFormationByLien(lien: string) {
|
|
const db = await getDb();
|
|
if (!db) return undefined;
|
|
|
|
const result = await db.select().from(formations).where(eq(formations.lienUnique, lien)).limit(1);
|
|
return result.length > 0 ? result[0] : undefined;
|
|
}
|
|
|
|
export async function updateFormation(id: number, data: Partial<InsertFormation>) {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
|
|
await db.update(formations).set(data).where(eq(formations.id, id));
|
|
}
|
|
|
|
export async function deleteFormation(id: number) {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
|
|
await db.delete(formations).where(eq(formations.id, id));
|
|
}
|
|
|
|
// ==================== SÉQUENCES ====================
|
|
|
|
export async function createSequence(data: InsertSequence) {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
|
|
const result = await db.insert(sequences).values(data);
|
|
return result;
|
|
}
|
|
|
|
export async function getSequences() {
|
|
const db = await getDb();
|
|
if (!db) return [];
|
|
|
|
return await db.select().from(sequences);
|
|
}
|
|
|
|
export async function getSequenceById(id: number): Promise<Sequence | undefined> {
|
|
const db = await getDb();
|
|
if (!db) return undefined;
|
|
|
|
const result = await db.select().from(sequences).where(eq(sequences.id, id)).limit(1);
|
|
return result.length > 0 ? result[0] : undefined;
|
|
}
|
|
|
|
export async function getSequencesByFormation(formationId: number) {
|
|
const db = await getDb();
|
|
if (!db) return [];
|
|
|
|
return await db.select().from(sequences).where(eq(sequences.formationId, formationId));
|
|
}
|
|
|
|
export async function updateSequence(id: number, data: Partial<InsertSequence>) {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
|
|
await db.update(sequences).set(data).where(eq(sequences.id, id));
|
|
}
|
|
|
|
export async function deleteSequence(id: number) {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
|
|
await db.delete(sequences).where(eq(sequences.id, id));
|
|
}
|
|
|
|
// ==================== DATES DE FORMATION ====================
|
|
|
|
export async function createDateFormation(data: InsertDateFormation) {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
|
|
const result = await db.insert(datesFormation).values(data);
|
|
return result;
|
|
}
|
|
|
|
export async function getDatesBySequence(sequenceId: number): Promise<DateFormation[]> {
|
|
const db = await getDb();
|
|
if (!db) return [];
|
|
|
|
const results = await db.select()
|
|
.from(datesFormation)
|
|
.where(eq(datesFormation.sequenceId, sequenceId))
|
|
.orderBy(datesFormation.ordre);
|
|
|
|
return results;
|
|
}
|
|
|
|
export async function deleteDatesBySequence(sequenceId: number) {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
|
|
await db.delete(datesFormation).where(eq(datesFormation.sequenceId, sequenceId));
|
|
}
|
|
|
|
// ==================== APPRENANTS ====================
|
|
|
|
export async function createApprenant(data: InsertApprenant) {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
|
|
const result = await db.insert(apprenants).values(data);
|
|
return result;
|
|
}
|
|
|
|
export async function getApprenants() {
|
|
const db = await getDb();
|
|
if (!db) return [];
|
|
|
|
return await db.select().from(apprenants);
|
|
}
|
|
|
|
export async function getApprenantById(id: number): Promise<Apprenant | undefined> {
|
|
const db = await getDb();
|
|
if (!db) return undefined;
|
|
|
|
const result = await db.select().from(apprenants).where(eq(apprenants.id, id)).limit(1);
|
|
return result.length > 0 ? result[0] : undefined;
|
|
}
|
|
|
|
export async function getApprenantByEmail(email: string) {
|
|
const db = await getDb();
|
|
if (!db) return undefined;
|
|
|
|
const result = await db.select().from(apprenants).where(eq(apprenants.email, email)).limit(1);
|
|
return result.length > 0 ? result[0] : undefined;
|
|
}
|
|
|
|
export async function updateApprenant(id: number, data: Partial<InsertApprenant>) {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
|
|
await db.update(apprenants).set(data).where(eq(apprenants.id, id));
|
|
}
|
|
|
|
export async function deleteApprenant(id: number) {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
|
|
await db.delete(apprenants).where(eq(apprenants.id, id));
|
|
}
|
|
|
|
// ==================== INSCRIPTIONS ====================
|
|
|
|
export async function createInscription(data: InsertInscription) {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
|
|
const result = await db.insert(inscriptions).values(data);
|
|
return result;
|
|
}
|
|
|
|
export async function getAllInscriptions() {
|
|
const db = await getDb();
|
|
if (!db) return [];
|
|
|
|
const results = await db.select({
|
|
inscription: inscriptions,
|
|
apprenant: apprenants,
|
|
})
|
|
.from(inscriptions)
|
|
.leftJoin(apprenants, eq(inscriptions.apprenantId, apprenants.id));
|
|
|
|
return results;
|
|
}
|
|
|
|
export async function getInscriptionsBySequence(sequenceId: number) {
|
|
const db = await getDb();
|
|
if (!db) return [];
|
|
|
|
const results = await db.select({
|
|
inscription: inscriptions,
|
|
apprenant: apprenants,
|
|
})
|
|
.from(inscriptions)
|
|
.leftJoin(apprenants, eq(inscriptions.apprenantId, apprenants.id))
|
|
.where(eq(inscriptions.sequenceId, sequenceId));
|
|
|
|
return results;
|
|
}
|
|
|
|
export async function getInscriptionsByApprenant(apprenantId: number) {
|
|
const db = await getDb();
|
|
if (!db) return [];
|
|
|
|
const results = await db.select({
|
|
inscription: inscriptions,
|
|
sequence: sequences,
|
|
})
|
|
.from(inscriptions)
|
|
.leftJoin(sequences, eq(inscriptions.sequenceId, sequences.id))
|
|
.where(eq(inscriptions.apprenantId, apprenantId));
|
|
|
|
return results;
|
|
}
|
|
|
|
export async function checkExistingInscription(apprenantId: number, sequenceId: number) {
|
|
const db = await getDb();
|
|
if (!db) return undefined;
|
|
|
|
const result = await db.select()
|
|
.from(inscriptions)
|
|
.where(and(
|
|
eq(inscriptions.apprenantId, apprenantId),
|
|
eq(inscriptions.sequenceId, sequenceId)
|
|
))
|
|
.limit(1);
|
|
|
|
return result.length > 0 ? result[0] : undefined;
|
|
}
|
|
|
|
export async function countInscriptionsBySequence(sequenceId: number, statut?: string) {
|
|
const db = await getDb();
|
|
if (!db) return 0;
|
|
|
|
const conditions = [eq(inscriptions.sequenceId, sequenceId)];
|
|
if (statut) {
|
|
conditions.push(eq(inscriptions.statut, statut as any));
|
|
}
|
|
|
|
const result = await db.select({ count: sql<number>`count(*)` })
|
|
.from(inscriptions)
|
|
.where(and(...conditions));
|
|
|
|
return result[0]?.count || 0;
|
|
}
|
|
|
|
export async function updateInscription(id: number, data: Partial<InsertInscription>) {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
|
|
await db.update(inscriptions).set(data).where(eq(inscriptions.id, id));
|
|
}
|
|
|
|
export async function deleteInscription(id: number) {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
|
|
await db.delete(inscriptions).where(eq(inscriptions.id, id));
|
|
}
|
|
|
|
// ==================== GESTION DES UTILISATEURS ====================
|
|
|
|
export async function createUser(data: InsertUser) {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
|
|
const result = await db.insert(users).values(data);
|
|
return result;
|
|
}
|
|
|
|
export async function getAllUsers() {
|
|
const db = await getDb();
|
|
if (!db) return [];
|
|
|
|
return await db.select().from(users);
|
|
}
|
|
|
|
export async function getUserById(id: number) {
|
|
const db = await getDb();
|
|
if (!db) return undefined;
|
|
|
|
const result = await db.select().from(users).where(eq(users.id, id)).limit(1);
|
|
return result.length > 0 ? result[0] : undefined;
|
|
}
|
|
|
|
export async function updateUser(id: number, data: Partial<InsertUser>) {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
|
|
await db.update(users).set(data).where(eq(users.id, id));
|
|
}
|
|
|
|
export async function toggleUserStatus(id: number, isActive: boolean) {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
|
|
await db.update(users).set({ isActive }).where(eq(users.id, id));
|
|
}
|
|
|
|
export async function deleteUser(id: number) {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
|
|
await db.delete(users).where(eq(users.id, id));
|
|
}
|
|
|
|
|
|
// ==================== GESTION DES TOKENS DE RÉINITIALISATION ====================
|
|
|
|
export async function createPasswordResetToken(userId: number, token: string, expiresAt: Date) {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
|
|
const result = await db.insert(passwordResetTokens).values({
|
|
userId,
|
|
token,
|
|
expiresAt,
|
|
used: false,
|
|
});
|
|
return result;
|
|
}
|
|
|
|
export async function getPasswordResetToken(token: string) {
|
|
const db = await getDb();
|
|
if (!db) return undefined;
|
|
|
|
const result = await db.select()
|
|
.from(passwordResetTokens)
|
|
.where(eq(passwordResetTokens.token, token))
|
|
.limit(1);
|
|
|
|
return result.length > 0 ? result[0] : undefined;
|
|
}
|
|
|
|
export async function markTokenAsUsed(tokenId: number) {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
|
|
await db.update(passwordResetTokens)
|
|
.set({ used: true })
|
|
.where(eq(passwordResetTokens.id, tokenId));
|
|
}
|
|
|
|
export async function deleteExpiredTokens() {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
|
|
const now = new Date();
|
|
await db.delete(passwordResetTokens)
|
|
.where(sql`${passwordResetTokens.expiresAt} < ${now}`);
|
|
}
|
|
|
|
// ============================================
|
|
// Email Templates Management
|
|
// ============================================
|
|
|
|
/**
|
|
* Récupère tous les templates d'emails
|
|
*/
|
|
export async function getAllEmailTemplates() {
|
|
const db = await getDb();
|
|
if (!db) return [];
|
|
|
|
return await db.select().from(emailTemplates);
|
|
}
|
|
|
|
/**
|
|
* Récupère un template par son type
|
|
*/
|
|
export async function getEmailTemplateByType(type: string) {
|
|
const db = await getDb();
|
|
if (!db) return undefined;
|
|
|
|
const results = await db.select().from(emailTemplates).where(eq(emailTemplates.type, type)).limit(1);
|
|
return results.length > 0 ? results[0] : undefined;
|
|
}
|
|
|
|
/**
|
|
* Crée ou met à jour un template d'email
|
|
*/
|
|
export async function upsertEmailTemplate(template: InsertEmailTemplate) {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
|
|
const existing = await getEmailTemplateByType(template.type);
|
|
|
|
if (existing) {
|
|
// Mise à jour
|
|
await db.update(emailTemplates)
|
|
.set({
|
|
name: template.name,
|
|
logoUrl: template.logoUrl,
|
|
primaryColor: template.primaryColor,
|
|
headerBgColor: template.headerBgColor,
|
|
headerTextColor: template.headerTextColor,
|
|
headerTitle: template.headerTitle,
|
|
footerText: template.footerText,
|
|
active: template.active,
|
|
updatedAt: new Date(),
|
|
})
|
|
.where(eq(emailTemplates.type, template.type));
|
|
|
|
return await getEmailTemplateByType(template.type);
|
|
} else {
|
|
// Création
|
|
await db.insert(emailTemplates).values(template);
|
|
return await getEmailTemplateByType(template.type);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Supprime un template d'email
|
|
*/
|
|
export async function deleteEmailTemplate(type: string) {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
|
|
await db.delete(emailTemplates).where(eq(emailTemplates.type, type));
|
|
}
|
|
|
|
/**
|
|
* Initialise les templates par défaut si la table est vide
|
|
*/
|
|
export async function initializeDefaultEmailTemplates() {
|
|
const db = await getDb();
|
|
if (!db) return;
|
|
|
|
const existing = await getAllEmailTemplates();
|
|
if (existing.length > 0) return; // Déjà initialisé
|
|
|
|
const defaultTemplates: InsertEmailTemplate[] = [
|
|
{
|
|
type: "inscription",
|
|
name: "Confirmation d'inscription",
|
|
logoUrl: null,
|
|
primaryColor: "#2563eb",
|
|
headerBgColor: "#2563eb",
|
|
headerTextColor: "#ffffff",
|
|
headerTitle: "Formation Manager Itinova",
|
|
footerText: "Cet email a été envoyé automatiquement par le système de gestion des formations Itinova. Pour toute question, veuillez contacter le service RH.",
|
|
active: true,
|
|
},
|
|
{
|
|
type: "teaser",
|
|
name: "Email teaser",
|
|
logoUrl: null,
|
|
primaryColor: "#2563eb",
|
|
headerBgColor: "#2563eb",
|
|
headerTextColor: "#ffffff",
|
|
headerTitle: "Formation Manager Itinova",
|
|
footerText: "Cet email a été envoyé automatiquement par le système de gestion des formations Itinova. Pour toute question, veuillez contacter le service RH.",
|
|
active: true,
|
|
},
|
|
{
|
|
type: "rappel",
|
|
name: "Rappel J-7",
|
|
logoUrl: null,
|
|
primaryColor: "#2563eb",
|
|
headerBgColor: "#2563eb",
|
|
headerTextColor: "#ffffff",
|
|
headerTitle: "Formation Manager Itinova",
|
|
footerText: "Cet email a été envoyé automatiquement par le système de gestion des formations Itinova. Pour toute question, veuillez contacter le service RH.",
|
|
active: true,
|
|
},
|
|
{
|
|
type: "reset_password",
|
|
name: "Réinitialisation de mot de passe",
|
|
logoUrl: null,
|
|
primaryColor: "#2563eb",
|
|
headerBgColor: "#2563eb",
|
|
headerTextColor: "#ffffff",
|
|
headerTitle: "Formation Manager Itinova",
|
|
footerText: "Cet email a été envoyé automatiquement par le système de gestion des formations Itinova. Pour toute question, veuillez contacter le service RH.",
|
|
active: true,
|
|
},
|
|
];
|
|
|
|
for (const template of defaultTemplates) {
|
|
await db.insert(emailTemplates).values(template);
|
|
}
|
|
|
|
console.log("[DB] Templates d'emails par défaut initialisés");
|
|
}
|
|
|
|
|
|
// ==================== Email Config ====================
|
|
|
|
/**
|
|
* Récupère la configuration email active
|
|
*/
|
|
export async function getActiveEmailConfig() {
|
|
const db = await getDb();
|
|
if (!db) return undefined;
|
|
|
|
const result = await db
|
|
.select()
|
|
.from(emailConfig)
|
|
.where(eq(emailConfig.active, true))
|
|
.limit(1);
|
|
|
|
return result.length > 0 ? result[0] : undefined;
|
|
}
|
|
|
|
/**
|
|
* Crée ou met à jour la configuration email
|
|
*/
|
|
export async function upsertEmailConfig(data: InsertEmailConfig) {
|
|
const db = await getDb();
|
|
if (!db) return;
|
|
|
|
// Désactiver toutes les configurations existantes
|
|
await db.update(emailConfig).set({ active: false });
|
|
|
|
// Créer la nouvelle configuration
|
|
await db.insert(emailConfig).values({
|
|
...data,
|
|
active: true,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Met à jour la configuration email
|
|
*/
|
|
export async function updateEmailConfig(id: number, data: Partial<InsertEmailConfig>) {
|
|
const db = await getDb();
|
|
if (!db) return;
|
|
|
|
await db
|
|
.update(emailConfig)
|
|
.set({
|
|
...data,
|
|
updatedAt: new Date(),
|
|
})
|
|
.where(eq(emailConfig.id, id));
|
|
}
|
|
|
|
// ===== FORMATEURS =====
|
|
|
|
/**
|
|
* Récupérer tous les formateurs actifs
|
|
*/
|
|
export async function getFormateurs() {
|
|
const db = await getDb();
|
|
if (!db) return [];
|
|
|
|
const result = await db.select().from(formateurs).where(eq(formateurs.actif, true));
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Créer un nouveau formateur
|
|
*/
|
|
export async function createFormateur(data: InsertFormateur) {
|
|
const db = await getDb();
|
|
if (!db) return null;
|
|
|
|
const result = await db.insert(formateurs).values(data);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Supprimer un formateur (désactivation logique)
|
|
*/
|
|
export async function deleteFormateur(id: number) {
|
|
const db = await getDb();
|
|
if (!db) return;
|
|
|
|
await db.update(formateurs).set({ actif: false }).where(eq(formateurs.id, id));
|
|
}
|
|
|
|
// ===== RAPPELS =====
|
|
|
|
/**
|
|
* Récupérer tous les rappels
|
|
*/
|
|
export async function getRappels() {
|
|
const db = await getDb();
|
|
if (!db) return [];
|
|
|
|
const { rappels } = await import("../drizzle/schema");
|
|
const result = await db.select().from(rappels);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Créer un nouveau rappel
|
|
*/
|
|
export async function createRappel(data: InsertRappel) {
|
|
const db = await getDb();
|
|
if (!db) return null;
|
|
|
|
const { rappels } = await import("../drizzle/schema");
|
|
const result = await db.insert(rappels).values(data);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Mettre à jour un rappel
|
|
*/
|
|
export async function updateRappel(id: number, data: Partial<InsertRappel>) {
|
|
const db = await getDb();
|
|
if (!db) return;
|
|
|
|
const { rappels } = await import("../drizzle/schema");
|
|
await db.update(rappels).set({
|
|
...data,
|
|
updatedAt: new Date(),
|
|
}).where(eq(rappels.id, id));
|
|
}
|
|
|
|
/**
|
|
* Supprimer un rappel
|
|
*/
|
|
export async function deleteRappel(id: number) {
|
|
const db = await getDb();
|
|
if (!db) return;
|
|
|
|
const { rappels } = await import("../drizzle/schema");
|
|
await db.delete(rappels).where(eq(rappels.id, id));
|
|
}
|
|
|
|
/**
|
|
* Enregistrer l'envoi d'un rappel
|
|
*/
|
|
export async function enregistrerRappelEnvoye(rappelId: number, inscriptionId: number) {
|
|
const db = await getDb();
|
|
if (!db) return;
|
|
|
|
const { rappelsEnvoyes } = await import("../drizzle/schema");
|
|
await db.insert(rappelsEnvoyes).values({
|
|
rappelId,
|
|
inscriptionId,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Vérifier si un rappel a déjà été envoyé
|
|
*/
|
|
export async function rappelDejaEnvoye(rappelId: number, inscriptionId: number) {
|
|
const db = await getDb();
|
|
if (!db) return false;
|
|
|
|
const { rappelsEnvoyes } = await import("../drizzle/schema");
|
|
const result = await db.select().from(rappelsEnvoyes)
|
|
.where(and(
|
|
eq(rappelsEnvoyes.rappelId, rappelId),
|
|
eq(rappelsEnvoyes.inscriptionId, inscriptionId)
|
|
))
|
|
.limit(1);
|
|
|
|
return result.length > 0;
|
|
}
|