diff --git a/attached_assets/{F856D9DD-5EA8-4B46-B668-7E568CF278C4}_1755092650737.png b/attached_assets/{F856D9DD-5EA8-4B46-B668-7E568CF278C4}_1755092650737.png new file mode 100644 index 0000000..9673b31 Binary files /dev/null and b/attached_assets/{F856D9DD-5EA8-4B46-B668-7E568CF278C4}_1755092650737.png differ diff --git a/client/src/pages/BLReconciliation.tsx b/client/src/pages/BLReconciliation.tsx index 55f5d08..135121b 100644 --- a/client/src/pages/BLReconciliation.tsx +++ b/client/src/pages/BLReconciliation.tsx @@ -69,11 +69,12 @@ export default function BLReconciliation() { // Fonction de vérification de facture const verifyInvoiceMutation = useMutation({ - mutationFn: async ({ deliveryId, invoiceReference, blNumber }: { deliveryId: number; invoiceReference?: string; blNumber?: string }) => { + mutationFn: async ({ deliveryId, invoiceReference, blNumber, forceRefresh }: { deliveryId: number; invoiceReference?: string; blNumber?: string; forceRefresh?: boolean }) => { try { const result = await apiRequest(`/api/deliveries/${deliveryId}/verify-invoice`, 'POST', { invoiceReference, - blNumber + blNumber, + forceRefresh: forceRefresh || false }); return result; } catch (error: any) { @@ -129,17 +130,16 @@ export default function BLReconciliation() { }); // Fonction pour déclencher la vérification - const handleVerifyInvoice = (delivery: any) => { - console.log('🔍 Debug vérification:', { - delivery: delivery, - group: delivery.group, - webhookUrl: delivery.group?.webhookUrl, - nocodbTableName: delivery.group?.nocodbTableName, - invoiceColumnName: delivery.group?.invoiceColumnName, - nocodbConfigId: delivery.group?.nocodbConfigId, - supplier: delivery.supplier - }); - + const handleVerifyInvoice = (delivery: any, forceRefresh: boolean = false) => { + if (!delivery.invoiceReference?.trim()) { + toast({ + title: "Référence manquante", + description: "Veuillez saisir une référence de facture avant la vérification", + variant: "destructive", + }); + return; + } + if (!delivery.group?.nocodbTableName && !delivery.group?.nocodbConfigId && !delivery.group?.webhookUrl) { toast({ title: "Vérification non disponible", @@ -154,7 +154,36 @@ export default function BLReconciliation() { verifyInvoiceMutation.mutate({ deliveryId: delivery.id, invoiceReference: delivery.invoiceReference, - blNumber: delivery.blNumber + blNumber: delivery.blNumber, + forceRefresh + }); + }; + + // Fonction pour vérifier toutes les factures avec un bouton + const handleVerifyAllInvoices = () => { + const deliveriesToVerify = manualReconciliationDeliveries.filter(delivery => + delivery.invoiceReference?.trim() && + (delivery.group?.nocodbTableName || delivery.group?.nocodbConfigId || delivery.group?.webhookUrl) + ); + + if (deliveriesToVerify.length === 0) { + toast({ + title: "Aucune facture à vérifier", + description: "Aucune livraison avec référence de facture trouvée", + }); + return; + } + + deliveriesToVerify.forEach((delivery, index) => { + // Délai échelonné pour éviter la surcharge + setTimeout(() => { + handleVerifyInvoice(delivery, true); // Force refresh pour toutes + }, index * 200); // 200ms entre chaque vérification + }); + + toast({ + title: "Vérification lancée", + description: `Vérification de ${deliveriesToVerify.length} facture(s) en cours...`, }); }; @@ -188,24 +217,7 @@ export default function BLReconciliation() { enabled: !!user }); - // VÉRIFICATION AUTOMATIQUE AU CHARGEMENT - useEffect(() => { - if (!deliveriesWithBL.length || !suppliers.length) return; - - console.log('🔄 Déclenchement vérifications automatiques...'); - - deliveriesWithBL.forEach((delivery: any) => { - // Vérifier seulement si on a une référence de facture et pas déjà de résultat - if (delivery.invoiceReference && !verificationResults[delivery.id] && !verifyingDeliveries.has(delivery.id)) { - console.log(`🔍 Vérification auto pour livraison ${delivery.id}:`, delivery.invoiceReference); - - // Délai pour éviter de surcharger le serveur - setTimeout(() => { - handleVerifyInvoice(delivery); - }, Math.random() * 2000); // Délai aléatoire entre 0 et 2 secondes - } - }); - }, [deliveriesWithBL, suppliers, verificationResults, verifyingDeliveries]); + // Plus de vérification automatique - seulement manuelle via bouton // Séparer les livraisons par mode de rapprochement const manualReconciliationDeliveries = deliveriesWithBL.filter((delivery: any) => { @@ -509,6 +521,15 @@ export default function BLReconciliation() { {automaticReconciliationDeliveries.length} automatiques + diff --git a/server/invoiceVerification.ts b/server/invoiceVerification.ts index 20b8944..48d402a 100644 --- a/server/invoiceVerification.ts +++ b/server/invoiceVerification.ts @@ -6,19 +6,82 @@ import { storage } from "./storage.js"; */ export class InvoiceVerificationService { + /** + * Générer une clé de cache unique + */ + private generateCacheKey(invoiceReference: string, groupId: number): string { + return `${groupId}_${invoiceReference.trim().toLowerCase()}`; + } + + /** + * Vérifier dans le cache d'abord + */ + async checkCache(invoiceReference: string, groupId: number): Promise { + try { + const cacheKey = this.generateCacheKey(invoiceReference, groupId); + const cached = await storage.getInvoiceVerificationCache(cacheKey); + + if (cached && new Date() < new Date(cached.expiresAt)) { + console.log('💾 Cache hit pour:', { invoiceReference, groupId }); + return { + exists: cached.exists, + matchType: cached.matchType, + errorMessage: cached.errorMessage, + invoiceReference: cached.invoiceReference, + supplierName: cached.supplierName, + fromCache: true + }; + } + + return null; + } catch (error) { + console.error('❌ Erreur lecture cache:', error); + return null; + } + } + + /** + * Sauvegarder dans le cache + */ + async saveToCache(invoiceReference: string, groupId: number, result: any, supplierName?: string): Promise { + try { + const cacheKey = this.generateCacheKey(invoiceReference, groupId); + const expiresAt = new Date(); + expiresAt.setHours(expiresAt.getHours() + 24); // Cache pendant 24h + + await storage.saveInvoiceVerificationCache({ + cacheKey, + groupId, + invoiceReference, + supplierName: supplierName || null, + exists: result.exists, + matchType: result.matchType, + errorMessage: result.errorMessage || null, + cacheHit: false, + apiCallTime: null, + expiresAt + }); + + console.log('💾 Résultat sauvé en cache:', { invoiceReference, groupId, exists: result.exists }); + } catch (error) { + console.error('❌ Erreur sauvegarde cache:', error); + } + } + /** * Vérifie une référence de facture pour un groupe donné */ - async verifyInvoice(invoiceReference: string, groupId: number): Promise<{ + async verifyInvoice(invoiceReference: string, groupId: number, forceRefresh: boolean = false): Promise<{ exists: boolean; matchType: 'invoice_reference' | 'bl_number' | 'none'; errorMessage?: string; invoiceReference?: string; invoiceAmount?: number; supplierName?: string; + fromCache?: boolean; }> { try { - console.log('🔍 Début vérification facture:', { invoiceReference, groupId }); + console.log('🔍 Début vérification facture:', { invoiceReference, groupId, forceRefresh }); if (!invoiceReference || !invoiceReference.trim()) { return { @@ -28,6 +91,14 @@ export class InvoiceVerificationService { }; } + // Vérifier le cache d'abord (sauf si refresh forcé) + if (!forceRefresh) { + const cachedResult = await this.checkCache(invoiceReference, groupId); + if (cachedResult) { + return cachedResult; + } + } + // Récupérer la configuration du groupe const group = await storage.getGroup(groupId); if (!group) { @@ -126,13 +197,17 @@ export class InvoiceVerificationService { ); if (matchResult.found) { - return { + const result = { exists: true, - matchType: 'invoice_reference', + matchType: 'invoice_reference' as const, invoiceReference: matchResult.data.invoice_reference || invoiceReference, invoiceAmount: parseFloat(matchResult.data[group.nocodbAmountColumnName || 'amount'] || '0'), supplierName: matchResult.data[group.nocodbSupplierColumnName || 'supplier'] || 'Inconnu' }; + + // Sauvegarder en cache si succès + await this.saveToCache(invoiceReference, groupId, result, result.supplierName); + return result; } // Si pas trouvé par référence de facture, chercher par numéro de BL @@ -145,13 +220,17 @@ export class InvoiceVerificationService { ); if (matchResult.found) { - return { + const result = { exists: true, - matchType: 'bl_number', + matchType: 'bl_number' as const, invoiceReference: matchResult.data.invoice_reference || `BL_${invoiceReference}`, invoiceAmount: parseFloat(matchResult.data[group.nocodbAmountColumnName || 'amount'] || '0'), supplierName: matchResult.data[group.nocodbSupplierColumnName || 'supplier'] || 'Inconnu' }; + + // Sauvegarder en cache si succès + await this.saveToCache(invoiceReference, groupId, result, result.supplierName); + return result; } } diff --git a/server/routes.ts b/server/routes.ts index e57148a..e271212 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -860,7 +860,7 @@ export async function registerRoutes(app: Express): Promise { } } - const { invoiceReference } = req.body; + const { invoiceReference, forceRefresh } = req.body; if (!delivery.supplier || !delivery.group) { console.log('❌ Livraison manque informations:', { @@ -886,7 +886,8 @@ export async function registerRoutes(app: Express): Promise { // Appeler le service de vérification const result = await invoiceVerificationService.verifyInvoice( invoiceReference, - delivery.groupId + delivery.groupId, + forceRefresh || false ); console.log('✅ Résultat vérification:', result);