From 8baf071c175ed4077d3737ebaf0b5425185bcff7 Mon Sep 17 00:00:00 2001 From: michaelschal <35957947-michaelschal@users.noreply.replit.com> Date: Mon, 8 Sep 2025 15:09:07 +0000 Subject: [PATCH] Improve the reliability of the task management system by fixing errors Update database schema to allow nullable fields for invoice reference and amount, adjust React components for safe access to optional data, and remove obsolete client-side debugging scripts. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 9fcf4b21-eb0c-4e53-a567-e2ce4a6ad869 Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1957c339-2757-4d1f-8e92-e9f71a1ce58e/9fcf4b21-eb0c-4e53-a567-e2ce4a6ad869/mpVybiP --- SOLUTION_COMPLETE_PRODUCTION.md | 646 -------------------------------- client/src/pages/Avoirs.tsx | 22 +- debug_client_error.js | 45 --- sql/fix_references.sql | 4 +- 4 files changed, 13 insertions(+), 704 deletions(-) delete mode 100644 SOLUTION_COMPLETE_PRODUCTION.md delete mode 100644 debug_client_error.js 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 - -