443 lines
12 KiB
Go
443 lines
12 KiB
Go
// ============================================
|
|
// 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,
|
|
"support": 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
|
|
}
|