From a95ee51f6c0ad9639bd03390b158f78f126eb3e3 Mon Sep 17 00:00:00 2001 From: Xor290 Date: Tue, 30 Jun 2026 22:06:55 +0200 Subject: [PATCH] chore: build --- backend/gestion/db/db_commands.go | 26 ++++++++++++++++++++++++- backend/gestion/db/db_init.go | 5 +++++ backend/gestion/db/db_parrain.go | 30 +++++++++++++++++++++++++++++ backend/gestion/handlers/parrain.go | 20 +++++++++---------- 4 files changed, 69 insertions(+), 12 deletions(-) diff --git a/backend/gestion/db/db_commands.go b/backend/gestion/db/db_commands.go index 4d6728fa..328ac686 100644 --- a/backend/gestion/db/db_commands.go +++ b/backend/gestion/db/db_commands.go @@ -1,6 +1,7 @@ package db import ( + "encoding/json" "fmt" "gestion/models" "log" @@ -424,9 +425,28 @@ func (d *Database) GetCommandByID(id int) (map[string]any, error) { return command, nil } +const lastDeliveryCoordsCacheTTL = 5 * time.Minute + +func lastDeliveryCoordsCacheKey(livreurUsername string) string { + return fmt.Sprintf("livreur:last_delivery_coords:%s", livreurUsername) +} + // GetLastDeliveryCoords retourne les coordonnées GPS de la dernière livraison terminée d'un livreur. -// Utilisé comme fallback quand le GPS temps réel est indisponible. +// Utilisé comme fallback quand le GPS temps réel est indisponible. Mis en cache quelques minutes +// car appelé à chaque calcul d'ETA et la dernière livraison ne change pas souvent. func (d *Database) GetLastDeliveryCoords(livreurUsername string) (float64, float64, error) { + cacheKey := lastDeliveryCoordsCacheKey(livreurUsername) + + if cached, err := Redis.Get(RedisCtx, cacheKey).Result(); err == nil { + var coords struct { + Lat float64 `json:"lat"` + Lon float64 `json:"lon"` + } + if jsonErr := json.Unmarshal([]byte(cached), &coords); jsonErr == nil { + return coords.Lat, coords.Lon, nil + } + } + var result struct { DestLatitude float64 `gorm:"column:dest_latitude"` DestLongitude float64 `gorm:"column:dest_longitude"` @@ -446,6 +466,10 @@ func (d *Database) GetLastDeliveryCoords(livreurUsername string) (float64, float return 0, 0, fmt.Errorf("coordonnées introuvables pour dernière livraison de %s", livreurUsername) } + if coordsJSON, err := json.Marshal(map[string]float64{"lat": result.DestLatitude, "lon": result.DestLongitude}); err == nil { + Redis.Set(RedisCtx, cacheKey, coordsJSON, lastDeliveryCoordsCacheTTL) + } + return result.DestLatitude, result.DestLongitude, nil } diff --git a/backend/gestion/db/db_init.go b/backend/gestion/db/db_init.go index d933fde4..6bd29940 100644 --- a/backend/gestion/db/db_init.go +++ b/backend/gestion/db/db_init.go @@ -304,6 +304,11 @@ func InitDB() *Database { log.Fatalf("❌ Erreur migration clients.parrain: %v", err) } + // Migration: index sur clients.parrain (lookups filleuls + stats parrainage) + if _, err = database.Exec(`CREATE INDEX IF NOT EXISTS idx_clients_parrain ON clients(parrain) WHERE parrain IS NOT NULL`); err != nil { + log.Fatalf("❌ Erreur migration idx_clients_parrain: %v", err) + } + // Lancer le nettoyage périodique des tokens expirés go database.cleanExpiredTokensPeriodically() diff --git a/backend/gestion/db/db_parrain.go b/backend/gestion/db/db_parrain.go index 775d03f0..65e32594 100644 --- a/backend/gestion/db/db_parrain.go +++ b/backend/gestion/db/db_parrain.go @@ -4,6 +4,8 @@ import ( "database/sql" "fmt" "gestion/models" + + "gorm.io/gorm" ) func (d *Database) SetClientParrain(clientUsername, parrainUsername string) error { @@ -19,6 +21,34 @@ func (d *Database) SetClientParrain(clientUsername, parrainUsername string) erro return nil } +// SetClientParrainAndCredit assigne un parrain à un client et crédite le parrain +// dans une seule transaction, pour éviter un lien parrain enregistré sans le crédit associé. +func (d *Database) SetClientParrainAndCredit(clientUsername, parrainUsername string, creditAmount float64) error { + return d.GDB.Transaction(func(tx *gorm.DB) error { + result := tx.Model(&models.Client{}). + Where("username = ? AND (parrain IS NULL OR parrain = '')", clientUsername). + Update("parrain", parrainUsername) + if result.Error != nil { + return result.Error + } + if result.RowsAffected == 0 { + return fmt.Errorf("client introuvable ou parrain déjà défini") + } + + if creditAmount > 0 { + result = tx.Model(&models.Client{}).Where("username = ?", parrainUsername). + Updates(map[string]any{"referral_balance": gorm.Expr("referral_balance + ?", creditAmount)}) + if result.Error != nil { + return result.Error + } + if result.RowsAffected == 0 { + return fmt.Errorf("parrain non trouvé") + } + } + return nil + }) +} + func (d *Database) GetClientParrain(clientUsername string) (string, error) { var parrain sql.NullString err := d.GDB.Table("clients"). diff --git a/backend/gestion/handlers/parrain.go b/backend/gestion/handlers/parrain.go index 69d6192f..4e311c21 100644 --- a/backend/gestion/handlers/parrain.go +++ b/backend/gestion/handlers/parrain.go @@ -46,25 +46,23 @@ func SetClientParrainAdmin(c *gin.Context) { return } - if err := database.SetClientParrain(targetUsername, req.Parrain); err != nil { + settings, _ := database.GetSettings() + creditAmount := 0.0 + if settings.ReferralEnabled && settings.ReferralAmount > 0 { + creditAmount = settings.ReferralAmount + } + + if err := database.SetClientParrainAndCredit(targetUsername, req.Parrain, creditAmount); err != nil { utils.ServerErr(c, "Erreur enregistrement parrain", err) return } - - settings, _ := database.GetSettings() - if settings.ReferralEnabled && settings.ReferralAmount > 0 { - if err := database.CreditClientReferral(req.Parrain, settings.ReferralAmount); err != nil { - log.Printf("⚠️ [PARRAIN] Impossible de créditer %s: %v", req.Parrain, err) - } else { - log.Printf("✅ [PARRAIN] %s parrainé par %s → +%.2f€ crédité", targetUsername, req.Parrain, settings.ReferralAmount) - } - } + log.Printf("✅ [PARRAIN] %s parrainé par %s → +%.2f€ crédité", targetUsername, req.Parrain, creditAmount) c.JSON(http.StatusOK, gin.H{ "message": "Parrain enregistré", "client": targetUsername, "parrain": req.Parrain, - "amount_credited": settings.ReferralAmount, + "amount_credited": creditAmount, }) }