From 13d978c76e91178383af1a7f9287b08459236222 Mon Sep 17 00:00:00 2001 From: Xor290 Date: Sun, 21 Jun 2026 12:13:28 +0200 Subject: [PATCH] chore: build --- backend/gestion/handlers/rating.go | 22 +++++ backend/gestion/handlers/stats.go | 127 ++++++++++++++++++++++++----- backend/gestion/routes/routes.go | 3 + 3 files changed, 131 insertions(+), 21 deletions(-) diff --git a/backend/gestion/handlers/rating.go b/backend/gestion/handlers/rating.go index d4b1c075..8c4086a6 100644 --- a/backend/gestion/handlers/rating.go +++ b/backend/gestion/handlers/rating.go @@ -90,6 +90,28 @@ func GetLivreurRatings(c *gin.Context) { }) } +// GetMyRatings retourne les avis reçus par le livreur connecté (uniquement les siens). +func GetMyRatings(c *gin.Context) { + username := c.GetString("username") + if username == "" || c.GetString("role") != "livreur" { + c.JSON(http.StatusForbidden, gin.H{"error": "Accès réservé aux livreurs"}) + return + } + + database := c.MustGet("database").(*db.Database) + ratings, avg, err := database.GetLivreurRatings(username) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur récupération avis"}) + return + } + + c.JSON(http.StatusOK, gin.H{ + "ratings": ratings, + "average": avg, + "count": len(ratings), + }) +} + func GetOrderRatingStatus(c *gin.Context) { clientUsername := c.GetString("username") if clientUsername == "" { diff --git a/backend/gestion/handlers/stats.go b/backend/gestion/handlers/stats.go index 65c8b83b..e80b0631 100644 --- a/backend/gestion/handlers/stats.go +++ b/backend/gestion/handlers/stats.go @@ -5,23 +5,108 @@ import ( "gestion/db" "gestion/models" "net/http" + "time" "github.com/gin-gonic/gin" ) var weekdayNames = []string{"Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"} +// sections valides pour le reset +var validStatsSections = map[string]string{ + "commandes": "stats_reset_commandes_at", + "revenus": "stats_reset_revenus_at", + "produits": "stats_reset_produits_at", +} + +// ResetAdminStats réinitialise une section précise des statistiques. +func ResetAdminStats(c *gin.Context) { + section := c.Param("section") + key, ok := validStatsSections[section] + if !ok { + c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("section invalide : %s", section)}) + return + } + database := c.MustGet("database").(*db.Database) + now := time.Now().UTC().Format(time.RFC3339) + upsert := `INSERT INTO app_settings (key, value) VALUES (?, ?) + ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value` + if err := database.GDB.Exec(upsert, key, now).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": "Erreur reset statistiques"}) + return + } + c.JSON(http.StatusOK, gin.H{"success": true, "section": section, "reset_at": now}) +} + +// readResetAt lit une date de reset depuis app_settings (zero value si absente). +func readResetAt(database *db.Database, key string) time.Time { + var row struct{ Value string } + database.GDB.Table("app_settings").Select("value").Where("key = ?", key).Scan(&row) + if row.Value != "" { + if t, err := time.Parse(time.RFC3339, row.Value); err == nil { + return t + } + } + return time.Time{} +} + +func dateFilter(t time.Time) string { + if t.IsZero() { + return "" + } + return t.Format(time.RFC3339) +} + // GetAdminStats returns aggregated order & product statistics for the admin dashboard. func GetAdminStats(c *gin.Context) { database := c.MustGet("database").(*db.Database) gdb := database.GDB - // ── Commandes par jour de la semaine (all time, non annulées) ────────────── + resetCmd := readResetAt(database, "stats_reset_commandes_at") + resetRev := readResetAt(database, "stats_reset_revenus_at") + resetProd := readResetAt(database, "stats_reset_produits_at") + + // whereCmd : filtre commandes non annulées depuis resetCmd + whereCmd := func() string { + base := "status != 'cancelled'" + if !resetCmd.IsZero() { + base += " AND created_at >= '" + resetCmd.Format(time.RFC3339) + "'" + } + return base + } + // whereRev : filtre commandes approuvées depuis resetRev + whereRev := func() string { + base := "status = 'approved'" + if !resetRev.IsZero() { + base += " AND created_at >= '" + resetRev.Format(time.RFC3339) + "'" + } + return base + } + // whereProd : filtre commandes non annulées depuis resetProd (pour les jointures sur c.) + whereProd := func() string { + base := "status != 'cancelled'" + if !resetProd.IsZero() { + base += " AND created_at >= '" + resetProd.Format(time.RFC3339) + "'" + } + return base + } + + // compatibilité : les anciennes clauses whereBase/whereApproved pointent sur commandes/revenus + whereBase := func(extra string) string { + base := whereCmd() + if extra != "" { + return base + " AND " + extra + } + return base + } + whereApproved := whereRev + + // ── Commandes par jour de la semaine (non annulées) ──────────────────────── var wdRows []models.WeekdayRow gdb.Raw(` SELECT EXTRACT(DOW FROM created_at)::int AS dow, COUNT(*) AS count FROM commandes - WHERE status != 'cancelled' + WHERE ` + whereBase("") + ` GROUP BY dow ORDER BY dow `).Scan(&wdRows) @@ -47,7 +132,7 @@ func GetAdminStats(c *gin.Context) { SELECT DATE(created_at) AS day, COUNT(*) AS count FROM commandes WHERE created_at >= NOW() - INTERVAL '30 days' - AND status != 'cancelled' + AND ` + whereBase("") + ` GROUP BY DATE(created_at) ORDER BY day `).Scan(&dayRows) @@ -67,7 +152,7 @@ func GetAdminStats(c *gin.Context) { SELECT DATE(created_at) AS day, COALESCE(SUM(total_prix - COALESCE(referral_used, 0)), 0) AS revenue FROM commandes WHERE created_at >= NOW() - INTERVAL '30 days' - AND status = 'approved' + AND ` + whereApproved() + ` GROUP BY DATE(created_at) ORDER BY day `).Scan(&dayRevRows) @@ -81,7 +166,7 @@ func GetAdminStats(c *gin.Context) { } } - // ── Commandes & revenus par heure (all time, non annulées) ─────────────── + // ── Commandes & revenus par heure (non annulées) ───────────────────────── var hourRows []models.HourRow gdb.Raw(` SELECT @@ -89,7 +174,7 @@ func GetAdminStats(c *gin.Context) { COUNT(*) AS count, COALESCE(SUM(total_prix - COALESCE(referral_used, 0)), 0) AS revenue FROM commandes - WHERE status != 'cancelled' + WHERE ` + whereBase("") + ` GROUP BY hour ORDER BY hour `).Scan(&hourRows) @@ -124,7 +209,7 @@ func GetAdminStats(c *gin.Context) { JOIN commandes c ON c.id = ci.command_id LEFT JOIN products p ON p.id = ci.product_id LEFT JOIN categories cat ON cat.name = p.category - WHERE c.status != 'cancelled' + WHERE c.` + whereProd() + ` GROUP BY ci.product_id, ci.produit, p.category, cat.color ORDER BY total_quantity DESC LIMIT 15 @@ -162,7 +247,7 @@ func GetAdminStats(c *gin.Context) { JOIN commandes c ON c.id = ci.command_id LEFT JOIN products p ON p.id = ci.product_id LEFT JOIN categories cat ON cat.name = p.category - WHERE c.status != 'cancelled' + WHERE c.` + whereProd() + ` GROUP BY ci.product_id, ci.produit, ci.quantite, cat.color ORDER BY ci.product_id, COUNT(DISTINCT ci.command_id) DESC `).Scan(&qtyRows) @@ -220,24 +305,21 @@ func GetAdminStats(c *gin.Context) { // ── Résumé global ───────────────────────────────────────────────────────── var totalOrders int64 var totalRevenue float64 - gdb.Raw(`SELECT COUNT(*) FROM commandes WHERE status != 'cancelled'`).Scan(&totalOrders) - gdb.Raw(`SELECT COALESCE(SUM(total_prix - COALESCE(referral_used, 0)), 0) FROM commandes WHERE status = 'approved'`).Scan(&totalRevenue) + gdb.Raw(`SELECT COUNT(*) FROM commandes WHERE ` + whereBase("")).Scan(&totalOrders) + gdb.Raw(`SELECT COALESCE(SUM(total_prix - COALESCE(referral_used, 0)), 0) FROM commandes WHERE ` + whereApproved()).Scan(&totalRevenue) avgPerDay := 0.0 if totalOrders > 0 { - // average over the last 30 days with data var activeDays int64 gdb.Raw(` SELECT COUNT(DISTINCT DATE(created_at)) FROM commandes - WHERE created_at >= NOW() - INTERVAL '30 days' AND status != 'cancelled' - `).Scan(&activeDays) + WHERE created_at >= NOW() - INTERVAL '30 days' AND ` + whereBase("")).Scan(&activeDays) if activeDays > 0 { var last30Count int64 gdb.Raw(` SELECT COUNT(*) FROM commandes - WHERE created_at >= NOW() - INTERVAL '30 days' AND status != 'cancelled' - `).Scan(&last30Count) + WHERE created_at >= NOW() - INTERVAL '30 days' AND ` + whereBase("")).Scan(&last30Count) avgPerDay = float64(last30Count) / float64(activeDays) } } @@ -250,11 +332,14 @@ func GetAdminStats(c *gin.Context) { "top_product": topProductName, "avg_per_day": avgPerDay, }, - "by_weekday": byWeekday, - "by_day_30": byDay, - "by_day_revenue": byDayRevenue, - "by_hour": byHour, - "top_products": topProducts, - "by_quantity": byQuantity, + "reset_at_commandes": dateFilter(resetCmd), + "reset_at_revenus": dateFilter(resetRev), + "reset_at_produits": dateFilter(resetProd), + "by_weekday": byWeekday, + "by_day_30": byDay, + "by_day_revenue": byDayRevenue, + "by_hour": byHour, + "top_products": topProducts, + "by_quantity": byQuantity, }) } diff --git a/backend/gestion/routes/routes.go b/backend/gestion/routes/routes.go index d0b9dddc..82b953c4 100644 --- a/backend/gestion/routes/routes.go +++ b/backend/gestion/routes/routes.go @@ -208,6 +208,7 @@ func SetupRoutes(router *gin.Engine, database *db.Database, geoService *services // STATISTIQUES ADMIN // ============================================ adminGroupV2.GET("/stats", handlers.GetAdminStats) + adminGroupV2.POST("/stats/reset/:section", handlers.ResetAdminStats) adminGroupV2.POST("/active/product/price/:id", handlers.ActivePrice) adminGroupV2.POST("/desactive/product/price/:id", handlers.DesActivePrice) @@ -398,6 +399,8 @@ func SetupRoutes(router *gin.Engine, database *db.Database, geoService *services // ============================================ // NOTIFICATIONS LIVREUR // ============================================ + livreurGroupV1.GET("/ratings", handlers.GetMyRatings) + livreurGroupV1.GET("/notifications", handlers.GetLivreurNotifications) livreurGroupV1.POST("/notifications/read", handlers.MarkLivreurNotificationsRead)