chore: build
This commit is contained in:
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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 $$
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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"`
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user