431 lines
13 KiB
Go
431 lines
13 KiB
Go
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)
|
||
}
|
||
|
||
// ✅ 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)
|
||
// ✅ MODIFIÉ: Position dans la queue pour info seulement
|
||
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")
|
||
|
||
log.Printf("✅ Commande %d -> Queue %s (pos: %d%s, ETA trajet: %d min)",
|
||
commandID, deliveryman, currentQueueSize+1, limitInfo, totalETA)
|
||
|
||
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()
|
||
|
||
// ✅ MODIFIÉ: Calculer le temps de trajet direct depuis la position du livreur
|
||
travelTime := d.CalculateETAForDeliveryman(deliveryman, queueItem.Lat, queueItem.Lng)
|
||
|
||
// ✅ ETA = temps de trajet direct uniquement
|
||
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)
|
||
}
|
||
|
||
// ============================================
|
||
// ✅ CORRECTION: Mettre à jour livreur_assign dans la DB
|
||
// ============================================
|
||
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
|
||
// 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
|
||
}
|