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
+36 -100
View File
@@ -10,6 +10,7 @@ import (
"database/sql"
"fmt"
"log"
"slices"
"time"
)
@@ -17,17 +18,15 @@ import (
// ANNULATION ATOMIQUE
// ============================================
func (d *Database) CancelCommandAtomic(commandID int, username, reason string, force bool) (int, map[string]int, error) {
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)
// ✅ TRANSACTION
tx, err := d.Begin()
if err != nil {
return 0, nil, fmt.Errorf("erreur transaction: %w", err)
return 0, fmt.Errorf("erreur transaction: %w", err)
}
defer tx.Rollback()
// ✅ SELECT FOR UPDATE - Verrouiller la ligne
var currentStatus, cmdUsername, livreurAssign string
err = tx.QueryRow(`
SELECT status, username, COALESCE(livreur_assign, '')
@@ -37,163 +36,101 @@ func (d *Database) CancelCommandAtomic(commandID int, username, reason string, f
`, commandID).Scan(&currentStatus, &cmdUsername, &livreurAssign)
if err == sql.ErrNoRows {
return 0, nil, fmt.Errorf("commande non trouvée")
return 0, fmt.Errorf("commande non trouvée")
}
if err != nil {
return 0, nil, err
return 0, err
}
log.Printf("📋 [CancelAtomic] Trouvée - status=%s, owner=%s, livreur=%s", currentStatus, cmdUsername, livreurAssign)
// ✅ VÉRIFIER PROPRIÉTÉ
if cmdUsername != username {
return 0, nil, fmt.Errorf("commande ne vous appartient pas")
return 0, fmt.Errorf("commande ne vous appartient pas")
}
// ✅ VÉRIFIER STATUT
nonCancellableStatuses := []string{"livre", "approved", "cancelled", "disabled"}
for _, s := range nonCancellableStatuses {
if currentStatus == s {
return 0, nil, fmt.Errorf("impossible d'annuler")
}
if slices.Contains(nonCancellableStatuses, currentStatus) {
return 0, fmt.Errorf("impossible d'annuler")
}
// ============================================
// ✅ FIX: DÉTECTION CORRECTE DE L'ANNULATION TARDIVE
// ============================================
// Une annulation est tardive UNIQUEMENT si :
// 1. Un livreur est assigné
// 2. Le statut est "en_route" (livreur parti) OU "arrived" (livreur arrivé)
// 3. OU une ETA a été définie (ce qui signifie que le livreur est en route)
isLateCancel := false
if livreurAssign != "" {
// Cas 1: Statut en_route ou arrived = toujours tardif
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 {
// Cas 2: Pour les autres statuts, vérifier si une ETA existe
hasRealETA := d.CheckCommandETAExistsAndValid(commandID)
if hasRealETA {
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)
}
log.Printf("✅ [CancelAtomic] Annulation SANS PÉNALITÉ - Statut: %s, Pas d'ETA valide", currentStatus)
}
} else {
log.Printf("✅ [CancelAtomic] Annulation SANS PÉNALITÉ - Aucun livreur assigné")
}
// ✅ SI ANNULATION TARDIVE SANS CONFIRMATION
if isLateCancel && !force {
return 0, nil, fmt.Errorf("confirmation requise")
return 0, fmt.Errorf("confirmation requise")
}
// ✅ UPDATE STATUT (avec vérification pour éviter race condition)
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, nil, err
return 0, err
}
rows, _ := result.RowsAffected()
if rows == 0 {
return 0, nil, fmt.Errorf("commande déjà modifiée")
if rows, _ := result.RowsAffected(); rows == 0 {
return 0, fmt.Errorf("commande déjà modifiée")
}
log.Printf("✅ [CancelAtomic] Statut mis à jour: %s → cancelled", currentStatus)
// ✅ REMBOURSER LE STOCK ATOMIQUEMENT
_, err = tx.Exec(`
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)
if err != nil {
`, commandID); err != nil {
log.Printf("⚠️ [CancelAtomic] Erreur remboursement stock: %v", err)
} else {
log.Printf("✅ [CancelAtomic] Stock remboursé")
}
// ============================================
// APPLIQUER PÉNALITÉ SI ANNULATION TARDIVE
// ============================================
penalty := 0
pointsLost := map[string]int{"weed": 0, "zipette": 0}
if isLateCancel && force {
if isLateCancel {
log.Printf("⚠️ [CancelAtomic] Annulation tardive confirmée - Application pénalité")
// ✅ RÉCUPÉRER LES POINTS ACTUELS
var currentPointsWeed, currentPointsZipette int
err := tx.QueryRow(`
SELECT point, point_zipette FROM clients WHERE username = $1
`, username).Scan(&currentPointsWeed, &currentPointsZipette)
if err == nil {
pointsLost["weed"] = currentPointsWeed
pointsLost["zipette"] = currentPointsZipette
// ✅ CALCULER PÉNALITÉ
penalty, _ = d.CalculateCancellationPenalty(username)
// ✅ APPLIQUER: Remettre points à 0 + Ajouter pénalité + Incrémenter compteur
_, err = tx.Exec(`
UPDATE clients
SET point = 0,
point_zipette = 0,
amende = amende + $1,
cancellations_count = COALESCE(cancellations_count, 0) + 1,
updated_at = CURRENT_TIMESTAMP
WHERE username = $2
`, penalty, username)
if err != nil {
log.Printf("❌ [CancelAtomic] Erreur pénalité: %v", err)
} else {
log.Printf("⚠️ [CancelAtomic] Pénalité: %d pts, Points perdus: weed=%d, zipette=%d",
penalty, pointsLost["weed"], pointsLost["zipette"])
}
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)
}
}
// ✅ LOG
_, err = tx.Exec(`
if _, err = tx.Exec(`
INSERT INTO command_logs (command_id, status, message, author, created_at)
VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP)
`, commandID, "cancelled",
fmt.Sprintf("Annulée par %s - Raison: %s", username, reason),
username)
if err != nil {
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)
}
// ✅ COMMIT
if err := tx.Commit(); err != nil {
return 0, nil, fmt.Errorf("erreur commit: %w", err)
return 0, fmt.Errorf("erreur commit: %w", err)
}
// ✅ NETTOYER QUEUE (async, après commit)
if livreurAssign != "" {
go func() {
err := d.CleanupCompletedCommandFromQueue(commandID, livreurAssign)
if err != nil {
if err := d.CleanupCompletedCommandFromQueue(commandID, livreurAssign); err != nil {
log.Printf("⚠️ [CancelAtomic] Erreur cleanup queue: %v", err)
}
}()
}
// ✅ INVALIDER CACHES (async)
go func() {
Redis.Del(RedisCtx,
fmt.Sprintf("command:%d", commandID),
@@ -203,8 +140,7 @@ func (d *Database) CancelCommandAtomic(commandID int, username, reason string, f
}()
log.Printf("🎉 [CancelAtomic] SUCCÈS - Commande %d annulée", commandID)
return penalty, pointsLost, nil
return penalty, nil
}
// ============================================