chore: refacto
This commit is contained in:
@@ -65,10 +65,14 @@ func (d *Database) CancelCommandAtomic(commandID int, username, reason string, f
|
|||||||
return fmt.Errorf("confirmation requise")
|
return fmt.Errorf("confirmation requise")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cancelMsg := reason
|
||||||
|
if cancelMsg == "Annulation par le client" {
|
||||||
|
cancelMsg = ""
|
||||||
|
}
|
||||||
result := tx.Exec(`
|
result := tx.Exec(`
|
||||||
UPDATE commandes
|
UPDATE commandes
|
||||||
SET status = 'cancelled', updated_at = CURRENT_TIMESTAMP
|
SET status = 'cancelled', cancel_reason = ?, updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = ? AND status = ? AND username = ?`, commandID, cmdResult.Status, username)
|
WHERE id = ? AND status = ? AND username = ?`, cancelMsg, commandID, cmdResult.Status, username)
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return result.Error
|
return result.Error
|
||||||
}
|
}
|
||||||
@@ -87,13 +91,20 @@ func (d *Database) CancelCommandAtomic(commandID int, username, reason string, f
|
|||||||
log.Printf("✅ [CancelAtomic] Stock remboursé")
|
log.Printf("✅ [CancelAtomic] Stock remboursé")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := tx.Exec(`
|
||||||
|
UPDATE clients
|
||||||
|
SET cancellations_count = COALESCE(cancellations_count, 0) + 1,
|
||||||
|
updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE username = ?`, username).Error; err != nil {
|
||||||
|
log.Printf("⚠️ [CancelAtomic] Erreur incrémentation count: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
if isLateCancel {
|
if isLateCancel {
|
||||||
log.Printf("⚠️ [CancelAtomic] Annulation tardive confirmée - Application pénalité")
|
log.Printf("⚠️ [CancelAtomic] Annulation tardive confirmée - Application pénalité")
|
||||||
penalty, _ = d.CalculateCancellationPenalty(username)
|
penalty, _ = d.CalculateCancellationPenalty(username)
|
||||||
if err := tx.Exec(`
|
if err := tx.Exec(`
|
||||||
UPDATE clients
|
UPDATE clients
|
||||||
SET amende = amende + ?,
|
SET amende = amende + ?,
|
||||||
cancellations_count = COALESCE(cancellations_count, 0) + 1,
|
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE username = ?`, penalty, username).Error; err != nil {
|
WHERE username = ?`, penalty, username).Error; err != nil {
|
||||||
log.Printf("❌ [CancelAtomic] Erreur pénalité: %v", err)
|
log.Printf("❌ [CancelAtomic] Erreur pénalité: %v", err)
|
||||||
@@ -250,7 +261,7 @@ func (d *Database) GetCommandPositionInQueue(livreurUsername string, commandID i
|
|||||||
|
|
||||||
func (d *Database) GetCancelledCommands(username string, limit int) ([]map[string]any, error) {
|
func (d *Database) GetCancelledCommands(username string, limit int) ([]map[string]any, error) {
|
||||||
query := `
|
query := `
|
||||||
SELECT id, client_order_id AS client_order_number, username, status, adresse, total_prix::float8 as total_prix, created_at, updated_at
|
SELECT id, client_order_id AS client_order_number, username, status, adresse, total_prix::float8 as total_prix, created_at, updated_at, COALESCE(cancel_reason, '') AS cancel_reason
|
||||||
FROM commandes
|
FROM commandes
|
||||||
WHERE status = 'cancelled'`
|
WHERE status = 'cancelled'`
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func (d *Database) UpdateCategory(id int, name, color string, isComingSoon bool)
|
|||||||
if err := d.GDB.First(&c, id).Error; err != nil {
|
if err := d.GDB.First(&c, id).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := d.GDB.Model(&c).Updates(Category{Name: name, Color: color, IsComingSoon: isComingSoon}).Error; err != nil {
|
if err := d.GDB.Model(&c).Updates(map[string]interface{}{"name": name, "color": color, "is_coming_soon": isComingSoon}).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &c, nil
|
return &c, nil
|
||||||
|
|||||||
@@ -88,11 +88,13 @@ func (d *Database) GetAllClients() ([]*models.Client, error) {
|
|||||||
Command int `gorm:"column:command"`
|
Command int `gorm:"column:command"`
|
||||||
Amende float64 `gorm:"column:amende"`
|
Amende float64 `gorm:"column:amende"`
|
||||||
ReferralBalance float64 `gorm:"column:referral_balance"`
|
ReferralBalance float64 `gorm:"column:referral_balance"`
|
||||||
|
CancellationsCount int `gorm:"column:cancellations_count"`
|
||||||
PointsExtraJSON []byte `gorm:"column:points_extra"`
|
PointsExtraJSON []byte `gorm:"column:points_extra"`
|
||||||
CreatedAt time.Time `gorm:"column:created_at"`
|
CreatedAt time.Time `gorm:"column:created_at"`
|
||||||
}
|
}
|
||||||
err := d.GDB.Raw(`
|
err := d.GDB.Raw(`
|
||||||
SELECT id, username, password, nom, prenom, telephone, command, amende, referral_balance,
|
SELECT id, username, password, nom, prenom, telephone, command, amende, referral_balance,
|
||||||
|
COALESCE(cancellations_count, 0) as cancellations_count,
|
||||||
COALESCE(points_extra, '{}'::jsonb) as points_extra, created_at
|
COALESCE(points_extra, '{}'::jsonb) as points_extra, created_at
|
||||||
FROM clients ORDER BY created_at DESC`).Scan(&rows).Error
|
FROM clients ORDER BY created_at DESC`).Scan(&rows).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -111,6 +113,7 @@ func (d *Database) GetAllClients() ([]*models.Client, error) {
|
|||||||
Command: row.Command,
|
Command: row.Command,
|
||||||
Amende: row.Amende,
|
Amende: row.Amende,
|
||||||
ReferralBalance: row.ReferralBalance,
|
ReferralBalance: row.ReferralBalance,
|
||||||
|
CancellationsCount: row.CancellationsCount,
|
||||||
CreatedAt: row.CreatedAt,
|
CreatedAt: row.CreatedAt,
|
||||||
}
|
}
|
||||||
client.PointsExtra = map[string]int{}
|
client.PointsExtra = map[string]int{}
|
||||||
@@ -125,14 +128,15 @@ func (d *Database) GetAllClients() ([]*models.Client, error) {
|
|||||||
|
|
||||||
// UpdateClient met à jour un client existant
|
// UpdateClient met à jour un client existant
|
||||||
func (d *Database) UpdateClient(client *models.Client) error {
|
func (d *Database) UpdateClient(client *models.Client) error {
|
||||||
result := d.GDB.Exec(`
|
result := d.GDB.Model(&models.Client{}).Where("id = ?", client.ID).Updates(map[string]any{
|
||||||
UPDATE clients
|
"username": client.Username,
|
||||||
SET username = ?, password = ?, nom = ?, prenom = ?, telephone = ?,
|
"password": client.Password,
|
||||||
command = ?, amende = ?
|
"nom": client.Nom,
|
||||||
WHERE id = ?`,
|
"prenom": client.Prenom,
|
||||||
client.Username, client.Password, client.Nom, client.Prenom, client.Telephone,
|
"telephone": client.Telephone,
|
||||||
client.Command, client.Amende, client.ID,
|
"command": client.Command,
|
||||||
)
|
"amende": client.Amende,
|
||||||
|
})
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return fmt.Errorf("erreur lors de la mise à jour du client: %w", result.Error)
|
return fmt.Errorf("erreur lors de la mise à jour du client: %w", result.Error)
|
||||||
}
|
}
|
||||||
@@ -147,7 +151,7 @@ func (d *Database) UpdateClient(client *models.Client) error {
|
|||||||
func (d *Database) DeleteClient(id int) error {
|
func (d *Database) DeleteClient(id int) error {
|
||||||
_ = d.RevokeAllUserTokens(id, "client")
|
_ = d.RevokeAllUserTokens(id, "client")
|
||||||
|
|
||||||
result := d.GDB.Exec(`DELETE FROM clients WHERE id = ?`, id)
|
result := d.GDB.Delete(&models.Client{}, id)
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return fmt.Errorf("erreur lors de la suppression du client: %w", result.Error)
|
return fmt.Errorf("erreur lors de la suppression du client: %w", result.Error)
|
||||||
}
|
}
|
||||||
@@ -160,7 +164,7 @@ func (d *Database) DeleteClient(id int) error {
|
|||||||
|
|
||||||
// UpdateClientPassword met à jour le mot de passe d'un client
|
// UpdateClientPassword met à jour le mot de passe d'un client
|
||||||
func (d *Database) UpdateClientPassword(clientID int, hashedPassword string) error {
|
func (d *Database) UpdateClientPassword(clientID int, hashedPassword string) error {
|
||||||
result := d.GDB.Exec(`UPDATE clients SET password = ? WHERE id = ?`, hashedPassword, clientID)
|
result := d.GDB.Model(&models.Client{}).Where("id = ?", clientID).Update("password", hashedPassword)
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return fmt.Errorf("erreur lors de la mise à jour du mot de passe: %w", result.Error)
|
return fmt.Errorf("erreur lors de la mise à jour du mot de passe: %w", result.Error)
|
||||||
}
|
}
|
||||||
@@ -173,9 +177,10 @@ func (d *Database) UpdateClientPassword(clientID int, hashedPassword string) err
|
|||||||
|
|
||||||
// UpdateClientPasswordAndClearFlag met à jour le mot de passe et remet must_change_password à false
|
// UpdateClientPasswordAndClearFlag met à jour le mot de passe et remet must_change_password à false
|
||||||
func (d *Database) UpdateClientPasswordAndClearFlag(clientID int, hashedPassword string) error {
|
func (d *Database) UpdateClientPasswordAndClearFlag(clientID int, hashedPassword string) error {
|
||||||
result := d.GDB.Exec(`
|
result := d.GDB.Model(&models.Client{}).Where("id = ?", clientID).Updates(map[string]any{
|
||||||
UPDATE clients SET password = ?, must_change_password = FALSE, updated_at = CURRENT_TIMESTAMP
|
"password": hashedPassword,
|
||||||
WHERE id = ?`, hashedPassword, clientID)
|
"must_change_password": false,
|
||||||
|
})
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return fmt.Errorf("erreur lors de la mise à jour du mot de passe: %w", result.Error)
|
return fmt.Errorf("erreur lors de la mise à jour du mot de passe: %w", result.Error)
|
||||||
}
|
}
|
||||||
@@ -254,9 +259,7 @@ func (d *Database) PayClientPenalties(username string, amountPaid float64) error
|
|||||||
return fmt.Errorf("montant insuffisant: %.2f payé, %.2f requis", amountPaid, currentAmount)
|
return fmt.Errorf("montant insuffisant: %.2f payé, %.2f requis", amountPaid, currentAmount)
|
||||||
}
|
}
|
||||||
|
|
||||||
result := d.GDB.Exec(`
|
result := d.GDB.Model(&models.Client{}).Where("username = ?", username).Update("amende", 0.0)
|
||||||
UPDATE clients SET amende = 0, updated_at = CURRENT_TIMESTAMP
|
|
||||||
WHERE username = ?`, username)
|
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
log.Printf("❌ [PayClientPenalties] Erreur UPDATE: %v", result.Error)
|
log.Printf("❌ [PayClientPenalties] Erreur UPDATE: %v", result.Error)
|
||||||
return fmt.Errorf("erreur paiement pénalités: %w", result.Error)
|
return fmt.Errorf("erreur paiement pénalités: %w", result.Error)
|
||||||
@@ -273,7 +276,7 @@ func (d *Database) PayClientPenalties(username string, amountPaid float64) error
|
|||||||
|
|
||||||
// IncrementClientCommandCount incrémente le compteur de commandes du client
|
// IncrementClientCommandCount incrémente le compteur de commandes du client
|
||||||
func (d *Database) IncrementClientCommandCount(username string) error {
|
func (d *Database) IncrementClientCommandCount(username string) error {
|
||||||
result := d.GDB.Exec(`UPDATE clients SET command = command + 1 WHERE username = ?`, username)
|
result := d.GDB.Model(&models.Client{}).Where("username = ?", username).UpdateColumn("command", gorm.Expr("command + 1"))
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return fmt.Errorf("erreur lors de l'incrémentation du compteur: %w", result.Error)
|
return fmt.Errorf("erreur lors de l'incrémentation du compteur: %w", result.Error)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -212,6 +212,7 @@ func (d *Database) GetCommandItems(commandID int) ([]map[string]interface{}, err
|
|||||||
LivreurAssign *string `gorm:"column:livreur_assign"`
|
LivreurAssign *string `gorm:"column:livreur_assign"`
|
||||||
CommandCreatedAt *time.Time `gorm:"column:command_created_at"`
|
CommandCreatedAt *time.Time `gorm:"column:command_created_at"`
|
||||||
Category string `gorm:"column:category"`
|
Category string `gorm:"column:category"`
|
||||||
|
ClientOrderNumber int `gorm:"column:client_order_number"`
|
||||||
}
|
}
|
||||||
|
|
||||||
err := d.GDB.Raw(`
|
err := d.GDB.Raw(`
|
||||||
@@ -236,7 +237,8 @@ func (d *Database) GetCommandItems(commandID int) ([]map[string]interface{}, err
|
|||||||
c.referral_used,
|
c.referral_used,
|
||||||
c.livreur_assign,
|
c.livreur_assign,
|
||||||
c.created_at as command_created_at,
|
c.created_at as command_created_at,
|
||||||
p.category
|
p.category,
|
||||||
|
c.client_order_id as client_order_number
|
||||||
FROM command_items ci
|
FROM command_items ci
|
||||||
LEFT JOIN commandes c ON ci.command_id = c.id
|
LEFT JOIN commandes c ON ci.command_id = c.id
|
||||||
LEFT JOIN products p ON ci.product_id = p.id
|
LEFT JOIN products p ON ci.product_id = p.id
|
||||||
@@ -282,6 +284,7 @@ func (d *Database) GetCommandItems(commandID int) ([]map[string]interface{}, err
|
|||||||
"livreur_assign": ptrStr(row.LivreurAssign),
|
"livreur_assign": ptrStr(row.LivreurAssign),
|
||||||
"command_created_at": commandCreatedAt,
|
"command_created_at": commandCreatedAt,
|
||||||
"category": row.Category,
|
"category": row.Category,
|
||||||
|
"client_order_number": row.ClientOrderNumber,
|
||||||
}
|
}
|
||||||
items = append(items, item)
|
items = append(items, item)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,10 +52,9 @@ type basketItem struct {
|
|||||||
|
|
||||||
func (d *Database) fetchBasketItems(username string) ([]basketItem, float64, error) {
|
func (d *Database) fetchBasketItems(username string) ([]basketItem, float64, error) {
|
||||||
var items []basketItem
|
var items []basketItem
|
||||||
if err := d.GDB.Raw(`SELECT product_id, quantity, price FROM baskets WHERE username = ?`, username).Scan(&items).Error; err != nil {
|
if err := d.GDB.Table("baskets").Select("product_id, quantity, price").Where("username = ?", username).Scan(&items).Error; err != nil {
|
||||||
return nil, 0, fmt.Errorf("erreur récupération panier: %w", err)
|
return nil, 0, fmt.Errorf("erreur récupération panier: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
total := 0.0
|
total := 0.0
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
total += item.Price
|
total += item.Price
|
||||||
@@ -84,12 +83,10 @@ func validateCommandStatus(status string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Database) CreateCommand(username string) (*models.Command, error) {
|
func (d *Database) CreateCommand(username string) (*models.Command, error) {
|
||||||
var addrResult struct {
|
|
||||||
Username string `gorm:"column:username"`
|
|
||||||
}
|
|
||||||
adresse := "Adresse non spécifiée"
|
adresse := "Adresse non spécifiée"
|
||||||
if err := d.GDB.Raw(`SELECT username FROM clients WHERE username = ?`, username).Scan(&addrResult).Error; err == nil && addrResult.Username != "" {
|
var clientCheck models.Client
|
||||||
adresse = addrResult.Username
|
if err := d.GDB.Select("username").Where("username = ?", username).First(&clientCheck).Error; err == nil && clientCheck.Username != "" {
|
||||||
|
adresse = clientCheck.Username
|
||||||
}
|
}
|
||||||
|
|
||||||
basketItems, totalPrix, err := d.fetchBasketItems(username)
|
basketItems, totalPrix, err := d.fetchBasketItems(username)
|
||||||
@@ -103,13 +100,14 @@ func (d *Database) CreateCommand(username string) (*models.Command, error) {
|
|||||||
|
|
||||||
var cmdResult struct {
|
var cmdResult struct {
|
||||||
ID int `gorm:"column:id"`
|
ID int `gorm:"column:id"`
|
||||||
|
ClientOrderID int `gorm:"column:client_order_id"`
|
||||||
CreatedAt time.Time `gorm:"column:created_at"`
|
CreatedAt time.Time `gorm:"column:created_at"`
|
||||||
UpdatedAt time.Time `gorm:"column:updated_at"`
|
UpdatedAt time.Time `gorm:"column:updated_at"`
|
||||||
}
|
}
|
||||||
err = d.GDB.Raw(`
|
err = d.GDB.Raw(`
|
||||||
INSERT INTO commandes (username, status, adresse, total_prix, client_order_id, created_at, updated_at)
|
INSERT INTO commandes (username, status, adresse, total_prix, client_order_id, created_at, updated_at)
|
||||||
VALUES (?, ?, ?, ?, (SELECT COALESCE(MAX(client_order_id), 0) + 1 FROM commandes WHERE username = ?), CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
VALUES (?, ?, ?, ?, (SELECT COALESCE(MAX(client_order_id), 0) + 1 FROM commandes WHERE username = ?), CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||||
RETURNING id, created_at, updated_at`,
|
RETURNING id, client_order_id, created_at, updated_at`,
|
||||||
username, "pending", adresse, totalPrix, username).Scan(&cmdResult).Error
|
username, "pending", adresse, totalPrix, username).Scan(&cmdResult).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("erreur lors de la création de la commande: %w", err)
|
return nil, fmt.Errorf("erreur lors de la création de la commande: %w", err)
|
||||||
@@ -123,12 +121,17 @@ func (d *Database) CreateCommand(username string) (*models.Command, error) {
|
|||||||
productName = "Produit inconnu"
|
productName = "Produit inconnu"
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := d.GDB.Exec(`
|
cmdItem := models.CommandItem{
|
||||||
INSERT INTO command_items (command_id, produit, product_id, quantite, prix)
|
CommandID: commandID,
|
||||||
VALUES (?, ?, ?, ?, ?)`,
|
Produit: productName,
|
||||||
commandID, productName, item.ProductID, item.Quantity, item.Price).Error; err != nil {
|
ProductID: item.ProductID,
|
||||||
|
Quantity: item.Quantity,
|
||||||
|
Price: item.Price,
|
||||||
|
}
|
||||||
|
if err := d.GDB.Create(&cmdItem).Error; err != nil {
|
||||||
return nil, fmt.Errorf("erreur lors de l'insertion des items: %w", err)
|
return nil, fmt.Errorf("erreur lors de l'insertion des items: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := d.GDB.Exec(`DELETE FROM baskets WHERE username = ?`, username).Error; err != nil {
|
if err := d.GDB.Exec(`DELETE FROM baskets WHERE username = ?`, username).Error; err != nil {
|
||||||
@@ -137,6 +140,7 @@ func (d *Database) CreateCommand(username string) (*models.Command, error) {
|
|||||||
|
|
||||||
command := &models.Command{
|
command := &models.Command{
|
||||||
ID: commandID,
|
ID: commandID,
|
||||||
|
ClientOrderID: cmdResult.ClientOrderID,
|
||||||
Status: "pending",
|
Status: "pending",
|
||||||
Total: totalPrix,
|
Total: totalPrix,
|
||||||
}
|
}
|
||||||
@@ -189,13 +193,14 @@ func (d *Database) CreateCommandWithAddress(username, deliveryAddress string) (*
|
|||||||
|
|
||||||
var cmdResult struct {
|
var cmdResult struct {
|
||||||
ID int `gorm:"column:id"`
|
ID int `gorm:"column:id"`
|
||||||
|
ClientOrderID int `gorm:"column:client_order_id"`
|
||||||
CreatedAt time.Time `gorm:"column:created_at"`
|
CreatedAt time.Time `gorm:"column:created_at"`
|
||||||
UpdatedAt time.Time `gorm:"column:updated_at"`
|
UpdatedAt time.Time `gorm:"column:updated_at"`
|
||||||
}
|
}
|
||||||
err = d.GDB.Raw(`
|
err = d.GDB.Raw(`
|
||||||
INSERT INTO commandes (username, status, adresse, total_prix, client_order_id, created_at, updated_at)
|
INSERT INTO commandes (username, status, adresse, total_prix, client_order_id, created_at, updated_at)
|
||||||
VALUES (?, ?, ?, ?, (SELECT COALESCE(MAX(client_order_id), 0) + 1 FROM commandes WHERE username = ?), CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
VALUES (?, ?, ?, ?, (SELECT COALESCE(MAX(client_order_id), 0) + 1 FROM commandes WHERE username = ?), CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||||
RETURNING id, created_at, updated_at`,
|
RETURNING id, client_order_id, created_at, updated_at`,
|
||||||
username, "pending", deliveryAddress, totalPrix, username).Scan(&cmdResult).Error
|
username, "pending", deliveryAddress, totalPrix, username).Scan(&cmdResult).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("erreur création commande: %w", err)
|
return nil, fmt.Errorf("erreur création commande: %w", err)
|
||||||
@@ -226,7 +231,7 @@ func (d *Database) CreateCommandWithAddress(username, deliveryAddress string) (*
|
|||||||
return nil, fmt.Errorf("erreur insertion items: %w", err)
|
return nil, fmt.Errorf("erreur insertion items: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
result := d.GDB.Exec(`UPDATE products SET stock = stock - ? WHERE id = ? AND stock >= ?`, item.Quantity, item.ProductID, item.Quantity)
|
result := d.GDB.Model(&models.Product{}).Where("id = ? AND stock >= ?", item.ProductID, item.Quantity).UpdateColumn("stock", gorm.Expr("stock - ?", item.Quantity))
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
log.Printf("⚠️ Erreur décrémentation stock produit %d: %v", item.ProductID, result.Error)
|
log.Printf("⚠️ Erreur décrémentation stock produit %d: %v", item.ProductID, result.Error)
|
||||||
return nil, fmt.Errorf("erreur mise à jour stock: %w", result.Error)
|
return nil, fmt.Errorf("erreur mise à jour stock: %w", result.Error)
|
||||||
@@ -236,7 +241,7 @@ func (d *Database) CreateCommandWithAddress(username, deliveryAddress string) (*
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := d.GDB.Exec(`DELETE FROM baskets WHERE username = ?`, username).Error; err != nil {
|
if err := d.GDB.Delete(&models.Panier{}, "username = ?", username).Error; err != nil {
|
||||||
log.Printf("⚠️ Erreur vidage panier: %v", err)
|
log.Printf("⚠️ Erreur vidage panier: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,6 +253,7 @@ func (d *Database) CreateCommandWithAddress(username, deliveryAddress string) (*
|
|||||||
|
|
||||||
command := &models.Command{
|
command := &models.Command{
|
||||||
ID: commandID,
|
ID: commandID,
|
||||||
|
ClientOrderID: cmdResult.ClientOrderID,
|
||||||
Username: username,
|
Username: username,
|
||||||
Status: "pending",
|
Status: "pending",
|
||||||
Total: totalPrix,
|
Total: totalPrix,
|
||||||
@@ -272,29 +278,6 @@ func (d *Database) GetAllCommands(status, username string) ([]map[string]any, er
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
query := `SELECT c.id, c.username, c.status, c.adresse, c.total_prix,
|
|
||||||
c.livreur_assign, c.created_at, c.updated_at,
|
|
||||||
c.proposed_address, c.address_proposal_status,
|
|
||||||
c.client_order_id AS client_order_number
|
|
||||||
FROM commandes c
|
|
||||||
WHERE 1=1`
|
|
||||||
|
|
||||||
args := []interface{}{}
|
|
||||||
|
|
||||||
if status == "" {
|
|
||||||
query += ` AND c.status IN ('pending', 'assigned', 'en_route', 'arrived', 'livre')`
|
|
||||||
} else {
|
|
||||||
query += ` AND c.status = ?`
|
|
||||||
args = append(args, status)
|
|
||||||
}
|
|
||||||
|
|
||||||
if username != "" {
|
|
||||||
query += ` AND c.username = ?`
|
|
||||||
args = append(args, username)
|
|
||||||
}
|
|
||||||
|
|
||||||
query += " ORDER BY c.created_at DESC LIMIT 1000"
|
|
||||||
|
|
||||||
var rows []struct {
|
var rows []struct {
|
||||||
ID int `gorm:"column:id"`
|
ID int `gorm:"column:id"`
|
||||||
Username string `gorm:"column:username"`
|
Username string `gorm:"column:username"`
|
||||||
@@ -309,7 +292,23 @@ func (d *Database) GetAllCommands(status, username string) ([]map[string]any, er
|
|||||||
ClientOrderNumber int `gorm:"column:client_order_number"`
|
ClientOrderNumber int `gorm:"column:client_order_number"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := d.GDB.Raw(query, args...).Scan(&rows).Error; err != nil {
|
gdb := d.GDB.Table("commandes c").
|
||||||
|
Select(`c.id, c.username, c.status, c.adresse, c.total_prix,
|
||||||
|
c.livreur_assign, c.created_at, c.updated_at,
|
||||||
|
c.proposed_address, c.address_proposal_status,
|
||||||
|
c.client_order_id AS client_order_number`)
|
||||||
|
|
||||||
|
if status == "" {
|
||||||
|
gdb = gdb.Where("c.status IN ?", []string{"pending", "assigned", "en_route", "arrived", "livre"})
|
||||||
|
} else {
|
||||||
|
gdb = gdb.Where("c.status = ?", status)
|
||||||
|
}
|
||||||
|
|
||||||
|
if username != "" {
|
||||||
|
gdb = gdb.Where("c.username = ?", username)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := gdb.Order("c.created_at DESC").Limit(1000).Scan(&rows).Error; err != nil {
|
||||||
return nil, fmt.Errorf("erreur lors de la récupération des commandes: %w", err)
|
return nil, fmt.Errorf("erreur lors de la récupération des commandes: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,14 +345,11 @@ func (d *Database) GetAllCommands(status, username string) ([]map[string]any, er
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Database) GetCommandCount() (int, error) {
|
func (d *Database) GetCommandCount() (int, error) {
|
||||||
var result struct {
|
var count int64
|
||||||
Count int `gorm:"column:count"`
|
if err := d.GDB.Model(&models.Command{}).Count(&count).Error; err != nil {
|
||||||
}
|
|
||||||
err := d.GDB.Raw("SELECT COUNT(*) as count FROM commandes").Scan(&result).Error
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("erreur récupération count commandes: %w", err)
|
return 0, fmt.Errorf("erreur récupération count commandes: %w", err)
|
||||||
}
|
}
|
||||||
return result.Count, nil
|
return int(count), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Database) SetCommandReferralUsed(commandID int, amount float64) error {
|
func (d *Database) SetCommandReferralUsed(commandID int, amount float64) error {
|
||||||
@@ -374,14 +370,16 @@ func (d *Database) GetCommandByID(id int) (map[string]any, error) {
|
|||||||
AddressProposalStatus string `gorm:"column:address_proposal_status"`
|
AddressProposalStatus string `gorm:"column:address_proposal_status"`
|
||||||
ReferralUsed float64 `gorm:"column:referral_used"`
|
ReferralUsed float64 `gorm:"column:referral_used"`
|
||||||
ClientOrderNumber int `gorm:"column:client_order_number"`
|
ClientOrderNumber int `gorm:"column:client_order_number"`
|
||||||
|
CancelReason string `gorm:"column:cancel_reason"`
|
||||||
}
|
}
|
||||||
|
|
||||||
err := d.GDB.Raw(`
|
if err := d.GDB.Table("commandes c").
|
||||||
SELECT c.id, c.username, c.status, c.adresse, c.total_prix, c.livreur_assign, c.created_at, c.updated_at,
|
Select(`c.id, c.username, c.status, c.adresse, c.total_prix, c.livreur_assign,
|
||||||
c.proposed_address, c.address_proposal_status, c.referral_used,
|
c.created_at, c.updated_at, c.proposed_address, c.address_proposal_status,
|
||||||
c.client_order_id AS client_order_number
|
c.referral_used, c.client_order_id AS client_order_number,
|
||||||
FROM commandes c WHERE c.id = ?`, id).Scan(&row).Error
|
COALESCE(c.cancel_reason, '') AS cancel_reason`).
|
||||||
if err != nil {
|
Where("c.id = ?", id).
|
||||||
|
First(&row).Error; err != nil {
|
||||||
return nil, fmt.Errorf("erreur lors de la récupération de la commande: %w", err)
|
return nil, fmt.Errorf("erreur lors de la récupération de la commande: %w", err)
|
||||||
}
|
}
|
||||||
if row.ID == 0 {
|
if row.ID == 0 {
|
||||||
@@ -399,6 +397,7 @@ func (d *Database) GetCommandByID(id int) (map[string]any, error) {
|
|||||||
"address_proposal_status": row.AddressProposalStatus,
|
"address_proposal_status": row.AddressProposalStatus,
|
||||||
"referral_used": row.ReferralUsed,
|
"referral_used": row.ReferralUsed,
|
||||||
"client_order_number": row.ClientOrderNumber,
|
"client_order_number": row.ClientOrderNumber,
|
||||||
|
"cancel_reason": row.CancelReason,
|
||||||
}
|
}
|
||||||
|
|
||||||
if row.LivreurAssign != nil {
|
if row.LivreurAssign != nil {
|
||||||
@@ -416,12 +415,23 @@ func (d *Database) GetCommandByID(id int) (map[string]any, error) {
|
|||||||
return command, nil
|
return command, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetClientOrderID retourne le client_order_id (numéro perso du client) pour un commandID global.
|
||||||
|
// Retourne commandID en fallback si introuvable.
|
||||||
|
func (d *Database) GetClientOrderID(commandID int) int {
|
||||||
|
var result struct {
|
||||||
|
ClientOrderID int `gorm:"column:client_order_id"`
|
||||||
|
}
|
||||||
|
if err := d.GDB.Model(&models.Command{}).Select("client_order_id").Where("id = ?", commandID).First(&result).Error; err != nil || result.ClientOrderID == 0 {
|
||||||
|
return commandID
|
||||||
|
}
|
||||||
|
return result.ClientOrderID
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Database) GetCommandAddress(commandID int) (string, error) {
|
func (d *Database) GetCommandAddress(commandID int) (string, error) {
|
||||||
var result struct {
|
var result struct {
|
||||||
Adresse string `gorm:"column:adresse"`
|
Adresse string `gorm:"column:adresse"`
|
||||||
}
|
}
|
||||||
err := d.GDB.Raw(`SELECT adresse FROM commandes WHERE id = ?`, commandID).Scan(&result).Error
|
if err := d.GDB.Model(&models.Command{}).Select("adresse").Where("id = ?", commandID).First(&result).Error; err != nil {
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("erreur lors de la récupération de l'adresse: %w", err)
|
return "", fmt.Errorf("erreur lors de la récupération de l'adresse: %w", err)
|
||||||
}
|
}
|
||||||
if result.Adresse == "" {
|
if result.Adresse == "" {
|
||||||
@@ -439,9 +449,10 @@ func (d *Database) UpdateCommandAddress(commandID int, deliveryAddress string) e
|
|||||||
return fmt.Errorf("adresse vide non autorisée")
|
return fmt.Errorf("adresse vide non autorisée")
|
||||||
}
|
}
|
||||||
|
|
||||||
result := d.GDB.Exec(`
|
result := d.GDB.Model(&models.Command{}).Where("id = ?", commandID).Updates(map[string]any{
|
||||||
UPDATE commandes SET adresse = ?, updated_at = CURRENT_TIMESTAMP
|
"adresse": deliveryAddress,
|
||||||
WHERE id = ?`, deliveryAddress, commandID)
|
"updated_at": time.Now(),
|
||||||
|
})
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return fmt.Errorf("erreur lors de la mise à jour de l'adresse: %w", result.Error)
|
return fmt.Errorf("erreur lors de la mise à jour de l'adresse: %w", result.Error)
|
||||||
}
|
}
|
||||||
@@ -459,10 +470,11 @@ func (d *Database) ProposeAddressChange(commandID int, proposedAddress, proposed
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
result := d.GDB.Exec(`
|
result := d.GDB.Model(&models.Command{}).Where("id = ?", commandID).Updates(map[string]any{
|
||||||
UPDATE commandes
|
"proposed_address": proposedAddress,
|
||||||
SET proposed_address = ?, address_proposal_status = 'pending', updated_at = CURRENT_TIMESTAMP
|
"address_proposal_status": "pending",
|
||||||
WHERE id = ?`, proposedAddress, commandID)
|
"updated_at": time.Now(),
|
||||||
|
})
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return fmt.Errorf("erreur proposition adresse: %w", result.Error)
|
return fmt.Errorf("erreur proposition adresse: %w", result.Error)
|
||||||
}
|
}
|
||||||
@@ -519,7 +531,10 @@ func (d *Database) UpdateCommandStatus(commandID int, status string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
result := d.GDB.Exec(`UPDATE commandes SET status = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?`, status, commandID)
|
result := d.GDB.Model(&models.Command{}).Where("id = ?", commandID).Updates(map[string]any{
|
||||||
|
"status": status,
|
||||||
|
"updated_at": time.Now(),
|
||||||
|
})
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return fmt.Errorf("erreur lors de la mise à jour du statut: %w", result.Error)
|
return fmt.Errorf("erreur lors de la mise à jour du statut: %w", result.Error)
|
||||||
}
|
}
|
||||||
@@ -534,10 +549,12 @@ func (d *Database) AddCommandLog(commandID int, status, message, author string)
|
|||||||
sanitizedMessage := sanitizeLogMessage(message)
|
sanitizedMessage := sanitizeLogMessage(message)
|
||||||
sanitizedAuthor := sanitizeLogMessage(author)
|
sanitizedAuthor := sanitizeLogMessage(author)
|
||||||
|
|
||||||
if err := d.GDB.Exec(`
|
if err := d.GDB.Create(&models.CommandLog{
|
||||||
INSERT INTO command_logs (command_id, status, message, author, created_at)
|
CommandID: commandID,
|
||||||
VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)`,
|
Status: status,
|
||||||
commandID, status, sanitizedMessage, sanitizedAuthor).Error; err != nil {
|
Message: sanitizedMessage,
|
||||||
|
Author: sanitizedAuthor,
|
||||||
|
}).Error; err != nil {
|
||||||
log.Printf("⚠️ Avertissement: impossible d'ajouter le log (table command_logs peut-être manquante): %v", err)
|
log.Printf("⚠️ Avertissement: impossible d'ajouter le log (table command_logs peut-être manquante): %v", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -547,22 +564,9 @@ func (d *Database) AddCommandLog(commandID int, status, message, author string)
|
|||||||
|
|
||||||
// GetCommandLogs récupère tous les logs d'une commande
|
// GetCommandLogs récupère tous les logs d'une commande
|
||||||
func (d *Database) GetCommandLogs(commandID int) ([]map[string]any, error) {
|
func (d *Database) GetCommandLogs(commandID int) ([]map[string]any, error) {
|
||||||
var rows []struct {
|
var rows []models.CommandLog
|
||||||
ID int `gorm:"column:id"`
|
|
||||||
CommandID int `gorm:"column:command_id"`
|
|
||||||
Status string `gorm:"column:status"`
|
|
||||||
Message string `gorm:"column:message"`
|
|
||||||
Author string `gorm:"column:author"`
|
|
||||||
CreatedAt time.Time `gorm:"column:created_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := d.GDB.Raw(`
|
if err := d.GDB.Where("command_id = ?", commandID).Order("created_at ASC").Find(&rows).Error; err != nil {
|
||||||
SELECT id, command_id, status, message, author, created_at
|
|
||||||
FROM command_logs
|
|
||||||
WHERE command_id = ?
|
|
||||||
ORDER BY created_at ASC`, commandID).Scan(&rows).Error
|
|
||||||
if err != nil {
|
|
||||||
// Si la table n'existe pas, retourner un tableau vide au lieu d'une erreur
|
|
||||||
log.Printf("⚠️ Avertissement: impossible de récupérer les logs: %v", err)
|
log.Printf("⚠️ Avertissement: impossible de récupérer les logs: %v", err)
|
||||||
return []map[string]any{}, nil
|
return []map[string]any{}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -187,6 +187,25 @@ func InitDB() *Database {
|
|||||||
log.Fatalf("❌ Erreur backfill commandes.client_order_id: %v", err)
|
log.Fatalf("❌ Erreur backfill commandes.client_order_id: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Migration: raison d'annulation par le client
|
||||||
|
if _, err = database.Exec(`ALTER TABLE commandes ADD COLUMN IF NOT EXISTS cancel_reason TEXT`); err != nil {
|
||||||
|
log.Fatalf("❌ Erreur migration commandes.cancel_reason: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Migration: coordonnées GPS de destination et du livreur
|
||||||
|
if _, err = database.Exec(`ALTER TABLE commandes ADD COLUMN IF NOT EXISTS dest_latitude DOUBLE PRECISION`); err != nil {
|
||||||
|
log.Fatalf("❌ Erreur migration commandes.dest_latitude: %v", err)
|
||||||
|
}
|
||||||
|
if _, err = database.Exec(`ALTER TABLE commandes ADD COLUMN IF NOT EXISTS dest_longitude DOUBLE PRECISION`); err != nil {
|
||||||
|
log.Fatalf("❌ Erreur migration commandes.dest_longitude: %v", err)
|
||||||
|
}
|
||||||
|
if _, err = database.Exec(`ALTER TABLE commandes ADD COLUMN IF NOT EXISTS livreur_latitude DOUBLE PRECISION`); err != nil {
|
||||||
|
log.Fatalf("❌ Erreur migration commandes.livreur_latitude: %v", err)
|
||||||
|
}
|
||||||
|
if _, err = database.Exec(`ALTER TABLE commandes ADD COLUMN IF NOT EXISTS livreur_longitude DOUBLE PRECISION`); err != nil {
|
||||||
|
log.Fatalf("❌ Erreur migration commandes.livreur_longitude: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Migration: table de suivi des paiements crypto
|
// Migration: table de suivi des paiements crypto
|
||||||
if _, err = database.Exec(`
|
if _, err = database.Exec(`
|
||||||
CREATE TABLE IF NOT EXISTS crypto_payments (
|
CREATE TABLE IF NOT EXISTS crypto_payments (
|
||||||
@@ -264,7 +283,6 @@ func (db *Database) createTables() error {
|
|||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
);`,
|
);`,
|
||||||
`ALTER TABLE clients ADD COLUMN IF NOT EXISTS referral_balance NUMERIC(10,2) DEFAULT 0.0;`,
|
|
||||||
|
|
||||||
// ============================
|
// ============================
|
||||||
// TABLE jwt_tokens
|
// TABLE jwt_tokens
|
||||||
@@ -423,7 +441,7 @@ func (db *Database) createTables() error {
|
|||||||
`CREATE INDEX IF NOT EXISTS idx_command_items_client_username ON command_items(client_username);`,
|
`CREATE INDEX IF NOT EXISTS idx_command_items_client_username ON command_items(client_username);`,
|
||||||
`CREATE INDEX IF NOT EXISTS idx_command_items_status ON command_items(status);`,
|
`CREATE INDEX IF NOT EXISTS idx_command_items_status ON command_items(status);`,
|
||||||
`CREATE INDEX IF NOT EXISTS idx_command_items_produit ON command_items(produit);`,
|
`CREATE INDEX IF NOT EXISTS idx_command_items_produit ON command_items(produit);`,
|
||||||
`CREATE INDEX IF NOT EXISTS idx_jwt_user_id ON jwt_tokens(user_id);`,
|
`CREATE INDEX IF NOT EXISTS idx_jwt_user_id ON jwt_tokens(user_id);`,
|
||||||
`CREATE INDEX IF NOT EXISTS idx_jwt_user_type ON jwt_tokens(user_type);`,
|
`CREATE INDEX IF NOT EXISTS idx_jwt_user_type ON jwt_tokens(user_type);`,
|
||||||
`CREATE INDEX IF NOT EXISTS idx_jwt_token ON jwt_tokens(token);`,
|
`CREATE INDEX IF NOT EXISTS idx_jwt_token ON jwt_tokens(token);`,
|
||||||
`CREATE INDEX IF NOT EXISTS idx_jwt_date_fin ON jwt_tokens(date_fin);`,
|
`CREATE INDEX IF NOT EXISTS idx_jwt_date_fin ON jwt_tokens(date_fin);`,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package db
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"gestion/models"
|
||||||
"gestion/services"
|
"gestion/services"
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
@@ -64,7 +65,7 @@ func (d *Database) NotifyAllAdminCabine(commandID int, clientUsername, deliveryA
|
|||||||
var users []struct {
|
var users []struct {
|
||||||
Username string `gorm:"column:username"`
|
Username string `gorm:"column:username"`
|
||||||
}
|
}
|
||||||
if err := d.GDB.Raw(`SELECT username FROM users WHERE role IN ('admin','cabine')`).Scan(&users).Error; err != nil {
|
if err := d.GDB.Model(&models.User{}).Select("username").Where("role IN ?", []string{"admin", "cabine"}).Scan(&users).Error; err != nil {
|
||||||
log.Printf("❌ [ADMIN_NOTIF] Erreur lecture users admin/cabine: %v", err)
|
log.Printf("❌ [ADMIN_NOTIF] Erreur lecture users admin/cabine: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -103,7 +104,7 @@ func (d *Database) NotifyAllAdminCabineAlert(alertID int, livreurUsername, alert
|
|||||||
var users []struct {
|
var users []struct {
|
||||||
Username string `gorm:"column:username"`
|
Username string `gorm:"column:username"`
|
||||||
}
|
}
|
||||||
if err := d.GDB.Raw(`SELECT username FROM users WHERE role IN ('admin','cabine')`).Scan(&users).Error; err != nil {
|
if err := d.GDB.Model(&models.User{}).Select("username").Where("role IN ?", []string{"admin", "cabine"}).Scan(&users).Error; err != nil {
|
||||||
log.Printf("❌ [ALERT_NOTIF] Erreur lecture users admin/cabine: %v", err)
|
log.Printf("❌ [ALERT_NOTIF] Erreur lecture users admin/cabine: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -136,18 +137,3 @@ func (d *Database) NotifyAllAdminCabineAlert(alertID int, livreurUsername, alert
|
|||||||
}
|
}
|
||||||
log.Printf("🚨 [ALERT_NOTIF] Notif Redis (%d users) pour alerte #%d de %s", count, alertID, livreurUsername)
|
log.Printf("🚨 [ALERT_NOTIF] Notif Redis (%d users) pour alerte #%d de %s", count, alertID, livreurUsername)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddDeliveryRating ajoute une note pour un livreur
|
|
||||||
func (d *Database) AddDeliveryRating(livreurUsername string, commandID, rating int, comment string) error {
|
|
||||||
err := d.GDB.Exec(`
|
|
||||||
INSERT INTO delivery_ratings (livreur_username, command_id, rating, comment, created_at)
|
|
||||||
VALUES (?, ?, ?, ?, NOW())`,
|
|
||||||
livreurUsername, commandID, rating, comment).Error
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("⚠️ Erreur sauvegarde note livreur: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("⭐ Note %d/5 ajoutée pour livreur %s (commande %d)", rating, livreurUsername, commandID)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package db
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"gestion/models"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
@@ -19,7 +20,9 @@ func (d *Database) CreditClientReferral(username string, amount float64) error {
|
|||||||
if amount <= 0 {
|
if amount <= 0 {
|
||||||
return fmt.Errorf("le montant doit être positif")
|
return fmt.Errorf("le montant doit être positif")
|
||||||
}
|
}
|
||||||
result := d.GDB.Exec(`UPDATE clients SET referral_balance = referral_balance + ? WHERE username = ?`, amount, username)
|
result := d.GDB.Model(&models.Client{}).Where("username = ?", username).Updates(map[string]any{
|
||||||
|
"referral_balance": gorm.Expr("referral_balance + ?", amount),
|
||||||
|
})
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return result.Error
|
return result.Error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import (
|
|||||||
"gestion/models"
|
"gestion/models"
|
||||||
"log"
|
"log"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetClientCancellationsCount récupère le nombre d'annulations tardives d'un client
|
// GetClientCancellationsCount récupère le nombre d'annulations tardives d'un client
|
||||||
@@ -17,7 +19,7 @@ func (d *Database) GetClientCancellationsCount(username string) (int, error) {
|
|||||||
var result struct {
|
var result struct {
|
||||||
Count int `gorm:"column:count"`
|
Count int `gorm:"column:count"`
|
||||||
}
|
}
|
||||||
err := d.GDB.Raw(`SELECT COALESCE(cancellations_count, 0) as count FROM clients WHERE username = ?`, username).Scan(&result).Error
|
err := d.GDB.Table("clients").Select("COALESCE(cancellations_count, 0) as count").Where("username = ?", username).Scan(&result).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("❌ [GetCancellationsCount] Erreur: %v", err)
|
log.Printf("❌ [GetCancellationsCount] Erreur: %v", err)
|
||||||
return 0, fmt.Errorf("erreur récupération compteur: %w", err)
|
return 0, fmt.Errorf("erreur récupération compteur: %w", err)
|
||||||
@@ -27,11 +29,9 @@ func (d *Database) GetClientCancellationsCount(username string) (int, error) {
|
|||||||
|
|
||||||
// IncrementClientCancellationsCount incrémente le compteur d'annulations
|
// IncrementClientCancellationsCount incrémente le compteur d'annulations
|
||||||
func (d *Database) IncrementClientCancellationsCount(username string) error {
|
func (d *Database) IncrementClientCancellationsCount(username string) error {
|
||||||
result := d.GDB.Exec(`
|
result := d.GDB.Model(&models.Client{}).Where("username = ?", username).Updates(map[string]any{
|
||||||
UPDATE clients
|
"cancellations_count": gorm.Expr("COALESCE(cancellations_count, 0) + 1"),
|
||||||
SET cancellations_count = COALESCE(cancellations_count, 0) + 1,
|
})
|
||||||
updated_at = CURRENT_TIMESTAMP
|
|
||||||
WHERE username = ?`, username)
|
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
log.Printf("❌ [IncrementCancellations] Erreur: %v", result.Error)
|
log.Printf("❌ [IncrementCancellations] Erreur: %v", result.Error)
|
||||||
return fmt.Errorf("erreur incrémentation: %w", result.Error)
|
return fmt.Errorf("erreur incrémentation: %w", result.Error)
|
||||||
@@ -98,11 +98,7 @@ func (d *Database) ApplyCancellationPenalty(username string) (int, error) {
|
|||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
result := d.GDB.Exec(`
|
result := d.GDB.Model(&models.Client{}).Where("username = ?", username).Update("amende", float64(penalty))
|
||||||
UPDATE clients
|
|
||||||
SET amende = ?,
|
|
||||||
updated_at = CURRENT_TIMESTAMP
|
|
||||||
WHERE username = ?`, float64(penalty), username)
|
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
log.Printf("❌ [ApplyCancellationPenalty] Erreur UPDATE: %v", result.Error)
|
log.Printf("❌ [ApplyCancellationPenalty] Erreur UPDATE: %v", result.Error)
|
||||||
return 0, fmt.Errorf("erreur application pénalité: %w", result.Error)
|
return 0, fmt.Errorf("erreur application pénalité: %w", result.Error)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"gestion/models"
|
"gestion/models"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
@@ -40,6 +41,7 @@ func DefaultSettings() models.AppSettings {
|
|||||||
},
|
},
|
||||||
PointsEnabled: true,
|
PointsEnabled: true,
|
||||||
ReferralEnabled: true,
|
ReferralEnabled: true,
|
||||||
|
ReferralAmount: 0,
|
||||||
PointsPools: []models.PointsPool{
|
PointsPools: []models.PointsPool{
|
||||||
{
|
{
|
||||||
Key: "pool_0",
|
Key: "pool_0",
|
||||||
@@ -90,7 +92,7 @@ func (d *Database) GetSettings() (models.AppSettings, error) {
|
|||||||
Key string `gorm:"column:key"`
|
Key string `gorm:"column:key"`
|
||||||
Value string `gorm:"column:value"`
|
Value string `gorm:"column:value"`
|
||||||
}
|
}
|
||||||
if err := d.GDB.Raw(`SELECT key, value FROM app_settings`).Scan(&rows).Error; err != nil {
|
if err := d.GDB.Table("app_settings").Select("key, value").Scan(&rows).Error; err != nil {
|
||||||
return settings, fmt.Errorf("erreur lecture settings: %w", err)
|
return settings, fmt.Errorf("erreur lecture settings: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,6 +111,10 @@ func (d *Database) GetSettings() (models.AppSettings, error) {
|
|||||||
}
|
}
|
||||||
case "referral_enabled":
|
case "referral_enabled":
|
||||||
settings.ReferralEnabled = row.Value == "true"
|
settings.ReferralEnabled = row.Value == "true"
|
||||||
|
case "referral_amount":
|
||||||
|
if v, err := strconv.ParseFloat(row.Value, 64); err == nil {
|
||||||
|
settings.ReferralAmount = v
|
||||||
|
}
|
||||||
case "crypto_payment_enabled":
|
case "crypto_payment_enabled":
|
||||||
settings.CryptoPaymentEnabled = row.Value == "true"
|
settings.CryptoPaymentEnabled = row.Value == "true"
|
||||||
case "crypto_only":
|
case "crypto_only":
|
||||||
@@ -207,6 +213,7 @@ func (d *Database) UpdateSettings(s models.AppSettings) error {
|
|||||||
{"points_enabled", boolStr(s.PointsEnabled)},
|
{"points_enabled", boolStr(s.PointsEnabled)},
|
||||||
{"points_pools", string(poolsJSON)},
|
{"points_pools", string(poolsJSON)},
|
||||||
{"referral_enabled", boolStr(s.ReferralEnabled)},
|
{"referral_enabled", boolStr(s.ReferralEnabled)},
|
||||||
|
{"referral_amount", strconv.FormatFloat(s.ReferralAmount, 'f', 2, 64)},
|
||||||
{"crypto_payment_enabled", boolStr(s.CryptoPaymentEnabled)},
|
{"crypto_payment_enabled", boolStr(s.CryptoPaymentEnabled)},
|
||||||
{"crypto_only", boolStr(s.CryptoOnly)},
|
{"crypto_only", boolStr(s.CryptoOnly)},
|
||||||
{"nowpayments_api_key", s.NowPaymentsAPIKey},
|
{"nowpayments_api_key", s.NowPaymentsAPIKey},
|
||||||
|
|||||||
@@ -67,15 +67,14 @@ func ValidateAndConsumeLinkToken(token string) (username, role string, err error
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Database) SaveClientTelegramChatID(username string, chatID int64) error {
|
func (d *Database) SaveClientTelegramChatID(username string, chatID int64) error {
|
||||||
return d.GDB.Exec(`UPDATE clients SET telegram_chat_id = ? WHERE username = ?`, chatID, username).Error
|
return d.GDB.Model(&models.Client{}).Where("username = ?", username).Update("telegram_chat_id", chatID).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Database) GetClientTelegramChatID(username string) (int64, bool, error) {
|
func (d *Database) GetClientTelegramChatID(username string) (int64, bool, error) {
|
||||||
var result struct {
|
var result struct {
|
||||||
ChatID *int64 `gorm:"column:telegram_chat_id"`
|
ChatID *int64 `gorm:"column:telegram_chat_id"`
|
||||||
}
|
}
|
||||||
err := d.GDB.Raw(`SELECT telegram_chat_id FROM clients WHERE username = ?`, username).Scan(&result).Error
|
if err := d.GDB.Table("clients").Select("telegram_chat_id").Where("username = ?", username).Limit(1).Scan(&result).Error; err != nil {
|
||||||
if err != nil {
|
|
||||||
return 0, false, err
|
return 0, false, err
|
||||||
}
|
}
|
||||||
if result.ChatID == nil {
|
if result.ChatID == nil {
|
||||||
@@ -85,19 +84,18 @@ func (d *Database) GetClientTelegramChatID(username string) (int64, bool, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Database) DeleteClientTelegramChatID(username string) error {
|
func (d *Database) DeleteClientTelegramChatID(username string) error {
|
||||||
return d.GDB.Exec(`UPDATE clients SET telegram_chat_id = NULL WHERE username = ?`, username).Error
|
return d.GDB.Model(&models.Client{}).Where("username = ?", username).Update("telegram_chat_id", nil).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Database) SaveUserTelegramChatID(username string, chatID int64) error {
|
func (d *Database) SaveUserTelegramChatID(username string, chatID int64) error {
|
||||||
return d.GDB.Exec(`UPDATE users SET telegram_chat_id = ? WHERE username = ?`, chatID, username).Error
|
return d.GDB.Model(&models.User{}).Where("username = ?", username).Update("telegram_chat_id", chatID).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Database) GetUserTelegramChatID(username string) (int64, bool, error) {
|
func (d *Database) GetUserTelegramChatID(username string) (int64, bool, error) {
|
||||||
var result struct {
|
var result struct {
|
||||||
ChatID *int64 `gorm:"column:telegram_chat_id"`
|
ChatID *int64 `gorm:"column:telegram_chat_id"`
|
||||||
}
|
}
|
||||||
err := d.GDB.Raw(`SELECT telegram_chat_id FROM users WHERE username = ?`, username).Scan(&result).Error
|
if err := d.GDB.Table("users").Select("telegram_chat_id").Where("username = ?", username).Limit(1).Scan(&result).Error; err != nil {
|
||||||
if err != nil {
|
|
||||||
return 0, false, err
|
return 0, false, err
|
||||||
}
|
}
|
||||||
if result.ChatID == nil {
|
if result.ChatID == nil {
|
||||||
@@ -107,7 +105,7 @@ func (d *Database) GetUserTelegramChatID(username string) (int64, bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Database) DeleteUserTelegramChatID(username string) error {
|
func (d *Database) DeleteUserTelegramChatID(username string) error {
|
||||||
return d.GDB.Exec(`UPDATE users SET telegram_chat_id = NULL WHERE username = ?`, username).Error
|
return d.GDB.Model(&models.User{}).Where("username = ?", username).Update("telegram_chat_id", nil).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserByTelegramChatID retrouve un utilisateur (clients + users) par chat_id
|
// GetUserByTelegramChatID retrouve un utilisateur (clients + users) par chat_id
|
||||||
@@ -115,7 +113,7 @@ func (d *Database) GetUserByTelegramChatID(chatID int64) (username, role string,
|
|||||||
var clientResult struct {
|
var clientResult struct {
|
||||||
Username string `gorm:"column:username"`
|
Username string `gorm:"column:username"`
|
||||||
}
|
}
|
||||||
if err = d.GDB.Raw(`SELECT username FROM clients WHERE telegram_chat_id = ?`, chatID).Scan(&clientResult).Error; err == nil && clientResult.Username != "" {
|
if err = d.GDB.Table("clients").Select("username").Where("telegram_chat_id = ?", chatID).Limit(1).Scan(&clientResult).Error; err == nil && clientResult.Username != "" {
|
||||||
return clientResult.Username, "client", nil
|
return clientResult.Username, "client", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +121,7 @@ func (d *Database) GetUserByTelegramChatID(chatID int64) (username, role string,
|
|||||||
Username string `gorm:"column:username"`
|
Username string `gorm:"column:username"`
|
||||||
Role string `gorm:"column:role"`
|
Role string `gorm:"column:role"`
|
||||||
}
|
}
|
||||||
if err = d.GDB.Raw(`SELECT username, role FROM users WHERE telegram_chat_id = ?`, chatID).Scan(&userResult).Error; err == nil && userResult.Username != "" {
|
if err = d.GDB.Model(&models.User{}).Select("username, role").Where("telegram_chat_id = ?", chatID).Limit(1).Scan(&userResult).Error; err == nil && userResult.Username != "" {
|
||||||
return userResult.Username, userResult.Role, nil
|
return userResult.Username, userResult.Role, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ func (d *Database) ProcessScheduledNotifications() error {
|
|||||||
|
|
||||||
// SendETANotification envoie une notification ETA
|
// SendETANotification envoie une notification ETA
|
||||||
func (d *Database) SendETANotification(commandID int, notifType string) {
|
func (d *Database) SendETANotification(commandID int, notifType string) {
|
||||||
message := fmt.Sprintf("Votre commande #%d arrive dans %s", commandID, notifType)
|
message := fmt.Sprintf("Votre commande #%d arrive dans %s", d.GetClientOrderID(commandID), notifType)
|
||||||
|
|
||||||
channel := fmt.Sprintf("notifications:command:%d", commandID)
|
channel := fmt.Sprintf("notifications:command:%d", commandID)
|
||||||
Redis.Publish(RedisCtx, channel, message)
|
Redis.Publish(RedisCtx, channel, message)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package db
|
|||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
func extractCommandID(member interface{}) int {
|
func extractCommandID(member any) int {
|
||||||
switch v := member.(type) {
|
switch v := member.(type) {
|
||||||
case int:
|
case int:
|
||||||
return v
|
return v
|
||||||
|
|||||||
@@ -70,7 +70,6 @@ func (d *Database) AssignCommandToDeliverymanQueue(commandID int, deliveryman st
|
|||||||
|
|
||||||
d.UpdateCommandStatus(commandID, "assigned")
|
d.UpdateCommandStatus(commandID, "assigned")
|
||||||
d.AssignDeliveryPerson(commandID, deliveryman)
|
d.AssignDeliveryPerson(commandID, deliveryman)
|
||||||
// ✅ MODIFIÉ: Position dans la queue pour info seulement
|
|
||||||
d.SetCommandETAWithDetails(commandID, totalETA, int(currentQueueSize)+1)
|
d.SetCommandETAWithDetails(commandID, totalETA, int(currentQueueSize)+1)
|
||||||
|
|
||||||
limitInfo := ""
|
limitInfo := ""
|
||||||
@@ -93,10 +92,8 @@ func (d *Database) AssignCommandToDeliverymanQueueUnlimited(deliveryman string,
|
|||||||
queueKey := fmt.Sprintf("queue:deliveryman:%s", deliveryman)
|
queueKey := fmt.Sprintf("queue:deliveryman:%s", deliveryman)
|
||||||
currentQueueSize, _ := Redis.ZCard(RedisCtx, queueKey).Result()
|
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)
|
travelTime := d.CalculateETAForDeliveryman(deliveryman, queueItem.Lat, queueItem.Lng)
|
||||||
|
|
||||||
// ✅ ETA = temps de trajet direct uniquement
|
|
||||||
queueItem.EstimatedETA = travelTime
|
queueItem.EstimatedETA = travelTime
|
||||||
|
|
||||||
err := d.AddToDeliverymanQueue(deliveryman, queueItem)
|
err := d.AddToDeliverymanQueue(deliveryman, queueItem)
|
||||||
|
|||||||
@@ -216,17 +216,17 @@ func (d *Database) StartQueueCleanupScheduler() {
|
|||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
// GetQueueValidationReport génère un rapport de validation sans supprimer
|
// GetQueueValidationReport génère un rapport de validation sans supprimer
|
||||||
func (d *Database) GetQueueValidationReport() (map[string]interface{}, error) {
|
func (d *Database) GetQueueValidationReport() (map[string]any, error) {
|
||||||
keys, err := Redis.Keys(RedisCtx, "queue:pending:*").Result()
|
keys, err := Redis.Keys(RedisCtx, "queue:pending:*").Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
report := map[string]interface{}{
|
report := map[string]any{
|
||||||
"total_commands": len(keys),
|
"total_commands": len(keys),
|
||||||
"valid_commands": 0,
|
"valid_commands": 0,
|
||||||
"invalid_commands": 0,
|
"invalid_commands": 0,
|
||||||
"invalid_details": []map[string]interface{}{},
|
"invalid_details": []map[string]any{},
|
||||||
"validation_results": []string{},
|
"validation_results": []string{},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,7 +259,7 @@ func (d *Database) GetQueueValidationReport() (map[string]interface{}, error) {
|
|||||||
|
|
||||||
if len(issues) > 0 {
|
if len(issues) > 0 {
|
||||||
report["invalid_commands"] = report["invalid_commands"].(int) + 1
|
report["invalid_commands"] = report["invalid_commands"].(int) + 1
|
||||||
report["invalid_details"] = append(report["invalid_details"].([]map[string]interface{}), map[string]interface{}{
|
report["invalid_details"] = append(report["invalid_details"].([]map[string]any), map[string]any{
|
||||||
"command_id": queueItem.CommandID,
|
"command_id": queueItem.CommandID,
|
||||||
"issues": issues,
|
"issues": issues,
|
||||||
"data": queueItem,
|
"data": queueItem,
|
||||||
|
|||||||
@@ -11,10 +11,6 @@ import (
|
|||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// 🆕 GESTION AUTOMATIQUE DU STATUT BUSY
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
// UpdateDeliverymanStatusBasedOnQueue met à jour automatiquement le statut
|
// UpdateDeliverymanStatusBasedOnQueue met à jour automatiquement le statut
|
||||||
func (d *Database) UpdateDeliverymanStatusBasedOnQueue(deliveryman string) error {
|
func (d *Database) UpdateDeliverymanStatusBasedOnQueue(deliveryman string) error {
|
||||||
queueKey := fmt.Sprintf("queue:deliveryman:%s", deliveryman)
|
queueKey := fmt.Sprintf("queue:deliveryman:%s", deliveryman)
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ require (
|
|||||||
github.com/lib/pq v1.10.9
|
github.com/lib/pq v1.10.9
|
||||||
github.com/redis/go-redis/v9 v9.17.0
|
github.com/redis/go-redis/v9 v9.17.0
|
||||||
golang.org/x/crypto v0.40.0
|
golang.org/x/crypto v0.40.0
|
||||||
|
gorm.io/driver/postgres v1.6.0
|
||||||
|
gorm.io/gorm v1.31.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -56,6 +58,4 @@ require (
|
|||||||
golang.org/x/text v0.27.0 // indirect
|
golang.org/x/text v0.27.0 // indirect
|
||||||
golang.org/x/tools v0.34.0 // indirect
|
golang.org/x/tools v0.34.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.9 // indirect
|
google.golang.org/protobuf v1.36.9 // indirect
|
||||||
gorm.io/driver/postgres v1.6.0 // indirect
|
|
||||||
gorm.io/gorm v1.31.1 // indirect
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package handlers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"gestion/db"
|
"gestion/db"
|
||||||
|
"gestion/utils"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -21,12 +22,12 @@ func AddAddress(c *gin.Context) {
|
|||||||
InvalidAddress string `json:"invalid_address" binding:"required"`
|
InvalidAddress string `json:"invalid_address" binding:"required"`
|
||||||
}
|
}
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
utils.BindErr(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := database.AddAddress(req.CorrectAddress, req.InvalidAddress); err != nil {
|
if err := database.AddAddress(req.CorrectAddress, req.InvalidAddress); err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
utils.ServerErr(c, "Impossible d'ajouter l'adresse", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,12 +47,12 @@ func DeleteAddress(c *gin.Context) {
|
|||||||
InvalidAddress string `json:"invalid_address" binding:"required"`
|
InvalidAddress string `json:"invalid_address" binding:"required"`
|
||||||
}
|
}
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
utils.BindErr(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := database.DeleteAddress(req.InvalidAddress, req.CorrectAddress); err != nil {
|
if err := database.DeleteAddress(req.InvalidAddress, req.CorrectAddress); err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
utils.ServerErr(c, "Impossible de supprimer l'adresse", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +69,7 @@ func GetAllAddress(c *gin.Context) {
|
|||||||
|
|
||||||
getAddress, err := database.AllAddress()
|
getAddress, err := database.AllAddress()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
utils.ServerErr(c, "Impossible de récupérer les adresses", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package handlers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"gestion/db"
|
"gestion/db"
|
||||||
|
"gestion/utils"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
@@ -32,14 +33,13 @@ func AlertPolice(c *gin.Context) {
|
|||||||
usernameStr := username.(string)
|
usernameStr := username.(string)
|
||||||
alert, err := database.CreateAlert(usernameStr, req.Message)
|
alert, err := database.CreateAlert(usernameStr, req.Message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
utils.ServerErr(c, "Impossible de créer l'alerte", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notifier tous les admins/cabines en temps réel
|
|
||||||
go database.NotifyAllAdminCabineAlert(alert.ID, usernameStr, req.Message)
|
go database.NotifyAllAdminCabineAlert(alert.ID, usernameStr, req.Message)
|
||||||
|
|
||||||
c.JSON(200, gin.H{
|
c.JSON(http.StatusCreated, gin.H{
|
||||||
"success": true,
|
"success": true,
|
||||||
"message": "Police alert created",
|
"message": "Police alert created",
|
||||||
"alert_id": alert.ID,
|
"alert_id": alert.ID,
|
||||||
@@ -61,13 +61,12 @@ func DeleteAlert(c *gin.Context) {
|
|||||||
c.JSON(http.StatusForbidden, gin.H{"error": "Accès réservé aux livreurs"})
|
c.JSON(http.StatusForbidden, gin.H{"error": "Accès réservé aux livreurs"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = database.DeleteAlertPolicy(alertID)
|
if err = database.DeleteAlertPolicy(alertID); err != nil {
|
||||||
if err != nil {
|
utils.ServerErr(c, "Impossible de supprimer l'alerte", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(200, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"success": true,
|
"success": true,
|
||||||
"message": "Alert deleted",
|
"message": "Alert deleted",
|
||||||
})
|
})
|
||||||
@@ -89,11 +88,11 @@ func GetAlert(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
alert, err := database.GetAlertPolicy(alertID)
|
alert, err := database.GetAlertPolicy(alertID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
c.JSON(http.StatusNotFound, gin.H{"error": "Alerte non trouvée"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(200, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"success": true,
|
"success": true,
|
||||||
"alert": alert,
|
"alert": alert,
|
||||||
})
|
})
|
||||||
@@ -124,7 +123,6 @@ func EndAlert(c *gin.Context) {
|
|||||||
|
|
||||||
usernameStr := username.(string)
|
usernameStr := username.(string)
|
||||||
|
|
||||||
// Vérifier que l'alerte appartient bien à ce livreur
|
|
||||||
alert, err := database.GetAlertPolicy(alertID)
|
alert, err := database.GetAlertPolicy(alertID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusNotFound, gin.H{"error": "Alerte non trouvée"})
|
c.JSON(http.StatusNotFound, gin.H{"error": "Alerte non trouvée"})
|
||||||
@@ -136,9 +134,8 @@ func EndAlert(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = database.EndAlert(alertID)
|
if err = database.EndAlert(alertID); err != nil {
|
||||||
if err != nil {
|
utils.ServerErr(c, "Impossible de mettre fin à l'alerte", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Impossible de mettre fin à l'alerte", "details": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,7 +166,7 @@ func GetMyAlerts(c *gin.Context) {
|
|||||||
|
|
||||||
alerts, err := database.GetAlertsByUsername(usernameStr)
|
alerts, err := database.GetAlertsByUsername(usernameStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Impossible de récupérer les alertes", "details": err.Error()})
|
utils.ServerErr(c, "Impossible de récupérer les alertes", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,7 +189,7 @@ func GetAllAlerts(c *gin.Context) {
|
|||||||
|
|
||||||
alerts, err := database.GetAllAlerts()
|
alerts, err := database.GetAllAlerts()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Impossible de récupérer les alertes", "details": err.Error()})
|
utils.ServerErr(c, "Impossible de récupérer les alertes", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,7 +211,7 @@ func GetActiveAlerts(c *gin.Context) {
|
|||||||
|
|
||||||
alerts, err := database.GetActiveAlerts()
|
alerts, err := database.GetActiveAlerts()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Impossible de récupérer les alertes", "details": err.Error()})
|
utils.ServerErr(c, "Impossible de récupérer les alertes", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -77,7 +77,6 @@ func RegisterClient(c *gin.Context) {
|
|||||||
log.Printf("❌ [REGISTER_CLIENT] Erreur binding: %v", err)
|
log.Printf("❌ [REGISTER_CLIENT] Erreur binding: %v", err)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": "Données invalides",
|
"error": "Données invalides",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -301,7 +300,7 @@ func ChangePassword(c *gin.Context) {
|
|||||||
NewPassword string `json:"new_password" binding:"required,min=8"`
|
NewPassword string `json:"new_password" binding:"required,min=8"`
|
||||||
}
|
}
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Données invalides", "details": err.Error()})
|
utils.BindErr(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,6 @@ func SetCommandDestinationCoordinates(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Erreur stockage Redis",
|
"error": "Erreur stockage Redis",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -217,7 +216,6 @@ func UpdateCommandAddressCabine(c *gin.Context) {
|
|||||||
if err := database.UpdateCommandAddress(commandID, req.DeliveryAddress); err != nil {
|
if err := database.UpdateCommandAddress(commandID, req.DeliveryAddress); err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Erreur lors de la mise à jour de l'adresse",
|
"error": "Erreur lors de la mise à jour de l'adresse",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -264,7 +262,6 @@ func GetLivreurPosition(c *gin.Context) {
|
|||||||
"success": false,
|
"success": false,
|
||||||
"livreur": livreurUsername,
|
"livreur": livreurUsername,
|
||||||
"message": "Position non disponible (GPS désactivé ou livraison terminée)",
|
"message": "Position non disponible (GPS désactivé ou livraison terminée)",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -394,7 +391,6 @@ func GetDeliveryIssues(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Erreur récupération problèmes",
|
"error": "Erreur récupération problèmes",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -431,7 +427,6 @@ func CreateDeliveryIssue(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Erreur création problème",
|
"error": "Erreur création problème",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -468,7 +463,6 @@ func UpdateDeliveryIssue(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Erreur mise à jour",
|
"error": "Erreur mise à jour",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -516,7 +510,6 @@ func AddDeliverySupport(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Erreur ajout support",
|
"error": "Erreur ajout support",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -540,7 +533,6 @@ func GetCommandLogs(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Erreur récupération logs",
|
"error": "Erreur récupération logs",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -580,7 +572,6 @@ func ForceValidateDelivery(c *gin.Context) {
|
|||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": "Raison requise pour validation forcée",
|
"error": "Raison requise pour validation forcée",
|
||||||
"details": err.Error(),
|
|
||||||
"example": gin.H{
|
"example": gin.H{
|
||||||
"reason": "Client confirmé par téléphone",
|
"reason": "Client confirmé par téléphone",
|
||||||
},
|
},
|
||||||
@@ -632,7 +623,6 @@ func ForceValidateDelivery(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Erreur lors de la validation forcée",
|
"error": "Erreur lors de la validation forcée",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -641,7 +631,7 @@ func ForceValidateDelivery(c *gin.Context) {
|
|||||||
livreurAssign, _ := command["livreur_assign"].(string)
|
livreurAssign, _ := command["livreur_assign"].(string)
|
||||||
|
|
||||||
if clientUsername != "" {
|
if clientUsername != "" {
|
||||||
clientMsg := fmt.Sprintf("Votre commande #%d a été livrée. Merci !", commandID)
|
clientMsg := fmt.Sprintf("Ta commande #%d a bien été livrée ! Bonne dégustation l'ami et à bientôt 😊", database.GetClientOrderID(commandID))
|
||||||
database.NotifyClient(clientUsername, commandID, "livre", clientMsg)
|
database.NotifyClient(clientUsername, commandID, "livre", clientMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -339,6 +339,7 @@ func GetAllCancelledOrders(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cancelReason, _ := order["cancel_reason"].(string)
|
||||||
enrichedOrder := map[string]any{
|
enrichedOrder := map[string]any{
|
||||||
"id": order["id"],
|
"id": order["id"],
|
||||||
"username": order["username"],
|
"username": order["username"],
|
||||||
@@ -346,12 +347,14 @@ func GetAllCancelledOrders(c *gin.Context) {
|
|||||||
"created_at": order["created_at"],
|
"created_at": order["created_at"],
|
||||||
"updated_at": order["updated_at"],
|
"updated_at": order["updated_at"],
|
||||||
"items_count": len(items),
|
"items_count": len(items),
|
||||||
|
"cancel_reason": cancelReason,
|
||||||
}
|
}
|
||||||
|
|
||||||
if cancellationLog != nil {
|
if cancellationLog != nil {
|
||||||
enrichedOrder["cancellation"] = gin.H{
|
enrichedOrder["cancellation"] = gin.H{
|
||||||
"cancelled_at": cancellationLog["created_at"],
|
"cancelled_at": cancellationLog["created_at"],
|
||||||
"cancelled_by": cancellationLog["author"],
|
"cancelled_by": cancellationLog["author"],
|
||||||
|
"reason": cancelReason,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ func CreateCategory(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := db.ValidateCategoryColor(req.Color); err != nil {
|
if err := db.ValidateCategoryColor(req.Color); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Couleur invalide (format hex requis, ex: #ff0000)"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ func UpdateCategory(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := db.ValidateCategoryColor(req.Color); err != nil {
|
if err := db.ValidateCategoryColor(req.Color); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Couleur invalide (format hex requis, ex: #ff0000)"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,7 +121,7 @@ func DeleteCategory(c *gin.Context) {
|
|||||||
if err := database.DeleteCategory(id); err != nil {
|
if err := database.DeleteCategory(id); err != nil {
|
||||||
log.Printf("❌ [CATEGORIES] Suppression erreur: %v", err)
|
log.Printf("❌ [CATEGORIES] Suppression erreur: %v", err)
|
||||||
if strings.Contains(err.Error(), "utilisée par") {
|
if strings.Contains(err.Error(), "utilisée par") {
|
||||||
c.JSON(http.StatusConflict, gin.H{"error": err.Error()})
|
c.JSON(http.StatusConflict, gin.H{"error": "Catégorie utilisée par des produits existants"})
|
||||||
} else {
|
} else {
|
||||||
c.JSON(http.StatusNotFound, gin.H{"error": "Catégorie non trouvée"})
|
c.JSON(http.StatusNotFound, gin.H{"error": "Catégorie non trouvée"})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -90,7 +91,6 @@ func GetMyCommandsWithTracking(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Erreur récupération",
|
"error": "Erreur récupération",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -118,6 +118,7 @@ func GetMyCommandsWithTracking(c *gin.Context) {
|
|||||||
|
|
||||||
enrichedCommands[i] = gin.H{
|
enrichedCommands[i] = gin.H{
|
||||||
"id": cmd["id"],
|
"id": cmd["id"],
|
||||||
|
"client_order_number": cmd["client_order_number"],
|
||||||
"status": cmd["status"],
|
"status": cmd["status"],
|
||||||
"status_message": getStatusMessage(cmd["status"].(string)),
|
"status_message": getStatusMessage(cmd["status"].(string)),
|
||||||
"adresse": cmd["adresse"],
|
"adresse": cmd["adresse"],
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ func UpdateCommandAddress(c *gin.Context) {
|
|||||||
// ✅ Récupération sécurisée du username
|
// ✅ Récupération sécurisée du username
|
||||||
adminUsername, err := safeGetUsername(c)
|
adminUsername, err := safeGetUsername(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Non authentifié"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,7 +161,7 @@ func ProposeAddressChange(c *gin.Context) {
|
|||||||
|
|
||||||
staffUsername, err := safeGetUsername(c)
|
staffUsername, err := safeGetUsername(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Non authentifié"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,15 +198,14 @@ func ProposeAddressChange(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := database.ProposeAddressChange(commandID, req.ProposedAddress, staffUsername); err != nil {
|
if err := database.ProposeAddressChange(commandID, req.ProposedAddress, staffUsername); err != nil {
|
||||||
log.Printf("❌ [PROPOSE_ADDR] Erreur: %v", err)
|
utils.ServerErr(c, "Impossible de proposer l'adresse", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notifier le client
|
// Notifier le client
|
||||||
clientUsername, _ := command["username"].(string)
|
clientUsername, _ := command["username"].(string)
|
||||||
if clientUsername != "" {
|
if clientUsername != "" {
|
||||||
msg := fmt.Sprintf("📍 Une nouvelle adresse de livraison vous est proposée pour la commande #%d : %s. Veuillez l'accepter ou la refuser dans le suivi de commande.", commandID, req.ProposedAddress)
|
msg := fmt.Sprintf("📍 Une nouvelle adresse de livraison vous est proposée pour votre commande #%d : %s. Veuillez l'accepter ou la refuser dans le suivi de commande.", database.GetClientOrderID(commandID), req.ProposedAddress)
|
||||||
database.NotifyClient(clientUsername, commandID, "address_proposal", msg)
|
database.NotifyClient(clientUsername, commandID, "address_proposal", msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,8 +247,7 @@ func RespondToAddressProposal(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := database.RespondToAddressProposal(commandID, clientUsername, req.Accepted); err != nil {
|
if err := database.RespondToAddressProposal(commandID, clientUsername, req.Accepted); err != nil {
|
||||||
log.Printf("❌ [RESPOND_ADDR] Erreur: %v", err)
|
utils.ServerErr(c, "Impossible de traiter la réponse", err)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,7 +294,6 @@ func GetAllCommands(c *gin.Context) {
|
|||||||
log.Printf("❌ [GET_CMDS] Erreur: %v", err)
|
log.Printf("❌ [GET_CMDS] Erreur: %v", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Erreur lors de la récupération des commandes",
|
"error": "Erreur lors de la récupération des commandes",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -444,7 +441,7 @@ func StaffApproveDelivery(c *gin.Context) {
|
|||||||
totalPoints, pointCategory, clientUsername, err := database.ApproveDeliveryAtomicByStaff(commandID, staffUsername)
|
totalPoints, pointCategory, clientUsername, err := database.ApproveDeliveryAtomicByStaff(commandID, staffUsername)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("❌ [STAFF_APPROVE] Erreur: %v", err)
|
log.Printf("❌ [STAFF_APPROVE] Erreur: %v", err)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Impossible de confirmer la réception: " + err.Error()})
|
utils.ServerErr(c, "Impossible de confirmer la réception", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -474,7 +471,7 @@ func ValidateDelivery(c *gin.Context) {
|
|||||||
|
|
||||||
adminUsername, err := safeGetUsername(c)
|
adminUsername, err := safeGetUsername(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Non authentifié"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -604,7 +601,6 @@ func GetAvailableDeliveryPersons(c *gin.Context) {
|
|||||||
log.Printf("❌ [GET_LIVREURS] Erreur: %v", err)
|
log.Printf("❌ [GET_LIVREURS] Erreur: %v", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Erreur lors de la récupération des livreurs",
|
"error": "Erreur lors de la récupération des livreurs",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -651,7 +647,6 @@ func AssignDeliveryPerson(c *gin.Context) {
|
|||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": "Données invalides",
|
"error": "Données invalides",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -665,7 +660,6 @@ func AssignDeliveryPerson(c *gin.Context) {
|
|||||||
log.Printf("❌ [ASSIGN] Erreur: %v", err)
|
log.Printf("❌ [ASSIGN] Erreur: %v", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Erreur assignation livreur",
|
"error": "Erreur assignation livreur",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -704,7 +698,6 @@ func GetClientCommandsHistory(c *gin.Context) {
|
|||||||
log.Printf("❌ [HISTORY] Erreur: %v", err)
|
log.Printf("❌ [HISTORY] Erreur: %v", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Erreur récupération historique",
|
"error": "Erreur récupération historique",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -805,7 +798,7 @@ func NotifyClientToDescend(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
staffUsername, _ := c.Get("username")
|
staffUsername, _ := c.Get("username")
|
||||||
msg := fmt.Sprintf("Votre commande #%d est prête ! Vous pouvez descendre la récupérer.", commandID)
|
msg := fmt.Sprintf("Votre commande #%d est prête ! Vous pouvez descendre la récupérer.", database.GetClientOrderID(commandID))
|
||||||
database.NotifyClient(clientUsername, commandID, "ready_pickup", msg)
|
database.NotifyClient(clientUsername, commandID, "ready_pickup", msg)
|
||||||
database.AddCommandLog(commandID, "notification", fmt.Sprintf("Client notifié de descendre par %s", staffUsername), staffUsername.(string))
|
database.AddCommandLog(commandID, "notification", fmt.Sprintf("Client notifié de descendre par %s", staffUsername), staffUsername.(string))
|
||||||
|
|
||||||
@@ -859,7 +852,6 @@ func ShowItems(c *gin.Context) {
|
|||||||
log.Printf("❌ [ITEMS] Erreur: %v", err)
|
log.Printf("❌ [ITEMS] Erreur: %v", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Erreur lors de la récupération des items",
|
"error": "Erreur lors de la récupération des items",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -951,7 +943,6 @@ func GetCommandItemsWithDetails(c *gin.Context) {
|
|||||||
log.Printf("❌ [ITEMS_DETAILED] Erreur DB: %v", err)
|
log.Printf("❌ [ITEMS_DETAILED] Erreur DB: %v", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Erreur récupération items",
|
"error": "Erreur récupération items",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -970,6 +961,7 @@ func GetCommandItemsWithDetails(c *gin.Context) {
|
|||||||
"total_prix": items[0]["total_prix"],
|
"total_prix": items[0]["total_prix"],
|
||||||
"livreur_assign": items[0]["livreur_assign"],
|
"livreur_assign": items[0]["livreur_assign"],
|
||||||
"command_created_at": items[0]["command_created_at"],
|
"command_created_at": items[0]["command_created_at"],
|
||||||
|
"client_order_number": items[0]["client_order_number"],
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
@@ -1007,7 +999,6 @@ func UpdateItemStatus(c *gin.Context) {
|
|||||||
log.Printf("❌ [UPD_ITEM] Erreur JSON: %v", err)
|
log.Printf("❌ [UPD_ITEM] Erreur JSON: %v", err)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": "Status requis",
|
"error": "Status requis",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1038,7 +1029,6 @@ func UpdateItemStatus(c *gin.Context) {
|
|||||||
log.Printf("❌ [UPD_ITEM] Erreur: %v", err)
|
log.Printf("❌ [UPD_ITEM] Erreur: %v", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Erreur lors de la mise à jour",
|
"error": "Erreur lors de la mise à jour",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1065,7 +1055,7 @@ func DeleteCommandItem(c *gin.Context) {
|
|||||||
|
|
||||||
adminUsername, err := safeGetUsername(c)
|
adminUsername, err := safeGetUsername(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Non authentifié"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1084,8 +1074,7 @@ func DeleteCommandItem(c *gin.Context) {
|
|||||||
log.Printf("🗑️ [DEL_ITEM] Admin %s supprime item %d de cmd %d", adminUsername, itemID, commandID)
|
log.Printf("🗑️ [DEL_ITEM] Admin %s supprime item %d de cmd %d", adminUsername, itemID, commandID)
|
||||||
|
|
||||||
if err := database.DeleteCommandItem(commandID, itemID); err != nil {
|
if err := database.DeleteCommandItem(commandID, itemID); err != nil {
|
||||||
log.Printf("❌ [DEL_ITEM] Erreur: %v", err)
|
utils.ServerErr(c, "Impossible de supprimer l'item", err)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1137,8 +1126,7 @@ func UpdateCommandStatusAdmin(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := database.UpdateCommandStatus(commandID, req.Status); err != nil {
|
if err := database.UpdateCommandStatus(commandID, req.Status); err != nil {
|
||||||
log.Printf("❌ [STATUS_ADMIN] Erreur: %v", err)
|
utils.ServerErr(c, "Impossible de mettre à jour le statut", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ func GetMyDeliveries(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Erreur récupération",
|
"error": "Erreur récupération",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -190,7 +189,6 @@ func UpdateDeliveryStatus(c *gin.Context) {
|
|||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": "Données invalides",
|
"error": "Données invalides",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -261,7 +259,6 @@ func UpdateDeliveryStatus(c *gin.Context) {
|
|||||||
if err := database.UpdateCommandStatus(commandID, req.Status); err != nil {
|
if err := database.UpdateCommandStatus(commandID, req.Status); err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Erreur mise à jour",
|
"error": "Erreur mise à jour",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -361,16 +358,16 @@ func UpdateDeliveryStatus(c *gin.Context) {
|
|||||||
} else {
|
} else {
|
||||||
etaStr = fmt.Sprintf("%d min", etaMinutes)
|
etaStr = fmt.Sprintf("%d min", etaMinutes)
|
||||||
}
|
}
|
||||||
clientMsg = fmt.Sprintf("🛵 Votre commande #%d est en route ! Arrivée dans ~%s", commandID, etaStr)
|
clientMsg = fmt.Sprintf("Un livreur vient de prendre en charge ta commande #%d, il est en route (~%s)", database.GetClientOrderID(commandID), etaStr)
|
||||||
} else {
|
} else {
|
||||||
clientMsg = fmt.Sprintf("🛵 Votre commande #%d est en route !", commandID)
|
clientMsg = fmt.Sprintf("Un livreur vient de prendre en charge ta commande #%d, il est en route.", database.GetClientOrderID(commandID))
|
||||||
}
|
}
|
||||||
case "arrived":
|
case "arrived":
|
||||||
clientMsg = fmt.Sprintf("🛵 Votre livreur est là ! Il sera chez vous dans 5 minutes (commande #%d)", commandID)
|
clientMsg = fmt.Sprintf("Descend, le livreur est là dans 3min (commande #%d) 🛵", database.GetClientOrderID(commandID))
|
||||||
case "livre":
|
case "livre":
|
||||||
clientMsg = fmt.Sprintf("Votre commande #%d a été livrée. Merci !", commandID)
|
clientMsg = fmt.Sprintf("Ta commande #%d a bien été livrée ! Bonne dégustation l'ami et à bientôt 😊", database.GetClientOrderID(commandID))
|
||||||
case "cancelled":
|
case "cancelled":
|
||||||
clientMsg = fmt.Sprintf("Votre commande #%d a été annulée par le livreur", commandID)
|
clientMsg = fmt.Sprintf("Votre commande #%d a été annulée par le livreur", database.GetClientOrderID(commandID))
|
||||||
}
|
}
|
||||||
if clientMsg != "" {
|
if clientMsg != "" {
|
||||||
database.NotifyClient(clientUsername, commandID, req.Status, clientMsg)
|
database.NotifyClient(clientUsername, commandID, req.Status, clientMsg)
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ func GetDeliveryPersonDetails(c *gin.Context) {
|
|||||||
log.Printf("❌ [GET_DELIVERY_DETAILS] Livreur non trouvé: %v", err)
|
log.Printf("❌ [GET_DELIVERY_DETAILS] Livreur non trouvé: %v", err)
|
||||||
c.JSON(http.StatusNotFound, gin.H{
|
c.JSON(http.StatusNotFound, gin.H{
|
||||||
"error": "Livreur non trouvé",
|
"error": "Livreur non trouvé",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -118,7 +117,6 @@ func UpdateDeliveryPersonStatusAdmin(c *gin.Context) {
|
|||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": "Statut requis",
|
"error": "Statut requis",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -163,7 +161,6 @@ func UpdateDeliveryPersonStatusAdmin(c *gin.Context) {
|
|||||||
log.Printf("❌ [UPDATE_DELIVERY_STATUS] Erreur mise à jour: %v", err)
|
log.Printf("❌ [UPDATE_DELIVERY_STATUS] Erreur mise à jour: %v", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Erreur mise à jour statut",
|
"error": "Erreur mise à jour statut",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -325,7 +322,6 @@ func GetDeliveryPersonHistory(c *gin.Context) {
|
|||||||
log.Printf("❌ [GET_DELIVERY_HISTORY] Erreur récupération: %v", err)
|
log.Printf("❌ [GET_DELIVERY_HISTORY] Erreur récupération: %v", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Erreur récupération historique",
|
"error": "Erreur récupération historique",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -373,7 +369,6 @@ func UpdateDeliveryPersonLocationAdmin(c *gin.Context) {
|
|||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": "Coordonnées GPS requises",
|
"error": "Coordonnées GPS requises",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -421,7 +416,6 @@ func UpdateDeliveryPersonLocationAdmin(c *gin.Context) {
|
|||||||
log.Printf("❌ [UPDATE_DELIVERY_LOCATION] Erreur mise à jour: %v", err)
|
log.Printf("❌ [UPDATE_DELIVERY_LOCATION] Erreur mise à jour: %v", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Erreur mise à jour position",
|
"error": "Erreur mise à jour position",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -515,7 +509,6 @@ func RemoveCommandFromQueue(c *gin.Context) {
|
|||||||
log.Printf("❌ [REMOVE_FROM_QUEUE] Erreur suppression: %v", err)
|
log.Printf("❌ [REMOVE_FROM_QUEUE] Erreur suppression: %v", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Erreur suppression de la queue",
|
"error": "Erreur suppression de la queue",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,34 +21,24 @@ import (
|
|||||||
// GÉOCODAGE D'ADRESSES
|
// GÉOCODAGE D'ADRESSES
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
// GeocodeAddress convertit une adresse en coordonnées GPS
|
|
||||||
func GeocodeAddress(c *gin.Context) {
|
func GeocodeAddress(c *gin.Context) {
|
||||||
geoService := c.MustGet("geoService").(*services.GeoService)
|
geoService := c.MustGet("geoService").(*services.GeoService)
|
||||||
|
|
||||||
var req struct {
|
var req struct {
|
||||||
Address string `json:"address" binding:"required"`
|
Address string `json:"address" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Adresse requise"})
|
||||||
"error": "Adresse requise",
|
|
||||||
"details": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
location, err := geoService.GeocodeAddress(req.Address)
|
location, err := geoService.GeocodeAddress(req.Address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusNotFound, gin.H{
|
c.JSON(http.StatusNotFound, gin.H{"error": "Impossible de géocoder cette adresse"})
|
||||||
"error": "Impossible de géocoder cette adresse",
|
|
||||||
"details": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("📍 Adresse géocodée: %s -> (%.6f, %.6f)",
|
log.Printf("📍 Adresse géocodée: %s -> (%.6f, %.6f)", req.Address, location.Latitude, location.Longitude)
|
||||||
req.Address, location.Latitude, location.Longitude)
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"success": true,
|
"success": true,
|
||||||
"latitude": location.Latitude,
|
"latitude": location.Latitude,
|
||||||
@@ -77,7 +67,6 @@ func FindNearestDeliveryPerson(c *gin.Context) {
|
|||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": "Données invalides",
|
"error": "Données invalides",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -90,7 +79,6 @@ func FindNearestDeliveryPerson(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": "Impossible de géocoder l'adresse",
|
"error": "Impossible de géocoder l'adresse",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -111,7 +99,6 @@ func FindNearestDeliveryPerson(c *gin.Context) {
|
|||||||
if err := services.ValidateCoordinates(targetCoords); err != nil {
|
if err := services.ValidateCoordinates(targetCoords); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": "Coordonnées invalides",
|
"error": "Coordonnées invalides",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -136,7 +123,6 @@ func FindNearestDeliveryPerson(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusNotFound, gin.H{
|
c.JSON(http.StatusNotFound, gin.H{
|
||||||
"error": "Aucun livreur avec position GPS valide",
|
"error": "Aucun livreur avec position GPS valide",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -194,7 +180,6 @@ func GetAllDeliveryDistances(c *gin.Context) {
|
|||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": "Données invalides",
|
"error": "Données invalides",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -206,7 +191,6 @@ func GetAllDeliveryDistances(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": "Impossible de géocoder l'adresse",
|
"error": "Impossible de géocoder l'adresse",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -225,7 +209,6 @@ func GetAllDeliveryDistances(c *gin.Context) {
|
|||||||
if err := services.ValidateCoordinates(targetCoords); err != nil {
|
if err := services.ValidateCoordinates(targetCoords); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": "Coordonnées invalides",
|
"error": "Coordonnées invalides",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -247,7 +230,6 @@ func GetAllDeliveryDistances(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Erreur calcul des distances",
|
"error": "Erreur calcul des distances",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -317,7 +299,6 @@ func AutoAssignNearestDeliveryPerson(c *gin.Context) {
|
|||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": "Impossible de géocoder l'adresse de livraison",
|
"error": "Impossible de géocoder l'adresse de livraison",
|
||||||
"address": address,
|
"address": address,
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -376,7 +357,6 @@ func AutoAssignNearestDeliveryPerson(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Erreur calcul ETA",
|
"error": "Erreur calcul ETA",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -386,7 +366,6 @@ func AutoAssignNearestDeliveryPerson(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Erreur lors de l'assignation",
|
"error": "Erreur lors de l'assignation",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -441,7 +420,6 @@ func AutoAssignNearestDeliveryPerson(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Erreur calcul ETA",
|
"error": "Erreur calcul ETA",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -451,7 +429,6 @@ func AutoAssignNearestDeliveryPerson(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Erreur lors de l'assignation forcée",
|
"error": "Erreur lors de l'assignation forcée",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -507,7 +484,6 @@ func AutoAssignNearestDeliveryPerson(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusNotFound, gin.H{
|
c.JSON(http.StatusNotFound, gin.H{
|
||||||
"error": "Aucun livreur avec position GPS valide",
|
"error": "Aucun livreur avec position GPS valide",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -528,7 +504,6 @@ func AutoAssignNearestDeliveryPerson(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Erreur lors de l'assignation",
|
"error": "Erreur lors de l'assignation",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -583,7 +558,6 @@ func AutoAssignAllPendingCommands(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Erreur récupération des commandes",
|
"error": "Erreur récupération des commandes",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -743,7 +717,6 @@ func GetAllDeliveryQueues(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Erreur récupération des queues",
|
"error": "Erreur récupération des queues",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -803,7 +776,6 @@ func GetDeliverymanQueue(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Erreur récupération de la queue",
|
"error": "Erreur récupération de la queue",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -814,41 +786,23 @@ func GetDeliverymanQueue(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// VALIDATION D'ADRESSE
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
// ValidateAddress vérifie si une adresse peut être géocodée
|
|
||||||
// POST /api/v1/validate-address
|
|
||||||
// Body: {"address": "123 Main St, Paris"}
|
|
||||||
func ValidateAddress(c *gin.Context) {
|
func ValidateAddress(c *gin.Context) {
|
||||||
geoService := c.MustGet("geoService").(*services.GeoService)
|
geoService := c.MustGet("geoService").(*services.GeoService)
|
||||||
|
|
||||||
var req struct {
|
var req struct {
|
||||||
Address string `json:"address" binding:"required"`
|
Address string `json:"address" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Adresse requise"})
|
||||||
"error": "Adresse requise",
|
|
||||||
"details": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
isValid := geoService.IsValidAddress(req.Address)
|
if !geoService.IsValidAddress(req.Address) {
|
||||||
|
c.JSON(http.StatusOK, gin.H{"valid": false, "message": "Adresse introuvable ou invalide"})
|
||||||
if !isValid {
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
|
||||||
"valid": false,
|
|
||||||
"message": "Adresse introuvable ou invalide",
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Récupérer les détails
|
|
||||||
location, _ := geoService.GeocodeAddress(req.Address)
|
location, _ := geoService.GeocodeAddress(req.Address)
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"valid": true,
|
"valid": true,
|
||||||
"message": "Adresse valide",
|
"message": "Adresse valide",
|
||||||
@@ -858,13 +812,7 @@ func ValidateAddress(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// HELPER FUNCTION - CALCUL ETA AVEC TOMTOM
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
// calculateTravelTimeWithTomTom calcule l'ETA avec TomTom ou fallback local
|
|
||||||
func calculateTravelTimeWithTomTom(geoService *services.GeoService, deliverymanUsername string, targetLat, targetLon float64) (int, float64, error) {
|
func calculateTravelTimeWithTomTom(geoService *services.GeoService, deliverymanUsername string, targetLat, targetLon float64) (int, float64, error) {
|
||||||
// Récupérer position du livreur
|
|
||||||
deliverymanLoc, err := geoService.GetDeliveryPersonLocation(deliverymanUsername)
|
deliverymanLoc, err := geoService.GetDeliveryPersonLocation(deliverymanUsername)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, fmt.Errorf("position du livreur introuvable: %w", err)
|
return 0, 0, fmt.Errorf("position du livreur introuvable: %w", err)
|
||||||
@@ -875,10 +823,8 @@ func calculateTravelTimeWithTomTom(geoService *services.GeoService, deliverymanU
|
|||||||
Longitude: targetLon,
|
Longitude: targetLon,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculer ETA avec TomTom (avec fallback automatique intégré)
|
|
||||||
travelTime, distance, err := services.GetETAWithTraffic(*deliverymanLoc, targetCoords)
|
travelTime, distance, err := services.GetETAWithTraffic(*deliverymanLoc, targetCoords)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Fallback sur calcul local
|
|
||||||
distance = services.CalculateDistance(*deliverymanLoc, targetCoords)
|
distance = services.CalculateDistance(*deliverymanLoc, targetCoords)
|
||||||
travelTime = services.CalculateETA(distance)
|
travelTime = services.CalculateETA(distance)
|
||||||
log.Printf("⚠️ TomTom indisponible pour %s, fallback: %.2f km -> %d min",
|
log.Printf("⚠️ TomTom indisponible pour %s, fallback: %.2f km -> %d min",
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -43,7 +44,6 @@ func GetDeliveryPersonMapLinks(c *gin.Context) {
|
|||||||
c.JSON(http.StatusNotFound, gin.H{
|
c.JSON(http.StatusNotFound, gin.H{
|
||||||
"success": false,
|
"success": false,
|
||||||
"error": "Position GPS non disponible pour ce livreur",
|
"error": "Position GPS non disponible pour ce livreur",
|
||||||
"details": err.Error(),
|
|
||||||
"message": "Le livreur n'a pas encore partagé sa position ou est hors ligne",
|
"message": "Le livreur n'a pas encore partagé sa position ou est hors ligne",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
@@ -117,7 +117,6 @@ func GetCommandNavigationLinks(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Erreur génération des liens",
|
"error": "Erreur génération des liens",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -41,7 +42,6 @@ func GetMyCompletedOrders(c *gin.Context) {
|
|||||||
log.Printf("❌ [HISTORY] Erreur: %v", err)
|
log.Printf("❌ [HISTORY] Erreur: %v", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Erreur lors de la récupération de l'historique",
|
"error": "Erreur lors de la récupération de l'historique",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -120,7 +120,6 @@ func GetMyCompletedOrdersWithItems(c *gin.Context) {
|
|||||||
log.Printf("❌ [HISTORY_DETAILED] Erreur: %v", err)
|
log.Printf("❌ [HISTORY_DETAILED] Erreur: %v", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Erreur lors de la récupération de l'historique",
|
"error": "Erreur lors de la récupération de l'historique",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"gestion/db"
|
"gestion/db"
|
||||||
"gestion/models"
|
"gestion/models"
|
||||||
"gestion/services"
|
"gestion/services"
|
||||||
|
"gestion/utils"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
@@ -32,7 +33,7 @@ func AddProductsBasket(c *gin.Context) {
|
|||||||
|
|
||||||
var req BasketsRequest
|
var req BasketsRequest
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Requête invalide", "details": err.Error()})
|
utils.BindErr(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,17 +63,17 @@ func AddProductsBasket(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
if err := database.DecrementProductStockByID(req.ProductID, req.Quantity); err != nil {
|
if err := database.DecrementProductStockByID(req.ProductID, req.Quantity); err != nil {
|
||||||
log.Printf("❌ [ADD_PANIER] Erreur décrement stock product_id=%d: %v", req.ProductID, err)
|
log.Printf("❌ [ADD_PANIER] Erreur décrement stock product_id=%d: %v", req.ProductID, err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Impossible d'ajouter le produit au panier", "details": err.Error()})
|
utils.ServerErr(c, "Impossible d'ajouter le produit au panier", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
panier, err := database.AddProductInBasketByID(req.Username, req.ProductID, req.Quantity)
|
panier, err := database.AddProductInBasketByID(req.Username, req.ProductID, req.Quantity)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("❌ [ADD_PANIER] Erreur ajout product_id=%d qty=%.3f: %v", req.ProductID, req.Quantity, err)
|
log.Printf("❌ [ADD_PANIER] Erreur ajout product_id=%d qty=%.3f: %v", req.ProductID, req.Quantity, err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Impossible d'ajouter le produit au panier", "details": err.Error()})
|
utils.ServerErr(c, "Impossible d'ajouter le produit au panier", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusCreated, gin.H{"success": true, "message": "Produit ajouté au panier avec succès", "panier": panier})
|
c.JSON(http.StatusOK, gin.H{"success": true, "message": "Produit ajouté au panier avec succès", "panier": panier})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,11 +129,7 @@ func GetAllBaskets(c *gin.Context) {
|
|||||||
|
|
||||||
baskets, err := database.GetAllProductsInBasket(username)
|
baskets, err := database.GetAllProductsInBasket(username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("❌ [GET_PANIER] Erreur récupération: %v", err)
|
utils.ServerErr(c, "Erreur lors de la récupération du panier", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
|
||||||
"error": "Erreur lors de la récupération du panier",
|
|
||||||
"details": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,11 +162,7 @@ func DeleteProductFromBasket(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
log.Printf("❌ [DEL_PANIER] Erreur JSON: %v", err)
|
utils.BindErr(c, err)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
|
||||||
"error": "Données requises manquantes",
|
|
||||||
"details": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,11 +204,7 @@ func DeleteProductFromBasket(c *gin.Context) {
|
|||||||
// Supprimer l'article
|
// Supprimer l'article
|
||||||
err = database.DeleteProductFromBasket(req.ID)
|
err = database.DeleteProductFromBasket(req.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("❌ [DEL_PANIER] Erreur suppression: %v", err)
|
utils.ServerErr(c, "Erreur lors de la suppression", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
|
||||||
"error": "Erreur lors de la suppression",
|
|
||||||
"details": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,11 +248,7 @@ func ClearBasket(c *gin.Context) {
|
|||||||
|
|
||||||
err = database.ClearBasket(authUsernameStr)
|
err = database.ClearBasket(authUsernameStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("❌ [CLEAR_PANIER] Erreur vidage: %v", err)
|
utils.ServerErr(c, "Erreur lors du vidage du panier", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
|
||||||
"error": "Erreur lors du vidage du panier",
|
|
||||||
"details": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,7 +290,7 @@ func ValidateBasket(c *gin.Context) {
|
|||||||
|
|
||||||
cmd := &models.Command{DeliveryAddress: req.DeliveryAddress}
|
cmd := &models.Command{DeliveryAddress: req.DeliveryAddress}
|
||||||
if err := database.CheckAddress(cmd); err != nil {
|
if err := database.CheckAddress(cmd); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error(), "corrected_address": cmd.DeliveryAddress})
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Adresse non reconnue", "corrected_address": cmd.DeliveryAddress})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
req.DeliveryAddress = cmd.DeliveryAddress
|
req.DeliveryAddress = cmd.DeliveryAddress
|
||||||
@@ -323,8 +308,7 @@ func ValidateBasket(c *gin.Context) {
|
|||||||
// ============================================
|
// ============================================
|
||||||
items, err := database.GetBasketItems(usernameStr)
|
items, err := database.GetBasketItems(usernameStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("❌ [CHECKOUT] Erreur récupération panier: %v", err)
|
utils.ServerErr(c, "Impossible de récupérer le panier", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Impossible de récupérer le panier", "details": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -487,7 +471,7 @@ func ValidateBasket(c *gin.Context) {
|
|||||||
_, _ = database.CreateCryptoPayment(commandID, payResp.PaymentID.String(), payResp.Status, payResp.PriceCurrency, payResp.PayCurrency, payResp.PayAddress, priceAmt, payAmt)
|
_, _ = database.CreateCryptoPayment(commandID, payResp.PaymentID.String(), payResp.Status, payResp.PriceCurrency, payResp.PayCurrency, payResp.PayAddress, priceAmt, payAmt)
|
||||||
|
|
||||||
log.Printf("✅ [CHECKOUT] Commande %d en attente paiement crypto (%s)", commandID, req.PayCurrency)
|
log.Printf("✅ [CHECKOUT] Commande %d en attente paiement crypto (%s)", commandID, req.PayCurrency)
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusCreated, gin.H{
|
||||||
"success": true,
|
"success": true,
|
||||||
"command_id": commandID,
|
"command_id": commandID,
|
||||||
"payment_method": "crypto",
|
"payment_method": "crypto",
|
||||||
@@ -510,8 +494,7 @@ func ValidateBasket(c *gin.Context) {
|
|||||||
// ============================================
|
// ============================================
|
||||||
err = database.ClearBasket(usernameStr)
|
err = database.ClearBasket(usernameStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("❌ [CHECKOUT] Erreur vidage panier: %v", err)
|
utils.ServerErr(c, "Impossible de vider le panier", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Impossible de vider le panier", "details": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Printf("🧹 [CHECKOUT] Panier vidé")
|
log.Printf("🧹 [CHECKOUT] Panier vidé")
|
||||||
@@ -585,7 +568,8 @@ func ValidateBasket(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Notifier le client
|
// Notifier le client
|
||||||
clientMsg := fmt.Sprintf("Votre commande #%d est confirmée ! Un livreur est en route.", commandID)
|
clientOrderID := database.GetClientOrderID(commandID)
|
||||||
|
clientMsg := fmt.Sprintf("Ta commande #%d est prise en compte ! Merci de rester branché et vigilant sur les notifs à venir.", clientOrderID)
|
||||||
database.NotifyClient(usernameStr, commandID, "assigned", clientMsg)
|
database.NotifyClient(usernameStr, commandID, "assigned", clientMsg)
|
||||||
|
|
||||||
assigned = true
|
assigned = true
|
||||||
@@ -613,6 +597,7 @@ func ValidateBasket(c *gin.Context) {
|
|||||||
resp := gin.H{
|
resp := gin.H{
|
||||||
"success": true,
|
"success": true,
|
||||||
"command_id": commandID,
|
"command_id": commandID,
|
||||||
|
"client_order_number": command.ClientOrderID,
|
||||||
"delivery_address": req.DeliveryAddress,
|
"delivery_address": req.DeliveryAddress,
|
||||||
"status": "pending",
|
"status": "pending",
|
||||||
"referral_used": referralUsed,
|
"referral_used": referralUsed,
|
||||||
@@ -631,7 +616,7 @@ func ValidateBasket(c *gin.Context) {
|
|||||||
log.Printf("✅ [CHECKOUT] Réponse 200 - Commande %d en attente", commandID)
|
log.Printf("✅ [CHECKOUT] Réponse 200 - Commande %d en attente", commandID)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, resp)
|
c.JSON(http.StatusCreated, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getBaseURL construit l'URL de base depuis la requête en cours
|
// getBaseURL construit l'URL de base depuis la requête en cours
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"gestion/db"
|
"gestion/db"
|
||||||
"gestion/services"
|
"gestion/services"
|
||||||
|
"gestion/utils"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -94,10 +95,7 @@ func AutoAssignNextCommand(c *gin.Context) {
|
|||||||
|
|
||||||
err = database.AutoAssignCommand(nextCommand.CommandID)
|
err = database.AutoAssignCommand(nextCommand.CommandID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
utils.ServerErr(c, "Erreur lors de l'assignation automatique", err)
|
||||||
"error": "Erreur lors de l'assignation automatique",
|
|
||||||
"details": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,11 +136,7 @@ func UpdateLivreurLocation(c *gin.Context) {
|
|||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": "Données invalides - latitude et longitude requises",
|
"error": "Données invalides - latitude et longitude requises",
|
||||||
"details": err.Error(),
|
"format": gin.H{"latitude": "number (required)", "longitude": "number (required)"},
|
||||||
"format": gin.H{
|
|
||||||
"latitude": "number (required)",
|
|
||||||
"longitude": "number (required)",
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -169,10 +163,7 @@ func UpdateLivreurLocation(c *gin.Context) {
|
|||||||
// ✅ 1. Mettre à jour la position GPS dans Redis
|
// ✅ 1. Mettre à jour la position GPS dans Redis
|
||||||
err := database.UpdateDeliveryPersonLocation(usernameStr, req.Latitude, req.Longitude)
|
err := database.UpdateDeliveryPersonLocation(usernameStr, req.Latitude, req.Longitude)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
utils.ServerErr(c, "Erreur lors de la mise à jour de la position", err)
|
||||||
"error": "Erreur lors de la mise à jour de la position",
|
|
||||||
"details": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,7 +242,6 @@ func GetMyLocation(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusNotFound, gin.H{
|
c.JSON(http.StatusNotFound, gin.H{
|
||||||
"error": "Position non disponible",
|
"error": "Position non disponible",
|
||||||
"details": err.Error(),
|
|
||||||
"message": "Veuillez d'abord mettre à jour votre position",
|
"message": "Veuillez d'abord mettre à jour votre position",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
@@ -286,10 +276,7 @@ func GetDeliveryPersonLocation(c *gin.Context) {
|
|||||||
|
|
||||||
position, err := database.GetLivreurPosition(username)
|
position, err := database.GetLivreurPosition(username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusNotFound, gin.H{
|
c.JSON(http.StatusNotFound, gin.H{"error": "Position non trouvée pour ce livreur"})
|
||||||
"error": "Position non trouvée pour ce livreur",
|
|
||||||
"details": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -373,7 +360,6 @@ func GetDeliverymanLocationForCommand(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusNotFound, gin.H{
|
c.JSON(http.StatusNotFound, gin.H{
|
||||||
"error": "Position du livreur non disponible",
|
"error": "Position du livreur non disponible",
|
||||||
"details": err.Error(),
|
|
||||||
"message": "Le livreur n'a pas encore partagé sa position",
|
"message": "Le livreur n'a pas encore partagé sa position",
|
||||||
"command_info": gin.H{
|
"command_info": gin.H{
|
||||||
"command_id": commandID,
|
"command_id": commandID,
|
||||||
@@ -501,10 +487,7 @@ func UpdateDeliveryPersonStatus(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
utils.BindErr(c, err)
|
||||||
"error": "Données invalides",
|
|
||||||
"details": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -531,10 +514,7 @@ func UpdateDeliveryPersonStatus(c *gin.Context) {
|
|||||||
|
|
||||||
err := database.SetDeliveryPersonStatus(usernameStr, req.Status, 0)
|
err := database.SetDeliveryPersonStatus(usernameStr, req.Status, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
utils.ServerErr(c, "Erreur lors de la mise à jour du statut", err)
|
||||||
"error": "Erreur lors de la mise à jour du statut",
|
|
||||||
"details": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -613,10 +593,7 @@ func GetMyQueue(c *gin.Context) {
|
|||||||
|
|
||||||
queueInfo, err := database.GetDeliverymanQueueInfo(usernameStr)
|
queueInfo, err := database.GetDeliverymanQueueInfo(usernameStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
utils.ServerErr(c, "Erreur récupération de la queue", err)
|
||||||
"error": "Erreur récupération de la queue",
|
|
||||||
"details": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -640,10 +617,7 @@ func GetAvailableDeliveryPersonsRealtime(c *gin.Context) {
|
|||||||
|
|
||||||
livreurs, err := database.GetAvailableDeliveryPersonsRedis()
|
livreurs, err := database.GetAvailableDeliveryPersonsRedis()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
utils.ServerErr(c, "Erreur lors de la récupération des livreurs", err)
|
||||||
"error": "Erreur lors de la récupération des livreurs",
|
|
||||||
"details": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -687,10 +661,7 @@ func SetCommandETAHandler(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
utils.BindErr(c, err)
|
||||||
"error": "Données invalides",
|
|
||||||
"details": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -724,10 +695,7 @@ func SetCommandETAHandler(c *gin.Context) {
|
|||||||
// Mettre à jour l'ETA dans Redis
|
// Mettre à jour l'ETA dans Redis
|
||||||
err = database.SetCommandETA(commandID, req.ETAMinutes)
|
err = database.SetCommandETA(commandID, req.ETAMinutes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
utils.ServerErr(c, "Erreur lors de la mise à jour de l'ETA", err)
|
||||||
"error": "Erreur lors de la mise à jour de l'ETA",
|
|
||||||
"details": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -830,10 +798,7 @@ func GetMyPenalties(c *gin.Context) {
|
|||||||
// ✅ UTILISE LA MÉTHODE DÉDIÉE GetClientPenaltiesInfo
|
// ✅ UTILISE LA MÉTHODE DÉDIÉE GetClientPenaltiesInfo
|
||||||
penaltiesInfo, err := database.GetClientPenaltiesInfo(usernameStr)
|
penaltiesInfo, err := database.GetClientPenaltiesInfo(usernameStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
utils.ServerErr(c, "Erreur lors de la récupération des pénalités", err)
|
||||||
"error": "Erreur lors de la récupération des pénalités",
|
|
||||||
"details": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -869,10 +834,7 @@ func GetClientPenaltiesAdmin(c *gin.Context) {
|
|||||||
// ✅ UTILISE GetClientPenaltiesInfo
|
// ✅ UTILISE GetClientPenaltiesInfo
|
||||||
penaltiesInfo, err := database.GetClientPenaltiesInfo(username)
|
penaltiesInfo, err := database.GetClientPenaltiesInfo(username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
utils.ServerErr(c, "Erreur lors de la récupération des pénalités", err)
|
||||||
"error": "Erreur lors de la récupération des pénalités",
|
|
||||||
"details": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -896,10 +858,7 @@ func GetAllClientsWithPenalties(c *gin.Context) {
|
|||||||
// ✅ UTILISE GetAllClientsWithPenalties
|
// ✅ UTILISE GetAllClientsWithPenalties
|
||||||
clients, err := database.GetAllClientsWithPenalties()
|
clients, err := database.GetAllClientsWithPenalties()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
utils.ServerErr(c, "Erreur lors de la récupération des clients", err)
|
||||||
"error": "Erreur lors de la récupération des clients",
|
|
||||||
"details": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -926,10 +885,7 @@ func GetPenaltiesStats(c *gin.Context) {
|
|||||||
// ✅ UTILISE GetClientPenaltiesStats
|
// ✅ UTILISE GetClientPenaltiesStats
|
||||||
stats, err := database.GetClientPenaltiesStats()
|
stats, err := database.GetClientPenaltiesStats()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
utils.ServerErr(c, "Erreur lors de la récupération des statistiques", err)
|
||||||
"error": "Erreur lors de la récupération des statistiques",
|
|
||||||
"details": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -970,10 +926,7 @@ func ResetClientPointAdmin(c *gin.Context) {
|
|||||||
|
|
||||||
err := database.ResetClientPoint(username, req.Pool, extraPoolKey)
|
err := database.ResetClientPoint(username, req.Pool, extraPoolKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
utils.ServerErr(c, "Erreur lors de la réinitialisation", err)
|
||||||
"error": "Erreur lors de la réinitialisation",
|
|
||||||
"details": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1008,10 +961,7 @@ func ResetClientPenaltiesAdmin(c *gin.Context) {
|
|||||||
// ✅ UTILISE ResetClientPenalties
|
// ✅ UTILISE ResetClientPenalties
|
||||||
err := database.ResetClientPenalties(username, req.ResetCancellationsCount)
|
err := database.ResetClientPenalties(username, req.ResetCancellationsCount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
utils.ServerErr(c, "Erreur lors de la réinitialisation", err)
|
||||||
"error": "Erreur lors de la réinitialisation",
|
|
||||||
"details": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package handlers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"gestion/db"
|
"gestion/db"
|
||||||
|
"gestion/utils"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
@@ -26,7 +27,7 @@ func GetMyReferralBalance(c *gin.Context) {
|
|||||||
|
|
||||||
balance, err := database.GetClientReferralBalance(username.(string))
|
balance, err := database.GetClientReferralBalance(username.(string))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
c.JSON(http.StatusNotFound, gin.H{"error": "Solde de parrainage introuvable"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,8 +48,7 @@ func CreditClientReferralAdmin(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := database.CreditClientReferral(targetUsername, req.Amount); err != nil {
|
if err := database.CreditClientReferral(targetUsername, req.Amount); err != nil {
|
||||||
log.Printf("❌ [REFERRAL] Crédit échoué pour %s: %v", targetUsername, err)
|
utils.ServerErr(c, "Impossible de créditer le solde", err)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ func GetClientReferralAdmin(c *gin.Context) {
|
|||||||
|
|
||||||
balance, err := database.GetClientReferralBalance(targetUsername)
|
balance, err := database.GetClientReferralBalance(targetUsername)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
c.JSON(http.StatusNotFound, gin.H{"error": "Client introuvable"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ func GetPublicSettings(c *gin.Context) {
|
|||||||
"pool_names": poolNames,
|
"pool_names": poolNames,
|
||||||
"pool_keys": poolKeys,
|
"pool_keys": poolKeys,
|
||||||
"referral_enabled": settings.ReferralEnabled,
|
"referral_enabled": settings.ReferralEnabled,
|
||||||
|
"referral_amount": settings.ReferralAmount,
|
||||||
"delivery_schedule": settings.DeliverySchedule,
|
"delivery_schedule": settings.DeliverySchedule,
|
||||||
"crypto_payment_enabled": settings.CryptoPaymentEnabled,
|
"crypto_payment_enabled": settings.CryptoPaymentEnabled,
|
||||||
"crypto_only": settings.CryptoOnly,
|
"crypto_only": settings.CryptoOnly,
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ func UpdateMyProfile(c *gin.Context) {
|
|||||||
log.Printf("❌ [UPDATE_MY_PROFILE] Erreur binding: %v", err)
|
log.Printf("❌ [UPDATE_MY_PROFILE] Erreur binding: %v", err)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": "Données invalides",
|
"error": "Données invalides",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -186,7 +185,6 @@ func UpdateClientByAdmin(c *gin.Context) {
|
|||||||
log.Printf("❌ [UPDATE_CLIENT_ADMIN] Erreur binding: %v", err)
|
log.Printf("❌ [UPDATE_CLIENT_ADMIN] Erreur binding: %v", err)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": "Données invalides",
|
"error": "Données invalides",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -56,7 +57,6 @@ func ValidateDeliveryByLivreur(c *gin.Context) {
|
|||||||
log.Printf("❌ [VALIDATE_LIVREUR] Erreur JSON: %v", err)
|
log.Printf("❌ [VALIDATE_LIVREUR] Erreur JSON: %v", err)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": "Coordonnées GPS requises",
|
"error": "Coordonnées GPS requises",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -99,7 +99,6 @@ func ValidateDeliveryByLivreur(c *gin.Context) {
|
|||||||
log.Printf("❌ [VALIDATE_LIVREUR] Erreur update statut: %v", err)
|
log.Printf("❌ [VALIDATE_LIVREUR] Erreur update statut: %v", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Erreur validation",
|
"error": "Erreur validation",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -299,7 +298,6 @@ func StartDelivery(c *gin.Context) {
|
|||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": "Coordonnées GPS requises",
|
"error": "Coordonnées GPS requises",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -336,7 +334,6 @@ func StartDelivery(c *gin.Context) {
|
|||||||
if err := database.UpdateCommandStatus(commandID, "en_route"); err != nil {
|
if err := database.UpdateCommandStatus(commandID, "en_route"); err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Erreur mise à jour statut",
|
"error": "Erreur mise à jour statut",
|
||||||
"details": err.Error(),
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -354,7 +351,7 @@ func StartDelivery(c *gin.Context) {
|
|||||||
|
|
||||||
// Notifier le client
|
// Notifier le client
|
||||||
if clientUsername, _ := command["username"].(string); clientUsername != "" {
|
if clientUsername, _ := command["username"].(string); clientUsername != "" {
|
||||||
msg := fmt.Sprintf("🛵 Votre commande #%d est en route !", commandID)
|
msg := fmt.Sprintf("Un livreur vient de prendre en charge ta commande #%d, il est en route.", database.GetClientOrderID(commandID))
|
||||||
database.NotifyClient(clientUsername, commandID, "en_route", msg)
|
database.NotifyClient(clientUsername, commandID, "en_route", msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,11 +47,6 @@ type AdminClaims struct {
|
|||||||
var (
|
var (
|
||||||
userJWTSecret = []byte(os.Getenv("USER_JWT_SECRET")) // ✅ Pour clients
|
userJWTSecret = []byte(os.Getenv("USER_JWT_SECRET")) // ✅ Pour clients
|
||||||
adminJWTSecret = []byte(os.Getenv("ADMIN_JWT_SECRET")) // ✅ Pour admin/cabine/livreur
|
adminJWTSecret = []byte(os.Getenv("ADMIN_JWT_SECRET")) // ✅ Pour admin/cabine/livreur
|
||||||
roleHierarchy = map[string][]string{
|
|
||||||
"admin": {"admin", "cabine", "livreur", "client"},
|
|
||||||
"cabine": {"cabine", "livreur", "client"},
|
|
||||||
"livreur": {"livreur", "client"},
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
@@ -68,7 +63,7 @@ func validateClientToken(tokenString string, database *db.Database) (*ClientClai
|
|||||||
log.Printf("🔍 [VALIDATE-CLIENT] Validating client token...")
|
log.Printf("🔍 [VALIDATE-CLIENT] Validating client token...")
|
||||||
|
|
||||||
// Parser JWT EN PREMIER avec userJWTSecret (CLIENT)
|
// Parser JWT EN PREMIER avec userJWTSecret (CLIENT)
|
||||||
token, err := jwt.ParseWithClaims(tokenString, &ClientClaims{}, func(token *jwt.Token) (interface{}, error) {
|
token, err := jwt.ParseWithClaims(tokenString, &ClientClaims{}, func(token *jwt.Token) (any, error) {
|
||||||
// Vérifier explicitement l'algorithme
|
// Vérifier explicitement l'algorithme
|
||||||
if token.Method.Alg() != jwt.SigningMethodHS256.Alg() {
|
if token.Method.Alg() != jwt.SigningMethodHS256.Alg() {
|
||||||
return nil, fmt.Errorf("unexpected signing algorithm: %v", token.Method.Alg())
|
return nil, fmt.Errorf("unexpected signing algorithm: %v", token.Method.Alg())
|
||||||
@@ -116,8 +111,7 @@ func validateAdminToken(tokenString string, database *db.Database) (*AdminClaims
|
|||||||
|
|
||||||
log.Printf("🔍 [VALIDATE-ADMIN] Validating admin token...")
|
log.Printf("🔍 [VALIDATE-ADMIN] Validating admin token...")
|
||||||
|
|
||||||
// Parser JWT EN PREMIER avec adminJWTSecret (ADMIN/CABINE/LIVREUR)
|
token, err := jwt.ParseWithClaims(tokenString, &AdminClaims{}, func(token *jwt.Token) (any, error) {
|
||||||
token, err := jwt.ParseWithClaims(tokenString, &AdminClaims{}, func(token *jwt.Token) (interface{}, error) {
|
|
||||||
if token.Method.Alg() != jwt.SigningMethodHS256.Alg() {
|
if token.Method.Alg() != jwt.SigningMethodHS256.Alg() {
|
||||||
return nil, fmt.Errorf("unexpected signing algorithm: %v", token.Method.Alg())
|
return nil, fmt.Errorf("unexpected signing algorithm: %v", token.Method.Alg())
|
||||||
}
|
}
|
||||||
@@ -155,11 +149,6 @@ func validateAdminToken(tokenString string, database *db.Database) (*AdminClaims
|
|||||||
return claims, nil
|
return claims, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// MIDDLEWARE AUTHENTIFICATION CLIENT
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
// ClientMiddleware valide le JWT d'un client
|
|
||||||
func ClientMiddleware(c *gin.Context) {
|
func ClientMiddleware(c *gin.Context) {
|
||||||
authHeader := c.GetHeader("Authorization")
|
authHeader := c.GetHeader("Authorization")
|
||||||
if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") {
|
if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") {
|
||||||
@@ -180,7 +169,6 @@ func ClientMiddleware(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ NOUVEAU : Vérifier que le token n'a pas été révoqué
|
|
||||||
valid, err := database.IsTokenValid(tokenStr)
|
valid, err := database.IsTokenValid(tokenStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("❌ [CLIENT-MWARE] Erreur vérification token DB: %v", err)
|
log.Printf("❌ [CLIENT-MWARE] Erreur vérification token DB: %v", err)
|
||||||
@@ -195,7 +183,6 @@ func ClientMiddleware(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stocker les infos du client dans le contexte
|
|
||||||
c.Set("client_id", claims.ClientID)
|
c.Set("client_id", claims.ClientID)
|
||||||
c.Set("username", claims.Username)
|
c.Set("username", claims.Username)
|
||||||
c.Set("role", claims.Role)
|
c.Set("role", claims.Role)
|
||||||
@@ -206,12 +193,6 @@ func ClientMiddleware(c *gin.Context) {
|
|||||||
c.Next()
|
c.Next()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// MIDDLEWARE AUTHENTIFICATION ADMIN
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
// AdminMiddleware valide le JWT d'un admin (role == "admin" SEULEMENT)
|
|
||||||
// ✅ Remplace le AdminMiddleware de handlers/auth.go
|
|
||||||
func AdminMiddleware(c *gin.Context) {
|
func AdminMiddleware(c *gin.Context) {
|
||||||
authHeader := c.GetHeader("Authorization")
|
authHeader := c.GetHeader("Authorization")
|
||||||
if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") {
|
if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") {
|
||||||
@@ -232,7 +213,6 @@ func AdminMiddleware(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ Check révocation
|
|
||||||
valid, err := database.IsTokenValid(tokenStr)
|
valid, err := database.IsTokenValid(tokenStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("❌ [ADMIN-MWARE] Erreur vérification token DB: %v", err)
|
log.Printf("❌ [ADMIN-MWARE] Erreur vérification token DB: %v", err)
|
||||||
@@ -247,23 +227,14 @@ func AdminMiddleware(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vérifier rôle
|
// Vérifier rôle — admin uniquement
|
||||||
validRoles := []string{"admin", "cabine", "livreur"}
|
if claims.Role != "admin" {
|
||||||
isValid := false
|
log.Printf("❌ [ADMIN-MWARE] Role invalide: %s (admin requis)", claims.Role)
|
||||||
for _, role := range validRoles {
|
|
||||||
if claims.Role == role {
|
|
||||||
isValid = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !isValid {
|
|
||||||
log.Printf("❌ [ADMIN-MWARE] Role invalide: %s", claims.Role)
|
|
||||||
c.JSON(http.StatusForbidden, gin.H{"error": "Droits insuffisants - Accès admin requis"})
|
c.JSON(http.StatusForbidden, gin.H{"error": "Droits insuffisants - Accès admin requis"})
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stocker les infos de l'admin dans le contexte
|
|
||||||
c.Set("user_id", claims.UserID)
|
c.Set("user_id", claims.UserID)
|
||||||
c.Set("username", claims.Username)
|
c.Set("username", claims.Username)
|
||||||
c.Set("role", claims.Role)
|
c.Set("role", claims.Role)
|
||||||
@@ -275,12 +246,6 @@ func AdminMiddleware(c *gin.Context) {
|
|||||||
c.Next()
|
c.Next()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// MIDDLEWARE AUTHENTIFICATION CABINE
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
// CabineMiddleware valide que l'utilisateur a accès à la cabine
|
|
||||||
// ✅ Remplace le CabineMiddleware de handlers/auth.go
|
|
||||||
func CabineMiddleware(c *gin.Context) {
|
func CabineMiddleware(c *gin.Context) {
|
||||||
authHeader := c.GetHeader("Authorization")
|
authHeader := c.GetHeader("Authorization")
|
||||||
if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") {
|
if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") {
|
||||||
@@ -316,22 +281,10 @@ func CabineMiddleware(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vérifier hiérarchie des rôles
|
// Vérifier rôle — admin ou cabine uniquement
|
||||||
allowedRoles := roleHierarchy[claims.Role]
|
if claims.Role != "admin" && claims.Role != "cabine" {
|
||||||
authorized := false
|
|
||||||
for _, r := range allowedRoles {
|
|
||||||
if r == "cabine" {
|
|
||||||
authorized = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !authorized {
|
|
||||||
log.Printf("❌ [CABINE-MWARE] Role non autorisé: %s", claims.Role)
|
log.Printf("❌ [CABINE-MWARE] Role non autorisé: %s", claims.Role)
|
||||||
c.JSON(http.StatusForbidden, gin.H{
|
c.JSON(http.StatusForbidden, gin.H{"error": "Droits insuffisants - Accès cabine requis"})
|
||||||
"error": "Droits insuffisants - Accès cabine requis",
|
|
||||||
"your_role": claims.Role,
|
|
||||||
"allowed_roles": "admin, cabine",
|
|
||||||
})
|
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -347,12 +300,6 @@ func CabineMiddleware(c *gin.Context) {
|
|||||||
c.Next()
|
c.Next()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// MIDDLEWARE AUTHENTIFICATION LIVREUR
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
// LivreurMiddleware valide que l'utilisateur est livreur
|
|
||||||
// ✅ Remplace le LivreurMiddleware de handlers/auth.go
|
|
||||||
func LivreurMiddleware(c *gin.Context) {
|
func LivreurMiddleware(c *gin.Context) {
|
||||||
authHeader := c.GetHeader("Authorization")
|
authHeader := c.GetHeader("Authorization")
|
||||||
if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") {
|
if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") {
|
||||||
@@ -388,22 +335,10 @@ func LivreurMiddleware(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vérifier hiérarchie des rôles
|
// Vérifier rôle — admin ou livreur uniquement
|
||||||
allowedRoles := roleHierarchy[claims.Role]
|
if claims.Role != "admin" && claims.Role != "livreur" {
|
||||||
authorized := false
|
|
||||||
for _, r := range allowedRoles {
|
|
||||||
if r == "livreur" {
|
|
||||||
authorized = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !authorized {
|
|
||||||
log.Printf("❌ [LIVREUR-MWARE] Role non autorisé: %s", claims.Role)
|
log.Printf("❌ [LIVREUR-MWARE] Role non autorisé: %s", claims.Role)
|
||||||
c.JSON(http.StatusForbidden, gin.H{
|
c.JSON(http.StatusForbidden, gin.H{"error": "Droits insuffisants - Accès livreur requis"})
|
||||||
"error": "Droits insuffisants - Accès livreur requis",
|
|
||||||
"your_role": claims.Role,
|
|
||||||
"allowed_roles": "admin, livreur",
|
|
||||||
})
|
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -419,11 +354,6 @@ func LivreurMiddleware(c *gin.Context) {
|
|||||||
c.Next()
|
c.Next()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// SESSION MIDDLEWARE CLIENT (Existant)
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
// ClientSessionMiddleware valide la session Redis du client
|
|
||||||
func ClientSessionMiddleware(c *gin.Context) {
|
func ClientSessionMiddleware(c *gin.Context) {
|
||||||
database := c.MustGet("database").(*db.Database)
|
database := c.MustGet("database").(*db.Database)
|
||||||
|
|
||||||
@@ -507,17 +437,10 @@ func ClientSessionMiddleware(c *gin.Context) {
|
|||||||
c.Next()
|
c.Next()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// RATE LIMITING MIDDLEWARE
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
// RateLimitMiddleware limite le nombre de requêtes par client
|
|
||||||
// Config: 100 requêtes par minute par client
|
|
||||||
func RateLimitMiddleware(c *gin.Context) {
|
func RateLimitMiddleware(c *gin.Context) {
|
||||||
// Récupérer le client_id
|
// Récupérer le client_id
|
||||||
clientID, ok := c.Get("client_id")
|
clientID, ok := c.Get("client_id")
|
||||||
if !ok {
|
if !ok {
|
||||||
// Pas de client_id (requête publique), pas de rate limit
|
|
||||||
c.Next()
|
c.Next()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -533,12 +456,10 @@ func RateLimitMiddleware(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialiser le TTL à la première requête
|
|
||||||
if count == 1 {
|
if count == 1 {
|
||||||
db.Redis.Expire(db.RedisCtx, rateLimitKey, 60*time.Second) // 1 minute
|
db.Redis.Expire(db.RedisCtx, rateLimitKey, 60*time.Second) // 1 minute
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vérifier si dépassement (100 requêtes/min)
|
|
||||||
if count > 100 {
|
if count > 100 {
|
||||||
log.Printf("❌ [RATELIMIT] Client %d dépassé le limite: %d requêtes/min", clientIDInt, count)
|
log.Printf("❌ [RATELIMIT] Client %d dépassé le limite: %d requêtes/min", clientIDInt, count)
|
||||||
c.JSON(http.StatusTooManyRequests, gin.H{
|
c.JSON(http.StatusTooManyRequests, gin.H{
|
||||||
@@ -548,7 +469,6 @@ func RateLimitMiddleware(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ajouter le header du remaining
|
|
||||||
c.Header("X-RateLimit-Remaining", strconv.FormatInt(100-count, 10))
|
c.Header("X-RateLimit-Remaining", strconv.FormatInt(100-count, 10))
|
||||||
|
|
||||||
log.Printf("📊 [RATELIMIT] Client %d: %d/%d requêtes", clientIDInt, count, 100)
|
log.Printf("📊 [RATELIMIT] Client %d: %d/%d requêtes", clientIDInt, count, 100)
|
||||||
@@ -556,6 +476,40 @@ func RateLimitMiddleware(c *gin.Context) {
|
|||||||
c.Next()
|
c.Next()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoginRateLimitMiddleware limite les tentatives de connexion par IP.
|
||||||
|
// Config: 10 tentatives par 15 minutes.
|
||||||
|
func LoginRateLimitMiddleware(c *gin.Context) {
|
||||||
|
ip := c.GetHeader("X-Real-IP")
|
||||||
|
if ip == "" {
|
||||||
|
ip = c.ClientIP()
|
||||||
|
}
|
||||||
|
|
||||||
|
rateLimitKey := "ratelimit:login:" + ip
|
||||||
|
|
||||||
|
count, err := db.Redis.Incr(db.RedisCtx, rateLimitKey).Result()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("⚠️ [LOGIN-RATELIMIT] Erreur Redis: %v", err)
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if count == 1 {
|
||||||
|
db.Redis.Expire(db.RedisCtx, rateLimitKey, 15*time.Minute)
|
||||||
|
}
|
||||||
|
|
||||||
|
if count > 10 {
|
||||||
|
log.Printf("❌ [LOGIN-RATELIMIT] IP %s bloquée: %d tentatives/15min", ip, count)
|
||||||
|
c.JSON(http.StatusTooManyRequests, gin.H{
|
||||||
|
"error": "Trop de tentatives de connexion - Réessayez dans 15 minutes",
|
||||||
|
})
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Header("X-RateLimit-Remaining", strconv.FormatInt(10-count, 10))
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// HELPER MIDDLEWARE
|
// HELPER MIDDLEWARE
|
||||||
// ============================================
|
// ============================================
|
||||||
@@ -640,11 +594,6 @@ func LoadClientContext(c *gin.Context, database *db.Database) (*db.SessionData,
|
|||||||
return session, nil
|
return session, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// DATABASE MIDDLEWARE
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
// DatabaseMiddleware injecte la base de données dans le contexte
|
|
||||||
func DatabaseMiddleware(db *db.Database) gin.HandlerFunc {
|
func DatabaseMiddleware(db *db.Database) gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
c.Set("database", db)
|
c.Set("database", db)
|
||||||
|
|||||||
@@ -5,19 +5,22 @@ import "time"
|
|||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
ID int `gorm:"primaryKey;autoIncrement" json:"id"`
|
ID int `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||||
Username string `gorm:"column:username" json:"username"`
|
Username string `gorm:"column:username;not null" json:"username"`
|
||||||
Password string `gorm:"column:password" json:"-"`
|
Password string `gorm:"column:password" json:"-"`
|
||||||
Nom string `gorm:"column:nom" json:"nom"`
|
Nom string `gorm:"column:nom" json:"nom"`
|
||||||
Prenom string `gorm:"column:prenom" json:"prenom"`
|
Prenom string `gorm:"column:prenom" json:"prenom"`
|
||||||
Telephone string `gorm:"column:telephone" json:"telephone"`
|
Telephone string `gorm:"column:telephone;not null" json:"telephone"`
|
||||||
Command int `gorm:"column:command" json:"command"`
|
Command int `gorm:"column:command;default:0" json:"command"`
|
||||||
PointsExtra map[string]int `gorm:"-" json:"points_extra"`
|
CancelCommande int `gorm:"column:cancel_commande;default:0" json:"cancel_commande"`
|
||||||
Amende float64 `gorm:"column:amende" json:"amende"`
|
Amende float64 `gorm:"column:amende;default:0" json:"amende"`
|
||||||
CancellationsCount int `gorm:"column:cancellations_count" json:"cancellations_count"`
|
CancellationsCount int `gorm:"column:cancellations_count;not null;default:0" json:"cancellations_count"`
|
||||||
LastPenaltyReason string `gorm:"column:last_penalty_reason" json:"last_penalty_reason"`
|
LastPenaltyReason string `gorm:"column:last_penalty_reason" json:"last_penalty_reason"`
|
||||||
MustChangePassword bool `gorm:"column:must_change_password" json:"must_change_password"`
|
MustChangePassword bool `gorm:"column:must_change_password;default:false" json:"must_change_password"`
|
||||||
ReferralBalance float64 `gorm:"column:referral_balance" json:"referral_balance"`
|
ReferralBalance float64 `gorm:"column:referral_balance;default:0" json:"referral_balance"`
|
||||||
|
// points_extra est un JSONB géré manuellement (type non supporté nativement par GORM)
|
||||||
|
PointsExtra map[string]int `gorm:"-" json:"points_extra"`
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Client) TableName() string { return "clients" }
|
func (Client) TableName() string { return "clients" }
|
||||||
|
|||||||
@@ -3,28 +3,43 @@ package models
|
|||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
type Command struct {
|
type Command struct {
|
||||||
ID int `json:"id"`
|
ID int `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||||
UserID int `json:"user_id"`
|
ClientOrderID int `gorm:"column:client_order_id" json:"client_order_id"`
|
||||||
Username string `json:"username"`
|
UserID int `gorm:"column:user_id" json:"user_id"`
|
||||||
Status string `json:"status"` // "pending", "assigned", "livre", "approved", "cancelled", "disabled"
|
Username string `gorm:"column:username" json:"username"`
|
||||||
Total float64 `json:"total"`
|
Status string `gorm:"column:status" json:"status"`
|
||||||
DeliveryAddress string `json:"delivery_address"` // Adresse de livraison pour cette commande
|
Total float64 `gorm:"column:total_prix" json:"total"`
|
||||||
LivreurAssign string `json:"livreur_assign,omitempty"`
|
DeliveryAddress string `gorm:"column:adresse" json:"delivery_address"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
LivreurAssign string `gorm:"column:livreur_assign" json:"livreur_assign,omitempty"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (Command) TableName() string { return "commandes" }
|
||||||
|
|
||||||
// CommandItem représente un produit dans une commande
|
// CommandItem représente un produit dans une commande
|
||||||
type CommandItem struct {
|
type CommandItem struct {
|
||||||
ID int `json:"id"`
|
ID int `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||||
CommandID int `json:"command_id"`
|
CommandID int `gorm:"column:command_id" json:"command_id"`
|
||||||
ProductID int `json:"product_id"`
|
Produit string `gorm:"column:produit" json:"produit"`
|
||||||
Quantity int `json:"quantity"`
|
ProductID int `gorm:"column:product_id" json:"product_id"`
|
||||||
Price float64 `json:"price"`
|
Quantity float64 `gorm:"column:quantite" json:"quantity"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
Price float64 `gorm:"column:prix" json:"price"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CommandLog struct {
|
||||||
|
ID int `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||||
|
CommandID int `gorm:"column:command_id" json:"command_id"`
|
||||||
|
Status string `gorm:"column:status" json:"status"`
|
||||||
|
Message string `gorm:"column:message" json:"message"`
|
||||||
|
Author string `gorm:"column:author" json:"author"`
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (CommandLog) TableName() string { return "command_logs" }
|
||||||
|
|
||||||
type CommandPriority struct {
|
type CommandPriority struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import "time"
|
|||||||
type CryptoPayment struct {
|
type CryptoPayment struct {
|
||||||
ID int `json:"id" gorm:"primaryKey;autoIncrement"`
|
ID int `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||||
CommandID int `json:"command_id" gorm:"column:command_id;index"`
|
CommandID int `json:"command_id" gorm:"column:command_id;index"`
|
||||||
NowPaymentID string `json:"nowpayment_id" gorm:"column:nowpayment_id;uniqueIndex"`
|
NowPaymentID string `json:"nowpayment_id" gorm:"column:nowpayment_id;not null"`
|
||||||
Status string `json:"status" gorm:"column:status"`
|
Status string `json:"status" gorm:"column:status"`
|
||||||
PriceAmount float64 `json:"price_amount" gorm:"column:price_amount"`
|
PriceAmount float64 `json:"price_amount" gorm:"column:price_amount"`
|
||||||
PriceCurrency string `json:"price_currency" gorm:"column:price_currency"`
|
PriceCurrency string `json:"price_currency" gorm:"column:price_currency"`
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ type AppSettings struct {
|
|||||||
PointsEnabled bool `json:"points_enabled"` // afficher/activer le système de points
|
PointsEnabled bool `json:"points_enabled"` // afficher/activer le système de points
|
||||||
PointsPools []PointsPool `json:"points_pools"` // types de points personnalisés
|
PointsPools []PointsPool `json:"points_pools"` // types de points personnalisés
|
||||||
ReferralEnabled bool `json:"referral_enabled"` // activer/désactiver le système de parrainage
|
ReferralEnabled bool `json:"referral_enabled"` // activer/désactiver le système de parrainage
|
||||||
|
ReferralAmount float64 `json:"referral_amount"` // montant crédité par parrainage
|
||||||
CryptoPaymentEnabled bool `json:"crypto_payment_enabled"` // activer/désactiver le paiement crypto
|
CryptoPaymentEnabled bool `json:"crypto_payment_enabled"` // activer/désactiver le paiement crypto
|
||||||
CryptoOnly bool `json:"crypto_only"` // forcer le paiement crypto uniquement (pas d'espèces)
|
CryptoOnly bool `json:"crypto_only"` // forcer le paiement crypto uniquement (pas d'espèces)
|
||||||
NowPaymentsAPIKey string `json:"nowpayments_api_key"` // clé API NowPayments
|
NowPaymentsAPIKey string `json:"nowpayments_api_key"` // clé API NowPayments
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ import "time"
|
|||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
ID int `json:"id" gorm:"primaryKey;autoIncrement"`
|
ID int `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||||
Username string `json:"username" gorm:"column:username;uniqueIndex"`
|
Username string `json:"username" gorm:"column:username;not null"`
|
||||||
Password string `json:"password,omitempty" gorm:"column:password"`
|
Password string `json:"password,omitempty" gorm:"column:password"`
|
||||||
Role string `json:"role" gorm:"column:role"`
|
Role string `json:"role" gorm:"column:role"`
|
||||||
|
Total float64 `json:"total" gorm:"column:total;default:0"`
|
||||||
|
Livraison float64 `json:"livraison" gorm:"column:livraison;default:0"`
|
||||||
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
|
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
|
||||||
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
|
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ func SetupRoutes(router *gin.Engine, database *db.Database, geoService *services
|
|||||||
// ============================================
|
// ============================================
|
||||||
authGroupV1 := router.Group("/api/v1/auth")
|
authGroupV1 := router.Group("/api/v1/auth")
|
||||||
{
|
{
|
||||||
authGroupV1.POST("/login", handlers.LoginClient)
|
authGroupV1.POST("/login", middleware.LoginRateLimitMiddleware, handlers.LoginClient)
|
||||||
authGroupV1.POST("/logout", handlers.LogoutClient)
|
authGroupV1.POST("/logout", handlers.LogoutClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,21 +61,22 @@ func SetupRoutes(router *gin.Engine, database *db.Database, geoService *services
|
|||||||
cartGroupV1 := router.Group("/api/v1")
|
cartGroupV1 := router.Group("/api/v1")
|
||||||
cartGroupV1.Use(middleware.ClientMiddleware)
|
cartGroupV1.Use(middleware.ClientMiddleware)
|
||||||
cartGroupV1.Use(middleware.ClientSessionMiddleware)
|
cartGroupV1.Use(middleware.ClientSessionMiddleware)
|
||||||
|
cartGroupV1.Use(middleware.RateLimitMiddleware)
|
||||||
{
|
{
|
||||||
// Panier
|
// Panier
|
||||||
cartGroupV1.POST("/panier/add", handlers.AddProductsBasket)
|
cartGroupV1.POST("/panier/add", handlers.AddProductsBasket)
|
||||||
cartGroupV1.GET("/panier/:username", handlers.GetAllBaskets)
|
cartGroupV1.GET("/panier/:username", handlers.GetAllBaskets)
|
||||||
cartGroupV1.DELETE("/panier/remove", handlers.DeleteProductFromBasket)
|
cartGroupV1.DELETE("/panier/remove", handlers.DeleteProductFromBasket)
|
||||||
cartGroupV1.DELETE("/panier/clear", handlers.ClearBasket) // ✅ CORRIGÉ - Sans :username
|
cartGroupV1.DELETE("/panier/clear", handlers.ClearBasket)
|
||||||
|
|
||||||
// Commandes
|
// Commandes
|
||||||
cartGroupV1.POST("/checkout", middleware.OrderHoursMiddleware, middleware.BlockClientIfPenalty, handlers.ValidateBasket) // ✅ Auto-assign GPS
|
cartGroupV1.POST("/checkout", middleware.OrderHoursMiddleware, middleware.BlockClientIfPenalty, handlers.ValidateBasket) // ✅ Auto-assign GPS
|
||||||
cartGroupV1.GET("/my-commands", handlers.GetMyCommandsWithTracking) // ✅ Avec suivi
|
cartGroupV1.GET("/my-commands", handlers.GetMyCommandsWithTracking) // ✅ Avec suivi
|
||||||
|
|
||||||
// ⭐ NOUVEAUX - SUIVI CLIENT TEMPS RÉEL
|
// ⭐ NOUVEAUX - SUIVI CLIENT TEMPS RÉEL
|
||||||
cartGroupV1.GET("/commands/:id/eta", handlers.GetOrderETA) // ✅ AJOUTÉ
|
cartGroupV1.GET("/commands/:id/eta", handlers.GetOrderETA)
|
||||||
cartGroupV1.GET("/commands/:id/status", handlers.GetCommandStatus) // ✅ AJOUTÉ
|
cartGroupV1.GET("/commands/:id/status", handlers.GetCommandStatus)
|
||||||
cartGroupV1.GET("/commands/:id/tracking", handlers.GetCommandTracking) // ✅ AJOUTÉ
|
cartGroupV1.GET("/commands/:id/tracking", handlers.GetCommandTracking)
|
||||||
cartGroupV1.GET("/commands/:id", handlers.GetCommandByID)
|
cartGroupV1.GET("/commands/:id", handlers.GetCommandByID)
|
||||||
cartGroupV1.GET("/commands/:id/items", handlers.GetCommandItemsWithDetails)
|
cartGroupV1.GET("/commands/:id/items", handlers.GetCommandItemsWithDetails)
|
||||||
// Approbation livraison
|
// Approbation livraison
|
||||||
@@ -123,15 +124,6 @@ func SetupRoutes(router *gin.Engine, database *db.Database, geoService *services
|
|||||||
// ============================================
|
// ============================================
|
||||||
router.POST("/webhook/telegram", handlers.TelegramWebhook)
|
router.POST("/webhook/telegram", handlers.TelegramWebhook)
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// 🌍 GÉOCODAGE PUBLIC (v1)
|
|
||||||
// ============================================
|
|
||||||
geoGroupV1 := router.Group("/api/v1")
|
|
||||||
{
|
|
||||||
geoGroupV1.POST("/geocode", handlers.GeocodeAddress)
|
|
||||||
geoGroupV1.POST("/validate-address", handlers.ValidateAddress)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// 📋 PATTERN v2: ADMIN API
|
// 📋 PATTERN v2: ADMIN API
|
||||||
// ============================================
|
// ============================================
|
||||||
@@ -141,8 +133,8 @@ func SetupRoutes(router *gin.Engine, database *db.Database, geoService *services
|
|||||||
// ============================================
|
// ============================================
|
||||||
adminAuthGroupV2 := router.Group("/api/v2/admin/auth")
|
adminAuthGroupV2 := router.Group("/api/v2/admin/auth")
|
||||||
{
|
{
|
||||||
adminAuthGroupV2.POST("/register", handlers.RegisterAdmin)
|
//adminAuthGroupV2.POST("/register", handlers.RegisterAdmin)
|
||||||
adminAuthGroupV2.POST("/login", handlers.LoginAdmin)
|
adminAuthGroupV2.POST("/login", middleware.LoginRateLimitMiddleware, handlers.LoginAdmin)
|
||||||
adminAuthGroupV2.POST("/logout", handlers.LogoutAdmin)
|
adminAuthGroupV2.POST("/logout", handlers.LogoutAdmin)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BindErr log l'erreur de binding et renvoie 400 sans détails internes.
|
||||||
|
func BindErr(c *gin.Context, err error) {
|
||||||
|
log.Printf("⚠️ [BIND] %s %s: %v", c.Request.Method, c.Request.URL.Path, err)
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Données invalides"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerErr log l'erreur interne et renvoie 500 avec un message générique.
|
||||||
|
func ServerErr(c *gin.Context, msg string, err error) {
|
||||||
|
log.Printf("❌ [SERVER] %s %s — %s: %v", c.Request.Method, c.Request.URL.Path, msg, err)
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": msg})
|
||||||
|
}
|
||||||
@@ -189,7 +189,8 @@ func tryAssignCommandWithPriority(
|
|||||||
|
|
||||||
// 9. Notifier le client
|
// 9. Notifier le client
|
||||||
if clientUsername != "" {
|
if clientUsername != "" {
|
||||||
clientMsg := fmt.Sprintf("Votre commande #%d est confirmée ! Un livreur est en route.", commandID)
|
clientOrderID := database.GetClientOrderID(commandID)
|
||||||
|
clientMsg := fmt.Sprintf("Ta commande #%d est prise en compte ! Merci de rester branché et vigilant sur les notifs à venir.", clientOrderID)
|
||||||
database.NotifyClient(clientUsername, commandID, "assigned", clientMsg)
|
database.NotifyClient(clientUsername, commandID, "assigned", clientMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -240,7 +240,7 @@ function ProductDetail() {
|
|||||||
<div className="product-detail-content">
|
<div className="product-detail-content">
|
||||||
<div className={`product-image-section ${isOutOfStock ? "out-of-stock" : ""}`}>
|
<div className={`product-image-section ${isOutOfStock ? "out-of-stock" : ""}`}>
|
||||||
<img
|
<img
|
||||||
src={product.image || ""}
|
src={product.media?.find(m => m.type === "image")?.url || ""}
|
||||||
alt={product.name}
|
alt={product.name}
|
||||||
className="product-detail-image"
|
className="product-detail-image"
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user