瀏覽代碼

!1069 【代码优化】商城: 满减送活动
Merge pull request !1069 from puhui999/develop

芋道源码 9 月之前
父節點
當前提交
ebf5693255
共有 18 個文件被更改,包括 256 次插入222 次删除
  1. 6 7
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApi.java
  2. 1 11
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/reward/dto/RewardActivityMatchRespDTO.java
  3. 1 3
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/coupon/CouponStatusEnum.java
  4. 5 4
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApiImpl.java
  5. 1 10
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityBaseVO.java
  6. 0 10
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/reward/RewardActivityDO.java
  7. 8 9
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java
  8. 53 48
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java
  9. 113 99
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java
  10. 0 1
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImpl.java
  11. 10 2
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java
  12. 11 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java
  13. 17 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
  14. 16 3
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java
  15. 1 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java
  16. 1 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java
  17. 7 7
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java
  18. 5 5
      yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculatorTest.java

+ 6 - 7
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApi.java

@@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO;
 import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponValidReqDTO;
 import jakarta.validation.Valid;
 
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -36,23 +37,21 @@ public interface CouponApi {
      */
     CouponRespDTO validateCoupon(@Valid CouponValidReqDTO validReqDTO);
 
-    // TODO @puhui999:可能需要根据 TradeOrderDO 的建议,进行修改;需要返回优惠劵编号
     /**
      * 【管理员】给指定用户批量发送优惠券
      *
-     * @param giveCouponsMap  key: 优惠劵编号,value:对应的优惠券数量
+     * @param giveCoupons  key: 优惠劵模版编号,value:对应的数量
      * @param userId      用户编号
+     * @return 优惠券编号列表
      */
-    // TODO @puhui999:giveCouponsMap 可能改成 giveCoupons 更合适?优惠劵模版编号、数量
-    void takeCouponsByAdmin(Map<Long, Integer> giveCouponsMap, Long userId);
+    List<Long> takeCouponsByAdmin(Map<Long, Integer> giveCoupons, Long userId);
 
-    // TODO @puhui999:可能需要根据 TradeOrderDO 的建议,进行修改 giveCouponsMap 参数
     /**
      * 【管理员】作废指定用户的指定优惠劵
      *
-     * @param giveCouponsMap key: 优惠劵编号,value:对应的优惠券数量
+     * @param giveCouponIds  赠送的优惠券编号
      * @param userId         用户编号
      */
-    void invalidateCouponsByAdmin(Map<Long, Integer> giveCouponsMap, Long userId);
+    void invalidateCouponsByAdmin(List<Long> giveCouponIds, Long userId);
 
 }

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

@@ -86,27 +86,17 @@ public class RewardActivityMatchRespDTO {
          * 是否包邮
          */
         private Boolean freeDelivery;
-        // TODO @puhui999:建议不返回 + 去掉 givePoint、giveCoupon 字段哈。
-        /**
-         * 是否赠送积分
-         */
-        private Boolean givePoint;
         /**
          * 赠送的积分
          */
         private Integer point;
-        /**
-         * 是否赠送优惠券
-         */
-        private Boolean giveCoupon;
-        // TODO @puhui999:giveCoupons 即可
         /**
          * 赠送的优惠劵
          *
          * key: 优惠劵模版编号
          * value:对应的优惠券数量
          */
-        private Map<Long, Integer> giveCouponsMap;
+        private Map<Long, Integer> giveCoupons;
 
     }
 

+ 1 - 3
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/coupon/CouponStatusEnum.java

@@ -17,9 +17,7 @@ public enum CouponStatusEnum implements IntArrayValuable {
 
     UNUSED(1, "未使用"),
     USED(2, "已使用"),
-    EXPIRE(3, "已过期"),
-    // TODO @puhui999:捉摸了下,貌似搞成逻辑删除好了?不然好多地方的 status 都要做一些变动。可能未来加个 invalidateType 来标识,是管理后台删除,还是取消回收。或者优惠劵的 change log 可能更好。
-    INVALID(4, "已作废");
+    EXPIRE(3, "已过期");
 
     public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CouponStatusEnum::getStatus).toArray();
 

+ 5 - 4
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApiImpl.java

@@ -11,6 +11,7 @@ import jakarta.annotation.Resource;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -43,13 +44,13 @@ public class CouponApiImpl implements CouponApi {
     }
 
     @Override
-    public void takeCouponsByAdmin(Map<Long, Integer> giveCouponsMap, Long userId) {
-        couponService.takeCouponsByAdmin(giveCouponsMap, userId);
+    public List<Long> takeCouponsByAdmin(Map<Long, Integer> giveCoupons, Long userId) {
+        return couponService.takeCouponsByAdmin(giveCoupons, userId);
     }
 
     @Override
-    public void invalidateCouponsByAdmin(Map<Long, Integer> giveCouponsMap, Long userId) {
-        couponService.invalidateCouponsByAdmin(giveCouponsMap, userId);
+    public void invalidateCouponsByAdmin(List<Long> giveCouponIds, Long userId) {
+        couponService.invalidateCouponsByAdmin(giveCouponIds, userId);
     }
 
 }

+ 1 - 10
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityBaseVO.java

@@ -1,7 +1,6 @@
 package cn.iocoder.yudao.module.promotion.controller.admin.reward.vo;
 
 import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.util.BooleanUtil;
 import cn.iocoder.yudao.framework.common.validation.InEnum;
 import cn.iocoder.yudao.module.promotion.enums.common.PromotionConditionTypeEnum;
 import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
@@ -77,24 +76,16 @@ public class RewardActivityBaseVO {
         @NotNull(message = "规则是否包邮不能为空")
         private Boolean freeDelivery;
 
-        @Schema(description = "是否赠送积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
-        @NotNull(message = "规则是否赠送积分不能为空")
-        private Boolean givePoint;
-
         @Schema(description = "赠送的积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
         private Integer point;
 
-        @Schema(description = "是否赠送优惠券", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
-        @NotNull(message = "规则是否赠送优惠券不能为空")
-        private Boolean giveCoupon;
-
         @Schema(description = "赠送的优惠劵编号的数组", example = "1,2,3")
         private Map<Long, Integer> giveCouponsMap;
 
         @AssertTrue(message = "赠送的积分不能小于 1")
         @JsonIgnore
         public boolean isPointValid() {
-            return BooleanUtil.isFalse(givePoint) || (point != null && point >= 1);
+            return point == null || point >= 1;
         }
 
     }

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

@@ -100,20 +100,10 @@ public class RewardActivityDO extends BaseDO {
          * 是否包邮
          */
         private Boolean freeDelivery;
-        // TODO @puhui999:是不是大于零,就认为赠送积分哈;简洁一点;
-        /**
-         * 是否赠送积分
-         */
-        private Boolean givePoint;
         /**
          * 赠送的积分
          */
         private Integer point;
-        // TODO @puhui999:非空,就认为赠送优惠劵
-        /**
-         * 是否赠送优惠券
-         */
-        private Boolean giveCoupon;
         /**
          * 赠送的优惠劵
          *

+ 8 - 9
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java

@@ -8,6 +8,7 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
 import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.github.yulichang.toolkit.MPJWrappers;
 import org.apache.ibatis.annotations.Mapper;
@@ -72,15 +73,6 @@ public interface CouponMapper extends BaseMapperX<CouponDO> {
         );
     }
 
-    default List<CouponDO> selectListByTemplateIdAndUserIdAndTakeType(Long templateId, Collection<Long> userIds,
-                                                                      Integer takeType) {
-        return selectList(new LambdaQueryWrapperX<CouponDO>()
-                .eq(CouponDO::getTemplateId, templateId)
-                .eq(CouponDO::getTakeType, takeType)
-                .in(CouponDO::getUserId, userIds)
-        );
-    }
-
     default Map<Long, Integer> selectCountByUserIdAndTemplateIdIn(Long userId, Collection<Long> templateIds) {
         String templateIdAlias = "templateId";
         String countAlias = "count";
@@ -116,4 +108,11 @@ public interface CouponMapper extends BaseMapperX<CouponDO> {
         );
     }
 
+    default List<CouponDO> selectListByIdAndUserIdAndTakeType(Long couponId, Long userId, Integer takeType) {
+        return selectList(new LambdaQueryWrapper<CouponDO>()
+                .eq(CouponDO::getId, couponId)
+                .eq(CouponDO::getUserId, userId)
+                .eq(CouponDO::getTakeType, takeType));
+    }
+
 }

+ 53 - 48
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java

@@ -38,14 +38,6 @@ public interface CouponService {
      */
     void validCoupon(CouponDO coupon);
 
-    /**
-     * 获得优惠劵分页
-     *
-     * @param pageReqVO 分页查询
-     * @return 优惠劵分页
-     */
-    PageResult<CouponDO> getCouponPage(CouponPageReqVO pageReqVO);
-
     /**
      * 使用优惠劵
      *
@@ -69,57 +61,43 @@ public interface CouponService {
      */
     void deleteCoupon(Long id);
 
-    /**
-     * 获得用户的优惠劵列表
-     *
-     * @param userId 用户编号
-     * @param status 优惠劵状态
-     * @return 优惠劵列表
-     */
-    List<CouponDO> getCouponList(Long userId, Integer status);
-
-    /**
-     * 获得未使用的优惠劵数量
-     *
-     * @param userId 用户编号
-     * @return 未使用的优惠劵数量
-     */
-    Long getUnusedCouponCount(Long userId);
-
     /**
      * 领取优惠券
      *
      * @param templateId 优惠券模板编号
      * @param userIds    用户编号列表
      * @param takeType   领取方式
+     * @return key: userId, value: 优惠券编号列表
      */
-    void takeCoupon(Long templateId, Set<Long> userIds, CouponTakeTypeEnum takeType);
+    Map<Long, List<Long>> takeCoupon(Long templateId, Set<Long> userIds, CouponTakeTypeEnum takeType);
 
     /**
      * 【管理员】给用户发送优惠券
      *
      * @param templateId 优惠券模板编号
      * @param userIds    用户编号列表
+     * @return key: userId, value: 优惠券编号列表
      */
-    default void takeCouponByAdmin(Long templateId, Set<Long> userIds) {
-        takeCoupon(templateId, userIds, CouponTakeTypeEnum.ADMIN);
+    default Map<Long, List<Long>> takeCouponByAdmin(Long templateId, Set<Long> userIds) {
+        return takeCoupon(templateId, userIds, CouponTakeTypeEnum.ADMIN);
     }
 
     /**
      * 【管理员】给指定用户批量发送优惠券
      *
-     * @param giveCouponsMap key: 优惠劵编号,value:对应的优惠券数量
+     * @param giveCoupons  key: 优惠劵模版编号,value:对应的数量
      * @param userId      用户编号
+     * @return 优惠券编号列表
      */
-    void takeCouponsByAdmin(Map<Long, Integer> giveCouponsMap, Long userId);
+    List<Long> takeCouponsByAdmin(Map<Long, Integer> giveCoupons, Long userId);
 
     /**
-     * 【管理员】收回给指定用户批量发送优惠券
+     * 【管理员】作废指定用户的指定优惠劵
      *
-     * @param giveCouponsMap key: 优惠劵编号,value:对应的优惠券数量
+     * @param giveCouponIds  赠送的优惠券编号
      * @param userId         用户编号
      */
-    void invalidateCouponsByAdmin(Map<Long, Integer> giveCouponsMap, Long userId);
+    void invalidateCouponsByAdmin(List<Long> giveCouponIds, Long userId);
 
     /**
      * 【会员】领取优惠券
@@ -139,16 +117,38 @@ public interface CouponService {
     void takeCouponByRegister(Long userId);
 
     /**
-     * 获取会员领取指定优惠券的数量
+     * 过期优惠券
      *
-     * @param templateId 优惠券模板编号
-     * @param userId     用户编号
-     * @return 领取优惠券的数量
+     * @return 过期数量
      */
-    default Integer getTakeCount(Long templateId, Long userId) {
-        Map<Long, Integer> map = getTakeCountMapByTemplateIds(Collections.singleton(templateId), userId);
-        return MapUtil.getInt(map, templateId, 0);
-    }
+    int expireCoupon();
+
+    //======================= 查询相关 =======================
+
+    /**
+     * 获得未使用的优惠劵数量
+     *
+     * @param userId 用户编号
+     * @return 未使用的优惠劵数量
+     */
+    Long getUnusedCouponCount(Long userId);
+
+    /**
+     * 获得优惠劵分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 优惠劵分页
+     */
+    PageResult<CouponDO> getCouponPage(CouponPageReqVO pageReqVO);
+
+    /**
+     * 获得用户的优惠劵列表
+     *
+     * @param userId 用户编号
+     * @param status 优惠劵状态
+     * @return 优惠劵列表
+     */
+    List<CouponDO> getCouponList(Long userId, Integer status);
 
     /**
      * 统计会员领取优惠券的数量
@@ -160,20 +160,25 @@ public interface CouponService {
     Map<Long, Integer> getTakeCountMapByTemplateIds(Collection<Long> templateIds, Long userId);
 
     /**
-     * 获取用户匹配的优惠券列表
+     * 获取会员领取指定优惠券的数量
      *
+     * @param templateId 优惠券模板编号
      * @param userId     用户编号
-     * @param matchReqVO 匹配参数
-     * @return 优惠券列表
+     * @return 领取优惠券的数量
      */
-    List<CouponDO> getMatchCouponList(Long userId, AppCouponMatchReqVO matchReqVO);
+    default Integer getTakeCount(Long templateId, Long userId) {
+        Map<Long, Integer> map = getTakeCountMapByTemplateIds(Collections.singleton(templateId), userId);
+        return MapUtil.getInt(map, templateId, 0);
+    }
 
     /**
-     * 过期优惠券
+     * 获取用户匹配的优惠券列表
      *
-     * @return 过期数量
+     * @param userId     用户编号
+     * @param matchReqVO 匹配参数
+     * @return 优惠券列表
      */
-    int expireCoupon();
+    List<CouponDO> getMatchCouponList(Long userId, AppCouponMatchReqVO matchReqVO);
 
     /**
      * 获取用户是否可以领取优惠券

+ 113 - 99
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java

@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.promotion.service.coupon;
 import cn.hutool.core.collection.CollStreamUtil;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.ObjUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.extra.spring.SpringUtil;
@@ -31,6 +32,7 @@ import java.util.stream.Collectors;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
+import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
 import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
 import static java.util.Arrays.asList;
 
@@ -75,20 +77,6 @@ public class CouponServiceImpl implements CouponService {
         }
     }
 
-    @Override
-    public PageResult<CouponDO> getCouponPage(CouponPageReqVO pageReqVO) {
-        // 获得用户编号
-        if (StrUtil.isNotEmpty(pageReqVO.getNickname())) {
-            List<MemberUserRespDTO> users = memberUserApi.getUserListByNickname(pageReqVO.getNickname());
-            if (CollUtil.isEmpty(users)) {
-                return PageResult.empty();
-            }
-            pageReqVO.setUserIds(convertSet(users, MemberUserRespDTO::getId));
-        }
-        // 分页查询
-        return couponMapper.selectPage(pageReqVO);
-    }
-
     @Override
     public void useCoupon(Long id, Long userId, Long orderId) {
         // 校验优惠劵
@@ -145,27 +133,9 @@ public class CouponServiceImpl implements CouponService {
         couponTemplateService.updateCouponTemplateTakeCount(coupon.getTemplateId(), -1);
     }
 
-    @Override
-    public List<CouponDO> getCouponList(Long userId, Integer status) {
-        return couponMapper.selectListByUserIdAndStatus(userId, status);
-    }
-
-    private CouponDO validateCouponExists(Long id) {
-        CouponDO coupon = couponMapper.selectById(id);
-        if (coupon == null) {
-            throw exception(COUPON_NOT_EXISTS);
-        }
-        return coupon;
-    }
-
-    @Override
-    public Long getUnusedCouponCount(Long userId) {
-        return couponMapper.selectCountByUserIdAndStatus(userId, CouponStatusEnum.UNUSED.getStatus());
-    }
-
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public void takeCoupon(Long templateId, Set<Long> userIds, CouponTakeTypeEnum takeType) {
+    public Map<Long, List<Long>> takeCoupon(Long templateId, Set<Long> userIds, CouponTakeTypeEnum takeType) {
         CouponTemplateDO template = couponTemplateService.getCouponTemplate(templateId);
         // 1. 过滤掉达到领取限制的用户
         removeTakeLimitUser(userIds, template);
@@ -173,40 +143,45 @@ public class CouponServiceImpl implements CouponService {
         validateCouponTemplateCanTake(template, userIds, takeType);
 
         // 3. 批量保存优惠劵
-        couponMapper.insertBatch(convertList(userIds, userId -> CouponConvert.INSTANCE.convert(template, userId)));
+        List<CouponDO> couponList = convertList(userIds, userId -> CouponConvert.INSTANCE.convert(template, userId));
+        couponMapper.insertBatch(couponList);
 
         // 4. 增加优惠劵模板的领取数量
         couponTemplateService.updateCouponTemplateTakeCount(templateId, userIds.size());
+
+        return convertMultiMap(couponList, CouponDO::getUserId, CouponDO::getId);
     }
 
     @Override
-    public void takeCouponsByAdmin(Map<Long, Integer> giveCouponsMap, Long userId) {
-        if (CollUtil.isEmpty(giveCouponsMap)) {
-            return;
+    public List<Long> takeCouponsByAdmin(Map<Long, Integer> giveCoupons, Long userId) {
+        if (CollUtil.isEmpty(giveCoupons)) {
+            return Collections.emptyList();
         }
 
+        List<Long> couponIds = new ArrayList<>();
         // 循环发放
-        for (Map.Entry<Long, Integer> entry : giveCouponsMap.entrySet()) {
+        for (Map.Entry<Long, Integer> entry : giveCoupons.entrySet()) {
             try {
                 for (int i = 0; i < entry.getValue(); i++) {
-                    getSelf().takeCoupon(entry.getKey(), CollUtil.newHashSet(userId), CouponTakeTypeEnum.ADMIN);
+                    Map<Long, List<Long>> userCouponIdsMap = getSelf().takeCoupon(entry.getKey(), CollUtil.newHashSet(userId),
+                            CouponTakeTypeEnum.ADMIN);
+                    findAndThen(userCouponIdsMap, userId, couponIds::addAll);
                 }
             } catch (Exception e) {
                 log.error("[takeCouponsByAdmin][coupon({}) 优惠券发放失败]", entry, e);
             }
         }
+        return couponIds;
     }
 
     @Override
-    public void invalidateCouponsByAdmin(Map<Long, Integer> giveCouponsMap, Long userId) {
+    public void invalidateCouponsByAdmin(List<Long> giveCouponIds, Long userId) {
         // 循环收回
-        for (Map.Entry<Long, Integer> entry : giveCouponsMap.entrySet()) {
+        for (Long couponId : giveCouponIds) {
             try {
-                for (int i = 0; i < entry.getValue(); i++) {
-                    getSelf().takeBackCoupon(entry.getKey(), CollUtil.newHashSet(userId), CouponTakeTypeEnum.ADMIN);
-                }
+                getSelf().takeBackCoupon(couponId, userId, CouponTakeTypeEnum.ADMIN);
             } catch (Exception e) {
-                log.error("[takeBackCouponsByAdmin][coupon({}) 收回优惠券失败]", entry, e);
+                log.error("[invalidateCouponsByAdmin][couponId({}) 收回优惠券失败]", couponId, e);
             }
         }
     }
@@ -214,32 +189,36 @@ public class CouponServiceImpl implements CouponService {
     /**
      * 【管理员】收回优惠券
      *
-     * @param templateId 模版编号
-     * @param userIds 用户编号列表
+     * @param couponId 模版编号
+     * @param userId   用户编号
      * @param takeType 领取方式
      */
     @Transactional(rollbackFor = Exception.class)
-    public void takeBackCoupon(Long templateId, Set<Long> userIds, CouponTakeTypeEnum takeType) {
-        CouponTemplateDO couponTemplate = couponTemplateService.getCouponTemplate(templateId);
-        // 1.1 校验模板
+    public void takeBackCoupon(Long couponId, Long userId, CouponTakeTypeEnum takeType) {
+        // 1.1 校验优惠券
+        CouponDO coupon = couponMapper.selectByIdAndUserId(couponId, userId);
+        if (coupon == null) {
+            throw exception(COUPON_NOT_EXISTS);
+        }
+        // 1.2 校验模板
+        CouponTemplateDO couponTemplate = couponTemplateService.getCouponTemplate(coupon.getTemplateId());
         if (couponTemplate == null) {
             throw exception(COUPON_TEMPLATE_NOT_EXISTS);
         }
-        // 1.2 校验领取方式
+        // 1.3 校验领取方式
         if (ObjectUtil.notEqual(couponTemplate.getTakeType(), takeType.getValue())) {
             throw exception(COUPON_TEMPLATE_CANNOT_TAKE);
         }
 
-        // 2.1 过滤出还未使用的赠送的优惠券
-        List<CouponDO> couponList = couponMapper.selectListByTemplateIdAndUserIdAndTakeType(templateId, userIds,
-                takeType.getValue());
-        List<CouponDO> unUsedCouponList = filterList(couponList, item -> !CouponStatusEnum.USED.getStatus().equals(item.getStatus()));
+        // 2.1 校验优惠券是否已经使用,如若使用则先不管
+        if (ObjUtil.equal(coupon.getStatus(), CouponStatusEnum.USED.getStatus())) {
+            return;
+        }
         // 2.2 减少优惠劵模板的领取数量
-        couponTemplateService.updateCouponTemplateTakeCount(templateId, unUsedCouponList.size() * -1);
-        // 2.3 批量更新优惠劵状态
-        couponMapper.updateById(convertList(unUsedCouponList, item -> new CouponDO().setId(item.getId())
-                .setStatus(CouponStatusEnum.INVALID.getStatus())));
-
+        couponTemplateService.updateCouponTemplateTakeCount(couponTemplate.getId(), -1);
+        // 2.3 批量作废优惠劵
+        // TODO @puhui999:捉摸了下,貌似搞成逻辑删除好了?不然好多地方的 status 都要做一些变动。可能未来加个 invalidateType 来标识,是管理后台删除,还是取消回收。或者优惠劵的 change log 可能更好。
+        couponMapper.deleteById(couponId);
     }
 
     @Override
@@ -251,24 +230,6 @@ public class CouponServiceImpl implements CouponService {
         }
     }
 
-    @Override
-    public Map<Long, Integer> getTakeCountMapByTemplateIds(Collection<Long> templateIds, Long userId) {
-        if (CollUtil.isEmpty(templateIds)) {
-            return Collections.emptyMap();
-        }
-        return couponMapper.selectCountByUserIdAndTemplateIdIn(userId, templateIds);
-    }
-
-    @Override
-    public List<CouponDO> getMatchCouponList(Long userId, AppCouponMatchReqVO matchReqVO) {
-        List<CouponDO> list = couponMapper.selectListByUserIdAndStatusAndUsePriceLeAndProductScope(userId,
-                CouponStatusEnum.UNUSED.getStatus(),
-                matchReqVO.getPrice(), matchReqVO.getSpuIds(), matchReqVO.getCategoryIds());
-        // 兜底逻辑:如果 CouponExpireJob 未执行,status 未变成 EXPIRE ,但是 validEndTime 已经过期了,需要进行过滤
-        list.removeIf(coupon -> !LocalDateTimeUtils.isBetween(coupon.getValidStartTime(), coupon.getValidEndTime()));
-        return list;
-    }
-
     @Override
     public int expireCoupon() {
         // 1. 查询待过期的优惠券
@@ -293,27 +254,6 @@ public class CouponServiceImpl implements CouponService {
         return count;
     }
 
-    @Override
-    public Map<Long, Boolean> getUserCanCanTakeMap(Long userId, List<CouponTemplateDO> templates) {
-        // 1. 未登录时,都显示可以领取
-        Map<Long, Boolean> userCanTakeMap = convertMap(templates, CouponTemplateDO::getId, templateId -> true);
-        if (userId == null) {
-            return userCanTakeMap;
-        }
-
-        // 2.1 过滤领取数量无限制的
-        Set<Long> templateIds = convertSet(templates, CouponTemplateDO::getId, template -> template.getTakeLimitCount() != -1);
-        // 2.2 检查用户领取的数量是否超过限制
-        if (CollUtil.isNotEmpty(templateIds)) {
-            Map<Long, Integer> couponTakeCountMap = this.getTakeCountMapByTemplateIds(templateIds, userId);
-            for (CouponTemplateDO template : templates) {
-                Integer takeCount = couponTakeCountMap.get(template.getId());
-                userCanTakeMap.put(template.getId(), takeCount == null || takeCount < template.getTakeLimitCount());
-            }
-        }
-        return userCanTakeMap;
-    }
-
     /**
      * 过期单个优惠劵
      *
@@ -385,11 +325,84 @@ public class CouponServiceImpl implements CouponService {
         userIds.removeIf(userId -> MapUtil.getInt(userTakeCountMap, userId, 0) >= couponTemplate.getTakeLimitCount());
     }
 
+    //======================= 查询相关 =======================
+
+    @Override
+    public Long getUnusedCouponCount(Long userId) {
+        return couponMapper.selectCountByUserIdAndStatus(userId, CouponStatusEnum.UNUSED.getStatus());
+    }
+
+    @Override
+    public PageResult<CouponDO> getCouponPage(CouponPageReqVO pageReqVO) {
+        // 获得用户编号
+        if (StrUtil.isNotEmpty(pageReqVO.getNickname())) {
+            List<MemberUserRespDTO> users = memberUserApi.getUserListByNickname(pageReqVO.getNickname());
+            if (CollUtil.isEmpty(users)) {
+                return PageResult.empty();
+            }
+            pageReqVO.setUserIds(convertSet(users, MemberUserRespDTO::getId));
+        }
+        // 分页查询
+        return couponMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public List<CouponDO> getCouponList(Long userId, Integer status) {
+        return couponMapper.selectListByUserIdAndStatus(userId, status);
+    }
+
+    @Override
+    public Map<Long, Integer> getTakeCountMapByTemplateIds(Collection<Long> templateIds, Long userId) {
+        if (CollUtil.isEmpty(templateIds)) {
+            return Collections.emptyMap();
+        }
+        return couponMapper.selectCountByUserIdAndTemplateIdIn(userId, templateIds);
+    }
+
+    @Override
+    public List<CouponDO> getMatchCouponList(Long userId, AppCouponMatchReqVO matchReqVO) {
+        List<CouponDO> list = couponMapper.selectListByUserIdAndStatusAndUsePriceLeAndProductScope(userId,
+                CouponStatusEnum.UNUSED.getStatus(),
+                matchReqVO.getPrice(), matchReqVO.getSpuIds(), matchReqVO.getCategoryIds());
+        // 兜底逻辑:如果 CouponExpireJob 未执行,status 未变成 EXPIRE ,但是 validEndTime 已经过期了,需要进行过滤
+        list.removeIf(coupon -> !LocalDateTimeUtils.isBetween(coupon.getValidStartTime(), coupon.getValidEndTime()));
+        return list;
+    }
+
+    @Override
+    public Map<Long, Boolean> getUserCanCanTakeMap(Long userId, List<CouponTemplateDO> templates) {
+        // 1. 未登录时,都显示可以领取
+        Map<Long, Boolean> userCanTakeMap = convertMap(templates, CouponTemplateDO::getId, templateId -> true);
+        if (userId == null) {
+            return userCanTakeMap;
+        }
+
+        // 2.1 过滤领取数量无限制的
+        Set<Long> templateIds = convertSet(templates, CouponTemplateDO::getId, template -> template.getTakeLimitCount() != -1);
+        // 2.2 检查用户领取的数量是否超过限制
+        if (CollUtil.isNotEmpty(templateIds)) {
+            Map<Long, Integer> couponTakeCountMap = this.getTakeCountMapByTemplateIds(templateIds, userId);
+            for (CouponTemplateDO template : templates) {
+                Integer takeCount = couponTakeCountMap.get(template.getId());
+                userCanTakeMap.put(template.getId(), takeCount == null || takeCount < template.getTakeLimitCount());
+            }
+        }
+        return userCanTakeMap;
+    }
+
     @Override
     public CouponDO getCoupon(Long userId, Long id) {
         return couponMapper.selectByIdAndUserId(id, userId);
     }
 
+    private CouponDO validateCouponExists(Long id) {
+        CouponDO coupon = couponMapper.selectById(id);
+        if (coupon == null) {
+            throw exception(COUPON_NOT_EXISTS);
+        }
+        return coupon;
+    }
+
     /**
      * 获得自身的代理对象,解决 AOP 生效问题
      *
@@ -398,4 +411,5 @@ public class CouponServiceImpl implements CouponService {
     private CouponServiceImpl getSelf() {
         return SpringUtil.getBean(getClass());
     }
+
 }

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

@@ -159,7 +159,6 @@ public class RewardActivityServiceImpl implements RewardActivityService {
 
     @Override
     public List<RewardActivityMatchRespDTO> getMatchRewardActivityList(Collection<Long> spuIds) {
-        // TODO 芋艿:待实现;先指定,然后再全局的;
         List<RewardActivityDO> list = rewardActivityMapper.selectListBySpuIdsAndStatus(spuIds, CommonStatusEnum.ENABLE.getStatus());
         return BeanUtils.toBean(list, RewardActivityMatchRespDTO.class);
     }

+ 10 - 2
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.trade.dal.dataobject.order;
 
 import cn.iocoder.yudao.framework.common.enums.TerminalEnum;
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO;
@@ -18,6 +19,7 @@ import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
 import lombok.*;
 
 import java.time.LocalDateTime;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -294,17 +296,23 @@ public class TradeOrderDO extends BaseDO {
      */
     private Integer vipPrice;
 
-    // TODO @puhui999:项了下,貌似这里存储 List<Long> giveCouponIds 更合适。因为优惠劵赠送到最后是对应的编号,然后从而进行取消?
     /**
      * 赠送的优惠劵
      *
      * key: 优惠劵编号
      * value:对应的优惠券数量
      *
-     * 目的:用于后续取消或者售后订单时,需要扣减赠送
+     * 目的:用于订单支付后赠送优惠券
      */
     @TableField(typeHandler = JacksonTypeHandler.class)
     private Map<Long, Integer> giveCouponsMap;
+    /**
+     * 赠送的优惠劵编号
+     *
+     * 目的:用于后续取消或者售后订单时,需要扣减赠送
+     */
+    @TableField(typeHandler = LongListTypeHandler.class)
+    private List<Long> giveCouponIds;
 
     /**
      * 秒杀活动编号

+ 11 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java

@@ -11,6 +11,8 @@ import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderI
 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
 import jakarta.validation.constraints.NotNull;
 
+import java.util.List;
+
 /**
  * 交易订单【写】Service 接口
  *
@@ -194,4 +196,13 @@ public interface TradeOrderUpdateService {
      */
     void cancelPaidOrder(Long userId, Long orderId, Integer cancelType);
 
+    /**
+     * 更新下单赠送的优惠券编号到订单
+     *
+     * @param userId        用户编号
+     * @param orderId       订单编号
+     * @param giveCouponIds 赠送的优惠券编号列表
+     */
+    void updateOrderGiveCouponIds(Long userId, Long orderId, List<Long> giveCouponIds);
+
 }

+ 17 - 1
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java

@@ -202,7 +202,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
         order.setProductCount(getSumValue(calculateRespBO.getItems(), TradePriceCalculateRespBO.OrderItem::getCount, Integer::sum));
         order.setUserIp(getClientIP()).setTerminal(getTerminal());
         // 使用 + 赠送优惠券
-        order.setGiveCouponsMap(calculateRespBO.getGiveCouponsMap());
+        order.setGiveCouponsMap(calculateRespBO.getGiveCoupons());
         // 支付 + 退款信息
         order.setAdjustPrice(0).setPayStatus(false);
         order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus()).setRefundPrice(0);
@@ -890,6 +890,22 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
                 .setReason(TradeOrderCancelTypeEnum.COMBINATION_CLOSE.getName()).setPrice(order.getPayPrice()));// 价格信息
     }
 
+    @Override
+    public void updateOrderGiveCouponIds(Long userId, Long orderId, List<Long> giveCouponIds) {
+        // 1.1 检验订单存在
+        TradeOrderDO order = tradeOrderMapper.selectOrderByIdAndUserId(orderId, userId);
+        if (order == null) {
+            throw exception(ORDER_NOT_FOUND);
+        }
+        // 1.2 校验订单是否支付
+        if (!order.getPayStatus()) {
+            throw exception(ORDER_CANCEL_PAID_FAIL, "已支付");
+        }
+
+        // 2. 更新订单赠送的优惠券编号列表
+        tradeOrderMapper.updateById(new TradeOrderDO().setId(orderId).setGiveCouponIds(giveCouponIds));
+    }
+
     /**
      * 创建单个订单的评论
      *

+ 16 - 3
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java

@@ -5,7 +5,10 @@ import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi;
 import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
+import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService;
+import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService;
 import jakarta.annotation.Resource;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Component;
 
 import java.util.List;
@@ -18,6 +21,12 @@ import java.util.List;
 @Component
 public class TradeCouponOrderHandler implements TradeOrderHandler {
 
+    @Resource
+    @Lazy // 延迟加载,避免循环依赖
+    private TradeOrderUpdateService orderUpdateService;
+    @Resource
+    private TradeOrderQueryService orderQueryService;
+
     @Resource
     private CouponApi couponApi;
 
@@ -37,7 +46,11 @@ public class TradeCouponOrderHandler implements TradeOrderHandler {
             return;
         }
         // 赠送优惠券
-        couponApi.takeCouponsByAdmin(order.getGiveCouponsMap(), order.getUserId());
+        List<Long> couponIds = couponApi.takeCouponsByAdmin(order.getGiveCouponsMap(), order.getUserId());
+        if (CollUtil.isEmpty(couponIds)) {
+            return;
+        }
+        orderUpdateService.updateOrderGiveCouponIds(order.getUserId(), order.getId(), couponIds);
     }
 
     @Override
@@ -48,10 +61,10 @@ public class TradeCouponOrderHandler implements TradeOrderHandler {
             couponApi.returnUsedCoupon(order.getCouponId());
         }
         // 情况二:收回赠送的优惠券
-        if (CollUtil.isEmpty(order.getGiveCouponsMap())) {
+        if (CollUtil.isEmpty(order.getGiveCouponIds())) {
             return;
         }
-        couponApi.invalidateCouponsByAdmin(order.getGiveCouponsMap(), order.getUserId());
+        couponApi.invalidateCouponsByAdmin(order.getGiveCouponIds(), order.getUserId());
     }
 
 }

+ 1 - 1
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java

@@ -79,7 +79,7 @@ public class TradePriceCalculateRespBO {
      *  key: 优惠劵编号,value:对应的优惠券数量
      * 目的:用于后续取消或者售后订单时,需要扣减赠送
      */
-    private Map<Long, Integer> giveCouponsMap;
+    private Map<Long, Integer> giveCoupons;
 
     /**
      * 订单价格

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

@@ -32,7 +32,7 @@ public class TradePriceCalculatorHelper {
                                                                List<ProductSpuRespDTO> spuList, List<ProductSkuRespDTO> skuList) {
         // 创建 PriceCalculateRespDTO 对象
         TradePriceCalculateRespBO result = new TradePriceCalculateRespBO();
-        result.setType(getOrderType(param)).setPromotions(new ArrayList<>()).setGiveCouponsMap(new LinkedHashMap<>());
+        result.setType(getOrderType(param)).setPromotions(new ArrayList<>()).setGiveCoupons(new LinkedHashMap<>());
 
         // 创建它的 OrderItem 属性
         result.setItems(new ArrayList<>(param.getItems().size()));

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

@@ -93,7 +93,7 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator
         TradePriceCalculatorHelper.recountAllPrice(result);
 
         // 4.1 记录赠送的积分
-        if (Boolean.TRUE.equals(rule.getGivePoint())) {
+        if (rule.getPoint() != null && rule.getPoint() > 0) {
             List<Integer> dividePoints = TradePriceCalculatorHelper.dividePrice(orderItems, rule.getPoint());
             for (int i = 0; i < orderItems.size(); i++) {
                 // 商品可能赠送了积分,所以这里要加上
@@ -107,13 +107,13 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator
             result.setFreeDelivery(true);
         }
         // 4.3 记录赠送的优惠券
-        if (Boolean.TRUE.equals(rule.getGiveCoupon())) {
-            for (Map.Entry<Long, Integer> entry : rule.getGiveCouponsMap().entrySet()) {
-                Map<Long, Integer> giveCouponsMap = result.getGiveCouponsMap();
-                if (giveCouponsMap.get(entry.getKey()) == null) { // 情况一:还没有赠送的优惠券
-                    result.setGiveCouponsMap(rule.getGiveCouponsMap());
+        if (CollUtil.isNotEmpty(rule.getGiveCoupons())) {
+            for (Map.Entry<Long, Integer> entry : rule.getGiveCoupons().entrySet()) {
+                Map<Long, Integer> giveCoupons = result.getGiveCoupons();
+                if (giveCoupons.get(entry.getKey()) == null) { // 情况一:还没有赠送的优惠券
+                    result.setGiveCoupons(rule.getGiveCoupons());
                 } else { // 情况二:别的满减活动送过同类优惠券,则直接增加数量
-                    giveCouponsMap.put(entry.getKey(), giveCouponsMap.get(entry.getKey()) + entry.getValue());
+                    giveCoupons.put(entry.getKey(), giveCoupons.get(entry.getKey()) + entry.getValue());
                 }
             }
         }

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

@@ -49,7 +49,7 @@ public class TradeRewardActivityPriceCalculatorTest extends BaseMockitoUnitTest
         TradePriceCalculateRespBO result = new TradePriceCalculateRespBO()
                 .setType(TradeOrderTypeEnum.NORMAL.getType())
                 .setPrice(new TradePriceCalculateRespBO.Price())
-                .setPromotions(new ArrayList<>()).setGiveCouponsMap(new LinkedHashMap<>())
+                .setPromotions(new ArrayList<>()).setGiveCoupons(new LinkedHashMap<>())
                 .setItems(asList(
                         new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true)
                                 .setPrice(100).setSpuId(1L),
@@ -68,16 +68,16 @@ public class TradeRewardActivityPriceCalculatorTest extends BaseMockitoUnitTest
                         .setConditionType(PromotionConditionTypeEnum.PRICE.getType())
                         .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductScopeValues(asList(1L, 2L))
                         .setRules(singletonList(new RewardActivityMatchRespDTO.Rule().setLimit(20).setDiscountPrice(70)
-                                .setGivePoint(false).setFreeDelivery(false)))),
+                                .setFreeDelivery(false)))),
                 randomPojo(RewardActivityMatchRespDTO.class, o -> o.setId(2000L).setName("活动 2000 号")
                         .setConditionType(PromotionConditionTypeEnum.COUNT.getType())
                         .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductScopeValues(singletonList(3L))
                         .setRules(asList(new RewardActivityMatchRespDTO.Rule().setLimit(1).setDiscountPrice(10)
-                                        .setGivePoint(true).setPoint(50).setFreeDelivery(false),
-                                new RewardActivityMatchRespDTO.Rule().setLimit(2).setDiscountPrice(60).setGivePoint(true)
+                                        .setPoint(50).setFreeDelivery(false),
+                                new RewardActivityMatchRespDTO.Rule().setLimit(2).setDiscountPrice(60)
                                         .setPoint(100).setFreeDelivery(false), // 最大可满足,因为是 4 个
                                 new RewardActivityMatchRespDTO.Rule().setLimit(10).setDiscountPrice(100)
-                                        .setGivePoint(false).setFreeDelivery(false))))
+                                        .setFreeDelivery(false))))
         ));
 
         // 调用