diff --git a/attached_assets/{196C82CB-1B5E-465C-8E2E-2085A6374715}_1754994443183.png b/attached_assets/{196C82CB-1B5E-465C-8E2E-2085A6374715}_1754994443183.png new file mode 100644 index 0000000..51e8462 Binary files /dev/null and b/attached_assets/{196C82CB-1B5E-465C-8E2E-2085A6374715}_1754994443183.png differ diff --git a/client/src/pages/BLReconciliation.tsx b/client/src/pages/BLReconciliation.tsx index cad52f9..c8de9b8 100644 --- a/client/src/pages/BLReconciliation.tsx +++ b/client/src/pages/BLReconciliation.tsx @@ -9,16 +9,19 @@ import { useToast } from "@/hooks/use-toast"; import { apiRequest } from "@/lib/queryClient"; import { useStore } from "@/components/Layout"; import { useAuthUnified } from "@/hooks/useAuthUnified"; -import { Search, Edit, FileText, Settings, Eye, AlertTriangle, X } from "lucide-react"; +import { usePermissions } from "@shared/permissions"; +import { Search, Edit, FileText, Settings, Eye, AlertTriangle, X, Check, Trash2, Ban } from "lucide-react"; +import type { Supplier } from "@shared/schema"; export default function BLReconciliation() { const { user } = useAuthUnified(); const { selectedStoreId } = useStore(); const { toast } = useToast(); const queryClient = useQueryClient(); + const permissions = usePermissions(user?.role); - // Redirection pour les employés - if (user?.role === 'employee') { + // Vérification des permissions d'accès au module + if (!permissions.canView('reconciliation')) { return (
@@ -29,7 +32,7 @@ export default function BLReconciliation() {

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

@@ -42,7 +45,7 @@ export default function BLReconciliation() { const [searchTerm, setSearchTerm] = useState(""); // Récupérer les fournisseurs pour la logique automatique - const { data: suppliers = [] } = useQuery({ + const { data: suppliers = [], isLoading: suppliersLoading } = useQuery({ queryKey: ['/api/suppliers'], }); @@ -81,36 +84,114 @@ export default function BLReconciliation() { 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') { + // Mutation pour valider un rapprochement manuel + const validateManualMutation = useMutation({ + mutationFn: async (deliveryId: number) => { + return await apiRequest(`/api/deliveries/${deliveryId}`, "PUT", { + reconciled: true, + validatedAt: new Date().toISOString() + }); + }, + onSuccess: () => { toast({ - title: "Accès refusé", - description: "Seuls les directeurs et administrateurs peuvent dévalider les rapprochements automatiques", + title: "Succès", + description: "Rapprochement validé avec succès", + }); + queryClient.invalidateQueries({ queryKey: ['/api/deliveries/bl'] }); + }, + onError: () => { + toast({ + title: "Erreur", + description: "Impossible de valider le rapprochement", variant: "destructive", }); - return; } + }); - try { - await apiRequest(`/api/deliveries/${deliveryId}`, "PUT", { + // Mutation pour dévalider un rapprochement (admins uniquement) + const devalidateMutation = useMutation({ + mutationFn: async (deliveryId: number) => { + return await apiRequest(`/api/deliveries/${deliveryId}`, "PUT", { reconciled: false, validatedAt: null }); - + }, + onSuccess: () => { toast({ title: "Succès", - description: "Rapprochement automatique dévalidé avec succès", + description: "Rapprochement dévalidé avec succès", }); - queryClient.invalidateQueries({ queryKey: ['/api/deliveries/bl'] }); - } catch (error) { + }, + onError: () => { toast({ title: "Erreur", description: "Impossible de dévalider le rapprochement", variant: "destructive", }); } + }); + + // Mutation pour supprimer une ligne (admins uniquement) + const deleteMutation = useMutation({ + mutationFn: async (deliveryId: number) => { + return await apiRequest(`/api/deliveries/${deliveryId}`, "DELETE"); + }, + onSuccess: () => { + toast({ + title: "Succès", + description: "Ligne supprimée avec succès", + }); + queryClient.invalidateQueries({ queryKey: ['/api/deliveries/bl'] }); + }, + onError: () => { + toast({ + title: "Erreur", + description: "Impossible de supprimer la ligne", + variant: "destructive", + }); + } + }); + + // Fonctions de gestion des actions + const handleValidateManual = (deliveryId: number) => { + if (!permissions.canValidate('reconciliation')) { + toast({ + title: "Accès refusé", + description: "Vous n'avez pas les permissions pour valider les rapprochements", + variant: "destructive", + }); + return; + } + validateManualMutation.mutate(deliveryId); + }; + + const handleDevalidate = (deliveryId: number) => { + if (!permissions.canEdit('reconciliation')) { + toast({ + title: "Accès refusé", + description: "Seuls les administrateurs peuvent dévalider les rapprochements", + variant: "destructive", + }); + return; + } + if (confirm("Êtes-vous sûr de vouloir dévalider ce rapprochement ?")) { + devalidateMutation.mutate(deliveryId); + } + }; + + const handleDelete = (deliveryId: number) => { + if (!permissions.canDelete('reconciliation')) { + toast({ + title: "Accès refusé", + description: "Seuls les administrateurs peuvent supprimer les lignes", + variant: "destructive", + }); + return; + } + if (confirm("Êtes-vous sûr de vouloir supprimer cette ligne ? Cette action est irréversible.")) { + deleteMutation.mutate(deliveryId); + } }; // Filtrage des livraisons par recherche @@ -128,7 +209,21 @@ export default function BLReconciliation() { const filteredManualDeliveries = filterDeliveries(manualReconciliationDeliveries); const filteredAutomaticDeliveries = filterDeliveries(automaticReconciliationDeliveries); - const canModify = user?.role === 'directeur' || user?.role === 'admin'; + // Vérifier si une ligne est dévalidée (pour la coloration) + const isDevalidated = (delivery: any) => { + return delivery.reconciled === false && delivery.validatedAt === null; + }; + + if (isLoading || suppliersLoading) { + return ( +
+
+
+
+
+
+ ); + } return (
@@ -226,7 +321,7 @@ export default function BLReconciliation() { Montant Fact. - Magasin + Statut Actions @@ -235,7 +330,13 @@ export default function BLReconciliation() { {filteredManualDeliveries.map((delivery: any) => ( - +
{delivery.supplier?.name} @@ -277,18 +378,71 @@ export default function BLReconciliation() {
-
- {delivery.group?.name} -
+ {delivery.reconciled ? ( + + Validé + + ) : isDevalidated(delivery) ? ( + + Dévalidé + + ) : ( + + En attente + + )}
- + {permissions.canEdit('reconciliation') && ( + + )} + + {!delivery.reconciled && permissions.canValidate('reconciliation') && ( + + )} + + {delivery.reconciled && permissions.canEdit('reconciliation') && user?.role === 'admin' && ( + + )} + + {permissions.canDelete('reconciliation') && ( + + )}
@@ -309,7 +463,7 @@ export default function BLReconciliation() {

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 ? ( + {permissions.canEdit('reconciliation') ? ( " Vous pouvez dévalider ces rapprochements si nécessaire." ) : ( " Seuls les directeurs et administrateurs peuvent modifier ces rapprochements." @@ -355,10 +509,7 @@ export default function BLReconciliation() { Ref. Facture - Montant Fact. - - - Magasin + Statut Actions @@ -367,13 +518,18 @@ export default function BLReconciliation() { {filteredAutomaticDeliveries.map((delivery: any) => ( - +

{delivery.supplier?.name}
- +
@@ -388,8 +544,8 @@ export default function BLReconciliation() {
- {delivery.reconciled && delivery.updatedAt ? - safeFormat(delivery.updatedAt, 'dd/MM/yy') : + {delivery.reconciled && delivery.validatedAt ? + safeFormat(delivery.validatedAt, 'dd/MM/yy') : '-' }
@@ -408,43 +564,65 @@ export default function BLReconciliation() {
-
- {delivery.invoiceAmount ? - `${parseFloat(delivery.invoiceAmount).toFixed(2)}€` : - '-' - } -
- - -
- {delivery.group?.name} -
+ {isDevalidated(delivery) ? ( + + Dévalidé + + ) : ( + + Auto-validé + + )}
- {canModify ? ( + {isDevalidated(delivery) && permissions.canEdit('reconciliation') && ( <> - - + + + - ) : ( - + + + )} + + {permissions.canDelete('reconciliation') && ( + )}
diff --git a/client/src/pages/BLReconciliation_backup.tsx b/client/src/pages/BLReconciliation_backup.tsx index f4f9b92..cad52f9 100644 --- a/client/src/pages/BLReconciliation_backup.tsx +++ b/client/src/pages/BLReconciliation_backup.tsx @@ -1,635 +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 { 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 && ( - - )} -
- )} -
- ); -}; +import { Search, Edit, FileText, Settings, Eye, AlertTriangle, X } from "lucide-react"; export default function BLReconciliation() { const { user } = useAuthUnified(); @@ -660,15 +40,6 @@ 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({ @@ -677,19 +48,13 @@ export default function BLReconciliation() { // 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({}); 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' }); @@ -701,7 +66,7 @@ export default function BLReconciliation() { 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()); + return filtered.sort((a: any, b: any) => new Date(b.deliveredDate || b.updatedAt).getTime() - new Date(a.deliveredDate || a.updatedAt).getTime()); }, }); @@ -748,120 +113,22 @@ export default function BLReconciliation() { } }; - // 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 || "", + // 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) + ); }); }; - const verifyAllInvoices = async () => { - // Implémentation similaire à l'ancien composant - console.log("Verification des factures..."); - }; + const filteredManualDeliveries = filterDeliveries(manualReconciliationDeliveries); + const filteredAutomaticDeliveries = filterDeliveries(automaticReconciliationDeliveries); + + const canModify = user?.role === 'directeur' || user?.role === 'admin'; return (
@@ -908,192 +175,288 @@ export default function BLReconciliation() {
+ {/* Filtre de recherche */} +
+
+ + setSearchTerm(e.target.value)} + className="pl-10 border border-gray-300 shadow-sm" + /> +
+
+ {/* Contenu des onglets */} - + {filteredManualDeliveries.length === 0 ? ( +
+ +

+ Aucun rapprochement manuel trouvé +

+

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

+
+ ) : ( +
+
+ + + + + + + + + + + + + + + {filteredManualDeliveries.map((delivery: any) => ( + + + + + + + + + + + ))} + +
+ Fournisseur + + N° BL + + Date Livr. + + Montant BL + + Ref. Facture + + Montant Fact. + + 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é + } +
+
+
+ {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 ? + safeFormat(delivery.updatedAt, 'dd/MM/yy') : + '-' + } +
+
+
+ {delivery.blAmount ? + `${parseFloat(delivery.blAmount).toFixed(2)}€` : + '-' + } +
+
+
+ {delivery.invoiceReference || '-'} +
+
+
+ {delivery.invoiceAmount ? + `${parseFloat(delivery.invoiceAmount).toFixed(2)}€` : + '-' + } +
+
+
+ {delivery.group?.name} +
+
+
+ {canModify ? ( + <> + + + + ) : ( + + )} +
+
+
+
+ )}
- - {/* 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/shared/permissions.ts b/shared/permissions.ts index c2192f7..2a4ef65 100644 --- a/shared/permissions.ts +++ b/shared/permissions.ts @@ -48,10 +48,10 @@ const PERMISSIONS: Record> = { employee: [] }, - // Rapprochement - Admin tout, Directeur tout sauf delete, Manager/Employé rien + // Rapprochement - Admin tout y compris validate, Directeur tout sauf delete, Manager/Employé rien reconciliation: { - admin: ['view', 'create', 'edit', 'delete'], - directeur: ['view', 'create', 'edit'], + admin: ['view', 'create', 'edit', 'delete', 'validate'], + directeur: ['view', 'create', 'edit', 'validate'], manager: [], employee: [] },