diff --git a/SOLUTION_COMPLETE_PRODUCTION.md b/SOLUTION_COMPLETE_PRODUCTION.md deleted file mode 100644 index 082a8e8..0000000 --- a/SOLUTION_COMPLETE_PRODUCTION.md +++ /dev/null @@ -1,646 +0,0 @@ -# 🚀 SOLUTION COMPLÈTE - MODULE AVOIRS PRODUCTION - -## đŸ”„ PROBLÈME RÉSOLU -- Erreur `contact_email` supprimĂ©e -- Interface React corrigĂ©e pour les champs optionnels -- PrĂȘt pour la production - -## 📋 ÉTAPES DE DÉPLOIEMENT - -### ÉTAPE 1 - Base de donnĂ©es (ExĂ©cuter dans votre console PostgreSQL) -```sql --- Corriger la structure de la table avoirs -ALTER TABLE avoirs ALTER COLUMN invoice_reference DROP NOT NULL; -ALTER TABLE avoirs ALTER COLUMN amount DROP NOT NULL; - --- VĂ©rifier la correction -SELECT column_name, is_nullable -FROM information_schema.columns -WHERE table_name = 'avoirs' -AND column_name IN ('invoice_reference', 'amount'); -``` - -### ÉTAPE 2 - Fichiers Ă  remplacer sur votre serveur - -#### A) shared/schema.ts (section avoirs uniquement) -Remplacez la section avoirs par : -```typescript -export const avoirs = pgTable("avoirs", { - id: serial("id").primaryKey(), - supplierId: integer("supplier_id").notNull(), - groupId: integer("group_id").notNull(), - invoiceReference: varchar("invoice_reference"), // OPTIONNEL - amount: decimal("amount", { precision: 10, scale: 2 }), // OPTIONNEL - comment: text("comment"), - commercialProcessed: boolean("commercial_processed").default(false), - status: varchar("status").notNull().default("En attente de demande"), - webhookSent: boolean("webhook_sent").default(false), - nocodbVerified: boolean("nocodb_verified").default(false), - nocodbVerifiedAt: timestamp("nocodb_verified_at"), - processedAt: timestamp("processed_at"), - createdBy: varchar("created_by").notNull(), - createdAt: timestamp("created_at").defaultNow(), - updatedAt: timestamp("updated_at").defaultNow(), -}); - -export const insertAvoirSchema = createInsertSchema(avoirs).omit({ - id: true, - createdAt: true, - updatedAt: true, - webhookSent: true, - nocodbVerified: true, - nocodbVerifiedAt: true, - processedAt: true, -}); - -export type Avoir = typeof avoirs.$inferSelect; -export type InsertAvoir = z.infer; -``` - -#### B) client/src/pages/Avoirs.tsx -**FICHIER COMPLET CORRIGÉ - Copiez tout :** - -```typescript -import { useState } from "react"; -import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; -import { format } from "date-fns"; -import { fr } from "date-fns/locale"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Textarea } from "@/components/ui/textarea"; -import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; -import { Badge } from "@/components/ui/badge"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import { Checkbox } from "@/components/ui/checkbox"; -import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; -import { Plus, Search, FileText, CheckCircle, Clock, AlertCircle } from "lucide-react"; -import { useForm } from "react-hook-form"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { z } from "zod"; -import { useToast } from "@/hooks/use-toast"; -import { apiRequest } from "@/lib/queryClient"; - -// Types -interface Avoir { - id: number; - supplierId: number; - groupId: number; - invoiceReference?: string; // OPTIONNEL - amount?: number; // OPTIONNEL - comment?: string; - commercialProcessed: boolean; - status: 'En attente de demande' | 'DemandĂ©' | 'Reçu'; - webhookSent: boolean; - nocodbVerified: boolean; - nocodbVerifiedAt?: Date; - processedAt?: Date; - createdBy: string; - createdAt: Date; - updatedAt: Date; - supplier: { - id: number; - name: string; - contact?: string; - phone?: string; - }; - group: { - id: number; - name: string; - color: string; - }; - creator: { - id: string; - username: string; - firstName?: string; - lastName?: string; - }; -} - -interface Supplier { - id: number; - name: string; -} - -interface Group { - id: number; - name: string; - color: string; -} - -// Schema for form validation -const avoirSchema = z.object({ - supplierId: z.number().min(1, "Veuillez sĂ©lectionner un fournisseur"), - groupId: z.number().min(1, "Veuillez sĂ©lectionner un magasin"), - invoiceReference: z.string().optional(), - amount: z.number().optional(), - comment: z.string().optional(), - commercialProcessed: z.boolean().optional(), -}); - -type AvoirFormData = z.infer; - -export default function Avoirs() { - const [searchTerm, setSearchTerm] = useState(""); - const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false); - const [selectedStoreId, setSelectedStoreId] = useState("all"); - const { toast } = useToast(); - const queryClient = useQueryClient(); - - // Fetch user profile - const { data: user } = useQuery({ - queryKey: ['/api/user'], - }); - - // Fetch groups for store filter - const { data: groups = [] } = useQuery({ - queryKey: ['/api/groups'], - enabled: !!user, - }); - - // Fetch suppliers - const { data: suppliers = [] } = useQuery({ - queryKey: ['/api/suppliers'], - enabled: !!user, - }); - - // Fetch avoirs - const { data: avoirs = [], isLoading } = useQuery({ - queryKey: ['/api/avoirs', selectedStoreId], - queryFn: () => apiRequest(`/api/avoirs${selectedStoreId !== 'all' ? `?storeId=${selectedStoreId}` : ''}`), - enabled: !!user, - }); - - // Create avoir mutation - const createAvoirMutation = useMutation({ - mutationFn: async (data: AvoirFormData) => { - const response = await fetch('/api/avoirs', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(data), - credentials: 'include', - }); - if (!response.ok) { - throw new Error('Failed to create avoir'); - } - return response.json(); - }, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['/api/avoirs'] }); - setIsCreateDialogOpen(false); - toast({ - title: "Avoir créé", - description: "L'avoir a Ă©tĂ© créé avec succĂšs.", - }); - }, - onError: (error: any) => { - toast({ - title: "Erreur", - description: error.message || "Erreur lors de la crĂ©ation de l'avoir", - variant: "destructive", - }); - } - }); - - // Initialize form - const form = useForm({ - resolver: zodResolver(avoirSchema), - defaultValues: { - supplierId: 0, - groupId: 0, - invoiceReference: "", - amount: undefined, - comment: "", - commercialProcessed: false, - }, - }); - - // Handle form submission - const onSubmit = (data: AvoirFormData) => { - createAvoirMutation.mutate(data); - }; - - // Filter avoirs based on search term - CORRECTION CRITIQUE - const filteredAvoirs = avoirs.filter(avoir => - avoir.invoiceReference?.toLowerCase().includes(searchTerm.toLowerCase()) || - avoir.supplier.name.toLowerCase().includes(searchTerm.toLowerCase()) || - avoir.comment?.toLowerCase().includes(searchTerm.toLowerCase()) || - (avoir.creator.firstName && avoir.creator.lastName - ? `${avoir.creator.firstName} ${avoir.creator.lastName}`.toLowerCase().includes(searchTerm.toLowerCase()) - : avoir.creator.username.toLowerCase().includes(searchTerm.toLowerCase()) - ) - ); - - // Status badge variant - const getStatusVariant = (status: string) => { - switch (status) { - case 'Reçu': - return 'default'; - case 'DemandĂ©': - return 'secondary'; - case 'En attente de demande': - return 'outline'; - default: - return 'outline'; - } - }; - - // Status icon - const getStatusIcon = (status: string) => { - switch (status) { - case 'Reçu': - return ; - case 'DemandĂ©': - return ; - case 'En attente de demande': - return ; - default: - return ; - } - }; - - if (!user) { - return
Chargement...
; - } - - // Check permissions - if ((user as any)?.role === 'employee') { - return ( -
-
-

AccÚs refusé

-

Vous n'avez pas les permissions pour accéder aux avoirs.

-
-
- ); - } - - const canCreate = ['admin', 'directeur', 'manager'].includes((user as any)?.role); - const userGroups = (user as any)?.userGroups || []; - const availableGroups = (user as any)?.role === 'admin' ? groups : userGroups.map((ug: any) => ug.group); - - return ( -
- {/* Header */} -
-
-

Avoirs

-

- Gestion des demandes d'avoirs et suivi des remboursements -

-
- - {canCreate && ( - - - - - - - Créer un nouvel avoir - -
- - ( - - Fournisseur * - - - - )} - /> - - ( - - Magasin * - - - - )} - /> - - ( - - Référence facture (optionnel) - - - - - - )} - /> - - ( - - Montant (optionnel) - - { - const value = e.target.value; - field.onChange(value ? parseFloat(value) : undefined); - }} - value={field.value || ''} - /> - - - - )} - /> - - ( - - Commentaire - -