diff --git a/client/src/pages/BLReconciliation.tsx b/client/src/pages/BLReconciliation.tsx index e4a2d80..4931724 100644 --- a/client/src/pages/BLReconciliation.tsx +++ b/client/src/pages/BLReconciliation.tsx @@ -12,7 +12,7 @@ import { useStore } from "@/components/Layout"; import { useAuthUnified } from "@/hooks/useAuthUnified"; import { usePermissions } from "@shared/permissions"; import { Pagination, usePagination } from "@/components/ui/pagination"; -import { Search, Edit, FileText, Settings, Eye, AlertTriangle, X, Check, Trash2, Ban, Filter, Upload, CheckCircle, AlertCircle } from "lucide-react"; +import { Search, Edit, FileText, Settings, Eye, AlertTriangle, X, Check, Trash2, Ban, Filter, Upload } from "lucide-react"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Label } from "@/components/ui/label"; @@ -58,10 +58,6 @@ export default function BLReconciliation() { const [selectedFile, setSelectedFile] = useState(null); const [isUploading, setIsUploading] = useState(false); - // État pour la vérification des factures - const [verificationResults, setVerificationResults] = useState>({}); - const [isVerifying, setIsVerifying] = useState>({}); - // Récupérer les fournisseurs pour la logique automatique const { data: suppliers = [] } = useQuery({ queryKey: ['/api/suppliers'], @@ -217,116 +213,6 @@ export default function BLReconciliation() { } }; - // Fonction pour vérifier une référence de facture - const verifyInvoiceReference = async (delivery: any) => { - if (!delivery.invoiceReference || !delivery.supplier?.name || !delivery.group?.id) { - return; - } - - setIsVerifying(prev => ({ ...prev, [delivery.id]: true })); - - try { - const response = await apiRequest('/api/deliveries/verify-invoice', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - groupId: delivery.group.id, - invoiceReference: delivery.invoiceReference, - supplierName: delivery.supplier.name - }) - }); - - setVerificationResults(prev => ({ - ...prev, - [delivery.id]: response - })); - - if (response.found && response.supplierMatch) { - toast({ - title: "✅ Facture vérifiée", - description: `Référence ${delivery.invoiceReference} trouvée et fournisseur correct`, - }); - } else if (response.found && !response.supplierMatch) { - toast({ - title: "⚠️ Fournisseur incorrect", - description: `Référence trouvée mais fournisseur ne correspond pas`, - variant: "destructive" - }); - } else { - toast({ - title: "❌ Référence non trouvée", - description: `Référence ${delivery.invoiceReference} introuvable`, - variant: "destructive" - }); - } - } catch (error: any) { - console.error('Erreur vérification:', error); - toast({ - title: "Erreur de vérification", - description: error.response?.data?.message || error.message || "Erreur de vérification - vérifiez la configuration NocoDB", - variant: "destructive" - }); - } finally { - setIsVerifying(prev => ({ ...prev, [delivery.id]: false })); - } - }; - - // Fonction pour rechercher par numéro de BL et auto-remplir - const searchByBLAndFill = async (delivery: any) => { - if (!delivery.blNumber || !delivery.supplier?.name || !delivery.group?.id) { - return; - } - - setIsVerifying(prev => ({ ...prev, [delivery.id]: true })); - - try { - const response = await apiRequest('/api/deliveries/search-by-bl', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - groupId: delivery.group.id, - blNumber: delivery.blNumber, - supplierName: delivery.supplier.name - }) - }); - - if (response.found && response.invoiceReference) { - // Auto-remplir les champs facture - const updateData: any = { - invoiceReference: response.invoiceReference - }; - - if (response.invoiceAmount) { - updateData.invoiceAmount = response.invoiceAmount; - } - - await apiRequest(`/api/deliveries/${delivery.id}`, "PUT", updateData); - - queryClient.invalidateQueries({ queryKey: ['/api/deliveries/bl'] }); - - toast({ - title: "✅ Auto-remplissage réussi", - description: `Référence ${response.invoiceReference} trouvée et ajoutée`, - }); - } else { - toast({ - title: "❌ BL non trouvé", - description: `Aucune facture trouvée pour le BL ${delivery.blNumber}`, - variant: "destructive" - }); - } - } catch (error: any) { - console.error('Erreur recherche BL:', error); - toast({ - title: "Erreur de recherche", - description: error.response?.data?.message || error.message || "Erreur de recherche BL - vérifiez la configuration NocoDB", - variant: "destructive" - }); - } finally { - setIsVerifying(prev => ({ ...prev, [delivery.id]: false })); - } - }; - // Fonction pour déterminer si le bouton "Envoyer Facture" doit être affiché const shouldShowInvoiceButton = (delivery: any) => { // Conditions : ligne non validée OU pas de RefFacture renseignée @@ -650,57 +536,9 @@ export default function BLReconciliation() { -
- {/* Coche verte si vérification réussie */} - {verificationResults[delivery.id]?.found && verificationResults[delivery.id]?.supplierMatch && ( - - )} - {/* Indicateur d'erreur si vérification échouée */} - {verificationResults[delivery.id]?.found && !verificationResults[delivery.id]?.supplierMatch && ( - - )} - - {/* Référence de facture */} -
- {delivery.invoiceReference || ( - Non renseigné - )} -
- - {/* Bouton de vérification si référence existe */} - {delivery.invoiceReference && delivery.group?.nocodbConfigId && ( - - )} - - {/* Bouton d'auto-remplissage si pas de référence mais BL existe */} - {!delivery.invoiceReference && delivery.blNumber && delivery.group?.nocodbConfigId && ( - +
+ {delivery.invoiceReference || ( + Non renseigné )}
diff --git a/replit.md b/replit.md index dace870..f32a8ea 100644 --- a/replit.md +++ b/replit.md @@ -19,32 +19,22 @@ La plateforme fournit une gestion robuste des flux de travail de livraison avec ## Recent Changes -### 2025-08-13 - Système de Vérification de Factures NocoDB avec Cache Production -✅ **Implémentation complète du système de vérification automatique des factures** : - -**Fonctionnalités développées :** -- Service de vérification production (`server/services/productionInvoiceVerification.ts`) -- Utilise les tables existantes de cache (`INVOICE_VERIFICATION_CACHE`, `INVOICE_VERIFICATIONS`) -- API endpoints pour vérification factures et recherche par BL -- Interface utilisateur avec coches vertes de validation -- Auto-remplissage des champs facture/montant depuis NocoDB -- Support multi-magasin avec configurations NocoDB spécifiques - -**Logique de fonctionnement :** -- Vérification d'abord dans le cache (tables production) -- Si pas trouvé, appel réel à NocoDB pour récupérer les données -- Sauvegarde automatique des résultats dans le cache (1h expiration) -- Indicateurs visuels : coche verte (succès), rouge (erreur) - -**Configuration technique :** -- Intégration avec tables cache existantes en production -- Appels API NocoDB authentifiés avec tokens personnels -- Mapping flexible des colonnes (facture, BL, montant, fournisseur) -- Gestion d'erreurs et notifications utilisateur améliorées -- Guide complet de configuration production fourni - ### 2025-08-13 - Uniformisation complète des interfaces utilisateur -✅ **Harmonisation du design des tableaux** terminée pour une expérience utilisateur cohérente +✅ **Harmonisation du design des tableaux** terminée pour une expérience utilisateur cohérente : + +**Pages uniformisées :** +- `Orders.tsx` - Page des commandes avec design responsive optimisé +- `Deliveries.tsx` - Page des livraisons avec structure identique +- `BLReconciliation.tsx` - Page de rapprochement BL/Factures harmonisée +- `Dashboard.tsx` - Tableau de bord avec erreurs TypeScript corrigées + +**Améliorations appliquées :** +- Headers responsifs adaptés mobile/tablette +- Filtres restructurés avec style cohérent +- Tableaux standardisés : `px-6 py-4` et `whitespace-nowrap` +- Boutons d'actions modernisés avec composants Button shadcn/ui +- Pagination repositionnée avec bordures uniformisées +- Espacement et typographie harmonisés ### 2025-08-12 - Nettoyage des fichiers inutiles ✅ **Suppression complète des fichiers inutiles** effectuée pour optimiser le projet diff --git a/server/routes.ts b/server/routes.ts index bf4a9bf..537fb0f 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -37,7 +37,6 @@ import { } from "@shared/schema"; import { hasPermission } from "@shared/permissions"; import { z } from "zod"; -import { ProductionInvoiceVerificationService } from "./services/productionInvoiceVerification"; export async function registerRoutes(app: Express): Promise { // Health check endpoint for Docker @@ -890,81 +889,6 @@ export async function registerRoutes(app: Express): Promise { } }); - // Routes de vérification NocoDB pour les factures - app.post('/api/deliveries/verify-invoice', isAuthenticated, async (req: any, res) => { - try { - const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id); - if (!user) { - return res.status(404).json({ message: "User not found" }); - } - - const { groupId, invoiceReference, supplierName } = req.body; - - if (!groupId || !invoiceReference || !supplierName) { - return res.status(400).json({ - message: "groupId, invoiceReference et supplierName sont requis" - }); - } - - // Vérifier les permissions d'accès au groupe - if (user.role !== 'admin') { - const userGroupIds = user.userGroups?.map((ug: any) => ug.groupId) || []; - if (!userGroupIds.includes(parseInt(groupId))) { - return res.status(403).json({ message: "Access denied to this group" }); - } - } - - const productionVerificationService = new ProductionInvoiceVerificationService(storage); - const result = await productionVerificationService.verifyInvoiceReference( - parseInt(groupId), - invoiceReference, - supplierName - ); - - res.json(result); - } catch (error) { - console.error("Error verifying invoice:", error); - res.status(500).json({ message: "Failed to verify invoice" }); - } - }); - - app.post('/api/deliveries/search-by-bl', isAuthenticated, async (req: any, res) => { - try { - const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id); - if (!user) { - return res.status(404).json({ message: "User not found" }); - } - - const { groupId, blNumber, supplierName } = req.body; - - if (!groupId || !blNumber || !supplierName) { - return res.status(400).json({ - message: "groupId, blNumber et supplierName sont requis" - }); - } - - // Vérifier les permissions d'accès au groupe - if (user.role !== 'admin') { - const userGroupIds = user.userGroups?.map((ug: any) => ug.groupId) || []; - if (!userGroupIds.includes(parseInt(groupId))) { - return res.status(403).json({ message: "Access denied to this group" }); - } - } - - const productionVerificationService = new ProductionInvoiceVerificationService(storage); - const result = await productionVerificationService.searchByBLNumber( - parseInt(groupId), - blNumber, - supplierName - ); - - res.json(result); - } catch (error) { - console.error("Error searching by BL:", error); - res.status(500).json({ message: "Failed to search by BL number" }); - } - }); - // Statistics routes app.get('/api/stats/monthly', isAuthenticated, async (req: any, res) => { try { diff --git a/server/services/nocodbVerification.ts b/server/services/nocodbVerification.ts deleted file mode 100644 index 54d1bad..0000000 --- a/server/services/nocodbVerification.ts +++ /dev/null @@ -1,206 +0,0 @@ -import { storage } from "../storage"; - -interface NocodbConfig { - id: number; - baseUrl: string; - apiToken: string; - projectId: string; - isActive: boolean; -} - -interface GroupConfig { - nocodbConfigId?: number; - nocodbTableName?: string; - invoiceColumnName?: string; - nocodbBlColumnName?: string; - nocodbAmountColumnName?: string; - nocodbSupplierColumnName?: string; -} - -class NocodbVerificationService { - private async getNocodbConfig(configId: number): Promise { - try { - return await storage.getNocodbConfig(configId); - } catch (error) { - console.error('Erreur récupération config NocoDB:', error); - return null; - } - } - - private async getGroupConfig(groupId: number): Promise { - try { - const group = await storage.getGroup(groupId); - if (!group) return null; - - return { - nocodbConfigId: group.nocodbConfigId || undefined, - nocodbTableName: group.nocodbTableName || undefined, - invoiceColumnName: group.invoiceColumnName || 'invoice_reference', - nocodbBlColumnName: group.nocodbBlColumnName || 'bl_number', - nocodbAmountColumnName: group.nocodbAmountColumnName || 'amount', - nocodbSupplierColumnName: group.nocodbSupplierColumnName || 'supplier' - }; - } catch (error) { - console.error('Erreur récupération config groupe:', error); - return null; - } - } - - private async makeNocodbRequest( - config: NocodbConfig, - tableName: string, - queryParams: Record - ): Promise { - try { - const params = new URLSearchParams(); - - // Construire les filtres NocoDB - Object.entries(queryParams).forEach(([key, value]) => { - if (value !== undefined && value !== null) { - params.append(`where`, `(${key},eq,${value})`); - } - }); - - const url = `${config.baseUrl}/api/v1/db/data/${config.projectId}/${tableName}?${params.toString()}`; - - console.log('🔍 Requête NocoDB:', { url, table: tableName, filters: queryParams }); - - const response = await fetch(url, { - method: 'GET', - headers: { - 'xc-auth': config.apiToken, - 'Content-Type': 'application/json' - } - }); - - if (!response.ok) { - throw new Error(`HTTP ${response.status}: ${response.statusText}`); - } - - const data = await response.json(); - console.log('✅ Réponse NocoDB:', { count: data.list?.length || 0 }); - - return data; - } catch (error) { - console.error('❌ Erreur requête NocoDB:', error); - throw error; - } - } - - async verifyInvoiceReference( - groupId: number, - invoiceReference: string, - supplierName: string - ): Promise<{ found: boolean; supplierMatch: boolean; data?: any }> { - try { - console.log('🔍 Vérification facture:', { groupId, invoiceReference, supplierName }); - - // Récupérer la configuration du groupe - const groupConfig = await this.getGroupConfig(groupId); - if (!groupConfig?.nocodbConfigId || !groupConfig?.nocodbTableName) { - console.log('⚠️ Configuration NocoDB manquante pour le groupe', groupId); - return { found: false, supplierMatch: false }; - } - - // Récupérer la configuration NocoDB - const nocodbConfig = await this.getNocodbConfig(groupConfig.nocodbConfigId); - if (!nocodbConfig || !nocodbConfig.isActive) { - console.log('⚠️ Configuration NocoDB inactive ou manquante'); - return { found: false, supplierMatch: false }; - } - - // Effectuer la recherche - const queryParams = { - [groupConfig.invoiceColumnName!]: invoiceReference - }; - - const response = await this.makeNocodbRequest( - nocodbConfig, - groupConfig.nocodbTableName, - queryParams - ); - - const records = response.list || response.rows || []; - - if (records.length === 0) { - return { found: false, supplierMatch: false }; - } - - // Vérifier la correspondance du fournisseur - const record = records[0]; - const recordSupplier = record[groupConfig.nocodbSupplierColumnName!]; - - const supplierMatch = recordSupplier && - recordSupplier.toString().toLowerCase().includes(supplierName.toLowerCase()); - - return { - found: true, - supplierMatch: !!supplierMatch, - data: record - }; - - } catch (error) { - console.error('❌ Erreur vérification facture:', error); - return { found: false, supplierMatch: false }; - } - } - - async searchByBLNumber( - groupId: number, - blNumber: string, - supplierName: string - ): Promise<{ found: boolean; invoiceReference?: string; invoiceAmount?: number; data?: any }> { - try { - console.log('🔍 Recherche par BL:', { groupId, blNumber, supplierName }); - - // Récupérer la configuration du groupe - const groupConfig = await this.getGroupConfig(groupId); - if (!groupConfig?.nocodbConfigId || !groupConfig?.nocodbTableName) { - console.log('⚠️ Configuration NocoDB manquante pour le groupe', groupId); - return { found: false }; - } - - // Récupérer la configuration NocoDB - const nocodbConfig = await this.getNocodbConfig(groupConfig.nocodbConfigId); - if (!nocodbConfig || !nocodbConfig.isActive) { - console.log('⚠️ Configuration NocoDB inactive ou manquante'); - return { found: false }; - } - - // Effectuer la recherche par BL - const queryParams = { - [groupConfig.nocodbBlColumnName!]: blNumber, - [groupConfig.nocodbSupplierColumnName!]: supplierName - }; - - const response = await this.makeNocodbRequest( - nocodbConfig, - groupConfig.nocodbTableName, - queryParams - ); - - const records = response.list || response.rows || []; - - if (records.length === 0) { - return { found: false }; - } - - const record = records[0]; - const invoiceReference = record[groupConfig.invoiceColumnName!]; - const invoiceAmount = record[groupConfig.nocodbAmountColumnName!]; - - return { - found: true, - invoiceReference: invoiceReference?.toString(), - invoiceAmount: invoiceAmount ? parseFloat(invoiceAmount.toString()) : undefined, - data: record - }; - - } catch (error) { - console.error('❌ Erreur recherche par BL:', error); - return { found: false }; - } - } -} - -export const nocodbVerificationService = new NocodbVerificationService(); \ No newline at end of file diff --git a/server/services/productionInvoiceVerification.ts b/server/services/productionInvoiceVerification.ts deleted file mode 100644 index 2858612..0000000 --- a/server/services/productionInvoiceVerification.ts +++ /dev/null @@ -1,300 +0,0 @@ -import { IStorage } from "../storage"; - -interface VerificationResult { - exists: boolean; - matchType: string; - invoiceReference?: string; - invoiceAmount?: number; - errorMessage?: string; -} - -export class ProductionInvoiceVerificationService { - constructor(private storage: IStorage) {} - - // Vérifie une référence facture (avec cache) - async verifyInvoiceReference( - groupId: number, - invoiceReference: string, - supplierName?: string - ): Promise { - console.log(`🔍 Production Verification - Invoice: ${invoiceReference}, Group: ${groupId}, Supplier: ${supplierName}`); - - // 1. Vérifier d'abord dans les vérifications existantes - const existingVerifications = await this.storage.getInvoiceVerifications(); - const existing = existingVerifications.find(v => - v.groupId === groupId && - v.invoiceReference === invoiceReference && - (!supplierName || v.supplierName === supplierName) - ); - - if (existing) { - console.log(`✅ Production Cache Hit - Found existing verification: ${existing.exists}`); - return { - exists: existing.exists, - matchType: existing.matchType || 'direct', - invoiceReference: existing.invoiceReference - }; - } - - // 2. Vérifier dans le cache - const cacheKey = `${groupId}_${invoiceReference}_${supplierName || 'any'}`; - const cache = await this.storage.getInvoiceVerificationCache(cacheKey); - - if (cache && cache.expiresAt > new Date()) { - console.log(`⚡ Production Cache Hit - Using cached result: ${cache.exists}`); - return { - exists: cache.exists, - matchType: cache.matchType, - invoiceReference: cache.invoiceReference - }; - } - - // 3. Faire la vérification réelle via NocoDB - try { - const result = await this.verifyWithNocoDB(groupId, invoiceReference, supplierName); - - // 4. Sauvegarder dans le cache - await this.saveToCaches(groupId, invoiceReference, supplierName, result, 'delivery_verification'); - - return result; - } catch (error) { - console.error(`❌ Production NocoDB Error:`, error); - - // Sauvegarder l'erreur dans le cache - const errorResult: VerificationResult = { - exists: false, - matchType: 'error', - errorMessage: error instanceof Error ? error.message : 'Erreur de vérification' - }; - - await this.saveToCaches(groupId, invoiceReference, supplierName, errorResult, 'error'); - return errorResult; - } - } - - // Recherche par numéro BL (avec cache) - async searchByBLNumber( - groupId: number, - blNumber: string, - supplierName?: string - ): Promise { - console.log(`🔍 Production BL Search - BL: ${blNumber}, Group: ${groupId}, Supplier: ${supplierName}`); - - // Vérifier dans le cache BL - const cacheKey = `bl_${groupId}_${blNumber}_${supplierName || 'any'}`; - const cache = await this.storage.getInvoiceVerificationCache(cacheKey); - - if (cache && cache.expiresAt > new Date()) { - console.log(`⚡ Production BL Cache Hit: ${cache.exists}`); - return { - exists: cache.exists, - matchType: cache.matchType, - invoiceReference: cache.invoiceReference, - invoiceAmount: cache.apiCallTime // On stocke le montant dans ce champ temporairement - }; - } - - // Faire la recherche réelle via NocoDB - try { - const result = await this.searchBLWithNocoDB(groupId, blNumber, supplierName); - - // Sauvegarder dans le cache - await this.saveToCaches(groupId, blNumber, supplierName, result, 'bl_search'); - - return result; - } catch (error) { - console.error(`❌ Production BL Search Error:`, error); - - const errorResult: VerificationResult = { - exists: false, - matchType: 'error', - errorMessage: error instanceof Error ? error.message : 'Erreur de recherche BL' - }; - - await this.saveToCaches(groupId, blNumber, supplierName, errorResult, 'bl_error'); - return errorResult; - } - } - - // Vérification réelle avec NocoDB - private async verifyWithNocoDB( - groupId: number, - invoiceReference: string, - supplierName?: string - ): Promise { - // Récupérer la configuration du groupe - const group = await this.storage.getGroup(groupId); - if (!group || !group.nocodbConfigId || !group.nocodbTableName) { - throw new Error(`Configuration NocoDB manquante pour le groupe ${groupId}`); - } - - // Récupérer la configuration NocoDB - const nocodbConfig = await this.storage.getNocodbConfig(group.nocodbConfigId); - if (!nocodbConfig || !nocodbConfig.isActive) { - throw new Error(`Configuration NocoDB inactive ou introuvable (ID: ${group.nocodbConfigId})`); - } - - console.log(`🌐 Production NocoDB Call - Table: ${group.nocodbTableName}, Invoice: ${invoiceReference}`); - - // Construire la requête NocoDB - const baseUrl = nocodbConfig.baseUrl.replace(/\/+$/, ''); // Enlever les trailing slashes - const tableUrl = `${baseUrl}/api/v1/db/data/${nocodbConfig.projectId}/${group.nocodbTableName}`; - - // Construire les conditions de recherche - const invoiceColumn = group.invoiceColumnName || 'invoice_reference'; - const supplierColumn = group.nocodbSupplierColumnName || 'supplier'; - - let whereClause = `(${invoiceColumn},eq,${invoiceReference})`; - if (supplierName && supplierColumn) { - whereClause += `~and(${supplierColumn},eq,${supplierName})`; - } - - const searchUrl = `${tableUrl}?where=${encodeURIComponent(whereClause)}&limit=1`; - - console.log(`🌐 Production NocoDB URL: ${searchUrl}`); - - // Faire l'appel API - const response = await fetch(searchUrl, { - method: 'GET', - headers: { - 'xc-token': nocodbConfig.apiToken, - 'Content-Type': 'application/json' - } - }); - - if (!response.ok) { - throw new Error(`NocoDB API Error: ${response.status} ${response.statusText}`); - } - - const data = await response.json(); - console.log(`✅ Production NocoDB Response:`, data); - - const exists = data.list && data.list.length > 0; - - return { - exists, - matchType: 'direct', - invoiceReference - }; - } - - // Recherche BL avec NocoDB - private async searchBLWithNocoDB( - groupId: number, - blNumber: string, - supplierName?: string - ): Promise { - // Récupérer la configuration du groupe - const group = await this.storage.getGroup(groupId); - if (!group || !group.nocodbConfigId || !group.nocodbTableName) { - throw new Error(`Configuration NocoDB manquante pour le groupe ${groupId}`); - } - - // Récupérer la configuration NocoDB - const nocodbConfig = await this.storage.getNocodbConfig(group.nocodbConfigId); - if (!nocodbConfig || !nocodbConfig.isActive) { - throw new Error(`Configuration NocoDB inactive ou introuvable (ID: ${group.nocodbConfigId})`); - } - - console.log(`🌐 Production NocoDB BL Search - Table: ${group.nocodbTableName}, BL: ${blNumber}`); - - // Construire la requête NocoDB pour recherche BL - const baseUrl = nocodbConfig.baseUrl.replace(/\/+$/, ''); - const tableUrl = `${baseUrl}/api/v1/db/data/${nocodbConfig.projectId}/${group.nocodbTableName}`; - - const blColumn = group.nocodbBlColumnName || 'bl_number'; - const supplierColumn = group.nocodbSupplierColumnName || 'supplier'; - const invoiceColumn = group.invoiceColumnName || 'invoice_reference'; - const amountColumn = group.nocodbAmountColumnName || 'amount'; - - let whereClause = `(${blColumn},eq,${blNumber})`; - if (supplierName && supplierColumn) { - whereClause += `~and(${supplierColumn},eq,${supplierName})`; - } - - const searchUrl = `${tableUrl}?where=${encodeURIComponent(whereClause)}&limit=1`; - - console.log(`🌐 Production NocoDB BL URL: ${searchUrl}`); - - // Faire l'appel API - const response = await fetch(searchUrl, { - method: 'GET', - headers: { - 'xc-token': nocodbConfig.apiToken, - 'Content-Type': 'application/json' - } - }); - - if (!response.ok) { - throw new Error(`NocoDB API Error: ${response.status} ${response.statusText}`); - } - - const data = await response.json(); - console.log(`✅ Production NocoDB BL Response:`, data); - - if (data.list && data.list.length > 0) { - const record = data.list[0]; - return { - exists: true, - matchType: 'bl_search', - invoiceReference: record[invoiceColumn], - invoiceAmount: parseFloat(record[amountColumn]) || undefined - }; - } - - return { - exists: false, - matchType: 'bl_search' - }; - } - - // Sauvegarder dans les caches - private async saveToCaches( - groupId: number, - reference: string, - supplierName: string | undefined, - result: VerificationResult, - context: string - ): Promise { - try { - // 1. Sauvegarder dans le cache avec expiration - const cacheKey = context.startsWith('bl_') - ? `bl_${groupId}_${reference}_${supplierName || 'any'}` - : `${groupId}_${reference}_${supplierName || 'any'}`; - - const expiresAt = new Date(); - expiresAt.setHours(expiresAt.getHours() + 1); // Cache 1 heure - - await this.storage.createInvoiceVerificationCache({ - cacheKey, - groupId, - invoiceReference: reference, - supplierName: supplierName || null, - exists: result.exists, - matchType: result.matchType, - errorMessage: result.errorMessage || null, - cacheHit: false, - apiCallTime: result.invoiceAmount ? Math.round(result.invoiceAmount) : undefined, - expiresAt - }); - - // 2. Si c'est une vérification de livraison réussie, sauvegarder aussi dans les vérifications permanentes - if (context === 'delivery_verification' && result.exists) { - // On a besoin du deliveryId, pour l'instant on utilise 0 comme placeholder - await this.storage.createInvoiceVerification({ - deliveryId: 0, // Sera mis à jour par l'API route - groupId, - invoiceReference: reference, - supplierName: supplierName || null, - exists: result.exists, - matchType: result.matchType, - isValid: true - }); - } - - console.log(`💾 Production Cache Saved - Key: ${cacheKey}, Exists: ${result.exists}`); - } catch (error) { - console.error(`❌ Production Cache Save Error:`, error); - } - } -} \ No newline at end of file diff --git a/server/storage.ts b/server/storage.ts index f2ed3ca..493d515 100644 --- a/server/storage.ts +++ b/server/storage.ts @@ -10,8 +10,6 @@ import { customerOrders, dlcProducts, tasks, - invoiceVerificationCache, - invoiceVerifications, type User, type UpsertUser, type Group, @@ -43,10 +41,6 @@ import { type DlcProductFrontend, type InsertDlcProductFrontend, type DlcProductWithRelations, - type InvoiceVerificationCache, - type InsertInvoiceVerificationCache, - type InvoiceVerification, - type InsertInvoiceVerification, type Task, type InsertTask, } from "@shared/schema"; @@ -158,12 +152,6 @@ export interface IStorage { updateTask(id: number, task: Partial): Promise; deleteTask(id: number): Promise; completeTask(id: number, completedBy?: string): Promise; - - // Invoice Verification operations - Tables existantes en production - getInvoiceVerifications(deliveryId?: number): Promise; - createInvoiceVerification(verification: InsertInvoiceVerification): Promise; - getInvoiceVerificationCache(cacheKey: string): Promise; - createInvoiceVerificationCache(cache: InsertInvoiceVerificationCache): Promise; } export class DatabaseStorage implements IStorage { @@ -1085,26 +1073,19 @@ export class DatabaseStorage implements IStorage { } async getNocodbConfig(id: number): Promise { - try { - const configs = await db.select({ - id: nocodbConfig.id, - name: nocodbConfig.name, - baseUrl: nocodbConfig.baseUrl, - projectId: nocodbConfig.projectId, - apiToken: nocodbConfig.apiToken, - description: nocodbConfig.description, - isActive: nocodbConfig.isActive, - createdBy: nocodbConfig.createdBy, - createdAt: nocodbConfig.createdAt, - updatedAt: nocodbConfig.updatedAt, - }).from(nocodbConfig).where(eq(nocodbConfig.id, id)); - - console.log(`🔍 Production NocoDB Config Query - ID: ${id}, Found: ${configs.length > 0}`); - return configs[0]; - } catch (error) { - console.error(`❌ Erreur récupération config NocoDB ${id}:`, error); - return undefined; - } + const configs = await db.select({ + id: nocodbConfig.id, + name: nocodbConfig.name, + baseUrl: nocodbConfig.baseUrl, + projectId: nocodbConfig.projectId, + apiToken: nocodbConfig.apiToken, + description: nocodbConfig.description, + isActive: nocodbConfig.isActive, + createdBy: nocodbConfig.createdBy, + createdAt: nocodbConfig.createdAt, + updatedAt: nocodbConfig.updatedAt, + }).from(nocodbConfig).where(eq(nocodbConfig.id, id)); + return configs[0]; } async createNocodbConfig(config: InsertNocodbConfig): Promise { @@ -2038,52 +2019,6 @@ export class DatabaseStorage implements IStorage { }) .where(eq(tasks.id, id)); } - - // Invoice Verification operations - Utilise les tables existantes en production - async getInvoiceVerifications(deliveryId?: number): Promise { - const query = db.select().from(invoiceVerifications); - if (deliveryId) { - return await query.where(eq(invoiceVerifications.deliveryId, deliveryId)); - } - return await query.orderBy(desc(invoiceVerifications.verifiedAt)); - } - - async createInvoiceVerification(verification: InsertInvoiceVerification): Promise { - const [newVerification] = await db - .insert(invoiceVerifications) - .values({ - ...verification, - verifiedAt: new Date(), - lastCheckedAt: new Date(), - }) - .returning(); - return newVerification; - } - - async getInvoiceVerificationCache(cacheKey: string): Promise { - const [cache] = await db - .select() - .from(invoiceVerificationCache) - .where( - and( - eq(invoiceVerificationCache.cacheKey, cacheKey), - sql`expires_at > NOW()` - ) - ); - return cache; - } - - async createInvoiceVerificationCache(cache: InsertInvoiceVerificationCache): Promise { - const [newCache] = await db - .insert(invoiceVerificationCache) - .values({ - ...cache, - createdAt: new Date(), - updatedAt: new Date(), - }) - .returning(); - return newCache; - } } // Simple in-memory storage for development @@ -2118,43 +2053,11 @@ class MemStorage implements IStorage { this.users.set('admin_dev', adminUser); this.users.set('admin', adminUser); // Also set by username for easy lookup - // Add test groups with NocoDB configuration + // Add test groups const testGroups = [ - { - id: 1, - name: 'Magasin A', - color: '#FF5733', - webhookUrl: 'https://webhook.site/test-facture-magasin-a', - nocodbConfigId: 1, - nocodbTableName: 'factures_magasin_a', - invoiceColumnName: 'reference_facture', - nocodbBlColumnName: 'numero_bl', - nocodbAmountColumnName: 'montant', - nocodbSupplierColumnName: 'fournisseur', - createdAt: new Date(), - updatedAt: new Date() - }, - { - id: 2, - name: 'Magasin B', - color: '#33FF57', - webhookUrl: 'https://webhook.site/test-facture-magasin-b', - nocodbConfigId: 1, - nocodbTableName: 'factures_magasin_b', - invoiceColumnName: 'ref_facture', - nocodbBlColumnName: 'bl_number', - nocodbAmountColumnName: 'amount', - nocodbSupplierColumnName: 'supplier_name', - createdAt: new Date(), - updatedAt: new Date() - }, - { - id: 3, - name: 'Magasin C', - color: '#3357FF', - createdAt: new Date(), - updatedAt: new Date() - } + { id: 1, name: 'Magasin A', color: '#FF5733', webhookUrl: 'https://webhook.site/test-facture-magasin-a', createdAt: new Date(), updatedAt: new Date() }, + { id: 2, name: 'Magasin B', color: '#33FF57', webhookUrl: 'https://webhook.site/test-facture-magasin-b', createdAt: new Date(), updatedAt: new Date() }, + { id: 3, name: 'Magasin C', color: '#3357FF', createdAt: new Date(), updatedAt: new Date() } ]; testGroups.forEach(group => this.groups.set(group.id, group as Group)); @@ -2318,9 +2221,7 @@ class MemStorage implements IStorage { async getGroups(): Promise { return Array.from(this.groups.values()); } - async getGroup(id: number): Promise { - return this.groups.get(id); - } + async getGroup(id: number): Promise { return undefined; } async createGroup(group: InsertGroup): Promise { return {} as Group; } async updateGroup(id: number, group: Partial): Promise { return {} as Group; } async deleteGroup(id: number): Promise {} @@ -2548,20 +2449,7 @@ class MemStorage implements IStorage { async removeUserFromGroup(): Promise {} async getMonthlyStats(): Promise { return {}; } async getNocodbConfigs(): Promise { return []; } - async getNocodbConfig(id: number): Promise { - // Retourner une configuration de test pour le développement - if (id === 1) { - return { - id: 1, - name: 'Config Test NocoDB', - baseUrl: 'https://nocodb-test.example.com', - projectId: 'test-project', - apiToken: 'test-token-123', - isActive: true - }; - } - return null; - } + async getNocodbConfig(): Promise { return null; } async createNocodbConfig(): Promise { return {}; } async updateNocodbConfig(): Promise { return {}; } async deleteNocodbConfig(): Promise {} @@ -2622,52 +2510,6 @@ class MemStorage implements IStorage { return { userId, roleId, assignedBy, assignedAt: new Date() }; } async removeRoleFromUser(): Promise {} - - // Invoice Verification operations - Stubs pour développement - async getInvoiceVerifications(deliveryId?: number): Promise { - console.log('📋 MemStorage - getInvoiceVerifications called (development mode)'); - return []; - } - - async createInvoiceVerification(verification: InsertInvoiceVerification): Promise { - console.log('📋 MemStorage - createInvoiceVerification called (development mode)'); - return { - id: 1, - deliveryId: verification.deliveryId, - groupId: verification.groupId, - invoiceReference: verification.invoiceReference, - supplierName: verification.supplierName, - exists: verification.exists, - matchType: verification.matchType, - verifiedAt: new Date(), - isValid: true, - lastCheckedAt: new Date() - }; - } - - async getInvoiceVerificationCache(cacheKey: string): Promise { - console.log('📋 MemStorage - getInvoiceVerificationCache called (development mode)'); - return undefined; - } - - async createInvoiceVerificationCache(cache: InsertInvoiceVerificationCache): Promise { - console.log('📋 MemStorage - createInvoiceVerificationCache called (development mode)'); - return { - id: 1, - cacheKey: cache.cacheKey, - groupId: cache.groupId, - invoiceReference: cache.invoiceReference, - supplierName: cache.supplierName, - exists: cache.exists, - matchType: cache.matchType, - errorMessage: cache.errorMessage, - cacheHit: cache.cacheHit, - apiCallTime: cache.apiCallTime, - expiresAt: cache.expiresAt, - createdAt: new Date(), - updatedAt: new Date() - }; - } } // Use MemStorage in development, DatabaseStorage in production diff --git a/shared/schema.ts b/shared/schema.ts index a2a98e3..6848689 100644 --- a/shared/schema.ts +++ b/shared/schema.ts @@ -143,16 +143,16 @@ export const publicityParticipations = pgTable("publicity_participations", { -// NocoDB configuration globale - Structure correspondant à la production +// NocoDB configuration globale (une seule instance NocoDB partagée) export const nocodbConfig = pgTable("nocodb_config", { id: serial("id").primaryKey(), - name: varchar("name", { length: 255 }).notNull(), // Nom de la configuration - baseUrl: varchar("base_url", { length: 255 }).notNull(), // URL de l'instance NocoDB - projectId: varchar("project_id", { length: 255 }).notNull(), // NocoDB Project ID - apiToken: varchar("api_token", { length: 255 }).notNull(), // Personal API Token + name: varchar("name").notNull(), // Nom de la configuration + baseUrl: varchar("base_url").notNull(), // URL de l'instance NocoDB + projectId: varchar("project_id").notNull(), // NocoDB Project ID + apiToken: varchar("api_token").notNull(), // Personal API Token description: text("description"), // Description de la configuration isActive: boolean("is_active").default(true), // Configuration active ou non - createdBy: varchar("created_by", { length: 255 }), // Créateur (nullable en production) + createdBy: varchar("created_by").notNull(), createdAt: timestamp("created_at").defaultNow(), updatedAt: timestamp("updated_at").defaultNow(), }); @@ -217,37 +217,6 @@ export const dlcProducts = pgTable("dlc_products", { updatedAt: timestamp("updated_at").defaultNow(), // Date de mise à jour }); -// Invoice Verification Cache - Table existante en production -export const invoiceVerificationCache = pgTable("invoice_verification_cache", { - id: serial("id").primaryKey(), - cacheKey: varchar("cache_key", { length: 255 }).notNull(), - groupId: integer("group_id").notNull(), - invoiceReference: varchar("invoice_reference", { length: 255 }).notNull(), - supplierName: varchar("supplier_name", { length: 255 }), - exists: boolean("exists").notNull(), - matchType: varchar("match_type", { length: 50 }).notNull(), - errorMessage: text("error_message"), - cacheHit: boolean("cache_hit").default(false), - apiCallTime: integer("api_call_time"), - expiresAt: timestamp("expires_at").notNull(), - createdAt: timestamp("created_at").defaultNow(), - updatedAt: timestamp("updated_at").defaultNow(), -}); - -// Invoice Verifications - Table existante en production -export const invoiceVerifications = pgTable("invoice_verifications", { - id: serial("id").primaryKey(), - deliveryId: integer("delivery_id").notNull(), - groupId: integer("group_id").notNull(), - invoiceReference: varchar("invoice_reference", { length: 255 }).notNull(), - supplierName: varchar("supplier_name", { length: 255 }), - exists: boolean("exists").notNull(), - matchType: varchar("match_type", { length: 50 }), - verifiedAt: timestamp("verified_at").defaultNow(), - isValid: boolean("is_valid").default(true), - lastCheckedAt: timestamp("last_checked_at").defaultNow(), -}); - // Tasks table export const tasks = pgTable("tasks", { id: serial("id").primaryKey(), @@ -512,18 +481,6 @@ export const insertDlcProductFrontendSchema = insertDlcProductSchema .omit({ expiryDate: true }) .extend({ dlcDate: z.coerce.date() }); -export const insertInvoiceVerificationCacheSchema = createInsertSchema(invoiceVerificationCache).omit({ - id: true, - createdAt: true, - updatedAt: true, -}); - -export const insertInvoiceVerificationsSchema = createInsertSchema(invoiceVerifications).omit({ - id: true, - verifiedAt: true, - lastCheckedAt: true, -}); - export const insertTaskSchema = createInsertSchema(tasks).omit({ id: true, createdAt: true, @@ -559,10 +516,6 @@ export type DlcProduct = typeof dlcProducts.$inferSelect; export type InsertDlcProduct = z.infer; export type DlcProductFrontend = Omit & { dlcDate: Date }; export type InsertDlcProductFrontend = z.infer; -export type InvoiceVerificationCache = typeof invoiceVerificationCache.$inferSelect; -export type InsertInvoiceVerificationCache = z.infer; -export type InvoiceVerification = typeof invoiceVerifications.$inferSelect; -export type InsertInvoiceVerification = z.infer; export type Task = typeof tasks.$inferSelect; export type InsertTask = z.infer; diff --git a/tmp/nocodb-setup/COMPLETE_SETUP.md b/tmp/nocodb-setup/COMPLETE_SETUP.md deleted file mode 100644 index 23c8f93..0000000 --- a/tmp/nocodb-setup/COMPLETE_SETUP.md +++ /dev/null @@ -1,107 +0,0 @@ -# Guide Complet - Configuration NocoDB Production - -## ❌ PROBLÈME IDENTIFIÉ - -Votre serveur de production n'a **aucune configuration NocoDB** dans la base de données. C'est pourquoi vous ne voyez rien. - -## ✅ SOLUTION : Configuration Complète - -### Étape 1 : Ajout Configuration NocoDB - -Connectez-vous à votre base PostgreSQL de production et exécutez : - -```sql --- 1. Vérifier si la table existe -\d nocodb_config - --- 2. Insérer votre configuration NocoDB -INSERT INTO nocodb_config ( - name, - base_url, - project_id, - api_token, - description, - is_active -) VALUES ( - 'Production Principal', - 'https://app.nocodb.com', -- ou votre URL NocoDB - 'VOTRE_PROJECT_ID', -- À remplacer - 'VOTRE_API_TOKEN', -- À remplacer - 'Configuration principale pour vérification automatique factures', - true -); - --- 3. Vérifier l'insertion -SELECT * FROM nocodb_config; -``` - -### Étape 2 : Configuration des Magasins - -```sql --- Lister vos magasins existants -SELECT id, name FROM groups; - --- Configurer chaque magasin (exemple pour ID 1) -UPDATE groups SET - nocodb_config_id = 1, -- ID de la config créée - nocodb_table_name = 'VOTRE_TABLE_FACTURES', -- Nom réel de votre table NocoDB - invoice_column_name = 'reference_facture', -- Colonne référence - nocodb_bl_column_name = 'numero_bl', -- Colonne BL - nocodb_amount_column_name = 'montant', -- Colonne montant - nocodb_supplier_column_name = 'fournisseur' -- Colonne fournisseur -WHERE id = 1; -- Remplacer par l'ID réel - --- Répéter pour chaque magasin avec ses propres paramètres -``` - -### Étape 3 : Obtenir les Informations NocoDB - -1. **Project ID** : Dans NocoDB, aller dans Project Settings -2. **API Token** : Account Settings > Tokens > Create new token -3. **Table Names** : Noter les noms exacts de vos tables -4. **Column Names** : Noter les noms exacts de vos colonnes - -### Étape 4 : Test de Configuration - -Une fois configuré, redémarrez votre serveur et vous devriez voir : -- Boutons loupe et coche dans le rapprochement -- Logs de débogage dans la console serveur -- Notifications de succès/erreur - -### Étape 5 : Vérification - -```sql --- Voir toutes les configurations -SELECT - g.id, - g.name as magasin, - g.nocodb_config_id, - g.nocodb_table_name, - g.invoice_column_name, - nc.base_url, - nc.is_active -FROM groups g -LEFT JOIN nocodb_config nc ON g.nocodb_config_id = nc.id; -``` - -## 🔧 Structure Attendue de vos Tables NocoDB - -Chaque table de factures doit contenir au minimum : -- **Référence Facture** (ex: FAC-2024-001) -- **Numéro BL** (ex: BL123456) -- **Montant** (ex: 150.75) -- **Fournisseur** (ex: "Fournisseur ABC") - -## 🚨 Points Critiques - -1. **API Token** : Doit avoir accès lecture à vos tables -2. **Noms Exacts** : Table/colonne names doivent être exacts (sensible à la casse) -3. **Données Test** : Ayez quelques factures test dans NocoDB -4. **Connexion** : Vérifiez que votre serveur peut accéder à NocoDB - -## 📞 Support - -Si ça ne marche toujours pas après configuration : -1. Vérifiez les logs serveur Node.js -2. Testez l'API NocoDB manuellement avec curl -3. Confirmez que les noms de colonnes sont corrects \ No newline at end of file diff --git a/tmp/nocodb-setup/SETUP_PRODUCTION.md b/tmp/nocodb-setup/SETUP_PRODUCTION.md deleted file mode 100644 index fa0e034..0000000 --- a/tmp/nocodb-setup/SETUP_PRODUCTION.md +++ /dev/null @@ -1,80 +0,0 @@ -# Configuration NocoDB pour Production - -## 1. Configuration de la base de données - -Votre table `nocodb_config` existe déjà. Vous devez y ajouter une configuration : - -```sql --- Insertion d'une configuration NocoDB de base -INSERT INTO nocodb_config ( - name, - base_url, - project_id, - api_token, - description, - is_active, - created_by, - created_at, - updated_at -) VALUES ( - 'Configuration Principale', - 'https://votre-nocodb.domain.com', - 'votre-project-id', - 'votre-api-token-personnel', - 'Configuration principale pour vérification factures', - true, - 'admin', - CURRENT_TIMESTAMP, - CURRENT_TIMESTAMP -); -``` - -## 2. Configuration des groupes (magasins) - -Mettez à jour vos magasins avec les configurations NocoDB : - -```sql --- Exemple pour Magasin A -UPDATE groups SET - nocodb_config_id = 1, -- ID de la config NocoDB créée ci-dessus - nocodb_table_name = 'nom_table_factures_magasin_a', -- Nom de votre table NocoDB - invoice_column_name = 'reference_facture', -- Colonne référence facture - nocodb_bl_column_name = 'numero_bl', -- Colonne numéro BL - nocodb_amount_column_name = 'montant', -- Colonne montant - nocodb_supplier_column_name = 'fournisseur' -- Colonne fournisseur -WHERE id = 1; -- Remplacer par l'ID de votre magasin - --- Répéter pour chaque magasin avec ses propres noms de table/colonnes -``` - -## 3. API Token NocoDB - -Pour obtenir votre API Token : -1. Allez dans NocoDB > Account Settings > Tokens -2. Créez un nouveau Personal API Token -3. Copiez le token dans la configuration ci-dessus - -## 4. Structure de votre table NocoDB - -Assurez-vous que vos tables NocoDB contiennent ces colonnes : -- Référence facture (ex: `reference_facture`) -- Numéro BL (ex: `numero_bl`) -- Montant (ex: `montant`) -- Fournisseur (ex: `fournisseur`) - -## 5. Test de la configuration - -Une fois configuré, le système : -- ✅ Affichera des boutons de vérification dans le rapprochement BL/Factures -- ✅ Vérifiera automatiquement les références factures existantes -- ✅ Auto-remplira les champs manquants en recherchant par BL -- ✅ Affichera des coches vertes pour les factures vérifiées - -## 6. Débogage - -Les logs de production afficheront : -``` -🔍 Vérification facture: {groupId: 1, invoiceReference: "FAC123", supplierName: "Fournisseur A"} -🌐 Requête NocoDB: https://votre-nocodb.domain.com/api/v1/db/data/project-id/table-name?where=... -✅ Réponse NocoDB: {count: 1} -``` \ No newline at end of file diff --git a/tmp/nocodb-setup/TEST_PRODUCTION.md b/tmp/nocodb-setup/TEST_PRODUCTION.md deleted file mode 100644 index 222270c..0000000 --- a/tmp/nocodb-setup/TEST_PRODUCTION.md +++ /dev/null @@ -1,70 +0,0 @@ -# Test de la Configuration Production - -## Système Mis en Place - -✅ **Service de Vérification Production** : -- Utilise vos tables `INVOICE_VERIFICATION_CACHE` et `INVOICE_VERIFICATIONS` pour le cache -- Fait les appels réels à NocoDB pour récupérer les données factures -- Sauvegarde les résultats dans le cache (1 heure d'expiration) - -✅ **Interface Utilisateur** : -- Boutons de vérification (loupe et coche) dans le rapprochement BL/Factures -- Coches vertes pour les factures vérifiées -- Messages d'erreur explicites - -## Configuration Requise sur Votre Serveur - -**1. Configuration NocoDB :** -```sql --- Insérer votre configuration -INSERT INTO nocodb_config ( - name, base_url, project_id, api_token, description, is_active -) VALUES ( - 'Production NocoDB', - 'https://app.nocodb.com', -- Votre URL NocoDB - 'p_votre_project_id', -- Votre Project ID - 'nc_votre_api_token', -- Votre API Token - 'Configuration production pour vérification factures', - true -); -``` - -**2. Configuration Groupes/Magasins :** -```sql --- Pour chaque magasin -UPDATE groups SET - nocodb_config_id = 1, -- ID de votre config NocoDB - nocodb_table_name = 'nom_de_votre_table_factures', - invoice_column_name = 'nom_colonne_reference_facture', - nocodb_bl_column_name = 'nom_colonne_numero_bl', - nocodb_amount_column_name = 'nom_colonne_montant', - nocodb_supplier_column_name = 'nom_colonne_fournisseur' -WHERE id = 1; -- ID de votre magasin -``` - -## Test du Système - -Une fois configuré, vous devriez voir : -1. **Boutons** : loupe et coche dans la colonne "Actions" du rapprochement -2. **Vérification** : cliquer la loupe vérifie la facture dans NocoDB -3. **Auto-remplissage** : cliquer la coche recherche par BL et remplit automatiquement -4. **Coches vertes** : apparaissent quand la vérification réussit -5. **Logs** : dans la console serveur vous verrez les appels NocoDB - -## Logs de Débogage - -Surveillez ces logs dans votre console serveur : -``` -🔍 Production Verification - Invoice: FAC123, Group: 1, Supplier: Fournisseur A -🌐 Production NocoDB Call - Table: ma_table_factures, Invoice: FAC123 -🌐 Production NocoDB URL: https://app.nocodb.com/api/v1/db/data/... -✅ Production NocoDB Response: {list: [...]} -💾 Production Cache Saved - Key: 1_FAC123_Fournisseur A, Exists: true -``` - -## Dépannage - -Si ça ne marche pas : -1. **Pas de boutons** : Configuration manquante dans la base -2. **Erreurs NocoDB** : Vérifiez l'API token et les noms de tables/colonnes -3. **Pas de coches** : Vérifiez les logs serveur pour voir les erreurs \ No newline at end of file