diff --git a/client/src/components/CustomerOrderForm.tsx b/client/src/components/CustomerOrderForm.tsx index 0e96b68..310c738 100644 --- a/client/src/components/CustomerOrderForm.tsx +++ b/client/src/components/CustomerOrderForm.tsx @@ -3,6 +3,8 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { useQuery } from "@tanstack/react-query"; import { useAuthUnified } from "@/hooks/useAuthUnified"; +import { useState, useRef } from "react"; +import { Loader2, Search } from "lucide-react"; import { Form, FormControl, @@ -68,15 +70,6 @@ export function CustomerOrderForm({ const { user } = useAuthUnified(); const { selectedStoreId } = useStore(); - // DEBUG: Log user data to see what we actually receive - console.log("🔍 FRONTEND USER DEBUG:", { - user: user, - userRole: user?.role, - userGroups: user?.userGroups, - userGroupsType: typeof user?.userGroups, - userGroupsLength: user?.userGroups?.length, - fullUserObject: JSON.stringify(user, null, 2) - }); // Fetch groups for store selection const { data: groups = [] } = useQuery({ @@ -128,51 +121,18 @@ export function CustomerOrderForm({ }); const handleSubmit = (data: CustomerOrderFormData) => { - console.log("🚀 FORM SUBMIT STARTED"); - console.log("📝 Form submission data:", data); - console.log("🔍 Form errors:", form.formState.errors); - console.log("✅ Form is valid:", form.formState.isValid); - console.log("👤 User context DETAILED:", { - role: user?.role, - userGroups: user?.userGroups, - userGroupsLength: user?.userGroups?.length, - userGroupsData: user?.userGroups?.map(ug => ({ - groupId: ug.groupId, - group: ug.group, - fullObject: ug - })), - selectedStoreId, - fullUser: user - }); - - // Validate required fields - if (!data.customerName || data.customerName.trim() === '') { - console.error("❌ Customer name is required but empty"); - return; - } - - // Always use user's assigned group - no override needed + if (!data.customerName || data.customerName.trim() === '') return; + const groupId = getUserAssignedGroupId(); - if (!groupId) { - console.error("❌❌❌ CRITICAL: No group available for user:", { - role: user?.role, - userGroups: user?.userGroups, - userGroupsCount: user?.userGroups?.length, - selectedStoreId, - getUserAssignedGroupIdResult: groupId - }); alert("ERREUR: Votre utilisateur n'a pas de magasin assigné. Contactez l'administrateur."); return; } - - console.log("✅ Customer Order: Using assigned group:", groupId, "for user:", user?.role); - - // Prepare final data with user's assigned group + const submitData = { orderTaker: data.orderTaker || user?.firstName + ' ' + user?.lastName || user?.username || "Inconnu", customerName: data.customerName.trim(), - customerPhone: data.contactNumber || '', + customerPhone: data.contactNumber || '', customerEmail: data.customerEmail || '', productDesignation: data.productName || '', productReference: data.productReference || '', @@ -184,29 +144,75 @@ export function CustomerOrderForm({ isPromotionalPrice: data.isPromotionalPrice || false, customerNotified: data.customerNotified || false, notes: data.notes || '', - groupId: groupId, // Always use the assigned group + groupId: groupId, }; - - console.log("✅ Customer Order Submit FINAL:", { - userRole: user?.role, - assignedGroupId: groupId, - submitData: submitData - }); - - console.log("🔥 CALLING onSubmit with data:", submitData); onSubmit(submitData); - console.log("🔥 onSubmit CALLED"); }; // Auto-ensure groupId is always set to user's assigned group const currentGroupId = form.getValues('groupId'); const userGroupId = getUserAssignedGroupId(); - if (!currentGroupId && userGroupId) { form.setValue('groupId', userGroupId); - console.log("🏪 Auto-setting user's assigned group:", userGroupId); } + // Lookup API ffnancy par gencode ou référence (déclenché manuellement) + const [articleLookupLoading, setArticleLookupLoading] = useState(false); + const [articleNotFound, setArticleNotFound] = useState(false); + + const fetchAndFillArticle = async (params: URLSearchParams) => { + setArticleLookupLoading(true); + setArticleNotFound(false); + try { + const res = await fetch(`/api/ffnancy/articles?${params}&limit=1`, { credentials: 'include' }); + if (!res.ok) { setArticleNotFound(true); return; } + const data = await res.json(); + const article = data.articles?.[0]; + if (!article) { setArticleNotFound(true); return; } + + setArticleNotFound(false); + form.setValue("productName", article.libelle1, { shouldValidate: true }); + if (article.gtin) form.setValue("gencode", article.gtin, { shouldValidate: true }); + if (article.ref_fou_principale) form.setValue("productReference", article.ref_fou_principale, { shouldValidate: true }); + + // Fournisseur : dernière entrée en stock en priorité + let codefouToMatch = article.codefou_principal; + try { + const mvtRes = await fetch( + `/api/ffnancy/mouvements/entrees?artNoId=${article.no_id}&limit=1&dateDebut=2000-01-01`, + { credentials: 'include' } + ); + if (mvtRes.ok) { + const mvtData = await mvtRes.json(); + const lastEntree = mvtData.entrees?.[0]; + if (lastEntree?.codefou) codefouToMatch = lastEntree.codefou; + } + } catch { /* fallback codefou_principal */ } + + if (codefouToMatch) { + const matched = (suppliers as any[]).find((s: any) => + s.codefou && s.codefou.trim().toLowerCase() === codefouToMatch.trim().toLowerCase() + ) || (suppliers as any[]).find((s: any) => + s.name.toLowerCase().trim().includes(article.nom_fou_principal?.toLowerCase().trim() || '') || + (article.nom_fou_principal?.toLowerCase().trim() || '').includes(s.name.toLowerCase().trim()) + ); + if (matched) form.setValue("supplierId", matched.id, { shouldValidate: true }); + } + } catch { setArticleNotFound(true); } finally { + setArticleLookupLoading(false); + } + }; + + const handleSearchByGencode = () => { + const val = form.getValues("gencode"); + if (val) fetchAndFillArticle(new URLSearchParams({ ean: val })); + }; + + const handleSearchByReference = () => { + const val = form.getValues("productReference"); + if (val) fetchAndFillArticle(new URLSearchParams({ codein: val })); + }; + return (
@@ -310,7 +316,19 @@ export function CustomerOrderForm({ Référence (optionnel) - +
+ + +
@@ -324,7 +342,19 @@ export function CustomerOrderForm({ Gencode (obligatoire) - +
+ + +
@@ -403,26 +433,26 @@ export function CustomerOrderForm({ {/* Case "Client appelé" cachée pour nouvelles commandes */} + {/* Produit non référencé — uniquement en création */} + {articleNotFound && !order && ( +
+ Produit non référencé — ce produit n'existe pas dans la base. La commande ne peut pas être créée. +
+ )} + {/* Actions */}
- - diff --git a/client/src/components/Layout.tsx b/client/src/components/Layout.tsx index c1f8407..3c22319 100644 --- a/client/src/components/Layout.tsx +++ b/client/src/components/Layout.tsx @@ -28,9 +28,7 @@ export default function Layout({ children }: LayoutProps) { const [selectedStoreId, setSelectedStoreId] = useState(() => { // Restaurer le selectedStoreId depuis localStorage si disponible const saved = localStorage.getItem('selectedStoreId'); - const restoredId = saved ? parseInt(saved) : null; - console.log('🏪 Layout - Restoring selectedStoreId from localStorage:', { saved, restoredId }); - return restoredId; + return saved ? parseInt(saved) : null; }); const [storeInitialized, setStoreInitialized] = useState(false); @@ -39,10 +37,8 @@ export default function Layout({ children }: LayoutProps) { // Restaurer l'état de la sidebar depuis localStorage, mais forcer collapsed sur mobile try { const saved = localStorage.getItem('sidebarCollapsed'); - console.log('🔧 Layout - Restoring sidebar state:', { saved, isMobile }); return isMobile ? true : (saved ? JSON.parse(saved) : false); - } catch (error) { - console.error('Error reading sidebar state from localStorage:', error); + } catch { return isMobile ? true : false; } }); @@ -63,55 +59,21 @@ export default function Layout({ children }: LayoutProps) { if (selectedStoreId) { const storeExists = stores.find(store => store.id === selectedStoreId); if (!storeExists) { - console.log('🏪 Selected store not found in available stores, clearing selection:', { - selectedStoreId, - userRole: user.role, - availableStores: stores.map(s => s.id) - }); setSelectedStoreId(null); localStorage.removeItem('selectedStoreId'); needsUpdate = true; - } else { - console.log('🏪 Selected store validated successfully:', { - selectedStoreId, - userRole: user.role, - storeName: storeExists.name - }); } } // IMPORTANT FIX: Pour les rôles directeur/manager, forcer la sélection d'un magasin spécifique // si aucun n'est sélectionné et qu'ils n'ont accès qu'à un seul magasin if ((user.role === 'directeur' || user.role === 'manager') && !selectedStoreId && stores.length === 1 && !needsUpdate) { - console.log('🏪 Auto-selecting single available store for directeur/manager:', { - storeId: stores[0].id, - storeName: stores[0].name, - userRole: user.role, - userId: user.id, - previousSelectedStoreId: selectedStoreId, - timestamp: new Date().toISOString() - }); const singleStoreId = stores[0].id; setSelectedStoreId(singleStoreId); localStorage.setItem('selectedStoreId', singleStoreId.toString()); } // VALIDATION CRITIQUE: Pour les directeurs/managers, s'assurer qu'un magasin est toujours sélectionné - if ((user.role === 'directeur' || user.role === 'manager') && !selectedStoreId && !needsUpdate) { - console.log('🚨 CRITICAL: Directeur/Manager has no store selected, this will cause empty data display:', { - userId: user.id, - userRole: user.role, - availableStores: stores.map(s => ({ id: s.id, name: s.name })) - }); - } - - console.log('🏪 Store initialization complete:', { - user: user.role, - selectedStoreId, - storesCount: stores.length, - validatedStore: selectedStoreId ? stores.find(s => s.id === selectedStoreId)?.name : 'None', - autoSelected: (user.role === 'directeur' || user.role === 'manager') && !selectedStoreId && stores.length === 1 - }); setStoreInitialized(true); } }, [user, stores, selectedStoreId]); @@ -172,15 +134,7 @@ export default function Layout({ children }: LayoutProps) {