775 lines
25 KiB
Go
775 lines
25 KiB
Go
package db
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"gestion/models"
|
||
"log"
|
||
"strings"
|
||
"time"
|
||
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
func (d *Database) CreateClient(client *models.Client) error {
|
||
var result struct {
|
||
ID int `gorm:"column:id"`
|
||
CreatedAt time.Time `gorm:"column:created_at"`
|
||
}
|
||
err := d.GDB.Raw(`
|
||
INSERT INTO clients (username, password, nom, prenom, telephone, command, amende, must_change_password, created_at)
|
||
VALUES (?, ?, ?, ?, ?, 0, 0.0, ?, CURRENT_TIMESTAMP)
|
||
RETURNING id, created_at`,
|
||
client.Username, client.Password, client.Nom, client.Prenom, client.Telephone, client.MustChangePassword,
|
||
).Scan(&result).Error
|
||
if err != nil {
|
||
return fmt.Errorf("erreur lors de la création du client: %w", err)
|
||
}
|
||
client.ID = result.ID
|
||
client.CreatedAt = result.CreatedAt
|
||
|
||
log.Printf("✅ Client créé avec succès: %s %s (ID: %d)", client.Prenom, client.Nom, client.ID)
|
||
return nil
|
||
}
|
||
|
||
// GetClientByID récupère un client par son ID
|
||
func (d *Database) GetClientByID(id int) (*models.Client, error) {
|
||
var row struct {
|
||
ID int `gorm:"column:id"`
|
||
Username string `gorm:"column:username"`
|
||
Password string `gorm:"column:password"`
|
||
Nom string `gorm:"column:nom"`
|
||
Prenom string `gorm:"column:prenom"`
|
||
Telephone string `gorm:"column:telephone"`
|
||
Command int `gorm:"column:command"`
|
||
Amende float64 `gorm:"column:amende"`
|
||
PointsExtraJSON []byte `gorm:"column:points_extra"`
|
||
CreatedAt time.Time `gorm:"column:created_at"`
|
||
}
|
||
err := d.GDB.Raw(`
|
||
SELECT id, username, password, nom, prenom, telephone, command, amende,
|
||
COALESCE(points_extra, '{}'::jsonb) as points_extra, created_at
|
||
FROM clients WHERE id = ?`, id).Scan(&row).Error
|
||
if err != nil {
|
||
return nil, fmt.Errorf("erreur lors de la récupération du client: %w", err)
|
||
}
|
||
if row.ID == 0 {
|
||
return nil, fmt.Errorf("client non trouvé")
|
||
}
|
||
|
||
client := &models.Client{
|
||
ID: row.ID,
|
||
Username: row.Username,
|
||
Password: row.Password,
|
||
Nom: row.Nom,
|
||
Prenom: row.Prenom,
|
||
Telephone: row.Telephone,
|
||
Command: row.Command,
|
||
Amende: row.Amende,
|
||
CreatedAt: row.CreatedAt,
|
||
}
|
||
client.PointsExtra = map[string]int{}
|
||
if len(row.PointsExtraJSON) > 0 {
|
||
json.Unmarshal(row.PointsExtraJSON, &client.PointsExtra)
|
||
}
|
||
|
||
return client, nil
|
||
}
|
||
|
||
// GetAllClients récupère tous les clients
|
||
func (d *Database) GetAllClients() ([]*models.Client, error) {
|
||
var rows []struct {
|
||
ID int `gorm:"column:id"`
|
||
Username string `gorm:"column:username"`
|
||
Password string `gorm:"column:password"`
|
||
Nom string `gorm:"column:nom"`
|
||
Prenom string `gorm:"column:prenom"`
|
||
Telephone string `gorm:"column:telephone"`
|
||
Command int `gorm:"column:command"`
|
||
Amende float64 `gorm:"column:amende"`
|
||
ReferralBalance float64 `gorm:"column:referral_balance"`
|
||
CancellationsCount int `gorm:"column:cancellations_count"`
|
||
PointsExtraJSON []byte `gorm:"column:points_extra"`
|
||
CreatedAt time.Time `gorm:"column:created_at"`
|
||
}
|
||
err := d.GDB.Raw(`
|
||
SELECT id, username, password, nom, prenom, telephone, command, amende, referral_balance,
|
||
COALESCE(cancellations_count, 0) as cancellations_count,
|
||
COALESCE(points_extra, '{}'::jsonb) as points_extra, created_at
|
||
FROM clients ORDER BY created_at DESC`).Scan(&rows).Error
|
||
if err != nil {
|
||
return nil, fmt.Errorf("erreur lors de la récupération des clients: %w", err)
|
||
}
|
||
|
||
clients := make([]*models.Client, 0, len(rows))
|
||
for _, row := range rows {
|
||
client := &models.Client{
|
||
ID: row.ID,
|
||
Username: row.Username,
|
||
Password: row.Password,
|
||
Nom: row.Nom,
|
||
Prenom: row.Prenom,
|
||
Telephone: row.Telephone,
|
||
Command: row.Command,
|
||
Amende: row.Amende,
|
||
ReferralBalance: row.ReferralBalance,
|
||
CancellationsCount: row.CancellationsCount,
|
||
CreatedAt: row.CreatedAt,
|
||
}
|
||
client.PointsExtra = map[string]int{}
|
||
if len(row.PointsExtraJSON) > 0 {
|
||
json.Unmarshal(row.PointsExtraJSON, &client.PointsExtra)
|
||
}
|
||
clients = append(clients, client)
|
||
}
|
||
|
||
return clients, nil
|
||
}
|
||
|
||
// UpdateClient met à jour un client existant
|
||
func (d *Database) UpdateClient(client *models.Client) error {
|
||
result := d.GDB.Model(&models.Client{}).Where("id = ?", client.ID).Updates(map[string]any{
|
||
"username": client.Username,
|
||
"nom": client.Nom,
|
||
"prenom": client.Prenom,
|
||
"telephone": client.Telephone,
|
||
"command": client.Command,
|
||
"amende": client.Amende,
|
||
})
|
||
if result.Error != nil {
|
||
return fmt.Errorf("erreur lors de la mise à jour du client: %w", result.Error)
|
||
}
|
||
if result.RowsAffected == 0 {
|
||
return fmt.Errorf("client non trouvé")
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// DeleteClient supprime un client
|
||
func (d *Database) DeleteClient(id int) error {
|
||
_ = d.RevokeAllUserTokens(id, "client")
|
||
|
||
result := d.GDB.Delete(&models.Client{}, id)
|
||
if result.Error != nil {
|
||
return fmt.Errorf("erreur lors de la suppression du client: %w", result.Error)
|
||
}
|
||
if result.RowsAffected == 0 {
|
||
return fmt.Errorf("client non trouvé")
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// UpdateClientPassword met à jour le mot de passe d'un client
|
||
func (d *Database) UpdateClientPassword(clientID int, hashedPassword string) error {
|
||
result := d.GDB.Model(&models.Client{}).Where("id = ?", clientID).Update("password", hashedPassword)
|
||
if result.Error != nil {
|
||
return fmt.Errorf("erreur lors de la mise à jour du mot de passe: %w", result.Error)
|
||
}
|
||
if result.RowsAffected == 0 {
|
||
return fmt.Errorf("client non trouvé")
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// UpdateClientPasswordAndClearFlag met à jour le mot de passe et remet must_change_password à false
|
||
func (d *Database) UpdateClientPasswordAndClearFlag(clientID int, hashedPassword string) error {
|
||
result := d.GDB.Model(&models.Client{}).Where("id = ?", clientID).Updates(map[string]any{
|
||
"password": hashedPassword,
|
||
"must_change_password": false,
|
||
})
|
||
if result.Error != nil {
|
||
return fmt.Errorf("erreur lors de la mise à jour du mot de passe: %w", result.Error)
|
||
}
|
||
if result.RowsAffected == 0 {
|
||
return fmt.Errorf("client non trouvé")
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
func (d *Database) GetClientAmende(username string) (float64, error) {
|
||
var result struct {
|
||
Amende float64 `gorm:"column:amende"`
|
||
}
|
||
err := d.GDB.Raw(`SELECT COALESCE(amende, 0) as amende FROM clients WHERE username = ?`, username).Scan(&result).Error
|
||
if err != nil {
|
||
log.Printf("❌ [GetClientAmende] Erreur pour %s: %v", username, err)
|
||
return 0, fmt.Errorf("erreur récupération pénalités: %w", err)
|
||
}
|
||
|
||
log.Printf("💰 [GetClientAmende] Client %s: %.2f points", username, result.Amende)
|
||
return result.Amende, nil
|
||
}
|
||
|
||
// IncrementClientCommandCount incrémente le compteur de commandes du client
|
||
func (d *Database) IncrementClientCommandCount(username string) error {
|
||
result := d.GDB.Model(&models.Client{}).Where("username = ?", username).UpdateColumn("command", gorm.Expr("command + 1"))
|
||
if result.Error != nil {
|
||
return fmt.Errorf("erreur lors de l'incrémentation du compteur: %w", result.Error)
|
||
}
|
||
if result.RowsAffected == 0 {
|
||
return fmt.Errorf("client non trouvé")
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
func (d *Database) AddClientPointsByCategory(username string, points int, poolKey string) error {
|
||
if poolKey == "" {
|
||
poolKey = "pool_0"
|
||
}
|
||
result := d.GDB.Exec(`
|
||
UPDATE clients
|
||
SET points_extra = jsonb_set(
|
||
COALESCE(points_extra, '{}'::jsonb),
|
||
ARRAY[?],
|
||
to_jsonb(COALESCE((points_extra->>?)::int, 0) + ?)
|
||
), updated_at = CURRENT_TIMESTAMP
|
||
WHERE username = ?`,
|
||
poolKey, poolKey, points, username)
|
||
if result.Error != nil {
|
||
return fmt.Errorf("erreur lors de l'ajout de points: %w", result.Error)
|
||
}
|
||
if result.RowsAffected == 0 {
|
||
return fmt.Errorf("client non trouvé")
|
||
}
|
||
log.Printf("✅ %d points (key=%s) ajoutés au client %s", points, poolKey, username)
|
||
return nil
|
||
}
|
||
|
||
func (d *Database) CalculateAndAddPointsForCommand(commandID int, username string) (int, error) {
|
||
var totalPoints int
|
||
err := d.GDB.Transaction(func(tx *gorm.DB) error {
|
||
points, _, err := d.CalculateAndAddPointsForCommandTx(tx, commandID, username)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
totalPoints = points
|
||
return nil
|
||
})
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
return totalPoints, nil
|
||
}
|
||
|
||
func (d *Database) GetClientByTelephone(telephone string) (*models.Client, error) {
|
||
var row struct {
|
||
ID int `gorm:"column:id"`
|
||
Username string `gorm:"column:username"`
|
||
Password string `gorm:"column:password"`
|
||
Nom string `gorm:"column:nom"`
|
||
Prenom string `gorm:"column:prenom"`
|
||
Telephone string `gorm:"column:telephone"`
|
||
Command int `gorm:"column:command"`
|
||
Amende float64 `gorm:"column:amende"`
|
||
CreatedAt time.Time `gorm:"column:created_at"`
|
||
}
|
||
err := d.GDB.Raw(`
|
||
SELECT id, username, password, nom, prenom, telephone, command, amende, created_at
|
||
FROM clients WHERE telephone = ?`, telephone).Scan(&row).Error
|
||
if err != nil {
|
||
return nil, fmt.Errorf("erreur lors de la récupération du client: %w", err)
|
||
}
|
||
if row.ID == 0 {
|
||
return nil, nil
|
||
}
|
||
|
||
return &models.Client{
|
||
ID: row.ID,
|
||
Username: row.Username,
|
||
Password: row.Password,
|
||
Nom: row.Nom,
|
||
Prenom: row.Prenom,
|
||
Telephone: row.Telephone,
|
||
Command: row.Command,
|
||
Amende: row.Amende,
|
||
CreatedAt: row.CreatedAt,
|
||
}, nil
|
||
}
|
||
|
||
// GetClientByUsername récupère un client par son username
|
||
func (d *Database) GetClientByUsername(username string) (*models.Client, error) {
|
||
var row struct {
|
||
ID int `gorm:"column:id"`
|
||
Username string `gorm:"column:username"`
|
||
Password string `gorm:"column:password"`
|
||
Nom string `gorm:"column:nom"`
|
||
Prenom string `gorm:"column:prenom"`
|
||
Telephone string `gorm:"column:telephone"`
|
||
Command int `gorm:"column:command"`
|
||
Amende float64 `gorm:"column:amende"`
|
||
MustChangePassword bool `gorm:"column:must_change_password"`
|
||
PointsExtraJSON []byte `gorm:"column:points_extra"`
|
||
CreatedAt time.Time `gorm:"column:created_at"`
|
||
TwoFAEnabled bool `gorm:"column:two_fa_enabled"`
|
||
}
|
||
err := d.GDB.Raw(`
|
||
SELECT id, username, password, nom, prenom, telephone, command, amende,
|
||
must_change_password, COALESCE(points_extra, '{}'::jsonb) as points_extra, created_at,
|
||
two_fa_enabled
|
||
FROM clients WHERE username = ?`, username).Scan(&row).Error
|
||
if err != nil {
|
||
return nil, fmt.Errorf("erreur lors de la récupération du client: %w", err)
|
||
}
|
||
if row.ID == 0 {
|
||
return nil, nil
|
||
}
|
||
|
||
client := &models.Client{
|
||
ID: row.ID,
|
||
Username: row.Username,
|
||
Password: row.Password,
|
||
Nom: row.Nom,
|
||
Prenom: row.Prenom,
|
||
Telephone: row.Telephone,
|
||
Command: row.Command,
|
||
Amende: row.Amende,
|
||
MustChangePassword: row.MustChangePassword,
|
||
CreatedAt: row.CreatedAt,
|
||
TwoFAEnabled: row.TwoFAEnabled,
|
||
}
|
||
client.PointsExtra = map[string]int{}
|
||
if len(row.PointsExtraJSON) > 0 {
|
||
json.Unmarshal(row.PointsExtraJSON, &client.PointsExtra)
|
||
}
|
||
|
||
return client, nil
|
||
}
|
||
|
||
func (d *Database) SetClientTwoFAEnabled(clientID int, enabled bool) error {
|
||
return d.GDB.Model(&models.Client{}).Where("id = ?", clientID).Update("two_fa_enabled", enabled).Error
|
||
}
|
||
|
||
func (d *Database) GetClientPenaltiesInfo(username string) (map[string]any, error) {
|
||
amende, err := d.GetClientAmende(username)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
cancellationsCount, err := d.GetClientCancellationsCount(username)
|
||
if err != nil {
|
||
log.Printf("⚠️ [GetClientPenaltiesInfo] Erreur récup annulations: %v", err)
|
||
cancellationsCount = 0
|
||
}
|
||
|
||
cancellationHistory, err := d.GetClientCancellationHistory(username)
|
||
if err != nil {
|
||
log.Printf("⚠️ [GetClientPenaltiesInfo] Erreur récup historique: %v", err)
|
||
cancellationHistory = map[string]any{
|
||
"cancellations_count": cancellationsCount,
|
||
"next_penalty": 20,
|
||
}
|
||
}
|
||
|
||
info := map[string]any{
|
||
"username": username,
|
||
"total_penalty": amende,
|
||
"cancellations_count": cancellationsCount,
|
||
"cancellation_history": cancellationHistory,
|
||
"has_penalties": amende > 0,
|
||
}
|
||
|
||
return info, nil
|
||
}
|
||
|
||
// ResetClientPoint réinitialise les points d'un client.
|
||
// extraPoolKey != "" → reset points_extra[extraPoolKey] uniquement
|
||
// extraPoolKey == "" (poolIdx=-1) → reset total points_extra
|
||
func (d *Database) ResetClientPoint(username string, poolIdx int, extraPoolKey string) error {
|
||
if extraPoolKey != "" {
|
||
err := d.GDB.Exec(`
|
||
UPDATE clients SET points_extra = points_extra - ?, updated_at = CURRENT_TIMESTAMP
|
||
WHERE username = ?`, extraPoolKey, username).Error
|
||
if err != nil {
|
||
log.Printf("❌ [ResetClientPointAdmin] Erreur UPDATE extra: %v", err)
|
||
} else {
|
||
cacheKey := fmt.Sprintf("client:%s", username)
|
||
Redis.Del(RedisCtx, cacheKey)
|
||
}
|
||
return err
|
||
}
|
||
|
||
// -1 ou poolIdx sans clé → reset total
|
||
result := d.GDB.Exec(`
|
||
UPDATE clients SET points_extra = '{}'::jsonb, updated_at = CURRENT_TIMESTAMP
|
||
WHERE username = ?`, username)
|
||
if result.Error != nil {
|
||
log.Printf("❌ [ResetClientPointAdmin] Erreur UPDATE: %v", result.Error)
|
||
return fmt.Errorf("erreur reset points: %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
|
||
}
|
||
|
||
func (d *Database) ResetClientPenalties(username string, _ bool) error {
|
||
log.Printf("🔄 [ResetClientPenalties] Reset amende + cancellations_count pour %s", username)
|
||
|
||
result := d.GDB.Exec(
|
||
`UPDATE clients SET amende = 0, cancellations_count = 0, updated_at = CURRENT_TIMESTAMP WHERE username = ?`,
|
||
username,
|
||
)
|
||
if result.Error != nil {
|
||
log.Printf("❌ [ResetClientPenalties] Erreur UPDATE: %v", result.Error)
|
||
return fmt.Errorf("erreur reset pénalités: %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
|
||
}
|
||
|
||
func (d *Database) GetAllClientsWithPenalties() ([]map[string]any, error) {
|
||
var rows []struct {
|
||
Username string `gorm:"column:username"`
|
||
Amende float64 `gorm:"column:amende"`
|
||
CancellationsCount int `gorm:"column:cancellations_count"`
|
||
UpdatedAt any `gorm:"column:updated_at"`
|
||
}
|
||
err := d.GDB.Raw(`
|
||
SELECT username, amende, COALESCE(cancellations_count, 0) as cancellations_count, updated_at
|
||
FROM clients
|
||
WHERE amende > 0
|
||
ORDER BY amende DESC`).Scan(&rows).Error
|
||
if err != nil {
|
||
log.Printf("❌ [GetAllClientsWithPenalties] Erreur query: %v", err)
|
||
return nil, fmt.Errorf("erreur récupération clients: %w", err)
|
||
}
|
||
|
||
clients := make([]map[string]any, 0, len(rows))
|
||
for _, row := range rows {
|
||
clients = append(clients, map[string]any{
|
||
"username": row.Username,
|
||
"total_penalty": row.Amende,
|
||
"cancellations_count": row.CancellationsCount,
|
||
"last_updated": row.UpdatedAt,
|
||
})
|
||
}
|
||
|
||
log.Printf("📊 [GetAllClientsWithPenalties] %d clients avec pénalités", len(clients))
|
||
|
||
return clients, nil
|
||
}
|
||
|
||
func (d *Database) GetClientPenaltiesStats() (map[string]any, error) {
|
||
var result struct {
|
||
ClientsWithPenalties int `gorm:"column:clients_with_penalties"`
|
||
TotalPenalties float64 `gorm:"column:total_penalties"`
|
||
AvgPenalty float64 `gorm:"column:avg_penalty"`
|
||
MaxPenalty float64 `gorm:"column:max_penalty"`
|
||
TotalClients int `gorm:"column:total_clients"`
|
||
}
|
||
|
||
err := d.GDB.Raw(`
|
||
SELECT
|
||
COUNT(CASE WHEN amende > 0 THEN 1 END) as clients_with_penalties,
|
||
COALESCE(SUM(amende), 0) as total_penalties,
|
||
COALESCE(AVG(amende), 0) as avg_penalty,
|
||
COALESCE(MAX(amende), 0) as max_penalty,
|
||
COUNT(*) as total_clients
|
||
FROM clients`).Scan(&result).Error
|
||
if err != nil {
|
||
log.Printf("❌ [GetClientPenaltiesStats] Erreur: %v", err)
|
||
return nil, fmt.Errorf("erreur récupération stats: %w", err)
|
||
}
|
||
|
||
stats := map[string]any{
|
||
"clients_with_penalties": result.ClientsWithPenalties,
|
||
"total_penalties": result.TotalPenalties,
|
||
"average_penalty": result.AvgPenalty,
|
||
"max_penalty": result.MaxPenalty,
|
||
"total_clients": result.TotalClients,
|
||
}
|
||
|
||
log.Printf("📊 [GetClientPenaltiesStats] Stats: %d/%d clients avec pénalités",
|
||
result.ClientsWithPenalties, result.TotalClients)
|
||
|
||
return stats, nil
|
||
}
|
||
|
||
func (d *Database) CalculateAndAddPointsForCommandTx(tx *gorm.DB, commandID int, username string) (int, string, error) {
|
||
log.Printf("💰 [CalcPointsTx] START - cmd=%d, user=%s", commandID, username)
|
||
|
||
// Charger les paramètres globaux
|
||
settings, err := d.GetSettings()
|
||
if err != nil {
|
||
log.Printf("⚠️ [CalcPointsTx] Erreur lecture settings, utilisation des défauts: %v", err)
|
||
settings = DefaultSettings()
|
||
}
|
||
|
||
pools := settings.PointsPools
|
||
if len(pools) == 0 {
|
||
log.Printf("ℹ️ [CalcPointsTx] Aucun pool configuré → 0 points")
|
||
return 0, "", nil
|
||
}
|
||
|
||
// Construire la map catégorie → index de pool
|
||
catToPool := make(map[string]int)
|
||
for i, pool := range pools {
|
||
for _, cat := range pool.Categories {
|
||
catToPool[strings.ToLower(cat)] = i
|
||
}
|
||
}
|
||
|
||
if len(catToPool) == 0 {
|
||
log.Printf("ℹ️ [CalcPointsTx] Aucune catégorie assignée aux pools → 0 points")
|
||
return 0, "", nil
|
||
}
|
||
|
||
// ✅ ÉTAPE 1: Récupérer tous les items de la commande avec leurs catégories
|
||
var items []struct {
|
||
Quantite float64 `gorm:"column:quantite"`
|
||
Prix float64 `gorm:"column:prix"`
|
||
Category string `gorm:"column:category"`
|
||
}
|
||
if err := tx.Raw(`
|
||
SELECT ci.quantite, ci.prix, COALESCE(p.category, '') as category
|
||
FROM command_items ci
|
||
LEFT JOIN products p ON ci.product_id = p.id
|
||
WHERE ci.command_id = ?
|
||
`, commandID).Scan(&items).Error; err != nil {
|
||
log.Printf("❌ [CalcPointsTx] Erreur query items: %v", err)
|
||
return 0, "", fmt.Errorf("erreur récupération items: %w", err)
|
||
}
|
||
|
||
if len(items) == 0 {
|
||
log.Printf("⚠️ [CalcPointsTx] Aucun item trouvé pour cmd %d", commandID)
|
||
return 0, "", nil
|
||
}
|
||
|
||
poolTotals := make([]float64, len(pools))
|
||
for _, item := range items {
|
||
catLower := strings.ToLower(item.Category)
|
||
if poolIdx, ok := catToPool[catLower]; ok {
|
||
poolTotals[poolIdx] += item.Prix
|
||
}
|
||
}
|
||
|
||
for i, t := range poolTotals {
|
||
log.Printf("📊 [CalcPointsTx] Pool[%d] (%s): %.2f€", i, pools[i].Name, t)
|
||
}
|
||
|
||
// ✅ ÉTAPE 2: Calculer les points pour tous les pools
|
||
var totalPoints int
|
||
var pointCategory string
|
||
|
||
poolPts := make([]int, len(pools))
|
||
var categoryParts []string
|
||
for i, pool := range pools {
|
||
poolPts[i] = CalcPointsFromTiers(poolTotals[i], pool.Tiers)
|
||
totalPoints += poolPts[i]
|
||
if poolPts[i] > 0 {
|
||
categoryParts = append(categoryParts, pool.Name)
|
||
}
|
||
}
|
||
|
||
log.Printf("💰 [CalcPointsTx] points par pool: %v, total=%d", poolPts, totalPoints)
|
||
|
||
if totalPoints == 0 {
|
||
return 0, "", nil
|
||
}
|
||
|
||
if len(categoryParts) > 0 {
|
||
pointCategory = strings.Join(categoryParts, " & ")
|
||
} else {
|
||
pointCategory = "points"
|
||
}
|
||
|
||
for i, pool := range pools {
|
||
if poolPts[i] == 0 {
|
||
continue
|
||
}
|
||
if err := tx.Exec(`
|
||
UPDATE clients
|
||
SET points_extra = jsonb_set(
|
||
COALESCE(points_extra, '{}'::jsonb),
|
||
ARRAY[?],
|
||
to_jsonb(COALESCE((points_extra->>?)::int, 0) + ?)
|
||
), updated_at = CURRENT_TIMESTAMP
|
||
WHERE username = ?
|
||
`, pool.Key, pool.Key, poolPts[i], username).Error; err != nil {
|
||
log.Printf("❌ [CalcPointsTx] Erreur UPDATE points_extra pool[%d] (%s): %v", i, pool.Key, err)
|
||
return 0, "", fmt.Errorf("erreur mise à jour points pool[%d]: %w", i, err)
|
||
}
|
||
log.Printf("💰 [CalcPointsTx] pool[%d] (%s / key=%s): +%d pts", i, pool.Name, pool.Key, poolPts[i])
|
||
}
|
||
|
||
log.Printf("🎉 [CalcPointsTx] SUCCÈS - %d points [%s] → %s", totalPoints, pointCategory, username)
|
||
|
||
// ✅ ÉTAPE 3: Déduire les points des récompenses reçues dans cette commande
|
||
var rewardItems []struct {
|
||
RewardPoolKey string `gorm:"column:reward_pool_key"`
|
||
}
|
||
if err := tx.Raw(`
|
||
SELECT reward_pool_key FROM command_items
|
||
WHERE command_id = ? AND is_reward = true AND reward_pool_key != ''
|
||
`, commandID).Scan(&rewardItems).Error; err != nil {
|
||
log.Printf("⚠️ [CalcPointsTx] Erreur query reward items: %v", err)
|
||
}
|
||
|
||
for _, ri := range rewardItems {
|
||
if settings.PointsReward == nil || settings.PointsReward.Threshold <= 0 {
|
||
break
|
||
}
|
||
threshold := settings.PointsReward.Threshold
|
||
poolKey := ri.RewardPoolKey
|
||
// Déduire threshold points de points_extra[poolKey] (plancher à 0)
|
||
if err := tx.Exec(`
|
||
UPDATE clients
|
||
SET points_extra = jsonb_set(
|
||
COALESCE(points_extra, '{}'::jsonb),
|
||
ARRAY[?],
|
||
to_jsonb(GREATEST(0, COALESCE((points_extra->>?)::int, 0) - ?))
|
||
), updated_at = CURRENT_TIMESTAMP
|
||
WHERE username = ?
|
||
`, poolKey, poolKey, threshold, username).Error; err != nil {
|
||
log.Printf("⚠️ [CalcPointsTx] Erreur déduction points reward pool=%s: %v", poolKey, err)
|
||
} else {
|
||
log.Printf("🎁 [CalcPointsTx] Récompense reçue: -%d pts pool=%s → %s", threshold, poolKey, username)
|
||
}
|
||
// Décrémenter points_redeemed[poolKey] (plancher à 0)
|
||
if err := tx.Exec(`
|
||
UPDATE clients
|
||
SET points_redeemed = jsonb_set(
|
||
COALESCE(points_redeemed, '{}'::jsonb),
|
||
ARRAY[?],
|
||
to_jsonb(GREATEST(0, COALESCE((points_redeemed->>?)::int, 0) - 1))
|
||
), updated_at = CURRENT_TIMESTAMP
|
||
WHERE username = ?
|
||
`, poolKey, poolKey, username).Error; err != nil {
|
||
log.Printf("⚠️ [CalcPointsTx] Erreur décrément redeemed pool=%s: %v", poolKey, err)
|
||
}
|
||
}
|
||
|
||
return totalPoints, pointCategory, nil
|
||
}
|
||
|
||
func (d *Database) CanUserAccessCommand(
|
||
commandID int,
|
||
username string,
|
||
role string,
|
||
) (bool, error) {
|
||
|
||
// 👑 Admin : accès total
|
||
if role == "admin" {
|
||
return true, nil
|
||
}
|
||
|
||
var exists bool
|
||
|
||
// 🚚 Livreur : seulement commandes assignées
|
||
if role == "livreur" {
|
||
err := d.GDB.Raw(`
|
||
SELECT EXISTS(
|
||
SELECT 1 FROM commandes
|
||
WHERE id = ? AND livreur_assign = ?
|
||
)
|
||
`, commandID, username).Scan(&exists).Error
|
||
return exists, err
|
||
}
|
||
|
||
// 👤 User : seulement SES commandes
|
||
err := d.GDB.Raw(`
|
||
SELECT EXISTS(
|
||
SELECT 1 FROM commandes
|
||
WHERE id = ? AND username = ?
|
||
)
|
||
`, commandID, username).Scan(&exists).Error
|
||
|
||
return exists, err
|
||
}
|
||
|
||
// GetClientPointsAndRewards retourne les points cumulés et les récompenses réclamées pour un client.
|
||
func (d *Database) GetClientPointsAndRewards(username string) (pointsExtra map[string]int, pointsRedeemed map[string]int, err error) {
|
||
var row struct {
|
||
PointsExtraJSON []byte `gorm:"column:points_extra"`
|
||
PointsRedeemedJSON []byte `gorm:"column:points_redeemed"`
|
||
}
|
||
if err = d.GDB.Raw(`
|
||
SELECT COALESCE(points_extra, '{}'::jsonb) as points_extra,
|
||
COALESCE(points_redeemed, '{}'::jsonb) as points_redeemed
|
||
FROM clients WHERE username = ?`, username).Scan(&row).Error; err != nil {
|
||
return nil, nil, fmt.Errorf("erreur lecture points client: %w", err)
|
||
}
|
||
pointsExtra = map[string]int{}
|
||
pointsRedeemed = map[string]int{}
|
||
if len(row.PointsExtraJSON) > 0 {
|
||
json.Unmarshal(row.PointsExtraJSON, &pointsExtra)
|
||
}
|
||
if len(row.PointsRedeemedJSON) > 0 {
|
||
json.Unmarshal(row.PointsRedeemedJSON, &pointsRedeemed)
|
||
}
|
||
return pointsExtra, pointsRedeemed, nil
|
||
}
|
||
|
||
// ClaimPoolReward réclame une récompense pour un pool donné si le client a assez de points.
|
||
// Retourne le nombre de récompenses disponibles restantes après la réclamation.
|
||
func (d *Database) ClaimPoolReward(username, poolKey string, threshold int) (remainingAvailable int, err error) {
|
||
var points, redeemed int
|
||
|
||
err = d.GDB.Transaction(func(tx *gorm.DB) error {
|
||
var row struct {
|
||
Points int `gorm:"column:pts"`
|
||
Redeemed int `gorm:"column:redeemed"`
|
||
}
|
||
if err := tx.Raw(`
|
||
SELECT
|
||
COALESCE((points_extra->>?)::int, 0) as pts,
|
||
COALESCE((points_redeemed->>?)::int, 0) as redeemed
|
||
FROM clients WHERE username = ? FOR UPDATE`,
|
||
poolKey, poolKey, username).Scan(&row).Error; err != nil {
|
||
return fmt.Errorf("erreur lecture: %w", err)
|
||
}
|
||
points = row.Points
|
||
redeemed = row.Redeemed
|
||
|
||
earned := points / threshold
|
||
available := earned - redeemed
|
||
if available <= 0 {
|
||
return fmt.Errorf("pas de récompense disponible pour ce pool")
|
||
}
|
||
|
||
return tx.Exec(`
|
||
UPDATE clients
|
||
SET points_redeemed = jsonb_set(
|
||
COALESCE(points_redeemed, '{}'::jsonb),
|
||
ARRAY[?],
|
||
to_jsonb(COALESCE((points_redeemed->>?)::int, 0) + 1)
|
||
), updated_at = CURRENT_TIMESTAMP
|
||
WHERE username = ?`,
|
||
poolKey, poolKey, username).Error
|
||
})
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
|
||
earned := points / threshold
|
||
remainingAvailable = earned - (redeemed + 1)
|
||
return remainingAvailable, nil
|
||
}
|
||
|
||
// ResetClientRedeemed remet à zéro les récompenses réclamées (admin).
|
||
func (d *Database) ResetClientRedeemed(username, poolKey string) error {
|
||
if poolKey != "" {
|
||
return d.GDB.Exec(`
|
||
UPDATE clients SET points_redeemed = points_redeemed - ?, updated_at = CURRENT_TIMESTAMP
|
||
WHERE username = ?`, poolKey, username).Error
|
||
}
|
||
return d.GDB.Exec(`
|
||
UPDATE clients SET points_redeemed = '{}'::jsonb, updated_at = CURRENT_TIMESTAMP
|
||
WHERE username = ?`, username).Error
|
||
}
|