import { int, mysqlEnum, mysqlTable, text, timestamp, varchar, uniqueIndex, decimal } from "drizzle-orm/mysql-core"; /** * Core user table backing auth flow. * Supports multiple authentication methods: Manus OAuth, local, and Azure AD */ export const users = mysqlTable("users", { id: int("id").autoincrement().primaryKey(), /** Manus OAuth identifier (openId) - Optional for backward compatibility */ openId: varchar("openId", { length: 64 }).unique(), /** Azure AD Object ID - Unique identifier from Azure AD */ azureAdId: varchar("azureAdId", { length: 64 }).unique(), name: text("name"), email: varchar("email", { length: 320 }).notNull().unique(), /** Hashed password for local authentication (bcrypt) */ passwordHash: varchar("passwordHash", { length: 255 }), /** Authentication method: 'manus', 'local', 'azure-ad' */ loginMethod: mysqlEnum("loginMethod", ["manus", "local", "azure-ad"]).notNull(), role: mysqlEnum("role", ["user", "admin"]).default("user").notNull(), /** Account status for manual user management */ isActive: int("isActive").default(1).notNull(), // 0 = inactive, 1 = active 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; /** * Source files table storing uploaded PDF files that may contain multiple invoices */ export const sourceFiles = mysqlTable("sourceFiles", { id: int("id").autoincrement().primaryKey(), userId: int("userId").notNull(), fileName: varchar("fileName", { length: 255 }).notNull(), fileKey: text("fileKey").notNull(), // Local storage key with YYYY-MM prefix fileUrl: text("fileUrl").notNull(), // Public URL totalInvoicesDetected: int("totalInvoicesDetected").default(0).notNull(), processingStatus: mysqlEnum("processingStatus", ["processing", "completed", "error"]).default("processing").notNull(), processingProgress: varchar("processingProgress", { length: 255 }), // Progress message (e.g., "Extraction 3/9 factures...") createdAt: timestamp("createdAt").defaultNow().notNull(), updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(), }); export type SourceFile = typeof sourceFiles.$inferSelect; export type InsertSourceFile = typeof sourceFiles.$inferInsert; /** * Invoices table storing individual invoices extracted from source files */ export const invoices = mysqlTable("invoices", { id: int("id").autoincrement().primaryKey(), userId: int("userId").notNull(), sourceFileId: int("sourceFileId").notNull(), // Reference to the source PDF file invoiceIndexInFile: int("invoiceIndexInFile").default(1).notNull(), // Position in the source file (1, 2, 3...) // File storage information (for individual invoice if split, or reference to source) fileName: varchar("fileName", { length: 255 }).notNull(), fileKey: text("fileKey").notNull(), // Local storage key fileUrl: text("fileUrl").notNull(), // Public URL // Extracted metadata supplierName: varchar("supplierName", { length: 255 }), invoiceNumber: varchar("invoiceNumber", { length: 100 }), invoiceDate: timestamp("invoiceDate"), deliveryNoteNumber: varchar("deliveryNoteNumber", { length: 100 }), orderNumber: varchar("orderNumber", { length: 100 }), totalAmount: decimal("totalAmount", { precision: 10, scale: 2 }), recipientName: varchar("recipientName", { length: 255 }), // Destinataire de la facture pageRange: varchar("pageRange", { length: 20 }), // ex: "1-2" ou "5" qualityScore: int("qualityScore"), // Score de qualité de l'extraction (0-100) // Metadata JSON file metadataFileKey: text("metadataFileKey"), // Storage key for JSON metadata metadataFileUrl: text("metadataFileUrl"), // Public URL for JSON // Processing status status: mysqlEnum("status", ["processing", "completed", "error"]).default("processing").notNull(), errorMessage: text("errorMessage"), // Export status exportStatus: mysqlEnum("exportStatus", ["not_exported", "exported", "export_error"]).default("not_exported").notNull(), // Manual correction tracking manuallyEdited: int("manuallyEdited").default(0).notNull(), // 0 = false, 1 = true // Business fields (optional) serviceConcerne: varchar("serviceConcerne", { length: 100 }), // Service concerné (DSI, TRAVAUX, etc.) typeAchat: mysqlEnum("typeAchat", ["CAPEX", "OPEX"]), // Type d'achat ventilationComptable: varchar("ventilationComptable", { length: 100 }), // Ventilation comptable (TOUS, PA, HEP, etc.) // Automation tracking - JSON array of field names that were auto-filled by automation rules // Example: ["typeAchat", "serviceConcerne", "ventilationComptable"] autoFilledFields: text("autoFilledFields"), // JSON string array // Extracted text from PDF extractedText: text("extractedText"), // Full text extracted from the invoice PDF // Subscription flag isSubscription: int("isSubscription").default(0).notNull(), // 0 = NON, 1 = OUI // BAP Validation bapValidated: int("bapValidated").default(0).notNull(), // 0 = non validé, 1 = validé BAP bapValidatedAt: timestamp("bapValidatedAt"), // Date de validation BAP // SFTP Export tracking exportedAt: timestamp("exportedAt"), exportMode: mysqlEnum("exportMode", ["manual", "automatic"]), createdAt: timestamp("createdAt").defaultNow().notNull(), updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(), }, (table) => { return { // Unique constraint: no duplicate invoices with same supplier, invoice number, and date supplierInvoiceDateIdx: uniqueIndex("supplier_invoice_date_unique").on(table.supplierName, table.invoiceNumber, table.invoiceDate), }; }); export type Invoice = typeof invoices.$inferSelect; export type InsertInvoice = typeof invoices.$inferInsert; /** * User settings table for application preferences */ export const userSettings = mysqlTable("userSettings", { id: int("id").autoincrement().primaryKey(), userId: int("userId").notNull().unique(), // One settings record per user llmModel: varchar("llmModel", { length: 50 }).default("mistral-large-latest").notNull(), // Mistral model for invoice extraction orderNumberFormat: text("orderNumberFormat"), // Format/pattern du numéro de commande pour aider l'extraction // Mots-clés personnalisés pour améliorer la détection (séparés par des virgules) invoiceNumberKeywords: text("invoiceNumberKeywords"), // Ex: "Référence, Ref facture, Invoice ref" deliveryNoteKeywords: text("deliveryNoteKeywords"), // Ex: "Livraison, Delivery, Expédition" orderNumberKeywords: text("orderNumberKeywords"), // Ex: "Cde client, Référence commande, PO Number" supplierKeywords: text("supplierKeywords"), // Ex: "Vendeur, Société, Émetteur" totalAmountKeywords: text("totalAmountKeywords"), // Ex: "Net à payer, Total à régler, Amount due" subscriptionKeywords: text("subscriptionKeywords"), // Ex: "Abonnement, Subscription, Mensuel, Annuel" recipientKeywords: text("recipientKeywords"), // Ex: "Destinataire, À l'attention de, Client" sftpRecipientFilter: text("sftpRecipientFilter"), // Filtre destinataire pour l'export SFTP (vide = tous) // SFTP Configuration sftpHost: varchar("sftpHost", { length: 255 }), sftpPort: int("sftpPort").default(22), sftpUsername: varchar("sftpUsername", { length: 255 }), sftpPassword: text("sftpPassword"), // Encrypted password sftpRemotePath: varchar("sftpRemotePath", { length: 500 }).default("/"), // Remote directory path sftpAutoExport: int("sftpAutoExport").default(0).notNull(), // 0 = manual, 1 = automatic // LLM Logs retention llmLogsRetentionMonths: int("llmLogsRetentionMonths").default(3).notNull(), // Durée de conservation des logs LLM en mois (défaut: 3 mois) // Seuil de confiance pour les apprentissages IA learningConfidenceThreshold: int("learningConfidenceThreshold").default(2).notNull(), // Nombre minimum d'applications pour marquer un apprentissage comme Confirmé // AI Engine configuration aiProvider: mysqlEnum("aiProvider", ["mistral", "manus"]).default("mistral").notNull(), // AI provider: 'mistral' or 'manus' mistralApiKey: text("mistralApiKey"), // Mistral API key (overrides env MISTRAL_API_KEY) manusForgeApiKey: text("manusForgeApiKey"), // Manus Forge API key (overrides env BUILT_IN_FORGE_API_KEY) manusForgeApiUrl: text("manusForgeApiUrl"), // Manus Forge API URL (overrides env BUILT_IN_FORGE_API_URL) createdAt: timestamp("createdAt").defaultNow().notNull(), updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(), }); export type UserSettings = typeof userSettings.$inferSelect; export type InsertUserSettings = typeof userSettings.$inferInsert; /** * Import settings table for configuring different import methods */ export const importSettings = mysqlTable("importSettings", { id: int("id").autoincrement().primaryKey(), userId: int("userId").notNull().unique(), // One settings record per user // Manual import settings manualImportEnabled: int("manualImportEnabled").default(1).notNull(), // 0 = disabled, 1 = enabled // Automatic import from folder settings autoImportEnabled: int("autoImportEnabled").default(0).notNull(), // 0 = disabled, 1 = enabled autoImportSourcePath: text("autoImportSourcePath"), // Path to folder to watch autoImportFrequency: int("autoImportFrequency").default(60).notNull(), // Frequency in minutes (default: 60 = 1 hour) // Email import settings emailImportEnabled: int("emailImportEnabled").default(0).notNull(), // 0 = disabled, 1 = enabled emailImportAddress: varchar("emailImportAddress", { length: 320 }), // Email address to monitor emailImportPassword: text("emailImportPassword"), // Encrypted password emailImportHost: varchar("emailImportHost", { length: 255 }), // IMAP server host emailImportPort: int("emailImportPort").default(993), // IMAP port (default: 993 for SSL) emailImportFrequency: int("emailImportFrequency").default(30).notNull(), // Frequency in minutes (default: 30) emailImportSinceDate: int("emailImportSinceDate"), // Timestamp Unix (s) — ne pas lire les emails antérieurs à cette date emailImportAuthMode: mysqlEnum("emailImportAuthMode", ["basic", "oauth2"]).default("basic").notNull(), // Auth mode: basic (login/password) or oauth2 (Azure AD token) // Export folder settings exportFolder: text("exportFolder"), // Path to folder for exporting invoices exportFolderType: mysqlEnum("exportFolderType", ["local", "teams", "sharepoint"]).default("local").notNull(), // Type de destination d'export bapExportMode: mysqlEnum("bapExportMode", ["browser", "folder", "both"]).default("browser").notNull(), // BAP export mode: open in browser or save to folder // Azure AD / Microsoft Graph credentials for SharePoint export azureTenantId: varchar("azureTenantId", { length: 100 }), // Azure AD Tenant ID azureClientId: varchar("azureClientId", { length: 100 }), // Azure AD Application (client) ID azureClientSecret: text("azureClientSecret"), // Azure AD Client Secret (encrypted) azureSecretExpiresAt: timestamp("azureSecretExpiresAt"), // Azure AD Client Secret expiration date // AI Engine settings aiProvider: mysqlEnum("aiProvider", ["mistral", "manus"]).default("mistral").notNull(), // AI provider for invoice extraction mistralApiKey: text("mistralApiKey"), // Mistral API key (overrides env MISTRAL_API_KEY) manusForgeApiKey: text("manusForgeApiKey"), // Manus Forge API key (overrides env BUILT_IN_FORGE_API_KEY) manusForgeApiUrl: text("manusForgeApiUrl"), // Manus Forge API URL (overrides env BUILT_IN_FORGE_API_URL) createdAt: timestamp("createdAt").defaultNow().notNull(), updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(), }); export type ImportSettings = typeof importSettings.$inferSelect; export type InsertImportSettings = typeof importSettings.$inferInsert; /** * Import logs table for tracking all import operations */ export const importLogs = mysqlTable("importLogs", { id: int("id").autoincrement().primaryKey(), userId: int("userId").notNull(), sourceFileId: int("sourceFileId").notNull(), // Reference to source file fileName: varchar("fileName", { length: 255 }).notNull(), totalInvoicesDetected: int("totalInvoicesDetected").default(0).notNull(), invoicesImported: int("invoicesImported").default(0).notNull(), duplicatesIgnored: int("duplicatesIgnored").default(0).notNull(), errors: int("errors").default(0).notNull(), duplicateDetails: text("duplicateDetails"), // JSON array of duplicate invoice info errorDetails: text("errorDetails"), // JSON array of error messages importedAt: timestamp("importedAt").defaultNow().notNull(), }); export type ImportLog = typeof importLogs.$inferSelect; export type InsertImportLog = typeof importLogs.$inferInsert; /** * LLM Logs table for storing raw LLM responses for debugging and improvement */ export const llmLogs = mysqlTable("llmLogs", { id: int("id").autoincrement().primaryKey(), userId: int("userId").notNull(), sourceFileId: int("sourceFileId"), // Optional: link to source file if applicable invoiceId: int("invoiceId"), // Optional: link to invoice if applicable operation: varchar("operation", { length: 50 }).notNull(), // "detection" or "extraction" model: varchar("model", { length: 50 }).notNull(), // LLM model used promptSent: text("promptSent").notNull(), // Full prompt sent to LLM rawResponse: text("rawResponse").notNull(), // Raw response from LLM (before cleaning) cleanedResponse: text("cleanedResponse"), // Response after markdown cleaning success: int("success").default(1).notNull(), // 1 = success, 0 = error errorMessage: text("errorMessage"), // Error message if failed processingTimeMs: int("processingTimeMs"), // Processing time in milliseconds pageRange: varchar("pageRange", { length: 50 }), // Page range for this log (e.g., "1-2") createdAt: timestamp("createdAt").defaultNow().notNull(), }); export type LlmLog = typeof llmLogs.$inferSelect; export type InsertLlmLog = typeof llmLogs.$inferInsert; /** * Department list table for managing "Service concerné" values */ export const departmentList = mysqlTable("departmentList", { id: int("id").autoincrement().primaryKey(), userId: int("userId").notNull(), // Each user has their own list name: varchar("name", { length: 100 }).notNull(), createdAt: timestamp("createdAt").defaultNow().notNull(), }, (table) => { return { // Unique constraint: no duplicate department names for the same user userDepartmentIdx: uniqueIndex("user_department_unique").on(table.userId, table.name), }; }); export type Department = typeof departmentList.$inferSelect; export type InsertDepartment = typeof departmentList.$inferInsert; /** * Accounting allocation list table for managing "Ventilation comptable" values */ export const accountingAllocationList = mysqlTable("accountingAllocationList", { id: int("id").autoincrement().primaryKey(), userId: int("userId").notNull(), // Each user has their own list name: varchar("name", { length: 100 }).notNull(), createdAt: timestamp("createdAt").defaultNow().notNull(), }, (table) => { return { // Unique constraint: no duplicate allocation names for the same user userAllocationIdx: uniqueIndex("user_allocation_unique").on(table.userId, table.name), }; }); export type AccountingAllocation = typeof accountingAllocationList.$inferSelect; export type InsertAccountingAllocation = typeof accountingAllocationList.$inferInsert; /** * Automation rules table for automatic field filling based on conditions */ export const automationRules = mysqlTable("automationRules", { id: int("id").autoincrement().primaryKey(), userId: int("userId").notNull(), // Each user has their own rules name: varchar("name", { length: 255 }).notNull(), // Rule name for identification isActive: int("isActive").default(1).notNull(), // 0 = disabled, 1 = enabled priority: int("priority").default(0).notNull(), // Execution order (lower = higher priority) // Conditions (IF) - JSON array of condition objects // Example: [{"field": "supplierName", "operator": "contains", "value": "Microsoft"}, {"field": "totalAmount", "operator": ">", "value": "1000"}] conditions: text("conditions").notNull(), // JSON string conditionsLogic: mysqlEnum("conditionsLogic", ["AND", "OR"]).default("AND").notNull(), // How to combine conditions // Actions (THEN) - JSON object with field assignments // Example: {"typeAchat": "CAPEX", "serviceConcerne": "DSI", "ventilationComptable": "TOUS"} actions: text("actions").notNull(), // JSON string createdAt: timestamp("createdAt").defaultNow().notNull(), updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(), }); export type AutomationRule = typeof automationRules.$inferSelect; export type InsertAutomationRule = typeof automationRules.$inferInsert; /** * LLM Fields Configuration table * Stores configuration for each field used in invoice extraction * Allows users to define which fields are required for quality score calculation */ export const llmFieldsConfig = mysqlTable("llmFieldsConfig", { id: int("id").autoincrement().primaryKey(), userId: int("userId").notNull(), // Each user has their own configuration fieldName: varchar("fieldName", { length: 100 }).notNull(), // Field identifier (supplierName, invoiceNumber, etc.) displayName: varchar("displayName", { length: 255 }).notNull(), // Human-readable field name isRequired: int("isRequired").default(1).notNull(), // 1 = required for 100% score, 0 = optional displayOrder: int("displayOrder").default(0).notNull(), // Order in UI createdAt: timestamp("createdAt").defaultNow().notNull(), updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(), }); export type LlmFieldConfig = typeof llmFieldsConfig.$inferSelect; export type InsertLlmFieldConfig = typeof llmFieldsConfig.$inferInsert; /** * Signatures table storing user signature images with first/last name */ export const signatures = mysqlTable("signatures", { id: int("id").autoincrement().primaryKey(), userId: int("userId").notNull(), firstName: varchar("firstName", { length: 100 }).notNull(), lastName: varchar("lastName", { length: 100 }).notNull(), imageKey: text("imageKey").notNull(), // Local storage key imageUrl: text("imageUrl").notNull(), // Public URL to the signature image createdAt: timestamp("createdAt").defaultNow().notNull(), updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(), }); export type Signature = typeof signatures.$inferSelect; export type InsertSignature = typeof signatures.$inferInsert; /** * Service-Signature association table * Associates a department/service with a specific signature for PDF exports */ export const serviceSignatures = mysqlTable("serviceSignatures", { id: int("id").autoincrement().primaryKey(), userId: int("userId").notNull(), serviceName: varchar("serviceName", { length: 100 }).notNull(), // Department name (e.g., "DSI", "TRAVAUX") signatureId: int("signatureId").notNull(), // FK to signatures.id createdAt: timestamp("createdAt").defaultNow().notNull(), updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(), }, (table) => { return { // One association per service per user userServiceIdx: uniqueIndex("user_service_unique").on(table.userId, table.serviceName), }; }); export type ServiceSignature = typeof serviceSignatures.$inferSelect; export type InsertServiceSignature = typeof serviceSignatures.$inferInsert; /** * BAP History table - records every BAP validation with PDF export details */ export const bapHistory = mysqlTable("bapHistory", { id: int("id").autoincrement().primaryKey(), userId: int("userId").notNull(), invoiceId: int("invoiceId").notNull(), supplierName: varchar("supplierName", { length: 255 }), invoiceNumber: varchar("invoiceNumber", { length: 100 }), invoiceDate: timestamp("invoiceDate"), totalAmount: varchar("totalAmount", { length: 50 }), typeAchat: varchar("typeAchat", { length: 50 }), // CAPEX / OPEX serviceConcerne: varchar("serviceConcerne", { length: 100 }), ventilationComptable: varchar("ventilationComptable", { length: 100 }), recipientName: varchar("recipientName", { length: 255 }), exportMode: mysqlEnum("exportMode", ["browser", "folder"]).default("browser").notNull(), exportPath: text("exportPath"), // null for browser mode pdfUrl: text("pdfUrl"), // S3 URL for browser mode signatureName: varchar("signatureName", { length: 255 }), // Signer name if applied sharepointUploadStatus: mysqlEnum("sharepointUploadStatus", ["success", "error", "skipped"]), // SharePoint upload result sharepointUploadPath: text("sharepointUploadPath"), // Path/URL of uploaded file in SharePoint sharepointUploadError: text("sharepointUploadError"), // Error message if upload failed validatedAt: timestamp("validatedAt").defaultNow().notNull(), }); export type BapHistory = typeof bapHistory.$inferSelect; export type InsertBapHistory = typeof bapHistory.$inferInsert; /** * Invoice learnings table — corrections manuelles apprises par le système * Quand l'utilisateur modifie un champ détecté par le LLM, le système mémorise * la correction pour l'appliquer automatiquement aux prochains imports similaires. */ export const invoiceLearnings = mysqlTable("invoiceLearnings", { id: int("id").autoincrement().primaryKey(), userId: int("userId").notNull(), /** Clé de correspondance : fournisseur normalisé (minuscules, sans espaces superflus) */ supplierKey: varchar("supplierKey", { length: 255 }).notNull(), /** Champ corrigé : 'isSubscription' | 'typeAchat' | 'serviceConcerne' | 'ventilationComptable' */ fieldName: varchar("fieldName", { length: 100 }).notNull(), /** Valeur originale détectée par le LLM (pour affichage dans la page de gestion) */ originalValue: varchar("originalValue", { length: 255 }), /** Valeur corrigée manuellement par l'utilisateur */ correctedValue: varchar("correctedValue", { length: 255 }).notNull(), /** Nombre de fois que cette correction a été appliquée */ applyCount: int("applyCount").default(1).notNull(), createdAt: timestamp("createdAt").defaultNow().notNull(), updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(), }); export type InvoiceLearning = typeof invoiceLearnings.$inferSelect; export type InsertInvoiceLearning = typeof invoiceLearnings.$inferInsert; /** * FreePro ventilation imports — one record per imported monthly file */ export const freeproImports = mysqlTable("freeproImports", { id: int("id").autoincrement().primaryKey(), userId: int("userId").notNull(), /** Label du mois, ex: "06/2025" */ moisLabel: varchar("moisLabel", { length: 10 }).notNull(), /** Année (ex: 2025) */ annee: int("annee").notNull(), /** Mois numérique (1-12) */ mois: int("mois").notNull(), /** Référence de la pièce comptable, ex: F202506006010 */ refPiece: varchar("refPiece", { length: 50 }), /** Nom du fichier Excel importé */ fileName: varchar("fileName", { length: 255 }).notNull(), /** Nombre de lignes traitées */ nbLignes: int("nbLignes").default(0).notNull(), /** Total général TTC calculé */ totalTtc: varchar("totalTtc", { length: 30 }), /** Statut de l'export SharePoint : null = jamais exporté, 'success' = exporté, 'error' = erreur */ sharepointUploadStatus: mysqlEnum("sharepointUploadStatus", ["success", "error"]), /** URL/chemin du fichier dans SharePoint après export réussi */ sharepointUploadPath: text("sharepointUploadPath"), /** Message d'erreur si l'export SharePoint a échoué */ sharepointUploadError: text("sharepointUploadError"), /** Date de l'export SharePoint */ sharepointExportedAt: timestamp("sharepointExportedAt"), createdAt: timestamp("createdAt").defaultNow().notNull(), updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(), }); export type FreeproImport = typeof freeproImports.$inferSelect; export type InsertFreeproImport = typeof freeproImports.$inferInsert; /** * FreePro ventilation lines — aggregated result (structure + type → montant TTC) */ export const freeproVentilationLines = mysqlTable("freeproVentilationLines", { id: int("id").autoincrement().primaryKey(), importId: int("importId").notNull(), // FK to freeproImports.id /** Code structure, ex: "1083ADV" ou null pour (vide) */ structure: varchar("structure", { length: 100 }), /** Type: "Lien fibre" | "Lien 5G" | "Tél. mobile" */ type: varchar("type", { length: 50 }).notNull(), /** Montant TTC agrégé en centimes (pour éviter les flottants) */ montantCentimes: int("montantCentimes").notNull(), createdAt: timestamp("createdAt").defaultNow().notNull(), }); export type FreeproVentilationLine = typeof freeproVentilationLines.$inferSelect; export type InsertFreeproVentilationLine = typeof freeproVentilationLines.$inferInsert; /** * FreePro settings — paramètres de connexion automatique au portail FreePro * et de récupération périodique des factures CSV */ export const freeproSettings = mysqlTable("freeproSettings", { id: int("id").autoincrement().primaryKey(), userId: int("userId").notNull().unique(), // Un paramétrage par utilisateur /** URL du portail FreePro (ex: https://pro.free.fr) */ portalUrl: varchar("portalUrl", { length: 255 }).default("https://pro.free.fr").notNull(), /** Email de connexion au portail FreePro */ loginEmail: varchar("loginEmail", { length: 320 }), /** Mot de passe de connexion au portail FreePro */ loginPassword: text("loginPassword"), /** Fréquence de récupération automatique */ frequency: mysqlEnum("frequency", ["manual", "daily", "weekly", "monthly"]).default("manual").notNull(), /** Date d'antériorité max (timestamp Unix ms) — ne pas récupérer les factures antérieures à cette date */ maxAnteriority: int("maxAnteriority"), // Timestamp Unix en secondes /** Activation de la récupération automatique */ autoEnabled: int("autoEnabled").default(0).notNull(), // 0 = désactivé, 1 = activé /** Date de la dernière récupération réussie */ lastSuccessAt: timestamp("lastSuccessAt"), /** Message de statut de la dernière récupération */ lastStatus: text("lastStatus"), /** Nombre de factures récupérées lors du dernier run */ lastImportCount: int("lastImportCount").default(0), createdAt: timestamp("createdAt").defaultNow().notNull(), updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(), }); export type FreeproSettings = typeof freeproSettings.$inferSelect; export type InsertFreeproSettings = typeof freeproSettings.$inferInsert;