diff --git a/client/src/pages/BLReconciliation.tsx b/client/src/pages/BLReconciliation.tsx index c2243a9..5b1d3be 100644 --- a/client/src/pages/BLReconciliation.tsx +++ b/client/src/pages/BLReconciliation.tsx @@ -4,13 +4,15 @@ import { safeFormat } from "@/lib/dateUtils"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { useToast } from "@/hooks/use-toast"; import { apiRequest } from "@/lib/queryClient"; import { useStore } from "@/components/Layout"; import { useAuthUnified } from "@/hooks/useAuthUnified"; import { usePermissions } from "@shared/permissions"; -import { Search, Edit, FileText, Settings, Eye, AlertTriangle, X, Check, Trash2, Ban } from "lucide-react"; +import { Pagination, usePagination } from "@/components/ui/pagination"; +import { Search, Edit, FileText, Settings, Eye, AlertTriangle, X, Check, Trash2, Ban, Filter } from "lucide-react"; import ReconciliationModal from "@/components/modals/ReconciliationModal"; export default function BLReconciliation() { @@ -73,50 +75,52 @@ export default function BLReconciliation() { return filtered.sort((a: any, b: any) => new Date(b.deliveredDate || b.updatedAt).getTime() - new Date(a.deliveredDate || a.updatedAt).getTime()); }, + enabled: !!user }); - // Séparer les livraisons selon le mode de rapprochement du fournisseur + // Séparer les livraisons par mode de rapprochement const manualReconciliationDeliveries = deliveriesWithBL.filter((delivery: any) => { - const supplier = suppliers.find((s: any) => s.id === delivery.supplierId); - return !supplier?.automaticReconciliation; + const supplier = suppliers.find(s => s.id === delivery.supplierId); + return !supplier?.isAutoReconciliation; }); const automaticReconciliationDeliveries = deliveriesWithBL.filter((delivery: any) => { - const supplier = suppliers.find((s: any) => s.id === delivery.supplierId); - return supplier?.automaticReconciliation; + const supplier = suppliers.find(s => s.id === delivery.supplierId); + return supplier?.isAutoReconciliation; }); - // Fonction pour ouvrir le modal d'édition + // Fonctions de gestion const handleOpenModal = (delivery: any) => { setSelectedDelivery(delivery); setIsModalOpen(true); }; - // Fonction pour valider un rapprochement rapidement (avec validation des données) + const handleCloseModal = () => { + setSelectedDelivery(null); + setIsModalOpen(false); + }; + + const handleSaveReconciliation = async (data: any) => { + try { + await apiRequest(`/api/deliveries/${selectedDelivery.id}`, "PUT", data); + + toast({ + title: "Succès", + description: "Données de rapprochement mises à jour", + }); + + queryClient.invalidateQueries({ queryKey: ['/api/deliveries/bl'] }); + handleCloseModal(); + } catch (error) { + toast({ + title: "Erreur", + description: "Impossible de mettre à jour les données", + variant: "destructive", + }); + } + }; + const handleQuickValidate = async (delivery: any) => { - if (!permissions.canValidate('reconciliation')) { - toast({ - title: "Accès refusé", - description: "Vous n'avez pas les permissions pour valider les rapprochements", - variant: "destructive", - }); - return; - } - - // Vérifier que les données minimales sont présentes - if (!delivery.blNumber || delivery.blNumber.trim() === '') { - toast({ - title: "Données manquantes", - description: "Le numéro de BL est requis. Cliquez sur 'Voir les détails' pour le renseigner.", - variant: "destructive", - }); - return; - } - - if (!window.confirm("Êtes-vous sûr de vouloir valider ce rapprochement ?")) { - return; - } - try { await apiRequest(`/api/deliveries/${delivery.id}`, "PUT", { reconciled: true, @@ -138,7 +142,6 @@ export default function BLReconciliation() { } }; - // Fonction pour dévalider un rapprochement (admins uniquement) const handleDevalidateReconciliation = async (deliveryId: number) => { if (user?.role !== 'admin') { toast({ @@ -174,7 +177,6 @@ export default function BLReconciliation() { } }; - // Fonction pour supprimer une livraison (admins uniquement) const handleDeleteDelivery = async (deliveryId: number) => { if (!permissions.canDelete('reconciliation')) { toast({ @@ -207,42 +209,6 @@ export default function BLReconciliation() { } }; - // Fonction pour dévalider un rapprochement automatique (admins uniquement) - const handleDevalidateAutoReconciliation = async (deliveryId: number) => { - if (user?.role !== 'admin') { - toast({ - title: "Accès refusé", - description: "Seuls les administrateurs peuvent dévalider les rapprochements automatiques", - variant: "destructive", - }); - return; - } - - if (!window.confirm("Êtes-vous sûr de vouloir dévalider ce rapprochement automatique ?")) { - 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", - }); - } - }; - // Filtrage des livraisons par recherche const filterDeliveries = (deliveries: any[]) => { return deliveries.filter((delivery: any) => { @@ -258,11 +224,37 @@ export default function BLReconciliation() { const filteredManualDeliveries = filterDeliveries(manualReconciliationDeliveries); const filteredAutomaticDeliveries = filterDeliveries(automaticReconciliationDeliveries); + // Pagination pour les rapprochements manuels + const { + currentPage: manualCurrentPage, + setCurrentPage: setManualCurrentPage, + itemsPerPage: manualItemsPerPage, + setItemsPerPage: setManualItemsPerPage, + totalPages: manualTotalPages, + paginatedData: paginatedManualDeliveries, + totalItems: manualTotalItems + } = usePagination(filteredManualDeliveries, 20); + + // Pagination pour les rapprochements automatiques + const { + currentPage: autoCurrentPage, + setCurrentPage: setAutoCurrentPage, + itemsPerPage: autoItemsPerPage, + setItemsPerPage: setAutoItemsPerPage, + totalPages: autoTotalPages, + paginatedData: paginatedAutoDeliveries, + totalItems: autoTotalItems + } = usePagination(filteredAutomaticDeliveries, 20); + const canModify = user?.role === 'directeur' || user?.role === 'admin'; + if (isLoading) { + return
Chargement...
; + } + return (
- {/* Header avec onglets */} + {/* Header */}
@@ -306,17 +298,19 @@ export default function BLReconciliation() {
{/* Filtre de recherche */} -
-
- - setSearchTerm(e.target.value)} - className="pl-10 border border-gray-300 shadow-sm" - /> -
-
+ + +
+ + setSearchTerm(e.target.value)} + className="pl-10 border border-gray-300 shadow-sm" + /> +
+
+
{/* Contenu des onglets */} @@ -332,182 +326,210 @@ export default function BLReconciliation() {

) : ( -
-
- - - - - - - - - - - - - - - - {filteredManualDeliveries.map((delivery: any) => ( - - - - - - - - - - + + ))} + +
- 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 || ( - Non renseigné - )} -
-
-
- {delivery.invoiceAmount ? - `${parseFloat(delivery.invoiceAmount).toFixed(2)}€` : - Non renseigné - } -
-
- {(() => { - const blAmount = delivery.blAmount ? parseFloat(delivery.blAmount) : 0; - const invoiceAmount = delivery.invoiceAmount ? parseFloat(delivery.invoiceAmount) : 0; - if (blAmount && invoiceAmount) { - const diff = blAmount - invoiceAmount; - const diffAbs = Math.abs(diff); - return ( -
10 ? 'text-red-600' : 'text-orange-600' - }`}> - {diff > 0 ? '+' : ''}{diff.toFixed(2)}€ -
- ); - } - return -; - })()} -
-
- {delivery.group?.name} -
-
-
- {/* Actions selon le statut de rapprochement et les permissions */} - {!delivery.reconciled ? ( - // Livraison non rapprochée : Valider + Supprimer - <> - {permissions.canValidate('reconciliation') && ( + + + + + Rapprochements Manuels + + + + {/* Pagination du haut */} + + +
+
+ + + + + + + + + + + + + + + + {paginatedManualDeliveries.map((delivery: any) => ( + + + + + + + + + + - - ))} - -
+ 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 || ( + Non renseigné + )} +
+
+
+ {delivery.invoiceAmount ? + `${parseFloat(delivery.invoiceAmount).toFixed(2)}€` : + Non renseigné + } +
+
+ {(() => { + const blAmount = delivery.blAmount ? parseFloat(delivery.blAmount) : 0; + const invoiceAmount = delivery.invoiceAmount ? parseFloat(delivery.invoiceAmount) : 0; + if (blAmount && invoiceAmount) { + const diff = blAmount - invoiceAmount; + const diffAbs = Math.abs(diff); + return ( +
10 ? 'text-red-600' : 'text-orange-600' + }`}> + {diff > 0 ? '+' : ''}{diff.toFixed(2)}€ +
+ ); + } + return -; + })()} +
+
+ {delivery.group?.name} +
+
+
+ {!delivery.reconciled ? ( + <> + {permissions.canValidate('reconciliation') && ( + + )} + {permissions.canDelete('reconciliation') && ( + + )} + + ) : ( + <> + {user?.role === 'admin' && ( + + )} + {permissions.canDelete('reconciliation') && ( + + )} + + )} + {!delivery.reconciled && ( )} - {permissions.canDelete('reconciliation') && ( - - )} - - ) : ( - // Livraison rapprochée : Dévalider (admin seulement) + Supprimer - <> - {user?.role === 'admin' && ( - - )} - {permissions.canDelete('reconciliation') && ( - - )} - - )} - {/* Bouton Modifier : disponible seulement si pas encore validé */} - {!delivery.reconciled && ( - - )} -
-
-
-
+
+
+
+
+ + {/* Pagination du bas */} + + + )} @@ -541,179 +563,144 @@ export default function BLReconciliation() {

) : ( -
-
- - - - - - - - - - - - - - - - - {filteredAutomaticDeliveries.map((delivery: any) => ( - - - - - - - - - - - - - ))} - -
- 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.invoiceAmount ? - `${parseFloat(delivery.invoiceAmount).toFixed(2)}€` : - '-' - } -
-
- {(() => { - const blAmount = delivery.blAmount ? parseFloat(delivery.blAmount) : 0; - const invoiceAmount = delivery.invoiceAmount ? parseFloat(delivery.invoiceAmount) : 0; - if (blAmount && invoiceAmount) { - const diff = blAmount - invoiceAmount; - const diffAbs = Math.abs(diff); - return ( -
10 ? 'text-red-600' : 'text-orange-600' - }`}> - {diff > 0 ? '+' : ''}{diff.toFixed(2)}€ -
- ); - } - return -; - })()} -
-
- {delivery.group?.name} -
-
-
- {user?.role === 'admin' && delivery.reconciled && ( - - )} - {user?.role === 'admin' && ( - - )} - {/* Bouton Modifier : disponible seulement si pas encore validé */} - {!delivery.reconciled && ( - - )} -
-
-
-
+ + + + + Rapprochements Automatiques + + + + {/* Pagination du haut */} + + +
+
+ + + + + + + + + + + + + {paginatedAutoDeliveries.map((delivery: any) => ( + + + + + + + + + ))} + +
+ Fournisseur + AUTO + + N° BL + + Date Livr. + + Date Valid. + + Magasin + + Actions +
+
+ {delivery.supplier?.name} +
+
+
+ {delivery.blNumber || ( + Non renseigné + )} +
+
+
+ {safeFormat(delivery.scheduledDate, 'dd/MM/yy')} +
+
+
+ {delivery.validatedAt ? + safeFormat(delivery.validatedAt, 'dd/MM/yy HH:mm') : + Non validé + } +
+
+
+ {delivery.group?.name} +
+
+
+ {user?.role === 'admin' && ( + + )} + {permissions.canDelete('reconciliation') && ( + + )} + +
+
+
+
+ + {/* Pagination du bas */} + +
+
)} {/* Modal de rapprochement */} - setIsModalOpen(false)} - delivery={selectedDelivery} - onSave={() => { - // Rafraîchir les données après sauvegarde - queryClient.invalidateQueries({ queryKey: ['/api/deliveries/bl'] }); - }} - /> + {isModalOpen && selectedDelivery && ( + + )}
); } \ No newline at end of file