chore: update
This commit is contained in:
@@ -1,8 +1,3 @@
|
||||
// ============================================
|
||||
// handlers/commands_handlers_CORRIGES.go
|
||||
// ============================================
|
||||
// ⚠️ CreateCommandFromBasket SUPPRIMÉ (utiliser ValidateBasket à la place)
|
||||
|
||||
package handlers
|
||||
|
||||
import (
|
||||
@@ -12,6 +7,7 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -19,14 +15,17 @@ import (
|
||||
|
||||
var (
|
||||
rateLimitMap = make(map[string][]time.Time)
|
||||
rateLimitMu sync.Mutex
|
||||
maxRequests = 10
|
||||
timeWindow = time.Minute
|
||||
)
|
||||
|
||||
func checkRateLimit(key string) bool {
|
||||
rateLimitMu.Lock()
|
||||
defer rateLimitMu.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
if timestamps, exists := rateLimitMap[key]; exists {
|
||||
// Nettoyer les anciennes entrées
|
||||
var validTimestamps []time.Time
|
||||
for _, ts := range timestamps {
|
||||
if now.Sub(ts) < timeWindow {
|
||||
@@ -399,7 +398,7 @@ func ApproveDelivery(c *gin.Context) {
|
||||
|
||||
// ✅ TRANSACTION ATOMIQUE dans la DB pour éviter race condition
|
||||
// Cette fonction doit être créée dans le fichier db
|
||||
totalPoints, err := database.ApproveDeliveryAtomic(commandID, username)
|
||||
totalPoints, pointCategory, err := database.ApproveDeliveryAtomic(commandID, username)
|
||||
if err != nil {
|
||||
log.Printf("❌ [APPROVE] Erreur: %v", err)
|
||||
// ❌ Ne pas exposer les détails de l'erreur
|
||||
@@ -409,13 +408,14 @@ func ApproveDelivery(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("✅ [APPROVE] %d points attribués à %s", totalPoints, username)
|
||||
log.Printf("✅ [APPROVE] %d points attribués à %s (catégorie: %s)", totalPoints, username, pointCategory)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": "Livraison confirmée",
|
||||
"command_id": commandID,
|
||||
"points_earned": totalPoints,
|
||||
"category": pointCategory,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -446,14 +446,14 @@ func StaffApproveDelivery(c *gin.Context) {
|
||||
|
||||
log.Printf("✅ [STAFF_APPROVE] %s (%s) confirme réception cmd %d", staffUsername, role, commandID)
|
||||
|
||||
totalPoints, clientUsername, err := database.ApproveDeliveryAtomicByStaff(commandID, staffUsername)
|
||||
totalPoints, pointCategory, clientUsername, err := database.ApproveDeliveryAtomicByStaff(commandID, staffUsername)
|
||||
if err != nil {
|
||||
log.Printf("❌ [STAFF_APPROVE] Erreur: %v", err)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Impossible de confirmer la réception: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("✅ [STAFF_APPROVE] %d points attribués au client %s", totalPoints, clientUsername)
|
||||
log.Printf("✅ [STAFF_APPROVE] %d points attribués au client %s (catégorie: %s)", totalPoints, clientUsername, pointCategory)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
@@ -461,6 +461,7 @@ func StaffApproveDelivery(c *gin.Context) {
|
||||
"command_id": commandID,
|
||||
"client_username": clientUsername,
|
||||
"points_earned": totalPoints,
|
||||
"category": pointCategory,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -542,7 +543,7 @@ func ValidateDelivery(c *gin.Context) {
|
||||
|
||||
currentStatus, _ := command["status"].(string)
|
||||
|
||||
validStatuses := []string{"assigned", "en_route", "pending", "support", "livre"}
|
||||
validStatuses := []string{"assigned", "en_route", "pending", "livre"}
|
||||
isValid := false
|
||||
for _, s := range validStatuses {
|
||||
if currentStatus == s {
|
||||
@@ -674,7 +675,7 @@ func AssignDeliveryPerson(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
database.AddCommandLog(commandID, "support",
|
||||
database.AddCommandLog(commandID, "assigned",
|
||||
fmt.Sprintf("Livreur '%s' assigné manuellement par %s", livreurUsername, staffUsername),
|
||||
staffUsername.(string))
|
||||
|
||||
@@ -689,131 +690,6 @@ func AssignDeliveryPerson(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// DisableCommands désactive une ou plusieurs commandes
|
||||
// POST /api/v1/admin/commands/disable
|
||||
func DisableCommands(c *gin.Context) {
|
||||
database := c.MustGet("database").(*db.Database)
|
||||
|
||||
var req struct {
|
||||
CommandIDs []int `json:"command_ids" binding:"required"`
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "Données invalides",
|
||||
"details": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.CommandIDs) == 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "Aucun ID de commande fourni",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
adminUsername, exists := c.Get("username")
|
||||
if !exists {
|
||||
adminUsername = "admin"
|
||||
}
|
||||
|
||||
log.Printf("❌ [DISABLE] Désactivation de %d commande(s) par %s", len(req.CommandIDs), adminUsername)
|
||||
|
||||
disabledCount := 0
|
||||
failedCount := 0
|
||||
errors := []string{}
|
||||
|
||||
for _, commandID := range req.CommandIDs {
|
||||
command, err := database.GetCommandByID(commandID)
|
||||
if err != nil {
|
||||
errors = append(errors, "Commande "+strconv.Itoa(commandID)+" non trouvée")
|
||||
failedCount++
|
||||
continue
|
||||
}
|
||||
|
||||
currentStatus := command["status"].(string)
|
||||
if currentStatus == "disabled" {
|
||||
errors = append(errors, "Commande "+strconv.Itoa(commandID)+" déjà désactivée")
|
||||
failedCount++
|
||||
continue
|
||||
}
|
||||
|
||||
err = database.UpdateCommandStatus(commandID, "disabled")
|
||||
if err != nil {
|
||||
errors = append(errors, "Erreur désactivation cmd "+strconv.Itoa(commandID))
|
||||
failedCount++
|
||||
continue
|
||||
}
|
||||
|
||||
reason := req.Reason
|
||||
if reason == "" {
|
||||
reason = "Désactivée par admin"
|
||||
}
|
||||
database.AddCommandLog(commandID, "disabled", reason, adminUsername.(string))
|
||||
disabledCount++
|
||||
|
||||
log.Printf("✅ [DISABLE] Commande %d désactivée", commandID)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": "Traitement des commandes terminé",
|
||||
"disabled_count": disabledCount,
|
||||
"failed_count": failedCount,
|
||||
"errors": errors,
|
||||
})
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// COMMANDES CLIENT (My Orders)
|
||||
// ============================================
|
||||
|
||||
// GetMyCommands récupère les commandes du client authentifié
|
||||
// GET /api/v1/my-commands?status=pending
|
||||
func GetMyCommands(c *gin.Context) {
|
||||
database := c.MustGet("database").(*db.Database)
|
||||
|
||||
// ✅ SÉCURITÉ: Récupérer depuis JWT validé
|
||||
username, exists := c.Get("username")
|
||||
if !exists {
|
||||
log.Printf("❌ [MY_CMDS] Utilisateur non authentifié")
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"error": "Utilisateur non authentifié",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
usernameStr := username.(string)
|
||||
status := c.Query("status")
|
||||
|
||||
// ✅ NOUVEAU: Paramètre pour exclure les commandes approved
|
||||
excludeApproved := c.Query("exclude_approved") == "true"
|
||||
|
||||
log.Printf("📋 [MY_CMDS] Récupération pour %s (status=%s, exclude_approved=%v)",
|
||||
usernameStr, status, excludeApproved)
|
||||
|
||||
// ✅ Utiliser la nouvelle fonction avec filtrage
|
||||
commands, err := database.GetCommandsWithFilter(status, usernameStr, excludeApproved)
|
||||
if err != nil {
|
||||
log.Printf("❌ [MY_CMDS] Erreur: %v", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "Erreur lors de la récupération des commandes",
|
||||
"details": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("✅ [MY_CMDS] Trouvées: %d commandes", len(commands))
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"commands": commands,
|
||||
"count": len(commands),
|
||||
})
|
||||
}
|
||||
|
||||
func GetClientCommandsHistory(c *gin.Context) {
|
||||
database := c.MustGet("database").(*db.Database)
|
||||
|
||||
@@ -924,9 +800,9 @@ func NotifyClientToDescend(c *gin.Context) {
|
||||
log.Printf("🔔 [NOTIFY] Client %s notifié pour commande %d par %s", clientUsername, commandID, staffUsername)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": "Client notifié",
|
||||
"client_username": clientUsername,
|
||||
"success": true,
|
||||
"message": "Client notifié",
|
||||
"client_username": clientUsername,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -989,12 +865,13 @@ func ShowItems(c *gin.Context) {
|
||||
log.Printf("✅ [ITEMS] %d items récupérés", len(items))
|
||||
|
||||
commandInfo := map[string]interface{}{
|
||||
"id": items[0]["command_id"],
|
||||
"status": items[0]["command_status"],
|
||||
"address": items[0]["command_address"],
|
||||
"total_prix": items[0]["total_prix"],
|
||||
"livreur": items[0]["livreur_assign"],
|
||||
"created_at": items[0]["command_created_at"],
|
||||
"id": items[0]["command_id"],
|
||||
"status": items[0]["command_status"],
|
||||
"address": items[0]["command_address"],
|
||||
"total_prix": items[0]["total_prix"],
|
||||
"referral_used": items[0]["referral_used"],
|
||||
"livreur": items[0]["livreur_assign"],
|
||||
"created_at": items[0]["command_created_at"],
|
||||
}
|
||||
|
||||
clientInfo := map[string]interface{}{
|
||||
@@ -1098,90 +975,6 @@ func GetCommandItemsWithDetails(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// GetClientCommandsItems récupère tous les items du client
|
||||
// GET /api/v1/clients/:username/commands/items
|
||||
func GetClientCommandsItems(c *gin.Context) {
|
||||
database := c.MustGet("database").(*db.Database)
|
||||
|
||||
username := c.Param("username")
|
||||
if username == "" {
|
||||
log.Printf("❌ [CLIENT_ITEMS] Username manquant")
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Username requis"})
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("📦 [CLIENT_ITEMS] Récupération pour: %s", username)
|
||||
|
||||
client, err := database.GetClientByUsername(username)
|
||||
if err != nil {
|
||||
log.Printf("❌ [CLIENT_ITEMS] Client non trouvé")
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Client non trouvé"})
|
||||
return
|
||||
}
|
||||
|
||||
items, err := database.GetCommandItemsByUsername(username)
|
||||
if err != nil {
|
||||
log.Printf("❌ [CLIENT_ITEMS] Erreur: %v", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "Erreur lors de la récupération des items",
|
||||
"details": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
commandsMap := make(map[int][]map[string]interface{})
|
||||
var commandIDs []int
|
||||
|
||||
for _, item := range items {
|
||||
cmdID := int(item["command_id"].(float64))
|
||||
if _, exists := commandsMap[cmdID]; !exists {
|
||||
commandIDs = append(commandIDs, cmdID)
|
||||
}
|
||||
commandsMap[cmdID] = append(commandsMap[cmdID], item)
|
||||
}
|
||||
|
||||
log.Printf("✅ [CLIENT_ITEMS] %d items dans %d commandes", len(items), len(commandsMap))
|
||||
|
||||
type CommandGroup struct {
|
||||
CommandID int `json:"command_id"`
|
||||
Status string `json:"status"`
|
||||
Address string `json:"address"`
|
||||
TotalPrice float64 `json:"total_price"`
|
||||
LivreurAssign string `json:"livreur_assign"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
ItemsCount int `json:"items_count"`
|
||||
Items []map[string]interface{} `json:"items"`
|
||||
}
|
||||
|
||||
var commandGroups []CommandGroup
|
||||
for _, cmdID := range commandIDs {
|
||||
items := commandsMap[cmdID]
|
||||
if len(items) > 0 {
|
||||
group := CommandGroup{
|
||||
CommandID: cmdID,
|
||||
Status: items[0]["command_status"].(string),
|
||||
Address: items[0]["command_address"].(string),
|
||||
TotalPrice: items[0]["total_prix"].(float64),
|
||||
LivreurAssign: fmt.Sprintf("%v", items[0]["livreur_assign"]),
|
||||
CreatedAt: items[0]["command_created_at"].(string),
|
||||
ItemsCount: len(items),
|
||||
Items: items,
|
||||
}
|
||||
commandGroups = append(commandGroups, group)
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"client": client.Username,
|
||||
"client_name": fmt.Sprintf("%s %s", client.Prenom, client.Nom),
|
||||
"phone": client.Telephone,
|
||||
"total_items": len(items),
|
||||
"total_commands": len(commandGroups),
|
||||
"commands": commandGroups,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateItemStatus met à jour le statut d'un item
|
||||
// PUT /api/v1/admin/items/:item_id/status
|
||||
func UpdateItemStatus(c *gin.Context) {
|
||||
@@ -1209,7 +1002,7 @@ func UpdateItemStatus(c *gin.Context) {
|
||||
|
||||
log.Printf("📝 [UPD_ITEM] Mise à jour: item=%d, status=%s", itemID, req.Status)
|
||||
|
||||
validStatuses := []string{"pending", "preparing", "ready", "shipped", "delivered"}
|
||||
validStatuses := []string{"pending", "preparing", "delivered"}
|
||||
isValid := false
|
||||
for _, vs := range validStatuses {
|
||||
if req.Status == vs {
|
||||
@@ -1248,65 +1041,6 @@ func UpdateItemStatus(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// GetCommandFullDetails récupère tous les détails d'une commande
|
||||
// GET /api/v1/admin/commands/:id/full-details
|
||||
func GetCommandFullDetails(c *gin.Context) {
|
||||
database := c.MustGet("database").(*db.Database)
|
||||
|
||||
userRole := c.GetString("role")
|
||||
if userRole != "admin" {
|
||||
log.Printf("❌ [FULL_DETAILS] Accès refusé - role=%s", userRole)
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "Accès refusé - Admin uniquement"})
|
||||
return
|
||||
}
|
||||
|
||||
commandID, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "ID de commande invalide"})
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("📋 [FULL_DETAILS] Récupération complète: cmd %d", commandID)
|
||||
|
||||
command, err := database.GetCommandByID(commandID)
|
||||
if err != nil {
|
||||
log.Printf("❌ [FULL_DETAILS] Non trouvée")
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Commande non trouvée"})
|
||||
return
|
||||
}
|
||||
|
||||
items, _ := database.GetCommandItems(commandID)
|
||||
logs, _ := database.GetCommandLogs(commandID)
|
||||
|
||||
var clientFullInfo map[string]interface{}
|
||||
if username, ok := command["username"].(string); ok {
|
||||
if client, err := database.GetClientByUsername(username); err == nil {
|
||||
clientFullInfo = map[string]interface{}{
|
||||
"username": client.Username,
|
||||
"nom": client.Nom,
|
||||
"prenom": client.Prenom,
|
||||
"telephone": client.Telephone,
|
||||
"commands": client.Command,
|
||||
"points": client.Point,
|
||||
"penalties": client.Amende,
|
||||
"created_at": client.CreatedAt,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("✅ [FULL_DETAILS] Récupéré: %d items, %d logs", len(items), len(logs))
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"command": command,
|
||||
"client_info": clientFullInfo,
|
||||
"items": items,
|
||||
"items_count": len(items),
|
||||
"logs": logs,
|
||||
"logs_count": len(logs),
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteCommandItem supprime un item d'une commande
|
||||
// DELETE /api/v2/admin/protected/orders/:id/items/:item_id
|
||||
func DeleteCommandItem(c *gin.Context) {
|
||||
@@ -1404,65 +1138,3 @@ func UpdateCommandStatusAdmin(c *gin.Context) {
|
||||
"new_status": req.Status,
|
||||
})
|
||||
}
|
||||
|
||||
// GetCommandItemsStats récupère les stats d'une commande
|
||||
// GET /api/v1/commands/:id/stats
|
||||
func GetCommandItemsStats(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 invalide"})
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("📊 [STATS] Calcul stats: cmd %d", commandID)
|
||||
|
||||
items, err := database.GetCommandItems(commandID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if len(items) == 0 {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"items_count": 0,
|
||||
"total_items": 0,
|
||||
"total_price": 0,
|
||||
"avg_price": 0,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
totalItems := 0
|
||||
totalPrice := 0.0
|
||||
statusCounts := make(map[string]int)
|
||||
|
||||
for _, item := range items {
|
||||
quantite := int(item["quantite"].(float64))
|
||||
prix := item["prix"].(float64)
|
||||
status := item["status"].(string)
|
||||
|
||||
totalItems += quantite
|
||||
totalPrice += prix * float64(quantite)
|
||||
statusCounts[status]++
|
||||
}
|
||||
|
||||
avgPrice := 0.0
|
||||
if len(items) > 0 {
|
||||
avgPrice = totalPrice / float64(len(items))
|
||||
}
|
||||
|
||||
log.Printf("✅ [STATS] Items=%d, Total=%.2f€", totalItems, totalPrice)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"command_id": commandID,
|
||||
"items_count": len(items),
|
||||
"total_items": totalItems,
|
||||
"total_price": totalPrice,
|
||||
"avg_price": avgPrice,
|
||||
"status_breakdown": statusCounts,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user