diff --git a/URGENT-FIX-SUPPLIERS-EMAIL.sql b/URGENT-FIX-SUPPLIERS-EMAIL.sql deleted file mode 100644 index 5a69106..0000000 --- a/URGENT-FIX-SUPPLIERS-EMAIL.sql +++ /dev/null @@ -1,26 +0,0 @@ --- CORRECTION URGENTE : Ajouter la colonne email manquante dans suppliers --- Cette colonne est requise par le code Drizzle mais n'existe pas en production - -BEGIN; - --- Ajouter la colonne email après phone -ALTER TABLE suppliers ADD COLUMN email character varying(255); - --- Mettre à jour la timestamp -UPDATE suppliers SET updated_at = CURRENT_TIMESTAMP; - --- Vérifier le résultat -SELECT - column_name, - data_type, - is_nullable, - column_default -FROM information_schema.columns -WHERE table_name = 'suppliers' -AND table_schema = 'public' -ORDER BY ordinal_position; - -COMMIT; - --- Message de confirmation -SELECT 'Colonne email ajoutée avec succès à la table suppliers' as status; \ No newline at end of file diff --git a/URGENT-PRODUCTION-FIX-GUIDE.md b/URGENT-PRODUCTION-FIX-GUIDE.md deleted file mode 100644 index 72f0998..0000000 --- a/URGENT-PRODUCTION-FIX-GUIDE.md +++ /dev/null @@ -1,66 +0,0 @@ -# CORRECTION URGENTE PRODUCTION - Commandes et Livraisons - -## Problème identifié -La colonne `email` manque dans la table `suppliers` en production, ce qui cause des erreurs 500 sur les API `/api/orders` et `/api/deliveries`. - -## Erreurs constatées -``` -Error fetching orders: error: column orders_supplier.email does not exist -Error fetching deliveries: error: column deliveries_supplier.email does not exist -``` - -## Solution immédiate - -### 1. Exécuter le script SQL -Connectez-vous à votre base de données de production et exécutez : - -```sql --- Ajouter la colonne email manquante -ALTER TABLE suppliers ADD COLUMN email character varying(255); -``` - -### 2. Vérification -Après exécution, vérifiez que la colonne a été ajoutée : - -```sql -SELECT column_name, data_type, is_nullable -FROM information_schema.columns -WHERE table_name = 'suppliers' -AND table_schema = 'public' -ORDER BY ordinal_position; -``` - -### 3. Redémarrer l'application -Une fois la colonne ajoutée, redémarrez votre application Node.js. - -## Colonnes attendues vs réelles - -### Actuellement en production (8 colonnes) -1. id -2. name -3. contact -4. phone -5. has_dlc -6. created_at -7. updated_at -8. automatic_reconciliation - -### Attendu par le code Drizzle (9 colonnes) -1. id -2. name -3. contact -4. phone -5. **email** ← MANQUANTE -6. address -7. has_dlc -8. automatic_reconciliation -9. created_at -10. updated_at - -## Diagnostic complet -- 40 fournisseurs en base -- Relations Drizzle tentent d'accéder à supplier.email -- Colonne n'existe pas → erreur SQL → erreur 500 - -## Test après correction -Les pages Orders et Deliveries devraient fonctionner normalement une fois la colonne ajoutée. \ No newline at end of file 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' && ( -
+ )}
- {/* 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' && (
-
- - -
+ {user?.role === 'admin' && ( +
+ + +
+ )}

{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() { />
-
- - handleChange('email', e.target.value)} - placeholder="Adresse email" - /> -
- -
- - 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' : ''} +

+
+ +
+
+ + {/* 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 && ( + + )} +
+ ) : ( +
+
+ {filteredSuppliers.map((supplier) => { + const stats = getSupplierStats(supplier.id); + return ( +
+
+
+
+ +
+
+

{supplier.name}

+

#{supplier.id}

+
+
+
+ + +
+
+ +
+ {supplier.contact && ( +
+ + {supplier.contact} +
+ )} + {supplier.phone && ( +
+ + {supplier.phone} +
+ )} +
+
+ {supplier.hasDlc && ( +
+ + DLC +
+ )} + {supplier.automaticReconciliation && ( +
+ + Auto-rapprochement +
+ )} +
+
+ + {!supplier.hasDlc && !supplier.automaticReconciliation && ( +
+ Aucune option activée +
+ )} + + {/* Statistics section follows */} +
+ +
+
+
+
+ + {stats.orders} +
+

Commandes

+
+
+
+ + {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'} +

+
+ +
+
+ + handleChange('name', e.target.value)} + placeholder="Nom du fournisseur" + required + /> +
+ +
+ + handleChange('contact', e.target.value)} + placeholder="Nom du contact" + /> +
+ +
+ + handleChange('phone', e.target.value)} + placeholder="Numéro de téléphone" + /> +
+ +
+ + handleChange('email', e.target.value)} + placeholder="Adresse email" + /> +
+ +
+ + handleChange('address', e.target.value)} + placeholder="Adresse complète" + /> +
+ +
+ handleChange('hasDlc', checked === true)} + /> + +
+ +
+ handleChange('automaticReconciliation', checked === true)} + /> + +
+ +
+ + +
+
+
+
+
+ ); +} diff --git a/debug-production-suppliers-schema.js b/debug-production-suppliers-schema.js deleted file mode 100644 index 45081bd..0000000 --- a/debug-production-suppliers-schema.js +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/env node - -/** - * Script de debug pour vérifier le schéma de la table suppliers en production - * et corriger les colonnes manquantes si nécessaire - */ - -import pg from 'pg'; -import fs from 'fs'; - -const { Pool } = pg; - -// Configuration de la base de données de production -const pool = new Pool({ - connectionString: process.env.DATABASE_URL, - ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false -}); - -async function checkSuppliersSchema() { - console.log('🔍 Vérification du schéma de la table suppliers en production...'); - - try { - // Vérifier les colonnes existantes - const result = await pool.query(` - SELECT - column_name, - data_type, - is_nullable, - column_default - FROM information_schema.columns - WHERE table_name = 'suppliers' - AND table_schema = 'public' - ORDER BY ordinal_position - `); - - console.log('\n📊 Colonnes actuelles dans la table suppliers:'); - console.log('='.repeat(60)); - - const columns = result.rows; - columns.forEach((col, index) => { - const nullable = col.is_nullable === 'YES' ? 'NULL' : 'NOT NULL'; - const defaultVal = col.column_default ? ` DEFAULT ${col.column_default}` : ''; - console.log(`${index + 1:2}. ${col.column_name.padEnd(25)} : ${col.data_type} ${nullable}${defaultVal}`); - }); - - // Vérifier les colonnes requises - const requiredColumns = ['email', 'automatic_reconciliation']; - const existingColumns = columns.map(col => col.column_name); - const missingColumns = requiredColumns.filter(col => !existingColumns.includes(col)); - - if (missingColumns.length > 0) { - console.log(`\n❌ Colonnes manquantes: ${missingColumns.join(', ')}`); - console.log('\n📝 Script SQL généré pour corriger le problème:'); - console.log('='.repeat(60)); - - let sqlScript = 'BEGIN;\n\n'; - - if (missingColumns.includes('email')) { - sqlScript += 'ALTER TABLE suppliers ADD COLUMN email VARCHAR;\n'; - sqlScript += 'COMMENT ON COLUMN suppliers.email IS \'Email du fournisseur\';\n\n'; - } - - if (missingColumns.includes('automatic_reconciliation')) { - sqlScript += 'ALTER TABLE suppliers ADD COLUMN automatic_reconciliation BOOLEAN DEFAULT false;\n'; - sqlScript += 'COMMENT ON COLUMN suppliers.automatic_reconciliation IS \'Mode rapprochement automatique BL/Factures\';\n\n'; - } - - sqlScript += 'COMMIT;'; - - console.log(sqlScript); - - // Sauvegarder le script - fs.writeFileSync('fix-suppliers-missing-columns.sql', sqlScript); - console.log('\n💾 Script sauvegardé dans: fix-suppliers-missing-columns.sql'); - - } else { - console.log('\n✅ Toutes les colonnes requises sont présentes'); - } - - // Compter les enregistrements - const countResult = await pool.query('SELECT COUNT(*) as total FROM suppliers'); - console.log(`\n📈 Nombre de fournisseurs: ${countResult.rows[0].total}`); - - } catch (error) { - console.error('❌ Erreur lors de la vérification:', error.message); - process.exit(1); - } finally { - await pool.end(); - } -} - -// Exécuter le script -checkSuppliersSchema() - .then(() => { - console.log('\n✅ Vérification terminée'); - process.exit(0); - }) - .catch((error) => { - console.error('❌ Erreur:', error); - process.exit(1); - }); \ No newline at end of file diff --git a/fix-production-deliveries-orders-schema.js b/fix-production-deliveries-orders-schema.js deleted file mode 100644 index c98ba04..0000000 --- a/fix-production-deliveries-orders-schema.js +++ /dev/null @@ -1,162 +0,0 @@ -#!/usr/bin/env node - -/** - * Script de correction d'urgence pour résoudre les erreurs de colonnes manquantes - * qui empêchent l'affichage des commandes et livraisons en production - */ - -import pg from 'pg'; - -const { Pool } = pg; - -// Configuration de la base de données de production -const pool = new Pool({ - connectionString: process.env.DATABASE_URL, - ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false -}); - -async function fixProductionSchema() { - console.log('🚨 CORRECTION D\'URGENCE - Schéma de production pour commandes/livraisons'); - console.log('='.repeat(70)); - - try { - // 1. Vérifier et corriger la table suppliers - console.log('\n1️⃣ Vérification de la table suppliers...'); - - const suppliersColumns = await pool.query(` - SELECT column_name - FROM information_schema.columns - WHERE table_name = 'suppliers' - AND table_schema = 'public' - `); - - const existingSupplierColumns = suppliersColumns.rows.map(row => row.column_name); - console.log(' Colonnes existantes:', existingSupplierColumns.join(', ')); - - // Ajouter les colonnes manquantes pour suppliers - if (!existingSupplierColumns.includes('email')) { - console.log(' 🔧 Ajout de la colonne email...'); - await pool.query('ALTER TABLE suppliers ADD COLUMN email VARCHAR'); - console.log(' ✅ Colonne email ajoutée'); - } - - if (!existingSupplierColumns.includes('automatic_reconciliation')) { - console.log(' 🔧 Ajout de la colonne automatic_reconciliation...'); - await pool.query('ALTER TABLE suppliers ADD COLUMN automatic_reconciliation BOOLEAN DEFAULT false'); - console.log(' ✅ Colonne automatic_reconciliation ajoutée'); - } - - // 2. Vérifier les relations et contraintes - console.log('\n2️⃣ Vérification des relations de clés étrangères...'); - - const foreignKeys = await pool.query(` - SELECT - tc.table_name, - kcu.column_name, - ccu.table_name AS foreign_table_name, - ccu.column_name AS foreign_column_name - FROM information_schema.table_constraints AS tc - JOIN information_schema.key_column_usage AS kcu - ON tc.constraint_name = kcu.constraint_name - JOIN information_schema.constraint_column_usage AS ccu - ON ccu.constraint_name = tc.constraint_name - WHERE tc.constraint_type = 'FOREIGN KEY' - AND tc.table_schema = 'public' - AND (tc.table_name = 'orders' OR tc.table_name = 'deliveries') - ORDER BY tc.table_name, kcu.column_name - `); - - console.log(' Relations trouvées:'); - foreignKeys.rows.forEach(fk => { - console.log(` - ${fk.table_name}.${fk.column_name} → ${fk.foreign_table_name}.${fk.foreign_column_name}`); - }); - - // 3. Test des requêtes problématiques - console.log('\n3️⃣ Test des requêtes de commandes et livraisons...'); - - try { - console.log(' 🧪 Test requête orders avec relations...'); - const testOrders = await pool.query(` - SELECT - o.id, - o.planned_date, - o.status, - s.name as supplier_name, - g.name as group_name - FROM orders o - LEFT JOIN suppliers s ON o.supplier_id = s.id - LEFT JOIN groups g ON o.group_id = g.id - LIMIT 1 - `); - console.log(` ✅ Test orders réussi (${testOrders.rows.length} résultat)`); - - } catch (error) { - console.log(` ❌ Erreur test orders: ${error.message}`); - } - - try { - console.log(' 🧪 Test requête deliveries avec relations...'); - const testDeliveries = await pool.query(` - SELECT - d.id, - d.scheduled_date, - d.status, - s.name as supplier_name, - g.name as group_name - FROM deliveries d - LEFT JOIN suppliers s ON d.supplier_id = s.id - LEFT JOIN groups g ON d.group_id = g.id - LIMIT 1 - `); - console.log(` ✅ Test deliveries réussi (${testDeliveries.rows.length} résultat)`); - - } catch (error) { - console.log(` ❌ Erreur test deliveries: ${error.message}`); - } - - // 4. Statistiques finales - console.log('\n4️⃣ Statistiques des tables...'); - - const tablesStats = ['suppliers', 'orders', 'deliveries', 'groups']; - for (const table of tablesStats) { - try { - const countResult = await pool.query(`SELECT COUNT(*) as total FROM ${table}`); - console.log(` 📊 ${table}: ${countResult.rows[0].total} enregistrements`); - } catch (error) { - console.log(` ❌ Erreur comptage ${table}: ${error.message}`); - } - } - - console.log('\n✅ CORRECTION TERMINÉE - Redémarrez votre application'); - - } catch (error) { - console.error('\n❌ ERREUR CRITIQUE:', error.message); - console.error('Stack:', error.stack); - process.exit(1); - } finally { - await pool.end(); - } -} - -// Vérifier que nous sommes en production -if (process.env.NODE_ENV !== 'production') { - console.error('❌ Ce script ne doit être exécuté qu\'en production'); - console.error(' NODE_ENV =', process.env.NODE_ENV); - process.exit(1); -} - -if (!process.env.DATABASE_URL) { - console.error('❌ DATABASE_URL manquante'); - process.exit(1); -} - -// Exécuter la correction -fixProductionSchema() - .then(() => { - console.log('\n🎉 Script de correction exécuté avec succès'); - process.exit(0); - }) - .catch((error) => { - console.error('\n💥 Échec du script de correction:', error); - process.exit(1); - }); \ No newline at end of file diff --git a/fix-suppliers-email-column-production.sql b/fix-suppliers-email-column-production.sql deleted file mode 100644 index 9d6c1c6..0000000 --- a/fix-suppliers-email-column-production.sql +++ /dev/null @@ -1,50 +0,0 @@ --- Fix pour ajouter la colonne email manquante dans la table suppliers en production --- Cette colonne est définie dans le schéma Drizzle mais n'existe pas en base - -BEGIN; - --- Vérifier si la colonne email existe déjà -DO $$ -BEGIN - -- Essayer d'ajouter la colonne email si elle n'existe pas - IF NOT EXISTS ( - SELECT 1 - FROM information_schema.columns - WHERE table_name = 'suppliers' - AND column_name = 'email' - AND table_schema = 'public' - ) THEN - ALTER TABLE suppliers ADD COLUMN email VARCHAR; - RAISE NOTICE 'Colonne email ajoutée à la table suppliers'; - ELSE - RAISE NOTICE 'Colonne email existe déjà dans la table suppliers'; - END IF; - - -- Vérifier si la colonne automatic_reconciliation existe déjà - IF NOT EXISTS ( - SELECT 1 - FROM information_schema.columns - WHERE table_name = 'suppliers' - AND column_name = 'automatic_reconciliation' - AND table_schema = 'public' - ) THEN - ALTER TABLE suppliers ADD COLUMN automatic_reconciliation BOOLEAN DEFAULT false; - RAISE NOTICE 'Colonne automatic_reconciliation ajoutée à la table suppliers'; - ELSE - RAISE NOTICE 'Colonne automatic_reconciliation existe déjà dans la table suppliers'; - END IF; -END -$$; - --- Vérifier l'état final des colonnes -SELECT - column_name, - data_type, - is_nullable, - column_default -FROM information_schema.columns -WHERE table_name = 'suppliers' -AND table_schema = 'public' -ORDER BY ordinal_position; - -COMMIT; \ No newline at end of file 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