package db import ( "fmt" "gestion/models" "log" "time" "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 } // GetProductPriceByID récupère le prix d'un produit par son ID et quantité func (d *Database) GetProductPriceByID(productID int, quantity float64) (float64, error) { var result struct { Price float64 `gorm:"column:price"` } err := d.GDB.Raw(` SELECT price FROM product_prices WHERE product_id = ? AND quantity = ROUND(?::NUMERIC, 3) LIMIT 1`, productID, quantity).Scan(&result).Error if err == nil && result.Price > 0 { return result.Price, nil } err = d.GDB.Raw(` SELECT price FROM product_prices WHERE product_id = ? AND quantity <= ROUND(?::NUMERIC, 3) ORDER BY quantity DESC LIMIT 1`, productID, quantity).Scan(&result).Error if err != nil || result.Price == 0 { return 0, fmt.Errorf("aucun prix trouvé pour product_id=%d qty=%.3f", productID, quantity) } return result.Price, nil } // GetProductStockByID récupère le stock d'un produit par son ID func (d *Database) GetProductStockByID(productID int) (float64, error) { var result struct { Stock float64 `gorm:"column:stock"` } err := d.GDB.Raw(`SELECT stock FROM products WHERE id = ?`, productID).Scan(&result).Error if err != nil { return 0, fmt.Errorf("produit %d non trouvé: %w", productID, err) } return result.Stock, nil } // AddProductInBasketByID ajoute un produit au panier en utilisant son ID directement func (d *Database) AddProductInBasketByID(username string, productID int, quantity float64) (*models.Panier, error) { price, err := d.GetProductPriceByID(productID, 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 } 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 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 } // DecrementProductStockByID décrémente le stock d'un produit par son ID func (d *Database) DecrementProductStockByID(productID int, quantity float64) error { result := d.GDB.Exec(` UPDATE products SET stock = stock - ? WHERE id = ? AND stock >= ?`, quantity, productID, quantity) if result.Error != nil { return fmt.Errorf("erreur lors de la mise à jour du stock: %w", result.Error) } if result.RowsAffected == 0 { return fmt.Errorf("stock insuffisant pour le produit %d", productID) } return 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 } // UpdateBasketItemQuantity met à jour la quantité d'un item du panier func (d *Database) UpdateBasketItemQuantity(basketID int, quantity float64) error { if quantity <= 0 { return fmt.Errorf("la quantité doit être supérieure à 0") } result := d.GDB.Exec(`UPDATE baskets SET quantity = ?, created_at = CURRENT_TIMESTAMP WHERE id = ?`, quantity, basketID) if result.Error != nil { return fmt.Errorf("erreur lors de la mise à jour de la quantité: %w", result.Error) } if result.RowsAffected == 0 { return fmt.Errorf("produit non trouvé dans le panier") } return nil } // ExtendBasketReservations prolonge les réservations func (d *Database) ExtendBasketReservations(username string) error { var items []struct { ProductID int `gorm:"column:product_id"` Quantity float64 `gorm:"column:quantity"` } if err := d.GDB.Raw(`SELECT product_id, quantity FROM baskets WHERE username = ?`, username).Scan(&items).Error; err != nil { return fmt.Errorf("erreur récupération panier: %w", err) } for _, item := range items { var stockResult struct { Stock float64 `gorm:"column:stock"` } if err := d.GDB.Raw(`SELECT stock FROM products WHERE id = ?`, item.ProductID).Scan(&stockResult).Error; err != nil { return fmt.Errorf("produit %d non trouvé: %w", item.ProductID, err) } if stockResult.Stock < item.Quantity { return fmt.Errorf("stock insuffisant pour le produit %d (demandé: %g, disponible: %g)", item.ProductID, item.Quantity, stockResult.Stock) } } newReservation := time.Now().Add(15 * time.Minute) if err := d.GDB.Exec(`UPDATE baskets SET reserved_until = ? WHERE username = ?`, newReservation, username).Error; err != nil { return fmt.Errorf("erreur prolongation: %w", err) } log.Printf("✅ Réservations prolongées pour %s jusqu'à %s", username, newReservation.Format("15:04:05")) return nil } // CheckBasketReservations vérifie si les réservations sont expirées func (d *Database) CheckBasketReservations(username string) (bool, error) { var result struct { Count int `gorm:"column:count"` } err := d.GDB.Raw(` SELECT COUNT(*) as count FROM baskets WHERE username = ? AND (reserved_until IS NULL OR reserved_until < CURRENT_TIMESTAMP)`, username).Scan(&result).Error if err != nil { return false, err } return result.Count > 0, 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 }