chore: update

This commit is contained in:
2026-03-03 23:42:23 +01:00
parent 52b059fafa
commit 0073f80a48
95 changed files with 2720 additions and 40619 deletions
+2 -2
View File
@@ -432,8 +432,8 @@ func (d *Database) GetBasketItems(username string) ([]map[string]interface{}, er
var items []map[string]interface{}
for rows.Next() {
var productID, quantity int
var price float64
var productID int
var quantity, price float64
if err := rows.Scan(&productID, &quantity, &price); err != nil {
return nil, err
}
+30 -6
View File
@@ -876,7 +876,7 @@ func (d *Database) CalculateAndAddPointsForCommandTx(tx *sql.Tx, commandID int,
type ItemPoints struct {
Category string
Quantite int
Quantite float64
Prix float64
}
@@ -894,8 +894,11 @@ func (d *Database) CalculateAndAddPointsForCommandTx(tx *sql.Tx, commandID int,
items = append(items, item)
// Cumuler par catégorie
if item.Category == "zipette" {
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
@@ -915,10 +918,31 @@ func (d *Database) CalculateAndAddPointsForCommandTx(tx *sql.Tx, commandID int,
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)
// ✅ É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",
+7 -4
View File
@@ -31,7 +31,7 @@ func validateItemID(itemID int) error {
}
return nil
}
func validateQuantite(quantite int) error {
func validateQuantite(quantite float64) error {
if quantite <= 0 {
return fmt.Errorf("quantité doit être > 0")
}
@@ -96,7 +96,8 @@ func validateItemStatus(status string) error {
func (d *Database) InsertCommandItemWithClientInfo(
commandID int,
produit string,
productID, quantite int,
productID int,
quantite float64,
prix float64,
clientUsername, clientNom, clientPrenom, clientTelephone, deliveryAddress string,
) error {
@@ -231,7 +232,8 @@ func (d *Database) GetCommandItems(commandID int) ([]map[string]interface{}, err
var items []map[string]interface{}
for rows.Next() {
var id, commandID, quantite int
var id, commandID int
var quantite float64
var productID sql.NullInt64 // ✅ FIX: Utiliser NullInt64 pour gérer NULL
var produit, clientUsername, clientNom, clientPrenom, clientTelephone string
var deliveryAddress, status sql.NullString
@@ -345,7 +347,8 @@ func (d *Database) GetCommandItemsByUsername(username string) ([]map[string]inte
var items []map[string]interface{}
for rows.Next() {
var id, commandID, quantite int
var id, commandID int
var quantite float64
var productID sql.NullInt64 // ✅ FIX: NullInt64
var produit, clientUsername, clientNom, clientPrenom, clientTelephone string
var deliveryAddress, status sql.NullString
+2 -2
View File
@@ -82,7 +82,7 @@ func (d *Database) CreateCommand(username string) (*models.Command, error) {
type BasketItem struct {
ProductID int
Quantity int
Quantity float64
Price float64
}
@@ -190,7 +190,7 @@ func (d *Database) CreateCommandWithAddress(username, deliveryAddress string) (*
type BasketItem struct {
ProductID int
Quantity int
Quantity float64
Price float64
}
+46 -1
View File
@@ -76,11 +76,55 @@ func InitDB() *Database {
log.Fatalf("❌ Erreur migration must_change_password: %v", err)
}
// Migration: ajouter colonne push_token pour les notifications push
// Migration: ajouter colonne push_token pour les notifications push (clients)
if _, err = database.Exec(`ALTER TABLE clients ADD COLUMN IF NOT EXISTS push_token TEXT`); err != nil {
log.Fatalf("❌ Erreur migration push_token: %v", err)
}
// Migration: ajouter colonne push_token pour les notifications push (livreurs/users)
if _, err = database.Exec(`ALTER TABLE users ADD COLUMN IF NOT EXISTS push_token TEXT`); err != nil {
log.Fatalf("❌ Erreur migration push_token users: %v", err)
}
// Migration: ajouter colonne unit pour l'unité de mesure des produits (kg, g, bag, l, cl, pcs, u)
if _, err = database.Exec(`ALTER TABLE products ADD COLUMN IF NOT EXISTS unit VARCHAR(10) NOT NULL DEFAULT 'u'`); err != nil {
log.Fatalf("❌ Erreur migration unit products: %v", err)
}
// Migration: baskets.quantity INTEGER → NUMERIC(10,3) pour supporter les quantités fractionnaires (ex: 0.5g)
if _, err = database.Exec(`
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'baskets' AND column_name = 'quantity'
AND data_type = 'integer'
) THEN
ALTER TABLE baskets ALTER COLUMN quantity TYPE NUMERIC(10,3) USING quantity::NUMERIC(10,3);
END IF;
END
$$;
`); err != nil {
log.Fatalf("❌ Erreur migration baskets.quantity: %v", err)
}
// Migration: command_items.quantite INTEGER → NUMERIC(10,3) pour supporter les quantités fractionnaires
if _, err = database.Exec(`
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'command_items' AND column_name = 'quantite'
AND data_type = 'integer'
) THEN
ALTER TABLE command_items ALTER COLUMN quantite TYPE NUMERIC(10,3) USING quantite::NUMERIC(10,3);
END IF;
END
$$;
`); err != nil {
log.Fatalf("❌ Erreur migration command_items.quantite: %v", err)
}
// Lancer le nettoyage périodique des tokens expirés
go database.cleanExpiredTokensPeriodically()
@@ -151,6 +195,7 @@ func (db *Database) createTables() error {
name VARCHAR(255) NOT NULL,
category VARCHAR(100) NOT NULL,
stock DECIMAL(10,2) NOT NULL DEFAULT 0,
unit VARCHAR(10) NOT NULL DEFAULT 'u',
description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+59 -2
View File
@@ -2,6 +2,7 @@ package db
import (
"bytes"
"database/sql"
"encoding/json"
"fmt"
"log"
@@ -36,11 +37,15 @@ func (d *Database) NotifyClient(username string, commandID int, notifType, messa
}
func sendExpoPush(token, title, body string, commandID int, notifType string) {
sendExpoPushWithChannel(token, title, body, commandID, notifType, "orders")
}
func sendExpoPushWithChannel(token, title, body string, commandID int, notifType, channelID string) {
payload := map[string]interface{}{
"to": token,
"title": title,
"body": body,
"channelId": "orders",
"channelId": channelID,
"data": map[string]interface{}{
"command_id": commandID,
"type": notifType,
@@ -71,7 +76,59 @@ func sendExpoPush(token, title, body string, commandID int, notifType string) {
}
defer resp.Body.Close()
log.Printf("✅ [EXPO_PUSH] Push envoyé à %s (status: %d)", token, resp.StatusCode)
log.Printf("✅ [EXPO_PUSH] Push envoyé à %s (channel: %s, status: %d)", token, channelID, resp.StatusCode)
}
// ============================================
// PUSH TOKEN - LIVREURS (table users)
// ============================================
// SaveUserPushToken sauvegarde le token push d'un livreur dans la table users
func (d *Database) SaveUserPushToken(username, token string) error {
_, err := d.Exec(`UPDATE users SET push_token = $1 WHERE username = $2`, token, username)
return err
}
// GetUserPushToken récupère le token push d'un livreur depuis la table users
func (d *Database) GetUserPushToken(username string) (string, error) {
var token sql.NullString
err := d.QueryRow(`SELECT push_token FROM users WHERE username = $1`, username).Scan(&token)
if err != nil || !token.Valid {
return "", err
}
return token.String, nil
}
// DeleteUserPushToken supprime le token push d'un livreur
func (d *Database) DeleteUserPushToken(username string) error {
_, err := d.Exec(`UPDATE users SET push_token = NULL WHERE username = $1`, username)
return err
}
// NotifyLivreur envoie une notification in-app (Redis) + push Expo à un livreur
func (d *Database) NotifyLivreur(username string, commandID int, notifType, message string) error {
notifKey := fmt.Sprintf("notifications:%s", username)
notification := map[string]interface{}{
"command_id": commandID,
"type": notifType,
"message": message,
"created_at": time.Now().Format(time.RFC3339),
"read": false,
}
notifJSON, _ := json.Marshal(notification)
Redis.LPush(RedisCtx, notifKey, notifJSON)
Redis.Expire(RedisCtx, notifKey, 7*24*time.Hour)
// Push notification si le livreur a un token enregistré
pushToken, err := d.GetUserPushToken(username)
if err == nil && pushToken != "" {
go sendExpoPushWithChannel(pushToken, "Nouvelle commande assignée", message, commandID, notifType, "deliveries")
}
log.Printf("📬 [LIVREUR_NOTIF] Notification envoyée à %s: %s", username, message)
return nil
}
// AddDeliveryRating ajoute une note pour un livreur
+12 -13
View File
@@ -17,7 +17,8 @@ func (db *Database) CreateProduct(product interface{}) error {
GetName() string
GetCategory() string
GetDescription() string
GetStock() float64 // ← ajouter méthode pour le stock
GetStock() float64
GetUnit() string
GetPrices() []models.ProductPrice
SetID(int)
SetCreatedAt(time.Time)
@@ -45,15 +46,15 @@ func (db *Database) CreateProduct(product interface{}) error {
log.Printf("📦 [DB CreateProduct] Nombre de prix: %d", len(p.GetPrices()))
}
// Insérer le produit avec le stock
query := `INSERT INTO products (name, category, description, stock, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6) RETURNING id, created_at, updated_at`
// Insérer le produit avec le stock et l'unité
query := `INSERT INTO products (name, category, description, stock, unit, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id, created_at, updated_at`
now := time.Now()
var productID int
var createdAt, updatedAt time.Time
err := db.QueryRow(query, p.GetName(), p.GetCategory(), p.GetDescription(), p.GetStock(), now, now).
err := db.QueryRow(query, p.GetName(), p.GetCategory(), p.GetDescription(), p.GetStock(), p.GetUnit(), now, now).
Scan(&productID, &createdAt, &updatedAt)
if err != nil {
log.Printf("❌ [DB CreateProduct] Erreur INSERT: %v", err)
@@ -93,10 +94,10 @@ func (d *Database) GetProductByID(id int) (models.Product, error) {
// ✅ AJOUTER stock dans le SELECT
err := d.QueryRow(`
SELECT id, name, category, description, stock, created_at, updated_at
SELECT id, name, category, description, stock, unit, created_at, updated_at
FROM products
WHERE id=$1
`, id).Scan(&p.ID, &p.Name, &p.Category, &p.Description, &p.Stock, &p.CreatedAt, &p.UpdatedAt)
`, id).Scan(&p.ID, &p.Name, &p.Category, &p.Description, &p.Stock, &p.Unit, &p.CreatedAt, &p.UpdatedAt)
if err != nil {
log.Printf("❌ [GetProductByID] Erreur query: %v", err)
@@ -122,7 +123,7 @@ func (d *Database) GetAllProducts() ([]models.Product, error) {
log.Println("📦 [GetAllProducts] START")
rows, err := d.Query(`
SELECT id, name, category, description, stock, created_at, updated_at
SELECT id, name, category, description, stock, unit, created_at, updated_at
FROM products
ORDER BY id ASC
`)
@@ -135,7 +136,7 @@ func (d *Database) GetAllProducts() ([]models.Product, error) {
var products []models.Product
for rows.Next() {
var p models.Product
if err := rows.Scan(&p.ID, &p.Name, &p.Category, &p.Description, &p.Stock, &p.CreatedAt, &p.UpdatedAt); err != nil {
if err := rows.Scan(&p.ID, &p.Name, &p.Category, &p.Description, &p.Stock, &p.Unit, &p.CreatedAt, &p.UpdatedAt); err != nil {
log.Printf("❌ [GetAllProducts] Erreur scan: %v", err)
return nil, err
}
@@ -173,9 +174,8 @@ func (d *Database) GetAllProducts() ([]models.Product, error) {
func (db *Database) GetProductsByCategory(category string) ([]models.Product, error) {
log.Printf("📦 [GetProductsByCategory] START - Category=%s", category)
// ✅ AJOUTER stock dans le SELECT
rows, err := db.Query(`
SELECT id, name, category, description, stock, created_at, updated_at
SELECT id, name, category, description, stock, unit, created_at, updated_at
FROM products
WHERE category = $1
ORDER BY created_at DESC
@@ -190,8 +190,7 @@ func (db *Database) GetProductsByCategory(category string) ([]models.Product, er
var products []models.Product
for rows.Next() {
var p models.Product
// ✅ AJOUTER &p.Stock dans le Scan
if err := rows.Scan(&p.ID, &p.Name, &p.Category, &p.Description, &p.Stock, &p.CreatedAt, &p.UpdatedAt); err != nil {
if err := rows.Scan(&p.ID, &p.Name, &p.Category, &p.Description, &p.Stock, &p.Unit, &p.CreatedAt, &p.UpdatedAt); err != nil {
log.Printf("❌ [GetProductsByCategory] Erreur scan: %v", err)
return nil, fmt.Errorf("erreur lors du scan d'un produit: %w", err)
}