mirror of
https://github.com/R0m1k3/LogiFlow.git
synced 2026-10-22 14:38:19 +02:00
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:
1 parent
89211e4f28
commit
e3785d4457
4 files changed
+23
-101
No files matched your search
@@ -515,24 +515,25 @@ export default function BLReconciliation() {
|
|||||||
try {
|
try {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', selectedFile);
|
formData.append('file', selectedFile);
|
||||||
formData.append('webhookUrl', selectedDeliveryForInvoice.group.webhookUrl);
|
|
||||||
formData.append('supplier', selectedDeliveryForInvoice.supplier?.name || '');
|
formData.append('supplier', selectedDeliveryForInvoice.supplier?.name || '');
|
||||||
formData.append('blNumber', selectedDeliveryForInvoice.blNumber || '');
|
formData.append('blNumber', selectedDeliveryForInvoice.blNumber || '');
|
||||||
formData.append('type', 'Facture');
|
formData.append('type', 'Facture');
|
||||||
|
|
||||||
// Envoyer via notre route backend proxy (évite problèmes CORS et erreurs webhook)
|
// Créer un AbortController pour gérer le timeout
|
||||||
const response = await fetch('/api/reconciliation/send-invoice', {
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), 60000); // 60 secondes
|
||||||
|
|
||||||
|
const response = await fetch(selectedDeliveryForInvoice.group.webhookUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData,
|
body: formData,
|
||||||
credentials: 'include' // Important pour l'authentification
|
signal: controller.signal,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
clearTimeout(timeoutId);
|
||||||
const errorData = await response.json().catch(() => ({}));
|
|
||||||
throw new Error(errorData.error || `Erreur ${response.status}: ${response.statusText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await response.json();
|
if (!response.ok) {
|
||||||
|
throw new Error(`Erreur ${response.status}: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
handleCloseWaitingModal();
|
handleCloseWaitingModal();
|
||||||
|
|
||||||
|
|||||||
Generated
+11
@@ -41,12 +41,14 @@
|
|||||||
"@radix-ui/react-tooltip": "^1.2.0",
|
"@radix-ui/react-tooltip": "^1.2.0",
|
||||||
"@tanstack/react-query": "^5.60.5",
|
"@tanstack/react-query": "^5.60.5",
|
||||||
"@types/bcrypt": "^6.0.0",
|
"@types/bcrypt": "^6.0.0",
|
||||||
|
"@types/busboy": "^1.5.4",
|
||||||
"@types/form-data": "^2.2.1",
|
"@types/form-data": "^2.2.1",
|
||||||
"@types/memoizee": "^0.4.12",
|
"@types/memoizee": "^0.4.12",
|
||||||
"@types/multer": "^2.0.0",
|
"@types/multer": "^2.0.0",
|
||||||
"@types/node-fetch": "^2.6.13",
|
"@types/node-fetch": "^2.6.13",
|
||||||
"@types/pg": "^8.15.4",
|
"@types/pg": "^8.15.4",
|
||||||
"bcrypt": "^6.0.0",
|
"bcrypt": "^6.0.0",
|
||||||
|
"busboy": "^1.6.0",
|
||||||
"caniuse-lite": "^1.0.30001727",
|
"caniuse-lite": "^1.0.30001727",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
@@ -3625,6 +3627,15 @@
|
|||||||
"@types/node": "*"
|
"@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": {
|
"node_modules/@types/connect": {
|
||||||
"version": "3.4.38",
|
"version": "3.4.38",
|
||||||
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
|
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
|
||||||
|
|||||||
@@ -43,12 +43,14 @@
|
|||||||
"@radix-ui/react-tooltip": "^1.2.0",
|
"@radix-ui/react-tooltip": "^1.2.0",
|
||||||
"@tanstack/react-query": "^5.60.5",
|
"@tanstack/react-query": "^5.60.5",
|
||||||
"@types/bcrypt": "^6.0.0",
|
"@types/bcrypt": "^6.0.0",
|
||||||
|
"@types/busboy": "^1.5.4",
|
||||||
"@types/form-data": "^2.2.1",
|
"@types/form-data": "^2.2.1",
|
||||||
"@types/memoizee": "^0.4.12",
|
"@types/memoizee": "^0.4.12",
|
||||||
"@types/multer": "^2.0.0",
|
"@types/multer": "^2.0.0",
|
||||||
"@types/node-fetch": "^2.6.13",
|
"@types/node-fetch": "^2.6.13",
|
||||||
"@types/pg": "^8.15.4",
|
"@types/pg": "^8.15.4",
|
||||||
"bcrypt": "^6.0.0",
|
"bcrypt": "^6.0.0",
|
||||||
|
"busboy": "^1.6.0",
|
||||||
"caniuse-lite": "^1.0.30001727",
|
"caniuse-lite": "^1.0.30001727",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
|||||||
@@ -128,7 +128,6 @@ import { invoiceVerificationService } from "./invoiceVerification";
|
|||||||
import { backupService } from "./backupService";
|
import { backupService } from "./backupService";
|
||||||
import { weatherService } from "./weatherService.js";
|
import { weatherService } from "./weatherService.js";
|
||||||
import fetch from "node-fetch";
|
import fetch from "node-fetch";
|
||||||
import multer from "multer";
|
|
||||||
|
|
||||||
export async function registerRoutes(app: Express): Promise<Server> {
|
export async function registerRoutes(app: Express): Promise<Server> {
|
||||||
// Detect environment
|
// 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)
|
// Auth routes handled by authSwitch (local or Replit)
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user