Checkpoint: Application complète de dématérialisation de facturation avec extraction IA (Mistral), authentification locale + Azure AD, stockage local, et export SFTP.

Fonctionnalités implémentées :
 Authentification locale (email/password) + Azure AD + Manus OAuth
 Upload drag-and-drop de fichiers PDF avec suivi en temps réel
 Extraction automatique avec Mistral AI (OCR + LLM)
 Détection de doublons (fournisseur, numéro, date)
 Score de qualité d'extraction (0-100)
 Tableau de bord avec statistiques
 Liste des factures avec recherche et filtres
 Paramètres utilisateur (LLM, keywords, SFTP)
 Historique des imports avec logs détaillés
 Gestion des utilisateurs (admin)
 Export SFTP manuel/automatique
 Stockage local avec organisation YYYY-MM
 Tests unitaires d'authentification

Architecture :
- Frontend : React 19 + Vite + TailwindCSS + Radix UI
- Backend : Express + tRPC + Drizzle ORM
- Base de données : MySQL (6 tables)
- IA : Mistral AI pour extraction
- Stockage : Local filesystem
- Export : SFTP

Pages :
- Login (choix local/Azure/Manus)
- Dashboard (statistiques)
- Upload (drag-and-drop)
- Invoices (liste avec recherche)
- Settings (LLM, keywords, SFTP)
- History (logs d'import)
- Users (gestion admin)
This commit is contained in:
Manus
2026-01-08 06:02:07 -05:00
parent 205b6061ef
commit 5a01860aba
31 changed files with 4644 additions and 99 deletions

115
server/sftpExport.ts Normal file
View File

@@ -0,0 +1,115 @@
import SftpClient from "ssh2-sftp-client";
import { localStorageGet } from "./localStorage";
import { getUserSettings } from "./db";
export interface SftpConfig {
host: string;
port: number;
username: string;
password: string;
remotePath: string;
}
/**
* Test SFTP connection
*/
export async function testSftpConnection(config: SftpConfig): Promise<boolean> {
const sftp = new SftpClient();
try {
await sftp.connect({
host: config.host,
port: config.port,
username: config.username,
password: config.password,
});
console.log("[SFTP] Connection successful");
return true;
} catch (error) {
console.error("[SFTP] Connection failed:", error);
return false;
} finally {
await sftp.end();
}
}
/**
* Export invoice files (PDF + JSON) to SFTP server
*/
export async function exportInvoiceToSftp(
config: SftpConfig,
pdfFileKey: string,
jsonFileKey: string | null,
invoiceDate: Date
): Promise<void> {
const sftp = new SftpClient();
try {
// Connect to SFTP
await sftp.connect({
host: config.host,
port: config.port,
username: config.username,
password: config.password,
});
console.log("[SFTP] Connected successfully");
// Create directory structure: remotePath/YYYY/MM/DD
const year = invoiceDate.getFullYear();
const month = String(invoiceDate.getMonth() + 1).padStart(2, "0");
const day = String(invoiceDate.getDate()).padStart(2, "0");
const targetDir = `${config.remotePath}/${year}/${month}/${day}`.replace(/\/+/g, "/");
// Ensure directory exists
await sftp.mkdir(targetDir, true);
console.log(`[SFTP] Created directory: ${targetDir}`);
// Upload PDF file
const pdfBuffer = await localStorageGet(pdfFileKey);
const pdfFileName = pdfFileKey.split("/").pop() || "invoice.pdf";
const pdfRemotePath = `${targetDir}/${pdfFileName}`;
await sftp.put(pdfBuffer, pdfRemotePath);
console.log(`[SFTP] Uploaded PDF: ${pdfRemotePath}`);
// Upload JSON metadata file if exists
if (jsonFileKey) {
const jsonBuffer = await localStorageGet(jsonFileKey);
const jsonFileName = jsonFileKey.split("/").pop() || "metadata.json";
const jsonRemotePath = `${targetDir}/${jsonFileName}`;
await sftp.put(jsonBuffer, jsonRemotePath);
console.log(`[SFTP] Uploaded JSON: ${jsonRemotePath}`);
}
console.log("[SFTP] Export completed successfully");
} catch (error) {
console.error("[SFTP] Export failed:", error);
throw error;
} finally {
await sftp.end();
}
}
/**
* Get SFTP configuration for a user
*/
export async function getUserSftpConfig(userId: number): Promise<SftpConfig | null> {
const settings = await getUserSettings(userId);
if (!settings || !settings.sftpHost || !settings.sftpUsername || !settings.sftpPassword) {
return null;
}
return {
host: settings.sftpHost,
port: settings.sftpPort || 22,
username: settings.sftpUsername,
password: settings.sftpPassword,
remotePath: settings.sftpRemotePath || "/",
};
}