|
|
|
@@ -1,6 +1,7 @@
|
|
|
|
|
package handlers
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"fmt"
|
|
|
|
|
"gestion/db"
|
|
|
|
|
"gestion/models"
|
|
|
|
@@ -8,6 +9,7 @@ import (
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var weekdayNames = []string{"Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"}
|
|
|
|
@@ -200,10 +202,62 @@ func GetAdminStats(c *gin.Context) {
|
|
|
|
|
|
|
|
|
|
filters := database.LoadAdminStatsFilters()
|
|
|
|
|
|
|
|
|
|
// ── Commandes par jour de la semaine (non annulées) ────────────────────────
|
|
|
|
|
var wdRows []models.WeekdayRow
|
|
|
|
|
database.OrderPerDaysPerWeeks(&wdRows, filters.ResetJours)
|
|
|
|
|
// Toutes les requêtes sont indépendantes — on les lance en parallèle.
|
|
|
|
|
var (
|
|
|
|
|
wdRows []models.WeekdayRow
|
|
|
|
|
dayRows []models.DayRow
|
|
|
|
|
dayRevRows []models.DayRevenueRow
|
|
|
|
|
hourRows []models.HourRow
|
|
|
|
|
prodRows []models.ProductRow
|
|
|
|
|
qtyRows []models.QuantityBreakdownRow
|
|
|
|
|
dailyRows []models.DailyProductRow
|
|
|
|
|
totalOrders int64
|
|
|
|
|
totalRevenue float64
|
|
|
|
|
dailyTotalOrders int64
|
|
|
|
|
activeDays int64
|
|
|
|
|
last30Count int64
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
eg, _ := errgroup.WithContext(context.Background())
|
|
|
|
|
eg.Go(func() error { return database.OrderPerDaysPerWeeks(&wdRows, filters.ResetJours) })
|
|
|
|
|
eg.Go(func() error { return database.OrdersByDayLast30(&dayRows, filters.ResetCommandes) })
|
|
|
|
|
eg.Go(func() error { return database.RevenueByDayLast30(&dayRevRows, filters.ResetRevenus) })
|
|
|
|
|
eg.Go(func() error { return database.OrdersAndRevenueByHour(&hourRows, filters.ResetHeures) })
|
|
|
|
|
eg.Go(func() error { return database.TopProducts(&prodRows, filters.ResetProduits, 15) })
|
|
|
|
|
eg.Go(func() error { return database.QuantityBreakdown(&qtyRows, filters.ResetDoses) })
|
|
|
|
|
eg.Go(func() error { return database.DailyProductDetail(&dailyRows) })
|
|
|
|
|
eg.Go(func() error {
|
|
|
|
|
var err error
|
|
|
|
|
totalOrders, err = database.TotalOrders(filters.ResetCommandes)
|
|
|
|
|
return err
|
|
|
|
|
})
|
|
|
|
|
eg.Go(func() error {
|
|
|
|
|
var err error
|
|
|
|
|
totalRevenue, err = database.TotalRevenue(filters.ResetRevenus)
|
|
|
|
|
return err
|
|
|
|
|
})
|
|
|
|
|
eg.Go(func() error {
|
|
|
|
|
var err error
|
|
|
|
|
dailyTotalOrders, err = database.DailyOrdersCount()
|
|
|
|
|
return err
|
|
|
|
|
})
|
|
|
|
|
eg.Go(func() error {
|
|
|
|
|
var err error
|
|
|
|
|
activeDays, err = database.ActiveDaysLast30(filters.ResetCommandes)
|
|
|
|
|
return err
|
|
|
|
|
})
|
|
|
|
|
eg.Go(func() error {
|
|
|
|
|
var err error
|
|
|
|
|
last30Count, err = database.OrdersCountLast30(filters.ResetCommandes)
|
|
|
|
|
return err
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if err := eg.Wait(); err != nil {
|
|
|
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur lors de la récupération des statistiques"})
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Commandes par jour de la semaine ──────────────────────────────────────
|
|
|
|
|
byWeekday := make([]gin.H, 7)
|
|
|
|
|
wdMap := make(map[int]int, len(wdRows))
|
|
|
|
|
for _, r := range wdRows {
|
|
|
|
@@ -220,9 +274,6 @@ func GetAdminStats(c *gin.Context) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── 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{
|
|
|
|
@@ -232,10 +283,7 @@ func GetAdminStats(c *gin.Context) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Revenus par jour sur 30 jours (commandes approuvées) ─────────────────
|
|
|
|
|
var dayRevRows []models.DayRevenueRow
|
|
|
|
|
database.RevenueByDayLast30(&dayRevRows, filters.ResetRevenus)
|
|
|
|
|
|
|
|
|
|
// ── Revenus par jour sur 30 jours ─────────────────────────────────────────
|
|
|
|
|
byDayRevenue := make([]gin.H, len(dayRevRows))
|
|
|
|
|
for i, r := range dayRevRows {
|
|
|
|
|
byDayRevenue[i] = gin.H{
|
|
|
|
@@ -245,10 +293,7 @@ func GetAdminStats(c *gin.Context) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Commandes & revenus par heure (non annulées) ─────────────────────────
|
|
|
|
|
var hourRows []models.HourRow
|
|
|
|
|
database.OrdersAndRevenueByHour(&hourRows, filters.ResetHeures)
|
|
|
|
|
|
|
|
|
|
// ── Commandes & revenus par heure ─────────────────────────────────────────
|
|
|
|
|
hourMap := make(map[int]models.HourRow, len(hourRows))
|
|
|
|
|
for _, r := range hourRows {
|
|
|
|
|
hourMap[r.Hour] = r
|
|
|
|
@@ -264,10 +309,7 @@ func GetAdminStats(c *gin.Context) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Top produits (quantité vendue, commandes terminées) ───────────────────
|
|
|
|
|
var prodRows []models.ProductRow
|
|
|
|
|
database.TopProducts(&prodRows, filters.ResetProduits, 15)
|
|
|
|
|
|
|
|
|
|
// ── Top produits ──────────────────────────────────────────────────────────
|
|
|
|
|
topProducts := make([]gin.H, len(prodRows))
|
|
|
|
|
topProductName := ""
|
|
|
|
|
for i, r := range prodRows {
|
|
|
|
@@ -285,10 +327,7 @@ func GetAdminStats(c *gin.Context) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Répartition des doses/quantités par produit ───────────────────────────
|
|
|
|
|
var qtyRows []models.QuantityBreakdownRow
|
|
|
|
|
database.QuantityBreakdown(&qtyRows, filters.ResetDoses)
|
|
|
|
|
|
|
|
|
|
// ── Répartition des doses/quantités ───────────────────────────────────────
|
|
|
|
|
type productGroup struct {
|
|
|
|
|
ProductID int
|
|
|
|
|
Name string
|
|
|
|
@@ -317,8 +356,6 @@ func GetAdminStats(c *gin.Context) {
|
|
|
|
|
"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 {
|
|
|
|
@@ -329,22 +366,18 @@ func GetAdminStats(c *gin.Context) {
|
|
|
|
|
if len(groups) > 15 {
|
|
|
|
|
groups = groups[:15]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
byQuantity := make([]gin.H, len(groups))
|
|
|
|
|
for i, g := range groups {
|
|
|
|
|
for i, grp := range groups {
|
|
|
|
|
byQuantity[i] = gin.H{
|
|
|
|
|
"product_id": g.ProductID,
|
|
|
|
|
"name": g.Name,
|
|
|
|
|
"category_color": g.CategoryColor,
|
|
|
|
|
"total_orders": g.TotalOrders,
|
|
|
|
|
"quantities": g.Quantities,
|
|
|
|
|
"product_id": grp.ProductID,
|
|
|
|
|
"name": grp.Name,
|
|
|
|
|
"category_color": grp.CategoryColor,
|
|
|
|
|
"total_orders": grp.TotalOrders,
|
|
|
|
|
"quantities": grp.Quantities,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Détail du jour (catégorie → produits) ────────────────────────────────
|
|
|
|
|
var dailyRows []models.DailyProductRow
|
|
|
|
|
database.DailyProductDetail(&dailyRows)
|
|
|
|
|
|
|
|
|
|
// ── Détail du jour ────────────────────────────────────────────────────────
|
|
|
|
|
type dailyCatGroup struct {
|
|
|
|
|
Category string
|
|
|
|
|
CategoryColor string
|
|
|
|
@@ -356,7 +389,6 @@ func GetAdminStats(c *gin.Context) {
|
|
|
|
|
dailyCatIdx := map[string]int{}
|
|
|
|
|
dailyTotalRevenue := 0.0
|
|
|
|
|
dailyTotalQty := 0.0
|
|
|
|
|
|
|
|
|
|
for _, r := range dailyRows {
|
|
|
|
|
dailyTotalRevenue += r.Revenue
|
|
|
|
|
dailyTotalQty += r.TotalQuantity
|
|
|
|
@@ -379,32 +411,22 @@ func GetAdminStats(c *gin.Context) {
|
|
|
|
|
"revenue": r.Revenue,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dailyCatsJSON := make([]gin.H, len(dailyCats))
|
|
|
|
|
for i, g := range dailyCats {
|
|
|
|
|
for i, grp := range dailyCats {
|
|
|
|
|
dailyCatsJSON[i] = gin.H{
|
|
|
|
|
"category": g.Category,
|
|
|
|
|
"category_color": g.CategoryColor,
|
|
|
|
|
"total_quantity": g.TotalQuantity,
|
|
|
|
|
"total_revenue": g.TotalRevenue,
|
|
|
|
|
"products": g.Products,
|
|
|
|
|
"category": grp.Category,
|
|
|
|
|
"category_color": grp.CategoryColor,
|
|
|
|
|
"total_quantity": grp.TotalQuantity,
|
|
|
|
|
"total_revenue": grp.TotalRevenue,
|
|
|
|
|
"products": grp.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)
|
|
|
|
|
if totalOrders > 0 && activeDays > 0 {
|
|
|
|
|
avgPerDay = float64(last30Count) / float64(activeDays)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
|
|
|
"summary": gin.H{
|
|
|
|
|