From f75a7ea25cb1748df0e3edcc23f108a966280f5d Mon Sep 17 00:00:00 2001 From: michaelschal <35957947-michaelschal@users.noreply.replit.com> Date: Wed, 13 Aug 2025 08:25:56 +0000 Subject: [PATCH] Add functionality to upload and send invoices via webhook integration Implement a modal for uploading PDF invoices, selecting invoice types, and sending them to configured webhook URLs for deliveries lacking validation or invoice numbers. Replit-Commit-Author: Agent Replit-Commit-Session-Id: c52a701b-4841-4d00-b8cf-5a41c12abdf3 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1957c339-2757-4d1f-8e92-e9f71a1ce58e/c52a701b-4841-4d00-b8cf-5a41c12abdf3/MvDLhQ6 --- client/src/pages/BLReconciliation.tsx | 198 +++++++++++++++++++++++++- 1 file changed, 197 insertions(+), 1 deletion(-) diff --git a/client/src/pages/BLReconciliation.tsx b/client/src/pages/BLReconciliation.tsx index 155afbd..1dc304f 100644 --- a/client/src/pages/BLReconciliation.tsx +++ b/client/src/pages/BLReconciliation.tsx @@ -12,7 +12,10 @@ import { useStore } from "@/components/Layout"; import { useAuthUnified } from "@/hooks/useAuthUnified"; import { usePermissions } from "@shared/permissions"; import { Pagination, usePagination } from "@/components/ui/pagination"; -import { Search, Edit, FileText, Settings, Eye, AlertTriangle, X, Check, Trash2, Ban, Filter } from "lucide-react"; +import { Search, Edit, FileText, Settings, Eye, AlertTriangle, X, Check, Trash2, Ban, Filter, Upload } from "lucide-react"; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Label } from "@/components/ui/label"; import ReconciliationModal from "@/components/modals/ReconciliationModal"; export default function BLReconciliation() { @@ -47,6 +50,13 @@ export default function BLReconciliation() { const [searchTerm, setSearchTerm] = useState(""); const [selectedDelivery, setSelectedDelivery] = useState(null); const [isModalOpen, setIsModalOpen] = useState(false); + + // État pour le modal d'envoi de facture + const [showInvoiceModal, setShowInvoiceModal] = useState(false); + const [selectedDeliveryForInvoice, setSelectedDeliveryForInvoice] = useState(null); + const [invoiceType, setInvoiceType] = useState(""); + const [selectedFile, setSelectedFile] = useState(null); + const [isUploading, setIsUploading] = useState(false); // Récupérer les fournisseurs pour la logique automatique const { data: suppliers = [] } = useQuery({ @@ -120,6 +130,101 @@ export default function BLReconciliation() { } }; + // Nouvelles fonctions pour le modal d'envoi de facture + const handleOpenInvoiceModal = (delivery: any) => { + setSelectedDeliveryForInvoice(delivery); + setInvoiceType(""); + setSelectedFile(null); + setShowInvoiceModal(true); + }; + + const handleCloseInvoiceModal = () => { + setShowInvoiceModal(false); + setSelectedDeliveryForInvoice(null); + setInvoiceType(""); + setSelectedFile(null); + setIsUploading(false); + }; + + const handleFileChange = (event: React.ChangeEvent) => { + const file = event.target.files?.[0]; + if (file && file.type === 'application/pdf') { + setSelectedFile(file); + } else { + toast({ + title: "Erreur", + description: "Veuillez sélectionner un fichier PDF", + variant: "destructive", + }); + } + }; + + const handleSendInvoice = async () => { + if (!selectedDeliveryForInvoice || !invoiceType || !selectedFile) { + toast({ + title: "Erreur", + description: "Veuillez remplir tous les champs", + variant: "destructive", + }); + return; + } + + if (!selectedDeliveryForInvoice.group?.webhookUrl) { + toast({ + title: "Erreur", + description: "Aucun webhook configuré pour ce magasin", + variant: "destructive", + }); + return; + } + + setIsUploading(true); + + try { + const formData = new FormData(); + formData.append('file', selectedFile); + formData.append('supplier', selectedDeliveryForInvoice.supplier?.name || ''); + formData.append('blNumber', selectedDeliveryForInvoice.blNumber || ''); + formData.append('type', invoiceType); + + const response = await fetch(selectedDeliveryForInvoice.group.webhookUrl, { + method: 'POST', + body: formData, + }); + + if (!response.ok) { + throw new Error(`Erreur ${response.status}: ${response.statusText}`); + } + + toast({ + title: "Succès", + description: `${invoiceType} envoyée avec succès via le webhook`, + }); + + handleCloseInvoiceModal(); + } catch (error: any) { + toast({ + title: "Erreur", + description: `Impossible d'envoyer la ${invoiceType.toLowerCase()}: ${error.message}`, + variant: "destructive", + }); + } finally { + setIsUploading(false); + } + }; + + // Fonction pour déterminer si le bouton "Envoyer Facture" doit être affiché + const shouldShowInvoiceButton = (delivery: any) => { + // Conditions : ligne non validée OU pas de RefFacture renseignée + const isNotValidated = !delivery.reconciled; + const hasNoInvoiceReference = !delivery.invoiceReference; + + // Et il faut qu'il y ait un magasin assigné avec un webhook + const hasValidGroup = delivery.group && delivery.group.webhookUrl; + + return (isNotValidated || hasNoInvoiceReference) && hasValidGroup; + }; + const handleQuickValidate = async (delivery: any) => { try { await apiRequest(`/api/deliveries/${delivery.id}`, "PUT", { @@ -459,6 +564,17 @@ export default function BLReconciliation() {
+ {shouldShowInvoiceButton(delivery) && ( + + )} {!delivery.reconciled ? ( <> {permissions.canValidate('reconciliation') && ( @@ -647,6 +763,15 @@ export default function BLReconciliation() {
+ {shouldShowInvoiceButton(delivery) && ( + + )} {user?.role === 'admin' && ( + + + +
); } \ No newline at end of file