519 lines
15 KiB
Go
519 lines
15 KiB
Go
// ============================================
|
|
// handlers/delivery_admin_handlers.go
|
|
// HANDLERS ADMIN POUR LA GESTION DES LIVREURS
|
|
// ============================================
|
|
|
|
package handlers
|
|
|
|
import (
|
|
"fmt"
|
|
"gestion/db"
|
|
"gestion/utils"
|
|
"log"
|
|
"net/http"
|
|
"slices"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// GetDeliveryPersonDetails récupère les détails complets d'un livreur
|
|
func GetDeliveryPersonDetails(c *gin.Context) {
|
|
database := c.MustGet("database").(*db.Database)
|
|
|
|
userRole := c.GetString("role")
|
|
if userRole != "admin" && userRole != "cabine" && userRole != "livreur" {
|
|
log.Printf("❌ [GET_DELIVERY_DETAILS] Accès refusé - role=%s", userRole)
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "Accès refusé"})
|
|
return
|
|
}
|
|
|
|
username := c.Param("username")
|
|
if username == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Username requis"})
|
|
return
|
|
}
|
|
|
|
livreur, err := database.GetUserByUsername(username)
|
|
if err != nil {
|
|
log.Printf("❌ [GET_DELIVERY_DETAILS] Livreur non trouvé: %v", err)
|
|
c.JSON(http.StatusNotFound, gin.H{
|
|
"error": "Livreur non trouvé",
|
|
})
|
|
return
|
|
}
|
|
|
|
// Vérifier que c'est bien un livreur
|
|
if livreur.Role != "livreur" {
|
|
log.Printf("❌ [GET_DELIVERY_DETAILS] Utilisateur n'est pas un livreur: role=%s", livreur.Role)
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "Cet utilisateur n'est pas un livreur",
|
|
"role": livreur.Role,
|
|
})
|
|
return
|
|
}
|
|
|
|
status, err := database.GetDeliveryPersonStatus(username)
|
|
if err != nil {
|
|
log.Printf("⚠️ [GET_DELIVERY_DETAILS] Impossible de récupérer le statut: %v", err)
|
|
status = "offline" // Statut par défaut
|
|
}
|
|
|
|
// Utiliser la fonction GPS existante
|
|
lat, lon, err := database.GetDeliveryPersonLocation(username)
|
|
var locationInfo map[string]interface{}
|
|
if err == nil {
|
|
locationInfo = map[string]interface{}{
|
|
"latitude": lat,
|
|
"longitude": lon,
|
|
}
|
|
}
|
|
|
|
queueSize, _ := database.GetDeliverymanQueueSize(username)
|
|
currentCommand, _ := database.GetCurrentCommand(username)
|
|
|
|
totalDeliveries, _ := database.CountDeliveriesByStatus(username, "")
|
|
completedDeliveries, _ := database.CountDeliveriesByStatus(username, "approved")
|
|
pendingDeliveries, _ := database.CountDeliveriesByStatus(username, "assigned,en_route,livre")
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"deliveryman": gin.H{
|
|
"id": livreur.ID,
|
|
"username": livreur.Username,
|
|
"role": livreur.Role,
|
|
"status": status,
|
|
"current_command": currentCommand,
|
|
"queue_size": queueSize,
|
|
"total_deliveries": totalDeliveries,
|
|
"completed_deliveries": completedDeliveries,
|
|
"pending_deliveries": pendingDeliveries,
|
|
"location": locationInfo,
|
|
},
|
|
})
|
|
}
|
|
|
|
// UpdateDeliveryPersonStatusAdmin modifie le statut d'un livreur (Admin)
|
|
func UpdateDeliveryPersonStatusAdmin(c *gin.Context) {
|
|
database := c.MustGet("database").(*db.Database)
|
|
|
|
userRole := c.GetString("role")
|
|
if !utils.CheckRoleAdmin(c, userRole) {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "Accès refusé"})
|
|
return
|
|
}
|
|
|
|
username := c.Param("username")
|
|
if username == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Username requis"})
|
|
return
|
|
}
|
|
|
|
var req struct {
|
|
Status string `json:"status" binding:"required"`
|
|
}
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "Statut requis",
|
|
})
|
|
return
|
|
}
|
|
|
|
validStatuses := []string{"available", "busy", "offline"}
|
|
|
|
if !slices.Contains(validStatuses, req.Status) {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "Statut invalide",
|
|
"valid_statuses": validStatuses,
|
|
"received": req.Status,
|
|
})
|
|
return
|
|
}
|
|
|
|
log.Printf("📝 [UPDATE_DELIVERY_STATUS] Modification: %s → %s", username, req.Status)
|
|
|
|
livreur, err := database.GetUserByUsername(username)
|
|
if err != nil {
|
|
log.Printf("❌ [UPDATE_DELIVERY_STATUS] Livreur non trouvé")
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Livreur non trouvé"})
|
|
return
|
|
}
|
|
|
|
if livreur.Role != "livreur" {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "Cet utilisateur n'est pas un livreur",
|
|
})
|
|
return
|
|
}
|
|
|
|
err = database.UpdateDeliveryPersonStatus(username, req.Status)
|
|
if err != nil {
|
|
log.Printf("❌ [UPDATE_DELIVERY_STATUS] Erreur mise à jour: %v", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"error": "Erreur mise à jour statut",
|
|
})
|
|
return
|
|
}
|
|
|
|
adminUsername, _ := c.Get("username")
|
|
log.Printf("✅ [UPDATE_DELIVERY_STATUS] Statut modifié par admin %s: %s → %s",
|
|
adminUsername, username, req.Status)
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "Statut du livreur mis à jour",
|
|
"username": username,
|
|
"new_status": req.Status,
|
|
"updated_by": adminUsername,
|
|
})
|
|
}
|
|
|
|
// GetDeliveryPersonStats récupère les statistiques d'un livreur
|
|
func GetDeliveryPersonStats(c *gin.Context) {
|
|
database := c.MustGet("database").(*db.Database)
|
|
|
|
userRole := c.GetString("role")
|
|
if userRole != "admin" {
|
|
log.Printf("❌ [GET_DELIVERY_STATS] Accès refusé - role=%s", userRole)
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "Accès refusé"})
|
|
return
|
|
}
|
|
|
|
username := c.Param("username")
|
|
if username == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Username requis"})
|
|
return
|
|
}
|
|
|
|
log.Printf("📊 [GET_DELIVERY_STATS] Calcul stats pour: %s", username)
|
|
|
|
livreur, err := database.GetUserByUsername(username)
|
|
if err != nil {
|
|
log.Printf("❌ [GET_DELIVERY_STATS] Livreur non trouvé")
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Livreur non trouvé"})
|
|
return
|
|
}
|
|
|
|
if livreur.Role != "livreur" {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "Cet utilisateur n'est pas un livreur",
|
|
})
|
|
return
|
|
}
|
|
|
|
// ============================================
|
|
// Récupérer les statistiques
|
|
// ============================================
|
|
totalDeliveries, _ := database.CountDeliveriesByStatus(username, "")
|
|
completedDeliveries, _ := database.CountDeliveriesByStatus(username, "approved")
|
|
cancelledDeliveries, _ := database.CountDeliveriesByStatus(username, "cancelled")
|
|
pendingDeliveries, _ := database.CountDeliveriesByStatus(username, "pending,assigned")
|
|
inProgressDeliveries, _ := database.CountDeliveriesByStatus(username, "en_route,livre")
|
|
|
|
// Récupérer statut et queue
|
|
status, _ := database.GetDeliveryPersonStatus(username)
|
|
queueSize, _ := database.GetDeliverymanQueueSize(username)
|
|
|
|
// Calculer le taux de succès
|
|
successRate := 0.0
|
|
if totalDeliveries > 0 {
|
|
successRate = (float64(completedDeliveries) / float64(totalDeliveries)) * 100
|
|
}
|
|
|
|
// Récupérer la dernière livraison
|
|
lastDeliveryDate := ""
|
|
lastDelivery, err := database.GetLastDeliveryDate(username)
|
|
if err == nil && lastDelivery != nil {
|
|
lastDeliveryDate = lastDelivery.Format("2006-01-02 15:04:05")
|
|
}
|
|
|
|
log.Printf("✅ [GET_DELIVERY_STATS] Stats calculées: total=%d, completed=%d",
|
|
totalDeliveries, completedDeliveries)
|
|
|
|
// ============================================
|
|
// RÉPONSE
|
|
// ============================================
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"stats": gin.H{
|
|
"username": username,
|
|
"total_deliveries": totalDeliveries,
|
|
"completed_deliveries": completedDeliveries,
|
|
"cancelled_deliveries": cancelledDeliveries,
|
|
"pending_deliveries": pendingDeliveries,
|
|
"in_progress_deliveries": inProgressDeliveries,
|
|
"success_rate": successRate,
|
|
"current_queue_size": queueSize,
|
|
"last_delivery_date": lastDeliveryDate,
|
|
"status": status,
|
|
},
|
|
})
|
|
}
|
|
|
|
// GetDeliveryPersonHistory récupère l'historique des livraisons d'un livreur
|
|
func GetDeliveryPersonHistory(c *gin.Context) {
|
|
database := c.MustGet("database").(*db.Database)
|
|
|
|
// ✅ SÉCURITÉ: Admin seulement
|
|
userRole := c.GetString("role")
|
|
if userRole != "admin" {
|
|
log.Printf("❌ [GET_DELIVERY_HISTORY] Accès refusé - role=%s", userRole)
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "Accès refusé"})
|
|
return
|
|
}
|
|
|
|
username := c.Param("username")
|
|
if username == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Username requis"})
|
|
return
|
|
}
|
|
|
|
// Paramètres de pagination
|
|
limit := 20
|
|
offset := 0
|
|
|
|
if limitStr := c.Query("limit"); limitStr != "" {
|
|
if l, err := strconv.Atoi(limitStr); err == nil && l > 0 && l <= 100 {
|
|
limit = l
|
|
}
|
|
}
|
|
|
|
if offsetStr := c.Query("offset"); offsetStr != "" {
|
|
if o, err := strconv.Atoi(offsetStr); err == nil && o >= 0 {
|
|
offset = o
|
|
}
|
|
}
|
|
|
|
log.Printf("📜 [GET_DELIVERY_HISTORY] Récupération historique: %s (limit=%d, offset=%d)",
|
|
username, limit, offset)
|
|
|
|
// ============================================
|
|
// Vérifier que le livreur existe
|
|
// ============================================
|
|
livreur, err := database.GetUserByUsername(username)
|
|
if err != nil {
|
|
log.Printf("❌ [GET_DELIVERY_HISTORY] Livreur non trouvé")
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Livreur non trouvé"})
|
|
return
|
|
}
|
|
|
|
if livreur.Role != "livreur" {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "Cet utilisateur n'est pas un livreur",
|
|
})
|
|
return
|
|
}
|
|
|
|
// ============================================
|
|
// Récupérer l'historique
|
|
// ============================================
|
|
history, err := database.GetDeliveryPersonHistory(username, limit, offset)
|
|
if err != nil {
|
|
log.Printf("❌ [GET_DELIVERY_HISTORY] Erreur récupération: %v", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"error": "Erreur récupération historique",
|
|
})
|
|
return
|
|
}
|
|
|
|
// Compter le total
|
|
total, _ := database.CountDeliveriesByStatus(username, "")
|
|
|
|
log.Printf("✅ [GET_DELIVERY_HISTORY] Historique récupéré: %d livraisons (total=%d)",
|
|
len(history), total)
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"history": history,
|
|
"count": len(history),
|
|
"total": total,
|
|
"limit": limit,
|
|
"offset": offset,
|
|
})
|
|
}
|
|
|
|
// UpdateDeliveryPersonLocationAdmin modifie la position GPS d'un livreur (Admin)
|
|
// PUT /api/v2/admin/protected/delivery-persons/:username/location
|
|
func UpdateDeliveryPersonLocationAdmin(c *gin.Context) {
|
|
database := c.MustGet("database").(*db.Database)
|
|
|
|
// ✅ SÉCURITÉ: Admin seulement
|
|
userRole := c.GetString("role")
|
|
if userRole != "admin" && userRole != "cabine" {
|
|
log.Printf("❌ [UPDATE_DELIVERY_LOCATION] Accès refusé - role=%s", userRole)
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "Accès refusé"})
|
|
return
|
|
}
|
|
|
|
username := c.Param("username")
|
|
if username == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Username requis"})
|
|
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": "Coordonnées GPS requises",
|
|
})
|
|
return
|
|
}
|
|
|
|
// Valider les coordonnées
|
|
if req.Latitude < -90 || req.Latitude > 90 {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "Latitude invalide (doit être entre -90 et 90)",
|
|
})
|
|
return
|
|
}
|
|
|
|
if req.Longitude < -180 || req.Longitude > 180 {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "Longitude invalide (doit être entre -180 et 180)",
|
|
})
|
|
return
|
|
}
|
|
|
|
log.Printf("📍 [UPDATE_DELIVERY_LOCATION] Modification: %s → (%.6f, %.6f)",
|
|
username, req.Latitude, req.Longitude)
|
|
|
|
// ============================================
|
|
// Vérifier que le livreur existe
|
|
// ============================================
|
|
livreur, err := database.GetUserByUsername(username)
|
|
if err != nil {
|
|
log.Printf("❌ [UPDATE_DELIVERY_LOCATION] Livreur non trouvé")
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Livreur non trouvé"})
|
|
return
|
|
}
|
|
|
|
if livreur.Role != "livreur" {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "Cet utilisateur n'est pas un livreur",
|
|
})
|
|
return
|
|
}
|
|
|
|
// ============================================
|
|
// Mettre à jour la position (utilise la fonction existante)
|
|
// ============================================
|
|
err = database.UpdateDeliveryPersonLocation(username, req.Latitude, req.Longitude)
|
|
if err != nil {
|
|
log.Printf("❌ [UPDATE_DELIVERY_LOCATION] Erreur mise à jour: %v", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"error": "Erreur mise à jour position",
|
|
})
|
|
return
|
|
}
|
|
|
|
adminUsername, _ := c.Get("username")
|
|
log.Printf("✅ [UPDATE_DELIVERY_LOCATION] Position modifiée par admin %s: %s → (%.6f, %.6f)",
|
|
adminUsername, username, req.Latitude, req.Longitude)
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "Position GPS mise à jour",
|
|
"location": gin.H{
|
|
"username": username,
|
|
"latitude": req.Latitude,
|
|
"longitude": req.Longitude,
|
|
"updated_at": time.Now().Unix(),
|
|
},
|
|
"updated_by": adminUsername,
|
|
})
|
|
}
|
|
|
|
func RemoveCommandFromQueue(c *gin.Context) {
|
|
database := c.MustGet("database").(*db.Database)
|
|
|
|
// ✅ SÉCURITÉ: Admin seulement
|
|
userRole := c.GetString("role")
|
|
if userRole != "admin" {
|
|
log.Printf("❌ [REMOVE_FROM_QUEUE] Accès refusé - role=%s", userRole)
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "Accès refusé"})
|
|
return
|
|
}
|
|
|
|
username := c.Param("username")
|
|
commandIDStr := c.Param("command_id")
|
|
|
|
if username == "" || commandIDStr == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "Username et command_id requis",
|
|
})
|
|
return
|
|
}
|
|
|
|
commandID, err := strconv.Atoi(commandIDStr)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "ID de commande invalide",
|
|
})
|
|
return
|
|
}
|
|
|
|
log.Printf("🗑️ [REMOVE_FROM_QUEUE] Suppression: cmd %d de la queue de %s", commandID, username)
|
|
|
|
livreur, err := database.GetUserByUsername(username)
|
|
if err != nil {
|
|
log.Printf("❌ [REMOVE_FROM_QUEUE] Livreur non trouvé")
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Livreur non trouvé"})
|
|
return
|
|
}
|
|
|
|
if livreur.Role != "livreur" {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "Cet utilisateur n'est pas un livreur",
|
|
})
|
|
return
|
|
}
|
|
|
|
command, err := database.GetCommandByID(commandID)
|
|
if err != nil {
|
|
log.Printf("❌ [REMOVE_FROM_QUEUE] Commande non trouvée")
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Commande non trouvée"})
|
|
return
|
|
}
|
|
|
|
err = database.RemoveCommandFromDeliverymanQueue(username, commandID)
|
|
if err != nil {
|
|
log.Printf("❌ [REMOVE_FROM_QUEUE] Erreur suppression: %v", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"error": "Erreur suppression de la queue",
|
|
})
|
|
return
|
|
}
|
|
|
|
currentStatus, _ := command["status"].(string)
|
|
if currentStatus == "assigned" || currentStatus == "en_route" {
|
|
err = database.UpdateCommandStatus(commandID, "pending")
|
|
if err != nil {
|
|
log.Printf("⚠️ [REMOVE_FROM_QUEUE] Impossible de réinitialiser le statut: %v", err)
|
|
} else {
|
|
database.UpdateCommandLivreur(commandID, "")
|
|
log.Printf("✅ [REMOVE_FROM_QUEUE] Commande réinitialisée en 'pending'")
|
|
}
|
|
}
|
|
|
|
adminUsername, _ := c.Get("username")
|
|
database.AddCommandLog(commandID, "queue_removed",
|
|
fmt.Sprintf("Commande retirée de la queue du livreur %s par admin %s", username, adminUsername),
|
|
adminUsername.(string))
|
|
|
|
log.Printf("✅ [REMOVE_FROM_QUEUE] Commande %d retirée de la queue de %s", commandID, username)
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "Commande retirée de la queue du livreur",
|
|
"command_id": commandID,
|
|
"username": username,
|
|
"removed_by": adminUsername,
|
|
})
|
|
}
|