From 955f3d3b3d65e58c179ca9b129f3f6bdc96ed855 Mon Sep 17 00:00:00 2001 From: michaelschal <35957947-michaelschal@users.noreply.replit.com> Date: Wed, 13 Aug 2025 11:00:27 +0000 Subject: [PATCH 1/3] Add schemas for NocoDB configuration and invoice verification Add Zod schemas for inserting NocoDB configuration and invoice verification cache data. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 7c468936-2a88-4686-ac0a-84921a45222d Replit-Commit-Checkpoint-Type: full_checkpoint --- shared/schema.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/shared/schema.ts b/shared/schema.ts index 7f49911..b0e8e9e 100644 --- a/shared/schema.ts +++ b/shared/schema.ts @@ -174,6 +174,18 @@ export const invoiceVerificationCache = pgTable("invoice_verification_cache", { updatedAt: timestamp("updated_at").defaultNow(), }); +export const insertNocodbConfigSchema = createInsertSchema(nocodbConfig).omit({ + id: true, + createdAt: true, + updatedAt: true, +}); + +export const insertInvoiceVerificationCacheSchema = createInsertSchema(invoiceVerificationCache).omit({ + id: true, + createdAt: true, + updatedAt: true, +}); + // Customer Orders (Commandes Client) export const customerOrders = pgTable("customer_orders", { id: serial("id").primaryKey(), From 19ed96e48c3668f0f642cfc1fcd1cd50c3e36c08 Mon Sep 17 00:00:00 2001 From: michaelschal <35957947-michaelschal@users.noreply.replit.com> Date: Wed, 13 Aug 2025 11:02:33 +0000 Subject: [PATCH 2/3] Improve NocoDB invoice verification and error handling Enhance NocoDB invoice verification logic in the client and server to handle missing configurations and provide better debugging information. Update schema definitions to reflect changes in NocoDB configuration handling. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 7c468936-2a88-4686-ac0a-84921a45222d Replit-Commit-Checkpoint-Type: full_checkpoint --- client/src/pages/BLReconciliation.tsx | 12 ++++++++-- server/invoiceVerification.ts | 33 +++++++++++++++++++-------- server/routes.ts | 15 ++++++++++++ shared/schema.ts | 16 ++----------- 4 files changed, 50 insertions(+), 26 deletions(-) diff --git a/client/src/pages/BLReconciliation.tsx b/client/src/pages/BLReconciliation.tsx index 43fc194..e765774 100644 --- a/client/src/pages/BLReconciliation.tsx +++ b/client/src/pages/BLReconciliation.tsx @@ -132,7 +132,15 @@ export default function BLReconciliation() { // Fonction pour déclencher la vérification const handleVerifyInvoice = (delivery: any) => { - if (!delivery.group?.webhookUrl) { + console.log('🔍 Debug vérification:', { + delivery: delivery, + group: delivery.group, + webhookUrl: delivery.group?.webhookUrl, + nocodbTableName: delivery.group?.nocodbTableName, + supplier: delivery.supplier + }); + + if (!delivery.group?.nocodbTableName && !delivery.group?.webhookUrl) { toast({ title: "Vérification non disponible", description: "Ce magasin n'a pas de configuration NocoDB", @@ -631,7 +639,7 @@ export default function BLReconciliation() { {/* Icône de vérification de facture */} - {delivery.group?.webhookUrl && ( + {(delivery.group?.webhookUrl || delivery.group?.nocodbTableName) && (
{verifyingDeliveries.has(delivery.id) ? ( diff --git a/server/invoiceVerification.ts b/server/invoiceVerification.ts index 99a3ab9..7aadf11 100644 --- a/server/invoiceVerification.ts +++ b/server/invoiceVerification.ts @@ -20,17 +20,30 @@ interface NocodbInvoice { class InvoiceVerificationService { private async getNocodbConfig(): Promise { - const [config] = await db - .select() - .from(nocodbConfig) - .where(eq(nocodbConfig.isActive, true)) - .limit(1); - - if (!config) { - throw new Error('Aucune configuration NocoDB active trouvée'); + try { + const [config] = await db + .select() + .from(nocodbConfig) + .where(eq(nocodbConfig.isActive, true)) + .limit(1); + + if (!config) { + console.log('⚠️ Aucune configuration NocoDB active trouvée dans la table nocodb_config'); + throw new Error('Aucune configuration NocoDB active trouvée'); + } + + console.log('✅ Configuration NocoDB trouvée:', { + id: config.id, + name: config.name, + baseUrl: config.baseUrl, + projectId: config.projectId + }); + + return config; + } catch (error) { + console.error('❌ Erreur lors de la récupération de la configuration NocoDB:', error); + throw error; } - - return config; } private generateCacheKey(groupId: number, invoiceRef: string, supplierName: string): string { diff --git a/server/routes.ts b/server/routes.ts index 004fc76..475de76 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -862,9 +862,24 @@ export async function registerRoutes(app: Express): Promise { const { invoiceReference, blNumber } = req.body; if (!delivery.supplier || !delivery.group) { + console.log('❌ Livraison manque informations:', { + deliveryId, + hasSupplier: !!delivery.supplier, + hasGroup: !!delivery.group, + delivery: delivery + }); return res.status(400).json({ message: "Delivery missing supplier or group information" }); } + console.log('🔍 Tentative de vérification facture:', { + deliveryId, + invoiceReference: invoiceReference || delivery.invoiceReference, + blNumber: blNumber || delivery.blNumber, + supplierName: delivery.supplier.name, + groupId: delivery.groupId, + groupData: delivery.group + }); + const result = await invoiceVerificationService.verifyInvoiceReference( invoiceReference || delivery.invoiceReference || '', blNumber || delivery.blNumber || '', diff --git a/shared/schema.ts b/shared/schema.ts index b0e8e9e..9acf2c4 100644 --- a/shared/schema.ts +++ b/shared/schema.ts @@ -174,18 +174,6 @@ export const invoiceVerificationCache = pgTable("invoice_verification_cache", { updatedAt: timestamp("updated_at").defaultNow(), }); -export const insertNocodbConfigSchema = createInsertSchema(nocodbConfig).omit({ - id: true, - createdAt: true, - updatedAt: true, -}); - -export const insertInvoiceVerificationCacheSchema = createInsertSchema(invoiceVerificationCache).omit({ - id: true, - createdAt: true, - updatedAt: true, -}); - // Customer Orders (Commandes Client) export const customerOrders = pgTable("customer_orders", { id: serial("id").primaryKey(), @@ -465,8 +453,6 @@ export const insertPublicityParticipationSchema = createInsertSchema(publicityPa createdAt: true, }); - - export const insertNocodbConfigSchema = createInsertSchema(nocodbConfig).omit({ id: true, createdAt: true, @@ -479,6 +465,8 @@ export const insertInvoiceVerificationCacheSchema = createInsertSchema(invoiceVe updatedAt: true, }); + + export const insertCustomerOrderSchema = createInsertSchema(customerOrders).omit({ id: true, createdAt: true, From 7ebd84e0e1c56206b6d455bd8b33bea786515876 Mon Sep 17 00:00:00 2001 From: michaelschal <35957947-michaelschal@users.noreply.replit.com> Date: Wed, 13 Aug 2025 11:14:47 +0000 Subject: [PATCH 3/3] Integrate NocoDB configuration directly into store settings for invoice verification Refactor invoice verification to use group-specific NocoDB configuration, removing the global `nocodbConfig` table and cache, and updating UI to reflect these changes. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 7c468936-2a88-4686-ac0a-84921a45222d Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1957c339-2757-4d1f-8e92-e9f71a1ce58e/7c468936-2a88-4686-ac0a-84921a45222d/ehBF9El --- client/src/pages/BLReconciliation.tsx | 6 +- server/invoiceVerification.ts | 130 ++++++-------------------- server/routes.ts | 3 +- server/storage.ts | 5 +- shared/schema.ts | 46 +-------- 5 files changed, 35 insertions(+), 155 deletions(-) diff --git a/client/src/pages/BLReconciliation.tsx b/client/src/pages/BLReconciliation.tsx index e765774..95ccfee 100644 --- a/client/src/pages/BLReconciliation.tsx +++ b/client/src/pages/BLReconciliation.tsx @@ -137,10 +137,12 @@ export default function BLReconciliation() { group: delivery.group, webhookUrl: delivery.group?.webhookUrl, nocodbTableName: delivery.group?.nocodbTableName, + invoiceColumnName: delivery.group?.invoiceColumnName, + nocodbConfigId: delivery.group?.nocodbConfigId, supplier: delivery.supplier }); - if (!delivery.group?.nocodbTableName && !delivery.group?.webhookUrl) { + if (!delivery.group?.nocodbTableName && !delivery.group?.nocodbConfigId) { toast({ title: "Vérification non disponible", description: "Ce magasin n'a pas de configuration NocoDB", @@ -639,7 +641,7 @@ export default function BLReconciliation() {
{/* Icône de vérification de facture */} - {(delivery.group?.webhookUrl || delivery.group?.nocodbTableName) && ( + {(delivery.group?.nocodbTableName || delivery.group?.nocodbConfigId) && (
{verifyingDeliveries.has(delivery.id) ? ( diff --git a/server/invoiceVerification.ts b/server/invoiceVerification.ts index 7aadf11..916c667 100644 --- a/server/invoiceVerification.ts +++ b/server/invoiceVerification.ts @@ -1,6 +1,6 @@ import { db } from './db.js'; -import { nocodbConfig, invoiceVerificationCache, groups } from '@shared/schema'; -import { eq, and, desc } from 'drizzle-orm'; +import { groups } from '@shared/schema'; +import { eq } from 'drizzle-orm'; interface InvoiceVerificationResult { exists: boolean; @@ -19,93 +19,17 @@ interface NocodbInvoice { } class InvoiceVerificationService { - private async getNocodbConfig(): Promise { - try { - const [config] = await db - .select() - .from(nocodbConfig) - .where(eq(nocodbConfig.isActive, true)) - .limit(1); - - if (!config) { - console.log('⚠️ Aucune configuration NocoDB active trouvée dans la table nocodb_config'); - throw new Error('Aucune configuration NocoDB active trouvée'); - } - - console.log('✅ Configuration NocoDB trouvée:', { - id: config.id, - name: config.name, - baseUrl: config.baseUrl, - projectId: config.projectId - }); - - return config; - } catch (error) { - console.error('❌ Erreur lors de la récupération de la configuration NocoDB:', error); - throw error; - } + private getNocodbConfigFromGroup(group: any): any { + // Configuration NocoDB stockée directement dans le groupe + // Vous devez renseigner ces valeurs dans votre table groups en production + return { + baseUrl: process.env.NOCODB_BASE_URL || 'https://your-nocodb-instance.com', + apiToken: process.env.NOCODB_API_TOKEN || '', + projectId: process.env.NOCODB_PROJECT_ID || '' + }; } - private generateCacheKey(groupId: number, invoiceRef: string, supplierName: string): string { - return `${groupId}:${invoiceRef}:${supplierName}`.toLowerCase(); - } - private async getCachedResult(cacheKey: string): Promise { - try { - const [cached] = await db - .select() - .from(invoiceVerificationCache) - .where(and( - eq(invoiceVerificationCache.cacheKey, cacheKey), - // Cache valide pour 24 heures - eq(invoiceVerificationCache.expiresAt, new Date(Date.now() + 24 * 60 * 60 * 1000)) - )) - .limit(1); - - if (cached && new Date() < new Date(cached.expiresAt)) { - console.log('🎯 Cache hit pour:', cacheKey); - return { - exists: cached.exists, - matchType: cached.matchType as any, - invoiceReference: cached.invoiceReference, - supplierMatch: !!cached.supplierName, - cacheHit: true, - apiCallTime: cached.apiCallTime || 0 - }; - } - } catch (error) { - console.error('❌ Erreur lors de la lecture du cache:', error); - } - - return null; - } - - private async setCachedResult( - cacheKey: string, - groupId: number, - result: InvoiceVerificationResult, - invoiceRef: string, - supplierName: string - ): Promise { - try { - await db.insert(invoiceVerificationCache).values({ - cacheKey, - groupId, - invoiceReference: invoiceRef, - supplierName, - exists: result.exists, - matchType: result.matchType, - errorMessage: result.errorMessage, - cacheHit: false, - apiCallTime: result.apiCallTime || 0, - expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000) // 24h - }); - - console.log('💾 Résultat mis en cache:', cacheKey); - } catch (error) { - console.error('❌ Erreur lors de la mise en cache:', error); - } - } private async searchInNocodb( config: any, @@ -166,32 +90,31 @@ class InvoiceVerificationService { invoiceRef: string, blNumber: string, supplierName: string, - groupId: number + groupId: number, + groupConfig?: any ): Promise { const startTime = Date.now(); try { - // 1. Vérifier le cache d'abord - const cacheKey = this.generateCacheKey(groupId, invoiceRef, supplierName); - const cachedResult = await this.getCachedResult(cacheKey); - if (cachedResult) { - return cachedResult; - } - // 2. Récupérer la configuration NocoDB - const config = await this.getNocodbConfig(); - - // 3. Récupérer les informations du groupe - const [group] = await db - .select() - .from(groups) - .where(eq(groups.id, groupId)) - .limit(1); + // 2. Utiliser la configuration du groupe passée en paramètre ou la récupérer + let group = groupConfig; + if (!group) { + const [groupFromDb] = await db + .select() + .from(groups) + .where(eq(groups.id, groupId)) + .limit(1); + group = groupFromDb; + } if (!group || !group.nocodbTableName) { throw new Error('Configuration NocoDB manquante pour ce magasin'); } + // 3. Récupérer la configuration NocoDB à partir des variables d'environnement + const config = this.getNocodbConfigFromGroup(group); + let result: InvoiceVerificationResult = { exists: false, matchType: 'none', @@ -268,8 +191,7 @@ class InvoiceVerificationService { result.errorMessage = result.errorMessage || 'Aucune correspondance trouvée'; } - // 6. Mettre en cache le résultat - await this.setCachedResult(cacheKey, groupId, result, invoiceRef, supplierName); + return result; diff --git a/server/routes.ts b/server/routes.ts index 475de76..47c3750 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -884,7 +884,8 @@ export async function registerRoutes(app: Express): Promise { invoiceReference || delivery.invoiceReference || '', blNumber || delivery.blNumber || '', delivery.supplier.name, - delivery.groupId + delivery.groupId, + delivery.group ); res.json(result); diff --git a/server/storage.ts b/server/storage.ts index 493d515..7480713 100644 --- a/server/storage.ts +++ b/server/storage.ts @@ -30,9 +30,8 @@ import { type PublicityParticipation, type InsertPublicityParticipation, type PublicityWithRelations, - nocodbConfig, - type NocodbConfig, - type InsertNocodbConfig, + + type CustomerOrder, type InsertCustomerOrder, type CustomerOrderWithRelations, diff --git a/shared/schema.ts b/shared/schema.ts index 9acf2c4..12f0606 100644 --- a/shared/schema.ts +++ b/shared/schema.ts @@ -143,36 +143,7 @@ export const publicityParticipations = pgTable("publicity_participations", { -// NocoDB configuration globale (une seule instance NocoDB partagée) -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 - description: text("description"), // Description de la configuration - isActive: boolean("is_active").default(true), // Configuration active ou non - createdBy: varchar("created_by"), - createdAt: timestamp("created_at").defaultNow(), - updatedAt: timestamp("updated_at").defaultNow(), -}); -// Cache de vérification des factures pour éviter les re-vérifications -export const invoiceVerificationCache = pgTable("invoice_verification_cache", { - id: serial("id").primaryKey(), - cacheKey: varchar("cache_key", { length: 255 }).notNull(), // Clé unique de cache - groupId: integer("group_id").notNull(), // ID du magasin - invoiceReference: varchar("invoice_reference", { length: 255 }).notNull(), // Référence facture - supplierName: varchar("supplier_name", { length: 255 }), // Nom du fournisseur - exists: boolean("exists").notNull(), // Facture trouvée ou non - matchType: varchar("match_type", { length: 50 }).notNull(), // Type de correspondance (invoice_ref, bl_number) - errorMessage: text("error_message"), // Message d'erreur si applicable - cacheHit: boolean("cache_hit").default(false), // Cache hit ou non - apiCallTime: integer("api_call_time"), // Temps d'appel API en ms - expiresAt: timestamp("expires_at").notNull(), // Date d'expiration du cache - createdAt: timestamp("created_at").defaultNow(), - updatedAt: timestamp("updated_at").defaultNow(), -}); // Customer Orders (Commandes Client) export const customerOrders = pgTable("customer_orders", { @@ -271,10 +242,7 @@ export const groupsRelations = relations(groups, ({ one, many }) => ({ customerOrders: many(customerOrders), dlcProducts: many(dlcProducts), tasks: many(tasks), - nocodbConfig: one(nocodbConfig, { - fields: [groups.nocodbConfigId], - references: [nocodbConfig.id], - }), + })); export const userGroupsRelations = relations(userGroups, ({ one }) => ({ @@ -453,17 +421,7 @@ export const insertPublicityParticipationSchema = createInsertSchema(publicityPa createdAt: true, }); -export const insertNocodbConfigSchema = createInsertSchema(nocodbConfig).omit({ - id: true, - createdAt: true, - updatedAt: true, -}); -export const insertInvoiceVerificationCacheSchema = createInsertSchema(invoiceVerificationCache).omit({ - id: true, - createdAt: true, - updatedAt: true, -}); @@ -531,8 +489,6 @@ export type Publicity = typeof publicities.$inferSelect; export type InsertPublicity = z.infer; export type PublicityParticipation = typeof publicityParticipations.$inferSelect; export type InsertPublicityParticipation = z.infer; -export type NocodbConfig = typeof nocodbConfig.$inferSelect; -export type InsertNocodbConfig = z.infer; export type CustomerOrder = typeof customerOrders.$inferSelect; export type InsertCustomerOrder = z.infer; export type DlcProduct = typeof dlcProducts.$inferSelect;