Checkpoint: Checkpoint saved: Implémentation complète de l'import automatique depuis dossier et du système de notifications.

Nouvelles fonctionnalités :

**1. Import automatique depuis dossier (folderImportService.ts)**
 Surveillance périodique d'un dossier local configuré
 Détection automatique des nouveaux fichiers PDF
 Traitement identique à l'upload manuel (extraction Mistral AI, détection doublons, stockage)
 Déplacement automatique des fichiers traités vers un sous-dossier "processed"
 Scheduler configurable (fréquence en minutes)
 Routes tRPC pour démarrer/arrêter/vérifier le statut du service
 Boutons de contrôle dans la page Paramètres de réception

**2. Système de notifications (notificationService.ts)**
 Notifications automatiques après chaque import (email ou dossier)
 Résumé détaillé : nombre de factures importées, doublons ignorés, erreurs
 Format adapté selon la source (email avec nom de fichier, dossier avec nombre de fichiers)
 Intégration avec le système de notifications Manus (notifyOwner)
 Notifications envoyées au propriétaire du projet

**Fonctionnement de l'import dossier :**
1. L'utilisateur configure le chemin du dossier source dans "Paramètres de réception"
2. Il active l'import automatique et configure la fréquence de vérification
3. Il clique sur "Démarrer le service" pour lancer la surveillance
4. Le service scanne le dossier selon la fréquence configurée
5. Il détecte les nouveaux fichiers PDF (non encore traités)
6. Il traite chaque PDF avec la même logique que l'upload manuel
7. Les fichiers traités sont déplacés vers un sous-dossier "processed"
8. Une notification est envoyée avec le résumé des résultats

**Fonctionnement des notifications :**
- Après chaque import automatique (email ou dossier), une notification est envoyée
- La notification contient :
  * Source de l'import (📧 email ou 📁 dossier)
  * Nom du fichier (email) ou nombre de fichiers (dossier)
  *  Nombre de factures importées
  * 🔄 Nombre de doublons ignorés
  *  Nombre d'erreurs
  * Total de factures détectées
- Les notifications apparaissent dans l'interface Manus du propriétaire

**Interface utilisateur :**
- Chaque carte (Import dossier, Import email) dispose maintenant de :
  * Indicateur visuel "Service actif" avec point vert animé
  * Bouton "Démarrer le service" (désactivé si l'import n'est pas activé)
  * Bouton "Arrêter le service" (rouge) pour stopper la surveillance
  * Messages toast pour confirmer le démarrage/arrêt

**Dépendances ajoutées :**
- chokidar : Surveillance de fichiers et dossiers pour Node.js

**Architecture :**
- folderImportService.ts : Service d'import automatique depuis dossier
- notificationService.ts : Service de notifications centralisé
- Routes tRPC : folderImportService.{start, stop, status}
- Intégration des notifications dans emailImportService et folderImportService

Les deux services d'import automatique (email et dossier) sont maintenant opérationnels avec notifications intégrées.
This commit is contained in:
Manus
2026-02-11 05:16:44 -05:00
parent ff916d6a79
commit cd61c75c4c
8 changed files with 664 additions and 17 deletions

View File

@@ -11,10 +11,13 @@ import DashboardLayout from "@/components/DashboardLayout";
export default function ImportSettings() {
const { data: settings, isLoading } = trpc.importSettings.get.useQuery();
const { data: serviceStatus } = trpc.emailImportService.status.useQuery();
const { data: emailServiceStatus } = trpc.emailImportService.status.useQuery();
const { data: folderServiceStatus } = trpc.folderImportService.status.useQuery();
const updateMutation = trpc.importSettings.update.useMutation();
const startServiceMutation = trpc.emailImportService.start.useMutation();
const stopServiceMutation = trpc.emailImportService.stop.useMutation();
const startEmailServiceMutation = trpc.emailImportService.start.useMutation();
const stopEmailServiceMutation = trpc.emailImportService.stop.useMutation();
const startFolderServiceMutation = trpc.folderImportService.start.useMutation();
const stopFolderServiceMutation = trpc.folderImportService.stop.useMutation();
// Manual import
const [manualImportEnabled, setManualImportEnabled] = useState(true);
@@ -174,9 +177,73 @@ export default function ImportSettings() {
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>
Intervalle de temps entre chaque vérification du dossier (minimum: 1 minute)
</p>
</div>
{/* Service Control Buttons */}
<div className="flex gap-2 pt-4 border-t">
{folderServiceStatus?.isRunning ? (
<Button
variant="destructive"
onClick={async () => {
try {
await stopFolderServiceMutation.mutateAsync();
toast.success("Service arrêté", {
description: "L'import automatique depuis dossier a é arrêté.",
});
} catch (error) {
toast.error("Erreur", {
description: "Impossible d'arrêter le service.",
});
}
}}
disabled={stopFolderServiceMutation.isPending}
>
{stopFolderServiceMutation.isPending ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
) : (
<Square className="mr-2 h-4 w-4" />
)}
Arrêter le service
</Button>
) : (
<Button
onClick={async () => {
try {
const result = await startFolderServiceMutation.mutateAsync();
if (result.success) {
toast.success("Service démarré", {
description: "L'import automatique depuis dossier est maintenant actif.",
});
} else {
toast.error("Erreur", {
description: "Impossible de démarrer le service. Vérifiez la configuration.",
});
}
} catch (error) {
toast.error("Erreur", {
description: "Impossible de démarrer le service.",
});
}
}}
disabled={!autoImportEnabled || startFolderServiceMutation.isPending}
>
{startFolderServiceMutation.isPending ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
) : (
<Play className="mr-2 h-4 w-4" />
)}
Démarrer le service
</Button>
)}
{folderServiceStatus?.isRunning && (
<span className="flex items-center text-sm text-green-600">
<span className="w-2 h-2 bg-green-600 rounded-full mr-2 animate-pulse" />
Service actif
</span>
)}
</div>
</>
)}
</CardContent>
@@ -286,12 +353,12 @@ export default function ImportSettings() {
{/* Service Control Buttons */}
<div className="flex gap-2 pt-4 border-t">
{serviceStatus?.isRunning ? (
{emailServiceStatus?.isRunning ? (
<Button
variant="destructive"
onClick={async () => {
try {
await stopServiceMutation.mutateAsync();
await stopEmailServiceMutation.mutateAsync();
toast.success("Service arrêté", {
description: "L'import automatique par email a été arrêté.",
});
@@ -301,9 +368,9 @@ export default function ImportSettings() {
});
}
}}
disabled={stopServiceMutation.isPending}
disabled={stopEmailServiceMutation.isPending}
>
{stopServiceMutation.isPending ? (
{stopEmailServiceMutation.isPending ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
) : (
<Square className="mr-2 h-4 w-4" />
@@ -314,7 +381,7 @@ export default function ImportSettings() {
<Button
onClick={async () => {
try {
const result = await startServiceMutation.mutateAsync();
const result = await startEmailServiceMutation.mutateAsync();
if (result.success) {
toast.success("Service démarré", {
description: "L'import automatique par email est maintenant actif.",
@@ -330,9 +397,9 @@ export default function ImportSettings() {
});
}
}}
disabled={!emailImportEnabled || startServiceMutation.isPending}
disabled={!emailImportEnabled || startEmailServiceMutation.isPending}
>
{startServiceMutation.isPending ? (
{startEmailServiceMutation.isPending ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
) : (
<Play className="mr-2 h-4 w-4" />
@@ -340,7 +407,7 @@ export default function ImportSettings() {
Démarrer le service
</Button>
)}
{serviceStatus?.isRunning && (
{emailServiceStatus?.isRunning && (
<span className="flex items-center text-sm text-green-600">
<span className="w-2 h-2 bg-green-600 rounded-full mr-2 animate-pulse" />
Service actif