package handlers import ( "gestion/db" "gestion/models" "gestion/utils" "log" "net/http" "strings" "github.com/gin-gonic/gin" ) // GetMyPointsRewards retourne les points et les récompenses disponibles du client connecté. // La récompense est globale : son seuil s'applique indépendamment à chaque pool. func GetMyPointsRewards(c *gin.Context) { username := c.GetString("username") if username == "" { c.JSON(http.StatusUnauthorized, gin.H{"error": "Non authentifié"}) return } database := c.MustGet("database").(*db.Database) settings, err := database.GetSettings() if err != nil { utils.ServerErr(c, "Erreur lecture paramètres", err) return } if !settings.PointsEnabled || len(settings.PointsPools) == 0 { c.JSON(http.StatusOK, gin.H{"enabled": false, "pools": []gin.H{}, "reward": nil}) return } pointsExtra, pointsRedeemed, err := database.GetClientPointsAndRewards(username) if err != nil { utils.ServerErr(c, "Erreur lecture points", err) return } reward := settings.PointsReward type EligibleConfigResponse struct { Category string `json:"category"` AllProducts bool `json:"all_products"` ProductIDs []int `json:"product_ids"` ProductNames []string `json:"product_names"` } type PoolInfo struct { Key string `json:"key"` Name string `json:"name"` Points int `json:"points"` RewardsEarned int `json:"rewards_earned"` RewardsClaimed int `json:"rewards_claimed"` RewardsAvailable int `json:"rewards_available"` EligibleConfigs []EligibleConfigResponse `json:"eligible_configs"` } // Collecter tous les product_ids nécessaires en un seul passage allProductIDs := make([]int, 0) if reward != nil { for _, cfg := range reward.CategoryConfigs { if !cfg.AllProducts { allProductIDs = append(allProductIDs, cfg.ProductIDs...) } } for _, item := range reward.RewardItems { if item.ProductID > 0 { allProductIDs = append(allProductIDs, item.ProductID) } } } productNames, _ := database.GetProductNamesByIDs(allProductIDs) pools := make([]PoolInfo, 0, len(settings.PointsPools)) for _, pool := range settings.PointsPools { pts := pointsExtra[pool.Key] redeemed := pointsRedeemed[pool.Key] var earned, available int if reward != nil && reward.Threshold > 0 { earned = pts / reward.Threshold available = earned - redeemed if available < 0 { available = 0 } } // Filtrer les category_configs aux seules catégories du pool poolCats := make(map[string]bool, len(pool.Categories)) for _, c := range pool.Categories { poolCats[c] = true } eligibleConfigs := make([]EligibleConfigResponse, 0) if reward != nil { for _, cfg := range reward.CategoryConfigs { if !poolCats[cfg.Category] { continue } names := make([]string, 0, len(cfg.ProductIDs)) for _, pid := range cfg.ProductIDs { if n, ok := productNames[pid]; ok { names = append(names, n) } } eligibleConfigs = append(eligibleConfigs, EligibleConfigResponse{ Category: cfg.Category, AllProducts: cfg.AllProducts, ProductIDs: cfg.ProductIDs, ProductNames: names, }) } } pools = append(pools, PoolInfo{ Key: pool.Key, Name: pool.Name, Points: pts, RewardsEarned: earned, RewardsClaimed: redeemed, RewardsAvailable: available, EligibleConfigs: eligibleConfigs, }) } // Construire la liste des produits récompense avec leurs noms type RewardItemResponse struct { ProductID int `json:"product_id"` ProductName string `json:"product_name"` Quantity float64 `json:"quantity"` Price float64 `json:"price"` } var rewardMeta gin.H if reward != nil { rewardItems := make([]RewardItemResponse, 0, len(reward.RewardItems)) for _, item := range reward.RewardItems { if item.ProductID <= 0 { continue } name := productNames[item.ProductID] rewardItems = append(rewardItems, RewardItemResponse{ ProductID: item.ProductID, ProductName: name, Quantity: item.Quantity, Price: item.Price, }) } rewardMeta = gin.H{ "threshold": reward.Threshold, "type": reward.Type, "description": reward.Description, "reward_items": rewardItems, } } c.JSON(http.StatusOK, gin.H{"enabled": true, "pools": pools, "reward": rewardMeta}) } // ClaimMyReward réclame une récompense sur un pool donné si le client a atteint le seuil. func ClaimMyReward(c *gin.Context) { username := c.GetString("username") if username == "" { c.JSON(http.StatusUnauthorized, gin.H{"error": "Non authentifié"}) return } var req struct { PoolKey string `json:"pool_key" binding:"required"` ProductID int `json:"product_id"` // optionnel : 0 = automatique (1 seul item) } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "pool_key requis"}) return } database := c.MustGet("database").(*db.Database) settings, err := database.GetSettings() if err != nil { utils.ServerErr(c, "Erreur lecture paramètres", err) return } if !settings.PointsEnabled { c.JSON(http.StatusForbidden, gin.H{"error": "Système de points désactivé"}) return } reward := settings.PointsReward if reward == nil || reward.Threshold <= 0 { c.JSON(http.StatusBadRequest, gin.H{"error": "Aucune récompense configurée"}) return } // Vérifier que le pool existe poolExists := false for _, p := range settings.PointsPools { if p.Key == req.PoolKey { poolExists = true break } } if !poolExists { c.JSON(http.StatusBadRequest, gin.H{"error": "Pool introuvable"}) return } remaining, err := database.ClaimPoolReward(username, req.PoolKey, reward.Threshold) if err != nil { if strings.Contains(err.Error(), "pas de récompense disponible") { c.JSON(http.StatusConflict, gin.H{"error": "Pas assez de points pour réclamer une récompense"}) return } utils.ServerErr(c, "Erreur réclamation récompense", err) return } // Si le client a sélectionné un produit spécifique parmi plusieurs, ne donner que celui-là itemsToAdd := reward.RewardItems if req.ProductID > 0 && len(reward.RewardItems) > 1 { for _, item := range reward.RewardItems { if item.ProductID == req.ProductID { itemsToAdd = []models.RewardItem{item} break } } } // Ajouter les produits récompense au panier si configurés productAdded := false var productNames []string if len(itemsToAdd) > 0 { if added, addErr := database.AddRewardsToBasket(username, itemsToAdd, req.PoolKey); addErr == nil && len(added) > 0 { productAdded = true for _, item := range added { productNames = append(productNames, item.ProductName) } log.Printf("✅ [CLAIM] %d produit(s) récompense ajoutés au panier de %s", len(added), username) } else if addErr != nil { log.Printf("⚠️ [CLAIM] Impossible d'ajouter produits récompense: %v", addErr) } } c.JSON(http.StatusOK, gin.H{ "success": true, "description": reward.Description, "remaining_rewards": remaining, "product_added": productAdded, "product_names": productNames, }) } // AdminResetClientRedeemed remet à zéro les récompenses réclamées d'un client (admin). func AdminResetClientRedeemed(c *gin.Context) { username := c.Param("username") poolKey := c.Query("pool_key") database := c.MustGet("database").(*db.Database) if err := database.ResetClientRedeemed(username, poolKey); err != nil { utils.ServerErr(c, "Erreur reset récompenses", err) return } c.JSON(http.StatusOK, gin.H{"success": true}) }