feat: add and delete amende and point by admin
This commit is contained in:
@@ -0,0 +1,69 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"gestion/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (d *Database) SetClientParrain(clientUsername, parrainUsername string) error {
|
||||||
|
result := d.GDB.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")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Database) GetClientParrain(clientUsername string) (string, error) {
|
||||||
|
var parrain string
|
||||||
|
err := d.GDB.Table("clients").
|
||||||
|
Select("parrain").
|
||||||
|
Where("username = ?", clientUsername).
|
||||||
|
Scan(&parrain).Error
|
||||||
|
return parrain, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Database) GetClientsByParrain(parrainUsername string) ([]models.Client, error) {
|
||||||
|
var clients []models.Client
|
||||||
|
err := d.GDB.
|
||||||
|
Where("parrain = ?", parrainUsername).
|
||||||
|
Find(&clients).Error
|
||||||
|
return clients, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Database) GetParrainStats() ([]map[string]any, error) {
|
||||||
|
var rows []struct {
|
||||||
|
Parrain string `gorm:"column:parrain"`
|
||||||
|
FilleulCount int `gorm:"column:filleul_count"`
|
||||||
|
ReferralBalance float64 `gorm:"column:referral_balance"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err := d.GDB.Raw(`
|
||||||
|
SELECT
|
||||||
|
c.parrain,
|
||||||
|
COUNT(c.username) AS filleul_count,
|
||||||
|
p.referral_balance
|
||||||
|
FROM clients c
|
||||||
|
JOIN clients p ON p.username = c.parrain
|
||||||
|
WHERE c.parrain IS NOT NULL AND c.parrain <> ''
|
||||||
|
GROUP BY c.parrain, p.referral_balance
|
||||||
|
ORDER BY filleul_count DESC
|
||||||
|
`).Scan(&rows).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]map[string]any, 0, len(rows))
|
||||||
|
for _, r := range rows {
|
||||||
|
result = append(result, map[string]any{
|
||||||
|
"parrain": r.Parrain,
|
||||||
|
"filleul_count": r.FilleulCount,
|
||||||
|
"referral_balance": r.ReferralBalance,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gestion/db"
|
||||||
|
"gestion/utils"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetClientParrainAdmin — POST /api/v2/admin/protected/client/:username/parrain/set (admin)
|
||||||
|
// Assigne un parrain à un client. Le parrain reçoit settings.ReferralAmount sur son solde.
|
||||||
|
func SetClientParrainAdmin(c *gin.Context) {
|
||||||
|
database := c.MustGet("database").(*db.Database)
|
||||||
|
targetUsername := c.Param("username")
|
||||||
|
|
||||||
|
var req struct {
|
||||||
|
Parrain string `json:"parrain" binding:"required"`
|
||||||
|
}
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Champ parrain requis"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Parrain == targetUsername {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Un client ne peut pas être son propre parrain"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérifier que le parrain existe
|
||||||
|
parrain, err := database.GetClientByUsername(req.Parrain)
|
||||||
|
if err != nil || parrain == nil {
|
||||||
|
c.JSON(http.StatusNotFound, gin.H{"error": "Parrain introuvable"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérifier que le client n'a pas déjà un parrain
|
||||||
|
existing, err := database.GetClientParrain(targetUsername)
|
||||||
|
if err != nil {
|
||||||
|
utils.ServerErr(c, "Erreur vérification parrain", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if existing != "" {
|
||||||
|
c.JSON(http.StatusConflict, gin.H{"error": "Ce client a déjà un parrain", "parrain": existing})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := database.SetClientParrain(targetUsername, req.Parrain); 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"message": "Parrain enregistré",
|
||||||
|
"client": targetUsername,
|
||||||
|
"parrain": req.Parrain,
|
||||||
|
"amount_credited": settings.ReferralAmount,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMyParrainInfo — GET /api/v1/parrain (client authentifié)
|
||||||
|
// Retourne le parrain du client + ses filleuls + son solde parrainage.
|
||||||
|
func GetMyParrainInfo(c *gin.Context) {
|
||||||
|
database := c.MustGet("database").(*db.Database)
|
||||||
|
|
||||||
|
username, exists := c.Get("username")
|
||||||
|
if !exists {
|
||||||
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Utilisateur non authentifié"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
clientUsername := username.(string)
|
||||||
|
|
||||||
|
settings, _ := database.GetSettings()
|
||||||
|
|
||||||
|
parrain, err := database.GetClientParrain(clientUsername)
|
||||||
|
if err != nil {
|
||||||
|
utils.ServerErr(c, "Erreur récupération parrain", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
filleuls, err := database.GetClientsByParrain(clientUsername)
|
||||||
|
if err != nil {
|
||||||
|
utils.ServerErr(c, "Erreur récupération filleuls", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
balance, err := database.GetClientReferralBalance(clientUsername)
|
||||||
|
if err != nil {
|
||||||
|
balance = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
filleulNames := make([]string, 0, len(filleuls))
|
||||||
|
for _, f := range filleuls {
|
||||||
|
filleulNames = append(filleulNames, f.Username)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"parrain": parrain,
|
||||||
|
"filleuls": filleulNames,
|
||||||
|
"filleul_count": len(filleulNames),
|
||||||
|
"referral_balance": balance,
|
||||||
|
"referral_enabled": settings.ReferralEnabled,
|
||||||
|
"referral_amount": settings.ReferralAmount,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetClientParrainAdmin — GET /api/v2/admin/protected/client/:username/parrain (admin)
|
||||||
|
// Retourne les infos parrainage d'un client spécifique.
|
||||||
|
func GetClientParrainAdmin(c *gin.Context) {
|
||||||
|
database := c.MustGet("database").(*db.Database)
|
||||||
|
targetUsername := c.Param("username")
|
||||||
|
|
||||||
|
parrain, err := database.GetClientParrain(targetUsername)
|
||||||
|
if err != nil {
|
||||||
|
utils.ServerErr(c, "Erreur récupération parrain", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
filleuls, err := database.GetClientsByParrain(targetUsername)
|
||||||
|
if err != nil {
|
||||||
|
utils.ServerErr(c, "Erreur récupération filleuls", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
balance, _ := database.GetClientReferralBalance(targetUsername)
|
||||||
|
|
||||||
|
filleulNames := make([]string, 0, len(filleuls))
|
||||||
|
for _, f := range filleuls {
|
||||||
|
filleulNames = append(filleulNames, f.Username)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"username": targetUsername,
|
||||||
|
"parrain": parrain,
|
||||||
|
"filleuls": filleulNames,
|
||||||
|
"filleul_count": len(filleulNames),
|
||||||
|
"referral_balance": balance,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetParrainStatsAdmin — GET /api/v2/admin/protected/parrain/stats (admin)
|
||||||
|
// Retourne la liste de tous les parrains avec leur nombre de filleuls et leur solde.
|
||||||
|
func GetParrainStatsAdmin(c *gin.Context) {
|
||||||
|
database := c.MustGet("database").(*db.Database)
|
||||||
|
|
||||||
|
stats, err := database.GetParrainStats()
|
||||||
|
if err != nil {
|
||||||
|
utils.ServerErr(c, "Erreur récupération stats parrainage", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"parrains": stats,
|
||||||
|
"total": len(stats),
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -975,6 +975,86 @@ func ResetClientPenaltiesAdmin(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddClientPointsAdmin ajoute des points à un client dans un pool donné (Admin/Cabine)
|
||||||
|
// POST /api/v2/admin/protected/client/:username/points/add
|
||||||
|
// Body: {"pool_key": "pool_0", "points": 10}
|
||||||
|
func AddClientPointsAdmin(c *gin.Context) {
|
||||||
|
userRole := c.GetString("role")
|
||||||
|
if userRole != "admin" && userRole != "cabine" {
|
||||||
|
c.JSON(http.StatusForbidden, gin.H{"error": "Accès refusé"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
username := c.Param("username")
|
||||||
|
if username == "" {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Username requis"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req struct {
|
||||||
|
PoolKey string `json:"pool_key" binding:"required"`
|
||||||
|
Points int `json:"points" binding:"required"`
|
||||||
|
}
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Données invalides"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Points <= 0 {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Le nombre de points doit être positif"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
database := c.MustGet("database").(*db.Database)
|
||||||
|
|
||||||
|
// Vérifier que le pool_key existe dans les settings
|
||||||
|
settings, err := database.GetSettings()
|
||||||
|
if err != nil {
|
||||||
|
utils.ServerErr(c, "Erreur récupération paramètres", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
poolExists := false
|
||||||
|
for _, pool := range settings.PointsPools {
|
||||||
|
if pool.Key == req.PoolKey {
|
||||||
|
poolExists = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !poolExists {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": "Pool de points invalide",
|
||||||
|
"pools_valides": func() []string {
|
||||||
|
keys := make([]string, 0, len(settings.PointsPools))
|
||||||
|
for _, p := range settings.PointsPools {
|
||||||
|
keys = append(keys, p.Key)
|
||||||
|
}
|
||||||
|
return keys
|
||||||
|
}(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := database.AddClientPointsByCategory(username, req.Points, req.PoolKey); err != nil {
|
||||||
|
if strings.Contains(err.Error(), "non trouvé") {
|
||||||
|
c.JSON(http.StatusNotFound, gin.H{"error": "Client non trouvé"})
|
||||||
|
} else {
|
||||||
|
utils.ServerErr(c, "Erreur ajout de points", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("✅ [ADD_POINTS] %d points (pool=%s) ajoutés à %s par %s",
|
||||||
|
req.Points, req.PoolKey, username, c.GetString("username"))
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": true,
|
||||||
|
"message": "Points ajoutés avec succès",
|
||||||
|
"username": username,
|
||||||
|
"pool_key": req.PoolKey,
|
||||||
|
"points": req.Points,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// STATISTIQUES TEMPS RÉEL
|
// STATISTIQUES TEMPS RÉEL
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ type Client struct {
|
|||||||
LastPenaltyReason string `gorm:"column:last_penalty_reason" json:"last_penalty_reason"`
|
LastPenaltyReason string `gorm:"column:last_penalty_reason" json:"last_penalty_reason"`
|
||||||
MustChangePassword bool `gorm:"column:must_change_password;default:false" json:"must_change_password"`
|
MustChangePassword bool `gorm:"column:must_change_password;default:false" json:"must_change_password"`
|
||||||
ReferralBalance float64 `gorm:"column:referral_balance;default:0" json:"referral_balance"`
|
ReferralBalance float64 `gorm:"column:referral_balance;default:0" json:"referral_balance"`
|
||||||
// points_extra est un JSONB géré manuellement (type non supporté nativement par GORM)
|
|
||||||
PointsExtra map[string]int `gorm:"-" json:"points_extra"`
|
PointsExtra map[string]int `gorm:"-" json:"points_extra"`
|
||||||
|
Parrain string `gorm:"column:parrain" json:"parrain"`
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ func SetupRoutes(router *gin.Engine, database *db.Database, geoService *services
|
|||||||
|
|
||||||
// 🎁 PARRAINAGE CLIENT
|
// 🎁 PARRAINAGE CLIENT
|
||||||
cartGroupV1.GET("/referral/balance", handlers.GetMyReferralBalance)
|
cartGroupV1.GET("/referral/balance", handlers.GetMyReferralBalance)
|
||||||
|
cartGroupV1.GET("/parrain", handlers.GetMyParrainInfo)
|
||||||
|
|
||||||
// 💸 STATUT PAIEMENT CRYPTO
|
// 💸 STATUT PAIEMENT CRYPTO
|
||||||
cartGroupV1.GET("/commands/:id/payment-status", handlers.GetCommandPaymentStatus)
|
cartGroupV1.GET("/commands/:id/payment-status", handlers.GetCommandPaymentStatus)
|
||||||
@@ -247,7 +248,9 @@ func SetupRoutes(router *gin.Engine, database *db.Database, geoService *services
|
|||||||
// ============================================
|
// ============================================
|
||||||
adminGroupV2.POST("/penalty", handlers.ApplyClientPenalty) // Appliquer pénalité
|
adminGroupV2.POST("/penalty", handlers.ApplyClientPenalty) // Appliquer pénalité
|
||||||
adminGroupV2.GET("/client/:username/penalties", handlers.GetClientPenaltiesAdmin) // Voir pénalités client
|
adminGroupV2.GET("/client/:username/penalties", handlers.GetClientPenaltiesAdmin) // Voir pénalités client
|
||||||
adminGroupV2.POST("/client/:username/penalties/reset", handlers.ResetClientPenaltiesAdmin) // Reset pénalités
|
adminGroupV2.POST("/client/:username/penalties/reset", handlers.ResetClientPenaltiesAdmin) // Reset amende
|
||||||
|
adminGroupV2.POST("/client/:username/point/reset", handlers.ResetClientPointAdmin) // Reset points → 0
|
||||||
|
adminGroupV2.POST("/client/:username/points/add", handlers.AddClientPointsAdmin) // Ajouter points par pool
|
||||||
adminGroupV2.GET("/penalties/all", handlers.GetAllClientsWithPenalties) // Liste clients avec pénalités
|
adminGroupV2.GET("/penalties/all", handlers.GetAllClientsWithPenalties) // Liste clients avec pénalités
|
||||||
adminGroupV2.GET("/penalties/stats", handlers.GetPenaltiesStats)
|
adminGroupV2.GET("/penalties/stats", handlers.GetPenaltiesStats)
|
||||||
|
|
||||||
@@ -260,6 +263,9 @@ func SetupRoutes(router *gin.Engine, database *db.Database, geoService *services
|
|||||||
// 🎁 PARRAINAGE ADMIN
|
// 🎁 PARRAINAGE ADMIN
|
||||||
adminGroupV2.GET("/client/:username/referral", handlers.GetClientReferralAdmin)
|
adminGroupV2.GET("/client/:username/referral", handlers.GetClientReferralAdmin)
|
||||||
adminGroupV2.POST("/client/:username/referral/credit", handlers.CreditClientReferralAdmin)
|
adminGroupV2.POST("/client/:username/referral/credit", handlers.CreditClientReferralAdmin)
|
||||||
|
adminGroupV2.POST("/client/:username/parrain/set", handlers.SetClientParrainAdmin)
|
||||||
|
adminGroupV2.GET("/client/:username/parrain", handlers.GetClientParrainAdmin)
|
||||||
|
adminGroupV2.GET("/parrain/stats", handlers.GetParrainStatsAdmin)
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// ⭐⭐ ALERTES POLICE - GESTION ADMIN
|
// ⭐⭐ ALERTES POLICE - GESTION ADMIN
|
||||||
|
|||||||
Reference in New Issue
Block a user