diff --git a/client/src/pages/BLReconciliation.tsx b/client/src/pages/BLReconciliation.tsx index 4869cbd..cad52f9 100644 --- a/client/src/pages/BLReconciliation.tsx +++ b/client/src/pages/BLReconciliation.tsx @@ -1,41 +1,15 @@ -import React, { useState, useEffect } from "react"; +import React, { useState } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; -import { format } from "date-fns"; -import { fr } from "date-fns/locale"; import { safeFormat } from "@/lib/dateUtils"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; -import { Pagination, usePagination } from "@/components/ui/pagination"; -import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; -import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; -import { useForm } from "react-hook-form"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { z } from "zod"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { useToast } from "@/hooks/use-toast"; import { apiRequest } from "@/lib/queryClient"; -import { isUnauthorizedError } from "@/lib/authUtils"; import { useStore } from "@/components/Layout"; import { useAuthUnified } from "@/hooks/useAuthUnified"; -import { Search, Plus, Edit, FileText, Euro, Calendar, Building2, CheckCircle, X, Trash2, RefreshCw, Loader2 } from "lucide-react"; -import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; -import { cn } from "@/lib/utils"; -import { format as formatDate } from "date-fns"; -import { DayPicker } from "react-day-picker"; -import ConfirmDeleteModal from "@/components/modals/ConfirmDeleteModal"; - -const reconciliationSchema = z.object({ - blNumber: z.string().optional(), - blAmount: z.string().optional().refine((val) => !val || val === '' || (!isNaN(Number(val)) && Number(val) >= 0), { - message: "Le montant BL doit être un nombre positif ou vide", - }), - invoiceReference: z.string().optional(), - invoiceAmount: z.string().optional().refine((val) => !val || val === '' || (!isNaN(Number(val)) && Number(val) >= 0), { - message: "Le montant facture doit être un nombre positif ou vide", - }), -}); - -type ReconciliationForm = z.infer; +import { Search, Edit, FileText, Settings, Eye, AlertTriangle, X } from "lucide-react"; export default function BLReconciliation() { const { user } = useAuthUnified(); @@ -64,35 +38,23 @@ export default function BLReconciliation() { ); } + const [activeTab, setActiveTab] = useState("manual"); const [searchTerm, setSearchTerm] = useState(""); - const [showReconciliationModal, setShowReconciliationModal] = useState(false); - const [selectedDelivery, setSelectedDelivery] = useState(null); - const [selectedDate, setSelectedDate] = useState(null); - const [isDatePickerOpen, setIsDatePickerOpen] = useState(false); - const [showDeleteModal, setShowDeleteModal] = useState(false); - const [deliveryToDelete, setDeliveryToDelete] = useState(null); - const [invoiceVerifications, setInvoiceVerifications] = useState>({}); - const [isVerifyingInvoices, setIsVerifyingInvoices] = useState(false); - const [isVerifyingCurrentInvoice, setIsVerifyingCurrentInvoice] = useState(false); + + // Récupérer les fournisseurs pour la logique automatique + const { data: suppliers = [] } = useQuery({ + queryKey: ['/api/suppliers'], + }); // Récupérer les livraisons validées avec BL const { data: deliveriesWithBL = [], isLoading } = useQuery({ - queryKey: ['/api/deliveries/bl', selectedStoreId, selectedDate], + queryKey: ['/api/deliveries/bl', selectedStoreId], queryFn: async () => { - const params = new URLSearchParams({ - // Remove withBL filter - show all delivered deliveries regardless of BL status - }); + const params = new URLSearchParams({}); if (selectedStoreId && user?.role === 'admin') { params.append('storeId', selectedStoreId.toString()); } - // Ajouter filtre par date si sélectionné - if (selectedDate) { - const dateStr = formatDate(selectedDate, 'yyyy-MM-dd'); - params.append('startDate', dateStr); - params.append('endDate', dateStr); - } - const response = await fetch(`/api/deliveries?${params.toString()}`, { credentials: 'include' }); @@ -102,621 +64,178 @@ export default function BLReconciliation() { } const deliveries = await response.json(); - console.log('🚚 BL Reconciliation - All deliveries received:', Array.isArray(deliveries) ? deliveries.length : 'NOT_ARRAY', deliveries); const filtered = Array.isArray(deliveries) ? deliveries.filter((d: any) => d.status === 'delivered') : []; - console.log('🚚 BL Reconciliation - Filtered deliveries:', filtered.length, filtered); - // Ne filtrer que les livraisons livrées (status === 'delivered') - // Toutes les livraisons livrées doivent apparaître, même sans BL encore saisi - - // Verify invoice references for deliveries with invoice data - if (filtered.length > 0) { - const invoiceReferencesToVerify = filtered - .filter((delivery: any) => delivery.invoiceReference && delivery.groupId) - .map((delivery: any) => ({ - groupId: delivery.groupId, - invoiceReference: delivery.invoiceReference, - deliveryId: delivery.id, - supplierName: delivery.supplier?.name, // Include supplier name for verification - })); - - if (Array.isArray(invoiceReferencesToVerify) && invoiceReferencesToVerify.length > 0) { - try { - const verificationResponse = await fetch('/api/verify-invoices', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - credentials: 'include', - body: JSON.stringify({ invoiceReferences: invoiceReferencesToVerify }), - }); - - if (verificationResponse.ok) { - const verificationResults = await verificationResponse.json(); - setInvoiceVerifications(verificationResults); - } - } catch (error) { - console.error('Error verifying invoice references:', error); - } - } - } - - return filtered.sort((a: any, b: any) => new Date(b.deliveredDate).getTime() - new Date(a.deliveredDate).getTime()); + return filtered.sort((a: any, b: any) => new Date(b.deliveredDate || b.updatedAt).getTime() - new Date(a.deliveredDate || a.updatedAt).getTime()); }, }); - // Fonction pour vérifier automatiquement les BL sans facture - const checkPendingInvoices = async () => { - if (!deliveriesWithBL || deliveriesWithBL.length === 0) return; - - // Trouver les livraisons sans référence facture ou avec facture non vérifiée - const unverifiedDeliveries = deliveriesWithBL.filter((delivery: any) => - delivery.blNumber && !delivery.invoiceReference - ); - - if (unverifiedDeliveries.length === 0) return; - - console.log(`🔍 Checking ${unverifiedDeliveries.length} deliveries for pending invoices...`); - - // TODO: Ici on pourrait ajouter une logique pour rechercher automatiquement - // des factures basées sur les BL numbers ou autres critères - }; + // Séparer les livraisons selon le mode de rapprochement du fournisseur + const manualReconciliationDeliveries = deliveriesWithBL.filter((delivery: any) => { + const supplier = suppliers.find((s: any) => s.id === delivery.supplierId); + return !supplier?.automaticReconciliation; + }); - // Vérification automatique toutes les 30 minutes - useEffect(() => { - if (deliveriesWithBL && deliveriesWithBL.length > 0) { - const interval = setInterval(() => { - checkPendingInvoices(); - }, 30 * 60 * 1000); // 30 minutes - - return () => clearInterval(interval); - } - }, [deliveriesWithBL]); + const automaticReconciliationDeliveries = deliveriesWithBL.filter((delivery: any) => { + const supplier = suppliers.find((s: any) => s.id === delivery.supplierId); + return supplier?.automaticReconciliation; + }); - // Fonction pour vérifier les factures NocoDB - const verifyAllInvoices = async () => { - if (!deliveriesWithBL || deliveriesWithBL.length === 0) return; - - const invoiceReferencesToVerify = deliveriesWithBL - .filter((delivery: any) => delivery.invoiceReference && delivery.invoiceReference.trim() !== '' && delivery.groupId) - .map((delivery: any) => ({ - groupId: delivery.groupId, - invoiceReference: delivery.invoiceReference, - deliveryId: delivery.id, - supplierName: delivery.supplier?.name, - })); - - if (invoiceReferencesToVerify.length > 0) { - setIsVerifyingInvoices(true); - try { - console.log('🔍 Verifying invoices:', invoiceReferencesToVerify); - const verificationResponse = await fetch('/api/verify-invoices', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - credentials: 'include', - body: JSON.stringify({ invoiceReferences: invoiceReferencesToVerify }), - }); - - if (verificationResponse.ok) { - const verificationResults = await verificationResponse.json(); - console.log('✅ Verification results:', verificationResults); - setInvoiceVerifications(verificationResults); - toast({ - title: "Vérification terminée", - description: `${invoiceReferencesToVerify.length} facture(s) vérifiée(s)`, - }); - } - } catch (error) { - console.error('Error verifying invoice references:', error); - toast({ - title: "Erreur", - description: "Impossible de vérifier les factures", - variant: "destructive", - }); - } finally { - setIsVerifyingInvoices(false); - } - } else { + // Fonction pour dévalider un rapprochement automatique (directeurs et admins uniquement) + const handleDevalidateAutoReconciliation = async (deliveryId: number) => { + if (user?.role !== 'directeur' && user?.role !== 'admin') { toast({ - title: "Aucune facture à vérifier", - description: "Aucune référence facture trouvée dans les BL validés", - variant: "default", + title: "Accès refusé", + description: "Seuls les directeurs et administrateurs peuvent dévalider les rapprochements automatiques", + variant: "destructive", }); - } - }; - - // Vérifier les factures NocoDB après chaque mise à jour des livraisons - useEffect(() => { - if (deliveriesWithBL && deliveriesWithBL.length > 0) { - verifyAllInvoices(); - } - }, [deliveriesWithBL]); - - const form = useForm({ - resolver: zodResolver(reconciliationSchema), - defaultValues: { - blNumber: "", - blAmount: "", - invoiceReference: "", - invoiceAmount: "", - }, - }); - - // Fonction pour vérifier une facture en temps réel - const verifyInvoiceRealtime = async (invoiceRef: string) => { - if (!invoiceRef || !invoiceRef.trim() || !selectedDelivery) return; - - setIsVerifyingCurrentInvoice(true); - try { - console.log('🔍 Real-time verification for:', invoiceRef); - const verificationResponse = await fetch('/api/verify-invoices', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - credentials: 'include', - body: JSON.stringify({ - invoiceReferences: [{ - groupId: selectedDelivery.groupId, - invoiceReference: invoiceRef.trim(), - deliveryId: selectedDelivery.id, - supplierName: selectedDelivery.supplier?.name, - }] - }), - }); - - if (verificationResponse.ok) { - const verificationResults = await verificationResponse.json(); - console.log('✅ Real-time verification result:', verificationResults); - setInvoiceVerifications(prev => ({ ...prev, ...verificationResults })); - } - } catch (error) { - console.error('Error in real-time verification:', error); - } finally { - setIsVerifyingCurrentInvoice(false); - } - }; - - // Debounce pour la vérification en temps réel - useEffect(() => { - const invoiceRef = form.watch("invoiceReference"); - if (!invoiceRef || !invoiceRef.trim()) { - setIsVerifyingCurrentInvoice(false); return; } - - const timeoutId = setTimeout(() => { - verifyInvoiceRealtime(invoiceRef); - }, 1000); // Attendre 1 seconde après la dernière saisie - - return () => clearTimeout(timeoutId); - }, [form.watch("invoiceReference"), selectedDelivery]); - const updateReconciliationMutation = useMutation({ - mutationFn: async (data: { id: number; blNumber: string; blAmount: string; invoiceReference: string; invoiceAmount: string }) => { - console.log('🔄 Updating reconciliation data:', data); - const response = await apiRequest(`/api/deliveries/${data.id}`, "PUT", { - blNumber: data.blNumber, - blAmount: data.blAmount, - invoiceReference: data.invoiceReference, - invoiceAmount: data.invoiceAmount, - }); - console.log('✅ Reconciliation update response:', response); - return response; - }, - onSuccess: (data, variables) => { - toast({ - title: "Succès", - description: "Données de rapprochement mises à jour avec succès", - }); - // Invalider tous les caches BL/Rapprochement avec toutes les variations de clés - queryClient.invalidateQueries({ - predicate: (query) => - query.queryKey[0] === '/api/deliveries/bl' || - query.queryKey[0] === '/api/deliveries' + try { + await apiRequest(`/api/deliveries/${deliveryId}`, "PUT", { + reconciled: false, + validatedAt: null }); - // Vérifier immédiatement la facture si une référence a été ajoutée ou modifiée - if (variables.invoiceReference && variables.invoiceReference.trim() !== '') { - const verifyInvoice = async () => { - try { - console.log('🔍 Immediate verification for:', variables.invoiceReference); - const verificationResponse = await fetch('/api/verify-invoices', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - credentials: 'include', - body: JSON.stringify({ - invoiceReferences: [{ - groupId: selectedDelivery?.groupId, - invoiceReference: variables.invoiceReference, - deliveryId: variables.id, - supplierName: selectedDelivery?.supplier?.name, - }] - }), - }); - - if (verificationResponse.ok) { - const verificationResults = await verificationResponse.json(); - console.log('✅ Immediate verification result:', verificationResults); - setInvoiceVerifications(prev => ({ ...prev, ...verificationResults })); - - // Notification du résultat - const result = verificationResults[variables.id]; - if (result) { - toast({ - title: result.exists ? "Facture trouvée" : "Facture non trouvée", - description: result.exists ? - `La facture ${variables.invoiceReference} a été trouvée dans NocoDB` : - `La facture ${variables.invoiceReference} n'a pas été trouvée dans NocoDB`, - variant: result.exists ? "default" : "destructive", - }); - } - } - } catch (error) { - console.error('Error verifying invoice reference:', error); - toast({ - title: "Erreur de vérification", - description: "Impossible de vérifier la facture dans NocoDB", - variant: "destructive", - }); - } - }; - - verifyInvoice(); - } + toast({ + title: "Succès", + description: "Rapprochement automatique dévalidé avec succès", + }); - form.reset(); - setShowReconciliationModal(false); - setSelectedDelivery(null); - }, - onError: (error) => { - if (isUnauthorizedError(error)) { - toast({ - title: "Non autorisé", - description: "Vous êtes déconnecté. Reconnexion...", - variant: "destructive", - }); - setTimeout(() => { - window.location.href = "/api/login"; - }, 500); - return; - } + queryClient.invalidateQueries({ queryKey: ['/api/deliveries/bl'] }); + } catch (error) { toast({ title: "Erreur", - description: "Impossible de mettre à jour les données de rapprochement", + description: "Impossible de dévalider le rapprochement", variant: "destructive", }); - }, - }); - - const reconcileMutation = useMutation({ - mutationFn: async (id: number) => { - console.log('🌐 API Request:', { url: `/api/deliveries/${id}`, method: "PUT", body: { reconciled: true } }); - await apiRequest(`/api/deliveries/${id}`, "PUT", { - reconciled: true, - }); - }, - onSuccess: () => { - toast({ - title: "Succès", - description: "Rapprochement validé avec succès", - }); - // Invalider tous les caches BL/Rapprochement - queryClient.invalidateQueries({ - predicate: (query) => - query.queryKey[0] === '/api/deliveries/bl' || - query.queryKey[0] === '/api/deliveries' - }); - }, - onError: (error) => { - console.error('❌ Error reconciling delivery:', error); - if (isUnauthorizedError(error)) { - toast({ - title: "Non autorisé", - description: "Vous êtes déconnecté. Reconnexion...", - variant: "destructive", - }); - setTimeout(() => { - window.location.href = "/api/login"; - }, 500); - return; - } - toast({ - title: "Erreur", - description: `Impossible de valider le rapprochement: ${error.message}`, - variant: "destructive", - }); - }, - }); - - const deleteDeliveryMutation = useMutation({ - mutationFn: async (id: number) => { - console.log('🌐 API Request:', { url: `/api/deliveries/${id}`, method: "DELETE" }); - await apiRequest(`/api/deliveries/${id}`, "DELETE"); - }, - onSuccess: () => { - toast({ - title: "Succès", - description: "Livraison supprimée avec succès", - }); - // Invalider tous les caches BL/Rapprochement - queryClient.invalidateQueries({ - predicate: (query) => - query.queryKey[0] === '/api/deliveries/bl' || - query.queryKey[0] === '/api/deliveries' - }); - }, - onError: (error) => { - console.error('❌ Error deleting delivery:', error); - if (isUnauthorizedError(error)) { - toast({ - title: "Non autorisé", - description: "Vous êtes déconnecté. Reconnexion...", - variant: "destructive", - }); - setTimeout(() => { - window.location.href = "/api/login"; - }, 500); - return; - } - toast({ - title: "Erreur", - description: `Impossible de supprimer la livraison: ${error.message}`, - variant: "destructive", - }); - }, - }); - - const handleDeleteDelivery = (delivery: any) => { - setDeliveryToDelete(delivery); - setShowDeleteModal(true); - }; - - const confirmDeleteDelivery = () => { - if (deliveryToDelete) { - deleteDeliveryMutation.mutate(deliveryToDelete.id); - setShowDeleteModal(false); - setDeliveryToDelete(null); } }; - const handleEditReconciliation = (delivery: any) => { - setSelectedDelivery(delivery); - form.reset({ - blNumber: delivery.blNumber || "", - blAmount: delivery.blAmount || "", - invoiceReference: delivery.invoiceReference || "", - invoiceAmount: delivery.invoiceAmount || "", + // Filtrage des livraisons par recherche + const filterDeliveries = (deliveries: any[]) => { + return deliveries.filter((delivery: any) => { + const searchLower = searchTerm.toLowerCase(); + return ( + delivery.supplier?.name?.toLowerCase().includes(searchLower) || + delivery.blNumber?.toLowerCase().includes(searchLower) || + delivery.invoiceReference?.toLowerCase().includes(searchLower) + ); }); - setShowReconciliationModal(true); }; - const onSubmit = (data: ReconciliationForm) => { - if (selectedDelivery) { - updateReconciliationMutation.mutate({ - id: selectedDelivery.id, - blNumber: data.blNumber, - blAmount: data.blAmount, - invoiceReference: data.invoiceReference, - invoiceAmount: data.invoiceAmount, - }); - } - }; + const filteredManualDeliveries = filterDeliveries(manualReconciliationDeliveries); + const filteredAutomaticDeliveries = filterDeliveries(automaticReconciliationDeliveries); - const calculateDifference = (blAmount: number, invoiceAmount?: number) => { - if (!invoiceAmount) return null; - return blAmount - invoiceAmount; - }; - - const getStatusBadge = (delivery: any) => { - if (delivery.reconciled) { - return Validé; - } - if (delivery.invoiceReference && delivery.invoiceAmount) { - return Prêt à valider; - } - if (delivery.invoiceReference) { - return Facture partielle; - } - return En attente; - }; - - const canValidate = (delivery: any) => { - return delivery.invoiceReference && delivery.invoiceAmount && !delivery.reconciled; - }; - - const handleValidateReconciliation = (delivery: any) => { - reconcileMutation.mutate(delivery.id); - }; - - const filteredDeliveries = Array.isArray(deliveriesWithBL) ? deliveriesWithBL.filter((delivery: any) => { - if (!searchTerm) return true; - return ( - delivery.supplier?.name.toLowerCase().includes(searchTerm.toLowerCase()) || - delivery.blNumber?.toLowerCase().includes(searchTerm.toLowerCase()) || - delivery.invoiceReference?.toLowerCase().includes(searchTerm.toLowerCase()) - ); - }) : []; - - // Pagination - const { - currentPage, - setCurrentPage, - itemsPerPage, - setItemsPerPage, - totalPages, - paginatedData: paginatedDeliveries, - totalItems - } = usePagination(filteredDeliveries, 20); - - if (isLoading) { - return ( -
-
-
- ); - } + const canModify = user?.role === 'directeur' || user?.role === 'admin'; return (
- {/* Header */} + {/* Header avec onglets */}
-
+

- Rapprochement + Rapprochement BL/Factures

- Rapprochement des bons de livraison et factures + Gestion des rapprochements manuels et automatiques

- {filteredDeliveries.length} bon(s) de livraison + {manualReconciliationDeliveries.length} rapprochements manuels + + + {automaticReconciliationDeliveries.length} rapprochements automatiques
+ + {/* Onglets */} + + + + + Rapprochement Manuel + + {manualReconciliationDeliveries.length} + + + + + Rapprochement Automatique + + {automaticReconciliationDeliveries.length} + + + +
- {/* Filters */} + {/* Filtre de recherche */}
-
-
- - setSearchTerm(e.target.value)} - className="pl-10 border border-gray-300 shadow-sm" - /> -
- - {/* Sélecteur de date */} -
- - - - - - { - setSelectedDate(date); - setIsDatePickerOpen(false); - }} - locale={fr} - className="rounded-md border" - /> - - - - {selectedDate && ( - - )} - - -
+
+ + setSearchTerm(e.target.value)} + className="pl-10 border border-gray-300 shadow-sm" + />
- {/* Table */} -
- {totalItems === 0 ? ( -
- -

- Aucun BL trouvé -

-

- Les livraisons validées avec numéro de BL apparaîtront ici. -

-
- ) : ( -
- {/* Pagination en haut */} - {totalItems > 0 && ( - - )} -
- - - - - - - - - - - - - - - - - {paginatedDeliveries.map((delivery: any) => { - const difference = calculateDifference( - parseFloat(delivery.blAmount || '0'), - delivery.invoiceAmount ? parseFloat(delivery.invoiceAmount) : undefined - ); - - return ( - + {/* Contenu des onglets */} + + + {filteredManualDeliveries.length === 0 ? ( +
+ +

+ Aucun rapprochement manuel trouvé +

+

+ Les livraisons de fournisseurs en mode manuel apparaîtront ici. +

+
+ ) : ( +
+
+
- Fournisseur - - N° BL - - Date Livr. - - Date Valid. - - Montant BL - - Ref. Facture - - Montant Fact. - - Écart - - Magasin - - Actions -
+ + + + + + + + + + + + + + {filteredManualDeliveries.map((delivery: any) => ( + @@ -744,6 +253,139 @@ export default function BLReconciliation() { {safeFormat(delivery.scheduledDate, 'dd/MM/yy')} + + + + + + + ))} + +
+ Fournisseur + + N° BL + + Date Livr. + + Montant BL + + Ref. Facture + + Montant Fact. + + Magasin + + Actions +
{delivery.supplier?.name} @@ -725,17 +244,7 @@ export default function BLReconciliation() {
{delivery.blNumber || ( -
- Non renseigné - -
+ Non renseigné )}
+
+ {delivery.blAmount ? + `${parseFloat(delivery.blAmount).toFixed(2)}€` : + Non renseigné + } +
+
+
+ {delivery.invoiceReference || ( + Non renseigné + )} +
+
+
+ {delivery.invoiceAmount ? + `${parseFloat(delivery.invoiceAmount).toFixed(2)}€` : + Non renseigné + } +
+
+
+ {delivery.group?.name} +
+
+
+ +
+
+
+
+ )} + + + + {/* Message d'information */} +
+
+ +
+

Mode rapprochement automatique

+

+ Les livraisons de fournisseurs en mode automatique sont validées automatiquement lorsqu'elles ont le statut "delivered" et un numéro de BL. + {canModify ? ( + " Vous pouvez dévalider ces rapprochements si nécessaire." + ) : ( + " Seuls les directeurs et administrateurs peuvent modifier ces rapprochements." + )} +

+
+
+
+ + {filteredAutomaticDeliveries.length === 0 ? ( +
+ +

+ Aucun rapprochement automatique trouvé +

+

+ Les livraisons de fournisseurs en mode automatique apparaîtront ici. +

+
+ ) : ( +
+
+ + + + + + + + + + + + + + + + {filteredAutomaticDeliveries.map((delivery: any) => ( + + + + - - - ); - })} - -
+ Fournisseur + AUTO + + N° BL + + Date Livr. + + Date Valid. + + Montant BL + + Ref. Facture + + Montant Fact. + + Magasin + + Actions +
+
+
+ {delivery.supplier?.name} +
+ +
+
+
+ {delivery.blNumber || '-'} +
+
+
+ {safeFormat(delivery.scheduledDate, 'dd/MM/yy')} +
+
{delivery.reconciled && delivery.updatedAt ? @@ -756,338 +398,65 @@ export default function BLReconciliation() {
{delivery.blAmount ? `${parseFloat(delivery.blAmount).toFixed(2)}€` : - ( -
- Non renseigné - -
- ) + '-' }
- {delivery.invoiceReference ? ( -
- {delivery.invoiceReference} - {invoiceVerifications[delivery.id] && ( -
- {invoiceVerifications[delivery.id].exists ? ( - - ) : ( - - )} -
- )} -
- ) : ( - - )} + {delivery.invoiceReference || '-'}
{delivery.invoiceAmount ? `${parseFloat(delivery.invoiceAmount).toFixed(2)}€` : - + '-' }
-
0 ? 'text-orange-600' : 'text-red-600' - }`}> - {difference === null ? '-' : `${difference.toFixed(2)}€`} +
+ {delivery.group?.name}
-
-
- {delivery.group?.name} -
-
-
- {getStatusBadge(delivery)} - {!delivery.reconciled && ( +
+ {canModify ? ( <> - - {canValidate(delivery) && ( - - )} - - )} - {user?.role === 'admin' && ( - - )} -
-
-
- - {/* Pagination en bas */} - {totalItems > 0 && ( - - )} -
- )} -
- - {/* Reconciliation Modal */} - - - - - - Modifier les données de rapprochement - -

- Modifier les informations du bon de livraison et de la facture -

-
- -
- - {/* Informations générales */} -
-

Informations générales

-
-

Fournisseur: {selectedDelivery?.supplier?.name}

-

Magasin: {selectedDelivery?.group?.name}

-
-
- - {/* Données BL */} -
-

Bon de livraison

- - ( - - Numéro de BL - - - - - - )} - /> - - ( - - Montant BL (€) - - - - - - )} - /> -
- - {/* Données facture */} -
-

Facture

- - ( - - Référence Facture - -
- - {/* Indicateur de vérification en temps réel */} -
- {isVerifyingCurrentInvoice && ( - - )} - {!isVerifyingCurrentInvoice && field.value && field.value.trim() && selectedDelivery && invoiceVerifications[selectedDelivery.id] && ( -
- {invoiceVerifications[selectedDelivery.id].exists ? ( - - ) : ( - - )} -
+ + + + + ) : ( + )}
-
-
- - {/* Status de vérification */} - {!isVerifyingCurrentInvoice && field.value && field.value.trim() && selectedDelivery && invoiceVerifications[selectedDelivery.id] && ( -
- {invoiceVerifications[selectedDelivery.id].exists ? ( -

- - Facture trouvée dans NocoDB -

- ) : ( -

- - Facture non trouvée dans NocoDB -

- )} -
- )} -
- )} - /> - - ( - - Montant Facture (€) - - - - - - )} - /> + + + ))} + +
- - {/* Aperçu de l'écart */} - {form.watch("blAmount") && form.watch("invoiceAmount") && ( -
-

Aperçu de l'écart

-
-

Montant BL: {parseFloat(form.watch("blAmount") || "0").toFixed(2)} €

-

Montant Facture: {parseFloat(form.watch("invoiceAmount") || "0").toFixed(2)} €

-

- Écart: {(parseFloat(form.watch("blAmount") || "0") - parseFloat(form.watch("invoiceAmount") || "0")).toFixed(2)} € -

-
-
- )} - - {/* Actions */} -
- - -
-
- -
-
- - {/* Modal de confirmation de suppression */} - { - setShowDeleteModal(false); - setDeliveryToDelete(null); - }} - onConfirm={confirmDeleteDelivery} - title="Supprimer la livraison" - description="Êtes-vous sûr de vouloir supprimer cette livraison du module rapprochement ?" - itemName={deliveryToDelete ? `${deliveryToDelete.supplier?.name} - BL ${deliveryToDelete.blNumber}` : undefined} - isLoading={deleteDeliveryMutation.isPending} - /> +
+ )} + +
); } \ No newline at end of file diff --git a/client/src/pages/BLReconciliation_backup.tsx b/client/src/pages/BLReconciliation_backup.tsx new file mode 100644 index 0000000..f4f9b92 --- /dev/null +++ b/client/src/pages/BLReconciliation_backup.tsx @@ -0,0 +1,1099 @@ +import React, { useState, useEffect } from "react"; +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { format } from "date-fns"; +import { fr } from "date-fns/locale"; +import { safeFormat } from "@/lib/dateUtils"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Badge } from "@/components/ui/badge"; +import { Pagination, usePagination } from "@/components/ui/pagination"; +import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; +import { useToast } from "@/hooks/use-toast"; +import { apiRequest } from "@/lib/queryClient"; +import { useStore } from "@/components/Layout"; +import { useAuthUnified } from "@/hooks/useAuthUnified"; +import { Search, Plus, Edit, FileText, Euro, Calendar, Building2, CheckCircle, X, Trash2, RefreshCw, Loader2, Settings, Eye, AlertTriangle } from "lucide-react"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; +import { cn } from "@/lib/utils"; +import { format as formatDate } from "date-fns"; +import { DayPicker } from "react-day-picker"; +import ConfirmDeleteModal from "@/components/modals/ConfirmDeleteModal"; + +const reconciliationSchema = z.object({ + blNumber: z.string().optional(), + blAmount: z.string().optional().refine((val) => !val || val === '' || (!isNaN(Number(val)) && Number(val) >= 0), { + message: "Le montant BL doit être un nombre positif ou vide", + }), + invoiceReference: z.string().optional(), + invoiceAmount: z.string().optional().refine((val) => !val || val === '' || (!isNaN(Number(val)) && Number(val) >= 0), { + message: "Le montant facture doit être un nombre positif ou vide", + }), +}); + +type ReconciliationForm = z.infer; + +// Composant pour les filtres réutilisables +const ReconciliationFilters = ({ + searchTerm, + setSearchTerm, + selectedDate, + setSelectedDate, + isDatePickerOpen, + setIsDatePickerOpen, + isVerifyingInvoices, + verifyAllInvoices +}: { + searchTerm: string; + setSearchTerm: (term: string) => void; + selectedDate: Date | null; + setSelectedDate: (date: Date | null) => void; + isDatePickerOpen: boolean; + setIsDatePickerOpen: (open: boolean) => void; + isVerifyingInvoices?: boolean; + verifyAllInvoices?: () => void; +}) => ( +
+
+
+ + setSearchTerm(e.target.value)} + className="pl-10 border border-gray-300 shadow-sm" + /> +
+ +
+ + + + + + { + setSelectedDate(date); + setIsDatePickerOpen(false); + }} + locale={fr} + className="rounded-md border" + /> + + + + {selectedDate && ( + + )} + + {verifyAllInvoices && ( + + )} +
+
+
+); + +// Composant pour la table des rapprochements manuels +const ManualReconciliationTab = ({ + deliveries, + searchTerm, + setSearchTerm, + selectedDate, + setSelectedDate, + isDatePickerOpen, + setIsDatePickerOpen, + invoiceVerifications, + isVerifyingInvoices, + verifyAllInvoices, + handleEditReconciliation, + handleDeleteDelivery, + updateReconciliationMutation, + deleteDeliveryMutation, + calculateDifference +}: any) => { + // Filtrage des livraisons + const filteredDeliveries = deliveries.filter((delivery: any) => { + const searchLower = searchTerm.toLowerCase(); + return ( + delivery.supplier?.name?.toLowerCase().includes(searchLower) || + delivery.blNumber?.toLowerCase().includes(searchLower) || + delivery.invoiceReference?.toLowerCase().includes(searchLower) + ); + }); + + // Pagination + const { currentPage, setCurrentPage, itemsPerPage, setItemsPerPage, paginatedItems: paginatedDeliveries, totalPages, totalItems } = usePagination(filteredDeliveries, 20); + + return ( +
+ + + {totalItems === 0 ? ( +
+ +

+ Aucun rapprochement manuel trouvé +

+

+ Les livraisons de fournisseurs en mode manuel apparaîtront ici. +

+
+ ) : ( +
+ {totalItems > 0 && ( + + )} +
+ + + + + + + + + + + + + + + + {paginatedDeliveries.map((delivery: any) => { + const difference = calculateDifference( + parseFloat(delivery.blAmount || '0'), + delivery.invoiceAmount ? parseFloat(delivery.invoiceAmount) : undefined + ); + + return ( + + + + + + + + + + + + ); + })} + +
+ Fournisseur + + N° BL + + Date Livr. + + Montant BL + + Ref. Facture + + Montant Fact. + + Écart + + Magasin + + Actions +
+
+ {delivery.supplier?.name} +
+
+
+ {delivery.blNumber || ( +
+ Non renseigné + +
+ )} +
+
+
+ {safeFormat(delivery.scheduledDate, 'dd/MM/yy')} +
+
+
+ {delivery.blAmount ? + `${parseFloat(delivery.blAmount).toFixed(2)}€` : + ( +
+ Non renseigné + +
+ ) + } +
+
+
+ {delivery.invoiceReference ? ( +
+ {delivery.invoiceReference} + {invoiceVerifications[delivery.id] && ( +
+ {invoiceVerifications[delivery.id].exists ? ( + + ) : ( + + )} +
+ )} +
+ ) : ( + + )} +
+
+
+ {delivery.invoiceAmount ? + `${parseFloat(delivery.invoiceAmount).toFixed(2)}€` : + + } +
+
+
+ {difference?.display || '-'} +
+
+
+ {delivery.group?.name} +
+
+
+ + +
+
+
+ {totalItems > 0 && ( + + )} +
+ )} +
+ ); +}; + +// Composant pour la table des rapprochements automatiques +const AutomaticReconciliationTab = ({ + deliveries, + searchTerm, + setSearchTerm, + selectedDate, + setSelectedDate, + isDatePickerOpen, + setIsDatePickerOpen, + invoiceVerifications, + user, + calculateDifference, + handleDevalidateAutoReconciliation +}: any) => { + // Filtrage des livraisons + const filteredDeliveries = deliveries.filter((delivery: any) => { + const searchLower = searchTerm.toLowerCase(); + return ( + delivery.supplier?.name?.toLowerCase().includes(searchLower) || + delivery.blNumber?.toLowerCase().includes(searchLower) || + delivery.invoiceReference?.toLowerCase().includes(searchLower) + ); + }); + + // Pagination + const { currentPage, setCurrentPage, itemsPerPage, setItemsPerPage, paginatedItems: paginatedDeliveries, totalPages, totalItems } = usePagination(filteredDeliveries, 20); + + const canModify = user?.role === 'directeur' || user?.role === 'admin'; + + return ( +
+ {/* Message d'information */} +
+
+ +
+

Mode rapprochement automatique

+

+ Les livraisons de fournisseurs en mode automatique sont validées automatiquement lorsqu'elles ont le statut "delivered" et un numéro de BL. + {canModify ? ( + " Vous pouvez dévalider ou supprimer ces rapprochements si nécessaire." + ) : ( + " Seuls les directeurs et administrateurs peuvent modifier ces rapprochements." + )} +

+
+
+
+ + + + {totalItems === 0 ? ( +
+ +

+ Aucun rapprochement automatique trouvé +

+

+ Les livraisons de fournisseurs en mode automatique apparaîtront ici. +

+
+ ) : ( +
+ {totalItems > 0 && ( + + )} +
+ + + + + + + + + + + + + + + + + {paginatedDeliveries.map((delivery: any) => { + const difference = calculateDifference( + parseFloat(delivery.blAmount || '0'), + delivery.invoiceAmount ? parseFloat(delivery.invoiceAmount) : undefined + ); + + return ( + + + + + + + + + + + + + ); + })} + +
+ Fournisseur + AUTO + + N° BL + + Date Livr. + + Date Valid. + + Montant BL + + Ref. Facture + + Montant Fact. + + Écart + + Magasin + + Actions +
+
+
+ {delivery.supplier?.name} +
+ +
+
+
+ {delivery.blNumber || '-'} +
+
+
+ {safeFormat(delivery.scheduledDate, 'dd/MM/yy')} +
+
+
+ {delivery.reconciled && delivery.updatedAt ? + safeFormat(delivery.updatedAt, 'dd/MM/yy') : + '-' + } +
+
+
+ {delivery.blAmount ? + `${parseFloat(delivery.blAmount).toFixed(2)}€` : + '-' + } +
+
+
+ {delivery.invoiceReference ? ( +
+ {delivery.invoiceReference} + {invoiceVerifications[delivery.id] && ( +
+ {invoiceVerifications[delivery.id].exists ? ( + + ) : ( + + )} +
+ )} +
+ ) : '-'} +
+
+
+ {delivery.invoiceAmount ? + `${parseFloat(delivery.invoiceAmount).toFixed(2)}€` : + '-' + } +
+
+
+ {difference?.display || '-'} +
+
+
+ {delivery.group?.name} +
+
+
+ {canModify ? ( + <> + + + + ) : ( + + )} +
+
+
+ {totalItems > 0 && ( + + )} +
+ )} +
+ ); +}; + +export default function BLReconciliation() { + const { user } = useAuthUnified(); + const { selectedStoreId } = useStore(); + const { toast } = useToast(); + const queryClient = useQueryClient(); + + // Redirection pour les employés + if (user?.role === 'employee') { + return ( +
+
+
+
+ +
+
+

+ Accès restreint
+ Seuls les managers et administrateurs peuvent accéder au module de rapprochement BL/Factures. +

+
+
+
+
+ ); + } + + const [activeTab, setActiveTab] = useState("manual"); + const [searchTerm, setSearchTerm] = useState(""); + const [showReconciliationModal, setShowReconciliationModal] = useState(false); + const [selectedDelivery, setSelectedDelivery] = useState(null); + const [selectedDate, setSelectedDate] = useState(null); + const [isDatePickerOpen, setIsDatePickerOpen] = useState(false); + const [showDeleteModal, setShowDeleteModal] = useState(false); + const [deliveryToDelete, setDeliveryToDelete] = useState(null); + const [invoiceVerifications, setInvoiceVerifications] = useState>({}); + const [isVerifyingInvoices, setIsVerifyingInvoices] = useState(false); + const [isVerifyingCurrentInvoice, setIsVerifyingCurrentInvoice] = useState(false); + + // Récupérer les fournisseurs pour la logique automatique + const { data: suppliers = [] } = useQuery({ + queryKey: ['/api/suppliers'], + }); + + // Récupérer les livraisons validées avec BL + const { data: deliveriesWithBL = [], isLoading } = useQuery({ + queryKey: ['/api/deliveries/bl', selectedStoreId, selectedDate], + queryFn: async () => { + const params = new URLSearchParams({}); + if (selectedStoreId && user?.role === 'admin') { + params.append('storeId', selectedStoreId.toString()); + } + + if (selectedDate) { + const dateStr = formatDate(selectedDate, 'yyyy-MM-dd'); + params.append('startDate', dateStr); + params.append('endDate', dateStr); + } + + const response = await fetch(`/api/deliveries?${params.toString()}`, { + credentials: 'include' + }); + + if (!response.ok) { + throw new Error('Failed to fetch deliveries'); + } + + const deliveries = await response.json(); + const filtered = Array.isArray(deliveries) ? deliveries.filter((d: any) => d.status === 'delivered') : []; + + return filtered.sort((a: any, b: any) => new Date(b.deliveredDate).getTime() - new Date(a.deliveredDate).getTime()); + }, + }); + + // Séparer les livraisons selon le mode de rapprochement du fournisseur + const manualReconciliationDeliveries = deliveriesWithBL.filter((delivery: any) => { + const supplier = suppliers.find((s: any) => s.id === delivery.supplierId); + return !supplier?.automaticReconciliation; + }); + + const automaticReconciliationDeliveries = deliveriesWithBL.filter((delivery: any) => { + const supplier = suppliers.find((s: any) => s.id === delivery.supplierId); + return supplier?.automaticReconciliation; + }); + + // Fonction pour dévalider un rapprochement automatique (directeurs et admins uniquement) + const handleDevalidateAutoReconciliation = async (deliveryId: number) => { + if (user?.role !== 'directeur' && user?.role !== 'admin') { + toast({ + title: "Accès refusé", + description: "Seuls les directeurs et administrateurs peuvent dévalider les rapprochements automatiques", + variant: "destructive", + }); + return; + } + + try { + await apiRequest(`/api/deliveries/${deliveryId}`, "PUT", { + reconciled: false, + validatedAt: null + }); + + toast({ + title: "Succès", + description: "Rapprochement automatique dévalidé avec succès", + }); + + queryClient.invalidateQueries({ queryKey: ['/api/deliveries/bl'] }); + } catch (error) { + toast({ + title: "Erreur", + description: "Impossible de dévalider le rapprochement", + variant: "destructive", + }); + } + }; + + // Fonctions réutilisées de l'ancien composant + const calculateDifference = (blAmount: number, invoiceAmount?: number) => { + if (!invoiceAmount || invoiceAmount === 0) return null; + + const diff = blAmount - invoiceAmount; + const tolerance = 0.01; + + if (Math.abs(diff) < tolerance) { + return { display: '0.00€', color: 'text-green-600' }; + } else if (diff > 0) { + return { display: `+${diff.toFixed(2)}€`, color: 'text-red-600' }; + } else { + return { display: `${diff.toFixed(2)}€`, color: 'text-red-600' }; + } + }; + + const form = useForm({ + resolver: zodResolver(reconciliationSchema), + defaultValues: { + blNumber: "", + blAmount: "", + invoiceReference: "", + invoiceAmount: "", + }, + }); + + const updateReconciliationMutation = useMutation({ + mutationFn: async (data: { id: number; blNumber: string; blAmount: string; invoiceReference: string; invoiceAmount: string }) => { + const response = await apiRequest(`/api/deliveries/${data.id}`, "PUT", { + blNumber: data.blNumber, + blAmount: data.blAmount, + invoiceReference: data.invoiceReference, + invoiceAmount: data.invoiceAmount, + }); + return response; + }, + onSuccess: () => { + toast({ + title: "Succès", + description: "Données de rapprochement mises à jour avec succès", + }); + setShowReconciliationModal(false); + queryClient.invalidateQueries({ queryKey: ['/api/deliveries/bl'] }); + }, + onError: () => { + toast({ + title: "Erreur", + description: "Impossible de mettre à jour les données de rapprochement", + variant: "destructive", + }); + }, + }); + + const deleteDeliveryMutation = useMutation({ + mutationFn: async (id: number) => { + await apiRequest(`/api/deliveries/${id}`, "DELETE"); + return id; + }, + onSuccess: () => { + toast({ + title: "Succès", + description: "Livraison supprimée avec succès", + }); + setShowDeleteModal(false); + setDeliveryToDelete(null); + queryClient.invalidateQueries({ queryKey: ['/api/deliveries/bl'] }); + }, + onError: () => { + toast({ + title: "Erreur", + description: "Impossible de supprimer la livraison", + variant: "destructive", + }); + }, + }); + + const handleEditReconciliation = (delivery: any) => { + setSelectedDelivery(delivery); + form.reset({ + blNumber: delivery.blNumber || "", + blAmount: delivery.blAmount || "", + invoiceReference: delivery.invoiceReference || "", + invoiceAmount: delivery.invoiceAmount || "", + }); + setShowReconciliationModal(true); + }; + + const handleDeleteDelivery = (delivery: any) => { + setDeliveryToDelete(delivery); + setShowDeleteModal(true); + }; + + const confirmDeleteDelivery = () => { + if (deliveryToDelete) { + deleteDeliveryMutation.mutate(deliveryToDelete.id); + } + }; + + const onSubmit = (data: ReconciliationForm) => { + if (!selectedDelivery) return; + + updateReconciliationMutation.mutate({ + id: selectedDelivery.id, + blNumber: data.blNumber || "", + blAmount: data.blAmount || "", + invoiceReference: data.invoiceReference || "", + invoiceAmount: data.invoiceAmount || "", + }); + }; + + const verifyAllInvoices = async () => { + // Implémentation similaire à l'ancien composant + console.log("Verification des factures..."); + }; + + return ( +
+ {/* Header avec onglets */} +
+
+
+

+ + Rapprochement BL/Factures +

+

+ Gestion des rapprochements manuels et automatiques +

+
+
+ + {manualReconciliationDeliveries.length} rapprochements manuels + + + {automaticReconciliationDeliveries.length} rapprochements automatiques + +
+
+ + {/* Onglets */} + + + + + Rapprochement Manuel + + {manualReconciliationDeliveries.length} + + + + + Rapprochement Automatique + + {automaticReconciliationDeliveries.length} + + + + +
+ + {/* Contenu des onglets */} + + + + + + + + + + + {/* Modal de modification des données de rapprochement */} + + + + + Données de rapprochement - {selectedDelivery?.supplier?.name} + + +
+ + {/* Données BL */} +
+

Bon de Livraison

+ +
+ ( + + Numéro BL + + + + + + )} + /> + + ( + + Montant BL (€) + + + + + + )} + /> +
+
+ + {/* Données facture */} +
+

Facture

+ + ( + + Référence Facture + + + + + + )} + /> + + ( + + Montant Facture (€) + + + + + + )} + /> +
+ + {/* Aperçu de l'écart */} + {form.watch("blAmount") && form.watch("invoiceAmount") && ( +
+

Aperçu de l'écart

+
+

Montant BL: {parseFloat(form.watch("blAmount") || "0").toFixed(2)} €

+

Montant Facture: {parseFloat(form.watch("invoiceAmount") || "0").toFixed(2)} €

+

+ Écart: {(parseFloat(form.watch("blAmount") || "0") - parseFloat(form.watch("invoiceAmount") || "0")).toFixed(2)} € +

+
+
+ )} + + {/* Actions */} +
+ + +
+
+ +
+
+ + {/* Modal de confirmation de suppression */} + { + setShowDeleteModal(false); + setDeliveryToDelete(null); + }} + onConfirm={confirmDeleteDelivery} + title="Supprimer la livraison" + description="Êtes-vous sûr de vouloir supprimer cette livraison du module rapprochement ?" + itemName={deliveryToDelete ? `${deliveryToDelete.supplier?.name} - BL ${deliveryToDelete.blNumber}` : undefined} + isLoading={deleteDeliveryMutation.isPending} + /> +
+ ); +} \ No newline at end of file diff --git a/client/src/pages/Suppliers.tsx b/client/src/pages/Suppliers.tsx index 45309ba..19171de 100644 --- a/client/src/pages/Suppliers.tsx +++ b/client/src/pages/Suppliers.tsx @@ -20,7 +20,8 @@ import { Package, Truck, CheckCircle, - XCircle + XCircle, + Clock } from "lucide-react"; import type { Supplier } from "@shared/schema"; @@ -37,7 +38,10 @@ export default function Suppliers() { name: "", contact: "", phone: "", + email: "", + address: "", hasDlc: false, + automaticReconciliation: false, }); const { data: suppliers = [], isLoading } = useQuery({ @@ -66,7 +70,15 @@ export default function Suppliers() { }); queryClient.invalidateQueries({ queryKey: ['/api/suppliers'] }); setShowCreateModal(false); - setFormData({ name: "", contact: "", phone: "", hasDlc: false }); + setFormData({ + name: "", + contact: "", + phone: "", + email: "", + address: "", + hasDlc: false, + automaticReconciliation: false + }); }, onError: (error) => { if (isUnauthorizedError(error)) { @@ -137,7 +149,15 @@ export default function Suppliers() { setShowEditModal(false); setSelectedSupplier(null); - setFormData({ name: "", contact: "", phone: "", hasDlc: false }); + setFormData({ + name: "", + contact: "", + phone: "", + email: "", + address: "", + hasDlc: false, + automaticReconciliation: false + }); }, onError: (error, variables, context) => { // Rollback optimiste en cas d'erreur @@ -214,7 +234,15 @@ export default function Suppliers() { }; const handleCreate = () => { - setFormData({ name: "", contact: "", phone: "", hasDlc: false }); + setFormData({ + name: "", + contact: "", + phone: "", + email: "", + address: "", + hasDlc: false, + automaticReconciliation: false + }); setShowCreateModal(true); }; @@ -224,7 +252,10 @@ export default function Suppliers() { name: supplier.name, contact: supplier.contact || "", phone: supplier.phone || "", + email: supplier.email || "", + address: supplier.address || "", hasDlc: supplier.hasDlc || false, + automaticReconciliation: supplier.automaticReconciliation || false, }); setShowEditModal(true); }; @@ -400,19 +431,30 @@ export default function Suppliers() { {supplier.phone}
)} -
- {supplier.hasDlc ? ( -
- - DLC activé -
- ) : ( -
- - DLC désactivé -
- )} +
+
+ {supplier.hasDlc && ( +
+ + DLC +
+ )} + {supplier.automaticReconciliation && ( +
+ + Auto-rapprochement +
+ )} +
+ + {!supplier.hasDlc && !supplier.automaticReconciliation && ( +
+ Aucune option activée +
+ )} + + {/* Statistics section follows */}
@@ -453,7 +495,15 @@ export default function Suppliers() { setShowCreateModal(false); setShowEditModal(false); setSelectedSupplier(null); - setFormData({ name: "", contact: "", phone: "", hasDlc: false }); + setFormData({ + name: "", + contact: "", + phone: "", + email: "", + address: "", + hasDlc: false, + automaticReconciliation: false + }); }}> @@ -497,6 +547,27 @@ export default function Suppliers() { />
+
+ + handleChange('email', e.target.value)} + placeholder="Adresse email" + /> +
+ +
+ + handleChange('address', e.target.value)} + placeholder="Adresse complète" + /> +
+
+
+ handleChange('automaticReconciliation', checked === true)} + /> + +
+