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