diff --git a/backend/gestion/db/db_product.go b/backend/gestion/db/db_product.go index 093ff385..6a7e4506 100644 --- a/backend/gestion/db/db_product.go +++ b/backend/gestion/db/db_product.go @@ -5,6 +5,8 @@ import ( "gestion/models" "log" "time" + + "gorm.io/gorm" ) // CreateProduct crée un nouveau produit avec ses prix @@ -262,14 +264,27 @@ func (d *Database) UpdateProduct(productID int, name, category, description, uni return nil } +// SetProductStock fixe le stock à une valeur absolue. Le verrou FOR UPDATE +// sérialise cette écriture avec les décréments du checkout (db_commands.go) : +// sans lui, une modification admin pourrait écraser silencieusement le +// décrément d'une commande passée au même instant sur le même produit. func (d *Database) SetProductStock(productID int, stock float64) error { - result := d.GDB.Exec(`UPDATE products SET stock = ?, updated_at = ? WHERE id = ?`, - stock, time.Now(), productID) - if result.Error != nil { - return fmt.Errorf("erreur mise à jour stock: %w", result.Error) - } - if result.RowsAffected == 0 { - return fmt.Errorf("produit non trouvé") + err := d.GDB.Transaction(func(tx *gorm.DB) error { + var exists int + if err := tx.Raw(`SELECT 1 FROM products WHERE id = ? FOR UPDATE`, productID).Scan(&exists).Error; err != nil { + return fmt.Errorf("erreur verrouillage produit: %w", err) + } + if exists == 0 { + return fmt.Errorf("produit non trouvé") + } + if err := tx.Exec(`UPDATE products SET stock = ?, updated_at = ? WHERE id = ?`, + stock, time.Now(), productID).Error; err != nil { + return fmt.Errorf("erreur mise à jour stock: %w", err) + } + return nil + }) + if err != nil { + return err } return nil } diff --git a/backend/gestion/models/redis.go b/backend/gestion/models/redis.go index 9081677c..25656059 100644 --- a/backend/gestion/models/redis.go +++ b/backend/gestion/models/redis.go @@ -19,11 +19,3 @@ type DeliveryPersonStatus struct { CurrentCommand int `json:"current_command,omitempty"` LastUpdate time.Time `json:"last_update"` } - -type StockReservation struct { - ProductID int `json:"product_id"` - Quantity int `json:"quantity"` - Username string `json:"username"` - ExpiresAt time.Time `json:"expires_at"` - CommandID int `json:"command_id"` -} diff --git a/backend/gestion/workers/redis_worker.go b/backend/gestion/workers/redis_worker.go index 26ac29e7..ae092073 100644 --- a/backend/gestion/workers/redis_worker.go +++ b/backend/gestion/workers/redis_worker.go @@ -17,8 +17,6 @@ func StartRedisWorkers(database *db.Database) { go AutoAssignWorker(database) - go StockCleanupWorker(database) - go PointsSyncWorker(database) log.Println("✅ Tous les workers Redis sont démarrés") @@ -76,48 +74,6 @@ func AutoAssignWorker(database *db.Database) { } } -// ============================================ -// WORKER NETTOYAGE STOCK -// ============================================ - -// StockCleanupWorker nettoie les réservations expirées -func StockCleanupWorker(database *db.Database) { - ticker := time.NewTicker(5 * time.Minute) - defer ticker.Stop() - - log.Println("🧹 Worker Nettoyage Stock démarré (check toutes les 5 min)") - - for range ticker.C { - cleanedCount := 0 - - // Récupérer toutes les clés de réservation - keys, err := db.Redis.Keys(db.RedisCtx, "stock:reserve:*").Result() - if err != nil { - log.Printf("⚠️ Erreur récupération réservations: %v", err) - continue - } - - for _, key := range keys { - // Vérifier si la réservation est expirée - ttl, err := db.Redis.TTL(db.RedisCtx, key).Result() - if err != nil { - continue - } - - // Si TTL <= 0, la réservation est expirée - if ttl <= 0 { - // Redis va automatiquement supprimer la clé - // Mais on peut log pour traçabilité - cleanedCount++ - } - } - - if cleanedCount > 0 { - log.Printf("🧹 %d réservations de stock expirées nettoyées", cleanedCount) - } - } -} - // ============================================ // WORKER SYNCHRONISATION POINTS // ============================================