chore: fix recompense categorie
Backend - Build & Lint / build (push) Has been cancelled
Frontend Client - EAS Build / build (push) Failing after 2h1m17s
Frontend Web - Build & Lint / build (push) Failing after 16m7s

This commit is contained in:
Nuxgrid
2026-07-18 16:41:42 +02:00
parent d6f5f2b1f2
commit dfa432862a
6 changed files with 156 additions and 47 deletions
+21
View File
@@ -143,6 +143,27 @@ func (d *Database) GetProductNamesByIDs(ids []int) (map[int]string, error) {
return result, nil return result, nil
} }
// GetProductCategoriesByIDs retourne un map id→category pour une liste d'IDs.
func (d *Database) GetProductCategoriesByIDs(ids []int) (map[int]string, error) {
result := make(map[int]string, len(ids))
if len(ids) == 0 {
return result, nil
}
rows, err := d.GDB.Raw(`SELECT id, category FROM products WHERE id IN ?`, ids).Rows()
if err != nil {
return result, err
}
defer rows.Close()
for rows.Next() {
var id int
var category string
if err := rows.Scan(&id, &category); err == nil {
result[id] = category
}
}
return result, nil
}
func (d *Database) GetAllProducts() ([]models.Product, error) { func (d *Database) GetAllProducts() ([]models.Product, error) {
log.Println("📦 [GetAllProducts] START") log.Println("📦 [GetAllProducts] START")
+112 -29
View File
@@ -11,6 +11,34 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
// eligibleRewardProductIDs détermine, pour un pool donné, quels product_id de
// reward.RewardItems sont éligibles : sa catégorie (via CategoryConfigs) doit
// faire partie des catégories du pool, soit par whitelist explicite (ProductIDs)
// soit par correspondance de catégorie produit (AllProducts).
func eligibleRewardProductIDs(reward *models.PointsReward, poolCategories map[string]bool, productCategories map[int]string) map[int]bool {
eligible := make(map[int]bool)
if reward == nil {
return eligible
}
for _, cfg := range reward.CategoryConfigs {
if !poolCategories[cfg.Category] {
continue
}
if cfg.AllProducts {
for pid, cat := range productCategories {
if cat == cfg.Category {
eligible[pid] = true
}
}
} else {
for _, pid := range cfg.ProductIDs {
eligible[pid] = true
}
}
}
return eligible
}
// GetMyPointsRewards retourne les points et les récompenses disponibles du client connecté. // GetMyPointsRewards retourne les points et les récompenses disponibles du client connecté.
// La récompense est globale : son seuil s'applique indépendamment à chaque pool. // La récompense est globale : son seuil s'applique indépendamment à chaque pool.
func GetMyPointsRewards(c *gin.Context) { func GetMyPointsRewards(c *gin.Context) {
@@ -48,14 +76,22 @@ func GetMyPointsRewards(c *gin.Context) {
ProductNames []string `json:"product_names"` ProductNames []string `json:"product_names"`
} }
type RewardItemResponse struct {
ProductID int `json:"product_id"`
ProductName string `json:"product_name"`
Quantity float64 `json:"quantity"`
Price float64 `json:"price"`
}
type PoolInfo struct { type PoolInfo struct {
Key string `json:"key"` Key string `json:"key"`
Name string `json:"name"` Name string `json:"name"`
Points int `json:"points"` Points int `json:"points"`
RewardsEarned int `json:"rewards_earned"` RewardsEarned int `json:"rewards_earned"`
RewardsClaimed int `json:"rewards_claimed"` RewardsClaimed int `json:"rewards_claimed"`
RewardsAvailable int `json:"rewards_available"` RewardsAvailable int `json:"rewards_available"`
EligibleConfigs []EligibleConfigResponse `json:"eligible_configs"` EligibleConfigs []EligibleConfigResponse `json:"eligible_configs"`
EligibleRewardItems []RewardItemResponse `json:"eligible_reward_items"`
} }
// Collecter tous les product_ids nécessaires en un seul passage // Collecter tous les product_ids nécessaires en un seul passage
@@ -73,6 +109,7 @@ func GetMyPointsRewards(c *gin.Context) {
} }
} }
productNames, _ := database.GetProductNamesByIDs(allProductIDs) productNames, _ := database.GetProductNamesByIDs(allProductIDs)
productCategories, _ := database.GetProductCategoriesByIDs(allProductIDs)
pools := make([]PoolInfo, 0, len(settings.PointsPools)) pools := make([]PoolInfo, 0, len(settings.PointsPools))
for _, pool := range settings.PointsPools { for _, pool := range settings.PointsPools {
@@ -111,24 +148,35 @@ func GetMyPointsRewards(c *gin.Context) {
} }
} }
eligibleProductIDs := eligibleRewardProductIDs(reward, poolCats, productCategories)
eligibleRewardItems := make([]RewardItemResponse, 0)
if reward != nil {
for _, item := range reward.RewardItems {
if !eligibleProductIDs[item.ProductID] {
continue
}
eligibleRewardItems = append(eligibleRewardItems, RewardItemResponse{
ProductID: item.ProductID,
ProductName: productNames[item.ProductID],
Quantity: item.Quantity,
Price: item.Price,
})
}
}
pools = append(pools, PoolInfo{ pools = append(pools, PoolInfo{
Key: pool.Key, Key: pool.Key,
Name: pool.Name, Name: pool.Name,
Points: pts, Points: pts,
RewardsEarned: earned, RewardsEarned: earned,
RewardsClaimed: redeemed, RewardsClaimed: redeemed,
RewardsAvailable: available, RewardsAvailable: available,
EligibleConfigs: eligibleConfigs, EligibleConfigs: eligibleConfigs,
EligibleRewardItems: eligibleRewardItems,
}) })
} }
// Construire la liste des produits récompense avec leurs noms // Construire la liste des produits récompense avec leurs noms
type RewardItemResponse struct {
ProductID int `json:"product_id"`
ProductName string `json:"product_name"`
Quantity float64 `json:"quantity"`
Price float64 `json:"price"`
}
var rewardMeta gin.H var rewardMeta gin.H
if reward != nil { if reward != nil {
rewardItems := make([]RewardItemResponse, 0, len(reward.RewardItems)) rewardItems := make([]RewardItemResponse, 0, len(reward.RewardItems))
@@ -191,27 +239,62 @@ func ClaimMyReward(c *gin.Context) {
return return
} }
// Vérifier que le pool existe // Vérifier que le pool existe et récupérer ses catégories
poolExists := false var selectedPool *models.PointsPool
for _, p := range settings.PointsPools { for i := range settings.PointsPools {
if p.Key == req.PoolKey { if settings.PointsPools[i].Key == req.PoolKey {
poolExists = true selectedPool = &settings.PointsPools[i]
break break
} }
} }
if !poolExists { if selectedPool == nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Pool introuvable"}) c.JSON(http.StatusBadRequest, gin.H{"error": "Pool introuvable"})
return return
} }
itemsToAdd := reward.RewardItems // Un produit récompense n'est éligible pour ce pool que si sa catégorie
if req.ProductID > 0 && len(reward.RewardItems) > 1 { // fait partie des catégories du pool (via CategoryConfigs) — sans ce
for _, item := range reward.RewardItems { // filtre, un client pourrait réclamer n'importe quel produit récompense
// (toutes catégories confondues) avec les points d'un pool quelconque.
poolCategories := make(map[string]bool, len(selectedPool.Categories))
for _, cat := range selectedPool.Categories {
poolCategories[cat] = true
}
rewardProductIDs := make([]int, 0, len(reward.RewardItems))
for _, item := range reward.RewardItems {
if item.ProductID > 0 {
rewardProductIDs = append(rewardProductIDs, item.ProductID)
}
}
productCategories, err := database.GetProductCategoriesByIDs(rewardProductIDs)
if err != nil {
utils.ServerErr(c, "Erreur lecture catégories produits", err)
return
}
eligibleProductIDs := eligibleRewardProductIDs(reward, poolCategories, productCategories)
eligibleItems := make([]models.RewardItem, 0, len(reward.RewardItems))
for _, item := range reward.RewardItems {
if eligibleProductIDs[item.ProductID] {
eligibleItems = append(eligibleItems, item)
}
}
itemsToAdd := eligibleItems
if req.ProductID > 0 {
itemsToAdd = nil
for _, item := range eligibleItems {
if item.ProductID == req.ProductID { if item.ProductID == req.ProductID {
itemsToAdd = []models.RewardItem{item} itemsToAdd = []models.RewardItem{item}
break break
} }
} }
if itemsToAdd == nil {
c.JSON(http.StatusForbidden, gin.H{"error": "Ce produit n'est pas éligible pour cette récompense"})
return
}
} }
remaining, added, err := database.ClaimPoolRewardAndAddToBasket(username, req.PoolKey, reward.Threshold, itemsToAdd) remaining, added, err := database.ClaimPoolRewardAndAddToBasket(username, req.PoolKey, reward.Threshold, itemsToAdd)
+8 -7
View File
@@ -2172,6 +2172,13 @@ export type RewardCategoryConfig = {
amount: number; amount: number;
}; };
export type RewardItemConfig = {
product_id: number;
product_name: string;
quantity: number;
price: number;
};
export type PointsPoolInfo = { export type PointsPoolInfo = {
key: string; key: string;
name: string; name: string;
@@ -2180,13 +2187,7 @@ export type PointsPoolInfo = {
rewards_claimed: number; rewards_claimed: number;
rewards_available: number; rewards_available: number;
eligible_configs: RewardCategoryConfig[]; eligible_configs: RewardCategoryConfig[];
}; eligible_reward_items: RewardItemConfig[];
export type RewardItemConfig = {
product_id: number;
product_name: string;
quantity: number;
price: number;
}; };
export type PointsRewardConfig = { export type PointsRewardConfig = {
@@ -180,7 +180,7 @@ function ConsultationHistorique() {
// Ouvre la modal de choix si plusieurs produits sont éligibles, // Ouvre la modal de choix si plusieurs produits sont éligibles,
// sinon réclame directement (1 seul produit configuré ou aucun item). // sinon réclame directement (1 seul produit configuré ou aucun item).
const openRewardModal = (pool: PointsPoolInfo) => { const openRewardModal = (pool: PointsPoolInfo) => {
const items = pointsRewards?.reward?.reward_items ?? []; const items = pool.eligible_reward_items ?? [];
if (items.length > 1) { if (items.length > 1) {
setSelectedProductId(items[0]?.product_id ?? null); setSelectedProductId(items[0]?.product_id ?? null);
setRewardModalPool(pool); setRewardModalPool(pool);
@@ -716,7 +716,7 @@ function ConsultationHistorique() {
</p> </p>
<div className="reward-modal-products"> <div className="reward-modal-products">
{(pointsRewards.reward.reward_items ?? []).map( {(rewardModalPool.eligible_reward_items ?? []).map(
(item) => { (item) => {
const isSelected = const isSelected =
selectedProductId === item.product_id; selectedProductId === item.product_id;
+8 -7
View File
@@ -971,6 +971,13 @@ export type RewardCategoryConfig = {
amount: number; amount: number;
}; };
export type RewardItemConfig = {
product_id: number;
product_name: string;
quantity: number;
price: number;
};
export type PointsPoolInfo = { export type PointsPoolInfo = {
key: string; key: string;
name: string; name: string;
@@ -979,13 +986,7 @@ export type PointsPoolInfo = {
rewards_claimed: number; rewards_claimed: number;
rewards_available: number; rewards_available: number;
eligible_configs: RewardCategoryConfig[]; eligible_configs: RewardCategoryConfig[];
}; eligible_reward_items: RewardItemConfig[];
export type RewardItemConfig = {
product_id: number;
product_name: string;
quantity: number;
price: number;
}; };
export type PointsRewardConfig = { export type PointsRewardConfig = {
@@ -124,7 +124,8 @@ export default function OrderHistoryScreen() {
}, []); }, []);
const handleClaim = (poolKey: string) => { const handleClaim = (poolKey: string) => {
const items = pointsRewards?.reward?.reward_items ?? []; const pool = pointsRewards?.pools.find((p) => p.key === poolKey);
const items = pool?.eligible_reward_items ?? [];
if (items.length > 1) { if (items.length > 1) {
setClaimFeedback(null); setClaimFeedback(null);
setRewardPickerPool(poolKey); setRewardPickerPool(poolKey);
@@ -1096,7 +1097,9 @@ export default function OrderHistoryScreen() {
<ScrollView showsVerticalScrollIndicator={false}> <ScrollView showsVerticalScrollIndicator={false}>
{( {(
pointsRewards?.reward?.reward_items ?? [] pointsRewards?.pools.find(
(p) => p.key === rewardPickerPool,
)?.eligible_reward_items ?? []
).map((item: RewardItemConfig, idx: number) => ( ).map((item: RewardItemConfig, idx: number) => (
<TouchableOpacity <TouchableOpacity
key={idx} key={idx}