chore: refacto db
This commit is contained in:
@@ -13,37 +13,6 @@ func (d *Database) GetProductPrices(productID int) ([]models.ProductPrice, error
|
||||
return prices, nil
|
||||
}
|
||||
|
||||
func (d *Database) CreateProductPrice(productID int, quantity float64, price float64) error {
|
||||
p := models.ProductPrice{ProductID: productID, Quantity: quantity, Price: price}
|
||||
if err := d.GDB.Create(&p).Error; err != nil {
|
||||
return fmt.Errorf("erreur création prix: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Database) UpdateProductPrice(priceID int, quantity float64, price float64) error {
|
||||
result := d.GDB.Model(&models.ProductPrice{}).Where("id = ?", priceID).
|
||||
Updates(map[string]any{"quantity": quantity, "price": price})
|
||||
if result.Error != nil {
|
||||
return fmt.Errorf("erreur mise à jour prix: %w", result.Error)
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return fmt.Errorf("prix introuvable")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Database) DeleteProductPrice(priceID int) error {
|
||||
result := d.GDB.Delete(&models.ProductPrice{}, priceID)
|
||||
if result.Error != nil {
|
||||
return fmt.Errorf("erreur suppression prix: %w", result.Error)
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return fmt.Errorf("prix introuvable")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Database) AddActivePrice(priceID int) error {
|
||||
result := d.GDB.Model(&models.ProductPrice{}).
|
||||
Where("id = ?", priceID).
|
||||
|
||||
@@ -58,17 +58,3 @@ func (d *Database) ResetClientReferralBalance(username string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Database) UseClientReferralBalance(tx *gorm.DB, username string, amount float64) error {
|
||||
if amount <= 0 {
|
||||
return nil
|
||||
}
|
||||
var balance float64
|
||||
if err := tx.Raw(`SELECT referral_balance FROM clients WHERE username = ? FOR UPDATE`, username).Scan(&balance).Error; err != nil {
|
||||
return fmt.Errorf("client non trouvé")
|
||||
}
|
||||
if balance < amount {
|
||||
return fmt.Errorf("solde parrainage insuffisant (disponible: %.2f€)", balance)
|
||||
}
|
||||
return tx.Exec(`UPDATE clients SET referral_balance = referral_balance - ? WHERE username = ?`, amount, username).Error
|
||||
}
|
||||
|
||||
@@ -67,57 +67,6 @@ func (d *Database) FindLeastLoadedDeliveryman() (string, error) {
|
||||
return leastLoaded, nil
|
||||
}
|
||||
|
||||
// FindAvailableOrLeastLoadedDeliveryman trouve un livreur disponible ou le moins chargé
|
||||
func (d *Database) FindAvailableOrLeastLoadedDeliveryman() (string, string, int, error) {
|
||||
keys, err := Redis.Keys(RedisCtx, "delivery:status:*").Result()
|
||||
if err != nil || len(keys) == 0 {
|
||||
return "", "", 0, fmt.Errorf("aucun livreur trouvé")
|
||||
}
|
||||
|
||||
var bestDeliveryman string
|
||||
var bestStatus string
|
||||
bestQueueSize := int64(MAX_COMMANDS_PER_DELIVERYMAN + 1)
|
||||
|
||||
for _, key := range keys {
|
||||
username := key[len("delivery:status:"):]
|
||||
|
||||
data, err := Redis.Get(RedisCtx, key).Result()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var status models.DeliveryPersonStatus
|
||||
json.Unmarshal([]byte(data), &status)
|
||||
|
||||
if status.Status == "offline" {
|
||||
continue
|
||||
}
|
||||
|
||||
if !d.CanDeliverymanAcceptCommands(username) {
|
||||
continue
|
||||
}
|
||||
|
||||
queueKey := fmt.Sprintf("queue:deliveryman:%s", username)
|
||||
queueSize, _ := Redis.ZCard(RedisCtx, queueKey).Result()
|
||||
|
||||
if status.Status == "available" && queueSize == 0 {
|
||||
return username, "available", 0, nil
|
||||
}
|
||||
|
||||
if queueSize < bestQueueSize {
|
||||
bestQueueSize = queueSize
|
||||
bestDeliveryman = username
|
||||
bestStatus = status.Status
|
||||
}
|
||||
}
|
||||
|
||||
if bestDeliveryman == "" {
|
||||
return "", "", 0, fmt.Errorf("tous les livreurs sont au maximum de leur capacité")
|
||||
}
|
||||
|
||||
return bestDeliveryman, bestStatus, int(bestQueueSize), nil
|
||||
}
|
||||
|
||||
// GetLeastLoadedDeliverymanForced retourne le livreur avec le moins de commandes (SANS limite)
|
||||
func (d *Database) GetLeastLoadedDeliverymanForced() (string, int64, error) {
|
||||
activeUsernames, err := d.GetAllActiveDeliverymenUsernames()
|
||||
|
||||
@@ -156,10 +156,6 @@ func (d *Database) CalculateETABetweenPoints(lat1, lng1, lat2, lng2 float64) int
|
||||
return services.CalculateETA(distance)
|
||||
}
|
||||
|
||||
func (d *Database) GetDeliverymanQueueStats(deliveryman string) (map[string]any, error) {
|
||||
return d.GetDeliverymanQueueInfo(deliveryman)
|
||||
}
|
||||
|
||||
func (d *Database) SetCommandETAWithDetails(commandID, totalETA, queuePosition int) error {
|
||||
key := fmt.Sprintf("command:eta:%d", commandID)
|
||||
|
||||
|
||||
@@ -87,35 +87,6 @@ func (d *Database) AssignCommandToDeliverymanQueue(commandID int, deliveryman st
|
||||
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()
|
||||
|
||||
travelTime := d.CalculateETAForDeliveryman(deliveryman, queueItem.Lat, queueItem.Lng)
|
||||
|
||||
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)
|
||||
|
||||
@@ -203,64 +203,3 @@ func (d *Database) StartQueueCleanupScheduler() {
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// RAPPORT DE VALIDATION
|
||||
// ============================================
|
||||
|
||||
// GetQueueValidationReport génère un rapport de validation sans supprimer
|
||||
func (d *Database) GetQueueValidationReport() (map[string]any, error) {
|
||||
keys, err := Redis.Keys(RedisCtx, "queue:pending:*").Result()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
report := map[string]any{
|
||||
"total_commands": len(keys),
|
||||
"valid_commands": 0,
|
||||
"invalid_commands": 0,
|
||||
"invalid_details": []map[string]any{},
|
||||
"validation_results": []string{},
|
||||
}
|
||||
|
||||
for _, key := range keys {
|
||||
data, err := Redis.Get(RedisCtx, key).Result()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var queueItem models.CommandQueue
|
||||
if err := json.Unmarshal([]byte(data), &queueItem); err != nil {
|
||||
report["invalid_commands"] = report["invalid_commands"].(int) + 1
|
||||
continue
|
||||
}
|
||||
|
||||
// Validation
|
||||
issues := []string{}
|
||||
if queueItem.Username == "" {
|
||||
issues = append(issues, "username vide")
|
||||
}
|
||||
if queueItem.Address == "" {
|
||||
issues = append(issues, "adresse vide")
|
||||
}
|
||||
if queueItem.Lat == 0 || queueItem.Lng == 0 {
|
||||
issues = append(issues, "GPS manquant")
|
||||
}
|
||||
if queueItem.CreatedAt.IsZero() {
|
||||
issues = append(issues, "date invalide")
|
||||
}
|
||||
|
||||
if len(issues) > 0 {
|
||||
report["invalid_commands"] = report["invalid_commands"].(int) + 1
|
||||
report["invalid_details"] = append(report["invalid_details"].([]map[string]any), map[string]any{
|
||||
"command_id": queueItem.CommandID,
|
||||
"issues": issues,
|
||||
"data": queueItem,
|
||||
})
|
||||
} else {
|
||||
report["valid_commands"] = report["valid_commands"].(int) + 1
|
||||
}
|
||||
}
|
||||
|
||||
return report, nil
|
||||
}
|
||||
|
||||
@@ -86,40 +86,6 @@ func (d *Database) CanDeliverymanAcceptCommands(deliveryman string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// GetAvailableDeliveryPersonsForAssignment récupère UNIQUEMENT les livreurs pouvant accepter
|
||||
func (d *Database) GetAvailableDeliveryPersonsForAssignment() ([]models.DeliveryPersonStatus, error) {
|
||||
keys, err := Redis.Keys(RedisCtx, "delivery:status:*").Result()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var available []models.DeliveryPersonStatus
|
||||
|
||||
for _, key := range keys {
|
||||
data, err := Redis.Get(RedisCtx, key).Result()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var status models.DeliveryPersonStatus
|
||||
if err := json.Unmarshal([]byte(data), &status); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if d.CanDeliverymanAcceptCommands(status.Username) {
|
||||
available = append(available, status)
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("📊 [AVAILABLE] %d livreur(s) disponible(s) pour assignation", len(available))
|
||||
|
||||
return available, nil
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 🔄 FONCTIONS MODIFIÉES AVEC AUTO-STATUS
|
||||
// ============================================
|
||||
|
||||
// AddToDeliverymanQueue - VERSION MISE À JOUR avec auto-update du statut
|
||||
func (d *Database) AddToDeliverymanQueue(deliveryman string, queueItem models.CommandQueue) error {
|
||||
data, err := json.Marshal(queueItem)
|
||||
@@ -150,111 +116,6 @@ func (d *Database) AddToDeliverymanQueue(deliveryman string, queueItem models.Co
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddCommandToQueue ajoute une commande à la file d'attente Redis (version simple)
|
||||
func (d *Database) AddCommandToQueue(commandID int) error {
|
||||
if err := d.ValidateCommandBeforeQueue(commandID); err != nil {
|
||||
log.Printf("❌ [QUEUE] Commande %d REFUSÉE: %v", commandID, err)
|
||||
return fmt.Errorf("validation échouée: %w", err)
|
||||
}
|
||||
|
||||
command, err := d.GetCommandByID(commandID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("commande introuvable: %w", err)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
queueItem := models.CommandQueue{
|
||||
CommandID: commandID,
|
||||
Username: command["username"].(string),
|
||||
TotalPrice: totalPrice,
|
||||
Address: address,
|
||||
Lat: lat,
|
||||
Lng: lng,
|
||||
CreatedAt: time.Now(),
|
||||
EstimatedETA: 0,
|
||||
}
|
||||
|
||||
return d.AddToGeneralQueue(queueItem)
|
||||
}
|
||||
|
||||
// AddCommandToSmartQueue - Ajoute une commande avec attribution au livreur le moins chargé
|
||||
func (d *Database) AddCommandToSmartQueue(commandID int, address string) error {
|
||||
if err := d.ValidateCommandBeforeQueue(commandID); err != nil {
|
||||
log.Printf("❌ [QUEUE] Commande %d REFUSÉE: %v", commandID, err)
|
||||
return fmt.Errorf("validation échouée: %w", err)
|
||||
}
|
||||
command, err := d.GetCommandByID(commandID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("commande introuvable: %w", err)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
queueItem := models.CommandQueue{
|
||||
CommandID: commandID,
|
||||
Username: command["username"].(string),
|
||||
TotalPrice: totalPrice,
|
||||
Address: address,
|
||||
Lat: lat,
|
||||
Lng: lng,
|
||||
CreatedAt: time.Now(),
|
||||
EstimatedETA: 0,
|
||||
}
|
||||
|
||||
// ✅ MODIFIÉ: Utiliser FindLeastLoadedDeliveryman qui respecte maintenant le statut
|
||||
assignedDeliveryman, err := d.FindLeastLoadedDeliveryman()
|
||||
if err != nil {
|
||||
log.Printf("⚠️ Aucun livreur trouvé, ajout à la queue générale")
|
||||
return d.AddToGeneralQueue(queueItem)
|
||||
}
|
||||
|
||||
err = d.AddToDeliverymanQueue(assignedDeliveryman, queueItem)
|
||||
if err != nil {
|
||||
return fmt.Errorf("erreur ajout à la queue du livreur: %w", err)
|
||||
}
|
||||
|
||||
log.Printf("📋 Commande %d assignée à la queue de %s", commandID, assignedDeliveryman)
|
||||
|
||||
d.PublishCommandEvent(commandID, "queued",
|
||||
fmt.Sprintf("En attente dans la queue de %s", assignedDeliveryman))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddToGeneralQueue ajoute une commande à la queue générale (fallback)
|
||||
func (d *Database) AddToGeneralQueue(queueItem models.CommandQueue) error {
|
||||
data, err := json.Marshal(queueItem)
|
||||
@@ -355,109 +216,6 @@ func (d *Database) GetNextCommandInQueue() (*models.CommandQueue, error) {
|
||||
return &queue, nil
|
||||
}
|
||||
|
||||
// GetLastCommandInQueue récupère la dernière commande dans la queue d'un livreur
|
||||
func (d *Database) GetLastCommandInQueue(deliveryman string) (*models.CommandQueue, error) {
|
||||
queueKey := fmt.Sprintf("queue:deliveryman:%s", deliveryman)
|
||||
|
||||
// Récupérer la dernière commande (index -1)
|
||||
commandIDs, err := Redis.ZRange(RedisCtx, queueKey, -1, -1).Result()
|
||||
if err != nil || len(commandIDs) == 0 {
|
||||
return nil, fmt.Errorf("queue vide")
|
||||
}
|
||||
|
||||
commandID := extractCommandID(commandIDs[0])
|
||||
if commandID <= 0 {
|
||||
return nil, fmt.Errorf("ID invalide")
|
||||
}
|
||||
|
||||
commandKey := fmt.Sprintf("queue:pending:%d", commandID)
|
||||
data, err := Redis.Get(RedisCtx, commandKey).Result()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var queueItem models.CommandQueue
|
||||
if err := json.Unmarshal([]byte(data), &queueItem); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &queueItem, nil
|
||||
}
|
||||
|
||||
// GetCommandQueuePosition récupère la position d'une commande dans la queue
|
||||
func (d *Database) GetCommandQueuePosition(commandID int) (int, error) {
|
||||
commandIDStr := strconv.Itoa(commandID)
|
||||
|
||||
// Chercher d'abord dans les queues des livreurs
|
||||
keys, _ := Redis.Keys(RedisCtx, "queue:deliveryman:*").Result()
|
||||
|
||||
for _, queueKey := range keys {
|
||||
// Éviter les clés de compteur
|
||||
if len(queueKey) > 6 && queueKey[len(queueKey)-6:] == ":count" {
|
||||
continue
|
||||
}
|
||||
|
||||
rank, err := Redis.ZRank(RedisCtx, queueKey, commandIDStr).Result()
|
||||
if err == nil {
|
||||
return int(rank) + 1, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Chercher dans la queue générale
|
||||
rank, err := Redis.ZRank(RedisCtx, "queue:pending:sorted", commandIDStr).Result()
|
||||
if err == nil {
|
||||
return int(rank) + 1, nil
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("commande non trouvée dans les queues")
|
||||
}
|
||||
|
||||
func (d *Database) ClearDeliverymanQueue(deliveryman string) error {
|
||||
queueKey := fmt.Sprintf("queue:deliveryman:%s", deliveryman)
|
||||
|
||||
// Récupérer toutes les commandes
|
||||
commandIDs, _ := Redis.ZRange(RedisCtx, queueKey, 0, -1).Result()
|
||||
|
||||
// Redistribuer chaque commande
|
||||
for _, cmdIDStr := range commandIDs {
|
||||
commandID := extractCommandID(cmdIDStr)
|
||||
if commandID <= 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
commandKey := fmt.Sprintf("queue:pending:%d", commandID)
|
||||
data, err := Redis.Get(RedisCtx, commandKey).Result()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var queueItem models.CommandQueue
|
||||
if err := json.Unmarshal([]byte(data), &queueItem); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
newDeliveryman, err := d.FindLeastLoadedDeliveryman()
|
||||
if err != nil {
|
||||
d.AddToGeneralQueue(queueItem)
|
||||
continue
|
||||
}
|
||||
|
||||
if newDeliveryman != deliveryman {
|
||||
d.AddToDeliverymanQueue(newDeliveryman, queueItem)
|
||||
log.Printf("🔄 Commande %d réassignée de %s à %s",
|
||||
commandID, deliveryman, newDeliveryman)
|
||||
}
|
||||
}
|
||||
|
||||
// Vider la queue
|
||||
Redis.Del(RedisCtx, queueKey)
|
||||
Redis.Del(RedisCtx, fmt.Sprintf("queue:deliveryman:%s:count", deliveryman))
|
||||
|
||||
go d.UpdateDeliverymanStatusBasedOnQueue(deliveryman)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Database) SetDeliveryPersonStatus(username, status string, commandID int) error {
|
||||
key := fmt.Sprintf("delivery:status:%s", username)
|
||||
|
||||
@@ -543,46 +301,6 @@ func (d *Database) SyncAllDeliverymanStatuses() error {
|
||||
})
|
||||
}
|
||||
|
||||
// GetDeliverymanCapacityReport génère un rapport détaillé
|
||||
func (d *Database) GetDeliverymanCapacityReport() (map[string]any, error) {
|
||||
report := map[string]any{
|
||||
"total_deliverymen": 0,
|
||||
"available": 0,
|
||||
"busy_full": 0,
|
||||
"busy_delivering": 0,
|
||||
"offline": 0,
|
||||
"details": []map[string]any{},
|
||||
}
|
||||
|
||||
err := d.iterDeliveryStatuses(func(s models.DeliveryPersonStatus) {
|
||||
queueKey := fmt.Sprintf("queue:deliveryman:%s", s.Username)
|
||||
queueSize, _ := Redis.ZCard(RedisCtx, queueKey).Result()
|
||||
canAccept := d.CanDeliverymanAcceptCommands(s.Username)
|
||||
|
||||
report["total_deliverymen"] = report["total_deliverymen"].(int) + 1
|
||||
switch {
|
||||
case s.Status == "offline":
|
||||
report["offline"] = report["offline"].(int) + 1
|
||||
case s.Status == "busy" && queueSize >= MAX_COMMANDS_PER_DELIVERYMAN:
|
||||
report["busy_full"] = report["busy_full"].(int) + 1
|
||||
case s.Status == "busy":
|
||||
report["busy_delivering"] = report["busy_delivering"].(int) + 1
|
||||
case canAccept:
|
||||
report["available"] = report["available"].(int) + 1
|
||||
}
|
||||
|
||||
report["details"] = append(report["details"].([]map[string]any), map[string]any{
|
||||
"username": s.Username,
|
||||
"status": s.Status,
|
||||
"queue_size": queueSize,
|
||||
"capacity": fmt.Sprintf("%d/10", queueSize),
|
||||
"can_accept": canAccept,
|
||||
"current_order": s.CurrentCommand,
|
||||
})
|
||||
})
|
||||
return report, err
|
||||
}
|
||||
|
||||
// iterDeliveryStatuses itère sur tous les statuts Redis des livreurs et appelle fn pour chacun.
|
||||
func (d *Database) iterDeliveryStatuses(fn func(models.DeliveryPersonStatus)) error {
|
||||
keys, err := Redis.Keys(RedisCtx, "delivery:status:*").Result()
|
||||
|
||||
@@ -288,10 +288,6 @@ func (d *Database) RecalculateQueueETAs(deliveryman string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Database) UpdateQueueETAsAfterCompletion(deliveryman string) error {
|
||||
return d.RecalculateQueueETAs(deliveryman)
|
||||
}
|
||||
|
||||
// FindNearestCommandInQueue trouve la commande la plus proche du livreur
|
||||
func (d *Database) FindNearestCommandInQueue(deliveryman string) (*models.CommandQueue, int, error) {
|
||||
livreurLat, livreurLng, err := d.GetDeliveryPersonLocation(deliveryman)
|
||||
|
||||
@@ -3,7 +3,6 @@ package db
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gestion/models"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
@@ -183,221 +182,3 @@ func (d *Database) InvalidateSession(clientID int) error {
|
||||
log.Printf("✅ [SESSION] Session invalidée pour client %d", clientID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// PANIER EN CACHE REDIS
|
||||
// ============================================
|
||||
|
||||
// BasketItemCache représente un item du panier en cache
|
||||
type BasketItemCache struct {
|
||||
ID int `json:"id"`
|
||||
ProductID int `json:"product_id"`
|
||||
ProductName string `json:"product_name"`
|
||||
Quantity int `json:"quantity"`
|
||||
Price float64 `json:"price"`
|
||||
Category string `json:"category"`
|
||||
AddedAt int64 `json:"added_at"`
|
||||
}
|
||||
|
||||
// GetSessionBasket récupère le panier en cache Redis
|
||||
// Retourne les items du panier avec total
|
||||
func (d *Database) GetSessionBasket(clientID int) ([]BasketItemCache, float64, error) {
|
||||
basketKey := fmt.Sprintf("session:basket:%d", clientID)
|
||||
|
||||
// Récupérer tous les items du panier
|
||||
items, err := Redis.HGetAll(RedisCtx, basketKey).Result()
|
||||
if err != nil {
|
||||
log.Printf("⚠️ [BASKET] Pas de panier en cache pour client %d", clientID)
|
||||
return []BasketItemCache{}, 0, nil
|
||||
}
|
||||
|
||||
var basketItems []BasketItemCache
|
||||
var totalPrice float64
|
||||
|
||||
for _, itemJSON := range items {
|
||||
var item BasketItemCache
|
||||
if err := json.Unmarshal([]byte(itemJSON), &item); err != nil {
|
||||
log.Printf("⚠️ [BASKET] Erreur parsing item: %v", err)
|
||||
continue
|
||||
}
|
||||
basketItems = append(basketItems, item)
|
||||
totalPrice += item.Price * float64(item.Quantity)
|
||||
}
|
||||
|
||||
return basketItems, totalPrice, nil
|
||||
}
|
||||
|
||||
// UpdateSessionBasket met à jour le panier en cache Redis
|
||||
// Appelé après ajout/modification d'un produit au panier
|
||||
func (d *Database) UpdateSessionBasket(clientID int, basketItems []BasketItemCache) error {
|
||||
basketKey := fmt.Sprintf("session:basket:%d", clientID)
|
||||
|
||||
// Vider le panier existant
|
||||
Redis.Del(RedisCtx, basketKey)
|
||||
|
||||
// Ajouter tous les items
|
||||
for _, item := range basketItems {
|
||||
itemJSON, _ := json.Marshal(item)
|
||||
if err := Redis.HSet(RedisCtx, basketKey, item.ProductID, itemJSON).Err(); err != nil {
|
||||
log.Printf("⚠️ [BASKET] Erreur ajout item: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TTL: 24 heures
|
||||
if err := Redis.Expire(RedisCtx, basketKey, 24*time.Hour).Err(); err != nil {
|
||||
log.Printf("⚠️ [BASKET] Erreur TTL: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClearSessionBasket vide le panier en cache Redis
|
||||
// Appelé après validation de commande (checkout)
|
||||
func (d *Database) ClearSessionBasket(clientID int) error {
|
||||
basketKey := fmt.Sprintf("session:basket:%d", clientID)
|
||||
if err := Redis.Del(RedisCtx, basketKey).Err(); err != nil {
|
||||
log.Printf("⚠️ [BASKET] Erreur clear: %v", err)
|
||||
return nil
|
||||
}
|
||||
log.Printf("✅ [BASKET] Panier vidé pour client %d", clientID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// UTILITAIRES SESSION
|
||||
// ============================================
|
||||
|
||||
// GetAllActiveSessions récupère toutes les sessions actives
|
||||
// Utile pour admin/stats
|
||||
func (d *Database) GetAllActiveSessions() ([]SessionData, error) {
|
||||
clientIDs, err := Redis.SMembers(RedisCtx, "session:active:clients").Result()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("erreur récupération sessions: %w", err)
|
||||
}
|
||||
|
||||
var sessions []SessionData
|
||||
for _, clientIDStr := range clientIDs {
|
||||
var clientID int
|
||||
if _, err := fmt.Sscanf(clientIDStr, "%d", &clientID); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if session, err := d.GetClientSession(clientID); err == nil {
|
||||
sessions = append(sessions, *session)
|
||||
}
|
||||
}
|
||||
|
||||
return sessions, nil
|
||||
}
|
||||
|
||||
// GetSessionCount retourne le nombre de sessions actives
|
||||
func (d *Database) GetSessionCount() (int64, error) {
|
||||
count, err := Redis.SCard(RedisCtx, "session:active:clients").Result()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("erreur comptage sessions: %w", err)
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// CACHE PROFIL CLIENT
|
||||
// ============================================
|
||||
|
||||
// CacheClientProfile met en cache les infos du client (pour 1h)
|
||||
func (d *Database) CacheClientProfile(client interface{}) error {
|
||||
// Récupérer le client depuis DB si c'est un username
|
||||
var clientData *models.Client
|
||||
|
||||
// Si c'est un username string
|
||||
if username, ok := client.(string); ok {
|
||||
var err error
|
||||
clientData, err = d.GetClientByUsername(username)
|
||||
if err != nil {
|
||||
return fmt.Errorf("client non trouvé: %w", err)
|
||||
}
|
||||
} else {
|
||||
// Si c'est déjà un *models.Client
|
||||
clientData = client.(*models.Client)
|
||||
}
|
||||
|
||||
cacheKey := fmt.Sprintf("cache:client:profile:%d", clientData.ID)
|
||||
|
||||
// Sérialiser
|
||||
profileJSON, err := json.Marshal(clientData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("erreur sérialisation: %w", err)
|
||||
}
|
||||
|
||||
// Sauvegarder avec TTL 1h
|
||||
if err := Redis.Set(RedisCtx, cacheKey, profileJSON, 1*time.Hour).Err(); err != nil {
|
||||
return fmt.Errorf("erreur cache: %w", err)
|
||||
}
|
||||
|
||||
log.Printf("✅ [CACHE] Profil client %d mis en cache (1h)", clientData.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCachedClientProfile récupère le profil en cache
|
||||
func (d *Database) GetCachedClientProfile(clientID int) (*models.Client, error) {
|
||||
cacheKey := fmt.Sprintf("cache:client:profile:%d", clientID)
|
||||
|
||||
data, err := Redis.Get(RedisCtx, cacheKey).Result()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cache miss")
|
||||
}
|
||||
|
||||
var client models.Client
|
||||
if err := json.Unmarshal([]byte(data), &client); err != nil {
|
||||
return nil, fmt.Errorf("erreur désérialisation: %w", err)
|
||||
}
|
||||
|
||||
return &client, nil
|
||||
}
|
||||
|
||||
// InvalidateClientCache invalide le cache du client
|
||||
func (d *Database) InvalidateClientCache(clientID int) error {
|
||||
cacheKey := fmt.Sprintf("cache:client:profile:%d", clientID)
|
||||
if err := Redis.Del(RedisCtx, cacheKey).Err(); err != nil {
|
||||
return fmt.Errorf("erreur invalidation: %w", err)
|
||||
}
|
||||
log.Printf("✅ [CACHE] Profil client %d invalidé", clientID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// COMMANDES EN CACHE (POUR TRACKING)
|
||||
// ============================================
|
||||
|
||||
// CacheCommandInfo met en cache les infos d'une commande
|
||||
func (d *Database) CacheCommandInfo(commandID int, command map[string]interface{}) error {
|
||||
cacheKey := fmt.Sprintf("cache:command:%d", commandID)
|
||||
|
||||
commandJSON, err := json.Marshal(command)
|
||||
if err != nil {
|
||||
return fmt.Errorf("erreur sérialisation: %w", err)
|
||||
}
|
||||
|
||||
// TTL: 4 heures
|
||||
if err := Redis.Set(RedisCtx, cacheKey, commandJSON, 4*time.Hour).Err(); err != nil {
|
||||
return fmt.Errorf("erreur cache: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCachedCommand récupère une commande en cache
|
||||
func (d *Database) GetCachedCommand(commandID int) (map[string]interface{}, error) {
|
||||
cacheKey := fmt.Sprintf("cache:command:%d", commandID)
|
||||
|
||||
data, err := Redis.Get(RedisCtx, cacheKey).Result()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cache miss")
|
||||
}
|
||||
|
||||
var command map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(data), &command); err != nil {
|
||||
return nil, fmt.Errorf("erreur désérialisation: %w", err)
|
||||
}
|
||||
|
||||
return command, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user