diff --git a/client/src/components/PublicityForm.tsx b/client/src/components/PublicityForm.tsx index b573472..612f688 100644 --- a/client/src/components/PublicityForm.tsx +++ b/client/src/components/PublicityForm.tsx @@ -51,8 +51,8 @@ export default function PublicityForm({ publicity, groups, onSuccess, selectedYe defaultValues: { pubNumber: publicity?.pubNumber || "", designation: publicity?.designation || "", - startDate: publicity?.startDate ? safeDate(publicity.startDate) : undefined, - endDate: publicity?.endDate ? safeDate(publicity.endDate) : undefined, + startDate: (publicity?.startDate ? safeDate(publicity.startDate) : undefined) as Date, + endDate: (publicity?.endDate ? safeDate(publicity.endDate) : undefined) as Date, year: publicity?.year || selectedYear || 2025, // Utilise l'année sélectionnée ou 2025 par défaut participatingGroups: publicity?.participations?.map(p => p.groupId) || [], }, @@ -62,7 +62,7 @@ export default function PublicityForm({ publicity, groups, onSuccess, selectedYe const createMutation = useMutation({ mutationFn: async (data: PublicityFormData) => { - const response = await fetch('/api/publicities', { + const response = await fetch('/api/ad-campaigns', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ @@ -81,21 +81,21 @@ export default function PublicityForm({ publicity, groups, onSuccess, selectedYe return response.json(); }, onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['/api/publicities'] }); + queryClient.invalidateQueries({ queryKey: ['/api/ad-campaigns'] }); toast({ description: "Publicité créée avec succès" }); onSuccess(); }, onError: (error: Error) => { - toast({ + toast({ variant: "destructive", - description: error.message || "Erreur lors de la création" + description: error.message || "Erreur lors de la création" }); } }); const updateMutation = useMutation({ mutationFn: async (data: PublicityFormData) => { - const response = await fetch(`/api/publicities/${publicity!.id}`, { + const response = await fetch(`/api/ad-campaigns/${publicity!.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ @@ -114,23 +114,31 @@ export default function PublicityForm({ publicity, groups, onSuccess, selectedYe return response.json(); }, onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['/api/publicities'] }); + queryClient.invalidateQueries({ queryKey: ['/api/ad-campaigns'] }); toast({ description: "Publicité modifiée avec succès" }); onSuccess(); }, onError: (error: Error) => { - toast({ + toast({ variant: "destructive", - description: error.message || "Erreur lors de la modification" + description: error.message || "Erreur lors de la modification" }); } }); - const onSubmit = (data: PublicityFormData) => { + const onSubmit = (data: any) => { + // Transformer l'année en nombre si c'est une string + const formattedData = { + ...data, + year: parseInt(data.year.toString()), + // s'assurer que les dates sont bien des objets Date ou null + startDate: data.startDate ? new Date(data.startDate) : undefined, + endDate: data.endDate ? new Date(data.endDate) : undefined, + }; if (publicity) { - updateMutation.mutate(data); + updateMutation.mutate(formattedData); } else { - createMutation.mutate(data); + createMutation.mutate(formattedData); } }; @@ -284,9 +292,9 @@ export default function PublicityForm({ publicity, groups, onSuccess, selectedYe htmlFor={`group-${group.id}`} className="flex items-center space-x-2 cursor-pointer" > -
{group.name} diff --git a/client/src/pages/Calendar.tsx b/client/src/pages/Calendar.tsx index be59cab..d88896d 100644 --- a/client/src/pages/Calendar.tsx +++ b/client/src/pages/Calendar.tsx @@ -35,9 +35,9 @@ export default function Calendar() { // Fetch orders and deliveries for the current month with store filtering const { data: orders = [], isLoading: loadingOrders } = useQuery({ - queryKey: ['/api/orders', selectedStoreId, { - startDate: format(monthStart, 'yyyy-MM-dd'), - endDate: format(monthEnd, 'yyyy-MM-dd') + queryKey: ['/api/orders', selectedStoreId, { + startDate: format(monthStart, 'yyyy-MM-dd'), + endDate: format(monthEnd, 'yyyy-MM-dd') }], queryFn: async () => { const params = new URLSearchParams({ @@ -48,7 +48,7 @@ export default function Calendar() { if (selectedStoreId) { params.append('storeId', selectedStoreId.toString()); } - + const url = `/api/orders?${params.toString()}`; if (import.meta.env.DEV) { console.log('📅 Calendar fetching orders:', { @@ -58,20 +58,20 @@ export default function Calendar() { params: params.toString() }); } - + const response = await fetch(url, { credentials: 'include' }); - + if (!response.ok) { throw new Error('Failed to fetch orders'); } - + const data = await response.json(); if (import.meta.env.DEV) { console.log('📅 Calendar orders received:', Array.isArray(data) ? data.length : 'NOT_ARRAY', 'items'); } - + // Protection contre les données invalides en production if (!Array.isArray(data)) { console.warn('⚠️ Orders data is not an array, returning empty array'); @@ -84,9 +84,9 @@ export default function Calendar() { }); const { data: deliveries = [], isLoading: loadingDeliveries } = useQuery({ - queryKey: ['/api/deliveries', selectedStoreId, { - startDate: format(monthStart, 'yyyy-MM-dd'), - endDate: format(monthEnd, 'yyyy-MM-dd') + queryKey: ['/api/deliveries', selectedStoreId, { + startDate: format(monthStart, 'yyyy-MM-dd'), + endDate: format(monthEnd, 'yyyy-MM-dd') }], queryFn: async () => { const params = new URLSearchParams({ @@ -97,7 +97,7 @@ export default function Calendar() { if (selectedStoreId) { params.append('storeId', selectedStoreId.toString()); } - + const url = `/api/deliveries?${params.toString()}`; if (import.meta.env.DEV) { console.log('📅 Calendar fetching deliveries:', { @@ -107,20 +107,20 @@ export default function Calendar() { params: params.toString() }); } - + const response = await fetch(url, { credentials: 'include' }); - + if (!response.ok) { throw new Error('Failed to fetch deliveries'); } - + const data = await response.json(); if (import.meta.env.DEV) { console.log('📅 Calendar deliveries received:', Array.isArray(data) ? data.length : 'NOT_ARRAY', 'items'); } - + // Protection contre les données invalides en production if (!Array.isArray(data)) { console.warn('⚠️ Deliveries data is not an array, returning empty array'); @@ -134,15 +134,15 @@ export default function Calendar() { // Fetch publicities for the current year const { data: publicities = [], isLoading: loadingPublicities } = useQuery({ - queryKey: ['/api/publicities', currentDate.getFullYear(), selectedStoreId], + queryKey: ['/api/ad-campaigns', currentDate.getFullYear(), selectedStoreId], queryFn: async () => { const params = new URLSearchParams(); params.append('year', currentDate.getFullYear().toString()); if (selectedStoreId && user?.role === 'admin') { params.append('storeId', selectedStoreId.toString()); } - - const url = `/api/publicities?${params.toString()}`; + + const url = `/api/ad-campaigns?${params.toString()}`; if (import.meta.env.DEV) { console.log('📅 Calendar fetching publicities:', { url, @@ -151,15 +151,15 @@ export default function Calendar() { year: currentDate.getFullYear() }); } - + const response = await fetch(url, { credentials: 'include' }); - + if (!response.ok) { throw new Error('Failed to fetch publicities'); } - + const data = await response.json(); if (import.meta.env.DEV) { console.log('📅 Calendar publicities received:', Array.isArray(data) ? data.length : 'NOT_ARRAY', 'items'); diff --git a/client/src/pages/Dashboard.tsx b/client/src/pages/Dashboard.tsx index 43b9994..87fb0f6 100644 --- a/client/src/pages/Dashboard.tsx +++ b/client/src/pages/Dashboard.tsx @@ -33,19 +33,19 @@ export default function Dashboard() { year: currentDate.getFullYear().toString(), month: (currentDate.getMonth() + 1).toString(), }); - + if (selectedStoreId && user?.role === 'admin') { params.append('storeId', selectedStoreId.toString()); } - + const response = await fetch(`/api/stats/monthly?${params.toString()}`, { credentials: 'include' }); - + if (!response.ok) { throw new Error('Failed to fetch stats'); } - + return response.json(); }, }); @@ -58,19 +58,19 @@ export default function Dashboard() { const params = new URLSearchParams({ year: currentDate.getFullYear().toString(), }); - + if (selectedStoreId && user?.role === 'admin') { params.append('storeId', selectedStoreId.toString()); } - + const response = await fetch(`/api/stats/yearly?${params.toString()}`, { credentials: 'include' }); - + if (!response.ok) { throw new Error('Failed to fetch yearly stats'); } - + return response.json(); }, }); @@ -102,34 +102,34 @@ export default function Dashboard() { params.append('storeId', selectedStoreId.toString()); } params.append('recent', 'true'); - + const response = await fetch(`/api/announcements?${params.toString()}`, { credentials: 'include' }); - + if (!response.ok) { return null; } - + const announcements: DashboardMessage[] = await response.json(); - + // Filtrer les annonces créées il y a moins de 2 jours côté client const twoDaysAgo = new Date(); twoDaysAgo.setDate(twoDaysAgo.getDate() - 2); - + const recentAnnouncements = announcements.filter(announcement => { if (!announcement.createdAt) return false; const createdAt = safeDate(announcement.createdAt); return createdAt && createdAt >= twoDaysAgo; }); - + // Retourner la plus récente - return recentAnnouncements.length > 0 + return recentAnnouncements.length > 0 ? recentAnnouncements.sort((a, b) => { - const dateA = safeDate(a.createdAt); - const dateB = safeDate(b.createdAt); - return (dateB?.getTime() || 0) - (dateA?.getTime() || 0); - })[0] + const dateA = safeDate(a.createdAt); + const dateB = safeDate(b.createdAt); + return (dateB?.getTime() || 0) - (dateA?.getTime() || 0); + })[0] : null; }, enabled: !!user, @@ -139,30 +139,30 @@ export default function Dashboard() { const getAnnouncementPriorityConfig = (type: string) => { switch (type) { case 'error': - return { - label: 'Nouveauté', + return { + label: 'Nouveauté', color: 'bg-purple-100 text-purple-800', icon: Sparkles, iconColor: 'text-purple-600' }; case 'warning': - return { - label: 'Attention', + return { + label: 'Attention', color: 'bg-orange-100 text-orange-800', icon: AlertTriangle, iconColor: 'text-orange-600' }; case 'success': - return { - label: 'Important', + return { + label: 'Important', color: 'bg-red-100 text-red-800', icon: Star, iconColor: 'text-red-600' }; case 'info': default: - return { - label: 'Information', + return { + label: 'Information', color: 'bg-blue-100 text-blue-800', icon: Info, iconColor: 'text-blue-600' @@ -195,19 +195,19 @@ export default function Dashboard() { // Récupérer les publicités à venir (chercher dans 2024 ET 2025) - TOUTES les publicités const { data: upcomingPublicities = [] } = useQuery({ - queryKey: ['/api/publicities', 'upcoming'], + queryKey: ['/api/ad-campaigns', 'upcoming'], queryFn: async () => { // Essayer d'abord 2024, puis 2025 pour avoir toutes les publicités const years = [2024, 2025]; let allPublicities: PublicityWithRelations[] = []; - + for (const year of years) { const params = new URLSearchParams(); params.append('year', year.toString()); // NE PAS filtrer par magasin - on veut toutes les publicités - + try { - const response = await fetch(`/api/publicities?${params}`, { credentials: 'include' }); + const response = await fetch(`/api/ad-campaigns?${params}`, { credentials: 'include' }); if (response.ok) { const yearPublicities = await response.json(); allPublicities = [...allPublicities, ...yearPublicities]; @@ -216,12 +216,12 @@ export default function Dashboard() { console.log(`Erreur lors de la récupération des publicités ${year}:`, error); } } - + // Filtrer les publicités à venir et les trier par date const futurePublicities = allPublicities .filter((publicity: any) => new Date(publicity.startDate) > new Date()) .sort((a: any, b: any) => new Date(a.startDate).getTime() - new Date(b.startDate).getTime()); - + return futurePublicities; }, }); @@ -249,10 +249,10 @@ export default function Dashboard() { } return; } - + // Vérifier s'il y a des problèmes DLC const shouldShowDlcModal = dlcStats.expired > 0 || dlcStats.expiringSoon > 0; - + if (shouldShowDlcModal && !showDlcAlertModal) { // Vérifier s'il n'y a pas de snooze actif const snoozedUntil = localStorage.getItem('dlcAlertSnooze'); @@ -305,7 +305,7 @@ export default function Dashboard() { const dateB = safeDate(b.createdAt); return (dateB ? dateB.getTime() : 0) - (dateA ? dateA.getTime() : 0); }) : []; // Afficher toutes les commandes en attente - + const upcomingDeliveries = Array.isArray(allDeliveries) ? allDeliveries .filter((d: any) => { const isPlanned = d.status === 'planned'; @@ -314,7 +314,7 @@ export default function Dashboard() { }) .sort((a: any, b: any) => new Date(a.scheduledDate).getTime() - new Date(b.scheduledDate).getTime()) .slice(0, 4) : []; // Afficher les 4 prochaines livraisons - + console.log('🚚 Dashboard - Upcoming deliveries result:', upcomingDeliveries.length, upcomingDeliveries); // Calculs pour les statistiques @@ -324,9 +324,9 @@ export default function Dashboard() { const deliveryDate = safeDate(delivery.deliveredDate || delivery.createdAt); if (!deliveryDate) return false; const now = new Date(); - return deliveryDate.getMonth() === now.getMonth() && - deliveryDate.getFullYear() === now.getFullYear() && - delivery.status === 'delivered'; + return deliveryDate.getMonth() === now.getMonth() && + deliveryDate.getFullYear() === now.getFullYear() && + delivery.status === 'delivered'; }).length : 0; // Calculer le total des palettes pour les livraisons 'delivered' du mois en cours @@ -346,35 +346,35 @@ export default function Dashboard() { const getPriorityConfig = (priority: string) => { switch (priority) { case 'high': - return { - color: 'destructive' as const, - icon: AlertTriangle, - label: 'Élevée' + return { + color: 'destructive' as const, + icon: AlertTriangle, + label: 'Élevée' }; case 'medium': - return { - color: 'default' as const, - icon: Clock, - label: 'Moyenne' + return { + color: 'default' as const, + icon: Clock, + label: 'Moyenne' }; case 'low': - return { - color: 'secondary' as const, - icon: Circle, - label: 'Faible' + return { + color: 'secondary' as const, + icon: Circle, + label: 'Faible' }; default: - return { - color: 'secondary' as const, - icon: Circle, - label: 'Moyenne' + return { + color: 'secondary' as const, + icon: Circle, + label: 'Moyenne' }; } }; console.log('📊 Dashboard Debug - Raw Data:', { allOrders: Array.isArray(allOrders) ? allOrders.length : 'NOT_ARRAY', - allDeliveries: Array.isArray(allDeliveries) ? allDeliveries.length : 'NOT_ARRAY', + allDeliveries: Array.isArray(allDeliveries) ? allDeliveries.length : 'NOT_ARRAY', customerOrders: Array.isArray(customerOrders) ? customerOrders.length : 'NOT_ARRAY', upcomingPublicities: Array.isArray(upcomingPublicities) ? upcomingPublicities.length : 'NOT_ARRAY', stats: stats, @@ -423,7 +423,7 @@ export default function Dashboard() { {/* Alerts */}
- + {dlcStats.expiringSoon > 0 && (
@@ -432,7 +432,7 @@ export default function Dashboard() {
)} - + {dlcStats.expired > 0 && (
@@ -523,11 +523,10 @@ export default function Dashboard() { const orderDate = safeDate(order.plannedDate); const daysPending = orderDate ? Math.floor((new Date().getTime() - orderDate.getTime()) / (1000 * 60 * 60 * 24)) : 0; const isOverdue = daysPending > 10; - + return ( -
+
@@ -536,7 +535,7 @@ export default function Dashboard() {
- @@ -604,7 +603,7 @@ export default function Dashboard() { .map((publicity: any) => { const participatingStores = publicity.participations || []; const isCurrentStoreParticipating = selectedStoreId && participatingStores.some((p: any) => p.groupId === parseInt(selectedStoreId.toString())); - + return (
@@ -624,7 +623,7 @@ export default function Dashboard() {

- + {/* Magasins participants */}
Magasins: @@ -635,12 +634,12 @@ export default function Dashboard() { {participatingStores.map((participation: any) => { const isCurrentStore = selectedStoreId && participation.groupId === parseInt(selectedStoreId.toString()); const groupColor = participation.group?.color || '#1976D2'; - + return ( - { const priorityConfig = getPriorityConfig(task.priority); const PriorityIcon = priorityConfig.icon; - + return (
@@ -702,7 +701,7 @@ export default function Dashboard() {
- @@ -716,11 +715,11 @@ export default function Dashboard() {
); }) : ( -
-

Aucune tâche en cours

-

Toutes les tâches sont terminées

-
- )} +
+

Aucune tâche en cours

+

Toutes les tâches sont terminées

+
+ )}
@@ -734,7 +733,7 @@ export default function Dashboard() { Nouvelle Annonce - + {recentAnnouncement && (
@@ -756,7 +755,7 @@ export default function Dashboard() { {safeFormat(recentAnnouncement.createdAt, "d MMMM yyyy 'à' HH:mm")}

- +

{recentAnnouncement.title} @@ -767,7 +766,7 @@ export default function Dashboard() {

- + {recentAnnouncement.createdBy && (
@@ -778,7 +777,7 @@ export default function Dashboard() { )}
)} - +
); diff --git a/client/src/pages/Publicities.tsx b/client/src/pages/Publicities.tsx index 9b7c256..05a24bc 100644 --- a/client/src/pages/Publicities.tsx +++ b/client/src/pages/Publicities.tsx @@ -39,15 +39,15 @@ export default function Publicities() { const startYear = currentYear - 2; const yearOptions = Array.from({ length: 13 }, (_, i) => startYear + i); - const { data: publicities = [], isLoading } = useQuery({ - queryKey: ['/api/publicities', selectedYear, selectedStoreId], + const { data: publicities = [], isLoading } = useQuery({ + queryKey: ['/api/ad-campaigns', selectedYear, selectedStoreId], queryFn: async () => { const params = new URLSearchParams(); params.append('year', selectedYear.toString()); if (selectedStoreId) { params.append('storeId', selectedStoreId.toString()); } - const response = await fetch(`/api/publicities?${params}`); + const response = await fetch(`/api/ad-campaigns?${params}`); if (!response.ok) { throw new Error('Failed to fetch publicities'); } @@ -99,7 +99,7 @@ export default function Publicities() { console.log(`🗑️ [Frontend] Attempting to delete publicity ${id} using POST method`); try { - const response = await fetch(`/api/publicities/${id}/delete`, { + const response = await fetch(`/api/ad-campaigns/${id}/delete`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({}) @@ -111,7 +111,7 @@ export default function Publicities() { } else if (response.status === 404) { // Route not found, try DELETE method as fallback (for dev environment) console.log(`⚠️ [Frontend] POST route not found, trying DELETE method`); - const deleteResponse = await fetch(`/api/publicities/${id}`, { method: 'DELETE' }); + const deleteResponse = await fetch(`/api/ad-campaigns/${id}`, { method: 'DELETE' }); if (!deleteResponse.ok) { const error = await deleteResponse.json(); @@ -133,7 +133,7 @@ export default function Publicities() { } }, onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['/api/publicities'] }); + queryClient.invalidateQueries({ queryKey: ['/api/ad-campaigns'] }); toast({ description: "Publicité supprimée avec succès" }); setIsDeleteModalOpen(false); setSelectedPublicity(null); diff --git a/client/src/pages/mobile/PublicitiesPage.tsx b/client/src/pages/mobile/PublicitiesPage.tsx index b025d91..a012984 100644 --- a/client/src/pages/mobile/PublicitiesPage.tsx +++ b/client/src/pages/mobile/PublicitiesPage.tsx @@ -34,16 +34,17 @@ export default function MobilePublicitiesPage() { const [selectedYear, setSelectedYear] = useState(new Date().getFullYear()); const [searchTerm, setSearchTerm] = useState(""); - // Générer les années pour le select (année courante +/- 2 ans) + // Générer les années pour le select (année courante - 2 ans à + 10 ans) const currentYear = new Date().getFullYear(); - const years = Array.from({ length: 5 }, (_, i) => currentYear - 2 + i); + const startYear = currentYear - 2; + const years = Array.from({ length: 13 }, (_, i) => startYear + i); const { data: publicities = [], isLoading } = useQuery({ - queryKey: ["/api/publicities", selectedStoreId, selectedYear], + queryKey: ["/api/ad-campaigns", selectedStoreId, selectedYear], queryFn: async () => { if (!selectedStoreId) return []; const response = await fetch( - `/api/publicities?storeId=${selectedStoreId}&year=${selectedYear}`, + `/api/ad-campaigns?storeId=${selectedStoreId}&year=${selectedYear}`, { credentials: 'include' } ); if (!response.ok) throw new Error("Failed to fetch publicities"); diff --git a/package-lock.json b/package-lock.json index 395b528..c41fe18 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,7 +45,6 @@ "@types/form-data": "^2.2.1", "@types/memoizee": "^0.4.12", "@types/multer": "^2.0.0", - "@types/node-fetch": "^2.6.13", "@types/pg": "^8.15.4", "bcrypt": "^6.0.0", "busboy": "^1.6.0", @@ -102,13 +101,15 @@ "@tailwindcss/typography": "^0.5.15", "@tailwindcss/vite": "^4.1.3", "@types/connect-pg-simple": "^7.0.3", - "@types/express": "4.17.21", + "@types/date-fns": "^2.5.3", + "@types/express": "^4.17.21", "@types/express-session": "^1.18.0", - "@types/node": "20.16.11", + "@types/node": "^20.16.11", + "@types/node-fetch": "^2.6.13", "@types/passport": "^1.0.16", "@types/passport-local": "^1.0.38", - "@types/react": "^18.3.11", - "@types/react-dom": "^18.3.1", + "@types/react": "^18.3.27", + "@types/react-dom": "^18.3.7", "@types/ws": "^8.5.13", "@vitejs/plugin-react": "^4.3.2", "autoprefixer": "^10.4.20", @@ -3720,6 +3721,13 @@ "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", "license": "MIT" }, + "node_modules/@types/date-fns": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/@types/date-fns/-/date-fns-2.5.3.tgz", + "integrity": "sha512-4KVPD3g5RjSgZtdOjvI/TDFkLNUHhdoWxmierdQbDeEg17Rov0hbBYtIzNaQA67ORpteOhvR9YEMTb6xeDCang==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -3810,6 +3818,7 @@ "version": "2.6.13", "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", + "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -3880,14 +3889,14 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "18.3.23", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz", - "integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==", + "version": "18.3.27", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", + "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", "devOptional": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", - "csstype": "^3.0.2" + "csstype": "^3.2.2" } }, "node_modules/@types/react-dom": { @@ -4600,9 +4609,9 @@ } }, "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, "node_modules/d": { diff --git a/package.json b/package.json index 3c7f96f..d2e916c 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,6 @@ "@types/form-data": "^2.2.1", "@types/memoizee": "^0.4.12", "@types/multer": "^2.0.0", - "@types/node-fetch": "^2.6.13", "@types/pg": "^8.15.4", "bcrypt": "^6.0.0", "busboy": "^1.6.0", @@ -104,13 +103,15 @@ "@tailwindcss/typography": "^0.5.15", "@tailwindcss/vite": "^4.1.3", "@types/connect-pg-simple": "^7.0.3", - "@types/express": "4.17.21", + "@types/date-fns": "^2.5.3", + "@types/express": "^4.17.21", "@types/express-session": "^1.18.0", - "@types/node": "20.16.11", + "@types/node": "^20.16.11", + "@types/node-fetch": "^2.6.13", "@types/passport": "^1.0.16", "@types/passport-local": "^1.0.38", - "@types/react": "^18.3.11", - "@types/react-dom": "^18.3.1", + "@types/react": "^18.3.27", + "@types/react-dom": "^18.3.7", "@types/ws": "^8.5.13", "@vitejs/plugin-react": "^4.3.2", "autoprefixer": "^10.4.20", diff --git a/server/routes.ts b/server/routes.ts index 1efbab4..782a37c 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -1,4 +1,4 @@ -import type { Express } from "express"; +import type { Express, Request, Response } from "express"; import { createServer, type Server } from "http"; import { storage } from "./storage"; import { setupLocalAuth, requireAuth } from "./localAuth"; @@ -10,32 +10,32 @@ console.log('🔍 Using development storage and authentication'); // Fonction de normalisation des dates pour gérer différents formats de NocoDB function normalizeDateString(dateString: string | null | undefined): string | null { if (!dateString || typeof dateString !== 'string') return null; - + const trimmed = dateString.trim(); if (!trimmed) return null; - + try { // Si déjà au format ISO (YYYY-MM-DD), le retourner tel quel if (/^\d{4}-\d{2}-\d{2}$/.test(trimmed)) { return trimmed; } - + // Format slash ou tiret : DD/MM/YYYY, MM/DD/YYYY, DD-MM-YYYY, MM-DD-YYYY const slashMatch = trimmed.match(/^(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})$/); if (slashMatch) { const [, first, second, year] = slashMatch; const firstNum = parseInt(first); const secondNum = parseInt(second); - + // Validation basique des valeurs if (firstNum > 31 || secondNum > 31 || firstNum === 0 || secondNum === 0) { console.warn(`⚠️ Date invalide (valeurs hors limites): ${trimmed}`); return null; } - + // Déterminer le format en fonction des valeurs let day: string, month: string; - + if (firstNum > 12) { // first > 12 → forcément DD/MM (format français/européen) day = first.padStart(2, '0'); @@ -50,7 +50,7 @@ function normalizeDateString(dateString: string | null | undefined): string | nu day = first.padStart(2, '0'); month = second.padStart(2, '0'); } - + // Validation finale : mois entre 1-12, jour entre 1-31 const monthNum = parseInt(month); const dayNum = parseInt(day); @@ -58,10 +58,10 @@ function normalizeDateString(dateString: string | null | undefined): string | nu console.warn(`⚠️ Date invalide après parsing: ${trimmed} → month=${month}, day=${day}`); return null; } - + return `${year}-${month}-${day}`; } - + // Essayer de parser avec Date (format ISO complet avec heures) const date = new Date(trimmed); if (!isNaN(date.getTime())) { @@ -70,7 +70,7 @@ function normalizeDateString(dateString: string | null | undefined): string | nu const day = String(date.getDate()).padStart(2, '0'); return `${year}-${month}-${day}`; } - + // Si aucun format reconnu, retourner null (ne pas persister une date invalide) console.warn(`⚠️ Format de date non reconnu: ${trimmed}`); return null; @@ -85,7 +85,7 @@ async function hashPasswordSimple(password: string) { const crypto = await import('crypto'); const { promisify } = await import('util'); const scryptAsync = promisify(crypto.scrypt); - + const salt = crypto.randomBytes(16).toString("hex"); const buf = (await scryptAsync(password, salt, 64)) as Buffer; return `${buf.toString("hex")}.${salt}`; @@ -95,10 +95,10 @@ async function hashPasswordSimple(password: string) { // Alias pour compatibilité const isAuthenticated = requireAuth; const setupAuth = setupLocalAuth; -import { - insertGroupSchema, - insertSupplierSchema, - insertOrderSchema, +import { + insertGroupSchema, + insertSupplierSchema, + insertOrderSchema, insertDeliverySchema, insertUserGroupSchema, insertPublicitySchema, @@ -162,10 +162,10 @@ export async function registerRoutes(app: Express): Promise { const config = await storage.getWebhookBapConfig(); res.json(config || null); - + } catch (error: any) { console.error('❌ Erreur récupération config webhook BAP:', error); - + // Si la table n'existe pas, retourner une configuration par défaut if (error.code === '42P01') { // Relation does not exist console.log('⚠️ Table webhook_bap_config n\'existe pas, retour config par défaut'); @@ -178,7 +178,7 @@ export async function registerRoutes(app: Express): Promise { needsTableCreation: true }); } - + res.status(500).json({ error: 'Erreur serveur', details: error.message }); } }); @@ -201,7 +201,7 @@ export async function registerRoutes(app: Express): Promise { // Vérifier si une configuration existe déjà const existingConfig = await storage.getWebhookBapConfig(); - + let config: any; if (existingConfig) { // Mettre à jour la configuration existante @@ -211,14 +211,14 @@ export async function registerRoutes(app: Express): Promise { config = await storage.createWebhookBapConfig(validatedData); } - console.log('✅ Configuration webhook BAP sauvegardée:', { - id: config.id, + console.log('✅ Configuration webhook BAP sauvegardée:', { + id: config.id, name: config.name, - isActive: config.isActive + isActive: config.isActive }); - + res.json(config); - + } catch (error: any) { console.error('❌ Erreur sauvegarde config webhook BAP:', error); res.status(500).json({ error: 'Erreur serveur', details: error.message }); @@ -271,24 +271,24 @@ export async function registerRoutes(app: Express): Promise { } const result = await testResponse.text(); - + res.json({ success: true, status: testResponse.status, message: 'Webhook accessible', response: result.substring(0, 200) // Limiter la réponse }); - + } catch (error: any) { console.error('❌ Erreur test webhook BAP:', error); - + let errorMessage = 'Échec du test de connectivité'; if (error.name === 'AbortError') { errorMessage = 'Timeout - Le webhook ne répond pas'; } else if (error.message) { errorMessage = error.message; } - + res.status(500).json({ error: errorMessage, details: error.message }); } }); @@ -308,7 +308,7 @@ export async function registerRoutes(app: Express): Promise { const config = await storage.getUtilities(); res.json(config || null); - + } catch (error: any) { console.error('❌ Erreur récupération utilities:', error); res.status(500).json({ error: 'Erreur serveur', details: error.message }); @@ -330,7 +330,7 @@ export async function registerRoutes(app: Express): Promise { const validatedData = insertUtilitiesSchema.parse(req.body); const existingConfig = await storage.getUtilities(); - + let config: any; if (existingConfig) { config = await storage.updateUtilities(existingConfig.id, validatedData); @@ -338,12 +338,12 @@ export async function registerRoutes(app: Express): Promise { config = await storage.createUtilities(validatedData); } - console.log('✅ Configuration utilities sauvegardée:', { - id: config.id, + console.log('✅ Configuration utilities sauvegardée:', { + id: config.id, salesAnalysisUrl: config.salesAnalysisUrl }); res.json(config); - + } catch (error: any) { console.error('❌ Erreur sauvegarde utilities:', error); res.status(500).json({ error: 'Erreur serveur', details: error.message }); @@ -366,16 +366,17 @@ export async function registerRoutes(app: Express): Promise { // Validation du groupId avec Zod const groupIdSchema = z.coerce.number().int().positive(); const validation = groupIdSchema.safeParse(req.query.groupId); - + if (!validation.success) { return res.status(400).json({ error: 'groupId invalide - doit être un entier positif' }); } - + const groupId = validation.data; // Vérifier l'autorisation : directeur ne peut voir que ses groupes if (user.role === 'directeur') { - const userGroupIds = await storage.getUserGroups(user.id); + const userGroups = await storage.getUserGroups(user.id); + const userGroupIds = userGroups.map(ug => ug.groupId); if (!userGroupIds.includes(groupId)) { return res.status(403).json({ error: 'Accès refusé - Vous ne pouvez accéder qu\'aux données de votre groupe' }); } @@ -402,7 +403,7 @@ export async function registerRoutes(app: Express): Promise { // FALLBACK : Pour les livraisons sans dueDate ou sans TTC, interroger NocoDB const { InvoiceVerificationService } = await import('./invoiceVerification.js'); const verificationService = new InvoiceVerificationService(); - + // Traiter les livraisons sans dueDate for (const delivery of deliveriesWithoutDueDate) { try { @@ -412,9 +413,9 @@ export async function registerRoutes(app: Express): Promise { false, // Ne pas forcer le refresh, utiliser cache si disponible delivery.reconciled || false ); - + const updateData: any = {}; - + if (result.exists && result.dueDate) { // Normaliser la date avant de la stocker const normalizedDateString = normalizeDateString(result.dueDate); @@ -424,13 +425,15 @@ export async function registerRoutes(app: Express): Promise { delivery.dueDate = normalizedDate; } } - - // Récupérer aussi le TTC si manquant - if (result.exists && result.invoiceAmountTTC && !delivery.invoiceAmountTTC) { - updateData.invoiceAmountTTC = result.invoiceAmountTTC.toString(); - delivery.invoiceAmountTTC = result.invoiceAmountTTC.toString(); + + // Récupérer aussi le montant si manquant + if (result.exists && result.invoiceAmount && !delivery.invoiceAmount) { + // @ts-ignore + updateData.invoiceAmount = result.invoiceAmount.toString(); + // @ts-ignore + delivery.invoiceAmount = result.invoiceAmount.toString(); } - + // Mettre à jour si nécessaire if (Object.keys(updateData).length > 0) { await storage.updateDelivery(delivery.id, updateData); @@ -443,12 +446,12 @@ export async function registerRoutes(app: Express): Promise { // Combiner toutes les livraisons qui ont maintenant une dueDate const allDeliveriesWithDueDate = [...deliveriesWithDueDate, ...deliveriesWithoutDueDate.filter((d: any) => d.dueDate)]; - + // FALLBACK TTC : Pour les livraisons qui ont une dueDate mais pas de TTC const deliveriesNeedingTTC = allDeliveriesWithDueDate.filter((d: any) => !d.invoiceAmountTTC || parseFloat(d.invoiceAmountTTC) === 0); - + console.log(`💰 Livraisons nécessitant récupération TTC: ${deliveriesNeedingTTC.length}`); - + for (const delivery of deliveriesNeedingTTC) { try { const result = await verificationService.verifyInvoice( @@ -457,13 +460,19 @@ export async function registerRoutes(app: Express): Promise { false, delivery.reconciled || false ); - - if (result.exists && result.invoiceAmountTTC) { - await storage.updateDelivery(delivery.id, { - invoiceAmountTTC: result.invoiceAmountTTC.toString() - }); - delivery.invoiceAmountTTC = result.invoiceAmountTTC.toString(); - console.log(`💰 TTC récupéré pour livraison #${delivery.id}: ${result.invoiceAmountTTC}€`); + + if (result.exists && result.invoiceAmount) { + const updates: any = { + invoiceAmount: result.invoiceAmount.toString(), + supplierName: result.supplierName + }; + if (result.dueDate) { + updates.dueDate = new Date(result.dueDate); + } + await storage.updateDelivery(delivery.id, updates); + // @ts-ignore + delivery.invoiceAmount = result.invoiceAmount.toString(); + console.log(`💰 Montant récupéré pour livraison #${delivery.id}: ${result.invoiceAmount}€`); } } catch (error) { console.error(`❌ Fallback TTC échoué pour livraison #${delivery.id}:`, error); @@ -477,7 +486,7 @@ export async function registerRoutes(app: Express): Promise { // Formatter les échéances const schedules = allDeliveriesWithDueDate.map((delivery: any) => { const supplier = supplierMap.get(delivery.supplierId); - + return { id: delivery.id, invoiceReference: delivery.invoiceReference, @@ -493,7 +502,7 @@ export async function registerRoutes(app: Express): Promise { console.log(`📅 Total échéances retournées: ${schedules.length}`); res.json({ schedules }); - + } catch (error: any) { console.error('❌ Erreur récupération échéances:', error); res.status(500).json({ error: 'Erreur serveur', details: error.message }); @@ -504,7 +513,7 @@ export async function registerRoutes(app: Express): Promise { app.post('/api/payment-schedule/export', isAuthenticated, async (req: any, res) => { try { console.log('📊 [EXPORT] Début de l\'export Excel'); - + const userId = req.user?.claims?.sub || req.user?.id; if (!userId) { console.error('❌ [EXPORT] Utilisateur non authentifié'); @@ -513,7 +522,7 @@ export async function registerRoutes(app: Express): Promise { const user = await storage.getUser(userId); console.log(`🔍 [EXPORT] Utilisateur: ${user?.username} (${user?.role})`); - + if (!user || (user.role !== 'admin' && user.role !== 'directeur')) { console.error('❌ [EXPORT] Accès refusé'); return res.status(403).json({ error: 'Accès refusé - Admin ou Directeur uniquement' }); @@ -534,7 +543,8 @@ export async function registerRoutes(app: Express): Promise { // Vérifier l'autorisation pour les directeurs if (user.role === 'directeur') { - const userGroupIds = await storage.getUserGroups(user.id); + const userGroups = await storage.getUserGroups(user.id); + const userGroupIds = userGroups.map(ug => ug.groupId); if (!userGroupIds.includes(validatedGroupId)) { return res.status(403).json({ error: 'Accès refusé - Vous ne pouvez accéder qu\'aux données de votre groupe' }); } @@ -548,9 +558,9 @@ export async function registerRoutes(app: Express): Promise { // Récupérer toutes les livraisons du groupe avec échéance const allDeliveries = await storage.getDeliveries(); - const groupDeliveries = allDeliveries.filter((d: any) => - d.groupId === validatedGroupId && - d.invoiceReference && + const groupDeliveries = allDeliveries.filter((d: any) => + d.groupId === validatedGroupId && + d.invoiceReference && d.dueDate ); @@ -599,7 +609,7 @@ export async function registerRoutes(app: Express): Promise { // Créer les lignes de données const csvLines: string[] = []; - + // Ajouter la ligne d'en-tête csvLines.push(headers.join(';')); @@ -641,8 +651,8 @@ export async function registerRoutes(app: Express): Promise { console.error('❌ [EXPORT] Erreur export Excel:', error); console.error('❌ [EXPORT] Message:', error.message); console.error('❌ [EXPORT] Stack:', error.stack); - res.status(500).json({ - error: 'Erreur lors de l\'export', + res.status(500).json({ + error: 'Erreur lors de l\'export', details: error.message, stack: process.env.NODE_ENV === 'development' ? error.stack : undefined }); @@ -652,8 +662,8 @@ export async function registerRoutes(app: Express): Promise { // Route BAP pour envoi webhook n8n app.post('/api/bap/send-webhook', isAuthenticated, async (req: any, res) => { try { - console.log('🔍 BAP: Requête reçue', { - hasUser: !!req.user, + console.log('🔍 BAP: Requête reçue', { + hasUser: !!req.user, userType: typeof req.user, hasClaims: !!(req.user?.claims), hasId: !!(req.user?.id), @@ -663,7 +673,7 @@ export async function registerRoutes(app: Express): Promise { // Vérifier que l'utilisateur est admin const userId = req.user?.claims?.sub || req.user?.id; console.log('🔍 BAP: User ID extracted:', userId); - + if (!userId) { console.error('❌ BAP: No user ID found'); return res.status(401).json({ error: 'Utilisateur non authentifié' }); @@ -671,7 +681,7 @@ export async function registerRoutes(app: Express): Promise { const user = await storage.getUser(userId); console.log('🔍 BAP: User found:', { id: user?.id, role: user?.role }); - + if (!user || user.role !== 'admin') { console.error('❌ BAP: Access denied', { user: user?.role }); return res.status(403).json({ error: 'Accès refusé - Admin uniquement' }); @@ -702,9 +712,9 @@ export async function registerRoutes(app: Express): Promise { return res.status(400).json({ error: 'Format base64 invalide' }); } - console.log('📤 BAP: Envoi webhook n8n', { - recipient, - fileName, + console.log('📤 BAP: Envoi webhook n8n', { + recipient, + fileName, fileSize: fileBuffer.length, userId: user.id }); @@ -718,10 +728,10 @@ export async function registerRoutes(app: Express): Promise { contentType: 'application/pdf' }; - console.log('✅ BAP: Payload JSON préparé', { - recipient, - fileName, - fileSize: fileBuffer.length + console.log('✅ BAP: Payload JSON préparé', { + recipient, + fileName, + fileSize: fileBuffer.length }); // Récupérer l'URL du webhook depuis la configuration @@ -732,12 +742,12 @@ export async function registerRoutes(app: Express): Promise { } const webhookUrl = webhookConfig.webhookUrl; - + const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 60000); // 60 secondes console.log('🌐 BAP: Envoi vers webhook n8n (POST avec body JSON)...'); - + const response = await fetch(webhookUrl, { method: 'POST', headers: { @@ -748,7 +758,7 @@ export async function registerRoutes(app: Express): Promise { }); clearTimeout(timeoutId); - + console.log('🌐 BAP: Réponse webhook reçue', { status: response.status, ok: response.ok }); if (!response.ok) { @@ -758,9 +768,9 @@ export async function registerRoutes(app: Express): Promise { } const result = await response.text(); - + console.log('✅ BAP: Webhook n8n réussi', { recipient, result: result.substring(0, 100) }); - + res.json({ success: true, message: 'Fichier envoyé avec succès', @@ -773,17 +783,17 @@ export async function registerRoutes(app: Express): Promise { message: error.message, stack: error.stack?.substring(0, 500) }); - + let errorMessage = 'Erreur lors de l\'envoi du fichier'; if (error.name === 'AbortError') { errorMessage = 'Timeout - Le traitement a pris trop de temps'; } else if (error.message) { errorMessage = error.message; } - - res.status(500).json({ + + res.status(500).json({ error: errorMessage, - details: error.message + details: error.message }); } }); @@ -793,32 +803,32 @@ export async function registerRoutes(app: Express): Promise { const parts: any = {}; const boundaryBuffer = Buffer.from(`--${boundary}`); const sections = []; - + let start = 0; while (true) { const boundaryIndex = buffer.indexOf(boundaryBuffer, start); if (boundaryIndex === -1) break; - + if (start > 0) { sections.push(buffer.slice(start, boundaryIndex)); } start = boundaryIndex + boundaryBuffer.length; } - + for (const section of sections) { const headerEnd = section.indexOf('\r\n\r\n'); if (headerEnd === -1) continue; - + const headers = section.slice(0, headerEnd).toString(); const content = section.slice(headerEnd + 4, section.length - 2); - + const nameMatch = headers.match(/name="([^"]+)"/); const filenameMatch = headers.match(/filename="([^"]+)"/); const contentTypeMatch = headers.match(/Content-Type: ([^\r\n]+)/); - + if (!nameMatch) continue; const name = nameMatch[1]; - + if (filenameMatch) { parts[name] = { filename: filenameMatch[1], @@ -829,7 +839,7 @@ export async function registerRoutes(app: Express): Promise { parts[name] = content.toString('utf-8'); } } - + return parts; } @@ -852,16 +862,16 @@ export async function registerRoutes(app: Express): Promise { if (!boundaryMatch) { return res.status(400).json({ error: 'Format multipart invalide' }); } - + const boundary = boundaryMatch[1]; const chunks: Buffer[] = []; - + await new Promise((resolve, reject) => { req.on('data', (chunk: Buffer) => chunks.push(chunk)); req.on('end', () => resolve()); req.on('error', reject); }); - + const buffer = Buffer.concat(chunks); const parts = parseMultipart(buffer, boundary); @@ -879,11 +889,11 @@ export async function registerRoutes(app: Express): Promise { // Importer form-data dynamiquement avec eval pour ESM const FormDataModule = await eval('import("form-data")'); const FormData = FormDataModule.default; - + const formData = new FormData(); - formData.append('file', parts.file.buffer, { - filename: parts.file.filename, - contentType: parts.file.contentType + formData.append('file', parts.file.buffer, { + filename: parts.file.filename, + contentType: parts.file.contentType }); formData.append('supplier', parts.supplier || ''); formData.append('blNumber', parts.blNumber || ''); @@ -942,9 +952,9 @@ export async function registerRoutes(app: Express): Promise { 'content-length': req.headers['content-length'], 'user-agent': req.headers['user-agent']?.substring(0, 50) + '...' }); - + console.log('📋 POST /api/groups - Request body:', JSON.stringify(req.body, null, 2)); - + // Déterminer l'ID utilisateur selon l'environnement let userId; if (req.user.claims && req.user.claims.sub) { @@ -957,36 +967,36 @@ export async function registerRoutes(app: Express): Promise { console.error('❌ No user ID found in request:', { user: req.user }); return res.status(401).json({ message: "User authentication failed" }); } - + console.log('🔐 User requesting group creation:', userId); - + // Vérifier l'utilisateur const user = await storage.getUser(userId); if (!user) { console.error('❌ User not found:', userId); return res.status(404).json({ message: "User not found" }); } - + console.log('✅ User found:', { username: user.username, role: user.role }); - + // Vérifier les permissions if (user.role !== 'admin' && user.role !== 'manager') { console.error('❌ Insufficient permissions:', { userRole: user.role, required: ['admin', 'manager'] }); return res.status(403).json({ message: "Insufficient permissions" }); } - + console.log('✅ User has permission to create group'); - + // Valider les données console.log('🔍 Validating group data with schema...'); const data = insertGroupSchema.parse(req.body); console.log('✅ Group data validation passed:', data); - + // Créer le groupe console.log('🏪 Creating group in database...'); const group = await storage.createGroup(data); console.log('✅ Group creation successful:', { id: group.id, name: group.name }); - + res.json(group); } catch (error: any) { console.error('❌ Failed to create group:', { @@ -995,16 +1005,16 @@ export async function registerRoutes(app: Express): Promise { body: req.body, userId: req.user?.id || req.user?.claims?.sub || 'unknown' }); - + // Erreur de validation Zod if (error?.name === 'ZodError') { console.error('❌ Validation error details:', error.errors); - return res.status(400).json({ - message: "Validation failed", - errors: error.errors + return res.status(400).json({ + message: "Validation failed", + errors: error.errors }); } - + res.status(500).json({ message: "Failed to create group" }); } }); @@ -1053,7 +1063,7 @@ export async function registerRoutes(app: Express): Promise { // Check if DLC filter is requested const dlcFilter = req.query.dlc === 'true'; const suppliers = await storage.getSuppliers(); - + // Filter suppliers for DLC enabled only if requested if (dlcFilter) { const dlcSuppliers = suppliers.filter(supplier => supplier.hasDlc === true); @@ -1074,9 +1084,9 @@ export async function registerRoutes(app: Express): Promise { 'content-type': req.headers['content-type'], 'content-length': req.headers['content-length'] }); - + console.log('📋 POST /api/suppliers - Request body:', JSON.stringify(req.body, null, 2)); - + // Déterminer l'ID utilisateur selon l'environnement let userId; if (req.user.claims && req.user.claims.sub) { @@ -1089,36 +1099,36 @@ export async function registerRoutes(app: Express): Promise { console.error('❌ No user ID found in request:', { user: req.user }); return res.status(401).json({ message: "User authentication failed" }); } - + console.log('🔐 User requesting supplier creation:', userId); - + // Vérifier l'utilisateur const user = await storage.getUser(userId); if (!user) { console.error('❌ User not found:', userId); return res.status(404).json({ message: "User not found" }); } - + console.log('✅ User found:', { username: user.username, role: user.role }); - + // Vérifier les permissions if (user.role !== 'admin' && user.role !== 'manager' && user.role !== 'directeur') { console.error('❌ Insufficient permissions:', { userRole: user.role, required: ['admin', 'manager', 'directeur'] }); return res.status(403).json({ message: "Insufficient permissions" }); } - + console.log('✅ User has permission to create supplier'); - + // Valider les données console.log('🔍 Validating supplier data with schema...'); const data = insertSupplierSchema.parse(req.body); console.log('✅ Supplier data validation passed:', data); - + // Créer le fournisseur console.log('🚚 Creating supplier in database...'); const supplier = await storage.createSupplier(data); console.log('✅ Supplier creation successful:', { id: supplier.id, name: supplier.name }); - + res.json(supplier); } catch (error: any) { console.error('❌ Failed to create supplier:', { @@ -1127,16 +1137,16 @@ export async function registerRoutes(app: Express): Promise { body: req.body, userId: req.user?.id || req.user?.claims?.sub || 'unknown' }); - + // Erreur de validation Zod if (error.name === 'ZodError') { console.error('❌ Validation error details:', error.errors); - return res.status(400).json({ - message: "Validation failed", - errors: error.errors + return res.status(400).json({ + message: "Validation failed", + errors: error.errors }); } - + res.status(500).json({ message: "Failed to create supplier" }); } }); @@ -1189,7 +1199,7 @@ export async function registerRoutes(app: Express): Promise { if (user.role === 'admin') { let groupIds: number[] | undefined; - + // If admin selected a specific store, filter by it if (storeId) { groupIds = [parseInt(storeId as string)]; @@ -1197,7 +1207,7 @@ export async function registerRoutes(app: Express): Promise { } else { console.log('🔍 Admin orders - showing all stores', { role: user.role }); } - + // Only filter by date if both startDate and endDate are provided if (startDate && endDate) { console.log('Fetching orders by date range:', startDate, 'to', endDate); @@ -1210,16 +1220,16 @@ export async function registerRoutes(app: Express): Promise { // For manager and employee roles, filter by their assigned groups const userGroupIds = (user as any).userGroups?.map((ug: any) => ug.groupId) || []; let groupIds: number[] | undefined; - + if (storeId) { // If a specific store is requested, verify user has access to it const requestedStoreId = parseInt(storeId as string); if (userGroupIds.includes(requestedStoreId)) { groupIds = [requestedStoreId]; - console.log('🔍 Non-admin orders - filtering by accessible store:', { - userId: user.id, - role: user.role, - requestedStoreId + console.log('🔍 Non-admin orders - filtering by accessible store:', { + userId: user.id, + role: user.role, + requestedStoreId }); } else { // User doesn't have access to this store, return empty array @@ -1277,7 +1287,7 @@ export async function registerRoutes(app: Express): Promise { return res.json([]); } } - + // Only filter by date if both startDate and endDate are provided if (startDate && endDate) { orders = await storage.getOrdersByDateRange(startDate as string, endDate as string, groupIds); @@ -1304,7 +1314,7 @@ export async function registerRoutes(app: Express): Promise { const id = parseInt(req.params.id); const order = await storage.getOrder(id); - + if (!order) { return res.status(404).json({ message: "Order not found" }); } @@ -1363,8 +1373,8 @@ export async function registerRoutes(app: Express): Promise { console.log('🚀 Creating order in storage...'); const order = await storage.createOrder(data); - console.log('✅ Order created successfully:', { - id: order.id, + console.log('✅ Order created successfully:', { + id: order.id, groupId: order.groupId, plannedDate: order.plannedDate, supplierId: order.supplierId @@ -1378,16 +1388,16 @@ export async function registerRoutes(app: Express): Promise { body: req.body, userId: req.user?.id || req.user?.claims?.sub || 'unknown' }); - + // Erreur de validation Zod if (error.name === 'ZodError') { console.error('❌ Order validation error details:', error.errors); - return res.status(400).json({ - message: "Validation failed", - errors: error.errors + return res.status(400).json({ + message: "Validation failed", + errors: error.errors }); } - + res.status(500).json({ message: "Failed to create order" }); } }); @@ -1401,7 +1411,7 @@ export async function registerRoutes(app: Express): Promise { const id = parseInt(req.params.id); const order = await storage.getOrder(id); - + if (!order) { return res.status(404).json({ message: "Order not found" }); } @@ -1436,7 +1446,7 @@ export async function registerRoutes(app: Express): Promise { const id = parseInt(req.params.id); const order = await storage.getOrder(id); - + if (!order) { return res.status(404).json({ message: "Order not found" }); } @@ -1480,10 +1490,10 @@ export async function registerRoutes(app: Express): Promise { // Get deliveries for this order separately since OrderWithRelations doesn't include deliveries const deliveries = await storage.getDeliveries(); const orderDeliveries = deliveries.filter(d => d.orderId === order.id); - + if (orderDeliveries && orderDeliveries.length > 0) { const hasDeliveredDeliveries = orderDeliveries.some((d: any) => d.status === 'delivered'); - + if (hasDeliveredDeliveries && order.status !== 'delivered') { console.log(`🔍 Found problematic order: #CMD-${order.id} (status: ${order.status}) with delivered deliveries`); problematicOrders.push({ @@ -1506,7 +1516,7 @@ export async function registerRoutes(app: Express): Promise { } console.log('🔄 Synchronization completed'); - + res.json({ message: "Synchronization completed", diagnostics: { @@ -1538,7 +1548,7 @@ export async function registerRoutes(app: Express): Promise { if (user.role === 'admin') { let groupIds: number[] | undefined; - + // If admin selected a specific store, filter by it if (storeId) { groupIds = [parseInt(storeId as string)]; @@ -1546,7 +1556,7 @@ export async function registerRoutes(app: Express): Promise { } else { console.log('🔍 Admin deliveries - showing all stores', { role: user.role }); } - + // Only filter by date if both startDate and endDate are provided if (startDate && endDate) { console.log('Fetching deliveries by date range:', startDate, 'to', endDate); @@ -1559,16 +1569,16 @@ export async function registerRoutes(app: Express): Promise { // For manager and employee roles, filter by their assigned groups const userGroupIds = (user as any).userGroups?.map((ug: any) => ug.groupId) || []; let groupIds: number[] | undefined; - + if (storeId) { // If a specific store is requested, verify user has access to it const requestedStoreId = parseInt(storeId as string); if (userGroupIds.includes(requestedStoreId)) { groupIds = [requestedStoreId]; - console.log('🔍 Non-admin deliveries - filtering by accessible store:', { - userId: user.id, - role: user.role, - requestedStoreId + console.log('🔍 Non-admin deliveries - filtering by accessible store:', { + userId: user.id, + role: user.role, + requestedStoreId }); } else { // User doesn't have access to this store, return empty array @@ -1626,7 +1636,7 @@ export async function registerRoutes(app: Express): Promise { return res.json([]); } } - + // Only filter by date if both startDate and endDate are provided if (startDate && endDate) { deliveries = await storage.getDeliveriesByDateRange(startDate as string, endDate as string, groupIds); @@ -1658,7 +1668,7 @@ export async function registerRoutes(app: Express): Promise { const id = parseInt(req.params.id); const delivery = await storage.getDelivery(id); - + if (!delivery) { return res.status(404).json({ message: "Delivery not found" }); } @@ -1687,7 +1697,7 @@ export async function registerRoutes(app: Express): Promise { const id = parseInt(req.params.id); const delivery = await storage.getDelivery(id); - + if (!delivery) { return res.status(404).json({ message: "Delivery not found" }); } @@ -1705,48 +1715,48 @@ export async function registerRoutes(app: Express): Promise { } console.log('🔄 Updating delivery:', { id, data: req.body, user: user.id }); - + // Transform data types before validation const transformedData = { ...req.body }; - + // Convert decimal amounts to strings or null (schema expects string for decimal fields) if (transformedData.blAmount !== undefined) { - transformedData.blAmount = (transformedData.blAmount === null || transformedData.blAmount === '') - ? null + transformedData.blAmount = (transformedData.blAmount === null || transformedData.blAmount === '') + ? null : transformedData.blAmount.toString(); } if (transformedData.invoiceAmount !== undefined) { - transformedData.invoiceAmount = (transformedData.invoiceAmount === null || transformedData.invoiceAmount === '') - ? null + transformedData.invoiceAmount = (transformedData.invoiceAmount === null || transformedData.invoiceAmount === '') + ? null : transformedData.invoiceAmount.toString(); } - + // Convert text fields - allow null for clearing if (transformedData.invoiceReference !== undefined) { - transformedData.invoiceReference = (transformedData.invoiceReference === '') - ? null + transformedData.invoiceReference = (transformedData.invoiceReference === '') + ? null : transformedData.invoiceReference; } if (transformedData.blNumber !== undefined) { - transformedData.blNumber = (transformedData.blNumber === '') - ? null + transformedData.blNumber = (transformedData.blNumber === '') + ? null : transformedData.blNumber; } - + // Convert timestamp fields to Date objects or null (schema expects Date for timestamp fields) if (transformedData.validatedAt !== undefined) { - transformedData.validatedAt = (transformedData.validatedAt === null || transformedData.validatedAt === '') - ? null + transformedData.validatedAt = (transformedData.validatedAt === null || transformedData.validatedAt === '') + ? null : new Date(transformedData.validatedAt); } if (transformedData.dueDate !== undefined) { - transformedData.dueDate = (transformedData.dueDate === null || transformedData.dueDate === '') - ? null + transformedData.dueDate = (transformedData.dueDate === null || transformedData.dueDate === '') + ? null : new Date(transformedData.dueDate); } - + const data = insertDeliverySchema.partial().parse(transformedData); - + // CRITICAL FIX: Si une commande est liée lors de la modification, vérifier qu'elle appartient au même magasin if (data.orderId !== undefined) { if (data.orderId !== null) { @@ -1755,14 +1765,14 @@ export async function registerRoutes(app: Express): Promise { return res.status(400).json({ message: "La commande liée n'existe pas" }); } if (linkedOrder.groupId !== delivery.groupId) { - return res.status(400).json({ - message: `Impossible de lier une livraison du magasin ${delivery.groupId} avec une commande du magasin ${linkedOrder.groupId}` + return res.status(400).json({ + message: `Impossible de lier une livraison du magasin ${delivery.groupId} avec une commande du magasin ${linkedOrder.groupId}` }); } console.log(`✅ Validation OK: Livraison #${id} et commande #${data.orderId} appartiennent au même magasin ${delivery.groupId}`); } } - + // GESTION ÉCHÉANCE : Si la référence facture change, mettre à jour la date d'échéance if (data.invoiceReference !== undefined && data.invoiceReference !== delivery.invoiceReference) { if (data.invoiceReference && data.invoiceReference.trim()) { @@ -1776,7 +1786,7 @@ export async function registerRoutes(app: Express): Promise { true, // forceRefresh delivery.reconciled || false ); - + if (result.exists && result.dueDate) { // Normaliser la date avant de la stocker const normalizedDateString = normalizeDateString(result.dueDate); @@ -1803,10 +1813,10 @@ export async function registerRoutes(app: Express): Promise { console.log(`📅 Référence facture vidée, échéance également vidée`); } } - + const updatedDelivery = await storage.updateDelivery(id, data); console.log('✅ Delivery updated successfully:', { id, updatedDelivery }); - + // SYNCHRONISATION AUTOMATIQUE : Si livraison devient "delivered", marquer la commande associée comme "delivered" // MAIS seulement après validation explicite (pas juste mise à jour status) // Cette sync sera gérée dans validateDelivery endpoint uniquement @@ -1817,26 +1827,26 @@ export async function registerRoutes(app: Express): Promise { // Récupérer le fournisseur pour vérifier le mode automatique const suppliers = await storage.getSuppliers(); const supplier = suppliers.find((s: any) => s.id === updatedDelivery.supplierId); - - if (supplier?.automaticReconciliation && - updatedDelivery.status === 'delivered' && - updatedDelivery.blNumber) { - + + if (supplier?.automaticReconciliation && + updatedDelivery.status === 'delivered' && + updatedDelivery.blNumber) { + console.log(`🤖 Auto-reconciliation: Delivery #${id} from automatic supplier ${supplier.name}, auto-validating...`); - + // Auto-valider le rapprochement await storage.updateDelivery(id, { reconciled: true, validatedAt: new Date() }); - + console.log(`✅ Auto-reconciliation: Delivery #${id} automatically validated for supplier ${supplier.name}`); } } catch (error) { console.error(`❌ Auto-reconciliation failed for delivery #${id}:`, error); } } - + res.json(updatedDelivery); } catch (error) { console.error("Error updating delivery:", error); @@ -1883,22 +1893,22 @@ export async function registerRoutes(app: Express): Promise { return res.status(400).json({ message: "La commande liée n'existe pas" }); } if (linkedOrder.groupId !== data.groupId) { - return res.status(400).json({ - message: `Impossible de lier une livraison du magasin ${data.groupId} avec une commande du magasin ${linkedOrder.groupId}` + return res.status(400).json({ + message: `Impossible de lier une livraison du magasin ${data.groupId} avec une commande du magasin ${linkedOrder.groupId}` }); } console.log(`✅ Validation OK: Livraison et commande #${data.orderId} appartiennent au même magasin ${data.groupId}`); } const delivery = await storage.createDelivery(data); - + // Log de liaison avec commande if (data.orderId) { console.log(`🔗 Delivery #${delivery.id} linked to order #${data.orderId}`); } else { console.log(`🚛 Delivery #${delivery.id} created without order link`); } - + res.json(delivery); } catch (error) { console.error("Error creating delivery:", error); @@ -1917,7 +1927,7 @@ export async function registerRoutes(app: Express): Promise { const id = parseInt(req.params.id); const delivery = await storage.getDelivery(id); - + if (!delivery) { return res.status(404).json({ message: "Delivery not found" }); } @@ -1956,7 +1966,7 @@ export async function registerRoutes(app: Express): Promise { const deliveryId = parseInt(req.params.id); const delivery = await storage.getDelivery(deliveryId); - + if (!delivery) { return res.status(404).json({ message: "Delivery not found" }); } @@ -1992,7 +2002,7 @@ export async function registerRoutes(app: Express): Promise { const deliveryId = parseInt(req.params.id); const delivery = await storage.getDelivery(deliveryId); - + if (!delivery) { return res.status(404).json({ message: "Delivery not found" }); } @@ -2035,7 +2045,7 @@ export async function registerRoutes(app: Express): Promise { const commentId = parseInt(req.params.id); const comment = await storage.getReconciliationCommentById(commentId); - + if (!comment) { return res.status(404).json({ message: "Comment not found" }); } @@ -2055,7 +2065,7 @@ export async function registerRoutes(app: Express): Promise { const updateData = insertReconciliationCommentSchema.partial().parse(req.body); const updatedComment = await storage.updateReconciliationComment(commentId, updateData); - + res.json(updatedComment); } catch (error) { console.error("Error updating reconciliation comment:", error); @@ -2073,7 +2083,7 @@ export async function registerRoutes(app: Express): Promise { const commentId = parseInt(req.params.id); const comment = await storage.getReconciliationCommentById(commentId); - + if (!comment) { return res.status(404).json({ message: "Comment not found" }); } @@ -2109,7 +2119,7 @@ export async function registerRoutes(app: Express): Promise { const deliveryId = parseInt(req.params.id); const delivery = await storage.getDelivery(deliveryId); - + if (!delivery) { return res.status(404).json({ message: "Delivery not found" }); } @@ -2135,7 +2145,7 @@ export async function registerRoutes(app: Express): Promise { } const { invoiceReference, blNumber, forceRefresh } = req.body; - + if (!delivery.supplier || !delivery.group) { console.log('❌ Livraison manque informations:', { deliveryId, @@ -2160,7 +2170,7 @@ export async function registerRoutes(app: Express): Promise { }); let result; - + if (invoiceReference && invoiceReference.trim()) { // Vérifier par référence de facture result = await invoiceVerificationService.verifyInvoice( @@ -2187,27 +2197,27 @@ export async function registerRoutes(app: Express): Promise { } console.log('✅ Résultat vérification:', result); - + // CRITICAL FIX: Sauvegarder les données dans la table deliveries après vérification réussie if (result.exists && (result.invoiceAmount !== undefined || result.invoiceAmountTTC !== undefined || result.dueDate !== undefined || result.invoiceReference !== undefined)) { try { const updateData: any = {}; - + // Ajouter la référence facture si trouvée if (result.invoiceReference) { updateData.invoiceReference = result.invoiceReference; } - + // Ajouter le montant facture HT si trouvé if (result.invoiceAmount !== undefined && result.invoiceAmount !== null) { updateData.invoiceAmount = result.invoiceAmount.toString(); } - + // Ajouter le montant facture TTC si trouvé if (result.invoiceAmountTTC !== undefined && result.invoiceAmountTTC !== null) { updateData.invoiceAmountTTC = result.invoiceAmountTTC.toString(); } - + // Ajouter l'échéance si trouvée if (result.dueDate) { const normalizedDateString = normalizeDateString(result.dueDate); @@ -2216,7 +2226,7 @@ export async function registerRoutes(app: Express): Promise { console.log(`✅ Sauvegarde échéance dans deliveries: ${normalizedDateString}`); } } - + // Mettre à jour la livraison si on a des données if (Object.keys(updateData).length > 0) { await storage.updateDelivery(deliveryId, updateData); @@ -2227,11 +2237,11 @@ export async function registerRoutes(app: Express): Promise { // Ne pas bloquer la réponse, juste logger l'erreur } } - + res.json(result); } catch (error) { console.error("Error verifying invoice:", error); - res.status(500).json({ + res.status(500).json({ message: "Failed to verify invoice", error: error instanceof Error ? error.message : 'Unknown error' }); @@ -2252,7 +2262,7 @@ export async function registerRoutes(app: Express): Promise { const id = parseInt(req.params.id); const delivery = await storage.getDelivery(id); - + if (!delivery) { return res.status(404).json({ message: "Delivery not found" }); } @@ -2266,19 +2276,19 @@ export async function registerRoutes(app: Express): Promise { } const { blNumber, blAmount } = req.body; - + // Le numéro de BL est maintenant obligatoire pour valider une livraison if (!blNumber || !blNumber.trim()) { return res.status(400).json({ message: "Le numéro de bon de livraison est obligatoire pour valider une livraison" }); } - + let blData: any = { blNumber: blNumber.trim() }; if (blAmount !== undefined && blAmount !== null && blAmount !== '') { blData.blAmount = blAmount; } - + await storage.validateDelivery(id, blData); - + // MISE À JOUR DU CACHE : Marquer le cache comme permanent pour cette livraison validée try { if (delivery.invoiceReference && delivery.invoiceReference.trim()) { @@ -2292,7 +2302,7 @@ export async function registerRoutes(app: Express): Promise { } catch (error) { console.error('❌ Erreur mise à jour cache après validation:', error); } - + // SYNCHRONISATION AUTOMATIQUE : Quand validation, marquer la commande associée comme "delivered" if (delivery.orderId) { try { @@ -2303,7 +2313,7 @@ export async function registerRoutes(app: Express): Promise { console.error(`❌ Auto-sync failed for order #${delivery.orderId}:`, error); } } - + res.json({ message: "Delivery validated successfully" }); } catch (error) { console.error("Error validating delivery:", error); @@ -2337,7 +2347,7 @@ export async function registerRoutes(app: Express): Promise { // Mettre à jour le contrôle await storage.markDeliveryControlValidated(id, user.id); - + res.json({ message: "Delivery control validated successfully" }); } catch (error) { console.error("Error validating delivery control:", error); @@ -2362,11 +2372,11 @@ export async function registerRoutes(app: Express): Promise { const deliveries = await storage.getDeliveries(); const reconciledCount = deliveries.filter(d => d.reconciled).length; const totalCount = deliveries.length; - + // Vérifier quelques caches const sampleCaches = []; const reconciledDeliveries = deliveries.filter(d => d.reconciled).slice(0, 5); // Prendre 5 exemples - + for (const delivery of reconciledDeliveries) { if (delivery.invoiceReference) { const cacheKey = `${delivery.invoiceReference.toLowerCase()}_${delivery.groupId}`; @@ -2382,7 +2392,7 @@ export async function registerRoutes(app: Express): Promise { }); } } - + res.json({ statistics: { totalDeliveries: totalCount, @@ -2394,7 +2404,7 @@ export async function registerRoutes(app: Express): Promise { }); } catch (error) { console.error("Erreur diagnostic cache:", error); - res.status(500).json({ + res.status(500).json({ message: "Erreur lors du diagnostic", error: error instanceof Error ? error.message : 'Unknown error' }); @@ -2416,14 +2426,14 @@ export async function registerRoutes(app: Express): Promise { console.log('🔧 [ADMIN] Exécution mise à jour des caches permanents...'); await invoiceVerificationService.updateExistingReconciledCaches(); - - res.json({ + + res.json({ message: "Mise à jour des caches permanents terminée avec succès", - success: true + success: true }); } catch (error) { console.error("Erreur mise à jour caches:", error); - res.status(500).json({ + res.status(500).json({ message: "Erreur lors de la mise à jour des caches", error: error instanceof Error ? error.message : 'Unknown error' }); @@ -2440,7 +2450,7 @@ export async function registerRoutes(app: Express): Promise { const { storeId, status, supplierId, search } = req.query; let groupIds: number[] | undefined; - + if (user.role === 'admin') { groupIds = storeId ? [parseInt(storeId as string)] : undefined; } else { @@ -2474,7 +2484,7 @@ export async function registerRoutes(app: Express): Promise { const { storeId } = req.query; let groupIds: number[] | undefined; - + if (user.role === 'admin') { groupIds = storeId ? [parseInt(storeId as string)] : undefined; } else { @@ -2503,7 +2513,7 @@ export async function registerRoutes(app: Express): Promise { const id = parseInt(req.params.id); const dlcProduct = await storage.getDlcProduct(id); - + if (!dlcProduct) { return res.status(404).json({ message: "DLC Product not found" }); } @@ -2560,7 +2570,7 @@ export async function registerRoutes(app: Express): Promise { const id = parseInt(req.params.id); const dlcProduct = await storage.getDlcProduct(id); - + if (!dlcProduct) { return res.status(404).json({ message: "DLC Product not found" }); } @@ -2590,7 +2600,7 @@ export async function registerRoutes(app: Express): Promise { const id = parseInt(req.params.id); const dlcProduct = await storage.getDlcProduct(id); - + if (!dlcProduct) { return res.status(404).json({ message: "DLC Product not found" }); } @@ -2625,7 +2635,7 @@ export async function registerRoutes(app: Express): Promise { const id = parseInt(req.params.id); const dlcProduct = await storage.getDlcProduct(id); - + if (!dlcProduct) { return res.status(404).json({ message: "DLC Product not found" }); } @@ -2638,9 +2648,9 @@ export async function registerRoutes(app: Express): Promise { } } - console.log('🔍 DLC Validation attempt:', { + console.log('🔍 DLC Validation attempt:', { userId: user.id, - userRole: user.role, + userRole: user.role, dlcProductId: id, dlcGroupId: dlcProduct.groupId, userGroups: user.role !== 'admin' ? user.userGroups.map(ug => ug.groupId) : 'all' @@ -2648,7 +2658,7 @@ export async function registerRoutes(app: Express): Promise { const validatedProduct = await storage.validateDlcProduct(id, user.id); console.log('✅ DLC Product validated successfully by:', user.role, user.id); - + res.json(validatedProduct); } catch (error) { console.error("Error validating DLC product:", error); @@ -2666,7 +2676,7 @@ export async function registerRoutes(app: Express): Promise { const id = parseInt(req.params.id); const dlcProduct = await storage.getDlcProduct(id); - + if (!dlcProduct) { return res.status(404).json({ message: "DLC Product not found" }); } @@ -2679,16 +2689,16 @@ export async function registerRoutes(app: Express): Promise { } } - console.log('🔍 DLC Stock épuisé attempt:', { + console.log('🔍 DLC Stock épuisé attempt:', { userId: user.id, - userRole: user.role, + userRole: user.role, dlcProductId: id, dlcGroupId: dlcProduct.groupId }); const markedProduct = await storage.markDlcProductStockEpuise(id, user.id); console.log('✅ DLC Product marked as stock épuisé by:', user.role, user.id); - + res.json(markedProduct); } catch (error) { console.error("Error marking DLC product as stock épuisé:", error); @@ -2711,7 +2721,7 @@ export async function registerRoutes(app: Express): Promise { const id = parseInt(req.params.id); const dlcProduct = await storage.getDlcProduct(id); - + if (!dlcProduct) { return res.status(404).json({ message: "DLC Product not found" }); } @@ -2724,16 +2734,16 @@ export async function registerRoutes(app: Express): Promise { } } - console.log('🔍 DLC Stock restore attempt:', { + console.log('🔍 DLC Stock restore attempt:', { userId: user.id, - userRole: user.role, + userRole: user.role, dlcProductId: id, dlcGroupId: dlcProduct.groupId }); const restoredProduct = await storage.restoreDlcProductStock(id); console.log('✅ DLC Product stock restored by:', user.role, user.id); - + res.json(restoredProduct); } catch (error) { console.error("Error restoring DLC product stock:", error); @@ -2751,7 +2761,7 @@ export async function registerRoutes(app: Express): Promise { const id = parseInt(req.params.id); const dlcProduct = await storage.getDlcProduct(id); - + if (!dlcProduct) { return res.status(404).json({ message: "DLC Product not found" }); } @@ -2764,16 +2774,16 @@ export async function registerRoutes(app: Express): Promise { } } - console.log('🔍 DLC Product mark as processed attempt:', { + console.log('🔍 DLC Product mark as processed attempt:', { userId: user.id, - userRole: user.role, + userRole: user.role, dlcProductId: id, dlcGroupId: dlcProduct.groupId }); const processedProduct = await storage.markDlcProductAsProcessed(id, user.id); console.log('✅ DLC Product marked as processed by:', user.role, user.id); - + res.json(processedProduct); } catch (error) { console.error("Error marking DLC product as processed:", error); @@ -2796,7 +2806,7 @@ export async function registerRoutes(app: Express): Promise { const id = parseInt(req.params.id); const dlcProduct = await storage.getDlcProduct(id); - + if (!dlcProduct) { return res.status(404).json({ message: "DLC Product not found" }); } @@ -2809,16 +2819,16 @@ export async function registerRoutes(app: Express): Promise { } } - console.log('🔍 DLC Product unmark processed attempt:', { + console.log('🔍 DLC Product unmark processed attempt:', { userId: user.id, - userRole: user.role, + userRole: user.role, dlcProductId: id, dlcGroupId: dlcProduct.groupId }); const unprocessedProduct = await storage.unmarkDlcProductAsProcessed(id); console.log('✅ DLC Product unmarked as processed by:', user.role, user.id); - + res.json(unprocessedProduct); } catch (error) { console.error("Error unmarking DLC product as processed:", error); @@ -2836,25 +2846,25 @@ export async function registerRoutes(app: Express): Promise { const { storeId } = req.query; let groupIds: number[] | undefined; - + if (user.role === 'admin') { // Admin can see all tasks or filter by specific store groupIds = storeId ? [parseInt(storeId as string)] : undefined; - console.log('🔍 Admin tasks filtering:', { - storeId, + console.log('🔍 Admin tasks filtering:', { + storeId, groupIds, - message: storeId ? `Filtering by store ${storeId}` : 'Showing all stores' + message: storeId ? `Filtering by store ${storeId}` : 'Showing all stores' }); } else { // For directeur and other non-admin users: always restrict to their assigned groups const userGroupIds = user.userGroups.map(ug => ug.groupId); - + if (storeId) { // If a specific store is requested, verify user has access to it const requestedStoreId = parseInt(storeId as string); if (userGroupIds.includes(requestedStoreId)) { groupIds = [requestedStoreId]; - console.log('🔍 Non-admin user requesting specific accessible store:', { + console.log('🔍 Non-admin user requesting specific accessible store:', { userId: user.id, role: user.role, requestedStoreId, @@ -2886,15 +2896,15 @@ export async function registerRoutes(app: Express): Promise { } } - console.log('🔍 Tasks API called with:', { - groupIds, - userRole: user.role, + console.log('🔍 Tasks API called with:', { + groupIds, + userRole: user.role, userId: user.id, requestedStoreId: storeId, userGroups: user.role !== 'admin' ? user.userGroups.map(ug => ug.groupId) : 'all', timestamp: new Date().toISOString() }); - + const tasks = await storage.getTasks(groupIds, user.role); console.log('📋 Tasks returned:', { count: tasks.length, @@ -2983,7 +2993,7 @@ export async function registerRoutes(app: Express): Promise { const id = parseInt(req.params.id); const task = await storage.getTask(id); - + if (!task) { return res.status(404).json({ message: "Task not found" }); } @@ -3011,7 +3021,7 @@ export async function registerRoutes(app: Express): Promise { // Nettoyer les données reçues pour éviter les problèmes de types const cleanData: any = {}; - + // Ne pas utiliser de conditions qui ignorent les valeurs falsy légitimes if (req.body.title !== undefined) cleanData.title = req.body.title; if (req.body.description !== undefined) cleanData.description = req.body.description; @@ -3052,7 +3062,7 @@ export async function registerRoutes(app: Express): Promise { dueDate: req.body.dueDate, } : null }); - res.status(500).json({ + res.status(500).json({ message: "Failed to update task", details: error instanceof Error ? error.message : String(error) }); @@ -3068,7 +3078,7 @@ export async function registerRoutes(app: Express): Promise { const id = parseInt(req.params.id); const task = await storage.getTask(id); - + if (!task) { return res.status(404).json({ message: "Task not found" }); } @@ -3098,7 +3108,7 @@ export async function registerRoutes(app: Express): Promise { const id = parseInt(req.params.id); const task = await storage.getTask(id); - + if (!task) { return res.status(404).json({ message: "Task not found" }); } @@ -3129,7 +3139,7 @@ export async function registerRoutes(app: Express): Promise { const { storeId } = req.query; let groupIds: number[] | undefined; - + if (user.role === 'admin') { groupIds = storeId ? [parseInt(storeId as string)] : undefined; } else { @@ -3181,17 +3191,17 @@ export async function registerRoutes(app: Express): Promise { const userGroupIds = user.userGroups ? user.userGroups.map(ug => ug.groupId) : []; console.log('🔍 CUSTOMER ORDER - User group IDs:', userGroupIds); console.log('🔍 CUSTOMER ORDER - Requested group ID:', data.groupId); - + // Convert data.groupId to number if it's a string const requestedGroupId = typeof data.groupId === 'string' ? parseInt(data.groupId) : data.groupId; console.log('🔍 CUSTOMER ORDER - Converted group ID:', requestedGroupId); - + // Allow managers, directeurs, and employees to create orders in their assigned groups if (!['manager', 'directeur', 'employee'].includes(user.role)) { console.log('❌ CUSTOMER ORDER - Access denied: Invalid role for customer orders'); return res.status(403).json({ message: "Insufficient permissions to create customer orders" }); } - + if (!userGroupIds.includes(requestedGroupId)) { console.log('❌ CUSTOMER ORDER - Access denied: User not in requested group'); console.log('🔍 Available groups:', userGroupIds, 'Requested:', requestedGroupId); @@ -3199,7 +3209,7 @@ export async function registerRoutes(app: Express): Promise { console.log('🔍 Type check - requestedGroupId type:', typeof requestedGroupId); return res.status(403).json({ message: "Access denied to this group" }); } - + console.log('✅ CUSTOMER ORDER - Permission granted for user role:', user.role); } @@ -3220,7 +3230,7 @@ export async function registerRoutes(app: Express): Promise { const id = parseInt(req.params.id); const customerOrder = await storage.getCustomerOrder(id); - + if (!customerOrder) { return res.status(404).json({ message: "Customer order not found" }); } @@ -3250,7 +3260,7 @@ export async function registerRoutes(app: Express): Promise { const id = parseInt(req.params.id); const customerOrder = await storage.getCustomerOrder(id); - + if (!customerOrder) { return res.status(404).json({ message: "Customer order not found" }); } @@ -3299,7 +3309,7 @@ export async function registerRoutes(app: Express): Promise { } const pendingCalls = await storage.getPendingClientCalls(groupIds); - + console.log(`📞 Pending client calls fetched: ${pendingCalls.length} calls for user ${user.role}`); res.json(pendingCalls); } catch (error) { @@ -3336,7 +3346,7 @@ export async function registerRoutes(app: Express): Promise { } const updatedOrder = await storage.markClientCalled(id, user.id); - + console.log(`📞 Client marked as called: order ${id} by user ${user.id} (${user.role})`); res.json(updatedOrder); } catch (error) { @@ -3355,7 +3365,7 @@ export async function registerRoutes(app: Express): Promise { const { storeId } = req.query; let groupIds: number[] | undefined; - + if (user.role === 'admin' || user.role === 'directeur') { groupIds = storeId ? [parseInt(storeId as string)] : undefined; } else { @@ -3389,7 +3399,7 @@ export async function registerRoutes(app: Express): Promise { const id = parseInt(req.params.id); const avoir = await storage.getAvoir(id); - + if (!avoir) { return res.status(404).json({ message: "Avoir not found" }); } @@ -3438,7 +3448,7 @@ export async function registerRoutes(app: Express): Promise { const avoir = await storage.createAvoir(validatedData); console.log('✅ Avoir created:', avoir.id, 'by user:', user.id); - + // Send webhook after avoir creation try { const group = await storage.getGroup(avoir.groupId); @@ -3455,7 +3465,7 @@ export async function registerRoutes(app: Express): Promise { createdBy: user.firstName && user.lastName ? `${user.firstName} ${user.lastName}` : user.username, createdAt: avoir.createdAt }; - + const webhookResponse = await fetch(group.webhookUrl, { method: 'POST', headers: { @@ -3463,7 +3473,7 @@ export async function registerRoutes(app: Express): Promise { }, body: JSON.stringify(webhookData) }); - + if (webhookResponse.ok) { await storage.updateAvoirWebhookStatus(avoir.id, true); console.log('✅ Avoir webhook sent successfully:', avoir.id); @@ -3474,7 +3484,7 @@ export async function registerRoutes(app: Express): Promise { } catch (webhookError) { console.error('❌ Error sending avoir webhook:', webhookError); } - + res.json(avoir); } catch (error) { if (error instanceof z.ZodError) { @@ -3495,7 +3505,7 @@ export async function registerRoutes(app: Express): Promise { const id = parseInt(req.params.id); const existingAvoir = await storage.getAvoir(id); - + if (!existingAvoir) { return res.status(404).json({ message: "Avoir not found" }); } @@ -3524,14 +3534,14 @@ export async function registerRoutes(app: Express): Promise { const updatedAvoir = await storage.updateAvoir(id, dataForDb); console.log('✅ Avoir updated:', id, 'by user:', user.id); - + // 🎯 WEBHOOK QUAND STATUT PASSE À "Reçu" if (validatedData.status === 'Reçu' && existingAvoir.status !== 'Reçu') { try { // Utiliser groupe par défaut (1) pour admin si pas de groupe sélectionné const groupId = updatedAvoir.groupId || (user.role === 'admin' ? 1 : updatedAvoir.groupId); const group = await storage.getGroup(groupId); - + if (group && group.webhookUrl) { const webhookData = { type: "Avoir", @@ -3546,9 +3556,9 @@ export async function registerRoutes(app: Express): Promise { createdBy: user.firstName && user.lastName ? `${user.firstName} ${user.lastName}` : user.username, processedAt: new Date().toISOString() }; - + console.log('🌐 Envoi webhook avoir reçu:', { groupId, webhookUrl: group.webhookUrl }); - + const webhookResponse = await fetch(group.webhookUrl, { method: 'POST', headers: { @@ -3556,7 +3566,7 @@ export async function registerRoutes(app: Express): Promise { }, body: JSON.stringify(webhookData) }); - + if (webhookResponse.ok) { await storage.updateAvoirWebhookStatus(updatedAvoir.id, true); console.log('✅ Webhook avoir reçu envoyé:', updatedAvoir.id); @@ -3568,7 +3578,7 @@ export async function registerRoutes(app: Express): Promise { console.error('❌ Erreur webhook avoir reçu:', webhookError); } } - + res.json(updatedAvoir); } catch (error) { if (error instanceof z.ZodError) { @@ -3589,7 +3599,7 @@ export async function registerRoutes(app: Express): Promise { const id = parseInt(req.params.id); const avoir = await storage.getAvoir(id); - + if (!avoir) { return res.status(404).json({ message: "Avoir not found" }); } @@ -3626,7 +3636,7 @@ export async function registerRoutes(app: Express): Promise { const avoirId = parseInt(req.params.id); const avoir = await storage.getAvoir(avoirId); - + if (!avoir) { return res.status(404).json({ message: "Avoir not found" }); } @@ -3652,7 +3662,7 @@ export async function registerRoutes(app: Express): Promise { } const { invoiceReference, forceRefresh } = req.body; - + if (!avoir.supplier || !avoir.group) { console.log('❌ Avoir manque informations:', { avoirId, @@ -3687,7 +3697,7 @@ export async function registerRoutes(app: Express): Promise { res.json(result); } catch (error) { console.error("Error verifying avoir invoice:", error); - res.status(500).json({ + res.status(500).json({ message: "Failed to verify avoir invoice", error: error instanceof Error ? error.message : 'Unknown error' }); @@ -3704,7 +3714,7 @@ export async function registerRoutes(app: Express): Promise { const id = parseInt(req.params.id); const { webhookSent } = req.body; - + // Only allow admin and directeur to update webhook status if (user.role !== 'admin' && user.role !== 'directeur') { return res.status(403).json({ message: "Insufficient permissions" }); @@ -3727,7 +3737,7 @@ export async function registerRoutes(app: Express): Promise { const id = parseInt(req.params.id); const { verified } = req.body; - + // Only allow admin and directeur to update verification status if (user.role !== 'admin' && user.role !== 'directeur') { return res.status(403).json({ message: "Insufficient permissions" }); @@ -3741,7 +3751,7 @@ export async function registerRoutes(app: Express): Promise { const avoir = await storage.getAvoir(id); if (avoir?.invoiceReference?.trim()) { await invoiceVerificationService.updateCacheAsReconciled( - avoir.invoiceReference, + avoir.invoiceReference, avoir.groupId ); console.log('✅ Cache marqué comme réconcilié pour avoir:', id); @@ -3769,7 +3779,7 @@ export async function registerRoutes(app: Express): Promise { } const { invoiceReference, groupId } = req.body; - + if (!invoiceReference || !groupId) { return res.status(400).json({ message: "Invoice reference and group ID required" }); } @@ -3781,7 +3791,7 @@ export async function registerRoutes(app: Express): Promise { await invoiceVerificationService.updateCacheAsReconciled(invoiceReference, groupId); console.log('✅ Cache marqué comme réconcilié:', { invoiceReference, groupId, user: user.id }); - + res.json({ message: "Cache marked as reconciled successfully" }); } catch (error) { console.error("Error marking cache as reconciled:", error); @@ -3802,14 +3812,14 @@ export async function registerRoutes(app: Express): Promise { const currentMonth = month ? parseInt(month as string) : new Date().getMonth() + 1; let groupIds: number[] | undefined; - + if (user.role === 'admin') { // Admin can view all stores or filter by selected store groupIds = storeId ? [parseInt(storeId as string)] : undefined; } else { // Non-admin users: filter by their assigned groups const userGroupIds = user.userGroups.map(ug => ug.groupId); - + // If a specific store is selected and user has access, filter by it if (storeId && userGroupIds.includes(parseInt(storeId as string))) { groupIds = [parseInt(storeId as string)]; @@ -3838,14 +3848,14 @@ export async function registerRoutes(app: Express): Promise { const currentYear = year ? parseInt(year as string) : new Date().getFullYear(); let groupIds: number[] | undefined; - + if (user.role === 'admin') { // Admin can view all stores or filter by selected store groupIds = storeId ? [parseInt(storeId as string)] : undefined; } else { // Non-admin users: filter by their assigned groups const userGroupIds = user.userGroups.map(ug => ug.groupId); - + // If a specific store is selected and user has access, filter by it if (storeId && userGroupIds.includes(parseInt(storeId as string))) { groupIds = [parseInt(storeId as string)]; @@ -3912,7 +3922,7 @@ export async function registerRoutes(app: Express): Promise { // Get all basic users first const baseUsers = await storage.getUsers(); - + // Add userGroups and userRoles to each user individually with error handling const usersWithData = await Promise.all( baseUsers.map(async (baseUser) => { @@ -3934,7 +3944,7 @@ export async function registerRoutes(app: Express): Promise { } }) ); - + res.json(usersWithData); } catch (error) { console.error("❌ Critical error fetching users:", error); @@ -3964,7 +3974,7 @@ export async function registerRoutes(app: Express): Promise { }); const userData = createUserSchema.parse(req.body); - + // Hash password with improved error handling let hashedPassword = userData.password; if (userData.password) { @@ -3975,10 +3985,10 @@ export async function registerRoutes(app: Express): Promise { return res.status(500).json({ message: "Failed to secure password" }); } } - + // Generate unique ID const newUserId = userData.id || `manual_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; - + const userToCreate = { id: newUserId, username: userData.username, @@ -3988,7 +3998,7 @@ export async function registerRoutes(app: Express): Promise { password: hashedPassword, role: userData.role || 'employee', }; - + const newUser = await storage.createUser(userToCreate); res.json(newUser); @@ -3998,31 +4008,31 @@ export async function registerRoutes(app: Express): Promise { console.error("❌ Error code:", (error as any).code); console.error("❌ Error constraint:", (error as any).constraint); console.error("❌ Error stack:", (error as any).stack); - + if (error instanceof z.ZodError) { console.log('❌ Validation error:', error.errors); return res.status(400).json({ message: "Invalid user data", errors: error.errors }); } - + // Handle specific database constraint errors if ((error as any).code === '23505') { if ((error as any).constraint === 'users_username_key') { - return res.status(409).json({ - message: "Un utilisateur avec ce nom d'utilisateur existe déjà. Veuillez choisir un autre nom d'utilisateur." + return res.status(409).json({ + message: "Un utilisateur avec ce nom d'utilisateur existe déjà. Veuillez choisir un autre nom d'utilisateur." }); } if ((error as any).constraint === 'users_email_key') { - return res.status(409).json({ - message: "Un utilisateur avec cette adresse email existe déjà." + return res.status(409).json({ + message: "Un utilisateur avec cette adresse email existe déjà." }); } } - + // Handle connection errors if ((error as any).code === 'ECONNREFUSED' || (error as any).code === 'ENOTFOUND') { return res.status(503).json({ message: "Database connection error" }); } - + res.status(500).json({ message: "Failed to create user" }); } }); @@ -4045,15 +4055,15 @@ export async function registerRoutes(app: Express): Promise { }); const userData = updateUserSchema.parse(req.body); - + // Clean up the data - handle empty emails properly const cleanUserData: any = { ...userData }; - + // Handle email field - convert empty string to undefined if (cleanUserData.email !== undefined) { cleanUserData.email = cleanUserData.email && cleanUserData.email.trim() !== '' ? cleanUserData.email : undefined; } - + // Hash password if provided if (cleanUserData.password) { try { @@ -4064,7 +4074,7 @@ export async function registerRoutes(app: Express): Promise { return res.status(500).json({ message: "Failed to secure password" }); } } - + const updatedUser = await storage.updateUser(req.params.id, cleanUserData); res.json(updatedUser); } catch (error: any) { @@ -4073,25 +4083,25 @@ export async function registerRoutes(app: Express): Promise { console.error("❌ Error code:", (error as any).code); console.error("❌ Error constraint:", (error as any).constraint); console.error("❌ Error stack:", (error as any).stack); - + if (error instanceof z.ZodError) { return res.status(400).json({ message: "Invalid user data", errors: error.errors }); } - + // Handle specific database constraint errors if ((error as any).code === '23505') { if ((error as any).constraint === 'users_username_key') { - return res.status(409).json({ - message: "Un utilisateur avec ce nom d'utilisateur existe déjà. Veuillez choisir un autre nom d'utilisateur." + return res.status(409).json({ + message: "Un utilisateur avec ce nom d'utilisateur existe déjà. Veuillez choisir un autre nom d'utilisateur." }); } if ((error as any).constraint === 'users_email_key') { - return res.status(409).json({ - message: "Un utilisateur avec cette adresse email existe déjà. Veuillez utiliser une autre adresse email ou laisser le champ vide." + return res.status(409).json({ + message: "Un utilisateur avec cette adresse email existe déjà. Veuillez utiliser une autre adresse email ou laisser le champ vide." }); } } - + res.status(500).json({ message: "Failed to update user" }); } }); @@ -4140,7 +4150,7 @@ export async function registerRoutes(app: Express): Promise { } const userToDelete = req.params.id; - + // Prevent admin from deleting themselves if (userToDelete === user.id) { return res.status(400).json({ message: "Vous ne pouvez pas supprimer votre propre compte" }); @@ -4163,8 +4173,8 @@ export async function registerRoutes(app: Express): Promise { } }); - // Publicity routes (renamed to campaigns to avoid adblocker issues) - app.get('/api/campaigns', isAuthenticated, async (req: any, res) => { + // Publicity routes (renamed to ad-campaigns to avoid adblocker issues) + app.get('/api/ad-campaigns/debug', isAuthenticated, async (req: any, res) => { try { const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id); if (!user) { @@ -4175,10 +4185,10 @@ export async function registerRoutes(app: Express): Promise { const filterYear = year ? parseInt(year as string) : undefined; // DEBUG: Log pour identifier le problème avec 2025 - console.log(`📋 API PUBLICITIES REQUEST:`, { year, filterYear, storeId, userRole: user.role }); + console.log(`📋 API AD-CAMPAIGNS DEBUG REQUEST:`, { year, filterYear, storeId, userRole: user.role }); let groupIds: number[] | undefined; - + if (user.role === 'admin') { // Admin can view all publicities or filter by selected store groupIds = storeId ? [parseInt(storeId as string)] : undefined; @@ -4190,7 +4200,7 @@ export async function registerRoutes(app: Express): Promise { console.log(`📋 CALLING storage.getPublicities:`, { filterYear, groupIds }); const publicities = await storage.getPublicities(filterYear, groupIds); console.log(`📋 PUBLICITIES RETURNED:`, { count: publicities.length }); - + res.json(publicities); } catch (error) { console.error("❌ ERROR fetching publicities:", error); @@ -4198,7 +4208,7 @@ export async function registerRoutes(app: Express): Promise { } }); - app.get('/api/campaigns/:id', isAuthenticated, async (req: any, res) => { + app.get('/api/ad-campaigns/:id', isAuthenticated, async (req: any, res) => { try { const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id); if (!user) { @@ -4207,7 +4217,7 @@ export async function registerRoutes(app: Express): Promise { const id = parseInt(req.params.id); const publicity = await storage.getPublicity(id); - + if (!publicity) { return res.status(404).json({ message: "Publicity not found" }); } @@ -4216,7 +4226,7 @@ export async function registerRoutes(app: Express): Promise { if (user.role !== 'admin') { const userGroupIds = user.userGroups.map(ug => ug.groupId); const hasAccess = publicity.participations.some((p: any) => userGroupIds.includes(p.groupId)); - + if (!hasAccess) { return res.status(403).json({ message: "Access denied" }); } @@ -4230,7 +4240,7 @@ export async function registerRoutes(app: Express): Promise { }); // Get all publicities (with optional year and store filtering) - app.get('/api/publicities', isAuthenticated, async (req: any, res) => { + app.get('/api/ad-campaigns', isAuthenticated, async (req: any, res) => { try { const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id); if (!user) { @@ -4239,7 +4249,7 @@ export async function registerRoutes(app: Express): Promise { const { year, storeId } = req.query; let groupIds: number[] | undefined; - + // Determine which groups to filter by if (user.role === 'admin') { // Admins can filter by specific store or see all @@ -4256,7 +4266,7 @@ export async function registerRoutes(app: Express): Promise { const yearNum = year ? parseInt(year as string) : undefined; const publicities = await storage.getPublicities(yearNum, groupIds); - + res.json(publicities); } catch (error) { console.error("Error fetching publicities:", error); @@ -4264,7 +4274,7 @@ export async function registerRoutes(app: Express): Promise { } }); - app.post('/api/publicities', isAuthenticated, async (req: any, res) => { + app.post('/api/ad-campaigns', isAuthenticated, async (req: any, res) => { try { const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id); if (!user) { @@ -4300,7 +4310,7 @@ export async function registerRoutes(app: Express): Promise { } }); - app.put('/api/publicities/:id', isAuthenticated, async (req: any, res) => { + app.put('/api/ad-campaigns/:id', isAuthenticated, async (req: any, res) => { try { const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id); if (!user) { @@ -4332,11 +4342,11 @@ export async function registerRoutes(app: Express): Promise { } }); - app.delete('/api/publicities/:id', isAuthenticated, async (req: any, res) => { + app.delete('/api/ad-campaigns/:id', isAuthenticated, async (req: any, res) => { const publicityId = req.params.id; - console.log(`🗑️ [API] DELETE request received for publicity ID: ${publicityId}`); - console.log(`🗑️ [API] User info:`, { - hasUser: !!req.user, + console.log(`🗑️ [API] DELETE request received for publicity ID: ${publicityId} (ad-campaigns)`); + console.log(`🗑️ [API] User info:`, { + hasUser: !!req.user, userId: req.user?.id || req.user?.claims?.sub, method: req.method, url: req.url, @@ -4360,9 +4370,9 @@ export async function registerRoutes(app: Express): Promise { const id = parseInt(publicityId); console.log(`🗑️ [API] Admin ${user.name} (${user.id}) attempting to delete publicity ${id}`); - + await storage.deletePublicity(id); - + console.log(`✅ [API] Successfully deleted publicity ${id} by admin ${user.name}`); res.json({ message: "Publicity deleted successfully" }); } catch (error) { @@ -4372,11 +4382,11 @@ export async function registerRoutes(app: Express): Promise { }); // Alternative DELETE route using POST (for production environments that block DELETE) - app.post('/api/publicities/:id/delete', isAuthenticated, async (req: any, res) => { + app.post('/api/ad-campaigns/:id/delete', isAuthenticated, async (req: any, res) => { const publicityId = req.params.id; - console.log(`🗑️ [API-POST] DELETE via POST request received for publicity ID: ${publicityId}`); - console.log(`🗑️ [API-POST] User info:`, { - hasUser: !!req.user, + console.log(`🗑️ [API-POST] DELETE via POST request received for publicity ID: ${publicityId} (ad-campaigns)`); + console.log(`🗑️ [API-POST] User info:`, { + hasUser: !!req.user, userId: req.user?.id || req.user?.claims?.sub, method: req.method, url: req.url @@ -4399,9 +4409,9 @@ export async function registerRoutes(app: Express): Promise { const id = parseInt(publicityId); console.log(`🗑️ [API-POST] Admin ${user.name} (${user.id}) attempting to delete publicity ${id} via POST`); - + await storage.deletePublicity(id); - + console.log(`✅ [API-POST] Successfully deleted publicity ${id} by admin ${user.name} via POST`); res.json({ message: "Publicity deleted successfully" }); } catch (error) { @@ -4419,7 +4429,7 @@ export async function registerRoutes(app: Express): Promise { } const isProduction = process.env.NODE_ENV === 'production'; - + if (!isProduction) { return res.status(400).json({ error: 'Cette route fonctionne uniquement en production sur votre serveur privé' }); } @@ -4439,19 +4449,19 @@ export async function registerRoutes(app: Express): Promise { WHERE table_schema = 'public' ORDER BY table_name `; - + const tablesResult = await pool.query(tablesQuery); - + console.log(`\n📊 TOTAL DES TABLES TROUVÉES: ${tablesResult.rows.length}`); console.log('=========================================='); - + // Pour chaque table, récupérer les colonnes for (const tableRow of tablesResult.rows) { const tableName = tableRow.table_name; - + console.log(`\n🔸 TABLE: ${tableName.toUpperCase()}`); console.log(`${'='.repeat(tableName.length + 8)}`); - + const columnsQuery = ` SELECT column_name, @@ -4465,18 +4475,18 @@ export async function registerRoutes(app: Express): Promise { AND table_name = $1 ORDER BY ordinal_position `; - + const columnsResult = await pool.query(columnsQuery, [tableName]); - + if (columnsResult.rows.length > 0) { columnsResult.rows.forEach((col: any, index: number) => { const nullable = col.is_nullable === 'YES' ? 'NULL' : 'NOT NULL'; const maxLength = col.character_maximum_length ? `(${col.character_maximum_length})` : ''; const defaultVal = col.column_default ? ` DEFAULT ${col.column_default}` : ''; - + console.log(` ${(index + 1).toString().padStart(2, '0')}. ${col.column_name.padEnd(25)} : ${col.data_type}${maxLength} ${nullable}${defaultVal}`); }); - + // Compter les enregistrements dans la table try { const countQuery = `SELECT COUNT(*) as total FROM "${tableName}"`; @@ -4487,11 +4497,11 @@ export async function registerRoutes(app: Express): Promise { } } } - + // Récupérer les contraintes de clés étrangères console.log('\n🔗 CONTRAINTES DE CLÉS ÉTRANGÈRES:'); console.log('==================================='); - + const fkQuery = ` SELECT tc.table_name, @@ -4510,9 +4520,9 @@ export async function registerRoutes(app: Express): Promise { AND tc.table_schema = 'public' ORDER BY tc.table_name, kcu.column_name `; - + const fkResult = await pool.query(fkQuery); - + if (fkResult.rows.length > 0) { fkResult.rows.forEach((fk: any) => { console.log(` ${fk.table_name}.${fk.column_name} → ${fk.foreign_table_name}.${fk.foreign_column_name}`); @@ -4522,16 +4532,16 @@ export async function registerRoutes(app: Express): Promise { } console.log('\n🏁 ===== FIN SCAN SCHÉMA BASE DE DONNÉES =====\n'); - - res.json({ - success: true, + + res.json({ + success: true, message: 'Schéma loggé avec succès dans les logs du serveur', totalTables: tablesResult.rows.length, totalForeignKeys: fkResult.rows.length, timestamp: new Date().toISOString(), downloadUrl: '/api/debug/download-schema' }); - + } catch (error) { console.error('❌ ERREUR lors du scan du schéma:', error); res.status(500).json({ error: (error as Error).message }); @@ -4547,7 +4557,7 @@ export async function registerRoutes(app: Express): Promise { } const isProduction = process.env.NODE_ENV === 'production'; - + if (!isProduction) { return res.status(400).json({ error: 'Cette route fonctionne uniquement en production sur votre serveur privé' }); } @@ -4579,19 +4589,19 @@ RÉSUMÉ DU SCAN WHERE table_schema = 'public' ORDER BY table_name `; - + const tablesResult = await pool.query(tablesQuery); - + reportContent += `TOTAL DES TABLES TROUVÉES: ${tablesResult.rows.length}\n`; reportContent += `==========================================\n\n`; - + // Pour chaque table, récupérer les colonnes for (const tableRow of tablesResult.rows) { const tableName = tableRow.table_name; - + reportContent += `TABLE: ${tableName.toUpperCase()}\n`; reportContent += `${'='.repeat(tableName.length + 6)}\n\n`; - + const columnsQuery = ` SELECT column_name, @@ -4605,18 +4615,18 @@ RÉSUMÉ DU SCAN AND table_name = $1 ORDER BY ordinal_position `; - + const columnsResult = await pool.query(columnsQuery, [tableName]); - + if (columnsResult.rows.length > 0) { columnsResult.rows.forEach((col: any, index: number) => { const nullable = col.is_nullable === 'YES' ? 'NULL' : 'NOT NULL'; const maxLength = col.character_maximum_length ? `(${col.character_maximum_length})` : ''; const defaultVal = col.column_default ? ` DEFAULT ${col.column_default}` : ''; - + reportContent += ` ${(index + 1).toString().padStart(2, '0')}. ${col.column_name.padEnd(25)} : ${col.data_type}${maxLength} ${nullable}${defaultVal}\n`; }); - + // Compter les enregistrements dans la table try { const countQuery = `SELECT COUNT(*) as total FROM "${tableName}"`; @@ -4627,11 +4637,11 @@ RÉSUMÉ DU SCAN } } } - + // Récupérer les contraintes de clés étrangères reportContent += `CONTRAINTES DE CLÉS ÉTRANGÈRES:\n`; reportContent += `===================================\n\n`; - + const fkQuery = ` SELECT tc.table_name, @@ -4650,9 +4660,9 @@ RÉSUMÉ DU SCAN AND tc.table_schema = 'public' ORDER BY tc.table_name, kcu.column_name `; - + const fkResult = await pool.query(fkQuery); - + if (fkResult.rows.length > 0) { fkResult.rows.forEach((fk: any) => { reportContent += ` ${fk.table_name}.${fk.column_name} → ${fk.foreign_table_name}.${fk.foreign_column_name}\n`; @@ -4670,7 +4680,7 @@ RÉSUMÉ DU SCAN res.setHeader('Content-Type', 'text/plain; charset=utf-8'); res.setHeader('Content-Disposition', `attachment; filename="${filename}"`); res.send(reportContent); - + } catch (error) { console.error('❌ ERREUR lors de la génération du rapport de schéma:', error); res.status(500).json({ error: (error as Error).message }); @@ -4681,7 +4691,7 @@ RÉSUMÉ DU SCAN app.post('/api/verify-invoice', isAuthenticated, async (req: any, res) => { try { const { groupId, invoiceReference } = req.body; - + if (!groupId || !invoiceReference) { return res.status(400).json({ message: "groupId and invoiceReference are required" }); } @@ -4691,7 +4701,7 @@ RÉSUMÉ DU SCAN const { InvoiceVerificationService } = await import('./invoiceVerification.js'); const verificationService = new InvoiceVerificationService(); const result = await verificationService.verifyInvoice(invoiceReference, groupId); - + console.log('✅ Résultat vérification:', result); res.json(result); } catch (error) { @@ -4703,7 +4713,7 @@ RÉSUMÉ DU SCAN app.post('/api/verify-invoices', isAuthenticated, async (req: any, res) => { try { const { invoiceReferences } = req.body; - + if (!Array.isArray(invoiceReferences)) { return res.status(400).json({ message: "invoiceReferences must be an array" }); } @@ -4716,7 +4726,7 @@ RÉSUMÉ DU SCAN const { verifyMultipleInvoiceReferences } = await import('./nocodbService.js'); const results = await verifyMultipleInvoiceReferences(enrichedReferences); - + res.json(results); } catch (error) { console.error("Error verifying invoices:", error); @@ -4847,7 +4857,7 @@ RÉSUMÉ DU SCAN const { filename } = req.params; const filepath = await backupService.downloadBackup(filename); - + res.download(filepath, filename, (err: any) => { if (err) { console.error("Error downloading backup:", err); @@ -4933,7 +4943,7 @@ RÉSUMÉ DU SCAN const ticketId = parseInt(req.params.id); const ticket = await storage.getSavTicket(ticketId); - + if (!ticket) { return res.status(404).json({ message: "Ticket not found" }); } @@ -4990,7 +5000,7 @@ RÉSUMÉ DU SCAN // Parse and validate request body const assignedGroupId = req.body.groupId || availableGroupIds[0]; console.log(`🎫 [SAV] Creating ticket for user ${user.username} with groupId: ${assignedGroupId} (requested: ${req.body.groupId}, available: ${availableGroupIds})`); - + const ticketData = insertSavTicketSchema.parse({ ...req.body, groupId: assignedGroupId, // Use first available group if not specified @@ -5040,7 +5050,7 @@ RÉSUMÉ DU SCAN const ticketId = parseInt(req.params.id); const existingTicket = await storage.getSavTicket(ticketId); - + if (!existingTicket) { return res.status(404).json({ message: "Ticket not found" }); } @@ -5093,7 +5103,7 @@ RÉSUMÉ DU SCAN const ticketId = parseInt(req.params.id); const existingTicket = await storage.getSavTicket(ticketId); - + if (!existingTicket) { return res.status(404).json({ message: "Ticket not found" }); } @@ -5121,7 +5131,7 @@ RÉSUMÉ DU SCAN const ticketId = parseInt(req.params.id); const ticket = await storage.getSavTicket(ticketId); - + if (!ticket) { return res.status(404).json({ message: "Ticket not found" }); } @@ -5199,25 +5209,25 @@ RÉSUMÉ DU SCAN app.post('/api/admin/emergency-migration', async (req, res) => { try { console.log('🚨 EMERGENCY: Forcing SAV migration execution...'); - + // Import migration function const { runProductionMigrations } = await import('./migrations.production.js'); - + // Force run the migration await runProductionMigrations(); - + console.log('✅ EMERGENCY: Migration executed successfully'); - res.json({ - success: true, + res.json({ + success: true, message: 'Emergency migration executed successfully', timestamp: new Date().toISOString() }); - + } catch (error) { console.error('❌ EMERGENCY: Migration failed:', error); - res.status(500).json({ + res.status(500).json({ success: false, - error: 'Emergency migration failed', + error: 'Emergency migration failed', details: (error as Error).message, timestamp: new Date().toISOString() }); @@ -5287,7 +5297,7 @@ RÉSUMÉ DU SCAN } const { apiKey, location } = req.body; - + if (!apiKey || !location) { return res.status(400).json({ message: "API key and location are required" }); } @@ -5303,14 +5313,14 @@ RÉSUMÉ DU SCAN app.get('/api/weather/current', isAuthenticated, async (req: any, res) => { try { const settings = await storage.getWeatherSettings(); - + if (!settings || !settings.isActive) { return res.status(404).json({ message: "Weather service not configured or disabled" }); } const today = new Date().toISOString().split('T')[0]; const previousYearDate = weatherService.getPreviousYearDate(); - + // Check if we already have today's data let currentYearData = await storage.getWeatherData(today, true); let previousYearData = await storage.getWeatherData(previousYearDate, false); @@ -5393,7 +5403,7 @@ RÉSUMÉ DU SCAN try { const userId = req.user.claims ? req.user.claims.sub : req.user.id; console.log('📢 [SERVER] Fetching announcements for user:', userId, 'environment:', environment); - + const user = await storage.getUserWithGroups(userId); if (!user) { return res.status(404).json({ message: "User not found" }); @@ -5404,7 +5414,7 @@ RÉSUMÉ DU SCAN if (environment === 'production') { // PRODUCTION: Utiliser PostgreSQL avec DASHBOARD_MESSAGES console.log('🎯 [PRODUCTION] Using PostgreSQL DASHBOARD_MESSAGES table'); - + try { let query = db.select({ id: dashboardMessages.id, @@ -5415,7 +5425,7 @@ RÉSUMÉ DU SCAN createdBy: dashboardMessages.createdBy, createdAt: dashboardMessages.createdAt, }).from(dashboardMessages); - + // Filtrage par magasin pour admin : inclure les annonces globales + annonces du magasin if (user.role === 'admin' && req.query.storeId) { const storeId = parseInt(req.query.storeId as string); @@ -5427,11 +5437,11 @@ RÉSUMÉ DU SCAN ) ); } - + const messages = await query.orderBy(desc(dashboardMessages.createdAt)).limit(5); - + console.log('🔍 [PRODUCTION] Raw messages from DB:', messages.length, 'items:', messages); - + // Ajouter les relations manuellement const announcements = await Promise.all( messages.map(async (message: any) => { @@ -5450,10 +5460,10 @@ RÉSUMÉ DU SCAN console.log('🔍 [PRODUCTION] User search result:', userResult ? 'Found' : 'Not found'); if (userResult) { // Utiliser name s'il existe, sinon firstName + lastName, sinon username - const displayName = userResult.name || + const displayName = userResult.name || (userResult.firstName && userResult.lastName ? `${userResult.firstName} ${userResult.lastName}` : '') || userResult.username; - + author = { id: userResult.id, firstName: userResult.firstName || displayName.split(' ')[0] || userResult.username, @@ -5466,7 +5476,7 @@ RÉSUMÉ DU SCAN } catch (e) { console.warn('❌ [PRODUCTION] Could not fetch author for message:', message.id, e); } - + // Récupérer le groupe si storeId est défini let group = null; if (message.storeId) { @@ -5479,7 +5489,7 @@ RÉSUMÉ DU SCAN console.warn('Could not fetch group for message:', message.id); } } - + return { ...message, author, @@ -5487,10 +5497,10 @@ RÉSUMÉ DU SCAN }; }) ); - + console.log('🎯 [PRODUCTION] Found announcements:', announcements.length); res.json(announcements); - + } catch (dbError) { console.error('🎯 [PRODUCTION] Database error:', dbError); // Fallback au stockage mémoire en cas d'erreur DB @@ -5501,10 +5511,10 @@ RÉSUMÉ DU SCAN } else { // DÉVELOPPEMENT: Utiliser stockage mémoire console.log('🧠 [DEV] Using memory storage for announcements'); - const groupIds = user.role === 'admin' && req.query.storeId + const groupIds = user.role === 'admin' && req.query.storeId ? [parseInt(req.query.storeId as string)] : undefined; - + const announcements = await storage.getAnnouncements(groupIds); res.json(announcements); } @@ -5522,11 +5532,11 @@ RÉSUMÉ DU SCAN hasId: !!req.user.id, userId: req.user.claims ? req.user.claims.sub : req.user.id }); - + try { const userId = req.user.claims ? req.user.claims.sub : req.user.id; console.log('🎯 [SERVER] Extracted userId:', userId); - + const user = await storage.getUserWithGroups(userId); if (!user) { console.error('🎯 [SERVER] User not found for ID:', userId); @@ -5564,13 +5574,13 @@ RÉSUMÉ DU SCAN storeId: announcementData.storeId, createdBy: user.username, // Utiliser username plutôt que ID pour PostgreSQL }).returning(); - + const announcement = { ...newMessage, author: { id: user.id, firstName: user.firstName, lastName: user.lastName }, group: null }; - + console.log('🎯 [PRODUCTION] Announcement created successfully in DB:', announcement); res.status(201).json(announcement); } catch (dbError) { @@ -5598,11 +5608,11 @@ RÉSUMÉ DU SCAN app.put('/api/announcements/:id', isAuthenticated, async (req: any, res) => { console.log('📝 [SERVER] PUT /api/announcements/:id endpoint hit'); console.log('📝 [SERVER] Request body:', JSON.stringify(req.body, null, 2)); - + try { const userId = req.user.claims ? req.user.claims.sub : req.user.id; console.log('📝 [SERVER] Extracted userId:', userId); - + const user = await storage.getUserWithGroups(userId); if (!user) { console.error('📝 [SERVER] User not found for ID:', userId); @@ -5634,7 +5644,7 @@ RÉSUMÉ DU SCAN const updatedAnnouncement = await storage.updateAnnouncement(id, announcementData); console.log('📝 [SERVER] Announcement updated successfully:', updatedAnnouncement); - + res.json(updatedAnnouncement); } catch (error) { console.error('📝 [SERVER] Error updating announcement:', error); @@ -5649,11 +5659,11 @@ RÉSUMÉ DU SCAN app.delete('/api/announcements/:id', isAuthenticated, async (req: any, res) => { console.log('🗑️ [SERVER] DELETE /api/announcements/:id endpoint hit'); console.log('🗑️ [SERVER] Announcement ID:', req.params.id); - + try { const userId = req.user.claims ? req.user.claims.sub : req.user.id; console.log('🗑️ [SERVER] Extracted userId:', userId); - + const user = await storage.getUserWithGroups(userId); if (!user) { console.error('🗑️ [SERVER] User not found for ID:', userId); @@ -5676,34 +5686,34 @@ RÉSUMÉ DU SCAN console.log('🗑️ [PRODUCTION] Deleting from PostgreSQL DASHBOARD_MESSAGES table'); try { const deletedRows = await db.delete(dashboardMessages).where(eq(dashboardMessages.id, id)).returning(); - + if (deletedRows.length === 0) { console.error('🗑️ [PRODUCTION] Announcement not found:', id); return res.status(404).json({ message: "Announcement not found" }); } - + console.log('🗑️ [PRODUCTION] Announcement deleted successfully from DB:', deletedRows[0]); res.json({ message: "Announcement deleted successfully" }); } catch (dbError) { console.error('🗑️ [PRODUCTION] DB error, fallback to memory:', dbError); const success = await storage.deleteAnnouncement(id); - + if (!success) { return res.status(404).json({ message: "Announcement not found" }); } - + res.json({ message: "Announcement deleted successfully" }); } } else { // DÉVELOPPEMENT: Supprimer en mémoire console.log('🗑️ [DEV] Deleting from memory storage'); const success = await storage.deleteAnnouncement(id); - + if (!success) { console.error('🗑️ [DEV] Announcement not found:', id); return res.status(404).json({ message: "Announcement not found" }); } - + console.log('🗑️ [DEV] Announcement deleted from memory'); res.json({ message: "Announcement deleted successfully" }); } @@ -5784,7 +5794,7 @@ RÉSUMÉ DU SCAN } const { sql: sqlQuery } = req.body; - + if (!sqlQuery || typeof sqlQuery !== 'string') { return res.status(400).json({ error: 'SQL query requis' }); } @@ -5793,15 +5803,15 @@ RÉSUMÉ DU SCAN console.log('🔧 [SQL-EXECUTOR] Query:', sqlQuery.substring(0, 200) + '...'); const logs = [`🔄 Exécution SQL démarrée...`]; - + try { // Utilisation de drizzle-orm pour l'exécution const { sql } = await import('drizzle-orm'); const result = await db.execute(sql.raw(sqlQuery)); - + logs.push(`✅ SQL exécuté avec succès`); logs.push(`📊 Nombre de lignes affectées: ${result.rowCount || 0}`); - + if (result.rows && result.rows.length > 0) { logs.push(`📋 Nombre de lignes retournées: ${result.rows.length}`); if (result.rows.length <= 10) { @@ -5810,11 +5820,11 @@ RÉSUMÉ DU SCAN logs.push(`📋 Échantillon (10 premières lignes): ${JSON.stringify(result.rows.slice(0, 10), null, 2)}`); } } - + console.log('✅ [SQL-EXECUTOR] Exécution réussie'); - - res.json({ - success: true, + + res.json({ + success: true, logs, results: result.rows, rowCount: result.rowCount @@ -5823,10 +5833,10 @@ RÉSUMÉ DU SCAN } catch (sqlError: any) { console.error('❌ [SQL-EXECUTOR] Erreur SQL:', sqlError); logs.push(`❌ Erreur SQL: ${sqlError.message}`); - - return res.status(500).json({ + + return res.status(500).json({ error: `Erreur SQL: ${sqlError.message}`, - logs + logs }); } @@ -5856,7 +5866,7 @@ RÉSUMÉ DU SCAN // Apply role-based filtering if (user.role !== 'admin' && user.role !== 'directeur') { const userGroupIds = user.userGroups?.map((ug: any) => ug.groupId) || []; - filters.groupIds = filters.groupIds + filters.groupIds = filters.groupIds ? filters.groupIds.filter(id => userGroupIds.includes(id)) : userGroupIds; } @@ -5887,7 +5897,7 @@ RÉSUMÉ DU SCAN // Apply role-based filtering if (user.role !== 'admin' && user.role !== 'directeur') { const userGroupIds = user.userGroups?.map((ug: any) => ug.groupId) || []; - filters.groupIds = filters.groupIds + filters.groupIds = filters.groupIds ? filters.groupIds.filter(id => userGroupIds.includes(id)) : userGroupIds; } @@ -5916,7 +5926,7 @@ RÉSUMÉ DU SCAN // Apply role-based filtering if (user.role !== 'admin' && user.role !== 'directeur') { const userGroupIds = user.userGroups?.map((ug: any) => ug.groupId) || []; - filters.groupIds = filters.groupIds + filters.groupIds = filters.groupIds ? filters.groupIds.filter(id => userGroupIds.includes(id)) : userGroupIds; } @@ -5969,15 +5979,15 @@ RÉSUMÉ DU SCAN // Apply role-based filtering if (user.role !== 'admin' && user.role !== 'directeur') { const userGroupIds = user.userGroups?.map((ug: any) => ug.groupId) || []; - filters.groupIds = filters.groupIds + filters.groupIds = filters.groupIds ? filters.groupIds.filter(id => userGroupIds.includes(id)) : userGroupIds; } let data: any; let filename = `analytics_${type}_${new Date().toISOString().split('T')[0]}.csv`; - - switch(type) { + + switch (type) { case 'timeseries': data = await storage.getAnalyticsTimeseries({ ...filters, granularity: 'day' }); break; @@ -5994,14 +6004,14 @@ RÉSUMÉ DU SCAN // Convert to CSV if (data && data.length > 0) { const headers = Object.keys(data[0]).join(','); - const rows = data.map((item: any) => - Object.values(item).map((val: any) => + const rows = data.map((item: any) => + Object.values(item).map((val: any) => typeof val === 'object' ? JSON.stringify(val) : val ).join(',') ).join('\n'); - + const csv = `${headers}\n${rows}`; - + res.setHeader('Content-Type', 'text/csv'); res.setHeader('Content-Disposition', `attachment; filename="${filename}"`); res.send(csv); diff --git a/task.md b/task.md index c4d93d7..78f51b9 100644 --- a/task.md +++ b/task.md @@ -1,25 +1,38 @@ -# Task: Redirection authentification si déconnecté +# Task: Correction blocage API Publicités ## Context -L'utilisateur signale que lorsqu'il est déconnecté et navigue ou se trouve sur une page, il n'est pas systématiquement renvoyé vers la page d'authentification. Il souhaite une redirection immédiate. +L'utilisateur rencontre une erreur `net::ERR_BLOCKED_BY_CLIENT` lors de l'appel à l'API `/api/publicities`. Cette erreur est typique des bloqueurs de publicité (AdBlock, uBlock) qui filtrent les requêtes contenant des mots-clés comme "publicity" ou "ads". ## Current Focus -Vérification terminée. +Renommer les endpoints API pour éviter le blocage par les bloqueurs de publicité. ## Master Plan -- [x] Analyser le fichier principal de routing (ex: `App.tsx` ou `main.tsx`). -- [x] Examiner le hook d'authentification `useAuthUnified` et les composants de protection de route (ex: `ProtectedRoute`). -- [x] Identifier pourquoi la redirection ne se fait pas ou pas correctement. -- [x] Implémenter/Corriger la logique de redirection vers `/auth` (ou la page de login). -- [x] Vérifier le comportement. +- [x] **Remplacement des API calls (Client-side)** + - [x] `client/src/pages/Calendar.tsx` + - [x] `client/src/pages/Dashboard.tsx` + - [x] `client/src/components/PublicityForm.tsx` + - [x] `client/src/pages/Publicities.tsx` + - [x] `client/src/pages/mobile/PublicitiesPage.tsx` + +- [x] **Revue du sélecteur d'année Mobile** + - [x] Vérifier la logique de génération des années dans `client/src/pages/mobile/PublicitiesPage.tsx` pour inclure les années passées (ex: 2024 en 2026). + - [x] Aligner avec la correction desktop si nécessaire. + +- [x] **Correction des erreurs Lint TypeScript** + - [x] Installer les paquets `@types` manquants (`node`, `express`, `react`, etc.). + - [x] Fixer les paramètres `req` et `res` (implicitement `any`) dans `server/routes.ts`. + - [x] Corriger l'erreur de propriété `invoiceAmountTTC` dans `server/routes.ts`. + - [x] Résoudre `Property 'env' does not exist on type 'ImportMeta'`. + - [x] Résoudre `JSX element implicitly has type 'any'`. + +- [x] **Tests et Validation** + - [x] Vérifier que les appels API utilisent bien `/api/ad-campaigns`. + - [x] Tester le sélecteur d'année sur mobile. + - [x] Compiler le projet (`npm run check`) pour s'assurer que les erreurs critiques sont résolues. ## Progress Log -- Création de la tâche. -- Analyse de `App.tsx` et `useAuthUnified.ts`. -- Identification que `RouterProduction.tsx` renvoyait `NotFound` au lieu de rediriger. -- Modification de `RouterProduction.tsx` pour rediriger vers `/auth` toutes les routes inconnues si non authentifié. -- Correction d'une erreur de remplacement de code en réécrivant le fichier complet. +- Création de la tâche suite aux logs fournis. diff --git a/tsconfig.json b/tsconfig.json index 244ce53..097d1d1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,15 @@ { - "include": ["client/src/**/*", "shared/**/*", "server/**/*"], - "exclude": ["node_modules", "build", "dist", "**/*.test.ts"], + "include": [ + "client/src/**/*", + "shared/**/*", + "server/**/*" + ], + "exclude": [ + "node_modules", + "build", + "dist", + "**/*.test.ts" + ], "compilerOptions": { "incremental": true, "tsBuildInfoFile": "./node_modules/typescript/tsbuildinfo", @@ -8,7 +17,11 @@ "target": "ES2020", "module": "ES2022", "strict": true, - "lib": ["ES2020", "dom", "dom.iterable"], + "lib": [ + "ES2020", + "dom", + "dom.iterable" + ], "jsx": "preserve", "esModuleInterop": true, "skipLibCheck": true, @@ -16,10 +29,22 @@ "moduleResolution": "bundler", "downlevelIteration": true, "baseUrl": ".", - "types": ["node", "vite/client"], + "types": [ + "node", + "vite/client", + "express", + "react", + "react-dom", + "node-fetch", + "date-fns" + ], "paths": { - "@/*": ["./client/src/*"], - "@shared/*": ["./shared/*"] + "@/*": [ + "./client/src/*" + ], + "@shared/*": [ + "./shared/*" + ] } } -} +} \ No newline at end of file