package handlers import ( "encoding/json" "fmt" "gestion/db" "gestion/utils" "log" "net/http" "slices" "strconv" "github.com/gin-gonic/gin" ) func GetMyDeliveries(c *gin.Context) { database := c.MustGet("database").(*db.Database) username, exists := c.Get("username") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "Non authentifié"}) return } if c.GetString("role") != "livreur" { c.JSON(http.StatusForbidden, gin.H{"error": "Accès réservé aux livreurs"}) return } usernameStr := username.(string) status := c.Query("status") commands, err := database.GetDeliveryPersonCommands(usernameStr, status) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": "Erreur récupération", "details": err.Error(), }) return } filteredCommands := make([]gin.H, len(commands)) for i, cmd := range commands { commandID, _ := strconv.Atoi(fmt.Sprintf("%v", cmd["id"])) items, _ := database.GetCommandItems(commandID) // Client info SANS téléphone clientUsername, _ := cmd["username"].(string) client, _ := database.GetClientByUsername(clientUsername) clientInfo := gin.H{"nom": "Client", "prenom": ""} if client != nil { clientInfo = gin.H{ "nom": client.Nom, "prenom": client.Prenom, } } itemsSummary := make([]gin.H, len(items)) for j, item := range items { itemsSummary[j] = gin.H{ "produit": item["produit"], "quantite": item["quantite"], "prix": item["prix"], } } etaData, _ := database.GetCommandETA(commandID) filteredCommands[i] = gin.H{ "id": cmd["id"], "status": cmd["status"], "adresse": cmd["adresse"], "total_prix": cmd["total_prix"], "created_at": cmd["created_at"], "client_info": clientInfo, "items": itemsSummary, "items_count": len(items), "eta": etaData, } } log.Printf("✅ [MY_DELIVERIES] %d livraisons (données filtrées)", len(filteredCommands)) c.JSON(http.StatusOK, gin.H{ "success": true, "deliveries": filteredCommands, "count": len(filteredCommands), }) } // ============================================ // GetDeliveryDetails // ============================================ func GetDeliveryDetails(c *gin.Context) { database := c.MustGet("database").(*db.Database) username := c.GetString("username") if c.GetString("role") != "livreur" { c.JSON(http.StatusForbidden, gin.H{"error": "Accès réservé aux livreurs"}) return } commandID, err := strconv.Atoi(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "ID invalide"}) return } command, err := database.GetCommandByID(commandID) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Commande non trouvée"}) return } // ✅ VÉRIFIER PROPRIÉTÉ livreurAssign, _ := command["livreur_assign"].(string) if livreurAssign != username { log.Printf("❌ Accès refusé - cmd assignée à %s", livreurAssign) c.JSON(http.StatusForbidden, gin.H{ "error": "Cette livraison ne vous est pas assignée", }) return } items, _ := database.GetCommandItems(commandID) clientUsername, _ := command["username"].(string) client, _ := database.GetClientByUsername(clientUsername) clientInfo := gin.H{"nom": "Client", "prenom": ""} if client != nil { clientInfo = gin.H{ "nom": client.Nom, "prenom": client.Prenom, } } itemsSummary := make([]gin.H, len(items)) for i, item := range items { itemsSummary[i] = gin.H{ "produit": item["produit"], "quantite": item["quantite"], "prix": item["prix"], } } etaData, _ := database.GetCommandETA(commandID) c.JSON(http.StatusOK, gin.H{ "success": true, "delivery": gin.H{ "id": command["id"], "status": command["status"], "adresse": command["adresse"], "total_prix": command["total_prix"], "referral_used": command["referral_used"], "created_at": command["created_at"], "client_info": clientInfo, "items": itemsSummary, "items_count": len(items), "eta": etaData, }, }) } func UpdateDeliveryStatus(c *gin.Context) { database := c.MustGet("database").(*db.Database) username, exists := c.Get("username") if !exists || c.GetString("role") != "livreur" { c.JSON(http.StatusForbidden, gin.H{"error": "Accès réservé aux livreurs"}) return } usernameStr := username.(string) commandID, err := strconv.Atoi(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "ID invalide"}) return } var req struct { Status string `json:"status" binding:"required"` Notes string `json:"notes"` Latitude float64 `json:"latitude"` Longitude float64 `json:"longitude"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": "Données invalides", "details": err.Error(), }) return } log.Printf("📝 [UPD_STATUS] %s update cmd %d: %s", usernameStr, commandID, req.Status) command, err := database.GetCommandByID(commandID) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Commande non trouvée"}) return } livreurAssign, _ := command["livreur_assign"].(string) if livreurAssign != usernameStr { log.Printf("❌ Accès refusé - assigné à %s", livreurAssign) c.JSON(http.StatusForbidden, gin.H{ "error": "Cette commande ne vous est pas assignée", }) return } validStatuses := []string{ "assigned", "en_route", "arrived", "livre", "cancelled", } if !slices.Contains(validStatuses, req.Status) { c.JSON(http.StatusBadRequest, gin.H{ "error": "Statut invalide", "valid_statuses": validStatuses, "received": req.Status, }) return } if req.Status == "livre" { if req.Latitude == 0 || req.Longitude == 0 { c.JSON(http.StatusBadRequest, gin.H{"error": "Coordonnées GPS requises pour confirmer la livraison"}) return } destLat, _ := command["dest_latitude"].(float64) destLon, _ := command["dest_longitude"].(float64) if destLat != 0 && destLon != 0 { distance := utils.CalculateDistance(req.Latitude, req.Longitude, destLat, destLon) log.Printf("📍 [GPS] Distance: %.2f m", distance) if distance > 100 { c.JSON(http.StatusBadRequest, gin.H{ "error": "Vous êtes trop loin de la destination", "current_distance": fmt.Sprintf("%.2f", distance), "unit": "meters", }) return } log.Printf("✅ [GPS] Validation OK") } else { log.Printf("⚠️ [GPS] Coordonnées de destination non disponibles, validation ignorée") } } // Mettre à jour le statut if err := database.UpdateCommandStatus(commandID, req.Status); err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": "Erreur mise à jour", "details": err.Error(), }) return } // ✅ SI PASSAGE EN "EN_ROUTE" → CALCULER ET DÉFINIR L'ETA var etaMinutes int var etaMessage string if req.Status == "en_route" { log.Printf("🚗 [STATUS_LIVREUR] Passage en 'en_route' - Calcul 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("📍 [STATUS_LIVREUR] Coords depuis cache Redis: (%.6f, %.6f)", destLat, destLon) } } // 2. Fallback: récupérer depuis la DB if destLat == 0 || destLon == 0 { if dLat, ok := command["dest_latitude"].(float64); ok && dLat != 0 { destLat = dLat } if dLon, ok := command["dest_longitude"].(float64); ok && dLon != 0 { destLon = dLon } if destLat != 0 && destLon != 0 { log.Printf("📍 [STATUS_LIVREUR] Coords depuis DB: (%.6f, %.6f)", destLat, destLon) } } if destLat != 0 && destLon != 0 { etaMinutes = database.CalculateETAForDeliveryman(usernameStr, destLat, destLon) if err := database.SetCommandETA(commandID, etaMinutes); err != nil { log.Printf("⚠️ [STATUS_LIVREUR] Erreur définition ETA: %v", err) } 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) } } else { etaMessage = fmt.Sprintf("Arrivée prévue dans %d minutes", etaMinutes) } } } else { log.Printf("⚠️ [STATUS_LIVREUR] Coordonnées destination manquantes - ETA par défaut") etaMinutes = 30 database.SetCommandETA(commandID, etaMinutes) } // Mettre à jour le statut du livreur en "delivering" database.SetDeliveryPersonStatus(usernameStr, "delivering", commandID) log.Printf("🚗 [STATUS_LIVREUR] Statut livreur mis à jour: delivering") } // Log message := req.Notes if message == "" { message = utils.GetDeliveryStatusMessage(req.Status) } if etaMessage != "" { message += fmt.Sprintf(" - %s", etaMessage) } database.AddCommandLog(commandID, req.Status, message, usernameStr) // ✅ NOTIFICATION CLIENT clientUsername, _ := command["username"].(string) if clientUsername != "" { var clientMsg string switch req.Status { case "en_route": if etaMinutes > 0 { var etaStr string if etaMinutes >= 60 { h := etaMinutes / 60 m := etaMinutes % 60 if m > 0 { etaStr = fmt.Sprintf("%dh%02d", h, m) } else { etaStr = fmt.Sprintf("%dh", h) } } else { etaStr = fmt.Sprintf("%d min", etaMinutes) } clientMsg = fmt.Sprintf("🛵 Votre commande #%d est en route ! Arrivée dans ~%s", commandID, etaStr) } else { clientMsg = fmt.Sprintf("🛵 Votre commande #%d est en route !", commandID) } case "arrived": clientMsg = fmt.Sprintf("🛵 Votre livreur est là ! Il sera chez vous dans 5 minutes (commande #%d)", commandID) case "livre": clientMsg = fmt.Sprintf("Votre commande #%d a été livrée. Merci !", commandID) case "cancelled": clientMsg = fmt.Sprintf("Votre commande #%d a été annulée par le livreur", commandID) } if clientMsg != "" { database.NotifyClient(clientUsername, commandID, req.Status, clientMsg) } } // ✅ GESTION SPÉCIALE SELON LE STATUT switch req.Status { case "livre": // Livraison terminée - Optimiser la queue log.Printf("📦 Livraison marquée 'livre' - Optimisation queue...") database.CompleteDeliveryAndProcessNext(usernameStr, commandID) case "cancelled": // Annulation par le livreur - Nettoyer la queue log.Printf("🚫 Livraison annulée par livreur - Nettoyage queue cmd %d", commandID) database.CompleteDeliveryAndProcessNext(usernameStr, commandID) case "arrived": log.Printf("📍 Livreur arrivé à destination - Commande %d", commandID) } response := gin.H{ "success": true, "message": "Statut mis à jour", "command_id": commandID, "status": req.Status, } if req.Status == "en_route" && etaMinutes > 0 { response["eta_minutes"] = etaMinutes response["eta_message"] = etaMessage } c.JSON(http.StatusOK, response) }