diff --git a/client/src/App.tsx b/client/src/App.tsx
index 96a19d5..b5cad5b 100644
--- a/client/src/App.tsx
+++ b/client/src/App.tsx
@@ -11,6 +11,7 @@ import Upload from "./pages/Upload";
import Invoices from "./pages/Invoices";
import InvoiceDetail from "./pages/InvoiceDetail";
import Settings from "./pages/Settings";
+import ImportSettings from "./pages/ImportSettings";
import History from "./pages/History";
import Users from "./pages/Users";
@@ -24,6 +25,7 @@ function Router() {
+
diff --git a/client/src/components/DashboardLayout.tsx b/client/src/components/DashboardLayout.tsx
index b601e07..7011e39 100644
--- a/client/src/components/DashboardLayout.tsx
+++ b/client/src/components/DashboardLayout.tsx
@@ -21,7 +21,7 @@ import {
} from "@/components/ui/sidebar";
import { getLoginUrl } from "@/const";
import { useIsMobile } from "@/hooks/useMobile";
-import { LayoutDashboard, LogOut, PanelLeft, Users, FileText, Upload, History, Settings } from "lucide-react";
+import { LayoutDashboard, LogOut, PanelLeft, Users, FileText, Upload, History, Settings, Download } from "lucide-react";
import { CSSProperties, useEffect, useRef, useState } from "react";
import { useLocation } from "wouter";
import { DashboardLayoutSkeleton } from './DashboardLayoutSkeleton';
@@ -33,6 +33,7 @@ const menuItems = [
{ icon: FileText, label: "Factures", path: "/invoices" },
{ icon: History, label: "Historique", path: "/history" },
{ icon: Settings, label: "Param\u00e8tres", path: "/settings" },
+ { icon: Download, label: "Param\u00e8tres de r\u00e9ception", path: "/import-settings" },
{ icon: Users, label: "Utilisateurs", path: "/users", adminOnly: true },
];
diff --git a/client/src/pages/ImportSettings.tsx b/client/src/pages/ImportSettings.tsx
new file mode 100644
index 0000000..21a05ac
--- /dev/null
+++ b/client/src/pages/ImportSettings.tsx
@@ -0,0 +1,309 @@
+import { useState, useEffect } from "react";
+import { trpc } from "@/lib/trpc";
+import { Button } from "@/components/ui/button";
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { Switch } from "@/components/ui/switch";
+import { toast } from "sonner";
+import { Loader2, Save, Upload, FolderOpen, Mail } from "lucide-react";
+
+export default function ImportSettings() {
+ const { data: settings, isLoading } = trpc.importSettings.get.useQuery();
+ const updateMutation = trpc.importSettings.update.useMutation();
+
+ // Manual import
+ const [manualImportEnabled, setManualImportEnabled] = useState(true);
+
+ // Automatic import from folder
+ const [autoImportEnabled, setAutoImportEnabled] = useState(false);
+ const [autoImportSourcePath, setAutoImportSourcePath] = useState("");
+ const [autoImportFrequency, setAutoImportFrequency] = useState(60);
+
+ // Email import
+ const [emailImportEnabled, setEmailImportEnabled] = useState(false);
+ const [emailImportAddress, setEmailImportAddress] = useState("");
+ const [emailImportPassword, setEmailImportPassword] = useState("");
+ const [emailImportHost, setEmailImportHost] = useState("");
+ const [emailImportPort, setEmailImportPort] = useState(993);
+ const [emailImportFrequency, setEmailImportFrequency] = useState(30);
+
+ // Initialize form with settings from database
+ useEffect(() => {
+ if (settings) {
+ setManualImportEnabled(settings.manualImportEnabled === 1);
+ setAutoImportEnabled(settings.autoImportEnabled === 1);
+ setAutoImportSourcePath(settings.autoImportSourcePath || "");
+ setAutoImportFrequency(settings.autoImportFrequency || 60);
+ setEmailImportEnabled(settings.emailImportEnabled === 1);
+ setEmailImportAddress(settings.emailImportAddress || "");
+ setEmailImportPassword(settings.emailImportPassword || "");
+ setEmailImportHost(settings.emailImportHost || "");
+ setEmailImportPort(settings.emailImportPort || 993);
+ setEmailImportFrequency(settings.emailImportFrequency || 30);
+ }
+ }, [settings]);
+
+ const handleSave = async () => {
+ try {
+ await updateMutation.mutateAsync({
+ manualImportEnabled: manualImportEnabled ? 1 : 0,
+ autoImportEnabled: autoImportEnabled ? 1 : 0,
+ autoImportSourcePath: autoImportSourcePath || null,
+ autoImportFrequency: autoImportFrequency,
+ emailImportEnabled: emailImportEnabled ? 1 : 0,
+ emailImportAddress: emailImportAddress || null,
+ emailImportPassword: emailImportPassword || null,
+ emailImportHost: emailImportHost || null,
+ emailImportPort: emailImportPort,
+ emailImportFrequency: emailImportFrequency,
+ });
+
+ toast.success("Paramètres enregistrés", {
+ description: "Vos paramètres de réception ont été mis à jour avec succès.",
+ });
+ } catch (error) {
+ toast.error("Erreur", {
+ description: "Impossible d'enregistrer les paramètres. Veuillez réessayer.",
+ });
+ }
+ };
+
+ if (isLoading) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
+
Paramètres de réception
+
+ Configurez les différentes méthodes d'importation de factures
+
+
+
+
+ {/* Manual Import */}
+
+
+
+
+
+
+
+ Import manuel
+
+ Autoriser l'upload manuel de fichiers PDF via l'interface
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Automatic Import from Folder */}
+
+
+
+
+
+
+
+ Import automatique depuis dossier
+
+ Surveiller un dossier et importer automatiquement les nouveaux fichiers PDF
+
+
+
+
+
+
+
+
+
+
+ {autoImportEnabled && (
+ <>
+
+
+
setAutoImportSourcePath(e.target.value)}
+ />
+
+ Chemin absolu du dossier à surveiller pour les nouveaux fichiers PDF
+
+
+
+
+
+
setAutoImportFrequency(parseInt(e.target.value) || 60)}
+ />
+
+ Intervalle de temps entre chaque vérification du dossier (minimum: 1 minute)
+
+
+ >
+ )}
+
+
+
+ {/* Email Import */}
+
+
+
+
+
+
+
+ Import par email
+
+ Récupérer automatiquement les factures reçues par email
+
+
+
+
+
+
+
+
+
+
+ {emailImportEnabled && (
+ <>
+
+
+
setEmailImportAddress(e.target.value)}
+ />
+
+ Adresse email à surveiller pour les factures
+
+
+
+
+
+
setEmailImportPassword(e.target.value)}
+ />
+
+ Mot de passe du compte email (stocké de manière sécurisée)
+
+
+
+
+
+
+
setEmailImportHost(e.target.value)}
+ />
+
+ Adresse du serveur IMAP
+
+
+
+
+
+
setEmailImportPort(parseInt(e.target.value) || 993)}
+ />
+
+ Port SSL (993 par défaut)
+
+
+
+
+
+
+
setEmailImportFrequency(parseInt(e.target.value) || 30)}
+ />
+
+ Intervalle de temps entre chaque vérification des emails (minimum: 1 minute)
+
+
+ >
+ )}
+
+
+
+ {/* Save Button */}
+
+
+
+
+
+ );
+}
diff --git a/drizzle/0003_previous_killraven.sql b/drizzle/0003_previous_killraven.sql
new file mode 100644
index 0000000..207520e
--- /dev/null
+++ b/drizzle/0003_previous_killraven.sql
@@ -0,0 +1,18 @@
+CREATE TABLE `importSettings` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `userId` int NOT NULL,
+ `manualImportEnabled` int NOT NULL DEFAULT 1,
+ `autoImportEnabled` int NOT NULL DEFAULT 0,
+ `autoImportSourcePath` text,
+ `autoImportFrequency` int NOT NULL DEFAULT 60,
+ `emailImportEnabled` int NOT NULL DEFAULT 0,
+ `emailImportAddress` varchar(320),
+ `emailImportPassword` text,
+ `emailImportHost` varchar(255),
+ `emailImportPort` int DEFAULT 993,
+ `emailImportFrequency` int NOT NULL DEFAULT 30,
+ `createdAt` timestamp NOT NULL DEFAULT (now()),
+ `updatedAt` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP,
+ CONSTRAINT `importSettings_id` PRIMARY KEY(`id`),
+ CONSTRAINT `importSettings_userId_unique` UNIQUE(`userId`)
+);
diff --git a/drizzle/meta/0003_snapshot.json b/drizzle/meta/0003_snapshot.json
new file mode 100644
index 0000000..4beff40
--- /dev/null
+++ b/drizzle/meta/0003_snapshot.json
@@ -0,0 +1,950 @@
+{
+ "version": "5",
+ "dialect": "mysql",
+ "id": "1e89df9d-47b6-47da-8afc-6024d8bdc8ba",
+ "prevId": "5a495536-d69b-4bd9-9618-0644c5a001bd",
+ "tables": {
+ "importLogs": {
+ "name": "importLogs",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "sourceFileId": {
+ "name": "sourceFileId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "fileName": {
+ "name": "fileName",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "totalInvoicesDetected": {
+ "name": "totalInvoicesDetected",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "invoicesImported": {
+ "name": "invoicesImported",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "duplicatesIgnored": {
+ "name": "duplicatesIgnored",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "errors": {
+ "name": "errors",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "duplicateDetails": {
+ "name": "duplicateDetails",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "errorDetails": {
+ "name": "errorDetails",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "importedAt": {
+ "name": "importedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "importLogs_id": {
+ "name": "importLogs_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "importSettings": {
+ "name": "importSettings",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "manualImportEnabled": {
+ "name": "manualImportEnabled",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 1
+ },
+ "autoImportEnabled": {
+ "name": "autoImportEnabled",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "autoImportSourcePath": {
+ "name": "autoImportSourcePath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "autoImportFrequency": {
+ "name": "autoImportFrequency",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 60
+ },
+ "emailImportEnabled": {
+ "name": "emailImportEnabled",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "emailImportAddress": {
+ "name": "emailImportAddress",
+ "type": "varchar(320)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "emailImportPassword": {
+ "name": "emailImportPassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "emailImportHost": {
+ "name": "emailImportHost",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "emailImportPort": {
+ "name": "emailImportPort",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 993
+ },
+ "emailImportFrequency": {
+ "name": "emailImportFrequency",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 30
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "importSettings_id": {
+ "name": "importSettings_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {
+ "importSettings_userId_unique": {
+ "name": "importSettings_userId_unique",
+ "columns": [
+ "userId"
+ ]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "invoices": {
+ "name": "invoices",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "sourceFileId": {
+ "name": "sourceFileId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "invoiceIndexInFile": {
+ "name": "invoiceIndexInFile",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 1
+ },
+ "fileName": {
+ "name": "fileName",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "fileKey": {
+ "name": "fileKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "fileUrl": {
+ "name": "fileUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "supplierName": {
+ "name": "supplierName",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "invoiceNumber": {
+ "name": "invoiceNumber",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "invoiceDate": {
+ "name": "invoiceDate",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "deliveryNoteNumber": {
+ "name": "deliveryNoteNumber",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "orderNumber": {
+ "name": "orderNumber",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "totalAmount": {
+ "name": "totalAmount",
+ "type": "decimal(10,2)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "pageRange": {
+ "name": "pageRange",
+ "type": "varchar(20)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "qualityScore": {
+ "name": "qualityScore",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "metadataFileKey": {
+ "name": "metadataFileKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "metadataFileUrl": {
+ "name": "metadataFileUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "enum('processing','completed','error')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'processing'"
+ },
+ "errorMessage": {
+ "name": "errorMessage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "exportStatus": {
+ "name": "exportStatus",
+ "type": "enum('not_exported','exported','export_error')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'not_exported'"
+ },
+ "manuallyEdited": {
+ "name": "manuallyEdited",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "exportedAt": {
+ "name": "exportedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "exportMode": {
+ "name": "exportMode",
+ "type": "enum('manual','automatic')",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ }
+ },
+ "indexes": {
+ "supplier_invoice_date_unique": {
+ "name": "supplier_invoice_date_unique",
+ "columns": [
+ "supplierName",
+ "invoiceNumber",
+ "invoiceDate"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "invoices_id": {
+ "name": "invoices_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "llmLogs": {
+ "name": "llmLogs",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "sourceFileId": {
+ "name": "sourceFileId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "invoiceId": {
+ "name": "invoiceId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "operation": {
+ "name": "operation",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "model": {
+ "name": "model",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "promptSent": {
+ "name": "promptSent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "rawResponse": {
+ "name": "rawResponse",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "cleanedResponse": {
+ "name": "cleanedResponse",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "success": {
+ "name": "success",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 1
+ },
+ "errorMessage": {
+ "name": "errorMessage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "processingTimeMs": {
+ "name": "processingTimeMs",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "pageRange": {
+ "name": "pageRange",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "llmLogs_id": {
+ "name": "llmLogs_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "sourceFiles": {
+ "name": "sourceFiles",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "fileName": {
+ "name": "fileName",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "fileKey": {
+ "name": "fileKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "fileUrl": {
+ "name": "fileUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "totalInvoicesDetected": {
+ "name": "totalInvoicesDetected",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "processingStatus": {
+ "name": "processingStatus",
+ "type": "enum('processing','completed','error')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'processing'"
+ },
+ "processingProgress": {
+ "name": "processingProgress",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "sourceFiles_id": {
+ "name": "sourceFiles_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "userSettings": {
+ "name": "userSettings",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "llmModel": {
+ "name": "llmModel",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'mistral-large-latest'"
+ },
+ "orderNumberFormat": {
+ "name": "orderNumberFormat",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "invoiceNumberKeywords": {
+ "name": "invoiceNumberKeywords",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "deliveryNoteKeywords": {
+ "name": "deliveryNoteKeywords",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "orderNumberKeywords": {
+ "name": "orderNumberKeywords",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "supplierKeywords": {
+ "name": "supplierKeywords",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "totalAmountKeywords": {
+ "name": "totalAmountKeywords",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sftpHost": {
+ "name": "sftpHost",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sftpPort": {
+ "name": "sftpPort",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 22
+ },
+ "sftpUsername": {
+ "name": "sftpUsername",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sftpPassword": {
+ "name": "sftpPassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sftpRemotePath": {
+ "name": "sftpRemotePath",
+ "type": "varchar(500)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'/'"
+ },
+ "sftpAutoExport": {
+ "name": "sftpAutoExport",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "llmLogsRetentionMonths": {
+ "name": "llmLogsRetentionMonths",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 3
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "userSettings_id": {
+ "name": "userSettings_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {
+ "userSettings_userId_unique": {
+ "name": "userSettings_userId_unique",
+ "columns": [
+ "userId"
+ ]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "users": {
+ "name": "users",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "openId": {
+ "name": "openId",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "azureAdId": {
+ "name": "azureAdId",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "varchar(320)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "passwordHash": {
+ "name": "passwordHash",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "loginMethod": {
+ "name": "loginMethod",
+ "type": "enum('manus','local','azure-ad')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "role": {
+ "name": "role",
+ "type": "enum('user','admin')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'user'"
+ },
+ "isActive": {
+ "name": "isActive",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 1
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ },
+ "lastSignedIn": {
+ "name": "lastSignedIn",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "users_id": {
+ "name": "users_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {
+ "users_openId_unique": {
+ "name": "users_openId_unique",
+ "columns": [
+ "openId"
+ ]
+ },
+ "users_azureAdId_unique": {
+ "name": "users_azureAdId_unique",
+ "columns": [
+ "azureAdId"
+ ]
+ },
+ "users_email_unique": {
+ "name": "users_email_unique",
+ "columns": [
+ "email"
+ ]
+ }
+ },
+ "checkConstraint": {}
+ }
+ },
+ "views": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "tables": {},
+ "indexes": {}
+ }
+}
\ No newline at end of file
diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json
index 938f13a..3a5ce16 100644
--- a/drizzle/meta/_journal.json
+++ b/drizzle/meta/_journal.json
@@ -22,6 +22,13 @@
"when": 1767881720296,
"tag": "0002_striped_red_skull",
"breakpoints": true
+ },
+ {
+ "idx": 3,
+ "version": "5",
+ "when": 1770742897031,
+ "tag": "0003_previous_killraven",
+ "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/drizzle/schema.ts b/drizzle/schema.ts
index 721e5ac..0cbd47f 100644
--- a/drizzle/schema.ts
+++ b/drizzle/schema.ts
@@ -130,6 +130,36 @@ export const userSettings = mysqlTable("userSettings", {
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)
+
+ 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
*/
diff --git a/server/db.ts b/server/db.ts
index 1ab2bfb..dca3085 100644
--- a/server/db.ts
+++ b/server/db.ts
@@ -17,7 +17,10 @@ import {
ImportLog,
llmLogs,
InsertLlmLog,
- LlmLog
+ LlmLog,
+ importSettings,
+ InsertImportSettings,
+ ImportSettings
} from "../drizzle/schema";
import { ENV } from './_core/env';
@@ -434,3 +437,40 @@ export async function getLlmLogsByInvoice(invoiceId: number): Promise
if (!db) return [];
return db.select().from(llmLogs).where(eq(llmLogs.invoiceId, invoiceId)).orderBy(desc(llmLogs.createdAt));
}
+
+// ============= IMPORT SETTINGS OPERATIONS =============
+
+export async function getImportSettingsByUser(userId: number): Promise {
+ const db = await getDb();
+ if (!db) return null;
+ const result = await db.select().from(importSettings).where(eq(importSettings.userId, userId)).limit(1);
+ return result[0] || null;
+}
+
+export async function upsertImportSettings(data: InsertImportSettings): Promise {
+ const db = await getDb();
+ if (!db) throw new Error("Database not available");
+
+ // Check if settings exist for this user
+ const existing = await getImportSettingsByUser(data.userId!);
+
+ if (existing) {
+ // Update existing settings
+ await db.update(importSettings)
+ .set({
+ ...data,
+ updatedAt: new Date(),
+ })
+ .where(eq(importSettings.userId, data.userId!));
+
+ const updated = await getImportSettingsByUser(data.userId!);
+ return updated!;
+ } else {
+ // Insert new settings
+ const result = await db.insert(importSettings).values(data);
+ const insertedId = Number(result[0].insertId);
+
+ const inserted = await db.select().from(importSettings).where(eq(importSettings.id, insertedId)).limit(1);
+ return inserted[0]!;
+ }
+}
diff --git a/server/routers.ts b/server/routers.ts
index 7c69712..ca206b9 100644
--- a/server/routers.ts
+++ b/server/routers.ts
@@ -26,6 +26,8 @@ import {
getImportLogsByUser,
getLlmLogsBySourceFile,
getLlmLogsByInvoice,
+ getImportSettingsByUser,
+ upsertImportSettings,
} from "./db";
import { loginLocal, hashPassword, getAzureAuthUrl, isAzureAdConfigured, generateToken } from "./auth";
import { extractInvoicesWithMistral, generateMetadataJSON } from "./invoiceExtractor";
@@ -572,6 +574,54 @@ export const appRouter = router({
return getLlmLogsByInvoice(input.invoiceId);
}),
}),
+
+ // ============= IMPORT SETTINGS ROUTES =============
+ importSettings: router({
+ get: protectedProcedure.query(async ({ ctx }) => {
+ const settings = await getImportSettingsByUser(ctx.user.id);
+
+ // Return default settings if none exist
+ if (!settings) {
+ return {
+ userId: ctx.user.id,
+ manualImportEnabled: 1,
+ autoImportEnabled: 0,
+ autoImportSourcePath: null,
+ autoImportFrequency: 60,
+ emailImportEnabled: 0,
+ emailImportAddress: null,
+ emailImportPassword: null,
+ emailImportHost: null,
+ emailImportPort: 993,
+ emailImportFrequency: 30,
+ };
+ }
+
+ return settings;
+ }),
+
+ update: protectedProcedure
+ .input(z.object({
+ manualImportEnabled: z.number().min(0).max(1),
+ autoImportEnabled: z.number().min(0).max(1),
+ autoImportSourcePath: z.string().nullable().optional(),
+ autoImportFrequency: z.number().min(1).optional(),
+ emailImportEnabled: z.number().min(0).max(1),
+ emailImportAddress: z.string().email().nullable().optional(),
+ emailImportPassword: z.string().nullable().optional(),
+ emailImportHost: z.string().nullable().optional(),
+ emailImportPort: z.number().min(1).max(65535).optional(),
+ emailImportFrequency: z.number().min(1).optional(),
+ }))
+ .mutation(async ({ input, ctx }) => {
+ const settings = await upsertImportSettings({
+ userId: ctx.user.id,
+ ...input,
+ });
+
+ return settings;
+ }),
+ }),
});
export type AppRouter = typeof appRouter;
diff --git a/todo.md b/todo.md
index 599d9ca..9f97375 100644
--- a/todo.md
+++ b/todo.md
@@ -136,3 +136,12 @@
- [x] Identifier le bug: l'URL de localStoragePut était écrasée par une URL locale
- [x] Corriger la route upload pour utiliser l'URL retournée par localStoragePut
- [ ] Tester le stockage avec un upload réel
+
+## Paramètres de réception
+- [x] Créer table importSettings dans le schéma de base de données
+- [x] Ajouter routes tRPC pour gérer les paramètres de réception (get, update)
+- [x] Créer page Paramètres de réception avec configuration import manuel/automatique/email
+- [x] Implémenter activation/désactivation import manuel
+- [x] Implémenter activation/désactivation import automatique avec dossier source et fréquence
+- [x] Implémenter activation/désactivation import par email avec compte mail et mot de passe
+- [x] Tester la sauvegarde et récupération des paramètres