@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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").
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user