From 7fb037bf53ef244897d36f411f8543a5d5d7ba17 Mon Sep 17 00:00:00 2001 From: michaelschal <35957947-michaelschal@users.noreply.replit.com> Date: Wed, 13 Aug 2025 13:46:11 +0000 Subject: [PATCH] Improve invoice verification by adding caching and manual refresh functionality Implement an invoice verification cache to avoid redundant checks, introduce a manual refresh button to trigger verification on demand, and modify the API to accept a forceRefresh parameter. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 7c468936-2a88-4686-ac0a-84921a45222d Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1957c339-2757-4d1f-8e92-e9f71a1ce58e/7c468936-2a88-4686-ac0a-84921a45222d/Sh7jLhH --- ...-4B46-B668-7E568CF278C4}_1755092650737.png | Bin 0 -> 16421 bytes client/src/pages/BLReconciliation.tsx | 85 ++++++++++------ server/invoiceVerification.ts | 91 ++++++++++++++++-- server/routes.ts | 5 +- 4 files changed, 141 insertions(+), 40 deletions(-) create mode 100644 attached_assets/{F856D9DD-5EA8-4B46-B668-7E568CF278C4}_1755092650737.png 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 0000000000000000000000000000000000000000..9673b3105c9c58ada0fa489a6d343bf87efb1e09 GIT binary patch literal 16421 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 literal 0 HcmV?d00001 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);