diff --git a/client/src/pages/BLReconciliation.tsx b/client/src/pages/BLReconciliation.tsx index 5822bb4..c8ef0bb 100644 --- a/client/src/pages/BLReconciliation.tsx +++ b/client/src/pages/BLReconciliation.tsx @@ -135,10 +135,14 @@ export default function BLReconciliation() { // Fonction pour déclencher la vérification const handleVerifyInvoice = (delivery: any, forceRefresh: boolean = false) => { - if (!delivery.invoiceReference?.trim()) { + // Accepter soit une référence de facture soit un numéro BL + const hasInvoiceRef = delivery.invoiceReference?.trim(); + const hasBlNumber = delivery.blNumber?.trim(); + + if (!hasInvoiceRef && !hasBlNumber) { toast({ title: "Référence manquante", - description: "Veuillez saisir une référence de facture avant la vérification", + description: "Veuillez saisir une référence de facture ou un numéro BL avant la vérification", variant: "destructive", }); return; @@ -152,6 +156,15 @@ export default function BLReconciliation() { }); return; } + + console.log('🔍 Déclenchement vérification:', { + deliveryId: delivery.id, + hasInvoiceRef, + hasBlNumber, + invoiceReference: delivery.invoiceReference, + blNumber: delivery.blNumber, + supplier: delivery.supplier?.name + }); setVerifyingDeliveries(prev => new Set(prev).add(delivery.id)); @@ -166,14 +179,14 @@ export default function BLReconciliation() { // Fonction pour vérifier toutes les factures avec un bouton const handleVerifyAllInvoices = () => { const deliveriesToVerify = manualReconciliationDeliveries.filter(delivery => - delivery.invoiceReference?.trim() && + (delivery.invoiceReference?.trim() || delivery.blNumber?.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", + description: "Aucune livraison avec référence de facture ou numéro BL trouvée", }); return; } @@ -187,7 +200,7 @@ export default function BLReconciliation() { toast({ title: "Vérification lancée", - description: `Vérification de ${deliveriesToVerify.length} facture(s) en cours...`, + description: `Vérification de ${deliveriesToVerify.length} facture(s)/BL en cours...`, }); }; @@ -231,10 +244,16 @@ export default function BLReconciliation() { } 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)) { + // Vérifier seulement si on a une référence de facture ou un BL et pas déjà de résultat + const hasVerifiableData = delivery.invoiceReference || delivery.blNumber; + const notAlreadyProcessed = !verificationResults[delivery.id] && !verifyingDeliveries.has(delivery.id); + + if (hasVerifiableData && notAlreadyProcessed) { if (import.meta.env.DEV) { - console.log(`🔍 Vérification auto pour livraison ${delivery.id}:`, delivery.invoiceReference); + console.log(`🔍 Vérification auto pour livraison ${delivery.id}:`, { + invoiceRef: delivery.invoiceReference, + blNumber: delivery.blNumber + }); } // Délai pour éviter de surcharger le serveur - le cache évitera les appels inutiles diff --git a/server/invoiceVerification.ts b/server/invoiceVerification.ts index 7a99b11..5fc214d 100644 --- a/server/invoiceVerification.ts +++ b/server/invoiceVerification.ts @@ -414,29 +414,335 @@ export class InvoiceVerificationService { } /** - * Recherche par numéro de BL + * Vérifie une facture par numéro de BL et nom de fournisseur */ - async searchByBLNumber(blNumber: string, groupId: number): Promise { - console.log('🔍 Recherche par BL:', { blNumber, groupId }); - - // Pour le développement, simuler une recherche - if (process.env.NODE_ENV === 'development') { - if (blNumber && blNumber.trim()) { + async verifyInvoiceByBL(blNumber: string, supplierName: 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 par BL:', { blNumber, supplierName, groupId, forceRefresh }); + + if (!blNumber || !blNumber.trim()) { return { - found: true, - data: { - invoiceReference: `FACT_${blNumber}`, - amount: 156.78, - supplier: 'Fournisseur BL' - } + exists: false, + matchType: 'none', + errorMessage: 'Numéro BL vide' }; } + + // Générer une clé de cache pour le BL + const cacheKey = `bl_${groupId}_${blNumber.trim().toLowerCase()}_${supplierName.toLowerCase()}`; + + // Vérifier le cache d'abord (sauf si refresh forcé) + if (!forceRefresh) { + try { + const cached = await storage.getInvoiceVerificationCache(cacheKey); + if (cached && new Date() < new Date(cached.expiresAt)) { + console.log('💾 Cache hit pour BL:', { blNumber, groupId }); + return { + exists: cached.exists, + matchType: cached.matchType as 'invoice_reference' | 'bl_number' | 'none', + errorMessage: cached.errorMessage || undefined, + invoiceReference: cached.invoiceReference || undefined, + supplierName: cached.supplierName || undefined, + fromCache: true + }; + } + } catch (error) { + console.error('❌ Erreur lecture cache BL:', error); + } + } + + // Récupérer la configuration du groupe + const group = await storage.getGroup(groupId); + if (!group) { + return { + exists: false, + matchType: 'none', + errorMessage: 'Groupe non trouvé' + }; + } + + console.log('🔧 Configuration groupe pour BL:', { + groupName: group.name, + hasNocodbConfig: !!group.nocodbConfigId, + hasTableName: !!group.nocodbTableName, + hasWebhook: !!group.webhookUrl, + blColumnName: group.nocodbBlColumnName + }); + + // Si pas de configuration NocoDB, retourner un résultat par défaut + if (!group.nocodbConfigId && !group.nocodbTableName && !group.webhookUrl) { + console.log('⚠️ Pas de configuration NocoDB pour ce groupe'); + return { + exists: false, + matchType: 'none', + errorMessage: 'Configuration NocoDB manquante pour ce magasin' + }; + } + + // Pour le développement, simuler une vérification + if (process.env.NODE_ENV === 'development') { + console.log('🔧 Mode développement - simulation vérification BL'); + + if (blNumber && blNumber.trim()) { + const result = { + exists: true, + matchType: 'bl_number' as const, + invoiceReference: `FACT_${blNumber}`, + invoiceAmount: 156.78, + supplierName: supplierName + }; + + // Sauvegarder en cache + try { + const expiresAt = new Date(); + expiresAt.setHours(expiresAt.getHours() + 24); + + await storage.saveInvoiceVerificationCache({ + cacheKey, + groupId, + invoiceReference: result.invoiceReference, + supplierName: supplierName, + exists: result.exists, + matchType: result.matchType, + errorMessage: null, + cacheHit: false, + apiCallTime: null, + expiresAt + }); + } catch (error) { + console.error('❌ Erreur sauvegarde cache BL:', error); + } + + return result; + } + + return { + exists: false, + matchType: 'none', + errorMessage: 'BL non trouvé (mode développement)' + }; + } + + // En production, faire l'appel réel à NocoDB + console.log('🔍 Vérification BL NocoDB en production...'); + + try { + // Récupérer la configuration NocoDB active + const nocodbConfig = await storage.getActiveNocodbConfig(); + if (!nocodbConfig) { + console.log('⚠️ Pas de configuration NocoDB active'); + return { + exists: false, + matchType: 'none', + errorMessage: 'Configuration NocoDB non trouvée' + }; + } + + // Utiliser l'ID de table configuré dans le groupe + const tableId = group.nocodbTableId || 'mrr733dfb8wtt9b'; + console.log('🔧 Utilisation table ID pour BL:', { + groupTable: group.nocodbTableName, + configuredId: group.nocodbTableId, + resolvedId: tableId + }); + + // Rechercher par numéro de BL + const blColumnName = group.nocodbBlColumnName || 'Numero_BL'; + let matchResult = await this.searchInNocoDB( + blNumber, + blColumnName, + nocodbConfig, + tableId + ); + + // Gérer les erreurs d'API spécifiques + if (matchResult.error) { + const result = { + exists: false, + matchType: 'none' as const, + errorMessage: matchResult.error + }; + + // Sauvegarder en cache même les erreurs + try { + const expiresAt = new Date(); + expiresAt.setHours(expiresAt.getHours() + 24); + + await storage.saveInvoiceVerificationCache({ + cacheKey, + groupId, + invoiceReference: blNumber, + supplierName: supplierName, + exists: false, + matchType: 'none', + errorMessage: result.errorMessage, + cacheHit: false, + apiCallTime: null, + expiresAt + }); + } catch (error) { + console.error('❌ Erreur sauvegarde cache erreur BL:', error); + } + + return result; + } + + if (matchResult.found) { + // Vérifier que le fournisseur correspond + const foundSupplier = matchResult.data[group.nocodbSupplierColumnName || 'supplier'] || ''; + const supplierMatch = foundSupplier.toLowerCase().includes(supplierName.toLowerCase()) || + supplierName.toLowerCase().includes(foundSupplier.toLowerCase()); + + if (!supplierMatch) { + console.log('⚠️ Fournisseur ne correspond pas:', { + expected: supplierName, + found: foundSupplier + }); + return { + exists: false, + matchType: 'none', + errorMessage: `BL trouvé mais fournisseur différent: ${foundSupplier} vs ${supplierName}` + }; + } + + const result = { + exists: true, + matchType: 'bl_number' as const, + invoiceReference: matchResult.data[group.invoiceColumnName || 'RefFacture'] || `BL_${blNumber}`, + invoiceAmount: parseFloat(matchResult.data[group.nocodbAmountColumnName || 'amount'] || '0'), + supplierName: foundSupplier || supplierName + }; + + // Sauvegarder en cache si succès + try { + const expiresAt = new Date(); + expiresAt.setHours(expiresAt.getHours() + 24); + + await storage.saveInvoiceVerificationCache({ + cacheKey, + groupId, + invoiceReference: result.invoiceReference, + supplierName: result.supplierName, + exists: true, + matchType: 'bl_number', + errorMessage: null, + cacheHit: false, + apiCallTime: null, + expiresAt + }); + } catch (error) { + console.error('❌ Erreur sauvegarde cache succès BL:', error); + } + + return result; + } + + // Aucune correspondance trouvée + const notFoundResult = { + exists: false, + matchType: 'none' as const, + errorMessage: 'Aucun BL correspondant trouvé dans NocoDB' + }; + + // Sauvegarder même les résultats "non trouvé" + try { + const expiresAt = new Date(); + expiresAt.setHours(expiresAt.getHours() + 24); + + await storage.saveInvoiceVerificationCache({ + cacheKey, + groupId, + invoiceReference: blNumber, + supplierName: supplierName, + exists: false, + matchType: 'none', + errorMessage: notFoundResult.errorMessage, + cacheHit: false, + apiCallTime: null, + expiresAt + }); + } catch (error) { + console.error('❌ Erreur sauvegarde cache not found BL:', error); + } + + return notFoundResult; + + } catch (error) { + const errorMessage = `Erreur NocoDB BL: ${error instanceof Error ? error.message : 'Erreur inconnue'}`; + + console.error('❌ Erreur lors de la vérification BL NocoDB:', error); + + const errorResult = { + exists: false, + matchType: 'none' as const, + errorMessage + }; + + // Sauvegarder les erreurs en cache + try { + const expiresAt = new Date(); + expiresAt.setHours(expiresAt.getHours() + 24); + + await storage.saveInvoiceVerificationCache({ + cacheKey, + groupId, + invoiceReference: blNumber, + supplierName: supplierName, + exists: false, + matchType: 'none', + errorMessage: errorResult.errorMessage, + cacheHit: false, + apiCallTime: null, + expiresAt + }); + } catch (error) { + console.error('❌ Erreur sauvegarde cache erreur BL:', error); + } + + return errorResult; + } + + } catch (error) { + console.error('❌ Erreur vérification facture par BL:', error); + return { + exists: false, + matchType: 'none', + errorMessage: error instanceof Error ? error.message : 'Erreur inconnue' + }; } + } + + /** + * Recherche par numéro de BL (méthode simplifiée pour compatibilité) + */ + async searchByBLNumber(blNumber: string, groupId: number): Promise { + console.log('🔍 Recherche par BL (legacy):', { blNumber, groupId }); - return { - found: false, - error: 'BL non trouvé' - }; + const result = await this.verifyInvoiceByBL(blNumber, '', groupId, false); + + if (result.exists) { + return { + found: true, + data: { + invoiceReference: result.invoiceReference, + amount: result.invoiceAmount, + supplier: result.supplierName + } + }; + } else { + return { + found: false, + error: result.errorMessage || 'BL non trouvé' + }; + } } /** diff --git a/server/routes.ts b/server/routes.ts index df7d982..4bf8ca1 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -882,7 +882,7 @@ export async function registerRoutes(app: Express): Promise { } } - const { invoiceReference, forceRefresh } = req.body; + const { invoiceReference, blNumber, forceRefresh } = req.body; if (!delivery.supplier || !delivery.group) { console.log('❌ Livraison manque informations:', { @@ -893,24 +893,44 @@ export async function registerRoutes(app: Express): Promise { return res.status(400).json({ message: "Delivery missing supplier or group information" }); } - if (!invoiceReference || !invoiceReference.trim()) { - return res.status(400).json({ message: "Référence de facture requise" }); + // Accepter soit une référence de facture soit un numéro de BL + if ((!invoiceReference || !invoiceReference.trim()) && (!blNumber || !blNumber.trim())) { + return res.status(400).json({ message: "Référence de facture ou numéro BL requis" }); } console.log('🔍 Vérification facture:', { deliveryId, invoiceReference, + blNumber, supplier: delivery.supplier?.name, group: delivery.group?.name, groupId: delivery.groupId }); - // Appeler le service de vérification - const result = await invoiceVerificationService.verifyInvoice( - invoiceReference, - delivery.groupId, - forceRefresh || false - ); + let result; + + if (invoiceReference && invoiceReference.trim()) { + // Vérifier par référence de facture + result = await invoiceVerificationService.verifyInvoice( + invoiceReference, + delivery.groupId, + forceRefresh || false + ); + } else if (blNumber && blNumber.trim()) { + // Vérifier par numéro BL + result = await invoiceVerificationService.verifyInvoiceByBL( + blNumber, + delivery.supplier.name, + delivery.groupId, + forceRefresh || false + ); + } else { + result = { + exists: false, + matchType: 'none', + errorMessage: 'Aucune référence de facture ou numéro BL fourni' + }; + } console.log('✅ Résultat vérification:', result); res.json(result);