diff --git a/backend/gestion/db/db_address.go b/backend/gestion/db/db_address.go
index c8b0ddcb..b600d9da 100644
--- a/backend/gestion/db/db_address.go
+++ b/backend/gestion/db/db_address.go
@@ -1,67 +1,47 @@
package db
import (
- "database/sql"
- "errors"
"fmt"
"gestion/models"
)
func (d *Database) CheckAddress(addressByUser *models.Command) error {
var correction models.Address
- err := d.DB.QueryRow(
- `SELECT invalid_address, correct_address FROM adresse_correction WHERE invalid_address = $1`,
- addressByUser.DeliveryAddress,
- ).Scan(&correction.InvalidAddress, &correction.CorrectAddress)
- if errors.Is(err, sql.ErrNoRows) {
- return nil
+ result := d.GDB.Where("invalid_address = ?", addressByUser.DeliveryAddress).First(&correction)
+ if result.Error != nil {
+ if isNotFound(result.Error) {
+ return nil
+ }
+ return fmt.Errorf("checkAddress: %w", result.Error)
}
- if err != nil {
- return fmt.Errorf("checkAddress: %w", err)
- }
- var correctedAddress = correction.CorrectAddress
- addressByUser.DeliveryAddress = correctedAddress
- return fmt.Errorf("Adresse invalide %s", correctedAddress)
+ addressByUser.DeliveryAddress = correction.CorrectAddress
+ return fmt.Errorf("Adresse invalide %s", correction.CorrectAddress)
}
func (d *Database) AddAddress(CorrectAddressByAdmin string, InvalidAddressByAdmin string) error {
- _, err := d.DB.Exec(
- `INSERT INTO adresse_correction
- (invalid_address, correct_address)
- VALUES ($1, $2)`,
- InvalidAddressByAdmin, CorrectAddressByAdmin,
- )
- if err != nil {
+ address := models.Address{
+ InvalidAddress: InvalidAddressByAdmin,
+ CorrectAddress: CorrectAddressByAdmin,
+ }
+ if err := d.GDB.Create(&address).Error; err != nil {
return fmt.Errorf("addAddress: %w", err)
}
return nil
}
func (d *Database) DeleteAddress(InvalidAddressByAdmin string, CorrectAddressByAdmin string) error {
- _, err := d.DB.Exec(
- `DELETE FROM adresse_correction WHERE invalid_address = $1 AND correct_address = $2`,
- InvalidAddressByAdmin, CorrectAddressByAdmin,
- )
- if err != nil {
- return fmt.Errorf("deleteAddress: %w", err)
+ result := d.GDB.Where("invalid_address = ? AND correct_address = ?", InvalidAddressByAdmin, CorrectAddressByAdmin).
+ Delete(&models.Address{})
+ if result.Error != nil {
+ return fmt.Errorf("deleteAddress: %w", result.Error)
}
return nil
}
func (d *Database) AllAddress() ([]models.Address, error) {
- rows, err := d.DB.Query("SELECT invalid_address, correct_address FROM adresse_correction")
- if err != nil {
+ var addresses []models.Address
+ if err := d.GDB.Find(&addresses).Error; err != nil {
return nil, fmt.Errorf("getAllAddress: %w", err)
}
- defer rows.Close()
-
- var addresses []models.Address
- for rows.Next() {
- var a models.Address
- if err := rows.Scan(&a.InvalidAddress, &a.CorrectAddress); err != nil {
- return nil, fmt.Errorf("getAllAddress scan: %w", err)
- }
- addresses = append(addresses, a)
- }
- return addresses, rows.Err()
+ return addresses, nil
}
diff --git a/backend/gestion/db/db_alert.go b/backend/gestion/db/db_alert.go
index 51ec11fe..63950898 100644
--- a/backend/gestion/db/db_alert.go
+++ b/backend/gestion/db/db_alert.go
@@ -6,182 +6,75 @@ import (
)
func (d *Database) CreateAlert(username string, message string) (models.AlertPolicy, error) {
-
- query := `
- INSERT INTO alerte_policy (username, status, message)
- VALUES ($1, 'true', $2)
- RETURNING id, username, status, message, created_at, updated_at
- `
- var alert models.AlertPolicy
- err := d.QueryRow(query, username, message).Scan(&alert.ID, &alert.Username, &alert.Status, &alert.Message, &alert.CreatedAt, &alert.UpdatedAt)
- if err != nil {
+ alert := models.AlertPolicy{
+ Username: username,
+ Status: "true",
+ Message: message,
+ }
+ if err := d.GDB.Create(&alert).Error; err != nil {
return models.AlertPolicy{}, err
}
-
return alert, nil
-
}
func (d *Database) GetAlertPolicy(id int) (models.AlertPolicy, error) {
-
- query := `
- SELECT id, username, status, message, created_at, updated_at
- FROM alerte_policy
- WHERE id = $1
- `
var alert models.AlertPolicy
- err := d.QueryRow(query, id).Scan(&alert.ID, &alert.Username, &alert.Status, &alert.Message, &alert.CreatedAt, &alert.UpdatedAt)
- if err != nil {
+ if err := d.GDB.First(&alert, id).Error; err != nil {
return models.AlertPolicy{}, err
}
-
return alert, nil
-
}
func (d *Database) GetAllAlerts() ([]models.AlertPolicy, error) {
-
- query := `
- SELECT id, username, status, message, created_at, updated_at
- FROM alerte_policy
- `
- rows, err := d.Query(query)
- if err != nil {
+ var alerts []models.AlertPolicy
+ if err := d.GDB.Find(&alerts).Error; err != nil {
return nil, err
}
- defer rows.Close()
-
- var alerts []models.AlertPolicy
- for rows.Next() {
- var alert models.AlertPolicy
- err := rows.Scan(&alert.ID, &alert.Username, &alert.Status, &alert.Message, &alert.CreatedAt, &alert.UpdatedAt)
- if err != nil {
- return nil, err
- }
- alerts = append(alerts, alert)
- }
-
return alerts, nil
-
}
func (d *Database) DeleteAlertPolicy(id int) error {
-
- query := `
- DELETE FROM alerte_policy
- WHERE id = $1
- `
- _, err := d.Exec(query, id)
- if err != nil {
- return err
- }
-
- return nil
-
+ return d.GDB.Delete(&models.AlertPolicy{}, id).Error
}
-// EndAlert met fin à une alerte en changeant son statut à 'false'
func (d *Database) EndAlert(id int) error {
-
- query := `
- UPDATE alerte_policy
- SET status = 'false', updated_at = CURRENT_TIMESTAMP
- WHERE id = $1 AND status = 'true'
- `
- result, err := d.Exec(query, id)
- if err != nil {
- return err
+ result := d.GDB.Model(&models.AlertPolicy{}).
+ Where("id = ? AND status = 'true'", id).
+ Update("status", "false")
+ if result.Error != nil {
+ return result.Error
}
-
- rowsAffected, err := result.RowsAffected()
- if err != nil {
- return err
- }
-
- if rowsAffected == 0 {
+ if result.RowsAffected == 0 {
return fmt.Errorf("alerte non trouvée ou déjà terminée")
}
-
return nil
}
-// ActivateAlert active une alerte en changeant son statut à 'true'
func (d *Database) ActivateAlert(id int) error {
-
- query := `
- UPDATE alerte_policy
- SET status = 'true', updated_at = CURRENT_TIMESTAMP
- WHERE id = $1 AND status = 'false'
- `
- result, err := d.Exec(query, id)
- if err != nil {
- return err
+ result := d.GDB.Model(&models.AlertPolicy{}).
+ Where("id = ? AND status = 'false'", id).
+ Update("status", "true")
+ if result.Error != nil {
+ return result.Error
}
-
- rowsAffected, err := result.RowsAffected()
- if err != nil {
- return err
- }
-
- if rowsAffected == 0 {
+ if result.RowsAffected == 0 {
return fmt.Errorf("alerte non trouvée ou déjà active")
}
-
return nil
}
-// GetActiveAlerts récupère toutes les alertes actives (status = 'true')
func (d *Database) GetActiveAlerts() ([]models.AlertPolicy, error) {
-
- query := `
- SELECT id, username, status, message, created_at, updated_at
- FROM alerte_policy
- WHERE status = 'true'
- ORDER BY created_at DESC
- `
- rows, err := d.Query(query)
- if err != nil {
+ var alerts []models.AlertPolicy
+ if err := d.GDB.Where("status = 'true'").Order("created_at DESC").Find(&alerts).Error; err != nil {
return nil, err
}
- defer rows.Close()
-
- var alerts []models.AlertPolicy
- for rows.Next() {
- var alert models.AlertPolicy
- err := rows.Scan(&alert.ID, &alert.Username, &alert.Status, &alert.Message, &alert.CreatedAt, &alert.UpdatedAt)
- if err != nil {
- return nil, err
- }
- alerts = append(alerts, alert)
- }
-
return alerts, nil
}
-// GetAlertsByUsername récupère toutes les alertes d'un livreur
func (d *Database) GetAlertsByUsername(username string) ([]models.AlertPolicy, error) {
-
- query := `
- SELECT id, username, status, message, created_at, updated_at
- FROM alerte_policy
- WHERE username = $1
- ORDER BY created_at DESC
- `
- rows, err := d.Query(query, username)
- if err != nil {
+ var alerts []models.AlertPolicy
+ if err := d.GDB.Where("username = ?", username).Order("created_at DESC").Find(&alerts).Error; err != nil {
return nil, err
}
- defer rows.Close()
-
- var alerts []models.AlertPolicy
- for rows.Next() {
- var alert models.AlertPolicy
- err := rows.Scan(&alert.ID, &alert.Username, &alert.Status, &alert.Message, &alert.CreatedAt, &alert.UpdatedAt)
- if err != nil {
- return nil, err
- }
- alerts = append(alerts, alert)
- }
-
return alerts, nil
}
diff --git a/backend/gestion/db/db_basket.go b/backend/gestion/db/db_basket.go
index 45b52f5d..0fcbd33f 100644
--- a/backend/gestion/db/db_basket.go
+++ b/backend/gestion/db/db_basket.go
@@ -1,117 +1,100 @@
package db
import (
- "database/sql"
"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) {
- // Rechercher le produit par son nom et catégorie (case-insensitive)
- var productID int
- productQuery := `SELECT id FROM products WHERE LOWER(name) = LOWER($1) AND LOWER(category) = LOWER($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)
+ 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
- // 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, existingPrice float64
- checkQuery := `SELECT id, quantity, price FROM baskets WHERE username = $1 AND product_id = $2`
- err = d.QueryRow(checkQuery, username, productID).Scan(&existingID, &existingQuantity, &existingPrice)
+ 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)
- if err == nil {
- // Produit déjà dans le panier : cumuler quantité et prix total de la ligne
- newQuantity := existingQuantity + quantity
- newPrice := existingPrice + price
- 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, newPrice, existingID).Scan(
- &basket.ID,
- &basket.Username,
- &basket.ProductID,
- &basket.Quantity,
- &basket.Price,
- &basket.CreatedAt,
- )
+ 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)
}
- return &basket, nil
+ } 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)
+ }
}
-
- // 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
}
-// GetProductPriceByID récupère le prix d'un produit par son ID et quantité (NUMERIC exact)
+// 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 price float64
-
- // Comparaison NUMERIC précise : évite les problèmes float64 vs NUMERIC(10,3)
- exactQuery := `
- SELECT price FROM product_prices
- WHERE product_id = $1 AND quantity = ROUND($2::NUMERIC, 3)
- LIMIT 1
- `
- err := d.QueryRow(exactQuery, productID, quantity).Scan(&price)
- if err == nil {
- return price, nil
+ var result struct {
+ Price float64 `gorm:"column:price"`
}
- // Fallback : palier inférieur le plus proche
- tierQuery := `
+ err := d.GDB.Raw(`
SELECT price FROM product_prices
- WHERE product_id = $1 AND quantity <= ROUND($2::NUMERIC, 3)
- ORDER BY quantity DESC LIMIT 1
- `
- err = d.QueryRow(tierQuery, productID, quantity).Scan(&price)
- if err != nil {
- return 0, fmt.Errorf("aucun prix trouvé pour product_id=%d qty=%.3f: %w", productID, quantity, err)
+ 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
}
- return 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 stock float64
- err := d.QueryRow(`SELECT stock FROM products WHERE id = $1`, productID).Scan(&stock)
+ 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 stock, nil
+ return result.Stock, nil
}
// AddProductInBasketByID ajoute un produit au panier en utilisant son ID directement
@@ -121,81 +104,76 @@ func (d *Database) AddProductInBasketByID(username string, productID int, quanti
return nil, fmt.Errorf("erreur récupération prix: %w", err)
}
- var existingID int
- var existingQuantity, existingPrice float64
- checkQuery := `SELECT id, quantity, price FROM baskets WHERE username = $1 AND product_id = $2`
- err = d.QueryRow(checkQuery, username, productID).Scan(&existingID, &existingQuantity, &existingPrice)
-
- if err == nil {
- newQuantity := existingQuantity + quantity
- newPrice := existingPrice + price
- 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, newPrice, 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
+ 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)
- 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 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 lors de l'ajout au panier: %w", err)
+ 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 price float64
-
- query := `
+ 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($1)
- AND LOWER(p.category) = LOWER($2)
- AND pp.quantity <= $3
+ WHERE LOWER(p.name) = LOWER(?)
+ AND LOWER(p.category) = LOWER(?)
+ AND pp.quantity <= ?
ORDER BY pp.quantity DESC
- LIMIT 1
- `
-
- err := d.QueryRow(query, name, category, quantity).Scan(&price)
- if err != nil {
+ 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 price, nil
+ return result.Price, nil
}
func (d *Database) GetProductStock(name, category string) (float64, error) {
- var stock float64
- query := `SELECT stock FROM products WHERE LOWER(name) = LOWER($1) AND LOWER(category) = LOWER($2)`
- err := d.QueryRow(query, name, category).Scan(&stock)
+ 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 stock, nil
+ return result.Stock, nil
}
func (d *Database) DecrementProductStock(name, category string, quantity float64) error {
- query := `UPDATE products SET stock = stock - $1 WHERE LOWER(name) = LOWER($2) AND LOWER(category) = LOWER($3) AND stock >= $1`
- result, err := d.Exec(query, quantity, name, category)
- if err != nil {
- return fmt.Errorf("erreur mise à jour stock: %w", err)
+ 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)
}
-
- rows, _ := result.RowsAffected()
- if rows == 0 {
+ if result.RowsAffected == 0 {
return fmt.Errorf("stock insuffisant pour le produit")
}
return nil
@@ -203,164 +181,105 @@ func (d *Database) DecrementProductStock(name, category string, quantity float64
// 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)
+ 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)
}
- 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)
+// DecrementProductStockByID décrémente le stock d'un produit par son ID
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)
+ 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)
}
-
- rowsAffected, err := result.RowsAffected()
- if err != nil {
- return fmt.Errorf("erreur lors de la vérification du stock affecté: %w", err)
- }
-
- if rowsAffected == 0 {
+ 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 {
- tx, err := d.Begin()
- if err != nil {
- return fmt.Errorf("erreur transaction: %w", err)
- }
- defer tx.Rollback()
+ 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")
+ }
- var productID int
- var quantity float64
- err = tx.QueryRow(
- `SELECT product_id, quantity FROM baskets WHERE id = $1`,
- basketID,
- ).Scan(&productID, &quantity)
- if err != nil {
- 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)
+ }
- _, err = tx.Exec(
- `UPDATE products SET stock = stock + $1 WHERE id = $2`,
- quantity, productID,
- )
- if err != nil {
- return fmt.Errorf("erreur restitution stock: %w", err)
- }
-
- result, err := tx.Exec(`DELETE FROM baskets WHERE id = $1`, basketID)
- if err != nil {
- return fmt.Errorf("erreur lors de la suppression du produit: %w", err)
- }
- rows, _ := result.RowsAffected()
- if rows == 0 {
- return fmt.Errorf("produit non trouvé dans le panier")
- }
-
- return tx.Commit()
+ 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 {
- tx, err := d.Begin()
- if err != nil {
- return fmt.Errorf("erreur transaction: %w", err)
- }
- defer tx.Rollback()
-
- _, err = tx.Exec(`
- UPDATE products p
- SET stock = stock + b.quantity
- FROM baskets b
- WHERE b.username = $1 AND b.product_id = p.id
- `, username)
- if err != nil {
- return fmt.Errorf("erreur restitution stock: %w", err)
- }
-
- _, err = tx.Exec(`DELETE FROM baskets WHERE username = $1`, username)
- if err != nil {
- return fmt.Errorf("erreur lors du vidage du panier: %w", err)
- }
-
- return tx.Commit()
+ 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
+ })
}
// GetBasketTotal calcule le montant total du panier d'un utilisateur
-// price dans baskets = prix total de la ligne (cumul des ajouts)
func (d *Database) GetBasketTotal(username string) (float64, error) {
- query := `SELECT COALESCE(SUM(price), 0) as total
- FROM baskets
- WHERE username = $1`
-
- var total float64
- err := d.QueryRow(query, username).Scan(&total)
+ 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 total, nil
+ return result.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)
+ 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 count, nil
+ return result.Count, nil
}
// UpdateBasketItemQuantity met à jour la quantité d'un item du panier
@@ -368,70 +287,43 @@ func (d *Database) UpdateBasketItemQuantity(basketID int, quantity float64) erro
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)
+ 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)
}
-
- 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 {
+ 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 {
- query := `SELECT product_id, quantity FROM baskets WHERE username = $1`
- rows, err := d.Query(query, username)
- if err != nil {
+ 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)
}
- 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 {
+ 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 stock < item.Quantity {
- return fmt.Errorf("stock insuffisant pour le produit %d (demandé: %d, disponible: %d)",
- item.ProductID, item.Quantity, stock)
+ if stockResult.Stock < item.Quantity {
+ return fmt.Errorf("stock insuffisant pour le produit %d (demandé: %g, disponible: %g)",
+ item.ProductID, item.Quantity, stockResult.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 {
+ if err := d.GDB.Exec(`UPDATE baskets SET reserved_until = ? WHERE username = ?`,
+ newReservation, username).Error; err != nil {
return fmt.Errorf("erreur prolongation: %w", err)
}
@@ -442,39 +334,24 @@ func (d *Database) ExtendBasketReservations(username string) error {
// 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)
+ 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 expiredCount > 0, nil
+ return result.Count > 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 {
+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
}
- defer rows.Close()
-
- var items []map[string]interface{}
- for rows.Next() {
- var productID int
- var quantity, 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
}
diff --git a/backend/gestion/db/db_cancel_command.go b/backend/gestion/db/db_cancel_command.go
index ea7f3ad7..9c6b459c 100644
--- a/backend/gestion/db/db_cancel_command.go
+++ b/backend/gestion/db/db_cancel_command.go
@@ -1,164 +1,155 @@
// ============================================
// db/cancel_commands_db.go
// FONCTIONS DB ATOMIQUES POUR L'ANNULATION
-// VERSION 100% SÉCURISÉE - FIX ETA CHECK
// ============================================
package db
import (
- "database/sql"
"fmt"
"log"
"slices"
- "time"
-)
-// ============================================
-// ANNULATION ATOMIQUE
-// ============================================
+ "gorm.io/gorm"
+)
func (d *Database) CancelCommandAtomic(commandID int, username, reason string, force bool) (int, error) {
log.Printf("🔒 [CancelAtomic] START - cmd=%d, user=%s, force=%v", commandID, username, force)
- tx, err := d.Begin()
- if err != nil {
- return 0, fmt.Errorf("erreur transaction: %w", err)
- }
- defer tx.Rollback()
+ var penalty int
- var currentStatus, cmdUsername, livreurAssign string
- err = tx.QueryRow(`
- SELECT status, username, COALESCE(livreur_assign, '')
- FROM commandes
- WHERE id = $1
- FOR UPDATE
- `, commandID).Scan(¤tStatus, &cmdUsername, &livreurAssign)
-
- if err == sql.ErrNoRows {
- return 0, fmt.Errorf("commande non trouvée")
- }
- if err != nil {
- return 0, err
- }
-
- log.Printf("📋 [CancelAtomic] Trouvée - status=%s, owner=%s, livreur=%s", currentStatus, cmdUsername, livreurAssign)
-
- if cmdUsername != username {
- return 0, fmt.Errorf("commande ne vous appartient pas")
- }
-
- nonCancellableStatuses := []string{"livre", "approved", "cancelled", "disabled"}
- if slices.Contains(nonCancellableStatuses, currentStatus) {
- return 0, fmt.Errorf("impossible d'annuler")
- }
-
- isLateCancel := false
- if livreurAssign != "" {
- if currentStatus == "en_route" || currentStatus == "arrived" {
- isLateCancel = true
- log.Printf("⚠️ [CancelAtomic] Annulation TARDIVE détectée - Statut: %s", currentStatus)
- } else if d.CheckCommandETAExistsAndValid(commandID) {
- isLateCancel = true
- log.Printf("⚠️ [CancelAtomic] Annulation TARDIVE détectée - ETA définie")
- } else {
- log.Printf("✅ [CancelAtomic] Annulation SANS PÉNALITÉ - Statut: %s, Pas d'ETA valide", currentStatus)
+ err := d.GDB.Transaction(func(tx *gorm.DB) error {
+ var cmdResult struct {
+ Status string `gorm:"column:status"`
+ Username string `gorm:"column:username"`
+ LivreurAssign string `gorm:"column:livreur_assign"`
}
- } else {
- log.Printf("✅ [CancelAtomic] Annulation SANS PÉNALITÉ - Aucun livreur assigné")
- }
-
- if isLateCancel && !force {
- return 0, fmt.Errorf("confirmation requise")
- }
-
- result, err := tx.Exec(`
- UPDATE commandes
- SET status = 'cancelled', updated_at = CURRENT_TIMESTAMP
- WHERE id = $1 AND status = $2 AND username = $3
- `, commandID, currentStatus, username)
- if err != nil {
- return 0, err
- }
- if rows, _ := result.RowsAffected(); rows == 0 {
- return 0, fmt.Errorf("commande déjà modifiée")
- }
- log.Printf("✅ [CancelAtomic] Statut mis à jour: %s → cancelled", currentStatus)
-
- if _, err = tx.Exec(`
- UPDATE products p
- SET stock = stock + ci.quantite, updated_at = CURRENT_TIMESTAMP
- FROM command_items ci
- WHERE ci.command_id = $1 AND ci.product_id = p.id
- `, commandID); err != nil {
- log.Printf("⚠️ [CancelAtomic] Erreur remboursement stock: %v", err)
- } else {
- log.Printf("✅ [CancelAtomic] Stock remboursé")
- }
-
- penalty := 0
- if isLateCancel {
- log.Printf("⚠️ [CancelAtomic] Annulation tardive confirmée - Application pénalité")
- penalty, _ = d.CalculateCancellationPenalty(username)
- if _, err = tx.Exec(`
- UPDATE clients
- SET amende = amende + $1,
- cancellations_count = COALESCE(cancellations_count, 0) + 1,
- updated_at = CURRENT_TIMESTAMP
- WHERE username = $2
- `, penalty, username); err != nil {
- log.Printf("❌ [CancelAtomic] Erreur pénalité: %v", err)
- } else {
- log.Printf("⚠️ [CancelAtomic] Pénalité: %d appliquée à %s", penalty, username)
+ err := tx.Raw(`
+ SELECT status, username, COALESCE(livreur_assign, '') as livreur_assign
+ FROM commandes WHERE id = ? FOR UPDATE`, commandID).Scan(&cmdResult).Error
+ if err != nil {
+ return err
+ }
+ if cmdResult.Username == "" {
+ return fmt.Errorf("commande non trouvée")
}
- }
- if _, err = tx.Exec(`
- INSERT INTO command_logs (command_id, status, message, author, created_at)
- VALUES ($1, 'cancelled', $2, $3, CURRENT_TIMESTAMP)
- `, commandID, fmt.Sprintf("Annulée par %s - Raison: %s", username, reason), username); err != nil {
- log.Printf("⚠️ [CancelAtomic] Erreur log: %v", err)
- }
+ log.Printf("📋 [CancelAtomic] Trouvée - status=%s, owner=%s, livreur=%s",
+ cmdResult.Status, cmdResult.Username, cmdResult.LivreurAssign)
- if err := tx.Commit(); err != nil {
- return 0, fmt.Errorf("erreur commit: %w", err)
- }
+ if cmdResult.Username != username {
+ return fmt.Errorf("commande ne vous appartient pas")
+ }
- if livreurAssign != "" {
- go func() {
- if err := d.CleanupCompletedCommandFromQueue(commandID, livreurAssign); err != nil {
- log.Printf("⚠️ [CancelAtomic] Erreur cleanup queue: %v", err)
+ nonCancellableStatuses := []string{"livre", "approved", "cancelled", "disabled"}
+ if slices.Contains(nonCancellableStatuses, cmdResult.Status) {
+ return fmt.Errorf("impossible d'annuler")
+ }
+
+ isLateCancel := false
+ if cmdResult.LivreurAssign != "" {
+ if cmdResult.Status == "en_route" || cmdResult.Status == "arrived" {
+ isLateCancel = true
+ log.Printf("⚠️ [CancelAtomic] Annulation TARDIVE détectée - Statut: %s", cmdResult.Status)
+ } else if d.CheckCommandETAExistsAndValid(commandID) {
+ isLateCancel = true
+ log.Printf("⚠️ [CancelAtomic] Annulation TARDIVE détectée - ETA définie")
+ } else {
+ log.Printf("✅ [CancelAtomic] Annulation SANS PÉNALITÉ - Statut: %s, Pas d'ETA valide", cmdResult.Status)
}
+ } else {
+ log.Printf("✅ [CancelAtomic] Annulation SANS PÉNALITÉ - Aucun livreur assigné")
+ }
+
+ if isLateCancel && !force {
+ return fmt.Errorf("confirmation requise")
+ }
+
+ result := tx.Exec(`
+ UPDATE commandes
+ SET status = 'cancelled', updated_at = CURRENT_TIMESTAMP
+ WHERE id = ? AND status = ? AND username = ?`, commandID, cmdResult.Status, username)
+ if result.Error != nil {
+ return result.Error
+ }
+ if result.RowsAffected == 0 {
+ return fmt.Errorf("commande déjà modifiée")
+ }
+ log.Printf("✅ [CancelAtomic] Statut mis à jour: %s → cancelled", cmdResult.Status)
+
+ if err := 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; err != nil {
+ log.Printf("⚠️ [CancelAtomic] Erreur remboursement stock: %v", err)
+ } else {
+ log.Printf("✅ [CancelAtomic] Stock remboursé")
+ }
+
+ if isLateCancel {
+ log.Printf("⚠️ [CancelAtomic] Annulation tardive confirmée - Application pénalité")
+ penalty, _ = d.CalculateCancellationPenalty(username)
+ if err := tx.Exec(`
+ UPDATE clients
+ SET amende = amende + ?,
+ cancellations_count = COALESCE(cancellations_count, 0) + 1,
+ updated_at = CURRENT_TIMESTAMP
+ WHERE username = ?`, penalty, username).Error; err != nil {
+ log.Printf("❌ [CancelAtomic] Erreur pénalité: %v", err)
+ } else {
+ log.Printf("⚠️ [CancelAtomic] Pénalité: %d appliquée à %s", penalty, username)
+ }
+ }
+
+ if err := tx.Exec(`
+ INSERT INTO command_logs (command_id, status, message, author, created_at)
+ VALUES (?, 'cancelled', ?, ?, CURRENT_TIMESTAMP)`,
+ commandID,
+ fmt.Sprintf("Annulée par %s - Raison: %s", username, reason),
+ username,
+ ).Error; err != nil {
+ log.Printf("⚠️ [CancelAtomic] Erreur log: %v", err)
+ }
+
+ // Nettoyage async après commit
+ livreur := cmdResult.LivreurAssign
+ if livreur != "" {
+ go func() {
+ if err := d.CleanupCompletedCommandFromQueue(commandID, livreur); err != nil {
+ log.Printf("⚠️ [CancelAtomic] Erreur cleanup queue: %v", err)
+ }
+ }()
+ }
+ go func() {
+ Redis.Del(RedisCtx,
+ fmt.Sprintf("command:%d", commandID),
+ fmt.Sprintf("client:%s", username),
+ fmt.Sprintf("client:%s:commands", username),
+ )
}()
+
+ return nil
+ })
+
+ if err != nil {
+ return 0, err
}
- go func() {
- Redis.Del(RedisCtx,
- fmt.Sprintf("command:%d", commandID),
- fmt.Sprintf("client:%s", username),
- fmt.Sprintf("client:%s:commands", username),
- )
- }()
log.Printf("🎉 [CancelAtomic] SUCCÈS - Commande %d annulée", commandID)
return penalty, nil
}
-// ============================================
-// ✅ NOUVELLE FONCTION: CHECK ETA VALIDE
-// ============================================
-
// CheckCommandETAExistsAndValid vérifie si une ETA RÉELLE existe (> 0 minutes, non expirée)
func (d *Database) CheckCommandETAExistsAndValid(commandID int) bool {
etaKey := fmt.Sprintf("command:eta:%d", commandID)
- // Récupérer l'ETA depuis Redis
etaMinutesStr, err := Redis.Get(RedisCtx, etaKey).Result()
if err != nil {
log.Printf("⚠️ [CheckETA] Pas d'ETA trouvée pour cmd %d", commandID)
return false
}
- // Parser l'ETA
var etaMinutes int
_, err = fmt.Sscanf(etaMinutesStr, "%d", &etaMinutes)
if err != nil || etaMinutes <= 0 {
@@ -166,7 +157,6 @@ func (d *Database) CheckCommandETAExistsAndValid(commandID int) bool {
return false
}
- // Vérifier le TTL (si l'ETA existe, elle doit avoir un TTL)
ttl, err := Redis.TTL(RedisCtx, etaKey).Result()
if err != nil || ttl <= 0 {
log.Printf("⚠️ [CheckETA] ETA expirée pour cmd %d", commandID)
@@ -177,106 +167,76 @@ func (d *Database) CheckCommandETAExistsAndValid(commandID int) bool {
return true
}
-// ============================================
-// SUPPRESSION ATOMIQUE
-// ============================================
-
func (d *Database) DeleteCommandAtomic(commandID int, deletedBy, role string) error {
log.Printf("🔒 [DeleteAtomic] START - cmd=%d, by=%s (%s)", commandID, deletedBy, role)
- // ✅ TRANSACTION
- tx, err := d.Begin()
- if err != nil {
- return fmt.Errorf("erreur transaction: %w", err)
- }
- defer tx.Rollback()
+ return d.GDB.Transaction(func(tx *gorm.DB) error {
+ var cmdResult struct {
+ Status string `gorm:"column:status"`
+ Username string `gorm:"column:username"`
+ LivreurAssign string `gorm:"column:livreur_assign"`
+ }
+ err := tx.Raw(`
+ SELECT status, username, COALESCE(livreur_assign, '') as livreur_assign
+ FROM commandes WHERE id = ? FOR UPDATE`, commandID).Scan(&cmdResult).Error
+ if err != nil {
+ return err
+ }
+ if cmdResult.Username == "" {
+ return fmt.Errorf("commande non trouvée")
+ }
- // ✅ SELECT FOR UPDATE
- var currentStatus, cmdUsername, livreurAssign string
- err = tx.QueryRow(`
- SELECT status, username, COALESCE(livreur_assign, '')
- FROM commandes
- WHERE id = $1
- FOR UPDATE
- `, commandID).Scan(¤tStatus, &cmdUsername, &livreurAssign)
+ log.Printf("📋 [DeleteAtomic] Trouvée - status=%s, client=%s", cmdResult.Status, cmdResult.Username)
- if err == sql.ErrNoRows {
- return fmt.Errorf("commande non trouvée")
- }
- if err != nil {
- return err
- }
+ if err := 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; err != nil {
+ log.Printf("⚠️ [DeleteAtomic] Erreur remboursement: %v", err)
+ } else {
+ log.Printf("✅ [DeleteAtomic] Stock remboursé")
+ }
- log.Printf("📋 [DeleteAtomic] Trouvée - status=%s, client=%s", currentStatus, cmdUsername)
+ tx.Exec(`
+ INSERT INTO command_logs (command_id, status, message, author, created_at)
+ VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)`,
+ commandID, "deleted",
+ fmt.Sprintf("Supprimée par %s (%s) - Ancien statut: %s", deletedBy, role, cmdResult.Status),
+ deletedBy)
- // ✅ REMBOURSER STOCK ATOMIQUEMENT
- _, err = tx.Exec(`
- UPDATE products p
- SET stock = stock + ci.quantite, updated_at = CURRENT_TIMESTAMP
- FROM command_items ci
- WHERE ci.command_id = $1 AND ci.product_id = p.id
- `, commandID)
+ if err := tx.Exec(`DELETE FROM command_items WHERE command_id = ?`, commandID).Error; err != nil {
+ return err
+ }
- if err != nil {
- log.Printf("⚠️ [DeleteAtomic] Erreur remboursement: %v", err)
- } else {
- log.Printf("✅ [DeleteAtomic] Stock remboursé")
- }
+ result := tx.Exec(`DELETE FROM commandes WHERE id = ?`, commandID)
+ if result.Error != nil {
+ return result.Error
+ }
+ if result.RowsAffected == 0 {
+ return fmt.Errorf("commande non trouvée")
+ }
- // ✅ LOG AVANT SUPPRESSION
- _, err = tx.Exec(`
- INSERT INTO command_logs (command_id, status, message, author, created_at)
- VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP)
- `, commandID, "deleted",
- fmt.Sprintf("Supprimée par %s (%s) - Ancien statut: %s", deletedBy, role, currentStatus),
- deletedBy)
+ log.Printf("✅ [DeleteAtomic] Supprimée de la DB")
- // ✅ SUPPRIMER ITEMS
- _, err = tx.Exec(`DELETE FROM command_items WHERE command_id = $1`, commandID)
- if err != nil {
- return err
- }
+ livreur := cmdResult.LivreurAssign
+ cmdUsername := cmdResult.Username
- // ✅ SUPPRIMER COMMANDE
- result, err := tx.Exec(`DELETE FROM commandes WHERE id = $1`, commandID)
- if err != nil {
- return err
- }
+ if livreur != "" {
+ go d.RemoveCommandFromAllQueues(commandID, livreur)
+ }
+ go func() {
+ Redis.Del(RedisCtx,
+ fmt.Sprintf("command:%d", commandID),
+ fmt.Sprintf("client:%s", cmdUsername),
+ fmt.Sprintf("client:%s:commands", cmdUsername),
+ )
+ }()
- rows, _ := result.RowsAffected()
- if rows == 0 {
- return fmt.Errorf("commande non trouvée")
- }
-
- log.Printf("✅ [DeleteAtomic] Supprimée de la DB")
-
- // ✅ COMMIT
- if err := tx.Commit(); err != nil {
- return fmt.Errorf("erreur commit: %w", err)
- }
-
- // ✅ NETTOYER QUEUES (async)
- if livreurAssign != "" {
- go d.RemoveCommandFromAllQueues(commandID, livreurAssign)
- }
-
- // ✅ INVALIDER CACHES (async)
- go func() {
- Redis.Del(RedisCtx,
- fmt.Sprintf("command:%d", commandID),
- fmt.Sprintf("client:%s", cmdUsername),
- fmt.Sprintf("client:%s:commands", cmdUsername),
- )
- }()
-
- log.Printf("🎉 [DeleteAtomic] SUCCÈS - Commande %d supprimée", commandID)
- return nil
+ return nil
+ })
}
-// ============================================
-// FONCTIONS HELPERS (déjà sécurisées)
-// ============================================
-
func (d *Database) GetCommandPositionInQueue(livreurUsername string, commandID int) (int, error) {
queueKey := fmt.Sprintf("queue:deliveryman:%s", livreurUsername)
commandIDStr := fmt.Sprintf("%d", commandID)
@@ -285,104 +245,61 @@ func (d *Database) GetCommandPositionInQueue(livreurUsername string, commandID i
if err != nil {
return 0, fmt.Errorf("commande non trouvée dans la queue")
}
-
return int(rank) + 1, nil
}
-func (d *Database) GetCancelledCommands(username string, limit int) ([]map[string]interface{}, error) {
+func (d *Database) GetCancelledCommands(username string, limit int) ([]map[string]any, error) {
query := `
- SELECT id, username, status, adresse, total_prix, created_at, updated_at
+ SELECT id, client_order_id AS client_order_number, username, status, adresse, total_prix::float8 as total_prix, created_at, updated_at
FROM commandes
- WHERE status = 'cancelled'
- `
+ WHERE status = 'cancelled'`
- args := []interface{}{}
- argPos := 1
+ args := []any{}
if username != "" {
- query += fmt.Sprintf(" AND username = $%d", argPos)
+ query += " AND username = ?"
args = append(args, username)
- argPos++
}
query += " ORDER BY updated_at DESC"
if limit > 0 {
- query += fmt.Sprintf(" LIMIT $%d", argPos)
+ query += " LIMIT ?"
args = append(args, limit)
}
- rows, err := d.Query(query, args...)
- if err != nil {
+ var commands []map[string]any
+ if err := d.GDB.Raw(query, args...).Scan(&commands).Error; err != nil {
return nil, fmt.Errorf("erreur récupération: %w", err)
}
- defer rows.Close()
-
- var commands []map[string]interface{}
- for rows.Next() {
- var id int
- var username, status, adresse string
- var totalPrix float64
- var createdAt, updatedAt time.Time
-
- err := rows.Scan(&id, &username, &status, &adresse, &totalPrix, &createdAt, &updatedAt)
- if err != nil {
- return nil, err
- }
-
- commands = append(commands, map[string]interface{}{
- "id": id,
- "username": username,
- "status": status,
- "adresse": adresse,
- "total_prix": totalPrix,
- "created_at": createdAt,
- "updated_at": updatedAt,
- })
- }
-
return commands, nil
}
-// ============================================
-// GESTION DES PÉNALITÉS
-// ============================================
-
// AddClientPenalty ajoute une pénalité à un client
func (d *Database) AddClientPenalty(username string, points int) error {
log.Printf("⚠️ [AddPenalty] Ajout pénalité: %d points pour client %s", points, username)
- // ✅ Validation
if points <= 0 {
return fmt.Errorf("points invalides: %d", points)
}
-
if username == "" {
return fmt.Errorf("username vide")
}
- // ✅ UPDATE dans PostgreSQL
- query := `UPDATE clients
- SET amende = amende + $1, updated_at = CURRENT_TIMESTAMP
- WHERE username = $2`
-
- result, err := d.Exec(query, points, username)
- if err != nil {
- log.Printf("❌ [AddPenalty] Erreur UPDATE: %v", err)
- return fmt.Errorf("erreur ajout pénalité: %w", err)
+ result := d.GDB.Exec(`
+ UPDATE clients
+ SET amende = amende + ?, updated_at = CURRENT_TIMESTAMP
+ WHERE username = ?`, points, username)
+ if result.Error != nil {
+ log.Printf("❌ [AddPenalty] Erreur UPDATE: %v", result.Error)
+ return fmt.Errorf("erreur ajout pénalité: %w", result.Error)
}
-
- rowsAffected, err := result.RowsAffected()
- if err != nil {
- return fmt.Errorf("erreur vérification: %w", err)
- }
- if rowsAffected == 0 {
+ if result.RowsAffected == 0 {
return fmt.Errorf("client non trouvé: %s", username)
}
log.Printf("✅ [AddPenalty] Pénalité ajoutée: +%d points pour %s", points, username)
- // ✅ Invalider le cache Redis du client
cacheKey := fmt.Sprintf("client:%s", username)
Redis.Del(RedisCtx, cacheKey)
diff --git a/backend/gestion/db/db_categories.go b/backend/gestion/db/db_categories.go
index 18bf3605..3e412f58 100644
--- a/backend/gestion/db/db_categories.go
+++ b/backend/gestion/db/db_categories.go
@@ -3,21 +3,24 @@ package db
import (
"fmt"
"regexp"
+ "time"
)
var hexColorRegex = regexp.MustCompile(`^#[0-9A-Fa-f]{6}$`)
type Category struct {
- ID int `json:"id"`
- Name string `json:"name"`
- Color string `json:"color"`
- IsComingSoon bool `json:"is_coming_soon"`
- CreatedAt string `json:"created_at"`
+ ID int `json:"id" gorm:"primaryKey;autoIncrement"`
+ Name string `json:"name" gorm:"column:name"`
+ Color string `json:"color" gorm:"column:color"`
+ IsComingSoon bool `json:"is_coming_soon" gorm:"column:is_coming_soon"`
+ CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
}
+func (Category) TableName() string { return "categories" }
+
func ValidateCategoryColor(color string) error {
if color == "" {
- return nil // valeur par défaut utilisée
+ return nil
}
if !hexColorRegex.MatchString(color) {
return fmt.Errorf("couleur invalide : format hexadécimal requis (ex: #7c3aed)")
@@ -26,19 +29,9 @@ func ValidateCategoryColor(color string) error {
}
func (d *Database) GetAllCategories() ([]Category, error) {
- rows, err := d.Query(`SELECT id, name, color, is_coming_soon, created_at FROM categories ORDER BY name ASC`)
- if err != nil {
- return nil, err
- }
- defer rows.Close()
-
var categories []Category
- for rows.Next() {
- var c Category
- if err := rows.Scan(&c.ID, &c.Name, &c.Color, &c.IsComingSoon, &c.CreatedAt); err != nil {
- return nil, err
- }
- categories = append(categories, c)
+ if err := d.GDB.Order("name ASC").Find(&categories).Error; err != nil {
+ return nil, err
}
if categories == nil {
categories = []Category{}
@@ -50,12 +43,8 @@ func (d *Database) CreateCategory(name, color string, isComingSoon bool) (*Categ
if color == "" {
color = "#7c3aed"
}
- var c Category
- err := d.QueryRow(
- `INSERT INTO categories (name, color, is_coming_soon) VALUES ($1, $2, $3) RETURNING id, name, color, is_coming_soon, created_at`,
- name, color, isComingSoon,
- ).Scan(&c.ID, &c.Name, &c.Color, &c.IsComingSoon, &c.CreatedAt)
- if err != nil {
+ c := Category{Name: name, Color: color, IsComingSoon: isComingSoon}
+ if err := d.GDB.Create(&c).Error; err != nil {
return nil, err
}
return &c, nil
@@ -66,38 +55,35 @@ func (d *Database) UpdateCategory(id int, name, color string, isComingSoon bool)
color = "#7c3aed"
}
var c Category
- err := d.QueryRow(
- `UPDATE categories SET name = $1, color = $2, is_coming_soon = $3 WHERE id = $4 RETURNING id, name, color, is_coming_soon, created_at`,
- name, color, isComingSoon, id,
- ).Scan(&c.ID, &c.Name, &c.Color, &c.IsComingSoon, &c.CreatedAt)
- if err != nil {
+ if err := d.GDB.First(&c, id).Error; err != nil {
+ return nil, err
+ }
+ if err := d.GDB.Model(&c).Updates(Category{Name: name, Color: color, IsComingSoon: isComingSoon}).Error; err != nil {
return nil, err
}
return &c, nil
}
func (d *Database) DeleteCategory(id int) error {
- var count int
- err := d.QueryRow(`SELECT COUNT(*) FROM products WHERE category = (SELECT name FROM categories WHERE id = $1)`, id).Scan(&count)
- if err != nil {
- return err
- }
+ var count int64
+ d.GDB.Table("products").
+ Where("category = (SELECT name FROM categories WHERE id = ?)", id).
+ Count(&count)
if count > 0 {
return fmt.Errorf("catégorie utilisée par %d produit(s)", count)
}
- res, err := d.Exec(`DELETE FROM categories WHERE id = $1`, id)
- if err != nil {
- return err
+ result := d.GDB.Delete(&Category{}, id)
+ if result.Error != nil {
+ return result.Error
}
- n, _ := res.RowsAffected()
- if n == 0 {
+ if result.RowsAffected == 0 {
return fmt.Errorf("catégorie non trouvée")
}
return nil
}
func (d *Database) CategoryExists(name string) (bool, error) {
- var count int
- err := d.QueryRow(`SELECT COUNT(*) FROM categories WHERE name = $1`, name).Scan(&count)
+ var count int64
+ err := d.GDB.Model(&Category{}).Where("name = ?", name).Count(&count).Error
return count > 0, err
}
diff --git a/backend/gestion/db/db_clients.go b/backend/gestion/db/db_clients.go
index 2fd3d80a..96da7667 100644
--- a/backend/gestion/db/db_clients.go
+++ b/backend/gestion/db/db_clients.go
@@ -1,26 +1,32 @@
package db
import (
- "database/sql"
"encoding/json"
"fmt"
"gestion/models"
"log"
"strings"
+ "time"
+
+ "gorm.io/gorm"
)
func (d *Database) CreateClient(client *models.Client) error {
- query := `INSERT INTO clients (username, password, nom, prenom, telephone, command, amende, must_change_password, created_at)
- VALUES ($1, $2, $3, $4, $5, 0, 0.0, $6, CURRENT_TIMESTAMP)
- RETURNING id, created_at`
-
- err := d.QueryRow(query, client.Username, client.Password, client.Nom, client.Prenom, client.Telephone, client.MustChangePassword).Scan(
- &client.ID,
- &client.CreatedAt,
- )
+ var result struct {
+ ID int `gorm:"column:id"`
+ CreatedAt time.Time `gorm:"column:created_at"`
+ }
+ err := d.GDB.Raw(`
+ INSERT INTO clients (username, password, nom, prenom, telephone, command, amende, must_change_password, created_at)
+ VALUES (?, ?, ?, ?, ?, 0, 0.0, ?, CURRENT_TIMESTAMP)
+ RETURNING id, created_at`,
+ client.Username, client.Password, client.Nom, client.Prenom, client.Telephone, client.MustChangePassword,
+ ).Scan(&result).Error
if err != nil {
return fmt.Errorf("erreur lors de la création du client: %w", err)
}
+ client.ID = result.ID
+ client.CreatedAt = result.CreatedAt
log.Printf("✅ Client créé avec succès: %s %s (ID: %d)", client.Prenom, client.Nom, client.ID)
return nil
@@ -28,110 +34,109 @@ func (d *Database) CreateClient(client *models.Client) error {
// GetClientByID récupère un client par son ID
func (d *Database) GetClientByID(id int) (*models.Client, error) {
- var client models.Client
- query := `SELECT id, username, password, nom, prenom, telephone, command, amende, COALESCE(points_extra, '{}'::jsonb), created_at
- FROM clients WHERE id = $1`
-
- var pointsExtraJSON []byte
- err := d.QueryRow(query, id).Scan(
- &client.ID,
- &client.Username,
- &client.Password,
- &client.Nom,
- &client.Prenom,
- &client.Telephone,
- &client.Command,
- &client.Amende,
- &pointsExtraJSON,
- &client.CreatedAt,
- )
-
- if err == sql.ErrNoRows {
- return nil, fmt.Errorf("client non trouvé")
+ var row struct {
+ ID int `gorm:"column:id"`
+ Username string `gorm:"column:username"`
+ Password string `gorm:"column:password"`
+ Nom string `gorm:"column:nom"`
+ Prenom string `gorm:"column:prenom"`
+ Telephone string `gorm:"column:telephone"`
+ Command int `gorm:"column:command"`
+ Amende float64 `gorm:"column:amende"`
+ PointsExtraJSON []byte `gorm:"column:points_extra"`
+ CreatedAt time.Time `gorm:"column:created_at"`
}
+ err := d.GDB.Raw(`
+ SELECT id, username, password, nom, prenom, telephone, command, amende,
+ COALESCE(points_extra, '{}'::jsonb) as points_extra, created_at
+ FROM clients WHERE id = ?`, id).Scan(&row).Error
if err != nil {
return nil, fmt.Errorf("erreur lors de la récupération du client: %w", err)
}
-
- if len(pointsExtraJSON) > 0 {
- json.Unmarshal(pointsExtraJSON, &client.PointsExtra)
+ if row.ID == 0 {
+ return nil, fmt.Errorf("client non trouvé")
}
- return &client, nil
+ client := &models.Client{
+ ID: row.ID,
+ Username: row.Username,
+ Password: row.Password,
+ Nom: row.Nom,
+ Prenom: row.Prenom,
+ Telephone: row.Telephone,
+ Command: row.Command,
+ Amende: row.Amende,
+ CreatedAt: row.CreatedAt,
+ }
+ client.PointsExtra = map[string]int{}
+ if len(row.PointsExtraJSON) > 0 {
+ json.Unmarshal(row.PointsExtraJSON, &client.PointsExtra)
+ }
+
+ return client, nil
}
// GetAllClients récupère tous les clients
func (d *Database) GetAllClients() ([]*models.Client, error) {
- query := `SELECT id, username, password, nom, prenom, telephone, command, amende, referral_balance, COALESCE(points_extra, '{}'::jsonb), created_at
- FROM clients ORDER BY created_at DESC`
-
- rows, err := d.Query(query)
+ var rows []struct {
+ ID int `gorm:"column:id"`
+ Username string `gorm:"column:username"`
+ Password string `gorm:"column:password"`
+ Nom string `gorm:"column:nom"`
+ Prenom string `gorm:"column:prenom"`
+ Telephone string `gorm:"column:telephone"`
+ Command int `gorm:"column:command"`
+ Amende float64 `gorm:"column:amende"`
+ ReferralBalance float64 `gorm:"column:referral_balance"`
+ PointsExtraJSON []byte `gorm:"column:points_extra"`
+ CreatedAt time.Time `gorm:"column:created_at"`
+ }
+ err := d.GDB.Raw(`
+ SELECT id, username, password, nom, prenom, telephone, command, amende, referral_balance,
+ COALESCE(points_extra, '{}'::jsonb) as points_extra, created_at
+ FROM clients ORDER BY created_at DESC`).Scan(&rows).Error
if err != nil {
return nil, fmt.Errorf("erreur lors de la récupération des clients: %w", err)
}
- defer rows.Close()
- var clients []*models.Client
- for rows.Next() {
- client := &models.Client{}
- var pointsExtraJSON []byte
- err := rows.Scan(
- &client.ID,
- &client.Username,
- &client.Password,
- &client.Nom,
- &client.Prenom,
- &client.Telephone,
- &client.Command,
- &client.Amende,
- &client.ReferralBalance,
- &pointsExtraJSON,
- &client.CreatedAt,
- )
- if err != nil {
- return nil, fmt.Errorf("erreur lors du scan du client: %w", err)
+ clients := make([]*models.Client, 0, len(rows))
+ for _, row := range rows {
+ client := &models.Client{
+ ID: row.ID,
+ Username: row.Username,
+ Password: row.Password,
+ Nom: row.Nom,
+ Prenom: row.Prenom,
+ Telephone: row.Telephone,
+ Command: row.Command,
+ Amende: row.Amende,
+ ReferralBalance: row.ReferralBalance,
+ CreatedAt: row.CreatedAt,
}
client.PointsExtra = map[string]int{}
- if len(pointsExtraJSON) > 0 {
- json.Unmarshal(pointsExtraJSON, &client.PointsExtra)
+ if len(row.PointsExtraJSON) > 0 {
+ json.Unmarshal(row.PointsExtraJSON, &client.PointsExtra)
}
clients = append(clients, client)
}
- if err = rows.Err(); err != nil {
- return nil, fmt.Errorf("erreur lors de l'itération des résultats: %w", err)
- }
-
return clients, nil
}
// UpdateClient met à jour un client existant
func (d *Database) UpdateClient(client *models.Client) error {
- query := `UPDATE clients
- SET username = $1, password = $2, nom = $3, prenom = $4, telephone = $5,
- command = $6, amende = $7
- WHERE id = $8`
-
- result, err := d.Exec(query,
- client.Username,
- client.Password,
- client.Nom,
- client.Prenom,
- client.Telephone,
- client.Command,
- client.Amende,
- client.ID,
+ result := d.GDB.Exec(`
+ UPDATE clients
+ SET username = ?, password = ?, nom = ?, prenom = ?, telephone = ?,
+ command = ?, amende = ?
+ WHERE id = ?`,
+ client.Username, client.Password, client.Nom, client.Prenom, client.Telephone,
+ client.Command, client.Amende, client.ID,
)
- if err != nil {
- return fmt.Errorf("erreur lors de la mise à jour du client: %w", err)
+ if result.Error != nil {
+ return fmt.Errorf("erreur lors de la mise à jour du client: %w", result.Error)
}
-
- 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 {
+ if result.RowsAffected == 0 {
return fmt.Errorf("client non trouvé")
}
@@ -142,19 +147,11 @@ func (d *Database) UpdateClient(client *models.Client) error {
func (d *Database) DeleteClient(id int) error {
_ = d.RevokeAllUserTokens(id, "client")
- query := `DELETE FROM clients WHERE id = $1`
-
- result, err := d.Exec(query, id)
- if err != nil {
- return fmt.Errorf("erreur lors de la suppression du client: %w", err)
+ result := d.GDB.Exec(`DELETE FROM clients WHERE id = ?`, id)
+ if result.Error != nil {
+ return fmt.Errorf("erreur lors de la suppression du client: %w", result.Error)
}
-
- 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 {
+ if result.RowsAffected == 0 {
return fmt.Errorf("client non trouvé")
}
@@ -163,19 +160,11 @@ func (d *Database) DeleteClient(id int) error {
// UpdateClientPassword met à jour le mot de passe d'un client
func (d *Database) UpdateClientPassword(clientID int, hashedPassword string) error {
- query := `UPDATE clients SET password = $1 WHERE id = $2`
-
- result, err := d.Exec(query, hashedPassword, clientID)
- if err != nil {
- return fmt.Errorf("erreur lors de la mise à jour du mot de passe: %w", err)
+ result := d.GDB.Exec(`UPDATE clients SET password = ? WHERE id = ?`, hashedPassword, clientID)
+ if result.Error != nil {
+ return fmt.Errorf("erreur lors de la mise à jour du mot de passe: %w", result.Error)
}
-
- rowsAffected, err := result.RowsAffected()
- if err != nil {
- return fmt.Errorf("erreur lors de la vérification: %w", err)
- }
-
- if rowsAffected == 0 {
+ if result.RowsAffected == 0 {
return fmt.Errorf("client non trouvé")
}
@@ -184,19 +173,13 @@ func (d *Database) UpdateClientPassword(clientID int, hashedPassword string) err
// UpdateClientPasswordAndClearFlag met à jour le mot de passe et remet must_change_password à false
func (d *Database) UpdateClientPasswordAndClearFlag(clientID int, hashedPassword string) error {
- query := `UPDATE clients SET password = $1, must_change_password = FALSE, updated_at = CURRENT_TIMESTAMP WHERE id = $2`
-
- result, err := d.Exec(query, hashedPassword, clientID)
- if err != nil {
- return fmt.Errorf("erreur lors de la mise à jour du mot de passe: %w", err)
+ result := d.GDB.Exec(`
+ UPDATE clients SET password = ?, must_change_password = FALSE, updated_at = CURRENT_TIMESTAMP
+ WHERE id = ?`, hashedPassword, clientID)
+ if result.Error != nil {
+ return fmt.Errorf("erreur lors de la mise à jour du mot de passe: %w", result.Error)
}
-
- rowsAffected, err := result.RowsAffected()
- if err != nil {
- return fmt.Errorf("erreur lors de la vérification: %w", err)
- }
-
- if rowsAffected == 0 {
+ if result.RowsAffected == 0 {
return fmt.Errorf("client non trouvé")
}
@@ -210,19 +193,18 @@ func (d *Database) GetClientStats(clientID int) (map[string]interface{}, error)
return nil, err
}
- // Compter les commandes du client
- var totalCommands, pendingCommands, completedCommands int
-
- countQuery := `SELECT
- COUNT(*) as total,
- SUM(CASE WHEN status = 'pending' OR status = 'livre' THEN 1 ELSE 0 END) as pending,
- SUM(CASE WHEN status = 'approved' THEN 1 ELSE 0 END) as completed
- FROM commandes WHERE username = $1`
-
- err = d.QueryRow(countQuery, client.Username).Scan(&totalCommands, &pendingCommands, &completedCommands)
- if err != nil {
+ var statsResult struct {
+ Total int `gorm:"column:total"`
+ Pending int `gorm:"column:pending"`
+ Completed int `gorm:"column:completed"`
+ }
+ if err := d.GDB.Raw(`
+ SELECT
+ COUNT(*) as total,
+ COALESCE(SUM(CASE WHEN status = 'pending' OR status = 'livre' THEN 1 ELSE 0 END), 0) as pending,
+ COALESCE(SUM(CASE WHEN status = 'approved' THEN 1 ELSE 0 END), 0) as completed
+ FROM commandes WHERE username = ?`, client.Username).Scan(&statsResult).Error; err != nil {
log.Printf("⚠️ Erreur calcul stats: %v", err)
- totalCommands, pendingCommands, completedCommands = 0, 0, 0
}
stats := map[string]interface{}{
@@ -231,9 +213,9 @@ func (d *Database) GetClientStats(clientID int) (map[string]interface{}, error)
"nom": client.Nom,
"prenom": client.Prenom,
"telephone": client.Telephone,
- "total_commands": totalCommands,
- "pending_commands": pendingCommands,
- "completed_commands": completedCommands,
+ "total_commands": statsResult.Total,
+ "pending_commands": statsResult.Pending,
+ "completed_commands": statsResult.Completed,
"points_extra": client.PointsExtra,
"amende": client.Amende,
"member_since": client.CreatedAt,
@@ -243,23 +225,22 @@ func (d *Database) GetClientStats(clientID int) (map[string]interface{}, error)
}
func (d *Database) GetClientAmende(username string) (float64, error) {
- var amende float64
- query := `SELECT COALESCE(amende, 0) FROM clients WHERE username = $1`
-
- err := d.QueryRow(query, username).Scan(&amende)
+ var result struct {
+ Amende float64 `gorm:"column:amende"`
+ }
+ err := d.GDB.Raw(`SELECT COALESCE(amende, 0) as amende FROM clients WHERE username = ?`, username).Scan(&result).Error
if err != nil {
log.Printf("❌ [GetClientAmende] Erreur pour %s: %v", username, err)
return 0, fmt.Errorf("erreur récupération pénalités: %w", err)
}
- log.Printf("💰 [GetClientAmende] Client %s: %.2f points", username, amende)
- return amende, nil
+ log.Printf("💰 [GetClientAmende] Client %s: %.2f points", username, result.Amende)
+ return result.Amende, nil
}
func (d *Database) PayClientPenalties(username string, amountPaid float64) error {
log.Printf("💳 [PayClientPenalties] Paiement de %.2f points pour %s", amountPaid, username)
- // Vérifier le montant actuel
currentAmount, err := d.GetClientAmende(username)
if err != nil {
return err
@@ -273,21 +254,14 @@ func (d *Database) PayClientPenalties(username string, amountPaid float64) error
return fmt.Errorf("montant insuffisant: %.2f payé, %.2f requis", amountPaid, currentAmount)
}
- query := `UPDATE clients
- SET amende = 0, updated_at = CURRENT_TIMESTAMP
- WHERE username = $1`
-
- result, err := d.Exec(query, username)
- if err != nil {
- log.Printf("❌ [PayClientPenalties] Erreur UPDATE: %v", err)
- return fmt.Errorf("erreur paiement pénalités: %w", err)
+ result := d.GDB.Exec(`
+ UPDATE clients SET amende = 0, updated_at = CURRENT_TIMESTAMP
+ WHERE username = ?`, username)
+ if result.Error != nil {
+ log.Printf("❌ [PayClientPenalties] Erreur UPDATE: %v", result.Error)
+ return fmt.Errorf("erreur paiement pénalités: %w", result.Error)
}
-
- rowsAffected, err := result.RowsAffected()
- if err != nil {
- return fmt.Errorf("erreur vérification: %w", err)
- }
- if rowsAffected == 0 {
+ if result.RowsAffected == 0 {
return fmt.Errorf("client non trouvé")
}
@@ -299,20 +273,11 @@ func (d *Database) PayClientPenalties(username string, amountPaid float64) error
// IncrementClientCommandCount incrémente le compteur de commandes du client
func (d *Database) IncrementClientCommandCount(username string) error {
- query := `UPDATE clients
- SET command = command + 1
- WHERE username = $1`
-
- result, err := d.Exec(query, username)
- if err != nil {
- return fmt.Errorf("erreur lors de l'incrémentation du compteur: %w", err)
+ result := d.GDB.Exec(`UPDATE clients SET command = command + 1 WHERE username = ?`, username)
+ if result.Error != nil {
+ return fmt.Errorf("erreur lors de l'incrémentation du compteur: %w", result.Error)
}
-
- rowsAffected, err := result.RowsAffected()
- if err != nil {
- return fmt.Errorf("erreur lors de la vérification: %w", err)
- }
- if rowsAffected == 0 {
+ if result.RowsAffected == 0 {
return fmt.Errorf("client non trouvé")
}
@@ -323,23 +288,19 @@ func (d *Database) AddClientPointsByCategory(username string, points int, poolKe
if poolKey == "" {
poolKey = "pool_0"
}
- result, err := d.Exec(`
+ result := d.GDB.Exec(`
UPDATE clients
SET points_extra = jsonb_set(
COALESCE(points_extra, '{}'::jsonb),
- ARRAY[$2],
- to_jsonb(COALESCE((points_extra->>$2)::int, 0) + $3)
+ ARRAY[?],
+ to_jsonb(COALESCE((points_extra->>?)::int, 0) + ?)
), updated_at = CURRENT_TIMESTAMP
- WHERE username = $1
- `, username, poolKey, points)
- if err != nil {
- return fmt.Errorf("erreur lors de l'ajout de points: %w", err)
+ WHERE username = ?`,
+ poolKey, poolKey, points, username)
+ if result.Error != nil {
+ return fmt.Errorf("erreur lors de l'ajout de points: %w", result.Error)
}
- rowsAffected, err := result.RowsAffected()
- if err != nil {
- return fmt.Errorf("erreur lors de la vérification: %w", err)
- }
- if rowsAffected == 0 {
+ if result.RowsAffected == 0 {
return fmt.Errorf("client non trouvé")
}
log.Printf("✅ %d points (key=%s) ajoutés au client %s", points, poolKey, username)
@@ -347,101 +308,114 @@ func (d *Database) AddClientPointsByCategory(username string, points int, poolKe
}
func (d *Database) CalculateAndAddPointsForCommand(commandID int, username string) (int, error) {
- tx, err := d.Begin()
- if err != nil {
- return 0, fmt.Errorf("erreur transaction: %w", err)
- }
- defer tx.Rollback()
-
- points, _, err := d.CalculateAndAddPointsForCommandTx(tx, commandID, username)
+ var totalPoints int
+ err := d.GDB.Transaction(func(tx *gorm.DB) error {
+ points, _, err := d.CalculateAndAddPointsForCommandTx(tx, commandID, username)
+ if err != nil {
+ return err
+ }
+ totalPoints = points
+ return nil
+ })
if err != nil {
return 0, err
}
-
- if err := tx.Commit(); err != nil {
- return 0, fmt.Errorf("erreur commit: %w", err)
- }
- return points, nil
+ return totalPoints, nil
}
func (d *Database) GetClientByTelephone(telephone string) (*models.Client, error) {
- client := &models.Client{}
- query := `SELECT id, username, password, nom, prenom, telephone, command, amende, created_at
- FROM clients WHERE telephone = $1`
-
- err := d.QueryRow(query, telephone).Scan(
- &client.ID,
- &client.Username,
- &client.Password,
- &client.Nom,
- &client.Prenom,
- &client.Telephone,
- &client.Command,
- &client.Amende,
- &client.CreatedAt,
- )
-
- if err == sql.ErrNoRows {
- return nil, nil
+ var row struct {
+ ID int `gorm:"column:id"`
+ Username string `gorm:"column:username"`
+ Password string `gorm:"column:password"`
+ Nom string `gorm:"column:nom"`
+ Prenom string `gorm:"column:prenom"`
+ Telephone string `gorm:"column:telephone"`
+ Command int `gorm:"column:command"`
+ Amende float64 `gorm:"column:amende"`
+ CreatedAt time.Time `gorm:"column:created_at"`
}
+ err := d.GDB.Raw(`
+ SELECT id, username, password, nom, prenom, telephone, command, amende, created_at
+ FROM clients WHERE telephone = ?`, telephone).Scan(&row).Error
if err != nil {
return nil, fmt.Errorf("erreur lors de la récupération du client: %w", err)
}
+ if row.ID == 0 {
+ return nil, nil
+ }
- return client, nil
+ return &models.Client{
+ ID: row.ID,
+ Username: row.Username,
+ Password: row.Password,
+ Nom: row.Nom,
+ Prenom: row.Prenom,
+ Telephone: row.Telephone,
+ Command: row.Command,
+ Amende: row.Amende,
+ CreatedAt: row.CreatedAt,
+ }, nil
}
// GetClientByUsername récupère un client par son username
func (d *Database) GetClientByUsername(username string) (*models.Client, error) {
- client := &models.Client{}
- var pointsExtraJSON []byte
- query := `SELECT id, username, password, nom, prenom, telephone, command, amende, must_change_password, COALESCE(points_extra, '{}'::jsonb), created_at
- FROM clients WHERE username = $1`
-
- err := d.QueryRow(query, username).Scan(
- &client.ID,
- &client.Username,
- &client.Password,
- &client.Nom,
- &client.Prenom,
- &client.Telephone,
- &client.Command,
- &client.Amende,
- &client.MustChangePassword,
- &pointsExtraJSON,
- &client.CreatedAt,
- )
-
- if err == sql.ErrNoRows {
- return nil, nil
+ var row struct {
+ ID int `gorm:"column:id"`
+ Username string `gorm:"column:username"`
+ Password string `gorm:"column:password"`
+ Nom string `gorm:"column:nom"`
+ Prenom string `gorm:"column:prenom"`
+ Telephone string `gorm:"column:telephone"`
+ Command int `gorm:"column:command"`
+ Amende float64 `gorm:"column:amende"`
+ MustChangePassword bool `gorm:"column:must_change_password"`
+ PointsExtraJSON []byte `gorm:"column:points_extra"`
+ CreatedAt time.Time `gorm:"column:created_at"`
}
+ err := d.GDB.Raw(`
+ SELECT id, username, password, nom, prenom, telephone, command, amende,
+ must_change_password, COALESCE(points_extra, '{}'::jsonb) as points_extra, created_at
+ FROM clients WHERE username = ?`, username).Scan(&row).Error
if err != nil {
return nil, fmt.Errorf("erreur lors de la récupération du client: %w", err)
}
+ if row.ID == 0 {
+ return nil, nil
+ }
+ client := &models.Client{
+ ID: row.ID,
+ Username: row.Username,
+ Password: row.Password,
+ Nom: row.Nom,
+ Prenom: row.Prenom,
+ Telephone: row.Telephone,
+ Command: row.Command,
+ Amende: row.Amende,
+ MustChangePassword: row.MustChangePassword,
+ CreatedAt: row.CreatedAt,
+ }
client.PointsExtra = map[string]int{}
- if len(pointsExtraJSON) > 0 {
- json.Unmarshal(pointsExtraJSON, &client.PointsExtra)
+ if len(row.PointsExtraJSON) > 0 {
+ json.Unmarshal(row.PointsExtraJSON, &client.PointsExtra)
}
return client, nil
}
func (d *Database) GetClientPenaltiesInfo(username string) (map[string]interface{}, error) {
- // Récupérer le montant des pénalités
amende, err := d.GetClientAmende(username)
if err != nil {
return nil, err
}
- // Récupérer le nombre d'annulations
cancellationsCount, err := d.GetClientCancellationsCount(username)
if err != nil {
log.Printf("⚠️ [GetClientPenaltiesInfo] Erreur récup annulations: %v", err)
cancellationsCount = 0
}
- // Récupérer l'historique d'annulations
cancellationHistory, err := d.GetClientCancellationHistory(username)
if err != nil {
log.Printf("⚠️ [GetClientPenaltiesInfo] Erreur récup historique: %v", err)
@@ -481,13 +455,10 @@ func (d *Database) CheckClientCanOrder(username string) (bool, float64, error) {
// extraPoolKey != "" → reset points_extra[extraPoolKey] uniquement
// extraPoolKey == "" (poolIdx=-1) → reset total points_extra
func (d *Database) ResetClientPoint(username string, poolIdx int, extraPoolKey string) error {
- var query string
- switch {
- case extraPoolKey != "":
- _, err := d.Exec(
- `UPDATE clients SET points_extra = points_extra - $2, updated_at = CURRENT_TIMESTAMP WHERE username = $1`,
- username, extraPoolKey,
- )
+ if extraPoolKey != "" {
+ err := d.GDB.Exec(`
+ UPDATE clients SET points_extra = points_extra - ?, updated_at = CURRENT_TIMESTAMP
+ WHERE username = ?`, extraPoolKey, username).Error
if err != nil {
log.Printf("❌ [ResetClientPointAdmin] Erreur UPDATE extra: %v", err)
} else {
@@ -495,25 +466,20 @@ func (d *Database) ResetClientPoint(username string, poolIdx int, extraPoolKey s
Redis.Del(RedisCtx, cacheKey)
}
return err
- default: // -1 ou poolIdx sans clé → reset total
- query = `UPDATE clients SET points_extra = '{}'::jsonb, updated_at = CURRENT_TIMESTAMP WHERE username = $1`
}
- result, err := d.Exec(query, username)
- if err != nil {
- log.Printf("❌ [ResetClientPointAdmin] Erreur UPDATE: %v", err)
- return fmt.Errorf("erreur reset points: %w", err)
+ // -1 ou poolIdx sans clé → reset total
+ result := d.GDB.Exec(`
+ UPDATE clients SET points_extra = '{}'::jsonb, updated_at = CURRENT_TIMESTAMP
+ WHERE username = ?`, username)
+ if result.Error != nil {
+ log.Printf("❌ [ResetClientPointAdmin] Erreur UPDATE: %v", result.Error)
+ return fmt.Errorf("erreur reset points: %w", result.Error)
}
-
- rowsAffected, err := result.RowsAffected()
- if err != nil {
- return fmt.Errorf("erreur vérification: %w", err)
- }
- if rowsAffected == 0 {
+ if result.RowsAffected == 0 {
return fmt.Errorf("client non trouvé")
}
- // Invalider le cache Redis du client
cacheKey := fmt.Sprintf("client:%s", username)
Redis.Del(RedisCtx, cacheKey)
@@ -525,33 +491,20 @@ func (d *Database) ResetClientPenalties(username string, resetCancellationsCount
var query string
if resetCancellationsCount {
- query = `UPDATE clients
- SET amende = 0,
- cancellations_count = 0,
- updated_at = CURRENT_TIMESTAMP
- WHERE username = $1`
+ query = `UPDATE clients SET amende = 0, cancellations_count = 0, updated_at = CURRENT_TIMESTAMP WHERE username = ?`
} else {
- query = `UPDATE clients
- SET amende = 0,
- updated_at = CURRENT_TIMESTAMP
- WHERE username = $1`
+ query = `UPDATE clients SET amende = 0, updated_at = CURRENT_TIMESTAMP WHERE username = ?`
}
- result, err := d.Exec(query, username)
- if err != nil {
- log.Printf("❌ [ResetClientPenalties] Erreur UPDATE: %v", err)
- return fmt.Errorf("erreur reset pénalités: %w", err)
+ result := d.GDB.Exec(query, username)
+ if result.Error != nil {
+ log.Printf("❌ [ResetClientPenalties] Erreur UPDATE: %v", result.Error)
+ return fmt.Errorf("erreur reset pénalités: %w", result.Error)
}
-
- rowsAffected, err := result.RowsAffected()
- if err != nil {
- return fmt.Errorf("erreur vérification: %w", err)
- }
- if rowsAffected == 0 {
+ if result.RowsAffected == 0 {
return fmt.Errorf("client non trouvé")
}
- // Invalider le cache Redis du client
cacheKey := fmt.Sprintf("client:%s", username)
Redis.Del(RedisCtx, cacheKey)
@@ -559,38 +512,29 @@ func (d *Database) ResetClientPenalties(username string, resetCancellationsCount
}
func (d *Database) GetAllClientsWithPenalties() ([]map[string]interface{}, error) {
- query := `
+ var rows []struct {
+ Username string `gorm:"column:username"`
+ Amende float64 `gorm:"column:amende"`
+ CancellationsCount int `gorm:"column:cancellations_count"`
+ UpdatedAt interface{} `gorm:"column:updated_at"`
+ }
+ err := d.GDB.Raw(`
SELECT username, amende, COALESCE(cancellations_count, 0) as cancellations_count, updated_at
FROM clients
WHERE amende > 0
- ORDER BY amende DESC
- `
-
- rows, err := d.Query(query)
+ ORDER BY amende DESC`).Scan(&rows).Error
if err != nil {
log.Printf("❌ [GetAllClientsWithPenalties] Erreur query: %v", err)
return nil, fmt.Errorf("erreur récupération clients: %w", err)
}
- defer rows.Close()
-
- var clients []map[string]interface{}
- for rows.Next() {
- var username string
- var amende float64
- var cancellationsCount int
- var updatedAt interface{}
-
- err := rows.Scan(&username, &amende, &cancellationsCount, &updatedAt)
- if err != nil {
- log.Printf("⚠️ [GetAllClientsWithPenalties] Erreur scan: %v", err)
- continue
- }
+ clients := make([]map[string]interface{}, 0, len(rows))
+ for _, row := range rows {
clients = append(clients, map[string]interface{}{
- "username": username,
- "total_penalty": amende,
- "cancellations_count": cancellationsCount,
- "last_updated": updatedAt,
+ "username": row.Username,
+ "total_penalty": row.Amende,
+ "cancellations_count": row.CancellationsCount,
+ "last_updated": row.UpdatedAt,
})
}
@@ -600,51 +544,42 @@ func (d *Database) GetAllClientsWithPenalties() ([]map[string]interface{}, error
}
func (d *Database) GetClientPenaltiesStats() (map[string]interface{}, error) {
- query := `
+ var result struct {
+ ClientsWithPenalties int `gorm:"column:clients_with_penalties"`
+ TotalPenalties float64 `gorm:"column:total_penalties"`
+ AvgPenalty float64 `gorm:"column:avg_penalty"`
+ MaxPenalty float64 `gorm:"column:max_penalty"`
+ TotalClients int `gorm:"column:total_clients"`
+ }
+
+ err := d.GDB.Raw(`
SELECT
COUNT(CASE WHEN amende > 0 THEN 1 END) as clients_with_penalties,
COALESCE(SUM(amende), 0) as total_penalties,
COALESCE(AVG(amende), 0) as avg_penalty,
COALESCE(MAX(amende), 0) as max_penalty,
COUNT(*) as total_clients
- FROM clients
- `
-
- var stats struct {
- ClientsWithPenalties int
- TotalPenalties float64
- AvgPenalty float64
- MaxPenalty float64
- TotalClients int
- }
-
- err := d.QueryRow(query).Scan(
- &stats.ClientsWithPenalties,
- &stats.TotalPenalties,
- &stats.AvgPenalty,
- &stats.MaxPenalty,
- &stats.TotalClients,
- )
+ FROM clients`).Scan(&result).Error
if err != nil {
log.Printf("❌ [GetClientPenaltiesStats] Erreur: %v", err)
return nil, fmt.Errorf("erreur récupération stats: %w", err)
}
- result := map[string]interface{}{
- "clients_with_penalties": stats.ClientsWithPenalties,
- "total_penalties": stats.TotalPenalties,
- "average_penalty": stats.AvgPenalty,
- "max_penalty": stats.MaxPenalty,
- "total_clients": stats.TotalClients,
+ stats := map[string]interface{}{
+ "clients_with_penalties": result.ClientsWithPenalties,
+ "total_penalties": result.TotalPenalties,
+ "average_penalty": result.AvgPenalty,
+ "max_penalty": result.MaxPenalty,
+ "total_clients": result.TotalClients,
}
log.Printf("📊 [GetClientPenaltiesStats] Stats: %d/%d clients avec pénalités",
- stats.ClientsWithPenalties, stats.TotalClients)
+ result.ClientsWithPenalties, result.TotalClients)
- return result, nil
+ return stats, nil
}
-func (d *Database) CalculateAndAddPointsForCommandTx(tx *sql.Tx, commandID int, username string) (int, string, error) {
+func (d *Database) CalculateAndAddPointsForCommandTx(tx *gorm.DB, commandID int, username string) (int, string, error) {
log.Printf("💰 [CalcPointsTx] START - cmd=%d, user=%s", commandID, username)
// Charger les paramètres globaux
@@ -674,45 +609,34 @@ func (d *Database) CalculateAndAddPointsForCommandTx(tx *sql.Tx, commandID int,
}
// ✅ ÉTAPE 1: Récupérer tous les items de la commande avec leurs catégories
- rows, err := tx.Query(`
+ var items []struct {
+ Quantite float64 `gorm:"column:quantite"`
+ Prix float64 `gorm:"column:prix"`
+ Category string `gorm:"column:category"`
+ }
+ if err := tx.Raw(`
SELECT ci.quantite, ci.prix, COALESCE(p.category, '') as category
FROM command_items ci
LEFT JOIN products p ON ci.product_id = p.id
- WHERE ci.command_id = $1
- `, commandID)
- if err != nil {
+ WHERE ci.command_id = ?
+ `, commandID).Scan(&items).Error; err != nil {
log.Printf("❌ [CalcPointsTx] Erreur query items: %v", err)
return 0, "", fmt.Errorf("erreur récupération items: %w", err)
}
- defer rows.Close()
- var itemCount int
- poolTotals := make([]float64, len(pools))
-
- for rows.Next() {
- var quantite, prix float64
- var category string
- if err := rows.Scan(&quantite, &prix, &category); err != nil {
- log.Printf("❌ [CalcPointsTx] Erreur scan: %v", err)
- return 0, "", fmt.Errorf("erreur lecture item: %w", err)
- }
- itemCount++
- catLower := strings.ToLower(category)
- if poolIdx, ok := catToPool[catLower]; ok {
- poolTotals[poolIdx] += prix
- }
- }
-
- if err = rows.Err(); err != nil {
- log.Printf("❌ [CalcPointsTx] Erreur rows: %v", err)
- return 0, "", fmt.Errorf("erreur itération items: %w", err)
- }
-
- if itemCount == 0 {
+ if len(items) == 0 {
log.Printf("⚠️ [CalcPointsTx] Aucun item trouvé pour cmd %d", commandID)
return 0, "", nil
}
+ poolTotals := make([]float64, len(pools))
+ for _, item := range items {
+ catLower := strings.ToLower(item.Category)
+ if poolIdx, ok := catToPool[catLower]; ok {
+ poolTotals[poolIdx] += item.Prix
+ }
+ }
+
for i, t := range poolTotals {
log.Printf("📊 [CalcPointsTx] Pool[%d] (%s): %.2f€", i, pools[i].Name, t)
}
@@ -747,16 +671,15 @@ func (d *Database) CalculateAndAddPointsForCommandTx(tx *sql.Tx, commandID int,
if poolPts[i] == 0 {
continue
}
- _, err = tx.Exec(`
+ if err := tx.Exec(`
UPDATE clients
SET points_extra = jsonb_set(
COALESCE(points_extra, '{}'::jsonb),
- ARRAY[$2],
- to_jsonb(COALESCE((points_extra->>$2)::int, 0) + $3)
+ ARRAY[?],
+ to_jsonb(COALESCE((points_extra->>?)::int, 0) + ?)
), updated_at = CURRENT_TIMESTAMP
- WHERE username = $1
- `, username, pool.Key, poolPts[i])
- if err != nil {
+ WHERE username = ?
+ `, pool.Key, pool.Key, poolPts[i], username).Error; err != nil {
log.Printf("❌ [CalcPointsTx] Erreur UPDATE points_extra pool[%d] (%s): %v", i, pool.Key, err)
return 0, "", fmt.Errorf("erreur mise à jour points pool[%d]: %w", i, err)
}
@@ -783,22 +706,22 @@ func (d *Database) CanUserAccessCommand(
// 🚚 Livreur : seulement commandes assignées
if role == "livreur" {
- err := d.QueryRow(`
+ err := d.GDB.Raw(`
SELECT EXISTS(
SELECT 1 FROM commandes
- WHERE id = $1 AND livreur_assign = $2
+ WHERE id = ? AND livreur_assign = ?
)
- `, commandID, username).Scan(&exists)
+ `, commandID, username).Scan(&exists).Error
return exists, err
}
// 👤 User : seulement SES commandes
- err := d.QueryRow(`
+ err := d.GDB.Raw(`
SELECT EXISTS(
SELECT 1 FROM commandes
- WHERE id = $1 AND username = $2
+ WHERE id = ? AND username = ?
)
- `, commandID, username).Scan(&exists)
+ `, commandID, username).Scan(&exists).Error
return exists, err
}
diff --git a/backend/gestion/db/db_command_items.go b/backend/gestion/db/db_command_items.go
index e762a3bb..8052636f 100644
--- a/backend/gestion/db/db_command_items.go
+++ b/backend/gestion/db/db_command_items.go
@@ -1,7 +1,6 @@
package db
import (
- "database/sql"
"fmt"
"log"
"strings"
@@ -151,9 +150,7 @@ func (d *Database) InsertCommandItemWithClientInfo(
// ✅ VÉRIFIER QUE LA COMMANDE EXISTE
var exists bool
- checkQuery := `SELECT EXISTS(SELECT 1 FROM commandes WHERE id = $1)`
- err := d.QueryRow(checkQuery, commandID).Scan(&exists)
- if err != nil {
+ if err := d.GDB.Raw(`SELECT EXISTS(SELECT 1 FROM commandes WHERE id = ?)`, commandID).Scan(&exists).Error; err != nil {
log.Printf("❌ Erreur vérification commande: %v", err)
return fmt.Errorf("erreur vérification commande: %w", err)
}
@@ -162,16 +159,15 @@ func (d *Database) InsertCommandItemWithClientInfo(
}
// ✅ INSERT
- query := `INSERT INTO command_items (
- command_id, produit, product_id, quantite, prix,
- client_username, client_nom, client_prenom, client_telephone, delivery_address,
- status, created_at, updated_at
- ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, 'pending', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`
-
- _, err = d.Exec(query,
+ err := d.GDB.Exec(`
+ INSERT INTO command_items (
+ command_id, produit, product_id, quantite, prix,
+ client_username, client_nom, client_prenom, client_telephone, delivery_address,
+ status, created_at, updated_at
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
commandID, produit, productID, quantite, prix,
clientUsername, clientNom, clientPrenom, clientTelephone, deliveryAddress,
- )
+ ).Error
if err != nil {
log.Printf("❌ Erreur INSERT command_items: %v", err)
return fmt.Errorf("erreur insertion item: %w", err)
@@ -194,7 +190,31 @@ func (d *Database) GetCommandItems(commandID int) ([]map[string]interface{}, err
return nil, err
}
- query := `
+ var rows []struct {
+ ID int `gorm:"column:id"`
+ CommandID int `gorm:"column:command_id"`
+ Produit string `gorm:"column:produit"`
+ ProductID *int64 `gorm:"column:product_id"`
+ Quantite float64 `gorm:"column:quantite"`
+ Prix float64 `gorm:"column:prix"`
+ ClientUsername string `gorm:"column:client_username"`
+ ClientNom string `gorm:"column:client_nom"`
+ ClientPrenom string `gorm:"column:client_prenom"`
+ ClientTelephone string `gorm:"column:client_telephone"`
+ DeliveryAddress *string `gorm:"column:delivery_address"`
+ Status *string `gorm:"column:status"`
+ CreatedAt time.Time `gorm:"column:created_at"`
+ UpdatedAt time.Time `gorm:"column:updated_at"`
+ CommandStatus *string `gorm:"column:command_status"`
+ CommandAddress *string `gorm:"column:command_address"`
+ TotalPrix float64 `gorm:"column:total_prix"`
+ ReferralUsed float64 `gorm:"column:referral_used"`
+ LivreurAssign *string `gorm:"column:livreur_assign"`
+ CommandCreatedAt *time.Time `gorm:"column:command_created_at"`
+ Category string `gorm:"column:category"`
+ }
+
+ err := d.GDB.Raw(`
SELECT
ci.id,
ci.command_id,
@@ -220,94 +240,93 @@ func (d *Database) GetCommandItems(commandID int) ([]map[string]interface{}, err
FROM command_items ci
LEFT JOIN commandes c ON ci.command_id = c.id
LEFT JOIN products p ON ci.product_id = p.id
- WHERE ci.command_id = $1
- ORDER BY ci.id ASC`
-
- rows, err := d.Query(query, commandID)
+ WHERE ci.command_id = ?
+ ORDER BY ci.id ASC`, commandID).Scan(&rows).Error
if err != nil {
log.Printf("❌ Erreur query: %v", err)
return nil, fmt.Errorf("erreur récupération items: %w", err)
}
- defer rows.Close()
- var items []map[string]interface{}
-
- for rows.Next() {
- var id, commandID int
- var quantite float64
- var productID sql.NullInt64 // ✅ FIX: Utiliser NullInt64 pour gérer NULL
- var produit, clientUsername, clientNom, clientPrenom, clientTelephone string
- var deliveryAddress, status sql.NullString
- var prix, totalPrix float64
- var createdAt, updatedAt, commandCreatedAt time.Time
- var commandStatus, commandAddress, livreurAssign sql.NullString
- var category string
- var referralUsed float64
-
- // ✅ FIX: Utiliser &productID (sql.NullInt64)
- err := rows.Scan(
- &id, &commandID, &produit, &productID, &quantite, &prix,
- &clientUsername, &clientNom, &clientPrenom, &clientTelephone, &deliveryAddress, &status,
- &createdAt, &updatedAt,
- &commandStatus, &commandAddress, &totalPrix, &referralUsed, &livreurAssign, &commandCreatedAt,
- &category,
- )
-
- if err != nil {
- log.Printf("❌ Erreur scan: %v", err)
- return nil, fmt.Errorf("erreur scan: %w", err)
+ items := make([]map[string]interface{}, 0, len(rows))
+ for _, row := range rows {
+ productIDValue := 0
+ if row.ProductID != nil {
+ productIDValue = int(*row.ProductID)
}
- // ✅ CONVERTIR sql.NullInt64 en int (0 si NULL)
- productIDValue := 0
- if productID.Valid {
- productIDValue = int(productID.Int64)
+ var commandCreatedAt interface{}
+ if row.CommandCreatedAt != nil {
+ commandCreatedAt = *row.CommandCreatedAt
}
item := map[string]interface{}{
- "id": id,
- "command_id": commandID,
- "produit": produit,
- "product_id": productIDValue, // ✅ FIX: Utiliser la valeur convertie
- "quantite": quantite,
- "prix": prix,
- "client_username": clientUsername,
- "client_nom": clientNom,
- "client_prenom": clientPrenom,
- "client_telephone": clientTelephone,
- "delivery_address": deliveryAddress.String,
- "status": status.String,
- "created_at": createdAt,
- "updated_at": updatedAt,
+ "id": row.ID,
+ "command_id": row.CommandID,
+ "produit": row.Produit,
+ "product_id": productIDValue,
+ "quantite": row.Quantite,
+ "prix": row.Prix,
+ "client_username": row.ClientUsername,
+ "client_nom": row.ClientNom,
+ "client_prenom": row.ClientPrenom,
+ "client_telephone": row.ClientTelephone,
+ "delivery_address": ptrStr(row.DeliveryAddress),
+ "status": ptrStr(row.Status),
+ "created_at": row.CreatedAt,
+ "updated_at": row.UpdatedAt,
// Infos commande
- "command_status": commandStatus.String,
- "command_address": commandAddress.String,
- "total_prix": totalPrix,
- "referral_used": referralUsed,
- "livreur_assign": livreurAssign.String,
+ "command_status": ptrStr(row.CommandStatus),
+ "command_address": ptrStr(row.CommandAddress),
+ "total_prix": row.TotalPrix,
+ "referral_used": row.ReferralUsed,
+ "livreur_assign": ptrStr(row.LivreurAssign),
"command_created_at": commandCreatedAt,
- "category": category,
+ "category": row.Category,
}
-
items = append(items, item)
}
- if err = rows.Err(); err != nil {
- log.Printf("❌ Erreur itération: %v", err)
- return nil, fmt.Errorf("erreur itération: %w", err)
- }
-
log.Printf("✅ %d items récupérés avec infos client et catégories", len(items))
return items, nil
}
+// ptrStr retourne la valeur d'un *string ou "" si nil
+func ptrStr(s *string) string {
+ if s == nil {
+ return ""
+ }
+ return *s
+}
+
func (d *Database) GetCommandItemsByUsername(username string) ([]map[string]interface{}, error) {
if err := validateUsername(username); err != nil {
log.Printf("❌ [GetCommandItemsByUsername] %v", err)
return nil, err
}
- query := `
+ var rows []struct {
+ ID int `gorm:"column:id"`
+ CommandID int `gorm:"column:command_id"`
+ Produit string `gorm:"column:produit"`
+ ProductID *int64 `gorm:"column:product_id"`
+ Quantite float64 `gorm:"column:quantite"`
+ Prix float64 `gorm:"column:prix"`
+ ClientUsername string `gorm:"column:client_username"`
+ ClientNom string `gorm:"column:client_nom"`
+ ClientPrenom string `gorm:"column:client_prenom"`
+ ClientTelephone string `gorm:"column:client_telephone"`
+ DeliveryAddress *string `gorm:"column:delivery_address"`
+ Status *string `gorm:"column:status"`
+ CreatedAt time.Time `gorm:"column:created_at"`
+ UpdatedAt time.Time `gorm:"column:updated_at"`
+ CommandStatus *string `gorm:"column:command_status"`
+ CommandAddress *string `gorm:"column:command_address"`
+ TotalPrix float64 `gorm:"column:total_prix"`
+ LivreurAssign *string `gorm:"column:livreur_assign"`
+ CommandCreatedAt *time.Time `gorm:"column:command_created_at"`
+ }
+
+ err := d.GDB.Raw(`
SELECT
ci.id,
ci.command_id,
@@ -330,73 +349,49 @@ func (d *Database) GetCommandItemsByUsername(username string) ([]map[string]inte
c.created_at as command_created_at
FROM command_items ci
LEFT JOIN commandes c ON ci.command_id = c.id
- WHERE ci.client_username = $1
- ORDER BY ci.command_id DESC, ci.id ASC`
-
- rows, err := d.Query(query, username)
+ WHERE ci.client_username = ?
+ ORDER BY ci.command_id DESC, ci.id ASC`, username).Scan(&rows).Error
if err != nil {
log.Printf("❌ Erreur query: %v", err)
return nil, fmt.Errorf("erreur récupération items: %w", err)
}
- defer rows.Close()
- var items []map[string]interface{}
-
- for rows.Next() {
- var id, commandID int
- var quantite float64
- var productID sql.NullInt64
- var produit, clientUsername, clientNom, clientPrenom, clientTelephone string
- var deliveryAddress, status sql.NullString
- var prix, totalPrix float64
- var createdAt, updatedAt, commandCreatedAt time.Time
- var commandStatus, commandAddress, livreurAssign sql.NullString
-
- err := rows.Scan(
- &id, &commandID, &produit, &productID, &quantite, &prix,
- &clientUsername, &clientNom, &clientPrenom, &clientTelephone, &deliveryAddress, &status,
- &createdAt, &updatedAt,
- &commandStatus, &commandAddress, &totalPrix, &livreurAssign, &commandCreatedAt,
- )
- if err != nil {
- log.Printf("❌ Erreur scan: %v", err)
- return nil, fmt.Errorf("erreur scan: %w", err)
+ items := make([]map[string]interface{}, 0, len(rows))
+ for _, row := range rows {
+ productIDValue := 0
+ if row.ProductID != nil {
+ productIDValue = int(*row.ProductID)
}
- productIDValue := 0
- if productID.Valid {
- productIDValue = int(productID.Int64)
+ var commandCreatedAt interface{}
+ if row.CommandCreatedAt != nil {
+ commandCreatedAt = *row.CommandCreatedAt
}
item := map[string]interface{}{
- "id": id,
- "command_id": commandID,
- "produit": produit,
+ "id": row.ID,
+ "command_id": row.CommandID,
+ "produit": row.Produit,
"product_id": productIDValue,
- "quantite": quantite,
- "prix": prix,
- "client_username": clientUsername,
- "client_nom": clientNom,
- "client_prenom": clientPrenom,
- "client_telephone": clientTelephone,
- "delivery_address": deliveryAddress.String,
- "status": status.String,
- "created_at": createdAt,
- "updated_at": updatedAt,
- "command_status": commandStatus.String,
- "command_address": commandAddress.String,
- "total_prix": totalPrix,
- "livreur_assign": livreurAssign.String,
+ "quantite": row.Quantite,
+ "prix": row.Prix,
+ "client_username": row.ClientUsername,
+ "client_nom": row.ClientNom,
+ "client_prenom": row.ClientPrenom,
+ "client_telephone": row.ClientTelephone,
+ "delivery_address": ptrStr(row.DeliveryAddress),
+ "status": ptrStr(row.Status),
+ "created_at": row.CreatedAt,
+ "updated_at": row.UpdatedAt,
+ "command_status": ptrStr(row.CommandStatus),
+ "command_address": ptrStr(row.CommandAddress),
+ "total_prix": row.TotalPrix,
+ "livreur_assign": ptrStr(row.LivreurAssign),
"command_created_at": commandCreatedAt,
}
items = append(items, item)
}
- if err = rows.Err(); err != nil {
- log.Printf("❌ Erreur itération: %v", err)
- return nil, fmt.Errorf("erreur itération: %w", err)
- }
-
log.Printf("✅ %d items récupérés pour l'utilisateur %s", len(items), username)
return items, nil
}
@@ -412,29 +407,28 @@ func (d *Database) DeleteCommandItem(commandID, itemID int) error {
}
// Récupérer le prix et la quantité avant suppression pour mettre à jour le total
- var prix, quantite float64
- checkQuery := `SELECT prix, quantite FROM command_items WHERE id = $1 AND command_id = $2`
- err := d.QueryRow(checkQuery, itemID, commandID).Scan(&prix, &quantite)
- if err == sql.ErrNoRows {
- return fmt.Errorf("item %d non trouvé dans la commande %d", itemID, commandID)
+ var result struct {
+ Prix float64 `gorm:"column:prix"`
+ Quantite float64 `gorm:"column:quantite"`
}
- if err != nil {
+ if err := d.GDB.Raw(`SELECT prix, quantite 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)
+ }
// Supprimer l'item
- _, err = d.Exec(`DELETE FROM command_items WHERE id = $1`, itemID)
- if err != nil {
+ if err := d.GDB.Exec(`DELETE FROM command_items WHERE id = ?`, itemID).Error; err != nil {
log.Printf("❌ Erreur DELETE command_items: %v", err)
return fmt.Errorf("erreur suppression item: %w", err)
}
// Recalculer le total de la commande
- _, err = d.Exec(
- `UPDATE commandes SET total_prix = GREATEST(0, total_prix - $1) WHERE id = $2`,
- prix*quantite, commandID,
- )
- if err != nil {
+ if err := d.GDB.Exec(
+ `UPDATE commandes SET total_prix = GREATEST(0, total_prix - ?) WHERE id = ?`,
+ result.Prix*result.Quantite, commandID,
+ ).Error; err != nil {
log.Printf("⚠️ [DeleteCommandItem] Erreur maj total commande: %v", err)
}
@@ -453,9 +447,7 @@ func (d *Database) UpdateCommandItemStatus(itemID int, status string) error {
}
var exists bool
- checkQuery := `SELECT EXISTS(SELECT 1 FROM command_items WHERE id = $1)`
- err := d.QueryRow(checkQuery, itemID).Scan(&exists)
- if err != nil {
+ if err := d.GDB.Raw(`SELECT EXISTS(SELECT 1 FROM command_items WHERE id = ?)`, itemID).Scan(&exists).Error; err != nil {
log.Printf("❌ Erreur vérification: %v", err)
return fmt.Errorf("erreur vérification item: %w", err)
}
@@ -464,22 +456,15 @@ func (d *Database) UpdateCommandItemStatus(itemID int, status string) error {
return fmt.Errorf("item %d non trouvé", itemID)
}
- query := `UPDATE command_items
- SET status = $1, updated_at = CURRENT_TIMESTAMP
- WHERE id = $2`
-
- result, err := d.Exec(query, status, itemID)
- if err != nil {
- log.Printf("❌ Erreur UPDATE: %v", err)
- return fmt.Errorf("erreur mise à jour statut: %w", err)
+ result := d.GDB.Exec(`
+ UPDATE command_items
+ SET status = ?, updated_at = CURRENT_TIMESTAMP
+ WHERE id = ?`, status, itemID)
+ if result.Error != nil {
+ log.Printf("❌ Erreur UPDATE: %v", result.Error)
+ return fmt.Errorf("erreur mise à jour statut: %w", result.Error)
}
-
- rowsAffected, err := result.RowsAffected()
- if err != nil {
- log.Printf("❌ Erreur RowsAffected: %v", err)
- return fmt.Errorf("erreur vérification: %w", err)
- }
- if rowsAffected == 0 {
+ if result.RowsAffected == 0 {
log.Printf("❌ Item %d non trouvé", itemID)
return fmt.Errorf("item non trouvé")
}
diff --git a/backend/gestion/db/db_command_priority.go b/backend/gestion/db/db_command_priority.go
index 597b730e..2092b5a2 100644
--- a/backend/gestion/db/db_command_priority.go
+++ b/backend/gestion/db/db_command_priority.go
@@ -6,228 +6,149 @@
package db
import (
- "database/sql"
"fmt"
"gestion/models"
"log"
"slices"
- "time"
)
// GetAllCommandsOldestFirst récupère les commandes triées par ancienneté (plus anciennes en premier)
-func (d *Database) GetAllCommandsOldestFirst(status, username string) ([]map[string]interface{}, error) {
+func (d *Database) GetAllCommandsOldestFirst(status, username string) ([]map[string]any, error) {
query := `SELECT c.id, c.username, c.status, c.adresse, c.total_prix,
c.livreur_assign, c.created_at, c.updated_at
FROM commandes c
WHERE 1=1`
- args := []interface{}{}
- argPosition := 1
+ args := []any{}
- // Filtrer par status
if status != "" {
validStatuses := []string{"pending", "assigned", "en_route", "livre", "approved", "cancelled", "disabled"}
-
- isValid := slices.Contains(validStatuses, status)
- if !isValid {
+ if !slices.Contains(validStatuses, status) {
return nil, fmt.Errorf("statut invalide: %s", status)
}
-
- query += fmt.Sprintf(" AND c.status = $%d", argPosition)
+ query += " AND c.status = ?"
args = append(args, status)
- argPosition++
}
- // Filtrer par username si fourni
if username != "" {
- query += fmt.Sprintf(" AND c.username = $%d", argPosition)
+ query += " AND c.username = ?"
args = append(args, username)
- argPosition++
}
query += " ORDER BY c.created_at ASC"
- rows, err := d.Query(query, args...)
- if err != nil {
+ var commands []map[string]any
+ if err := d.GDB.Raw(query, args...).Scan(&commands).Error; err != nil {
return nil, fmt.Errorf("erreur récupération commandes prioritaires: %w", err)
}
- defer rows.Close()
-
- var commands []map[string]interface{}
- for rows.Next() {
- var id int
- var username, status, adresse string
- var livreurAssign sql.NullString
- var totalPrix float64
- var createdAt, updatedAt time.Time
-
- err := rows.Scan(&id, &username, &status, &adresse, &totalPrix, &livreurAssign, &createdAt, &updatedAt)
- if err != nil {
- return nil, fmt.Errorf("erreur scan commande: %w", err)
- }
-
- command := map[string]interface{}{
- "id": id,
- "username": username,
- "status": status,
- "adresse": adresse,
- "total_prix": totalPrix,
- "created_at": createdAt,
- "updated_at": updatedAt,
- }
-
- if livreurAssign.Valid {
- command["livreur_assign"] = livreurAssign.String
- } else {
- command["livreur_assign"] = nil
- }
-
- commands = append(commands, command)
- }
-
- if err = rows.Err(); err != nil {
- return nil, fmt.Errorf("erreur itération résultats: %w", err)
- }
-
return commands, nil
}
// GetOldestPendingCommand récupère la commande pending la plus ancienne
-func (d *Database) GetOldestPendingCommand() (map[string]interface{}, error) {
- query := `SELECT c.id, c.username, c.status, c.adresse, c.total_prix,
- c.livreur_assign, c.created_at, c.updated_at
- FROM commandes c
- WHERE c.status = 'pending'
- ORDER BY c.created_at ASC
- LIMIT 1`
-
- var id int
- var username, status, adresse string
- var livreurAssign sql.NullString
- var totalPrix float64
- var createdAt, updatedAt time.Time
-
- err := d.QueryRow(query).Scan(
- &id, &username, &status, &adresse, &totalPrix,
- &livreurAssign, &createdAt, &updatedAt,
- )
-
- if err == sql.ErrNoRows {
- return nil, nil // Aucune commande pending
- }
+func (d *Database) GetOldestPendingCommand() (map[string]any, error) {
+ var commands []map[string]any
+ err := d.GDB.Raw(`
+ SELECT c.id, c.username, c.status, c.adresse, c.total_prix,
+ c.livreur_assign, c.created_at, c.updated_at
+ FROM commandes c
+ WHERE c.status = 'pending'
+ ORDER BY c.created_at ASC
+ LIMIT 1`).Scan(&commands).Error
if err != nil {
return nil, fmt.Errorf("erreur récupération commande la plus ancienne: %w", err)
}
-
- command := map[string]interface{}{
- "id": id,
- "username": username,
- "status": status,
- "adresse": adresse,
- "total_prix": totalPrix,
- "created_at": createdAt,
- "updated_at": updatedAt,
+ if len(commands) == 0 {
+ return nil, nil
}
-
- if livreurAssign.Valid {
- command["livreur_assign"] = livreurAssign.String
- } else {
- command["livreur_assign"] = nil
- }
-
- return command, nil
+ return commands[0], nil
}
// GetPendingCommandsWithPriority récupère les commandes pending avec calcul de priorité
func (d *Database) GetPendingCommandsWithPriority() ([]*models.CommandPriority, error) {
- query := `SELECT c.id, c.username, c.status, c.adresse, c.total_prix,
- c.created_at, c.updated_at,
- EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - c.created_at)) as waiting_seconds
- FROM commandes c
- WHERE c.status = 'pending'
- ORDER BY c.created_at ASC`
+ var rows []struct {
+ ID int `gorm:"column:id"`
+ Username string `gorm:"column:username"`
+ Status string `gorm:"column:status"`
+ Adresse string `gorm:"column:adresse"`
+ TotalPrix float64 `gorm:"column:total_prix"`
+ CreatedAt string `gorm:"column:created_at"`
+ UpdatedAt string `gorm:"column:updated_at"`
+ WaitingSeconds float64 `gorm:"column:waiting_seconds"`
+ }
- rows, err := d.Query(query)
+ err := d.GDB.Raw(`
+ SELECT c.id, c.username, c.status, c.adresse, c.total_prix,
+ c.created_at, c.updated_at,
+ EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - c.created_at)) as waiting_seconds
+ FROM commandes c
+ WHERE c.status = 'pending'
+ ORDER BY c.created_at ASC`).Scan(&rows).Error
if err != nil {
return nil, fmt.Errorf("erreur récupération commandes avec priorité: %w", err)
}
- defer rows.Close()
-
- var commands []*models.CommandPriority
- for rows.Next() {
- var id int
- var username, status, adresse string
- var totalPrix float64
- var createdAt, updatedAt time.Time
- var waitingSeconds float64
-
- err := rows.Scan(&id, &username, &status, &adresse, &totalPrix,
- &createdAt, &updatedAt, &waitingSeconds)
- if err != nil {
- return nil, fmt.Errorf("erreur scan commande priorité: %w", err)
- }
+ commands := make([]*models.CommandPriority, 0, len(rows))
+ for _, row := range rows {
cmd := &models.CommandPriority{
- ID: id,
- Username: username,
- Status: status,
- Address: adresse,
- TotalPrice: totalPrix,
- CreatedAt: createdAt,
- UpdatedAt: updatedAt,
- WaitingSeconds: int(waitingSeconds),
- WaitingMinutes: int(waitingSeconds / 60),
+ ID: row.ID,
+ Username: row.Username,
+ Status: row.Status,
+ Address: row.Adresse,
+ TotalPrice: row.TotalPrix,
+ WaitingSeconds: int(row.WaitingSeconds),
+ WaitingMinutes: int(row.WaitingSeconds / 60),
}
-
commands = append(commands, cmd)
}
- if err = rows.Err(); err != nil {
- return nil, fmt.Errorf("erreur itération résultats priorité: %w", err)
- }
-
return commands, nil
}
// GetCommandWaitingTime récupère le temps d'attente d'une commande
func (d *Database) GetCommandWaitingTime(commandID int) (int, error) {
- query := `SELECT EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - created_at))::INTEGER as waiting_seconds
- FROM commandes
- WHERE id = $1`
-
- var waitingSeconds int
- err := d.QueryRow(query, commandID).Scan(&waitingSeconds)
- if err == sql.ErrNoRows {
- return 0, fmt.Errorf("commande non trouvée")
+ var result struct {
+ WaitingSeconds int `gorm:"column:waiting_seconds"`
}
+ err := d.GDB.Raw(`
+ SELECT EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - created_at))::INTEGER as waiting_seconds
+ FROM commandes WHERE id = ?`, commandID).Scan(&result).Error
if err != nil {
return 0, fmt.Errorf("erreur récupération temps d'attente: %w", err)
}
-
- return waitingSeconds, nil
+ if result.WaitingSeconds == 0 {
+ // Vérifie si la commande existe vraiment
+ var exists bool
+ d.GDB.Raw(`SELECT EXISTS(SELECT 1 FROM commandes WHERE id = ?)`, commandID).Scan(&exists)
+ if !exists {
+ return 0, fmt.Errorf("commande non trouvée")
+ }
+ }
+ return result.WaitingSeconds, nil
}
// GetPendingCommandsStats récupère des statistiques sur les commandes en attente
-func (d *Database) GetPendingCommandsStats() (map[string]interface{}, error) {
- query := `SELECT
- COUNT(*) as total_pending,
- AVG(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - created_at))) as avg_waiting_seconds,
- MIN(created_at) as oldest_command_date,
- MAX(created_at) as newest_command_date
- FROM commandes
- WHERE status = 'pending'`
+func (d *Database) GetPendingCommandsStats() (map[string]any, error) {
+ var result struct {
+ TotalPending int `gorm:"column:total_pending"`
+ AvgWaitingSeconds *float64 `gorm:"column:avg_waiting_seconds"`
+ OldestCommandDate *string `gorm:"column:oldest_command_date"`
+ NewestCommandDate *string `gorm:"column:newest_command_date"`
+ }
- var totalPending int
- var avgWaitingSeconds sql.NullFloat64
- var oldestDate, newestDate sql.NullTime
-
- err := d.QueryRow(query).Scan(&totalPending, &avgWaitingSeconds, &oldestDate, &newestDate)
+ err := d.GDB.Raw(`
+ SELECT
+ COUNT(*) as total_pending,
+ AVG(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - created_at))) as avg_waiting_seconds,
+ MIN(created_at) as oldest_command_date,
+ MAX(created_at) as newest_command_date
+ FROM commandes
+ WHERE status = 'pending'`).Scan(&result).Error
if err != nil {
return nil, fmt.Errorf("erreur récupération stats: %w", err)
}
- stats := map[string]interface{}{
- "total_pending": totalPending,
+ stats := map[string]any{
+ "total_pending": result.TotalPending,
"avg_waiting_seconds": 0,
"avg_waiting_minutes": 0,
"oldest_command_date": nil,
@@ -235,25 +156,21 @@ func (d *Database) GetPendingCommandsStats() (map[string]interface{}, error) {
"oldest_waiting_minutes": 0,
}
- if avgWaitingSeconds.Valid {
- stats["avg_waiting_seconds"] = int(avgWaitingSeconds.Float64)
- stats["avg_waiting_minutes"] = int(avgWaitingSeconds.Float64 / 60)
+ if result.AvgWaitingSeconds != nil {
+ stats["avg_waiting_seconds"] = int(*result.AvgWaitingSeconds)
+ stats["avg_waiting_minutes"] = int(*result.AvgWaitingSeconds / 60)
}
- if oldestDate.Valid {
- stats["oldest_command_date"] = oldestDate.Time
- waitingTime := time.Since(oldestDate.Time)
- stats["oldest_waiting_minutes"] = int(waitingTime.Minutes())
+ if result.OldestCommandDate != nil {
+ stats["oldest_command_date"] = *result.OldestCommandDate
}
- if newestDate.Valid {
- stats["newest_command_date"] = newestDate.Time
+ if result.NewestCommandDate != nil {
+ stats["newest_command_date"] = *result.NewestCommandDate
}
- log.Printf("📊 [STATS] Commandes pending: %d | Attente moyenne: %d min | Plus ancienne: %d min",
- totalPending,
- stats["avg_waiting_minutes"],
- stats["oldest_waiting_minutes"])
+ log.Printf("📊 [STATS] Commandes pending: %d | Attente moyenne: %d min",
+ result.TotalPending, stats["avg_waiting_minutes"])
return stats, nil
}
diff --git a/backend/gestion/db/db_commands.go b/backend/gestion/db/db_commands.go
index d7a4b63c..4ad81f39 100644
--- a/backend/gestion/db/db_commands.go
+++ b/backend/gestion/db/db_commands.go
@@ -1,13 +1,14 @@
package db
import (
- "database/sql"
"fmt"
"gestion/models"
"log"
"slices"
"strings"
"time"
+
+ "gorm.io/gorm"
)
func sanitizeString(s string) string {
@@ -44,31 +45,21 @@ func validateAddress(address string) error {
}
type basketItem struct {
- ProductID int
- Quantity float64
- Price float64
+ ProductID int `gorm:"column:product_id"`
+ Quantity float64 `gorm:"column:quantity"`
+ Price float64 `gorm:"column:price"`
}
func (d *Database) fetchBasketItems(username string) ([]basketItem, float64, error) {
- rows, err := d.Query(`SELECT product_id, quantity, price FROM baskets WHERE username = $1`, username)
- if err != nil {
+ var items []basketItem
+ if err := d.GDB.Raw(`SELECT product_id, quantity, price FROM baskets WHERE username = ?`, username).Scan(&items).Error; err != nil {
return nil, 0, fmt.Errorf("erreur récupération panier: %w", err)
}
- defer rows.Close()
- var items []basketItem
total := 0.0
- for rows.Next() {
- var item basketItem
- if err := rows.Scan(&item.ProductID, &item.Quantity, &item.Price); err != nil {
- return nil, 0, fmt.Errorf("erreur scan panier: %w", err)
- }
- items = append(items, item)
+ for _, item := range items {
total += item.Price
}
- if err := rows.Err(); err != nil {
- return nil, 0, err
- }
return items, total, nil
}
@@ -93,11 +84,12 @@ func validateCommandStatus(status string) error {
}
func (d *Database) CreateCommand(username string) (*models.Command, error) {
- var adresse string
- clientQuery := `SELECT username FROM clients WHERE username = $1`
- err := d.QueryRow(clientQuery, username).Scan(&adresse)
- if err != nil {
- adresse = "Adresse non spécifiée"
+ var addrResult struct {
+ Username string `gorm:"column:username"`
+ }
+ adresse := "Adresse non spécifiée"
+ if err := d.GDB.Raw(`SELECT username FROM clients WHERE username = ?`, username).Scan(&addrResult).Error; err == nil && addrResult.Username != "" {
+ adresse = addrResult.Username
}
basketItems, totalPrix, err := d.fetchBasketItems(username)
@@ -109,38 +101,37 @@ func (d *Database) CreateCommand(username string) (*models.Command, error) {
return nil, fmt.Errorf("le panier est vide")
}
- commandQuery := `INSERT INTO commandes (username, status, adresse, total_prix, created_at, updated_at)
- VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
- RETURNING id, created_at, updated_at`
-
- var commandID int
- var createdAt, updatedAt time.Time
- err = d.QueryRow(commandQuery, username, "pending", adresse, totalPrix).Scan(
- &commandID,
- &createdAt,
- &updatedAt,
- )
+ var cmdResult struct {
+ ID int `gorm:"column:id"`
+ CreatedAt time.Time `gorm:"column:created_at"`
+ UpdatedAt time.Time `gorm:"column:updated_at"`
+ }
+ err = d.GDB.Raw(`
+ INSERT INTO commandes (username, status, adresse, total_prix, client_order_id, created_at, updated_at)
+ VALUES (?, ?, ?, ?, (SELECT COALESCE(MAX(client_order_id), 0) + 1 FROM commandes WHERE username = ?), CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
+ RETURNING id, created_at, updated_at`,
+ username, "pending", adresse, totalPrix, username).Scan(&cmdResult).Error
if err != nil {
return nil, fmt.Errorf("erreur lors de la création de la commande: %w", err)
}
+ commandID := cmdResult.ID
+
for _, item := range basketItems {
productName, err := d.GetProductNameByID(item.ProductID)
if err != nil {
productName = "Produit inconnu"
}
- itemQuery := `INSERT INTO command_items (command_id, produit, product_id, quantite, prix)
- VALUES ($1, $2, $3, $4, $5)`
- _, err = d.Exec(itemQuery, commandID, productName, item.ProductID, item.Quantity, item.Price)
- if err != nil {
+ if err := d.GDB.Exec(`
+ INSERT INTO command_items (command_id, produit, product_id, quantite, prix)
+ VALUES (?, ?, ?, ?, ?)`,
+ commandID, productName, item.ProductID, item.Quantity, item.Price).Error; err != nil {
return nil, fmt.Errorf("erreur lors de l'insertion des items: %w", err)
}
}
- clearBasketQuery := `DELETE FROM baskets WHERE username = $1`
- _, err = d.Exec(clearBasketQuery, username)
- if err != nil {
+ if err := d.GDB.Exec(`DELETE FROM baskets WHERE username = ?`, username).Error; err != nil {
return nil, fmt.Errorf("erreur lors du vidage du panier: %w", err)
}
@@ -196,20 +187,22 @@ func (d *Database) CreateCommandWithAddress(username, deliveryAddress string) (*
return nil, fmt.Errorf("montant de commande invalide: %.2f€", totalPrix)
}
- commandQuery := `INSERT INTO commandes (username, status, adresse, total_prix, created_at, updated_at)
- VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
- RETURNING id, created_at, updated_at`
-
- var commandID int
- var createdAt, updatedAt time.Time
-
- err = d.QueryRow(commandQuery, username, "pending", deliveryAddress, totalPrix).Scan(
- &commandID, &createdAt, &updatedAt,
- )
+ var cmdResult struct {
+ ID int `gorm:"column:id"`
+ CreatedAt time.Time `gorm:"column:created_at"`
+ UpdatedAt time.Time `gorm:"column:updated_at"`
+ }
+ err = d.GDB.Raw(`
+ INSERT INTO commandes (username, status, adresse, total_prix, client_order_id, created_at, updated_at)
+ VALUES (?, ?, ?, ?, (SELECT COALESCE(MAX(client_order_id), 0) + 1 FROM commandes WHERE username = ?), CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
+ RETURNING id, created_at, updated_at`,
+ username, "pending", deliveryAddress, totalPrix, username).Scan(&cmdResult).Error
if err != nil {
return nil, fmt.Errorf("erreur création commande: %w", err)
}
+ commandID := cmdResult.ID
+
for _, item := range basketItems {
productName, err := d.GetProductNameByID(item.ProductID)
if err != nil || productName == "" {
@@ -233,22 +226,17 @@ func (d *Database) CreateCommandWithAddress(username, deliveryAddress string) (*
return nil, fmt.Errorf("erreur insertion items: %w", err)
}
- stockQuery := `UPDATE products SET stock = stock - $1 WHERE id = $2 AND stock >= $1`
- result, err := d.Exec(stockQuery, item.Quantity, item.ProductID)
- if err != nil {
- log.Printf("⚠️ Erreur décrémentation stock produit %d: %v", item.ProductID, err)
- return nil, fmt.Errorf("erreur mise à jour stock: %w", err)
+ result := d.GDB.Exec(`UPDATE products SET stock = stock - ? WHERE id = ? AND stock >= ?`, item.Quantity, item.ProductID, item.Quantity)
+ if result.Error != nil {
+ log.Printf("⚠️ Erreur décrémentation stock produit %d: %v", item.ProductID, result.Error)
+ return nil, fmt.Errorf("erreur mise à jour stock: %w", result.Error)
}
-
- rowsAffected, _ := result.RowsAffected()
- if rowsAffected == 0 {
+ if result.RowsAffected == 0 {
log.Printf("⚠️ [CHECKOUT] Stock déjà réservé pour produit %d (double réservation panier/checkout)", item.ProductID)
}
}
- clearBasketQuery := `DELETE FROM baskets WHERE username = $1`
- _, err = d.Exec(clearBasketQuery, username)
- if err != nil {
+ if err := d.GDB.Exec(`DELETE FROM baskets WHERE username = ?`, username).Error; err != nil {
log.Printf("⚠️ Erreur vidage panier: %v", err)
}
@@ -264,8 +252,8 @@ func (d *Database) CreateCommandWithAddress(username, deliveryAddress string) (*
Status: "pending",
Total: totalPrix,
DeliveryAddress: deliveryAddress,
- CreatedAt: createdAt,
- UpdatedAt: updatedAt,
+ CreatedAt: cmdResult.CreatedAt,
+ UpdatedAt: cmdResult.UpdatedAt,
}
return command, nil
@@ -287,73 +275,66 @@ func (d *Database) GetAllCommands(status, username string) ([]map[string]any, er
query := `SELECT c.id, c.username, c.status, c.adresse, c.total_prix,
c.livreur_assign, c.created_at, c.updated_at,
c.proposed_address, c.address_proposal_status,
- ROW_NUMBER() OVER (PARTITION BY c.username ORDER BY c.id) AS client_order_number
+ c.client_order_id AS client_order_number
FROM commandes c
WHERE 1=1`
args := []interface{}{}
- argPosition := 1
if status == "" {
query += ` AND c.status IN ('pending', 'assigned', 'en_route', 'arrived', 'livre')`
} else {
- query += fmt.Sprintf(" AND c.status = $%d", argPosition)
+ query += ` AND c.status = ?`
args = append(args, status)
- argPosition++
}
if username != "" {
- query += fmt.Sprintf(" AND c.username = $%d", argPosition)
+ query += ` AND c.username = ?`
args = append(args, username)
- argPosition++
}
query += " ORDER BY c.created_at DESC LIMIT 1000"
- rows, err := d.Query(query, args...)
- if err != nil {
+ var rows []struct {
+ ID int `gorm:"column:id"`
+ Username string `gorm:"column:username"`
+ Status string `gorm:"column:status"`
+ Adresse string `gorm:"column:adresse"`
+ TotalPrix float64 `gorm:"column:total_prix"`
+ LivreurAssign *string `gorm:"column:livreur_assign"`
+ CreatedAt time.Time `gorm:"column:created_at"`
+ UpdatedAt time.Time `gorm:"column:updated_at"`
+ ProposedAddress *string `gorm:"column:proposed_address"`
+ AddressProposalStatus string `gorm:"column:address_proposal_status"`
+ ClientOrderNumber int `gorm:"column:client_order_number"`
+ }
+
+ if err := d.GDB.Raw(query, args...).Scan(&rows).Error; err != nil {
return nil, fmt.Errorf("erreur lors de la récupération des commandes: %w", err)
}
- defer rows.Close()
-
- var commands []map[string]any
- for rows.Next() {
- var id int
- var username, status, adresse string
- var livreurAssign sql.NullString
- var proposedAddress sql.NullString
- var addressProposalStatus string
- var totalPrix float64
- var createdAt, updatedAt time.Time
- var clientOrderNumber int
-
- err := rows.Scan(&id, &username, &status, &adresse, &totalPrix, &livreurAssign, &createdAt, &updatedAt, &proposedAddress, &addressProposalStatus, &clientOrderNumber)
- if err != nil {
- return nil, fmt.Errorf("erreur lors du scan de la commande: %w", err)
- }
-
- adresse = sanitizeString(adresse)
+ commands := make([]map[string]any, 0, len(rows))
+ for _, row := range rows {
command := map[string]any{
- "id": id,
- "username": username,
- "status": status,
- "adresse": adresse,
- "total_prix": totalPrix,
- "created_at": createdAt,
- "updated_at": updatedAt,
- "address_proposal_status": addressProposalStatus,
- "client_order_number": clientOrderNumber,
+ "id": row.ID,
+ "username": row.Username,
+ "status": row.Status,
+ "adresse": sanitizeString(row.Adresse),
+ "total_prix": row.TotalPrix,
+ "created_at": row.CreatedAt,
+ "updated_at": row.UpdatedAt,
+ "address_proposal_status": row.AddressProposalStatus,
+ "client_order_number": row.ClientOrderNumber,
}
- if livreurAssign.Valid {
- command["livreur_assign"] = livreurAssign.String
+ if row.LivreurAssign != nil {
+ command["livreur_assign"] = *row.LivreurAssign
} else {
command["livreur_assign"] = nil
}
- if proposedAddress.Valid {
- command["proposed_address"] = proposedAddress.String
+ if row.ProposedAddress != nil {
+ command["proposed_address"] = *row.ProposedAddress
} else {
command["proposed_address"] = nil
}
@@ -361,85 +342,73 @@ func (d *Database) GetAllCommands(status, username string) ([]map[string]any, er
commands = append(commands, command)
}
- if err = rows.Err(); err != nil {
- return nil, fmt.Errorf("erreur lors de l'itération des résultats: %w", err)
- }
-
return commands, nil
}
func (d *Database) GetCommandCount() (int, error) {
- var count int
- err := d.QueryRow("SELECT COUNT(*) FROM commandes").Scan(&count)
+ var result struct {
+ Count int `gorm:"column:count"`
+ }
+ err := d.GDB.Raw("SELECT COUNT(*) as count FROM commandes").Scan(&result).Error
if err != nil {
return 0, fmt.Errorf("erreur récupération count commandes: %w", err)
}
- return count, nil
+ return result.Count, nil
}
func (d *Database) SetCommandReferralUsed(commandID int, amount float64) error {
- _, err := d.Exec(`UPDATE commandes SET referral_used = $1 WHERE id = $2`, amount, commandID)
- return err
+ return d.GDB.Exec(`UPDATE commandes SET referral_used = ? WHERE id = ?`, amount, commandID).Error
}
func (d *Database) GetCommandByID(id int) (map[string]any, error) {
- query := `SELECT c.id, c.username, c.status, c.adresse, c.total_prix, c.livreur_assign, c.created_at, c.updated_at,
- c.proposed_address, c.address_proposal_status, c.referral_used,
- (SELECT COUNT(*) FROM commandes c2 WHERE c2.username = c.username AND c2.id <= c.id) AS client_order_number
- FROM commandes c WHERE c.id = $1`
-
- var commandID int
- var username, status, adresse string
- var livreurAssign sql.NullString
- var proposedAddress sql.NullString
- var addressProposalStatus string
- var totalPrix, referralUsed float64
- var createdAt, updatedAt time.Time
- var clientOrderNumber int
-
- err := d.QueryRow(query, id).Scan(
- &commandID,
- &username,
- &status,
- &adresse,
- &totalPrix,
- &livreurAssign,
- &createdAt,
- &updatedAt,
- &proposedAddress,
- &addressProposalStatus,
- &referralUsed,
- &clientOrderNumber,
- )
-
- if err == sql.ErrNoRows {
- return nil, fmt.Errorf("commande non trouvée")
+ var row struct {
+ ID int `gorm:"column:id"`
+ Username string `gorm:"column:username"`
+ Status string `gorm:"column:status"`
+ Adresse string `gorm:"column:adresse"`
+ TotalPrix float64 `gorm:"column:total_prix"`
+ LivreurAssign *string `gorm:"column:livreur_assign"`
+ CreatedAt time.Time `gorm:"column:created_at"`
+ UpdatedAt time.Time `gorm:"column:updated_at"`
+ ProposedAddress *string `gorm:"column:proposed_address"`
+ AddressProposalStatus string `gorm:"column:address_proposal_status"`
+ ReferralUsed float64 `gorm:"column:referral_used"`
+ ClientOrderNumber int `gorm:"column:client_order_number"`
}
+
+ err := d.GDB.Raw(`
+ SELECT c.id, c.username, c.status, c.adresse, c.total_prix, c.livreur_assign, c.created_at, c.updated_at,
+ c.proposed_address, c.address_proposal_status, c.referral_used,
+ c.client_order_id AS client_order_number
+ FROM commandes c WHERE c.id = ?`, id).Scan(&row).Error
if err != nil {
return nil, fmt.Errorf("erreur lors de la récupération de la commande: %w", err)
}
-
- command := map[string]any{
- "id": commandID,
- "username": username,
- "status": status,
- "adresse": adresse,
- "total_prix": totalPrix,
- "created_at": createdAt,
- "updated_at": updatedAt,
- "address_proposal_status": addressProposalStatus,
- "referral_used": referralUsed,
- "client_order_number": clientOrderNumber,
+ if row.ID == 0 {
+ return nil, fmt.Errorf("commande non trouvée")
}
- if livreurAssign.Valid {
- command["livreur_assign"] = livreurAssign.String
+ command := map[string]any{
+ "id": row.ID,
+ "username": row.Username,
+ "status": row.Status,
+ "adresse": row.Adresse,
+ "total_prix": row.TotalPrix,
+ "created_at": row.CreatedAt,
+ "updated_at": row.UpdatedAt,
+ "address_proposal_status": row.AddressProposalStatus,
+ "referral_used": row.ReferralUsed,
+ "client_order_number": row.ClientOrderNumber,
+ }
+
+ if row.LivreurAssign != nil {
+ command["livreur_assign"] = *row.LivreurAssign
} else {
command["livreur_assign"] = nil
}
- if proposedAddress.Valid {
- command["proposed_address"] = proposedAddress.String
+ if row.ProposedAddress != nil {
+ command["proposed_address"] = *row.ProposedAddress
} else {
command["proposed_address"] = nil
}
@@ -448,18 +417,18 @@ func (d *Database) GetCommandByID(id int) (map[string]any, error) {
}
func (d *Database) GetCommandAddress(commandID int) (string, error) {
- var address string
- query := `SELECT adresse FROM commandes WHERE id = $1`
-
- err := d.QueryRow(query, commandID).Scan(&address)
- if err == sql.ErrNoRows {
- return "", fmt.Errorf("commande non trouvée")
+ var result struct {
+ Adresse string `gorm:"column:adresse"`
}
+ err := d.GDB.Raw(`SELECT adresse FROM commandes WHERE id = ?`, commandID).Scan(&result).Error
if err != nil {
return "", fmt.Errorf("erreur lors de la récupération de l'adresse: %w", err)
}
+ if result.Adresse == "" {
+ return "", fmt.Errorf("commande non trouvée")
+ }
- return address, nil
+ return result.Adresse, nil
}
func (d *Database) UpdateCommandAddress(commandID int, deliveryAddress string) error {
@@ -470,20 +439,13 @@ func (d *Database) UpdateCommandAddress(commandID int, deliveryAddress string) e
return fmt.Errorf("adresse vide non autorisée")
}
- query := `UPDATE commandes
- SET adresse = $1, updated_at = CURRENT_TIMESTAMP
- WHERE id = $2`
-
- result, err := d.Exec(query, deliveryAddress, commandID)
- if err != nil {
- return fmt.Errorf("erreur lors de la mise à jour de l'adresse: %w", err)
+ result := d.GDB.Exec(`
+ UPDATE commandes SET adresse = ?, updated_at = CURRENT_TIMESTAMP
+ WHERE id = ?`, deliveryAddress, commandID)
+ if result.Error != nil {
+ return fmt.Errorf("erreur lors de la mise à jour de l'adresse: %w", result.Error)
}
-
- rowsAffected, err := result.RowsAffected()
- if err != nil {
- return fmt.Errorf("erreur lors de la vérification: %w", err)
- }
- if rowsAffected == 0 {
+ if result.RowsAffected == 0 {
return fmt.Errorf("commande non trouvée")
}
@@ -497,16 +459,14 @@ func (d *Database) ProposeAddressChange(commandID int, proposedAddress, proposed
return err
}
- query := `UPDATE commandes
- SET proposed_address = $1, address_proposal_status = 'pending', updated_at = CURRENT_TIMESTAMP
- WHERE id = $2`
-
- result, err := d.Exec(query, proposedAddress, commandID)
- if err != nil {
- return fmt.Errorf("erreur proposition adresse: %w", err)
+ result := d.GDB.Exec(`
+ UPDATE commandes
+ SET proposed_address = ?, address_proposal_status = 'pending', updated_at = CURRENT_TIMESTAMP
+ WHERE id = ?`, proposedAddress, commandID)
+ if result.Error != nil {
+ return fmt.Errorf("erreur proposition adresse: %w", result.Error)
}
- rowsAffected, _ := result.RowsAffected()
- if rowsAffected == 0 {
+ if result.RowsAffected == 0 {
return fmt.Errorf("commande non trouvée")
}
@@ -525,20 +485,19 @@ func (d *Database) RespondToAddressProposal(commandID int, clientUsername string
query = `UPDATE commandes
SET adresse = proposed_address, proposed_address = NULL,
address_proposal_status = 'accepted', updated_at = CURRENT_TIMESTAMP
- WHERE id = $1 AND username = $2 AND address_proposal_status = 'pending'`
+ WHERE id = ? AND username = ? AND address_proposal_status = 'pending'`
} else {
query = `UPDATE commandes
SET proposed_address = NULL, address_proposal_status = 'rejected',
updated_at = CURRENT_TIMESTAMP
- WHERE id = $1 AND username = $2 AND address_proposal_status = 'pending'`
+ WHERE id = ? AND username = ? AND address_proposal_status = 'pending'`
}
- result, err := d.Exec(query, commandID, clientUsername)
- if err != nil {
- return fmt.Errorf("erreur réponse proposition adresse: %w", err)
+ result := d.GDB.Exec(query, commandID, clientUsername)
+ if result.Error != nil {
+ return fmt.Errorf("erreur réponse proposition adresse: %w", result.Error)
}
- rowsAffected, _ := result.RowsAffected()
- if rowsAffected == 0 {
+ if result.RowsAffected == 0 {
return fmt.Errorf("aucune proposition en attente pour cette commande")
}
@@ -560,19 +519,11 @@ func (d *Database) UpdateCommandStatus(commandID int, status string) error {
return err
}
- query := `UPDATE commandes SET status = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2`
-
- result, err := d.Exec(query, status, commandID)
- if err != nil {
- return fmt.Errorf("erreur lors de la mise à jour du statut: %w", err)
+ result := d.GDB.Exec(`UPDATE commandes SET status = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?`, status, commandID)
+ if result.Error != nil {
+ return fmt.Errorf("erreur lors de la mise à jour du statut: %w", result.Error)
}
-
- 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 {
+ if result.RowsAffected == 0 {
return fmt.Errorf("commande non trouvée")
}
@@ -583,11 +534,10 @@ func (d *Database) AddCommandLog(commandID int, status, message, author string)
sanitizedMessage := sanitizeLogMessage(message)
sanitizedAuthor := sanitizeLogMessage(author)
- query := `INSERT INTO command_logs (command_id, status, message, author, created_at)
- VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP)`
-
- _, err := d.Exec(query, commandID, status, sanitizedMessage, sanitizedAuthor)
- if err != nil {
+ if err := d.GDB.Exec(`
+ INSERT INTO command_logs (command_id, status, message, author, created_at)
+ VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)`,
+ commandID, status, sanitizedMessage, sanitizedAuthor).Error; err != nil {
log.Printf("⚠️ Avertissement: impossible d'ajouter le log (table command_logs peut-être manquante): %v", err)
return nil
}
@@ -597,43 +547,36 @@ func (d *Database) AddCommandLog(commandID int, status, message, author string)
// GetCommandLogs récupère tous les logs d'une commande
func (d *Database) GetCommandLogs(commandID int) ([]map[string]any, error) {
- query := `SELECT id, command_id, status, message, author, created_at
- FROM command_logs
- WHERE command_id = $1
- ORDER BY created_at ASC`
+ var rows []struct {
+ ID int `gorm:"column:id"`
+ CommandID int `gorm:"column:command_id"`
+ Status string `gorm:"column:status"`
+ Message string `gorm:"column:message"`
+ Author string `gorm:"column:author"`
+ CreatedAt time.Time `gorm:"column:created_at"`
+ }
- rows, err := d.Query(query, commandID)
+ err := d.GDB.Raw(`
+ SELECT id, command_id, status, message, author, created_at
+ FROM command_logs
+ WHERE command_id = ?
+ ORDER BY created_at ASC`, commandID).Scan(&rows).Error
if err != nil {
// Si la table n'existe pas, retourner un tableau vide au lieu d'une erreur
log.Printf("⚠️ Avertissement: impossible de récupérer les logs: %v", err)
return []map[string]any{}, nil
}
- defer rows.Close()
- var logs []map[string]any
- for rows.Next() {
- var id, commandID int
- var status, message, author string
- var createdAt time.Time
-
- err := rows.Scan(&id, &commandID, &status, &message, &author, &createdAt)
- if err != nil {
- return nil, fmt.Errorf("erreur lors du scan du log: %w", err)
- }
-
- logEntry := map[string]any{
- "id": id,
- "command_id": commandID,
- "status": status,
- "message": message,
- "author": author,
- "created_at": createdAt,
- }
- logs = append(logs, logEntry)
- }
-
- if err = rows.Err(); err != nil {
- return nil, fmt.Errorf("erreur lors de l'itération des résultats: %w", err)
+ logs := make([]map[string]any, 0, len(rows))
+ for _, row := range rows {
+ logs = append(logs, map[string]any{
+ "id": row.ID,
+ "command_id": row.CommandID,
+ "status": row.Status,
+ "message": row.Message,
+ "author": row.Author,
+ "created_at": row.CreatedAt,
+ })
}
return logs, nil
@@ -655,113 +598,100 @@ func sanitizeLogMessage(message string) string {
}
func (d *Database) ValidateDeliveryAtomic(commandID int, adminUsername string) (int, error) {
- tx, err := d.Begin()
- if err != nil {
- log.Printf("❌ [ValidateAtomic] Erreur début transaction: %v", err)
- return 0, fmt.Errorf("erreur transaction: %w", err)
- }
- defer tx.Rollback()
+ var totalPoints int
+ var cmdUsernameOut string
+ var livreurAssignOut string
- var currentStatus, cmdUsername, livreurAssign string
- var totalPrix float64
- err = tx.QueryRow(`
- SELECT status, username, COALESCE(livreur_assign, ''), total_prix
- FROM commandes
- WHERE id = $1
- FOR UPDATE
- `, commandID).Scan(¤tStatus, &cmdUsername, &livreurAssign, &totalPrix)
-
- if err == sql.ErrNoRows {
- log.Printf("❌ [ValidateAtomic] Commande %d non trouvée", commandID)
- return 0, fmt.Errorf("commande non trouvée")
- }
- if err != nil {
- log.Printf("❌ [ValidateAtomic] Erreur SELECT: %v", err)
- return 0, fmt.Errorf("erreur lecture commande: %w", err)
- }
-
- log.Printf("📋 [ValidateAtomic] Commande trouvée - status=%s, client=%s, livreur=%s",
- currentStatus, cmdUsername, livreurAssign)
-
- validStatuses := []string{"assigned", "en_route", "pending", "livre"}
- isValid := slices.Contains(validStatuses, currentStatus)
-
- if !isValid {
- log.Printf("❌ [ValidateAtomic] Statut invalide pour validation: %s", currentStatus)
- return 0, fmt.Errorf("statut invalide pour validation: %s", currentStatus)
- }
-
- if currentStatus == "approved" {
- log.Printf("⚠️ [ValidateAtomic] Commande %d déjà approuvée", commandID)
- return 0, fmt.Errorf("commande déjà approuvée")
- }
-
- result, err := tx.Exec(`
- UPDATE commandes
- SET status = 'approved', updated_at = CURRENT_TIMESTAMP
- WHERE id = $1 AND status = $2
- `, commandID, currentStatus)
-
- if err != nil {
- log.Printf("❌ [ValidateAtomic] Erreur UPDATE: %v", err)
- return 0, fmt.Errorf("erreur mise à jour statut: %w", err)
- }
-
- rows, _ := result.RowsAffected()
- if rows == 0 {
- log.Printf("❌ [ValidateAtomic] Commande %d déjà modifiée (race condition évitée)", commandID)
- return 0, fmt.Errorf("commande déjà modifiée par une autre requête")
- }
-
- totalPoints := 0
- if cmdUsername != "" {
- log.Printf("🔍 [ValidateAtomic] Calcul points pour client: %s", cmdUsername)
-
- points, _, err := d.CalculateAndAddPointsForCommandTx(tx, commandID, cmdUsername)
- if err != nil {
- log.Printf("❌ [ValidateAtomic] Erreur calcul/ajout points: %v", err)
- return 0, fmt.Errorf("erreur attribution points: %w", err)
+ err := d.GDB.Transaction(func(tx *gorm.DB) error {
+ var cmd struct {
+ Status string `gorm:"column:status"`
+ Username string `gorm:"column:username"`
+ LivreurAssign string `gorm:"column:livreur_assign"`
+ TotalPrix float64 `gorm:"column:total_prix"`
}
- totalPoints = points
-
- log.Printf("✅ [ValidateAtomic] %d points attribués à %s", totalPoints, cmdUsername)
-
- _, err = tx.Exec(`
- UPDATE clients
- SET command = command + 1, updated_at = CURRENT_TIMESTAMP
- WHERE username = $1
- `, cmdUsername)
- if err != nil {
- log.Printf("⚠️ [ValidateAtomic] Erreur incrémentation compteur: %v", err)
- } else {
- log.Printf("✅ [ValidateAtomic] Compteur commandes incrémenté pour %s", cmdUsername)
+ if err := tx.Raw(`
+ SELECT status, username, COALESCE(livreur_assign, '') as livreur_assign, total_prix
+ FROM commandes WHERE id = ? FOR UPDATE`, commandID).Scan(&cmd).Error; err != nil {
+ log.Printf("❌ [ValidateAtomic] Erreur SELECT: %v", err)
+ return fmt.Errorf("erreur lecture commande: %w", err)
+ }
+ if cmd.Username == "" {
+ log.Printf("❌ [ValidateAtomic] Commande %d non trouvée", commandID)
+ return fmt.Errorf("commande non trouvée")
}
- }
- _, err = tx.Exec(`
- INSERT INTO command_logs (command_id, status, message, author, created_at)
- VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP)
- `, commandID, "approved",
- fmt.Sprintf("Livraison validée par admin %s - %d points attribués", adminUsername, totalPoints),
- adminUsername)
+ log.Printf("📋 [ValidateAtomic] Commande trouvée - status=%s, client=%s, livreur=%s",
+ cmd.Status, cmd.Username, cmd.LivreurAssign)
+
+ validStatuses := []string{"assigned", "en_route", "pending", "livre"}
+ if !slices.Contains(validStatuses, cmd.Status) {
+ log.Printf("❌ [ValidateAtomic] Statut invalide pour validation: %s", cmd.Status)
+ return fmt.Errorf("statut invalide pour validation: %s", cmd.Status)
+ }
+
+ if cmd.Status == "approved" {
+ log.Printf("⚠️ [ValidateAtomic] Commande %d déjà approuvée", commandID)
+ return fmt.Errorf("commande déjà approuvée")
+ }
+
+ result := tx.Exec(`
+ UPDATE commandes SET status = 'approved', updated_at = CURRENT_TIMESTAMP
+ WHERE id = ? AND status = ?`, commandID, cmd.Status)
+ if result.Error != nil {
+ log.Printf("❌ [ValidateAtomic] Erreur UPDATE: %v", result.Error)
+ return fmt.Errorf("erreur mise à jour statut: %w", result.Error)
+ }
+ if result.RowsAffected == 0 {
+ log.Printf("❌ [ValidateAtomic] Commande %d déjà modifiée (race condition évitée)", commandID)
+ return fmt.Errorf("commande déjà modifiée par une autre requête")
+ }
+
+ if cmd.Username != "" {
+ log.Printf("🔍 [ValidateAtomic] Calcul points pour client: %s", cmd.Username)
+
+ points, _, err := d.CalculateAndAddPointsForCommandTx(tx, commandID, cmd.Username)
+ if err != nil {
+ log.Printf("❌ [ValidateAtomic] Erreur calcul/ajout points: %v", err)
+ return fmt.Errorf("erreur attribution points: %w", err)
+ }
+ totalPoints = points
+
+ log.Printf("✅ [ValidateAtomic] %d points attribués à %s", totalPoints, cmd.Username)
+
+ if err := tx.Exec(`
+ UPDATE clients SET command = command + 1, updated_at = CURRENT_TIMESTAMP
+ WHERE username = ?`, cmd.Username).Error; err != nil {
+ log.Printf("⚠️ [ValidateAtomic] Erreur incrémentation compteur: %v", err)
+ } else {
+ log.Printf("✅ [ValidateAtomic] Compteur commandes incrémenté pour %s", cmd.Username)
+ }
+ }
+
+ if err := tx.Exec(`
+ INSERT INTO command_logs (command_id, status, message, author, created_at)
+ VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)`,
+ commandID, "approved",
+ fmt.Sprintf("Livraison validée par admin %s - %d points attribués", adminUsername, totalPoints),
+ adminUsername).Error; err != nil {
+ log.Printf("⚠️ [ValidateAtomic] Erreur ajout log: %v", err)
+ }
+
+ cmdUsernameOut = cmd.Username
+ livreurAssignOut = cmd.LivreurAssign
+ return nil
+ })
if err != nil {
- log.Printf("⚠️ [ValidateAtomic] Erreur ajout log: %v", err)
- }
-
- if err := tx.Commit(); err != nil {
- log.Printf("❌ [ValidateAtomic] Erreur COMMIT: %v", err)
- return 0, fmt.Errorf("erreur commit transaction: %w", err)
+ return 0, err
}
log.Printf("🎉 [ValidateAtomic] SUCCÈS - Commande %d validée, %d points attribués",
commandID, totalPoints)
- if livreurAssign != "" {
- log.Printf("📦 [ValidateAtomic] Optimisation queue pour livreur: %s", livreurAssign)
+ if livreurAssignOut != "" {
+ log.Printf("📦 [ValidateAtomic] Optimisation queue pour livreur: %s", livreurAssignOut)
go func() {
- err := d.CompleteDeliveryAndProcessNext(livreurAssign, commandID)
- if err != nil {
+ if err := d.CompleteDeliveryAndProcessNext(livreurAssignOut, commandID); err != nil {
log.Printf("⚠️ [ValidateAtomic] Erreur optimisation queue: %v", err)
}
}()
@@ -771,11 +701,11 @@ func (d *Database) ValidateDeliveryAtomic(commandID int, adminUsername string) (
commandCacheKey := fmt.Sprintf("command:%d", commandID)
Redis.Del(RedisCtx, commandCacheKey)
- if cmdUsername != "" {
- clientCacheKey := fmt.Sprintf("client:%s", cmdUsername)
+ if cmdUsernameOut != "" {
+ clientCacheKey := fmt.Sprintf("client:%s", cmdUsernameOut)
Redis.Del(RedisCtx, clientCacheKey)
- clientCommandsCacheKey := fmt.Sprintf("client:%s:commands", cmdUsername)
+ clientCommandsCacheKey := fmt.Sprintf("client:%s:commands", cmdUsernameOut)
Redis.Del(RedisCtx, clientCommandsCacheKey)
}
@@ -789,120 +719,99 @@ func (d *Database) ValidateDeliveryAtomic(commandID int, adminUsername string) (
func (d *Database) ApproveDeliveryAtomic(commandID int, username string) (int, string, error) {
log.Printf("🔒 [ApproveAtomic] START - cmd=%d, client=%s", commandID, username)
- // ✅ ÉTAPE 1: Démarrer une transaction
- tx, err := d.Begin()
- if err != nil {
- log.Printf("❌ [ApproveAtomic] Erreur début transaction: %v", err)
- return 0, "", fmt.Errorf("erreur transaction: %w", err)
- }
- defer tx.Rollback()
+ var totalPoints int
+ var pointCategory string
+ var livreurAssignOut string
- // ✅ ÉTAPE 2: SELECT FOR UPDATE pour verrouiller la commande
- var currentStatus, cmdUsername, livreurAssign string
- err = tx.QueryRow(`
- SELECT status, username, COALESCE(livreur_assign, '')
- FROM commandes
- WHERE id = $1
- FOR UPDATE
- `, commandID).Scan(¤tStatus, &cmdUsername, &livreurAssign)
+ err := d.GDB.Transaction(func(tx *gorm.DB) error {
+ var cmd struct {
+ Status string `gorm:"column:status"`
+ Username string `gorm:"column:username"`
+ LivreurAssign string `gorm:"column:livreur_assign"`
+ }
+ if err := tx.Raw(`
+ SELECT status, username, COALESCE(livreur_assign, '') as livreur_assign
+ FROM commandes WHERE id = ? FOR UPDATE`, commandID).Scan(&cmd).Error; err != nil {
+ log.Printf("❌ [ApproveAtomic] Erreur SELECT: %v", err)
+ return fmt.Errorf("erreur lecture commande: %w", err)
+ }
+ if cmd.Username == "" {
+ log.Printf("❌ [ApproveAtomic] Commande %d non trouvée", commandID)
+ return fmt.Errorf("commande non trouvée")
+ }
- if err == sql.ErrNoRows {
- log.Printf("❌ [ApproveAtomic] Commande %d non trouvée", commandID)
- return 0, "", fmt.Errorf("commande non trouvée")
- }
- if err != nil {
- log.Printf("❌ [ApproveAtomic] Erreur SELECT: %v", err)
- return 0, "", fmt.Errorf("erreur lecture commande: %w", err)
- }
+ log.Printf("📋 [ApproveAtomic] Commande trouvée - status=%s, owner=%s", cmd.Status, cmd.Username)
- log.Printf("📋 [ApproveAtomic] Commande trouvée - status=%s, owner=%s", currentStatus, cmdUsername)
+ if cmd.Username != username {
+ log.Printf("❌ [ApproveAtomic] Commande n'appartient pas à %s (propriétaire: %s)",
+ username, cmd.Username)
+ return fmt.Errorf("cette commande ne vous appartient pas")
+ }
- // ✅ ÉTAPE 3: Vérifier propriété
- if cmdUsername != username {
- log.Printf("❌ [ApproveAtomic] Commande n'appartient pas à %s (propriétaire: %s)",
- username, cmdUsername)
- return 0, "", fmt.Errorf("cette commande ne vous appartient pas")
- }
+ if cmd.Status != "livre" {
+ log.Printf("❌ [ApproveAtomic] Statut invalide: %s (attendu: livre)", cmd.Status)
+ return fmt.Errorf("commande doit être en statut 'livre' (statut actuel: %s)", cmd.Status)
+ }
- // ✅ ÉTAPE 4: Vérifier le statut
- if currentStatus != "livre" {
- log.Printf("❌ [ApproveAtomic] Statut invalide: %s (attendu: livre)", currentStatus)
- return 0, "", fmt.Errorf("commande doit être en statut 'livre' (statut actuel: %s)", currentStatus)
- }
+ result := tx.Exec(`
+ UPDATE commandes SET status = 'approved', updated_at = CURRENT_TIMESTAMP
+ WHERE id = ? AND status = 'livre' AND username = ?`, commandID, username)
+ if result.Error != nil {
+ log.Printf("❌ [ApproveAtomic] Erreur UPDATE: %v", result.Error)
+ return fmt.Errorf("erreur mise à jour statut: %w", result.Error)
+ }
+ if result.RowsAffected == 0 {
+ log.Printf("❌ [ApproveAtomic] Commande %d déjà modifiée (race condition évitée)", commandID)
+ return fmt.Errorf("commande déjà approuvée ou modifiée")
+ }
- // ✅ ÉTAPE 5: UPDATE avec vérification du statut
- result, err := tx.Exec(`
- UPDATE commandes
- SET status = 'approved', updated_at = CURRENT_TIMESTAMP
- WHERE id = $1 AND status = 'livre' AND username = $2
- `, commandID, username)
+ log.Printf("✅ [ApproveAtomic] Statut mis à jour: livre → approved")
+
+ pts, cat, err := d.CalculateAndAddPointsForCommandTx(tx, commandID, username)
+ if err != nil {
+ log.Printf("❌ [ApproveAtomic] Erreur calcul points: %v", err)
+ return fmt.Errorf("erreur attribution points: %w", err)
+ }
+ totalPoints = pts
+ pointCategory = cat
+
+ log.Printf("✅ [ApproveAtomic] %d points [%s] attribués à %s", totalPoints, pointCategory, username)
+
+ if err := tx.Exec(`
+ UPDATE clients SET command = command + 1, updated_at = CURRENT_TIMESTAMP
+ WHERE username = ?`, username).Error; err != nil {
+ log.Printf("⚠️ [ApproveAtomic] Erreur incrémentation compteur: %v", err)
+ }
+
+ if err := tx.Exec(`
+ INSERT INTO command_logs (command_id, status, message, author, created_at)
+ VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)`,
+ commandID, "approved",
+ fmt.Sprintf("Livraison confirmée par le client %s - %d points [%s] attribués", username, totalPoints, pointCategory),
+ username).Error; err != nil {
+ log.Printf("⚠️ [ApproveAtomic] Erreur ajout log: %v", err)
+ }
+
+ livreurAssignOut = cmd.LivreurAssign
+ return nil
+ })
if err != nil {
- log.Printf("❌ [ApproveAtomic] Erreur UPDATE: %v", err)
- return 0, "", fmt.Errorf("erreur mise à jour statut: %w", err)
- }
-
- rows, _ := result.RowsAffected()
- if rows == 0 {
- log.Printf("❌ [ApproveAtomic] Commande %d déjà modifiée (race condition évitée)", commandID)
- return 0, "", fmt.Errorf("commande déjà approuvée ou modifiée")
- }
-
- log.Printf("✅ [ApproveAtomic] Statut mis à jour: livre → approved")
-
- // ✅ ÉTAPE 6: Calculer et ajouter les points
- totalPoints, pointCategory, err := d.CalculateAndAddPointsForCommandTx(tx, commandID, username)
- if err != nil {
- log.Printf("❌ [ApproveAtomic] Erreur calcul points: %v", err)
- return 0, "", fmt.Errorf("erreur attribution points: %w", err)
- }
-
- log.Printf("✅ [ApproveAtomic] %d points [%s] attribués à %s", totalPoints, pointCategory, username)
-
- // ✅ ÉTAPE 7: Incrémenter le compteur de commandes
- _, err = tx.Exec(`
- UPDATE clients
- SET command = command + 1, updated_at = CURRENT_TIMESTAMP
- WHERE username = $1
- `, username)
- if err != nil {
- log.Printf("⚠️ [ApproveAtomic] Erreur incrémentation compteur: %v", err)
- // On ne bloque pas pour ça
- }
-
- // ✅ ÉTAPE 8: Ajouter un log
- _, err = tx.Exec(`
- INSERT INTO command_logs (command_id, status, message, author, created_at)
- VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP)
- `, commandID, "approved",
- fmt.Sprintf("Livraison confirmée par le client %s - %d points [%s] attribués", username, totalPoints, pointCategory),
- username)
-
- if err != nil {
- log.Printf("⚠️ [ApproveAtomic] Erreur ajout log: %v", err)
- }
-
- // ✅ ÉTAPE 9: Commit
- if err := tx.Commit(); err != nil {
- log.Printf("❌ [ApproveAtomic] Erreur COMMIT: %v", err)
- return 0, "", fmt.Errorf("erreur commit transaction: %w", err)
+ return 0, "", err
}
log.Printf("🎉 [ApproveAtomic] SUCCÈS - Commande %d approuvée, %d points [%s] attribués",
commandID, totalPoints, pointCategory)
- // ✅ ÉTAPE 10: Optimiser queue livreur (async, après commit)
- if livreurAssign != "" {
- log.Printf("📦 [ApproveAtomic] Optimisation queue pour livreur: %s", livreurAssign)
+ if livreurAssignOut != "" {
+ log.Printf("📦 [ApproveAtomic] Optimisation queue pour livreur: %s", livreurAssignOut)
go func() {
- err := d.CompleteDeliveryAndProcessNext(livreurAssign, commandID)
- if err != nil {
+ if err := d.CompleteDeliveryAndProcessNext(livreurAssignOut, commandID); err != nil {
log.Printf("⚠️ [ApproveAtomic] Erreur optimisation queue: %v", err)
}
}()
}
- // ✅ ÉTAPE 11: Invalider caches (async)
go func() {
commandCacheKey := fmt.Sprintf("command:%d", commandID)
Redis.Del(RedisCtx, commandCacheKey)
@@ -923,79 +832,77 @@ func (d *Database) ApproveDeliveryAtomic(commandID int, username string) (int, s
func (d *Database) ApproveDeliveryAtomicByStaff(commandID int, staffUsername string) (int, string, string, error) {
log.Printf("🔒 [ApproveAtomicStaff] START - cmd=%d, staff=%s", commandID, staffUsername)
- tx, err := d.Begin()
+ var totalPoints int
+ var pointCategory string
+ var clientUsernameOut string
+ var livreurAssignOut string
+
+ err := d.GDB.Transaction(func(tx *gorm.DB) error {
+ var cmd struct {
+ Status string `gorm:"column:status"`
+ Username string `gorm:"column:username"`
+ LivreurAssign string `gorm:"column:livreur_assign"`
+ }
+ if err := tx.Raw(`
+ SELECT status, username, COALESCE(livreur_assign, '') as livreur_assign
+ FROM commandes WHERE id = ? FOR UPDATE`, commandID).Scan(&cmd).Error; err != nil {
+ return fmt.Errorf("erreur lecture commande: %w", err)
+ }
+ if cmd.Username == "" {
+ return fmt.Errorf("commande non trouvée")
+ }
+
+ if cmd.Status != "livre" {
+ return fmt.Errorf("commande doit être en statut 'livre' (statut actuel: %s)", cmd.Status)
+ }
+
+ result := tx.Exec(`
+ UPDATE commandes SET status = 'approved', updated_at = CURRENT_TIMESTAMP
+ WHERE id = ? AND status = 'livre'`, commandID)
+ if result.Error != nil {
+ return fmt.Errorf("erreur mise à jour statut: %w", result.Error)
+ }
+ if result.RowsAffected == 0 {
+ return fmt.Errorf("commande déjà approuvée ou modifiée")
+ }
+
+ pts, cat, err := d.CalculateAndAddPointsForCommandTx(tx, commandID, cmd.Username)
+ if err != nil {
+ return fmt.Errorf("erreur attribution points: %w", err)
+ }
+ totalPoints = pts
+ pointCategory = cat
+
+ if err := tx.Exec(`
+ UPDATE clients SET command = command + 1, updated_at = CURRENT_TIMESTAMP
+ WHERE username = ?`, cmd.Username).Error; err != nil {
+ log.Printf("⚠️ [ApproveAtomicStaff] Erreur incrémentation compteur: %v", err)
+ }
+
+ if err := tx.Exec(`
+ INSERT INTO command_logs (command_id, status, message, author, created_at)
+ VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)`,
+ commandID, "approved",
+ fmt.Sprintf("Réception confirmée par %s au nom du client %s - %d points attribués", staffUsername, cmd.Username, totalPoints),
+ staffUsername).Error; err != nil {
+ log.Printf("⚠️ [ApproveAtomicStaff] Erreur log: %v", err)
+ }
+
+ clientUsernameOut = cmd.Username
+ livreurAssignOut = cmd.LivreurAssign
+ return nil
+ })
+
if err != nil {
- return 0, "", "", fmt.Errorf("erreur transaction: %w", err)
- }
- defer tx.Rollback()
-
- var currentStatus, clientUsername, livreurAssign string
- err = tx.QueryRow(`
- SELECT status, username, COALESCE(livreur_assign, '')
- FROM commandes
- WHERE id = $1
- FOR UPDATE
- `, commandID).Scan(¤tStatus, &clientUsername, &livreurAssign)
-
- if err == sql.ErrNoRows {
- return 0, "", "", fmt.Errorf("commande non trouvée")
- }
- if err != nil {
- return 0, "", "", fmt.Errorf("erreur lecture commande: %w", err)
- }
-
- if currentStatus != "livre" {
- return 0, "", "", fmt.Errorf("commande doit être en statut 'livre' (statut actuel: %s)", currentStatus)
- }
-
- result, err := tx.Exec(`
- UPDATE commandes
- SET status = 'approved', updated_at = CURRENT_TIMESTAMP
- WHERE id = $1 AND status = 'livre'
- `, commandID)
- if err != nil {
- return 0, "", "", fmt.Errorf("erreur mise à jour statut: %w", err)
- }
-
- rows, _ := result.RowsAffected()
- if rows == 0 {
- return 0, "", "", fmt.Errorf("commande déjà approuvée ou modifiée")
- }
-
- totalPoints, pointCategory, err := d.CalculateAndAddPointsForCommandTx(tx, commandID, clientUsername)
- if err != nil {
- return 0, "", "", fmt.Errorf("erreur attribution points: %w", err)
- }
-
- _, err = tx.Exec(`
- UPDATE clients
- SET command = command + 1, updated_at = CURRENT_TIMESTAMP
- WHERE username = $1
- `, clientUsername)
- if err != nil {
- log.Printf("⚠️ [ApproveAtomicStaff] Erreur incrémentation compteur: %v", err)
- }
-
- _, err = tx.Exec(`
- INSERT INTO command_logs (command_id, status, message, author, created_at)
- VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP)
- `, commandID, "approved",
- fmt.Sprintf("Réception confirmée par %s au nom du client %s - %d points attribués", staffUsername, clientUsername, totalPoints),
- staffUsername)
- if err != nil {
- log.Printf("⚠️ [ApproveAtomicStaff] Erreur log: %v", err)
- }
-
- if err := tx.Commit(); err != nil {
- return 0, "", "", fmt.Errorf("erreur commit: %w", err)
+ return 0, "", "", err
}
log.Printf("🎉 [ApproveAtomicStaff] SUCCÈS - cmd=%d approuvée par %s, %d points → client %s",
- commandID, staffUsername, totalPoints, clientUsername)
+ commandID, staffUsername, totalPoints, clientUsernameOut)
- if livreurAssign != "" {
+ if livreurAssignOut != "" {
go func() {
- if err := d.CompleteDeliveryAndProcessNext(livreurAssign, commandID); err != nil {
+ if err := d.CompleteDeliveryAndProcessNext(livreurAssignOut, commandID); err != nil {
log.Printf("⚠️ [ApproveAtomicStaff] Erreur queue: %v", err)
}
}()
@@ -1003,9 +910,9 @@ func (d *Database) ApproveDeliveryAtomicByStaff(commandID int, staffUsername str
go func() {
Redis.Del(RedisCtx, fmt.Sprintf("command:%d", commandID))
- Redis.Del(RedisCtx, fmt.Sprintf("client:%s", clientUsername))
- Redis.Del(RedisCtx, fmt.Sprintf("client:%s:commands", clientUsername))
+ Redis.Del(RedisCtx, fmt.Sprintf("client:%s", clientUsernameOut))
+ Redis.Del(RedisCtx, fmt.Sprintf("client:%s:commands", clientUsernameOut))
}()
- return totalPoints, pointCategory, clientUsername, nil
+ return totalPoints, pointCategory, clientUsernameOut, nil
}
diff --git a/backend/gestion/db/db_delivery.go b/backend/gestion/db/db_delivery.go
index 698d5905..e364d6cf 100644
--- a/backend/gestion/db/db_delivery.go
+++ b/backend/gestion/db/db_delivery.go
@@ -1,46 +1,24 @@
package db
import (
- "database/sql"
"fmt"
"log"
"slices"
- "time"
+
+ "gorm.io/gorm"
)
// GetAvailableDeliveryPersons récupère tous les livreurs disponibles
func (d *Database) GetAvailableDeliveryPersons() ([]map[string]any, error) {
- query := `SELECT id, username, total, livraison
- FROM users
- WHERE role = 'livreur'
- ORDER BY username`
-
- rows, err := d.Query(query)
+ var livreurs []map[string]any
+ err := d.GDB.Raw(`
+ SELECT id, username, total, livraison
+ FROM users
+ WHERE role = 'livreur'
+ ORDER BY username`).Scan(&livreurs).Error
if err != nil {
return nil, fmt.Errorf("erreur lors de la récupération des livreurs: %w", err)
}
- defer rows.Close()
-
- var livreurs []map[string]any
- for rows.Next() {
- var id int
- var username string
- var total, livraison float64
-
- err := rows.Scan(&id, &username, &total, &livraison)
- if err != nil {
- return nil, fmt.Errorf("erreur lors du scan du livreur: %w", err)
- }
-
- livreur := map[string]any{
- "id": id,
- "username": username,
- "total": total,
- "livraison": livraison,
- }
- livreurs = append(livreurs, livreur)
- }
-
return livreurs, nil
}
@@ -48,255 +26,172 @@ func (d *Database) GetAvailableDeliveryPersons() ([]map[string]any, error) {
func (d *Database) AssignDeliveryPerson(commandID int, livreurUsername string) error {
log.Printf("📦 [AssignDeliveryPerson] START - commandID=%d, livreur=%s", commandID, livreurUsername)
- tx, err := d.Begin()
- if err != nil {
- log.Printf("❌ Erreur démarrage transaction: %v", err)
- return fmt.Errorf("erreur démarrage transaction: %w", err)
- }
- defer tx.Rollback()
+ return d.GDB.Transaction(func(tx *gorm.DB) error {
+ var roleResult struct {
+ Role string `gorm:"column:role"`
+ }
+ err := tx.Raw(`SELECT role FROM users WHERE username = ? FOR UPDATE`, livreurUsername).Scan(&roleResult).Error
+ if err != nil {
+ log.Printf("❌ Erreur vérification livreur: %v", err)
+ return fmt.Errorf("erreur lors de la vérification du livreur: %w", err)
+ }
+ if roleResult.Role == "" {
+ log.Printf("❌ Livreur '%s' non trouvé", livreurUsername)
+ return fmt.Errorf("livreur non trouvé")
+ }
+ if roleResult.Role != "livreur" {
+ log.Printf("❌ L'utilisateur '%s' n'est pas un livreur (role=%s)", livreurUsername, roleResult.Role)
+ return fmt.Errorf("l'utilisateur n'est pas un livreur")
+ }
- var role string
- checkQuery := `SELECT role FROM users WHERE username = $1 FOR UPDATE`
- err = tx.QueryRow(checkQuery, livreurUsername).Scan(&role)
- if err == sql.ErrNoRows {
- log.Printf("❌ Livreur '%s' non trouvé", livreurUsername)
- return fmt.Errorf("livreur non trouvé")
- }
- if err != nil {
- log.Printf("❌ Erreur vérification livreur: %v", err)
- return fmt.Errorf("erreur lors de la vérification du livreur: %w", err)
- }
- if role != "livreur" {
- log.Printf("❌ L'utilisateur '%s' n'est pas un livreur (role=%s)", livreurUsername, role)
- return fmt.Errorf("l'utilisateur n'est pas un livreur")
- }
+ var cmdResult struct {
+ Status string `gorm:"column:status"`
+ LivreurAssign *string `gorm:"column:livreur_assign"`
+ }
+ err = tx.Raw(`SELECT status, livreur_assign FROM commandes WHERE id = ? FOR UPDATE`, commandID).Scan(&cmdResult).Error
+ if err != nil {
+ log.Printf("❌ Erreur vérification commande: %v", err)
+ return fmt.Errorf("erreur lors de la vérification de la commande: %w", err)
+ }
+ if cmdResult.Status == "" {
+ log.Printf("❌ Commande %d non trouvée", commandID)
+ return fmt.Errorf("commande non trouvée")
+ }
- var currentStatus string
- var currentLivreur sql.NullString
- statusQuery := `SELECT status, livreur_assign
- FROM commandes
- WHERE id = $1
- FOR UPDATE`
+ validStatusesForAssignment := []string{"pending", "assigned"}
+ if !slices.Contains(validStatusesForAssignment, cmdResult.Status) {
+ log.Printf("❌ Statut invalide pour assignation: %s", cmdResult.Status)
+ return fmt.Errorf("commande en statut '%s', impossible d'assigner un livreur", cmdResult.Status)
+ }
- err = tx.QueryRow(statusQuery, commandID).Scan(¤tStatus, ¤tLivreur)
- if err == sql.ErrNoRows {
- log.Printf("❌ Commande %d non trouvée", commandID)
- return fmt.Errorf("commande non trouvée")
- }
- if err != nil {
- log.Printf("❌ Erreur vérification commande: %v", err)
- return fmt.Errorf("erreur lors de la vérification de la commande: %w", err)
- }
+ result := tx.Exec(`
+ UPDATE commandes
+ SET livreur_assign = ?,
+ status = 'assigned',
+ updated_at = CURRENT_TIMESTAMP
+ WHERE id = ?
+ AND status IN ('pending', 'assigned')`, livreurUsername, commandID)
+ if result.Error != nil {
+ log.Printf("❌ Erreur UPDATE: %v", result.Error)
+ return fmt.Errorf("erreur lors de l'assignation du livreur: %w", result.Error)
+ }
+ if result.RowsAffected == 0 {
+ log.Printf("❌ Impossible d'assigner: conditions non remplies")
+ return fmt.Errorf("impossible d'assigner la commande (déjà assignée ou statut changé)")
+ }
- validStatusesForAssignment := []string{"pending", "assigned"}
- isValidStatus := slices.Contains(validStatusesForAssignment, currentStatus)
+ if err := tx.Exec(`
+ INSERT INTO command_logs (command_id, status, message, author, created_at)
+ VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)`,
+ commandID, "assigned",
+ fmt.Sprintf("Livraison assignée au livreur %s", livreurUsername),
+ "admin",
+ ).Error; err != nil {
+ log.Printf("⚠️ Erreur ajout log: %v", err)
+ // Non bloquant
+ }
- if !isValidStatus {
- log.Printf("❌ Statut invalide pour assignation: %s", currentStatus)
- return fmt.Errorf("commande en statut '%s', impossible d'assigner un livreur", currentStatus)
- }
-
- updateQuery := `UPDATE commandes
- SET livreur_assign = $1,
- status = 'assigned',
- updated_at = CURRENT_TIMESTAMP
- WHERE id = $2
- AND status IN ('pending', 'assigned')`
-
- result, err := tx.Exec(updateQuery, livreurUsername, commandID)
- if err != nil {
- log.Printf("❌ Erreur UPDATE: %v", err)
- return fmt.Errorf("erreur lors de l'assignation du livreur: %w", err)
- }
-
- rowsAffected, err := result.RowsAffected()
- if err != nil {
- log.Printf("❌ Erreur RowsAffected: %v", err)
- return fmt.Errorf("erreur lors de la vérification: %w", err)
- }
- if rowsAffected == 0 {
- log.Printf("❌ Impossible d'assigner: conditions non remplies")
- return fmt.Errorf("impossible d'assigner la commande (déjà assignée ou statut changé)")
- }
-
- logQuery := `INSERT INTO command_logs (command_id, status, message, author, created_at)
- VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP)`
-
- _, err = tx.Exec(logQuery, commandID, "assigned",
- fmt.Sprintf("Livraison assignée au livreur %s", livreurUsername),
- "admin")
- if err != nil {
- log.Printf("⚠️ Erreur ajout log: %v", err)
- // Non bloquant
- }
-
- err = tx.Commit()
- if err != nil {
- log.Printf("❌ Erreur COMMIT: %v", err)
- return fmt.Errorf("erreur commit transaction: %w", err)
- }
-
- return nil
+ return nil
+ })
}
// GetDeliveryPersonCommands récupère les commandes assignées à un livreur
func (d *Database) GetDeliveryPersonCommands(livreurUsername string, status string) ([]map[string]any, error) {
- query := `SELECT id, username, status, adresse, total_prix, livreur_assign, created_at, updated_at
+ query := `SELECT id, username, status, adresse, total_prix::float8 as total_prix, livreur_assign, created_at, updated_at
FROM commandes
- WHERE livreur_assign = $1`
+ WHERE livreur_assign = ?`
args := []any{livreurUsername}
if status != "" {
- query += " AND status = $2"
+ query += " AND status = ?"
args = append(args, status)
}
query += " ORDER BY created_at DESC"
- rows, err := d.Query(query, args...)
- if err != nil {
+ var commands []map[string]any
+ if err := d.GDB.Raw(query, args...).Scan(&commands).Error; err != nil {
return nil, fmt.Errorf("erreur lors de la récupération des commandes: %w", err)
}
- defer rows.Close()
-
- var commands []map[string]any
- for rows.Next() {
- var id int
- var username, status, adresse string
- var livreurAssign sql.NullString
- var totalPrix float64
- var createdAt, updatedAt time.Time
-
- err := rows.Scan(&id, &username, &status, &adresse, &totalPrix, &livreurAssign, &createdAt, &updatedAt)
- if err != nil {
- return nil, fmt.Errorf("erreur lors du scan: %w", err)
- }
-
- command := map[string]any{
- "id": id,
- "username": username,
- "status": status,
- "adresse": adresse,
- "total_prix": totalPrix,
- "livreur_assign": livreurAssign.String,
- "created_at": createdAt,
- "updated_at": updatedAt,
- }
- commands = append(commands, command)
- }
-
return commands, nil
}
func (d *Database) IncrementLivreurDeliveryCount(livreurUsername string) error {
- query := `UPDATE users
- SET livraison = livraison + 1,
- total = total + 1,
- updated_at = CURRENT_TIMESTAMP
- WHERE username = $1 AND role = 'livreur'`
-
- result, err := d.Exec(query, livreurUsername)
- if err != nil {
- return fmt.Errorf("erreur lors de l'incrémentation des livraisons: %w", err)
+ result := d.GDB.Exec(`
+ UPDATE users
+ SET livraison = livraison + 1,
+ total = total + 1,
+ updated_at = CURRENT_TIMESTAMP
+ WHERE username = ? AND role = 'livreur'`, livreurUsername)
+ if result.Error != nil {
+ return fmt.Errorf("erreur lors de l'incrémentation des livraisons: %w", result.Error)
}
-
- rowsAffected, err := result.RowsAffected()
- if err != nil {
- return fmt.Errorf("erreur lors de la vérification: %w", err)
- }
- if rowsAffected == 0 {
+ if result.RowsAffected == 0 {
return fmt.Errorf("livreur non trouvé")
}
-
return nil
}
func (d *Database) ApproveDelivery(commandID int, clientUsername string) error {
- tx, err := d.Begin()
- if err != nil {
- log.Printf("❌ Erreur démarrage transaction: %v", err)
- return fmt.Errorf("erreur démarrage transaction: %w", err)
- }
- defer tx.Rollback()
-
- var commandUsername, currentStatus string
- var livreurAssign sql.NullString
-
- checkQuery := `SELECT username, status, livreur_assign
- FROM commandes
- WHERE id = $1
- FOR UPDATE`
-
- err = tx.QueryRow(checkQuery, commandID).Scan(&commandUsername, ¤tStatus, &livreurAssign)
- if err == sql.ErrNoRows {
- return fmt.Errorf("commande non trouvée")
- }
- if err != nil {
- return fmt.Errorf("erreur lors de la vérification de la commande: %w", err)
- }
-
- if commandUsername != clientUsername {
- return fmt.Errorf("cette commande ne vous appartient pas")
- }
-
- if currentStatus != "livre" {
- return fmt.Errorf("cette commande n'est pas encore livrée (statut actuel: %s)", currentStatus)
- }
-
- updateQuery := `UPDATE commandes
- SET status = 'approved',
- updated_at = CURRENT_TIMESTAMP
- WHERE id = $1
- AND status = 'livre'
- AND username = $2`
-
- result, err := tx.Exec(updateQuery, commandID, clientUsername)
- if err != nil {
- return fmt.Errorf("erreur lors de l'approbation de la livraison: %w", err)
- }
-
- rowsAffected, err := result.RowsAffected()
- if err != nil {
- return fmt.Errorf("erreur lors de la vérification: %w", err)
- }
- if rowsAffected == 0 {
- return fmt.Errorf("impossible d'approuver: statut changé ou commande introuvable")
- }
-
- if livreurAssign.Valid && livreurAssign.String != "" {
- incrementQuery := `UPDATE users
- SET livraison = livraison + 1,
- total = total + 1,
- updated_at = CURRENT_TIMESTAMP
- WHERE username = $1 AND role = 'livreur'`
-
- result, err := tx.Exec(incrementQuery, livreurAssign.String)
+ return d.GDB.Transaction(func(tx *gorm.DB) error {
+ var cmdResult struct {
+ Username string `gorm:"column:username"`
+ Status string `gorm:"column:status"`
+ LivreurAssign *string `gorm:"column:livreur_assign"`
+ }
+ err := tx.Raw(`
+ SELECT username, status, livreur_assign
+ FROM commandes WHERE id = ? FOR UPDATE`, commandID).Scan(&cmdResult).Error
if err != nil {
- log.Printf("⚠️ Erreur incrémentation livreur: %v", err)
- } else {
- rows, _ := result.RowsAffected()
- if rows > 0 {
- log.Printf(" ✅ Compteur livreur incrémenté: %s", livreurAssign.String)
+ return fmt.Errorf("erreur lors de la vérification de la commande: %w", err)
+ }
+ if cmdResult.Username == "" {
+ return fmt.Errorf("commande non trouvée")
+ }
+
+ if cmdResult.Username != clientUsername {
+ return fmt.Errorf("cette commande ne vous appartient pas")
+ }
+ if cmdResult.Status != "livre" {
+ return fmt.Errorf("cette commande n'est pas encore livrée (statut actuel: %s)", cmdResult.Status)
+ }
+
+ result := tx.Exec(`
+ UPDATE commandes
+ SET status = 'approved', updated_at = CURRENT_TIMESTAMP
+ WHERE id = ? AND status = 'livre' AND username = ?`, commandID, clientUsername)
+ if result.Error != nil {
+ return fmt.Errorf("erreur lors de l'approbation de la livraison: %w", result.Error)
+ }
+ if result.RowsAffected == 0 {
+ return fmt.Errorf("impossible d'approuver: statut changé ou commande introuvable")
+ }
+
+ if cmdResult.LivreurAssign != nil && *cmdResult.LivreurAssign != "" {
+ res := tx.Exec(`
+ UPDATE users
+ SET livraison = livraison + 1,
+ total = total + 1,
+ updated_at = CURRENT_TIMESTAMP
+ WHERE username = ? AND role = 'livreur'`, *cmdResult.LivreurAssign)
+ if res.Error != nil {
+ log.Printf("⚠️ Erreur incrémentation livreur: %v", res.Error)
+ } else if res.RowsAffected > 0 {
+ log.Printf(" ✅ Compteur livreur incrémenté: %s", *cmdResult.LivreurAssign)
}
}
- }
- logQuery := `INSERT INTO command_logs (command_id, status, message, author, created_at)
- VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP)`
+ if err := tx.Exec(`
+ INSERT INTO command_logs (command_id, status, message, author, created_at)
+ VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)`,
+ commandID, "approved",
+ fmt.Sprintf("Livraison approuvée par le client %s", clientUsername),
+ clientUsername,
+ ).Error; err != nil {
+ log.Printf("⚠️ Erreur ajout log: %v", err)
+ }
- _, err = tx.Exec(logQuery, commandID, "approved",
- fmt.Sprintf("Livraison approuvée par le client %s", clientUsername),
- clientUsername)
- if err != nil {
- log.Printf("⚠️ Erreur ajout log: %v", err)
- }
-
- err = tx.Commit()
- if err != nil {
- log.Printf("❌ Erreur COMMIT: %v", err)
- return fmt.Errorf("erreur commit transaction: %w", err)
- }
-
- return nil
+ return nil
+ })
}
diff --git a/backend/gestion/db/db_delivery_issues.go b/backend/gestion/db/db_delivery_issues.go
index 4922718c..ce245b05 100644
--- a/backend/gestion/db/db_delivery_issues.go
+++ b/backend/gestion/db/db_delivery_issues.go
@@ -7,96 +7,40 @@ import (
)
func (d *Database) GetDeliveryIssues(status string) ([]models.DeliveryIssue, error) {
- query := `
- SELECT id, command_id, issue_type, description, status,
- reported_by, COALESCE(resolved_by, ''), COALESCE(resolution, ''),
- created_at, updated_at
- FROM delivery_issues
- `
-
- var args []any
+ q := d.GDB.Order("created_at DESC")
if status != "" {
- query += " WHERE status = $1"
- args = append(args, status)
+ q = q.Where("status = ?", status)
}
-
- query += " ORDER BY created_at DESC"
-
- rows, err := d.Query(query, args...)
- if err != nil {
+ var issues []models.DeliveryIssue
+ if err := q.Find(&issues).Error; err != nil {
return nil, fmt.Errorf("erreur récupération problèmes: %w", err)
}
- defer rows.Close()
-
- var issues []models.DeliveryIssue
- for rows.Next() {
- var issue models.DeliveryIssue
- err := rows.Scan(
- &issue.ID,
- &issue.CommandID,
- &issue.IssueType,
- &issue.Description,
- &issue.Status,
- &issue.ReportedBy,
- &issue.ResolvedBy,
- &issue.Resolution,
- &issue.CreatedAt,
- &issue.UpdatedAt,
- )
- if err != nil {
- return nil, fmt.Errorf("erreur scan problème: %w", err)
- }
- issues = append(issues, issue)
- }
-
return issues, nil
}
-// CreateDeliveryIssue crée un nouveau problème
func (d *Database) CreateDeliveryIssue(commandID int, issueType, description, reportedBy string) (*models.DeliveryIssue, error) {
- query := `
- INSERT INTO delivery_issues (command_id, issue_type, description, status, reported_by, created_at, updated_at)
- VALUES ($1, $2, $3, 'open', $4, NOW(), NOW())
- RETURNING id, command_id, issue_type, description, status, reported_by, created_at, updated_at
- `
-
- var issue models.DeliveryIssue
- err := d.QueryRow(query, commandID, issueType, description, reportedBy).Scan(
- &issue.ID,
- &issue.CommandID,
- &issue.IssueType,
- &issue.Description,
- &issue.Status,
- &issue.ReportedBy,
- &issue.CreatedAt,
- &issue.UpdatedAt,
- )
-
- if err != nil {
+ issue := models.DeliveryIssue{
+ CommandID: commandID,
+ IssueType: issueType,
+ Description: description,
+ Status: "open",
+ ReportedBy: reportedBy,
+ }
+ if err := d.GDB.Create(&issue).Error; err != nil {
return nil, fmt.Errorf("erreur création problème: %w", err)
}
-
return &issue, nil
}
-// UpdateDeliveryIssue met à jour un problème
func (d *Database) UpdateDeliveryIssue(issueID int, status, resolution, resolvedBy string) error {
- query := `
- UPDATE delivery_issues
- SET status = $1, resolution = $2, resolved_by = $3, updated_at = NOW()
- WHERE id = $4
- `
-
- result, err := d.Exec(query, status, resolution, resolvedBy, issueID)
- if err != nil {
- return fmt.Errorf("erreur mise à jour problème: %w", err)
+ result := d.GDB.Model(&models.DeliveryIssue{}).Where("id = ?", issueID).
+ Updates(map[string]any{"status": status, "resolution": resolution, "resolved_by": resolvedBy})
+ if result.Error != nil {
+ return fmt.Errorf("erreur mise à jour problème: %w", result.Error)
}
-
- rowsAffected, _ := result.RowsAffected()
- if rowsAffected == 0 {
+ if result.RowsAffected == 0 {
return fmt.Errorf("problème non trouvé")
}
-
log.Printf("✅ Problème %d mis à jour: status=%s", issueID, status)
return nil
}
diff --git a/backend/gestion/db/db_delivery_manage.go b/backend/gestion/db/db_delivery_manage.go
index ff09a12b..57947a1e 100644
--- a/backend/gestion/db/db_delivery_manage.go
+++ b/backend/gestion/db/db_delivery_manage.go
@@ -6,7 +6,6 @@
package db
import (
- "database/sql"
"fmt"
"log"
"strings"
@@ -22,52 +21,38 @@ var allowedStatuses = map[string]bool{
// CountDeliveriesByStatus compte les livraisons d'un livreur par statut
func (d *Database) CountDeliveriesByStatus(livreurUsername string, statuses string) (int, error) {
if statuses == "" {
- // Cas simple: toutes les livraisons
- query := `SELECT COUNT(*)
- FROM commandes
- WHERE livreur_assign = $1`
-
- var count int
- err := d.QueryRow(query, livreurUsername).Scan(&count)
+ var result struct {
+ Count int `gorm:"column:count"`
+ }
+ err := d.GDB.Raw(`SELECT COUNT(*) as count FROM commandes WHERE livreur_assign = ?`, livreurUsername).Scan(&result).Error
if err != nil {
log.Printf("❌ [CountDeliveries] Erreur: %v", err)
return 0, fmt.Errorf("erreur comptage livraisons: %w", err)
}
- return count, nil
+ return result.Count, nil
}
- // ✅ Valider les statuts
cleanStatuses, err := ValidateStatuses(statuses)
if err != nil {
log.Printf("❌ [CountDeliveries] Validation échouée: %v", err)
return 0, err
}
- // ✅ Construire la requête avec IN et placeholders
- placeholders := make([]string, len(cleanStatuses))
- args := []interface{}{livreurUsername}
-
- for i, status := range cleanStatuses {
- placeholders[i] = fmt.Sprintf("$%d", i+2)
- args = append(args, status)
+ var result struct {
+ Count int `gorm:"column:count"`
}
-
- query := fmt.Sprintf(`SELECT COUNT(*)
- FROM commandes
- WHERE livreur_assign = $1
- AND status IN (%s)`, strings.Join(placeholders, ","))
-
- var count int
- err = d.QueryRow(query, args...).Scan(&count)
+ err = d.GDB.Raw(
+ `SELECT COUNT(*) as count FROM commandes WHERE livreur_assign = ? AND status IN ?`,
+ livreurUsername, cleanStatuses,
+ ).Scan(&result).Error
if err != nil {
log.Printf("❌ [CountDeliveries] Erreur: %v", err)
return 0, fmt.Errorf("erreur comptage livraisons: %w", err)
}
log.Printf("✅ [CountDeliveries] %d livraisons pour %s avec statuts %v",
- count, livreurUsername, cleanStatuses)
-
- return count, nil
+ result.Count, livreurUsername, cleanStatuses)
+ return result.Count, nil
}
func ValidateStatuses(statuses string) ([]string, error) {
@@ -77,7 +62,6 @@ func ValidateStatuses(statuses string) ([]string, error) {
statusList := strings.Split(statuses, ",")
- // Liste blanche complète
validStatusMap := map[string]bool{
"pending": true,
"assigned": true,
@@ -93,12 +77,9 @@ func ValidateStatuses(statuses string) ([]string, error) {
for _, status := range statusList {
status = strings.TrimSpace(status)
-
- // Ignorer les chaînes vides
if status == "" {
continue
}
-
if validStatusMap[status] {
cleanStatuses = append(cleanStatuses, status)
} else {
@@ -106,7 +87,6 @@ func ValidateStatuses(statuses string) ([]string, error) {
}
}
- // Logger les statuts invalides
if len(invalidStatuses) > 0 {
log.Printf("⚠️ [ValidateStatuses] Statuts invalides ignorés: %v", invalidStatuses)
}
@@ -120,32 +100,25 @@ func ValidateStatuses(statuses string) ([]string, error) {
// GetLastDeliveryDate récupère la date de la dernière livraison d'un livreur
func (d *Database) GetLastDeliveryDate(livreurUsername string) (*time.Time, error) {
- query := `SELECT MAX(updated_at)
- FROM commandes
- WHERE livreur_assign = $1
- AND status = 'approved'`
-
- var lastDate sql.NullTime
- err := d.QueryRow(query, livreurUsername).Scan(&lastDate)
+ var result struct {
+ LastDate *time.Time `gorm:"column:last_date"`
+ }
+ err := d.GDB.Raw(`
+ SELECT MAX(updated_at) as last_date
+ FROM commandes
+ WHERE livreur_assign = ? AND status = 'approved'`, livreurUsername).Scan(&result).Error
if err != nil {
return nil, fmt.Errorf("erreur récupération dernière livraison: %w", err)
}
-
- if !lastDate.Valid {
- return nil, nil // Aucune livraison
- }
-
- t := lastDate.Time
- return &t, nil
+ return result.LastDate, nil
}
// GetCurrentCommand récupère l'ID de la commande en cours d'un livreur
func (d *Database) GetCurrentCommand(livreurUsername string) (int, error) {
- // Récupérer depuis Redis
currentKey := fmt.Sprintf("delivery:current:%s", livreurUsername)
currentIDStr, err := Redis.Get(RedisCtx, currentKey).Result()
if err != nil {
- return 0, nil // Pas de commande en cours
+ return 0, nil
}
var currentID int
@@ -157,93 +130,40 @@ func (d *Database) GetCurrentCommand(livreurUsername string) (int, error) {
return currentID, nil
}
-// ============================================
-// 📜 HISTORIQUE DES LIVRAISONS
-// ============================================
-
// GetDeliveryPersonHistory récupère l'historique paginé des livraisons d'un livreur
-func (d *Database) GetDeliveryPersonHistory(livreurUsername string, limit, offset int) ([]map[string]interface{}, error) {
- query := `
+func (d *Database) GetDeliveryPersonHistory(livreurUsername string, limit, offset int) ([]map[string]any, error) {
+ var history []map[string]any
+ err := d.GDB.Raw(`
SELECT
c.id as command_id,
c.username as client,
c.status,
c.adresse,
- c.total_prix,
+ c.total_prix::float8 as total_prix,
c.created_at as assigned_at,
c.updated_at as completed_at
FROM commandes c
- WHERE c.livreur_assign = $1
+ WHERE c.livreur_assign = ?
ORDER BY c.created_at DESC
- LIMIT $2 OFFSET $3
- `
-
- rows, err := d.Query(query, livreurUsername, limit, offset)
+ LIMIT ? OFFSET ?`, livreurUsername, limit, offset).Scan(&history).Error
if err != nil {
return nil, fmt.Errorf("erreur récupération historique: %w", err)
}
- defer rows.Close()
-
- var history []map[string]interface{}
- for rows.Next() {
- var commandID int
- var client, status, adresse string
- var totalPrix float64
- var assignedAt, completedAt time.Time
-
- err := rows.Scan(
- &commandID,
- &client,
- &status,
- &adresse,
- &totalPrix,
- &assignedAt,
- &completedAt,
- )
- if err != nil {
- return nil, fmt.Errorf("erreur scan historique: %w", err)
- }
-
- // Calculer la durée de livraison si complétée
- var deliveryTime float64
- if status == "approved" || status == "livre" {
- deliveryTime = completedAt.Sub(assignedAt).Minutes()
- }
-
- entry := map[string]interface{}{
- "command_id": commandID,
- "client": client,
- "status": status,
- "adresse": adresse,
- "total_prix": totalPrix,
- "assigned_at": assignedAt.Format("2006-01-02 15:04:05"),
- "completed_at": completedAt.Format("2006-01-02 15:04:05"),
- "delivery_time": deliveryTime,
- }
-
- history = append(history, entry)
- }
-
return history, nil
}
// GetDeliveryPersonStatus récupère le statut d'un livreur
func (d *Database) GetDeliveryPersonStatus(livreurUsername string) (string, error) {
statusKey := fmt.Sprintf("delivery:status:%s", livreurUsername)
-
status, err := Redis.Get(RedisCtx, statusKey).Result()
if err != nil {
- // Statut par défaut si non trouvé
return "offline", nil
}
-
return status, nil
}
// UpdateDeliveryPersonStatus met à jour le statut d'un livreur
-
func (d *Database) UpdateDeliveryPersonStatus(livreurUsername string, status string) error {
- // 🔹 Valider le statut
if !allowedStatuses[status] {
return fmt.Errorf("statut invalide: %s", status)
}
@@ -251,9 +171,7 @@ func (d *Database) UpdateDeliveryPersonStatus(livreurUsername string, status str
log.Printf("🔄 [UpdateStatus] Mise à jour: %s → %s", livreurUsername, status)
statusKey := fmt.Sprintf("delivery:status:%s", livreurUsername)
-
- err := Redis.Set(RedisCtx, statusKey, status, 0).Err()
- if err != nil {
+ if err := Redis.Set(RedisCtx, statusKey, status, 0).Err(); err != nil {
log.Printf("❌ [UpdateStatus] Erreur Redis: %v", err)
return fmt.Errorf("erreur mise à jour statut: %w", err)
}
@@ -265,19 +183,16 @@ func (d *Database) UpdateDeliveryPersonStatus(livreurUsername string, status str
// GetDeliverymanQueueSize récupère la taille de la queue d'un livreur
func (d *Database) GetDeliverymanQueueSize(livreurUsername string) (int, error) {
queueKey := fmt.Sprintf("delivery:queue:%s", livreurUsername)
-
size, err := Redis.LLen(RedisCtx, queueKey).Result()
if err != nil {
return 0, fmt.Errorf("erreur récupération taille queue: %w", err)
}
-
return int(size), nil
}
// GetDeliverymanQueue récupère la queue complète d'un livreur
func (d *Database) GetDeliverymanQueue(livreurUsername string) ([]int, error) {
queueKey := fmt.Sprintf("delivery:queue:%s", livreurUsername)
-
commands, err := Redis.LRange(RedisCtx, queueKey, 0, -1).Result()
if err != nil {
return nil, fmt.Errorf("erreur récupération queue: %w", err)
@@ -289,84 +204,66 @@ func (d *Database) GetDeliverymanQueue(livreurUsername string) ([]int, error) {
fmt.Sscanf(cmdStr, "%d", &cmdID)
queue = append(queue, cmdID)
}
-
return queue, nil
}
// UpdateCommandLivreur met à jour le livreur assigné à une commande
func (d *Database) UpdateCommandLivreur(commandID int, livreurUsername string) error {
- query := `UPDATE commandes
- SET livreur_assign = $1, updated_at = CURRENT_TIMESTAMP
- WHERE id = $2`
-
- result, err := d.Exec(query, livreurUsername, commandID)
- if err != nil {
- return fmt.Errorf("erreur mise à jour livreur: %w", err)
+ result := d.GDB.Exec(`
+ UPDATE commandes
+ SET livreur_assign = ?, updated_at = CURRENT_TIMESTAMP
+ WHERE id = ?`, livreurUsername, commandID)
+ if result.Error != nil {
+ return fmt.Errorf("erreur mise à jour livreur: %w", result.Error)
}
-
- rowsAffected, err := result.RowsAffected()
- if err != nil {
- return fmt.Errorf("erreur vérification: %w", err)
- }
- if rowsAffected == 0 {
+ if result.RowsAffected == 0 {
return fmt.Errorf("commande non trouvée")
}
-
return nil
}
// GetAllDeliveryPersonsStats récupère les stats de tous les livreurs
func (d *Database) GetAllDeliveryPersonsStats() ([]map[string]any, error) {
- // Récupérer tous les livreurs
livreurs, err := d.GetAvailableDeliveryPersons()
if err != nil {
return nil, fmt.Errorf("erreur récupération livreurs: %w", err)
}
var stats []map[string]any
-
for _, livreur := range livreurs {
username := livreur["username"].(string)
- // Compter les livraisons
totalDeliveries, _ := d.CountDeliveriesByStatus(username, "")
completedDeliveries, _ := d.CountDeliveriesByStatus(username, "approved")
queueSize, _ := d.GetDeliverymanQueueSize(username)
status, _ := d.GetDeliveryPersonStatus(username)
- statEntry := map[string]any{
+ stats = append(stats, map[string]any{
"username": username,
"total_deliveries": totalDeliveries,
"completed_deliveries": completedDeliveries,
"queue_size": queueSize,
"status": status,
- }
-
- stats = append(stats, statEntry)
+ })
}
-
return stats, nil
}
// GetDeliveryPersonsByStatus récupère les livreurs par statut
func (d *Database) GetDeliveryPersonsByStatus(status string) ([]string, error) {
- // Récupérer tous les livreurs
livreurs, err := d.GetAvailableDeliveryPersons()
if err != nil {
return nil, fmt.Errorf("erreur récupération livreurs: %w", err)
}
var filteredLivreurs []string
-
for _, livreur := range livreurs {
username := livreur["username"].(string)
currentStatus, _ := d.GetDeliveryPersonStatus(username)
-
if currentStatus == status {
filteredLivreurs = append(filteredLivreurs, username)
}
}
-
return filteredLivreurs, nil
}
@@ -376,7 +273,6 @@ func (d *Database) GetAvailableDeliveryPersonsCount() (int, error) {
if err != nil {
return 0, err
}
-
return len(availableLivreurs), nil
}
@@ -384,7 +280,6 @@ func (d *Database) GetAvailableDeliveryPersonsCount() (int, error) {
func (d *Database) ClearDeliveryPersonData(livreurUsername string) error {
log.Printf("🗑️ [ClearDeliveryData] Nettoyage données pour: %s", livreurUsername)
- // Supprimer de Redis
keys := []string{
fmt.Sprintf("delivery:status:%s", livreurUsername),
fmt.Sprintf("delivery:location:%s", livreurUsername),
@@ -394,8 +289,7 @@ func (d *Database) ClearDeliveryPersonData(livreurUsername string) error {
}
for _, key := range keys {
- err := Redis.Del(RedisCtx, key).Err()
- if err != nil {
+ if err := Redis.Del(RedisCtx, key).Err(); err != nil {
log.Printf("⚠️ [ClearDeliveryData] Erreur suppression clé %s: %v", key, err)
}
}
diff --git a/backend/gestion/db/db_delivery_mode.go b/backend/gestion/db/db_delivery_mode.go
index d880f369..87c23763 100644
--- a/backend/gestion/db/db_delivery_mode.go
+++ b/backend/gestion/db/db_delivery_mode.go
@@ -5,34 +5,21 @@ import (
"log"
)
-// GetCommandCategories retourne les catégories distinctes des produits d'une commande
func (d *Database) GetCommandCategories(commandID int) ([]string, error) {
- query := `
+ var categories []string
+ err := d.GDB.Raw(`
SELECT DISTINCT p.category
FROM command_items ci
JOIN products p ON p.id = ci.product_id
- WHERE ci.command_id = $1 AND p.category IS NOT NULL AND p.category != ''`
-
- rows, err := d.Query(query, commandID)
+ WHERE ci.command_id = ? AND p.category IS NOT NULL AND p.category != ''`,
+ commandID,
+ ).Scan(&categories).Error
if err != nil {
return nil, fmt.Errorf("erreur lecture catégories commande %d: %w", commandID, err)
}
- defer rows.Close()
-
- var categories []string
- for rows.Next() {
- var cat string
- if err := rows.Scan(&cat); err == nil {
- categories = append(categories, cat)
- }
- }
- return categories, rows.Err()
+ return categories, nil
}
-// GetEligibleDeliverymenForCommand retourne les usernames des livreurs éligibles pour une commande.
-// En mode "single" → tous les livreurs actifs.
-// En mode "category_based" → le(s) livreur(s) assigné(s) aux catégories de la commande.
-// Si aucune correspondance → fallback sur tous les livreurs actifs.
func (d *Database) GetEligibleDeliverymenForCommand(commandID int) ([]string, error) {
settings, err := d.GetSettings()
if err != nil {
@@ -55,20 +42,17 @@ func (d *Database) GetEligibleDeliverymenForCommand(commandID int) ([]string, er
return allActive()
}
- // Mode category_based : récupérer les catégories de la commande
categories, err := d.GetCommandCategories(commandID)
if err != nil || len(categories) == 0 {
log.Printf("⚠️ [DELIVERY_MODE] Cmd %d: catégories non trouvées — fallback single", commandID)
return allActive()
}
- // Construire un set des catégories de la commande
catSet := make(map[string]bool, len(categories))
for _, c := range categories {
catSet[c] = true
}
- // Trouver les livreurs dont la route intersecte les catégories
eligible := make(map[string]bool)
for _, route := range settings.DeliveryMode.CategoryRoutes {
for _, routeCat := range route.Categories {
diff --git a/backend/gestion/db/db_history.go b/backend/gestion/db/db_history.go
index 6c77dfcd..70d8c437 100644
--- a/backend/gestion/db/db_history.go
+++ b/backend/gestion/db/db_history.go
@@ -6,11 +6,10 @@
package db
import (
- "database/sql"
"fmt"
"log"
"maps"
- "time"
+ "strconv"
)
// GetCompletedCommandsByUsername récupère toutes les commandes terminées (approved) d'un utilisateur
@@ -18,73 +17,24 @@ func (d *Database) GetCompletedCommandsByUsername(username string) ([]map[string
query := `
SELECT
id,
+ client_order_id AS client_order_number,
username,
status,
adresse,
- total_prix,
+ total_prix::float8 as total_prix,
livreur_assign,
created_at,
updated_at
FROM commandes
- WHERE username = $1 AND status = 'approved'
+ WHERE username = ? AND status = 'approved'
ORDER BY created_at DESC
`
- rows, err := d.Query(query, username)
- if err != nil {
+ var commands []map[string]any
+ if err := d.GDB.Raw(query, username).Scan(&commands).Error; err != nil {
log.Printf("❌ [GetCompletedCommands] Erreur query: %v", err)
return nil, fmt.Errorf("erreur lors de la récupération des commandes terminées: %w", err)
}
- defer rows.Close()
-
- var commands []map[string]any
-
- for rows.Next() {
- var id int
- var username, status, adresse string
- var livreurAssign sql.NullString
- var totalPrix float64
- var createdAt, updatedAt time.Time
-
- err := rows.Scan(
- &id,
- &username,
- &status,
- &adresse,
- &totalPrix,
- &livreurAssign,
- &createdAt,
- &updatedAt,
- )
- if err != nil {
- log.Printf("❌ [GetCompletedCommands] Erreur scan: %v", err)
- return nil, fmt.Errorf("erreur lors du scan de la commande: %w", err)
- }
-
- command := map[string]any{
- "id": id,
- "username": username,
- "status": status,
- "adresse": adresse,
- "total_prix": totalPrix,
- "created_at": createdAt,
- "updated_at": updatedAt,
- }
-
- if livreurAssign.Valid {
- command["livreur_assign"] = livreurAssign.String
- } else {
- command["livreur_assign"] = nil
- }
-
- commands = append(commands, command)
- }
-
- if err = rows.Err(); err != nil {
- log.Printf("❌ [GetCompletedCommands] Erreur itération: %v", err)
- return nil, fmt.Errorf("erreur lors de l'itération des résultats: %w", err)
- }
-
return commands, nil
}
@@ -98,12 +48,11 @@ func (d *Database) GetCompletedCommandsWithItems(username string) ([]map[string]
var enrichedCommands []map[string]any
for _, command := range commands {
- commandID, ok := command["id"].(int)
- if !ok {
+ commandID, _ := strconv.Atoi(fmt.Sprintf("%v", command["id"]))
+ if commandID == 0 {
continue
}
- // Récupérer les items
items, err := d.GetCommandItems(commandID)
if err != nil {
log.Printf("⚠️ [GetCompletedWithItems] Erreur items pour cmd %d: %v", commandID, err)
@@ -134,39 +83,16 @@ func (d *Database) GetCommandsStatsByUsername(username string) (map[string]any,
COUNT(*) as total_count,
COALESCE(SUM(total_prix) FILTER (WHERE status = 'approved'), 0) as total_spent
FROM commandes
- WHERE username = $1
+ WHERE username = ?
`
- var approvedCount, pendingCount, assignedCount, enRouteCount, livreCount, cancelledCount, totalCount int
- var totalSpent float64
-
- err := d.QueryRow(query, username).Scan(
- &approvedCount,
- &pendingCount,
- &assignedCount,
- &enRouteCount,
- &livreCount,
- &cancelledCount,
- &totalCount,
- &totalSpent,
- )
- if err != nil {
+ var result map[string]any
+ if err := d.GDB.Raw(query, username).Scan(&result).Error; err != nil {
log.Printf("❌ [GetCommandsStats] Erreur: %v", err)
return nil, fmt.Errorf("erreur lors de la récupération des statistiques: %w", err)
}
- stats := map[string]any{
- "approved_count": approvedCount,
- "pending_count": pendingCount,
- "assigned_count": assignedCount,
- "en_route_count": enRouteCount,
- "livre_count": livreCount,
- "cancelled_count": cancelledCount,
- "total_count": totalCount,
- "total_spent": totalSpent,
- }
-
- return stats, nil
+ return result, nil
}
// GetCommandsByStatus récupère les commandes d'un utilisateur par statut
@@ -176,145 +102,49 @@ func (d *Database) GetCommandsByStatus(username, status string) ([]map[string]an
query := `
SELECT
id,
+ client_order_id AS client_order_number,
username,
status,
adresse,
- total_prix,
+ total_prix::float8 as total_prix,
livreur_assign,
created_at,
updated_at
FROM commandes
- WHERE username = $1 AND status = $2
+ WHERE username = ? AND status = ?
ORDER BY created_at DESC
`
- rows, err := d.Query(query, username, status)
- if err != nil {
- return nil, fmt.Errorf("erreur lors de la récupération des commandes: %w", err)
- }
- defer rows.Close()
-
var commands []map[string]any
-
- for rows.Next() {
- var id int
- var username, status, adresse string
- var livreurAssign sql.NullString
- var totalPrix float64
- var createdAt, updatedAt time.Time
-
- err := rows.Scan(
- &id,
- &username,
- &status,
- &adresse,
- &totalPrix,
- &livreurAssign,
- &createdAt,
- &updatedAt,
- )
- if err != nil {
- log.Printf("❌ [GetCommandsByStatus] Erreur scan: %v", err)
- return nil, fmt.Errorf("erreur lors du scan: %w", err)
- }
-
- command := map[string]any{
- "id": id,
- "username": username,
- "status": status,
- "adresse": adresse,
- "total_prix": totalPrix,
- "created_at": createdAt,
- "updated_at": updatedAt,
- }
-
- if livreurAssign.Valid {
- command["livreur_assign"] = livreurAssign.String
- } else {
- command["livreur_assign"] = nil
- }
-
- commands = append(commands, command)
- }
-
- if err = rows.Err(); err != nil {
- return nil, fmt.Errorf("erreur lors de l'itération: %w", err)
+ if err := d.GDB.Raw(query, username, status).Scan(&commands).Error; err != nil {
+ return nil, fmt.Errorf("erreur lors de la récupération des commandes: %w", err)
}
return commands, nil
}
// GetRecentCompletedOrders récupère les N dernières commandes terminées d'un utilisateur
func (d *Database) GetRecentCompletedOrders(username string, limit int) ([]map[string]any, error) {
-
query := `
SELECT
id,
+ client_order_id AS client_order_number,
username,
status,
adresse,
- total_prix,
+ total_prix::float8 as total_prix,
livreur_assign,
created_at,
updated_at
FROM commandes
- WHERE username = $1 AND status = 'approved'
+ WHERE username = ? AND status = 'approved'
ORDER BY created_at DESC
- LIMIT $2
+ LIMIT ?
`
- rows, err := d.Query(query, username, limit)
- if err != nil {
+ var commands []map[string]any
+ if err := d.GDB.Raw(query, username, limit).Scan(&commands).Error; err != nil {
log.Printf("❌ [GetRecentCompleted] Erreur query: %v", err)
return nil, fmt.Errorf("erreur lors de la récupération: %w", err)
}
- defer rows.Close()
-
- var commands []map[string]any
-
- for rows.Next() {
- var id int
- var username, status, adresse string
- var livreurAssign sql.NullString
- var totalPrix float64
- var createdAt, updatedAt time.Time
-
- err := rows.Scan(
- &id,
- &username,
- &status,
- &adresse,
- &totalPrix,
- &livreurAssign,
- &createdAt,
- &updatedAt,
- )
- if err != nil {
- log.Printf("❌ [GetRecentCompleted] Erreur scan: %v", err)
- return nil, fmt.Errorf("erreur lors du scan: %w", err)
- }
-
- command := map[string]any{
- "id": id,
- "username": username,
- "status": status,
- "adresse": adresse,
- "total_prix": totalPrix,
- "created_at": createdAt,
- "updated_at": updatedAt,
- }
-
- if livreurAssign.Valid {
- command["livreur_assign"] = livreurAssign.String
- } else {
- command["livreur_assign"] = nil
- }
-
- commands = append(commands, command)
- }
-
- if err = rows.Err(); err != nil {
- return nil, fmt.Errorf("erreur lors de l'itération: %w", err)
- }
-
return commands, nil
}
diff --git a/backend/gestion/db/db_init.go b/backend/gestion/db/db_init.go
index 7b021b5e..9468c5c6 100644
--- a/backend/gestion/db/db_init.go
+++ b/backend/gestion/db/db_init.go
@@ -8,6 +8,9 @@ import (
"time"
_ "github.com/lib/pq"
+ "gorm.io/driver/postgres"
+ "gorm.io/gorm"
+ "gorm.io/gorm/logger"
)
// ============================================
@@ -17,6 +20,7 @@ import (
// Database encapsule la connexion à la base de données
type Database struct {
*sql.DB
+ GDB *gorm.DB
}
// DB est l'instance globale de la base de données
@@ -58,8 +62,18 @@ func InitDB() *Database {
log.Println("✅ Connexion à PostgreSQL établie avec succès")
+ // Initialiser GORM en réutilisant la connexion sql.DB existante
+ gormDB, err := gorm.Open(postgres.New(postgres.Config{
+ Conn: db,
+ }), &gorm.Config{
+ Logger: logger.Default.LogMode(logger.Silent),
+ })
+ if err != nil {
+ log.Fatalf("❌ Erreur initialisation GORM: %v", err)
+ }
+
// Créer l'instance Database
- database := &Database{db}
+ database := &Database{db, gormDB}
// Assigner à la variable globale
DB = database
@@ -76,16 +90,6 @@ func InitDB() *Database {
log.Fatalf("❌ Erreur migration must_change_password: %v", err)
}
- // Migration: ajouter colonne push_token pour les notifications push (clients)
- if _, err = database.Exec(`ALTER TABLE clients ADD COLUMN IF NOT EXISTS push_token TEXT`); err != nil {
- log.Fatalf("❌ Erreur migration push_token: %v", err)
- }
-
- // Migration: ajouter colonne push_token pour les notifications push (livreurs/users)
- if _, err = database.Exec(`ALTER TABLE users ADD COLUMN IF NOT EXISTS push_token TEXT`); err != nil {
- log.Fatalf("❌ Erreur migration push_token users: %v", err)
- }
-
// Migration: proposition de modification d'adresse par admin/cabine
if _, err = database.Exec(`ALTER TABLE commandes ADD COLUMN IF NOT EXISTS proposed_address TEXT`); err != nil {
log.Fatalf("❌ Erreur migration proposed_address: %v", err)
@@ -166,6 +170,23 @@ func InitDB() *Database {
log.Fatalf("❌ Erreur migration commandes.payment_method: %v", err)
}
+ // Migration: identifiant de commande par client (numérotation indépendante par client, commence à 1)
+ if _, err = database.Exec(`ALTER TABLE commandes ADD COLUMN IF NOT EXISTS client_order_id INTEGER NOT NULL DEFAULT 0`); err != nil {
+ log.Fatalf("❌ Erreur migration commandes.client_order_id: %v", err)
+ }
+ // Backfill: numéroter les commandes existantes par client dans l'ordre d'insertion
+ if _, err = database.Exec(`
+ UPDATE commandes c
+ SET client_order_id = sub.rn
+ FROM (
+ SELECT id, ROW_NUMBER() OVER (PARTITION BY username ORDER BY id) AS rn
+ FROM commandes
+ ) sub
+ WHERE c.id = sub.id AND c.client_order_id = 0
+ `); err != nil {
+ log.Fatalf("❌ Erreur backfill commandes.client_order_id: %v", err)
+ }
+
// Migration: table de suivi des paiements crypto
if _, err = database.Exec(`
CREATE TABLE IF NOT EXISTS crypto_payments (
diff --git a/backend/gestion/db/db_jwt_tokens.go b/backend/gestion/db/db_jwt_tokens.go
index 640de53a..30bcc746 100644
--- a/backend/gestion/db/db_jwt_tokens.go
+++ b/backend/gestion/db/db_jwt_tokens.go
@@ -1,167 +1,127 @@
package db
import (
- "database/sql"
"fmt"
"log"
"time"
+
+ "gorm.io/gorm"
)
-func (d *Database) SaveToken(userID int, userType string, token string, expiresAt time.Time) error {
- // Valider le user_type
- validTypes := map[string]bool{
- "client": true,
- "admin": true,
- "cabine": true,
- "livreur": true,
- }
+type jwtToken struct {
+ ID int `gorm:"primaryKey;autoIncrement"`
+ UserID int `gorm:"column:user_id;index"`
+ UserType string `gorm:"column:user_type"`
+ Token string `gorm:"column:token;uniqueIndex"`
+ DateSave time.Time `gorm:"column:date_save;autoCreateTime"`
+ DateFin time.Time `gorm:"column:date_fin"`
+}
- if !validTypes[userType] {
+func (jwtToken) TableName() string { return "jwt_tokens" }
+
+var validTokenTypes = map[string]bool{
+ "client": true, "admin": true, "cabine": true, "livreur": true,
+}
+
+func (d *Database) SaveToken(userID int, userType string, token string, expiresAt time.Time) error {
+ if !validTokenTypes[userType] {
return fmt.Errorf("type d'utilisateur invalide: %s", userType)
}
-
- query := `INSERT INTO jwt_tokens (user_id, user_type, token, date_save, date_fin)
- VALUES ($1, $2, $3, CURRENT_TIMESTAMP, $4)`
-
- _, err := d.Exec(query, userID, userType, token, expiresAt)
- if err != nil {
+ t := jwtToken{UserID: userID, UserType: userType, Token: token, DateFin: expiresAt}
+ if err := d.GDB.Create(&t).Error; err != nil {
return fmt.Errorf("erreur lors de l'enregistrement du token: %w", err)
}
-
log.Printf("✅ Token enregistré pour %s ID: %d", userType, userID)
return nil
}
-// IsTokenValid vérifie si un token existe et n'est pas expiré
func (d *Database) IsTokenValid(token string) (bool, error) {
- query := `SELECT COUNT(*) FROM jwt_tokens
- WHERE token = $1 AND date_fin > $2`
-
- var count int
- err := d.QueryRow(query, token, time.Now()).Scan(&count)
- if err != nil {
- return false, fmt.Errorf("erreur lors de la vérification du token: %w", err)
- }
-
- return count > 0, nil
+ var count int64
+ err := d.GDB.Model(&jwtToken{}).
+ Where("token = ? AND date_fin > ?", token, time.Now()).
+ Count(&count).Error
+ return count > 0, err
}
-// RevokeToken révoque un token (le supprime de la base)
func (d *Database) RevokeToken(token string) error {
- query := `DELETE FROM jwt_tokens WHERE token = $1`
-
- result, err := d.Exec(query, token)
- if err != nil {
- return fmt.Errorf("erreur lors de la révocation du token: %w", err)
+ result := d.GDB.Where("token = ?", token).Delete(&jwtToken{})
+ if result.Error != nil {
+ return fmt.Errorf("erreur lors de la révocation du token: %w", result.Error)
}
-
- rowsAffected, _ := result.RowsAffected()
- if rowsAffected > 0 {
+ if result.RowsAffected > 0 {
log.Printf("✅ Token révoqué avec succès")
}
-
return nil
}
-// RevokeAllUserTokens révoque tous les tokens d'un utilisateur
func (d *Database) RevokeAllUserTokens(userID int, userType string) error {
- query := `DELETE FROM jwt_tokens WHERE user_id = $1 AND user_type = $2`
-
- result, err := d.Exec(query, userID, userType)
- if err != nil {
- return fmt.Errorf("erreur lors de la révocation des tokens: %w", err)
+ result := d.GDB.Where("user_id = ? AND user_type = ?", userID, userType).Delete(&jwtToken{})
+ if result.Error != nil {
+ return fmt.Errorf("erreur lors de la révocation des tokens: %w", result.Error)
}
-
- rowsAffected, _ := result.RowsAffected()
- log.Printf("✅ %d token(s) révoqué(s) pour %s ID: %d", rowsAffected, userType, userID)
-
+ log.Printf("✅ %d token(s) révoqué(s) pour %s ID: %d", result.RowsAffected, userType, userID)
return nil
}
-// GetUserActiveTokens récupère tous les tokens actifs d'un utilisateur
func (d *Database) GetUserActiveTokens(userID int, userType string) ([]map[string]any, error) {
- query := `SELECT id, token, date_save, date_fin
- FROM jwt_tokens
- WHERE user_id = $1 AND user_type = $2 AND date_fin > $3
- ORDER BY date_save DESC`
-
- rows, err := d.Query(query, userID, userType, time.Now())
+ var tokens []jwtToken
+ err := d.GDB.Where("user_id = ? AND user_type = ? AND date_fin > ?", userID, userType, time.Now()).
+ Order("date_save DESC").Find(&tokens).Error
if err != nil {
return nil, fmt.Errorf("erreur lors de la récupération des tokens: %w", err)
}
- defer rows.Close()
- var tokens []map[string]any
- for rows.Next() {
- var id int
- var token string
- var dateSave, dateFin time.Time
-
- err := rows.Scan(&id, &token, &dateSave, &dateFin)
- if err != nil {
- return nil, fmt.Errorf("erreur lors du scan: %w", err)
+ result := make([]map[string]any, len(tokens))
+ for i, t := range tokens {
+ truncated := t.Token
+ if len(truncated) > 20 {
+ truncated = truncated[:20] + "..."
+ }
+ result[i] = map[string]any{
+ "id": t.ID,
+ "token": truncated,
+ "date_save": t.DateSave,
+ "date_fin": t.DateFin,
+ "user_type": t.UserType,
}
-
- tokens = append(tokens, map[string]any{
- "id": id,
- "token": token[:20] + "...", // Tronquer pour la sécurité
- "date_save": dateSave,
- "date_fin": dateFin,
- "user_type": userType,
- })
}
-
- return tokens, nil
+ return result, nil
}
func (d *Database) GetTokenInfo(token string) (map[string]any, error) {
- query := `SELECT user_id, user_type, date_save, date_fin
- FROM jwt_tokens
- WHERE token = $1 AND date_fin > $2`
-
- var userID int
- var userType string
- var dateSave, dateFin time.Time
-
- err := d.QueryRow(query, token, time.Now()).Scan(&userID, &userType, &dateSave, &dateFin)
- if err == sql.ErrNoRows {
- return nil, fmt.Errorf("token non trouvé ou expiré")
- }
+ var t jwtToken
+ err := d.GDB.Where("token = ? AND date_fin > ?", token, time.Now()).First(&t).Error
if err != nil {
+ if err == gorm.ErrRecordNotFound {
+ return nil, fmt.Errorf("token non trouvé ou expiré")
+ }
return nil, fmt.Errorf("erreur lors de la récupération des infos du token: %w", err)
}
-
- tokenInfo := map[string]any{
- "user_id": userID,
- "user_type": userType,
- "date_save": dateSave,
- "date_fin": dateFin,
- }
-
- return tokenInfo, nil
+ return map[string]any{
+ "user_id": t.UserID,
+ "user_type": t.UserType,
+ "date_save": t.DateSave,
+ "date_fin": t.DateFin,
+ }, nil
}
func (d *Database) CountActiveTokensByType() (map[string]int, error) {
- query := `SELECT user_type, COUNT(*) as count
- FROM jwt_tokens
- WHERE date_fin > $1
- GROUP BY user_type`
-
- rows, err := d.Query(query, time.Now())
+ type row struct {
+ UserType string
+ Count int
+ }
+ var rows []row
+ err := d.GDB.Model(&jwtToken{}).
+ Select("user_type, COUNT(*) as count").
+ Where("date_fin > ?", time.Now()).
+ Group("user_type").
+ Scan(&rows).Error
if err != nil {
return nil, fmt.Errorf("erreur lors du comptage des tokens: %w", err)
}
- defer rows.Close()
-
- counts := make(map[string]int)
- for rows.Next() {
- var userType string
- var count int
- if err := rows.Scan(&userType, &count); err != nil {
- return nil, err
- }
- counts[userType] = count
+ counts := make(map[string]int, len(rows))
+ for _, r := range rows {
+ counts[r.UserType] = r.Count
}
-
return counts, nil
}
diff --git a/backend/gestion/db/db_media.go b/backend/gestion/db/db_media.go
index 54140cf0..60548f18 100644
--- a/backend/gestion/db/db_media.go
+++ b/backend/gestion/db/db_media.go
@@ -1,7 +1,6 @@
package db
import (
- "database/sql"
"fmt"
"gestion/models"
"log"
@@ -42,7 +41,6 @@ func validateProductID(productID int) error {
// validateMediaType vérifie le type de média
func validateMediaType(mediaType string) error {
validTypes := []string{"image", "video"}
-
mediaType = strings.ToLower(strings.TrimSpace(mediaType))
if slices.Contains(validTypes, mediaType) {
return nil
@@ -55,46 +53,29 @@ func validateMediaURL(url string) error {
if len(url) == 0 {
return fmt.Errorf("URL vide")
}
-
if len(url) > 500 {
return fmt.Errorf("URL trop longue (max 500 caractères)")
}
-
- // ✅ PROTECTION PATH TRAVERSAL
if strings.Contains(url, "..") || strings.Contains(url, "...") || strings.Contains(url, "..//") {
return fmt.Errorf("path traversal détecté dans l'URL")
}
-
- // ✅ VÉRIFIER QUE L'URL COMMENCE PAR /uploads/
if !strings.HasPrefix(url, "/uploads/") {
return fmt.Errorf("URL doit commencer par /uploads/")
}
-
- // ✅ VÉRIFIER QU'IL N'Y A PAS DE CARACTÈRES DANGEREUX
- dangerousChars := []string{
- "<", ">", "\"", "'", ";", "|", "&", "$", "`", "\\",
- }
-
+ dangerousChars := []string{"<", ">", "\"", "'", ";", "|", "&", "$", "`", "\\"}
for _, char := range dangerousChars {
if strings.Contains(url, char) {
return fmt.Errorf("caractères interdits dans l'URL")
}
}
-
return nil
}
-// ============================================
-// CREATE MEDIA - VERSION SÉCURISÉE
-// ============================================
-
-func (db *Database) CreateMedia(media any) error {
+func (d *Database) CreateMedia(media any) error {
log.Printf("🔒 [CreateMedia] START - Type: %T", media)
- // ✅ TYPE ASSERTION SÉCURISÉE
m, ok := media.(MediaInterface)
if !ok {
- // Vérifier si c'est un pointeur vers models.Media
if mediaPtr, isPtr := media.(*models.Media); isPtr {
m = mediaPtr
ok = true
@@ -116,29 +97,25 @@ func (db *Database) CreateMedia(media any) error {
log.Printf("❌ [CreateMedia] %v", err)
return err
}
-
if err := validateMediaType(mediaType); err != nil {
log.Printf("❌ [CreateMedia] %v", err)
return err
}
-
if err := validateMediaURL(mediaURL); err != nil {
log.Printf("❌ [CreateMedia] %v", err)
return err
}
- if err := db.InsertMedia(m, productID, mediaURL, mediaType); err != nil {
+ if err := d.InsertMedia(m, productID, mediaURL, mediaType); err != nil {
log.Printf("❌ [InsertMedia] %v", err)
return err
}
return nil
}
-func (db *Database) InsertMedia(m any, productID int, mediaURL any, mediaType string) error {
+func (d *Database) InsertMedia(m any, productID int, mediaURL any, mediaType string) error {
var exists bool
- checkQuery := `SELECT EXISTS(SELECT 1 FROM products WHERE id = $1)`
- err := db.QueryRow(checkQuery, productID).Scan(&exists)
- if err != nil {
+ if err := d.GDB.Raw(`SELECT EXISTS(SELECT 1 FROM products WHERE id = ?)`, productID).Scan(&exists).Error; err != nil {
log.Printf("❌ [CreateMedia] Erreur vérification produit: %v", err)
return fmt.Errorf("erreur vérification produit: %w", err)
}
@@ -147,178 +124,111 @@ func (db *Database) InsertMedia(m any, productID int, mediaURL any, mediaType st
return fmt.Errorf("produit %d n'existe pas", productID)
}
- query := `INSERT INTO media (product_id, url, type, created_at)
- VALUES ($1, $2, $3, $4) RETURNING id`
-
- var mediaID int
- now := time.Now()
-
- err = db.QueryRow(query, productID, mediaURL, mediaType, now).Scan(&mediaID)
+ var result struct {
+ ID int `gorm:"column:id"`
+ }
+ err := d.GDB.Raw(`
+ INSERT INTO media (product_id, url, type, created_at)
+ VALUES (?, ?, ?, ?) RETURNING id`,
+ productID, mediaURL, mediaType, time.Now(),
+ ).Scan(&result).Error
if err != nil {
log.Printf("❌ [CreateMedia] Erreur INSERT: %v", err)
return fmt.Errorf("erreur création média: %w", err)
}
+
if mi, ok := m.(MediaInterface); ok {
- mi.SetID(mediaID)
+ mi.SetID(result.ID)
}
- log.Printf("✅ [CreateMedia] Média créé: ID=%d, Type=%s", mediaID, mediaType)
+ log.Printf("✅ [CreateMedia] Média créé: ID=%d, Type=%s", result.ID, mediaType)
return nil
}
-// ============================================
-// GET MEDIA BY ID - VERSION SÉCURISÉE
-// ============================================
-
-func (db *Database) GetMediaByID(mediaID int) (*models.Media, error) {
+func (d *Database) GetMediaByID(mediaID int) (*models.Media, error) {
log.Printf("🔍 [GetMediaByID] START - ID=%d", mediaID)
- // ✅ VALIDATION
if err := validateMediaID(mediaID); err != nil {
log.Printf("❌ [GetMediaByID] %v", err)
return nil, err
}
var media models.Media
-
- query := `SELECT id, product_id, url, type, created_at
- FROM media
- WHERE id = $1`
-
- err := db.QueryRow(query, mediaID).Scan(
- &media.ID,
- &media.ProductID,
- &media.URL,
- &media.Type,
- &media.CreatedAt,
- )
-
- if err == sql.ErrNoRows {
- log.Printf("❌ [GetMediaByID] Média %d non trouvé", mediaID)
- return nil, fmt.Errorf("média non trouvé")
- }
-
+ err := d.GDB.Raw(`
+ SELECT id, product_id, url, type, created_at
+ FROM media WHERE id = ?`, mediaID).Scan(&media).Error
if err != nil {
log.Printf("❌ [GetMediaByID] Erreur query: %v", err)
return nil, fmt.Errorf("erreur récupération média: %w", err)
}
+ if media.ID == 0 {
+ log.Printf("❌ [GetMediaByID] Média %d non trouvé", mediaID)
+ return nil, fmt.Errorf("média non trouvé")
+ }
log.Printf("✅ [GetMediaByID] Média trouvé: Type=%s", media.Type)
-
return &media, nil
}
-// ============================================
-// GET MEDIA BY PRODUCT ID - VERSION SÉCURISÉE
-// ============================================
-
-func (db *Database) GetMediaByProductID(productID int) ([]models.Media, error) {
+func (d *Database) GetMediaByProductID(productID int) ([]models.Media, error) {
log.Printf("🖼️ [GetMediaByProductID] START - ProductID=%d", productID)
- // ✅ VALIDATION
if err := validateProductID(productID); err != nil {
log.Printf("❌ [GetMediaByProductID] %v", err)
return nil, err
}
- query := `SELECT id, product_id, url, type, created_at
- FROM media
- WHERE product_id = $1
- ORDER BY id ASC`
-
- rows, err := db.Query(query, productID)
+ var mediaList []models.Media
+ err := d.GDB.Raw(`
+ SELECT id, product_id, url, type, created_at
+ FROM media WHERE product_id = ?
+ ORDER BY id ASC`, productID).Scan(&mediaList).Error
if err != nil {
log.Printf("❌ [GetMediaByProductID] Erreur query: %v", err)
return nil, fmt.Errorf("erreur récupération médias: %w", err)
}
- defer rows.Close()
-
- var mediaList []models.Media
-
- for rows.Next() {
- var media models.Media
-
- if err := rows.Scan(
- &media.ID,
- &media.ProductID,
- &media.URL,
- &media.Type,
- &media.CreatedAt,
- ); err != nil {
- log.Printf("❌ [GetMediaByProductID] Erreur scan: %v", err)
- return nil, fmt.Errorf("erreur scan média: %w", err)
- }
-
- log.Printf(" 🖼️ Media: ID=%d, Type=%s", media.ID, media.Type)
- mediaList = append(mediaList, media)
- }
-
- if err = rows.Err(); err != nil {
- log.Printf("❌ [GetMediaByProductID] Erreur iteration: %v", err)
- return nil, fmt.Errorf("erreur itération médias: %w", err)
- }
log.Printf("✅ [GetMediaByProductID] %d médias trouvés", len(mediaList))
-
return mediaList, nil
}
-// ============================================
-// UPDATE MEDIA - VERSION SÉCURISÉE
-// ============================================
-
-func (db *Database) UpdateMedia(media *models.Media) error {
+func (d *Database) UpdateMedia(media *models.Media) error {
if media == nil {
return fmt.Errorf("média nil")
}
-
if err := validateMediaID(media.ID); err != nil {
log.Printf("❌ [UpdateMedia] %v", err)
return err
}
-
if err := validateMediaType(media.Type); err != nil {
log.Printf("❌ [UpdateMedia] %v", err)
return err
}
-
if err := validateMediaURL(media.URL); err != nil {
log.Printf("❌ [UpdateMedia] %v", err)
return err
}
-
- // ✅ VÉRIFIER QUE LE MÉDIA EXISTE
- if err := db.CheckMediaExists(media); err != nil {
+ if err := d.CheckMediaExists(media); err != nil {
log.Printf("❌ [UpdateMedia] %v", err)
return err
}
- // ✅ UPDATE
- query := `UPDATE media
- SET url = $1, type = $2
- WHERE id = $3`
-
- result, err := db.Exec(query, media.URL, media.Type, media.ID)
- if err != nil {
- log.Printf("❌ [UpdateMedia] Erreur UPDATE: %v", err)
- return fmt.Errorf("erreur mise à jour média: %w", err)
+ result := d.GDB.Exec(`UPDATE media SET url = ?, type = ? WHERE id = ?`, media.URL, media.Type, media.ID)
+ if result.Error != nil {
+ log.Printf("❌ [UpdateMedia] Erreur UPDATE: %v", result.Error)
+ return fmt.Errorf("erreur mise à jour média: %w", result.Error)
}
-
- rowsAffected, _ := result.RowsAffected()
- if rowsAffected == 0 {
+ if result.RowsAffected == 0 {
log.Printf("❌ [UpdateMedia] Aucune ligne affectée")
return fmt.Errorf("média non trouvé")
}
log.Printf("✅ [UpdateMedia] Média %d mis à jour", media.ID)
-
return nil
}
-func (db *Database) CheckMediaExists(media *models.Media) error {
+func (d *Database) CheckMediaExists(media *models.Media) error {
var exists bool
- checkQuery := `SELECT EXISTS(SELECT 1 FROM media WHERE id = $1)`
- err := db.QueryRow(checkQuery, media.ID).Scan(&exists)
- if err != nil {
+ if err := d.GDB.Raw(`SELECT EXISTS(SELECT 1 FROM media WHERE id = ?)`, media.ID).Scan(&exists).Error; err != nil {
log.Printf("❌ [UpdateMedia] Erreur vérification: %v", err)
return fmt.Errorf("erreur vérification média: %w", err)
}
@@ -329,38 +239,33 @@ func (db *Database) CheckMediaExists(media *models.Media) error {
return nil
}
-func (db *Database) DeleteMedia(mediaID int) error {
+func (d *Database) DeleteMedia(mediaID int) error {
if err := validateMediaID(mediaID); err != nil {
log.Printf("❌ [DeleteMedia] %v", err)
return err
}
media := models.Media{ID: mediaID}
- if err := db.CheckMediaExists(&media); err != nil {
+ if err := d.CheckMediaExists(&media); err != nil {
log.Printf("❌ [DeleteMedia] %v", err)
return err
}
- query := `DELETE FROM media WHERE id = $1`
-
- result, err := db.Exec(query, mediaID)
- if err != nil {
- log.Printf("❌ [DeleteMedia] Erreur DELETE: %v", err)
- return fmt.Errorf("erreur suppression média: %w", err)
+ result := d.GDB.Exec(`DELETE FROM media WHERE id = ?`, mediaID)
+ if result.Error != nil {
+ log.Printf("❌ [DeleteMedia] Erreur DELETE: %v", result.Error)
+ return fmt.Errorf("erreur suppression média: %w", result.Error)
}
-
- rowsAffected, _ := result.RowsAffected()
- if rowsAffected == 0 {
+ if result.RowsAffected == 0 {
log.Printf("❌ [DeleteMedia] Aucune ligne affectée")
return fmt.Errorf("média non trouvé")
}
log.Printf("✅ [DeleteMedia] Média %d supprimé", mediaID)
-
return nil
}
-func (db *Database) DeleteMediaByProductID(productID int) error {
+func (d *Database) DeleteMediaByProductID(productID int) error {
log.Printf("🗑️ [DeleteMediaByProductID] START - ProductID=%d", productID)
if err := validateProductID(productID); err != nil {
@@ -369,9 +274,7 @@ func (db *Database) DeleteMediaByProductID(productID int) error {
}
var exists bool
- checkQuery := `SELECT EXISTS(SELECT 1 FROM products WHERE id = $1)`
- err := db.QueryRow(checkQuery, productID).Scan(&exists)
- if err != nil {
+ if err := d.GDB.Raw(`SELECT EXISTS(SELECT 1 FROM products WHERE id = ?)`, productID).Scan(&exists).Error; err != nil {
log.Printf("❌ [DeleteMediaByProductID] Erreur vérification: %v", err)
return fmt.Errorf("erreur vérification produit: %w", err)
}
@@ -380,17 +283,13 @@ func (db *Database) DeleteMediaByProductID(productID int) error {
return fmt.Errorf("produit %d non trouvé", productID)
}
- query := `DELETE FROM media WHERE product_id = $1`
-
- result, err := db.Exec(query, productID)
- if err != nil {
- log.Printf("❌ [DeleteMediaByProductID] Erreur DELETE: %v", err)
- return fmt.Errorf("erreur suppression médias: %w", err)
+ result := d.GDB.Exec(`DELETE FROM media WHERE product_id = ?`, productID)
+ if result.Error != nil {
+ log.Printf("❌ [DeleteMediaByProductID] Erreur DELETE: %v", result.Error)
+ return fmt.Errorf("erreur suppression médias: %w", result.Error)
}
- rowsAffected, _ := result.RowsAffected()
log.Printf("✅ [DeleteMediaByProductID] %d média(s) supprimé(s) pour produit %d",
- rowsAffected, productID)
-
+ result.RowsAffected, productID)
return nil
}
diff --git a/backend/gestion/db/db_notifications.go b/backend/gestion/db/db_notifications.go
index 31d77058..1b282b93 100644
--- a/backend/gestion/db/db_notifications.go
+++ b/backend/gestion/db/db_notifications.go
@@ -9,7 +9,6 @@ import (
)
func (d *Database) NotifyClient(username string, commandID int, notifType, message string) error {
- // Sauvegarder la notification dans Redis
notifKey := fmt.Sprintf("notifications:%s", username)
notification := map[string]any{
@@ -24,7 +23,6 @@ func (d *Database) NotifyClient(username string, commandID int, notifType, messa
Redis.LPush(RedisCtx, notifKey, notifJSON)
Redis.Expire(RedisCtx, notifKey, 7*24*time.Hour)
- // Diffusion Telegram si le client a lié son compte
if services.TelegramBot != nil && services.TelegramBot.IsConfigured() {
if chatID, ok, err := d.GetClientTelegramChatID(username); err == nil && ok {
go services.TelegramBot.SendMessage(chatID, fmt.Sprintf("🔔 Notification\n\n%s", message))
@@ -51,7 +49,6 @@ func (d *Database) NotifyLivreur(username string, commandID int, notifType, mess
Redis.LPush(RedisCtx, notifKey, notifJSON)
Redis.Expire(RedisCtx, notifKey, 7*24*time.Hour)
- // Diffusion Telegram si le livreur a lié son compte
if services.TelegramBot != nil && services.TelegramBot.IsConfigured() {
if chatID, ok, err := d.GetUserTelegramChatID(username); err == nil && ok {
go services.TelegramBot.SendMessage(chatID, fmt.Sprintf("🔔 Notification\n\n%s", message))
@@ -63,16 +60,14 @@ func (d *Database) NotifyLivreur(username string, commandID int, notifType, mess
}
// NotifyAllAdminCabine stocke une notification Redis pour tous les admins/cabines
-// et envoie un push aux ceux qui ont un token. Appelé dès qu'une nouvelle commande est créée.
func (d *Database) NotifyAllAdminCabine(commandID int, clientUsername, deliveryAddr string) {
- rows, err := d.Query(
- `SELECT username FROM users WHERE role IN ('admin','cabine')`,
- )
- if err != nil {
+ var users []struct {
+ Username string `gorm:"column:username"`
+ }
+ if err := d.GDB.Raw(`SELECT username FROM users WHERE role IN ('admin','cabine')`).Scan(&users).Error; err != nil {
log.Printf("❌ [ADMIN_NOTIF] Erreur lecture users admin/cabine: %v", err)
return
}
- defer rows.Close()
msg := fmt.Sprintf("Nouvelle commande #%d de %s — %s", commandID, clientUsername, deliveryAddr)
@@ -86,18 +81,13 @@ func (d *Database) NotifyAllAdminCabine(commandID int, clientUsername, deliveryA
notifJSON, _ := json.Marshal(notification)
count := 0
- for rows.Next() {
- var username string
- if err := rows.Scan(&username); err != nil {
- continue
- }
- notifKey := fmt.Sprintf("notifications:%s", username)
+ for _, u := range users {
+ notifKey := fmt.Sprintf("notifications:%s", u.Username)
Redis.LPush(RedisCtx, notifKey, notifJSON)
Redis.Expire(RedisCtx, notifKey, 7*24*time.Hour)
- // Diffusion Telegram individuelle
if services.TelegramBot != nil && services.TelegramBot.IsConfigured() {
- if chatID, ok, err := d.GetUserTelegramChatID(username); err == nil && ok {
+ if chatID, ok, err := d.GetUserTelegramChatID(u.Username); err == nil && ok {
capturedChatID := chatID
capturedMsg := msg
go services.TelegramBot.SendMessage(capturedChatID, fmt.Sprintf("🔔 Nouvelle commande\n\n%s", capturedMsg))
@@ -108,17 +98,15 @@ func (d *Database) NotifyAllAdminCabine(commandID int, clientUsername, deliveryA
log.Printf("📬 [ADMIN_NOTIF] Notif Redis (%d users) pour commande #%d", count, commandID)
}
-// NotifyAllAdminCabineAlert envoie une notification Redis + push à tous les admins/cabines
-// lors du déclenchement d'une alerte par un livreur.
+// NotifyAllAdminCabineAlert envoie une notification Redis à tous les admins/cabines lors d'une alerte
func (d *Database) NotifyAllAdminCabineAlert(alertID int, livreurUsername, alertMessage string) {
- rows, err := d.Query(
- `SELECT username FROM users WHERE role IN ('admin','cabine')`,
- )
- if err != nil {
+ var users []struct {
+ Username string `gorm:"column:username"`
+ }
+ if err := d.GDB.Raw(`SELECT username FROM users WHERE role IN ('admin','cabine')`).Scan(&users).Error; err != nil {
log.Printf("❌ [ALERT_NOTIF] Erreur lecture users admin/cabine: %v", err)
return
}
- defer rows.Close()
body := fmt.Sprintf("%s — livreur : %s", alertMessage, livreurUsername)
@@ -132,18 +120,13 @@ func (d *Database) NotifyAllAdminCabineAlert(alertID int, livreurUsername, alert
notifJSON, _ := json.Marshal(notification)
count := 0
- for rows.Next() {
- var username string
- if err := rows.Scan(&username); err != nil {
- continue
- }
- notifKey := fmt.Sprintf("notifications:%s", username)
+ for _, u := range users {
+ notifKey := fmt.Sprintf("notifications:%s", u.Username)
Redis.LPush(RedisCtx, notifKey, notifJSON)
Redis.Expire(RedisCtx, notifKey, 7*24*time.Hour)
- // Diffusion Telegram individuelle
if services.TelegramBot != nil && services.TelegramBot.IsConfigured() {
- if chatID, ok, err := d.GetUserTelegramChatID(username); err == nil && ok {
+ if chatID, ok, err := d.GetUserTelegramChatID(u.Username); err == nil && ok {
capturedChatID := chatID
capturedBody := body
go services.TelegramBot.SendMessage(capturedChatID, fmt.Sprintf("🚨 Alerte livreur\n\n%s", capturedBody))
@@ -156,11 +139,10 @@ func (d *Database) NotifyAllAdminCabineAlert(alertID int, livreurUsername, alert
// AddDeliveryRating ajoute une note pour un livreur
func (d *Database) AddDeliveryRating(livreurUsername string, commandID, rating int, comment string) error {
- query := `
+ err := d.GDB.Exec(`
INSERT INTO delivery_ratings (livreur_username, command_id, rating, comment, created_at)
- VALUES (?, ?, ?, ?, NOW())
- `
- _, err := d.Exec(query, livreurUsername, commandID, rating, comment)
+ VALUES (?, ?, ?, ?, NOW())`,
+ livreurUsername, commandID, rating, comment).Error
if err != nil {
log.Printf("⚠️ Erreur sauvegarde note livreur: %v", err)
return err
diff --git a/backend/gestion/db/db_payments.go b/backend/gestion/db/db_payments.go
index 8325e07b..83f94ba1 100644
--- a/backend/gestion/db/db_payments.go
+++ b/backend/gestion/db/db_payments.go
@@ -1,21 +1,25 @@
package db
import (
- "database/sql"
"fmt"
"gestion/models"
"log"
+
+ "gorm.io/gorm"
)
func (d *Database) CreateCryptoPayment(commandID int, nowPaymentID, status, priceCurrency, payCurrency, payAddress string, priceAmount, payAmount float64) (*models.CryptoPayment, error) {
- var p models.CryptoPayment
- err := d.DB.QueryRow(`
- INSERT INTO crypto_payments (command_id, nowpayment_id, status, price_amount, price_currency, pay_currency, pay_address, pay_amount)
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
- RETURNING id, command_id, nowpayment_id, status, price_amount, price_currency, pay_currency, pay_address, pay_amount, created_at, updated_at`,
- commandID, nowPaymentID, status, priceAmount, priceCurrency, payCurrency, payAddress, payAmount,
- ).Scan(&p.ID, &p.CommandID, &p.NowPaymentID, &p.Status, &p.PriceAmount, &p.PriceCurrency, &p.PayCurrency, &p.PayAddress, &p.PayAmount, &p.CreatedAt, &p.UpdatedAt)
- if err != nil {
+ p := models.CryptoPayment{
+ CommandID: commandID,
+ NowPaymentID: nowPaymentID,
+ Status: status,
+ PriceAmount: priceAmount,
+ PriceCurrency: priceCurrency,
+ PayCurrency: payCurrency,
+ PayAddress: payAddress,
+ PayAmount: payAmount,
+ }
+ if err := d.GDB.Create(&p).Error; err != nil {
return nil, fmt.Errorf("CreateCryptoPayment: %w", err)
}
return &p, nil
@@ -23,15 +27,11 @@ func (d *Database) CreateCryptoPayment(commandID int, nowPaymentID, status, pric
func (d *Database) GetCryptoPaymentByCommandID(commandID int) (*models.CryptoPayment, error) {
var p models.CryptoPayment
- err := d.DB.QueryRow(`
- SELECT id, command_id, nowpayment_id, status, price_amount, price_currency, pay_currency, pay_address, pay_amount, created_at, updated_at
- FROM crypto_payments WHERE command_id = $1
- ORDER BY created_at DESC LIMIT 1`, commandID,
- ).Scan(&p.ID, &p.CommandID, &p.NowPaymentID, &p.Status, &p.PriceAmount, &p.PriceCurrency, &p.PayCurrency, &p.PayAddress, &p.PayAmount, &p.CreatedAt, &p.UpdatedAt)
- if err == sql.ErrNoRows {
- return nil, nil
- }
+ err := d.GDB.Where("command_id = ?", commandID).Order("created_at DESC").First(&p).Error
if err != nil {
+ if isNotFound(err) {
+ return nil, nil
+ }
return nil, err
}
return &p, nil
@@ -39,84 +39,47 @@ func (d *Database) GetCryptoPaymentByCommandID(commandID int) (*models.CryptoPay
func (d *Database) GetCryptoPaymentByNowPaymentID(nowPaymentID string) (*models.CryptoPayment, error) {
var p models.CryptoPayment
- err := d.DB.QueryRow(`
- SELECT id, command_id, nowpayment_id, status, price_amount, price_currency, pay_currency, pay_address, pay_amount, created_at, updated_at
- FROM crypto_payments WHERE nowpayment_id = $1`, nowPaymentID,
- ).Scan(&p.ID, &p.CommandID, &p.NowPaymentID, &p.Status, &p.PriceAmount, &p.PriceCurrency, &p.PayCurrency, &p.PayAddress, &p.PayAmount, &p.CreatedAt, &p.UpdatedAt)
- if err == sql.ErrNoRows {
- return nil, nil
- }
+ err := d.GDB.Where("nowpayment_id = ?", nowPaymentID).First(&p).Error
if err != nil {
+ if isNotFound(err) {
+ return nil, nil
+ }
return nil, err
}
return &p, nil
}
func (d *Database) GetPendingCryptoPayments() ([]models.CryptoPayment, error) {
- rows, err := d.DB.Query(`
- SELECT id, command_id, nowpayment_id, status, price_amount, price_currency, pay_currency, pay_address, pay_amount, created_at, updated_at
- FROM crypto_payments
- WHERE status NOT IN ('finished', 'failed', 'expired', 'refunded')
- ORDER BY created_at ASC`)
- if err != nil {
- return nil, err
- }
- defer rows.Close()
-
var payments []models.CryptoPayment
- for rows.Next() {
- var p models.CryptoPayment
- if err := rows.Scan(&p.ID, &p.CommandID, &p.NowPaymentID, &p.Status, &p.PriceAmount, &p.PriceCurrency, &p.PayCurrency, &p.PayAddress, &p.PayAmount, &p.CreatedAt, &p.UpdatedAt); err != nil {
- continue
- }
- payments = append(payments, p)
- }
- return payments, nil
+ err := d.GDB.Where("status NOT IN ?", []string{"finished", "failed", "expired", "refunded"}).
+ Order("created_at ASC").Find(&payments).Error
+ return payments, err
}
func (d *Database) UpdateCryptoPaymentStatus(id int, status string, payAmount float64) error {
- _, err := d.DB.Exec(`
- UPDATE crypto_payments SET status = $1, pay_amount = $2, updated_at = NOW()
- WHERE id = $3`, status, payAmount, id)
- return err
+ return d.GDB.Model(&models.CryptoPayment{}).Where("id = ?", id).
+ Updates(map[string]any{"status": status, "pay_amount": payAmount}).Error
}
-// ActivateCryptoCommand passe la commande de 'pending_payment' → 'pending' une fois le paiement confirmé
func (d *Database) ActivateCryptoCommand(commandID int) error {
- _, err := d.DB.Exec(`
- UPDATE commandes SET status = 'pending', updated_at = NOW()
- WHERE id = $1 AND status = 'pending_payment'`, commandID)
- return err
+ return d.GDB.Exec(`UPDATE commandes SET status = 'pending', updated_at = NOW() WHERE id = ? AND status = 'pending_payment'`, commandID).Error
}
-// CancelCryptoCommand annule une commande en attente de paiement et restaure le stock
func (d *Database) CancelCryptoCommand(commandID int) error {
- tx, err := d.DB.Begin()
- if err != nil {
- return err
- }
- defer tx.Rollback()
-
- rows, err := tx.Query(`SELECT product_id, quantite FROM command_items WHERE command_id = $1`, commandID)
- if err != nil {
- return err
- }
- defer rows.Close()
-
- for rows.Next() {
- var productID int
- var qty float64
- if err := rows.Scan(&productID, &qty); err != nil {
- continue
+ return d.GDB.Transaction(func(tx *gorm.DB) error {
+ type item struct {
+ ProductID int
+ Quantite float64
}
- if _, err := tx.Exec(`UPDATE products SET stock = stock + $1 WHERE id = $2`, qty, productID); err != nil {
- log.Printf("[CANCEL CRYPTO] erreur restauration stock produit %d: %v", productID, err)
+ var items []item
+ if err := tx.Raw(`SELECT product_id, quantite FROM command_items WHERE command_id = ?`, commandID).Scan(&items).Error; err != nil {
+ return err
}
- }
-
- if _, err := tx.Exec(`UPDATE commandes SET status = 'cancelled', updated_at = NOW() WHERE id = $1 AND status = 'pending_payment'`, commandID); err != nil {
- return err
- }
-
- return tx.Commit()
+ 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 tx.Exec(`UPDATE commandes SET status = 'cancelled', updated_at = NOW() WHERE id = ? AND status = 'pending_payment'`, commandID).Error
+ })
}
diff --git a/backend/gestion/db/db_product.go b/backend/gestion/db/db_product.go
index 09c19c1f..acea1534 100644
--- a/backend/gestion/db/db_product.go
+++ b/backend/gestion/db/db_product.go
@@ -1,7 +1,6 @@
package db
import (
- "database/sql"
"fmt"
"gestion/models"
"log"
@@ -9,7 +8,7 @@ import (
)
// CreateProduct crée un nouveau produit avec ses prix
-func (db *Database) CreateProduct(product any) error {
+func (d *Database) CreateProduct(product any) error {
log.Printf("🔍 [DB CreateProduct] Type reçu: %T", product)
log.Printf("🔍 [DB CreateProduct] Valeur: %+v", product)
@@ -29,7 +28,7 @@ func (db *Database) CreateProduct(product any) error {
if !ok {
if prodPtr, isPtr := product.(*models.Product); isPtr {
log.Printf("✅ [DB CreateProduct] C'est un *models.Product, utilisons-le directement")
- p = prodPtr // ça fonctionne maintenant car *models.Product implémente ProductInterface
+ p = prodPtr
ok = true
} else {
return fmt.Errorf("type de produit invalide: reçu %T, attendu ProductInterface", product)
@@ -46,43 +45,40 @@ func (db *Database) CreateProduct(product any) error {
log.Printf("📦 [DB CreateProduct] Nombre de prix: %d", len(p.GetPrices()))
}
- // Insérer le produit avec le stock et l'unité
- query := `INSERT INTO products (name, category, description, stock, unit, created_at, updated_at)
- VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id, created_at, updated_at`
-
now := time.Now()
- var productID int
- var createdAt, updatedAt time.Time
+ var result struct {
+ ID int `gorm:"column:id"`
+ CreatedAt time.Time `gorm:"column:created_at"`
+ UpdatedAt time.Time `gorm:"column:updated_at"`
+ }
- err := db.QueryRow(query, p.GetName(), p.GetCategory(), p.GetDescription(), p.GetStock(), p.GetUnit(), now, now).
- Scan(&productID, &createdAt, &updatedAt)
+ err := d.GDB.Raw(`
+ INSERT INTO products (name, category, description, stock, unit, created_at, updated_at)
+ VALUES (?, ?, ?, ?, ?, ?, ?) RETURNING id, created_at, updated_at`,
+ p.GetName(), p.GetCategory(), p.GetDescription(), p.GetStock(), p.GetUnit(), now, now,
+ ).Scan(&result).Error
if err != nil {
log.Printf("❌ [DB CreateProduct] Erreur INSERT: %v", err)
return fmt.Errorf("erreur création produit: %v", err)
}
- log.Printf("✅ [DB CreateProduct] Produit inséré avec ID: %d", productID)
+ log.Printf("✅ [DB CreateProduct] Produit inséré avec ID: %d", result.ID)
- // Mettre à jour le produit avec l'ID et les dates
- p.SetID(productID)
- p.SetCreatedAt(createdAt)
- p.SetUpdatedAt(updatedAt)
+ p.SetID(result.ID)
+ p.SetCreatedAt(result.CreatedAt)
+ p.SetUpdatedAt(result.UpdatedAt)
- // Insérer les prix
- prices := p.GetPrices()
- for i, price := range prices {
- priceQuery := `INSERT INTO product_prices (product_id, quantity, price)
- VALUES ($1, $2, $3)`
- _, err := db.Exec(priceQuery, productID, price.Quantity, price.Price)
+ for i, price := range p.GetPrices() {
+ err := d.GDB.Exec(`INSERT INTO product_prices (product_id, quantity, price) VALUES (?, ?, ?)`,
+ result.ID, price.Quantity, price.Price).Error
if err != nil {
log.Printf("❌ [DB CreateProduct] Erreur insertion prix[%d]: %v", i, err)
return fmt.Errorf("erreur insertion prix: %v", err)
}
- log.Printf("✅ [DB CreateProduct] Prix[%d] inséré: quantity=%g, price=%.2f",
- i, price.Quantity, price.Price)
+ log.Printf("✅ [DB CreateProduct] Prix[%d] inséré: quantity=%g, price=%.2f", i, price.Quantity, price.Price)
}
- log.Printf("🎉 [DB CreateProduct] Produit créé avec succès! ID=%d", productID)
+ log.Printf("🎉 [DB CreateProduct] Produit créé avec succès! ID=%d", result.ID)
return nil
}
@@ -91,27 +87,21 @@ func (d *Database) GetProductByID(id int) (models.Product, error) {
log.Printf("📦 [GetProductByID] START - ID=%d", id)
var p models.Product
-
- // ✅ AJOUTER stock dans le SELECT
- err := d.QueryRow(`
+ err := d.GDB.Raw(`
SELECT id, name, category, description, stock, unit, created_at, updated_at
FROM products
- WHERE id=$1
- `, id).Scan(&p.ID, &p.Name, &p.Category, &p.Description, &p.Stock, &p.Unit, &p.CreatedAt, &p.UpdatedAt)
-
+ WHERE id = ?`, id).Scan(&p).Error
if err != nil {
log.Printf("❌ [GetProductByID] Erreur query: %v", err)
return p, err
}
- log.Printf("📊 [GetProductByID] Product scanned: ID=%d, Name=%s, Stock=%.2f",
- p.ID, p.Name, p.Stock)
+ log.Printf("📊 [GetProductByID] Product scanned: ID=%d, Name=%s, Stock=%.2f", p.ID, p.Name, p.Stock)
- // ✅ CHARGER LES PRICES
prices, err := d.GetProductPrices(p.ID)
if err != nil {
log.Printf("⚠️ [GetProductByID] Erreur loading prices: %v", err)
- p.Prices = []models.ProductPrice{} // Tableau vide au lieu de nil
+ p.Prices = []models.ProductPrice{}
} else {
p.Prices = prices
log.Printf("✅ [GetProductByID] Loaded %d prices for product %d", len(prices), p.ID)
@@ -119,51 +109,38 @@ func (d *Database) GetProductByID(id int) (models.Product, error) {
return p, nil
}
+
func (d *Database) GetAllProducts() ([]models.Product, error) {
log.Println("📦 [GetAllProducts] START")
- rows, err := d.Query(`
+ var products []models.Product
+ err := d.GDB.Raw(`
SELECT id, name, category, description, stock, unit, created_at, updated_at
FROM products
- ORDER BY id ASC
- `)
+ ORDER BY id ASC`).Scan(&products).Error
if err != nil {
log.Printf("❌ [GetAllProducts] Erreur query: %v", err)
return nil, err
}
- defer rows.Close()
- var products []models.Product
- for rows.Next() {
- var p models.Product
- if err := rows.Scan(&p.ID, &p.Name, &p.Category, &p.Description, &p.Stock, &p.Unit, &p.CreatedAt, &p.UpdatedAt); err != nil {
- log.Printf("❌ [GetAllProducts] Erreur scan: %v", err)
- return nil, err
- }
-
- log.Printf("📊 [GetAllProducts] Product scanned: ID=%d, Name=%s, Stock=%.2f", p.ID, p.Name, p.Stock)
-
- // ✅ CHARGER LES PRICES
- prices, err := d.GetProductPrices(p.ID)
+ for i := range products {
+ prices, err := d.GetProductPrices(products[i].ID)
if err != nil {
- log.Printf("⚠️ [GetAllProducts] Erreur loading prices for product %d: %v", p.ID, err)
- p.Prices = []models.ProductPrice{}
+ log.Printf("⚠️ [GetAllProducts] Erreur loading prices for product %d: %v", products[i].ID, err)
+ products[i].Prices = []models.ProductPrice{}
} else {
- p.Prices = prices
- log.Printf("✅ [GetAllProducts] Loaded %d prices for product %d", len(prices), p.ID)
+ products[i].Prices = prices
+ log.Printf("✅ [GetAllProducts] Loaded %d prices for product %d", len(prices), products[i].ID)
}
- // ✅ CHARGER LES MÉDIAS (MANQUANT AVANT!)
- media, err := d.GetMediaByProductID(p.ID)
+ media, err := d.GetMediaByProductID(products[i].ID)
if err != nil {
- log.Printf("⚠️ [GetAllProducts] Erreur loading media for product %d: %v", p.ID, err)
- p.Media = []models.Media{}
+ log.Printf("⚠️ [GetAllProducts] Erreur loading media for product %d: %v", products[i].ID, err)
+ products[i].Media = []models.Media{}
} else {
- p.Media = media
- log.Printf("✅ [GetAllProducts] Loaded %d media for product %d", len(media), p.ID)
+ products[i].Media = media
+ log.Printf("✅ [GetAllProducts] Loaded %d media for product %d", len(media), products[i].ID)
}
-
- products = append(products, p)
}
log.Printf("✅ [GetAllProducts] Total products loaded: %d", len(products))
@@ -171,73 +148,50 @@ func (d *Database) GetAllProducts() ([]models.Product, error) {
}
// GetProductsByCategory récupère les produits par catégorie avec prices ET stock
-func (db *Database) GetProductsByCategory(category string) ([]models.Product, error) {
+func (d *Database) GetProductsByCategory(category string) ([]models.Product, error) {
log.Printf("📦 [GetProductsByCategory] START - Category=%s", category)
- rows, err := db.Query(`
+ var products []models.Product
+ err := d.GDB.Raw(`
SELECT id, name, category, description, stock, unit, created_at, updated_at
FROM products
- WHERE category = $1
- ORDER BY created_at DESC
- `, category)
-
+ WHERE category = ?
+ ORDER BY created_at DESC`, category).Scan(&products).Error
if err != nil {
log.Printf("❌ [GetProductsByCategory] Erreur query: %v", err)
return nil, fmt.Errorf("erreur lors de la récupération des produits: %w", err)
}
- defer rows.Close()
- var products []models.Product
- for rows.Next() {
- var p models.Product
- if err := rows.Scan(&p.ID, &p.Name, &p.Category, &p.Description, &p.Stock, &p.Unit, &p.CreatedAt, &p.UpdatedAt); err != nil {
- log.Printf("❌ [GetProductsByCategory] Erreur scan: %v", err)
- return nil, fmt.Errorf("erreur lors du scan d'un produit: %w", err)
- }
-
- log.Printf("📊 [GetProductsByCategory] Product scanned: ID=%d, Name=%s, Stock=%.2f",
- p.ID, p.Name, p.Stock)
-
- // ✅ CHARGER LES PRICES pour chaque produit
- prices, err := db.GetProductPrices(p.ID)
+ for i := range products {
+ prices, err := d.GetProductPrices(products[i].ID)
if err != nil {
- log.Printf("⚠️ [GetProductsByCategory] Erreur loading prices for product %d: %v", p.ID, err)
- p.Prices = []models.ProductPrice{} // Tableau vide au lieu de nil
+ log.Printf("⚠️ [GetProductsByCategory] Erreur loading prices for product %d: %v", products[i].ID, err)
+ products[i].Prices = []models.ProductPrice{}
} else {
- p.Prices = prices
- log.Printf("✅ [GetProductsByCategory] Loaded %d prices for product %d", len(prices), p.ID)
+ products[i].Prices = prices
+ log.Printf("✅ [GetProductsByCategory] Loaded %d prices for product %d", len(prices), products[i].ID)
}
-
- products = append(products, p)
- }
-
- if err := rows.Err(); err != nil {
- log.Printf("❌ [GetProductsByCategory] Erreur iteration: %v", err)
- return nil, fmt.Errorf("erreur lors de l'itération des produits: %w", err)
}
log.Printf("✅ [GetProductsByCategory] Total products loaded: %d", len(products))
return products, nil
}
-func (db *Database) UpdateProduct(productID int, name, category, description, unit string, stock float64, prices []models.ProductPrice) error {
- _, err := db.Exec(`
- UPDATE products
- SET name = $1, category = $2, description = $3, stock = $4, unit = $5, updated_at = $6
- WHERE id = $7
- `, name, category, description, stock, unit, time.Now(), productID)
+func (d *Database) UpdateProduct(productID int, name, category, description, unit string, stock float64, prices []models.ProductPrice) error {
+ err := d.GDB.Exec(`
+ UPDATE products
+ SET name = ?, category = ?, description = ?, stock = ?, unit = ?, updated_at = ?
+ WHERE id = ?`,
+ name, category, description, stock, unit, time.Now(), productID).Error
if err != nil {
return fmt.Errorf("erreur mise à jour produit: %w", err)
}
- db.Exec(`DELETE FROM product_prices WHERE product_id = $1`, productID)
+ d.GDB.Exec(`DELETE FROM product_prices WHERE product_id = ?`, productID)
for _, price := range prices {
- _, err := db.Exec(`
- INSERT INTO product_prices (product_id, quantity, price)
- VALUES ($1, $2, $3)
- `, productID, price.Quantity, price.Price)
- if err != nil {
+ if err := d.GDB.Exec(`INSERT INTO product_prices (product_id, quantity, price) VALUES (?, ?, ?)`,
+ productID, price.Quantity, price.Price).Error; err != nil {
log.Printf("❌ [UpdateProduct] Erreur prix: %v", err)
}
}
@@ -246,30 +200,27 @@ func (db *Database) UpdateProduct(productID int, name, category, description, un
}
// DeleteProduct supprime un produit
-func (db *Database) DeleteProduct(productID int) error {
- query := `DELETE FROM products WHERE id = $1`
- result, err := db.Exec(query, productID)
- if err != nil {
- return fmt.Errorf("erreur suppression produit: %v", err)
+func (d *Database) DeleteProduct(productID int) error {
+ result := d.GDB.Exec(`DELETE FROM products WHERE id = ?`, productID)
+ if result.Error != nil {
+ return fmt.Errorf("erreur suppression produit: %v", result.Error)
}
-
- rowsAffected, _ := result.RowsAffected()
- if rowsAffected == 0 {
+ if result.RowsAffected == 0 {
return fmt.Errorf("produit introuvable")
}
-
return nil
}
func (d *Database) GetProductNameByID(productID int) (string, error) {
- var name string
- query := `SELECT name FROM products WHERE id = $1`
- err := d.QueryRow(query, productID).Scan(&name)
+ var result struct {
+ Name string `gorm:"column:name"`
+ }
+ err := d.GDB.Raw(`SELECT name FROM products WHERE id = ?`, productID).Scan(&result).Error
if err != nil {
- if err == sql.ErrNoRows {
- return "", fmt.Errorf("produit non trouvé")
- }
return "", err
}
- return name, nil
+ if result.Name == "" {
+ return "", fmt.Errorf("produit non trouvé")
+ }
+ return result.Name, nil
}
diff --git a/backend/gestion/db/db_product_price.go b/backend/gestion/db/db_product_price.go
index 4b2500af..f9cd7949 100644
--- a/backend/gestion/db/db_product_price.go
+++ b/backend/gestion/db/db_product_price.go
@@ -3,79 +3,43 @@ package db
import (
"fmt"
"gestion/models"
- "log"
)
-// GetProductPrices récupère tous les prix d'un produit
-func (db *Database) GetProductPrices(productID int) ([]models.ProductPrice, error) {
- log.Printf("💰 [GetProductPrices] Loading prices for product %d", productID)
-
- query := `SELECT id, product_id, quantity, price, created_at
- FROM product_prices
- WHERE product_id = $1
- ORDER BY quantity ASC`
-
- rows, err := db.Query(query, productID)
- if err != nil {
- log.Printf("❌ [GetProductPrices] Erreur query: %v", err)
- return nil, fmt.Errorf("erreur récupération prix: %v", err)
- }
- defer rows.Close()
-
+func (d *Database) GetProductPrices(productID int) ([]models.ProductPrice, error) {
var prices []models.ProductPrice
- for rows.Next() {
- var price models.ProductPrice
- if err := rows.Scan(&price.ID, &price.ProductID, &price.Quantity, &price.Price, &price.CreatedAt); err != nil {
- log.Printf("❌ [GetProductPrices] Erreur scan: %v", err)
- return nil, err
- }
- log.Printf(" 💰 Price: qty=%.0f, price=%.2f", price.Quantity, price.Price)
- prices = append(prices, price)
+ if err := d.GDB.Where("product_id = ?", productID).Order("quantity ASC").Find(&prices).Error; err != nil {
+ return nil, fmt.Errorf("erreur récupération prix: %w", err)
}
-
- log.Printf("✅ [GetProductPrices] Found %d prices for product %d", len(prices), productID)
return prices, nil
}
-// CreateProductPrice ajoute un nouveau prix pour un produit
-func (db *Database) CreateProductPrice(productID int, quantity float64, price float64) error {
- query := `INSERT INTO product_prices (product_id, quantity, price)
- VALUES ($1, $2, $3)`
- _, err := db.Exec(query, productID, quantity, price)
- if err != nil {
- return fmt.Errorf("erreur création prix: %v", err)
+func (d *Database) CreateProductPrice(productID int, quantity float64, price float64) error {
+ p := models.ProductPrice{ProductID: productID, Quantity: quantity, Price: price}
+ if err := d.GDB.Create(&p).Error; err != nil {
+ return fmt.Errorf("erreur création prix: %w", err)
}
return nil
}
-// UpdateProductPrice met à jour un prix
-func (db *Database) UpdateProductPrice(priceID int, quantity float64, price float64) error {
- query := `UPDATE product_prices SET quantity = $1, price = $2 WHERE id = $3`
- result, err := db.Exec(query, quantity, price, priceID)
- if err != nil {
- return fmt.Errorf("erreur mise à jour prix: %v", err)
+func (d *Database) UpdateProductPrice(priceID int, quantity float64, price float64) error {
+ result := d.GDB.Model(&models.ProductPrice{}).Where("id = ?", priceID).
+ Updates(map[string]any{"quantity": quantity, "price": price})
+ if result.Error != nil {
+ return fmt.Errorf("erreur mise à jour prix: %w", result.Error)
}
-
- rowsAffected, _ := result.RowsAffected()
- if rowsAffected == 0 {
+ if result.RowsAffected == 0 {
return fmt.Errorf("prix introuvable")
}
-
return nil
}
-// DeleteProductPrice supprime un prix
-func (db *Database) DeleteProductPrice(priceID int) error {
- query := `DELETE FROM product_prices WHERE id = $1`
- result, err := db.Exec(query, priceID)
- if err != nil {
- return fmt.Errorf("erreur suppression prix: %v", err)
+func (d *Database) DeleteProductPrice(priceID int) error {
+ result := d.GDB.Delete(&models.ProductPrice{}, priceID)
+ if result.Error != nil {
+ return fmt.Errorf("erreur suppression prix: %w", result.Error)
}
-
- rowsAffected, _ := result.RowsAffected()
- if rowsAffected == 0 {
+ if result.RowsAffected == 0 {
return fmt.Errorf("prix introuvable")
}
-
return nil
}
diff --git a/backend/gestion/db/db_referral.go b/backend/gestion/db/db_referral.go
index ecea58ab..a17c8eb7 100644
--- a/backend/gestion/db/db_referral.go
+++ b/backend/gestion/db/db_referral.go
@@ -1,94 +1,60 @@
package db
import (
- "database/sql"
"fmt"
+
+ "gorm.io/gorm"
)
-// GetClientReferralBalance retourne le solde parrainage d'un client.
func (d *Database) GetClientReferralBalance(username string) (float64, error) {
var balance float64
- err := d.QueryRow(
- `SELECT referral_balance FROM clients WHERE username = $1`,
- username,
- ).Scan(&balance)
- if err == sql.ErrNoRows {
- return 0, fmt.Errorf("client non trouvé")
+ err := d.GDB.Table("clients").Select("referral_balance").Where("username = ?", username).Scan(&balance).Error
+ if err != nil {
+ return 0, err
}
- return balance, err
+ return balance, nil
}
-// CreditClientReferral ajoute un montant au solde parrainage d'un client.
func (d *Database) CreditClientReferral(username string, amount float64) error {
if amount <= 0 {
return fmt.Errorf("le montant doit être positif")
}
- res, err := d.Exec(
- `UPDATE clients SET referral_balance = referral_balance + $1 WHERE username = $2`,
- amount, username,
- )
- if err != nil {
- return err
+ result := d.GDB.Exec(`UPDATE clients SET referral_balance = referral_balance + ? WHERE username = ?`, amount, username)
+ if result.Error != nil {
+ return result.Error
}
- n, _ := res.RowsAffected()
- if n == 0 {
+ if result.RowsAffected == 0 {
return fmt.Errorf("client non trouvé")
}
return nil
}
-// DebitReferralBalance déduit atomiquement le solde parrainage avant la création de commande.
-// Gère sa propre transaction avec FOR UPDATE pour éviter le double-spend concurrent.
-// Retourne une erreur si le solde est insuffisant.
func (d *Database) DebitReferralBalance(username string, amount float64) error {
if amount <= 0 {
return nil
}
- tx, err := d.Begin()
- if err != nil {
- return fmt.Errorf("erreur transaction: %w", err)
- }
- var balance float64
- if err := tx.QueryRow(
- `SELECT referral_balance FROM clients WHERE username = $1 FOR UPDATE`,
- username,
- ).Scan(&balance); err != nil {
- tx.Rollback()
- return fmt.Errorf("client non trouvé")
- }
- if balance < amount {
- tx.Rollback()
- return fmt.Errorf("solde parrainage insuffisant (disponible: %.2f€)", balance)
- }
- if _, err := tx.Exec(
- `UPDATE clients SET referral_balance = referral_balance - $1 WHERE username = $2`,
- amount, username,
- ); err != nil {
- tx.Rollback()
- return err
- }
- return tx.Commit()
+ return d.GDB.Transaction(func(tx *gorm.DB) error {
+ var balance float64
+ if err := tx.Raw(`SELECT referral_balance FROM clients WHERE username = ? FOR UPDATE`, username).Scan(&balance).Error; err != nil {
+ return fmt.Errorf("client non trouvé")
+ }
+ if balance < amount {
+ return fmt.Errorf("solde parrainage insuffisant (disponible: %.2f€)", balance)
+ }
+ return tx.Exec(`UPDATE clients SET referral_balance = referral_balance - ? WHERE username = ?`, amount, username).Error
+ })
}
-// UseClientReferralBalance déduit un montant du solde parrainage dans une transaction.
-func (d *Database) UseClientReferralBalance(tx *sql.Tx, username string, amount float64) error {
+func (d *Database) UseClientReferralBalance(tx *gorm.DB, username string, amount float64) error {
if amount <= 0 {
return nil
}
var balance float64
- err := tx.QueryRow(
- `SELECT referral_balance FROM clients WHERE username = $1 FOR UPDATE`,
- username,
- ).Scan(&balance)
- if err != nil {
+ if err := tx.Raw(`SELECT referral_balance FROM clients WHERE username = ? FOR UPDATE`, username).Scan(&balance).Error; err != nil {
return fmt.Errorf("client non trouvé")
}
if balance < amount {
return fmt.Errorf("solde parrainage insuffisant (disponible: %.2f€)", balance)
}
- _, err = tx.Exec(
- `UPDATE clients SET referral_balance = referral_balance - $1 WHERE username = $2`,
- amount, username,
- )
- return err
+ return tx.Exec(`UPDATE clients SET referral_balance = referral_balance - ? WHERE username = ?`, amount, username).Error
}
diff --git a/backend/gestion/db/db_sanction_client.go b/backend/gestion/db/db_sanction_client.go
index 53935b9f..cf307deb 100644
--- a/backend/gestion/db/db_sanction_client.go
+++ b/backend/gestion/db/db_sanction_client.go
@@ -14,36 +14,29 @@ import (
// GetClientCancellationsCount récupère le nombre d'annulations tardives d'un client
func (d *Database) GetClientCancellationsCount(username string) (int, error) {
- var count int
- query := `SELECT COALESCE(cancellations_count, 0) FROM clients WHERE username = $1`
-
- err := d.QueryRow(query, username).Scan(&count)
+ var result struct {
+ Count int `gorm:"column:count"`
+ }
+ err := d.GDB.Raw(`SELECT COALESCE(cancellations_count, 0) as count FROM clients WHERE username = ?`, username).Scan(&result).Error
if err != nil {
log.Printf("❌ [GetCancellationsCount] Erreur: %v", err)
return 0, fmt.Errorf("erreur récupération compteur: %w", err)
}
-
- return count, nil
+ return result.Count, nil
}
// IncrementClientCancellationsCount incrémente le compteur d'annulations
func (d *Database) IncrementClientCancellationsCount(username string) error {
- query := `UPDATE clients
- SET cancellations_count = COALESCE(cancellations_count, 0) + 1,
- updated_at = CURRENT_TIMESTAMP
- WHERE username = $1`
-
- result, err := d.Exec(query, username)
- if err != nil {
- log.Printf("❌ [IncrementCancellations] Erreur: %v", err)
- return fmt.Errorf("erreur incrémentation: %w", err)
+ result := d.GDB.Exec(`
+ UPDATE clients
+ SET cancellations_count = COALESCE(cancellations_count, 0) + 1,
+ updated_at = CURRENT_TIMESTAMP
+ WHERE username = ?`, username)
+ if result.Error != nil {
+ log.Printf("❌ [IncrementCancellations] Erreur: %v", result.Error)
+ return fmt.Errorf("erreur incrémentation: %w", result.Error)
}
-
- rowsAffected, err := result.RowsAffected()
- if err != nil {
- return fmt.Errorf("erreur vérification: %w", err)
- }
- if rowsAffected == 0 {
+ if result.RowsAffected == 0 {
return fmt.Errorf("client non trouvé")
}
@@ -105,22 +98,16 @@ func (d *Database) ApplyCancellationPenalty(username string) (int, error) {
return 0, err
}
- query := `UPDATE clients
- SET amende = $1,
- updated_at = CURRENT_TIMESTAMP
- WHERE username = $2`
-
- result, err := d.Exec(query, float64(penalty), username)
- if err != nil {
- log.Printf("❌ [ApplyCancellationPenalty] Erreur UPDATE: %v", err)
- return 0, fmt.Errorf("erreur application pénalité: %w", err)
+ result := d.GDB.Exec(`
+ UPDATE clients
+ SET amende = ?,
+ updated_at = CURRENT_TIMESTAMP
+ WHERE username = ?`, float64(penalty), username)
+ if result.Error != nil {
+ log.Printf("❌ [ApplyCancellationPenalty] Erreur UPDATE: %v", result.Error)
+ return 0, fmt.Errorf("erreur application pénalité: %w", result.Error)
}
-
- rowsAffected, err := result.RowsAffected()
- if err != nil {
- return 0, fmt.Errorf("erreur vérification: %w", err)
- }
- if rowsAffected == 0 {
+ if result.RowsAffected == 0 {
return 0, fmt.Errorf("client non trouvé")
}
diff --git a/backend/gestion/db/db_settings.go b/backend/gestion/db/db_settings.go
index 97f46ae0..ed0507b3 100644
--- a/backend/gestion/db/db_settings.go
+++ b/backend/gestion/db/db_settings.go
@@ -4,6 +4,8 @@ import (
"encoding/json"
"fmt"
"gestion/models"
+
+ "gorm.io/gorm"
)
// DefaultDeliverySchedule retourne un planning de livraison par défaut (tous les jours, 9h-20h)
@@ -84,66 +86,64 @@ func DefaultSettings() models.AppSettings {
func (d *Database) GetSettings() (models.AppSettings, error) {
settings := DefaultSettings()
- rows, err := d.Query(`SELECT key, value FROM app_settings`)
- if err != nil {
+ var rows []struct {
+ Key string `gorm:"column:key"`
+ Value string `gorm:"column:value"`
+ }
+ if err := d.GDB.Raw(`SELECT key, value FROM app_settings`).Scan(&rows).Error; err != nil {
return settings, fmt.Errorf("erreur lecture settings: %w", err)
}
- defer rows.Close()
- for rows.Next() {
- var key, value string
- if err := rows.Scan(&key, &value); err != nil {
- continue
- }
- switch key {
+ for _, row := range rows {
+ switch row.Key {
case "penalties_enabled":
- settings.PenaltiesEnabled = value == "true"
+ settings.PenaltiesEnabled = row.Value == "true"
case "show_amende_score":
- settings.ShowAmendeScore = value == "true"
+ settings.ShowAmendeScore = row.Value == "true"
case "points_enabled":
- settings.PointsEnabled = value == "true"
+ settings.PointsEnabled = row.Value == "true"
case "points_pools":
var pools []models.PointsPool
- if err := json.Unmarshal([]byte(value), &pools); err == nil {
+ if err := json.Unmarshal([]byte(row.Value), &pools); err == nil {
settings.PointsPools = pools
}
case "referral_enabled":
- settings.ReferralEnabled = value == "true"
+ settings.ReferralEnabled = row.Value == "true"
case "crypto_payment_enabled":
- settings.CryptoPaymentEnabled = value == "true"
+ settings.CryptoPaymentEnabled = row.Value == "true"
case "crypto_only":
- settings.CryptoOnly = value == "true"
+ settings.CryptoOnly = row.Value == "true"
case "nowpayments_api_key":
- settings.NowPaymentsAPIKey = value
+ settings.NowPaymentsAPIKey = row.Value
case "nowpayments_ipn_secret":
- settings.NowPaymentsIPNSecret = value
+ settings.NowPaymentsIPNSecret = row.Value
case "nowpayments_currencies":
var currencies []string
- if err := json.Unmarshal([]byte(value), ¤cies); err == nil {
+ if err := json.Unmarshal([]byte(row.Value), ¤cies); err == nil {
settings.NowPaymentsCurrencies = currencies
}
case "delivery_schedule":
var sched models.DeliverySchedule
- if err := json.Unmarshal([]byte(value), &sched); err == nil {
+ if err := json.Unmarshal([]byte(row.Value), &sched); err == nil {
settings.DeliverySchedule = sched
}
case "postal_zones":
var zones []models.PostalZone
- if err := json.Unmarshal([]byte(value), &zones); err == nil {
+ if err := json.Unmarshal([]byte(row.Value), &zones); err == nil {
settings.PostalZones = zones
}
case "telegram_bot_token":
- settings.TelegramBotToken = value
+ settings.TelegramBotToken = row.Value
case "telegram_bot_username":
- settings.TelegramBotUsername = value
+ settings.TelegramBotUsername = row.Value
case "delivery_mode":
var mode models.DeliveryModeConfig
- if err := json.Unmarshal([]byte(value), &mode); err == nil {
+ if err := json.Unmarshal([]byte(row.Value), &mode); err == nil {
settings.DeliveryMode = mode
}
}
}
- return settings, rows.Err()
+ return settings, nil
}
// UpdateSettings sauvegarde les paramètres dans la DB
@@ -158,7 +158,6 @@ func (d *Database) UpdateSettings(s models.AppSettings) error {
if s.PointsPools == nil {
s.PointsPools = []models.PointsPool{}
}
- // S'assurer que chaque pool a des slices non-nil
for i := range s.PointsPools {
if s.PointsPools[i].Categories == nil {
s.PointsPools[i].Categories = []string{}
@@ -173,15 +172,6 @@ func (d *Database) UpdateSettings(s models.AppSettings) error {
return fmt.Errorf("erreur sérialisation pools: %w", err)
}
- tx, err := d.Begin()
- if err != nil {
- return err
- }
- defer tx.Rollback()
-
- upsert := `INSERT INTO app_settings (key, value) VALUES ($1, $2)
- ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value`
-
if s.NowPaymentsCurrencies == nil {
s.NowPaymentsCurrencies = []string{}
}
@@ -190,6 +180,27 @@ func (d *Database) UpdateSettings(s models.AppSettings) error {
return fmt.Errorf("erreur sérialisation nowpayments_currencies: %w", err)
}
+ schedJSON, err := json.Marshal(s.DeliverySchedule)
+ if err != nil {
+ return fmt.Errorf("erreur sérialisation delivery_schedule: %w", err)
+ }
+
+ if s.PostalZones == nil {
+ s.PostalZones = []models.PostalZone{}
+ }
+ zonesJSON, err := json.Marshal(s.PostalZones)
+ if err != nil {
+ return fmt.Errorf("erreur sérialisation postal_zones: %w", err)
+ }
+
+ if s.DeliveryMode.CategoryRoutes == nil {
+ s.DeliveryMode.CategoryRoutes = []models.CategoryRoute{}
+ }
+ deliveryModeJSON, err := json.Marshal(s.DeliveryMode)
+ if err != nil {
+ return fmt.Errorf("erreur sérialisation delivery_mode: %w", err)
+ }
+
pairs := [][2]string{
{"penalties_enabled", boolStr(s.PenaltiesEnabled)},
{"show_amende_score", boolStr(s.ShowAmendeScore)},
@@ -201,39 +212,22 @@ func (d *Database) UpdateSettings(s models.AppSettings) error {
{"nowpayments_api_key", s.NowPaymentsAPIKey},
{"nowpayments_ipn_secret", s.NowPaymentsIPNSecret},
{"nowpayments_currencies", string(currenciesJSON)},
+ {"delivery_schedule", string(schedJSON)},
+ {"postal_zones", string(zonesJSON)},
+ {"telegram_bot_token", s.TelegramBotToken},
+ {"telegram_bot_username", s.TelegramBotUsername},
+ {"delivery_mode", string(deliveryModeJSON)},
}
- schedJSON, err := json.Marshal(s.DeliverySchedule)
- if err != nil {
- return fmt.Errorf("erreur sérialisation delivery_schedule: %w", err)
- }
- pairs = append(pairs, [2]string{"delivery_schedule", string(schedJSON)})
+ upsert := `INSERT INTO app_settings (key, value) VALUES (?, ?)
+ ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value`
- if s.PostalZones == nil {
- s.PostalZones = []models.PostalZone{}
- }
- zonesJSON, err := json.Marshal(s.PostalZones)
- if err != nil {
- return fmt.Errorf("erreur sérialisation postal_zones: %w", err)
- }
- pairs = append(pairs, [2]string{"postal_zones", string(zonesJSON)})
- pairs = append(pairs, [2]string{"telegram_bot_token", s.TelegramBotToken})
- pairs = append(pairs, [2]string{"telegram_bot_username", s.TelegramBotUsername})
-
- if s.DeliveryMode.CategoryRoutes == nil {
- s.DeliveryMode.CategoryRoutes = []models.CategoryRoute{}
- }
- deliveryModeJSON, err := json.Marshal(s.DeliveryMode)
- if err != nil {
- return fmt.Errorf("erreur sérialisation delivery_mode: %w", err)
- }
- pairs = append(pairs, [2]string{"delivery_mode", string(deliveryModeJSON)})
-
- for _, p := range pairs {
- if _, err = tx.Exec(upsert, p[0], p[1]); err != nil {
- return fmt.Errorf("erreur upsert %s: %w", p[0], err)
+ return d.GDB.Transaction(func(tx *gorm.DB) error {
+ for _, p := range pairs {
+ if err := tx.Exec(upsert, p[0], p[1]).Error; err != nil {
+ return fmt.Errorf("erreur upsert %s: %w", p[0], err)
+ }
}
- }
-
- return tx.Commit()
+ return nil
+ })
}
diff --git a/backend/gestion/db/db_telegram.go b/backend/gestion/db/db_telegram.go
index 44ed1210..b4fc9206 100644
--- a/backend/gestion/db/db_telegram.go
+++ b/backend/gestion/db/db_telegram.go
@@ -2,7 +2,6 @@ package db
import (
"crypto/rand"
- "database/sql"
"encoding/hex"
"encoding/json"
"fmt"
@@ -18,7 +17,7 @@ func (d *Database) MigrateAddTelegramColumns() {
`ALTER TABLE users ADD COLUMN IF NOT EXISTS telegram_chat_id BIGINT`,
}
for _, q := range migrations {
- if _, err := d.Exec(q); err != nil {
+ if err := d.GDB.Exec(q).Error; err != nil {
log.Printf("⚠️ [TELEGRAM_MIGRATION] %v", err)
}
}
@@ -68,53 +67,64 @@ func ValidateAndConsumeLinkToken(token string) (username, role string, err error
}
func (d *Database) SaveClientTelegramChatID(username string, chatID int64) error {
- _, err := d.Exec(`UPDATE clients SET telegram_chat_id = $1 WHERE username = $2`, chatID, username)
- return err
+ return d.GDB.Exec(`UPDATE clients SET telegram_chat_id = ? WHERE username = ?`, chatID, username).Error
}
func (d *Database) GetClientTelegramChatID(username string) (int64, bool, error) {
- var chatID sql.NullInt64
- err := d.QueryRow(`SELECT telegram_chat_id FROM clients WHERE username = $1`, username).Scan(&chatID)
+ var result struct {
+ ChatID *int64 `gorm:"column:telegram_chat_id"`
+ }
+ err := d.GDB.Raw(`SELECT telegram_chat_id FROM clients WHERE username = ?`, username).Scan(&result).Error
if err != nil {
return 0, false, err
}
- return chatID.Int64, chatID.Valid, nil
+ if result.ChatID == nil {
+ return 0, false, nil
+ }
+ return *result.ChatID, true, nil
}
func (d *Database) DeleteClientTelegramChatID(username string) error {
- _, err := d.Exec(`UPDATE clients SET telegram_chat_id = NULL WHERE username = $1`, username)
- return err
+ return d.GDB.Exec(`UPDATE clients SET telegram_chat_id = NULL WHERE username = ?`, username).Error
}
func (d *Database) SaveUserTelegramChatID(username string, chatID int64) error {
- _, err := d.Exec(`UPDATE users SET telegram_chat_id = $1 WHERE username = $2`, chatID, username)
- return err
+ return d.GDB.Exec(`UPDATE users SET telegram_chat_id = ? WHERE username = ?`, chatID, username).Error
}
func (d *Database) GetUserTelegramChatID(username string) (int64, bool, error) {
- var chatID sql.NullInt64
- err := d.QueryRow(`SELECT telegram_chat_id FROM users WHERE username = $1`, username).Scan(&chatID)
+ var result struct {
+ ChatID *int64 `gorm:"column:telegram_chat_id"`
+ }
+ err := d.GDB.Raw(`SELECT telegram_chat_id FROM users WHERE username = ?`, username).Scan(&result).Error
if err != nil {
return 0, false, err
}
- return chatID.Int64, chatID.Valid, nil
+ if result.ChatID == nil {
+ return 0, false, nil
+ }
+ return *result.ChatID, true, nil
}
func (d *Database) DeleteUserTelegramChatID(username string) error {
- _, err := d.Exec(`UPDATE users SET telegram_chat_id = NULL WHERE username = $1`, username)
- return err
+ return d.GDB.Exec(`UPDATE users SET telegram_chat_id = NULL WHERE username = ?`, username).Error
}
// GetUserByTelegramChatID retrouve un utilisateur (clients + users) par chat_id
func (d *Database) GetUserByTelegramChatID(chatID int64) (username, role string, err error) {
- err = d.QueryRow(`SELECT username FROM clients WHERE telegram_chat_id = $1`, chatID).Scan(&username)
- if err == nil {
- return username, "client", nil
+ var clientResult struct {
+ Username string `gorm:"column:username"`
+ }
+ if err = d.GDB.Raw(`SELECT username FROM clients WHERE telegram_chat_id = ?`, chatID).Scan(&clientResult).Error; err == nil && clientResult.Username != "" {
+ return clientResult.Username, "client", nil
}
- err = d.QueryRow(`SELECT username, role FROM users WHERE telegram_chat_id = $1`, chatID).Scan(&username, &role)
- if err == nil {
- return username, role, nil
+ var userResult struct {
+ Username string `gorm:"column:username"`
+ Role string `gorm:"column:role"`
+ }
+ if err = d.GDB.Raw(`SELECT username, role FROM users WHERE telegram_chat_id = ?`, chatID).Scan(&userResult).Error; err == nil && userResult.Username != "" {
+ return userResult.Username, userResult.Role, nil
}
return "", "", fmt.Errorf("aucun compte lié à ce chat_id")
diff --git a/backend/gestion/db/db_users.go b/backend/gestion/db/db_users.go
index 639dc0ac..3675bad8 100644
--- a/backend/gestion/db/db_users.go
+++ b/backend/gestion/db/db_users.go
@@ -1,205 +1,90 @@
package db
import (
- "database/sql"
"fmt"
"gestion/models"
"log"
- "time"
)
func (d *Database) CreateUser(user *models.User) error {
- query := `INSERT INTO users (username, password, role, created_at, updated_at)
- VALUES ($1, $2, $3, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
- RETURNING id, created_at, updated_at`
-
- var createdAt, updatedAt time.Time
- err := d.QueryRow(query, user.Username, user.Password, user.Role).Scan(
- &user.ID,
- &createdAt,
- &updatedAt,
- )
- if err != nil {
+ if err := d.GDB.Create(user).Error; err != nil {
return fmt.Errorf("erreur lors de la création de l'utilisateur: %w", err)
}
-
return nil
}
-// GetAllUsers récupère tous les utilisateurs
func (d *Database) GetAllUsers() ([]*models.User, error) {
- query := `SELECT id, username, password, role, created_at, updated_at
- FROM users ORDER BY created_at DESC`
-
- rows, err := d.Query(query)
- if err != nil {
+ var users []*models.User
+ if err := d.GDB.Order("created_at DESC").Find(&users).Error; err != nil {
return nil, fmt.Errorf("erreur lors de la récupération des utilisateurs: %w", err)
}
- defer rows.Close()
-
- var users []*models.User
- for rows.Next() {
- user := &models.User{}
- var createdAt, updatedAt time.Time
- err := rows.Scan(
- &user.ID,
- &user.Username,
- &user.Password,
- &user.Role,
- &createdAt,
- &updatedAt,
- )
- if err != nil {
- return nil, fmt.Errorf("erreur lors du scan de l'utilisateur: %w", err)
- }
- users = append(users, user)
- }
-
- if err = rows.Err(); err != nil {
- return nil, fmt.Errorf("erreur lors de l'itération des résultats: %w", err)
- }
-
return users, nil
}
func (d *Database) GetAllDeliveryMen() ([]*models.User, error) {
- query := `
- SELECT id, username, password, role
- FROM users
- WHERE role = 'livreur'
- `
-
- rows, err := d.Query(query)
- if err != nil {
- return nil, fmt.Errorf("erreur lors de la récupération des utilisateurs: %w", err)
- }
- defer rows.Close()
-
var users []*models.User
-
- for rows.Next() {
- user := &models.User{}
- err := rows.Scan(
- &user.ID,
- &user.Username,
- &user.Password,
- &user.Role,
- )
- if err != nil {
- return nil, fmt.Errorf("erreur lors du scan de l'utilisateur: %w", err)
- }
- users = append(users, user)
+ if err := d.GDB.Where("role = ?", "livreur").Find(&users).Error; err != nil {
+ return nil, fmt.Errorf("erreur lors de la récupération des livreurs: %w", err)
}
-
- if err = rows.Err(); err != nil {
- return nil, fmt.Errorf("erreur lors de l'itération des résultats: %w", err)
- }
-
return users, nil
}
-// UpdateUser met à jour un utilisateur existant
func (d *Database) UpdateUser(user *models.User) error {
- query := `UPDATE users
- SET username = $1, password = $2, role = $3, updated_at = CURRENT_TIMESTAMP
- WHERE id = $4`
-
- result, err := d.Exec(query, user.Username, user.Password, user.Role, user.ID)
- if err != nil {
- return fmt.Errorf("erreur lors de la mise à jour de l'utilisateur: %w", err)
+ result := d.GDB.Model(user).Updates(models.User{
+ Username: user.Username,
+ Password: user.Password,
+ Role: user.Role,
+ })
+ if result.Error != nil {
+ return fmt.Errorf("erreur lors de la mise à jour de l'utilisateur: %w", result.Error)
}
-
- 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 {
+ if result.RowsAffected == 0 {
return fmt.Errorf("utilisateur non trouvé")
}
-
return nil
}
-// DeleteUser supprime un utilisateur
func (d *Database) DeleteUser(id int) error {
- var role string
- err := d.QueryRow(`SELECT role FROM users WHERE id = $1`, id).Scan(&role)
- if err != nil {
- if err == sql.ErrNoRows {
+ var user models.User
+ if err := d.GDB.First(&user, id).Error; err != nil {
+ if isNotFound(err) {
return fmt.Errorf("utilisateur non trouvé")
}
return fmt.Errorf("erreur lors de la récupération du rôle: %w", err)
}
- _ = d.RevokeAllUserTokens(id, role)
+ _ = d.RevokeAllUserTokens(id, user.Role)
- query := `DELETE FROM users WHERE id = $1`
-
- result, err := d.Exec(query, id)
- if err != nil {
- return fmt.Errorf("erreur lors de la suppression de l'utilisateur: %w", err)
+ result := d.GDB.Delete(&models.User{}, id)
+ if result.Error != nil {
+ return fmt.Errorf("erreur lors de la suppression de l'utilisateur: %w", result.Error)
}
-
- 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 {
+ if result.RowsAffected == 0 {
return fmt.Errorf("utilisateur non trouvé")
}
- log.Printf("✅ Utilisateur supprimé (ID: %d, Role: %s)", id, role)
+ log.Printf("✅ Utilisateur supprimé (ID: %d, Role: %s)", id, user.Role)
return nil
}
-// GetUserByID récupère un utilisateur par son ID
func (d *Database) GetUserByID(id int) (*models.User, error) {
- user := &models.User{}
- query := `SELECT id, username, password, role, created_at, updated_at
- FROM users WHERE id = $1`
-
- var createdAt, updatedAt time.Time
- err := d.QueryRow(query, id).Scan(
- &user.ID,
- &user.Username,
- &user.Password,
- &user.Role,
- &createdAt,
- &updatedAt,
- )
-
- if err == sql.ErrNoRows {
- return nil, fmt.Errorf("utilisateur non trouvé")
- }
- if err != nil {
+ var user models.User
+ if err := d.GDB.First(&user, id).Error; err != nil {
+ if isNotFound(err) {
+ return nil, fmt.Errorf("utilisateur non trouvé")
+ }
return nil, fmt.Errorf("erreur lors de la récupération de l'utilisateur: %w", err)
}
-
- return user, nil
+ return &user, nil
}
func (d *Database) GetUserByUsername(username string) (*models.User, error) {
- user := &models.User{}
- query := `SELECT id, username, password, role
- FROM users WHERE username = $1`
-
- err := d.QueryRow(query, username).Scan(
- &user.ID,
- &user.Username,
- &user.Password,
- &user.Role,
- )
-
- if err == sql.ErrNoRows {
- return nil, fmt.Errorf("utilisateur non trouvé")
- }
-
- // Autres erreurs
- if err != nil {
+ var user models.User
+ if err := d.GDB.Where("username = ?", username).First(&user).Error; err != nil {
+ if isNotFound(err) {
+ return nil, fmt.Errorf("utilisateur non trouvé")
+ }
return nil, fmt.Errorf("erreur lors de la récupération de l'utilisateur: %w", err)
}
-
- return user, nil
+ return &user, nil
}
diff --git a/backend/gestion/db/helpers.go b/backend/gestion/db/helpers.go
new file mode 100644
index 00000000..e4f6ae69
--- /dev/null
+++ b/backend/gestion/db/helpers.go
@@ -0,0 +1,8 @@
+package db
+
+import "gorm.io/gorm"
+
+// isNotFound retourne true si l'erreur GORM est un "record not found"
+func isNotFound(err error) bool {
+ return err == gorm.ErrRecordNotFound
+}
diff --git a/backend/gestion/go.mod b/backend/gestion/go.mod
index 27fa329a..d16f4379 100644
--- a/backend/gestion/go.mod
+++ b/backend/gestion/go.mod
@@ -30,6 +30,12 @@ require (
github.com/gorilla/context v1.1.2 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/gorilla/sessions v1.4.0 // indirect
+ github.com/jackc/pgpassfile v1.0.0 // indirect
+ github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
+ github.com/jackc/pgx/v5 v5.6.0 // indirect
+ github.com/jackc/puddle/v2 v2.2.2 // indirect
+ github.com/jinzhu/inflection v1.0.0 // indirect
+ github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
@@ -50,4 +56,6 @@ require (
golang.org/x/text v0.27.0 // indirect
golang.org/x/tools v0.34.0 // indirect
google.golang.org/protobuf v1.36.9 // indirect
+ gorm.io/driver/postgres v1.6.0 // indirect
+ gorm.io/gorm v1.31.1 // indirect
)
diff --git a/backend/gestion/go.sum b/backend/gestion/go.sum
index 9b9bdff2..5c211e60 100644
--- a/backend/gestion/go.sum
+++ b/backend/gestion/go.sum
@@ -52,6 +52,18 @@ github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kX
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
+github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
+github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
+github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
+github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
+github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
+github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
+github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
+github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
+github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
+github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
+github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
@@ -83,6 +95,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
@@ -117,3 +130,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
+gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
+gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
+gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
diff --git a/backend/gestion/handlers/auth.go b/backend/gestion/handlers/auth.go
index f563bfc2..0a0e5aa4 100644
--- a/backend/gestion/handlers/auth.go
+++ b/backend/gestion/handlers/auth.go
@@ -176,11 +176,13 @@ func RegisterClient(c *gin.Context) {
func AdminCreateClient(c *gin.Context) {
var req models.RegisterClientRequest
if err := c.ShouldBindJSON(&req); err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"error": "Données invalides"})
+ log.Printf("❌ [ADMIN_CREATE_CLIENT] Binding error: %v | body: username=%q nom=%q prenom=%q tel=%q", err, req.Username, req.Nom, req.Prenom, req.Telephone)
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Données invalides", "detail": err.Error()})
return
}
if !utils.ValidatePhoneNumber(req.Telephone) {
+ log.Printf("❌ [ADMIN_CREATE_CLIENT] Téléphone invalide: %q", req.Telephone)
c.JSON(http.StatusBadRequest, gin.H{"error": "Numéro de téléphone invalide"})
return
}
diff --git a/backend/gestion/handlers/cancel_command.go b/backend/gestion/handlers/cancel_command.go
index e98d3fec..7cdccda0 100644
--- a/backend/gestion/handlers/cancel_command.go
+++ b/backend/gestion/handlers/cancel_command.go
@@ -325,7 +325,7 @@ func GetAllCancelledOrders(c *gin.Context) {
// ✅ ENRICHIR les données (sans exposer d'infos sensibles inutiles)
var enrichedOrders []map[string]any
for _, order := range cancelledOrders {
- orderID, _ := order["id"].(int)
+ orderID, _ := strconv.Atoi(fmt.Sprintf("%v", order["id"]))
items, _ := database.GetCommandItems(orderID)
logs, _ := database.GetCommandLogs(orderID)
diff --git a/backend/gestion/handlers/deleviry.go b/backend/gestion/handlers/deleviry.go
index 91959b19..fa2f48c9 100644
--- a/backend/gestion/handlers/deleviry.go
+++ b/backend/gestion/handlers/deleviry.go
@@ -41,7 +41,7 @@ func GetMyDeliveries(c *gin.Context) {
filteredCommands := make([]gin.H, len(commands))
for i, cmd := range commands {
- commandID, _ := cmd["id"].(int)
+ commandID, _ := strconv.Atoi(fmt.Sprintf("%v", cmd["id"]))
items, _ := database.GetCommandItems(commandID)
// Client info SANS téléphone
diff --git a/backend/gestion/handlers/history.go b/backend/gestion/handlers/history.go
index 902e8ae5..9cf09ed0 100644
--- a/backend/gestion/handlers/history.go
+++ b/backend/gestion/handlers/history.go
@@ -10,6 +10,7 @@ import (
"gestion/db"
"log"
"net/http"
+ "strconv"
"github.com/gin-gonic/gin"
)
@@ -128,8 +129,8 @@ func GetMyCompletedOrdersWithItems(c *gin.Context) {
var enrichedCommands []map[string]interface{}
for _, command := range commands {
- commandID, ok := command["id"].(int)
- if !ok {
+ commandID, _ := strconv.Atoi(fmt.Sprintf("%v", command["id"]))
+ if commandID == 0 {
continue
}
diff --git a/backend/gestion/main.go b/backend/gestion/main.go
index e0419225..346f0704 100644
--- a/backend/gestion/main.go
+++ b/backend/gestion/main.go
@@ -22,32 +22,26 @@ import (
)
func main() {
- // Forcer la timezone Europe/Paris (UTC+1/+2)
if loc, err := time.LoadLocation("Europe/Paris"); err == nil {
time.Local = loc
} else {
log.Printf("⚠️ Impossible de charger la timezone Europe/Paris: %v", err)
}
- // Chargement des variables d'environnement
if err := godotenv.Load(); err != nil {
log.Println("⚠️ Aucun fichier .env trouvé, utilisation des valeurs par défaut.")
}
- // Initialisation de la base de données
database := db.InitDB()
defer database.Close()
log.Printf("✅ Database initialisée: %+v", database)
- // Initialisation de Redis
db.InitRedis()
defer db.Redis.Close()
log.Println("✅ Redis initialisé avec succès")
- // Initialisation du service de géolocalisation
geoService := services.NewGeoService(db.Redis, db.RedisCtx)
log.Println("✅ Service de géolocalisation initialisé")
- // Initialisation du service Telegram
telegramService := services.NewTelegramService()
if telegramService.IsConfigured() {
log.Println("✅ Service Telegram initialisé")
@@ -60,15 +54,12 @@ func main() {
log.Println("ℹ️ Service Telegram désactivé (TELEGRAM_BOT_TOKEN non défini)")
}
- // Migration: ajout des colonnes telegram_chat_id
database.MigrateAddTelegramColumns()
- // Priorité DB > .env pour la config Telegram
if dbSettings, err := database.GetSettings(); err == nil {
telegramService.Reload(dbSettings.TelegramBotToken, dbSettings.TelegramBotUsername)
if dbSettings.TelegramBotToken != "" {
log.Printf("✅ [TELEGRAM] Config chargée depuis la DB (username: %s)", dbSettings.TelegramBotUsername)
- // Enregistrer le webhook si l'URL est configurée (et pas déjà fait depuis l'env)
if webhookURL := os.Getenv("TELEGRAM_WEBHOOK_URL"); webhookURL != "" {
if err := telegramService.SetWebhook(webhookURL); err != nil {
log.Printf("⚠️ [TELEGRAM] Erreur enregistrement webhook (DB reload): %v", err)
@@ -77,9 +68,6 @@ func main() {
}
}
- // ============================================
- // 🧹 NETTOYAGE INITIAL DES COMMANDES INVALIDES
- // ============================================
log.Println("")
log.Println("🧹 Démarrage du nettoyage des commandes invalides...")
removed, err := database.CleanupInvalidQueueCommands()
@@ -93,32 +81,21 @@ func main() {
}
}
- // ============================================
- // 🔄 DÉMARRAGE DU SCHEDULER DE NETTOYAGE AUTO
- // ============================================
go database.StartQueueCleanupScheduler()
log.Println("✅ Scheduler de nettoyage démarré (toutes les 5 min)")
- // ============================================
- // 🔄 SYNCHRONISATION DES STATUTS LIVREURS
- // ============================================
if err := database.SyncAllDeliverymanStatuses(); err != nil {
log.Printf("⚠️ Erreur synchronisation statuts: %v", err)
} else {
log.Println("✅ Synchronisation des statuts livreurs terminée")
}
- // ============================================
- // ⭐ DÉMARRAGE DU CRON JOB AUTO-ASSIGNATION
- // ============================================
go workers.StartAutoAssignmentCron(database, geoService)
log.Println("✅ Cron job auto-assignation démarré (5 min)")
- // Démarrage des workers Redis en arrière-plan
go workers.StartRedisWorkers(database)
log.Println("✅ Workers Redis démarrés")
- // Worker de vérification des paiements crypto (recharge les clés dynamiquement)
workers.StartDynamicPaymentChecker(database, func() *services.NowPaymentsClient {
s, err := database.GetSettings()
if err != nil || !s.CryptoPaymentEnabled || s.NowPaymentsAPIKey == "" {
@@ -130,7 +107,6 @@ func main() {
gin.SetMode(gin.ReleaseMode)
r := gin.Default()
- // Configuration des sessions
store := cookie.NewStore([]byte(os.Getenv("SESSION_SECRET")))
store.Options(sessions.Options{
Path: "/",
@@ -142,7 +118,6 @@ func main() {
})
r.Use(sessions.Sessions("mysession", store))
- // Configuration CORS
r.Use(cors.New(cors.Config{
AllowOrigins: []string{"https://uber-stup.club", "https://5.181.0.112.nip.io", "https://5.181.0.112.nip.io:8080", "https://5.181.0.112.nip.io:8443", "https://mln-uber.club", "http://localhost:5173", "http://5.181.0.112"},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"},
@@ -151,7 +126,6 @@ func main() {
AllowCredentials: true,
}))
- // Middleware pour injecter la base de données et geoService
r.Use(func(c *gin.Context) {
c.Set("database", database)
c.Set("geoService", geoService)
@@ -167,12 +141,8 @@ func main() {
c.Next()
})
- // Fichiers statiques
r.Static("/uploads", "./uploads")
- // ============================================
- // ENREGISTRER TOUTES LES ROUTES
- // ============================================
routes.SetupRoutes(r, database, geoService)
if err := r.Run(":8080"); err != nil {
diff --git a/backend/gestion/models/address.go b/backend/gestion/models/address.go
index 65cd58ea..c20117b2 100644
--- a/backend/gestion/models/address.go
+++ b/backend/gestion/models/address.go
@@ -3,9 +3,11 @@ package models
import "time"
type Address struct {
- ID int64 `db:"id"`
- InvalidAddress string `db:"invalid_address"`
- CorrectAddress string `db:"correct_address"`
- CreatedAt time.Time `db:"created_at"`
- UpdatedAt time.Time `db:"updated_at"`
+ ID int64 `db:"id" gorm:"primaryKey;autoIncrement"`
+ InvalidAddress string `db:"invalid_address" gorm:"column:invalid_address"`
+ CorrectAddress string `db:"correct_address" gorm:"column:correct_address"`
+ CreatedAt time.Time `db:"created_at" gorm:"autoCreateTime"`
+ UpdatedAt time.Time `db:"updated_at" gorm:"autoUpdateTime"`
}
+
+func (Address) TableName() string { return "adresse_correction" }
diff --git a/backend/gestion/models/alert.go b/backend/gestion/models/alert.go
index a56e5f0f..e7eb0410 100644
--- a/backend/gestion/models/alert.go
+++ b/backend/gestion/models/alert.go
@@ -1,10 +1,14 @@
package models
+import "time"
+
type AlertPolicy struct {
- ID int `json:"id"`
- Username string `json:"username"`
- Status string `json:"status"`
- Message string `json:"message"`
- CreatedAt string `json:"created_at"`
- UpdatedAt string `json:"updated_at"`
+ ID int `json:"id" gorm:"primaryKey;autoIncrement"`
+ Username string `json:"username" gorm:"column:username"`
+ Status string `json:"status" gorm:"column:status"`
+ Message string `json:"message" gorm:"column:message"`
+ CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
+ UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
}
+
+func (AlertPolicy) TableName() string { return "alerte_policy" }
diff --git a/backend/gestion/models/client.go b/backend/gestion/models/client.go
index 8e6e410a..485e9715 100644
--- a/backend/gestion/models/client.go
+++ b/backend/gestion/models/client.go
@@ -4,19 +4,20 @@ package models
import "time"
type Client struct {
- ID int `json:"id"`
- Username string `json:"username"`
- Password string `json:"-"` // Ne jamais exposer le password dans le JSON
- Nom string `json:"nom"`
- Prenom string `json:"prenom"`
- Telephone string `json:"telephone"`
- Command int `json:"command"`
- PointsExtra map[string]int `json:"points_extra"`
- Amende float64 `json:"amende"`
- CancellationsCount int `json:"cancellations_count"`
- LastPenaltyReason string `json:"last_penalty_reason"`
- MustChangePassword bool `json:"must_change_password"`
- PushToken string `json:"-"`
- ReferralBalance float64 `json:"referral_balance"`
- CreatedAt time.Time `json:"created_at"`
+ ID int `gorm:"primaryKey;autoIncrement" json:"id"`
+ Username string `gorm:"column:username" json:"username"`
+ Password string `gorm:"column:password" json:"-"`
+ Nom string `gorm:"column:nom" json:"nom"`
+ Prenom string `gorm:"column:prenom" json:"prenom"`
+ Telephone string `gorm:"column:telephone" json:"telephone"`
+ Command int `gorm:"column:command" json:"command"`
+ PointsExtra map[string]int `gorm:"-" json:"points_extra"`
+ Amende float64 `gorm:"column:amende" json:"amende"`
+ CancellationsCount int `gorm:"column:cancellations_count" json:"cancellations_count"`
+ LastPenaltyReason string `gorm:"column:last_penalty_reason" json:"last_penalty_reason"`
+ MustChangePassword bool `gorm:"column:must_change_password" json:"must_change_password"`
+ ReferralBalance float64 `gorm:"column:referral_balance" json:"referral_balance"`
+ CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
}
+
+func (Client) TableName() string { return "clients" }
diff --git a/backend/gestion/models/livreur.go b/backend/gestion/models/livreur.go
index 83e0682e..2c9e0d68 100644
--- a/backend/gestion/models/livreur.go
+++ b/backend/gestion/models/livreur.go
@@ -10,14 +10,16 @@ type LivreurPosition struct {
}
type DeliveryIssue struct {
- ID int `json:"id"`
- CommandID int `json:"command_id"`
- IssueType string `json:"issue_type"`
- Description string `json:"description"`
- Status string `json:"status"` // "open", "in_progress", "resolved"
- ReportedBy string `json:"reported_by"`
- ResolvedBy string `json:"resolved_by,omitempty"`
- Resolution string `json:"resolution,omitempty"`
- CreatedAt time.Time `json:"created_at"`
- UpdatedAt time.Time `json:"updated_at"`
+ ID int `json:"id" gorm:"primaryKey;autoIncrement"`
+ CommandID int `json:"command_id" gorm:"column:command_id;index"`
+ IssueType string `json:"issue_type" gorm:"column:issue_type"`
+ Description string `json:"description" gorm:"column:description"`
+ Status string `json:"status" gorm:"column:status"`
+ ReportedBy string `json:"reported_by" gorm:"column:reported_by"`
+ ResolvedBy string `json:"resolved_by,omitempty" gorm:"column:resolved_by"`
+ Resolution string `json:"resolution,omitempty" gorm:"column:resolution"`
+ CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
+ UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
}
+
+func (DeliveryIssue) TableName() string { return "delivery_issues" }
diff --git a/backend/gestion/models/media.go b/backend/gestion/models/media.go
index 5c642fbe..9dfc41dd 100644
--- a/backend/gestion/models/media.go
+++ b/backend/gestion/models/media.go
@@ -3,13 +3,15 @@ package models
import "time"
type Media struct {
- ID int `json:"id"`
- ProductID int `json:"product_id"`
- Type string `json:"type"`
- URL string `json:"url"`
- CreatedAt time.Time `json:"created_at"` // ✅ Ajouté
+ ID int `gorm:"primaryKey;autoIncrement" json:"id"`
+ ProductID int `gorm:"column:product_id" json:"product_id"`
+ Type string `gorm:"column:type" json:"type"`
+ URL string `gorm:"column:url" json:"url"`
+ CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
}
+func (Media) TableName() string { return "media" }
+
// Méthodes pour l'interface Database
func (m *Media) GetProductID() int { return m.ProductID }
func (m *Media) GetType() string { return m.Type }
diff --git a/backend/gestion/models/payment.go b/backend/gestion/models/payment.go
index 4693002b..1b759117 100644
--- a/backend/gestion/models/payment.go
+++ b/backend/gestion/models/payment.go
@@ -4,15 +4,17 @@ import "time"
// CryptoPayment suit un paiement crypto NowPayments lié à une commande
type CryptoPayment struct {
- ID int `json:"id"`
- CommandID int `json:"command_id"`
- NowPaymentID string `json:"nowpayment_id"`
- Status string `json:"status"` // waiting, confirming, confirmed, sending, partially_paid, finished, failed, refunded, expired
- PriceAmount float64 `json:"price_amount"`
- PriceCurrency string `json:"price_currency"`
- PayCurrency string `json:"pay_currency"`
- PayAddress string `json:"pay_address"`
- PayAmount float64 `json:"pay_amount"`
- CreatedAt time.Time `json:"created_at"`
- UpdatedAt time.Time `json:"updated_at"`
+ ID int `json:"id" gorm:"primaryKey;autoIncrement"`
+ CommandID int `json:"command_id" gorm:"column:command_id;index"`
+ NowPaymentID string `json:"nowpayment_id" gorm:"column:nowpayment_id;uniqueIndex"`
+ Status string `json:"status" gorm:"column:status"`
+ PriceAmount float64 `json:"price_amount" gorm:"column:price_amount"`
+ PriceCurrency string `json:"price_currency" gorm:"column:price_currency"`
+ PayCurrency string `json:"pay_currency" gorm:"column:pay_currency"`
+ PayAddress string `json:"pay_address" gorm:"column:pay_address"`
+ PayAmount float64 `json:"pay_amount" gorm:"column:pay_amount"`
+ CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
+ UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
}
+
+func (CryptoPayment) TableName() string { return "crypto_payments" }
diff --git a/backend/gestion/models/product.go b/backend/gestion/models/product.go
index defc823a..b75ccdad 100644
--- a/backend/gestion/models/product.go
+++ b/backend/gestion/models/product.go
@@ -3,26 +3,30 @@ package models
import "time"
type Product struct {
- ID int `json:"id"`
- Name string `json:"name" binding:"required"`
- Category string `json:"category" binding:"required"`
- Description string `json:"description"`
- Stock float64 `json:"stock"` // ← ajouter le stock ici
- Unit string `json:"unit"` // kg | g | bag | l | cl | pcs | u
- Prices []ProductPrice `json:"prices"`
- Media []Media `json:"media,omitempty"`
- CreatedAt time.Time `json:"created_at"`
- UpdatedAt time.Time `json:"updated_at"`
+ ID int `json:"id" gorm:"primaryKey;autoIncrement"`
+ Name string `json:"name" gorm:"column:name" binding:"required"`
+ Category string `json:"category" gorm:"column:category" binding:"required"`
+ Description string `json:"description" gorm:"column:description"`
+ Stock float64 `json:"stock" gorm:"column:stock"`
+ Unit string `json:"unit" gorm:"column:unit"`
+ Prices []ProductPrice `json:"prices" gorm:"foreignKey:ProductID"`
+ Media []Media `json:"media,omitempty" gorm:"foreignKey:ProductID"`
+ CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
+ UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
}
+func (Product) TableName() string { return "products" }
+
type ProductPrice struct {
- ID int `json:"id"`
- ProductID int `json:"product_id"`
- Quantity float64 `json:"quantity" binding:"required"`
- Price float64 `json:"price" binding:"required"`
- CreatedAt time.Time `json:"created_at"`
+ ID int `json:"id" gorm:"primaryKey;autoIncrement"`
+ ProductID int `json:"product_id" gorm:"column:product_id;index"`
+ Quantity float64 `json:"quantity" gorm:"column:quantity" binding:"required"`
+ Price float64 `json:"price" gorm:"column:price" binding:"required"`
+ CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
}
+func (ProductPrice) TableName() string { return "product_prices" }
+
type StockInfo struct {
ProductID int `json:"product_id"`
ProductName string `json:"product_name"`
diff --git a/backend/gestion/models/user.go b/backend/gestion/models/user.go
index 2f085297..e5481fe7 100644
--- a/backend/gestion/models/user.go
+++ b/backend/gestion/models/user.go
@@ -1,8 +1,14 @@
package models
+import "time"
+
type User struct {
- ID int `json:"id"`
- Username string `json:"username"`
- Password string `json:"password,omitempty"` // omitempty pour ne pas l'exposer dans les réponses JSON
- Role string `json:"role"` // "user" ou "admin"
+ ID int `json:"id" gorm:"primaryKey;autoIncrement"`
+ Username string `json:"username" gorm:"column:username;uniqueIndex"`
+ Password string `json:"password,omitempty" gorm:"column:password"`
+ Role string `json:"role" gorm:"column:role"`
+ CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
+ UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
}
+
+func (User) TableName() string { return "users" }
diff --git a/backend/gestion/workers/cron_auto_assign.go b/backend/gestion/workers/cron_auto_assign.go
index d0c0d46d..0f79bcb5 100644
--- a/backend/gestion/workers/cron_auto_assign.go
+++ b/backend/gestion/workers/cron_auto_assign.go
@@ -5,6 +5,7 @@ import (
"gestion/db"
"gestion/services"
"log"
+ "strconv"
"time"
)
@@ -57,14 +58,10 @@ func processAutoAssignmentWithPriority(database *db.Database, geoService *servic
skippedCount := 0
for i, cmd := range commands {
- commandID, ok := cmd["id"].(int)
- if !ok {
- if idFloat, ok := cmd["id"].(float64); ok {
- commandID = int(idFloat)
- } else {
- skippedCount++
- continue
- }
+ commandID, _ := strconv.Atoi(fmt.Sprintf("%v", cmd["id"]))
+ if commandID == 0 {
+ skippedCount++
+ continue
}
// Log la position dans la queue de priorité
diff --git a/mobile/src/api/api_types.ts b/mobile/src/api/api_types.ts
index 4290010a..b971b63d 100644
--- a/mobile/src/api/api_types.ts
+++ b/mobile/src/api/api_types.ts
@@ -226,6 +226,7 @@ export interface OrderItem {
*/
export interface OrderDetail {
id: number;
+ client_order_number?: number;
username: string;
status: string;
delivery_address?: string;
diff --git a/mobile/src/screens/client/OrderDetailsScreen.tsx b/mobile/src/screens/client/OrderDetailsScreen.tsx
index 56fdd40f..342e53e2 100644
--- a/mobile/src/screens/client/OrderDetailsScreen.tsx
+++ b/mobile/src/screens/client/OrderDetailsScreen.tsx
@@ -327,7 +327,7 @@ export default function OrderDetailsScreen() {
>
- Commande #{order.id}
+ Commande #{order.client_order_number ?? order.id}
diff --git a/mobile/src/screens/client/OrderHistoryScreen.tsx b/mobile/src/screens/client/OrderHistoryScreen.tsx
index 1100f9ac..5562c563 100644
--- a/mobile/src/screens/client/OrderHistoryScreen.tsx
+++ b/mobile/src/screens/client/OrderHistoryScreen.tsx
@@ -370,7 +370,7 @@ export default function OrderHistoryScreen() {
>
- Commande #{order.id}
+ Commande #{order.client_order_number ?? order.id}
diff --git a/mobile/src/screens/client/OrderTrackingScreen.tsx b/mobile/src/screens/client/OrderTrackingScreen.tsx
index dc6db74b..4d4ffac2 100644
--- a/mobile/src/screens/client/OrderTrackingScreen.tsx
+++ b/mobile/src/screens/client/OrderTrackingScreen.tsx
@@ -406,7 +406,7 @@ export default function OrderTrackingScreen() {
>
- Commande #{order.id}
+ Commande #{order.client_order_number ?? order.id}
diff --git a/mobile/src/screens/client/ProfileScreen.tsx b/mobile/src/screens/client/ProfileScreen.tsx
index ba30d340..d4775391 100644
--- a/mobile/src/screens/client/ProfileScreen.tsx
+++ b/mobile/src/screens/client/ProfileScreen.tsx
@@ -6,6 +6,7 @@ import {
StyleSheet,
TouchableOpacity,
Alert,
+ Modal,
KeyboardAvoidingView,
Platform,
Linking,
@@ -45,6 +46,9 @@ export default function ProfileScreen() {
const [telegramEnabled, setTelegramEnabled] = useState(false);
const [telegramLoading, setTelegramLoading] = useState(false);
+ // Modal confirmation infos par défaut
+ const [showSaveModal, setShowSaveModal] = useState(false);
+
const loadData = useCallback(async () => {
setLoadingProfile(true);
const [savedAddress, savedPhone, savedSignal, profileRes, tgStatus] = await Promise.all([
@@ -85,6 +89,7 @@ export default function ProfileScreen() {
AsyncStorage.setItem(STORAGE_PHONE, defaultPhone.trim()),
AsyncStorage.setItem(STORAGE_SIGNAL, signalPseudo.trim()),
]);
+ setShowSaveModal(false);
Alert.alert("Enregistré", "Informations par défaut sauvegardées");
};
@@ -201,6 +206,74 @@ export default function ProfileScreen() {
},
saveBtnText: { color: "#fff", fontSize: fontSize.sm, fontWeight: fontWeight.semibold },
saveBtnTextSecondary: { color: colors.accent },
+ // Modal
+ modalOverlay: {
+ flex: 1,
+ backgroundColor: "rgba(0,0,0,0.6)",
+ justifyContent: "center",
+ alignItems: "center",
+ padding: spacing.l,
+ },
+ modalBox: {
+ backgroundColor: colors.bgCard,
+ borderRadius: borderRadius.md,
+ borderWidth: 1,
+ borderColor: colors.borderLight,
+ padding: spacing.xl,
+ width: "100%",
+ maxWidth: 360,
+ alignItems: "center",
+ },
+ modalIconWrap: {
+ width: 52,
+ height: 52,
+ borderRadius: 26,
+ backgroundColor: colors.accent + "22",
+ borderWidth: 1,
+ borderColor: colors.accent + "44",
+ justifyContent: "center",
+ alignItems: "center",
+ marginBottom: spacing.m,
+ },
+ modalTitle: {
+ color: colors.textPrimary,
+ fontSize: fontSize.md,
+ fontWeight: fontWeight.bold,
+ textAlign: "center",
+ marginBottom: spacing.s,
+ },
+ modalBody: {
+ color: colors.textMuted,
+ fontSize: fontSize.sm,
+ textAlign: "center",
+ lineHeight: 20,
+ marginBottom: spacing.l,
+ },
+ modalActions: {
+ flexDirection: "row",
+ gap: spacing.m,
+ width: "100%",
+ },
+ modalBtnCancel: {
+ flex: 1,
+ paddingVertical: spacing.m,
+ borderRadius: borderRadius.sm,
+ backgroundColor: "transparent",
+ borderWidth: 1,
+ borderColor: colors.borderLight,
+ alignItems: "center",
+ },
+ modalBtnConfirm: {
+ flex: 1,
+ paddingVertical: spacing.m,
+ borderRadius: borderRadius.sm,
+ backgroundColor: "transparent",
+ borderWidth: 1,
+ borderColor: colors.accent + "66",
+ alignItems: "center",
+ },
+ modalBtnCancelText: { color: colors.textMuted, fontSize: fontSize.sm, fontWeight: fontWeight.semibold },
+ modalBtnConfirmText: { color: colors.accent, fontSize: fontSize.sm, fontWeight: fontWeight.semibold },
}), [colors]);
if (loadingProfile) {
@@ -316,7 +389,7 @@ export default function ProfileScreen() {
setShowSaveModal(true)}
>
@@ -365,6 +438,46 @@ export default function ProfileScreen() {
)}
+
+ setShowSaveModal(false)}
+ >
+ setShowSaveModal(false)}
+ >
+ {}}>
+
+
+
+
+ Enregistrer les infos par défaut ?
+
+ Adresse, téléphone de livraison et pseudo Signal seront sauvegardés et pré-remplis lors de vos prochaines commandes.
+
+
+ setShowSaveModal(false)}
+ >
+ Annuler
+
+
+ Confirmer
+
+
+
+
+
+
+
);
}