// ============================================ // db/delivery_management_db.go // FONCTIONS DB POUR LA GESTION COMPLÈTE DES LIVREURS // ============================================ package db import ( "database/sql" "fmt" "log" "strings" "time" ) var allowedStatuses = map[string]bool{ "available": true, "offline": true, "busy": true, } // ============================================ // 📊 STATISTIQUES LIVREUR // ============================================ // CountDeliveriesByStatus compte les livraisons d'un livreur par statut func (d *Database) CountDeliveriesByStatus(livreurUsername string, statuses string) (int, error) { if statuses == "" { // Cas simple: toutes les livraisons query := `SELECT COUNT(*) FROM commandes WHERE livreur_assign = $1` var count int err := d.QueryRow(query, livreurUsername).Scan(&count) if err != nil { log.Printf("❌ [CountDeliveries] Erreur: %v", err) return 0, fmt.Errorf("erreur comptage livraisons: %w", err) } return count, nil } // ✅ Valider les statuts cleanStatuses, err := ValidateStatuses(statuses) if err != nil { log.Printf("❌ [CountDeliveries] Validation échouée: %v", err) return 0, err } // ✅ Construire la requête avec IN et placeholders placeholders := make([]string, len(cleanStatuses)) args := []interface{}{livreurUsername} for i, status := range cleanStatuses { placeholders[i] = fmt.Sprintf("$%d", i+2) args = append(args, status) } query := fmt.Sprintf(`SELECT COUNT(*) FROM commandes WHERE livreur_assign = $1 AND status IN (%s)`, strings.Join(placeholders, ",")) var count int err = d.QueryRow(query, args...).Scan(&count) if err != nil { log.Printf("❌ [CountDeliveries] Erreur: %v", err) return 0, fmt.Errorf("erreur comptage livraisons: %w", err) } log.Printf("✅ [CountDeliveries] %d livraisons pour %s avec statuts %v", count, livreurUsername, cleanStatuses) return count, nil } func ValidateStatuses(statuses string) ([]string, error) { if statuses == "" { return nil, nil } statusList := strings.Split(statuses, ",") // Liste blanche complète validStatusMap := map[string]bool{ "pending": true, "assigned": true, "en_route": true, "arrived": true, "livre": true, "approved": true, "failed": true, "cancelled": true, } var cleanStatuses []string var invalidStatuses []string for _, status := range statusList { status = strings.TrimSpace(status) // Ignorer les chaînes vides if status == "" { continue } if validStatusMap[status] { cleanStatuses = append(cleanStatuses, status) } else { invalidStatuses = append(invalidStatuses, status) } } // Logger les statuts invalides if len(invalidStatuses) > 0 { log.Printf("⚠️ [ValidateStatuses] Statuts invalides ignorés: %v", invalidStatuses) } if len(cleanStatuses) == 0 { return nil, fmt.Errorf("aucun statut valide trouvé dans: %s", statuses) } return cleanStatuses, nil } // GetLastDeliveryDate récupère la date de la dernière livraison d'un livreur func (d *Database) GetLastDeliveryDate(livreurUsername string) (*time.Time, error) { query := `SELECT MAX(updated_at) FROM commandes WHERE livreur_assign = $1 AND status = 'approved'` var lastDate sql.NullTime err := d.QueryRow(query, livreurUsername).Scan(&lastDate) if err != nil { return nil, fmt.Errorf("erreur récupération dernière livraison: %w", err) } if !lastDate.Valid { return nil, nil // Aucune livraison } t := lastDate.Time return &t, nil } // GetCurrentCommand récupère l'ID de la commande en cours d'un livreur func (d *Database) GetCurrentCommand(livreurUsername string) (int, error) { // Récupérer depuis Redis currentKey := fmt.Sprintf("delivery:current:%s", livreurUsername) currentIDStr, err := Redis.Get(RedisCtx, currentKey).Result() if err != nil { return 0, nil // Pas de commande en cours } var currentID int _, err = fmt.Sscanf(currentIDStr, "%d", ¤tID) if err != nil { return 0, nil } return currentID, nil } // ============================================ // 📜 HISTORIQUE DES LIVRAISONS // ============================================ // GetDeliveryPersonHistory récupère l'historique paginé des livraisons d'un livreur func (d *Database) GetDeliveryPersonHistory(livreurUsername string, limit, offset int) ([]map[string]interface{}, error) { query := ` SELECT c.id as command_id, c.username as client, c.status, c.adresse, c.total_prix, c.created_at as assigned_at, c.updated_at as completed_at FROM commandes c WHERE c.livreur_assign = $1 ORDER BY c.created_at DESC LIMIT $2 OFFSET $3 ` rows, err := d.Query(query, livreurUsername, limit, offset) if err != nil { return nil, fmt.Errorf("erreur récupération historique: %w", err) } defer rows.Close() var history []map[string]interface{} for rows.Next() { var commandID int var client, status, adresse string var totalPrix float64 var assignedAt, completedAt time.Time err := rows.Scan( &commandID, &client, &status, &adresse, &totalPrix, &assignedAt, &completedAt, ) if err != nil { return nil, fmt.Errorf("erreur scan historique: %w", err) } // Calculer la durée de livraison si complétée var deliveryTime float64 if status == "approved" || status == "livre" { deliveryTime = completedAt.Sub(assignedAt).Minutes() } entry := map[string]interface{}{ "command_id": commandID, "client": client, "status": status, "adresse": adresse, "total_prix": totalPrix, "assigned_at": assignedAt.Format("2006-01-02 15:04:05"), "completed_at": completedAt.Format("2006-01-02 15:04:05"), "delivery_time": deliveryTime, } history = append(history, entry) } return history, nil } // ============================================ // 📍 GESTION POSITION GPS // ============================================ // ⚠️ REMARQUE: Les fonctions UpdateDeliveryPersonLocation et GetDeliveryPersonLocation // sont déjà définies dans db/delivery_db.go // Nous réutilisons ces fonctions existantes au lieu de les redéfinir ici // ============================================ // 🔄 GESTION STATUT LIVREUR // ============================================ // GetDeliveryPersonStatus récupère le statut d'un livreur func (d *Database) GetDeliveryPersonStatus(livreurUsername string) (string, error) { statusKey := fmt.Sprintf("delivery:status:%s", livreurUsername) status, err := Redis.Get(RedisCtx, statusKey).Result() if err != nil { // Statut par défaut si non trouvé return "offline", nil } return status, nil } // UpdateDeliveryPersonStatus met à jour le statut d'un livreur func (d *Database) UpdateDeliveryPersonStatus(livreurUsername string, status string) error { // 🔹 Valider le statut if !allowedStatuses[status] { return fmt.Errorf("statut invalide: %s", status) } log.Printf("🔄 [UpdateStatus] Mise à jour: %s → %s", livreurUsername, status) statusKey := fmt.Sprintf("delivery:status:%s", livreurUsername) err := Redis.Set(RedisCtx, statusKey, status, 0).Err() if err != nil { log.Printf("❌ [UpdateStatus] Erreur Redis: %v", err) return fmt.Errorf("erreur mise à jour statut: %w", err) } log.Printf("✅ [UpdateStatus] Statut mis à jour pour %s: %s", livreurUsername, status) return nil } // ============================================ // 📦 GESTION QUEUE LIVREUR // ============================================ // GetDeliverymanQueueSize récupère la taille de la queue d'un livreur func (d *Database) GetDeliverymanQueueSize(livreurUsername string) (int, error) { queueKey := fmt.Sprintf("delivery:queue:%s", livreurUsername) size, err := Redis.LLen(RedisCtx, queueKey).Result() if err != nil { return 0, fmt.Errorf("erreur récupération taille queue: %w", err) } return int(size), nil } // GetDeliverymanQueue récupère la queue complète d'un livreur func (d *Database) GetDeliverymanQueue(livreurUsername string) ([]int, error) { queueKey := fmt.Sprintf("delivery:queue:%s", livreurUsername) commands, err := Redis.LRange(RedisCtx, queueKey, 0, -1).Result() if err != nil { return nil, fmt.Errorf("erreur récupération queue: %w", err) } var queue []int for _, cmdStr := range commands { var cmdID int fmt.Sscanf(cmdStr, "%d", &cmdID) queue = append(queue, cmdID) } return queue, nil } // ============================================ // 🔧 FONCTIONS UTILITAIRES // ============================================ // UpdateCommandLivreur met à jour le livreur assigné à une commande func (d *Database) UpdateCommandLivreur(commandID int, livreurUsername string) error { query := `UPDATE commandes SET livreur_assign = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2` result, err := d.Exec(query, livreurUsername, commandID) if err != nil { return fmt.Errorf("erreur mise à jour livreur: %w", err) } rowsAffected, err := result.RowsAffected() if err != nil { return fmt.Errorf("erreur vérification: %w", err) } if rowsAffected == 0 { return fmt.Errorf("commande non trouvée") } return nil } // ============================================ // 📊 STATISTIQUES SYSTÈME // ============================================ // GetAllDeliveryPersonsStats récupère les stats de tous les livreurs func (d *Database) GetAllDeliveryPersonsStats() ([]map[string]interface{}, error) { // Récupérer tous les livreurs livreurs, err := d.GetAvailableDeliveryPersons() if err != nil { return nil, fmt.Errorf("erreur récupération livreurs: %w", err) } var stats []map[string]interface{} for _, livreur := range livreurs { username := livreur["username"].(string) // Compter les livraisons totalDeliveries, _ := d.CountDeliveriesByStatus(username, "") completedDeliveries, _ := d.CountDeliveriesByStatus(username, "approved") queueSize, _ := d.GetDeliverymanQueueSize(username) status, _ := d.GetDeliveryPersonStatus(username) statEntry := map[string]interface{}{ "username": username, "total_deliveries": totalDeliveries, "completed_deliveries": completedDeliveries, "queue_size": queueSize, "status": status, } stats = append(stats, statEntry) } return stats, nil } // ============================================ // 🔍 RECHERCHE & FILTRAGE // ============================================ // GetDeliveryPersonsByStatus récupère les livreurs par statut func (d *Database) GetDeliveryPersonsByStatus(status string) ([]string, error) { // Récupérer tous les livreurs livreurs, err := d.GetAvailableDeliveryPersons() if err != nil { return nil, fmt.Errorf("erreur récupération livreurs: %w", err) } var filteredLivreurs []string for _, livreur := range livreurs { username := livreur["username"].(string) currentStatus, _ := d.GetDeliveryPersonStatus(username) if currentStatus == status { filteredLivreurs = append(filteredLivreurs, username) } } return filteredLivreurs, nil } // GetAvailableDeliveryPersonsCount compte les livreurs disponibles func (d *Database) GetAvailableDeliveryPersonsCount() (int, error) { availableLivreurs, err := d.GetDeliveryPersonsByStatus("available") if err != nil { return 0, err } return len(availableLivreurs), nil } // ============================================ // 🗑️ SUPPRESSION & NETTOYAGE // ============================================ // ClearDeliveryPersonData supprime toutes les données d'un livreur (admin uniquement) func (d *Database) ClearDeliveryPersonData(livreurUsername string) error { log.Printf("🗑️ [ClearDeliveryData] Nettoyage données pour: %s", livreurUsername) // Supprimer de Redis keys := []string{ fmt.Sprintf("delivery:status:%s", livreurUsername), fmt.Sprintf("delivery:location:%s", livreurUsername), fmt.Sprintf("delivery:queue:%s", livreurUsername), fmt.Sprintf("delivery:queue:size:%s", livreurUsername), fmt.Sprintf("delivery:current:%s", livreurUsername), } for _, key := range keys { err := Redis.Del(RedisCtx, key).Err() if err != nil { log.Printf("⚠️ [ClearDeliveryData] Erreur suppression clé %s: %v", key, err) } } log.Printf("✅ [ClearDeliveryData] Données nettoyées pour %s", livreurUsername) return nil }