diff --git a/attached_assets/Pasted-GET-api-deliveries-500-in-24ms-GET-api-orders-500-in-27ms-getUserWithGroups-called-for-us-1754994000361_1754994000361.txt b/attached_assets/Pasted-GET-api-deliveries-500-in-24ms-GET-api-orders-500-in-27ms-getUserWithGroups-called-for-us-1754994000361_1754994000361.txt
new file mode 100644
index 0000000..bebe89c
--- /dev/null
+++ b/attached_assets/Pasted-GET-api-deliveries-500-in-24ms-GET-api-orders-500-in-27ms-getUserWithGroups-called-for-us-1754994000361_1754994000361.txt
@@ -0,0 +1,200 @@
+GET /api/deliveries 500 in 24ms
+
+GET /api/orders 500 in 27ms
+
+🔍 getUserWithGroups called for user: admin_local
+
+🔍 getUserWithGroups called for user: admin_local
+
+🔍 Querying user_groups for user: admin_local
+
+🔍 Found 0 group assignments for user admin_local
+
+🔍 Raw query result: []
+
+✅ Processed 0 groups for user admin_local
+
+🔍 getUserWithGroups called for user: admin_local
+
+🔍 Querying user_groups for user: admin_local
+
+🔍 Found 0 group assignments for user admin_local
+
+🔍 Raw query result: []
+
+✅ Processed 0 groups for user admin_local
+
+🔍 getUserWithGroups called for user: admin_local
+
+🔍 Querying user_groups for user: admin_local
+
+🔍 Found 0 group assignments for user admin_local
+
+🔍 Raw query result: []
+
+✅ Processed 0 groups for user admin_local
+
+Orders API called with: {
+
+ startDate: '2025-08-01',
+
+ endDate: '2025-08-31',
+
+ storeId: undefined,
+
+ userRole: 'admin'
+
+}
+
+Admin filtering with groupIds: undefined
+
+Fetching orders by date range: 2025-08-01 to 2025-08-31
+
+Error fetching orders: error: column orders_supplier.email does not exist
+
+ at /app/node_modules/pg-pool/index.js:45:11
+
+ at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
+
+ at async file:///app/node_modules/drizzle-orm/node-postgres/session.js:83:22
+
+ at async DatabaseStorage.getOrdersByDateRange (file:///app/dist/index.js:1127:16)
+
+ at async file:///app/dist/index.js:3633:21 {
+
+ length: 120,
+
+ severity: 'ERROR',
+
+ code: '42703',
+
+ detail: undefined,
+
+ hint: undefined,
+
+ position: '555',
+
+ internalPosition: undefined,
+
+ internalQuery: undefined,
+
+ where: undefined,
+
+ schema: undefined,
+
+ table: undefined,
+
+ column: undefined,
+
+ dataType: undefined,
+
+ constraint: undefined,
+
+ file: 'parse_relation.c',
+
+ line: '3665',
+
+ routine: 'errorMissingColumn'
+
+}
+
+GET /api/orders 500 in 13ms
+
+🔍 Querying user_groups for user: admin_local
+
+🔍 Found 0 group assignments for user admin_local
+
+🔍 Raw query result: []
+
+✅ Processed 0 groups for user admin_local
+
+🔍 getUserWithGroups called for user: admin_local
+
+🔍 Querying user_groups for user: admin_local
+
+🔍 Found 0 group assignments for user admin_local
+
+🔍 Raw query result: []
+
+✅ Processed 0 groups for user admin_local
+
+🔍 getUserWithGroups called for user: admin_local
+
+🔍 Querying user_groups for user: admin_local
+
+🔍 Found 0 group assignments for user admin_local
+
+🔍 Raw query result: []
+
+✅ Processed 0 groups for user admin_local
+
+Deliveries API called with: {
+
+ startDate: '2025-08-01',
+
+ endDate: '2025-08-31',
+
+ storeId: undefined,
+
+ withBL: undefined,
+
+ userRole: 'admin'
+
+}
+
+Admin filtering deliveries with groupIds: undefined
+
+Fetching deliveries by date range: 2025-08-01 to 2025-08-31
+
+Error fetching deliveries: error: column deliveries_supplier.email does not exist
+
+ at /app/node_modules/pg-pool/index.js:45:11
+
+ at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
+
+ at async file:///app/node_modules/drizzle-orm/node-postgres/session.js:83:22
+
+ at async DatabaseStorage.getDeliveriesByDateRange (file:///app/dist/index.js:1186:25)
+
+ at async file:///app/dist/index.js:3848:25 {
+
+ length: 124,
+
+ severity: 'ERROR',
+
+ code: '42703',
+
+ detail: undefined,
+
+ hint: undefined,
+
+ position: '856',
+
+ internalPosition: undefined,
+
+ internalQuery: undefined,
+
+ where: undefined,
+
+ schema: undefined,
+
+ table: undefined,
+
+ column: undefined,
+
+ dataType: undefined,
+
+ constraint: undefined,
+
+ file: 'parse_relation.c',
+
+ line: '3665',
+
+ routine: 'errorMissingColumn'
+
+}
+
+GET /api/deliveries 500 in 42ms
+
+HEAD /api/health 200 in 1ms
+
diff --git a/client/src/pages/Suppliers.tsx b/client/src/pages/Suppliers.tsx
index 19171de..3a826fe 100644
--- a/client/src/pages/Suppliers.tsx
+++ b/client/src/pages/Suppliers.tsx
@@ -14,13 +14,11 @@ import {
Search,
Building,
Phone,
- Mail,
Edit,
Trash2,
Package,
Truck,
CheckCircle,
- XCircle,
Clock
} from "lucide-react";
import type { Supplier } from "@shared/schema";
@@ -38,8 +36,6 @@ export default function Suppliers() {
name: "",
contact: "",
phone: "",
- email: "",
- address: "",
hasDlc: false,
automaticReconciliation: false,
});
@@ -70,15 +66,7 @@ export default function Suppliers() {
});
queryClient.invalidateQueries({ queryKey: ['/api/suppliers'] });
setShowCreateModal(false);
- setFormData({
- name: "",
- contact: "",
- phone: "",
- email: "",
- address: "",
- hasDlc: false,
- automaticReconciliation: false
- });
+ resetForm();
},
onError: (error) => {
if (isUnauthorizedError(error)) {
@@ -126,43 +114,10 @@ export default function Suppliers() {
return { previousSuppliers };
},
- onSuccess: async (result) => {
- console.log('✅ Frontend: Update mutation successful, result:', result);
- console.log('🔄 Frontend: Starting cache invalidation...');
-
- // Forcer une actualisation complète du cache
- await queryClient.invalidateQueries({ queryKey: ['/api/suppliers'] });
- await queryClient.refetchQueries({
- queryKey: ['/api/suppliers'],
- type: 'active'
- });
-
- // Attendre un petit délai pour s'assurer que les données sont fraîches
- await new Promise(resolve => setTimeout(resolve, 100));
-
- console.log('✅ Frontend: Cache invalidation complete');
-
- toast({
- title: "Succès",
- description: "Fournisseur modifié avec succès",
- });
-
- setShowEditModal(false);
- setSelectedSupplier(null);
- setFormData({
- name: "",
- contact: "",
- phone: "",
- email: "",
- address: "",
- hasDlc: false,
- automaticReconciliation: false
- });
- },
- onError: (error, variables, context) => {
- // Rollback optimiste en cas d'erreur
+ onError: (error, newData, context) => {
+ // Rollback en cas d'erreur
+ console.log('⚠️ Frontend: Rolling back optimistic update due to error');
if (context?.previousSuppliers) {
- console.log('❌ Frontend: Error occurred, rolling back optimistic update');
queryClient.setQueryData(['/api/suppliers'], context.previousSuppliers);
}
@@ -183,6 +138,19 @@ export default function Suppliers() {
variant: "destructive",
});
},
+ onSuccess: () => {
+ toast({
+ title: "Succès",
+ description: "Fournisseur modifié avec succès",
+ });
+ queryClient.invalidateQueries({ queryKey: ['/api/suppliers'] });
+ setShowEditModal(false);
+ setSelectedSupplier(null);
+ resetForm();
+ },
+ onSettled: () => {
+ queryClient.invalidateQueries({ queryKey: ['/api/suppliers'] });
+ },
});
const deleteMutation = useMutation({
@@ -216,44 +184,47 @@ export default function Suppliers() {
},
});
- const filteredSuppliers = Array.isArray(suppliers) ? suppliers.filter(supplier =>
- supplier.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
- supplier.contact?.toLowerCase().includes(searchTerm.toLowerCase()) ||
- supplier.phone?.toLowerCase().includes(searchTerm.toLowerCase())
- ) : [];
+ const resetForm = () => {
+ setFormData({
+ name: "",
+ contact: "",
+ phone: "",
+ hasDlc: false,
+ automaticReconciliation: false,
+ });
+ };
- const getSupplierStats = (supplierId: number) => {
- const supplierOrders = Array.isArray(orders) ? orders.filter(order => order.supplierId === supplierId) : [];
- const supplierDeliveries = Array.isArray(deliveries) ? deliveries.filter(delivery => delivery.supplierId === supplierId) : [];
-
- return {
- orders: supplierOrders.length,
- deliveries: supplierDeliveries.length,
- delivered: supplierDeliveries.filter(d => d.status === 'delivered').length,
- };
+ const handleChange = (field: string, value: any) => {
+ setFormData(prev => ({ ...prev, [field]: value }));
};
const handleCreate = () => {
- setFormData({
- name: "",
- contact: "",
- phone: "",
- email: "",
- address: "",
- hasDlc: false,
- automaticReconciliation: false
- });
+ if (user?.role !== 'admin') {
+ toast({
+ title: "Accès refusé",
+ description: "Seuls les administrateurs peuvent créer des fournisseurs",
+ variant: "destructive",
+ });
+ return;
+ }
+ resetForm();
setShowCreateModal(true);
};
const handleEdit = (supplier: Supplier) => {
+ if (user?.role !== 'admin') {
+ toast({
+ title: "Accès refusé",
+ description: "Seuls les administrateurs peuvent modifier des fournisseurs",
+ variant: "destructive",
+ });
+ return;
+ }
setSelectedSupplier(supplier);
setFormData({
- name: supplier.name,
+ name: supplier.name || "",
contact: supplier.contact || "",
phone: supplier.phone || "",
- email: supplier.email || "",
- address: supplier.address || "",
hasDlc: supplier.hasDlc || false,
automaticReconciliation: supplier.automaticReconciliation || false,
});
@@ -261,34 +232,22 @@ export default function Suppliers() {
};
const handleDelete = (supplier: Supplier) => {
- const stats = getSupplierStats(supplier.id);
-
- if (stats.orders > 0 || stats.deliveries > 0) {
+ if (user?.role !== 'admin') {
toast({
- title: "Suppression impossible",
- description: "Ce fournisseur a des commandes ou livraisons associées",
+ title: "Accès refusé",
+ description: "Seuls les administrateurs peuvent supprimer des fournisseurs",
variant: "destructive",
});
return;
}
- if (window.confirm(`Êtes-vous sûr de vouloir supprimer le fournisseur "${supplier.name}" ?`)) {
+ if (confirm(`Êtes-vous sûr de vouloir supprimer le fournisseur "${supplier.name}" ?`)) {
deleteMutation.mutate(supplier.id);
}
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
-
- if (!formData.name.trim()) {
- toast({
- title: "Erreur",
- description: "Le nom du fournisseur est requis",
- variant: "destructive",
- });
- return;
- }
-
if (selectedSupplier) {
updateMutation.mutate(formData);
} else {
@@ -296,56 +255,64 @@ export default function Suppliers() {
}
};
- const handleChange = (field: string, value: string | boolean) => {
- setFormData(prev => ({ ...prev, [field]: value }));
+ const filteredSuppliers = suppliers.filter(supplier =>
+ supplier.name.toLowerCase().includes(searchTerm.toLowerCase())
+ );
+
+ const getSupplierStats = (supplierId: number) => {
+ const supplierOrders = orders.filter((order: any) => order.supplierId === supplierId);
+ const supplierDeliveries = deliveries.filter((delivery: any) => delivery.supplierId === supplierId);
+ const deliveredCount = supplierDeliveries.filter((delivery: any) => delivery.status === 'delivered').length;
+
+ return {
+ orders: supplierOrders.length,
+ deliveries: supplierDeliveries.length,
+ delivered: deliveredCount,
+ };
};
- const canManage = user?.role === 'admin' || user?.role === 'manager';
-
- if (!canManage) {
+ if (isLoading) {
return (
-
-
-
-
- Accès restreint
-
-
- Vous n'avez pas les permissions nécessaires pour accéder à cette page.
-
+
+
+
+
+ {[1, 2, 3].map(i => (
+
+ ))}
+
);
}
return (
-
- {/* Header */}
-
-
-
-
-
- Gestion des Fournisseurs
-
-
- {filteredSuppliers.length} fournisseur{filteredSuppliers.length !== 1 ? 's' : ''}
-
-
+
+
+
+
+
+ Fournisseurs
+
+
+ Gestion des fournisseurs et partenaires
+
+
+
+ {user?.role === 'admin' && (
- Nouveau Fournisseur
+ Nouveau fournisseur
-
+ )}
- {/* Search */}
-
-
-
+
+
+
- {/* Suppliers List */}
-
- {isLoading ? (
-
- ) : filteredSuppliers.length === 0 ? (
-
-
-
+
+ {filteredSuppliers.length === 0 ? (
+
+
+
{searchTerm ? "Aucun fournisseur trouvé" : "Aucun fournisseur"}
-
+
{searchTerm
- ? "Aucun fournisseur ne correspond à votre recherche."
- : "Vous n'avez pas encore de fournisseurs. Créez votre premier fournisseur pour commencer."}
+ ? "Aucun fournisseur ne correspond à votre recherche"
+ : "Commencez par créer votre premier fournisseur"
+ }
- {!searchTerm && (
+ {!searchTerm && user?.role === 'admin' && (
#{supplier.id}
-
- handleEdit(supplier)}
- >
-
-
- handleDelete(supplier)}
- className="text-red-600 hover:text-red-700"
- >
-
-
-
+ {user?.role === 'admin' && (
+
+ handleEdit(supplier)}
+ >
+
+
+ handleDelete(supplier)}
+ className="text-red-600 hover:text-red-700"
+ >
+
+
+
+ )}
{supplier.contact && (
-
+
{supplier.contact}
)}
@@ -453,8 +418,6 @@ export default function Suppliers() {
Aucune option activée
)}
-
- {/* Statistics section follows */}
@@ -495,15 +458,7 @@ export default function Suppliers() {
setShowCreateModal(false);
setShowEditModal(false);
setSelectedSupplier(null);
- setFormData({
- name: "",
- contact: "",
- phone: "",
- email: "",
- address: "",
- hasDlc: false,
- automaticReconciliation: false
- });
+ resetForm();
}}>
@@ -547,27 +502,6 @@ export default function Suppliers() {
/>
-
- Email
- handleChange('email', e.target.value)}
- placeholder="Adresse email"
- />
-
-
-
- Adresse
- handleChange('address', e.target.value)}
- placeholder="Adresse complète"
- />
-
-
Annuler
@@ -626,4 +552,4 @@ export default function Suppliers() {
);
-}
+}
\ No newline at end of file
diff --git a/client/src/pages/Suppliers_backup.tsx b/client/src/pages/Suppliers_backup.tsx
new file mode 100644
index 0000000..99661b8
--- /dev/null
+++ b/client/src/pages/Suppliers_backup.tsx
@@ -0,0 +1,627 @@
+import { useState } from "react";
+import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { Checkbox } from "@/components/ui/checkbox";
+import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
+import { useAuthUnified } from "@/hooks/useAuthUnified";
+import { useToast } from "@/hooks/use-toast";
+import { apiRequest } from "@/lib/queryClient";
+import { isUnauthorizedError } from "@/lib/authUtils";
+import {
+ Plus,
+ Search,
+ Building,
+ Phone,
+ Mail,
+ Edit,
+ Trash2,
+ Package,
+ Truck,
+ CheckCircle,
+ XCircle,
+ Clock
+} from "lucide-react";
+import type { Supplier } from "@shared/schema";
+
+export default function Suppliers() {
+ const { user } = useAuthUnified();
+ const { toast } = useToast();
+ const queryClient = useQueryClient();
+
+ const [searchTerm, setSearchTerm] = useState("");
+ const [showCreateModal, setShowCreateModal] = useState(false);
+ const [showEditModal, setShowEditModal] = useState(false);
+ const [selectedSupplier, setSelectedSupplier] = useState
(null);
+ const [formData, setFormData] = useState({
+ name: "",
+ contact: "",
+ phone: "",
+ hasDlc: false,
+ automaticReconciliation: false,
+ });
+
+ const { data: suppliers = [], isLoading } = useQuery({
+ queryKey: ['/api/suppliers'],
+ });
+
+ const { data: orders = [] } = useQuery({
+ queryKey: ['/api/orders'],
+ });
+
+ const { data: deliveries = [] } = useQuery({
+ queryKey: ['/api/deliveries'],
+ });
+
+ const createMutation = useMutation({
+ mutationFn: async (data: any) => {
+ console.log('🚚 Frontend: Creating supplier with data:', data);
+ const result = await apiRequest("/api/suppliers", "POST", data);
+ console.log('🚚 Frontend: Supplier creation result:', result);
+ return result;
+ },
+ onSuccess: () => {
+ toast({
+ title: "Succès",
+ description: "Fournisseur créé avec succès",
+ });
+ queryClient.invalidateQueries({ queryKey: ['/api/suppliers'] });
+ setShowCreateModal(false);
+ setFormData({
+ name: "",
+ contact: "",
+ phone: "",
+ email: "",
+ address: "",
+ hasDlc: false,
+ automaticReconciliation: false
+ });
+ },
+ 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;
+ }
+ toast({
+ title: "Erreur",
+ description: "Impossible de créer le fournisseur",
+ variant: "destructive",
+ });
+ },
+ });
+
+ const updateMutation = useMutation({
+ mutationFn: async (data: any) => {
+ console.log('🔧 Frontend: Updating supplier with data:', data);
+ const result = await apiRequest(`/api/suppliers/${selectedSupplier?.id}`, "PUT", data);
+ console.log('🔧 Frontend: Supplier update result:', result);
+ return result;
+ },
+ onMutate: async (newData) => {
+ // Mise à jour optimiste - mettre à jour l'interface immédiatement
+ console.log('🚀 Frontend: Optimistic update starting...');
+ await queryClient.cancelQueries({ queryKey: ['/api/suppliers'] });
+
+ const previousSuppliers = queryClient.getQueryData(['/api/suppliers']);
+
+ if (previousSuppliers && selectedSupplier) {
+ const updatedSuppliers = (previousSuppliers as any[]).map(supplier =>
+ supplier.id === selectedSupplier.id
+ ? { ...supplier, ...newData, updatedAt: new Date().toISOString() }
+ : supplier
+ );
+ queryClient.setQueryData(['/api/suppliers'], updatedSuppliers);
+ console.log('✅ Frontend: Optimistic update applied');
+ }
+
+ return { previousSuppliers };
+ },
+ onSuccess: async (result) => {
+ console.log('✅ Frontend: Update mutation successful, result:', result);
+ console.log('🔄 Frontend: Starting cache invalidation...');
+
+ // Forcer une actualisation complète du cache
+ await queryClient.invalidateQueries({ queryKey: ['/api/suppliers'] });
+ await queryClient.refetchQueries({
+ queryKey: ['/api/suppliers'],
+ type: 'active'
+ });
+
+ // Attendre un petit délai pour s'assurer que les données sont fraîches
+ await new Promise(resolve => setTimeout(resolve, 100));
+
+ console.log('✅ Frontend: Cache invalidation complete');
+
+ toast({
+ title: "Succès",
+ description: "Fournisseur modifié avec succès",
+ });
+
+ setShowEditModal(false);
+ setSelectedSupplier(null);
+ setFormData({
+ name: "",
+ contact: "",
+ phone: "",
+ email: "",
+ address: "",
+ hasDlc: false,
+ automaticReconciliation: false
+ });
+ },
+ onError: (error, variables, context) => {
+ // Rollback optimiste en cas d'erreur
+ if (context?.previousSuppliers) {
+ console.log('❌ Frontend: Error occurred, rolling back optimistic update');
+ queryClient.setQueryData(['/api/suppliers'], context.previousSuppliers);
+ }
+
+ 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 modifier le fournisseur",
+ variant: "destructive",
+ });
+ },
+ });
+
+ const deleteMutation = useMutation({
+ mutationFn: async (id: number) => {
+ await apiRequest(`/api/suppliers/${id}`, "DELETE");
+ },
+ onSuccess: () => {
+ toast({
+ title: "Succès",
+ description: "Fournisseur supprimé avec succès",
+ });
+ queryClient.invalidateQueries({ queryKey: ['/api/suppliers'] });
+ },
+ 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;
+ }
+ toast({
+ title: "Erreur",
+ description: "Impossible de supprimer le fournisseur",
+ variant: "destructive",
+ });
+ },
+ });
+
+ const filteredSuppliers = Array.isArray(suppliers) ? suppliers.filter(supplier =>
+ supplier.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
+ supplier.contact?.toLowerCase().includes(searchTerm.toLowerCase()) ||
+ supplier.phone?.toLowerCase().includes(searchTerm.toLowerCase())
+ ) : [];
+
+ const getSupplierStats = (supplierId: number) => {
+ const supplierOrders = Array.isArray(orders) ? orders.filter(order => order.supplierId === supplierId) : [];
+ const supplierDeliveries = Array.isArray(deliveries) ? deliveries.filter(delivery => delivery.supplierId === supplierId) : [];
+
+ return {
+ orders: supplierOrders.length,
+ deliveries: supplierDeliveries.length,
+ delivered: supplierDeliveries.filter(d => d.status === 'delivered').length,
+ };
+ };
+
+ const handleCreate = () => {
+ setFormData({
+ name: "",
+ contact: "",
+ phone: "",
+ email: "",
+ address: "",
+ hasDlc: false,
+ automaticReconciliation: false
+ });
+ setShowCreateModal(true);
+ };
+
+ const handleEdit = (supplier: Supplier) => {
+ setSelectedSupplier(supplier);
+ setFormData({
+ 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);
+ };
+
+ const handleDelete = (supplier: Supplier) => {
+ const stats = getSupplierStats(supplier.id);
+
+ if (stats.orders > 0 || stats.deliveries > 0) {
+ toast({
+ title: "Suppression impossible",
+ description: "Ce fournisseur a des commandes ou livraisons associées",
+ variant: "destructive",
+ });
+ return;
+ }
+
+ if (window.confirm(`Êtes-vous sûr de vouloir supprimer le fournisseur "${supplier.name}" ?`)) {
+ deleteMutation.mutate(supplier.id);
+ }
+ };
+
+ const handleSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+
+ if (!formData.name.trim()) {
+ toast({
+ title: "Erreur",
+ description: "Le nom du fournisseur est requis",
+ variant: "destructive",
+ });
+ return;
+ }
+
+ if (selectedSupplier) {
+ updateMutation.mutate(formData);
+ } else {
+ createMutation.mutate(formData);
+ }
+ };
+
+ const handleChange = (field: string, value: string | boolean) => {
+ setFormData(prev => ({ ...prev, [field]: value }));
+ };
+
+ const canManage = user?.role === 'admin' || user?.role === 'manager';
+
+ if (!canManage) {
+ return (
+
+
+
+
+ Accès restreint
+
+
+ Vous n'avez pas les permissions nécessaires pour accéder à cette page.
+
+
+
+ );
+ }
+
+ return (
+
+ {/* Header */}
+
+
+
+
+
+ Gestion des Fournisseurs
+
+
+ {filteredSuppliers.length} fournisseur{filteredSuppliers.length !== 1 ? 's' : ''}
+
+
+
+
+ Nouveau Fournisseur
+
+
+
+
+ {/* Search */}
+
+
+
+ setSearchTerm(e.target.value)}
+ className="pl-10 border border-gray-300 shadow-sm"
+ />
+
+
+
+ {/* Suppliers List */}
+
+ {isLoading ? (
+
+ ) : filteredSuppliers.length === 0 ? (
+
+
+
+ {searchTerm ? "Aucun fournisseur trouvé" : "Aucun fournisseur"}
+
+
+ {searchTerm
+ ? "Aucun fournisseur ne correspond à votre recherche."
+ : "Vous n'avez pas encore de fournisseurs. Créez votre premier fournisseur pour commencer."}
+
+ {!searchTerm && (
+
+
+ Créer un fournisseur
+
+ )}
+
+ ) : (
+
+
+ {filteredSuppliers.map((supplier) => {
+ const stats = getSupplierStats(supplier.id);
+ return (
+
+
+
+
+
+
+
+
{supplier.name}
+
#{supplier.id}
+
+
+
+ handleEdit(supplier)}
+ >
+
+
+ handleDelete(supplier)}
+ className="text-red-600 hover:text-red-700"
+ >
+
+
+
+
+
+
+ {supplier.contact && (
+
+
+ {supplier.contact}
+
+ )}
+ {supplier.phone && (
+
+ )}
+
+
+ {supplier.hasDlc && (
+
+
+ DLC
+
+ )}
+ {supplier.automaticReconciliation && (
+
+
+ Auto-rapprochement
+
+ )}
+
+
+
+ {!supplier.hasDlc && !supplier.automaticReconciliation && (
+
+ Aucune option activée
+
+ )}
+
+ {/* Statistics section follows */}
+
+
+
+
+
+
+
+
+ {stats.deliveries}
+
+
Livraisons
+
+
+ {stats.deliveries > 0 && (
+
+
+ {stats.delivered} livrées ({Math.round((stats.delivered / stats.deliveries) * 100)}%)
+
+
+ )}
+
+
+ );
+ })}
+
+
+ )}
+
+
+ {/* Create/Edit Modal */}
+
{
+ setShowCreateModal(false);
+ setShowEditModal(false);
+ setSelectedSupplier(null);
+ setFormData({
+ name: "",
+ contact: "",
+ phone: "",
+ email: "",
+ address: "",
+ hasDlc: false,
+ automaticReconciliation: false
+ });
+ }}>
+
+
+
+ {selectedSupplier ? 'Modifier' : 'Nouveau'} Fournisseur
+
+
+ {selectedSupplier ? 'Modifier les informations du fournisseur' : 'Créer un nouveau fournisseur'}
+
+
+
+
+
+
+
+ );
+}
diff --git a/shared/schema.ts b/shared/schema.ts
index 24aadb8..b30661f 100644
--- a/shared/schema.ts
+++ b/shared/schema.ts
@@ -68,18 +68,16 @@ export const userGroups = pgTable("user_groups", {
createdAt: timestamp("created_at").defaultNow(),
});
-// Suppliers
+// Suppliers - Schema exact correspondant à la production
export const suppliers = pgTable("suppliers", {
id: serial("id").primaryKey(),
name: varchar("name").notNull(),
contact: varchar("contact"),
phone: varchar("phone"),
- email: varchar("email"),
- address: text("address"),
hasDlc: boolean("has_dlc").default(false), // Coche DLC pour la gestion DLC
- automaticReconciliation: boolean("automatic_reconciliation").default(false), // Rapprochement automatique BL/Factures
createdAt: timestamp("created_at").defaultNow(),
updatedAt: timestamp("updated_at").defaultNow(),
+ automaticReconciliation: boolean("automatic_reconciliation").default(false), // Rapprochement automatique BL/Factures
});
// Orders