Files
projet_gestion_commande/backend/gestion/db/redis_queue_management.go
T
2026-05-13 19:03:33 +02:00

323 lines
9.4 KiB
Go

package db
import (
"encoding/json"
"fmt"
"gestion/models"
"log"
"strconv"
"time"
"github.com/redis/go-redis/v9"
)
// UpdateDeliverymanStatusBasedOnQueue met à jour automatiquement le statut
func (d *Database) UpdateDeliverymanStatusBasedOnQueue(deliveryman string) error {
queueKey := fmt.Sprintf("queue:deliveryman:%s", deliveryman)
queueSize, err := Redis.ZCard(RedisCtx, queueKey).Result()
if err != nil {
return fmt.Errorf("erreur récupération taille queue: %w", err)
}
statusKey := fmt.Sprintf("delivery:status:%s", deliveryman)
data, err := Redis.Get(RedisCtx, statusKey).Result()
if err != nil {
log.Printf("⚠️ [STATUS] Livreur %s n'a pas de statut Redis", deliveryman)
return nil
}
var status models.DeliveryPersonStatus
if err := json.Unmarshal([]byte(data), &status); err != nil {
return err
}
var newStatus string
var currentCommand int
if queueSize >= MAX_COMMANDS_PER_DELIVERYMAN {
newStatus = "busy"
currentCommand = 0
log.Printf("🔴 [STATUS] %s -> BUSY (queue pleine: %d/10)", deliveryman, queueSize)
} else {
if status.Status == "delivering" && status.CurrentCommand > 0 {
newStatus = "delivering"
currentCommand = status.CurrentCommand
log.Printf("🟡 [STATUS] %s -> DELIVERING (queue: %d/10, livraison en cours: cmd %d)",
deliveryman, queueSize, currentCommand)
} else {
newStatus = "available"
currentCommand = 0
log.Printf("🟢 [STATUS] %s -> AVAILABLE (queue: %d/10)", deliveryman, queueSize)
}
}
return d.SetDeliveryPersonStatus(deliveryman, newStatus, currentCommand)
}
// CanDeliverymanAcceptCommands vérifie si un livreur peut accepter de nouvelles commandes
func (d *Database) CanDeliverymanAcceptCommands(deliveryman string) bool {
statusKey := fmt.Sprintf("delivery:status:%s", deliveryman)
data, err := Redis.Get(RedisCtx, statusKey).Result()
if err != nil {
log.Printf("⚠️ [CHECK] Livreur %s sans statut Redis", deliveryman)
return true
}
var status models.DeliveryPersonStatus
if err := json.Unmarshal([]byte(data), &status); err != nil {
return true
}
if status.Status == "offline" {
return false
}
// 3. Vérifier la taille de la queue
queueKey := fmt.Sprintf("queue:deliveryman:%s", deliveryman)
queueSize, _ := Redis.ZCard(RedisCtx, queueKey).Result()
// 4. Si queue >= 10, refuser
if queueSize >= MAX_COMMANDS_PER_DELIVERYMAN {
log.Printf("🔴 [CHECK] %s REFUSÉ: queue pleine (%d/10)", deliveryman, queueSize)
return false
}
log.Printf("🟢 [CHECK] %s AUTORISÉ (%d/10)", deliveryman, queueSize)
return true
}
// AddToDeliverymanQueue - VERSION MISE À JOUR avec auto-update du statut
func (d *Database) AddToDeliverymanQueue(deliveryman string, queueItem models.CommandQueue) error {
data, err := json.Marshal(queueItem)
if err != nil {
return fmt.Errorf("erreur serialisation: %w", err)
}
commandKey := fmt.Sprintf("queue:pending:%d", queueItem.CommandID)
Redis.Set(RedisCtx, commandKey, data, 24*time.Hour)
queueKey := fmt.Sprintf("queue:deliveryman:%s", deliveryman)
score := float64(time.Now().Unix())
err = Redis.ZAdd(RedisCtx, queueKey, redis.Z{
Score: score,
Member: queueItem.CommandID,
}).Err()
if err != nil {
return fmt.Errorf("erreur ajout queue Redis: %w", err)
}
counterKey := fmt.Sprintf("queue:deliveryman:%s:count", deliveryman)
Redis.Incr(RedisCtx, counterKey)
go d.UpdateDeliverymanStatusBasedOnQueue(deliveryman)
return nil
}
// AddToGeneralQueue ajoute une commande à la queue générale (fallback)
func (d *Database) AddToGeneralQueue(queueItem models.CommandQueue) error {
data, err := json.Marshal(queueItem)
if err != nil {
return fmt.Errorf("erreur serialisation: %w", err)
}
score := float64(time.Now().Unix())
key := fmt.Sprintf("queue:pending:%d", queueItem.CommandID)
pipe := Redis.Pipeline()
pipe.Set(RedisCtx, key, data, 24*time.Hour)
pipe.ZAdd(RedisCtx, "queue:pending:sorted", redis.Z{
Score: score,
Member: queueItem.CommandID,
})
_, err = pipe.Exec(RedisCtx)
if err != nil {
return fmt.Errorf("erreur ajout queue générale: %w", err)
}
log.Printf("📥 Commande %d ajoutée à la queue générale", queueItem.CommandID)
return nil
}
// RemoveCommandFromQueue - VERSION AMÉLIORÉE avec auto-update du statut
func (d *Database) RemoveCommandFromQueue(commandID int) error {
key := fmt.Sprintf("queue:pending:%d", commandID)
commandIDStr := strconv.Itoa(commandID)
pipe := Redis.Pipeline()
pipe.Del(RedisCtx, key)
pipe.ZRem(RedisCtx, "queue:pending:sorted", commandIDStr)
pipe.ZRem(RedisCtx, "queue:priority:sorted", commandIDStr)
// Trouver et retirer de la queue du livreur
keys, _ := Redis.Keys(RedisCtx, "queue:deliveryman:*").Result()
var affectedDeliveryman string
for _, queueKey := range keys {
if len(queueKey) > 6 && queueKey[len(queueKey)-6:] == ":count" {
continue
}
// Vérifier si la commande est dans cette queue
_, err := Redis.ZRank(RedisCtx, queueKey, commandIDStr).Result()
if err == nil {
// Commande trouvée dans cette queue
affectedDeliveryman = queueKey[len("queue:deliveryman:"):]
pipe.ZRem(RedisCtx, queueKey, commandIDStr)
// Décrémenter le compteur
counterKey := fmt.Sprintf("queue:deliveryman:%s:count", affectedDeliveryman)
pipe.Decr(RedisCtx, counterKey)
break
}
}
_, err := pipe.Exec(RedisCtx)
if err != nil {
return fmt.Errorf("erreur suppression: %w", err)
}
log.Printf("✅ Commande %d retirée de la file", commandID)
// ✅ NOUVEAU: Mettre à jour le statut si un livreur était affecté
if affectedDeliveryman != "" {
go d.UpdateDeliverymanStatusBasedOnQueue(affectedDeliveryman)
}
return nil
}
func (d *Database) GetNextCommandInQueue() (*models.CommandQueue, error) {
normalResults, err := Redis.ZRangeWithScores(RedisCtx, "queue:pending:sorted", 0, 0).Result()
if err != nil || len(normalResults) == 0 {
return nil, fmt.Errorf("aucune commande en attente")
}
commandID := extractCommandID(normalResults[0].Member)
if commandID <= 0 {
return nil, fmt.Errorf("ID commande invalide")
}
key := fmt.Sprintf("queue:pending:%d", commandID)
data, err := Redis.Get(RedisCtx, key).Result()
if err != nil {
return nil, fmt.Errorf("commande introuvable: %w", err)
}
var queue models.CommandQueue
if err := json.Unmarshal([]byte(data), &queue); err != nil {
return nil, fmt.Errorf("erreur désérialisation: %w", err)
}
return &queue, nil
}
func (d *Database) SetDeliveryPersonStatus(username, status string, commandID int) error {
key := fmt.Sprintf("delivery:status:%s", username)
statusData := models.DeliveryPersonStatus{
Username: username,
Status: status,
CurrentCommand: commandID,
LastUpdate: time.Now(),
}
data, _ := json.Marshal(statusData)
err := Redis.Set(RedisCtx, key, data, 24*time.Hour).Err()
if err == nil {
log.Printf("✅ Statut livreur %s: %s", username, status)
}
return err
}
// GetAvailableDeliveryPersonsRedis récupère les livreurs disponibles (LEGACY)
func (d *Database) GetAvailableDeliveryPersonsRedis() ([]models.DeliveryPersonStatus, error) {
var available []models.DeliveryPersonStatus
err := d.iterDeliveryStatuses(func(s models.DeliveryPersonStatus) {
if s.Status == "available" {
available = append(available, s)
}
})
return available, err
}
// GetAllActiveDeliveryPersons retourne les livreurs actifs pouvant accepter des commandes
func (d *Database) GetAllActiveDeliveryPersons() ([]models.DeliveryPersonStatus, error) {
var active []models.DeliveryPersonStatus
err := d.iterDeliveryStatuses(func(s models.DeliveryPersonStatus) {
if s.Status != "offline" && d.CanDeliverymanAcceptCommands(s.Username) {
active = append(active, s)
}
})
return active, err
}
// CountActiveDeliverymen compte le nombre de livreurs actifs (non offline)
func (d *Database) CountActiveDeliverymen() (int, error) {
count := 0
err := d.iterDeliveryStatuses(func(s models.DeliveryPersonStatus) {
if s.Status != "offline" {
count++
}
})
return count, err
}
func (d *Database) GetSingleActiveDeliveryman() (string, error) {
found := ""
d.iterDeliveryStatuses(func(s models.DeliveryPersonStatus) { //nolint
if found == "" && s.Status != "offline" {
found = s.Username
}
})
if found == "" {
return "", fmt.Errorf("aucun livreur actif trouvé")
}
return found, nil
}
// GetAllActiveDeliverymenUsernames retourne les usernames de tous les livreurs actifs
func (d *Database) GetAllActiveDeliverymenUsernames() ([]string, error) {
var usernames []string
err := d.iterDeliveryStatuses(func(s models.DeliveryPersonStatus) {
if s.Status != "offline" {
usernames = append(usernames, s.Username)
}
})
return usernames, err
}
// SyncAllDeliverymanStatuses synchronise tous les statuts (à appeler au démarrage)
func (d *Database) SyncAllDeliverymanStatuses() error {
log.Println("🔄 [SYNC] Synchronisation des statuts livreurs...")
return d.iterDeliveryStatuses(func(s models.DeliveryPersonStatus) {
d.UpdateDeliverymanStatusBasedOnQueue(s.Username)
})
}
// iterDeliveryStatuses itère sur tous les statuts Redis des livreurs et appelle fn pour chacun.
func (d *Database) iterDeliveryStatuses(fn func(models.DeliveryPersonStatus)) error {
keys, err := Redis.Keys(RedisCtx, "delivery:status:*").Result()
if err != nil {
return err
}
for _, key := range keys {
data, err := Redis.Get(RedisCtx, key).Result()
if err != nil {
continue
}
var status models.DeliveryPersonStatus
if err := json.Unmarshal([]byte(data), &status); err != nil {
continue
}
fn(status)
}
return nil
}