mirror of
https://github.com/R0m1k3/LogiFlow.git
synced 2026-10-19 21:18:25 +02:00
Merge pull request #44 from R0m1k3/1.1
Fix client order validation errors for production
This commit is contained in:
5 files changed
+62
-108
No files matched your search
+43
-88
@@ -1,106 +1,61 @@
|
|||||||
# Fix Urgent - Erreurs de Validation Zod
|
# Fix Validation Errors - Commandes Client Production
|
||||||
|
|
||||||
## ❌ Problème Identifié
|
## Problème Identifié
|
||||||
**Erreur 500 :** ZodError lors de PUT /api/deliveries/:id
|
|
||||||
|
|
||||||
### Erreurs Specifiques
|
**Erreurs de validation dans le formulaire de commande client :**
|
||||||
1. **blAmount** : Expected string, received number
|
1. `"Expected number, received string"` pour le champ **Fournisseur** (`supplierId`)
|
||||||
2. **validatedAt** : Expected date, received string
|
2. `"Expected number, received string"` pour le champ **Acompte** (`deposit`)
|
||||||
|
|
||||||
## 🔍 Analyse du Problème
|
## Cause du Problème
|
||||||
|
|
||||||
### Schema Drizzle (shared/schema.ts)
|
Le formulaire frontend envoie des valeurs string, mais le schema Zod attend des numbers :
|
||||||
```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
|
|
||||||
```
|
|
||||||
|
|
||||||
### Données Envoyées depuis Frontend
|
|
||||||
```javascript
|
```javascript
|
||||||
// ❌ Problématique
|
// AVANT (Problématique)
|
||||||
{
|
supplierId: z.number().int().positive().optional().default(1)
|
||||||
blAmount: 3397.86, // number → should be string
|
deposit: z.number().optional().default(0)
|
||||||
invoiceAmount: null, // OK
|
|
||||||
validatedAt: '2025-08-12T11:42:39.679Z' // string → should be Date
|
// 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)
|
**Fichier modifié :** `shared/schema.ts`
|
||||||
```typescript
|
|
||||||
// Avant validation Zod, transformer les types
|
|
||||||
const transformedData = { ...req.body };
|
|
||||||
|
|
||||||
// Convertir montants number → string
|
✅ Ajouté `z.coerce.number()` pour forcer la conversion automatique string → number
|
||||||
if (transformedData.blAmount !== undefined && transformedData.blAmount !== null) {
|
✅ Appliqué aussi pour `quantity` et `groupId` par précaution
|
||||||
transformedData.blAmount = transformedData.blAmount.toString();
|
✅ Corrigé `customerEmail` pour accepter chaîne vide avec `.or(z.literal(""))`
|
||||||
}
|
|
||||||
if (transformedData.invoiceAmount !== undefined && transformedData.invoiceAmount !== null) {
|
|
||||||
transformedData.invoiceAmount = transformedData.invoiceAmount.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convertir validatedAt string → Date
|
## Schema Corrigé
|
||||||
if (transformedData.validatedAt && typeof transformedData.validatedAt === 'string') {
|
|
||||||
transformedData.validatedAt = new Date(transformedData.validatedAt);
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
```javascript
|
||||||
// ✅ Correct après transformation
|
export const insertCustomerOrderFrontendSchema = z.object({
|
||||||
{
|
customerName: z.string().min(1, "Customer name is required"),
|
||||||
blNumber: 'LD2250800571',
|
contactNumber: z.string().min(1, "Contact number is required"),
|
||||||
blAmount: '3397.86', // string ✅
|
productName: z.string().min(1, "Product name is required"),
|
||||||
invoiceReference: null,
|
productDescription: z.string().optional(),
|
||||||
invoiceAmount: null,
|
quantity: z.coerce.number().int().positive().default(1), // ✅ CORRIGÉ
|
||||||
reconciled: true,
|
groupId: z.coerce.number().int().positive(), // ✅ CORRIGÉ
|
||||||
validatedAt: new Date('2025-08-12T11:42:39.679Z') // Date object ✅
|
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
|
## Test
|
||||||
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
|
|
||||||
|
|
||||||
## 🔄 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.
|
||||||
|
|
||||||
```
|
## Déploiement
|
||||||
Frontend Form → JSON.stringify → HTTP Request → Server Transform → Zod Validation → Database
|
|
||||||
↓ ↓ ↓ ↓ ↓ ↓
|
|
||||||
{number} → "3397.86" → "3397.86" → "3397.86" → ✅ → DECIMAL
|
|
||||||
{Date} → "2025-..." → "2025-..." → Date(...) → ✅ → TIMESTAMP
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📊 Status
|
1. ✅ Schema corrigé dans `shared/schema.ts`
|
||||||
|
2. 🔄 **Redémarrer le serveur production**
|
||||||
- ✅ Transformation automatique côté serveur
|
3. 🧪 **Tester création commande client avec employé**
|
||||||
- ✅ 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 !**
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 63 KiB |
@@ -26,8 +26,7 @@ import { insertCustomerOrderFrontendSchema, type CustomerOrderWithRelations, typ
|
|||||||
import { useStore } from "@/components/Layout";
|
import { useStore } from "@/components/Layout";
|
||||||
|
|
||||||
const customerOrderFormSchema = insertCustomerOrderFrontendSchema.extend({
|
const customerOrderFormSchema = insertCustomerOrderFrontendSchema.extend({
|
||||||
deposit: z.number().optional().default(0),
|
groupId: z.coerce.number().int().positive().optional(),
|
||||||
groupId: z.number().int().positive().optional(),
|
|
||||||
createdBy: z.string().optional(),
|
createdBy: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -72,14 +71,14 @@ export function CustomerOrderForm({
|
|||||||
defaultValues: {
|
defaultValues: {
|
||||||
orderTaker: order?.orderTaker || user?.name || "",
|
orderTaker: order?.orderTaker || user?.name || "",
|
||||||
customerName: order?.customerName || "",
|
customerName: order?.customerName || "",
|
||||||
customerPhone: order?.customerPhone || "",
|
contactNumber: order?.customerPhone || "",
|
||||||
productDesignation: order?.productDesignation || "",
|
productName: order?.productDesignation || "",
|
||||||
productReference: order?.productReference || "",
|
productReference: order?.productReference || "",
|
||||||
gencode: order?.gencode || "",
|
gencode: order?.gencode || "",
|
||||||
quantity: order?.quantity || 1,
|
quantity: order?.quantity || 1,
|
||||||
supplierId: order?.supplierId || undefined,
|
supplierId: order?.supplierId || undefined,
|
||||||
status: "En attente de Commande", // Statut fixe
|
status: "En attente de Commande", // Statut fixe
|
||||||
deposit: order?.deposit ? order.deposit.toString() : "0",
|
deposit: order?.deposit || 0,
|
||||||
isPromotionalPrice: order?.isPromotionalPrice || false,
|
isPromotionalPrice: order?.isPromotionalPrice || false,
|
||||||
customerNotified: order?.customerNotified || false,
|
customerNotified: order?.customerNotified || false,
|
||||||
groupId: order?.groupId || (user?.role === 'admin' && selectedStoreId ? selectedStoreId : user?.userGroups?.[0]?.groupId) || undefined, // Respect admin store selection
|
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
|
// Prepare data with proper types for frontend schema
|
||||||
const submitData = {
|
const submitData = {
|
||||||
customerName: data.customerName.trim(),
|
customerName: data.customerName.trim(),
|
||||||
contactNumber: (data as any).customerPhone || (data as any).contactNumber || '',
|
contactNumber: data.contactNumber || '',
|
||||||
productName: (data as any).productDesignation || (data as any).productName || '',
|
productName: data.productName || '',
|
||||||
quantity: data.quantity,
|
quantity: data.quantity,
|
||||||
groupId: typeof groupId === 'number' ? groupId : parseInt(groupId.toString()),
|
groupId: typeof groupId === 'number' ? groupId : parseInt(groupId.toString()),
|
||||||
isPickup: false,
|
isPickup: false,
|
||||||
notes: (data as any).notes,
|
notes: data.notes,
|
||||||
deposit: data.deposit || 0,
|
deposit: data.deposit || 0,
|
||||||
isPromotionalPrice: (data as any).isPromotionalPrice || false,
|
isPromotionalPrice: data.isPromotionalPrice || false,
|
||||||
customerEmail: (data as any).customerEmail,
|
customerEmail: data.customerEmail,
|
||||||
gencode: (data as any).gencode || '',
|
gencode: data.gencode || '',
|
||||||
supplierId: (data as any).supplierId || 1,
|
supplierId: data.supplierId || 1,
|
||||||
};
|
};
|
||||||
console.log("🔍 Frontend submit data:", submitData);
|
console.log("🔍 Frontend submit data:", submitData);
|
||||||
onSubmit(submitData);
|
onSubmit(submitData);
|
||||||
@@ -193,7 +192,7 @@ export function CustomerOrderForm({
|
|||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="customerPhone"
|
name="contactNumber"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>N° de téléphone</FormLabel>
|
<FormLabel>N° de téléphone</FormLabel>
|
||||||
@@ -236,7 +235,7 @@ export function CustomerOrderForm({
|
|||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="productDesignation"
|
name="productName"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Désignation du produit</FormLabel>
|
<FormLabel>Désignation du produit</FormLabel>
|
||||||
|
|||||||
+1
-1
@@ -2,4 +2,4 @@
|
|||||||
# https://curl.se/docs/http-cookies.html
|
# https://curl.se/docs/http-cookies.html
|
||||||
# This file was generated by libcurl! Edit at your own risk.
|
# 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
|
||||||
+5
-5
@@ -456,17 +456,17 @@ export const insertCustomerOrderFrontendSchema = z.object({
|
|||||||
contactNumber: z.string().min(1, "Contact number is required"), // Maps to customerPhone
|
contactNumber: z.string().min(1, "Contact number is required"), // Maps to customerPhone
|
||||||
productName: z.string().min(1, "Product name is required"), // Maps to productDesignation
|
productName: z.string().min(1, "Product name is required"), // Maps to productDesignation
|
||||||
productDescription: z.string().optional(), // Optional description
|
productDescription: z.string().optional(), // Optional description
|
||||||
quantity: z.number().int().positive().default(1),
|
quantity: z.coerce.number().int().positive().default(1),
|
||||||
groupId: z.number().int().positive(),
|
groupId: z.coerce.number().int().positive(),
|
||||||
isPickup: z.boolean().default(false),
|
isPickup: z.boolean().default(false),
|
||||||
notes: z.string().optional(),
|
notes: z.string().optional(),
|
||||||
// Optional fields with defaults
|
// Optional fields with defaults
|
||||||
orderTaker: z.string().optional(), // Will be set to creator name if not provided
|
orderTaker: z.string().optional(), // Will be set to creator name if not provided
|
||||||
gencode: z.string().optional().default(""), // Will be empty 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
|
supplierId: z.coerce.number().int().positive().optional().default(1), // Default supplier ID
|
||||||
deposit: z.number().optional().default(0),
|
deposit: z.coerce.number().optional().default(0),
|
||||||
isPromotionalPrice: z.boolean().default(false),
|
isPromotionalPrice: z.boolean().default(false),
|
||||||
customerEmail: z.string().email().optional(),
|
customerEmail: z.string().email().optional().or(z.literal("")),
|
||||||
productReference: z.string().optional(),
|
productReference: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user