chore: add ansible backend docker frontend-prep

This commit is contained in:
2026-01-21 13:05:13 +01:00
parent 5a280b6b01
commit 943fe4de7d
14930 changed files with 2341433 additions and 0 deletions
+942
View File
@@ -0,0 +1,942 @@
package db
import (
"database/sql"
"fmt"
"gestion/models"
"log"
"strings"
)
func (d *Database) CreateClient(client *models.Client) error {
query := `INSERT INTO clients (username, password, nom, prenom, telephone, command, point, point_zipette, amende, created_at)
VALUES ($1, $2, $3, $4, $5, 0, 0, 0, 0.0, CURRENT_TIMESTAMP)
RETURNING id, created_at`
err := d.QueryRow(query, client.Username, client.Password, client.Nom, client.Prenom, client.Telephone).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, point, point_zipette, amende, created_at
FROM clients WHERE id = $1`
err := d.QueryRow(query, id).Scan(
&client.ID,
&client.Username,
&client.Password,
&client.Nom,
&client.Prenom,
&client.Telephone,
&client.Command,
&client.Point,
&client.PointZipette,
&client.Amende,
&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)
}
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, point, point_zipette, amende, 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{}
err := rows.Scan(
&client.ID,
&client.Username,
&client.Password,
&client.Nom,
&client.Prenom,
&client.Telephone,
&client.Command,
&client.Point,
&client.PointZipette,
&client.Amende,
&client.CreatedAt,
)
if err != nil {
return nil, fmt.Errorf("erreur lors du scan du client: %w", err)
}
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, point = $7, point_zipette = $8, amende = $9
WHERE id = $10`
result, err := d.Exec(query,
client.Username,
client.Password,
client.Nom,
client.Prenom,
client.Telephone,
client.Command,
client.Point,
client.PointZipette,
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é")
}
log.Printf("✅ Client mis à jour: %s (ID: %d)", client.Username, client.ID)
return nil
}
// DeleteClient supprime un client
func (d *Database) DeleteClient(id int) error {
// ✅ MODIFIÉ : Supprimer tous les tokens du client avec le user_type "client"
_ = 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é")
}
log.Printf("✅ Client supprimé (ID: %d)", id)
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é")
}
log.Printf("✅ Mot de passe client mis à jour (ID: %d)", clientID)
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 = 'support' 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": client.Point,
"points_zipette": client.PointZipette,
"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)
}
// Réinitialiser les pénalités
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é")
}
log.Printf("✅ [PayClientPenalties] Pénalités réglées pour %s", username)
// Invalider le cache Redis du client
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
}
// ✅ NOUVELLE FONCTION: Ajouter des points selon la catégorie
func (d *Database) AddClientPointsByCategory(username string, points int, category string) error {
var query string
if category == "zipette&co" {
query = `UPDATE clients
SET point_zipette = point_zipette + $1
WHERE username = $2`
log.Printf("🎁 [ADD_POINTS] Ajout de %d points ZIPETTE à %s", points, username)
} else {
query = `UPDATE clients
SET point = point + $1
WHERE username = $2`
log.Printf("🎁 [ADD_POINTS] Ajout de %d points WEED/HASH à %s", points, username)
}
result, err := d.Exec(query, points, username)
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 (%s) ajoutés au client %s (EN DB)", points, category, username)
return nil
}
// ✅ ANCIENNE FONCTION CONSERVÉE POUR COMPATIBILITÉ (utilise weed/hash par défaut)
func (d *Database) AddClientPoints(username string, points int) error {
return d.AddClientPointsByCategory(username, points, "weed_hash")
}
func (d *Database) GetClientByTelephone(telephone string) (*models.Client, error) {
client := &models.Client{}
query := `SELECT id, username, password, nom, prenom, telephone, command, point, point_zipette, 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.Point,
&client.PointZipette,
&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{}
query := `SELECT id, username, password, nom, prenom, telephone, command, point, point_zipette, amende, 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.Point,
&client.PointZipette,
&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
}
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
}
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é")
}
log.Printf("✅ [ResetClientPenalties] Reset effectué pour %s", username)
// 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
}
// ✅ FONCTION MODIFIÉE: Calculer les points sans cumuler entre catégories
// À remplacer dans db/clients.go à partir de la ligne 498
// À remplacer dans db/clients.go
func (d *Database) CalculatePointsForCommand(commandID int) (int, string, error) {
query := `
SELECT p.category, ci.prix, ci.quantite
FROM command_items ci
JOIN products p ON ci.product_id = p.id
WHERE ci.command_id = $1
`
rows, err := d.Query(query, commandID)
if err != nil {
return 0, "", fmt.Errorf("erreur récupération items: %w", err)
}
defer rows.Close()
zipetteTotal := 0.0
weedTotal := 0.0
grosSemiTotal := 0.0
for rows.Next() {
var category string
var prix float64
var quantite int
if err := rows.Scan(&category, &prix, &quantite); err != nil {
log.Printf("⚠️ Erreur scan item: %v", err)
continue
}
itemTotal := prix // Prix déjà calculé pour la quantité
categoryLower := strings.ToLower(category)
if categoryLower == "zipette&co" || categoryLower == "zipette_co" {
zipetteTotal += itemTotal
} else if categoryLower == "gros&semi" || categoryLower == "gros_semi" {
grosSemiTotal += itemTotal
} else {
weedTotal += itemTotal
}
}
log.Printf("💰 [CALC_POINTS] Cmd %d - Zipette: %.2f€, Weed: %.2f€, GrosSemi: %.2f€",
commandID, zipetteTotal, weedTotal, grosSemiTotal)
// ✅ CALCUL POINTS WEED&HASH
weedPoints := 0
if weedTotal > 0 {
switch {
case weedTotal >= 30 && weedTotal <= 50:
weedPoints = 1
case weedTotal >= 60 && weedTotal <= 150:
weedPoints = 2
case weedTotal >= 160 && weedTotal <= 300:
weedPoints = 3
case weedTotal >= 310 && weedTotal <= 400:
weedPoints = 5
case weedTotal >= 400:
weedPoints = 10
}
if weedPoints > 0 {
log.Printf("🎁 [CALC_POINTS] Weed: %.2f€ → %d points", weedTotal, weedPoints)
}
}
// ✅ CALCUL POINTS ZIPETTE&CO
zipettePoints := 0
if zipetteTotal > 0 {
switch {
case zipetteTotal >= 30 && zipetteTotal <= 100:
zipettePoints = 1
case zipetteTotal >= 110 && zipetteTotal <= 200:
zipettePoints = 2
case zipetteTotal >= 210:
zipettePoints = 3
}
if zipettePoints > 0 {
log.Printf("🎁 [CALC_POINTS] Zipette: %.2f€ → %d points", zipetteTotal, zipettePoints)
}
}
totalPoints := zipettePoints + weedPoints
if totalPoints == 0 {
if grosSemiTotal > 0 && zipetteTotal == 0 && weedTotal == 0 {
log.Printf("️ [CALC_POINTS] Cmd %d - Catégorie GROS&SEMI uniquement (%.2f€) → 0 points",
commandID, grosSemiTotal)
return 0, "gros&semi", nil
}
log.Printf("️ [CALC_POINTS] Cmd %d - Aucun montant éligible aux points", commandID)
return 0, "unknown", nil
}
var dominantCategory string
if zipetteTotal >= weedTotal {
dominantCategory = "zipette&co"
} else {
dominantCategory = "weed&hash"
}
log.Printf("✅ [CALC_POINTS] Cmd %d - Total: %d points (Zipette: %d, Weed: %d)",
commandID, totalPoints, zipettePoints, weedPoints)
return totalPoints, dominantCategory, nil
}
// ✅ FONCTION CORRIGÉE: Calculer et ajouter les points séparément avec le BON BARÈME
func (d *Database) CalculateAndAddPointsForCommand(commandID int, username string) (int, error) {
query := `
SELECT p.category, ci.prix, ci.quantite
FROM command_items ci
JOIN products p ON ci.product_id = p.id
WHERE ci.command_id = $1
`
rows, err := d.Query(query, commandID)
if err != nil {
return 0, fmt.Errorf("erreur récupération items: %w", err)
}
defer rows.Close()
zipetteTotal := 0.0
weedTotal := 0.0
grosSemiTotal := 0.0
for rows.Next() {
var category string
var prix float64
var quantite int
if err := rows.Scan(&category, &prix, &quantite); err != nil {
log.Printf("⚠️ Erreur scan item: %v", err)
continue
}
// ✅ prix contient déjà le total pour la quantité
itemTotal := prix
categoryLower := strings.ToLower(category)
if categoryLower == "zipette&co" || categoryLower == "zipette_co" {
zipetteTotal += itemTotal
} else if categoryLower == "gros&semi" || categoryLower == "gros_semi" {
grosSemiTotal += itemTotal
} else {
weedTotal += itemTotal
}
}
log.Printf("💰 [CALC_POINTS] Cmd %d - Zipette: %.2f€, Weed: %.2f€, GrosSemi: %.2f€",
commandID, zipetteTotal, weedTotal, grosSemiTotal)
// ✅ CALCUL POINTS WEED&HASH - NOUVEAU BARÈME
// De 30 à 50€ -> 1 point
// De 60 à 150€ -> 2 points
// De 160 à 300€ -> 3 points
// De 310 à 400€ -> 5 points
// 400€ et + -> 10 points
weedPoints := 0
if weedTotal > 0 {
switch {
case weedTotal >= 30 && weedTotal <= 50:
weedPoints = 1
case weedTotal >= 60 && weedTotal <= 150:
weedPoints = 2
case weedTotal >= 160 && weedTotal <= 300:
weedPoints = 3
case weedTotal >= 310 && weedTotal <= 400:
weedPoints = 5
case weedTotal >= 400:
weedPoints = 10
}
if weedPoints > 0 {
log.Printf("🎁 [CALC_POINTS] Weed: %.2f€ → %d points", weedTotal, weedPoints)
}
}
// ✅ CALCUL POINTS ZIPETTE&CO - NOUVEAU BARÈME
// De 30 à 100€ -> 1 point
// De 110 à 200€ -> 2 points (je suppose que c'est 110 et non 1100)
// De 210€ et + -> 3 points
zipettePoints := 0
if zipetteTotal > 0 {
switch {
case zipetteTotal >= 30 && zipetteTotal <= 100:
zipettePoints = 1
case zipetteTotal >= 110 && zipetteTotal <= 200:
zipettePoints = 2
case zipetteTotal >= 210:
zipettePoints = 3
}
if zipettePoints > 0 {
log.Printf("🎁 [CALC_POINTS] Zipette: %.2f€ → %d points", zipetteTotal, zipettePoints)
}
}
totalPoints := 0
// ✅ AJOUTER LES POINTS SÉPARÉMENT PAR CATÉGORIE
if zipettePoints > 0 {
if err := d.AddClientPointsByCategory(username, zipettePoints, "zipette&co"); err != nil {
log.Printf("❌ Erreur ajout points Zipette: %v", err)
} else {
totalPoints += zipettePoints
log.Printf("✅ %d points ZIPETTE ajoutés à %s", zipettePoints, username)
}
}
if weedPoints > 0 {
if err := d.AddClientPointsByCategory(username, weedPoints, "weed&hash"); err != nil {
log.Printf("❌ Erreur ajout points Weed: %v", err)
} else {
totalPoints += weedPoints
log.Printf("✅ %d points WEED ajoutés à %s", weedPoints, username)
}
}
if totalPoints == 0 {
if grosSemiTotal > 0 && zipetteTotal == 0 && weedTotal == 0 {
log.Printf("️ [CALC_POINTS] Cmd %d - Catégorie GROS&SEMI uniquement → 0 points", commandID)
} else {
log.Printf("️ [CALC_POINTS] Cmd %d - Aucun point éligible (Weed: %.2f€, Zipette: %.2f€)",
commandID, weedTotal, zipetteTotal)
}
}
log.Printf("✅ [CALC_POINTS] Cmd %d - Total: %d points (Zipette: %d, Weed: %d)",
commandID, totalPoints, zipettePoints, weedPoints)
return totalPoints, nil
}
func (d *Database) CalculateAndAddPointsForCommandTx(tx *sql.Tx, commandID int, username string) (int, error) {
log.Printf("💰 [CalcPointsTx] START - cmd=%d, user=%s", commandID, username)
// ✅ ÉTAPE 1: Récupérer tous les items de la commande avec leurs catégories
query := `
SELECT ci.quantite, ci.prix, COALESCE(p.category, 'weed_hash') as category
FROM command_items ci
LEFT JOIN products p ON ci.product_id = p.id
WHERE ci.command_id = $1
`
rows, err := tx.Query(query, 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()
type ItemPoints struct {
Category string
Quantite int
Prix float64
}
var items []ItemPoints
totalPrixWeedHash := 0.0
totalPrixZipette := 0.0
for rows.Next() {
var item ItemPoints
if err := rows.Scan(&item.Quantite, &item.Prix, &item.Category); err != nil {
log.Printf("❌ [CalcPointsTx] Erreur scan: %v", err)
return 0, fmt.Errorf("erreur lecture item: %w", err)
}
items = append(items, item)
// Cumuler par catégorie
if item.Category == "zipette" {
totalPrixZipette += item.Prix
} else {
// weed_hash ou autres catégories
totalPrixWeedHash += item.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 len(items) == 0 {
log.Printf("⚠️ [CalcPointsTx] Aucun item trouvé pour cmd %d", commandID)
return 0, nil
}
log.Printf("📊 [CalcPointsTx] %d items - weed_hash: %.2f€, zipette: %.2f€",
len(items), totalPrixWeedHash, totalPrixZipette)
// ✅ ÉTAPE 2: Calculer les points par catégorie
// Règle: 1 point par tranche de 10€
pointsWeedHash := int(totalPrixWeedHash / 10.0)
pointsZipette := int(totalPrixZipette / 10.0)
totalPoints := pointsWeedHash + pointsZipette
log.Printf("💰 [CalcPointsTx] Points calculés - weed_hash: %d, zipette: %d, total: %d",
pointsWeedHash, pointsZipette, totalPoints)
// ✅ ÉTAPE 3: Mettre à jour les points du client (dans la transaction)
if pointsWeedHash > 0 || pointsZipette > 0 {
updateQuery := `
UPDATE clients
SET
point = point + $1,
point_zipette = point_zipette + $2,
updated_at = CURRENT_TIMESTAMP
WHERE username = $3
`
result, err := tx.Exec(updateQuery, pointsWeedHash, pointsZipette, username)
if err != nil {
log.Printf("❌ [CalcPointsTx] Erreur UPDATE points: %v", err)
return 0, fmt.Errorf("erreur mise à jour points: %w", err)
}
rows, _ := result.RowsAffected()
if rows == 0 {
log.Printf("⚠️ [CalcPointsTx] Client %s non trouvé", username)
return 0, fmt.Errorf("client non trouvé")
}
log.Printf("✅ [CalcPointsTx] Points ajoutés: +%d weed_hash, +%d zipette pour %s",
pointsWeedHash, pointsZipette, username)
}
log.Printf("🎉 [CalcPointsTx] SUCCÈS - Total %d points attribués", totalPoints)
return totalPoints, 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
}