670 lines
17 KiB
Go
670 lines
17 KiB
Go
// ============================================
|
|
// 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",
|
|
})
|
|
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",
|
|
})
|
|
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)",
|
|
})
|
|
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",
|
|
})
|
|
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",
|
|
})
|
|
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",
|
|
})
|
|
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",
|
|
})
|
|
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",
|
|
})
|
|
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",
|
|
"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",
|
|
})
|
|
return
|
|
}
|
|
|
|
clientUsername, _ := command["username"].(string)
|
|
livreurAssign, _ := command["livreur_assign"].(string)
|
|
|
|
if clientUsername != "" {
|
|
clientMsg := fmt.Sprintf("Ta commande #%d a bien été livrée ! Bonne dégustation l'ami et à bientôt 😊\n\n<b>⚠️ VALIDE LA RÉCEPTION DE TA COMMANDE DANS LA RUBRIQUE SUIVI POUR RÉCUPÉRER TES POINTS DE FIDÉLITÉ ⚠️</b>", database.GetClientOrderID(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 != "",
|
|
})
|
|
}
|