Files
projet_gestion_commande/backend/gestion/db/db_basket.go
T

368 lines
10 KiB
Go

package db
import (
"database/sql"
"fmt"
"gestion/models"
"log"
"time"
)
// AddProductInBasket ajoute un produit au panier de l'utilisateur
func (d *Database) AddProductInBasket(username, nameProduct string, quantity float64, category string) (*models.Panier, error) {
// Rechercher le produit par son nom et catégorie
var productID int
productQuery := `SELECT id FROM products WHERE name = $1 AND category = $2`
err := d.QueryRow(productQuery, nameProduct, category).Scan(&productID)
if err == sql.ErrNoRows {
return nil, fmt.Errorf("produit '%s' non trouvé dans la catégorie '%s'", nameProduct, category)
}
if err != nil {
return nil, fmt.Errorf("erreur lors de la recherche du produit: %w", err)
}
// Récupérer le prix correct selon la quantité
price, err := d.GetProductPrice(nameProduct, category, quantity)
if err != nil {
return nil, fmt.Errorf("erreur récupération prix: %w", err)
}
// Vérifier si le produit existe déjà dans le panier
var existingID int
var existingQuantity float64
checkQuery := `SELECT id, quantity FROM baskets WHERE username = $1 AND product_id = $2`
err = d.QueryRow(checkQuery, username, productID).Scan(&existingID, &existingQuantity)
if err == nil {
// Produit déjà dans le panier : mettre à jour quantité et prix
newQuantity := existingQuantity + quantity
updateQuery := `UPDATE baskets SET quantity = $1, price = $2, created_at = CURRENT_TIMESTAMP
WHERE id = $3 RETURNING id, username, product_id, quantity, price, created_at`
var basket models.Panier
err = d.QueryRow(updateQuery, newQuantity, price, existingID).Scan(
&basket.ID,
&basket.Username,
&basket.ProductID,
&basket.Quantity,
&basket.Price,
&basket.CreatedAt,
)
if err != nil {
return nil, fmt.Errorf("erreur lors de la mise à jour du panier: %w", err)
}
return &basket, nil
}
// Produit non présent : l'ajouter
insertQuery := `INSERT INTO baskets (username, product_id, quantity, price, created_at)
VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP)
RETURNING id, username, product_id, quantity, price, created_at`
var basket models.Panier
err = d.QueryRow(insertQuery, username, productID, quantity, price).Scan(
&basket.ID,
&basket.Username,
&basket.ProductID,
&basket.Quantity,
&basket.Price,
&basket.CreatedAt,
)
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
func (d *Database) GetProductPrice(name, category string, quantity float64) (float64, error) {
var price float64
query := `
SELECT price
FROM product_prices pp
INNER JOIN products p ON pp.product_id = p.id
WHERE LOWER(p.name) = LOWER($1)
AND LOWER(p.category) = LOWER($2)
AND pp.quantity <= $3
ORDER BY pp.quantity DESC
LIMIT 1
`
err := d.QueryRow(query, name, category, quantity).Scan(&price)
if err != nil {
return 0, fmt.Errorf("prix produit introuvable pour %f %s: %w", quantity, name, err)
}
return price, nil
}
func (d *Database) GetProductStock(name, category string) (float64, error) {
var stock float64
query := `SELECT stock FROM products WHERE name = $1 AND category = $2`
err := d.QueryRow(query, name, category).Scan(&stock)
if err != nil {
return 0, fmt.Errorf("produit non trouvé: %w", err)
}
return stock, nil
}
func (d *Database) DecrementProductStock(name, category string, quantity float64) error {
query := `UPDATE products SET stock = stock - $1 WHERE name = $2 AND category = $3 AND stock >= $1`
result, err := d.Exec(query, quantity, name, category)
if err != nil {
return fmt.Errorf("erreur mise à jour stock: %w", err)
}
rows, _ := result.RowsAffected()
if rows == 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) {
query := `SELECT b.id, b.username, b.product_id, b.quantity, b.price, b.created_at,
p.name, p.category, p.description
FROM baskets b
INNER JOIN products p ON b.product_id = p.id
WHERE b.username = $1
ORDER BY b.created_at DESC`
rows, err := d.Query(query, username)
if err != nil {
return nil, fmt.Errorf("erreur lors de la récupération du panier: %w", err)
}
defer rows.Close()
var baskets []models.Panier
for rows.Next() {
var basket models.Panier
err := rows.Scan(
&basket.ID,
&basket.Username,
&basket.ProductID, // ✔ FIX MAJEUR
&basket.Quantity,
&basket.Price,
&basket.CreatedAt,
&basket.ProductName,
&basket.Category,
&basket.Description,
)
if err != nil {
return nil, fmt.Errorf("erreur lors du scan du panier: %w", err)
}
baskets = append(baskets, basket)
}
if err = rows.Err(); err != nil {
return nil, fmt.Errorf("erreur lors de l'itération des résultats: %w", err)
}
return baskets, nil
}
// DecrementProductStockByID décrémente le stock d'un produit par son ID de manière sécurisée (évite race condition)
func (d *Database) DecrementProductStockByID(productID int, quantity float64) error {
query := `
UPDATE products
SET stock = stock - $1
WHERE id = $2
AND stock >= $1
`
result, err := d.Exec(query, quantity, productID)
if err != nil {
return fmt.Errorf("erreur lors de la mise à jour du stock: %w", err)
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return fmt.Errorf("erreur lors de la vérification du stock affecté: %w", err)
}
if rowsAffected == 0 {
return fmt.Errorf("stock insuffisant pour le produit %d", productID)
}
return nil
}
// DeleteProductFromBasket supprime un produit spécifique du panier
func (d *Database) DeleteProductFromBasket(basketID int) error {
query := `DELETE FROM baskets WHERE id = $1`
result, err := d.Exec(query, basketID)
if err != nil {
return fmt.Errorf("erreur lors de la suppression du produit: %w", err)
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return fmt.Errorf("erreur lors de la vérification des lignes affectées: %w", err)
}
if rowsAffected == 0 {
return fmt.Errorf("produit non trouvé dans le panier")
}
return nil
}
// ClearBasket vide complètement le panier d'un utilisateur
func (d *Database) ClearBasket(username string) error {
query := `DELETE FROM baskets WHERE username = $1`
_, err := d.Exec(query, username)
if err != nil {
return fmt.Errorf("erreur lors du vidage du panier: %w", err)
}
return nil
}
// GetBasketTotal calcule le montant total du panier d'un utilisateur
func (d *Database) GetBasketTotal(username string) (float64, error) {
query := `SELECT COALESCE(SUM(quantity * price), 0) as total
FROM baskets
WHERE username = $1`
var total float64
err := d.QueryRow(query, username).Scan(&total)
if err != nil {
return 0, fmt.Errorf("erreur lors du calcul du total: %w", err)
}
return total, nil
}
// GetBasketItemCount compte le nombre d'items dans le panier
func (d *Database) GetBasketItemCount(username string) (int, error) {
query := `SELECT COUNT(*) FROM baskets WHERE username = $1`
var count int
err := d.QueryRow(query, username).Scan(&count)
if err != nil {
return 0, fmt.Errorf("erreur lors du comptage des items: %w", err)
}
return 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")
}
query := `UPDATE baskets SET quantity = $1, created_at = CURRENT_TIMESTAMP WHERE id = $2`
result, err := d.Exec(query, quantity, basketID)
if err != nil {
return fmt.Errorf("erreur lors de la mise à jour de la quantité: %w", err)
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return fmt.Errorf("erreur lors de la vérification des lignes affectées: %w", err)
}
if 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 {
query := `SELECT product_id, quantity FROM baskets WHERE username = $1`
rows, err := d.Query(query, username)
if err != nil {
return fmt.Errorf("erreur récupération panier: %w", err)
}
defer rows.Close()
type Item struct {
ProductID int
Quantity int
}
var items []Item
for rows.Next() {
var item Item
if err := rows.Scan(&item.ProductID, &item.Quantity); err != nil {
return fmt.Errorf("erreur scan: %w", err)
}
items = append(items, item)
}
// Vérifier stock disponible pour chaque item
for _, item := range items {
var stock int
err := d.QueryRow(`SELECT stock FROM products WHERE id = $1`, item.ProductID).Scan(&stock)
if err != nil {
return fmt.Errorf("produit %d non trouvé: %w", item.ProductID, err)
}
if stock < item.Quantity {
return fmt.Errorf("stock insuffisant pour le produit %d (demandé: %d, disponible: %d)",
item.ProductID, item.Quantity, stock)
}
}
// Prolonger les réservations de 15 minutes
newReservation := time.Now().Add(15 * time.Minute)
updateQuery := `UPDATE baskets
SET reserved_until = $1
WHERE username = $2`
_, err = d.Exec(updateQuery, newReservation, username)
if 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) {
query := `SELECT COUNT(*) FROM baskets
WHERE username = $1
AND (reserved_until IS NULL OR reserved_until < CURRENT_TIMESTAMP)`
var expiredCount int
err := d.QueryRow(query, username).Scan(&expiredCount)
if err != nil {
return false, err
}
return expiredCount > 0, nil
}
func (d *Database) GetBasketItems(username string) ([]map[string]interface{}, error) {
query := `SELECT product_id, quantity, price FROM baskets WHERE username = $1`
rows, err := d.Query(query, username)
if err != nil {
return nil, err
}
defer rows.Close()
var items []map[string]interface{}
for rows.Next() {
var productID, quantity int
var price float64
if err := rows.Scan(&productID, &quantity, &price); err != nil {
return nil, err
}
items = append(items, map[string]interface{}{
"product_id": productID,
"quantity": quantity,
"price": price,
})
}
return items, nil
}