Update invoice sending to directly use webhook URL and add timeout

Replaced the backend proxy route for sending invoices with a direct fetch to the webhook URL, incorporating a 60-second timeout using AbortController. Also added 'busboy' and '@types/busboy' to package.json.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 1e4433b5-47ab-464c-b663-fea2e53367dd
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: ca0ae43e-5b56-4b02-8cb5-0e76adf608ba
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1957c339-2757-4d1f-8e92-e9f71a1ce58e/1e4433b5-47ab-464c-b663-fea2e53367dd/qOJ89TE
This commit is contained in:
michaelschal committed 2025-11-04 15:21:16 +00:00
1 parent 89211e4f28
commit e3785d4457
4 files changed
+23 -101

No files matched your search

-92
View File
@@ -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<Server> {
// Detect environment
@@ -789,97 +788,6 @@ export async function registerRoutes(app: Express): Promise<Server> {
}
});
// 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)