package db import ( "fmt" "gestion/models" "gorm.io/gorm" ) // AddProductInBasket ajoute un produit au panier de l'utilisateur func (d *Database) AddProductInBasket(username, nameProduct string, quantity float64, category string) (*models.Panier, error) { var productResult struct { ID int `gorm:"column:id"` } err := d.GDB.Raw(`SELECT id FROM products WHERE LOWER(name) = LOWER(?) AND LOWER(category) = LOWER(?)`, nameProduct, category).Scan(&productResult).Error if err != nil { return nil, fmt.Errorf("erreur lors de la recherche du produit: %w", err) } if productResult.ID == 0 { return nil, fmt.Errorf("produit '%s' non trouvé dans la catégorie '%s'", nameProduct, category) } productID := productResult.ID price, err := d.GetProductPrice(nameProduct, category, quantity) if err != nil { return nil, fmt.Errorf("erreur récupération prix: %w", err) } var existing struct { ID int `gorm:"column:id"` Quantity float64 `gorm:"column:quantity"` Price float64 `gorm:"column:price"` } d.GDB.Raw(`SELECT id, quantity, price FROM baskets WHERE username = ? AND product_id = ?`, username, productID).Scan(&existing) var basket models.Panier if existing.ID != 0 { newQuantity := existing.Quantity + quantity newPrice := existing.Price + price err = d.GDB.Raw(` UPDATE baskets SET quantity = ?, price = ?, created_at = CURRENT_TIMESTAMP WHERE id = ? RETURNING id, username, product_id, quantity, price, created_at`, newQuantity, newPrice, existing.ID).Scan(&basket).Error if err != nil { return nil, fmt.Errorf("erreur lors de la mise à jour du panier: %w", err) } } else { err = d.GDB.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, price).Scan(&basket).Error if err != nil { return nil, fmt.Errorf("erreur lors de l'ajout au panier: %w", err) } } return &basket, nil } // 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 } func (d *Database) GetProductStock(name, category string) (float64, error) { var result struct { Stock float64 `gorm:"column:stock"` } err := d.GDB.Raw(`SELECT stock FROM products WHERE LOWER(name) = LOWER(?) AND LOWER(category) = LOWER(?)`, name, category).Scan(&result).Error if err != nil { return 0, fmt.Errorf("produit non trouvé: %w", err) } return result.Stock, nil } func (d *Database) DecrementProductStock(name, category string, quantity float64) error { result := d.GDB.Exec(` UPDATE products SET stock = stock - ? WHERE LOWER(name) = LOWER(?) AND LOWER(category) = LOWER(?) AND stock >= ?`, quantity, name, category, quantity) if result.Error != nil { return fmt.Errorf("erreur mise à jour stock: %w", result.Error) } if result.RowsAffected == 0 { return fmt.Errorf("stock insuffisant pour le produit") } return 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.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 } func (d *Database) DecrementAndAddToBasket(username string, productID int, quantity float64) (*models.Panier, error) { var basket models.Panier err := d.GDB.Transaction(func(tx *gorm.DB) error { result := tx.Exec(`UPDATE products SET stock = stock - ? WHERE id = ? AND stock >= ?`, quantity, productID, quantity) if result.Error != nil { return fmt.Errorf("erreur stock: %w", result.Error) } if result.RowsAffected == 0 { 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 et restitue le stock. func (d *Database) DeleteProductFromBasket(basketID int) error { return d.GDB.Transaction(func(tx *gorm.DB) error { var item struct { ProductID int `gorm:"column:product_id"` Quantity float64 `gorm:"column:quantity"` } if err := tx.Raw(`SELECT product_id, quantity FROM baskets WHERE id = ?`, basketID).Scan(&item).Error; err != nil { return fmt.Errorf("produit non trouvé dans le panier") } if item.ProductID == 0 { return fmt.Errorf("produit non trouvé dans le panier") } if err := tx.Exec(`UPDATE products SET stock = stock + ? WHERE id = ?`, item.Quantity, item.ProductID).Error; err != nil { return fmt.Errorf("erreur restitution stock: %w", err) } result := tx.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 et restitue les stocks. func (d *Database) ClearBasket(username string) error { return d.GDB.Transaction(func(tx *gorm.DB) error { if err := tx.Exec(` UPDATE products p SET stock = stock + b.quantity FROM baskets b WHERE b.username = ? AND b.product_id = p.id`, username).Error; err != nil { return fmt.Errorf("erreur restitution stock: %w", err) } if err := tx.Exec(`DELETE FROM baskets WHERE username = ?`, username).Error; err != nil { return fmt.Errorf("erreur lors du vidage du panier: %w", err) } return nil }) } // ClearBasketOnCheckout vide le panier après commande validée SANS restituer le stock. func (d *Database) ClearBasketOnCheckout(username string) error { return d.GDB.Exec(`DELETE FROM baskets WHERE username = ?`, username).Error } // GetBasketTotal calcule le montant total du panier d'un utilisateur func (d *Database) GetBasketTotal(username string) (float64, error) { var result struct { Total float64 `gorm:"column:total"` } err := d.GDB.Raw(`SELECT COALESCE(SUM(price), 0) as total FROM baskets WHERE username = ?`, username).Scan(&result).Error if err != nil { return 0, fmt.Errorf("erreur lors du calcul du total: %w", err) } return result.Total, nil } // GetBasketItemCount compte le nombre d'items dans le panier func (d *Database) GetBasketItemCount(username string) (int, error) { var result struct { Count int `gorm:"column:count"` } err := d.GDB.Raw(`SELECT COUNT(*) as count FROM baskets WHERE username = ?`, username).Scan(&result).Error if err != nil { return 0, fmt.Errorf("erreur lors du comptage des items: %w", err) } return result.Count, nil } func (d *Database) UpdateBasketItemQuantity(basketID int, newQuantity float64) error { if newQuantity <= 0 { return fmt.Errorf("quantité invalide") } return d.GDB.Transaction(func(tx *gorm.DB) error { var item struct { ProductID int `gorm:"column:product_id"` Quantity float64 `gorm:"column:quantity"` } if err := tx.Raw(`SELECT product_id, quantity FROM baskets WHERE id = ? FOR UPDATE`, basketID).Scan(&item).Error; err != nil { return fmt.Errorf("produit non trouvé: %w", err) } if item.ProductID == 0 { return fmt.Errorf("panier item introuvable: %d", basketID) } diff := newQuantity - item.Quantity if diff > 0 { result := tx.Exec(`UPDATE products SET stock = stock - ? WHERE id = ? AND stock >= ?`, diff, item.ProductID, diff) if result.Error != nil { return fmt.Errorf("erreur stock: %w", result.Error) } if result.RowsAffected == 0 { return fmt.Errorf("stock insuffisant") } } else if diff < 0 { if err := tx.Exec(`UPDATE products SET stock = stock + ? WHERE id = ?`, -diff, item.ProductID).Error; err != nil { return fmt.Errorf("erreur stock: %w", err) } } if err := tx.Exec(`UPDATE baskets SET quantity = ? WHERE id = ?`, newQuantity, basketID).Error; err != nil { return fmt.Errorf("erreur panier: %w", err) } return nil }) } 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 } 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 FROM baskets WHERE username = ?`, username).Scan(&items).Error; err != nil { return nil, err } return items, nil }