chore: refacto

This commit is contained in:
2026-03-27 22:23:46 +01:00
parent 75bf4caaa1
commit 5380abe8ed
67 changed files with 1751 additions and 2573 deletions
+67 -244
View File
@@ -5,6 +5,7 @@ import (
"fmt"
"gestion/models"
"log"
"slices"
"strings"
"time"
)
@@ -42,6 +43,35 @@ func validateAddress(address string) error {
return nil
}
type basketItem struct {
ProductID int
Quantity float64
Price float64
}
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 {
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)
total += item.Price
}
if err := rows.Err(); err != nil {
return nil, 0, err
}
return items, total, nil
}
// validateCommandStatus vérifie si le statut est valide
func validateCommandStatus(status string) error {
validStatuses := map[string]bool{
@@ -63,48 +93,22 @@ func validateCommandStatus(status string) error {
}
func (d *Database) CreateCommand(username string) (*models.Command, error) {
// Récupérer le client pour obtenir son adresse
var adresse string
clientQuery := `SELECT username FROM clients WHERE username = $1`
err := d.QueryRow(clientQuery, username).Scan(&adresse)
if err != nil {
// Si le client n'existe pas dans la table clients, utiliser une adresse par défaut
adresse = "Adresse non spécifiée"
}
// Récupérer les items du panier
basketQuery := `SELECT product_id, quantity, price FROM baskets WHERE username = $1`
rows, err := d.Query(basketQuery, username)
basketItems, totalPrix, err := d.fetchBasketItems(username)
if err != nil {
return nil, fmt.Errorf("erreur lors de la récupération du panier: %w", err)
}
defer rows.Close()
type BasketItem struct {
ProductID int
Quantity float64
Price float64
}
var basketItems []BasketItem
totalPrix := 0.0
for rows.Next() {
var item BasketItem
if err := rows.Scan(&item.ProductID, &item.Quantity, &item.Price); err != nil {
return nil, fmt.Errorf("erreur lors du scan du panier: %w", err)
}
basketItems = append(basketItems, item)
// ✅ FIX: Ne PAS multiplier par quantity
totalPrix += item.Price // Prix déjà calculé pour les grammes
return nil, err
}
if len(basketItems) == 0 {
return nil, fmt.Errorf("le panier est vide")
}
// Créer la commande
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`
@@ -120,9 +124,7 @@ func (d *Database) CreateCommand(username string) (*models.Command, error) {
return nil, fmt.Errorf("erreur lors de la création de la commande: %w", err)
}
// Insérer les items de la commande
for _, item := range basketItems {
// Récupérer le nom du produit
productName, err := d.GetProductNameByID(item.ProductID)
if err != nil {
productName = "Produit inconnu"
@@ -136,7 +138,6 @@ func (d *Database) CreateCommand(username string) (*models.Command, error) {
}
}
// Vider le panier
clearBasketQuery := `DELETE FROM baskets WHERE username = $1`
_, err = d.Exec(clearBasketQuery, username)
if err != nil {
@@ -153,7 +154,6 @@ func (d *Database) CreateCommand(username string) (*models.Command, error) {
}
func (d *Database) CreateCommandWithAddress(username, deliveryAddress string) (*models.Command, error) {
// ✅ SÉCURITÉ: Validation des entrées
if err := validateUsername(username); err != nil {
return nil, err
}
@@ -162,9 +162,6 @@ func (d *Database) CreateCommandWithAddress(username, deliveryAddress string) (*
return nil, err
}
log.Printf("📝 [CreateCommandWithAddress] START - user=%s", username)
// Récupérer les infos du client
client, err := d.GetClientByUsername(username)
if err != nil {
log.Printf("⚠️ Client non trouvé: %v", err)
@@ -179,51 +176,26 @@ func (d *Database) CreateCommandWithAddress(username, deliveryAddress string) (*
clientTelephone = sanitizeString(client.Telephone)
}
// Récupérer les items du panier
basketQuery := `SELECT product_id, quantity, price FROM baskets WHERE username = $1`
rows, err := d.Query(basketQuery, username)
basketItems, totalPrix, err := d.fetchBasketItems(username)
if err != nil {
log.Printf("❌ Erreur query basket: %v", err)
return nil, fmt.Errorf("erreur récupération panier: %w", err)
}
defer rows.Close()
type BasketItem struct {
ProductID int
Quantity float64
Price float64
}
var basketItems []BasketItem
totalPrix := 0.0
for rows.Next() {
var item BasketItem
if err := rows.Scan(&item.ProductID, &item.Quantity, &item.Price); err != nil {
return nil, fmt.Errorf("erreur scan panier: %w", err)
}
// ✅ SÉCURITÉ: Validation des valeurs
if item.ProductID <= 0 || item.Quantity <= 0 || item.Price < 0 {
return nil, fmt.Errorf("données panier invalides")
}
basketItems = append(basketItems, item)
totalPrix += item.Price
return nil, err
}
if len(basketItems) == 0 {
return nil, fmt.Errorf("le panier est vide")
}
// ✅ SÉCURITÉ: Vérifier que le total est cohérent
for _, item := range basketItems {
if item.ProductID <= 0 || item.Quantity <= 0 || item.Price < 0 {
return nil, fmt.Errorf("données panier invalides")
}
}
if totalPrix <= 0 || totalPrix > 100000 {
return nil, fmt.Errorf("montant de commande invalide: %.2f€", totalPrix)
}
log.Printf("✅ Panier validé: %d items, total=%.2f€", len(basketItems), totalPrix)
// Créer la commande en base
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`
@@ -235,13 +207,9 @@ func (d *Database) CreateCommandWithAddress(username, deliveryAddress string) (*
&commandID, &createdAt, &updatedAt,
)
if err != nil {
log.Printf("❌ Erreur INSERT commande: %v", err)
return nil, fmt.Errorf("erreur création commande: %w", err)
}
log.Printf("✅ Commande %d créée en base", commandID)
// Insérer les items de la commande AVEC les infos client
for _, item := range basketItems {
productName, err := d.GetProductNameByID(item.ProductID)
if err != nil || productName == "" {
@@ -265,7 +233,6 @@ func (d *Database) CreateCommandWithAddress(username, deliveryAddress string) (*
return nil, fmt.Errorf("erreur insertion items: %w", err)
}
// Décrémenter le stock du produit
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 {
@@ -275,28 +242,22 @@ func (d *Database) CreateCommandWithAddress(username, deliveryAddress string) (*
rowsAffected, _ := result.RowsAffected()
if rowsAffected == 0 {
// Le stock était déjà réservé lors de l'ajout au panier (DecrementProductStockByID).
// On ne bloque pas la commande : tous les articles doivent être insérés.
log.Printf("⚠️ [CHECKOUT] Stock déjà réservé pour produit %d (double réservation panier/checkout)", item.ProductID)
}
}
// Vider le panier
clearBasketQuery := `DELETE FROM baskets WHERE username = $1`
_, err = d.Exec(clearBasketQuery, username)
if err != nil {
log.Printf("⚠️ Erreur vidage panier: %v", err)
}
// Ajouter un log de commande
sanitizedAddress := sanitizeLogMessage(deliveryAddress)
d.AddCommandLog(commandID, "created",
fmt.Sprintf("Commande créée - Adresse: %s - Total: %.2f€ - Client: %s %s",
sanitizedAddress, totalPrix, sanitizeLogMessage(clientNom), sanitizeLogMessage(clientPrenom)),
username)
log.Printf("🎉 SUCCÈS - Commande %d | User: %s | Total: %.2f€", commandID, username, totalPrix)
command := &models.Command{
ID: commandID,
Username: username,
@@ -310,9 +271,7 @@ func (d *Database) CreateCommandWithAddress(username, deliveryAddress string) (*
return command, nil
}
// ✅ CORRECTION MAJEURE: Utiliser des paramètres préparés au lieu de fmt.Sprintf
func (d *Database) GetAllCommands(status, username string) ([]map[string]interface{}, error) {
// ✅ SÉCURITÉ: Validation des paramètres
func (d *Database) GetAllCommands(status, username string) ([]map[string]any, error) {
if username != "" {
if err := validateUsername(username); err != nil {
return nil, err
@@ -327,14 +286,14 @@ func (d *Database) GetAllCommands(status, username string) ([]map[string]interfa
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.proposed_address, c.address_proposal_status,
ROW_NUMBER() OVER (PARTITION BY c.username ORDER BY c.id) AS client_order_number
FROM commandes c
WHERE 1=1`
args := []interface{}{}
argPosition := 1
// Filtrage du statut
if status == "" {
query += ` AND c.status IN ('pending', 'assigned', 'en_route', 'arrived', 'livre')`
} else {
@@ -343,16 +302,13 @@ func (d *Database) GetAllCommands(status, username string) ([]map[string]interfa
argPosition++
}
// Filtrage du username
if username != "" {
query += fmt.Sprintf(" AND c.username = $%d", argPosition)
args = append(args, username)
argPosition++
}
query += " ORDER BY c.created_at DESC LIMIT 1000" // ✅ Protection DoS
log.Printf("🔍 [GetAllCommands] Query avec %d args", len(args))
query += " ORDER BY c.created_at DESC LIMIT 1000"
rows, err := d.Query(query, args...)
if err != nil {
@@ -360,7 +316,7 @@ func (d *Database) GetAllCommands(status, username string) ([]map[string]interfa
}
defer rows.Close()
var commands []map[string]interface{}
var commands []map[string]any
for rows.Next() {
var id int
var username, status, adresse string
@@ -369,16 +325,16 @@ func (d *Database) GetAllCommands(status, username string) ([]map[string]interfa
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)
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)
}
// ✅ SÉCURITÉ: Sanitization de l'adresse
adresse = sanitizeString(adresse)
command := map[string]interface{}{
command := map[string]any{
"id": id,
"username": username,
"status": status,
@@ -387,6 +343,7 @@ func (d *Database) GetAllCommands(status, username string) ([]map[string]interfa
"created_at": createdAt,
"updated_at": updatedAt,
"address_proposal_status": addressProposalStatus,
"client_order_number": clientOrderNumber,
}
if livreurAssign.Valid {
@@ -408,8 +365,6 @@ func (d *Database) GetAllCommands(status, username string) ([]map[string]interfa
return nil, fmt.Errorf("erreur lors de l'itération des résultats: %w", err)
}
log.Printf("✅ [GetAllCommands] %d commandes récupérées", len(commands))
return commands, nil
}
@@ -427,11 +382,11 @@ func (d *Database) SetCommandReferralUsed(commandID int, amount float64) error {
return err
}
func (d *Database) GetCommandByID(id int) (map[string]interface{}, error) {
// ✅ Déjà sécurisé avec paramètre $1
query := `SELECT id, username, status, adresse, total_prix, livreur_assign, created_at, updated_at,
proposed_address, address_proposal_status, referral_used
FROM commandes WHERE id = $1`
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
@@ -440,6 +395,7 @@ func (d *Database) GetCommandByID(id int) (map[string]interface{}, error) {
var addressProposalStatus string
var totalPrix, referralUsed float64
var createdAt, updatedAt time.Time
var clientOrderNumber int
err := d.QueryRow(query, id).Scan(
&commandID,
@@ -453,6 +409,7 @@ func (d *Database) GetCommandByID(id int) (map[string]interface{}, error) {
&proposedAddress,
&addressProposalStatus,
&referralUsed,
&clientOrderNumber,
)
if err == sql.ErrNoRows {
@@ -462,7 +419,7 @@ func (d *Database) GetCommandByID(id int) (map[string]interface{}, error) {
return nil, fmt.Errorf("erreur lors de la récupération de la commande: %w", err)
}
command := map[string]interface{}{
command := map[string]any{
"id": commandID,
"username": username,
"status": status,
@@ -472,6 +429,7 @@ func (d *Database) GetCommandByID(id int) (map[string]interface{}, error) {
"updated_at": updatedAt,
"address_proposal_status": addressProposalStatus,
"referral_used": referralUsed,
"client_order_number": clientOrderNumber,
}
if livreurAssign.Valid {
@@ -505,7 +463,6 @@ func (d *Database) GetCommandAddress(commandID int) (string, error) {
}
func (d *Database) UpdateCommandAddress(commandID int, deliveryAddress string) error {
// ✅ SÉCURITÉ: Validation de l'adresse
if len(deliveryAddress) > 500 {
return fmt.Errorf("adresse trop longue (max 500 caractères)")
}
@@ -565,7 +522,6 @@ func (d *Database) ProposeAddressChange(commandID int, proposedAddress, proposed
func (d *Database) RespondToAddressProposal(commandID int, clientUsername string, accepted bool) error {
var query string
if accepted {
// Remplace l'adresse par la proposition
query = `UPDATE commandes
SET adresse = proposed_address, proposed_address = NULL,
address_proposal_status = 'accepted', updated_at = CURRENT_TIMESTAMP
@@ -600,17 +556,8 @@ func (d *Database) RespondToAddressProposal(commandID int, clientUsername string
// UpdateCommandStatus met à jour le statut d'une commande
func (d *Database) UpdateCommandStatus(commandID int, status string) error {
// ✅ SÉCURITÉ: Validation du statut
validStatuses := []string{"pending", "assigned", "en_route", "arrived", "livre", "approved", "cancelled", "disabled"}
isValid := false
for _, vs := range validStatuses {
if status == vs {
isValid = true
break
}
}
if !isValid {
return fmt.Errorf("statut invalide: %s", status)
if err := validateCommandStatus(status); err != nil {
return err
}
query := `UPDATE commandes SET status = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2`
@@ -632,9 +579,7 @@ func (d *Database) UpdateCommandStatus(commandID int, status string) error {
return nil
}
// AddCommandLog ajoute un log pour une commande
func (d *Database) AddCommandLog(commandID int, status, message, author string) error {
// ✅ SÉCURITÉ: Sanitize le message pour éviter l'injection dans les logs
sanitizedMessage := sanitizeLogMessage(message)
sanitizedAuthor := sanitizeLogMessage(author)
@@ -643,7 +588,6 @@ func (d *Database) AddCommandLog(commandID int, status, message, author string)
_, err := d.Exec(query, commandID, status, sanitizedMessage, sanitizedAuthor)
if err != nil {
// Si la table n'existe pas, on ne retourne pas d'erreur pour ne pas bloquer
log.Printf("⚠️ Avertissement: impossible d'ajouter le log (table command_logs peut-être manquante): %v", err)
return nil
}
@@ -652,7 +596,7 @@ 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]interface{}, error) {
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
@@ -662,11 +606,11 @@ func (d *Database) GetCommandLogs(commandID int) ([]map[string]interface{}, erro
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]interface{}{}, nil
return []map[string]any{}, nil
}
defer rows.Close()
var logs []map[string]interface{}
var logs []map[string]any
for rows.Next() {
var id, commandID int
var status, message, author string
@@ -677,7 +621,7 @@ func (d *Database) GetCommandLogs(commandID int) ([]map[string]interface{}, erro
return nil, fmt.Errorf("erreur lors du scan du log: %w", err)
}
logEntry := map[string]interface{}{
logEntry := map[string]any{
"id": id,
"command_id": commandID,
"status": status,
@@ -695,108 +639,14 @@ func (d *Database) GetCommandLogs(commandID int) ([]map[string]interface{}, erro
return logs, nil
}
// ✅ CORRECTION MAJEURE: Utiliser des paramètres préparés
func (d *Database) GetCommandsWithFilter(status, username string, excludeApproved bool) ([]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 1=1`
args := []interface{}{}
argPosition := 1
// ✅ Filtrer par username si fourni
if username != "" {
query += fmt.Sprintf(" AND c.username = $%d", argPosition)
args = append(args, username)
argPosition++
}
// ✅ Filtrer par status si fourni avec validation
if status != "" {
validStatuses := []string{"pending", "assigned", "en_route", "arrived", "livre", "approved", "cancelled", "disabled"}
isValid := false
for _, vs := range validStatuses {
if status == vs {
isValid = true
break
}
}
if !isValid {
return nil, fmt.Errorf("statut invalide")
}
query += fmt.Sprintf(" AND c.status = $%d", argPosition)
args = append(args, status)
argPosition++
}
// ✅ Exclure les commandes approved si demandé
if excludeApproved {
query += " AND c.status != 'approved'"
log.Printf("🔍 [FILTER] Exclusion des commandes approved activée")
}
query += " ORDER BY c.created_at DESC"
log.Printf("🔍 [FILTER] Query: %s | Args: %v", query, args)
rows, err := d.Query(query, args...)
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]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 lors du scan de la 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 lors de l'itération des résultats: %w", err)
}
log.Printf("✅ [FILTER] %d commandes trouvées (excludeApproved=%v)", len(commands), excludeApproved)
return commands, nil
}
// ✅ NOUVELLE FONCTION: Sanitize les messages de log
func sanitizeLogMessage(message string) string {
// Supprimer les caractères de contrôle et limiter la longueur
sanitized := strings.Map(func(r rune) rune {
if r < 32 || r == 127 {
return -1 // Supprimer les caractères de contrôle
return -1
}
return r
}, message)
// Limiter à 1000 caractères
if len(sanitized) > 1000 {
sanitized = sanitized[:1000]
}
@@ -805,17 +655,13 @@ func sanitizeLogMessage(message string) string {
}
func (d *Database) ValidateDeliveryAtomic(commandID int, adminUsername string) (int, error) {
log.Printf("🔒 [ValidateAtomic] START - cmd=%d, admin=%s", commandID, adminUsername)
// ✅ ÉTAPE 1: Démarrer une transaction
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() // Rollback automatique si pas de commit
defer tx.Rollback()
// ✅ ÉTAPE 2: SELECT FOR UPDATE pour verrouiller la commande
var currentStatus, cmdUsername, livreurAssign string
var totalPrix float64
err = tx.QueryRow(`
@@ -837,28 +683,19 @@ func (d *Database) ValidateDeliveryAtomic(commandID int, adminUsername string) (
log.Printf("📋 [ValidateAtomic] Commande trouvée - status=%s, client=%s, livreur=%s",
currentStatus, cmdUsername, livreurAssign)
// ✅ ÉTAPE 3: Vérifier que le statut permet la validation
validStatuses := []string{"assigned", "en_route", "pending", "livre"}
isValid := false
for _, s := range validStatuses {
if currentStatus == s {
isValid = true
break
}
}
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)
}
// ✅ ÉTAPE 4: Vérifier si déjà approuvée (double protection)
if currentStatus == "approved" {
log.Printf("⚠️ [ValidateAtomic] Commande %d déjà approuvée", commandID)
return 0, fmt.Errorf("commande déjà approuvée")
}
// ✅ ÉTAPE 5: UPDATE avec vérification du statut (protection race condition)
result, err := tx.Exec(`
UPDATE commandes
SET status = 'approved', updated_at = CURRENT_TIMESTAMP
@@ -876,14 +713,10 @@ func (d *Database) ValidateDeliveryAtomic(commandID int, adminUsername string) (
return 0, fmt.Errorf("commande déjà modifiée par une autre requête")
}
log.Printf("✅ [ValidateAtomic] Statut mis à jour: %s → approved", currentStatus)
// ✅ ÉTAPE 6: Calculer et ajouter les points au client
totalPoints := 0
if cmdUsername != "" {
log.Printf("🔍 [ValidateAtomic] Calcul points pour client: %s", cmdUsername)
// Utiliser la version transactionnelle du calcul de points
points, _, err := d.CalculateAndAddPointsForCommandTx(tx, commandID, cmdUsername)
if err != nil {
log.Printf("❌ [ValidateAtomic] Erreur calcul/ajout points: %v", err)
@@ -893,7 +726,6 @@ func (d *Database) ValidateDeliveryAtomic(commandID int, adminUsername string) (
log.Printf("✅ [ValidateAtomic] %d points attribués à %s", totalPoints, cmdUsername)
// ✅ ÉTAPE 7: Incrémenter le compteur de commandes du client
_, err = tx.Exec(`
UPDATE clients
SET command = command + 1, updated_at = CURRENT_TIMESTAMP
@@ -901,13 +733,11 @@ func (d *Database) ValidateDeliveryAtomic(commandID int, adminUsername string) (
`, cmdUsername)
if err != nil {
log.Printf("⚠️ [ValidateAtomic] Erreur incrémentation compteur: %v", err)
// On ne bloque pas pour ça
} else {
log.Printf("✅ [ValidateAtomic] Compteur commandes incrémenté pour %s", cmdUsername)
}
}
// ✅ ÉTAPE 8: Ajouter un log dans command_logs
_, err = tx.Exec(`
INSERT INTO command_logs (command_id, status, message, author, created_at)
VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP)
@@ -917,10 +747,8 @@ func (d *Database) ValidateDeliveryAtomic(commandID int, adminUsername string) (
if err != nil {
log.Printf("⚠️ [ValidateAtomic] Erreur ajout log: %v", err)
// On ne bloque pas pour ça
}
// ✅ ÉTAPE 9: Commit de la transaction
if err := tx.Commit(); err != nil {
log.Printf("❌ [ValidateAtomic] Erreur COMMIT: %v", err)
return 0, fmt.Errorf("erreur commit transaction: %w", err)
@@ -929,12 +757,9 @@ func (d *Database) ValidateDeliveryAtomic(commandID int, adminUsername string) (
log.Printf("🎉 [ValidateAtomic] SUCCÈS - Commande %d validée, %d points attribués",
commandID, totalPoints)
// ✅ ÉTAPE 10: Optimiser la queue du livreur (APRÈS le commit)
// Cette opération est faite en dehors de la transaction car elle touche Redis
if livreurAssign != "" {
log.Printf("📦 [ValidateAtomic] Optimisation queue pour livreur: %s", livreurAssign)
go func() {
// Async pour ne pas bloquer la réponse
err := d.CompleteDeliveryAndProcessNext(livreurAssign, commandID)
if err != nil {
log.Printf("⚠️ [ValidateAtomic] Erreur optimisation queue: %v", err)
@@ -942,9 +767,7 @@ func (d *Database) ValidateDeliveryAtomic(commandID int, adminUsername string) (
}()
}
// ✅ ÉTAPE 11: Invalider les caches Redis (APRÈS le commit)
go func() {
// Async pour ne pas bloquer
commandCacheKey := fmt.Sprintf("command:%d", commandID)
Redis.Del(RedisCtx, commandCacheKey)