package handlers import ( "encoding/json" "fmt" "gestion/db" "gestion/services" "log" "net/http" "strconv" "time" "github.com/gin-gonic/gin" ) // returnStaleOrUnavailable retourne le cache périmé avec le temps restant recalculé, // ou {eta_available: false, message: "Aucune heure disponible"} si le cache est absent ou expiré. func returnStaleOrUnavailable(commandID int, status string, etaData map[string]string) gin.H { if len(etaData) > 0 { if updatedAtStr, ok := etaData["updated_at"]; ok { var updatedAt int64 fmt.Sscanf(updatedAtStr, "%d", &updatedAt) var etaMin int64 if etaStr, ok2 := etaData["eta_minutes"]; ok2 { fmt.Sscanf(etaStr, "%d", &etaMin) } elapsed := int64(time.Since(time.Unix(updatedAt, 0)).Minutes()) remaining := etaMin - elapsed if remaining > 0 { arrival := time.Now().Add(time.Duration(remaining) * time.Minute) log.Printf("📦 [ETA] Cache périmé utilisé - %d min restantes", remaining) return gin.H{ "success": true, "command_id": commandID, "status": status, "eta_minutes": remaining, "estimated_arrival": arrival.Format("15:04"), "eta_available": true, } } } } return gin.H{ "success": true, "command_id": commandID, "status": status, "eta_available": false, "message": "Aucune heure disponible", } } 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 } 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 } } } cmdStatus, _ := command["status"].(string) 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 } 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 } 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 } 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 } } } log.Printf("🔄 [ETA] Cache miss ou expiré - Recalcul de l'ETA...") 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 - retour cache périmé ou message") c.JSON(http.StatusOK, returnStaleOrUnavailable(commandID, cmdStatus, etaData)) return } // Récupérer position du livreur livreurAssign, _ := command["livreur_assign"].(string) if livreurAssign == "" { log.Printf("⚠️ [ETA] Aucun livreur assigné") c.JSON(http.StatusOK, gin.H{ "success": true, "command_id": commandID, "status": cmdStatus, "eta_available": false, "message": "Aucune heure disponible", }) return } toCoords := services.Coordinates{Latitude: destLat, Longitude: destLon} livreurLocation, gpsErr := geoService.GetDeliveryPersonLocation(livreurAssign) if gpsErr != nil { lastLat, lastLon, lastErr := database.GetLastDeliveryCoords(livreurAssign) if lastErr != nil || lastLat == 0 { log.Printf("⚠️ [ETA] Aucune position disponible pour %s", livreurAssign) c.JSON(http.StatusOK, returnStaleOrUnavailable(commandID, cmdStatus, etaData)) return } livreurLocation = &services.Coordinates{Latitude: lastLat, Longitude: lastLon} log.Printf("📍 [ETA] Position depuis dernière livraison: (%.6f, %.6f)", lastLat, lastLon) } 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) } now := time.Now() arrivalTime := now.Add(time.Duration(etaMinutes) * time.Minute) etaCache := map[string]any{ "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"], }) }