diff --git a/attached_assets/{B4E9E8F0-2C31-4B0E-AC00-C5EBBDB91FCC}_1762269830356.png b/attached_assets/{B4E9E8F0-2C31-4B0E-AC00-C5EBBDB91FCC}_1762269830356.png new file mode 100644 index 0000000..e3b81f9 Binary files /dev/null and b/attached_assets/{B4E9E8F0-2C31-4B0E-AC00-C5EBBDB91FCC}_1762269830356.png differ diff --git a/client/src/pages/BLReconciliation.tsx b/client/src/pages/BLReconciliation.tsx index aa2299f..4926446 100644 --- a/client/src/pages/BLReconciliation.tsx +++ b/client/src/pages/BLReconciliation.tsx @@ -515,24 +515,21 @@ 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'); - // 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, { + // Envoyer via route backend proxy (résout problèmes CORS) + const response = await fetch('/api/reconciliation/send-invoice', { method: 'POST', body: formData, - signal: controller.signal, + credentials: 'include' }); - clearTimeout(timeoutId); - if (!response.ok) { - throw new Error(`Erreur ${response.status}: ${response.statusText}`); + const errorData = await response.json().catch(() => ({ error: 'Erreur inconnue' })); + throw new Error(errorData.error || `Erreur ${response.status}`); } handleCloseWaitingModal(); diff --git a/server/routes.ts b/server/routes.ts index 0917a3e..9f9ab02 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -788,6 +788,87 @@ export async function registerRoutes(app: Express): Promise { } }); + // Route proxy SIMPLE pour envoi de factures (admin + directeur) + app.post('/api/reconciliation/send-invoice', isAuthenticated, async (req: any, res) => { + try { + const userId = req.user?.claims?.sub || req.user?.id; + if (!userId) { + return res.status(401).json({ error: 'Non authentifié' }); + } + + const user = await storage.getUserWithGroups(userId); + if (!user || (user.role !== 'admin' && user.role !== 'directeur')) { + return res.status(403).json({ error: 'Accès refusé' }); + } + + // Utiliser busboy pour parser le multipart/form-data + const busboy = Busboy({ headers: req.headers }); + const fields: any = {}; + let fileBuffer: Buffer | null = null; + let fileName = ''; + let mimeType = ''; + + busboy.on('file', (fieldname, file, info) => { + const chunks: Buffer[] = []; + fileName = info.filename; + mimeType = info.mimeType; + + file.on('data', (chunk) => chunks.push(chunk)); + file.on('end', () => { + fileBuffer = Buffer.concat(chunks); + }); + }); + + busboy.on('field', (fieldname, value) => { + fields[fieldname] = value; + }); + + await new Promise((resolve, reject) => { + busboy.on('finish', resolve); + busboy.on('error', reject); + req.pipe(busboy); + }); + + if (!fileBuffer || !fields.webhookUrl) { + return res.status(400).json({ error: 'Fichier ou webhook manquant' }); + } + + console.log('📤 INVOICE PROXY: Envoi facture', { + userId: user.id, + role: user.role, + fileName, + size: fileBuffer.length + }); + + // Importer form-data dynamiquement avec eval pour ESM + const FormDataModule = await eval('import("form-data")'); + const FormData = FormDataModule.default; + + const formData = new FormData(); + formData.append('file', fileBuffer, { filename: fileName, contentType: mimeType }); + formData.append('supplier', fields.supplier || ''); + formData.append('blNumber', fields.blNumber || ''); + formData.append('type', fields.type || 'Facture'); + + const response = await fetch(fields.webhookUrl, { + method: 'POST', + body: formData as any, + headers: formData.getHeaders() + }); + + if (!response.ok) { + console.error('❌ INVOICE PROXY: Webhook error', response.status); + return res.status(500).json({ error: `Webhook error: ${response.status}` }); + } + + console.log('✅ INVOICE PROXY: Success'); + res.json({ success: true, message: 'Facture envoyée' }); + + } catch (error: any) { + console.error('❌ INVOICE PROXY: Error', error); + res.status(500).json({ error: error.message || 'Erreur serveur' }); + } + }); // Auth routes handled by authSwitch (local or Replit)