chore: build

This commit is contained in:
2026-06-13 19:18:12 +02:00
parent 59d4f89a14
commit 093a7e0c42
8 changed files with 110 additions and 35 deletions
+14 -13
View File
@@ -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). // 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. // 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 var basket models.Panier
err := d.GDB.Transaction(func(tx *gorm.DB) error { err := d.GDB.Transaction(func(tx *gorm.DB) error {
// Vérifier que le produit existe // 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 == "" { 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) return fmt.Errorf("produit récompense introuvable (id=%d)", productID)
} }
// Supprimer tout article récompense existant pour ce produit (remplacement) // Supprimer tout article récompense existant (remplacement)
tx.Exec(`DELETE FROM baskets WHERE username = ? AND product_id = ? AND is_reward = true`, username, productID) tx.Exec(`DELETE FROM baskets WHERE username = ? AND is_reward = true`, username)
// Insérer avec prix 0 et is_reward = true // Insérer avec prix 0, is_reward = true et le pool_key
return tx.Raw(` return tx.Raw(`
INSERT INTO baskets (username, product_id, quantity, price, is_reward, created_at) INSERT INTO baskets (username, product_id, quantity, price, is_reward, reward_pool_key, created_at)
VALUES (?, ?, ?, 0, true, CURRENT_TIMESTAMP) VALUES (?, ?, ?, 0, true, ?, CURRENT_TIMESTAMP)
RETURNING id, username, product_id, quantity, price, is_reward, created_at`, RETURNING id, username, product_id, quantity, price, is_reward, reward_pool_key, created_at`,
username, productID, quantity).Scan(&basket).Error username, productID, quantity, poolKey).Scan(&basket).Error
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@@ -113,20 +113,21 @@ func (d *Database) AddToBasket(username string, productID int, quantity float64)
Quantity float64 `gorm:"column:quantity"` Quantity float64 `gorm:"column:quantity"`
Price float64 `gorm:"column:price"` 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) username, productID).Scan(&existing)
if existing.ID != 0 { if existing.ID != 0 {
return tx.Raw(` return tx.Raw(`
UPDATE baskets SET quantity = ?, price = ?, created_at = CURRENT_TIMESTAMP 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.Quantity+quantity, existing.Price+priceResult.Price,
existing.ID).Scan(&basket).Error existing.ID).Scan(&basket).Error
} }
return tx.Raw(` return tx.Raw(`
INSERT INTO baskets (username, product_id, quantity, price, created_at) INSERT INTO baskets (username, product_id, quantity, price, is_reward, created_at)
VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP) VALUES (?, ?, ?, ?, false, CURRENT_TIMESTAMP)
RETURNING id, username, product_id, quantity, price, created_at`, RETURNING id, username, product_id, quantity, price, is_reward, created_at`,
username, productID, quantity, priceResult.Price).Scan(&basket).Error username, productID, quantity, priceResult.Price).Scan(&basket).Error
}) })
if err != nil { if err != nil {
+45
View File
@@ -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) 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 return totalPoints, pointCategory, nil
} }
+10 -3
View File
@@ -97,6 +97,8 @@ func (d *Database) InsertCommandItemWithClientInfo(
productID int, productID int,
quantite float64, quantite float64,
prix float64, prix float64,
isReward bool,
rewardPoolKey string,
clientUsername, clientNom, clientPrenom, clientTelephone, deliveryAddress string, clientUsername, clientNom, clientPrenom, clientTelephone, deliveryAddress string,
) error { ) error {
log.Printf("📝 [InsertCommandItemWithClientInfo] START - commandID=%d, produit=%s", commandID, produit) log.Printf("📝 [InsertCommandItemWithClientInfo] START - commandID=%d, produit=%s", commandID, produit)
@@ -114,8 +116,11 @@ func (d *Database) InsertCommandItemWithClientInfo(
return err return err
} }
if err := validatePrix(prix); err != nil { // Les articles récompense ont prix=0, on saute la validation de prix pour eux
return err if !isReward {
if err := validatePrix(prix); err != nil {
return err
}
} }
if err := validateUsername(clientUsername); err != nil { if err := validateUsername(clientUsername); err != nil {
@@ -161,10 +166,12 @@ func (d *Database) InsertCommandItemWithClientInfo(
err := d.GDB.Exec(` err := d.GDB.Exec(`
INSERT INTO command_items ( INSERT INTO command_items (
command_id, produit, product_id, quantite, prix, command_id, produit, product_id, quantite, prix,
is_reward, reward_pool_key,
client_username, client_nom, client_prenom, client_telephone, delivery_address, client_username, client_nom, client_prenom, client_telephone, delivery_address,
status, created_at, updated_at status, created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`, ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
commandID, produit, productID, quantite, prix, commandID, produit, productID, quantite, prix,
isReward, rewardPoolKey,
clientUsername, clientNom, clientPrenom, clientTelephone, deliveryAddress, clientUsername, clientNom, clientPrenom, clientTelephone, deliveryAddress,
).Error ).Error
if err != nil { if err != nil {
+15 -9
View File
@@ -45,14 +45,16 @@ func validateAddress(address string) error {
} }
type basketItem struct { type basketItem struct {
ProductID int `gorm:"column:product_id"` ProductID int `gorm:"column:product_id"`
Quantity float64 `gorm:"column:quantity"` Quantity float64 `gorm:"column:quantity"`
Price float64 `gorm:"column:price"` 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) { func (d *Database) fetchBasketItems(username string) ([]basketItem, float64, error) {
var items []basketItem 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) return nil, 0, fmt.Errorf("erreur récupération panier: %w", err)
} }
total := 0.0 total := 0.0
@@ -122,11 +124,13 @@ func (d *Database) CreateCommand(username string) (*models.Command, error) {
} }
cmdItem := models.CommandItem{ cmdItem := models.CommandItem{
CommandID: commandID, CommandID: commandID,
Produit: productName, Produit: productName,
ProductID: item.ProductID, ProductID: item.ProductID,
Quantity: item.Quantity, Quantity: item.Quantity,
Price: item.Price, Price: item.Price,
IsReward: item.IsReward,
RewardPoolKey: item.RewardPoolKey,
} }
if err := d.GDB.Create(&cmdItem).Error; err != nil { if err := d.GDB.Create(&cmdItem).Error; err != nil {
return nil, fmt.Errorf("erreur lors de l'insertion des items: %w", err) 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.ProductID,
item.Quantity, item.Quantity,
item.Price, item.Price,
item.IsReward,
item.RewardPoolKey,
username, username,
clientNom, clientNom,
clientPrenom, clientPrenom,
+13
View File
@@ -125,6 +125,19 @@ func InitDB() *Database {
log.Fatalf("❌ Erreur migration baskets.is_reward: %v", err) 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 // Migration: command_items.quantite INTEGER → NUMERIC(10,3) pour supporter les quantités fractionnaires
if _, err = database.Exec(` if _, err = database.Exec(`
DO $$ DO $$
+1 -1
View File
@@ -197,7 +197,7 @@ func ClaimMyReward(c *gin.Context) {
var productName string var productName string
if reward.RewardProductID > 0 && reward.RewardQuantity > 0 { if reward.RewardProductID > 0 && reward.RewardQuantity > 0 {
qty := reward.RewardQuantity 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 productAdded = true
productName = item.ProductName productName = item.ProductName
log.Printf("✅ [CLAIM] Produit récompense id=%d (%.2f) ajouté au panier de %s", reward.RewardProductID, qty, username) log.Printf("✅ [CLAIM] Produit récompense id=%d (%.2f) ajouté au panier de %s", reward.RewardProductID, qty, username)
+10 -8
View File
@@ -19,14 +19,16 @@ func (Command) TableName() string { return "commandes" }
// CommandItem représente un produit dans une commande // CommandItem représente un produit dans une commande
type CommandItem struct { type CommandItem struct {
ID int `gorm:"primaryKey;autoIncrement" json:"id"` ID int `gorm:"primaryKey;autoIncrement" json:"id"`
CommandID int `gorm:"column:command_id" json:"command_id"` CommandID int `gorm:"column:command_id" json:"command_id"`
Produit string `gorm:"column:produit" json:"produit"` Produit string `gorm:"column:produit" json:"produit"`
ProductID int `gorm:"column:product_id" json:"product_id"` ProductID int `gorm:"column:product_id" json:"product_id"`
Quantity float64 `gorm:"column:quantite" json:"quantity"` Quantity float64 `gorm:"column:quantite" json:"quantity"`
Price float64 `gorm:"column:prix" json:"price"` Price float64 `gorm:"column:prix" json:"price"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` IsReward bool `gorm:"column:is_reward" json:"is_reward"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` 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 { type CommandLog struct {
+2 -1
View File
@@ -11,7 +11,8 @@ type Panier struct {
Description string `json:"description"` Description string `json:"description"`
Quantity float64 `json:"quantity"` Quantity float64 `json:"quantity"`
Price float64 `json:"price"` 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"` CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at,omitempty"` UpdatedAt time.Time `json:"updated_at,omitempty"`
} }