package db import ( "encoding/json" "fmt" "gestion/models" "log" "strconv" "time" ) // AssignCommandToDeliverymanQueue assigne une commande à la queue d'un livreur func (d *Database) AssignCommandToDeliverymanQueue(commandID int, deliveryman string, estimatedTravelTime int) error { command, err := d.GetCommandByID(commandID) if err != nil { return fmt.Errorf("commande introuvable: %w", err) } activeCount, _ := d.CountActiveDeliverymen() queueKey := fmt.Sprintf("queue:deliveryman:%s", deliveryman) currentQueueSize, _ := Redis.ZCard(RedisCtx, queueKey).Result() // Si plusieurs livreurs, appliquer la limite de 10 if activeCount > 1 && currentQueueSize >= MAX_COMMANDS_PER_DELIVERYMAN { return fmt.Errorf("livreur %s a atteint le maximum de commandes (%d)", deliveryman, MAX_COMMANDS_PER_DELIVERYMAN) } // ✅ MODIFIÉ: ETA = temps de trajet direct uniquement totalETA := estimatedTravelTime var lat, lng float64 if command["dest_latitude"] != nil { if latVal, ok := command["dest_latitude"].(float64); ok { lat = latVal } } if command["dest_longitude"] != nil { if lngVal, ok := command["dest_longitude"].(float64); ok { lng = lngVal } } var totalPrice float64 if tp, ok := command["total_prix"].(float64); ok { totalPrice = tp } var address string if addr, ok := command["delivery_address"].(string); ok { address = addr } else if addr, ok := command["adresse"].(string); ok { address = addr } queueItem := models.CommandQueue{ CommandID: commandID, Username: command["username"].(string), TotalPrice: totalPrice, Address: address, Lat: lat, Lng: lng, CreatedAt: time.Now(), EstimatedETA: totalETA, } err = d.AddToDeliverymanQueue(deliveryman, queueItem) if err != nil { return fmt.Errorf("erreur ajout à la queue: %w", err) } d.UpdateCommandStatus(commandID, "assigned") d.AssignDeliveryPerson(commandID, deliveryman) // ✅ MODIFIÉ: Position dans la queue pour info seulement d.SetCommandETAWithDetails(commandID, totalETA, int(currentQueueSize)+1) limitInfo := "" if activeCount > 1 { limitInfo = fmt.Sprintf("/%d", MAX_COMMANDS_PER_DELIVERYMAN) } else { limitInfo = " (sans limite - seul livreur)" } d.AddCommandLog(commandID, "queued", fmt.Sprintf("Ajouté à la queue de %s (position: %d%s, ETA trajet: %d min)", deliveryman, currentQueueSize+1, limitInfo, totalETA), "system") log.Printf("✅ Commande %d -> Queue %s (pos: %d%s, ETA trajet: %d min)", commandID, deliveryman, currentQueueSize+1, limitInfo, totalETA) return nil } // AssignCommandToDeliverymanQueueUnlimited assigne sans limite (pour un seul livreur) func (d *Database) AssignCommandToDeliverymanQueueUnlimited(deliveryman string, queueItem models.CommandQueue) error { queueKey := fmt.Sprintf("queue:deliveryman:%s", deliveryman) currentQueueSize, _ := Redis.ZCard(RedisCtx, queueKey).Result() // ✅ MODIFIÉ: Calculer le temps de trajet direct depuis la position du livreur travelTime := d.CalculateETAForDeliveryman(deliveryman, queueItem.Lat, queueItem.Lng) // ✅ ETA = temps de trajet direct uniquement queueItem.EstimatedETA = travelTime err := d.AddToDeliverymanQueue(deliveryman, queueItem) if err != nil { return fmt.Errorf("erreur ajout à la queue: %w", err) } d.UpdateCommandStatus(queueItem.CommandID, "assigned") d.AssignDeliveryPerson(queueItem.CommandID, deliveryman) d.SetCommandETAWithDetails(queueItem.CommandID, travelTime, int(currentQueueSize)+1) d.AddCommandLog(queueItem.CommandID, "queued", fmt.Sprintf("Assigné au seul livreur actif %s (position: %d, ETA trajet: %d min)", deliveryman, currentQueueSize+1, travelTime), "system") log.Printf("✅ Commande %d -> Queue %s (SANS LIMITE - pos: %d, ETA trajet: %d min)", queueItem.CommandID, deliveryman, currentQueueSize+1, travelTime) return nil } // AssignCommandToDeliverymanQueueWithCoords assigne une commande avec les coordonnées GPS func (d *Database) AssignCommandToDeliverymanQueueWithCoords(commandID int, deliveryman string, estimatedTravelTime int, lat, lng float64, address string) error { command, err := d.GetCommandByID(commandID) if err != nil { return fmt.Errorf("commande introuvable: %w", err) } activeCount, _ := d.CountActiveDeliverymen() queueKey := fmt.Sprintf("queue:deliveryman:%s", deliveryman) currentQueueSize, _ := Redis.ZCard(RedisCtx, queueKey).Result() if activeCount > 1 && currentQueueSize >= MAX_COMMANDS_PER_DELIVERYMAN { return fmt.Errorf("livreur %s a atteint le maximum de commandes (%d)", deliveryman, MAX_COMMANDS_PER_DELIVERYMAN) } // ✅ ETA = temps de trajet direct uniquement totalETA := estimatedTravelTime var totalPrice float64 if tp, ok := command["total_prix"].(float64); ok { totalPrice = tp } var username string if u, ok := command["username"].(string); ok { username = u } queueItem := models.CommandQueue{ CommandID: commandID, Username: username, TotalPrice: totalPrice, Address: address, Lat: lat, Lng: lng, CreatedAt: time.Now(), EstimatedETA: totalETA, } err = d.AddToDeliverymanQueue(deliveryman, queueItem) if err != nil { return fmt.Errorf("erreur ajout à la queue: %w", err) } // ============================================ // ✅ CORRECTION: Mettre à jour livreur_assign dans la DB // ============================================ updateQuery := `UPDATE commandes SET livreur_assign = $1, status = 'assigned', updated_at = CURRENT_TIMESTAMP WHERE id = $2` _, err = d.Exec(updateQuery, deliveryman, commandID) if err != nil { log.Printf("❌ Erreur mise à jour livreur_assign: %v", err) return fmt.Errorf("erreur mise à jour DB: %w", err) } log.Printf("✅ DB mise à jour: livreur_assign=%s pour commande %d", deliveryman, commandID) d.SetCommandETAWithDetails(commandID, totalETA, int(currentQueueSize)+1) // Sauvegarder dans le cache destination destCacheKey := fmt.Sprintf("command:destination:%d", commandID) coordsJSON, _ := json.Marshal(map[string]float64{ "lat": lat, "lon": lng, }) Redis.Set(RedisCtx, destCacheKey, coordsJSON, 4*time.Hour) limitInfo := "" if activeCount > 1 { limitInfo = fmt.Sprintf("/%d", MAX_COMMANDS_PER_DELIVERYMAN) } else { limitInfo = " (sans limite - seul livreur)" } d.AddCommandLog(commandID, "assigned", fmt.Sprintf("Assigné à %s (position: %d%s, ETA trajet: %d min, coords: %.4f,%.4f)", deliveryman, currentQueueSize+1, limitInfo, totalETA, lat, lng), "system") log.Printf("✅ Commande %d -> Livreur %s (pos: %d%s, ETA trajet: %d min, coords: %.4f,%.4f)", commandID, deliveryman, currentQueueSize+1, limitInfo, totalETA, lat, lng) return nil } // ForceAssignCommandToDeliverymanWithCoords assigne une commande de force avec coordonnées // ForceAssignCommandToDeliverymanWithCoords assigne une commande de force avec coordonnées func (d *Database) ForceAssignCommandToDeliverymanWithCoords(commandID int, deliveryman string, estimatedTravelTime int, lat, lng float64, address string) error { command, err := d.GetCommandByID(commandID) if err != nil { return fmt.Errorf("commande introuvable: %w", err) } queueKey := fmt.Sprintf("queue:deliveryman:%s", deliveryman) currentQueueSize, _ := Redis.ZCard(RedisCtx, queueKey).Result() // ✅ MODIFIÉ: ETA = temps de trajet direct uniquement totalETA := estimatedTravelTime var totalPrice float64 if tp, ok := command["total_prix"].(float64); ok { totalPrice = tp } var username string if u, ok := command["username"].(string); ok { username = u } queueItem := models.CommandQueue{ CommandID: commandID, Username: username, TotalPrice: totalPrice, Address: address, Lat: lat, Lng: lng, CreatedAt: time.Now(), EstimatedETA: totalETA, } err = d.AddToDeliverymanQueue(deliveryman, queueItem) if err != nil { return fmt.Errorf("erreur ajout à la queue: %w", err) } d.UpdateCommandStatus(commandID, "assigned") d.AssignDeliveryPerson(commandID, deliveryman) d.SetCommandETAWithDetails(commandID, totalETA, int(currentQueueSize)+1) // Sauvegarder dans le cache destination destCacheKey := fmt.Sprintf("command:destination:%d", commandID) coordsJSON, _ := json.Marshal(map[string]float64{ "lat": lat, "lon": lng, }) Redis.Set(RedisCtx, destCacheKey, coordsJSON, 4*time.Hour) d.AddCommandLog(commandID, "force_queued", fmt.Sprintf("Assignation forcée à %s (position: %d, ETA trajet: %d min, coords: %.4f,%.4f)", deliveryman, currentQueueSize+1, totalETA, lat, lng), "system") log.Printf("⚠️ FORCE: Commande %d -> Queue %s (pos: %d, ETA trajet: %d min, coords: %.4f,%.4f)", commandID, deliveryman, currentQueueSize+1, totalETA, lat, lng) return nil } // ForceAssignCommandToDeliveryman assigne une commande à un livreur SANS vérifier la limite de 10 func (d *Database) ForceAssignCommandToDeliveryman(commandID int, deliveryman string, estimatedTravelTime int) error { command, err := d.GetCommandByID(commandID) if err != nil { return fmt.Errorf("commande introuvable: %w", err) } queueKey := fmt.Sprintf("queue:deliveryman:%s", deliveryman) currentQueueSize, _ := Redis.ZCard(RedisCtx, queueKey).Result() // ✅ MODIFIÉ: ETA = temps de trajet direct uniquement totalETA := estimatedTravelTime var lat, lng float64 if command["dest_latitude"] != nil { if latVal, ok := command["dest_latitude"].(float64); ok { lat = latVal } } if command["dest_longitude"] != nil { if lngVal, ok := command["dest_longitude"].(float64); ok { lng = lngVal } } var totalPrice float64 if tp, ok := command["total_prix"].(float64); ok { totalPrice = tp } var address string if addr, ok := command["delivery_address"].(string); ok { address = addr } else if addr, ok := command["adresse"].(string); ok { address = addr } queueItem := models.CommandQueue{ CommandID: commandID, Username: command["username"].(string), TotalPrice: totalPrice, Address: address, Lat: lat, Lng: lng, CreatedAt: time.Now(), EstimatedETA: totalETA, } err = d.AddToDeliverymanQueue(deliveryman, queueItem) if err != nil { return fmt.Errorf("erreur ajout à la queue: %w", err) } d.UpdateCommandStatus(commandID, "assigned") d.AssignDeliveryPerson(commandID, deliveryman) d.SetCommandETAWithDetails(commandID, totalETA, int(currentQueueSize)+1) d.AddCommandLog(commandID, "force_queued", fmt.Sprintf("Assignation forcée à %s (position: %d, ETA trajet: %d min)", deliveryman, currentQueueSize+1, totalETA), "system") log.Printf("⚠️ FORCE: Commande %d -> Queue %s (pos: %d, ETA trajet: %d min)", commandID, deliveryman, currentQueueSize+1, totalETA) return nil } // AutoAssignCommand assigne automatiquement une commande au premier livreur disponible func (d *Database) AutoAssignCommand(commandID int) error { // Récupérer les livreurs disponibles available, err := d.GetAvailableDeliveryPersonsRedis() if err != nil || len(available) == 0 { return fmt.Errorf("aucun livreur disponible") } // Prendre le premier livreur livreur := available[0] // Assigner dans la DB principale err = d.AssignDeliveryPerson(commandID, livreur.Username) if err != nil { return err } // Mettre à jour le statut dans Redis d.SetDeliveryPersonStatus(livreur.Username, "busy", commandID) // Retirer de la file d'attente d.RemoveCommandFromQueue(commandID) // Définir l'ETA initial d.SetCommandETA(commandID, 30) log.Printf("✅ Commande %d auto-assignée à %s", commandID, livreur.Username) return nil } func (d *Database) ProcessNextCommandInQueue(deliveryman string) error { log.Printf("🔄 Traitement de la prochaine commande pour %s", deliveryman) // Vérifier d'abord la queue spécifique du livreur queueKey := fmt.Sprintf("queue:deliveryman:%s", deliveryman) results, err := Redis.ZRangeWithScores(RedisCtx, queueKey, 0, 0).Result() if err == nil && len(results) > 0 { // Une commande est dans sa queue - la traiter commandID := extractCommandID(results[0].Member) // Mettre à jour le statut d.SetDeliveryPersonStatus(deliveryman, "busy", commandID) log.Printf("✅ Livreur %s traite la commande %d de sa queue", deliveryman, commandID) return nil } // Si aucune commande dans sa queue, chercher dans la queue générale generalResults, err := Redis.ZRangeWithScores(RedisCtx, "queue:pending:sorted", 0, 0).Result() if err != nil || len(generalResults) == 0 { log.Printf("ℹ️ Aucune commande en attente pour %s", deliveryman) return nil } commandID := extractCommandID(generalResults[0].Member) // Assigner la commande err = d.AssignDeliveryPerson(commandID, deliveryman) if err != nil { log.Printf("❌ Erreur assignation commande %d: %v", commandID, err) return err } // Retirer de la queue générale Redis.ZRem(RedisCtx, "queue:pending:sorted", strconv.Itoa(commandID)) // Mettre à jour le statut d.SetDeliveryPersonStatus(deliveryman, "busy", commandID) // Définir un ETA par défaut d.SetCommandETA(commandID, 30) // Ajouter log d.AddCommandLog(commandID, "auto_assigned", fmt.Sprintf("Assigné automatiquement à %s depuis la queue générale", deliveryman), "system") log.Printf("✅ Commande %d assignée automatiquement à %s (queue générale)", commandID, deliveryman) return nil }