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 1bc9d43..54d1bad 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 || 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 { + ): 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 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