644 lines
19 KiB
Go
644 lines
19 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"gestion/db"
|
|
"gestion/services"
|
|
"gestion/utils"
|
|
"log"
|
|
"net/http"
|
|
"slices"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
func GetMyDeliveries(c *gin.Context) {
|
|
database := c.MustGet("database").(*db.Database)
|
|
|
|
username, exists := c.Get("username")
|
|
if !exists {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Non authentifié"})
|
|
return
|
|
}
|
|
|
|
if c.GetString("role") != "livreur" {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "Accès réservé aux livreurs"})
|
|
return
|
|
}
|
|
|
|
usernameStr := username.(string)
|
|
status := c.Query("status")
|
|
|
|
commands, err := database.GetDeliveryPersonCommands(usernameStr, status)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"error": "Erreur récupération",
|
|
})
|
|
return
|
|
}
|
|
|
|
filteredCommands := make([]gin.H, len(commands))
|
|
for i, cmd := range commands {
|
|
commandID, _ := strconv.Atoi(fmt.Sprintf("%v", cmd["id"]))
|
|
items, _ := database.GetCommandItems(commandID)
|
|
|
|
// Client info SANS téléphone
|
|
clientUsername, _ := cmd["username"].(string)
|
|
client, _ := database.GetClientByUsername(clientUsername)
|
|
|
|
clientInfo := gin.H{"nom": "Client", "prenom": ""}
|
|
if client != nil {
|
|
clientInfo = gin.H{
|
|
"nom": client.Nom,
|
|
"prenom": client.Prenom,
|
|
}
|
|
}
|
|
|
|
itemsSummary := make([]gin.H, len(items))
|
|
for j, item := range items {
|
|
itemsSummary[j] = gin.H{
|
|
"produit": item["produit"],
|
|
"quantite": item["quantite"],
|
|
"prix": item["prix"],
|
|
"is_reward": item["is_reward"],
|
|
}
|
|
}
|
|
|
|
etaData, _ := database.GetCommandETA(commandID)
|
|
|
|
filteredCommands[i] = gin.H{
|
|
"id": cmd["id"],
|
|
"status": cmd["status"],
|
|
"adresse": cmd["adresse"],
|
|
"total_prix": cmd["total_prix"],
|
|
"referral_used": cmd["referral_used"],
|
|
"created_at": cmd["created_at"],
|
|
"client_info": clientInfo,
|
|
"items": itemsSummary,
|
|
"items_count": len(items),
|
|
"eta": etaData,
|
|
}
|
|
}
|
|
|
|
log.Printf("✅ [MY_DELIVERIES] %d livraisons (données filtrées)", len(filteredCommands))
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"deliveries": filteredCommands,
|
|
"count": len(filteredCommands),
|
|
})
|
|
}
|
|
|
|
// ============================================
|
|
// GetDeliveryDetails
|
|
// ============================================
|
|
func GetDeliveryDetails(c *gin.Context) {
|
|
database := c.MustGet("database").(*db.Database)
|
|
|
|
username := c.GetString("username")
|
|
if c.GetString("role") != "livreur" {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "Accès réservé aux livreurs"})
|
|
return
|
|
}
|
|
|
|
commandID, err := strconv.Atoi(c.Param("id"))
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "ID invalide"})
|
|
return
|
|
}
|
|
|
|
command, err := database.GetCommandByID(commandID)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Commande non trouvée"})
|
|
return
|
|
}
|
|
|
|
// ✅ VÉRIFIER PROPRIÉTÉ
|
|
livreurAssign, _ := command["livreur_assign"].(string)
|
|
if livreurAssign != username {
|
|
log.Printf("❌ Accès refusé - cmd assignée à %s", livreurAssign)
|
|
c.JSON(http.StatusForbidden, gin.H{
|
|
"error": "Cette livraison ne vous est pas assignée",
|
|
})
|
|
return
|
|
}
|
|
|
|
items, _ := database.GetCommandItems(commandID)
|
|
|
|
clientUsername, _ := command["username"].(string)
|
|
client, _ := database.GetClientByUsername(clientUsername)
|
|
|
|
clientInfo := gin.H{"nom": "Client", "prenom": ""}
|
|
if client != nil {
|
|
clientInfo = gin.H{
|
|
"nom": client.Nom,
|
|
"prenom": client.Prenom,
|
|
}
|
|
}
|
|
|
|
itemsSummary := make([]gin.H, len(items))
|
|
for i, item := range items {
|
|
itemsSummary[i] = gin.H{
|
|
"produit": item["produit"],
|
|
"quantite": item["quantite"],
|
|
"prix": item["prix"],
|
|
}
|
|
}
|
|
|
|
etaData, _ := database.GetCommandETA(commandID)
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"delivery": gin.H{
|
|
"id": command["id"],
|
|
"status": command["status"],
|
|
"adresse": command["adresse"],
|
|
"total_prix": command["total_prix"],
|
|
"referral_used": command["referral_used"],
|
|
"created_at": command["created_at"],
|
|
"client_info": clientInfo,
|
|
"items": itemsSummary,
|
|
"items_count": len(items),
|
|
"eta": etaData,
|
|
},
|
|
})
|
|
}
|
|
|
|
func UpdateDeliveryStatus(c *gin.Context) {
|
|
database := c.MustGet("database").(*db.Database)
|
|
|
|
username, exists := c.Get("username")
|
|
if !exists || c.GetString("role") != "livreur" {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "Accès réservé aux livreurs"})
|
|
return
|
|
}
|
|
|
|
usernameStr := username.(string)
|
|
commandID, err := strconv.Atoi(c.Param("id"))
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "ID invalide"})
|
|
return
|
|
}
|
|
|
|
var req struct {
|
|
Status string `json:"status" binding:"required"`
|
|
Notes string `json:"notes"`
|
|
Latitude float64 `json:"latitude"`
|
|
Longitude float64 `json:"longitude"`
|
|
}
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "Données invalides",
|
|
})
|
|
return
|
|
}
|
|
|
|
log.Printf("📝 [UPD_STATUS] %s update cmd %d: %s", usernameStr, commandID, req.Status)
|
|
|
|
command, err := database.GetCommandByID(commandID)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Commande non trouvée"})
|
|
return
|
|
}
|
|
|
|
livreurAssign, _ := command["livreur_assign"].(string)
|
|
if livreurAssign != usernameStr {
|
|
log.Printf("❌ Accès refusé - assigné à %s", livreurAssign)
|
|
c.JSON(http.StatusForbidden, gin.H{
|
|
"error": "Cette commande ne vous est pas assignée",
|
|
})
|
|
return
|
|
}
|
|
|
|
validStatuses := []string{
|
|
"assigned",
|
|
"en_route",
|
|
"arrived",
|
|
"livre",
|
|
"cancelled",
|
|
}
|
|
|
|
if !slices.Contains(validStatuses, req.Status) {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "Statut invalide",
|
|
"valid_statuses": validStatuses,
|
|
"received": req.Status,
|
|
})
|
|
return
|
|
}
|
|
|
|
if req.Status == "livre" {
|
|
if req.Latitude == 0 || req.Longitude == 0 {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Coordonnées GPS requises pour confirmer la livraison"})
|
|
return
|
|
}
|
|
|
|
destLat, _ := command["dest_latitude"].(float64)
|
|
destLon, _ := command["dest_longitude"].(float64)
|
|
|
|
if destLat != 0 && destLon != 0 {
|
|
distance := utils.CalculateDistance(req.Latitude, req.Longitude, destLat, destLon)
|
|
log.Printf("📍 [GPS] Distance: %.2f m", distance)
|
|
|
|
if distance > 100 {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "Vous êtes trop loin de la destination",
|
|
"current_distance": fmt.Sprintf("%.2f", distance),
|
|
"unit": "meters",
|
|
})
|
|
return
|
|
}
|
|
|
|
log.Printf("✅ [GPS] Validation OK")
|
|
} else {
|
|
log.Printf("⚠️ [GPS] Coordonnées de destination non disponibles, validation ignorée")
|
|
}
|
|
}
|
|
|
|
// Mettre à jour le statut
|
|
if err := database.UpdateCommandStatus(commandID, req.Status); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"error": "Erreur mise à jour",
|
|
})
|
|
return
|
|
}
|
|
|
|
if req.Status == "cancelled" {
|
|
cancelMsg := req.Notes
|
|
if cancelMsg == "" {
|
|
cancelMsg = "Annulé par le livreur"
|
|
}
|
|
database.SetCommandCancelReason(commandID, fmt.Sprintf("[Livreur: %s] %s", usernameStr, cancelMsg))
|
|
|
|
prevStatus, _ := command["status"].(string)
|
|
if prevStatus == "arrived" || prevStatus == "livre" {
|
|
clientUsername, _ := command["username"].(string)
|
|
if clientUsername != "" {
|
|
if penalty, err := database.ApplyCancellationPenalty(clientUsername); err == nil {
|
|
log.Printf("⚠️ [CANCEL_LIVREUR] Amende %d appliquée à %s (client absent)", penalty, clientUsername)
|
|
} else {
|
|
log.Printf("⚠️ [CANCEL_LIVREUR] Erreur application amende pour %s: %v", clientUsername, err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ✅ SI PASSAGE EN "EN_ROUTE" → CALCULER ET DÉFINIR L'ETA
|
|
var etaMinutes int
|
|
var etaMessage string
|
|
|
|
if req.Status == "en_route" {
|
|
log.Printf("🚗 [STATUS_LIVREUR] Passage en 'en_route' - Calcul ETA...")
|
|
|
|
var destLat, destLon float64
|
|
|
|
destCacheKey := fmt.Sprintf("command:destination:%d", commandID)
|
|
destData, err := db.Redis.Get(db.RedisCtx, destCacheKey).Result()
|
|
if err == nil && destData != "" {
|
|
var coords struct {
|
|
Lat float64 `json:"lat"`
|
|
Lon float64 `json:"lon"`
|
|
}
|
|
if err := json.Unmarshal([]byte(destData), &coords); err == nil && coords.Lat != 0 && coords.Lon != 0 {
|
|
destLat = coords.Lat
|
|
destLon = coords.Lon
|
|
log.Printf("📍 [STATUS_LIVREUR] Coords depuis cache Redis: (%.6f, %.6f)", destLat, destLon)
|
|
}
|
|
}
|
|
|
|
// 2. Fallback: récupérer depuis la DB
|
|
if destLat == 0 || destLon == 0 {
|
|
if dLat, ok := command["dest_latitude"].(float64); ok && dLat != 0 {
|
|
destLat = dLat
|
|
}
|
|
if dLon, ok := command["dest_longitude"].(float64); ok && dLon != 0 {
|
|
destLon = dLon
|
|
}
|
|
if destLat != 0 && destLon != 0 {
|
|
log.Printf("📍 [STATUS_LIVREUR] Coords depuis DB: (%.6f, %.6f)", destLat, destLon)
|
|
}
|
|
}
|
|
|
|
if destLat != 0 && destLon != 0 {
|
|
toCoords := services.Coordinates{Latitude: destLat, Longitude: destLon}
|
|
|
|
// Cas 1 : GPS du livreur disponible
|
|
gpsLat, gpsLon, gpsErr := database.GetDeliveryPersonLocation(usernameStr)
|
|
if gpsErr == nil && gpsLat != 0 {
|
|
from := services.Coordinates{Latitude: gpsLat, Longitude: gpsLon}
|
|
eta, _, err := services.GetETAWithTraffic(from, toCoords)
|
|
if err != nil {
|
|
eta = services.CalculateETA(services.CalculateDistance(from, toCoords))
|
|
}
|
|
etaMinutes = eta
|
|
log.Printf("📍 [STATUS_LIVREUR] ETA depuis GPS livreur: %d min", etaMinutes)
|
|
} else {
|
|
// Cas 2 : GPS absent → dernière adresse de livraison
|
|
lastLat, lastLon, lastErr := database.GetLastDeliveryCoords(usernameStr)
|
|
if lastErr == nil && lastLat != 0 {
|
|
from := services.Coordinates{Latitude: lastLat, Longitude: lastLon}
|
|
eta, _, err := services.GetETAWithTraffic(from, toCoords)
|
|
if err != nil {
|
|
eta = services.CalculateETA(services.CalculateDistance(from, toCoords))
|
|
}
|
|
etaMinutes = eta
|
|
log.Printf("📍 [STATUS_LIVREUR] ETA depuis dernière livraison: %d min", etaMinutes)
|
|
} else {
|
|
// Cas 3 : Aucune position disponible
|
|
etaMinutes = 30
|
|
log.Printf("⚠️ [STATUS_LIVREUR] Aucune position disponible - ETA par défaut: %d min", etaMinutes)
|
|
}
|
|
}
|
|
} else {
|
|
etaMinutes = 30
|
|
log.Printf("⚠️ [STATUS_LIVREUR] Coordonnées destination manquantes - ETA par défaut: %d min", etaMinutes)
|
|
}
|
|
|
|
database.SetCommandETA(commandID, etaMinutes)
|
|
log.Printf("✅ [STATUS_LIVREUR] ETA défini: %d minutes", etaMinutes)
|
|
|
|
if etaMinutes >= 60 {
|
|
h := etaMinutes / 60
|
|
m := etaMinutes % 60
|
|
if m > 0 {
|
|
etaMessage = fmt.Sprintf("Arrivée prévue dans %dh%02d", h, m)
|
|
} else {
|
|
etaMessage = fmt.Sprintf("Arrivée prévue dans %dh", h)
|
|
}
|
|
} else {
|
|
etaMessage = fmt.Sprintf("Arrivée prévue dans %d minutes", etaMinutes)
|
|
}
|
|
|
|
// Mettre à jour le statut du livreur en "delivering"
|
|
database.SetDeliveryPersonStatus(usernameStr, "delivering", commandID)
|
|
log.Printf("🚗 [STATUS_LIVREUR] Statut livreur mis à jour: delivering")
|
|
}
|
|
|
|
// Log
|
|
message := req.Notes
|
|
if message == "" {
|
|
message = utils.GetDeliveryStatusMessage(req.Status)
|
|
}
|
|
if etaMessage != "" {
|
|
message += fmt.Sprintf(" - %s", etaMessage)
|
|
}
|
|
database.AddCommandLog(commandID, req.Status, message, usernameStr)
|
|
|
|
// ✅ NOTIFICATION CLIENT
|
|
clientUsername, _ := command["username"].(string)
|
|
if clientUsername != "" {
|
|
var clientMsg string
|
|
switch req.Status {
|
|
case "en_route":
|
|
notifETA := etaMinutes
|
|
if notifETA == 0 {
|
|
if etaData, err := database.GetCommandETA(commandID); err == nil {
|
|
if v, ok := etaData["total_eta_minutes"]; ok {
|
|
if n, err2 := strconv.Atoi(v); err2 == nil && n > 0 {
|
|
notifETA = n
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if notifETA > 0 {
|
|
var etaStr string
|
|
if notifETA >= 60 {
|
|
h := notifETA / 60
|
|
m := notifETA % 60
|
|
if m > 0 {
|
|
etaStr = fmt.Sprintf("%dh%02d", h, m)
|
|
} else {
|
|
etaStr = fmt.Sprintf("%dh", h)
|
|
}
|
|
} else {
|
|
etaStr = fmt.Sprintf("%d min", notifETA)
|
|
}
|
|
clientMsg = fmt.Sprintf("Un livreur vient de prendre en charge ta commande #%d, il est en route (~%s)", database.GetClientOrderID(commandID), etaStr)
|
|
} else {
|
|
clientMsg = fmt.Sprintf("Un livreur vient de prendre en charge ta commande #%d, il est en route.", database.GetClientOrderID(commandID))
|
|
}
|
|
case "arrived":
|
|
clientMsg = fmt.Sprintf("Descend, le livreur est là dans 3min (commande #%d) 🛵", database.GetClientOrderID(commandID))
|
|
case "livre":
|
|
clientMsg = fmt.Sprintf("Ta commande #%d a bien été livrée ! Bonne dégustation l'ami et à bientôt 😊\n\n<b>⚠️ VALIDE LA RÉCEPTION DE TA COMMANDE DANS LA RUBRIQUE SUIVI POUR RÉCUPÉRER TES POINTS DE FIDÉLITÉ ⚠️</b>", database.GetClientOrderID(commandID))
|
|
case "cancelled":
|
|
clientMsg = fmt.Sprintf("Votre commande #%d a été annulée par le livreur", database.GetClientOrderID(commandID))
|
|
}
|
|
if clientMsg != "" {
|
|
database.NotifyClient(clientUsername, commandID, req.Status, clientMsg)
|
|
}
|
|
}
|
|
|
|
// ✅ GESTION SPÉCIALE SELON LE STATUT
|
|
switch req.Status {
|
|
case "livre":
|
|
// Livraison terminée - Optimiser la queue
|
|
log.Printf("📦 Livraison marquée 'livre' - Optimisation queue...")
|
|
database.CompleteDeliveryAndProcessNext(usernameStr, commandID)
|
|
|
|
case "cancelled":
|
|
log.Printf("🚫 Livraison annulée par livreur - Nettoyage queue cmd %d", commandID)
|
|
if err := database.RestoreCommandStock(commandID); err != nil {
|
|
log.Printf("⚠️ [STATUS_LIVREUR] Erreur restauration stock cmd %d: %v", commandID, err)
|
|
} else {
|
|
log.Printf("✅ [STATUS_LIVREUR] Stock restauré pour cmd %d", commandID)
|
|
}
|
|
database.CompleteDeliveryAndProcessNext(usernameStr, commandID)
|
|
|
|
case "arrived":
|
|
log.Printf("📍 Livreur arrivé à destination - Commande %d", commandID)
|
|
}
|
|
|
|
response := gin.H{
|
|
"success": true,
|
|
"message": "Statut mis à jour",
|
|
"command_id": commandID,
|
|
"status": req.Status,
|
|
}
|
|
|
|
if req.Status == "en_route" && etaMinutes > 0 {
|
|
response["eta_minutes"] = etaMinutes
|
|
response["eta_message"] = etaMessage
|
|
}
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
// POST /api/v1/livreur/deliveries/:id/issue
|
|
func ReportDeliveryIssue(c *gin.Context) {
|
|
username := c.GetString("username")
|
|
if c.GetString("role") != "livreur" {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "Accès refusé"})
|
|
return
|
|
}
|
|
|
|
commandID, err := strconv.Atoi(c.Param("id"))
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "ID invalide"})
|
|
return
|
|
}
|
|
|
|
var req struct {
|
|
IssueType string `json:"issue_type" binding:"required"`
|
|
Description string `json:"description"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "issue_type requis"})
|
|
return
|
|
}
|
|
|
|
validTypes := map[string]bool{
|
|
"client_absent": true,
|
|
"wrong_address": true,
|
|
"refused_delivery": true,
|
|
"no_access": true,
|
|
"other": true,
|
|
}
|
|
if !validTypes[req.IssueType] {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Type de problème invalide"})
|
|
return
|
|
}
|
|
|
|
database := c.MustGet("database").(*db.Database)
|
|
|
|
command, err := database.GetCommandByID(commandID)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Commande introuvable"})
|
|
return
|
|
}
|
|
if livreur, _ := command["livreur_assign"].(string); livreur != username {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "Commande non assignée à vous"})
|
|
return
|
|
}
|
|
|
|
issue, err := database.CreateDeliveryIssue(commandID, req.IssueType, req.Description, username)
|
|
if err != nil {
|
|
log.Printf("❌ [ISSUE] Erreur création: %v", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur création problème"})
|
|
return
|
|
}
|
|
|
|
log.Printf("📋 [ISSUE] Créé par %s pour commande #%d: %s", username, commandID, req.IssueType)
|
|
c.JSON(http.StatusCreated, gin.H{"success": true, "issue": issue})
|
|
}
|
|
|
|
// GET /api/v1/livreur/stats
|
|
func GetMyDeliveryStats(c *gin.Context) {
|
|
database := c.MustGet("database").(*db.Database)
|
|
|
|
username, exists := c.Get("username")
|
|
if !exists {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Non authentifié"})
|
|
return
|
|
}
|
|
if c.GetString("role") != "livreur" {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "Accès réservé aux livreurs"})
|
|
return
|
|
}
|
|
usernameStr := username.(string)
|
|
gdb := database.GDB
|
|
|
|
type DayRow struct {
|
|
Day time.Time `gorm:"column:day"`
|
|
Count int `gorm:"column:count"`
|
|
Revenue float64 `gorm:"column:revenue"`
|
|
}
|
|
type WeekRow struct {
|
|
WeekNum int `gorm:"column:week_num"`
|
|
Year int `gorm:"column:year"`
|
|
Count int `gorm:"column:count"`
|
|
Revenue float64 `gorm:"column:revenue"`
|
|
}
|
|
type MonthRow struct {
|
|
MonthNum int `gorm:"column:month_num"`
|
|
Year int `gorm:"column:year"`
|
|
Count int `gorm:"column:count"`
|
|
Revenue float64 `gorm:"column:revenue"`
|
|
}
|
|
|
|
var dayRows []DayRow
|
|
gdb.Raw(`
|
|
SELECT DATE(updated_at) AS day,
|
|
COUNT(*) AS count,
|
|
COALESCE(SUM(total_prix - COALESCE(referral_used, 0)), 0) AS revenue
|
|
FROM commandes
|
|
WHERE livreur_assign = ?
|
|
AND status IN ('livre', 'approved')
|
|
AND updated_at >= NOW() - INTERVAL '30 days'
|
|
GROUP BY DATE(updated_at)
|
|
ORDER BY day
|
|
`, usernameStr).Scan(&dayRows)
|
|
|
|
var weekRows []WeekRow
|
|
gdb.Raw(`
|
|
SELECT EXTRACT(WEEK FROM updated_at)::int AS week_num,
|
|
EXTRACT(YEAR FROM updated_at)::int AS year,
|
|
COUNT(*) AS count,
|
|
COALESCE(SUM(total_prix - COALESCE(referral_used, 0)), 0) AS revenue
|
|
FROM commandes
|
|
WHERE livreur_assign = ?
|
|
AND status IN ('livre', 'approved')
|
|
AND updated_at >= NOW() - INTERVAL '12 weeks'
|
|
GROUP BY week_num, year
|
|
ORDER BY year, week_num
|
|
`, usernameStr).Scan(&weekRows)
|
|
|
|
var monthRows []MonthRow
|
|
gdb.Raw(`
|
|
SELECT EXTRACT(MONTH FROM updated_at)::int AS month_num,
|
|
EXTRACT(YEAR FROM updated_at)::int AS year,
|
|
COUNT(*) AS count,
|
|
COALESCE(SUM(total_prix - COALESCE(referral_used, 0)), 0) AS revenue
|
|
FROM commandes
|
|
WHERE livreur_assign = ?
|
|
AND status IN ('livre', 'approved')
|
|
AND updated_at >= NOW() - INTERVAL '12 months'
|
|
GROUP BY month_num, year
|
|
ORDER BY year, month_num
|
|
`, usernameStr).Scan(&monthRows)
|
|
|
|
monthNames := [13]string{"", "Jan", "Fév", "Mar", "Avr", "Mai", "Jun", "Jul", "Aoû", "Sep", "Oct", "Nov", "Déc"}
|
|
|
|
byDay := make([]gin.H, len(dayRows))
|
|
for i, r := range dayRows {
|
|
byDay[i] = gin.H{
|
|
"label": r.Day.Format("02/01"),
|
|
"count": r.Count,
|
|
"revenue": r.Revenue,
|
|
}
|
|
}
|
|
|
|
byWeek := make([]gin.H, len(weekRows))
|
|
for i, r := range weekRows {
|
|
byWeek[i] = gin.H{
|
|
"label": fmt.Sprintf("S%d", r.WeekNum),
|
|
"count": r.Count,
|
|
"revenue": r.Revenue,
|
|
}
|
|
}
|
|
|
|
byMonth := make([]gin.H, len(monthRows))
|
|
for i, r := range monthRows {
|
|
label := "?"
|
|
if r.MonthNum >= 1 && r.MonthNum <= 12 {
|
|
label = monthNames[r.MonthNum]
|
|
}
|
|
byMonth[i] = gin.H{
|
|
"label": label,
|
|
"count": r.Count,
|
|
"revenue": r.Revenue,
|
|
}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"by_day": byDay,
|
|
"by_week": byWeek,
|
|
"by_month": byMonth,
|
|
})
|
|
}
|