Files
projet_gestion_commande/backend/gestion/db/db_sanction_client.go
T
Xor290 22a8d5026c
Frontend Admin - EAS Build / build (push) Canceled after 0s
Frontend Client - EAS Build / build (push) Canceled after 0s
Backend - Build & Lint / build (push) Failing after 25m18s
Frontend Web - Build & Lint / build (push) Failing after 9m58s
chore: build
2026-08-06 12:06:05 +02:00

149 lines
4.6 KiB
Go

// ============================================
// db/cancel_sanctions_db.go
// GESTION DES SANCTIONS ÉVOLUTIVES
// ============================================
package db
import (
"fmt"
"gestion/models"
"log"
"sort"
"gorm.io/gorm"
)
// GetClientCancellationsCount récupère le nombre d'annulations tardives d'un client
func (d *Database) GetClientCancellationsCount(username string) (int, error) {
var result struct {
Count int `gorm:"column:count"`
}
err := d.GDB.Table("clients").Select("COALESCE(cancellations_count, 0) as count").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 result.Count, nil
}
// penaltyForCount retourne le montant du palier applicable pour un nombre d'annulations donné
func penaltyForCount(count int, tiers []models.PenaltyTier) int {
if len(tiers) == 0 {
return 0
}
sorted := make([]models.PenaltyTier, len(tiers))
copy(sorted, tiers)
sort.Slice(sorted, func(i, j int) bool {
return sorted[i].MinCancel > sorted[j].MinCancel
})
for _, t := range sorted {
if count >= t.MinCancel {
return t.Amount
}
}
return sorted[len(sorted)-1].Amount
}
// penaltyTiers charge le barème de pénalités configuré, avec repli sur le barème par défaut si les settings sont indisponibles
func (d *Database) penaltyTiers(logCtx string) []models.PenaltyTier {
settings, err := d.GetSettings()
if err != nil {
log.Printf("⚠️ [%s] Impossible de charger les settings, barème par défaut: %v", logCtx, err)
settings = DefaultSettings()
}
return settings.PenaltyTiers
}
// CalculateCancellationPenalty calcule la pénalité selon l'historique et le barème configuré
func (d *Database) CalculateCancellationPenalty(username string) (int, error) {
count, err := d.GetClientCancellationsCount(username)
if err != nil {
return 0, err
}
penalty := penaltyForCount(count, d.penaltyTiers("CalculatePenalty"))
log.Printf("💰 [CalculatePenalty] Client %s - Annulations: %d → Pénalité: %d points",
username, count, penalty)
return penalty, nil
}
// ApplyCancellationPenalty applique une pénalité (cumulative) et incrémente le compteur d'annulations.
// Verrouillée via FOR UPDATE pour éviter qu'un appel concurrent (même client, deux livraisons en parallèle)
// calcule la pénalité sur un compteur pas encore à jour, et l'amende s'additionne au lieu d'écraser
// le solde existant (cohérent avec CancelCommandAtomic pour l'annulation côté client).
func (d *Database) ApplyCancellationPenalty(username string) (int, error) {
tiers := d.penaltyTiers("ApplyCancellationPenalty")
var penalty int
err := d.GDB.Transaction(func(tx *gorm.DB) error {
var count int
if err := tx.Raw(`
SELECT COALESCE(cancellations_count, 0) FROM clients
WHERE username = ? FOR UPDATE`, username).Scan(&count).Error; err != nil {
return fmt.Errorf("erreur récupération compteur: %w", err)
}
penalty = penaltyForCount(count, tiers)
log.Printf("⚠️ [ApplyCancellationPenalty] Client %s - Pénalité calculée: %d points", username, penalty)
result := tx.Exec(`
UPDATE clients
SET cancellations_count = COALESCE(cancellations_count, 0) + 1,
amende = amende + ?,
updated_at = CURRENT_TIMESTAMP
WHERE username = ?`, penalty, username)
if result.Error != nil {
log.Printf("❌ [ApplyCancellationPenalty] Erreur UPDATE: %v", result.Error)
return fmt.Errorf("erreur application pénalité: %w", result.Error)
}
if result.RowsAffected == 0 {
return fmt.Errorf("client non trouvé")
}
log.Printf("✅ [ApplyCancellationPenalty] Amende %d appliquée à %s", penalty, username)
return nil
})
if err != nil {
return 0, err
}
cacheKey := fmt.Sprintf("client:%s", username)
Redis.Del(RedisCtx, cacheKey)
return penalty, nil
}
// GetClientCancellationHistory récupère l'historique d'annulations d'un client
func (d *Database) GetClientCancellationHistory(username string) (map[string]any, error) {
nextPenalty, err := d.CalculateCancellationPenalty(username)
if err != nil {
return nil, err
}
count, err := d.GetClientCancellationsCount(username)
if err != nil {
return nil, err
}
settings, _ := d.GetSettings()
client, err := d.GetClientByUsername(username)
var currentAmende float64
if err == nil {
currentAmende = client.Amende
}
return map[string]any{
"cancellations_count": count,
"current_amende": currentAmende,
"next_penalty": nextPenalty,
"penalty_tiers": settings.PenaltyTiers,
"warning": "Une amende sera appliquée lors de la prochaine annulation tardive",
}, nil
}