package handlers import ( "encoding/json" "fmt" "gestion/db" "gestion/services" "log" "net/http" "strconv" "time" "github.com/gin-gonic/gin" ) 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 } 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/assigned: pas encore de position livreur disponible if cmdStatus == "pending" || cmdStatus == "assigned" { log.Printf("⏳ [ETA] Commande %s - pas d'ETA disponible", cmdStatus) c.JSON(http.StatusOK, gin.H{ "success": true, "command_id": commandID, "status": cmdStatus, "eta_available": false, "message": "En attente de démarrage de la livraison", }) return } // Pour arrived: livreur sur place, ETA non pertinent if cmdStatus == "arrived" { log.Printf("ℹ️ [ETA] Commande arrived - livreur déjà sur place") c.JSON(http.StatusOK, gin.H{ "success": true, "command_id": commandID, "status": cmdStatus, "eta_available": false, "message": "Le livreur est arrivé à destination", }) return } // Pour en_route: 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"], }) }