Checkpoint: Checkpoint saved: Ajout d'une page "Paramètres de réception" complète pour configurer les différentes méthodes d'import de factures.
Nouvelles fonctionnalités : ✅ Table importSettings dans la base de données (7 tables au total) ✅ Routes tRPC pour gérer les paramètres de réception (get, update) ✅ Page "Paramètres de réception" accessible via le menu de navigation Configuration disponible : 1. **Import manuel** : Activation/désactivation de l'upload manuel de fichiers PDF via l'interface 2. **Import automatique depuis dossier** : - Activation/désactivation - Configuration du chemin du dossier source à surveiller - Fréquence de lecture du dossier (en minutes) 3. **Import par email** : - Activation/désactivation - Configuration du compte email (adresse + mot de passe) - Serveur IMAP (host + port) - Fréquence de lecture des emails (en minutes) Interface utilisateur : - 3 cartes distinctes avec icônes (Upload, FolderOpen, Mail) - Switches pour activer/désactiver chaque méthode - Champs de configuration qui apparaissent uniquement si la méthode est activée - Bouton "Enregistrer les paramètres" avec feedback visuel - Notifications toast pour confirmer la sauvegarde ou signaler les erreurs Architecture : - Schéma de base de données : table importSettings avec 14 colonnes - Backend : Fonctions db.ts (getImportSettingsByUser, upsertImportSettings) - Routes tRPC : importSettings.get et importSettings.update - Frontend : Page ImportSettings.tsx avec gestion d'état React - Navigation : Nouveau lien "Paramètres de réception" dans le menu latéral La page est maintenant accessible via /import-settings et permet aux utilisateurs de configurer toutes les méthodes d'importation de factures de manière centralisée.
This commit is contained in:
@@ -11,6 +11,7 @@ import Upload from "./pages/Upload";
|
|||||||
import Invoices from "./pages/Invoices";
|
import Invoices from "./pages/Invoices";
|
||||||
import InvoiceDetail from "./pages/InvoiceDetail";
|
import InvoiceDetail from "./pages/InvoiceDetail";
|
||||||
import Settings from "./pages/Settings";
|
import Settings from "./pages/Settings";
|
||||||
|
import ImportSettings from "./pages/ImportSettings";
|
||||||
import History from "./pages/History";
|
import History from "./pages/History";
|
||||||
import Users from "./pages/Users";
|
import Users from "./pages/Users";
|
||||||
|
|
||||||
@@ -24,6 +25,7 @@ function Router() {
|
|||||||
<Route path="/invoices" component={Invoices} />
|
<Route path="/invoices" component={Invoices} />
|
||||||
<Route path="/invoices/:id" component={InvoiceDetail} />
|
<Route path="/invoices/:id" component={InvoiceDetail} />
|
||||||
<Route path="/settings" component={Settings} />
|
<Route path="/settings" component={Settings} />
|
||||||
|
<Route path="/import-settings" component={ImportSettings} />
|
||||||
<Route path="/history" component={History} />
|
<Route path="/history" component={History} />
|
||||||
<Route path="/users" component={Users} />
|
<Route path="/users" component={Users} />
|
||||||
<Route path="/404" component={NotFound} />
|
<Route path="/404" component={NotFound} />
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import {
|
|||||||
} from "@/components/ui/sidebar";
|
} from "@/components/ui/sidebar";
|
||||||
import { getLoginUrl } from "@/const";
|
import { getLoginUrl } from "@/const";
|
||||||
import { useIsMobile } from "@/hooks/useMobile";
|
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 { CSSProperties, useEffect, useRef, useState } from "react";
|
||||||
import { useLocation } from "wouter";
|
import { useLocation } from "wouter";
|
||||||
import { DashboardLayoutSkeleton } from './DashboardLayoutSkeleton';
|
import { DashboardLayoutSkeleton } from './DashboardLayoutSkeleton';
|
||||||
@@ -33,6 +33,7 @@ const menuItems = [
|
|||||||
{ icon: FileText, label: "Factures", path: "/invoices" },
|
{ icon: FileText, label: "Factures", path: "/invoices" },
|
||||||
{ icon: History, label: "Historique", path: "/history" },
|
{ icon: History, label: "Historique", path: "/history" },
|
||||||
{ icon: Settings, label: "Param\u00e8tres", path: "/settings" },
|
{ 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 },
|
{ icon: Users, label: "Utilisateurs", path: "/users", adminOnly: true },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
309
client/src/pages/ImportSettings.tsx
Normal file
309
client/src/pages/ImportSettings.tsx
Normal file
@@ -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 (
|
||||||
|
<div className="flex items-center justify-center h-[calc(100vh-200px)]">
|
||||||
|
<Loader2 className="h-8 w-8 animate-spin text-primary" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container max-w-4xl py-8">
|
||||||
|
<div className="mb-8">
|
||||||
|
<h1 className="text-3xl font-bold mb-2">Paramètres de réception</h1>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
Configurez les différentes méthodes d'importation de factures
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
{/* Manual Import */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="p-2 bg-primary/10 rounded-lg">
|
||||||
|
<Upload className="h-5 w-5 text-primary" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<CardTitle>Import manuel</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Autoriser l'upload manuel de fichiers PDF via l'interface
|
||||||
|
</CardDescription>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<Label htmlFor="manual-import" className="text-base">
|
||||||
|
Activer l'import manuel
|
||||||
|
</Label>
|
||||||
|
<Switch
|
||||||
|
id="manual-import"
|
||||||
|
checked={manualImportEnabled}
|
||||||
|
onCheckedChange={setManualImportEnabled}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Automatic Import from Folder */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="p-2 bg-blue-500/10 rounded-lg">
|
||||||
|
<FolderOpen className="h-5 w-5 text-blue-500" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<CardTitle>Import automatique depuis dossier</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Surveiller un dossier et importer automatiquement les nouveaux fichiers PDF
|
||||||
|
</CardDescription>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<Label htmlFor="auto-import" className="text-base">
|
||||||
|
Activer l'import automatique
|
||||||
|
</Label>
|
||||||
|
<Switch
|
||||||
|
id="auto-import"
|
||||||
|
checked={autoImportEnabled}
|
||||||
|
onCheckedChange={setAutoImportEnabled}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{autoImportEnabled && (
|
||||||
|
<>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="source-path">Chemin du dossier source</Label>
|
||||||
|
<Input
|
||||||
|
id="source-path"
|
||||||
|
type="text"
|
||||||
|
placeholder="/chemin/vers/dossier/factures"
|
||||||
|
value={autoImportSourcePath}
|
||||||
|
onChange={(e) => setAutoImportSourcePath(e.target.value)}
|
||||||
|
/>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Chemin absolu du dossier à surveiller pour les nouveaux fichiers PDF
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="auto-frequency">Fréquence de lecture (minutes)</Label>
|
||||||
|
<Input
|
||||||
|
id="auto-frequency"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
value={autoImportFrequency}
|
||||||
|
onChange={(e) => setAutoImportFrequency(parseInt(e.target.value) || 60)}
|
||||||
|
/>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Intervalle de temps entre chaque vérification du dossier (minimum: 1 minute)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Email Import */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="p-2 bg-green-500/10 rounded-lg">
|
||||||
|
<Mail className="h-5 w-5 text-green-500" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<CardTitle>Import par email</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Récupérer automatiquement les factures reçues par email
|
||||||
|
</CardDescription>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<Label htmlFor="email-import" className="text-base">
|
||||||
|
Activer l'import par email
|
||||||
|
</Label>
|
||||||
|
<Switch
|
||||||
|
id="email-import"
|
||||||
|
checked={emailImportEnabled}
|
||||||
|
onCheckedChange={setEmailImportEnabled}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{emailImportEnabled && (
|
||||||
|
<>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="email-address">Adresse email</Label>
|
||||||
|
<Input
|
||||||
|
id="email-address"
|
||||||
|
type="email"
|
||||||
|
placeholder="factures@votreentreprise.com"
|
||||||
|
value={emailImportAddress}
|
||||||
|
onChange={(e) => setEmailImportAddress(e.target.value)}
|
||||||
|
/>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Adresse email à surveiller pour les factures
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="email-password">Mot de passe</Label>
|
||||||
|
<Input
|
||||||
|
id="email-password"
|
||||||
|
type="password"
|
||||||
|
placeholder="••••••••"
|
||||||
|
value={emailImportPassword}
|
||||||
|
onChange={(e) => setEmailImportPassword(e.target.value)}
|
||||||
|
/>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Mot de passe du compte email (stocké de manière sécurisée)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="email-host">Serveur IMAP</Label>
|
||||||
|
<Input
|
||||||
|
id="email-host"
|
||||||
|
type="text"
|
||||||
|
placeholder="imap.gmail.com"
|
||||||
|
value={emailImportHost}
|
||||||
|
onChange={(e) => setEmailImportHost(e.target.value)}
|
||||||
|
/>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Adresse du serveur IMAP
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="email-port">Port IMAP</Label>
|
||||||
|
<Input
|
||||||
|
id="email-port"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
max="65535"
|
||||||
|
value={emailImportPort}
|
||||||
|
onChange={(e) => setEmailImportPort(parseInt(e.target.value) || 993)}
|
||||||
|
/>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Port SSL (993 par défaut)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="email-frequency">Fréquence de lecture (minutes)</Label>
|
||||||
|
<Input
|
||||||
|
id="email-frequency"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
value={emailImportFrequency}
|
||||||
|
onChange={(e) => setEmailImportFrequency(parseInt(e.target.value) || 30)}
|
||||||
|
/>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Intervalle de temps entre chaque vérification des emails (minimum: 1 minute)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Save Button */}
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button
|
||||||
|
onClick={handleSave}
|
||||||
|
disabled={updateMutation.isPending}
|
||||||
|
size="lg"
|
||||||
|
>
|
||||||
|
{updateMutation.isPending ? (
|
||||||
|
<>
|
||||||
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||||
|
Enregistrement...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Save className="mr-2 h-4 w-4" />
|
||||||
|
Enregistrer les paramètres
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
18
drizzle/0003_previous_killraven.sql
Normal file
18
drizzle/0003_previous_killraven.sql
Normal file
@@ -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`)
|
||||||
|
);
|
||||||
950
drizzle/meta/0003_snapshot.json
Normal file
950
drizzle/meta/0003_snapshot.json
Normal file
@@ -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": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,13 @@
|
|||||||
"when": 1767881720296,
|
"when": 1767881720296,
|
||||||
"tag": "0002_striped_red_skull",
|
"tag": "0002_striped_red_skull",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 3,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1770742897031,
|
||||||
|
"tag": "0003_previous_killraven",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -130,6 +130,36 @@ export const userSettings = mysqlTable("userSettings", {
|
|||||||
export type UserSettings = typeof userSettings.$inferSelect;
|
export type UserSettings = typeof userSettings.$inferSelect;
|
||||||
export type InsertUserSettings = typeof userSettings.$inferInsert;
|
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
|
* Import logs table for tracking all import operations
|
||||||
*/
|
*/
|
||||||
|
|||||||
42
server/db.ts
42
server/db.ts
@@ -17,7 +17,10 @@ import {
|
|||||||
ImportLog,
|
ImportLog,
|
||||||
llmLogs,
|
llmLogs,
|
||||||
InsertLlmLog,
|
InsertLlmLog,
|
||||||
LlmLog
|
LlmLog,
|
||||||
|
importSettings,
|
||||||
|
InsertImportSettings,
|
||||||
|
ImportSettings
|
||||||
} from "../drizzle/schema";
|
} from "../drizzle/schema";
|
||||||
import { ENV } from './_core/env';
|
import { ENV } from './_core/env';
|
||||||
|
|
||||||
@@ -434,3 +437,40 @@ export async function getLlmLogsByInvoice(invoiceId: number): Promise<LlmLog[]>
|
|||||||
if (!db) return [];
|
if (!db) return [];
|
||||||
return db.select().from(llmLogs).where(eq(llmLogs.invoiceId, invoiceId)).orderBy(desc(llmLogs.createdAt));
|
return db.select().from(llmLogs).where(eq(llmLogs.invoiceId, invoiceId)).orderBy(desc(llmLogs.createdAt));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============= IMPORT SETTINGS OPERATIONS =============
|
||||||
|
|
||||||
|
export async function getImportSettingsByUser(userId: number): Promise<ImportSettings | null> {
|
||||||
|
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<ImportSettings> {
|
||||||
|
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]!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ import {
|
|||||||
getImportLogsByUser,
|
getImportLogsByUser,
|
||||||
getLlmLogsBySourceFile,
|
getLlmLogsBySourceFile,
|
||||||
getLlmLogsByInvoice,
|
getLlmLogsByInvoice,
|
||||||
|
getImportSettingsByUser,
|
||||||
|
upsertImportSettings,
|
||||||
} from "./db";
|
} from "./db";
|
||||||
import { loginLocal, hashPassword, getAzureAuthUrl, isAzureAdConfigured, generateToken } from "./auth";
|
import { loginLocal, hashPassword, getAzureAuthUrl, isAzureAdConfigured, generateToken } from "./auth";
|
||||||
import { extractInvoicesWithMistral, generateMetadataJSON } from "./invoiceExtractor";
|
import { extractInvoicesWithMistral, generateMetadataJSON } from "./invoiceExtractor";
|
||||||
@@ -572,6 +574,54 @@ export const appRouter = router({
|
|||||||
return getLlmLogsByInvoice(input.invoiceId);
|
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;
|
export type AppRouter = typeof appRouter;
|
||||||
|
|||||||
9
todo.md
9
todo.md
@@ -136,3 +136,12 @@
|
|||||||
- [x] Identifier le bug: l'URL de localStoragePut était écrasée par une URL locale
|
- [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
|
- [x] Corriger la route upload pour utiliser l'URL retournée par localStoragePut
|
||||||
- [ ] Tester le stockage avec un upload réel
|
- [ ] 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
|
||||||
|
|||||||
Reference in New Issue
Block a user