diff --git a/VALIDATION-ERROR-FIX.md b/VALIDATION-ERROR-FIX.md index db12d27..b8f12f7 100644 --- a/VALIDATION-ERROR-FIX.md +++ b/VALIDATION-ERROR-FIX.md @@ -1,106 +1,61 @@ -# Fix Urgent - Erreurs de Validation Zod +# Fix Validation Errors - Commandes Client Production -## ❌ Problème Identifié -**Erreur 500 :** ZodError lors de PUT /api/deliveries/:id +## Problème Identifié -### Erreurs Specifiques -1. **blAmount** : Expected string, received number -2. **validatedAt** : Expected date, received string +**Erreurs de validation dans le formulaire de commande client :** +1. `"Expected number, received string"` pour le champ **Fournisseur** (`supplierId`) +2. `"Expected number, received string"` pour le champ **Acompte** (`deposit`) -## 🔍 Analyse du Problème +## Cause du Problème -### Schema Drizzle (shared/schema.ts) -```typescript -// Dans deliveries table: -blAmount: decimal("bl_amount", { precision: 10, scale: 2 }) // → Zod attend string -invoiceAmount: decimal("invoice_amount", { precision: 10, scale: 2 }) // → Zod attend string -validatedAt: timestamp("validated_at") // → Zod attend Date object -``` +Le formulaire frontend envoie des valeurs string, mais le schema Zod attend des numbers : -### Données Envoyées depuis Frontend ```javascript -// ❌ Problématique -{ - blAmount: 3397.86, // number → should be string - invoiceAmount: null, // OK - validatedAt: '2025-08-12T11:42:39.679Z' // string → should be Date -} +// AVANT (Problématique) +supplierId: z.number().int().positive().optional().default(1) +deposit: z.number().optional().default(0) + +// APRÈS (Corrigé) +supplierId: z.coerce.number().int().positive().optional().default(1) +deposit: z.coerce.number().optional().default(0) ``` -## ✅ Solutions Appliquées +## Solution Appliquée -### 1. **Transformation côté Serveur** (server/routes.ts) -```typescript -// Avant validation Zod, transformer les types -const transformedData = { ...req.body }; +**Fichier modifié :** `shared/schema.ts` -// Convertir montants number → string -if (transformedData.blAmount !== undefined && transformedData.blAmount !== null) { - transformedData.blAmount = transformedData.blAmount.toString(); -} -if (transformedData.invoiceAmount !== undefined && transformedData.invoiceAmount !== null) { - transformedData.invoiceAmount = transformedData.invoiceAmount.toString(); -} +✅ Ajouté `z.coerce.number()` pour forcer la conversion automatique string → number +✅ Appliqué aussi pour `quantity` et `groupId` par précaution +✅ Corrigé `customerEmail` pour accepter chaîne vide avec `.or(z.literal(""))` -// Convertir validatedAt string → Date -if (transformedData.validatedAt && typeof transformedData.validatedAt === 'string') { - transformedData.validatedAt = new Date(transformedData.validatedAt); -} +## Schema Corrigé -const data = insertDeliverySchema.partial().parse(transformedData); -``` - -### 2. **Correction côté Frontend** (ReconciliationModal.tsx) -```typescript -// Avant -validatedAt: new Date() // ❌ Date object → string lors JSON.stringify - -// Après -validatedAt: new Date().toISOString() // ✅ string → convertie en Date côté serveur -``` - -### 3. **Montants toujours en string** (ReconciliationModal.tsx) -```typescript -// Déjà corrigé précédemment -blAmount: formData.blAmount ? formData.blAmount.toString() : null, -invoiceAmount: formData.invoiceAmount ? formData.invoiceAmount.toString() : null, -``` - -## 🧪 Test de Validation - -### Payload Attendu Maintenant ```javascript -// ✅ Correct après transformation -{ - blNumber: 'LD2250800571', - blAmount: '3397.86', // string ✅ - invoiceReference: null, - invoiceAmount: null, - reconciled: true, - validatedAt: new Date('2025-08-12T11:42:39.679Z') // Date object ✅ -} +export const insertCustomerOrderFrontendSchema = z.object({ + customerName: z.string().min(1, "Customer name is required"), + contactNumber: z.string().min(1, "Contact number is required"), + productName: z.string().min(1, "Product name is required"), + productDescription: z.string().optional(), + quantity: z.coerce.number().int().positive().default(1), // ✅ CORRIGÉ + groupId: z.coerce.number().int().positive(), // ✅ CORRIGÉ + isPickup: z.boolean().default(false), + notes: z.string().optional(), + orderTaker: z.string().optional(), + gencode: z.string().optional().default(""), + supplierId: z.coerce.number().int().positive().optional().default(1), // ✅ CORRIGÉ + deposit: z.coerce.number().optional().default(0), // ✅ CORRIGÉ + isPromotionalPrice: z.boolean().default(false), + customerEmail: z.string().email().optional().or(z.literal("")), // ✅ CORRIGÉ + productReference: z.string().optional(), +}); ``` -### Tests à Effectuer -1. **Enregistrer données BL** : blAmount doit passer en string -2. **Valider rapprochement** : validatedAt doit passer en Date -3. **Enregistrer montant facture** : invoiceAmount doit passer en string -4. **Vérifier console** : Plus d'erreurs ZodError 500 +## Test -## 🔄 Flux de Données Complet +Après cette correction, les employés devraient pouvoir créer des commandes client sans erreur de validation en production. -``` -Frontend Form → JSON.stringify → HTTP Request → Server Transform → Zod Validation → Database - ↓ ↓ ↓ ↓ ↓ ↓ -{number} → "3397.86" → "3397.86" → "3397.86" → ✅ → DECIMAL -{Date} → "2025-..." → "2025-..." → Date(...) → ✅ → TIMESTAMP -``` +## Déploiement -## 📊 Status - -- ✅ Transformation automatique côté serveur -- ✅ Frontend envoie validatedAt en ISO string -- ✅ Montants forcés en string côté client -- 🔄 **À tester en production** - -**L'erreur 500 ZodError devrait être résolue !** \ No newline at end of file +1. ✅ Schema corrigé dans `shared/schema.ts` +2. 🔄 **Redémarrer le serveur production** +3. 🧪 **Tester création commande client avec employé** \ No newline at end of file diff --git a/attached_assets/{F3D194AF-8FB6-4D0B-8963-F3959CF3A5F3}_1755010018200.png b/attached_assets/{F3D194AF-8FB6-4D0B-8963-F3959CF3A5F3}_1755010018200.png new file mode 100644 index 0000000..d9a380c Binary files /dev/null and b/attached_assets/{F3D194AF-8FB6-4D0B-8963-F3959CF3A5F3}_1755010018200.png differ diff --git a/client/src/components/CustomerOrderForm.tsx b/client/src/components/CustomerOrderForm.tsx index 53dea96..e8f1267 100644 --- a/client/src/components/CustomerOrderForm.tsx +++ b/client/src/components/CustomerOrderForm.tsx @@ -26,8 +26,7 @@ import { insertCustomerOrderFrontendSchema, type CustomerOrderWithRelations, typ import { useStore } from "@/components/Layout"; const customerOrderFormSchema = insertCustomerOrderFrontendSchema.extend({ - deposit: z.number().optional().default(0), - groupId: z.number().int().positive().optional(), + groupId: z.coerce.number().int().positive().optional(), createdBy: z.string().optional(), }); @@ -72,14 +71,14 @@ export function CustomerOrderForm({ defaultValues: { orderTaker: order?.orderTaker || user?.name || "", customerName: order?.customerName || "", - customerPhone: order?.customerPhone || "", - productDesignation: order?.productDesignation || "", + contactNumber: order?.customerPhone || "", + productName: order?.productDesignation || "", productReference: order?.productReference || "", gencode: order?.gencode || "", quantity: order?.quantity || 1, supplierId: order?.supplierId || undefined, status: "En attente de Commande", // Statut fixe - deposit: order?.deposit ? order.deposit.toString() : "0", + deposit: order?.deposit || 0, isPromotionalPrice: order?.isPromotionalPrice || false, customerNotified: order?.customerNotified || false, groupId: order?.groupId || (user?.role === 'admin' && selectedStoreId ? selectedStoreId : user?.userGroups?.[0]?.groupId) || undefined, // Respect admin store selection @@ -119,17 +118,17 @@ export function CustomerOrderForm({ // Prepare data with proper types for frontend schema const submitData = { customerName: data.customerName.trim(), - contactNumber: (data as any).customerPhone || (data as any).contactNumber || '', - productName: (data as any).productDesignation || (data as any).productName || '', + contactNumber: data.contactNumber || '', + productName: data.productName || '', quantity: data.quantity, groupId: typeof groupId === 'number' ? groupId : parseInt(groupId.toString()), isPickup: false, - notes: (data as any).notes, + notes: data.notes, deposit: data.deposit || 0, - isPromotionalPrice: (data as any).isPromotionalPrice || false, - customerEmail: (data as any).customerEmail, - gencode: (data as any).gencode || '', - supplierId: (data as any).supplierId || 1, + isPromotionalPrice: data.isPromotionalPrice || false, + customerEmail: data.customerEmail, + gencode: data.gencode || '', + supplierId: data.supplierId || 1, }; console.log("🔍 Frontend submit data:", submitData); onSubmit(submitData); @@ -193,7 +192,7 @@ export function CustomerOrderForm({ ( N° de téléphone @@ -236,7 +235,7 @@ export function CustomerOrderForm({ ( Désignation du produit diff --git a/cookies.txt b/cookies.txt index dff7434..9bd4875 100644 --- a/cookies.txt +++ b/cookies.txt @@ -2,4 +2,4 @@ # https://curl.se/docs/http-cookies.html # This file was generated by libcurl! Edit at your own risk. -#HttpOnly_localhost FALSE / FALSE 1755095028 connect.sid s%3AxwnR6osGUOsL-I6MHjHctBuJf4K1HUWh.jqsaHkMTtzflaI4dg%2BmJBmTrVplUTDTG45KCUiL%2FVfw +#HttpOnly_localhost FALSE / FALSE 1755096543 connect.sid s%3AdW4IaK_l9KAwScxqJTml_UK7Lh2MI3U-.9wChJ5YVxYxvwztaxrxvedzNdY2gvauFeMasHVP8jsk diff --git a/shared/schema.ts b/shared/schema.ts index 4389173..6848689 100644 --- a/shared/schema.ts +++ b/shared/schema.ts @@ -456,17 +456,17 @@ export const insertCustomerOrderFrontendSchema = z.object({ contactNumber: z.string().min(1, "Contact number is required"), // Maps to customerPhone productName: z.string().min(1, "Product name is required"), // Maps to productDesignation productDescription: z.string().optional(), // Optional description - quantity: z.number().int().positive().default(1), - groupId: z.number().int().positive(), + quantity: z.coerce.number().int().positive().default(1), + groupId: z.coerce.number().int().positive(), isPickup: z.boolean().default(false), notes: z.string().optional(), // Optional fields with defaults orderTaker: z.string().optional(), // Will be set to creator name if not provided gencode: z.string().optional().default(""), // Will be empty if not provided - supplierId: z.number().int().positive().optional().default(1), // Default supplier ID - deposit: z.number().optional().default(0), + supplierId: z.coerce.number().int().positive().optional().default(1), // Default supplier ID + deposit: z.coerce.number().optional().default(0), isPromotionalPrice: z.boolean().default(false), - customerEmail: z.string().email().optional(), + customerEmail: z.string().email().optional().or(z.literal("")), productReference: z.string().optional(), });