From 4881833925b4cf87af01d160809a66ae3cafbd9e Mon Sep 17 00:00:00 2001 From: michaelschal <35957947-michaelschal@users.noreply.replit.com> Date: Mon, 18 Aug 2025 09:10:15 +0000 Subject: [PATCH] Add ability to mark products as out of stock and restore them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce new API endpoints and storage logic for managing product stock status, including 'stock épuisé' and 'restore stock' functionalities with role-based access control. Replit-Commit-Author: Agent Replit-Commit-Session-Id: f9197b9b-a5b3-4469-a27d-930a9e9ee736 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1957c339-2757-4d1f-8e92-e9f71a1ce58e/f9197b9b-a5b3-4469-a27d-930a9e9ee736/JKfj3tT --- client/src/pages/DlcPage.tsx | 90 +++++++++++++++++++++++++++++++++--- server/routes.ts | 85 ++++++++++++++++++++++++++++++++++ server/storage.ts | 68 +++++++++++++++++++++++++++ shared/schema.ts | 3 ++ 4 files changed, 239 insertions(+), 7 deletions(-) diff --git a/client/src/pages/DlcPage.tsx b/client/src/pages/DlcPage.tsx index aa80fa2..1b4c493 100644 --- a/client/src/pages/DlcPage.tsx +++ b/client/src/pages/DlcPage.tsx @@ -12,7 +12,7 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; import { Textarea } from "@/components/ui/textarea"; -import { AlertTriangle, Plus, Eye, Edit, Trash2, CheckCircle, Package, Clock, AlertCircle, Filter, Download, FileText } from "lucide-react"; +import { AlertTriangle, Plus, Eye, Edit, Trash2, CheckCircle, Package, Clock, AlertCircle, Filter, Download, FileText, PackageX, RotateCcw } from "lucide-react"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; @@ -190,6 +190,40 @@ export default function DlcPage() { }, }); + // Stock épuisé mutation - accessible à tous + const markStockEpuiseMutation = useMutation({ + mutationFn: (id: number) => apiRequest(`/api/dlc-products/${id}/stock-epuise`, "PUT"), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["/api/dlc-products"], exact: false }); + queryClient.invalidateQueries({ queryKey: ["/api/dlc-products/stats"], exact: false }); + toast({ title: "Produit marqué comme stock épuisé" }); + }, + onError: (error: any) => { + toast({ + title: "Erreur lors du marquage", + description: error.message || "Une erreur est survenue", + variant: "destructive", + }); + }, + }); + + // Restaurer stock mutation - réservé aux admins, directeurs et managers + const restoreStockMutation = useMutation({ + mutationFn: (id: number) => apiRequest(`/api/dlc-products/${id}/restore-stock`, "PUT"), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["/api/dlc-products"], exact: false }); + queryClient.invalidateQueries({ queryKey: ["/api/dlc-products/stats"], exact: false }); + toast({ title: "Stock restauré avec succès" }); + }, + onError: (error: any) => { + toast({ + title: "Erreur lors de la restauration", + description: error.message || "Une erreur est survenue", + variant: "destructive", + }); + }, + }); + const onSubmit = (data: DlcFormData) => { // Calculer la date d'expiration et le seuil d'alerte (15 jours avant) const dlcDate = new Date(data.dlcDate); @@ -275,6 +309,14 @@ export default function DlcPage() { validateMutation.mutate(id); }; + const handleMarkStockEpuise = (id: number) => { + markStockEpuiseMutation.mutate(id); + }; + + const handleRestoreStock = (id: number) => { + restoreStockMutation.mutate(id); + }; + // Fonction pour déterminer si un produit doit afficher le bouton de validation const shouldShowValidateButton = (product: DlcProductWithRelations) => { const today = new Date(); @@ -286,12 +328,17 @@ export default function DlcPage() { return (daysUntilExpiry <= 15 && product.status !== "valides"); }; - const getStatusBadge = (status: string, dlcDate: string | null) => { + const getStatusBadge = (status: string, dlcDate: string | null, stockEpuise?: boolean) => { if (!dlcDate) return Non défini; - // LOGIQUE SIMPLIFIÉE ET COHÉRENTE : - // 1. Si le statut en base est "valides", afficher "Validé" (peu importe la date) - // 2. Sinon, calculer selon la date d'expiration + // LOGIQUE MISE À JOUR : + // 1. Si stock épuisé, afficher "Stock épuisé" avec style spécifique + // 2. Si le statut en base est "valides", afficher "Validé" (peu importe la date) + // 3. Sinon, calculer selon la date d'expiration + + if (stockEpuise) { + return Stock épuisé; + } if (status === "valides") { return Validé; @@ -767,7 +814,10 @@ export default function DlcPage() { {paginatedProducts.map((product) => ( {product.productName} @@ -782,7 +832,7 @@ export default function DlcPage() { {product.expiryDate ? format(new Date(product.expiryDate), "dd/MM/yyyy", { locale: fr }) : 'Date non définie'} {product.supplier?.name || 'Non défini'} - {getStatusBadge(product.status, product.expiryDate)} + {getStatusBadge(product.status, product.expiryDate, product.stockEpuise)}
)} + + {/* Bouton Stock épuisé - accessible à tous */} + {!product.stockEpuise && product.status !== "valides" && ( + + )} + + {/* Bouton Restaurer stock - réservé aux admins, directeurs et managers */} + {product.stockEpuise && (user?.role === 'admin' || user?.role === 'manager' || user?.role === 'directeur') && ( + + )}