diff --git a/PRODUCTION_DLC_STOCK_UPDATE.md b/PRODUCTION_DLC_STOCK_UPDATE.md new file mode 100644 index 0000000..c7e06d3 --- /dev/null +++ b/PRODUCTION_DLC_STOCK_UPDATE.md @@ -0,0 +1,92 @@ +# Mise à jour Production - Fonctionnalité Stock Épuisé DLC + +## Nouveaux champs ajoutés au module DLC + +Cette mise à jour ajoute la fonctionnalité de gestion du stock épuisé pour les produits DLC, permettant de distinguer les produits périmés des produits en rupture de stock. + +### Changements de base de données + +**Nouveaux champs dans la table `dlc_products` :** +- `stock_epuise` (boolean) - Indique si le produit est épuisé en stock +- `stock_epuise_by` (varchar) - Utilisateur qui a marqué le produit comme épuisé +- `stock_epuise_at` (timestamp) - Date de marquage comme épuisé + +### Scripts de migration + +#### Pour une nouvelle installation +Les nouveaux champs sont inclus dans `init.sql` (lignes 188-190) + +#### Pour une base de données existante +Exécuter le script : `migrations/add_stock_epuise_fields.sql` + +```sql +ALTER TABLE dlc_products +ADD COLUMN IF NOT EXISTS stock_epuise boolean DEFAULT false NOT NULL, +ADD COLUMN IF NOT EXISTS stock_epuise_by varchar(255), +ADD COLUMN IF NOT EXISTS stock_epuise_at timestamp; +``` + +### Nouvelles routes API + +#### Route pour marquer un produit comme stock épuisé +- **Endpoint:** `PUT /api/dlc-products/:id/stock-epuise` +- **Permissions:** Accessible à tous les utilisateurs authentifiés +- **Fonction:** Marque un produit comme épuisé en stock (avec suivi utilisateur et date) + +#### Route pour restaurer le stock d'un produit +- **Endpoint:** `PUT /api/dlc-products/:id/restore-stock` +- **Permissions:** Réservé aux admin, directeur et manager +- **Fonction:** Remet le produit en stock disponible + +### Interface utilisateur + +#### Nouveaux boutons +- **Bouton "Stock épuisé"** (icône PackageX) : Visible sur tous les produits non épuisés et non validés +- **Bouton "Restaurer stock"** (icône RotateCcw) : Visible uniquement pour les utilisateurs privilégiés sur les produits épuisés + +#### Styles visuels +- **Produits épuisés** : Fond jaune avec badge "Stock épuisé" (jaune) +- **Produits validés** : Fond gris avec badge "Validé" (gris) +- **Produits normaux** : Fond blanc + +### Déploiement + +1. **Mise à jour Docker :** + ```bash + docker-compose down + docker-compose build + docker-compose up -d + ``` + +2. **Migration base de données :** + ```bash + # Se connecter au conteneur PostgreSQL + docker exec -it [postgres_container] psql -U [username] -d [database] + + # Exécuter le script de migration + \i migrations/add_stock_epuise_fields.sql + ``` + +3. **Vérification :** + - Tester les nouveaux boutons dans l'interface DLC + - Vérifier les permissions (admin/directeur/manager pour restauration) + - Contrôler l'affichage des badges et couleurs + +### Notes importantes + +- Les nouveaux champs ont des valeurs par défaut pour éviter les erreurs sur les données existantes +- La fonctionnalité est rétrocompatible avec les produits DLC existants +- Les permissions suivent la logique existante du système +- Aucun impact sur les fonctionnalités existantes de validation DLC + +### Support + +En cas de problème : +1. Vérifier que la migration a été appliquée : `\d dlc_products` +2. Contrôler les logs de l'application pour les erreurs API +3. Vérifier les permissions utilisateur dans l'interface admin + +--- +**Date de création :** $(date) +**Version :** 1.0.0 +**Auteur :** Système de gestion DLC \ No newline at end of file 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') && ( + + )}