diff --git a/client/src/pages/BLReconciliation.tsx b/client/src/pages/BLReconciliation.tsx index eff3be6..aa2299f 100644 --- a/client/src/pages/BLReconciliation.tsx +++ b/client/src/pages/BLReconciliation.tsx @@ -515,24 +515,25 @@ export default function BLReconciliation() { try { const formData = new FormData(); formData.append('file', selectedFile); - formData.append('webhookUrl', selectedDeliveryForInvoice.group.webhookUrl); formData.append('supplier', selectedDeliveryForInvoice.supplier?.name || ''); formData.append('blNumber', selectedDeliveryForInvoice.blNumber || ''); formData.append('type', 'Facture'); - // Envoyer via notre route backend proxy (évite problèmes CORS et erreurs webhook) - const response = await fetch('/api/reconciliation/send-invoice', { + // Créer un AbortController pour gérer le timeout + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 60000); // 60 secondes + + const response = await fetch(selectedDeliveryForInvoice.group.webhookUrl, { method: 'POST', body: formData, - credentials: 'include' // Important pour l'authentification + signal: controller.signal, }); - if (!response.ok) { - const errorData = await response.json().catch(() => ({})); - throw new Error(errorData.error || `Erreur ${response.status}: ${response.statusText}`); - } + clearTimeout(timeoutId); - const result = await response.json(); + if (!response.ok) { + throw new Error(`Erreur ${response.status}: ${response.statusText}`); + } handleCloseWaitingModal(); diff --git a/package-lock.json b/package-lock.json index e36d502..395b528 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,12 +41,14 @@ "@radix-ui/react-tooltip": "^1.2.0", "@tanstack/react-query": "^5.60.5", "@types/bcrypt": "^6.0.0", + "@types/busboy": "^1.5.4", "@types/form-data": "^2.2.1", "@types/memoizee": "^0.4.12", "@types/multer": "^2.0.0", "@types/node-fetch": "^2.6.13", "@types/pg": "^8.15.4", "bcrypt": "^6.0.0", + "busboy": "^1.6.0", "caniuse-lite": "^1.0.30001727", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -3625,6 +3627,15 @@ "@types/node": "*" } }, + "node_modules/@types/busboy": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/busboy/-/busboy-1.5.4.tgz", + "integrity": "sha512-kG7WrUuAKK0NoyxfQHsVE6j1m01s6kMma64E+OZenQABMQyTJop1DumUWcLwAQ2JzpefU7PDYoRDKl8uZosFjw==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/connect": { "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", diff --git a/package.json b/package.json index 4d8af09..3c7f96f 100644 --- a/package.json +++ b/package.json @@ -43,12 +43,14 @@ "@radix-ui/react-tooltip": "^1.2.0", "@tanstack/react-query": "^5.60.5", "@types/bcrypt": "^6.0.0", + "@types/busboy": "^1.5.4", "@types/form-data": "^2.2.1", "@types/memoizee": "^0.4.12", "@types/multer": "^2.0.0", "@types/node-fetch": "^2.6.13", "@types/pg": "^8.15.4", "bcrypt": "^6.0.0", + "busboy": "^1.6.0", "caniuse-lite": "^1.0.30001727", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/server/routes.ts b/server/routes.ts index 4193627..0917a3e 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -128,7 +128,6 @@ import { invoiceVerificationService } from "./invoiceVerification"; import { backupService } from "./backupService"; import { weatherService } from "./weatherService.js"; import fetch from "node-fetch"; -import multer from "multer"; export async function registerRoutes(app: Express): Promise { // Detect environment @@ -789,97 +788,6 @@ export async function registerRoutes(app: Express): Promise { } }); - // Configuration multer pour upload en mémoire - const upload = multer({ storage: multer.memoryStorage() }); - - // Route proxy pour envoi de factures vers webhook (accessible admin + directeur) - app.post('/api/reconciliation/send-invoice', isAuthenticated, upload.single('file'), async (req: any, res) => { - try { - const userId = req.user?.claims?.sub || req.user?.id; - if (!userId) { - return res.status(401).json({ error: 'Utilisateur non authentifié' }); - } - - const user = await storage.getUserWithGroups(userId); - if (!user) { - return res.status(404).json({ error: 'Utilisateur non trouvé' }); - } - - // Autoriser admin ET directeur - if (user.role !== 'admin' && user.role !== 'directeur') { - return res.status(403).json({ error: 'Accès refusé - Admin ou Directeur uniquement' }); - } - - if (!req.file || !req.body.webhookUrl) { - return res.status(400).json({ error: 'Fichier ou URL webhook manquant' }); - } - - const { webhookUrl, supplier, blNumber, type } = req.body; - - console.log('📤 INVOICE: Envoi facture via webhook', { - webhookUrl: webhookUrl.substring(0, 50) + '...', - userId: user.id, - userRole: user.role, - fileName: req.file.originalname, - fileSize: req.file.size - }); - - // Importer form-data dynamiquement pour ESM - const FormDataLib = await eval('import("form-data")'); - const FormData = FormDataLib.default; - - const formData = new FormData(); - formData.append('file', req.file.buffer, { - filename: req.file.originalname, - contentType: req.file.mimetype - }); - formData.append('supplier', supplier || ''); - formData.append('blNumber', blNumber || ''); - formData.append('type', type || 'Facture'); - - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), 60000); - - const response = await fetch(webhookUrl, { - method: 'POST', - body: formData as any, - headers: formData.getHeaders(), - signal: controller.signal - }); - - clearTimeout(timeoutId); - - if (!response.ok) { - const errorText = await response.text(); - console.error('❌ INVOICE: Erreur webhook', { status: response.status, errorText: errorText.substring(0, 200) }); - return res.status(500).json({ error: `Erreur webhook: ${response.status}`, details: errorText }); - } - - const result = await response.text(); - console.log('✅ INVOICE: Webhook réussi', { userId: user.id, userRole: user.role }); - - res.json({ - success: true, - message: 'Facture envoyée avec succès', - webhookResponse: result - }); - - } catch (error: any) { - console.error('❌ INVOICE: Erreur envoi facture:', error); - - let errorMessage = 'Erreur lors de l\'envoi de la facture'; - if (error.name === 'AbortError') { - errorMessage = 'Timeout - Le traitement a pris trop de temps'; - } else if (error.message) { - errorMessage = error.message; - } - - res.status(500).json({ - error: errorMessage, - details: error.message - }); - } - }); // Auth routes handled by authSwitch (local or Replit)