From 093a7e0c421710aa3d3ed207570359fb187a2a81 Mon Sep 17 00:00:00 2001 From: Xor290 Date: Sat, 13 Jun 2026 19:18:12 +0200 Subject: [PATCH] chore: build --- backend/gestion/db/db_basket.go | 27 ++++++++-------- backend/gestion/db/db_clients.go | 45 ++++++++++++++++++++++++++ backend/gestion/db/db_command_items.go | 13 ++++++-- backend/gestion/db/db_commands.go | 24 ++++++++------ backend/gestion/db/db_init.go | 13 ++++++++ backend/gestion/handlers/points.go | 2 +- backend/gestion/models/commands.go | 18 ++++++----- backend/gestion/models/panier.go | 3 +- 8 files changed, 110 insertions(+), 35 deletions(-) diff --git a/backend/gestion/db/db_basket.go b/backend/gestion/db/db_basket.go index 684c1835..19bff606 100644 --- a/backend/gestion/db/db_basket.go +++ b/backend/gestion/db/db_basket.go @@ -45,7 +45,7 @@ func (d *Database) GetAllProductsInBasket(username string) ([]models.Panier, err // AddRewardToBasket ajoute un produit récompense au panier (prix = 0, is_reward = true). // 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) (*models.Panier, error) { +func (d *Database) AddRewardToBasket(username string, productID int, quantity float64, poolKey string) (*models.Panier, error) { var basket models.Panier err := d.GDB.Transaction(func(tx *gorm.DB) error { // Vérifier que le produit existe @@ -53,14 +53,14 @@ func (d *Database) AddRewardToBasket(username string, productID int, quantity fl 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 pour ce produit (remplacement) - tx.Exec(`DELETE FROM baskets WHERE username = ? AND product_id = ? AND is_reward = true`, username, productID) - // Insérer avec prix 0 et is_reward = true + // 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, created_at) - VALUES (?, ?, ?, 0, true, CURRENT_TIMESTAMP) - RETURNING id, username, product_id, quantity, price, is_reward, created_at`, - username, productID, quantity).Scan(&basket).Error + 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 }) if err != nil { return nil, err @@ -113,20 +113,21 @@ func (d *Database) AddToBasket(username string, productID int, quantity float64) Quantity float64 `gorm:"column:quantity"` Price float64 `gorm:"column:price"` } - tx.Raw(`SELECT id, quantity, price FROM baskets WHERE username = ? AND product_id = ?`, + // Chercher uniquement un item normal (non-récompense) pour ce produit + tx.Raw(`SELECT id, quantity, price FROM baskets WHERE username = ? AND product_id = ? AND is_reward = false`, username, productID).Scan(&existing) if existing.ID != 0 { return tx.Raw(` UPDATE baskets SET quantity = ?, price = ?, created_at = CURRENT_TIMESTAMP - WHERE id = ? RETURNING id, username, product_id, quantity, price, created_at`, + WHERE id = ? AND is_reward = false RETURNING id, username, product_id, quantity, price, is_reward, created_at`, existing.Quantity+quantity, existing.Price+priceResult.Price, existing.ID).Scan(&basket).Error } return tx.Raw(` - INSERT INTO baskets (username, product_id, quantity, price, created_at) - VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP) - RETURNING id, username, product_id, quantity, price, created_at`, + INSERT INTO baskets (username, product_id, quantity, price, is_reward, created_at) + VALUES (?, ?, ?, ?, false, CURRENT_TIMESTAMP) + RETURNING id, username, product_id, quantity, price, is_reward, created_at`, username, productID, quantity, priceResult.Price).Scan(&basket).Error }) if err != nil { diff --git a/backend/gestion/db/db_clients.go b/backend/gestion/db/db_clients.go index b5cab488..2afb93ca 100644 --- a/backend/gestion/db/db_clients.go +++ b/backend/gestion/db/db_clients.go @@ -609,6 +609,51 @@ func (d *Database) CalculateAndAddPointsForCommandTx(tx *gorm.DB, commandID int, log.Printf("🎉 [CalcPointsTx] SUCCÈS - %d points [%s] → %s", totalPoints, pointCategory, username) + // ✅ ÉTAPE 3: Déduire les points des récompenses reçues dans cette commande + var rewardItems []struct { + RewardPoolKey string `gorm:"column:reward_pool_key"` + } + if err := tx.Raw(` + SELECT reward_pool_key FROM command_items + WHERE command_id = ? AND is_reward = true AND reward_pool_key != '' + `, commandID).Scan(&rewardItems).Error; err != nil { + log.Printf("⚠️ [CalcPointsTx] Erreur query reward items: %v", err) + } + + for _, ri := range rewardItems { + if settings.PointsReward == nil || settings.PointsReward.Threshold <= 0 { + break + } + threshold := settings.PointsReward.Threshold + poolKey := ri.RewardPoolKey + // Déduire threshold points de points_extra[poolKey] (plancher à 0) + if err := tx.Exec(` + UPDATE clients + SET points_extra = jsonb_set( + COALESCE(points_extra, '{}'::jsonb), + ARRAY[?], + to_jsonb(GREATEST(0, COALESCE((points_extra->>?)::int, 0) - ?)) + ), updated_at = CURRENT_TIMESTAMP + WHERE username = ? + `, poolKey, poolKey, threshold, username).Error; err != nil { + log.Printf("⚠️ [CalcPointsTx] Erreur déduction points reward pool=%s: %v", poolKey, err) + } else { + log.Printf("🎁 [CalcPointsTx] Récompense reçue: -%d pts pool=%s → %s", threshold, poolKey, username) + } + // Décrémenter points_redeemed[poolKey] (plancher à 0) + if err := tx.Exec(` + UPDATE clients + SET points_redeemed = jsonb_set( + COALESCE(points_redeemed, '{}'::jsonb), + ARRAY[?], + to_jsonb(GREATEST(0, COALESCE((points_redeemed->>?)::int, 0) - 1)) + ), updated_at = CURRENT_TIMESTAMP + WHERE username = ? + `, poolKey, poolKey, username).Error; err != nil { + log.Printf("⚠️ [CalcPointsTx] Erreur décrément redeemed pool=%s: %v", poolKey, err) + } + } + return totalPoints, pointCategory, nil } diff --git a/backend/gestion/db/db_command_items.go b/backend/gestion/db/db_command_items.go index bc321059..6c5e6a28 100644 --- a/backend/gestion/db/db_command_items.go +++ b/backend/gestion/db/db_command_items.go @@ -97,6 +97,8 @@ func (d *Database) InsertCommandItemWithClientInfo( productID int, quantite float64, prix float64, + isReward bool, + rewardPoolKey string, clientUsername, clientNom, clientPrenom, clientTelephone, deliveryAddress string, ) error { log.Printf("📝 [InsertCommandItemWithClientInfo] START - commandID=%d, produit=%s", commandID, produit) @@ -114,8 +116,11 @@ func (d *Database) InsertCommandItemWithClientInfo( return err } - if err := validatePrix(prix); err != nil { - return err + // Les articles récompense ont prix=0, on saute la validation de prix pour eux + if !isReward { + if err := validatePrix(prix); err != nil { + return err + } } if err := validateUsername(clientUsername); err != nil { @@ -161,10 +166,12 @@ func (d *Database) InsertCommandItemWithClientInfo( err := d.GDB.Exec(` INSERT INTO command_items ( command_id, produit, product_id, quantite, prix, + is_reward, reward_pool_key, client_username, client_nom, client_prenom, client_telephone, delivery_address, status, created_at, updated_at - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`, + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`, commandID, produit, productID, quantite, prix, + isReward, rewardPoolKey, clientUsername, clientNom, clientPrenom, clientTelephone, deliveryAddress, ).Error if err != nil { diff --git a/backend/gestion/db/db_commands.go b/backend/gestion/db/db_commands.go index f6911757..0275fd4e 100644 --- a/backend/gestion/db/db_commands.go +++ b/backend/gestion/db/db_commands.go @@ -45,14 +45,16 @@ func validateAddress(address string) error { } type basketItem struct { - ProductID int `gorm:"column:product_id"` - Quantity float64 `gorm:"column:quantity"` - Price float64 `gorm:"column:price"` + ProductID int `gorm:"column:product_id"` + Quantity float64 `gorm:"column:quantity"` + Price float64 `gorm:"column:price"` + IsReward bool `gorm:"column:is_reward"` + RewardPoolKey string `gorm:"column:reward_pool_key"` } func (d *Database) fetchBasketItems(username string) ([]basketItem, float64, error) { var items []basketItem - if err := d.GDB.Table("baskets").Select("product_id, quantity, price").Where("username = ?", username).Scan(&items).Error; err != nil { + if err := d.GDB.Table("baskets").Select("product_id, quantity, price, is_reward, reward_pool_key").Where("username = ?", username).Scan(&items).Error; err != nil { return nil, 0, fmt.Errorf("erreur récupération panier: %w", err) } total := 0.0 @@ -122,11 +124,13 @@ func (d *Database) CreateCommand(username string) (*models.Command, error) { } cmdItem := models.CommandItem{ - CommandID: commandID, - Produit: productName, - ProductID: item.ProductID, - Quantity: item.Quantity, - Price: item.Price, + CommandID: commandID, + Produit: productName, + ProductID: item.ProductID, + Quantity: item.Quantity, + Price: item.Price, + IsReward: item.IsReward, + RewardPoolKey: item.RewardPoolKey, } if err := d.GDB.Create(&cmdItem).Error; err != nil { return nil, fmt.Errorf("erreur lors de l'insertion des items: %w", err) @@ -220,6 +224,8 @@ func (d *Database) CreateCommandWithAddress(username, deliveryAddress string) (* item.ProductID, item.Quantity, item.Price, + item.IsReward, + item.RewardPoolKey, username, clientNom, clientPrenom, diff --git a/backend/gestion/db/db_init.go b/backend/gestion/db/db_init.go index 3d72303a..12b50c2f 100644 --- a/backend/gestion/db/db_init.go +++ b/backend/gestion/db/db_init.go @@ -125,6 +125,19 @@ func InitDB() *Database { log.Fatalf("❌ Erreur migration baskets.is_reward: %v", err) } + // Migration: baskets.reward_pool_key — pool de points utilisé pour la récompense + if _, err = database.Exec(`ALTER TABLE baskets ADD COLUMN IF NOT EXISTS reward_pool_key VARCHAR(100) NOT NULL DEFAULT ''`); err != nil { + log.Fatalf("❌ Erreur migration baskets.reward_pool_key: %v", err) + } + + // Migration: command_items.is_reward + reward_pool_key + if _, err = database.Exec(`ALTER TABLE command_items ADD COLUMN IF NOT EXISTS is_reward BOOLEAN NOT NULL DEFAULT FALSE`); err != nil { + log.Fatalf("❌ Erreur migration command_items.is_reward: %v", err) + } + if _, err = database.Exec(`ALTER TABLE command_items ADD COLUMN IF NOT EXISTS reward_pool_key VARCHAR(100) NOT NULL DEFAULT ''`); err != nil { + log.Fatalf("❌ Erreur migration command_items.reward_pool_key: %v", err) + } + // Migration: command_items.quantite INTEGER → NUMERIC(10,3) pour supporter les quantités fractionnaires if _, err = database.Exec(` DO $$ diff --git a/backend/gestion/handlers/points.go b/backend/gestion/handlers/points.go index bad61396..90336f3d 100644 --- a/backend/gestion/handlers/points.go +++ b/backend/gestion/handlers/points.go @@ -197,7 +197,7 @@ func ClaimMyReward(c *gin.Context) { var productName string if reward.RewardProductID > 0 && reward.RewardQuantity > 0 { qty := reward.RewardQuantity - if item, addErr := database.AddRewardToBasket(username, reward.RewardProductID, qty); addErr == nil { + if item, addErr := database.AddRewardToBasket(username, reward.RewardProductID, qty, req.PoolKey); addErr == nil { productAdded = true productName = item.ProductName log.Printf("✅ [CLAIM] Produit récompense id=%d (%.2f) ajouté au panier de %s", reward.RewardProductID, qty, username) diff --git a/backend/gestion/models/commands.go b/backend/gestion/models/commands.go index 9de1102c..bb74ff91 100644 --- a/backend/gestion/models/commands.go +++ b/backend/gestion/models/commands.go @@ -19,14 +19,16 @@ func (Command) TableName() string { return "commandes" } // CommandItem représente un produit dans une commande type CommandItem struct { - ID int `gorm:"primaryKey;autoIncrement" json:"id"` - CommandID int `gorm:"column:command_id" json:"command_id"` - Produit string `gorm:"column:produit" json:"produit"` - ProductID int `gorm:"column:product_id" json:"product_id"` - Quantity float64 `gorm:"column:quantite" json:"quantity"` - Price float64 `gorm:"column:prix" json:"price"` - CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` - UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` + ID int `gorm:"primaryKey;autoIncrement" json:"id"` + CommandID int `gorm:"column:command_id" json:"command_id"` + Produit string `gorm:"column:produit" json:"produit"` + ProductID int `gorm:"column:product_id" json:"product_id"` + Quantity float64 `gorm:"column:quantite" json:"quantity"` + Price float64 `gorm:"column:prix" json:"price"` + IsReward bool `gorm:"column:is_reward" json:"is_reward"` + RewardPoolKey string `gorm:"column:reward_pool_key" json:"reward_pool_key,omitempty"` + CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` + UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` } type CommandLog struct { diff --git a/backend/gestion/models/panier.go b/backend/gestion/models/panier.go index 24e4c21a..695f3fe5 100644 --- a/backend/gestion/models/panier.go +++ b/backend/gestion/models/panier.go @@ -11,7 +11,8 @@ type Panier struct { Description string `json:"description"` Quantity float64 `json:"quantity"` Price float64 `json:"price"` - IsReward bool `json:"is_reward"` + IsReward bool `json:"is_reward"` + RewardPoolKey string `json:"reward_pool_key,omitempty"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at,omitempty"` }