package handlers import ( "fmt" "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", "heures": "stats_reset_heures_at", "jours": "stats_reset_jours_at", "doses": "stats_reset_doses_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) if err := database.ResetAdminStat(key); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("Erreur lors de la suppresion de la section statistique: %s", err)}) return } c.JSON(http.StatusOK, gin.H{"success": true, "section": section, "reset_at": now}) } func dateFilter(t time.Time) string { if t.IsZero() { return "" } return t.Format(time.RFC3339) } // GetAdminStatsByMonth renvoie, pour chaque jour du mois demandé (paramètre // de query "month" au format YYYY-MM, mois courant par défaut), le nombre de // commandes, le revenu et la quantité vendue. Les jours sans commande sont // inclus avec des valeurs à zéro. func GetAdminStatsByMonth(c *gin.Context) { database := c.MustGet("database").(*db.Database) monthParam := c.Query("month") monthStart := time.Now() if monthParam != "" { parsed, err := time.Parse("2006-01", monthParam) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("paramètre month invalide (attendu YYYY-MM) : %s", monthParam)}) return } monthStart = parsed } monthStart = time.Date(monthStart.Year(), monthStart.Month(), 1, 0, 0, 0, 0, monthStart.Location()) resetCmd := database.ReadResetAt("stats_reset_commandes_at") var rows []db.DailyMonthStatRow if err := database.StatsByDayForMonth(&rows, monthStart, resetCmd); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Erreur lors de la récupération des statistiques mensuelles: %s", err)}) return } rowByDay := make(map[string]db.DailyMonthStatRow, len(rows)) for _, r := range rows { rowByDay[r.Day.Format("2006-01-02")] = r } daysInMonth := monthStart.AddDate(0, 1, -1).Day() byDay := make([]gin.H, daysInMonth) var totalOrders int var totalRevenue float64 var totalQuantity float64 for i := range daysInMonth { day := monthStart.AddDate(0, 0, i) key := day.Format("2006-01-02") r, ok := rowByDay[key] if !ok { r = db.DailyMonthStatRow{Day: day} } byDay[i] = gin.H{ "day": key, "label": day.Format("02/01"), "count": r.Count, "revenue": r.Revenue, "quantity": r.Quantity, } totalOrders += r.Count totalRevenue += r.Revenue totalQuantity += r.Quantity } c.JSON(http.StatusOK, gin.H{ "month": monthStart.Format("2006-01"), "summary": gin.H{ "total_orders": totalOrders, "total_revenue": totalRevenue, "total_quantity": totalQuantity, }, "by_day": byDay, }) } // GetAdminStats returns aggregated order & product statistics for the admin dashboard. func GetAdminStats(c *gin.Context) { database := c.MustGet("database").(*db.Database) filters := database.LoadAdminStatsFilters() // ── Commandes par jour de la semaine (non annulées) ──────────────────────── var wdRows []models.WeekdayRow database.OrderPerDaysPerWeeks(&wdRows, filters.ResetJours) byWeekday := make([]gin.H, 7) wdMap := make(map[int]int, len(wdRows)) for _, r := range wdRows { wdMap[r.DOW] = r.Count } peakCount, peakWeekday := 0, "" for i := range 7 { cnt := wdMap[i] byWeekday[i] = gin.H{"weekday": weekdayNames[i], "count": cnt} if cnt > peakCount { peakCount = cnt peakWeekday = weekdayNames[i] } } // ── Commandes par jour sur 30 jours ─────────────────────────────────────── var dayRows []models.DayRow database.OrdersByDayLast30(&dayRows, filters.ResetCommandes) byDay := make([]gin.H, len(dayRows)) for i, r := range dayRows { byDay[i] = gin.H{ "day": r.Day.Format("2006-01-02"), "label": r.Day.Format("02/01"), "count": r.Count, } } // ── Revenus par jour sur 30 jours (commandes approuvées) ───────────────── var dayRevRows []models.DayRevenueRow database.RevenueByDayLast30(&dayRevRows, filters.ResetRevenus) byDayRevenue := make([]gin.H, len(dayRevRows)) for i, r := range dayRevRows { byDayRevenue[i] = gin.H{ "day": r.Day.Format("2006-01-02"), "label": r.Day.Format("02/01"), "revenue": r.Revenue, } } // ── Commandes & revenus par heure (non annulées) ───────────────────────── var hourRows []models.HourRow database.OrdersAndRevenueByHour(&hourRows, filters.ResetHeures) hourMap := make(map[int]models.HourRow, len(hourRows)) for _, r := range hourRows { hourMap[r.Hour] = r } byHour := make([]gin.H, 24) for h := range 24 { r := hourMap[h] byHour[h] = gin.H{ "hour": h, "label": fmt.Sprintf("%02dh", h), "count": r.Count, "revenue": r.Revenue, } } // ── Top produits (quantité vendue, commandes terminées) ─────────────────── var prodRows []models.ProductRow database.TopProducts(&prodRows, filters.ResetProduits, 15) topProducts := make([]gin.H, len(prodRows)) topProductName := "" for i, r := range prodRows { topProducts[i] = gin.H{ "product_id": r.ProductID, "name": r.Name, "quantity": r.Quantity, "order_count": r.OrderCount, "revenue": r.Revenue, "category": r.Category, "category_color": r.CategoryColor, } if i == 0 { topProductName = r.Name } } // ── Répartition des doses/quantités par produit ─────────────────────────── var qtyRows []models.QuantityBreakdownRow database.QuantityBreakdown(&qtyRows, filters.ResetDoses) type productGroup struct { ProductID int Name string CategoryColor string TotalOrders int Quantities []gin.H } var groups []productGroup groupIdx := map[int]int{} for _, r := range qtyRows { idx, ok := groupIdx[r.ProductID] if !ok { idx = len(groups) groups = append(groups, productGroup{ ProductID: r.ProductID, Name: r.ProductName, CategoryColor: r.CategoryColor, }) groupIdx[r.ProductID] = idx } groups[idx].TotalOrders += r.OrderCount groups[idx].Quantities = append(groups[idx].Quantities, gin.H{ "quantity": r.Quantity, "order_count": r.OrderCount, "total_sold": r.TotalSold, "revenue": r.Revenue, }) } // Trier par total de commandes décroissant, garder 15 max for i := 0; i < len(groups)-1; i++ { for j := i + 1; j < len(groups); j++ { if groups[j].TotalOrders > groups[i].TotalOrders { groups[i], groups[j] = groups[j], groups[i] } } } if len(groups) > 15 { groups = groups[:15] } byQuantity := make([]gin.H, len(groups)) for i, g := range groups { byQuantity[i] = gin.H{ "product_id": g.ProductID, "name": g.Name, "category_color": g.CategoryColor, "total_orders": g.TotalOrders, "quantities": g.Quantities, } } // ── Détail du jour (catégorie → produits) ──────────────────────────────── var dailyRows []models.DailyProductRow database.DailyProductDetail(&dailyRows) 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.DailyOrdersCount() // ── Résumé global ───────────────────────────────────────────────────────── totalOrders, _ := database.TotalOrders(filters.ResetCommandes) totalRevenue, _ := database.TotalRevenue(filters.ResetRevenus) avgPerDay := 0.0 if totalOrders > 0 { activeDays, _ := database.ActiveDaysLast30(filters.ResetCommandes) if activeDays > 0 { last30Count, _ := database.OrdersCountLast30(filters.ResetCommandes) avgPerDay = float64(last30Count) / float64(activeDays) } } c.JSON(http.StatusOK, gin.H{ "summary": gin.H{ "total_orders": totalOrders, "total_revenue": totalRevenue, "peak_weekday": peakWeekday, "top_product": topProductName, "avg_per_day": avgPerDay, }, "reset_at_commandes": dateFilter(filters.ResetCommandes), "reset_at_revenus": dateFilter(filters.ResetRevenus), "reset_at_produits": dateFilter(filters.ResetProduits), "reset_at_heures": dateFilter(filters.ResetHeures), "reset_at_jours": dateFilter(filters.ResetJours), "reset_at_doses": dateFilter(filters.ResetDoses), "by_weekday": byWeekday, "by_day_30": byDay, "by_day_revenue": byDayRevenue, "by_hour": byHour, "top_products": topProducts, "by_quantity": byQuantity, "daily_detail": gin.H{ "date": time.Now().Format("02/01/2006"), "total_orders": dailyTotalOrders, "total_quantity": dailyTotalQty, "total_revenue": dailyTotalRevenue, "categories": dailyCatsJSON, }, }) }