chore: build
Frontend Admin - EAS Build / build (push) Canceled after 0s
Frontend Client - EAS Build / build (push) Canceled after 0s
Backend - Build & Lint / build (push) Failing after 25m18s
Frontend Web - Build & Lint / build (push) Failing after 9m58s

This commit is contained in:
Xor290
2026-08-06 12:06:05 +02:00
parent c034088bee
commit 22a8d5026c
174 changed files with 30315 additions and 16120 deletions
+88 -35
View File
@@ -32,6 +32,7 @@ func (d *Database) CreateClient(client *models.Client) error {
return nil
}
// GetClientByID récupère un client par son ID
func (d *Database) GetClientByID(id int) (*models.Client, error) {
var row struct {
ID int `gorm:"column:id"`
@@ -75,6 +76,7 @@ func (d *Database) GetClientByID(id int) (*models.Client, error) {
return client, nil
}
// GetAllClients récupère tous les clients
func (d *Database) GetAllClients() ([]*models.Client, error) {
var rows []struct {
ID int `gorm:"column:id"`
@@ -290,6 +292,28 @@ func (d *Database) GetClientByTelephone(telephone string) (*models.Client, error
}
// GetClientByUsername récupère un client par son username
// GetClientsByUsernames charge plusieurs clients en une seule requête.
// Retourne map[username]*Client ; les usernames sans correspondance sont absents de la map.
func (d *Database) GetClientsByUsernames(usernames []string) (map[string]*models.Client, error) {
result := make(map[string]*models.Client, len(usernames))
if len(usernames) == 0 {
return result, nil
}
var rows []struct {
ID int `gorm:"column:id"`
Username string `gorm:"column:username"`
Nom string `gorm:"column:nom"`
Prenom string `gorm:"column:prenom"`
}
if err := d.GDB.Raw(`SELECT id, username, nom, prenom FROM clients WHERE username IN ?`, usernames).Scan(&rows).Error; err != nil {
return nil, err
}
for _, r := range rows {
result[r.Username] = &models.Client{ID: r.ID, Username: r.Username, Nom: r.Nom, Prenom: r.Prenom}
}
return result, nil
}
func (d *Database) GetClientByUsername(username string) (*models.Client, error) {
var row struct {
ID int `gorm:"column:id"`
@@ -713,52 +737,81 @@ func (d *Database) GetClientPointsAndRewards(username string) (pointsExtra map[s
return pointsExtra, pointsRedeemed, nil
}
// claimPoolRewardTx vérifie l'éligibilité et consomme une récompense pour un
// pool donné, dans la transaction fournie — factorisée pour être appelée
// seule (ClaimPoolReward) ou combinée avec la livraison du produit dans la
// même transaction (ClaimPoolRewardAndAddToBasket), afin qu'une récompense
// ne soit jamais consommée sans que son produit soit effectivement livré.
func claimPoolRewardTx(tx *gorm.DB, username, poolKey string, threshold int) (remainingAvailable int, err error) {
var row struct {
Points int `gorm:"column:pts"`
Redeemed int `gorm:"column:redeemed"`
}
if err := tx.Raw(`
SELECT
COALESCE((points_extra->>?)::int, 0) as pts,
COALESCE((points_redeemed->>?)::int, 0) as redeemed
FROM clients WHERE username = ? FOR UPDATE`,
poolKey, poolKey, username).Scan(&row).Error; err != nil {
return 0, fmt.Errorf("erreur lecture: %w", err)
}
earned := row.Points / threshold
available := earned - row.Redeemed
if available <= 0 {
return 0, fmt.Errorf("pas de récompense disponible pour ce pool")
}
if err := tx.Exec(`
UPDATE clients
SET points_redeemed = jsonb_set(
COALESCE(points_redeemed, '{}'::jsonb),
ARRAY[?],
to_jsonb(COALESCE((points_redeemed->>?)::int, 0) + 1)
), updated_at = CURRENT_TIMESTAMP
WHERE username = ?`,
poolKey, poolKey, username).Error; err != nil {
return 0, err
}
return earned - (row.Redeemed + 1), nil
}
// ClaimPoolReward réclame une récompense pour un pool donné si le client a assez de points.
// Retourne le nombre de récompenses disponibles restantes après la réclamation.
func (d *Database) ClaimPoolReward(username, poolKey string, threshold int) (remainingAvailable int, err error) {
var points, redeemed int
err = d.GDB.Transaction(func(tx *gorm.DB) error {
var row struct {
Points int `gorm:"column:pts"`
Redeemed int `gorm:"column:redeemed"`
}
if err := tx.Raw(`
SELECT
COALESCE((points_extra->>?)::int, 0) as pts,
COALESCE((points_redeemed->>?)::int, 0) as redeemed
FROM clients WHERE username = ? FOR UPDATE`,
poolKey, poolKey, username).Scan(&row).Error; err != nil {
return fmt.Errorf("erreur lecture: %w", err)
}
points = row.Points
redeemed = row.Redeemed
earned := points / threshold
available := earned - redeemed
if available <= 0 {
return fmt.Errorf("pas de récompense disponible pour ce pool")
}
return tx.Exec(`
UPDATE clients
SET points_redeemed = jsonb_set(
COALESCE(points_redeemed, '{}'::jsonb),
ARRAY[?],
to_jsonb(COALESCE((points_redeemed->>?)::int, 0) + 1)
), updated_at = CURRENT_TIMESTAMP
WHERE username = ?`,
poolKey, poolKey, username).Error
var err error
remainingAvailable, err = claimPoolRewardTx(tx, username, poolKey, threshold)
return err
})
if err != nil {
return 0, err
}
earned := points / threshold
remainingAvailable = earned - (redeemed + 1)
return remainingAvailable, nil
}
func (d *Database) ClaimPoolRewardAndAddToBasket(username, poolKey string, threshold int, items []models.RewardItem) (remainingAvailable int, added []models.Panier, err error) {
err = d.GDB.Transaction(func(tx *gorm.DB) error {
var err error
remainingAvailable, err = claimPoolRewardTx(tx, username, poolKey, threshold)
if err != nil {
return err
}
if len(items) > 0 {
added, err = addRewardsToBasketTx(tx, username, items, poolKey)
if err != nil {
return err
}
}
return nil
})
if err != nil {
return 0, nil, err
}
return remainingAvailable, added, nil
}
// ResetClientRedeemed remet à zéro les récompenses réclamées (admin).
func (d *Database) ResetClientRedeemed(username, poolKey string) error {
if poolKey != "" {