Files
projet_gestion_commande/backend/gestion/db/db_clients.go
T
2026-03-03 23:42:23 +01:00

1036 lines
29 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}
// 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é")
}
log.Printf("✅ Mot de passe client mis à jour + must_change_password=false (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, must_change_password, 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.MustChangePassword,
&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) ResetClientPoint(username string, resetCancellationsPoint bool) error {
var query string
if resetCancellationsPoint {
query = `UPDATE clients SET point = 0, point_zipette = 0, updated_at = CURRENT_TIMESTAMP WHERE username = $1`
} else {
query = `UPDATE clients SET point = 0, 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
}
// ✅ 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 float64
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
categoryLower := strings.ToLower(item.Category)
if categoryLower == "zipette&co" || categoryLower == "zipette_co" {
totalPrixZipette += item.Prix
} else if categoryLower == "gros&semi" || categoryLower == "gros_semi" {
// gros&semi → 0 points, on ne cumule pas
} 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 avec le bon barème
pointsWeedHash := 0
switch {
case totalPrixWeedHash >= 30 && totalPrixWeedHash <= 50:
pointsWeedHash = 1
case totalPrixWeedHash >= 60 && totalPrixWeedHash <= 150:
pointsWeedHash = 2
case totalPrixWeedHash >= 160 && totalPrixWeedHash <= 300:
pointsWeedHash = 3
case totalPrixWeedHash >= 310 && totalPrixWeedHash <= 400:
pointsWeedHash = 5
case totalPrixWeedHash >= 400:
pointsWeedHash = 10
}
pointsZipette := 0
switch {
case totalPrixZipette >= 30 && totalPrixZipette <= 100:
pointsZipette = 1
case totalPrixZipette >= 110 && totalPrixZipette <= 200:
pointsZipette = 2
case totalPrixZipette >= 210:
pointsZipette = 3
}
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
}
// SaveClientPushToken enregistre le push token Expo d'un client
func (d *Database) SaveClientPushToken(clientID int, pushToken string) error {
_, err := d.Exec(`UPDATE clients SET push_token = $1 WHERE id = $2`, pushToken, clientID)
return err
}
// DeleteClientPushToken supprime le push token d'un client
func (d *Database) DeleteClientPushToken(clientID int) error {
_, err := d.Exec(`UPDATE clients SET push_token = NULL WHERE id = $1`, clientID)
return err
}
// GetClientPushToken retourne le push token d'un client par son username
func (d *Database) GetClientPushToken(username string) (string, error) {
var token string
err := d.QueryRow(`SELECT COALESCE(push_token, '') FROM clients WHERE username = $1`, username).Scan(&token)
return token, err
}