267 lines
7.8 KiB
Go
267 lines
7.8 KiB
Go
// ============================================
|
||
// handlers/eta_handler_corrected.go
|
||
// CORRECTION: ETA visible UNIQUEMENT après en_route
|
||
// ============================================
|
||
|
||
package handlers
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"gestion/db"
|
||
"gestion/services"
|
||
"log"
|
||
"net/http"
|
||
"strconv"
|
||
"time"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
)
|
||
|
||
// ============================================
|
||
// GET /api/v1/orders/:id/eta
|
||
// ✅ CORRECTION: ETA visible UNIQUEMENT si status >= en_route
|
||
// ============================================
|
||
|
||
func GetOrderETA(c *gin.Context) {
|
||
database := c.MustGet("database").(*db.Database)
|
||
geoService := c.MustGet("geoService").(*services.GeoService)
|
||
|
||
// 1️⃣ AUTHENTIFICATION
|
||
username, exists := c.Get("username")
|
||
if !exists {
|
||
log.Printf("❌ [ETA] Non authentifié")
|
||
c.JSON(http.StatusUnauthorized, gin.H{
|
||
"success": false,
|
||
"error": "Utilisateur non authentifié",
|
||
})
|
||
return
|
||
}
|
||
|
||
// 2️⃣ RÉCUPÉRER L'ID DE LA COMMANDE
|
||
commandID, err := strconv.Atoi(c.Param("id"))
|
||
if err != nil {
|
||
log.Printf("❌ [ETA] ID invalide: %v", err)
|
||
c.JSON(http.StatusBadRequest, gin.H{
|
||
"success": false,
|
||
"error": "ID de commande invalide",
|
||
})
|
||
return
|
||
}
|
||
|
||
log.Printf("📊 [ETA] START - commandID=%d, username=%s", commandID, username.(string))
|
||
|
||
// 3️⃣ RÉCUPÉRER LA COMMANDE
|
||
command, err := database.GetCommandByID(commandID)
|
||
if err != nil {
|
||
log.Printf("❌ [ETA] Commande %d non trouvée", commandID)
|
||
c.JSON(http.StatusNotFound, gin.H{
|
||
"success": false,
|
||
"error": "Commande non trouvée",
|
||
})
|
||
return
|
||
}
|
||
|
||
// 4️⃣ VÉRIFIER LES DROITS D'ACCÈS
|
||
cmdUsername, _ := command["username"].(string)
|
||
userRole := c.GetString("role")
|
||
|
||
if userRole != "admin" {
|
||
if userRole == "client" && cmdUsername != username.(string) {
|
||
log.Printf("❌ [ETA] Accès refusé - commande appartient à %s", cmdUsername)
|
||
c.JSON(http.StatusForbidden, gin.H{
|
||
"success": false,
|
||
"error": "Vous n'avez pas accès à cette commande",
|
||
})
|
||
return
|
||
}
|
||
|
||
if userRole == "livreur" {
|
||
livreurAssign, _ := command["livreur_assign"].(string)
|
||
if livreurAssign != username.(string) {
|
||
log.Printf("❌ [ETA] Accès refusé - livreur non assigné")
|
||
c.JSON(http.StatusForbidden, gin.H{
|
||
"success": false,
|
||
"error": "Vous n'avez pas accès à cette commande",
|
||
})
|
||
return
|
||
}
|
||
}
|
||
}
|
||
|
||
// 5️⃣ VÉRIFIER LE STATUT DE LA COMMANDE
|
||
cmdStatus, _ := command["status"].(string)
|
||
|
||
// ✅ CORRECTION: Vérifier si commande terminée
|
||
if cmdStatus == "livre" || cmdStatus == "delivered" || cmdStatus == "approved" {
|
||
log.Printf("ℹ️ [ETA] Commande déjà %s - pas d'ETA applicable", cmdStatus)
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": true,
|
||
"command_id": commandID,
|
||
"status": cmdStatus,
|
||
"message": "La commande a déjà été livrée",
|
||
"eta_available": false,
|
||
"estimated_arrival": "Livraison complétée",
|
||
})
|
||
return
|
||
}
|
||
|
||
// Pour pending: aucune estimation disponible
|
||
if cmdStatus == "pending" {
|
||
log.Printf("⏳ [ETA] Commande en attente d'assignation - pas d'ETA")
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": true,
|
||
"command_id": commandID,
|
||
"status": cmdStatus,
|
||
"eta_available": false,
|
||
"message": "En attente d'assignation d'un livreur",
|
||
})
|
||
return
|
||
}
|
||
// Pour assigned/en_route/arrived: calcul ETA réel via position du livreur
|
||
|
||
// 6️⃣ VÉRIFIER LE CACHE REDIS POUR ETA
|
||
etaKey := fmt.Sprintf("command:eta:%d", commandID)
|
||
etaData, err := db.Redis.HGetAll(db.RedisCtx, etaKey).Result()
|
||
|
||
if err == nil && len(etaData) > 0 {
|
||
// ETA existe, vérifier s'il est récent
|
||
if updatedAtStr, ok := etaData["updated_at"]; ok {
|
||
var updatedAt int64
|
||
fmt.Sscanf(updatedAtStr, "%d", &updatedAt)
|
||
|
||
timeSinceUpdate := time.Since(time.Unix(updatedAt, 0))
|
||
if timeSinceUpdate < 30*time.Second {
|
||
// Cache valide
|
||
var etaMinutes int64
|
||
if etaStr, ok := etaData["eta_minutes"]; ok {
|
||
fmt.Sscanf(etaStr, "%d", &etaMinutes)
|
||
}
|
||
|
||
var arrivalTime int64
|
||
if arrivalStr, ok := etaData["arrival_time"]; ok {
|
||
fmt.Sscanf(arrivalStr, "%d", &arrivalTime)
|
||
}
|
||
|
||
log.Printf("✅ [ETA] Cache hit - ETA: %d min", etaMinutes)
|
||
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": true,
|
||
"command_id": commandID,
|
||
"status": cmdStatus,
|
||
"eta_minutes": etaMinutes,
|
||
"estimated_arrival": time.Unix(arrivalTime, 0).Format("15:04"),
|
||
"eta_available": true,
|
||
"with_traffic": true,
|
||
"livreur_assign": command["livreur_assign"],
|
||
"delivery_address": command["adresse"],
|
||
})
|
||
return
|
||
}
|
||
}
|
||
}
|
||
|
||
// 7️⃣ Pas de cache valide - Recalculer l'ETA
|
||
log.Printf("🔄 [ETA] Cache miss ou expiré - Recalcul de l'ETA...")
|
||
|
||
// Récupérer coordonnées destination
|
||
var destLat, destLon float64
|
||
|
||
destCacheKey := fmt.Sprintf("command:destination:%d", commandID)
|
||
destData, err := db.Redis.Get(db.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 && coords.Lat != 0 && coords.Lon != 0 {
|
||
destLat = coords.Lat
|
||
destLon = coords.Lon
|
||
log.Printf("📍 Destination depuis cache Redis: (%.6f, %.6f)", destLat, destLon)
|
||
}
|
||
}
|
||
|
||
if destLat == 0 || destLon == 0 {
|
||
if dLat, okLat := command["dest_latitude"].(float64); okLat && dLat != 0 {
|
||
destLat = dLat
|
||
}
|
||
if dLon, okLon := command["dest_longitude"].(float64); okLon && dLon != 0 {
|
||
destLon = dLon
|
||
}
|
||
}
|
||
|
||
if destLat == 0 || destLon == 0 {
|
||
log.Printf("❌ [ETA] Coordonnées destination manquantes")
|
||
c.JSON(http.StatusBadRequest, gin.H{
|
||
"success": false,
|
||
"error": "Coordonnées de destination manquantes",
|
||
})
|
||
return
|
||
}
|
||
|
||
// Récupérer position du livreur
|
||
livreurAssign, _ := command["livreur_assign"].(string)
|
||
if livreurAssign == "" {
|
||
log.Printf("⚠️ [ETA] Aucun livreur assigné")
|
||
c.JSON(http.StatusBadRequest, gin.H{
|
||
"success": false,
|
||
"error": "Aucun livreur assigné à cette commande",
|
||
})
|
||
return
|
||
}
|
||
|
||
livreurLocation, err := geoService.GetDeliveryPersonLocation(livreurAssign)
|
||
if err != nil {
|
||
log.Printf("❌ [ETA] Position livreur introuvable: %s", livreurAssign)
|
||
c.JSON(http.StatusNotFound, gin.H{
|
||
"success": false,
|
||
"error": "Position du livreur non disponible",
|
||
})
|
||
return
|
||
}
|
||
|
||
toCoords := services.Coordinates{Latitude: destLat, Longitude: destLon}
|
||
|
||
// Calculer ETA avec TomTom
|
||
log.Printf("🛣️ [ETA] Calcul TomTom: (%.6f, %.6f) -> (%.6f, %.6f)",
|
||
livreurLocation.Latitude, livreurLocation.Longitude, toCoords.Latitude, toCoords.Longitude)
|
||
|
||
etaMinutes, distanceKm, err := services.GetETAWithTraffic(*livreurLocation, toCoords)
|
||
if err != nil {
|
||
log.Printf("⚠️ [ETA] TomTom failed, fallback local: %v", err)
|
||
distanceKm = services.CalculateDistance(*livreurLocation, toCoords)
|
||
etaMinutes = services.CalculateETA(distanceKm)
|
||
}
|
||
|
||
// Sauvegarder en cache
|
||
now := time.Now()
|
||
arrivalTime := now.Add(time.Duration(etaMinutes) * time.Minute)
|
||
|
||
etaCache := map[string]interface{}{
|
||
"command_id": commandID,
|
||
"eta_minutes": etaMinutes,
|
||
"updated_at": now.Unix(),
|
||
"arrival_time": arrivalTime.Unix(),
|
||
"distance_km": distanceKm,
|
||
"with_traffic": err == nil,
|
||
}
|
||
|
||
db.Redis.HSet(db.RedisCtx, etaKey, etaCache)
|
||
db.Redis.Expire(db.RedisCtx, etaKey, 4*time.Hour)
|
||
|
||
log.Printf("✅ [ETA] SUCCESS - ETA: %d min, arrivée: %s", etaMinutes, arrivalTime.Format("15:04"))
|
||
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": true,
|
||
"command_id": commandID,
|
||
"status": cmdStatus,
|
||
"eta_minutes": etaMinutes,
|
||
"distance_km": fmt.Sprintf("%.2f", distanceKm),
|
||
"estimated_arrival": arrivalTime.Format("15:04"),
|
||
"eta_available": true,
|
||
"with_traffic": err == nil,
|
||
"livreur_assign": livreurAssign,
|
||
"delivery_address": command["adresse"],
|
||
})
|
||
}
|