fix: manage stock

This commit is contained in:
2026-05-14 15:31:38 +02:00
parent 426f4b42ee
commit 0fe8a06bfa
8 changed files with 153 additions and 22 deletions
+51 -6
View File
@@ -46,14 +46,18 @@ func (d *Database) GetAllProductsInBasket(username string) ([]models.Panier, err
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)
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")
}
result := tx.Exec(`UPDATE products SET stock = stock - ? WHERE id = ?`, quantity, productID)
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"`
@@ -100,7 +104,7 @@ func (d *Database) DeleteProductFromBasket(basketID int) error {
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 {
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é dans le panier")
}
if item.ProductID == 0 {
@@ -126,6 +130,17 @@ func (d *Database) DeleteProductFromBasket(basketID int) error {
// 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 {
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)
}
if len(items) == 0 {
return nil
}
if err := tx.Exec(`
UPDATE products p
SET stock = stock + b.quantity
@@ -157,6 +172,36 @@ func (d *Database) GetBasketItemOwner(basketID int) (string, error) {
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 FROM baskets WHERE username = ?`,
+12 -3
View File
@@ -86,10 +86,9 @@ func (d *Database) CancelCommandAtomic(commandID int, username, reason string, f
SET stock = stock + ci.quantite, updated_at = CURRENT_TIMESTAMP
FROM command_items ci
WHERE ci.command_id = ? AND ci.product_id = p.id`, commandID).Error; err != nil {
log.Printf("⚠️ [CancelAtomic] Erreur remboursement stock: %v", err)
} else {
log.Printf("✅ [CancelAtomic] Stock remboursé")
return fmt.Errorf("erreur remboursement stock: %w", err)
}
log.Printf("✅ [CancelAtomic] Stock remboursé")
if err := tx.Exec(`
UPDATE clients
@@ -321,3 +320,13 @@ func (d *Database) AddClientPenalty(username string, points int) error {
return nil
}
func (d *Database) RestoreCommandStock(commandID int) error {
return d.GDB.Transaction(func(tx *gorm.DB) error {
return tx.Exec(`
UPDATE products p
SET stock = stock + ci.quantite, updated_at = CURRENT_TIMESTAMP
FROM command_items ci
WHERE ci.command_id = ? AND ci.product_id = p.id`, commandID).Error
})
}
+20 -6
View File
@@ -81,10 +81,10 @@ func validateItemStatus(status string) error {
status = strings.ToLower(strings.TrimSpace(status))
if !slices.Contains(validStatuses, status) {
return fmt.Errorf("...")
return fmt.Errorf("statut invalide: %s", status)
}
return fmt.Errorf("statut invalide: %s", status)
return nil
}
// ============================================
@@ -310,18 +310,21 @@ func (d *Database) DeleteCommandItem(commandID, itemID int) error {
return err
}
// Récupérer le prix et la quantité avant suppression pour mettre à jour le total
var result struct {
Prix float64 `gorm:"column:prix"`
Quantite float64 `gorm:"column:quantite"`
Prix float64 `gorm:"column:prix"`
Quantite float64 `gorm:"column:quantite"`
ProductID int `gorm:"column:product_id"`
}
if err := d.GDB.Raw(`SELECT prix, quantite FROM command_items WHERE id = ? AND command_id = ?`, itemID, commandID).Scan(&result).Error; err != nil {
if err := d.GDB.Raw(`SELECT prix, quantite, product_id FROM command_items WHERE id = ? AND command_id = ?`, itemID, commandID).Scan(&result).Error; err != nil {
return fmt.Errorf("erreur vérification item: %w", err)
}
if result.Prix == 0 && result.Quantite == 0 {
return fmt.Errorf("item %d non trouvé dans la commande %d", itemID, commandID)
}
var cmdStatus string
d.GDB.Raw(`SELECT status FROM commandes WHERE id = ?`, commandID).Scan(&cmdStatus)
// Supprimer l'item
if err := d.GDB.Exec(`DELETE FROM command_items WHERE id = ?`, itemID).Error; err != nil {
log.Printf("❌ Erreur DELETE command_items: %v", err)
@@ -336,6 +339,17 @@ func (d *Database) DeleteCommandItem(commandID, itemID int) error {
log.Printf("⚠️ [DeleteCommandItem] Erreur maj total commande: %v", err)
}
// Restaurer le stock si la commande n'est pas déjà terminée
noRestoreStatuses := []string{"cancelled", "approved", "livre"}
if result.ProductID != 0 && !slices.Contains(noRestoreStatuses, cmdStatus) {
if err := d.GDB.Exec(`UPDATE products SET stock = stock + ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?`,
result.Quantite, result.ProductID).Error; err != nil {
log.Printf("⚠️ [DeleteCommandItem] Erreur restauration stock: %v", err)
} else {
log.Printf("✅ [DeleteCommandItem] Stock restauré: +%.3f pour produit %d", result.Quantite, result.ProductID)
}
}
return nil
}
+13 -3
View File
@@ -3,7 +3,6 @@ package db
import (
"fmt"
"gestion/models"
"log"
"gorm.io/gorm"
)
@@ -67,6 +66,17 @@ func (d *Database) ActivateCryptoCommand(commandID int) error {
func (d *Database) CancelCryptoCommand(commandID int) error {
return d.GDB.Transaction(func(tx *gorm.DB) error {
var cmdStatus string
if err := tx.Raw(`SELECT status FROM commandes WHERE id = ? FOR UPDATE`, commandID).Scan(&cmdStatus).Error; err != nil {
return err
}
if cmdStatus == "" {
return fmt.Errorf("commande non trouvée")
}
if cmdStatus != "pending_payment" {
return fmt.Errorf("commande non annulable (statut: %s)", cmdStatus)
}
type item struct {
ProductID int
Quantite float64
@@ -77,9 +87,9 @@ func (d *Database) CancelCryptoCommand(commandID int) error {
}
for _, it := range items {
if err := tx.Exec(`UPDATE products SET stock = stock + ? WHERE id = ?`, it.Quantite, it.ProductID).Error; err != nil {
log.Printf("[CANCEL CRYPTO] erreur restauration stock produit %d: %v", it.ProductID, err)
return fmt.Errorf("erreur restauration stock produit %d: %w", it.ProductID, err)
}
}
return tx.Exec(`UPDATE commandes SET status = 'cancelled', updated_at = NOW() WHERE id = ? AND status = 'pending_payment'`, commandID).Error
return tx.Exec(`UPDATE commandes SET status = 'cancelled', updated_at = NOW() WHERE id = ?`, commandID).Error
})
}