// ============================================ // handlers/cabine_handlers.go - COMPLET // INCLUT: SetCommandDestinationCoordinates // ============================================ package handlers import ( "encoding/json" "fmt" "gestion/db" "gestion/utils" "log" "net/http" "slices" "strconv" "time" "github.com/gin-gonic/gin" ) // ============================================ // 0️⃣ FONCTION ADMIN: SET DESTINATION COORDINATES // ============================================ // SetCommandDestinationCoordinates stocke les coordonnées destination en Redis func SetCommandDestinationCoordinates(c *gin.Context) { database := c.MustGet("database").(*db.Database) userRole := c.GetString("role") if userRole != "admin" { c.JSON(http.StatusForbidden, gin.H{"error": "Accès réservé aux administrateurs"}) return } adminUsername := c.GetString("username") commandID, err := strconv.Atoi(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "ID de commande invalide"}) return } var req struct { Latitude float64 `json:"latitude" binding:"required"` Longitude float64 `json:"longitude" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": "Latitude et longitude requises", }) return } // Validation des coordonnées GPS if req.Latitude < -90 || req.Latitude > 90 { c.JSON(http.StatusBadRequest, gin.H{ "error": "Latitude invalide (doit être entre -90 et 90)", "value": req.Latitude, }) return } if req.Longitude < -180 || req.Longitude > 180 { c.JSON(http.StatusBadRequest, gin.H{ "error": "Longitude invalide (doit être entre -180 et 180)", "value": req.Longitude, }) return } if !utils.CheckCommand(commandID, database) { c.JSON(http.StatusNotFound, gin.H{"error": "Commande non trouvée"}) } destCacheKey := fmt.Sprintf("command:destination:%d", commandID) coordsJSON, _ := json.Marshal(map[string]float64{ "lat": req.Latitude, "lon": req.Longitude, }) ttlSeconds := 24 * 60 * 60 // 24 heures err = db.Redis.Set(db.RedisCtx, destCacheKey, coordsJSON, time.Duration(ttlSeconds)*time.Second).Err() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": "Erreur stockage Redis", "details": err.Error(), }) return } // Ajouter un log database.AddCommandLog(commandID, "destination_set", fmt.Sprintf("Coordonnées destination définies par admin %s: (%.6f, %.6f) via Redis", adminUsername, req.Latitude, req.Longitude), adminUsername) log.Printf("✅ [ADMIN %s] Coordonnées destination définies pour CMD %d: (%.6f, %.6f) en Redis", adminUsername, commandID, req.Latitude, req.Longitude) c.JSON(http.StatusOK, gin.H{ "success": true, "message": "Coordonnées définies avec succès en Redis", "command_id": commandID, }) } // ============================================ // 1. CLIENT PROFILE // ============================================ func GetClientProfile(c *gin.Context) { database := c.MustGet("database").(*db.Database) username := c.Param("username") if username == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "Username requis"}) return } client, err := database.GetClientByUsername(username) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Client non trouvé"}) return } client.Password = "" c.JSON(http.StatusOK, gin.H{ "success": true, "client": gin.H{ "id": client.ID, "username": client.Username, "command": client.Command, "amende": client.Amende, "points_extra": client.PointsExtra, "created_at": client.CreatedAt, }, }) } func GetClientFullHistory(c *gin.Context) { database := c.MustGet("database").(*db.Database) username := c.Param("username") if username == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "Username requis"}) return } client, err := database.GetClientByUsername(username) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Client non trouvé"}) return } commands, err := database.GetAllCommands("", username) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur récupération historique"}) return } c.JSON(http.StatusOK, gin.H{ "success": true, "client": gin.H{ "username": client.Username, "total_commands": client.Command, "amende": client.Amende, "points_extra": client.PointsExtra, }, "commands": commands, "count": len(commands), }) } // ============================================ // 2. UPDATE ADDRESS // ============================================ func UpdateCommandAddressCabine(c *gin.Context) { database := c.MustGet("database").(*db.Database) commandID, err := strconv.Atoi(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "ID de commande invalide"}) return } var req struct { DeliveryAddress string `json:"delivery_address" binding:"required"` Reason string `json:"reason"` } if err := c.ShouldBindJSON(&req); err != nil || req.DeliveryAddress == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "Adresse de livraison requise"}) return } command, err := database.GetCommandByID(commandID) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Commande non trouvée"}) return } status, _ := command["status"].(string) allowedStatuses := []string{"pending", "", "assigned"} if !slices.Contains(allowedStatuses, status) { c.JSON(http.StatusBadRequest, gin.H{ "error": "Impossible de modifier l'adresse d'une commande en cours ou terminée", "current_status": status, "allowed_statuses": allowedStatuses, }) return } if err := database.UpdateCommandAddress(commandID, req.DeliveryAddress); err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": "Erreur lors de la mise à jour de l'adresse", "details": err.Error(), }) return } cabineUsername, _ := c.Get("username") message := fmt.Sprintf("Adresse modifiée par cabine: %s", req.DeliveryAddress) if req.Reason != "" { message += fmt.Sprintf(" (Raison: %s)", req.Reason) } database.AddCommandLog(commandID, "address_updated", message, cabineUsername.(string)) c.JSON(http.StatusOK, gin.H{ "success": true, "message": "Adresse de livraison mise à jour", "command_id": commandID, "delivery_address": req.DeliveryAddress, }) } // ============================================ // 3. LIVREUR POSITION // ============================================ func GetLivreurPosition(c *gin.Context) { database := c.MustGet("database").(*db.Database) livreurUsername := c.Param("username") userRole := c.GetString("role") if userRole != "admin" && userRole != "cabine" { c.JSON(http.StatusForbidden, gin.H{ "error": "Accès refusé - Réservé aux administrateurs et cabines", }) return } if livreurUsername == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "Username livreur requis"}) return } position, err := database.GetLivreurPosition(livreurUsername) if err != nil { c.JSON(http.StatusOK, gin.H{ "success": false, "livreur": livreurUsername, "message": "Position non disponible (GPS désactivé ou livraison terminée)", "details": err.Error(), }) return } c.JSON(http.StatusOK, gin.H{ "success": true, "livreur": livreurUsername, "position": position, }) } func GetDeliveryTrackingClient(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 } commandID, err := strconv.Atoi(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "ID de commande invalide"}) return } command, err := database.GetCommandByID(commandID) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Commande non trouvée"}) return } if command["username"].(string) != username.(string) { c.JSON(http.StatusForbidden, gin.H{"error": "Cette commande ne vous appartient pas"}) return } livreurAssign, _ := command["livreur_assign"].(string) logs, _ := database.GetCommandLogs(commandID) c.JSON(http.StatusOK, gin.H{ "success": true, "command_id": commandID, "status": command["status"], "livreur": livreurAssign, "address": command["adresse"], "logs": logs, "message": "Suivi en cours - ETA disponible via /api/v1/orders/:id/eta", }) } // ============================================ // 5. DELIVERY TRACKING ADMIN (AVEC GPS) // ============================================ func GetDeliveryTracking(c *gin.Context) { database := c.MustGet("database").(*db.Database) userRole := c.GetString("role") if userRole != "admin" && userRole != "cabine" { c.JSON(http.StatusForbidden, gin.H{ "error": "Accès refusé - Réservé aux administrateurs et cabines", }) return } commandID, err := strconv.Atoi(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "ID de commande invalide"}) return } 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 == "" { c.JSON(http.StatusOK, gin.H{ "success": true, "command": command, "status": "Aucun livreur assigné", }) return } position, err := database.GetLivreurPosition(livreurAssign) logs, _ := database.GetCommandLogs(commandID) response := gin.H{ "success": true, "command": command, "livreur": livreurAssign, "logs": logs, } status, _ := command["status"].(string) if err != nil && (status == "livre" || status == "approved") { response["livreur_position"] = nil response["position_status"] = "Livraison terminée - Position non suivie" } else if err != nil { response["livreur_position"] = nil response["position_status"] = "Position non disponible (GPS peut-être désactivé)" } else { response["livreur_position"] = position response["position_status"] = "Position en temps réel" } c.JSON(http.StatusOK, response) } func GetDeliveryIssues(c *gin.Context) { database := c.MustGet("database").(*db.Database) status := c.Query("status") userRole := c.GetString("role") if userRole != "admin" && userRole != "cabine" { c.JSON(http.StatusForbidden, gin.H{ "error": "Accès refusé - Réservé aux administrateurs et cabines", }) return } issues, err := database.GetDeliveryIssues(status) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": "Erreur récupération problèmes", "details": err.Error(), }) return } c.JSON(http.StatusOK, gin.H{ "success": true, "issues": issues, "count": len(issues), }) } func CreateDeliveryIssue(c *gin.Context) { database := c.MustGet("database").(*db.Database) var req struct { CommandID int `json:"command_id" binding:"required"` IssueType string `json:"issue_type" binding:"required"` Description string `json:"description" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Données invalides"}) return } cabineUsername, _ := c.Get("username") issue, err := database.CreateDeliveryIssue( req.CommandID, req.IssueType, req.Description, cabineUsername.(string), ) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": "Erreur création problème", "details": err.Error(), }) return } c.JSON(http.StatusCreated, gin.H{ "success": true, "message": "Problème enregistré", "issue": issue, }) } func UpdateDeliveryIssue(c *gin.Context) { database := c.MustGet("database").(*db.Database) issueID, 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"` Resolution string `json:"resolution"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Données invalides"}) return } cabineUsername, _ := c.Get("username") err = database.UpdateDeliveryIssue(issueID, req.Status, req.Resolution, cabineUsername.(string)) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": "Erreur mise à jour", "details": err.Error(), }) return } c.JSON(http.StatusOK, gin.H{ "success": true, "message": "Problème mis à jour", }) } func AddDeliverySupport(c *gin.Context) { database := c.MustGet("database").(*db.Database) commandID, err := strconv.Atoi(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "ID commande invalide"}) return } var req struct { Message string `json:"message"` } c.ShouldBindJSON(&req) if req.Message == "" { c.JSON(http.StatusBadRequest, gin.H{ "error": "Message requis", "example": gin.H{ "message": "Votre message de support ici", }, }) return } cabineUsername, _ := c.Get("username") err = database.AddCommandLog( commandID, "note", fmt.Sprintf("Note cabine: %s", req.Message), cabineUsername.(string), ) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": "Erreur ajout support", "details": err.Error(), }) return } c.JSON(http.StatusOK, gin.H{ "success": true, "message": "Support ajouté", }) } func GetCommandLogs(c *gin.Context) { database := c.MustGet("database").(*db.Database) commandID, err := strconv.Atoi(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "ID commande invalide"}) return } logs, err := database.GetCommandLogs(commandID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": "Erreur récupération logs", "details": err.Error(), }) return } c.JSON(http.StatusOK, gin.H{ "success": true, "logs": logs, "count": len(logs), }) } // ============================================ // 7. FORCE VALIDATE DELIVERY // ============================================ func ForceValidateDelivery(c *gin.Context) { database := c.MustGet("database").(*db.Database) userRole := c.GetString("role") if userRole != "admin" { c.JSON(http.StatusForbidden, gin.H{"error": "Accès refusé - Admin seulement"}) return } adminUsername, _ := c.Get("username") commandID, err := strconv.Atoi(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "ID de commande invalide"}) return } var req struct { Reason string `json:"reason" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": "Raison requise pour validation forcée", "details": err.Error(), "example": gin.H{ "reason": "Client confirmé par téléphone", }, }) return } if req.Reason == "" { c.JSON(http.StatusBadRequest, gin.H{ "error": "Veuillez fournir une raison pour la validation forcée", }) return } command, err := database.GetCommandByID(commandID) if err != nil { c.JSON(http.StatusNotFound, gin.H{ "error": "Commande non trouvée", "command_id": commandID, }) return } status, ok := command["status"].(string) if !ok { c.JSON(http.StatusBadRequest, gin.H{"error": "Statut de commande invalide"}) return } if status == "livre" { c.JSON(http.StatusBadRequest, gin.H{ "error": "Cette commande a déjà été validée", "current_status": status, }) return } validStatuses := []string{"assigned", "en_route", "pending", "priority"} if !slices.Contains(validStatuses, status) { c.JSON(http.StatusBadRequest, gin.H{ "error": "Commande ne peut pas être validée de force dans ce statut", "current_status": status, "valid_statuses": validStatuses, }) return } err = database.UpdateCommandStatus(commandID, "livre") if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": "Erreur lors de la validation forcée", "details": err.Error(), }) return } clientUsername, _ := command["username"].(string) livreurAssign, _ := command["livreur_assign"].(string) if clientUsername != "" { clientMsg := fmt.Sprintf("Votre commande #%d a été livrée. Merci !", commandID) database.NotifyClient(clientUsername, commandID, "livre", clientMsg) } if err := database.IncrementClientCommandCount(clientUsername); err != nil { log.Printf("⚠️ Erreur compteur commandes: %v", err) } if err := database.AddClientPointsByCategory(clientUsername, 10, ""); err != nil { log.Printf("⚠️ Erreur ajout points: %v", err) } if livreurAssign != "" { err := database.CompleteDeliveryAndProcessNext(livreurAssign, commandID) if err != nil { log.Printf("⚠️ Erreur optimisation: %v", err) } } message := fmt.Sprintf("VALIDATION FORCÉE par admin %s - Raison: %s", adminUsername.(string), req.Reason) database.AddCommandLog(commandID, "livre", message, adminUsername.(string)) log.Printf("🔴 Commande %d validée de force par %s - Raison: %s", commandID, adminUsername.(string), req.Reason) c.JSON(http.StatusOK, gin.H{ "success": true, "message": "Livraison validée de force (sans vérification GPS)", "command_id": commandID, "validation_type": "forced", "reason": req.Reason, "validated_by": adminUsername.(string), "new_status": "livre", "points_awarded": 10, "queue_optimized": livreurAssign != "", }) }