chore: fix recompense categorie
This commit is contained in:
@@ -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")
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
Reference in New Issue
Block a user