From e6b09fc9d381024e52353a2b87181f8047cd7d4d Mon Sep 17 00:00:00 2001 From: michaelschal <35957947-michaelschal@users.noreply.replit.com> Date: Wed, 13 Aug 2025 09:18:09 +0000 Subject: [PATCH 1/2] Refactor NocoDB verification service for improved configuration handling Update `NocodbVerificationService` to load `NocodbConfig` and `GroupConfig` separately, enhancing error handling and providing default column names. Replit-Commit-Author: Agent Replit-Commit-Session-Id: c52a701b-4841-4d00-b8cf-5a41c12abdf3 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1957c339-2757-4d1f-8e92-e9f71a1ce58e/c52a701b-4841-4d00-b8cf-5a41c12abdf3/3uCfEKb --- server/services/nocodbVerification.ts | 340 +++++++++++++------------- 1 file changed, 167 insertions(+), 173 deletions(-) diff --git a/server/services/nocodbVerification.ts b/server/services/nocodbVerification.ts index 1bc9d43..c204c08 100644 --- a/server/services/nocodbVerification.ts +++ b/server/services/nocodbVerification.ts @@ -1,212 +1,206 @@ -import { storage } from '../storage'; +import { storage } from "../storage"; -export interface InvoiceVerificationResult { - found: boolean; - invoiceReference?: string; - invoiceAmount?: string; - supplierMatch: boolean; - error?: string; +interface NocodbConfig { + id: number; + baseUrl: string; + apiToken: string; + projectId: string; + isActive: boolean; } -export interface BLSearchResult { - found: boolean; - invoiceReference?: string; - invoiceAmount?: string; - error?: string; +interface GroupConfig { + nocodbConfigId?: number; + nocodbTableName?: string; + invoiceColumnName?: string; + nocodbBlColumnName?: string; + nocodbAmountColumnName?: string; + nocodbSupplierColumnName?: string; } -export class NocoDBVerificationService { - - /** - * Vérifie l'existence d'une référence de facture dans NocoDB pour un magasin donné - */ +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, + nocodbTableName: group.nocodbTableName, + 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 { + ): Promise<{ found: boolean; supplierMatch: boolean; data?: any }> { try { - console.log(`🔍 Vérification facture ${invoiceReference} pour fournisseur ${supplierName} (magasin ${groupId})`); + 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 || []; - // Récupérer la configuration du magasin - const group = await storage.getGroup(groupId); - if (!group?.nocodbConfigId || !group.nocodbTableName) { - console.log(`❌ Pas de configuration NocoDB pour le magasin ${groupId}`); - return { - found: false, - supplierMatch: false, - error: 'Configuration NocoDB manquante pour ce magasin' - }; + if (records.length === 0) { + return { found: false, supplierMatch: false }; } - // Récupérer la configuration NocoDB globale - const nocodbConfig = await storage.getNocodbConfig(group.nocodbConfigId); - if (!nocodbConfig) { - return { - found: false, - supplierMatch: false, - error: 'Configuration NocoDB introuvable' - }; - } - - // Construire l'URL de l'API NocoDB - const apiUrl = `${nocodbConfig.baseUrl}/api/v1/db/data/v1/${nocodbConfig.projectId}/${group.nocodbTableName}`; + // Vérifier la correspondance du fournisseur + const record = records[0]; + const recordSupplier = record[groupConfig.nocodbSupplierColumnName!]; - // Construire les paramètres de recherche - const searchParams = new URLSearchParams(); - if (group.invoiceColumnName) { - searchParams.append('where', `(${group.invoiceColumnName},eq,${invoiceReference})`); - } - - const finalUrl = `${apiUrl}?${searchParams.toString()}`; - console.log(`🌐 Requête NocoDB: ${finalUrl}`); - - // Faire la requête à NocoDB - const response = await fetch(finalUrl, { - headers: { - 'xc-token': nocodbConfig.apiToken, - 'Content-Type': 'application/json' - } - }); - - if (!response.ok) { - console.error(`❌ Erreur NocoDB (${response.status}):`, await response.text()); - return { - found: false, - supplierMatch: false, - error: `Erreur NocoDB: ${response.status}` - }; - } - - const data = await response.json(); - console.log(`📊 Réponse NocoDB:`, { found: data.list?.length > 0, count: data.list?.length }); - - if (data.list && data.list.length > 0) { - const record = data.list[0]; - - // Vérifier si le fournisseur correspond - let supplierMatch = true; - if (group.nocodbSupplierColumnName) { - const nocodbSupplier = record[group.nocodbSupplierColumnName]; - supplierMatch = nocodbSupplier === supplierName; - console.log(`🏷️ Vérification fournisseur:`, { - expected: supplierName, - found: nocodbSupplier, - match: supplierMatch - }); - } - - return { - found: true, - invoiceReference, - invoiceAmount: group.nocodbAmountColumnName ? record[group.nocodbAmountColumnName]?.toString() : undefined, - supplierMatch - }; - } + const supplierMatch = recordSupplier && + recordSupplier.toString().toLowerCase().includes(supplierName.toLowerCase()); return { - found: false, - supplierMatch: false + found: true, + supplierMatch: !!supplierMatch, + data: record }; } catch (error) { - console.error('❌ Erreur lors de la vérification facture:', error); - return { - found: false, - supplierMatch: false, - error: `Erreur: ${error instanceof Error ? error.message : 'Erreur inconnue'}` - }; + console.error('❌ Erreur vérification facture:', error); + return { found: false, supplierMatch: false }; } } - /** - * Recherche une facture par numéro de BL et fournisseur - */ async searchByBLNumber( groupId: number, blNumber: string, supplierName: string - ): Promise { + ): Promise<{ found: boolean; invoiceReference?: string; invoiceAmount?: number; data?: any }> { try { - console.log(`🔍 Recherche par BL ${blNumber} pour fournisseur ${supplierName} (magasin ${groupId})`); + 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 || []; - // Récupérer la configuration du magasin - const group = await storage.getGroup(groupId); - if (!group?.nocodbConfigId || !group.nocodbTableName || !group.nocodbBlColumnName) { - console.log(`❌ Configuration BL manquante pour le magasin ${groupId}`); - return { - found: false, - error: 'Configuration NocoDB pour recherche BL manquante' - }; + if (records.length === 0) { + return { found: false }; } - // Récupérer la configuration NocoDB globale - const nocodbConfig = await storage.getNocodbConfig(group.nocodbConfigId); - if (!nocodbConfig) { - return { - found: false, - error: 'Configuration NocoDB introuvable' - }; - } - - // Construire l'URL de l'API NocoDB pour recherche par BL - const apiUrl = `${nocodbConfig.baseUrl}/api/v1/db/data/v1/${nocodbConfig.projectId}/${group.nocodbTableName}`; - - // Construire les paramètres de recherche par BL et éventuellement fournisseur - const searchParams = new URLSearchParams(); - let whereClause = `(${group.nocodbBlColumnName},eq,${blNumber})`; - - // Ajouter la condition fournisseur si configurée - if (group.nocodbSupplierColumnName) { - whereClause += `~and(${group.nocodbSupplierColumnName},eq,${supplierName})`; - } - - searchParams.append('where', whereClause); - - const finalUrl = `${apiUrl}?${searchParams.toString()}`; - console.log(`🌐 Requête NocoDB recherche BL: ${finalUrl}`); - - // Faire la requête à NocoDB - const response = await fetch(finalUrl, { - headers: { - 'xc-token': nocodbConfig.apiToken, - 'Content-Type': 'application/json' - } - }); - - if (!response.ok) { - console.error(`❌ Erreur NocoDB recherche BL (${response.status}):`, await response.text()); - return { - found: false, - error: `Erreur NocoDB: ${response.status}` - }; - } - - const data = await response.json(); - console.log(`📊 Réponse NocoDB recherche BL:`, { found: data.list?.length > 0, count: data.list?.length }); - - if (data.list && data.list.length > 0) { - const record = data.list[0]; - - return { - found: true, - invoiceReference: group.invoiceColumnName ? record[group.invoiceColumnName] : undefined, - invoiceAmount: group.nocodbAmountColumnName ? record[group.nocodbAmountColumnName]?.toString() : undefined - }; - } + const record = records[0]; + const invoiceReference = record[groupConfig.invoiceColumnName!]; + const invoiceAmount = record[groupConfig.nocodbAmountColumnName!]; return { - found: false + found: true, + invoiceReference: invoiceReference?.toString(), + invoiceAmount: invoiceAmount ? parseFloat(invoiceAmount.toString()) : undefined, + data: record }; } catch (error) { - console.error('❌ Erreur lors de la recherche par BL:', error); - return { - found: false, - error: `Erreur: ${error instanceof Error ? error.message : 'Erreur inconnue'}` - }; + console.error('❌ Erreur recherche par BL:', error); + return { found: false }; } } } -export const nocodbVerificationService = new NocoDBVerificationService(); \ No newline at end of file +export const nocodbVerificationService = new NocodbVerificationService(); \ No newline at end of file From 5a1693659122bcd1c745eeb02c96331227a755ac Mon Sep 17 00:00:00 2001 From: michaelschal <35957947-michaelschal@users.noreply.replit.com> Date: Wed, 13 Aug 2025 09:21:08 +0000 Subject: [PATCH 2/2] Improve NocoDB configuration handling for production deployments Update client and server logic to gracefully handle missing NocoDB configurations, providing more informative error messages and ensuring optional NocoDB fields are handled correctly. Replit-Commit-Author: Agent Replit-Commit-Session-Id: c52a701b-4841-4d00-b8cf-5a41c12abdf3 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1957c339-2757-4d1f-8e92-e9f71a1ce58e/c52a701b-4841-4d00-b8cf-5a41c12abdf3/3uCfEKb --- client/src/pages/BLReconciliation.tsx | 8 +- server/services/nocodbVerification.ts | 4 +- server/storage.ts | 33 ++++---- shared/schema.ts | 12 +-- tmp/nocodb-setup/COMPLETE_SETUP.md | 107 ++++++++++++++++++++++++++ tmp/nocodb-setup/SETUP_PRODUCTION.md | 80 +++++++++++++++++++ 6 files changed, 219 insertions(+), 25 deletions(-) create mode 100644 tmp/nocodb-setup/COMPLETE_SETUP.md create mode 100644 tmp/nocodb-setup/SETUP_PRODUCTION.md diff --git a/client/src/pages/BLReconciliation.tsx b/client/src/pages/BLReconciliation.tsx index be73d3a..1a47cf0 100644 --- a/client/src/pages/BLReconciliation.tsx +++ b/client/src/pages/BLReconciliation.tsx @@ -259,11 +259,11 @@ export default function BLReconciliation() { variant: "destructive" }); } - } catch (error) { + } catch (error: any) { console.error('Erreur vérification:', error); toast({ title: "Erreur de vérification", - description: "Impossible de vérifier la référence", + description: error.response?.data?.message || error.message || "Configuration NocoDB manquante ou incorrecte", variant: "destructive" }); } finally { @@ -315,11 +315,11 @@ export default function BLReconciliation() { variant: "destructive" }); } - } catch (error) { + } catch (error: any) { console.error('Erreur recherche BL:', error); toast({ title: "Erreur de recherche", - description: "Impossible de rechercher par BL", + description: error.response?.data?.message || error.message || "Configuration NocoDB manquante ou incorrecte", variant: "destructive" }); } finally { diff --git a/server/services/nocodbVerification.ts b/server/services/nocodbVerification.ts index c204c08..54d1bad 100644 --- a/server/services/nocodbVerification.ts +++ b/server/services/nocodbVerification.ts @@ -33,8 +33,8 @@ class NocodbVerificationService { if (!group) return null; return { - nocodbConfigId: group.nocodbConfigId, - nocodbTableName: group.nocodbTableName, + nocodbConfigId: group.nocodbConfigId || undefined, + nocodbTableName: group.nocodbTableName || undefined, invoiceColumnName: group.invoiceColumnName || 'invoice_reference', nocodbBlColumnName: group.nocodbBlColumnName || 'bl_number', nocodbAmountColumnName: group.nocodbAmountColumnName || 'amount', diff --git a/server/storage.ts b/server/storage.ts index 6da756d..20d7c46 100644 --- a/server/storage.ts +++ b/server/storage.ts @@ -1073,19 +1073,26 @@ export class DatabaseStorage implements IStorage { } async getNocodbConfig(id: number): Promise { - 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]; + 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; + } } async createNocodbConfig(config: InsertNocodbConfig): Promise { diff --git a/shared/schema.ts b/shared/schema.ts index 6848689..a2dd0f6 100644 --- a/shared/schema.ts +++ b/shared/schema.ts @@ -143,16 +143,16 @@ export const publicityParticipations = pgTable("publicity_participations", { -// NocoDB configuration globale (une seule instance NocoDB partagée) +// NocoDB configuration globale - Structure correspondant à la production export const nocodbConfig = pgTable("nocodb_config", { id: serial("id").primaryKey(), - 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 + 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 description: text("description"), // Description de la configuration isActive: boolean("is_active").default(true), // Configuration active ou non - createdBy: varchar("created_by").notNull(), + createdBy: varchar("created_by", { length: 255 }), // Créateur (nullable en production) createdAt: timestamp("created_at").defaultNow(), updatedAt: timestamp("updated_at").defaultNow(), }); diff --git a/tmp/nocodb-setup/COMPLETE_SETUP.md b/tmp/nocodb-setup/COMPLETE_SETUP.md new file mode 100644 index 0000000..23c8f93 --- /dev/null +++ b/tmp/nocodb-setup/COMPLETE_SETUP.md @@ -0,0 +1,107 @@ +# 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 new file mode 100644 index 0000000..fa0e034 --- /dev/null +++ b/tmp/nocodb-setup/SETUP_PRODUCTION.md @@ -0,0 +1,80 @@ +# 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