chore: build
Backend - Build & Lint / build (push) Has been cancelled
Frontend Client - EAS Build / build (push) Has been cancelled
Frontend Web - Build & Lint / build (push) Has been cancelled

This commit is contained in:
2026-06-30 19:24:36 +02:00
parent 781212f706
commit 8d131a1ade
6 changed files with 420 additions and 139 deletions
+37
View File
@@ -3,8 +3,10 @@ package handlers
import (
"bytes"
"encoding/csv"
"encoding/json"
"fmt"
"gestion/db"
"gestion/services"
"gestion/utils"
"log"
"net/http"
@@ -149,6 +151,7 @@ func UpdateCommandAddress(c *gin.Context) {
})
}
// ProposeAddressChange propose une nouvelle adresse au client pour validation
func ProposeAddressChange(c *gin.Context) {
database := c.MustGet("database").(*db.Database)
@@ -629,6 +632,7 @@ func GetAvailableDeliveryPersons(c *gin.Context) {
// Cabine: POST /api/v1/cabine/commands/:id/assign (body: {"livreur_username": "..."})
func AssignDeliveryPerson(c *gin.Context) {
database := c.MustGet("database").(*db.Database)
geoService := c.MustGet("geoService").(*services.GeoService)
// ✅ SÉCURITÉ: Admin ou Cabine
role := c.GetString("role")
@@ -678,6 +682,39 @@ func AssignDeliveryPerson(c *gin.Context) {
fmt.Sprintf("Livreur '%s' assigné manuellement par %s", livreurUsername, staffUsername),
staffUsername.(string))
// Géocodage async : stocker les coords si absentes
go func() {
cmd, err := database.GetCommandByID(commandID)
if err != nil {
return
}
dLat, _ := cmd["dest_latitude"].(float64)
dLon, _ := cmd["dest_longitude"].(float64)
if dLat != 0 && dLon != 0 {
return // coords déjà présentes
}
adresse, _ := cmd["adresse"].(string)
if adresse == "" {
return
}
location, err := geoService.GeocodeAddress(adresse)
if err != nil || location == nil {
log.Printf("⚠️ [ASSIGN] Géocodage échoué pour cmd %d: %v", commandID, err)
return
}
coordsJSON, _ := json.Marshal(map[string]float64{
"lat": location.Latitude,
"lon": location.Longitude,
})
destKey := fmt.Sprintf("command:destination:%d", commandID)
db.Redis.Set(db.RedisCtx, destKey, coordsJSON, 4*time.Hour)
database.GDB.Exec(
"UPDATE commandes SET dest_latitude = ?, dest_longitude = ? WHERE id = ?",
location.Latitude, location.Longitude, commandID,
)
log.Printf("📍 [ASSIGN] Coords stockées pour cmd %d: (%.6f, %.6f)", commandID, location.Latitude, location.Longitude)
}()
log.Printf("✅ [ASSIGN] Commande %d assignée à %s", commandID, livreurUsername)
c.JSON(http.StatusOK, gin.H{
+40 -14
View File
@@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"gestion/db"
"gestion/services"
"gestion/utils"
"log"
"net/http"
@@ -323,28 +324,53 @@ func UpdateDeliveryStatus(c *gin.Context) {
}
if destLat != 0 && destLon != 0 {
etaMinutes = database.CalculateETAForDeliveryman(usernameStr, destLat, destLon)
toCoords := services.Coordinates{Latitude: destLat, Longitude: destLon}
if err := database.SetCommandETA(commandID, etaMinutes); err != nil {
log.Printf("⚠️ [STATUS_LIVREUR] Erreur définition ETA: %v", err)
// Cas 1 : GPS du livreur disponible
gpsLat, gpsLon, gpsErr := database.GetDeliveryPersonLocation(usernameStr)
if gpsErr == nil && gpsLat != 0 {
from := services.Coordinates{Latitude: gpsLat, Longitude: gpsLon}
eta, _, err := services.GetETAWithTraffic(from, toCoords)
if err != nil {
eta = services.CalculateETA(services.CalculateDistance(from, toCoords))
}
etaMinutes = eta
log.Printf("📍 [STATUS_LIVREUR] ETA depuis GPS livreur: %d min", etaMinutes)
} else {
log.Printf("✅ [STATUS_LIVREUR] ETA défini: %d minutes", etaMinutes)
if etaMinutes >= 60 {
h := etaMinutes / 60
m := etaMinutes % 60
if m > 0 {
etaMessage = fmt.Sprintf("Arrivée prévue dans %dh%02d", h, m)
} else {
etaMessage = fmt.Sprintf("Arrivée prévue dans %dh", h)
// Cas 2 : GPS absent → dernière adresse de livraison
lastLat, lastLon, lastErr := database.GetLastDeliveryCoords(usernameStr)
if lastErr == nil && lastLat != 0 {
from := services.Coordinates{Latitude: lastLat, Longitude: lastLon}
eta, _, err := services.GetETAWithTraffic(from, toCoords)
if err != nil {
eta = services.CalculateETA(services.CalculateDistance(from, toCoords))
}
etaMinutes = eta
log.Printf("📍 [STATUS_LIVREUR] ETA depuis dernière livraison: %d min", etaMinutes)
} else {
etaMessage = fmt.Sprintf("Arrivée prévue dans %d minutes", etaMinutes)
// Cas 3 : Aucune position disponible
etaMinutes = 30
log.Printf("⚠️ [STATUS_LIVREUR] Aucune position disponible - ETA par défaut: %d min", etaMinutes)
}
}
} else {
log.Printf("⚠️ [STATUS_LIVREUR] Coordonnées destination manquantes - ETA par défaut")
etaMinutes = 30
database.SetCommandETA(commandID, etaMinutes)
log.Printf("⚠️ [STATUS_LIVREUR] Coordonnées destination manquantes - ETA par défaut: %d min", etaMinutes)
}
database.SetCommandETA(commandID, etaMinutes)
log.Printf("✅ [STATUS_LIVREUR] ETA défini: %d minutes", etaMinutes)
if etaMinutes >= 60 {
h := etaMinutes / 60
m := etaMinutes % 60
if m > 0 {
etaMessage = fmt.Sprintf("Arrivée prévue dans %dh%02d", h, m)
} else {
etaMessage = fmt.Sprintf("Arrivée prévue dans %dh", h)
}
} else {
etaMessage = fmt.Sprintf("Arrivée prévue dans %d minutes", etaMinutes)
}
// Mettre à jour le statut du livreur en "delivering"
+59 -18
View File
@@ -13,6 +13,42 @@ import (
"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)
@@ -191,11 +227,8 @@ func GetOrderETA(c *gin.Context) {
}
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",
})
log.Printf("⚠️ [ETA] Coordonnées destination manquantes - retour cache périmé ou message")
c.JSON(http.StatusOK, returnStaleOrUnavailable(commandID, cmdStatus, etaData))
return
}
@@ -203,25 +236,33 @@ func GetOrderETA(c *gin.Context) {
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",
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}
// Cas 1 : GPS livreur disponible
livreurLocation, gpsErr := geoService.GetDeliveryPersonLocation(livreurAssign)
if gpsErr != nil {
// Cas 2 : GPS absent → dernière adresse de livraison
lastLat, lastLon, lastErr := database.GetLastDeliveryCoords(livreurAssign)
if lastErr != nil || lastLat == 0 {
// Cas 3 : Aucune position → cache périmé ou message
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)
}
// Calculer ETA avec TomTom
log.Printf("🛣️ [ETA] Calcul TomTom: (%.6f, %.6f) -> (%.6f, %.6f)",
livreurLocation.Latitude, livreurLocation.Longitude, toCoords.Latitude, toCoords.Longitude)