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:
42
server/db.ts
42
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<LlmLog[]>
|
||||
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<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,
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user