805 lines
22 KiB
Go
805 lines
22 KiB
Go
package db
|
||
|
||
import (
|
||
"database/sql"
|
||
"encoding/json"
|
||
"fmt"
|
||
"gestion/models"
|
||
"log"
|
||
"strings"
|
||
)
|
||
|
||
func (d *Database) CreateClient(client *models.Client) error {
|
||
query := `INSERT INTO clients (username, password, nom, prenom, telephone, command, amende, must_change_password, created_at)
|
||
VALUES ($1, $2, $3, $4, $5, 0, 0.0, $6, CURRENT_TIMESTAMP)
|
||
RETURNING id, created_at`
|
||
|
||
err := d.QueryRow(query, client.Username, client.Password, client.Nom, client.Prenom, client.Telephone, client.MustChangePassword).Scan(
|
||
&client.ID,
|
||
&client.CreatedAt,
|
||
)
|
||
if err != nil {
|
||
return fmt.Errorf("erreur lors de la création du client: %w", err)
|
||
}
|
||
|
||
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 client models.Client
|
||
query := `SELECT id, username, password, nom, prenom, telephone, command, amende, COALESCE(points_extra, '{}'::jsonb), created_at
|
||
FROM clients WHERE id = $1`
|
||
|
||
var pointsExtraJSON []byte
|
||
err := d.QueryRow(query, id).Scan(
|
||
&client.ID,
|
||
&client.Username,
|
||
&client.Password,
|
||
&client.Nom,
|
||
&client.Prenom,
|
||
&client.Telephone,
|
||
&client.Command,
|
||
&client.Amende,
|
||
&pointsExtraJSON,
|
||
&client.CreatedAt,
|
||
)
|
||
|
||
if err == sql.ErrNoRows {
|
||
return nil, fmt.Errorf("client non trouvé")
|
||
}
|
||
if err != nil {
|
||
return nil, fmt.Errorf("erreur lors de la récupération du client: %w", err)
|
||
}
|
||
|
||
if len(pointsExtraJSON) > 0 {
|
||
json.Unmarshal(pointsExtraJSON, &client.PointsExtra)
|
||
}
|
||
|
||
return &client, nil
|
||
}
|
||
|
||
// GetAllClients récupère tous les clients
|
||
func (d *Database) GetAllClients() ([]*models.Client, error) {
|
||
query := `SELECT id, username, password, nom, prenom, telephone, command, amende, referral_balance, COALESCE(points_extra, '{}'::jsonb), created_at
|
||
FROM clients ORDER BY created_at DESC`
|
||
|
||
rows, err := d.Query(query)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("erreur lors de la récupération des clients: %w", err)
|
||
}
|
||
defer rows.Close()
|
||
|
||
var clients []*models.Client
|
||
for rows.Next() {
|
||
client := &models.Client{}
|
||
var pointsExtraJSON []byte
|
||
err := rows.Scan(
|
||
&client.ID,
|
||
&client.Username,
|
||
&client.Password,
|
||
&client.Nom,
|
||
&client.Prenom,
|
||
&client.Telephone,
|
||
&client.Command,
|
||
&client.Amende,
|
||
&client.ReferralBalance,
|
||
&pointsExtraJSON,
|
||
&client.CreatedAt,
|
||
)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("erreur lors du scan du client: %w", err)
|
||
}
|
||
client.PointsExtra = map[string]int{}
|
||
if len(pointsExtraJSON) > 0 {
|
||
json.Unmarshal(pointsExtraJSON, &client.PointsExtra)
|
||
}
|
||
clients = append(clients, client)
|
||
}
|
||
|
||
if err = rows.Err(); err != nil {
|
||
return nil, fmt.Errorf("erreur lors de l'itération des résultats: %w", err)
|
||
}
|
||
|
||
return clients, nil
|
||
}
|
||
|
||
// UpdateClient met à jour un client existant
|
||
func (d *Database) UpdateClient(client *models.Client) error {
|
||
query := `UPDATE clients
|
||
SET username = $1, password = $2, nom = $3, prenom = $4, telephone = $5,
|
||
command = $6, amende = $7
|
||
WHERE id = $8`
|
||
|
||
result, err := d.Exec(query,
|
||
client.Username,
|
||
client.Password,
|
||
client.Nom,
|
||
client.Prenom,
|
||
client.Telephone,
|
||
client.Command,
|
||
client.Amende,
|
||
client.ID,
|
||
)
|
||
if err != nil {
|
||
return fmt.Errorf("erreur lors de la mise à jour du client: %w", err)
|
||
}
|
||
|
||
rowsAffected, err := result.RowsAffected()
|
||
if err != nil {
|
||
return fmt.Errorf("erreur lors de la vérification des lignes affectées: %w", err)
|
||
}
|
||
|
||
if 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")
|
||
|
||
query := `DELETE FROM clients WHERE id = $1`
|
||
|
||
result, err := d.Exec(query, id)
|
||
if err != nil {
|
||
return fmt.Errorf("erreur lors de la suppression du client: %w", err)
|
||
}
|
||
|
||
rowsAffected, err := result.RowsAffected()
|
||
if err != nil {
|
||
return fmt.Errorf("erreur lors de la vérification des lignes affectées: %w", err)
|
||
}
|
||
|
||
if 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 {
|
||
query := `UPDATE clients SET password = $1 WHERE id = $2`
|
||
|
||
result, err := d.Exec(query, hashedPassword, clientID)
|
||
if err != nil {
|
||
return fmt.Errorf("erreur lors de la mise à jour du mot de passe: %w", err)
|
||
}
|
||
|
||
rowsAffected, err := result.RowsAffected()
|
||
if err != nil {
|
||
return fmt.Errorf("erreur lors de la vérification: %w", err)
|
||
}
|
||
|
||
if 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 {
|
||
query := `UPDATE clients SET password = $1, must_change_password = FALSE, updated_at = CURRENT_TIMESTAMP WHERE id = $2`
|
||
|
||
result, err := d.Exec(query, hashedPassword, clientID)
|
||
if err != nil {
|
||
return fmt.Errorf("erreur lors de la mise à jour du mot de passe: %w", err)
|
||
}
|
||
|
||
rowsAffected, err := result.RowsAffected()
|
||
if err != nil {
|
||
return fmt.Errorf("erreur lors de la vérification: %w", err)
|
||
}
|
||
|
||
if rowsAffected == 0 {
|
||
return fmt.Errorf("client non trouvé")
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// GetClientStats récupère les statistiques d'un client
|
||
func (d *Database) GetClientStats(clientID int) (map[string]interface{}, error) {
|
||
client, err := d.GetClientByID(clientID)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// Compter les commandes du client
|
||
var totalCommands, pendingCommands, completedCommands int
|
||
|
||
countQuery := `SELECT
|
||
COUNT(*) as total,
|
||
SUM(CASE WHEN status = 'pending' OR status = 'livre' THEN 1 ELSE 0 END) as pending,
|
||
SUM(CASE WHEN status = 'approved' THEN 1 ELSE 0 END) as completed
|
||
FROM commandes WHERE username = $1`
|
||
|
||
err = d.QueryRow(countQuery, client.Username).Scan(&totalCommands, &pendingCommands, &completedCommands)
|
||
if err != nil {
|
||
log.Printf("⚠️ Erreur calcul stats: %v", err)
|
||
totalCommands, pendingCommands, completedCommands = 0, 0, 0
|
||
}
|
||
|
||
stats := map[string]interface{}{
|
||
"id": clientID,
|
||
"username": client.Username,
|
||
"nom": client.Nom,
|
||
"prenom": client.Prenom,
|
||
"telephone": client.Telephone,
|
||
"total_commands": totalCommands,
|
||
"pending_commands": pendingCommands,
|
||
"completed_commands": completedCommands,
|
||
"points_extra": client.PointsExtra,
|
||
"amende": client.Amende,
|
||
"member_since": client.CreatedAt,
|
||
}
|
||
|
||
return stats, nil
|
||
}
|
||
|
||
func (d *Database) GetClientAmende(username string) (float64, error) {
|
||
var amende float64
|
||
query := `SELECT COALESCE(amende, 0) FROM clients WHERE username = $1`
|
||
|
||
err := d.QueryRow(query, username).Scan(&amende)
|
||
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, amende)
|
||
return amende, nil
|
||
}
|
||
|
||
func (d *Database) PayClientPenalties(username string, amountPaid float64) error {
|
||
log.Printf("💳 [PayClientPenalties] Paiement de %.2f points pour %s", amountPaid, username)
|
||
|
||
// Vérifier le montant actuel
|
||
currentAmount, err := d.GetClientAmende(username)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
if currentAmount <= 0 {
|
||
return fmt.Errorf("aucune pénalité à payer")
|
||
}
|
||
|
||
if amountPaid < currentAmount {
|
||
return fmt.Errorf("montant insuffisant: %.2f payé, %.2f requis", amountPaid, currentAmount)
|
||
}
|
||
|
||
query := `UPDATE clients
|
||
SET amende = 0, updated_at = CURRENT_TIMESTAMP
|
||
WHERE username = $1`
|
||
|
||
result, err := d.Exec(query, username)
|
||
if err != nil {
|
||
log.Printf("❌ [PayClientPenalties] Erreur UPDATE: %v", err)
|
||
return fmt.Errorf("erreur paiement pénalités: %w", err)
|
||
}
|
||
|
||
rowsAffected, err := result.RowsAffected()
|
||
if err != nil {
|
||
return fmt.Errorf("erreur vérification: %w", err)
|
||
}
|
||
if rowsAffected == 0 {
|
||
return fmt.Errorf("client non trouvé")
|
||
}
|
||
|
||
cacheKey := fmt.Sprintf("client:%s", username)
|
||
Redis.Del(RedisCtx, cacheKey)
|
||
|
||
return nil
|
||
}
|
||
|
||
// IncrementClientCommandCount incrémente le compteur de commandes du client
|
||
func (d *Database) IncrementClientCommandCount(username string) error {
|
||
query := `UPDATE clients
|
||
SET command = command + 1
|
||
WHERE username = $1`
|
||
|
||
result, err := d.Exec(query, username)
|
||
if err != nil {
|
||
return fmt.Errorf("erreur lors de l'incrémentation du compteur: %w", err)
|
||
}
|
||
|
||
rowsAffected, err := result.RowsAffected()
|
||
if err != nil {
|
||
return fmt.Errorf("erreur lors de la vérification: %w", err)
|
||
}
|
||
if 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, err := d.Exec(`
|
||
UPDATE clients
|
||
SET points_extra = jsonb_set(
|
||
COALESCE(points_extra, '{}'::jsonb),
|
||
ARRAY[$2],
|
||
to_jsonb(COALESCE((points_extra->>$2)::int, 0) + $3)
|
||
), updated_at = CURRENT_TIMESTAMP
|
||
WHERE username = $1
|
||
`, username, poolKey, points)
|
||
if err != nil {
|
||
return fmt.Errorf("erreur lors de l'ajout de points: %w", err)
|
||
}
|
||
rowsAffected, err := result.RowsAffected()
|
||
if err != nil {
|
||
return fmt.Errorf("erreur lors de la vérification: %w", err)
|
||
}
|
||
if 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) {
|
||
tx, err := d.Begin()
|
||
if err != nil {
|
||
return 0, fmt.Errorf("erreur transaction: %w", err)
|
||
}
|
||
defer tx.Rollback()
|
||
|
||
points, _, err := d.CalculateAndAddPointsForCommandTx(tx, commandID, username)
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
|
||
if err := tx.Commit(); err != nil {
|
||
return 0, fmt.Errorf("erreur commit: %w", err)
|
||
}
|
||
return points, nil
|
||
}
|
||
|
||
func (d *Database) GetClientByTelephone(telephone string) (*models.Client, error) {
|
||
client := &models.Client{}
|
||
query := `SELECT id, username, password, nom, prenom, telephone, command, amende, created_at
|
||
FROM clients WHERE telephone = $1`
|
||
|
||
err := d.QueryRow(query, telephone).Scan(
|
||
&client.ID,
|
||
&client.Username,
|
||
&client.Password,
|
||
&client.Nom,
|
||
&client.Prenom,
|
||
&client.Telephone,
|
||
&client.Command,
|
||
&client.Amende,
|
||
&client.CreatedAt,
|
||
)
|
||
|
||
if err == sql.ErrNoRows {
|
||
return nil, nil
|
||
}
|
||
if err != nil {
|
||
return nil, fmt.Errorf("erreur lors de la récupération du client: %w", err)
|
||
}
|
||
|
||
return client, nil
|
||
}
|
||
|
||
// GetClientByUsername récupère un client par son username
|
||
func (d *Database) GetClientByUsername(username string) (*models.Client, error) {
|
||
client := &models.Client{}
|
||
var pointsExtraJSON []byte
|
||
query := `SELECT id, username, password, nom, prenom, telephone, command, amende, must_change_password, COALESCE(points_extra, '{}'::jsonb), created_at
|
||
FROM clients WHERE username = $1`
|
||
|
||
err := d.QueryRow(query, username).Scan(
|
||
&client.ID,
|
||
&client.Username,
|
||
&client.Password,
|
||
&client.Nom,
|
||
&client.Prenom,
|
||
&client.Telephone,
|
||
&client.Command,
|
||
&client.Amende,
|
||
&client.MustChangePassword,
|
||
&pointsExtraJSON,
|
||
&client.CreatedAt,
|
||
)
|
||
|
||
if err == sql.ErrNoRows {
|
||
return nil, nil
|
||
}
|
||
if err != nil {
|
||
return nil, fmt.Errorf("erreur lors de la récupération du client: %w", err)
|
||
}
|
||
|
||
client.PointsExtra = map[string]int{}
|
||
if len(pointsExtraJSON) > 0 {
|
||
json.Unmarshal(pointsExtraJSON, &client.PointsExtra)
|
||
}
|
||
|
||
return client, nil
|
||
}
|
||
|
||
func (d *Database) GetClientPenaltiesInfo(username string) (map[string]interface{}, error) {
|
||
// Récupérer le montant des pénalités
|
||
amende, err := d.GetClientAmende(username)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// Récupérer le nombre d'annulations
|
||
cancellationsCount, err := d.GetClientCancellationsCount(username)
|
||
if err != nil {
|
||
log.Printf("⚠️ [GetClientPenaltiesInfo] Erreur récup annulations: %v", err)
|
||
cancellationsCount = 0
|
||
}
|
||
|
||
// Récupérer l'historique d'annulations
|
||
cancellationHistory, err := d.GetClientCancellationHistory(username)
|
||
if err != nil {
|
||
log.Printf("⚠️ [GetClientPenaltiesInfo] Erreur récup historique: %v", err)
|
||
cancellationHistory = map[string]interface{}{
|
||
"cancellations_count": cancellationsCount,
|
||
"next_penalty": 20,
|
||
}
|
||
}
|
||
|
||
info := map[string]interface{}{
|
||
"username": username,
|
||
"total_penalty": amende,
|
||
"cancellations_count": cancellationsCount,
|
||
"cancellation_history": cancellationHistory,
|
||
"has_penalties": amende > 0,
|
||
}
|
||
|
||
return info, nil
|
||
}
|
||
|
||
// CheckClientCanOrder vérifie si un client peut passer commande (pas de pénalités impayées)
|
||
func (d *Database) CheckClientCanOrder(username string) (bool, float64, error) {
|
||
amende, err := d.GetClientAmende(username)
|
||
if err != nil {
|
||
return false, 0, err
|
||
}
|
||
|
||
if amende > 0 {
|
||
log.Printf("⚠️ [CheckClientCanOrder] Client %s bloqué: %.2f points de pénalités", username, amende)
|
||
return false, amende, fmt.Errorf("pénalités impayées: %.2f points", amende)
|
||
}
|
||
|
||
return true, 0, 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 {
|
||
var query string
|
||
switch {
|
||
case extraPoolKey != "":
|
||
_, err := d.Exec(
|
||
`UPDATE clients SET points_extra = points_extra - $2, updated_at = CURRENT_TIMESTAMP WHERE username = $1`,
|
||
username, extraPoolKey,
|
||
)
|
||
if err != nil {
|
||
log.Printf("❌ [ResetClientPointAdmin] Erreur UPDATE extra: %v", err)
|
||
} else {
|
||
cacheKey := fmt.Sprintf("client:%s", username)
|
||
Redis.Del(RedisCtx, cacheKey)
|
||
}
|
||
return err
|
||
default: // -1 ou poolIdx sans clé → reset total
|
||
query = `UPDATE clients SET points_extra = '{}'::jsonb, updated_at = CURRENT_TIMESTAMP WHERE username = $1`
|
||
}
|
||
|
||
result, err := d.Exec(query, username)
|
||
if err != nil {
|
||
log.Printf("❌ [ResetClientPointAdmin] Erreur UPDATE: %v", err)
|
||
return fmt.Errorf("erreur reset points: %w", err)
|
||
}
|
||
|
||
rowsAffected, err := result.RowsAffected()
|
||
if err != nil {
|
||
return fmt.Errorf("erreur vérification: %w", err)
|
||
}
|
||
if rowsAffected == 0 {
|
||
return fmt.Errorf("client non trouvé")
|
||
}
|
||
|
||
// Invalider le cache Redis du client
|
||
cacheKey := fmt.Sprintf("client:%s", username)
|
||
Redis.Del(RedisCtx, cacheKey)
|
||
|
||
return nil
|
||
}
|
||
|
||
func (d *Database) ResetClientPenalties(username string, resetCancellationsCount bool) error {
|
||
log.Printf("🔄 [ResetClientPenalties] Reset pour %s (reset_count=%v)", username, resetCancellationsCount)
|
||
|
||
var query string
|
||
if resetCancellationsCount {
|
||
query = `UPDATE clients
|
||
SET amende = 0,
|
||
cancellations_count = 0,
|
||
updated_at = CURRENT_TIMESTAMP
|
||
WHERE username = $1`
|
||
} else {
|
||
query = `UPDATE clients
|
||
SET amende = 0,
|
||
updated_at = CURRENT_TIMESTAMP
|
||
WHERE username = $1`
|
||
}
|
||
|
||
result, err := d.Exec(query, username)
|
||
if err != nil {
|
||
log.Printf("❌ [ResetClientPenalties] Erreur UPDATE: %v", err)
|
||
return fmt.Errorf("erreur reset pénalités: %w", err)
|
||
}
|
||
|
||
rowsAffected, err := result.RowsAffected()
|
||
if err != nil {
|
||
return fmt.Errorf("erreur vérification: %w", err)
|
||
}
|
||
if rowsAffected == 0 {
|
||
return fmt.Errorf("client non trouvé")
|
||
}
|
||
|
||
// Invalider le cache Redis du client
|
||
cacheKey := fmt.Sprintf("client:%s", username)
|
||
Redis.Del(RedisCtx, cacheKey)
|
||
|
||
return nil
|
||
}
|
||
|
||
func (d *Database) GetAllClientsWithPenalties() ([]map[string]interface{}, error) {
|
||
query := `
|
||
SELECT username, amende, COALESCE(cancellations_count, 0) as cancellations_count, updated_at
|
||
FROM clients
|
||
WHERE amende > 0
|
||
ORDER BY amende DESC
|
||
`
|
||
|
||
rows, err := d.Query(query)
|
||
if err != nil {
|
||
log.Printf("❌ [GetAllClientsWithPenalties] Erreur query: %v", err)
|
||
return nil, fmt.Errorf("erreur récupération clients: %w", err)
|
||
}
|
||
defer rows.Close()
|
||
|
||
var clients []map[string]interface{}
|
||
for rows.Next() {
|
||
var username string
|
||
var amende float64
|
||
var cancellationsCount int
|
||
var updatedAt interface{}
|
||
|
||
err := rows.Scan(&username, &amende, &cancellationsCount, &updatedAt)
|
||
if err != nil {
|
||
log.Printf("⚠️ [GetAllClientsWithPenalties] Erreur scan: %v", err)
|
||
continue
|
||
}
|
||
|
||
clients = append(clients, map[string]interface{}{
|
||
"username": username,
|
||
"total_penalty": amende,
|
||
"cancellations_count": cancellationsCount,
|
||
"last_updated": updatedAt,
|
||
})
|
||
}
|
||
|
||
log.Printf("📊 [GetAllClientsWithPenalties] %d clients avec pénalités", len(clients))
|
||
|
||
return clients, nil
|
||
}
|
||
|
||
func (d *Database) GetClientPenaltiesStats() (map[string]interface{}, error) {
|
||
query := `
|
||
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
|
||
`
|
||
|
||
var stats struct {
|
||
ClientsWithPenalties int
|
||
TotalPenalties float64
|
||
AvgPenalty float64
|
||
MaxPenalty float64
|
||
TotalClients int
|
||
}
|
||
|
||
err := d.QueryRow(query).Scan(
|
||
&stats.ClientsWithPenalties,
|
||
&stats.TotalPenalties,
|
||
&stats.AvgPenalty,
|
||
&stats.MaxPenalty,
|
||
&stats.TotalClients,
|
||
)
|
||
if err != nil {
|
||
log.Printf("❌ [GetClientPenaltiesStats] Erreur: %v", err)
|
||
return nil, fmt.Errorf("erreur récupération stats: %w", err)
|
||
}
|
||
|
||
result := map[string]interface{}{
|
||
"clients_with_penalties": stats.ClientsWithPenalties,
|
||
"total_penalties": stats.TotalPenalties,
|
||
"average_penalty": stats.AvgPenalty,
|
||
"max_penalty": stats.MaxPenalty,
|
||
"total_clients": stats.TotalClients,
|
||
}
|
||
|
||
log.Printf("📊 [GetClientPenaltiesStats] Stats: %d/%d clients avec pénalités",
|
||
stats.ClientsWithPenalties, stats.TotalClients)
|
||
|
||
return result, nil
|
||
}
|
||
|
||
func (d *Database) CalculateAndAddPointsForCommandTx(tx *sql.Tx, 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
|
||
rows, err := tx.Query(`
|
||
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 = $1
|
||
`, commandID)
|
||
if err != nil {
|
||
log.Printf("❌ [CalcPointsTx] Erreur query items: %v", err)
|
||
return 0, "", fmt.Errorf("erreur récupération items: %w", err)
|
||
}
|
||
defer rows.Close()
|
||
|
||
var itemCount int
|
||
poolTotals := make([]float64, len(pools))
|
||
|
||
for rows.Next() {
|
||
var quantite, prix float64
|
||
var category string
|
||
if err := rows.Scan(&quantite, &prix, &category); err != nil {
|
||
log.Printf("❌ [CalcPointsTx] Erreur scan: %v", err)
|
||
return 0, "", fmt.Errorf("erreur lecture item: %w", err)
|
||
}
|
||
itemCount++
|
||
catLower := strings.ToLower(category)
|
||
if poolIdx, ok := catToPool[catLower]; ok {
|
||
poolTotals[poolIdx] += prix
|
||
}
|
||
}
|
||
|
||
if err = rows.Err(); err != nil {
|
||
log.Printf("❌ [CalcPointsTx] Erreur rows: %v", err)
|
||
return 0, "", fmt.Errorf("erreur itération items: %w", err)
|
||
}
|
||
|
||
if itemCount == 0 {
|
||
log.Printf("⚠️ [CalcPointsTx] Aucun item trouvé pour cmd %d", commandID)
|
||
return 0, "", nil
|
||
}
|
||
|
||
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
|
||
}
|
||
_, err = tx.Exec(`
|
||
UPDATE clients
|
||
SET points_extra = jsonb_set(
|
||
COALESCE(points_extra, '{}'::jsonb),
|
||
ARRAY[$2],
|
||
to_jsonb(COALESCE((points_extra->>$2)::int, 0) + $3)
|
||
), updated_at = CURRENT_TIMESTAMP
|
||
WHERE username = $1
|
||
`, username, pool.Key, poolPts[i])
|
||
if 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)
|
||
|
||
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.QueryRow(`
|
||
SELECT EXISTS(
|
||
SELECT 1 FROM commandes
|
||
WHERE id = $1 AND livreur_assign = $2
|
||
)
|
||
`, commandID, username).Scan(&exists)
|
||
return exists, err
|
||
}
|
||
|
||
// 👤 User : seulement SES commandes
|
||
err := d.QueryRow(`
|
||
SELECT EXISTS(
|
||
SELECT 1 FROM commandes
|
||
WHERE id = $1 AND username = $2
|
||
)
|
||
`, commandID, username).Scan(&exists)
|
||
|
||
return exists, err
|
||
}
|