Files
projet_gestion_commande/backend/gestion/db/redis_queue_assignment.go
T
2026-04-20 19:41:52 +02:00

420 lines
13 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package db
import (
"encoding/json"
"fmt"
"gestion/models"
"log"
"strconv"
"time"
)
// AssignCommandToDeliverymanQueue assigne une commande à la queue d'un livreur
func (d *Database) AssignCommandToDeliverymanQueue(commandID int, deliveryman string, estimatedTravelTime int) error {
command, err := d.GetCommandByID(commandID)
if err != nil {
return fmt.Errorf("commande introuvable: %w", err)
}
activeCount, _ := d.CountActiveDeliverymen()
queueKey := fmt.Sprintf("queue:deliveryman:%s", deliveryman)
currentQueueSize, _ := Redis.ZCard(RedisCtx, queueKey).Result()
// Si plusieurs livreurs, appliquer la limite de 10
if activeCount > 1 && currentQueueSize >= MAX_COMMANDS_PER_DELIVERYMAN {
return fmt.Errorf("livreur %s a atteint le maximum de commandes (%d)", deliveryman, MAX_COMMANDS_PER_DELIVERYMAN)
}
totalETA := estimatedTravelTime
var lat, lng float64
if command["dest_latitude"] != nil {
if latVal, ok := command["dest_latitude"].(float64); ok {
lat = latVal
}
}
if command["dest_longitude"] != nil {
if lngVal, ok := command["dest_longitude"].(float64); ok {
lng = lngVal
}
}
var totalPrice float64
if tp, ok := command["total_prix"].(float64); ok {
totalPrice = tp
}
var address string
if addr, ok := command["delivery_address"].(string); ok {
address = addr
} else if addr, ok := command["adresse"].(string); ok {
address = addr
}
queueItem := models.CommandQueue{
CommandID: commandID,
Username: command["username"].(string),
TotalPrice: totalPrice,
Address: address,
Lat: lat,
Lng: lng,
CreatedAt: time.Now(),
EstimatedETA: totalETA,
}
err = d.AddToDeliverymanQueue(deliveryman, queueItem)
if err != nil {
return fmt.Errorf("erreur ajout à la queue: %w", err)
}
d.UpdateCommandStatus(commandID, "assigned")
d.AssignDeliveryPerson(commandID, deliveryman)
d.SetCommandETAWithDetails(commandID, totalETA, int(currentQueueSize)+1)
limitInfo := ""
if activeCount > 1 {
limitInfo = fmt.Sprintf("/%d", MAX_COMMANDS_PER_DELIVERYMAN)
} else {
limitInfo = " (sans limite - seul livreur)"
}
d.AddCommandLog(commandID, "queued",
fmt.Sprintf("Ajouté à la queue de %s (position: %d%s, ETA trajet: %d min)",
deliveryman, currentQueueSize+1, limitInfo, totalETA),
"system")
return nil
}
// AssignCommandToDeliverymanQueueUnlimited assigne sans limite (pour un seul livreur)
func (d *Database) AssignCommandToDeliverymanQueueUnlimited(deliveryman string, queueItem models.CommandQueue) error {
queueKey := fmt.Sprintf("queue:deliveryman:%s", deliveryman)
currentQueueSize, _ := Redis.ZCard(RedisCtx, queueKey).Result()
travelTime := d.CalculateETAForDeliveryman(deliveryman, queueItem.Lat, queueItem.Lng)
queueItem.EstimatedETA = travelTime
err := d.AddToDeliverymanQueue(deliveryman, queueItem)
if err != nil {
return fmt.Errorf("erreur ajout à la queue: %w", err)
}
d.UpdateCommandStatus(queueItem.CommandID, "assigned")
d.AssignDeliveryPerson(queueItem.CommandID, deliveryman)
d.SetCommandETAWithDetails(queueItem.CommandID, travelTime, int(currentQueueSize)+1)
d.AddCommandLog(queueItem.CommandID, "queued",
fmt.Sprintf("Assigné au seul livreur actif %s (position: %d, ETA trajet: %d min)",
deliveryman, currentQueueSize+1, travelTime),
"system")
log.Printf("✅ Commande %d -> Queue %s (SANS LIMITE - pos: %d, ETA trajet: %d min)",
queueItem.CommandID, deliveryman, currentQueueSize+1, travelTime)
return nil
}
// AssignCommandToDeliverymanQueueWithCoords assigne une commande avec les coordonnées GPS
func (d *Database) AssignCommandToDeliverymanQueueWithCoords(commandID int, deliveryman string, estimatedTravelTime int, lat, lng float64, address string) error {
command, err := d.GetCommandByID(commandID)
if err != nil {
return fmt.Errorf("commande introuvable: %w", err)
}
activeCount, _ := d.CountActiveDeliverymen()
queueKey := fmt.Sprintf("queue:deliveryman:%s", deliveryman)
currentQueueSize, _ := Redis.ZCard(RedisCtx, queueKey).Result()
if activeCount > 1 && currentQueueSize >= MAX_COMMANDS_PER_DELIVERYMAN {
return fmt.Errorf("livreur %s a atteint le maximum de commandes (%d)", deliveryman, MAX_COMMANDS_PER_DELIVERYMAN)
}
// ✅ ETA = temps de trajet direct uniquement
totalETA := estimatedTravelTime
var totalPrice float64
if tp, ok := command["total_prix"].(float64); ok {
totalPrice = tp
}
var username string
if u, ok := command["username"].(string); ok {
username = u
}
queueItem := models.CommandQueue{
CommandID: commandID,
Username: username,
TotalPrice: totalPrice,
Address: address,
Lat: lat,
Lng: lng,
CreatedAt: time.Now(),
EstimatedETA: totalETA,
}
err = d.AddToDeliverymanQueue(deliveryman, queueItem)
if err != nil {
return fmt.Errorf("erreur ajout à la queue: %w", err)
}
updateQuery := `UPDATE commandes
SET livreur_assign = $1,
status = 'assigned',
updated_at = CURRENT_TIMESTAMP
WHERE id = $2`
_, err = d.Exec(updateQuery, deliveryman, commandID)
if err != nil {
log.Printf("❌ Erreur mise à jour livreur_assign: %v", err)
return fmt.Errorf("erreur mise à jour DB: %w", err)
}
log.Printf("✅ DB mise à jour: livreur_assign=%s pour commande %d", deliveryman, commandID)
d.SetCommandETAWithDetails(commandID, totalETA, int(currentQueueSize)+1)
// Sauvegarder dans le cache destination
destCacheKey := fmt.Sprintf("command:destination:%d", commandID)
coordsJSON, _ := json.Marshal(map[string]float64{
"lat": lat,
"lon": lng,
})
Redis.Set(RedisCtx, destCacheKey, coordsJSON, 4*time.Hour)
limitInfo := ""
if activeCount > 1 {
limitInfo = fmt.Sprintf("/%d", MAX_COMMANDS_PER_DELIVERYMAN)
} else {
limitInfo = " (sans limite - seul livreur)"
}
d.AddCommandLog(commandID, "assigned",
fmt.Sprintf("Assigné à %s (position: %d%s, ETA trajet: %d min, coords: %.4f,%.4f)",
deliveryman, currentQueueSize+1, limitInfo, totalETA, lat, lng),
"system")
log.Printf("✅ Commande %d -> Livreur %s (pos: %d%s, ETA trajet: %d min, coords: %.4f,%.4f)",
commandID, deliveryman, currentQueueSize+1, limitInfo, totalETA, lat, lng)
return nil
}
// ForceAssignCommandToDeliverymanWithCoords assigne une commande de force avec coordonnées
func (d *Database) ForceAssignCommandToDeliverymanWithCoords(commandID int, deliveryman string, estimatedTravelTime int, lat, lng float64, address string) error {
command, err := d.GetCommandByID(commandID)
if err != nil {
return fmt.Errorf("commande introuvable: %w", err)
}
queueKey := fmt.Sprintf("queue:deliveryman:%s", deliveryman)
currentQueueSize, _ := Redis.ZCard(RedisCtx, queueKey).Result()
// ✅ MODIFIÉ: ETA = temps de trajet direct uniquement
totalETA := estimatedTravelTime
var totalPrice float64
if tp, ok := command["total_prix"].(float64); ok {
totalPrice = tp
}
var username string
if u, ok := command["username"].(string); ok {
username = u
}
queueItem := models.CommandQueue{
CommandID: commandID,
Username: username,
TotalPrice: totalPrice,
Address: address,
Lat: lat,
Lng: lng,
CreatedAt: time.Now(),
EstimatedETA: totalETA,
}
err = d.AddToDeliverymanQueue(deliveryman, queueItem)
if err != nil {
return fmt.Errorf("erreur ajout à la queue: %w", err)
}
d.UpdateCommandStatus(commandID, "assigned")
d.AssignDeliveryPerson(commandID, deliveryman)
d.SetCommandETAWithDetails(commandID, totalETA, int(currentQueueSize)+1)
// Sauvegarder dans le cache destination
destCacheKey := fmt.Sprintf("command:destination:%d", commandID)
coordsJSON, _ := json.Marshal(map[string]float64{
"lat": lat,
"lon": lng,
})
Redis.Set(RedisCtx, destCacheKey, coordsJSON, 4*time.Hour)
d.AddCommandLog(commandID, "force_queued",
fmt.Sprintf("Assignation forcée à %s (position: %d, ETA trajet: %d min, coords: %.4f,%.4f)",
deliveryman, currentQueueSize+1, totalETA, lat, lng),
"system")
log.Printf("⚠️ FORCE: Commande %d -> Queue %s (pos: %d, ETA trajet: %d min, coords: %.4f,%.4f)",
commandID, deliveryman, currentQueueSize+1, totalETA, lat, lng)
return nil
}
// ForceAssignCommandToDeliveryman assigne une commande à un livreur SANS vérifier la limite de 10
func (d *Database) ForceAssignCommandToDeliveryman(commandID int, deliveryman string, estimatedTravelTime int) error {
command, err := d.GetCommandByID(commandID)
if err != nil {
return fmt.Errorf("commande introuvable: %w", err)
}
queueKey := fmt.Sprintf("queue:deliveryman:%s", deliveryman)
currentQueueSize, _ := Redis.ZCard(RedisCtx, queueKey).Result()
// ✅ MODIFIÉ: ETA = temps de trajet direct uniquement
totalETA := estimatedTravelTime
var lat, lng float64
if command["dest_latitude"] != nil {
if latVal, ok := command["dest_latitude"].(float64); ok {
lat = latVal
}
}
if command["dest_longitude"] != nil {
if lngVal, ok := command["dest_longitude"].(float64); ok {
lng = lngVal
}
}
var totalPrice float64
if tp, ok := command["total_prix"].(float64); ok {
totalPrice = tp
}
var address string
if addr, ok := command["delivery_address"].(string); ok {
address = addr
} else if addr, ok := command["adresse"].(string); ok {
address = addr
}
queueItem := models.CommandQueue{
CommandID: commandID,
Username: command["username"].(string),
TotalPrice: totalPrice,
Address: address,
Lat: lat,
Lng: lng,
CreatedAt: time.Now(),
EstimatedETA: totalETA,
}
err = d.AddToDeliverymanQueue(deliveryman, queueItem)
if err != nil {
return fmt.Errorf("erreur ajout à la queue: %w", err)
}
d.UpdateCommandStatus(commandID, "assigned")
d.AssignDeliveryPerson(commandID, deliveryman)
d.SetCommandETAWithDetails(commandID, totalETA, int(currentQueueSize)+1)
d.AddCommandLog(commandID, "force_queued",
fmt.Sprintf("Assignation forcée à %s (position: %d, ETA trajet: %d min)",
deliveryman, currentQueueSize+1, totalETA),
"system")
log.Printf("⚠️ FORCE: Commande %d -> Queue %s (pos: %d, ETA trajet: %d min)",
commandID, deliveryman, currentQueueSize+1, totalETA)
return nil
}
// AutoAssignCommand assigne automatiquement une commande au premier livreur disponible
func (d *Database) AutoAssignCommand(commandID int) error {
// Récupérer les livreurs disponibles
available, err := d.GetAvailableDeliveryPersonsRedis()
if err != nil || len(available) == 0 {
return fmt.Errorf("aucun livreur disponible")
}
// Prendre le premier livreur
livreur := available[0]
// Assigner dans la DB principale
err = d.AssignDeliveryPerson(commandID, livreur.Username)
if err != nil {
return err
}
// Mettre à jour le statut dans Redis
d.SetDeliveryPersonStatus(livreur.Username, "busy", commandID)
// Retirer de la file d'attente
d.RemoveCommandFromQueue(commandID)
// Définir l'ETA initial
d.SetCommandETA(commandID, 30)
log.Printf("✅ Commande %d auto-assignée à %s", commandID, livreur.Username)
return nil
}
func (d *Database) ProcessNextCommandInQueue(deliveryman string) error {
log.Printf("🔄 Traitement de la prochaine commande pour %s", deliveryman)
// Vérifier d'abord la queue spécifique du livreur
queueKey := fmt.Sprintf("queue:deliveryman:%s", deliveryman)
results, err := Redis.ZRangeWithScores(RedisCtx, queueKey, 0, 0).Result()
if err == nil && len(results) > 0 {
// Une commande est dans sa queue - la traiter
commandID := extractCommandID(results[0].Member)
// Mettre à jour le statut
d.SetDeliveryPersonStatus(deliveryman, "busy", commandID)
log.Printf("✅ Livreur %s traite la commande %d de sa queue", deliveryman, commandID)
return nil
}
// Si aucune commande dans sa queue, chercher dans la queue générale
generalResults, err := Redis.ZRangeWithScores(RedisCtx, "queue:pending:sorted", 0, 0).Result()
if err != nil || len(generalResults) == 0 {
log.Printf("️ Aucune commande en attente pour %s", deliveryman)
return nil
}
commandID := extractCommandID(generalResults[0].Member)
// Assigner la commande
err = d.AssignDeliveryPerson(commandID, deliveryman)
if err != nil {
log.Printf("❌ Erreur assignation commande %d: %v", commandID, err)
return err
}
// Retirer de la queue générale
Redis.ZRem(RedisCtx, "queue:pending:sorted", strconv.Itoa(commandID))
// Mettre à jour le statut
d.SetDeliveryPersonStatus(deliveryman, "busy", commandID)
// Définir un ETA par défaut
d.SetCommandETA(commandID, 30)
// Ajouter log
d.AddCommandLog(commandID, "auto_assigned",
fmt.Sprintf("Assigné automatiquement à %s depuis la queue générale", deliveryman),
"system")
log.Printf("✅ Commande %d assignée automatiquement à %s (queue générale)", commandID, deliveryman)
return nil
}