chore: refacto
This commit is contained in:
@@ -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(¤tStatus, &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(¤tPointsWeed, ¤tPointsZipette)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// ============================================
|
||||
|
||||
Reference in New Issue
Block a user