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..890ba17 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,7 +217,7 @@ export default function BLReconciliation() { enabled: !!user }); - // VÉRIFICATION AUTOMATIQUE AU CHARGEMENT + // VÉRIFICATION AUTOMATIQUE AU CHARGEMENT avec système de cache useEffect(() => { if (!deliveriesWithBL.length || !suppliers.length) return; @@ -199,10 +228,10 @@ export default function BLReconciliation() { 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 + // Délai pour éviter de surcharger le serveur - le cache évitera les appels inutiles setTimeout(() => { - handleVerifyInvoice(delivery); - }, Math.random() * 2000); // Délai aléatoire entre 0 et 2 secondes + handleVerifyInvoice(delivery, false); // Pas de force refresh, utilise le cache + }, Math.random() * 1000); // Délai aléatoire entre 0 et 1 seconde } }); }, [deliveriesWithBL, suppliers, verificationResults, verifyingDeliveries]); @@ -509,6 +538,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); diff --git a/server/storage.ts b/server/storage.ts index d7c823f..fb87cc6 100644 --- a/server/storage.ts +++ b/server/storage.ts @@ -651,7 +651,13 @@ export class DatabaseStorage implements IStorage { .from(orders) .where(pendingWhereCondition); - // Calculer les totaux de palettes et colis du mois + // Calculer les totaux de palettes et colis du mois (SEULEMENT livraisons delivered) + let deliveredWhereCondition = and( + deliveriesWhereCondition, + eq(deliveries.status, 'delivered'), + isNotNull(deliveries.deliveredDate) + ); + const deliveriesStatsResult = await db .select({ totalPalettes: sql`COALESCE(SUM(CAST(${deliveries.quantity} as INTEGER)), 0)`, @@ -659,10 +665,7 @@ export class DatabaseStorage implements IStorage { avgDelay: sql`COALESCE(AVG(EXTRACT(DAY FROM (${deliveries.deliveredDate} - ${deliveries.scheduledDate}))), 0)` }) .from(deliveries) - .where(and( - deliveriesWhereCondition, - isNotNull(deliveries.deliveredDate) - )); + .where(deliveredWhereCondition); const ordersCount = Number(ordersResult[0]?.count || 0); const deliveriesCount = Number(deliveriesResult[0]?.count || 0);