diff --git a/backend/gestion/db/db_basket.go b/backend/gestion/db/db_basket.go index 19bff606..a68d2623 100644 --- a/backend/gestion/db/db_basket.go +++ b/backend/gestion/db/db_basket.go @@ -43,29 +43,38 @@ func (d *Database) GetAllProductsInBasket(username string) ([]models.Panier, err return baskets, nil } -// AddRewardToBasket ajoute un produit récompense au panier (prix = 0, is_reward = true). +// AddRewardsToBasket ajoute plusieurs produits récompense au panier (prix = 0, is_reward = true). +// Supprime les anciens items récompense avant d'insérer les nouveaux. // Pas de vérification de stock — les récompenses sont gérées par l'admin. -func (d *Database) AddRewardToBasket(username string, productID int, quantity float64, poolKey string) (*models.Panier, error) { - var basket models.Panier +func (d *Database) AddRewardsToBasket(username string, items []models.RewardItem, poolKey string) ([]models.Panier, error) { + var baskets []models.Panier err := d.GDB.Transaction(func(tx *gorm.DB) error { - // Vérifier que le produit existe - var productName string - if err := tx.Raw(`SELECT name FROM products WHERE id = ?`, productID).Scan(&productName).Error; err != nil || productName == "" { - return fmt.Errorf("produit récompense introuvable (id=%d)", productID) - } // Supprimer tout article récompense existant (remplacement) tx.Exec(`DELETE FROM baskets WHERE username = ? AND is_reward = true`, username) - // Insérer avec prix 0, is_reward = true et le pool_key - return tx.Raw(` - INSERT INTO baskets (username, product_id, quantity, price, is_reward, reward_pool_key, created_at) - VALUES (?, ?, ?, 0, true, ?, CURRENT_TIMESTAMP) - RETURNING id, username, product_id, quantity, price, is_reward, reward_pool_key, created_at`, - username, productID, quantity, poolKey).Scan(&basket).Error + for _, item := range items { + if item.ProductID <= 0 || item.Quantity <= 0 { + continue + } + var productName string + if err := tx.Raw(`SELECT name FROM products WHERE id = ?`, item.ProductID).Scan(&productName).Error; err != nil || productName == "" { + return fmt.Errorf("produit récompense introuvable (id=%d)", item.ProductID) + } + var basket models.Panier + if err := tx.Raw(` + INSERT INTO baskets (username, product_id, quantity, price, is_reward, reward_pool_key, created_at) + VALUES (?, ?, ?, 0, true, ?, CURRENT_TIMESTAMP) + RETURNING id, username, product_id, quantity, price, is_reward, reward_pool_key, created_at`, + username, item.ProductID, item.Quantity, poolKey).Scan(&basket).Error; err != nil { + return err + } + baskets = append(baskets, basket) + } + return nil }) if err != nil { return nil, err } - return &basket, nil + return baskets, nil } // HasOnlyRewardItems retourne true si le panier ne contient que des articles récompense. diff --git a/backend/gestion/handlers/points.go b/backend/gestion/handlers/points.go index 90336f3d..9cd8ec16 100644 --- a/backend/gestion/handlers/points.go +++ b/backend/gestion/handlers/points.go @@ -192,17 +192,18 @@ func ClaimMyReward(c *gin.Context) { return } - // Ajouter le produit récompense au panier si configuré + // Ajouter les produits récompense au panier si configurés productAdded := false - var productName string - if reward.RewardProductID > 0 && reward.RewardQuantity > 0 { - qty := reward.RewardQuantity - if item, addErr := database.AddRewardToBasket(username, reward.RewardProductID, qty, req.PoolKey); addErr == nil { + var productNames []string + if len(reward.RewardItems) > 0 { + if added, addErr := database.AddRewardsToBasket(username, reward.RewardItems, req.PoolKey); addErr == nil && len(added) > 0 { productAdded = true - productName = item.ProductName - log.Printf("✅ [CLAIM] Produit récompense id=%d (%.2f) ajouté au panier de %s", reward.RewardProductID, qty, username) - } else { - log.Printf("⚠️ [CLAIM] Impossible d'ajouter produit récompense: %v", addErr) + 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) } } @@ -211,7 +212,7 @@ func ClaimMyReward(c *gin.Context) { "description": reward.Description, "remaining_rewards": remaining, "product_added": productAdded, - "product_name": productName, + "product_names": productNames, }) } diff --git a/backend/gestion/models/settings.go b/backend/gestion/models/settings.go index 097169d8..1c1a6c70 100644 --- a/backend/gestion/models/settings.go +++ b/backend/gestion/models/settings.go @@ -22,14 +22,20 @@ type RewardCategoryConfig struct { Amount float64 `json:"amount"` // valeur monétaire de la récompense pour cette catégorie (ex: 30.0) } +// RewardItem représente un produit offert lors d'une récompense, avec sa quantité et son prix associé +type RewardItem struct { + ProductID int `json:"product_id"` // ID du produit ajouté au panier + Quantity float64 `json:"quantity"` // quantité offerte + Price float64 `json:"price"` // valeur indicative affichée au client +} + // PointsReward représente la récompense débloquée à partir d'un seuil de points cumulés type PointsReward struct { - Threshold int `json:"threshold"` // points cumulés nécessaires (ex: 20) - Type string `json:"type"` // "free_product" | "half_price_product" | "custom" - Description string `json:"description"` // description libre affichée au client - CategoryConfigs []RewardCategoryConfig `json:"category_configs"` // catégories + produits éligibles - RewardProductID int `json:"reward_product_id"` // ID produit ajouté au panier (0 = désactivé) - RewardQuantity float64 `json:"reward_quantity"` // quantité du produit récompense + Threshold int `json:"threshold"` // points cumulés nécessaires (ex: 20) + Type string `json:"type"` // "free_product" | "half_price_product" | "custom" + Description string `json:"description"` // description libre affichée au client + CategoryConfigs []RewardCategoryConfig `json:"category_configs"` // catégories + produits éligibles + RewardItems []RewardItem `json:"reward_items"` // produits ajoutés au panier lors du claim } // DaySchedule représente les horaires de livraison pour un jour de la semaine