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)}