Procházet zdrojové kódy

【代码优化】商城: 满减送活动 CRUD 部分

puhui999 před 8 měsíci
rodič
revize
710f29d911
11 změnil soubory, kde provedl 220 přidání a 175 odebrání
  1. 40 6
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/reward/dto/RewardActivityMatchRespDTO.java
  2. 2 1
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java
  3. 13 0
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/common/PromotionProductScopeEnum.java
  4. 52 22
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/activity/AppActivityController.java
  5. 2 2
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/reward/RewardActivityDO.java
  6. 4 8
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/reward/RewardActivityMapper.java
  7. 1 2
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityService.java
  8. 33 67
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImpl.java
  9. 68 62
      yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImplTest.java
  10. 2 2
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java
  11. 3 3
      yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculatorTest.java

+ 40 - 6
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/reward/dto/RewardActivityMatchRespDTO.java

@@ -1,8 +1,12 @@
 package cn.iocoder.yudao.module.promotion.api.reward.dto;
 
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.module.promotion.enums.common.PromotionConditionTypeEnum;
+import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
 import lombok.Data;
 
+import java.io.Serializable;
+import java.time.LocalDateTime;
 import java.util.List;
 
 /**
@@ -21,6 +25,24 @@ public class RewardActivityMatchRespDTO {
      * 活动标题
      */
     private String name;
+    /**
+     * 状态
+     *
+     * 枚举 {@link CommonStatusEnum}
+     */
+    private Integer status;
+    /**
+     * 开始时间
+     */
+    private LocalDateTime startTime;
+    /**
+     * 结束时间
+     */
+    private LocalDateTime endTime;
+    /**
+     * 备注
+     */
+    private String remark;
     /**
      * 条件类型
      *
@@ -28,21 +50,25 @@ public class RewardActivityMatchRespDTO {
      */
     private Integer conditionType;
     /**
-     * 优惠规则的数组
+     * 商品范围
+     *
+     * 枚举 {@link PromotionProductScopeEnum}
      */
-    private List<Rule> rules;
-
+    private Integer productScope;
     /**
      * 商品 SPU 编号的数组
      */
-    private List<Long> spuIds;
+    private List<Long> productScopeValues;
+    /**
+     * 优惠规则的数组
+     */
+    private List<Rule> rules;
 
-    // TODO 芋艿:后面 RewardActivityRespDTO 有了之后,Rule 可以放过去
     /**
      * 优惠规则
      */
     @Data
-    public static class Rule {
+    public static class Rule implements Serializable {
 
         /**
          * 优惠门槛
@@ -59,10 +85,18 @@ public class RewardActivityMatchRespDTO {
          * 是否包邮
          */
         private Boolean freeDelivery;
+        /**
+         * 是否赠送积分
+         */
+        private Boolean givePoint;
         /**
          * 赠送的积分
          */
         private Integer point;
+        /**
+         * 是否赠送优惠券
+         */
+        private Boolean giveCoupon;
         /**
          * 赠送的优惠劵编号的数组
          */

+ 2 - 1
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java

@@ -44,7 +44,8 @@ public interface ErrorCodeConstants {
     ErrorCode REWARD_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_006_002, "满减送活动已关闭,不能修改");
     ErrorCode REWARD_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED = new ErrorCode(1_013_006_003, "满减送活动未关闭,不能删除");
     ErrorCode REWARD_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_006_004, "满减送活动已关闭,不能重复关闭");
-    ErrorCode REWARD_ACTIVITY_CLOSE_FAIL_STATUS_END = new ErrorCode(1_013_006_005, "满减送活动已结束,不能关闭");
+    ErrorCode REWARD_ACTIVITY_SCOPE_ALL_EXISTS = new ErrorCode(1_013_006_005, "已存在商品范围为全场的满减送活动");
+    ErrorCode REWARD_ACTIVITY_SCOPE_CATEGORY_EXISTS = new ErrorCode(1_013_006_006, "存在商品类型参加了其它满减送活动");
 
     // ========== TODO 空着 1-013-007-000 ============
 

+ 13 - 0
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/common/PromotionProductScopeEnum.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.promotion.enums.common;
 
+import cn.hutool.core.util.ObjUtil;
 import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
@@ -35,4 +36,16 @@ public enum PromotionProductScopeEnum implements IntArrayValuable {
         return ARRAYS;
     }
 
+    public static boolean isAll(Integer scope) {
+        return ObjUtil.equal(scope, ALL.scope);
+    }
+
+    public static boolean isSpu(Integer scope) {
+        return ObjUtil.equal(scope, SPU.scope);
+    }
+
+    public static boolean isCategory(Integer scope) {
+        return ObjUtil.equal(scope, CATEGORY.scope);
+    }
+
 }

+ 52 - 22
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/activity/AppActivityController.java

@@ -2,14 +2,19 @@ package cn.iocoder.yudao.module.promotion.controller.app.activity;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.ObjUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
+import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
 import cn.iocoder.yudao.module.promotion.controller.app.activity.vo.AppActivityRespVO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainActivityDO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountActivityDO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountProductDO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillActivityDO;
+import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
 import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
 import cn.iocoder.yudao.module.promotion.service.bargain.BargainActivityService;
 import cn.iocoder.yudao.module.promotion.service.combination.CombinationActivityService;
@@ -48,6 +53,8 @@ public class AppActivityController {
     private DiscountActivityService discountActivityService;
     @Resource
     private RewardActivityService rewardActivityService;
+    @Resource
+    private ProductSpuApi productSpuApi;
 
     @GetMapping("/list-by-spu-id")
     @Operation(summary = "获得单个商品,近期参与的每个活动")
@@ -141,29 +148,52 @@ public class AppActivityController {
                 item.getName(), productMap.get(item.getId()), item.getStartTime(), item.getEndTime())));
     }
 
+    private static void buildAppActivityRespVO(RewardActivityDO rewardActivity, Collection<Long> spuIds,
+                                               List<AppActivityRespVO> activityList) {
+        for (Long spuId : spuIds) {
+            // 校验商品是否已经加入过活动
+            if (anyMatch(activityList, appActivity -> ObjUtil.equal(appActivity.getId(), rewardActivity.getId()) &&
+                    ObjUtil.equal(appActivity.getSpuId(), spuId))) {
+                continue;
+            }
+            activityList.add(new AppActivityRespVO(rewardActivity.getId(),
+                    PromotionTypeEnum.REWARD_ACTIVITY.getType(), rewardActivity.getName(), spuId,
+                    rewardActivity.getStartTime(), rewardActivity.getEndTime()));
+        }
+    }
+
     private void getRewardActivities(Collection<Long> spuIds, LocalDateTime now, List<AppActivityRespVO> activityList) {
-        // TODO @puhui999:有 3 范围,不只 spuId,还有 categoryId,全部,下次 fix
-        //List<RewardActivityDO> rewardActivityList = rewardActivityService.getRewardActivityBySpuIdsAndStatusAndDateTimeLt(
-        //        spuIds, PromotionActivityStatusEnum.RUN.getStatus(), now);
-        //if (CollUtil.isEmpty(rewardActivityList)) {
-        //    return;
-        //}
-        //
-        //Map<Long, Optional<RewardActivityDO>> spuIdAndActivityMap = spuIds.stream()
-        //        .collect(Collectors.toMap(
-        //                spuId -> spuId,
-        //                spuId -> rewardActivityList.stream()
-        //                        .filter(activity -> activity.getProductSpuIds().contains(spuId))
-        //                        .max(Comparator.comparing(RewardActivityDO::getCreateTime))));
-        //for (Long supId : spuIdAndActivityMap.keySet()) {
-        //    if (spuIdAndActivityMap.get(supId).isEmpty()) {
-        //        continue;
-        //    }
-        //
-        //    RewardActivityDO rewardActivityDO = spuIdAndActivityMap.get(supId).get();
-        //    activityList.add(new AppActivityRespVO(rewardActivityDO.getId(), PromotionTypeEnum.REWARD_ACTIVITY.getType(),
-        //            rewardActivityDO.getName(), supId, rewardActivityDO.getStartTime(), rewardActivityDO.getEndTime()));
-        //}
+        // 1.1 获得所有的活动
+        List<RewardActivityDO> rewardActivityList = rewardActivityService.getRewardActivityByStatusAndDateTimeLt(
+                CommonStatusEnum.ENABLE.getStatus(), now);
+        if (CollUtil.isEmpty(rewardActivityList)) {
+            return;
+        }
+        // 1.2 获得所有的商品信息
+        List<ProductSpuRespDTO> spuList = productSpuApi.getSpuList(spuIds);
+        if (CollUtil.isEmpty(spuList)) {
+            return;
+        }
+
+        // 2. 构建活动
+        for (RewardActivityDO rewardActivity : rewardActivityList) {
+            // 情况一:所有商品都能参加
+            if (PromotionProductScopeEnum.isAll(rewardActivity.getProductScope())) {
+                buildAppActivityRespVO(rewardActivity, spuIds, activityList);
+            }
+            // 情况二:指定商品参加
+            if (PromotionProductScopeEnum.isSpu(rewardActivity.getProductScope())) {
+                List<Long> fSpuIds = spuList.stream().map(ProductSpuRespDTO::getId).filter(id ->
+                        rewardActivity.getProductScopeValues().contains(id)).toList();
+                buildAppActivityRespVO(rewardActivity, fSpuIds, activityList);
+            }
+            // 情况三:指定商品类型参加
+            if (PromotionProductScopeEnum.isCategory(rewardActivity.getProductScope())) {
+                List<Long> fSpuIds = spuList.stream().filter(spuItem -> rewardActivity.getProductScopeValues()
+                        .contains(spuItem.getCategoryId())).map(ProductSpuRespDTO::getId).toList();
+                buildAppActivityRespVO(rewardActivity, fSpuIds, activityList);
+            }
+        }
     }
 
 }

+ 2 - 2
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/reward/RewardActivityDO.java

@@ -1,8 +1,8 @@
 package cn.iocoder.yudao.module.promotion.dal.dataobject.reward;
 
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
 import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
-import cn.iocoder.yudao.module.promotion.enums.common.PromotionActivityStatusEnum;
 import cn.iocoder.yudao.module.promotion.enums.common.PromotionConditionTypeEnum;
 import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
 import com.baomidou.mybatisplus.annotation.KeySequence;
@@ -40,7 +40,7 @@ public class RewardActivityDO extends BaseDO {
     /**
      * 状态
      *
-     * 枚举 {@link PromotionActivityStatusEnum}
+     * 枚举 {@link CommonStatusEnum}
      */
     private Integer status;
     /**

+ 4 - 8
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/reward/RewardActivityMapper.java

@@ -30,10 +30,6 @@ public interface RewardActivityMapper extends BaseMapperX<RewardActivityDO> {
                 .orderByDesc(RewardActivityDO::getId));
     }
 
-    default List<RewardActivityDO> selectListByStatus(Collection<Integer> statuses) {
-        return selectList(RewardActivityDO::getStatus, statuses);
-    }
-
     default List<RewardActivityDO> selectListByProductScopeAndStatus(Integer productScope, Integer status) {
         return selectList(new LambdaQueryWrapperX<RewardActivityDO>()
                 .eq(RewardActivityDO::getProductScope, productScope)
@@ -53,16 +49,16 @@ public interface RewardActivityMapper extends BaseMapperX<RewardActivityDO> {
      * 获取指定活动编号的活动列表且
      * 开始时间和结束时间小于给定时间 dateTime 的活动列表
      *
-     * @param ids      活动编号
+     * @param status   状态
      * @param dateTime 指定日期
      * @return 活动列表
      */
-    default List<RewardActivityDO> selectListByIdsAndDateTimeLt(Collection<Long> ids, LocalDateTime dateTime) {
+    default List<RewardActivityDO> selectListByStatusAndDateTimeLt(Integer status, LocalDateTime dateTime) {
         return selectList(new LambdaQueryWrapperX<RewardActivityDO>()
-                .in(RewardActivityDO::getId, ids)
+                .eq(RewardActivityDO::getStatus, status)
                 .lt(RewardActivityDO::getStartTime, dateTime)
                 .gt(RewardActivityDO::getEndTime, dateTime)// 开始时间 < 指定时间 < 结束时间,也就是说获取指定时间段的活动
-                .orderByDesc(RewardActivityDO::getCreateTime)
+                .orderByAsc(RewardActivityDO::getStartTime)
         );
     }
 

+ 1 - 2
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityService.java

@@ -75,11 +75,10 @@ public interface RewardActivityService {
     /**
      * 获取指定 spu 编号最近参加的活动,每个 spuId 只返回一条记录
      *
-     * @param spuIds   spu 编号
      * @param status   状态
      * @param dateTime 当前日期时间
      * @return 满减送活动列表
      */
-    List<RewardActivityDO> getRewardActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime);
+    List<RewardActivityDO> getRewardActivityByStatusAndDateTimeLt(Integer status, LocalDateTime dateTime);
 
 }

+ 33 - 67
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImpl.java

@@ -1,17 +1,18 @@
 package cn.iocoder.yudao.module.promotion.service.reward;
 
-import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.product.api.category.ProductCategoryApi;
 import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
 import cn.iocoder.yudao.module.promotion.api.reward.dto.RewardActivityMatchRespDTO;
+import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityBaseVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityCreateReqVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityPageReqVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityUpdateReqVO;
 import cn.iocoder.yudao.module.promotion.convert.reward.RewardActivityConvert;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
 import cn.iocoder.yudao.module.promotion.dal.mysql.reward.RewardActivityMapper;
-import cn.iocoder.yudao.module.promotion.enums.common.PromotionActivityStatusEnum;
 import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
 import cn.iocoder.yudao.module.promotion.util.PromotionUtils;
 import jakarta.annotation.Resource;
@@ -20,14 +21,13 @@ import org.springframework.validation.annotation.Validated;
 
 import java.time.LocalDateTime;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 
+import static cn.hutool.core.collection.CollUtil.intersectionDistinct;
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.anyMatch;
 import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
-import static java.util.Arrays.asList;
 
 /**
  * 满减送活动 Service 实现类
@@ -51,7 +51,7 @@ public class RewardActivityServiceImpl implements RewardActivityService {
         // 1.1 校验商品范围
         validateProductScope(createReqVO.getProductScope(), createReqVO.getProductScopeValues());
         // 1.2 校验商品是否冲突
-        //validateRewardActivitySpuConflicts(null, createReqVO.getProductSpuIds());
+        validateRewardActivitySpuConflicts(null, createReqVO);
 
         // 2. 插入
         RewardActivityDO rewardActivity = RewardActivityConvert.INSTANCE.convert(createReqVO)
@@ -65,13 +65,13 @@ public class RewardActivityServiceImpl implements RewardActivityService {
     public void updateRewardActivity(RewardActivityUpdateReqVO updateReqVO) {
         // 1.1 校验存在
         RewardActivityDO dbRewardActivity = validateRewardActivityExists(updateReqVO.getId());
-        if (dbRewardActivity.getStatus().equals(PromotionActivityStatusEnum.CLOSE.getStatus())) { // 已关闭的活动,不能修改噢
+        if (dbRewardActivity.getStatus().equals(CommonStatusEnum.DISABLE.getStatus())) { // 已关闭的活动,不能修改噢
             throw exception(REWARD_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED);
         }
         // 1.2 校验商品范围
         validateProductScope(updateReqVO.getProductScope(), updateReqVO.getProductScopeValues());
         // 1.3 校验商品是否冲突
-        //validateRewardActivitySpuConflicts(updateReqVO.getId(), updateReqVO.getProductSpuIds());
+        validateRewardActivitySpuConflicts(updateReqVO.getId(), updateReqVO);
 
         // 2. 更新
         RewardActivityDO updateObj = RewardActivityConvert.INSTANCE.convert(updateReqVO)
@@ -82,17 +82,13 @@ public class RewardActivityServiceImpl implements RewardActivityService {
     @Override
     public void closeRewardActivity(Long id) {
         // 校验存在
-        // TODO @puhui999:去掉 PromotionActivityStatusEnum,使用 CommonStatus 作为状态哈。开启,关闭
         RewardActivityDO dbRewardActivity = validateRewardActivityExists(id);
-        if (dbRewardActivity.getStatus().equals(PromotionActivityStatusEnum.CLOSE.getStatus())) { // 已关闭的活动,不能关闭噢
+        if (dbRewardActivity.getStatus().equals(CommonStatusEnum.DISABLE.getStatus())) { // 已关闭的活动,不能关闭噢
             throw exception(REWARD_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED);
         }
-        if (dbRewardActivity.getStatus().equals(PromotionActivityStatusEnum.END.getStatus())) { // 已关闭的活动,不能关闭噢
-            throw exception(REWARD_ACTIVITY_CLOSE_FAIL_STATUS_END);
-        }
 
         // 更新
-        RewardActivityDO updateObj = new RewardActivityDO().setId(id).setStatus(PromotionActivityStatusEnum.CLOSE.getStatus());
+        RewardActivityDO updateObj = new RewardActivityDO().setId(id).setStatus(CommonStatusEnum.DISABLE.getStatus());
         rewardActivityMapper.updateById(updateObj);
     }
 
@@ -100,7 +96,7 @@ public class RewardActivityServiceImpl implements RewardActivityService {
     public void deleteRewardActivity(Long id) {
         // 校验存在
         RewardActivityDO dbRewardActivity = validateRewardActivityExists(id);
-        if (!dbRewardActivity.getStatus().equals(PromotionActivityStatusEnum.CLOSE.getStatus())) { // 未关闭的活动,不能删除噢
+        if (dbRewardActivity.getStatus().equals(CommonStatusEnum.ENABLE.getStatus())) { // 未关闭的活动,不能删除噢
             throw exception(REWARD_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED);
         }
 
@@ -116,27 +112,30 @@ public class RewardActivityServiceImpl implements RewardActivityService {
         return activity;
     }
 
-    // TODO @芋艿:逻辑有问题,需要优化;要分成全场、和指定来校验;
-    // TODO @puhui999: 下次提交 fix
     /**
      * 校验商品参加的活动是否冲突
      *
-     * @param id     活动编号
-     * @param spuIds 商品 SPU 编号数组
+     * @param id             活动编号
+     * @param rewardActivity 请求
      */
-    private void validateRewardActivitySpuConflicts(Long id, Collection<Long> spuIds) {
-        if (CollUtil.isEmpty(spuIds)) {
-            return;
-        }
-        // 查询商品参加的活动
-        List<RewardActivityDO> rewardActivityList = getRewardActivityListBySpuIds(spuIds,
-                asList(PromotionActivityStatusEnum.WAIT.getStatus(), PromotionActivityStatusEnum.RUN.getStatus()));
+    private void validateRewardActivitySpuConflicts(Long id, RewardActivityBaseVO rewardActivity) {
+        List<RewardActivityDO> list = rewardActivityMapper.selectList(RewardActivityDO::getProductScope,
+                rewardActivity.getProductScope(), RewardActivityDO::getStatus, CommonStatusEnum.ENABLE.getStatus());
         if (id != null) { // 排除自己这个活动
-            rewardActivityList.removeIf(activity -> id.equals(activity.getId()));
+            list.removeIf(activity -> id.equals(activity.getId()));
+        }
+
+        // 情况一:全部商品参加
+        if (PromotionProductScopeEnum.isAll(rewardActivity.getProductScope()) && !list.isEmpty()) {
+            throw exception(REWARD_ACTIVITY_SCOPE_ALL_EXISTS);
         }
-        // 如果非空,则说明冲突
-        if (CollUtil.isNotEmpty(rewardActivityList)) {
-            throw exception(REWARD_ACTIVITY_SPU_CONFLICTS);
+        if (PromotionProductScopeEnum.isSpu(rewardActivity.getProductScope()) ||  // 情况二:指定商品参加
+                PromotionProductScopeEnum.isCategory(rewardActivity.getProductScope())) {  // 情况三:指定商品类型参加
+            if (anyMatch(list, item -> !intersectionDistinct(item.getProductScopeValues(),
+                    rewardActivity.getProductScopeValues()).isEmpty())) {
+                throw exception(PromotionProductScopeEnum.isSpu(rewardActivity.getProductScope()) ?
+                        REWARD_ACTIVITY_SPU_CONFLICTS : REWARD_ACTIVITY_SCOPE_CATEGORY_EXISTS);
+            }
         }
     }
 
@@ -148,21 +147,6 @@ public class RewardActivityServiceImpl implements RewardActivityService {
         }
     }
 
-    /**
-     * 获得商品参加的满减送活动的数组
-     *
-     * @param spuIds   商品 SPU 编号数组
-     * @param statuses 活动状态数组
-     * @return 商品参加的满减送活动的数组
-     */
-    private List<RewardActivityDO> getRewardActivityListBySpuIds(Collection<Long> spuIds,
-                                                                 Collection<Integer> statuses) {
-        // TODO @puhui999: 下次 fix
-        //List<RewardActivityDO> list = rewardActivityMapper.selectListByStatus(statuses);
-        //return CollUtil.filter(list, activity -> CollUtil.containsAny(activity.getProductSpuIds(), spuIds));
-        return List.of();
-    }
-
     @Override
     public RewardActivityDO getRewardActivity(Long id) {
         return rewardActivityMapper.selectById(id);
@@ -176,31 +160,13 @@ public class RewardActivityServiceImpl implements RewardActivityService {
     @Override
     public List<RewardActivityMatchRespDTO> getMatchRewardActivityList(Collection<Long> spuIds) {
         // TODO 芋艿:待实现;先指定,然后再全局的;
-//        // 如果有全局活动,则直接选择它
-//        List<RewardActivityDO> allActivities = rewardActivityMapper.selectListByProductScopeAndStatus(
-//                PromotionProductScopeEnum.ALL.getScope(), PromotionActivityStatusEnum.RUN.getStatus());
-//        if (CollUtil.isNotEmpty(allActivities)) {
-//            return MapUtil.builder(allActivities.get(0), spuIds).build();
-//        }
-//
-//        // 查询某个活动参加的活动
-//        List<RewardActivityDO> productActivityList = getRewardActivityListBySpuIds(spuIds,
-//                singleton(PromotionActivityStatusEnum.RUN.getStatus()));
-//        return convertMap(productActivityList, activity -> activity,
-//                rewardActivityDO -> intersectionDistinct(rewardActivityDO.getProductSpuIds(), spuIds)); // 求交集返回
-        return null;
+        List<RewardActivityDO> list = rewardActivityMapper.selectListBySpuIdsAndStatus(spuIds, CommonStatusEnum.ENABLE.getStatus());
+        return BeanUtils.toBean(list, RewardActivityMatchRespDTO.class);
     }
 
     @Override
-    public List<RewardActivityDO> getRewardActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime) {
-        // 1. 查询出指定 spuId 的 spu 参加的活动
-        List<RewardActivityDO> rewardActivityList = rewardActivityMapper.selectListBySpuIdsAndStatus(spuIds, status);
-        if (CollUtil.isEmpty(rewardActivityList)) {
-            return Collections.emptyList();
-        }
-
-        // 2. 查询活动详情
-        return rewardActivityMapper.selectListByIdsAndDateTimeLt(convertSet(rewardActivityList, RewardActivityDO::getId), dateTime);
+    public List<RewardActivityDO> getRewardActivityByStatusAndDateTimeLt(Integer status, LocalDateTime dateTime) {
+        return rewardActivityMapper.selectListByStatusAndDateTimeLt(status, dateTime);
     }
 
 }

+ 68 - 62
yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImplTest.java

@@ -1,21 +1,23 @@
 package cn.iocoder.yudao.module.promotion.service.reward;
 
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+import cn.iocoder.yudao.module.promotion.api.reward.dto.RewardActivityMatchRespDTO;
 import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityCreateReqVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityPageReqVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityUpdateReqVO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
 import cn.iocoder.yudao.module.promotion.dal.mysql.reward.RewardActivityMapper;
-import cn.iocoder.yudao.module.promotion.enums.common.PromotionActivityStatusEnum;
 import cn.iocoder.yudao.module.promotion.enums.common.PromotionConditionTypeEnum;
 import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
+import jakarta.annotation.Resource;
 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 import org.springframework.context.annotation.Import;
 
-import jakarta.annotation.Resource;
 import java.time.Duration;
+import java.util.List;
 import java.util.Set;
 
 import static cn.hutool.core.util.RandomUtil.randomEle;
@@ -27,15 +29,15 @@ import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServic
 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
 import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.REWARD_ACTIVITY_NOT_EXISTS;
-import static java.util.Arrays.asList;
+import static com.google.common.primitives.Longs.asList;
 import static java.util.Collections.singletonList;
 import static org.junit.jupiter.api.Assertions.*;
 
 /**
-* {@link RewardActivityServiceImpl} 的单元测试类
-*
-* @author 芋道源码
-*/
+ * {@link RewardActivityServiceImpl} 的单元测试类
+ *
+ * @author 芋道源码
+ */
 @Disabled // TODO 芋艿:后续 fix 补充的单测
 @Import(RewardActivityServiceImpl.class)
 public class RewardActivityServiceImplTest extends BaseDbUnitTest {
@@ -63,7 +65,7 @@ public class RewardActivityServiceImplTest extends BaseDbUnitTest {
         // 校验记录的属性是否正确
         RewardActivityDO rewardActivity = rewardActivityMapper.selectById(rewardActivityId);
         assertPojoEquals(reqVO, rewardActivity, "rules");
-        assertEquals(rewardActivity.getStatus(), PromotionActivityStatusEnum.WAIT.getStatus());
+        assertEquals(rewardActivity.getStatus(), CommonStatusEnum.DISABLE.getStatus());
         for (int i = 0; i < reqVO.getRules().size(); i++) {
             assertPojoEquals(reqVO.getRules().get(i), rewardActivity.getRules().get(i));
         }
@@ -72,7 +74,7 @@ public class RewardActivityServiceImplTest extends BaseDbUnitTest {
     @Test
     public void testUpdateRewardActivity_success() {
         // mock 数据
-        RewardActivityDO dbRewardActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(PromotionActivityStatusEnum.WAIT.getStatus()));
+        RewardActivityDO dbRewardActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()));
         rewardActivityMapper.insert(dbRewardActivity);// @Sql: 先插入出一条存在的数据
         // 准备参数
         RewardActivityUpdateReqVO reqVO = randomPojo(RewardActivityUpdateReqVO.class, o -> {
@@ -88,7 +90,7 @@ public class RewardActivityServiceImplTest extends BaseDbUnitTest {
         // 校验是否更新正确
         RewardActivityDO rewardActivity = rewardActivityMapper.selectById(reqVO.getId()); // 获取最新的
         assertPojoEquals(reqVO, rewardActivity, "rules");
-        assertEquals(rewardActivity.getStatus(), PromotionActivityStatusEnum.WAIT.getStatus());
+        assertEquals(rewardActivity.getStatus(), CommonStatusEnum.DISABLE.getStatus());
         for (int i = 0; i < reqVO.getRules().size(); i++) {
             assertPojoEquals(reqVO.getRules().get(i), rewardActivity.getRules().get(i));
         }
@@ -97,7 +99,7 @@ public class RewardActivityServiceImplTest extends BaseDbUnitTest {
     @Test
     public void testCloseRewardActivity() {
         // mock 数据
-        RewardActivityDO dbRewardActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(PromotionActivityStatusEnum.WAIT.getStatus()));
+        RewardActivityDO dbRewardActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()));
         rewardActivityMapper.insert(dbRewardActivity);// @Sql: 先插入出一条存在的数据
         // 准备参数
         Long id = dbRewardActivity.getId();
@@ -106,7 +108,7 @@ public class RewardActivityServiceImplTest extends BaseDbUnitTest {
         rewardActivityService.closeRewardActivity(id);
         // 校验状态
         RewardActivityDO rewardActivity = rewardActivityMapper.selectById(id);
-        assertEquals(rewardActivity.getStatus(), PromotionActivityStatusEnum.CLOSE.getStatus());
+        assertEquals(rewardActivity.getStatus(), CommonStatusEnum.DISABLE.getStatus());
     }
 
     @Test
@@ -121,15 +123,15 @@ public class RewardActivityServiceImplTest extends BaseDbUnitTest {
     @Test
     public void testDeleteRewardActivity_success() {
         // mock 数据
-        RewardActivityDO dbRewardActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(PromotionActivityStatusEnum.CLOSE.getStatus()));
+        RewardActivityDO dbRewardActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()));
         rewardActivityMapper.insert(dbRewardActivity);// @Sql: 先插入出一条存在的数据
         // 准备参数
         Long id = dbRewardActivity.getId();
 
         // 调用
         rewardActivityService.deleteRewardActivity(id);
-       // 校验数据不存在了
-       assertNull(rewardActivityMapper.selectById(id));
+        // 校验数据不存在了
+        assertNull(rewardActivityMapper.selectById(id));
     }
 
     @Test
@@ -143,78 +145,82 @@ public class RewardActivityServiceImplTest extends BaseDbUnitTest {
 
     @Test
     public void testGetRewardActivityPage() {
-       // mock 数据
-       RewardActivityDO dbRewardActivity = randomPojo(RewardActivityDO.class, o -> { // 等会查询到
-           o.setName("芋艿");
-           o.setStatus(PromotionActivityStatusEnum.CLOSE.getStatus());
-       });
-       rewardActivityMapper.insert(dbRewardActivity);
-       // 测试 name 不匹配
-       rewardActivityMapper.insert(cloneIgnoreId(dbRewardActivity, o -> o.setName("土豆")));
-       // 测试 status 不匹配
-       rewardActivityMapper.insert(cloneIgnoreId(dbRewardActivity, o -> o.setStatus(PromotionActivityStatusEnum.RUN.getStatus())));
-       // 准备参数
-       RewardActivityPageReqVO reqVO = new RewardActivityPageReqVO();
-       reqVO.setName("芋艿");
-       reqVO.setStatus(PromotionActivityStatusEnum.CLOSE.getStatus());
-
-       // 调用
-       PageResult<RewardActivityDO> pageResult = rewardActivityService.getRewardActivityPage(reqVO);
-       // 断言
-       assertEquals(1, pageResult.getTotal());
-       assertEquals(1, pageResult.getList().size());
-       assertPojoEquals(dbRewardActivity, pageResult.getList().get(0), "rules");
+        // mock 数据
+        RewardActivityDO dbRewardActivity = randomPojo(RewardActivityDO.class, o -> { // 等会查询到
+            o.setName("芋艿");
+            o.setStatus(CommonStatusEnum.DISABLE.getStatus());
+        });
+        rewardActivityMapper.insert(dbRewardActivity);
+        // 测试 name 不匹配
+        rewardActivityMapper.insert(cloneIgnoreId(dbRewardActivity, o -> o.setName("土豆")));
+        // 测试 status 不匹配
+        rewardActivityMapper.insert(cloneIgnoreId(dbRewardActivity, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())));
+        // 准备参数
+        RewardActivityPageReqVO reqVO = new RewardActivityPageReqVO();
+        reqVO.setName("芋艿");
+        reqVO.setStatus(CommonStatusEnum.DISABLE.getStatus());
+
+        // 调用
+        PageResult<RewardActivityDO> pageResult = rewardActivityService.getRewardActivityPage(reqVO);
+        // 断言
+        assertEquals(1, pageResult.getTotal());
+        assertEquals(1, pageResult.getList().size());
+        assertPojoEquals(dbRewardActivity, pageResult.getList().get(0), "rules");
     }
 
     @Test
     public void testGetRewardActivities_all() {
         // mock 数据
-        RewardActivityDO allActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(PromotionActivityStatusEnum.RUN.getStatus())
+        RewardActivityDO allActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())
                 .setProductScope(PromotionProductScopeEnum.ALL.getScope()));
         rewardActivityMapper.insert(allActivity);
-        RewardActivityDO productActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(PromotionActivityStatusEnum.RUN.getStatus())
-                .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductSpuIds(asList(1L, 2L)));
+        RewardActivityDO productActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())
+                .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductScopeValues(asList(1L, 2L)));
         rewardActivityMapper.insert(productActivity);
         // 准备参数
         Set<Long> spuIds = asSet(1L, 2L);
 
         // 调用 TODO getMatchRewardActivities 没有这个方法,但是找到了 getMatchRewardActivityList
-        //Map<RewardActivityDO, Set<Long>> matchRewardActivities = rewardActivityService.getMatchRewardActivities(spuIds);
+        List<RewardActivityMatchRespDTO> matchRewardActivityList = rewardActivityService.getMatchRewardActivityList(spuIds);
         // 断言
-        //assertEquals(matchRewardActivities.size(), 1);
-        //Map.Entry<RewardActivityDO, Set<Long>> next = matchRewardActivities.entrySet().iterator().next();
-        //assertPojoEquals(next.getKey(), allActivity);
-        //assertEquals(next.getValue(), spuIds);
+        assertEquals(matchRewardActivityList.size(), 1);
+        matchRewardActivityList.forEach((activity) -> {
+            if (activity.getId().equals(productActivity.getId())) {
+                assertPojoEquals(activity, productActivity);
+                assertEquals(activity.getProductScopeValues(), asList(1L, 2L));
+            } else {
+                fail();
+            }
+        });
     }
 
     @Test
     public void testGetRewardActivities_product() {
         // mock 数据
-       // TODO @puhui999:有单测的问题,也一起瞅瞅
-        RewardActivityDO productActivity01 = randomPojo(RewardActivityDO.class, o -> o.setStatus(PromotionActivityStatusEnum.RUN.getStatus())
-                .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductSpuIds(asList(1L, 2L)));
+        RewardActivityDO productActivity01 = randomPojo(RewardActivityDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())
+                .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductScopeValues(asList(1L, 2L)));
         rewardActivityMapper.insert(productActivity01);
-        RewardActivityDO productActivity02 = randomPojo(RewardActivityDO.class, o -> o.setStatus(PromotionActivityStatusEnum.RUN.getStatus())
-                .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductSpuIds(singletonList(3L)));
+        RewardActivityDO productActivity02 = randomPojo(RewardActivityDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())
+                .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductScopeValues(singletonList(3L)));
         rewardActivityMapper.insert(productActivity02);
         // 准备参数
         Set<Long> spuIds = asSet(1L, 2L, 3L);
 
         // 调用  TODO getMatchRewardActivities 没有这个方法,但是找到了 getMatchRewardActivityList
-        //Map<RewardActivityDO, Set<Long>> matchRewardActivities = rewardActivityService.getMatchRewardActivities(spuIds);
+        List<RewardActivityMatchRespDTO> matchRewardActivityList = rewardActivityService.getMatchRewardActivityList(spuIds);
         // 断言
-        //assertEquals(matchRewardActivities.size(), 2);
-        //matchRewardActivities.forEach((activity, activitySpuIds) -> {
-        //    if (activity.getId().equals(productActivity01.getId())) {
-        //        assertPojoEquals(activity, productActivity01);
-        //        assertEquals(activitySpuIds, asSet(1L, 2L));
-        //    } else if (activity.getId().equals(productActivity02.getId())) {
-        //        assertPojoEquals(activity, productActivity02);
-        //        assertEquals(activitySpuIds, asSet(3L));
-        //    } else {
-        //        fail();
-        //    }
-        //});
+        assertEquals(matchRewardActivityList.size(), 2);
+        matchRewardActivityList.forEach((activity) -> {
+            if (activity.getId().equals(productActivity01.getId())) {
+                assertPojoEquals(activity, productActivity01);
+                assertEquals(activity.getProductScopeValues(), asList(1L, 2L));
+            } else if (activity.getId().equals(productActivity02.getId())) {
+                assertPojoEquals(activity, productActivity02);
+                assertEquals(activity.getProductScopeValues(), singletonList(3L));
+            } else {
+                fail();
+            }
+        });
     }
 
 }

+ 2 - 2
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java

@@ -10,10 +10,10 @@ import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
+import jakarta.annotation.Resource;
 import org.springframework.core.annotation.Order;
 import org.springframework.stereotype.Component;
 
-import jakarta.annotation.Resource;
 import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
@@ -96,7 +96,7 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator
     private List<TradePriceCalculateRespBO.OrderItem> filterMatchCouponOrderItems(TradePriceCalculateRespBO result,
                                                                                   RewardActivityMatchRespDTO rewardActivity) {
         return filterList(result.getItems(),
-                orderItem -> CollUtil.contains(rewardActivity.getSpuIds(), orderItem.getSpuId()));
+                orderItem -> CollUtil.contains(rewardActivity.getProductScopeValues(), orderItem.getSpuId()));
     }
 
     /**

+ 3 - 3
yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculatorTest.java

@@ -63,10 +63,10 @@ public class TradeRewardActivityPriceCalculatorTest extends BaseMockitoUnitTest
         // mock 方法(限时折扣 DiscountActivity 信息)
         when(rewardActivityApi.getMatchRewardActivityList(eq(asSet(1L, 2L, 3L)))).thenReturn(asList(
                 randomPojo(RewardActivityMatchRespDTO.class, o -> o.setId(1000L).setName("活动 1000 号")
-                        .setSpuIds(asList(1L, 2L)).setConditionType(PromotionConditionTypeEnum.PRICE.getType())
+                        .setProductScopeValues(asList(1L, 2L)).setConditionType(PromotionConditionTypeEnum.PRICE.getType())
                         .setRules(singletonList(new RewardActivityMatchRespDTO.Rule().setLimit(200).setDiscountPrice(70)))),
                 randomPojo(RewardActivityMatchRespDTO.class, o -> o.setId(2000L).setName("活动 2000 号")
-                        .setSpuIds(singletonList(3L)).setConditionType(PromotionConditionTypeEnum.COUNT.getType())
+                        .setProductScopeValues(singletonList(3L)).setConditionType(PromotionConditionTypeEnum.COUNT.getType())
                         .setRules(asList(new RewardActivityMatchRespDTO.Rule().setLimit(1).setDiscountPrice(10),
                                 new RewardActivityMatchRespDTO.Rule().setLimit(2).setDiscountPrice(60), // 最大可满足,因为是 4 个
                                 new RewardActivityMatchRespDTO.Rule().setLimit(10).setDiscountPrice(100))))
@@ -175,7 +175,7 @@ public class TradeRewardActivityPriceCalculatorTest extends BaseMockitoUnitTest
         // mock 方法(限时折扣 DiscountActivity 信息)
         when(rewardActivityApi.getMatchRewardActivityList(eq(asSet(1L, 2L)))).thenReturn(singletonList(
                 randomPojo(RewardActivityMatchRespDTO.class, o -> o.setId(1000L).setName("活动 1000 号")
-                        .setSpuIds(asList(1L, 2L)).setConditionType(PromotionConditionTypeEnum.PRICE.getType())
+                        .setProductScopeValues(asList(1L, 2L)).setConditionType(PromotionConditionTypeEnum.PRICE.getType())
                         .setRules(singletonList(new RewardActivityMatchRespDTO.Rule().setLimit(351).setDiscountPrice(70))))
         ));