This commit is contained in:
@@ -115,8 +115,6 @@ func (d *Database) RevenueByDayLast30(dayRevRows *[]models.DayRevenueRow, resetA
|
|||||||
|
|
||||||
// ── Commandes par jour sur un mois calendaire complet ────────────────────────
|
// ── 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 {
|
type DailyMonthStatRow struct {
|
||||||
Day time.Time
|
Day time.Time
|
||||||
Count int
|
Count int
|
||||||
@@ -124,10 +122,6 @@ type DailyMonthStatRow struct {
|
|||||||
Quantity float64
|
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 {
|
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())
|
start := time.Date(monthStart.Year(), monthStart.Month(), 1, 0, 0, 0, 0, monthStart.Location())
|
||||||
end := start.AddDate(0, 1, 0)
|
end := start.AddDate(0, 1, 0)
|
||||||
@@ -137,7 +131,7 @@ func (d *Database) StatsByDayForMonth(rows *[]DailyMonthStatRow, monthStart time
|
|||||||
query := `
|
query := `
|
||||||
SELECT
|
SELECT
|
||||||
d.day,
|
d.day,
|
||||||
COALESCE(co.count, 0) AS count,
|
COALESCE(d.count, 0) AS count,
|
||||||
COALESCE(rv.revenue, 0) AS revenue,
|
COALESCE(rv.revenue, 0) AS revenue,
|
||||||
COALESCE(qt.quantity, 0) AS quantity
|
COALESCE(qt.quantity, 0) AS quantity
|
||||||
FROM (
|
FROM (
|
||||||
@@ -166,7 +160,7 @@ func (d *Database) StatsByDayForMonth(rows *[]DailyMonthStatRow, monthStart time
|
|||||||
ORDER BY d.day
|
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".
|
// puis (start, end) pour "rv", puis (start, end) pour "qt".
|
||||||
args := []interface{}{start, end}
|
args := []interface{}{start, end}
|
||||||
args = append(args, whereArgs...)
|
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
|
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 {
|
func (d *Database) OrdersAndRevenueByHour(hourRows *[]models.HourRow, resetAt time.Time) error {
|
||||||
where, args := statusFilterClause("status != 'cancelled'", resetAt)
|
where, args := statusFilterClause("status != 'cancelled'", resetAt)
|
||||||
query := `
|
query := `
|
||||||
@@ -269,6 +259,45 @@ func (d *Database) DailyProductDetail(dailyRows *[]models.DailyProductRow) error
|
|||||||
return d.GDB.Raw(query).Scan(dailyRows).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.
|
// DailyOrdersCount renvoie le nombre de commandes (non annulées) du jour.
|
||||||
func (d *Database) DailyOrdersCount() (int64, error) {
|
func (d *Database) DailyOrdersCount() (int64, error) {
|
||||||
var count int64
|
var count int64
|
||||||
|
|||||||
@@ -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.
|
// GetAdminStats returns aggregated order & product statistics for the admin dashboard.
|
||||||
func GetAdminStats(c *gin.Context) {
|
func GetAdminStats(c *gin.Context) {
|
||||||
database := c.MustGet("database").(*db.Database)
|
database := c.MustGet("database").(*db.Database)
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ func SetupRoutes(router *gin.Engine, database *db.Database, geoService *services
|
|||||||
adminGroupV2.GET("/stats/monthly", handlers.GetAdminStatsByMonth)
|
adminGroupV2.GET("/stats/monthly", handlers.GetAdminStatsByMonth)
|
||||||
adminGroupV2.POST("/active/product/price/:id", handlers.ActivePrice)
|
adminGroupV2.POST("/active/product/price/:id", handlers.ActivePrice)
|
||||||
adminGroupV2.POST("/desactive/product/price/:id", handlers.DesActivePrice)
|
adminGroupV2.POST("/desactive/product/price/:id", handlers.DesActivePrice)
|
||||||
|
adminGroupV2.GET("/stats/daily", handlers.GetAdminDailyDetail)
|
||||||
// ============================================
|
// ============================================
|
||||||
// COMMANDES - GESTION DE BASE
|
// COMMANDES - GESTION DE BASE
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|||||||
Reference in New Issue
Block a user