|
|
|
@@ -27,25 +27,6 @@ func (d *Database) GetClientCancellationsCount(username string) (int, error) {
|
|
|
|
|
return result.Count, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// IncrementClientCancellationsCount incrémente le compteur d'annulations
|
|
|
|
|
func (d *Database) IncrementClientCancellationsCount(username string) error {
|
|
|
|
|
result := d.GDB.Model(&models.Client{}).Where("username = ?", username).Updates(map[string]any{
|
|
|
|
|
"cancellations_count": gorm.Expr("COALESCE(cancellations_count, 0) + 1"),
|
|
|
|
|
})
|
|
|
|
|
if result.Error != nil {
|
|
|
|
|
log.Printf("❌ [IncrementCancellations] Erreur: %v", result.Error)
|
|
|
|
|
return fmt.Errorf("erreur incrémentation: %w", result.Error)
|
|
|
|
|
}
|
|
|
|
|
if result.RowsAffected == 0 {
|
|
|
|
|
return fmt.Errorf("client non trouvé")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cacheKey := fmt.Sprintf("client:%s", username)
|
|
|
|
|
Redis.Del(RedisCtx, cacheKey)
|
|
|
|
|
|
|
|
|
|
return 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 {
|
|
|
|
@@ -64,6 +45,16 @@ func penaltyForCount(count int, tiers []models.PenaltyTier) int {
|
|
|
|
|
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)
|
|
|
|
@@ -71,13 +62,7 @@ func (d *Database) CalculateCancellationPenalty(username string) (int, error) {
|
|
|
|
|
return 0, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
settings, err := d.GetSettings()
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("⚠️ [CalculatePenalty] Impossible de charger les settings, barème par défaut: %v", err)
|
|
|
|
|
settings = DefaultSettings()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
penalty := penaltyForCount(count, settings.PenaltyTiers)
|
|
|
|
|
penalty := penaltyForCount(count, d.penaltyTiers("CalculatePenalty"))
|
|
|
|
|
|
|
|
|
|
log.Printf("💰 [CalculatePenalty] Client %s - Annulations: %d → Pénalité: %d points",
|
|
|
|
|
username, count, penalty)
|
|
|
|
@@ -85,30 +70,48 @@ func (d *Database) CalculateCancellationPenalty(username string) (int, error) {
|
|
|
|
|
return penalty, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ApplyCancellationPenalty applique une pénalité et incrémente le compteur d'annulations
|
|
|
|
|
// 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) {
|
|
|
|
|
penalty, err := d.CalculateCancellationPenalty(username)
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
log.Printf("⚠️ [ApplyCancellationPenalty] Client %s - Pénalité calculée: %d points", username, penalty)
|
|
|
|
|
|
|
|
|
|
if err := d.IncrementClientCancellationsCount(username); err != nil {
|
|
|
|
|
return 0, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result := d.GDB.Model(&models.Client{}).Where("username = ?", username).Update("amende", float64(penalty))
|
|
|
|
|
if result.Error != nil {
|
|
|
|
|
log.Printf("❌ [ApplyCancellationPenalty] Erreur UPDATE: %v", result.Error)
|
|
|
|
|
return 0, fmt.Errorf("erreur application pénalité: %w", result.Error)
|
|
|
|
|
}
|
|
|
|
|
if result.RowsAffected == 0 {
|
|
|
|
|
return 0, fmt.Errorf("client non trouvé")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
log.Printf("✅ [ApplyCancellationPenalty] Amende %d appliquée à %s", penalty, username)
|
|
|
|
|
|
|
|
|
|
cacheKey := fmt.Sprintf("client:%s", username)
|
|
|
|
|
Redis.Del(RedisCtx, cacheKey)
|
|
|
|
|
|
|
|
|
|