Files
projet_gestion_commande/backend/gestion/db/db_basket.go
T
2026-06-13 14:23:23 +02:00

236 lines
8.9 KiB
Go

package db
import (
"fmt"
"gestion/models"
"gorm.io/gorm"
)
// GetProductPrice récupère le prix réel d'un produit pour une quantité donnée (legacy)
func (d *Database) GetProductPrice(name, category string, quantity float64) (float64, error) {
var result struct {
Price float64 `gorm:"column:price"`
}
err := d.GDB.Raw(`
SELECT price
FROM product_prices pp
INNER JOIN products p ON pp.product_id = p.id
WHERE LOWER(p.name) = LOWER(?)
AND LOWER(p.category) = LOWER(?)
AND pp.quantity <= ?
ORDER BY pp.quantity DESC
LIMIT 1`, name, category, quantity).Scan(&result).Error
if err != nil || result.Price == 0 {
return 0, fmt.Errorf("prix produit introuvable pour %f %s: %w", quantity, name, err)
}
return result.Price, nil
}
// GetAllProductsInBasket récupère tous les produits du panier d'un utilisateur
func (d *Database) GetAllProductsInBasket(username string) ([]models.Panier, error) {
var baskets []models.Panier
err := d.GDB.Raw(`
SELECT b.id, b.username, b.product_id, b.quantity, b.price, b.is_reward, b.created_at,
p.name as product_name, p.category, p.description
FROM baskets b
INNER JOIN products p ON b.product_id = p.id
WHERE b.username = ?
ORDER BY b.created_at DESC`, username).Scan(&baskets).Error
if err != nil {
return nil, fmt.Errorf("erreur lors de la récupération du panier: %w", err)
}
return baskets, nil
}
// 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) {
var basket 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 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
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
})
if err != nil {
return nil, err
}
return &basket, nil
}
// HasOnlyRewardItems retourne true si le panier ne contient que des articles récompense.
func (d *Database) HasOnlyRewardItems(username string) (bool, error) {
var counts struct {
Total int `gorm:"column:total"`
Normal int `gorm:"column:normal"`
}
err := d.GDB.Raw(`
SELECT COUNT(*) as total,
COUNT(*) FILTER (WHERE is_reward = false) as normal
FROM baskets WHERE username = ?`, username).Scan(&counts).Error
if err != nil {
return false, err
}
return counts.Total > 0 && counts.Normal == 0, nil
}
// AddToBasket vérifie le stock disponible et ajoute l'article au panier.
// Le stock n'est pas décrémenté ici — il l'est uniquement au checkout.
func (d *Database) AddToBasket(username string, productID int, quantity float64) (*models.Panier, error) {
var basket models.Panier
err := d.GDB.Transaction(func(tx *gorm.DB) error {
var currentStock float64
if err := tx.Raw(`SELECT stock FROM products WHERE id = ? FOR UPDATE`, productID).Scan(&currentStock).Error; err != nil {
return fmt.Errorf("erreur lecture stock: %w", err)
}
if currentStock < quantity {
return fmt.Errorf("stock insuffisant")
}
var priceResult struct {
Price float64 `gorm:"column:price"`
}
if err := tx.Raw(`
SELECT price FROM product_prices
WHERE product_id = ? AND quantity <= ? AND active_price = true
ORDER BY quantity DESC LIMIT 1`,
productID, quantity).Scan(&priceResult).Error; err != nil || priceResult.Price == 0 {
return fmt.Errorf("prix introuvable pour product_id=%d qty=%.3f", productID, quantity)
}
var existing struct {
ID int `gorm:"column:id"`
Quantity float64 `gorm:"column:quantity"`
Price float64 `gorm:"column:price"`
}
tx.Raw(`SELECT id, quantity, price FROM baskets WHERE username = ? AND product_id = ?`,
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`,
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`,
username, productID, quantity, priceResult.Price).Scan(&basket).Error
})
if err != nil {
return nil, err
}
return &basket, nil
}
// DeleteProductFromBasket supprime un produit spécifique du panier.
// Le stock n'est pas restitué car il n'a pas été décrémenté à l'ajout.
func (d *Database) DeleteProductFromBasket(basketID int) error {
result := d.GDB.Exec(`DELETE FROM baskets WHERE id = ?`, basketID)
if result.Error != nil {
return fmt.Errorf("erreur lors de la suppression du produit: %w", result.Error)
}
if result.RowsAffected == 0 {
return fmt.Errorf("produit non trouvé dans le panier")
}
return nil
}
// ClearBasket vide complètement le panier d'un utilisateur.
// Le stock n'est pas restitué car il n'a pas été décrémenté à l'ajout.
func (d *Database) ClearBasket(username string) error {
return d.GDB.Exec(`DELETE FROM baskets WHERE username = ?`, username).Error
}
// ClearBasketOnCheckout décrémente le stock pour chaque article du panier puis vide le panier.
// C'est ici que le stock est effectivement consommé, au moment de la validation de la commande.
func (d *Database) ClearBasketOnCheckout(username string) error {
return d.GDB.Transaction(func(tx *gorm.DB) error {
var items []struct {
ProductID int `gorm:"column:product_id"`
Quantity float64 `gorm:"column:quantity"`
}
if err := tx.Raw(`SELECT product_id, quantity FROM baskets WHERE username = ? FOR UPDATE`, username).Scan(&items).Error; err != nil {
return fmt.Errorf("erreur lecture panier: %w", err)
}
for _, item := range items {
var currentStock float64
if err := tx.Raw(`SELECT stock FROM products WHERE id = ? FOR UPDATE`, item.ProductID).Scan(&currentStock).Error; err != nil {
return fmt.Errorf("erreur lecture stock produit %d: %w", item.ProductID, err)
}
if currentStock < item.Quantity {
return fmt.Errorf("stock insuffisant pour le produit %d", item.ProductID)
}
if err := tx.Exec(`UPDATE products SET stock = stock - ? WHERE id = ?`, item.Quantity, item.ProductID).Error; err != nil {
return fmt.Errorf("erreur décrémentation stock produit %d: %w", item.ProductID, err)
}
}
return tx.Exec(`DELETE FROM baskets WHERE username = ?`, username).Error
})
}
func (d *Database) GetBasketItemOwner(basketID int) (string, error) {
var username string
err := d.GDB.Raw(`SELECT username FROM baskets WHERE id = ?`, basketID).Scan(&username).Error
if err != nil {
return "", err
}
if username == "" {
return "", fmt.Errorf("article non trouvé")
}
return username, nil
}
// GetUnavailableBasketItems retourne les noms des produits du panier dont tous les prix ont été désactivés.
func (d *Database) GetUnavailableBasketItems(username string) ([]string, error) {
var names []string
err := d.GDB.Raw(`
SELECT DISTINCT p.name
FROM baskets b
INNER JOIN products p ON b.product_id = p.id
WHERE b.username = ?
AND NOT EXISTS (
SELECT 1 FROM product_prices pp
WHERE pp.product_id = b.product_id
AND pp.quantity <= b.quantity
AND pp.active_price = true
)`, username).Scan(&names).Error
if err != nil {
return nil, fmt.Errorf("erreur vérification disponibilité: %w", err)
}
return names, nil
}
// GetReservedQuantityInBaskets retourne la somme des quantités d'un produit dans tous les paniers actifs.
func (d *Database) GetReservedQuantityInBaskets(productID int) (float64, error) {
var total float64
err := d.GDB.Raw(`SELECT COALESCE(SUM(quantity), 0) FROM baskets WHERE product_id = ?`, productID).Scan(&total).Error
if err != nil {
return 0, fmt.Errorf("erreur lecture réservations panier: %w", err)
}
return total, nil
}
func (d *Database) GetBasketItems(username string) ([]map[string]any, error) {
var items []map[string]any
if err := d.GDB.Raw(`SELECT product_id, quantity::float8 as quantity, price::float8 as price, is_reward FROM baskets WHERE username = ?`,
username).Scan(&items).Error; err != nil {
return nil, err
}
return items, nil
}