This commit is contained in:
@@ -5,6 +5,8 @@ import (
|
|||||||
"gestion/models"
|
"gestion/models"
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CreateProduct crée un nouveau produit avec ses prix
|
// 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
|
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 {
|
func (d *Database) SetProductStock(productID int, stock float64) error {
|
||||||
result := d.GDB.Exec(`UPDATE products SET stock = ?, updated_at = ? WHERE id = ?`,
|
err := d.GDB.Transaction(func(tx *gorm.DB) error {
|
||||||
stock, time.Now(), productID)
|
var exists int
|
||||||
if result.Error != nil {
|
if err := tx.Raw(`SELECT 1 FROM products WHERE id = ? FOR UPDATE`, productID).Scan(&exists).Error; err != nil {
|
||||||
return fmt.Errorf("erreur mise à jour stock: %w", result.Error)
|
return fmt.Errorf("erreur verrouillage produit: %w", err)
|
||||||
}
|
}
|
||||||
if result.RowsAffected == 0 {
|
if exists == 0 {
|
||||||
return fmt.Errorf("produit non trouvé")
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,11 +19,3 @@ type DeliveryPersonStatus struct {
|
|||||||
CurrentCommand int `json:"current_command,omitempty"`
|
CurrentCommand int `json:"current_command,omitempty"`
|
||||||
LastUpdate time.Time `json:"last_update"`
|
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"`
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -17,8 +17,6 @@ func StartRedisWorkers(database *db.Database) {
|
|||||||
|
|
||||||
go AutoAssignWorker(database)
|
go AutoAssignWorker(database)
|
||||||
|
|
||||||
go StockCleanupWorker(database)
|
|
||||||
|
|
||||||
go PointsSyncWorker(database)
|
go PointsSyncWorker(database)
|
||||||
|
|
||||||
log.Println("✅ Tous les workers Redis sont démarrés")
|
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
|
// WORKER SYNCHRONISATION POINTS
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|||||||
Reference in New Issue
Block a user