Checkpoint: Amélioration de la page Paramètres avec :
- Ajout de 3 nouveaux paramètres globaux : délai d'expiration QR codes (minutes), durée de validité tokens (heures), notifications actives (boolean) - Création de la table historiqueParametres pour tracer toutes les modifications avec userId, userName, champModifié, ancienneValeur, nouvelleValeur et dateModification - Ajout d'un bouton "Tester l'URL" avec indicateur visuel de statut (vert si accessible, rouge si erreur) - Affichage de l'historique des modifications dans un tableau avec toutes les informations de traçabilité - Mise à jour automatique de l'historique lors de chaque modification de paramètre
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { boolean, datetime, int, mysqlEnum, mysqlTable, text, timestamp, varchar } from "drizzle-orm/mysql-core";
|
||||
import { boolean, int, mysqlEnum, mysqlTable, text, timestamp, varchar, datetime, decimal } from "drizzle-orm/mysql-core";
|
||||
|
||||
/**
|
||||
* Core user table backing auth flow.
|
||||
@@ -33,6 +33,20 @@ export const users = mysqlTable("users", {
|
||||
export type User = typeof users.$inferSelect;
|
||||
export type InsertUser = typeof users.$inferInsert;
|
||||
|
||||
/**
|
||||
* Table des tokens de réinitialisation de mot de passe
|
||||
*/
|
||||
export const passwordResetTokens = mysqlTable("passwordResetTokens", {
|
||||
id: int("id").autoincrement().primaryKey(),
|
||||
userId: int("userId").notNull(),
|
||||
token: varchar("token", { length: 255 }).notNull().unique(),
|
||||
expiresAt: timestamp("expiresAt").notNull(),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export type PasswordResetToken = typeof passwordResetTokens.$inferSelect;
|
||||
export type InsertPasswordResetToken = typeof passwordResetTokens.$inferInsert;
|
||||
|
||||
/**
|
||||
* Table des formations (ex: Manager Itinova)
|
||||
*/
|
||||
@@ -51,44 +65,23 @@ export type Formation = typeof formations.$inferSelect;
|
||||
export type InsertFormation = typeof formations.$inferInsert;
|
||||
|
||||
/**
|
||||
* Table des apprenants
|
||||
*/
|
||||
export const apprenants = mysqlTable("apprenants", {
|
||||
id: int("id").autoincrement().primaryKey(),
|
||||
nom: varchar("nom", { length: 255 }).notNull(),
|
||||
prenom: varchar("prenom", { length: 255 }).notNull(),
|
||||
email: varchar("email", { length: 320 }).notNull().unique(),
|
||||
codeEtablissement: varchar("codeEtablissement", { length: 50 }).notNull(),
|
||||
fonction: mysqlEnum("fonction", ["directeur", "chef_service", "autre"]).notNull(),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
|
||||
});
|
||||
|
||||
export type Apprenant = typeof apprenants.$inferSelect;
|
||||
export type InsertApprenant = typeof apprenants.$inferInsert;
|
||||
|
||||
/**
|
||||
* Table des séquences de formation
|
||||
* Une séquence peut contenir jusqu'à 4 dates de formation
|
||||
* Table des séquences de formation (ex: Groupe A, Groupe B)
|
||||
* Chaque séquence peut avoir jusqu'à 4 dates de formation
|
||||
*/
|
||||
export const sequences = mysqlTable("sequences", {
|
||||
id: int("id").autoincrement().primaryKey(),
|
||||
formationId: int("formationId").notNull(),
|
||||
nom: varchar("nom", { length: 255 }).notNull(),
|
||||
/** Lieu de la formation */
|
||||
lieu: varchar("lieu", { length: 500 }).notNull(),
|
||||
/** Public cible de la séquence (Directeur, Chef de service, Tous, Autre) */
|
||||
publicCible: mysqlEnum("publicCible", ["directeur", "chef_service", "tous", "autre"]).notNull(),
|
||||
lieu: varchar("lieu", { length: 255 }).notNull(),
|
||||
capaciteMax: int("capaciteMax").notNull(),
|
||||
dateBloquage: datetime("dateBloquage"),
|
||||
statut: mysqlEnum("statut", ["ouverte", "fermee", "annulee"]).default("ouverte").notNull(),
|
||||
/** Public cible de la séquence (Directeur, Chef de service, Autre, Tous) */
|
||||
publicCible: mysqlEnum("publicCible", ["directeur", "chef_service", "autre", "tous"]).default("tous").notNull(),
|
||||
/** ID du formateur assigné à cette séquence */
|
||||
formateurId: int("formateurId"),
|
||||
/** Capacité maximale (12 par défaut) */
|
||||
capaciteMax: int("capaciteMax").default(12).notNull(),
|
||||
/** Date de blocage des inscriptions (J-15 avant la première date) */
|
||||
dateBlocage: datetime("dateBlocage").notNull(),
|
||||
/** Statut de la séquence */
|
||||
statut: mysqlEnum("statut", ["ouverte", "bloquee", "terminee"]).default("ouverte").notNull(),
|
||||
/** Token unique pour le QR code d'émargement */
|
||||
qrCodeToken: varchar("qrCodeToken", { length: 64 }).unique(),
|
||||
qrCodeToken: varchar("qrCodeToken", { length: 100 }).unique(),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
|
||||
});
|
||||
@@ -103,30 +96,45 @@ export type InsertSequence = typeof sequences.$inferInsert;
|
||||
export const datesFormation = mysqlTable("datesFormation", {
|
||||
id: int("id").autoincrement().primaryKey(),
|
||||
sequenceId: int("sequenceId").notNull(),
|
||||
/** Date de début de cette journée de formation */
|
||||
dateDebut: datetime("dateDebut").notNull(),
|
||||
/** Date de fin de cette journée de formation */
|
||||
dateFin: datetime("dateFin").notNull(),
|
||||
/** Ordre de la date dans la séquence (1 à 4) */
|
||||
/** Ordre de la date (1, 2, 3 ou 4) */
|
||||
ordre: int("ordre").notNull(),
|
||||
dateDebut: datetime("dateDebut").notNull(),
|
||||
dateFin: datetime("dateFin").notNull(),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export type DateFormation = typeof datesFormation.$inferSelect;
|
||||
export type InsertDateFormation = typeof datesFormation.$inferInsert;
|
||||
|
||||
/**
|
||||
* Table des apprenants
|
||||
*/
|
||||
export const apprenants = mysqlTable("apprenants", {
|
||||
id: int("id").autoincrement().primaryKey(),
|
||||
nom: varchar("nom", { length: 255 }).notNull(),
|
||||
prenom: varchar("prenom", { length: 255 }).notNull(),
|
||||
email: varchar("email", { length: 320 }).notNull().unique(),
|
||||
codeEtablissement: varchar("codeEtablissement", { length: 50 }).notNull(),
|
||||
/** Fonction de l'apprenant (Directeur, Chef de service, Autre) */
|
||||
fonction: mysqlEnum("fonction", ["directeur", "chef_service", "autre"]).default("autre").notNull(),
|
||||
/** Statut du compte (actif/inactif) */
|
||||
isActive: boolean("isActive").default(true).notNull(),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
|
||||
});
|
||||
|
||||
export type Apprenant = typeof apprenants.$inferSelect;
|
||||
export type InsertApprenant = typeof apprenants.$inferInsert;
|
||||
|
||||
/**
|
||||
* Table des inscriptions
|
||||
* L'inscription à une séquence inscrit automatiquement à toutes ses dates
|
||||
*/
|
||||
export const inscriptions = mysqlTable("inscriptions", {
|
||||
id: int("id").autoincrement().primaryKey(),
|
||||
apprenantId: int("apprenantId").notNull(),
|
||||
sequenceId: int("sequenceId").notNull(),
|
||||
/** Statut de l'inscription */
|
||||
statut: mysqlEnum("statut", ["confirmee", "liste_attente", "annulee"]).notNull(),
|
||||
apprenantId: int("apprenantId").notNull(),
|
||||
statut: mysqlEnum("statut", ["confirmee", "en_attente", "annulee"]).default("confirmee").notNull(),
|
||||
dateInscription: timestamp("dateInscription").defaultNow().notNull(),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
|
||||
});
|
||||
|
||||
@@ -134,87 +142,30 @@ export type Inscription = typeof inscriptions.$inferSelect;
|
||||
export type InsertInscription = typeof inscriptions.$inferInsert;
|
||||
|
||||
/**
|
||||
* Table des tokens de réinitialisation de mot de passe
|
||||
* Stocke les tokens temporaires générés pour la réinitialisation
|
||||
*/
|
||||
export const passwordResetTokens = mysqlTable("passwordResetTokens", {
|
||||
id: int("id").autoincrement().primaryKey(),
|
||||
userId: int("userId").notNull(),
|
||||
/** Token unique généré pour la réinitialisation */
|
||||
token: varchar("token", { length: 255 }).notNull().unique(),
|
||||
/** Date d'expiration du token (24h après génération) */
|
||||
expiresAt: timestamp("expiresAt").notNull(),
|
||||
/** Indique si le token a été utilisé */
|
||||
used: boolean("used").default(false).notNull(),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export type PasswordResetToken = typeof passwordResetTokens.$inferSelect;
|
||||
export type InsertPasswordResetToken = typeof passwordResetTokens.$inferInsert;
|
||||
|
||||
/**
|
||||
* Table des templates d'emails personnalisables
|
||||
*/
|
||||
export const emailTemplates = mysqlTable("emailTemplates", {
|
||||
id: int("id").autoincrement().primaryKey(),
|
||||
/** Type de template (inscription, teaser, rappel, reset_password) */
|
||||
type: varchar("type", { length: 50 }).notNull().unique(),
|
||||
/** Nom affiché du template */
|
||||
name: varchar("name", { length: 255 }).notNull(),
|
||||
/** URL du logo (peut être vide pour utiliser le logo par défaut) */
|
||||
logoUrl: text("logoUrl"),
|
||||
/** Couleur principale (hex) */
|
||||
primaryColor: varchar("primaryColor", { length: 7 }).default("#2563eb").notNull(),
|
||||
/** Couleur de fond de l'en-tête (hex) */
|
||||
headerBgColor: varchar("headerBgColor", { length: 7 }).default("#2563eb").notNull(),
|
||||
/** Couleur du texte de l'en-tête (hex) */
|
||||
headerTextColor: varchar("headerTextColor", { length: 7 }).default("#ffffff").notNull(),
|
||||
/** Titre de l'en-tête */
|
||||
headerTitle: varchar("headerTitle", { length: 255 }).default("Formation Manager Itinova").notNull(),
|
||||
/** Texte du pied de page */
|
||||
footerText: text("footerText"),
|
||||
/** Contenu du corps de l'email (HTML ou texte) */
|
||||
bodyContent: text("bodyContent"),
|
||||
/** Actif ou non */
|
||||
active: boolean("active").default(true).notNull(),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
|
||||
});
|
||||
|
||||
export type EmailTemplate = typeof emailTemplates.$inferSelect;
|
||||
export type InsertEmailTemplate = typeof emailTemplates.$inferInsert;
|
||||
|
||||
/**
|
||||
* Table de configuration SMTP/Email
|
||||
* Stocke la configuration pour l'envoi d'emails (Resend, etc.)
|
||||
* Table de configuration des emails
|
||||
*/
|
||||
export const emailConfig = mysqlTable("emailConfig", {
|
||||
id: int("id").autoincrement().primaryKey(),
|
||||
/** Service d'envoi (resend, smtp) */
|
||||
provider: varchar("provider", { length: 50 }).default("resend").notNull(),
|
||||
/** Clé API Resend (chiffrée) */
|
||||
apiKey: text("apiKey"),
|
||||
/** Provider d'envoi d'emails (resend, smtp, simulation) */
|
||||
provider: mysqlEnum("provider", ["resend", "smtp", "simulation"]).default("simulation").notNull(),
|
||||
/** Clé API Resend */
|
||||
resendApiKey: varchar("resendApiKey", { length: 255 }),
|
||||
/** Hôte SMTP */
|
||||
smtpHost: varchar("smtpHost", { length: 255 }),
|
||||
/** Port SMTP */
|
||||
smtpPort: int("smtpPort"),
|
||||
/** Sécurité SMTP (TLS, SSL, None) */
|
||||
smtpSecure: mysqlEnum("smtpSecure", ["tls", "ssl", "none"]),
|
||||
/** Utilisateur SMTP */
|
||||
smtpUser: varchar("smtpUser", { length: 255 }),
|
||||
/** Mot de passe SMTP */
|
||||
smtpPassword: varchar("smtpPassword", { length: 255 }),
|
||||
/** Email expéditeur */
|
||||
fromEmail: varchar("fromEmail", { length: 320 }).notNull(),
|
||||
/** Nom de l'expéditeur */
|
||||
fromName: varchar("fromName", { length: 255 }).default("Formation Manager Itinova").notNull(),
|
||||
/** Mode: simulation ou production */
|
||||
fromName: varchar("fromName", { length: 255 }).notNull(),
|
||||
/** Mode de fonctionnement (simulation ou production) */
|
||||
mode: mysqlEnum("mode", ["simulation", "production"]).default("simulation").notNull(),
|
||||
/** Domaine vérifié (pour Resend) */
|
||||
domainVerified: boolean("domainVerified").default(false).notNull(),
|
||||
/** Actif ou non */
|
||||
active: boolean("active").default(true).notNull(),
|
||||
// Champs SMTP
|
||||
/** Hôte SMTP (ex: smtp.gmail.com) */
|
||||
smtpHost: varchar("smtpHost", { length: 255 }),
|
||||
/** Port SMTP (ex: 587, 465, 25) */
|
||||
smtpPort: int("smtpPort"),
|
||||
/** Sécurité SMTP (none, tls, ssl) */
|
||||
smtpSecure: mysqlEnum("smtpSecure", ["none", "tls", "ssl"]).default("tls"),
|
||||
/** Nom d'utilisateur SMTP */
|
||||
smtpUser: varchar("smtpUser", { length: 320 }),
|
||||
/** Mot de passe SMTP (chiffré) */
|
||||
smtpPassword: text("smtpPassword"),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
|
||||
});
|
||||
@@ -222,15 +173,32 @@ export const emailConfig = mysqlTable("emailConfig", {
|
||||
export type EmailConfig = typeof emailConfig.$inferSelect;
|
||||
export type InsertEmailConfig = typeof emailConfig.$inferInsert;
|
||||
|
||||
/**
|
||||
* Table des templates d'emails
|
||||
*/
|
||||
export const emailTemplates = mysqlTable("emailTemplates", {
|
||||
id: int("id").autoincrement().primaryKey(),
|
||||
/** Type de template (inscription, teaser, rappel, rappelJ1, rappel3, rappel4, rappel5, rappel6) */
|
||||
type: mysqlEnum("type", ["inscription", "teaser", "rappel", "rappelJ1", "rappel3", "rappel4", "rappel5", "rappel6"]).notNull().unique(),
|
||||
titre: varchar("titre", { length: 255 }).notNull(),
|
||||
/** Corps du message avec variables dynamiques */
|
||||
bodyContent: text("bodyContent").notNull(),
|
||||
couleurPrincipale: varchar("couleurPrincipale", { length: 7 }).default("#283581").notNull(),
|
||||
couleurSecondaire: varchar("couleurSecondaire", { length: 7 }).default("#0578BE").notNull(),
|
||||
piedDePage: text("piedDePage"),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
|
||||
});
|
||||
|
||||
export type EmailTemplate = typeof emailTemplates.$inferSelect;
|
||||
export type InsertEmailTemplate = typeof emailTemplates.$inferInsert;
|
||||
|
||||
/**
|
||||
* Table des formateurs
|
||||
* Stocke la liste des formateurs disponibles pour animer les séquences
|
||||
*/
|
||||
export const formateurs = mysqlTable("formateurs", {
|
||||
id: int("id").autoincrement().primaryKey(),
|
||||
/** Nom complet du formateur */
|
||||
nom: varchar("nom", { length: 255 }).notNull(),
|
||||
/** Email du formateur pour les notifications */
|
||||
email: varchar("email", { length: 320 }),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
|
||||
@@ -239,67 +207,32 @@ export const formateurs = mysqlTable("formateurs", {
|
||||
export type Formateur = typeof formateurs.$inferSelect;
|
||||
export type InsertFormateur = typeof formateurs.$inferInsert;
|
||||
|
||||
/**
|
||||
* Table des alertes automatiques
|
||||
* Configure les alertes à envoyer selon certaines conditions
|
||||
*/
|
||||
export const alertes = mysqlTable("alertes", {
|
||||
id: int("id").autoincrement().primaryKey(),
|
||||
/** Nom de l'alerte */
|
||||
nom: varchar("nom", { length: 255 }).notNull(),
|
||||
/** Type d'alerte (taux_remplissage, seuil_inscriptions) */
|
||||
type: mysqlEnum("type", ["taux_remplissage", "seuil_inscriptions"]).notNull(),
|
||||
/** Condition de déclenchement (ex: ">=", ">", "<=", "<", "=") */
|
||||
condition: varchar("condition", { length: 10 }).notNull(),
|
||||
/** Valeur seuil pour déclencher l'alerte */
|
||||
valeurSeuil: int("valeurSeuil").notNull(),
|
||||
/** Code établissement concerné (null = tous) */
|
||||
codeEtablissement: varchar("codeEtablissement", { length: 50 }),
|
||||
/** Email(s) destinataire(s) séparés par des virgules */
|
||||
emailsDestinataires: text("emailsDestinataires").notNull(),
|
||||
/** Actif ou non */
|
||||
actif: boolean("actif").default(true).notNull(),
|
||||
/** Dernière vérification */
|
||||
derniereVerification: timestamp("derniereVerification"),
|
||||
/** Dernier envoi d'alerte */
|
||||
dernierEnvoi: timestamp("dernierEnvoi"),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
|
||||
});
|
||||
|
||||
export type Alerte = typeof alertes.$inferSelect;
|
||||
export type InsertAlerte = typeof alertes.$inferInsert;
|
||||
|
||||
/**
|
||||
* Table des rappels automatiques
|
||||
* Stocke les règles d'envoi automatique de rappels avant les séquences
|
||||
*/
|
||||
export const rappels = mysqlTable("rappels", {
|
||||
id: int("id").autoincrement().primaryKey(),
|
||||
/** Nom du rappel */
|
||||
nom: varchar("nom", { length: 255 }).notNull(),
|
||||
/** Type de template à utiliser (rappel, rappelJ1, etc.) */
|
||||
templateType: varchar("templateType", { length: 50 }).notNull(),
|
||||
/** Timing : 'pre_formation' ou 'post_formation' */
|
||||
timing: mysqlEnum("timing", ["pre_formation", "post_formation"]).default("pre_formation").notNull(),
|
||||
/** Nombre de jours avant (positif) ou après (négatif) la première date de la séquence */
|
||||
joursAvant: int("joursAvant").notNull(),
|
||||
/** Heure d'envoi (format HH:MM) */
|
||||
/** Type de template d'email à utiliser (rappel, rappelJ1, rappel3, rappel4, rappel5, rappel6) */
|
||||
templateType: mysqlEnum("templateType", ["rappel", "rappelJ1", "rappel3", "rappel4", "rappel5", "rappel6"]).notNull(),
|
||||
/** Nombre de jours avant/après la séquence */
|
||||
nbJours: int("nbJours").notNull(),
|
||||
/** Heure d'envoi (format HH:mm) */
|
||||
heureEnvoi: varchar("heureEnvoi", { length: 5 }).default("09:00").notNull(),
|
||||
/** Actif ou non */
|
||||
actif: boolean("actif").default(true).notNull(),
|
||||
/** Dernière exécution */
|
||||
derniereExecution: timestamp("derniereExecution"),
|
||||
/** Timing du rappel (pre_formation ou post_formation) */
|
||||
timing: mysqlEnum("timing", ["pre_formation", "post_formation"]).default("pre_formation").notNull(),
|
||||
/** Nom du fichier joint (optionnel) */
|
||||
nomFichier: varchar("nomFichier", { length: 255 }),
|
||||
/** URL publique du fichier sur S3 */
|
||||
urlFichier: text("urlFichier"),
|
||||
/** Clé S3 du fichier pour suppression */
|
||||
/** URL du fichier sur S3 (optionnel) */
|
||||
urlFichier: varchar("urlFichier", { length: 500 }),
|
||||
/** Clé S3 du fichier (optionnel) */
|
||||
s3Key: varchar("s3Key", { length: 500 }),
|
||||
/** Type MIME du fichier */
|
||||
/** Type MIME du fichier (optionnel) */
|
||||
typeFichier: varchar("typeFichier", { length: 100 }),
|
||||
/** Taille du fichier en octets */
|
||||
/** Taille du fichier en octets (optionnel) */
|
||||
tailleFichier: int("tailleFichier"),
|
||||
actif: boolean("actif").default(true).notNull(),
|
||||
derniereExecution: datetime("derniereExecution"),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
|
||||
});
|
||||
@@ -309,14 +242,11 @@ export type InsertRappel = typeof rappels.$inferInsert;
|
||||
|
||||
/**
|
||||
* Table de liaison entre rappels et dates de formation
|
||||
* Permet d'associer un rappel à des dates de formation spécifiques
|
||||
* Si aucune association n'existe pour un rappel, il s'applique à toutes les dates
|
||||
* Si aucune date n'est associée, le rappel s'applique à toutes les dates
|
||||
*/
|
||||
export const rappelDates = mysqlTable("rappelDates", {
|
||||
id: int("id").autoincrement().primaryKey(),
|
||||
/** ID du rappel */
|
||||
rappelId: int("rappelId").notNull(),
|
||||
/** ID de la date de formation concernée */
|
||||
dateFormationId: int("dateFormationId").notNull(),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
});
|
||||
@@ -325,25 +255,38 @@ export type RappelDate = typeof rappelDates.$inferSelect;
|
||||
export type InsertRappelDate = typeof rappelDates.$inferInsert;
|
||||
|
||||
/**
|
||||
* Table des questionnaires
|
||||
* Stocke les questionnaires de satisfaction et d'évaluation des acquis
|
||||
* Table des logs de rappels envoyés
|
||||
*/
|
||||
export const logsRappels = mysqlTable("logsRappels", {
|
||||
id: int("id").autoincrement().primaryKey(),
|
||||
rappelId: int("rappelId").notNull(),
|
||||
sequenceId: int("sequenceId").notNull(),
|
||||
apprenantId: int("apprenantId").notNull(),
|
||||
email: varchar("email", { length: 320 }).notNull(),
|
||||
/** Type de rappel (rappel, rappelJ1, rappel3, rappel4, rappel5, rappel6) */
|
||||
type: mysqlEnum("type", ["rappel", "rappelJ1", "rappel3", "rappel4", "rappel5", "rappel6"]).notNull(),
|
||||
statut: mysqlEnum("statut", ["succes", "echec"]).notNull(),
|
||||
messageErreur: text("messageErreur"),
|
||||
/** Nombre de tentatives d'envoi */
|
||||
nbTentatives: int("nbTentatives").default(1).notNull(),
|
||||
/** Date du prochain essai en cas d'échec */
|
||||
prochainEssai: datetime("prochainEssai"),
|
||||
dateEnvoi: timestamp("dateEnvoi").defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export type LogRappel = typeof logsRappels.$inferSelect;
|
||||
export type InsertLogRappel = typeof logsRappels.$inferInsert;
|
||||
|
||||
/**
|
||||
* Table des questionnaires de satisfaction et d'évaluation
|
||||
*/
|
||||
export const questionnaires = mysqlTable("questionnaires", {
|
||||
id: int("id").autoincrement().primaryKey(),
|
||||
/** Nom du questionnaire */
|
||||
nom: varchar("nom", { length: 255 }).notNull(),
|
||||
titre: varchar("titre", { length: 255 }).notNull(),
|
||||
description: text("description"),
|
||||
/** Type de questionnaire (satisfaction, evaluation_pre, evaluation_post) */
|
||||
type: mysqlEnum("type", ["satisfaction", "evaluation_pre", "evaluation_post"]).notNull(),
|
||||
/** Description du questionnaire */
|
||||
description: text("description"),
|
||||
/** ID de la formation liée (null = tous les formations) */
|
||||
formationId: int("formationId"),
|
||||
/** Actif ou non */
|
||||
actif: boolean("actif").default(true).notNull(),
|
||||
/** Envoi automatique après la formation */
|
||||
envoiAutomatique: boolean("envoiAutomatique").default(false).notNull(),
|
||||
/** Délai en jours après la dernière date de la séquence (pour envoi automatique) */
|
||||
delaiEnvoiJours: int("delaiEnvoiJours").default(1).notNull(),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
|
||||
});
|
||||
@@ -352,33 +295,23 @@ export type Questionnaire = typeof questionnaires.$inferSelect;
|
||||
export type InsertQuestionnaire = typeof questionnaires.$inferInsert;
|
||||
|
||||
/**
|
||||
* Table des questions
|
||||
* Stocke les questions associées à chaque questionnaire
|
||||
* Table des questions d'un questionnaire
|
||||
*/
|
||||
export const questions = mysqlTable("questions", {
|
||||
id: int("id").autoincrement().primaryKey(),
|
||||
/** ID du questionnaire */
|
||||
questionnaireId: int("questionnaireId").notNull(),
|
||||
/** Ordre d'affichage */
|
||||
ordre: int("ordre").notNull(),
|
||||
/** Texte de la question */
|
||||
texte: text("texte").notNull(),
|
||||
/** Type de question (choix_multiple, echelle, texte_libre, oui_non) */
|
||||
typeQuestion: mysqlEnum("typeQuestion", ["choix_multiple", "echelle", "texte_libre", "oui_non"]).notNull(),
|
||||
/** Options pour choix multiple (JSON array) */
|
||||
/** Options pour les questions à choix multiples (JSON) */
|
||||
options: text("options"),
|
||||
/** Valeur minimale pour échelle */
|
||||
echelleMin: int("echelleMin"),
|
||||
/** Valeur maximale pour échelle */
|
||||
echelleMax: int("echelleMax"),
|
||||
/** Label pour valeur minimale (ex: "Pas du tout satisfait") */
|
||||
echelleLabelMin: varchar("echelleLabelMin", { length: 100 }),
|
||||
/** Label pour valeur maximale (ex: "Très satisfait") */
|
||||
echelleLabelMax: varchar("echelleLabelMax", { length: 100 }),
|
||||
/** Question obligatoire */
|
||||
/** Valeur minimale pour les échelles */
|
||||
valeurMin: int("valeurMin"),
|
||||
/** Valeur maximale pour les échelles */
|
||||
valeurMax: int("valeurMax"),
|
||||
obligatoire: boolean("obligatoire").default(false).notNull(),
|
||||
ordre: int("ordre").notNull(),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
|
||||
});
|
||||
|
||||
export type Question = typeof questions.$inferSelect;
|
||||
@@ -386,43 +319,30 @@ export type InsertQuestion = typeof questions.$inferInsert;
|
||||
|
||||
/**
|
||||
* Table des réponses aux questionnaires
|
||||
* Stocke les réponses complètes d'un apprenant à un questionnaire
|
||||
*/
|
||||
export const reponsesQuestionnaires = mysqlTable("reponsesQuestionnaires", {
|
||||
id: int("id").autoincrement().primaryKey(),
|
||||
/** ID du questionnaire */
|
||||
questionnaireId: int("questionnaireId").notNull(),
|
||||
/** ID de l'apprenant */
|
||||
apprenantId: int("apprenantId").notNull(),
|
||||
/** ID de la séquence */
|
||||
sequenceId: int("sequenceId").notNull(),
|
||||
/** Date de réponse */
|
||||
apprenantId: int("apprenantId").notNull(),
|
||||
dateReponse: timestamp("dateReponse").defaultNow().notNull(),
|
||||
/** Questionnaire complété */
|
||||
complete: boolean("complete").default(false).notNull(),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
|
||||
});
|
||||
|
||||
export type ReponseQuestionnaire = typeof reponsesQuestionnaires.$inferSelect;
|
||||
export type InsertReponseQuestionnaire = typeof reponsesQuestionnaires.$inferInsert;
|
||||
|
||||
/**
|
||||
* Table des réponses aux questions
|
||||
* Stocke les réponses individuelles à chaque question
|
||||
* Table des réponses individuelles aux questions
|
||||
*/
|
||||
export const reponsesQuestions = mysqlTable("reponsesQuestions", {
|
||||
id: int("id").autoincrement().primaryKey(),
|
||||
/** ID de la réponse au questionnaire */
|
||||
reponseQuestionnaireId: int("reponseQuestionnaireId").notNull(),
|
||||
/** ID de la question */
|
||||
questionId: int("questionId").notNull(),
|
||||
/** Réponse numérique (pour échelle, oui_non) */
|
||||
reponseNumerique: int("reponseNumerique"),
|
||||
/** Réponse texte (pour texte_libre, choix_multiple) */
|
||||
/** Réponse textuelle */
|
||||
reponseTexte: text("reponseTexte"),
|
||||
/** Réponse numérique (pour échelles et oui/non) */
|
||||
reponseNumerique: int("reponseNumerique"),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
|
||||
});
|
||||
|
||||
export type ReponseQuestion = typeof reponsesQuestions.$inferSelect;
|
||||
@@ -430,153 +350,69 @@ export type InsertReponseQuestion = typeof reponsesQuestions.$inferInsert;
|
||||
|
||||
/**
|
||||
* Table des envois de questionnaires
|
||||
* Stocke l'historique des envois de questionnaires aux apprenants
|
||||
*/
|
||||
export const envoisQuestionnaires = mysqlTable("envoisQuestionnaires", {
|
||||
id: int("id").autoincrement().primaryKey(),
|
||||
/** ID du questionnaire */
|
||||
questionnaireId: int("questionnaireId").notNull(),
|
||||
/** ID de l'apprenant */
|
||||
apprenantId: int("apprenantId").notNull(),
|
||||
/** ID de la séquence */
|
||||
sequenceId: int("sequenceId").notNull(),
|
||||
/** Date d'envoi */
|
||||
dateEnvoi: timestamp("dateEnvoi").defaultNow().notNull(),
|
||||
apprenantId: int("apprenantId").notNull(),
|
||||
/** Token unique pour accéder au questionnaire */
|
||||
token: varchar("token", { length: 100 }).notNull().unique(),
|
||||
/** Date de réponse (null si pas encore répondu) */
|
||||
dateEnvoi: timestamp("dateEnvoi").defaultNow().notNull(),
|
||||
dateReponse: timestamp("dateReponse"),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
|
||||
statut: mysqlEnum("statut", ["envoye", "repondu"]).default("envoye").notNull(),
|
||||
});
|
||||
|
||||
export type EnvoiQuestionnaire = typeof envoisQuestionnaires.$inferSelect;
|
||||
export type InsertEnvoiQuestionnaire = typeof envoisQuestionnaires.$inferInsert;
|
||||
|
||||
/**
|
||||
* Table des supports de formation
|
||||
* Stocke les documents uploadés par les formateurs pour chaque séquence
|
||||
* Table des supports de formation uploadés par les formateurs
|
||||
*/
|
||||
export const supportsFormation = mysqlTable("supportsFormation", {
|
||||
id: int("id").autoincrement().primaryKey(),
|
||||
/** ID de la séquence */
|
||||
sequenceId: int("sequenceId").notNull(),
|
||||
/** ID du formateur qui a uploadé le support */
|
||||
formateurId: int("formateurId").notNull(),
|
||||
/** Nom du fichier */
|
||||
nomFichier: varchar("nomFichier", { length: 255 }).notNull(),
|
||||
/** Type de fichier (PDF, PPTX, DOCX, etc.) */
|
||||
typeFichier: varchar("typeFichier", { length: 50 }).notNull(),
|
||||
/** Taille du fichier en octets */
|
||||
typeFichier: varchar("typeFichier", { length: 100 }).notNull(),
|
||||
tailleFichier: int("tailleFichier").notNull(),
|
||||
/** URL du fichier dans S3 */
|
||||
urlFichier: varchar("urlFichier", { length: 500 }).notNull(),
|
||||
/** Clé S3 du fichier */
|
||||
s3Key: varchar("s3Key", { length: 500 }).notNull(),
|
||||
/** Description optionnelle du support */
|
||||
description: text("description"),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
|
||||
});
|
||||
|
||||
export type SupportFormation = typeof supportsFormation.$inferSelect;
|
||||
export type InsertSupportFormation = typeof supportsFormation.$inferInsert;
|
||||
|
||||
/**
|
||||
* Table des logs des rappels envoyés
|
||||
* Stocke l'historique de tous les rappels envoyés pour éviter les doublons
|
||||
* Table des présences (émargement)
|
||||
*/
|
||||
export const logsRappels = mysqlTable("logsRappels", {
|
||||
export const presences = mysqlTable("presences", {
|
||||
id: int("id").autoincrement().primaryKey(),
|
||||
/** ID du rappel (règle de rappel) */
|
||||
rappelId: int("rappelId").notNull(),
|
||||
/** ID de la séquence concernée */
|
||||
sequenceId: int("sequenceId").notNull(),
|
||||
/** ID de l'apprenant destinataire */
|
||||
apprenantId: int("apprenantId").notNull(),
|
||||
/** Email du destinataire */
|
||||
emailDestinataire: varchar("emailDestinataire", { length: 320 }).notNull(),
|
||||
/** Type de rappel (rappel, rappelJ1) */
|
||||
typeRappel: varchar("typeRappel", { length: 50 }).notNull(),
|
||||
/** Date d'envoi du rappel */
|
||||
dateEnvoi: timestamp("dateEnvoi").defaultNow().notNull(),
|
||||
/** Statut de l'envoi (success, failed) */
|
||||
statut: mysqlEnum("statut", ["success", "failed"]).notNull(),
|
||||
/** Message d'erreur en cas d'échec */
|
||||
messageErreur: text("messageErreur"),
|
||||
/** Date de la première date de la séquence (pour référence) */
|
||||
dateSequence: timestamp("dateSequence").notNull(),
|
||||
/** Nombre de tentatives d'envoi */
|
||||
nbTentatives: int("nbTentatives").default(1).notNull(),
|
||||
/** Date du prochain essai en cas d'échec */
|
||||
prochainEssai: timestamp("prochainEssai"),
|
||||
inscriptionId: int("inscriptionId").notNull(),
|
||||
dateFormationId: int("dateFormationId").notNull(),
|
||||
heurePresence: timestamp("heurePresence").defaultNow().notNull(),
|
||||
/** Mode de validation (qrcode, manuel) */
|
||||
modeValidation: mysqlEnum("modeValidation", ["qrcode", "manuel"]).notNull(),
|
||||
/** ID de l'utilisateur qui a validé (formateur ou admin) */
|
||||
validateurId: int("validateurId"),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export type LogRappel = typeof logsRappels.$inferSelect;
|
||||
export type InsertLogRappel = typeof logsRappels.$inferInsert;
|
||||
|
||||
/**
|
||||
* Table des logs des notifications envoyées
|
||||
* Stocke l'historique de toutes les notifications (remerciements, alertes formateurs, etc.)
|
||||
*/
|
||||
export const logsNotifications = mysqlTable("logsNotifications", {
|
||||
id: int("id").autoincrement().primaryKey(),
|
||||
/** Type de notification (remerciement, notification_formateur_inscription, notification_formateur_annulation, alerte_capacite, notification_liste_attente) */
|
||||
type: mysqlEnum("type", [
|
||||
"remerciement",
|
||||
"notification_formateur_inscription",
|
||||
"notification_formateur_annulation",
|
||||
"alerte_capacite",
|
||||
"notification_liste_attente"
|
||||
]).notNull(),
|
||||
/** ID de la séquence concernée (si applicable) */
|
||||
sequenceId: int("sequenceId"),
|
||||
/** ID de l'apprenant concerné (si applicable) */
|
||||
apprenantId: int("apprenantId"),
|
||||
/** ID du formateur concerné (si applicable) */
|
||||
formateurId: int("formateurId"),
|
||||
/** Email du destinataire */
|
||||
emailDestinataire: varchar("emailDestinataire", { length: 320 }).notNull(),
|
||||
/** Sujet de l'email */
|
||||
sujet: varchar("sujet", { length: 500 }).notNull(),
|
||||
/** Date d'envoi */
|
||||
dateEnvoi: timestamp("dateEnvoi").defaultNow().notNull(),
|
||||
/** Statut de l'envoi (success, failed) */
|
||||
statut: mysqlEnum("statut", ["success", "failed"]).notNull(),
|
||||
/** Message d'erreur en cas d'échec */
|
||||
messageErreur: text("messageErreur"),
|
||||
/** Informations supplémentaires (JSON) */
|
||||
metadata: text("metadata"),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export type LogNotification = typeof logsNotifications.$inferSelect;
|
||||
export type InsertLogNotification = typeof logsNotifications.$inferInsert;
|
||||
export type Presence = typeof presences.$inferSelect;
|
||||
export type InsertPresence = typeof presences.$inferInsert;
|
||||
|
||||
/**
|
||||
* Table des attestations de formation
|
||||
* Stocke les attestations générées pour chaque apprenant ayant terminé une formation
|
||||
*/
|
||||
export const attestations = mysqlTable("attestations", {
|
||||
id: int("id").autoincrement().primaryKey(),
|
||||
/** ID de l'inscription concernée */
|
||||
inscriptionId: int("inscriptionId").notNull(),
|
||||
/** ID de l'apprenant */
|
||||
apprenantId: int("apprenantId").notNull(),
|
||||
/** ID de la séquence */
|
||||
sequenceId: int("sequenceId").notNull(),
|
||||
/** Clé S3 du PDF généré */
|
||||
urlPdf: varchar("urlPdf", { length: 500 }).notNull(),
|
||||
s3Key: varchar("s3Key", { length: 500 }).notNull(),
|
||||
/** URL publique du PDF */
|
||||
pdfUrl: varchar("pdfUrl", { length: 1000 }).notNull(),
|
||||
/** Date de génération de l'attestation */
|
||||
dateGeneration: timestamp("dateGeneration").defaultNow().notNull(),
|
||||
/** Statut de l'envoi par email */
|
||||
emailEnvoye: boolean("emailEnvoye").default(false).notNull(),
|
||||
/** Date d'envoi de l'email */
|
||||
dateEnvoiEmail: timestamp("dateEnvoiEmail"),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export type Attestation = typeof attestations.$inferSelect;
|
||||
@@ -584,25 +420,23 @@ export type InsertAttestation = typeof attestations.$inferInsert;
|
||||
|
||||
/**
|
||||
* Table de configuration des attestations
|
||||
* Stocke le logo, la signature et le template personnalisable
|
||||
*/
|
||||
export const configAttestation = mysqlTable("configAttestation", {
|
||||
id: int("id").autoincrement().primaryKey(),
|
||||
/** Texte personnalisable de l'attestation avec variables dynamiques */
|
||||
texteAttestation: text("texteAttestation").notNull(),
|
||||
/** Nom du signataire */
|
||||
nomSignataire: varchar("nomSignataire", { length: 255 }).notNull(),
|
||||
/** Fonction du signataire */
|
||||
fonctionSignataire: varchar("fonctionSignataire", { length: 255 }).notNull(),
|
||||
/** URL du logo sur S3 */
|
||||
logoUrl: varchar("logoUrl", { length: 500 }),
|
||||
/** Clé S3 du logo */
|
||||
logoS3Key: varchar("logoS3Key", { length: 500 }),
|
||||
/** URL du logo */
|
||||
logoUrl: varchar("logoUrl", { length: 1000 }),
|
||||
/** URL de la signature sur S3 */
|
||||
signatureUrl: varchar("signatureUrl", { length: 500 }),
|
||||
/** Clé S3 de la signature */
|
||||
signatureS3Key: varchar("signatureS3Key", { length: 500 }),
|
||||
/** URL de la signature */
|
||||
signatureUrl: varchar("signatureUrl", { length: 1000 }),
|
||||
/** Nom du signataire */
|
||||
nomSignataire: varchar("nomSignataire", { length: 255 }),
|
||||
/** Fonction du signataire */
|
||||
fonctionSignataire: varchar("fonctionSignataire", { length: 255 }),
|
||||
/** Texte personnalisable de l'attestation */
|
||||
texteAttestation: text("texteAttestation"),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
|
||||
});
|
||||
|
||||
@@ -610,29 +444,35 @@ export type ConfigAttestation = typeof configAttestation.$inferSelect;
|
||||
export type InsertConfigAttestation = typeof configAttestation.$inferInsert;
|
||||
|
||||
/**
|
||||
* Table des présences (émargement numérique)
|
||||
* Stocke les présences validées pour chaque apprenant à chaque date de formation
|
||||
* Table des notifications envoyées
|
||||
*/
|
||||
export const presences = mysqlTable("presences", {
|
||||
export const logsNotifications = mysqlTable("logsNotifications", {
|
||||
id: int("id").autoincrement().primaryKey(),
|
||||
/** ID de l'inscription */
|
||||
inscriptionId: int("inscriptionId").notNull(),
|
||||
/** ID de la date de formation */
|
||||
dateFormationId: int("dateFormationId").notNull(),
|
||||
/** Heure de validation de la présence */
|
||||
heurePresence: timestamp("heurePresence").notNull(),
|
||||
/** Mode de validation (qrcode, manuel) */
|
||||
modeValidation: mysqlEnum("modeValidation", ["qrcode", "manuel"]).notNull(),
|
||||
/** ID du validateur (formateur qui a validé manuellement, null si QR code) */
|
||||
validateurId: int("validateurId"),
|
||||
/** Commentaire optionnel */
|
||||
commentaire: text("commentaire"),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
/** Type de notification */
|
||||
type: mysqlEnum("type", [
|
||||
"inscription",
|
||||
"annulation",
|
||||
"capacite_atteinte",
|
||||
"place_disponible",
|
||||
"remerciement",
|
||||
"formateur_inscription",
|
||||
"formateur_annulation"
|
||||
]).notNull(),
|
||||
/** Email du destinataire */
|
||||
destinataire: varchar("destinataire", { length: 320 }).notNull(),
|
||||
/** ID de la séquence concernée */
|
||||
sequenceId: int("sequenceId"),
|
||||
/** ID de l'apprenant concerné */
|
||||
apprenantId: int("apprenantId"),
|
||||
/** Statut de l'envoi */
|
||||
statut: mysqlEnum("statut", ["succes", "echec"]).notNull(),
|
||||
/** Message d'erreur en cas d'échec */
|
||||
messageErreur: text("messageErreur"),
|
||||
dateEnvoi: timestamp("dateEnvoi").defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export type Presence = typeof presences.$inferSelect;
|
||||
export type InsertPresence = typeof presences.$inferInsert;
|
||||
|
||||
export type LogNotification = typeof logsNotifications.$inferSelect;
|
||||
export type InsertLogNotification = typeof logsNotifications.$inferInsert;
|
||||
|
||||
/**
|
||||
* Table des paramètres de l'application
|
||||
@@ -642,8 +482,36 @@ export const parametres = mysqlTable("parametres", {
|
||||
id: int("id").autoincrement().primaryKey(),
|
||||
/** URL publique de l'application pour les QR codes d'émargement */
|
||||
urlPublique: varchar("urlPublique", { length: 500 }).notNull().default("https://formations.itinova.org"),
|
||||
/** Délai d'expiration des QR codes en minutes (par défaut 30 minutes) */
|
||||
delaiExpirationQR: int("delaiExpirationQR").default(30).notNull(),
|
||||
/** Durée de validité des tokens d'émargement en heures (par défaut 24 heures) */
|
||||
dureeValiditeToken: int("dureeValiditeToken").default(24).notNull(),
|
||||
/** Activer ou désactiver les notifications automatiques */
|
||||
notificationsActives: boolean("notificationsActives").default(true).notNull(),
|
||||
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
|
||||
});
|
||||
|
||||
export type Parametre = typeof parametres.$inferSelect;
|
||||
export type InsertParametre = typeof parametres.$inferInsert;
|
||||
|
||||
/**
|
||||
* Table de l'historique des modifications des paramètres
|
||||
* Permet de tracer qui a modifié quoi et quand
|
||||
*/
|
||||
export const historiqueParametres = mysqlTable("historiqueParametres", {
|
||||
id: int("id").autoincrement().primaryKey(),
|
||||
/** ID de l'utilisateur qui a effectué la modification */
|
||||
userId: int("userId").notNull(),
|
||||
/** Nom de l'utilisateur (pour historique même si user supprimé) */
|
||||
userName: varchar("userName", { length: 255 }).notNull(),
|
||||
/** Champ modifié */
|
||||
champModifie: varchar("champModifie", { length: 100 }).notNull(),
|
||||
/** Ancienne valeur (JSON) */
|
||||
ancienneValeur: text("ancienneValeur"),
|
||||
/** Nouvelle valeur (JSON) */
|
||||
nouvelleValeur: text("nouvelleValeur"),
|
||||
dateModification: timestamp("dateModification").defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export type HistoriqueParametre = typeof historiqueParametres.$inferSelect;
|
||||
export type InsertHistoriqueParametre = typeof historiqueParametres.$inferInsert;
|
||||
|
||||
Reference in New Issue
Block a user