import { boolean, datetime, int, mysqlEnum, mysqlTable, text, timestamp, varchar } from "drizzle-orm/mysql-core"; /** * Core user table backing auth flow. * Extend this file with additional tables as your product grows. * Columns use camelCase to match both database fields and generated types. */ export const users = mysqlTable("users", { /** * Surrogate primary key. Auto-incremented numeric value managed by the database. * Use this for relations between tables. */ id: int("id").autoincrement().primaryKey(), /** Manus OAuth identifier (openId) returned from the OAuth callback. Unique per user. */ openId: varchar("openId", { length: 64 }).notNull().unique(), name: text("name"), email: varchar("email", { length: 320 }), /** Identifiant de connexion unique pour l'utilisateur */ username: varchar("username", { length: 100 }).unique(), /** Mot de passe hashé (bcrypt) */ password: varchar("password", { length: 255 }), loginMethod: varchar("loginMethod", { length: 64 }), role: mysqlEnum("role", ["user", "admin", "formateur"]).default("user").notNull(), /** Statut du compte (actif/inactif) */ isActive: boolean("isActive").default(true).notNull(), /** ID du formateur associé (si role = formateur) */ formateurId: int("formateurId"), createdAt: timestamp("createdAt").defaultNow().notNull(), updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(), lastSignedIn: timestamp("lastSignedIn").defaultNow().notNull(), }); export type User = typeof users.$inferSelect; export type InsertUser = typeof users.$inferInsert; /** * Table des formations (ex: Manager Itinova) */ export const formations = mysqlTable("formations", { id: int("id").autoincrement().primaryKey(), nom: varchar("nom", { length: 255 }).notNull(), description: text("description"), /** Lien unique pour l'inscription à cette formation */ lienUnique: varchar("lienUnique", { length: 100 }).notNull().unique(), actif: boolean("actif").default(true).notNull(), createdAt: timestamp("createdAt").defaultNow().notNull(), updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(), }); 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 */ 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(), /** 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(), createdAt: timestamp("createdAt").defaultNow().notNull(), updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(), }); export type Sequence = typeof sequences.$inferSelect; export type InsertSequence = typeof sequences.$inferInsert; /** * Table des dates de formation pour chaque séquence * Une séquence peut avoir jusqu'à 4 dates */ 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: int("ordre").notNull(), createdAt: timestamp("createdAt").defaultNow().notNull(), }); export type DateFormation = typeof datesFormation.$inferSelect; export type InsertDateFormation = typeof datesFormation.$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(), dateInscription: timestamp("dateInscription").defaultNow().notNull(), createdAt: timestamp("createdAt").defaultNow().notNull(), updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(), }); 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.) */ 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"), /** 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 */ 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(), }); export type EmailConfig = typeof emailConfig.$inferSelect; export type InsertEmailConfig = typeof emailConfig.$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(), }); 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) */ 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"), /** 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 */ s3Key: varchar("s3Key", { length: 500 }), /** Type MIME du fichier */ typeFichier: varchar("typeFichier", { length: 100 }), /** Taille du fichier en octets */ tailleFichier: int("tailleFichier"), createdAt: timestamp("createdAt").defaultNow().notNull(), updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(), }); export type Rappel = typeof rappels.$inferSelect; 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 */ 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(), }); 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 */ export const questionnaires = mysqlTable("questionnaires", { id: int("id").autoincrement().primaryKey(), /** Nom du questionnaire */ nom: varchar("nom", { length: 255 }).notNull(), /** 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(), }); export type Questionnaire = typeof questionnaires.$inferSelect; export type InsertQuestionnaire = typeof questionnaires.$inferInsert; /** * Table des questions * Stocke les questions associées à chaque 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: 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 */ obligatoire: boolean("obligatoire").default(false).notNull(), createdAt: timestamp("createdAt").defaultNow().notNull(), updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(), }); export type Question = typeof questions.$inferSelect; 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 */ 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 */ 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) */ reponseTexte: text("reponseTexte"), createdAt: timestamp("createdAt").defaultNow().notNull(), updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(), }); export type ReponseQuestion = typeof reponsesQuestions.$inferSelect; 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(), /** Token unique pour accéder au questionnaire */ token: varchar("token", { length: 100 }).notNull().unique(), /** Date de réponse (null si pas encore répondu) */ dateReponse: timestamp("dateReponse"), createdAt: timestamp("createdAt").defaultNow().notNull(), updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().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 */ 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 */ 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 */ export const logsRappels = mysqlTable("logsRappels", { 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"), 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; /** * 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é */ 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; 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(), /** Clé S3 du logo */ logoS3Key: varchar("logoS3Key", { length: 500 }), /** URL du logo */ logoUrl: varchar("logoUrl", { length: 1000 }), /** 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(), }); 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 */ export const presences = mysqlTable("presences", { 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(), }); export type Presence = typeof presences.$inferSelect; export type InsertPresence = typeof presences.$inferInsert; /** * Table des paramètres de l'application * Stocke les paramètres globaux de configuration */ 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"), updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(), }); export type Parametre = typeof parametres.$inferSelect; export type InsertParametre = typeof parametres.$inferInsert;