From b8aab5643f84201e8f487a79433118ec3463291c Mon Sep 17 00:00:00 2001 From: Xor290 Date: Sun, 28 Jun 2026 13:52:47 +0200 Subject: [PATCH] chore: build --- backend/gestion/db/db_stat.go | 53 ++++++++++++++++----- backend/gestion/handlers/stats.go | 77 +++++++++++++++++++++++++++++++ backend/gestion/routes/routes.go | 2 +- 3 files changed, 119 insertions(+), 13 deletions(-) diff --git a/backend/gestion/db/db_stat.go b/backend/gestion/db/db_stat.go index af302e57..194422ad 100644 --- a/backend/gestion/db/db_stat.go +++ b/backend/gestion/db/db_stat.go @@ -115,8 +115,6 @@ func (d *Database) RevenueByDayLast30(dayRevRows *[]models.DayRevenueRow, resetA // ── Commandes par jour sur un mois calendaire complet ──────────────────────── -// DailyMonthStatRow représente l'agrégat d'un jour donné (commandes, revenu, -// quantité vendue) au sein d'un mois calendaire. type DailyMonthStatRow struct { Day time.Time Count int @@ -124,10 +122,6 @@ type DailyMonthStatRow struct { Quantity float64 } -// StatsByDayForMonth renvoie, pour chaque jour ayant au moins une commande dans -// le mois calendaire de `monthStart` (1er jour du mois, 00:00), le nombre de -// commandes (non annulées), le revenu (commandes approuvées) et la quantité -// totale d'articles vendus. resetAt filtre sur created_at si non nul. func (d *Database) StatsByDayForMonth(rows *[]DailyMonthStatRow, monthStart time.Time, resetAt time.Time) error { start := time.Date(monthStart.Year(), monthStart.Month(), 1, 0, 0, 0, 0, monthStart.Location()) end := start.AddDate(0, 1, 0) @@ -137,7 +131,7 @@ func (d *Database) StatsByDayForMonth(rows *[]DailyMonthStatRow, monthStart time query := ` SELECT d.day, - COALESCE(co.count, 0) AS count, + COALESCE(d.count, 0) AS count, COALESCE(rv.revenue, 0) AS revenue, COALESCE(qt.quantity, 0) AS quantity FROM ( @@ -166,7 +160,7 @@ func (d *Database) StatsByDayForMonth(rows *[]DailyMonthStatRow, monthStart time ORDER BY d.day ` - // Ordre des "?" dans la requête : (start, end, [reset]) pour le bloc "co", + // Ordre des "?" dans la requête : (start, end, [reset]) pour le bloc "d", // puis (start, end) pour "rv", puis (start, end) pour "qt". args := []interface{}{start, end} args = append(args, whereArgs...) @@ -176,10 +170,6 @@ func (d *Database) StatsByDayForMonth(rows *[]DailyMonthStatRow, monthStart time return d.GDB.Raw(query, args...).Scan(rows).Error } -// ── Commandes & revenus par heure (non annulées) ───────────────────────────── - -// ── Commandes & revenus par heure (non annulées) ───────────────────────────── - func (d *Database) OrdersAndRevenueByHour(hourRows *[]models.HourRow, resetAt time.Time) error { where, args := statusFilterClause("status != 'cancelled'", resetAt) query := ` @@ -269,6 +259,45 @@ func (d *Database) DailyProductDetail(dailyRows *[]models.DailyProductRow) error return d.GDB.Raw(query).Scan(dailyRows).Error } +func (d *Database) DailyProductDetailForDate(dailyRows *[]models.DailyProductRow, date time.Time) error { + start := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, date.Location()) + end := start.AddDate(0, 0, 1) + + query := ` + SELECT + ci.product_id, + ci.produit AS product_name, + COALESCE(p.category, 'Sans catégorie') AS category, + COALESCE(cat.color, '#7c3aed') AS category_color, + SUM(ci.quantite) AS total_quantity, + COUNT(DISTINCT ci.command_id) AS order_count, + SUM(ci.prix) AS revenue + FROM command_items ci + 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.created_at >= ? AND c.created_at < ? + AND c.status != 'cancelled' + GROUP BY ci.product_id, ci.produit, p.category, cat.color + ORDER BY p.category, SUM(ci.quantite) DESC + ` + return d.GDB.Raw(query, start, end).Scan(dailyRows).Error +} + +// DailyOrdersCountForDate renvoie le nombre de commandes (non annulées) pour +// une date précise. +func (d *Database) DailyOrdersCountForDate(date time.Time) (int64, error) { + start := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, date.Location()) + end := start.AddDate(0, 0, 1) + + var count int64 + err := d.GDB.Raw(` + SELECT COUNT(DISTINCT id) FROM commandes + WHERE created_at >= ? AND created_at < ? AND status != 'cancelled' + `, start, end).Scan(&count).Error + return count, err +} + // DailyOrdersCount renvoie le nombre de commandes (non annulées) du jour. func (d *Database) DailyOrdersCount() (int64, error) { var count int64 diff --git a/backend/gestion/handlers/stats.go b/backend/gestion/handlers/stats.go index 34e3b131..fc939360 100644 --- a/backend/gestion/handlers/stats.go +++ b/backend/gestion/handlers/stats.go @@ -117,6 +117,83 @@ func GetAdminStatsByMonth(c *gin.Context) { }) } +func GetAdminDailyDetail(c *gin.Context) { + database := c.MustGet("database").(*db.Database) + + dateParam := c.Query("date") + date := time.Now() + if dateParam != "" { + parsed, err := time.Parse("2006-01-02", dateParam) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("paramètre date invalide (attendu YYYY-MM-DD) : %s", dateParam)}) + return + } + date = parsed + } + + var dailyRows []models.DailyProductRow + if err := database.DailyProductDetailForDate(&dailyRows, date); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Erreur lors de la récupération du détail du jour: %s", err)}) + return + } + + type dailyCatGroup struct { + Category string + CategoryColor string + TotalQuantity float64 + TotalRevenue float64 + Products []gin.H + } + var dailyCats []dailyCatGroup + dailyCatIdx := map[string]int{} + dailyTotalRevenue := 0.0 + dailyTotalQty := 0.0 + + for _, r := range dailyRows { + dailyTotalRevenue += r.Revenue + dailyTotalQty += r.TotalQuantity + idx, ok := dailyCatIdx[r.Category] + if !ok { + idx = len(dailyCats) + dailyCats = append(dailyCats, dailyCatGroup{ + Category: r.Category, + CategoryColor: r.CategoryColor, + }) + dailyCatIdx[r.Category] = idx + } + dailyCats[idx].TotalQuantity += r.TotalQuantity + dailyCats[idx].TotalRevenue += r.Revenue + dailyCats[idx].Products = append(dailyCats[idx].Products, gin.H{ + "product_id": r.ProductID, + "name": r.ProductName, + "quantity": r.TotalQuantity, + "order_count": r.OrderCount, + "revenue": r.Revenue, + }) + } + + dailyCatsJSON := make([]gin.H, len(dailyCats)) + for i, g := range dailyCats { + dailyCatsJSON[i] = gin.H{ + "category": g.Category, + "category_color": g.CategoryColor, + "total_quantity": g.TotalQuantity, + "total_revenue": g.TotalRevenue, + "products": g.Products, + } + } + + dailyTotalOrders, _ := database.DailyOrdersCountForDate(date) + + c.JSON(http.StatusOK, gin.H{ + "date": date.Format("02/01/2006"), + "total_orders": dailyTotalOrders, + "total_quantity": dailyTotalQty, + "total_revenue": dailyTotalRevenue, + "categories": dailyCatsJSON, + }) +} + // GetAdminStats returns aggregated order & product statistics for the admin dashboard. func GetAdminStats(c *gin.Context) { database := c.MustGet("database").(*db.Database) diff --git a/backend/gestion/routes/routes.go b/backend/gestion/routes/routes.go index 9f63ab0e..447556a1 100644 --- a/backend/gestion/routes/routes.go +++ b/backend/gestion/routes/routes.go @@ -212,7 +212,7 @@ func SetupRoutes(router *gin.Engine, database *db.Database, geoService *services adminGroupV2.GET("/stats/monthly", handlers.GetAdminStatsByMonth) adminGroupV2.POST("/active/product/price/:id", handlers.ActivePrice) adminGroupV2.POST("/desactive/product/price/:id", handlers.DesActivePrice) - + adminGroupV2.GET("/stats/daily", handlers.GetAdminDailyDetail) // ============================================ // COMMANDES - GESTION DE BASE // ============================================