Files
Xor290 22a8d5026c
Frontend Admin - EAS Build / build (push) Canceled after 0s
Frontend Client - EAS Build / build (push) Canceled after 0s
Backend - Build & Lint / build (push) Failing after 25m18s
Frontend Web - Build & Lint / build (push) Failing after 9m58s
chore: build
2026-08-06 12:06:05 +02:00

294 lines
8.7 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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"],
})
}