From 97b63b61e853cc645afc212689b5f7b504668086 Mon Sep 17 00:00:00 2001 From: michaelschal <35957947-michaelschal@users.noreply.replit.com> Date: Sun, 31 Aug 2025 07:39:11 +0000 Subject: [PATCH] Improve invoice verification caching with permanent storage for reconciled items Introduce adaptive caching durations for invoice verification: permanent caching for reconciled invoices, temporary caching for found invoices, and longer temporary caching for not-found invoices. Replit-Commit-Author: Agent Replit-Commit-Session-Id: daf3525c-b971-454a-b0c0-7a9845e786cc Replit-Commit-Checkpoint-Type: full_checkpoint --- server/invoiceVerification.ts | 78 +++++++++++++++++++++++++++-------- server/routes.ts | 6 ++- shared/schema.ts | 1 + 3 files changed, 65 insertions(+), 20 deletions(-) diff --git a/server/invoiceVerification.ts b/server/invoiceVerification.ts index 9dd12ae..35a3c1e 100644 --- a/server/invoiceVerification.ts +++ b/server/invoiceVerification.ts @@ -24,25 +24,42 @@ export class InvoiceVerificationService { const cached = await storage.getInvoiceVerificationCache(cacheKey); console.log('🔍 [CACHE] RĂ©sultat cache:', { found: !!cached, + isReconciled: cached?.isReconciled, expired: cached ? new Date() >= new Date(cached.expiresAt) : 'N/A', expiresAt: cached?.expiresAt, currentTime: new Date().toISOString() }); - if (cached && new Date() < new Date(cached.expiresAt)) { - console.log('✅ [CACHE] Cache hit pour:', { invoiceReference, groupId, exists: cached.exists }); + // Cache permanent si facture validĂ©e (isReconciled = true) + if (cached && cached.isReconciled) { + console.log('✅ [CACHE] Cache PERMANENT pour facture validĂ©e:', { invoiceReference, groupId, exists: cached.exists }); return { exists: cached.exists, matchType: cached.matchType, errorMessage: cached.errorMessage, invoiceReference: cached.invoiceReference, supplierName: cached.supplierName, - fromCache: true + fromCache: true, + permanent: true + }; + } + + // Cache temporaire non expirĂ© + if (cached && new Date() < new Date(cached.expiresAt)) { + console.log('✅ [CACHE] Cache temporaire hit pour:', { invoiceReference, groupId, exists: cached.exists }); + return { + exists: cached.exists, + matchType: cached.matchType, + errorMessage: cached.errorMessage, + invoiceReference: cached.invoiceReference, + supplierName: cached.supplierName, + fromCache: true, + permanent: false }; } if (cached && new Date() >= new Date(cached.expiresAt)) { - console.log('⏰ [CACHE] Cache expirĂ© pour:', { invoiceReference, groupId }); + console.log('⏰ [CACHE] Cache temporaire expirĂ© pour:', { invoiceReference, groupId }); } else { console.log('❌ [CACHE] Cache miss pour:', { invoiceReference, groupId }); } @@ -55,19 +72,37 @@ export class InvoiceVerificationService { } /** - * Sauvegarder dans le cache + * Sauvegarder dans le cache avec durĂ©e adaptative */ - async saveToCache(invoiceReference: string, groupId: number, result: any, supplierName?: string): Promise { + async saveToCache(invoiceReference: string, groupId: number, result: any, supplierName?: string, isReconciled: boolean = false): Promise { try { const cacheKey = this.generateCacheKey(invoiceReference, groupId); + + // DurĂ©e de cache adaptative selon les cas const expiresAt = new Date(); - expiresAt.setHours(expiresAt.getHours() + 24); // Cache pendant 24h + let cacheDescription = ''; + + if (isReconciled) { + // Cache PERMANENT pour factures validĂ©es - expire dans 50 ans + expiresAt.setFullYear(expiresAt.getFullYear() + 50); + cacheDescription = 'PERMANENT (validĂ©)'; + } else if (result.exists) { + // Facture trouvĂ©e mais non validĂ©e - cache 6h pour permettre corrections + expiresAt.setHours(expiresAt.getHours() + 6); + cacheDescription = 'temporaire 6h (trouvĂ©)'; + } else { + // Facture non trouvĂ©e - cache 12h pour Ă©viter spam + expiresAt.setHours(expiresAt.getHours() + 12); + cacheDescription = 'temporaire 12h (pas trouvĂ©)'; + } console.log('đŸ’Ÿ [CACHE] Tentative sauvegarde:', { invoiceReference, groupId, cacheKey, exists: result.exists, + isReconciled, + cacheType: cacheDescription, expiresAt: expiresAt.toISOString() }); @@ -81,6 +116,7 @@ export class InvoiceVerificationService { errorMessage: result.errorMessage || null, cacheHit: false, apiCallTime: null, + isReconciled, // Nouveau champ pour cache permanent expiresAt }; @@ -91,6 +127,8 @@ export class InvoiceVerificationService { invoiceReference, groupId, exists: result.exists, + isReconciled, + cacheType: cacheDescription, cacheKey }); } catch (error) { @@ -111,7 +149,7 @@ export class InvoiceVerificationService { /** * VĂ©rifie une rĂ©fĂ©rence de facture pour un groupe donnĂ© */ - async verifyInvoice(invoiceReference: string, groupId: number, forceRefresh: boolean = false): Promise<{ + async verifyInvoice(invoiceReference: string, groupId: number, forceRefresh: boolean = false, isReconciled: boolean = false): Promise<{ exists: boolean; matchType: 'invoice_reference' | 'bl_number' | 'none'; errorMessage?: string; @@ -186,7 +224,7 @@ export class InvoiceVerificationService { }; // Sauvegarder en cache - await this.saveToCache(invoiceReference, groupId, result, result.supplierName); + await this.saveToCache(invoiceReference, groupId, result, result.supplierName, isReconciled); return result; } @@ -200,7 +238,7 @@ export class InvoiceVerificationService { }; // Sauvegarder en cache - await this.saveToCache(invoiceReference, groupId, result, result.supplierName); + await this.saveToCache(invoiceReference, groupId, result, result.supplierName, isReconciled); return result; } @@ -211,7 +249,7 @@ export class InvoiceVerificationService { }; // Sauvegarder mĂȘme les rĂ©sultats "non trouvĂ©" pour Ă©viter les rappels rĂ©pĂ©tĂ©s - await this.saveToCache(invoiceReference, groupId, result); + await this.saveToCache(invoiceReference, groupId, result, undefined, isReconciled); return result; } @@ -262,7 +300,7 @@ export class InvoiceVerificationService { }; // Sauvegarder en cache mĂȘme les erreurs pour Ă©viter les rappels rĂ©pĂ©tĂ©s - await this.saveToCache(invoiceReference, groupId, result); + await this.saveToCache(invoiceReference, groupId, result, undefined, isReconciled); return result; } @@ -276,7 +314,7 @@ export class InvoiceVerificationService { }; // Sauvegarder en cache si succĂšs - await this.saveToCache(invoiceReference, groupId, result, result.supplierName); + await this.saveToCache(invoiceReference, groupId, result, result.supplierName, isReconciled); return result; } @@ -298,7 +336,7 @@ export class InvoiceVerificationService { }; // Sauvegarder en cache mĂȘme les erreurs - await this.saveToCache(invoiceReference, groupId, result); + await this.saveToCache(invoiceReference, groupId, result, undefined, isReconciled); return result; } @@ -312,7 +350,7 @@ export class InvoiceVerificationService { }; // Sauvegarder en cache si succĂšs - await this.saveToCache(invoiceReference, groupId, result, result.supplierName); + await this.saveToCache(invoiceReference, groupId, result, result.supplierName, isReconciled); return result; } } @@ -325,7 +363,7 @@ export class InvoiceVerificationService { }; // Sauvegarder mĂȘme les rĂ©sultats "non trouvĂ©" pour Ă©viter les rappels rĂ©pĂ©tĂ©s - await this.saveToCache(invoiceReference, groupId, notFoundResult); + await this.saveToCache(invoiceReference, groupId, notFoundResult, undefined, isReconciled); return notFoundResult; } catch (error) { @@ -345,7 +383,7 @@ export class InvoiceVerificationService { }; // Sauvegarder les erreurs en cache pour Ă©viter les rappels rĂ©pĂ©tĂ©s - await this.saveToCache(invoiceReference, groupId, errorResult); + await this.saveToCache(invoiceReference, groupId, errorResult, undefined, isReconciled); return errorResult; } @@ -468,7 +506,7 @@ export class InvoiceVerificationService { /** * VĂ©rifie une facture par numĂ©ro de BL et nom de fournisseur */ - async verifyInvoiceByBL(blNumber: string, supplierName: string, groupId: number, forceRefresh: boolean = false): Promise<{ + async verifyInvoiceByBL(blNumber: string, supplierName: string, groupId: number, forceRefresh: boolean = false, isReconciled: boolean = false): Promise<{ exists: boolean; matchType: 'invoice_reference' | 'bl_number' | 'none'; errorMessage?: string; @@ -638,6 +676,7 @@ export class InvoiceVerificationService { errorMessage: result.errorMessage, cacheHit: false, apiCallTime: null, + isReconciled, expiresAt }); } catch (error) { @@ -688,6 +727,7 @@ export class InvoiceVerificationService { errorMessage: null, cacheHit: false, apiCallTime: null, + isReconciled, expiresAt }); } catch (error) { @@ -719,6 +759,7 @@ export class InvoiceVerificationService { errorMessage: notFoundResult.errorMessage, cacheHit: false, apiCallTime: null, + isReconciled, expiresAt }); } catch (error) { @@ -753,6 +794,7 @@ export class InvoiceVerificationService { errorMessage: errorResult.errorMessage, cacheHit: false, apiCallTime: null, + isReconciled, expiresAt }); } catch (error) { diff --git a/server/routes.ts b/server/routes.ts index cf85b31..407c96b 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -1053,7 +1053,8 @@ export async function registerRoutes(app: Express): Promise { result = await invoiceVerificationService.verifyInvoice( invoiceReference, delivery.groupId, - forceRefresh || false + forceRefresh || false, + delivery.reconciled || false ); } else if (blNumber && blNumber.trim()) { // VĂ©rifier par numĂ©ro BL @@ -1061,7 +1062,8 @@ export async function registerRoutes(app: Express): Promise { blNumber, delivery.supplier.name, delivery.groupId, - forceRefresh || false + forceRefresh || false, + delivery.reconciled || false ); } else { result = { diff --git a/shared/schema.ts b/shared/schema.ts index 970c8d3..6e64e80 100644 --- a/shared/schema.ts +++ b/shared/schema.ts @@ -171,6 +171,7 @@ export const invoiceVerificationCache = pgTable("invoice_verification_cache", { errorMessage: text("error_message"), cacheHit: boolean("cache_hit").default(false), apiCallTime: integer("api_call_time"), + isReconciled: boolean("is_reconciled").default(false), // Si true, cache permanent expiresAt: timestamp("expires_at").notNull(), createdAt: timestamp("created_at").defaultNow(), updatedAt: timestamp("updated_at").defaultNow(),