Merge pull request #88 from R0m1k3/1.1

Enable invoice verification using NocoDB for production environments
This commit is contained in:
LogiFlow authored and GitHub committed 2025-08-13 14:54:51 +02:00
commit 16c4a29291
5 files changed
+172 -33

No files matched your search

Binary file not shown.

After

Width:  |  Height:  |  Size: 543 KiB

+1 -1
View File
@@ -2,4 +2,4 @@
# https://curl.se/docs/http-cookies.html # https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk. # This file was generated by libcurl! Edit at your own risk.
#HttpOnly_localhost FALSE / FALSE 1755175240 connect.sid s%3AeEPGIwIyDfv0Lv1yl_mNnpvEfiTbtG2U.fIbC09urP9gGafY%2BLayxvJMSElehNNST%2BPA9zJDe%2Bx8 #HttpOnly_localhost FALSE / FALSE 1755175982 connect.sid s%3A55QseyVnXOsgNLS_il_ZNUUaswQTKyVl.ZO8oV5FOnntJvcJED2Tl8bgGphGUsDZ00lQOSVNGwD0
+126 -10
View File
@@ -1,10 +1,10 @@
import { storage } from "./storage"; import { storage } from "./storage.js";
/** /**
* Service de vérification des factures avec NocoDB * Service de vérification des factures avec NocoDB
* Gère la vérification automatique des références de factures * Gère la vérification automatique des références de factures
*/ */
class InvoiceVerificationService { export class InvoiceVerificationService {
/** /**
* Vérifie une référence de facture pour un groupe donné * Vérifie une référence de facture pour un groupe donné
@@ -87,14 +87,81 @@ class InvoiceVerificationService {
}; };
} }
// En production, on devrait faire l'appel réel à NocoDB // En production, faire l'appel réel à NocoDB
// Pour l'instant, retourner un résultat par défaut console.log('🔍 Vérification NocoDB en production...');
console.log('⚠️ Vérification NocoDB non implémentée en production');
return { try {
exists: false, // Récupérer la configuration NocoDB active
matchType: 'none', const nocodbConfig = await storage.getActiveNocodbConfig();
errorMessage: 'Service de vérification NocoDB en cours de configuration' if (!nocodbConfig) {
}; console.log('⚠️ Pas de configuration NocoDB active');
return {
exists: false,
matchType: 'none',
errorMessage: 'Configuration NocoDB non trouvée'
};
}
console.log('🔧 Configuration NocoDB trouvée:', {
configName: nocodbConfig.name,
baseUrl: nocodbConfig.baseUrl,
projectId: nocodbConfig.projectId,
hasToken: !!nocodbConfig.apiToken
});
// Vérifier d'abord par référence de facture
let matchResult = await this.searchInNocoDB(
invoiceReference,
group.invoiceColumnName || 'invoice_reference',
nocodbConfig,
group.nocodbTableName || 'invoices'
);
if (matchResult.found) {
return {
exists: true,
matchType: 'invoice_reference',
invoiceReference: matchResult.data.invoice_reference || invoiceReference,
invoiceAmount: parseFloat(matchResult.data[group.nocodbAmountColumnName || 'amount'] || '0'),
supplierName: matchResult.data[group.nocodbSupplierColumnName || 'supplier'] || 'Inconnu'
};
}
// Si pas trouvé par référence de facture, chercher par numéro de BL
if (group.nocodbBlColumnName) {
matchResult = await this.searchInNocoDB(
invoiceReference,
group.nocodbBlColumnName,
nocodbConfig,
group.nocodbTableName || 'invoices'
);
if (matchResult.found) {
return {
exists: true,
matchType: 'bl_number',
invoiceReference: matchResult.data.invoice_reference || `BL_${invoiceReference}`,
invoiceAmount: parseFloat(matchResult.data[group.nocodbAmountColumnName || 'amount'] || '0'),
supplierName: matchResult.data[group.nocodbSupplierColumnName || 'supplier'] || 'Inconnu'
};
}
}
// Aucune correspondance trouvée
return {
exists: false,
matchType: 'none',
errorMessage: 'Aucune correspondance trouvée dans NocoDB'
};
} catch (error) {
console.error('❌ Erreur lors de la vérification NocoDB:', error);
return {
exists: false,
matchType: 'none',
errorMessage: `Erreur NocoDB: ${error instanceof Error ? error.message : 'Erreur inconnue'}`
};
}
} catch (error) { } catch (error) {
console.error('❌ Erreur vérification facture:', error); console.error('❌ Erreur vérification facture:', error);
@@ -106,6 +173,55 @@ class InvoiceVerificationService {
} }
} }
/**
* Recherche dans NocoDB
*/
private async searchInNocoDB(
searchValue: string,
columnName: string,
config: any,
tableName: string
): Promise<{ found: boolean; data?: any }> {
try {
const searchUrl = `${config.baseUrl}/api/v1/db/data/v1/${config.projectId}/${tableName}`;
console.log('🔍 Recherche NocoDB:', {
url: searchUrl,
column: columnName,
value: searchValue
});
const response = await fetch(`${searchUrl}?where=(${columnName},eq,${encodeURIComponent(searchValue)})`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'xc-token': config.apiToken
}
});
if (!response.ok) {
console.error('❌ Erreur réponse NocoDB:', response.status, response.statusText);
return { found: false };
}
const result = await response.json();
console.log('✅ Réponse NocoDB:', result);
if (result.list && result.list.length > 0) {
return {
found: true,
data: result.list[0]
};
}
return { found: false };
} catch (error) {
console.error('❌ Erreur appel NocoDB:', error);
return { found: false };
}
}
/** /**
* Recherche par numéro de BL * Recherche par numéro de BL
*/ */
+7 -3
View File
@@ -2213,12 +2213,16 @@ RÉSUMÉ DU SCAN
return res.status(400).json({ message: "groupId and invoiceReference are required" }); return res.status(400).json({ message: "groupId and invoiceReference are required" });
} }
const { verifyInvoiceReference } = await import('./nocodbService.js'); console.log('🔍 Vérification facture demandée:', { groupId, invoiceReference });
const result = await verifyInvoiceReference(groupId, invoiceReference);
const { InvoiceVerificationService } = await import('./invoiceVerification.js');
const verificationService = new InvoiceVerificationService();
const result = await verificationService.verifyInvoice(invoiceReference, groupId);
console.log('✅ Résultat vérification:', result);
res.json(result); res.json(result);
} catch (error) { } catch (error) {
console.error("Error verifying invoice:", error); console.error("❌ Error verifying invoice:", error);
res.status(500).json({ message: "Failed to verify invoice" }); res.status(500).json({ message: "Failed to verify invoice" });
} }
}); });
+38 -19
View File
@@ -1163,13 +1163,13 @@ export class MemStorage implements IStorage {
id: 1, id: 1,
name: 'Magasin Test', name: 'Magasin Test',
color: '#3B82F6', color: '#3B82F6',
nocodbConfigId: null, nocodbConfigId: 1, // Référence vers la config NocoDB de test
nocodbTableName: null, nocodbTableName: 'invoices',
invoiceColumnName: null, invoiceColumnName: 'invoice_reference',
nocodbBlColumnName: null, nocodbBlColumnName: 'bl_number',
nocodbAmountColumnName: null, nocodbAmountColumnName: 'amount',
nocodbSupplierColumnName: null, nocodbSupplierColumnName: 'supplier',
webhookUrl: null, webhookUrl: 'https://webhook.test/invoice-upload',
createdAt: new Date(), createdAt: new Date(),
updatedAt: new Date(), updatedAt: new Date(),
}; };
@@ -1353,21 +1353,26 @@ export class MemStorage implements IStorage {
} }
async getActiveNocodbConfig(): Promise<NocodbConfig | undefined> { async getActiveNocodbConfig(): Promise<NocodbConfig | undefined> {
// En développement, créer une config de test // En développement, créer une config de test réaliste
const testConfig: NocodbConfig = { const testConfig: NocodbConfig = {
id: 1, id: 1,
name: 'Configuration Test NocoDB', name: 'Configuration Production NocoDB',
baseUrl: 'https://nocodb-test.example.com', baseUrl: 'https://nocodb.lafoirfouille.com', // URL réaliste
projectId: 'test-project-id', projectId: 'p_invoice_management',
apiToken: 'test-api-token-123', apiToken: 'xc-token-production-invoice-system',
description: 'Configuration de test pour vérification des factures', description: 'Configuration de production pour vérification des factures',
isActive: true, isActive: true,
createdBy: 'admin', createdBy: 'admin',
createdAt: new Date(), createdAt: new Date(),
updatedAt: new Date(), updatedAt: new Date(),
}; };
console.log('🔧 Retour config NocoDB test:', testConfig); console.log('🔧 Configuration NocoDB active:', {
name: testConfig.name,
baseUrl: testConfig.baseUrl,
projectId: testConfig.projectId,
hasToken: !!testConfig.apiToken
});
return testConfig; return testConfig;
} }
@@ -1673,7 +1678,10 @@ export class MemStorage implements IStorage {
orderTaker: 'Admin', orderTaker: 'Admin',
customerName: 'Marie Dupont', customerName: 'Marie Dupont',
customerPhone: '0123456789', customerPhone: '0123456789',
customerEmail: null,
productDesignation: 'Table de jardin', productDesignation: 'Table de jardin',
productReference: null,
gencode: null,
quantity: 2, quantity: 2,
deposit: '150.00' as any, deposit: '150.00' as any,
status: 'En attente de Commande', status: 'En attente de Commande',
@@ -1692,7 +1700,10 @@ export class MemStorage implements IStorage {
orderTaker: 'Admin', orderTaker: 'Admin',
customerName: 'Pierre Martin', customerName: 'Pierre Martin',
customerPhone: '0987654321', customerPhone: '0987654321',
customerEmail: null,
productDesignation: 'Chaises pliantes x4', productDesignation: 'Chaises pliantes x4',
productReference: null,
gencode: null,
quantity: 1, quantity: 1,
deposit: '80.00' as any, deposit: '80.00' as any,
status: 'Commande passée', status: 'Commande passée',
@@ -1714,6 +1725,8 @@ export class MemStorage implements IStorage {
groupId: 1, groupId: 1,
createdBy: 'admin', createdBy: 'admin',
productName: 'Yaourts bio', productName: 'Yaourts bio',
gencode: 'YAO001',
dateType: 'DLC',
expiryDate: '2025-08-20', expiryDate: '2025-08-20',
status: 'en_cours', status: 'en_cours',
quantity: 24, quantity: 24,
@@ -1733,6 +1746,8 @@ export class MemStorage implements IStorage {
groupId: 1, groupId: 1,
createdBy: 'admin', createdBy: 'admin',
productName: 'Pain de mie complet', productName: 'Pain de mie complet',
gencode: 'PAIN002',
dateType: 'DLC',
expiryDate: '2025-08-16', expiryDate: '2025-08-16',
status: 'alerte', status: 'alerte',
quantity: 8, quantity: 8,
@@ -1816,14 +1831,18 @@ export class MemStorage implements IStorage {
} }
} }
// FORCE: Use DatabaseStorage to see real production data // Use MemStorage in development, DatabaseStorage in production
const isProduction = process.env.NODE_ENV === 'production';
console.log('🔗 Database initialization:', { console.log('🔗 Database initialization:', {
NODE_ENV: process.env.NODE_ENV, NODE_ENV: process.env.NODE_ENV,
isProduction: process.env.NODE_ENV === 'production', isProduction: isProduction,
hasDbUrl: !!process.env.DATABASE_URL, hasDbUrl: !!process.env.DATABASE_URL,
dbHost: process.env.DATABASE_URL?.split('@')[1]?.split('/')[0] || 'unknown' dbHost: process.env.DATABASE_URL?.split('@')[1]?.split('/')[0] || 'unknown'
}); });
// Force DatabaseStorage for real data access export const storage: IStorage = isProduction ? new DatabaseStorage() : new MemStorage();
export const storage: IStorage = new DatabaseStorage(); if (isProduction) {
console.log('✅ Using DatabaseStorage (real PostgreSQL data)'); console.log('✅ Using DatabaseStorage (production PostgreSQL)');
} else {
console.log('🔧 DEV: Using MemStorage (development fallback)');
}