344 lines
10 KiB
Go
344 lines
10 KiB
Go
package db
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"gestion/models"
|
|
"gestion/services"
|
|
"log"
|
|
"math"
|
|
"sort"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/redis/go-redis/v9"
|
|
)
|
|
|
|
// CompleteDeliveryAndProcessNext marque une livraison comme terminée et optimise la queue
|
|
func (d *Database) CompleteDeliveryAndProcessNext(deliveryman string, completedCommandID int) error {
|
|
queueKey := fmt.Sprintf("queue:deliveryman:%s", deliveryman)
|
|
|
|
// Retirer la commande complétée de la queue
|
|
Redis.ZRem(RedisCtx, queueKey, strconv.Itoa(completedCommandID))
|
|
|
|
// Supprimer les données de la commande
|
|
commandKey := fmt.Sprintf("queue:pending:%d", completedCommandID)
|
|
Redis.Del(RedisCtx, commandKey)
|
|
|
|
// Supprimer le cache de destination
|
|
destCacheKey := fmt.Sprintf("command:destination:%d", completedCommandID)
|
|
Redis.Del(RedisCtx, destCacheKey)
|
|
|
|
// Supprimer l'ETA
|
|
etaKey := fmt.Sprintf("command:eta:%d", completedCommandID)
|
|
Redis.Del(RedisCtx, etaKey)
|
|
|
|
// Décrémenter le compteur
|
|
counterKey := fmt.Sprintf("queue:deliveryman:%s:count", deliveryman)
|
|
Redis.Decr(RedisCtx, counterKey)
|
|
|
|
log.Printf("✅ Livraison %d complétée par %s", completedCommandID, deliveryman)
|
|
|
|
// Vérifier s'il reste des commandes
|
|
remainingCount, _ := Redis.ZCard(RedisCtx, queueKey).Result()
|
|
|
|
if remainingCount == 0 {
|
|
d.SetDeliveryPersonStatus(deliveryman, "available", 0)
|
|
log.Printf("🔓 Livreur %s libéré - Plus de commandes en queue", deliveryman)
|
|
|
|
// Chercher dans la queue générale
|
|
go d.ProcessNextCommandInQueue(deliveryman)
|
|
return nil
|
|
}
|
|
|
|
// ============================================
|
|
// 🔄 OPTIMISATION PAR PROXIMITÉ
|
|
// ============================================
|
|
log.Printf("🔄 Optimisation queue de %s: %d commande(s) restante(s)", deliveryman, remainingCount)
|
|
|
|
err := d.OptimizeDeliverymanQueueByProximity(deliveryman)
|
|
if err != nil {
|
|
log.Printf("⚠️ Erreur optimisation queue: %v", err)
|
|
// Fallback: recalculer les ETAs sans réorganiser
|
|
d.RecalculateQueueETAs(deliveryman)
|
|
}
|
|
|
|
// Récupérer la prochaine commande (maintenant la plus proche)
|
|
nextCommand, nextETA, err := d.FindNearestCommandInQueue(deliveryman)
|
|
if err == nil && nextCommand != nil {
|
|
d.SetDeliveryPersonStatus(deliveryman, "busy", nextCommand.CommandID)
|
|
log.Printf("📍 Prochaine livraison: Commande %d (ETA: %d min)", nextCommand.CommandID, nextETA)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *Database) OptimizeDeliverymanQueueByProximity(deliveryman string) error {
|
|
livreurLat, livreurLng, err := d.GetDeliveryPersonLocation(deliveryman)
|
|
if err != nil {
|
|
log.Printf("⚠️ Position livreur %s non disponible, recalcul ETAs simple", deliveryman)
|
|
return d.RecalculateQueueETAs(deliveryman)
|
|
}
|
|
|
|
queueKey := fmt.Sprintf("queue:deliveryman:%s", deliveryman)
|
|
|
|
commandIDs, err := Redis.ZRange(RedisCtx, queueKey, 0, -1).Result()
|
|
if err != nil || len(commandIDs) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Collecter les informations de chaque commande avec sa distance
|
|
var commandsWithDistance []CommandWithDistance
|
|
|
|
for _, cmdIDStr := range commandIDs {
|
|
commandID := extractCommandID(cmdIDStr)
|
|
if commandID <= 0 {
|
|
continue
|
|
}
|
|
|
|
// 1. Essayer de récupérer depuis queue:pending:{id}
|
|
commandKey := fmt.Sprintf("queue:pending:%d", commandID)
|
|
data, err := Redis.Get(RedisCtx, commandKey).Result()
|
|
|
|
var lat, lng float64
|
|
var address string
|
|
|
|
if err == nil && data != "" {
|
|
var queueItem models.CommandQueue
|
|
if err := json.Unmarshal([]byte(data), &queueItem); err == nil {
|
|
lat = queueItem.Lat
|
|
lng = queueItem.Lng
|
|
address = queueItem.Address
|
|
}
|
|
}
|
|
|
|
// 2. Si coordonnées à 0, essayer le cache command:destination:{id}
|
|
if lat == 0 && lng == 0 {
|
|
destCacheKey := fmt.Sprintf("command:destination:%d", commandID)
|
|
destData, err := Redis.Get(RedisCtx, destCacheKey).Result()
|
|
if err == nil && destData != "" {
|
|
var coords struct {
|
|
Lat float64 `json:"lat"`
|
|
Lon float64 `json:"lon"`
|
|
}
|
|
if err := json.Unmarshal([]byte(destData), &coords); err == nil {
|
|
lat = coords.Lat
|
|
lng = coords.Lon
|
|
log.Printf("📍 Coordonnées récupérées depuis cache destination pour commande %d: (%.6f, %.6f)", commandID, lat, lng)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 3. Si toujours 0, récupérer depuis la DB
|
|
if lat == 0 && lng == 0 {
|
|
command, err := d.GetCommandByID(commandID)
|
|
if err == nil {
|
|
if dLat, ok := command["dest_latitude"].(float64); ok && dLat != 0 {
|
|
lat = dLat
|
|
}
|
|
if dLng, ok := command["dest_longitude"].(float64); ok && dLng != 0 {
|
|
lng = dLng
|
|
}
|
|
if address == "" {
|
|
if addr, ok := command["adresse"].(string); ok {
|
|
address = addr
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 4. Si toujours 0, utiliser une distance très grande
|
|
if lat == 0 && lng == 0 {
|
|
log.Printf("⚠️ Coordonnées non disponibles pour commande %d, utilisation position par défaut", commandID)
|
|
commandsWithDistance = append(commandsWithDistance, CommandWithDistance{
|
|
CommandID: commandID,
|
|
Address: address,
|
|
Lat: 0,
|
|
Lng: 0,
|
|
Distance: 999999,
|
|
EstimatedETA: 120,
|
|
QueueItem: models.CommandQueue{
|
|
CommandID: commandID,
|
|
Address: address,
|
|
Lat: 0,
|
|
Lng: 0,
|
|
},
|
|
})
|
|
continue
|
|
}
|
|
|
|
// ✅ MODIFIÉ: Distance depuis la position ACTUELLE du livreur (pas chaînée)
|
|
distance := d.CalculateDistanceBetweenPoints(livreurLat, livreurLng, lat, lng)
|
|
|
|
commandsWithDistance = append(commandsWithDistance, CommandWithDistance{
|
|
CommandID: commandID,
|
|
Address: address,
|
|
Lat: lat,
|
|
Lng: lng,
|
|
Distance: distance,
|
|
EstimatedETA: services.CalculateETA(distance),
|
|
QueueItem: models.CommandQueue{
|
|
CommandID: commandID,
|
|
Address: address,
|
|
Lat: lat,
|
|
Lng: lng,
|
|
},
|
|
})
|
|
}
|
|
|
|
if len(commandsWithDistance) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Trier par distance (la plus proche en premier)
|
|
sort.Slice(commandsWithDistance, func(i, j int) bool {
|
|
return commandsWithDistance[i].Distance < commandsWithDistance[j].Distance
|
|
})
|
|
|
|
log.Printf("🔄 Optimisation queue de %s: %d commandes triées par proximité", deliveryman, len(commandsWithDistance))
|
|
|
|
// Vider la queue actuelle
|
|
Redis.Del(RedisCtx, queueKey)
|
|
Redis.Del(RedisCtx, fmt.Sprintf("queue:deliveryman:%s:count", deliveryman))
|
|
|
|
// ✅ MODIFIÉ: Recréer la queue avec ETA = distance directe depuis position livreur
|
|
for i, cmd := range commandsWithDistance {
|
|
var travelTime int
|
|
var distance float64
|
|
|
|
if cmd.Lat != 0 && cmd.Lng != 0 {
|
|
// ✅ Distance depuis la position ACTUELLE du livreur (pas cumulative)
|
|
distance = d.CalculateDistanceBetweenPoints(livreurLat, livreurLng, cmd.Lat, cmd.Lng)
|
|
travelTime = services.CalculateETA(distance)
|
|
} else {
|
|
distance = 0
|
|
travelTime = 10
|
|
}
|
|
|
|
// ✅ ETA = temps de trajet direct uniquement
|
|
cmd.QueueItem.EstimatedETA = travelTime
|
|
|
|
score := float64(i + 1)
|
|
|
|
data, _ := json.Marshal(cmd.QueueItem)
|
|
commandKey := fmt.Sprintf("queue:pending:%d", cmd.CommandID)
|
|
Redis.Set(RedisCtx, commandKey, data, 24*time.Hour)
|
|
|
|
Redis.ZAdd(RedisCtx, queueKey, redis.Z{
|
|
Score: score,
|
|
Member: cmd.CommandID,
|
|
})
|
|
|
|
d.SetCommandETAWithDetails(cmd.CommandID, travelTime, i+1)
|
|
|
|
log.Printf(" 📍 Position %d: Commande %d - %.2f km - ETA trajet: %d min",
|
|
i+1, cmd.CommandID, distance, travelTime)
|
|
}
|
|
|
|
Redis.Set(RedisCtx, fmt.Sprintf("queue:deliveryman:%s:count", deliveryman), len(commandsWithDistance), 0)
|
|
|
|
log.Printf("✅ Queue de %s optimisée: %d commandes réorganisées par proximité", deliveryman, len(commandsWithDistance))
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *Database) RecalculateQueueETAs(deliveryman string) error {
|
|
queueKey := fmt.Sprintf("queue:deliveryman:%s", deliveryman)
|
|
|
|
commandIDs, err := Redis.ZRange(RedisCtx, queueKey, 0, -1).Result()
|
|
if err != nil || len(commandIDs) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// ✅ Récupérer la position actuelle du livreur
|
|
livreurLat, livreurLng, err := d.GetDeliveryPersonLocation(deliveryman)
|
|
if err != nil {
|
|
log.Printf("⚠️ Position livreur %s non disponible pour recalcul ETA", deliveryman)
|
|
return err
|
|
}
|
|
|
|
for i, cmdIDStr := range commandIDs {
|
|
commandID := extractCommandID(cmdIDStr)
|
|
if commandID <= 0 {
|
|
continue
|
|
}
|
|
|
|
commandKey := fmt.Sprintf("queue:pending:%d", commandID)
|
|
data, err := Redis.Get(RedisCtx, commandKey).Result()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
var queueItem models.CommandQueue
|
|
if err := json.Unmarshal([]byte(data), &queueItem); err != nil {
|
|
continue
|
|
}
|
|
|
|
// ✅ MODIFIÉ: ETA = distance directe depuis position livreur
|
|
travelTime := d.CalculateETABetweenPoints(livreurLat, livreurLng, queueItem.Lat, queueItem.Lng)
|
|
|
|
d.SetCommandETAWithDetails(commandID, travelTime, i+1)
|
|
|
|
queueItem.EstimatedETA = travelTime
|
|
updatedData, _ := json.Marshal(queueItem)
|
|
Redis.Set(RedisCtx, commandKey, updatedData, 24*time.Hour)
|
|
}
|
|
|
|
log.Printf("🔄 ETAs recalculés pour %d commandes de %s (trajet direct)", len(commandIDs), deliveryman)
|
|
return nil
|
|
}
|
|
|
|
func (d *Database) UpdateQueueETAsAfterCompletion(deliveryman string) error {
|
|
return d.RecalculateQueueETAs(deliveryman)
|
|
}
|
|
|
|
// FindNearestCommandInQueue trouve la commande la plus proche du livreur
|
|
func (d *Database) FindNearestCommandInQueue(deliveryman string) (*models.CommandQueue, int, error) {
|
|
livreurLat, livreurLng, err := d.GetDeliveryPersonLocation(deliveryman)
|
|
if err != nil {
|
|
return nil, 0, fmt.Errorf("position livreur non disponible: %w", err)
|
|
}
|
|
|
|
queueKey := fmt.Sprintf("queue:deliveryman:%s", deliveryman)
|
|
commandIDs, err := Redis.ZRange(RedisCtx, queueKey, 0, -1).Result()
|
|
if err != nil || len(commandIDs) == 0 {
|
|
return nil, 0, fmt.Errorf("queue vide")
|
|
}
|
|
|
|
var nearestCommand *models.CommandQueue
|
|
var nearestDistance float64 = math.MaxFloat64
|
|
var nearestETA int
|
|
|
|
for _, cmdIDStr := range commandIDs {
|
|
commandID := extractCommandID(cmdIDStr)
|
|
if commandID <= 0 {
|
|
continue
|
|
}
|
|
|
|
commandKey := fmt.Sprintf("queue:pending:%d", commandID)
|
|
data, err := Redis.Get(RedisCtx, commandKey).Result()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
var queueItem models.CommandQueue
|
|
if err := json.Unmarshal([]byte(data), &queueItem); err != nil {
|
|
continue
|
|
}
|
|
|
|
distance := d.CalculateDistanceBetweenPoints(livreurLat, livreurLng, queueItem.Lat, queueItem.Lng)
|
|
|
|
if distance < nearestDistance {
|
|
nearestDistance = distance
|
|
nearestCommand = &queueItem
|
|
nearestETA = services.CalculateETA(distance)
|
|
}
|
|
}
|
|
|
|
if nearestCommand == nil {
|
|
return nil, 0, fmt.Errorf("aucune commande trouvée")
|
|
}
|
|
|
|
return nearestCommand, nearestETA, nil
|
|
}
|