From a431bccc02a41972a68998ab888efa40281ba5e9 Mon Sep 17 00:00:00 2001
From: owen <owen@evolsun.com>
Date: Tue, 26 Sep 2023 15:11:48 +0800
Subject: [PATCH 01/10] =?UTF-8?q?=E5=88=86=E9=94=80=EF=BC=9AReview?=
 =?UTF-8?q?=E4=BB=A3=E7=A0=81=E4=BF=AE=E6=94=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../brokerage/BrokerageUserController.java    | 17 +++++-----
 .../brokerage/AppBrokerageUserController.java | 31 ++++++++++---------
 .../brokerage/BrokerageUserConvert.java       | 16 ++++++++++
 .../brokerage/BrokerageRecordMapper.java      | 31 ++++++++++++++++---
 .../brokerage/BrokerageWithdrawMapper.java    | 31 +++++++++++++++----
 .../brokerage/BrokerageRecordService.java     | 20 ++++++++++--
 .../brokerage/BrokerageRecordServiceImpl.java | 12 +++----
 .../brokerage/BrokerageUserService.java       | 17 +---------
 .../brokerage/BrokerageUserServiceImpl.java   | 12 ++++---
 .../brokerage/BrokerageWithdrawService.java   | 23 ++++++++++++--
 .../BrokerageWithdrawServiceImpl.java         |  7 +++--
 .../brokerage/bo/UserBrokerageSummaryBO.java  |  5 ++-
 .../brokerage/bo/UserWithdrawSummaryBO.java   |  5 +++
 13 files changed, 154 insertions(+), 73 deletions(-)

diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/BrokerageUserController.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/BrokerageUserController.java
index 8d27dd3b4..71832fd8b 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/BrokerageUserController.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/BrokerageUserController.java
@@ -10,10 +10,10 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO;
 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum;
 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawStatusEnum;
-import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageWithdrawService;
-import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO;
 import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageRecordService;
 import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageUserService;
+import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageWithdrawService;
+import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO;
 import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserWithdrawSummaryBO;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
@@ -92,20 +92,17 @@ public class BrokerageUserController {
         Set<Long> userIds = convertSet(pageResult.getList(), BrokerageUserDO::getId);
         // 查询用户信息
         Map<Long, MemberUserRespDTO> userMap = memberUserApi.getUserMap(userIds);
-        // TODO @疯狂:看看下面两个 getBrokerageUserCountByBindUserId、getWithdrawSummaryByUserId 有没可能一次性出结果,不然 n 次有点太花性能了;
         // 合计分佣订单
-        Map<Long, UserBrokerageSummaryBO> userOrderSummaryMap = convertMap(userIds,
-                userId -> userId,
-                userId -> brokerageRecordService.getUserBrokerageSummaryByUserId(userId,
-                        BrokerageRecordBizTypeEnum.ORDER.getType(), BrokerageRecordStatusEnum.SETTLEMENT.getStatus()));
+        Map<Long, UserBrokerageSummaryBO> userOrderSummaryMap = brokerageRecordService.getUserBrokerageSummaryMapByUserId(
+                userIds, BrokerageRecordBizTypeEnum.ORDER.getType(), BrokerageRecordStatusEnum.SETTLEMENT.getStatus());
+        // TODO @芋艿:看看下面 getBrokerageUserCountByBindUserId 有没可能一次性出结果,不然 n 次有点太花性能了;
         // 合计推广用户数量
         Map<Long, Long> brokerageUserCountMap = convertMap(userIds,
                 userId -> userId,
                 userId -> brokerageUserService.getBrokerageUserCountByBindUserId(userId, null));
         // 合计提现
-        Map<Long, UserWithdrawSummaryBO> withdrawMap = convertMap(userIds,
-                userId -> userId,
-                userId -> brokerageWithdrawService.getWithdrawSummaryByUserId(userId, BrokerageWithdrawStatusEnum.AUDIT_SUCCESS));
+        Map<Long, UserWithdrawSummaryBO> withdrawMap = brokerageWithdrawService.getWithdrawSummaryMapByUserId(userIds,
+                BrokerageWithdrawStatusEnum.AUDIT_SUCCESS);
         // 拼接返回
         return success(BrokerageUserConvert.INSTANCE.convertPage(pageResult, userMap, brokerageUserCountMap,
                 userOrderSummaryMap, withdrawMap));
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/AppBrokerageUserController.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/AppBrokerageUserController.java
index da78f9dff..f7b7276ab 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/AppBrokerageUserController.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/AppBrokerageUserController.java
@@ -27,6 +27,7 @@ import org.springframework.web.bind.annotation.*;
 import javax.annotation.Resource;
 import javax.validation.Valid;
 import java.time.LocalDateTime;
+import java.util.Collections;
 import java.util.Map;
 import java.util.Optional;
 
@@ -76,23 +77,25 @@ public class AppBrokerageUserController {
     @Operation(summary = "获得个人分销统计")
     @PreAuthenticated
     public CommonResult<AppBrokerageUserMySummaryRespVO> getBrokerageUserSummary() {
-        Long userId = getLoginUserId();
-        // TODO @疯狂:后面这种,要不也改成 convert;感觉 controller 这样更容易看到整体;核心其实是 86、8/87、9/90、9/91 这阶段
-        // 统计 yesterdayPrice、withdrawPrice、firstBrokerageUserCount、secondBrokerageUserCount 字段
+        // 查询当前登录用户信息
+        BrokerageUserDO brokerageUser = brokerageUserService.getBrokerageUser(getLoginUserId());
+        // 统计用户昨日的佣金
         LocalDateTime yesterday = LocalDateTime.now().minusDays(1);
         LocalDateTime beginTime = LocalDateTimeUtil.beginOfDay(yesterday);
         LocalDateTime endTime = LocalDateTimeUtil.endOfDay(yesterday);
-        AppBrokerageUserMySummaryRespVO respVO = new AppBrokerageUserMySummaryRespVO()
-                .setYesterdayPrice(brokerageRecordService.getSummaryPriceByUserId(userId, BrokerageRecordBizTypeEnum.ORDER.getType(), beginTime, endTime))
-                .setWithdrawPrice(Optional.ofNullable(brokerageWithdrawService.getWithdrawSummaryByUserId(userId, BrokerageWithdrawStatusEnum.AUDIT_SUCCESS))
-                        .map(UserWithdrawSummaryBO::getPrice).orElse(0))
-                .setBrokeragePrice(0).setFrozenPrice(0)
-                .setFirstBrokerageUserCount(brokerageUserService.getBrokerageUserCountByBindUserId(userId, 1))
-                .setSecondBrokerageUserCount(brokerageUserService.getBrokerageUserCountByBindUserId(userId, 2));
-        // 设置 brokeragePrice、frozenPrice 字段
-        Optional.ofNullable(brokerageUserService.getBrokerageUser(userId))
-                .ifPresent(user -> respVO.setBrokeragePrice(user.getBrokeragePrice()).setFrozenPrice(user.getFrozenPrice()));
-        return success(respVO);
+        Integer yesterdayPrice = brokerageRecordService.getSummaryPriceByUserId(brokerageUser.getId(),
+                BrokerageRecordBizTypeEnum.ORDER.getType(), beginTime, endTime);
+        // 统计用户提现的佣金
+        Integer withdrawPrice = brokerageWithdrawService.getWithdrawSummaryByUserId(Collections.singleton(brokerageUser.getId()),
+                        BrokerageWithdrawStatusEnum.AUDIT_SUCCESS).stream()
+                .findFirst().map(UserWithdrawSummaryBO::getPrice).orElse(0);
+        // 统计分销用户数量(一级)
+        Long firstBrokerageUserCount = brokerageUserService.getBrokerageUserCountByBindUserId(brokerageUser.getId(), 1);
+        // 统计分销用户数量(二级)
+        Long secondBrokerageUserCount = brokerageUserService.getBrokerageUserCountByBindUserId(brokerageUser.getId(), 2);
+
+        // 拼接返回
+        return success(BrokerageUserConvert.INSTANCE.convert(yesterdayPrice, withdrawPrice, firstBrokerageUserCount, secondBrokerageUserCount, brokerageUser));
     }
 
     @GetMapping("/rank-page-by-user-count")
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/brokerage/BrokerageUserConvert.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/brokerage/BrokerageUserConvert.java
index 6a578032d..086ef0aa1 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/brokerage/BrokerageUserConvert.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/brokerage/BrokerageUserConvert.java
@@ -1,9 +1,11 @@
 package cn.iocoder.yudao.module.trade.convert.brokerage;
 
 import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.ObjUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
 import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.user.BrokerageUserRespVO;
+import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserMySummaryRespVO;
 import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserRankByUserCountRespVO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO;
 import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO;
@@ -71,4 +73,18 @@ public interface BrokerageUserConvert {
 
     void copyTo(MemberUserRespDTO from, @MappingTarget AppBrokerageUserRankByUserCountRespVO to);
 
+    default AppBrokerageUserMySummaryRespVO convert(Integer yesterdayPrice, Integer withdrawPrice,
+                                                    Long firstBrokerageUserCount, Long secondBrokerageUserCount,
+                                                    BrokerageUserDO brokerageUser) {
+        AppBrokerageUserMySummaryRespVO respVO = new AppBrokerageUserMySummaryRespVO()
+                .setYesterdayPrice(ObjUtil.defaultIfNull(yesterdayPrice, 0))
+                .setWithdrawPrice(ObjUtil.defaultIfNull(withdrawPrice, 0))
+                .setBrokeragePrice(0).setFrozenPrice(0)
+                .setFirstBrokerageUserCount(ObjUtil.defaultIfNull(firstBrokerageUserCount, 0L))
+                .setSecondBrokerageUserCount(ObjUtil.defaultIfNull(secondBrokerageUserCount, 0L));
+        // 设置 brokeragePrice、frozenPrice 字段
+        Optional.ofNullable(brokerageUser)
+                .ifPresent(user -> respVO.setBrokeragePrice(user.getBrokeragePrice()).setFrozenPrice(user.getFrozenPrice()));
+        return respVO;
+    }
 }
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageRecordMapper.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageRecordMapper.java
index eb55a8c4c..95c160515 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageRecordMapper.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageRecordMapper.java
@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.trade.dal.mysql.brokerage;
 
+import cn.hutool.core.bean.BeanUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
@@ -9,12 +10,16 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageRecordDO;
 import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.github.yulichang.toolkit.LambdaUtils;
+import com.github.yulichang.toolkit.MPJWrappers;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 
 import java.time.LocalDateTime;
+import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 
 /**
  * 佣金记录 Mapper
@@ -52,11 +57,27 @@ public interface BrokerageRecordMapper extends BaseMapperX<BrokerageRecordDO> {
                 BrokerageRecordDO::getUserId, userId);
     }
 
-    @Select("SELECT COUNT(1), SUM(price) FROM trade_brokerage_record " +
-            "WHERE user_id = #{userId} AND biz_type = #{bizType} AND status = #{status} AND deleted = FALSE")
-    UserBrokerageSummaryBO selectCountAndSumPriceByUserIdAndBizTypeAndStatus(@Param("userId") Long userId,
-                                                                             @Param("bizType") Integer bizType,
-                                                                             @Param("status") Integer status);
+    default List<UserBrokerageSummaryBO> selectCountAndSumPriceByUserIdInAndBizTypeAndStatus(Collection<Long> userIds,
+                                                                                             Integer bizType,
+                                                                                             Integer status) {
+        List<Map<String, Object>> list = selectMaps(MPJWrappers.lambdaJoin(BrokerageRecordDO.class)
+                .select(BrokerageRecordDO::getUserId)
+                .selectCount(BrokerageRecordDO::getId, LambdaUtils.getName(UserBrokerageSummaryBO::getCount))
+                .selectSum(BrokerageRecordDO::getPrice)
+                .in(BrokerageRecordDO::getUserId, userIds)
+                .eq(BrokerageRecordDO::getBizId, bizType)
+                .eq(BrokerageRecordDO::getStatus, status)
+                .groupBy(BrokerageRecordDO::getUserId));
+        return BeanUtil.copyToList(list, UserBrokerageSummaryBO.class);
+//            return selectJoinList(UserBrokerageSummaryBO.class, MPJWrappers.lambdaJoin(BrokerageRecordDO.class)
+//                    .select(BrokerageRecordDO::getUserId)
+//                    .selectCount(BrokerageRecordDO::getId, LambdaUtils.getName(UserBrokerageSummaryBO::getCount))
+//                    .selectSum(BrokerageRecordDO::getPrice)
+//                    .in(BrokerageRecordDO::getUserId, userIds)
+//                    .eq(BrokerageRecordDO::getBizId, bizType)
+//                    .eq(BrokerageRecordDO::getStatus, status)
+//                    .groupBy(BrokerageRecordDO::getUserId));
+    }
 
     @Select("SELECT SUM(price) FROM trade_brokerage_record " +
             "WHERE user_id = #{userId} AND biz_type = #{bizType} " +
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageWithdrawMapper.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageWithdrawMapper.java
index f8ca4c5b7..5a950a4cb 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageWithdrawMapper.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageWithdrawMapper.java
@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.trade.dal.mysql.brokerage;
 
+import cn.hutool.core.bean.BeanUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
@@ -7,9 +8,13 @@ import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw.Brok
 import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageWithdrawDO;
 import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserWithdrawSummaryBO;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.github.yulichang.toolkit.LambdaUtils;
+import com.github.yulichang.wrapper.MPJLambdaWrapper;
 import org.apache.ibatis.annotations.Mapper;
-import org.apache.ibatis.annotations.Param;
-import org.apache.ibatis.annotations.Select;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
 
 /**
  * 佣金提现 Mapper
@@ -37,9 +42,23 @@ public interface BrokerageWithdrawMapper extends BaseMapperX<BrokerageWithdrawDO
                 .eq(BrokerageWithdrawDO::getStatus, status));
     }
 
-    @Select("SELECT COUNT(1) AS count, SUM(price) AS price FROM trade_brokerage_withdraw " +
-            "WHERE user_id = #{userId} AND status = #{status} AND deleted = FALSE")
-    UserWithdrawSummaryBO selectCountAndSumPriceByUserIdAndStatus(@Param("userId") Long userId,
-                                                                  @Param("status") Integer status);
+    default List<UserWithdrawSummaryBO> selectCountAndSumPriceByUserIdAndStatus(Collection<Long> userIds, Integer status) {
+        List<Map<String, Object>> list = selectMaps(new MPJLambdaWrapper<BrokerageWithdrawDO>()
+                .select(BrokerageWithdrawDO::getUserId)
+                .selectCount(BrokerageWithdrawDO::getId, LambdaUtils.getName(UserWithdrawSummaryBO::getCount))
+                .selectSum(BrokerageWithdrawDO::getPrice)
+                .in(BrokerageWithdrawDO::getUserId, userIds)
+                .eq(BrokerageWithdrawDO::getStatus, status)
+                .groupBy(BrokerageWithdrawDO::getUserId));
+        return BeanUtil.copyToList(list, UserWithdrawSummaryBO.class);
+        // selectJoinList有BUG,会与租户插件冲突:解析SQL时,发生异常 https://gitee.com/best_handsome/mybatis-plus-join/issues/I84GYW
+//        return selectJoinList(UserWithdrawSummaryBO.class, new MPJLambdaWrapper<BrokerageWithdrawDO>()
+//                .select(BrokerageWithdrawDO::getUserId)
+//                    .selectCount(BrokerageWithdrawDO::getId, LambdaUtils.getName(UserWithdrawSummaryBO::getCount))
+//                .selectSum(BrokerageWithdrawDO::getPrice)
+//                .in(BrokerageWithdrawDO::getUserId, userIds)
+//                .eq(BrokerageWithdrawDO::getStatus, status)
+//                .groupBy(BrokerageWithdrawDO::getUserId));
+    }
 
 }
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordService.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordService.java
index 875c29cfd..a059c712d 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordService.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordService.java
@@ -12,7 +12,11 @@ import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO
 
 import javax.validation.Valid;
 import java.time.LocalDateTime;
+import java.util.Collection;
 import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
 
 /**
  * 佣金记录 Service 接口
@@ -76,12 +80,24 @@ public interface BrokerageRecordService {
     /**
      * 汇总用户佣金
      *
-     * @param userId  用户编号
+     * @param userIds 用户编号
      * @param bizType 业务类型
      * @param status  佣金状态
      * @return 用户佣金汇总
      */
-    UserBrokerageSummaryBO getUserBrokerageSummaryByUserId(Long userId, Integer bizType, Integer status);
+    List<UserBrokerageSummaryBO> getUserBrokerageSummaryByUserId(Collection<Long> userIds, Integer bizType, Integer status);
+
+    /**
+     * 汇总用户佣金
+     *
+     * @param userIds 用户编号
+     * @param bizType 业务类型
+     * @param status  佣金状态
+     * @return 用户佣金汇总
+     */
+    default Map<Long, UserBrokerageSummaryBO> getUserBrokerageSummaryMapByUserId(Collection<Long> userIds, Integer bizType, Integer status) {
+        return convertMap(getUserBrokerageSummaryByUserId(userIds, bizType, status), UserBrokerageSummaryBO::getUserId);
+    }
 
     /**
      * 获得用户佣金合计
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordServiceImpl.java
index 7630ce4c5..1c83c9ec6 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordServiceImpl.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordServiceImpl.java
@@ -36,10 +36,7 @@ import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
 import java.time.LocalDateTime;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
+import java.util.*;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getMaxValue;
@@ -235,9 +232,8 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService {
     }
 
     @Override
-    public UserBrokerageSummaryBO getUserBrokerageSummaryByUserId(Long userId, Integer bizType, Integer status) {
-        UserBrokerageSummaryBO summaryBO = brokerageRecordMapper.selectCountAndSumPriceByUserIdAndBizTypeAndStatus(userId, bizType, status);
-        return summaryBO != null ? summaryBO : new UserBrokerageSummaryBO(0, 0);
+    public List<UserBrokerageSummaryBO> getUserBrokerageSummaryByUserId(Collection<Long> userIds, Integer bizType, Integer status) {
+        return brokerageRecordMapper.selectCountAndSumPriceByUserIdInAndBizTypeAndStatus(userIds, bizType, status);
     }
 
     @Override
@@ -338,7 +334,7 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService {
         if (BooleanUtil.isTrue(spu.getSubCommissionType())) {
             fixedMinPrice = getMinValue(skuList, ProductSkuRespDTO::getFirstBrokeragePrice);
             fixedMaxPrice = getMaxValue(skuList, ProductSkuRespDTO::getFirstBrokeragePrice);
-        // 3.2 全局分佣模式(根据商品价格比例计算)
+            // 3.2 全局分佣模式(根据商品价格比例计算)
         } else {
             spuMinPrice = getMinValue(skuList, ProductSkuRespDTO::getPrice);
             spuMaxPrice = getMaxValue(skuList, ProductSkuRespDTO::getPrice);
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserService.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserService.java
index 8fc8c1960..861e3d8c4 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserService.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserService.java
@@ -1,7 +1,6 @@
 package cn.iocoder.yudao.module.trade.service.brokerage;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
 import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.user.BrokerageUserPageReqVO;
 import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserChildSummaryPageReqVO;
 import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserChildSummaryRespVO;
@@ -111,21 +110,7 @@ public interface BrokerageUserService {
      * @param registerTime 用户注册时间
      * @return 是否绑定
      */
-    default boolean bindBrokerageUser(@NotNull Long userId, @NotNull Long bindUserId, @NotNull LocalDateTime registerTime) {
-        // 注册时间在30秒内的,都算新用户
-        boolean isNewUser = LocalDateTimeUtils.afterNow(registerTime.minusSeconds(30));
-        return bindBrokerageUser(userId, bindUserId, isNewUser);
-    }
-
-    /**
-     * 【会员】绑定推广员
-     *
-     * @param userId     用户编号
-     * @param bindUserId 推广员编号
-     * @param isNewUser  是否为新用户
-     * @return 是否绑定
-     */
-    boolean bindBrokerageUser(Long userId, Long bindUserId, Boolean isNewUser);
+    boolean bindBrokerageUser(@NotNull Long userId, @NotNull Long bindUserId, @NotNull LocalDateTime registerTime);
 
     /**
      * 获取用户是否有分销资格
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserServiceImpl.java
index 8672f3069..71b33d36c 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserServiceImpl.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserServiceImpl.java
@@ -5,6 +5,7 @@ import cn.hutool.core.lang.Assert;
 import cn.hutool.core.util.ArrayUtil;
 import cn.hutool.core.util.BooleanUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
 import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
 import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.user.BrokerageUserPageReqVO;
 import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserChildSummaryPageReqVO;
@@ -154,7 +155,7 @@ public class BrokerageUserServiceImpl implements BrokerageUserService {
     }
 
     @Override
-    public boolean bindBrokerageUser(Long userId, Long bindUserId, Boolean isNewUser) {
+    public boolean bindBrokerageUser(Long userId, Long bindUserId, LocalDateTime registerTime) {
         // 1. 获得分销用户
         boolean isNewBrokerageUser = false;
         BrokerageUserDO brokerageUser = brokerageUserMapper.selectById(userId);
@@ -164,7 +165,7 @@ public class BrokerageUserServiceImpl implements BrokerageUserService {
         }
 
         // 2.1 校验是否能绑定用户
-        boolean validated = isUserCanBind(brokerageUser, isNewUser);
+        boolean validated = isUserCanBind(brokerageUser, registerTime);
         if (!validated) {
             return false;
         }
@@ -222,7 +223,7 @@ public class BrokerageUserServiceImpl implements BrokerageUserService {
         return new PageResult<>(pageResult.getRecords(), pageResult.getTotal());
     }
 
-    private boolean isUserCanBind(BrokerageUserDO user, Boolean isNewUser) {
+    private boolean isUserCanBind(BrokerageUserDO user, LocalDateTime registerTime) {
         // 校验分销功能是否启用
         TradeConfigDO tradeConfig = tradeConfigService.getTradeConfig();
         if (tradeConfig == null || !BooleanUtil.isTrue(tradeConfig.getBrokerageEnabled())) {
@@ -236,8 +237,9 @@ public class BrokerageUserServiceImpl implements BrokerageUserService {
 
         // 校验分销关系绑定模式
         if (BrokerageBindModeEnum.REGISTER.getMode().equals(tradeConfig.getBrokerageBindMode())) {
-            // TODO @疯狂:是不是把 isNewUser 挪到这里好点呀?
-            if (!BooleanUtil.isTrue(isNewUser)) {
+            // 判断是否为新用户:注册时间在30秒内的,都算新用户
+            boolean isNotNewUser = LocalDateTimeUtils.beforeNow(registerTime.plusSeconds(30));
+            if (isNotNewUser) {
                 throw exception(BROKERAGE_BIND_MODE_REGISTER); // 只有在注册时可以绑定
             }
         } else if (BrokerageBindModeEnum.ANYTIME.getMode().equals(tradeConfig.getBrokerageBindMode())) {
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawService.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawService.java
index 2d7ba622b..696b266f3 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawService.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawService.java
@@ -7,6 +7,13 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageWithdrawD
 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawStatusEnum;
 import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserWithdrawSummaryBO;
 
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+
 /**
  * 佣金提现 Service 接口
  *
@@ -51,10 +58,20 @@ public interface BrokerageWithdrawService {
     /**
      * 汇总用户提现
      *
-     * @param userId 用户编号
-     * @param status 提现状态
+     * @param userIds 用户编号
+     * @param status  提现状态
      * @return 用户提现汇总
      */
-    UserWithdrawSummaryBO getWithdrawSummaryByUserId(Long userId, BrokerageWithdrawStatusEnum status);
+    List<UserWithdrawSummaryBO> getWithdrawSummaryByUserId(Collection<Long> userIds, BrokerageWithdrawStatusEnum status);
 
+    /**
+     * 汇总用户提现
+     *
+     * @param userIds 用户编号
+     * @param status  提现状态
+     * @return 用户提现汇总
+     */
+    default Map<Long, UserWithdrawSummaryBO> getWithdrawSummaryMapByUserId(Set<Long> userIds, BrokerageWithdrawStatusEnum status) {
+        return convertMap(getWithdrawSummaryByUserId(userIds, status), UserWithdrawSummaryBO::getUserId);
+    }
 }
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java
index 102e65529..61dcf2817 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java
@@ -26,6 +26,8 @@ import org.springframework.validation.annotation.Validated;
 import javax.annotation.Resource;
 import javax.validation.Validator;
 import java.time.LocalDateTime;
+import java.util.Collection;
+import java.util.List;
 import java.util.Map;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -142,9 +144,8 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
     }
 
     @Override
-    public UserWithdrawSummaryBO getWithdrawSummaryByUserId(Long userId, BrokerageWithdrawStatusEnum status) {
-        UserWithdrawSummaryBO summaryBO = brokerageWithdrawMapper.selectCountAndSumPriceByUserIdAndStatus(userId, status.getStatus());
-        return summaryBO != null ? summaryBO : new UserWithdrawSummaryBO(0, 0);
+    public List<UserWithdrawSummaryBO> getWithdrawSummaryByUserId(Collection<Long> userIds, BrokerageWithdrawStatusEnum status) {
+        return brokerageWithdrawMapper.selectCountAndSumPriceByUserIdAndStatus(userIds, status.getStatus());
     }
 
     /**
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/bo/UserBrokerageSummaryBO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/bo/UserBrokerageSummaryBO.java
index 4504290be..872378e47 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/bo/UserBrokerageSummaryBO.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/bo/UserBrokerageSummaryBO.java
@@ -13,7 +13,10 @@ import lombok.NoArgsConstructor;
 @NoArgsConstructor
 @AllArgsConstructor
 public class UserBrokerageSummaryBO {
-
+    /**
+     * 用户编号
+     */
+    private Long userId;
     /**
      * 佣金数量
      */
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/bo/UserWithdrawSummaryBO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/bo/UserWithdrawSummaryBO.java
index cbfc79302..0061185fa 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/bo/UserWithdrawSummaryBO.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/bo/UserWithdrawSummaryBO.java
@@ -14,6 +14,11 @@ import lombok.NoArgsConstructor;
 @AllArgsConstructor
 public class UserWithdrawSummaryBO {
 
+    /**
+     * 用户编号
+     */
+    private Long userId;
+
     /**
      * 提现次数
      */

From 0a3a6825d5186ed252545ad1bea99b5157e416be Mon Sep 17 00:00:00 2001
From: owen <owen@evolsun.com>
Date: Tue, 26 Sep 2023 20:44:22 +0800
Subject: [PATCH 02/10] =?UTF-8?q?=E4=BC=98=E6=83=A0=E5=88=B8=EF=BC=9A?=
 =?UTF-8?q?=E5=AE=8C=E5=96=84APP=E4=BC=98=E6=83=A0=E5=88=B8=E6=9F=A5?=
 =?UTF-8?q?=E8=AF=A2=E6=8E=A5=E5=8F=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 sql/mysql/ruoyi-vue-pro.sql                   |   6 +-
 .../dto/RewardActivityMatchRespDTO.java       |   2 +-
 .../common/PromotionProductScopeEnum.java     |   6 +-
 .../vo/template/CouponTemplatePageReqVO.java  |   9 +-
 .../admin/reward/vo/RewardActivityBaseVO.java |   2 +-
 .../coupon/AppCouponTemplateController.java   | 123 +++++++-----------
 .../coupon/vo/coupon/AppCouponMatchReqVO.java |   6 +-
 .../template/AppCouponTemplatePageReqVO.java  |  10 +-
 .../convert/coupon/CouponTemplateConvert.java |  21 +++
 .../dataobject/reward/RewardActivityDO.java   |   2 +-
 .../dal/mysql/coupon/CouponMapper.java        |  11 ++
 .../mysql/coupon/CouponTemplateMapper.java    |   3 +
 .../service/coupon/CouponService.java         |  31 ++++-
 .../service/coupon/CouponServiceImpl.java     |  13 +-
 .../service/coupon/bo/CouponTakeCountBO.java  |  20 +++
 .../brokerage/BrokerageRecordMapper.java      |   5 +-
 .../brokerage/BrokerageWithdrawMapper.java    |   5 +-
 17 files changed, 169 insertions(+), 106 deletions(-)
 create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/bo/CouponTakeCountBO.java

diff --git a/sql/mysql/ruoyi-vue-pro.sql b/sql/mysql/ruoyi-vue-pro.sql
index 443be2c2c..ef0da9f0a 100644
--- a/sql/mysql/ruoyi-vue-pro.sql
+++ b/sql/mysql/ruoyi-vue-pro.sql
@@ -1188,8 +1188,8 @@ INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `st
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1166, 2, '折扣', '2', 'promotion_discount_type', 0, 'primary', '', '优惠类型 - 折扣', '1', '2022-11-01 12:46:51', '1', '2022-11-01 12:50:08', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1167, 1, '固定日期', '1', 'promotion_coupon_template_validity_type', 0, 'default', '', '优惠劵模板的有限期类型 - 固定日期', '1', '2022-11-02 00:07:34', '1', '2022-11-04 00:07:49', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1168, 2, '领取之后', '2', 'promotion_coupon_template_validity_type', 0, 'default', '', '优惠劵模板的有限期类型 - 领取之后', '1', '2022-11-02 00:07:54', '1', '2022-11-04 00:07:52', b'0');
-INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1169, 1, '通用卷', '1', 'promotion_product_scope', 0, 'default', '', '营销的商品范围 - 全部商品参与', '1', '2022-11-02 00:28:22', '1', '2023-09-01 23:42:49', b'0');
-INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1170, 2, '商品卷', '2', 'promotion_product_scope', 0, 'default', '', '营销的商品范围 - 指定商品参与', '1', '2022-11-02 00:28:34', '1', '2023-09-01 23:42:54', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1169, 1, '通用券', '1', 'promotion_product_scope', 0, 'default', '', '营销的商品范围 - 全部商品参与', '1', '2022-11-02 00:28:22', '1', '2023-09-01 23:42:49', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1170, 2, '商品券', '2', 'promotion_product_scope', 0, 'default', '', '营销的商品范围 - 指定商品参与', '1', '2022-11-02 00:28:34', '1', '2023-09-01 23:42:54', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1171, 1, '已领取', '1', 'promotion_coupon_status', 0, 'primary', '', '优惠劵的状态 - 已领取', '1', '2022-11-04 00:15:08', '1', '2022-11-04 19:16:04', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1172, 2, '已使用', '2', 'promotion_coupon_status', 0, 'success', '', '优惠劵的状态 - 已使用', '1', '2022-11-04 00:15:21', '1', '2022-11-04 19:16:08', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1173, 3, '已过期', '3', 'promotion_coupon_status', 0, 'info', '', '优惠劵的状态 - 已过期', '1', '2022-11-04 00:15:43', '1', '2022-11-04 19:16:12', b'0');
@@ -1284,7 +1284,7 @@ INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `st
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1355, 5, '抽奖奖励', '5', 'member_experience_biz_type', 0, '', '', NULL, '', '2023-08-22 12:41:01', '', '2023-08-22 12:41:01', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1356, 1, '快递发货', '1', 'trade_delivery_type', 0, '', '', '', '1', '2023-08-23 00:04:55', '1', '2023-08-23 00:04:55', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1357, 2, '用户自提', '2', 'trade_delivery_type', 0, '', '', '', '1', '2023-08-23 00:05:05', '1', '2023-08-23 00:05:05', b'0');
-INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1358, 3, '品类卷', '3', 'promotion_product_scope', 0, 'default', '', '', '1', '2023-09-01 23:43:07', '1', '2023-09-01 23:43:07', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1358, 3, '品类券', '3', 'promotion_product_scope', 0, 'default', '', '', '1', '2023-09-01 23:43:07', '1', '2023-09-01 23:43:07', b'0');
 COMMIT;
 
 -- ----------------------------
diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/reward/dto/RewardActivityMatchRespDTO.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/reward/dto/RewardActivityMatchRespDTO.java
index 19f46a49a..6ae71a1d9 100644
--- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/reward/dto/RewardActivityMatchRespDTO.java
+++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/reward/dto/RewardActivityMatchRespDTO.java
@@ -68,7 +68,7 @@ public class RewardActivityMatchRespDTO {
          */
         private List<Long> couponIds;
         /**
-         * 赠送的优惠卷数量的数组
+         * 赠送的优惠券数量的数组
          */
         private List<Integer> couponCounts;
 
diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/common/PromotionProductScopeEnum.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/common/PromotionProductScopeEnum.java
index 6ae9f9f46..882dc4aee 100644
--- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/common/PromotionProductScopeEnum.java
+++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/common/PromotionProductScopeEnum.java
@@ -15,9 +15,9 @@ import java.util.Arrays;
 @AllArgsConstructor
 public enum PromotionProductScopeEnum implements IntArrayValuable {
 
-    ALL(1, "通用卷"), // 全部商品
-    SPU(2, "商品卷"), // 指定商品
-    CATEGORY(3, "品类卷"), // 指定商品
+    ALL(1, "通用券"), // 全部商品
+    SPU(2, "商品券"), // 指定商品
+    CATEGORY(3, "品类券"), // 指定品类
     ;
 
     public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(PromotionProductScopeEnum::getScope).toArray();
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/template/CouponTemplatePageReqVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/template/CouponTemplatePageReqVO.java
index abc7134e1..551d076ea 100755
--- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/template/CouponTemplatePageReqVO.java
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/template/CouponTemplatePageReqVO.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template;
 
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
 import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
@@ -33,8 +34,14 @@ public class CouponTemplatePageReqVO extends PageParam {
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
     private LocalDateTime[] createTime;
 
-    @Schema(description = "可以领取的类型", example = "[1,2, 3]")
+    @Schema(description = "可以领取的类型", example = "[1, 2, 3]")
     @InEnum(value = CouponTakeTypeEnum.class, message = "可以领取的类型,必须是 {value}")
     private List<Integer> canTakeTypes;
 
+    @Schema(description = "商品范围", example = "1")
+    @InEnum(value = PromotionProductScopeEnum.class, message = "商品范围,必须是 {value}")
+    private Integer productScope;
+
+    @Schema(description = "商品范围编号", example = "1")
+    private Long productScopeValue;
 }
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityBaseVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityBaseVO.java
index 3cb3109f3..030a31a5d 100755
--- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityBaseVO.java
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityBaseVO.java
@@ -84,7 +84,7 @@ public class RewardActivityBaseVO {
         @Schema(description = "赠送的优惠劵编号的数组", example = "1,2,3")
         private List<Long> couponIds;
 
-        @Schema(description = "赠送的优惠卷数量的数组", example = "1,2,3")
+        @Schema(description = "赠送的优惠券数量的数组", example = "1,2,3")
         private List<Integer> couponCounts;
 
         @AssertTrue(message = "优惠劵和数量必须一一对应")
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/AppCouponTemplateController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/AppCouponTemplateController.java
index c4d81de2c..be2c53559 100755
--- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/AppCouponTemplateController.java
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/AppCouponTemplateController.java
@@ -1,27 +1,31 @@
 package cn.iocoder.yudao.module.promotion.controller.app.coupon;
 
+import cn.hutool.core.util.ObjUtil;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+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.coupon.vo.template.AppCouponTemplatePageReqVO;
 import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.template.AppCouponTemplateRespVO;
+import cn.iocoder.yudao.module.promotion.convert.coupon.CouponTemplateConvert;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
+import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
+import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
+import cn.iocoder.yudao.module.promotion.service.coupon.CouponService;
 import cn.iocoder.yudao.module.promotion.service.coupon.CouponTemplateService;
 import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.Parameter;
-import io.swagger.v3.oas.annotations.Parameters;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
 import javax.annotation.Resource;
-import java.time.LocalDateTime;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Random;
+import java.util.*;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
 
 @Tag(name = "用户 App - 优惠劵模板")
 @RestController
@@ -31,84 +35,45 @@ public class AppCouponTemplateController {
 
     @Resource
     private CouponTemplateService couponTemplateService;
+    @Resource
+    private CouponService couponService;
 
-    // TODO 芋艿:待实现
-    @GetMapping("/list")
-    @Operation(summary = "获得优惠劵模版列表")
-    @Parameters({
-            @Parameter(name = "spuId", description = "商品 SPU 编号"), // 目前主要给商品详情使用
-            @Parameter(name = "useType", description = "使用类型"),
-            @Parameter(name = "count", description = "数量", required = true)
-    })
-    public CommonResult<List<AppCouponTemplateRespVO>> getCouponTemplateList(@RequestParam(value = "spuId", required = false) Long spuId,
-                                                                             @RequestParam(value = "useType", required = false) Integer useType,
-                                                                             @RequestParam(value = "count", required = false, defaultValue = "10") Integer count) {
-        List<AppCouponTemplateRespVO> list = new ArrayList<>();
-        Random random = new Random();
-        for (int i = 0; i < 10; i++) {
-            AppCouponTemplateRespVO vo = new AppCouponTemplateRespVO();
-            vo.setId(i + 1L);
-            vo.setName("优惠劵" + (i + 1));
-            vo.setTakeLimitCount(random.nextInt(10) + 1);
-            vo.setUsePrice(random.nextInt(100) * 100);
-            vo.setValidityType(random.nextInt(2) + 1);
-            if (vo.getValidityType() == 1) {
-                vo.setValidStartTime(LocalDateTime.now().plusDays(random.nextInt(10)));
-                vo.setValidEndTime(LocalDateTime.now().plusDays(random.nextInt(20) + 10));
-            } else {
-                vo.setFixedStartTerm(random.nextInt(10));
-                vo.setFixedEndTerm(random.nextInt(10) + vo.getFixedStartTerm() + 1);
-            }
-            vo.setDiscountType(random.nextInt(2) + 1);
-            if (vo.getDiscountType() == 1) {
-                vo.setDiscountPercent(null);
-                vo.setDiscountPrice(random.nextInt(50) * 100);
-                vo.setDiscountLimitPrice(null);
-            } else {
-                vo.setDiscountPercent(random.nextInt(90) + 10);
-                vo.setDiscountPrice(null);
-                vo.setDiscountLimitPrice(random.nextInt(200) * 100);
-            }
-            vo.setTakeStatus(random.nextBoolean());
-            list.add(vo);
-        }
-        return success(list);
-    }
+    @Resource
+    private ProductSpuApi productSpuApi;
 
-    // TODO 芋艿:待实现;和 getCouponTemplateList 类似
     @GetMapping("/page")
     @Operation(summary = "获得优惠劵模版分页")
     public CommonResult<PageResult<AppCouponTemplateRespVO>> getCouponTemplatePage(AppCouponTemplatePageReqVO pageReqVO) {
-        List<AppCouponTemplateRespVO> list = new ArrayList<>();
-        Random random = new Random();
-        for (int i = 0; i < 10; i++) {
-            AppCouponTemplateRespVO vo = new AppCouponTemplateRespVO();
-            vo.setId(i + 1L);
-            vo.setName("优惠劵" + (i + 1));
-            vo.setTakeLimitCount(random.nextInt(10) + 1);
-            vo.setUsePrice(random.nextInt(100) * 100);
-            vo.setValidityType(random.nextInt(2) + 1);
-            if (vo.getValidityType() == 1) {
-                vo.setValidStartTime(LocalDateTime.now().plusDays(random.nextInt(10)));
-                vo.setValidEndTime(LocalDateTime.now().plusDays(random.nextInt(20) + 10));
-            } else {
-                vo.setFixedStartTerm(random.nextInt(10));
-                vo.setFixedEndTerm(random.nextInt(10) + vo.getFixedStartTerm() + 1);
-            }
-            vo.setDiscountType(random.nextInt(2) + 1);
-            if (vo.getDiscountType() == 1) {
-                vo.setDiscountPercent(null);
-                vo.setDiscountPrice(random.nextInt(50) * 100);
-                vo.setDiscountLimitPrice(null);
-            } else {
-                vo.setDiscountPercent(random.nextInt(90) + 10);
-                vo.setDiscountPrice(null);
-                vo.setDiscountLimitPrice(random.nextInt(200) * 100);
-            }
-            vo.setTakeStatus(random.nextBoolean());
-            list.add(vo);
+        // 1.1 处理查询条件:商品范围编号
+        Long productScopeValue = getaProductScopeValue(pageReqVO);
+        // 1.2 处理查询条件:领取方式=直接领取
+        List<Integer> canTakeTypes = Collections.singletonList(CouponTakeTypeEnum.USER.getValue());
+        // 2. 分页查询
+        PageResult<CouponTemplateDO> pageResult = couponTemplateService.getCouponTemplatePage(
+                CouponTemplateConvert.INSTANCE.convert(pageReqVO, canTakeTypes, pageReqVO.getProductScope(), productScopeValue));
+        // 3.1 领取数量
+        Map<Long, Integer> couponTakeCountMap = new HashMap<>(0);
+        Long userId = getLoginUserId();
+        if (userId != null) {
+            List<Long> templateIds = convertList(pageResult.getList(), CouponTemplateDO::getId,
+                    t -> ObjUtil.notEqual(t.getTakeLimitCount(), -1)); // 只查有设置“每人限领个数”的
+            couponTakeCountMap = couponService.getTakeCountMapByTemplateIds(templateIds, userId);
         }
-        return success(new PageResult<>(list, 20L));
+        // 3.2 拼接返回
+        return success(CouponTemplateConvert.INSTANCE.convertAppPage(pageResult, couponTakeCountMap));
+    }
+
+    private Long getaProductScopeValue(AppCouponTemplatePageReqVO pageReqVO) {
+        Long productScopeValue = pageReqVO.getSpuId();
+        if (pageReqVO.getProductScope() == null || Objects.equals(pageReqVO.getProductScope(), PromotionProductScopeEnum.ALL.getScope())) {
+            // 通用券:清除商品范围
+            productScopeValue = null;
+        } else if (Objects.equals(pageReqVO.getProductScope(), PromotionProductScopeEnum.CATEGORY.getScope()) && pageReqVO.getSpuId() != null) {
+            // 品类券:查询商品的品类
+            productScopeValue = Optional.ofNullable(productSpuApi.getSpu(pageReqVO.getSpuId()))
+                    .map(ProductSpuRespDTO::getCategoryId).orElse(null);
+        }
+        return productScopeValue;
     }
 
 }
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/vo/coupon/AppCouponMatchReqVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/vo/coupon/AppCouponMatchReqVO.java
index 633644ea3..9d36e3a4e 100755
--- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/vo/coupon/AppCouponMatchReqVO.java
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/vo/coupon/AppCouponMatchReqVO.java
@@ -15,15 +15,15 @@ public class AppCouponMatchReqVO {
     @NotNull(message = "商品金额不能为空")
     private Integer price;
 
-    @Schema(description = "商品 SPU 编号的数组", required = true, example = "[1, 2]")
+    @Schema(description = "商品 SPU 编号的数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 2]")
     @NotEmpty(message = "商品 SPU 编号不能为空")
     private List<Long> spuIds;
 
-    @Schema(description = "商品 SKU 编号的数组", required = true, example = "[1, 2]")
+    @Schema(description = "商品 SKU 编号的数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 2]")
     @NotEmpty(message = "商品 SKU 编号不能为空")
     private List<Long> skuIds;
 
-    @Schema(description = "分类编号的数组", required = true, example = "[10, 20]")
+    @Schema(description = "分类编号的数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[10, 20]")
     @NotEmpty(message = "分类编号不能为空")
     private List<Long> categoryIds;
 
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/vo/template/AppCouponTemplatePageReqVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/vo/template/AppCouponTemplatePageReqVO.java
index 6eda32167..48f2dc904 100644
--- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/vo/template/AppCouponTemplatePageReqVO.java
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/vo/template/AppCouponTemplatePageReqVO.java
@@ -1,6 +1,8 @@
 package cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.template;
 
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
@@ -12,8 +14,10 @@ import lombok.ToString;
 @ToString(callSuper = true)
 public class AppCouponTemplatePageReqVO extends PageParam {
 
-    @Schema(description = "使用类型", example = "1")
-    // TODO 芋艿:这里要限制下枚举的使用
-    private Integer useType;
+    @Schema(description = "商品范围", example = "1")
+    @InEnum(value = PromotionProductScopeEnum.class, message = "商品范围,必须是 {value}")
+    private Integer productScope;
 
+    @Schema(description = "商品标号", example = "1")
+    private Long spuId;
 }
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/coupon/CouponTemplateConvert.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/coupon/CouponTemplateConvert.java
index 22d78f46f..b6d3bed9b 100755
--- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/coupon/CouponTemplateConvert.java
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/coupon/CouponTemplateConvert.java
@@ -26,4 +26,25 @@ public interface CouponTemplateConvert {
 
     PageResult<CouponTemplateRespVO> convertPage(PageResult<CouponTemplateDO> page);
 
+    CouponTemplatePageReqVO convert(AppCouponTemplatePageReqVO pageReqVO, List<Integer> canTakeTypes, Integer productScope, Long productScopeValue);
+
+    PageResult<AppCouponTemplateRespVO> convertAppPage(PageResult<CouponTemplateDO> pageResult);
+
+    default PageResult<AppCouponTemplateRespVO> convertAppPage(PageResult<CouponTemplateDO> pageResult, Map<Long, Integer> couponTakeCountMap) {
+        PageResult<AppCouponTemplateRespVO> result = convertAppPage(pageResult);
+        if (MapUtil.isEmpty(couponTakeCountMap)) {
+            return result;
+        }
+
+        for (AppCouponTemplateRespVO vo : result.getList()) {
+            // 每人领取数量无限制
+            if (vo.getTakeLimitCount() == -1) {
+                vo.setTakeStatus(false);
+                continue;
+            }
+            // 检查已领取数量是否超过限领数量
+            vo.setTakeStatus(MapUtil.getInt(couponTakeCountMap, vo.getId(), 0) >= vo.getTakeLimitCount());
+        }
+        return result;
+    }
 }
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/reward/RewardActivityDO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/reward/RewardActivityDO.java
index 9d417d75f..a066d2f26 100644
--- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/reward/RewardActivityDO.java
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/reward/RewardActivityDO.java
@@ -110,7 +110,7 @@ public class RewardActivityDO extends BaseDO {
          */
         private List<Long> couponIds;
         /**
-         * 赠送的优惠卷数量的数组
+         * 赠送的优惠券数量的数组
          */
         private List<Integer> couponCounts;
 
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java
index ddf90691c..f79f23c21 100755
--- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java
@@ -1,11 +1,14 @@
 package cn.iocoder.yudao.module.promotion.dal.mysql.coupon;
 
+import cn.hutool.core.bean.BeanUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 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.service.coupon.bo.CouponTakeCountBO;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.github.yulichang.toolkit.MPJWrappers;
 import org.apache.ibatis.annotations.Mapper;
 
 import java.util.Collection;
@@ -62,4 +65,12 @@ public interface CouponMapper extends BaseMapperX<CouponDO> {
         );
     }
 
+    default List<CouponTakeCountBO> selectCountByUserIdAndTemplateIdIn(Long userId, Collection<Long> templateIds) {
+        return BeanUtil.copyToList(selectMaps(MPJWrappers.lambdaJoin(CouponDO.class)
+                .select(CouponDO::getTemplateId)
+                .selectCount(CouponDO::getId, CouponTakeCountBO::getCount)
+                .eq(CouponDO::getUserId, userId)
+                .in(CouponDO::getTemplateId, templateIds)
+                .groupBy(CouponDO::getTemplateId)), CouponTakeCountBO.class);
+    }
 }
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponTemplateMapper.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponTemplateMapper.java
index c95513e18..3c575c4d4 100755
--- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponTemplateMapper.java
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponTemplateMapper.java
@@ -39,6 +39,9 @@ public interface CouponTemplateMapper extends BaseMapperX<CouponTemplateDO> {
                 .eqIfPresent(CouponTemplateDO::getStatus, reqVO.getStatus())
                 .eqIfPresent(CouponTemplateDO::getDiscountType, reqVO.getDiscountType())
                 .betweenIfPresent(CouponTemplateDO::getCreateTime, reqVO.getCreateTime())
+                .eqIfPresent(CouponTemplateDO::getProductScope, reqVO.getProductScope())
+                .and(reqVO.getProductScopeValue() != null, w -> w.apply("FIND_IN_SET({0}, product_scope_values)",
+                        reqVO.getProductScopeValue()))
                 .and(canTakeConsumer != null, canTakeConsumer)
                 .orderByDesc(CouponTemplateDO::getId));
     }
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java
index cf22fe2b3..b3569a34b 100644
--- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java
@@ -5,10 +5,15 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
 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.coupon.CouponTakeTypeEnum;
+import cn.iocoder.yudao.module.promotion.service.coupon.bo.CouponTakeCountBO;
 
+import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+
 /**
  * 优惠劵 Service 接口
  *
@@ -18,11 +23,11 @@ public interface CouponService {
 
     /**
      * 校验优惠劵,包括状态、有限期
-     *
+     * <p>
      * 1. 如果校验通过,则返回优惠劵信息
      * 2. 如果校验不通过,则直接抛出业务异常
      *
-     * @param id 优惠劵编号
+     * @param id     优惠劵编号
      * @param userId 用户编号
      * @return 优惠劵信息
      */
@@ -31,9 +36,8 @@ public interface CouponService {
     /**
      * 校验优惠劵,包括状态、有限期
      *
-     * @see #validCoupon(Long, Long) 逻辑相同,只是入参不同
-     *
      * @param coupon 优惠劵
+     * @see #validCoupon(Long, Long) 逻辑相同,只是入参不同
      */
     void validCoupon(CouponDO coupon);
 
@@ -124,4 +128,23 @@ public interface CouponService {
         takeCoupon(templateId, CollUtil.newHashSet(userId), CouponTakeTypeEnum.REGISTER);
     }
 
+    /**
+     * 统计会员领取优惠券的数量
+     *
+     * @param templateIds 优惠券模板编号列表
+     * @param userId      用户编号
+     * @return 领取优惠券的数量
+     */
+    default Map<Long, Integer> getTakeCountMapByTemplateIds(Collection<Long> templateIds, Long userId) {
+        return convertMap(getTakeCountListByTemplateIds(templateIds, userId), CouponTakeCountBO::getTemplateId, CouponTakeCountBO::getCount);
+    }
+
+    /**
+     * 统计会员领取优惠券的数量
+     *
+     * @param templateIds 优惠券模板编号列表
+     * @param userId      用户编号
+     * @return 领取优惠券的数量
+     */
+    List<CouponTakeCountBO> getTakeCountListByTemplateIds(Collection<Long> templateIds, Long userId);
 }
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java
index f4b56260c..ab96fd14f 100644
--- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java
@@ -2,6 +2,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.collection.ListUtil;
 import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
@@ -18,12 +19,14 @@ import cn.iocoder.yudao.module.promotion.dal.mysql.coupon.CouponMapper;
 import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum;
 import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
 import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTemplateValidityTypeEnum;
+import cn.iocoder.yudao.module.promotion.service.coupon.bo.CouponTakeCountBO;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
 import java.time.LocalDateTime;
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -175,6 +178,14 @@ public class CouponServiceImpl implements CouponService {
         couponTemplateService.updateCouponTemplateTakeCount(templateId, userIds.size());
     }
 
+    @Override
+    public List<CouponTakeCountBO> getTakeCountListByTemplateIds(Collection<Long> templateIds, Long userId) {
+        if (CollUtil.isEmpty(templateIds)) {
+            return ListUtil.empty();
+        }
+        return couponMapper.selectCountByUserIdAndTemplateIdIn(userId, templateIds);
+    }
+
     /**
      * 校验优惠券是否可以领取
      *
@@ -211,7 +222,7 @@ public class CouponServiceImpl implements CouponService {
     /**
      * 过滤掉达到领取上线的用户
      *
-     * @param userIds 用户编号数组
+     * @param userIds        用户编号数组
      * @param couponTemplate 优惠劵模版
      */
     private void removeTakeLimitUser(Set<Long> userIds, CouponTemplateDO couponTemplate) {
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/bo/CouponTakeCountBO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/bo/CouponTakeCountBO.java
new file mode 100644
index 000000000..18928067d
--- /dev/null
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/bo/CouponTakeCountBO.java
@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.promotion.service.coupon.bo;
+
+import lombok.Data;
+
+/**
+ * 优惠券领取数量 BO
+ *
+ * @author owen
+ */
+@Data
+public class CouponTakeCountBO {
+    /**
+     * 优惠劵模板编号
+     */
+    private Long templateId;
+    /**
+     * 领取数量
+     */
+    private Integer count;
+}
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageRecordMapper.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageRecordMapper.java
index 95c160515..b4b606b5a 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageRecordMapper.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageRecordMapper.java
@@ -10,7 +10,6 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageRecordDO;
 import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
-import com.github.yulichang.toolkit.LambdaUtils;
 import com.github.yulichang.toolkit.MPJWrappers;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
@@ -62,7 +61,7 @@ public interface BrokerageRecordMapper extends BaseMapperX<BrokerageRecordDO> {
                                                                                              Integer status) {
         List<Map<String, Object>> list = selectMaps(MPJWrappers.lambdaJoin(BrokerageRecordDO.class)
                 .select(BrokerageRecordDO::getUserId)
-                .selectCount(BrokerageRecordDO::getId, LambdaUtils.getName(UserBrokerageSummaryBO::getCount))
+                .selectCount(BrokerageRecordDO::getId, UserBrokerageSummaryBO::getCount)
                 .selectSum(BrokerageRecordDO::getPrice)
                 .in(BrokerageRecordDO::getUserId, userIds)
                 .eq(BrokerageRecordDO::getBizId, bizType)
@@ -71,7 +70,7 @@ public interface BrokerageRecordMapper extends BaseMapperX<BrokerageRecordDO> {
         return BeanUtil.copyToList(list, UserBrokerageSummaryBO.class);
 //            return selectJoinList(UserBrokerageSummaryBO.class, MPJWrappers.lambdaJoin(BrokerageRecordDO.class)
 //                    .select(BrokerageRecordDO::getUserId)
-//                    .selectCount(BrokerageRecordDO::getId, LambdaUtils.getName(UserBrokerageSummaryBO::getCount))
+//                    .selectCount(BrokerageRecordDO::getId, UserBrokerageSummaryBO::getCount)
 //                    .selectSum(BrokerageRecordDO::getPrice)
 //                    .in(BrokerageRecordDO::getUserId, userIds)
 //                    .eq(BrokerageRecordDO::getBizId, bizType)
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageWithdrawMapper.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageWithdrawMapper.java
index 5a950a4cb..4bbf427e9 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageWithdrawMapper.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageWithdrawMapper.java
@@ -8,7 +8,6 @@ import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw.Brok
 import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageWithdrawDO;
 import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserWithdrawSummaryBO;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
-import com.github.yulichang.toolkit.LambdaUtils;
 import com.github.yulichang.wrapper.MPJLambdaWrapper;
 import org.apache.ibatis.annotations.Mapper;
 
@@ -45,7 +44,7 @@ public interface BrokerageWithdrawMapper extends BaseMapperX<BrokerageWithdrawDO
     default List<UserWithdrawSummaryBO> selectCountAndSumPriceByUserIdAndStatus(Collection<Long> userIds, Integer status) {
         List<Map<String, Object>> list = selectMaps(new MPJLambdaWrapper<BrokerageWithdrawDO>()
                 .select(BrokerageWithdrawDO::getUserId)
-                .selectCount(BrokerageWithdrawDO::getId, LambdaUtils.getName(UserWithdrawSummaryBO::getCount))
+                .selectCount(BrokerageWithdrawDO::getId, UserWithdrawSummaryBO::getCount)
                 .selectSum(BrokerageWithdrawDO::getPrice)
                 .in(BrokerageWithdrawDO::getUserId, userIds)
                 .eq(BrokerageWithdrawDO::getStatus, status)
@@ -54,7 +53,7 @@ public interface BrokerageWithdrawMapper extends BaseMapperX<BrokerageWithdrawDO
         // selectJoinList有BUG,会与租户插件冲突:解析SQL时,发生异常 https://gitee.com/best_handsome/mybatis-plus-join/issues/I84GYW
 //        return selectJoinList(UserWithdrawSummaryBO.class, new MPJLambdaWrapper<BrokerageWithdrawDO>()
 //                .select(BrokerageWithdrawDO::getUserId)
-//                    .selectCount(BrokerageWithdrawDO::getId, LambdaUtils.getName(UserWithdrawSummaryBO::getCount))
+//                    .selectCount(BrokerageWithdrawDO::getId, UserWithdrawSummaryBO::getCount)
 //                .selectSum(BrokerageWithdrawDO::getPrice)
 //                .in(BrokerageWithdrawDO::getUserId, userIds)
 //                .eq(BrokerageWithdrawDO::getStatus, status)

From f9aec7d8fd27a75250256ecb6df19cbd9b6acfbc Mon Sep 17 00:00:00 2001
From: owen <owen@evolsun.com>
Date: Tue, 26 Sep 2023 21:08:11 +0800
Subject: [PATCH 03/10] =?UTF-8?q?=E4=BC=98=E6=83=A0=E5=88=B8=EF=BC=9A?=
 =?UTF-8?q?=E5=AE=8C=E5=96=84=E6=88=91=E7=9A=84=E4=BC=98=E6=83=A0=E5=88=B8?=
 =?UTF-8?q?=E6=8E=A5=E5=8F=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../coupon/vo/coupon/CouponPageReqVO.java     |  7 +++++
 .../app/coupon/AppCouponController.java       | 30 ++++---------------
 .../coupon/vo/coupon/AppCouponPageReqVO.java  |  3 ++
 .../convert/coupon/CouponConvert.java         |  8 +++++
 .../convert/coupon/CouponTemplateConvert.java |  7 +++++
 .../dal/mysql/coupon/CouponMapper.java        |  4 +--
 .../service/coupon/CouponServiceImpl.java     |  6 ++--
 7 files changed, 36 insertions(+), 29 deletions(-)

diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/coupon/CouponPageReqVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/coupon/CouponPageReqVO.java
index 11d61a518..75aa2f74b 100755
--- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/coupon/CouponPageReqVO.java
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/coupon/CouponPageReqVO.java
@@ -1,6 +1,8 @@
 package cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon;
 
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
@@ -8,6 +10,7 @@ import lombok.ToString;
 import org.springframework.format.annotation.DateTimeFormat;
 
 import java.time.LocalDateTime;
+import java.util.Collection;
 
 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
 
@@ -21,6 +24,7 @@ public class CouponPageReqVO extends PageParam {
     private Long templateId;
 
     @Schema(description = "优惠码状态", example = "1")
+    @InEnum(value = CouponStatusEnum.class, message = "优惠劵状态,必须是 {value}")
     private Integer status;
 
     @Schema(description = "创建时间")
@@ -30,4 +34,7 @@ public class CouponPageReqVO extends PageParam {
     @Schema(description = "用户昵称", example = "芋艿")
     private String nickname;
 
+    @Schema(description = "用户编号", example = "1")
+    private Collection<Long> userIds;
+
 }
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/AppCouponController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/AppCouponController.java
index 8a9b82bbb..625df7942 100755
--- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/AppCouponController.java
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/AppCouponController.java
@@ -8,6 +8,8 @@ import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCoup
 import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponPageReqVO;
 import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponRespVO;
 import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.template.AppCouponTemplatePageReqVO;
+import cn.iocoder.yudao.module.promotion.convert.coupon.CouponConvert;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
 import cn.iocoder.yudao.module.promotion.service.coupon.CouponService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -17,6 +19,7 @@ import org.springframework.web.bind.annotation.*;
 import javax.annotation.Resource;
 import java.time.LocalDateTime;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Random;
 
@@ -71,33 +74,12 @@ public class AppCouponController {
         return success(list);
     }
 
-    // TODO 芋艿:待实现
     @GetMapping("/page")
     @Operation(summary = "优惠劵列表", description = "我的优惠劵")
     public CommonResult<PageResult<AppCouponRespVO>> takeCoupon(AppCouponPageReqVO pageReqVO) {
-        List<AppCouponRespVO> list = new ArrayList<>();
-        Random random = new Random();
-        for (int i = 0; i < 10; i++) {
-            AppCouponRespVO vo = new AppCouponRespVO();
-            vo.setId(i + 1L);
-            vo.setName("优惠劵" + (i + 1));
-            vo.setStatus(pageReqVO.getStatus());
-            vo.setUsePrice(random.nextInt(100) * 100);
-            vo.setValidStartTime(LocalDateTime.now().plusDays(random.nextInt(10)));
-            vo.setValidEndTime(LocalDateTime.now().plusDays(random.nextInt(20) + 10));
-            vo.setDiscountType(random.nextInt(2) + 1);
-            if (vo.getDiscountType() == 1) {
-                vo.setDiscountPercent(null);
-                vo.setDiscountPrice(random.nextInt(50) * 100);
-                vo.setDiscountLimitPrice(null);
-            } else {
-                vo.setDiscountPercent(random.nextInt(90) + 10);
-                vo.setDiscountPrice(null);
-                vo.setDiscountLimitPrice(random.nextInt(200) * 100);
-            }
-            list.add(vo);
-        }
-        return success(new PageResult<>(list, 20L));
+        PageResult<CouponDO> pageResult = couponService.getCouponPage(
+                CouponConvert.INSTANCE.convert(pageReqVO, Collections.singleton(getLoginUserId())));
+        return success(CouponConvert.INSTANCE.convertAppPage(pageResult));
     }
 
     @GetMapping(value = "/get-unused-count")
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/vo/coupon/AppCouponPageReqVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/vo/coupon/AppCouponPageReqVO.java
index 5bd057f37..0c423959b 100644
--- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/vo/coupon/AppCouponPageReqVO.java
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/vo/coupon/AppCouponPageReqVO.java
@@ -1,6 +1,8 @@
 package cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon;
 
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
@@ -13,6 +15,7 @@ import lombok.ToString;
 public class AppCouponPageReqVO extends PageParam {
 
     @Schema(description = "优惠劵状态", example = "1")
+    @InEnum(value = CouponStatusEnum.class, message = "优惠劵状态,必须是 {value}")
     private Integer status;
 
 }
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/coupon/CouponConvert.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/coupon/CouponConvert.java
index 364095a4d..d2359e7c7 100755
--- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/coupon/CouponConvert.java
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/coupon/CouponConvert.java
@@ -3,6 +3,9 @@ package cn.iocoder.yudao.module.promotion.convert.coupon;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponRespDTO;
 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageItemRespVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
+import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponPageReqVO;
+import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponRespVO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
 import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum;
@@ -11,6 +14,7 @@ import org.mapstruct.Mapper;
 import org.mapstruct.factory.Mappers;
 
 import java.time.LocalDateTime;
+import java.util.Collection;
 
 /**
  * 优惠劵 Convert
@@ -49,4 +53,8 @@ public interface CouponConvert {
         }
         return couponDO;
     }
+
+    CouponPageReqVO convert(AppCouponPageReqVO pageReqVO, Collection<Long> userIds);
+
+    PageResult<AppCouponRespVO> convertAppPage(PageResult<CouponDO> pageResult);
 }
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/coupon/CouponTemplateConvert.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/coupon/CouponTemplateConvert.java
index b6d3bed9b..1c5c9b31f 100755
--- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/coupon/CouponTemplateConvert.java
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/coupon/CouponTemplateConvert.java
@@ -1,13 +1,20 @@
 package cn.iocoder.yudao.module.promotion.convert.coupon;
 
+import cn.hutool.core.map.MapUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplateCreateReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplatePageReqVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplateRespVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplateUpdateReqVO;
+import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.template.AppCouponTemplatePageReqVO;
+import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.template.AppCouponTemplateRespVO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
 import org.mapstruct.Mapper;
 import org.mapstruct.factory.Mappers;
 
+import java.util.List;
+import java.util.Map;
+
 /**
  * 优惠劵模板 Convert
  *
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java
index f79f23c21..744989849 100755
--- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java
@@ -22,11 +22,11 @@ import java.util.List;
 @Mapper
 public interface CouponMapper extends BaseMapperX<CouponDO> {
 
-    default PageResult<CouponDO> selectPage(CouponPageReqVO reqVO, Collection<Long> userIds) {
+    default PageResult<CouponDO> selectPage(CouponPageReqVO reqVO) {
         return selectPage(reqVO, new LambdaQueryWrapperX<CouponDO>()
                 .eqIfPresent(CouponDO::getTemplateId, reqVO.getTemplateId())
                 .eqIfPresent(CouponDO::getStatus, reqVO.getStatus())
-                .inIfPresent(CouponDO::getUserId, userIds)
+                .inIfPresent(CouponDO::getUserId, reqVO.getUserIds())
                 .betweenIfPresent(CouponDO::getCreateTime, reqVO.getCreateTime())
                 .orderByDesc(CouponDO::getId));
     }
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java
index ab96fd14f..fcf515ec5 100644
--- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java
@@ -80,16 +80,16 @@ public class CouponServiceImpl implements CouponService {
     @Override
     public PageResult<CouponDO> getCouponPage(CouponPageReqVO pageReqVO) {
         // 获得用户编号
-        Set<Long> userIds = null;
         if (StrUtil.isNotEmpty(pageReqVO.getNickname())) {
-            userIds = CollectionUtils.convertSet(memberUserApi.getUserListByNickname(pageReqVO.getNickname()),
+            Set<Long> userIds = CollectionUtils.convertSet(memberUserApi.getUserListByNickname(pageReqVO.getNickname()),
                     MemberUserRespDTO::getId);
             if (CollUtil.isEmpty(userIds)) {
                 return PageResult.empty();
             }
+            pageReqVO.setUserIds(userIds);
         }
         // 分页查询
-        return couponMapper.selectPage(pageReqVO, userIds);
+        return couponMapper.selectPage(pageReqVO);
     }
 
     @Override

From b1ac3a0b8b6b832cd4dadb2c0be88c5c869dd4ff Mon Sep 17 00:00:00 2001
From: owen <owen@evolsun.com>
Date: Tue, 26 Sep 2023 22:46:06 +0800
Subject: [PATCH 04/10] =?UTF-8?q?=E4=BC=98=E6=83=A0=E5=88=B8=EF=BC=9A?=
 =?UTF-8?q?=E5=AE=8C=E5=96=84=E4=BC=98=E6=83=A0=E5=88=B8=E5=8C=B9=E9=85=8D?=
 =?UTF-8?q?=E6=8E=A5=E5=8F=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../app/coupon/AppCouponController.java       | 32 ++-----------------
 .../convert/coupon/CouponConvert.java         |  4 +++
 .../dal/mysql/coupon/CouponMapper.java        | 21 ++++++++++++
 .../service/coupon/CouponService.java         | 10 ++++++
 .../service/coupon/CouponServiceImpl.java     |  7 ++++
 .../vo/AppTradeOrderSettlementRespVO.java     |  2 ++
 6 files changed, 46 insertions(+), 30 deletions(-)

diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/AppCouponController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/AppCouponController.java
index 625df7942..d366459fc 100755
--- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/AppCouponController.java
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/AppCouponController.java
@@ -17,11 +17,8 @@ import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
-import java.time.LocalDateTime;
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.Random;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@@ -42,36 +39,11 @@ public class AppCouponController {
         return success(1L);
     }
 
-    // TODO 芋艿:待实现
     @GetMapping("/match-list")
     @Operation(summary = "获得匹配指定商品的优惠劵列表")
     public CommonResult<List<AppCouponMatchRespVO>> getMatchCouponList(AppCouponMatchReqVO matchReqVO) {
-        List<AppCouponMatchRespVO> list = new ArrayList<>();
-        Random random = new Random();
-        for (int i = 0; i < 10; i++) {
-            AppCouponMatchRespVO vo = new AppCouponMatchRespVO();
-            vo.setId(i + 1L);
-            vo.setName("优惠劵" + (i + 1));
-            vo.setUsePrice(random.nextInt(100) * 100);
-            vo.setValidStartTime(LocalDateTime.now().plusDays(random.nextInt(10)));
-            vo.setValidEndTime(LocalDateTime.now().plusDays(random.nextInt(20) + 10));
-            vo.setDiscountType(random.nextInt(2) + 1);
-            if (vo.getDiscountType() == 1) {
-                vo.setDiscountPercent(null);
-                vo.setDiscountPrice(random.nextInt(50) * 100);
-                vo.setDiscountLimitPrice(null);
-            } else {
-                vo.setDiscountPercent(random.nextInt(90) + 10);
-                vo.setDiscountPrice(null);
-                vo.setDiscountLimitPrice(random.nextInt(200) * 100);
-            }
-            vo.setMatch(random.nextBoolean());
-            if (!vo.getMatch()) {
-                vo.setDescription("不符合条件噢");
-            }
-            list.add(vo);
-        }
-        return success(list);
+        // todo: 优惠金额倒序
+        return success(CouponConvert.INSTANCE.convertList(couponService.getMatchCouponList(getLoginUserId(), matchReqVO)));
     }
 
     @GetMapping("/page")
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/coupon/CouponConvert.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/coupon/CouponConvert.java
index d2359e7c7..3fc42dbe2 100755
--- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/coupon/CouponConvert.java
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/coupon/CouponConvert.java
@@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponRespDTO;
 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageItemRespVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
+import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponMatchRespVO;
 import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponPageReqVO;
 import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponRespVO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
@@ -15,6 +16,7 @@ import org.mapstruct.factory.Mappers;
 
 import java.time.LocalDateTime;
 import java.util.Collection;
+import java.util.List;
 
 /**
  * 优惠劵 Convert
@@ -57,4 +59,6 @@ public interface CouponConvert {
     CouponPageReqVO convert(AppCouponPageReqVO pageReqVO, Collection<Long> userIds);
 
     PageResult<AppCouponRespVO> convertAppPage(PageResult<CouponDO> pageResult);
+
+    List<AppCouponMatchRespVO> convertList(List<CouponDO> list);
 }
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java
index 744989849..20852852f 100755
--- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java
@@ -1,11 +1,13 @@
 package cn.iocoder.yudao.module.promotion.dal.mysql.coupon;
 
 import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 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 cn.iocoder.yudao.module.promotion.service.coupon.bo.CouponTakeCountBO;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.github.yulichang.toolkit.MPJWrappers;
@@ -13,6 +15,8 @@ import org.apache.ibatis.annotations.Mapper;
 
 import java.util.Collection;
 import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 
 /**
  * 优惠劵 Mapper
@@ -73,4 +77,21 @@ public interface CouponMapper extends BaseMapperX<CouponDO> {
                 .in(CouponDO::getTemplateId, templateIds)
                 .groupBy(CouponDO::getTemplateId)), CouponTakeCountBO.class);
     }
+
+    default List<CouponDO> selectListByUserIdAndStatusAndUsePriceLeAndProductScope(
+            Long userId, Integer status, Integer usePrice, List<Long> spuIds, List<Long> categoryIds) {
+
+        Function<List<Long>, String> productScopeValuesFindInSetFunc = ids -> ids.stream()
+                .map(id -> StrUtil.format("FIND_IN_SET({}, product_scope_values) ", id))
+                .collect(Collectors.joining(" OR "));
+        return selectList(new LambdaQueryWrapperX<CouponDO>()
+                .eq(CouponDO::getUserId, userId)
+                .eq(CouponDO::getStatus, status)
+                .le(CouponDO::getUsePrice, usePrice)
+                .and(w -> w.eq(CouponDO::getProductScope, PromotionProductScopeEnum.ALL.getScope())
+                        .or(ww -> ww.eq(CouponDO::getProductScope, PromotionProductScopeEnum.SPU.getScope())
+                                .apply(productScopeValuesFindInSetFunc.apply(spuIds)))
+                        .or(ww -> ww.eq(CouponDO::getProductScope, PromotionProductScopeEnum.CATEGORY.getScope())
+                                .apply(productScopeValuesFindInSetFunc.apply(categoryIds)))));
+    }
 }
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java
index b3569a34b..1ee2096ef 100644
--- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.promotion.service.coupon;
 import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
+import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponMatchReqVO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
 import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
 import cn.iocoder.yudao.module.promotion.service.coupon.bo.CouponTakeCountBO;
@@ -147,4 +148,13 @@ public interface CouponService {
      * @return 领取优惠券的数量
      */
     List<CouponTakeCountBO> getTakeCountListByTemplateIds(Collection<Long> templateIds, Long userId);
+
+    /**
+     * 获取用户匹配的优惠券列表
+     *
+     * @param userId     用户编号
+     * @param matchReqVO 匹配参数
+     * @return 优惠券列表
+     */
+    List<CouponDO> getMatchCouponList(Long userId, AppCouponMatchReqVO matchReqVO);
 }
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java
index fcf515ec5..acb1028c4 100644
--- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java
@@ -12,6 +12,7 @@ import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
 import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
+import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponMatchReqVO;
 import cn.iocoder.yudao.module.promotion.convert.coupon.CouponConvert;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
@@ -186,6 +187,12 @@ public class CouponServiceImpl implements CouponService {
         return couponMapper.selectCountByUserIdAndTemplateIdIn(userId, templateIds);
     }
 
+    @Override
+    public List<CouponDO> getMatchCouponList(Long userId, AppCouponMatchReqVO matchReqVO) {
+        return couponMapper.selectListByUserIdAndStatusAndUsePriceLeAndProductScope(userId, CouponStatusEnum.UNUSED.getStatus(),
+                matchReqVO.getPrice(), matchReqVO.getSpuIds(), matchReqVO.getCategoryIds());
+    }
+
     /**
      * 校验优惠券是否可以领取
      *
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderSettlementRespVO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderSettlementRespVO.java
index 4b5d98a37..494b9bfa5 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderSettlementRespVO.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderSettlementRespVO.java
@@ -37,6 +37,8 @@ public class AppTradeOrderSettlementRespVO {
 
         // ========== SPU 信息 ==========
 
+        @Schema(description = "品类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
+        private Long categoryId;
         @Schema(description = "SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
         private Long spuId;
         @Schema(description = "SPU 名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "Apple iPhone 12")

From 77f776356011b878d8d191407cb5ef22e350e8a7 Mon Sep 17 00:00:00 2001
From: owen <owen@evolsun.com>
Date: Tue, 26 Sep 2023 23:21:01 +0800
Subject: [PATCH 05/10] =?UTF-8?q?=E4=BC=98=E6=83=A0=E5=88=B8=EF=BC=9A?=
 =?UTF-8?q?=E5=AE=8C=E5=96=84=E4=BC=98=E6=83=A0=E5=88=B8=E9=A2=86=E5=8F=96?=
 =?UTF-8?q?=E6=8E=A5=E5=8F=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../app/coupon/AppCouponController.java       | 32 ++++++++++++++-----
 1 file changed, 24 insertions(+), 8 deletions(-)

diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/AppCouponController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/AppCouponController.java
index d366459fc..f6b92eaae 100755
--- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/AppCouponController.java
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/AppCouponController.java
@@ -1,22 +1,25 @@
 package cn.iocoder.yudao.module.promotion.controller.app.coupon;
 
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.map.MapUtil;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
-import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponMatchReqVO;
-import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponMatchRespVO;
-import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponPageReqVO;
-import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponRespVO;
-import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.template.AppCouponTemplatePageReqVO;
+import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.*;
 import cn.iocoder.yudao.module.promotion.convert.coupon.CouponConvert;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
+import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
 import cn.iocoder.yudao.module.promotion.service.coupon.CouponService;
+import cn.iocoder.yudao.module.promotion.service.coupon.CouponTemplateService;
 import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
+import javax.validation.Valid;
 import java.util.Collections;
 import java.util.List;
 
@@ -31,12 +34,25 @@ public class AppCouponController {
 
     @Resource
     private CouponService couponService;
+    @Resource
+    private CouponTemplateService couponTemplateService;
 
-    // TODO 芋艿:待实现
     @PostMapping("/take")
     @Operation(summary = "领取优惠劵")
-    public CommonResult<Long> takeCoupon(@RequestBody AppCouponTemplatePageReqVO pageReqVO) {
-        return success(1L);
+    @Parameter(name = "templateId", description = "优惠券模板编号", required = true, example = "1024")
+    public CommonResult<Boolean> takeCoupon(@Valid @RequestBody AppCouponTakeReqVO reqVO) {
+        Long userId = getLoginUserId();
+        // 领取
+        couponService.takeCoupon(reqVO.getTemplateId(), CollUtil.newHashSet(userId), CouponTakeTypeEnum.USER);
+        // 检查是否可以继续领取
+        CouponTemplateDO couponTemplate = couponTemplateService.getCouponTemplate(reqVO.getTemplateId());
+        boolean canTakeAgain = true;
+        if (couponTemplate.getTakeLimitCount() != null && couponTemplate.getTakeLimitCount() > 0) {
+            Integer takeCount = MapUtil.getInt(couponService.getTakeCountMapByTemplateIds(
+                    Collections.singleton(reqVO.getTemplateId()), userId), reqVO.getTemplateId(), 0);
+            canTakeAgain = takeCount < couponTemplate.getTakeLimitCount();
+        }
+        return success(canTakeAgain);
     }
 
     @GetMapping("/match-list")

From 11ffa45b898e5ad9dde75065d61a91ba9185abe1 Mon Sep 17 00:00:00 2001
From: owen <owen@evolsun.com>
Date: Tue, 26 Sep 2023 23:54:43 +0800
Subject: [PATCH 06/10] =?UTF-8?q?=E7=A7=AF=E5=88=86=EF=BC=9AReview?=
 =?UTF-8?q?=E4=BB=A3=E7=A0=81=E4=BF=AE=E6=94=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../calculator/TradePointGiveCalculator.java  |  1 -
 .../TradePointUsePriceCalculator.java         | 41 ++++++++-----------
 2 files changed, 18 insertions(+), 24 deletions(-)

diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointGiveCalculator.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointGiveCalculator.java
index ea9faaec9..d93d04582 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointGiveCalculator.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointGiveCalculator.java
@@ -56,7 +56,6 @@ public class TradePointGiveCalculator implements TradePriceCalculator {
             TradePriceCalculateRespBO.OrderItem orderItem = orderItems.get(i);
             // 商品可能赠送了积分,所以这里要加上
             orderItem.setGivePoint(orderItem.getGivePoint() + dividePoints.get(i));
-            TradePriceCalculatorHelper.recountPayPrice(orderItem); // TODO @疯狂:这个应该不用调用哇?不影响支付金额
         }
         // 3.3 更新订单赠送积分
         TradePriceCalculatorHelper.recountAllGivePoint(result);
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointUsePriceCalculator.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointUsePriceCalculator.java
index 8436eaa2d..bcb43a11c 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointUsePriceCalculator.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointUsePriceCalculator.java
@@ -1,7 +1,6 @@
 package cn.iocoder.yudao.module.trade.service.price.calculator;
 
 import cn.hutool.core.util.BooleanUtil;
-import cn.hutool.core.util.NumberUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.module.member.api.point.MemberPointApi;
 import cn.iocoder.yudao.module.member.api.point.dto.MemberPointConfigRespDTO;
@@ -15,10 +14,11 @@ import org.springframework.core.annotation.Order;
 import org.springframework.stereotype.Component;
 
 import javax.annotation.Resource;
-import java.math.RoundingMode;
 import java.util.List;
 
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
+import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_PAY_PRICE_ILLEGAL;
 
 // TODO @疯狂:搞个单测,嘿嘿;
 /**
@@ -45,7 +45,7 @@ public class TradePointUsePriceCalculator implements TradePriceCalculator {
         }
         // 1.2 校验积分抵扣是否开启
         MemberPointConfigRespDTO config = memberPointApi.getConfig();
-        if (!checkDeductPointEnable(config)) {
+        if (!isDeductPointEnable(config)) {
             return;
         }
         // 1.3 校验用户积分余额
@@ -55,7 +55,6 @@ public class TradePointUsePriceCalculator implements TradePriceCalculator {
         }
 
         // 2.1 计算积分优惠金额
-        // TODO @疯狂:如果计算出来,优惠金额为 0,那是不是不用执行后续逻辑哈
         int pointPrice = calculatePointPrice(config, user.getPoint(), result);
         // 2.2 计算分摊的积分、抵扣金额
         List<TradePriceCalculateRespBO.OrderItem> orderItems = filterList(result.getItems(), TradePriceCalculateRespBO.OrderItem::getSelected);
@@ -77,18 +76,10 @@ public class TradePointUsePriceCalculator implements TradePriceCalculator {
         TradePriceCalculatorHelper.recountAllPrice(result);
     }
 
-    // TODO @疯狂:这个最好是 is 开头;因为 check 或者 validator,更多失败,会抛出异常;
-    private boolean checkDeductPointEnable(MemberPointConfigRespDTO config) {
-        // TODO @疯狂:这个要不直接写成 return config != null && config .... 多行这样一个形式;
-        if (config == null) {
-            return false;
-        }
-        if (!BooleanUtil.isTrue(config.getTradeDeductEnable())) {
-            return false;
-        }
-
-        // 有没有配置:1 积分抵扣多少分
-        return config.getTradeDeductUnitPrice() != null && config.getTradeDeductUnitPrice() > 0;
+    private boolean isDeductPointEnable(MemberPointConfigRespDTO config) {
+        return config != null &&
+                !BooleanUtil.isTrue(config.getTradeDeductEnable()) &&  // 积分功能是否启用
+                config.getTradeDeductUnitPrice() != null && config.getTradeDeductUnitPrice() > 0; // 有没有配置:1 积分抵扣多少分
     }
 
     private Integer calculatePointPrice(MemberPointConfigRespDTO config, Integer usePoint, TradePriceCalculateRespBO result) {
@@ -98,14 +89,18 @@ public class TradePointUsePriceCalculator implements TradePriceCalculator {
         }
         // 积分优惠金额(分)
         int pointPrice = usePoint * config.getTradeDeductUnitPrice();
-        // 0 元购!!!:用户积分比较多时,积分可以抵扣的金额要大于支付金额,这时需要根据支付金额反推使用多少积分
-        if (result.getPrice().getPayPrice() < pointPrice) {
-            pointPrice = result.getPrice().getPayPrice();
-            // 反推需要扣除的积分
-            usePoint = NumberUtil.toBigDecimal(pointPrice)
-                    .divide(NumberUtil.toBigDecimal(config.getTradeDeductUnitPrice()), 0, RoundingMode.HALF_UP)
-                    .intValue();
+        if (result.getPrice().getPayPrice() <= pointPrice) {
+            // 禁止0元购
+            throw exception(PRICE_CALCULATE_PAY_PRICE_ILLEGAL);
         }
+//        // 允许0 元购!!!:用户积分比较多时,积分可以抵扣的金额要大于支付金额,这时需要根据支付金额反推使用多少积分
+//        if (result.getPrice().getPayPrice() < pointPrice) {
+//            pointPrice = result.getPrice().getPayPrice();
+//            // 反推需要扣除的积分
+//            usePoint = NumberUtil.toBigDecimal(pointPrice)
+//                    .divide(NumberUtil.toBigDecimal(config.getTradeDeductUnitPrice()), 0, RoundingMode.HALF_UP)
+//                    .intValue();
+//        }
         // 记录使用的积分
         result.setUsePoint(usePoint);
         return pointPrice;

From 2766dd672a098350abd0bee7d291691555222678 Mon Sep 17 00:00:00 2001
From: owen <owen@evolsun.com>
Date: Wed, 27 Sep 2023 00:15:12 +0800
Subject: [PATCH 07/10] =?UTF-8?q?=E4=BC=98=E6=83=A0=E5=88=B8=EF=BC=9A?=
 =?UTF-8?q?=E8=BF=87=E6=9C=9F=E5=AE=9A=E6=97=B6=E4=BB=BB=E5=8A=A1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../yudao-module-promotion-biz/pom.xml        |  4 ++
 .../dal/mysql/coupon/CouponMapper.java        |  8 ++++
 .../module/promotion/job/CouponExpireJob.java | 29 +++++++++++
 .../service/coupon/CouponService.java         |  7 +++
 .../service/coupon/CouponServiceImpl.java     | 48 +++++++++++++++++++
 5 files changed, 96 insertions(+)
 create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/job/CouponExpireJob.java

diff --git a/yudao-module-mall/yudao-module-promotion-biz/pom.xml b/yudao-module-mall/yudao-module-promotion-biz/pom.xml
index 44d0e8967..c9a544c06 100644
--- a/yudao-module-mall/yudao-module-promotion-biz/pom.xml
+++ b/yudao-module-mall/yudao-module-promotion-biz/pom.xml
@@ -45,6 +45,10 @@
             <groupId>cn.iocoder.boot</groupId>
             <artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId>
         </dependency>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
+        </dependency>
         <dependency>
             <groupId>cn.iocoder.boot</groupId>
             <artifactId>yudao-spring-boot-starter-biz-weixin</artifactId>
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java
index 20852852f..dc909b4c6 100755
--- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java
@@ -13,6 +13,7 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.github.yulichang.toolkit.MPJWrappers;
 import org.apache.ibatis.annotations.Mapper;
 
+import java.time.LocalDateTime;
 import java.util.Collection;
 import java.util.List;
 import java.util.function.Function;
@@ -94,4 +95,11 @@ public interface CouponMapper extends BaseMapperX<CouponDO> {
                         .or(ww -> ww.eq(CouponDO::getProductScope, PromotionProductScopeEnum.CATEGORY.getScope())
                                 .apply(productScopeValuesFindInSetFunc.apply(categoryIds)))));
     }
+
+    default List<CouponDO> selectListByStatusAndValidEndTimeLe(Integer status, LocalDateTime validEndTime) {
+        return selectList(new LambdaQueryWrapperX<CouponDO>()
+                .eq(CouponDO::getStatus, status)
+                .le(CouponDO::getValidEndTime, validEndTime)
+        );
+    }
 }
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/job/CouponExpireJob.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/job/CouponExpireJob.java
new file mode 100644
index 000000000..0526d1a72
--- /dev/null
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/job/CouponExpireJob.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.promotion.job;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
+import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
+import cn.iocoder.yudao.module.promotion.service.coupon.CouponService;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ * 优惠券过期 Job
+ *
+ * @author owen
+ */
+@Component
+@TenantJob
+public class CouponExpireJob implements JobHandler {
+
+    @Resource
+    private CouponService couponService;
+
+    @Override
+    public String execute(String param) {
+        int count = couponService.expireCoupon();
+        return StrUtil.format("过期优惠券 {} 个", count);
+    }
+
+}
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java
index 1ee2096ef..a85ac31a9 100644
--- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java
@@ -157,4 +157,11 @@ public interface CouponService {
      * @return 优惠券列表
      */
     List<CouponDO> getMatchCouponList(Long userId, AppCouponMatchReqVO matchReqVO);
+
+    /**
+     * 过期优惠券
+     *
+     * @return 过期数量
+     */
+    int expireCoupon();
 }
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java
index acb1028c4..be2370c1a 100644
--- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java
@@ -6,6 +6,7 @@ import cn.hutool.core.collection.ListUtil;
 import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.spring.SpringUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
@@ -21,6 +22,7 @@ import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum;
 import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
 import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTemplateValidityTypeEnum;
 import cn.iocoder.yudao.module.promotion.service.coupon.bo.CouponTakeCountBO;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
@@ -43,6 +45,7 @@ import static java.util.Arrays.asList;
  *
  * @author 芋道源码
  */
+@Slf4j
 @Service
 @Validated
 public class CouponServiceImpl implements CouponService {
@@ -193,6 +196,43 @@ public class CouponServiceImpl implements CouponService {
                 matchReqVO.getPrice(), matchReqVO.getSpuIds(), matchReqVO.getCategoryIds());
     }
 
+    @Override
+    public int expireCoupon() {
+        // 1. 查询待过期的优惠券
+        List<CouponDO> list = couponMapper.selectListByStatusAndValidEndTimeLe(
+                CouponStatusEnum.UNUSED.getStatus(), LocalDateTime.now());
+        if (CollUtil.isEmpty(list)) {
+            return 0;
+        }
+
+        // 2. 遍历执行
+        int count = 0;
+        for (CouponDO coupon : list) {
+            try {
+                boolean success = getSelf().expireCoupon(coupon);
+                if (success) {
+                    count++;
+                }
+            } catch (Exception e) {
+                log.error("[expireCoupon][coupon({}) 更新为已过期失败]", coupon.getId(), e);
+            }
+        }
+        return count;
+    }
+
+    private boolean expireCoupon(CouponDO coupon) {
+        // 更新记录状态
+        CouponDO updateObj = new CouponDO().setStatus(CouponStatusEnum.EXPIRE.getStatus());
+        int updateRows = couponMapper.updateByIdAndStatus(coupon.getId(), CouponStatusEnum.UNUSED.getStatus(), updateObj);
+        if (updateRows == 0) {
+            log.error("[expireCoupon][coupon({}) 更新为已过期失败]", coupon.getId());
+            return false;
+        }
+
+        log.info("[expireCoupon][coupon({}) 更新为已过期成功]", coupon.getId());
+        return true;
+    }
+
     /**
      * 校验优惠券是否可以领取
      *
@@ -246,4 +286,12 @@ public class CouponServiceImpl implements CouponService {
         userIds.removeIf(userId -> MapUtil.getInt(userTakeCountMap, userId, 0) >= couponTemplate.getTakeLimitCount());
     }
 
+    /**
+     * 获得自身的代理对象,解决 AOP 生效问题
+     *
+     * @return 自己
+     */
+    private CouponServiceImpl getSelf() {
+        return SpringUtil.getBean(getClass());
+    }
 }

From 74ef98f8fc88b85d664cee793740e3b4ea75b8bb Mon Sep 17 00:00:00 2001
From: owen <owen@evolsun.com>
Date: Wed, 27 Sep 2023 00:24:22 +0800
Subject: [PATCH 08/10] =?UTF-8?q?=E5=94=AE=E5=90=8E=EF=BC=9A=E5=94=AE?=
 =?UTF-8?q?=E5=90=8E=E6=88=90=E5=8A=9F=E5=90=8E=EF=BC=8C=E5=9B=9E=E6=BB=9A?=
 =?UTF-8?q?=20SKU=20=E5=BA=93=E5=AD=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../trade/service/order/TradeOrderUpdateServiceImpl.java  | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
index 3b8b334ad..98abc349a 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
@@ -70,10 +70,7 @@ import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
 import java.time.LocalDateTime;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
+import java.util.*;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
@@ -823,7 +820,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
         // TODO 芋艿:这块扣减规则,需要在考虑下
         // 售后成功后,执行数据回滚逻辑
         if (Objects.equals(newAfterSaleStatus, TradeOrderItemAfterSaleStatusEnum.SUCCESS.getStatus())) {
-            // TODO @疯狂:这里库存也要扣减下;
+            // 增加 SKU 库存
+            productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convert(Collections.singletonList(orderItem)));
             // 扣减用户积分(赠送的)
             reduceUserPoint(order.getUserId(), orderItem.getGivePoint(), MemberPointBizTypeEnum.AFTER_SALE_DEDUCT_GIVE, afterSaleId);
             // 增加用户积分(返还抵扣)

From bc07e1dbf43941b51655ad8287d129a66c03d19c Mon Sep 17 00:00:00 2001
From: owen <owen@evolsun.com>
Date: Wed, 27 Sep 2023 01:19:49 +0800
Subject: [PATCH 09/10] =?UTF-8?q?=E5=94=AE=E5=90=8E=EF=BC=9A=E9=87=8D?=
 =?UTF-8?q?=E6=9E=84=E5=94=AE=E5=90=8E=E6=9B=B4=E6=96=B0=E8=AE=A2=E5=8D=95?=
 =?UTF-8?q?=E7=9A=84=E9=80=BB=E8=BE=91?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../aftersale/TradeAfterSaleServiceImpl.java  |  19 +--
 .../order/TradeOrderUpdateService.java        |  37 +++---
 .../order/TradeOrderUpdateServiceImpl.java    | 120 ++++++++++--------
 3 files changed, 92 insertions(+), 84 deletions(-)

diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/TradeAfterSaleServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/TradeAfterSaleServiceImpl.java
index 80acf9b99..a41b73ec3 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/TradeAfterSaleServiceImpl.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/TradeAfterSaleServiceImpl.java
@@ -85,7 +85,7 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa
 
     @Override
     public TradeAfterSaleDO getAfterSale(Long userId, Long id) {
-         return tradeAfterSaleMapper.selectByIdAndUserId(id, userId);
+        return tradeAfterSaleMapper.selectByIdAndUserId(id, userId);
     }
 
     @Override
@@ -167,9 +167,7 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa
         tradeAfterSaleMapper.insert(afterSale);
 
         // 更新交易订单项的售后状态
-        tradeOrderUpdateService.updateOrderItemAfterSaleStatus(orderItem.getId(),
-                TradeOrderItemAfterSaleStatusEnum.NONE.getStatus(), TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(),
-                afterSale.getId(), null);
+        tradeOrderUpdateService.updateOrderItemWhenAfterSaleCreate(orderItem.getId(), afterSale.getId());
 
         // 记录售后日志
         createAfterSaleLog(orderItem.getUserId(), UserTypeEnum.MEMBER.getValue(),
@@ -219,8 +217,7 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa
         // TODO 发送售后消息
 
         // 更新交易订单项的售后状态为【未申请】
-        tradeOrderUpdateService.updateOrderItemAfterSaleStatus(afterSale.getOrderItemId(),
-                TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(), TradeOrderItemAfterSaleStatusEnum.NONE.getStatus());
+        tradeOrderUpdateService.updateOrderItemWhenAfterSaleCancel(afterSale.getOrderItemId());
     }
 
     /**
@@ -313,8 +310,7 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa
         // TODO 发送售后消息
 
         // 更新交易订单项的售后状态为【未申请】
-        tradeOrderUpdateService.updateOrderItemAfterSaleStatus(afterSale.getOrderItemId(),
-                TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(), TradeOrderItemAfterSaleStatusEnum.NONE.getStatus());
+        tradeOrderUpdateService.updateOrderItemWhenAfterSaleCancel(afterSale.getOrderItemId());
     }
 
     /**
@@ -360,9 +356,7 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa
         // TODO 发送售后消息
 
         // 更新交易订单项的售后状态为【已完成】
-        tradeOrderUpdateService.updateOrderItemAfterSaleStatus(afterSale.getOrderItemId(),
-                TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(), TradeOrderItemAfterSaleStatusEnum.SUCCESS.getStatus(),
-                null, afterSale.getRefundPrice());
+        tradeOrderUpdateService.updateOrderItemWhenAfterSaleSuccess(afterSale.getOrderItemId(), afterSale.getRefundPrice());
     }
 
     private void createPayRefund(String userIp, TradeAfterSaleDO afterSale) {
@@ -403,8 +397,7 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa
         // TODO 发送售后消息
 
         // 更新交易订单项的售后状态为【未申请】
-        tradeOrderUpdateService.updateOrderItemAfterSaleStatus(afterSale.getOrderItemId(),
-                TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(), TradeOrderItemAfterSaleStatusEnum.NONE.getStatus());
+        tradeOrderUpdateService.updateOrderItemWhenAfterSaleCancel(afterSale.getOrderItemId());
     }
 
     @Override
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java
index 18a75b679..f612d0ca4 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java
@@ -1,15 +1,17 @@
 package cn.iocoder.yudao.module.trade.service.order;
 
-import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderUpdateAddressReqVO;
-import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderUpdatePriceReqVO;
 import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO;
 import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderRemarkReqVO;
+import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderUpdateAddressReqVO;
+import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderUpdatePriceReqVO;
 import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO;
 import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementReqVO;
 import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementRespVO;
 import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemCommentCreateReqVO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
 
+import javax.validation.constraints.NotNull;
+
 /**
  * 交易订单【写】Service 接口
  *
@@ -116,27 +118,28 @@ public interface TradeOrderUpdateService {
     // =================== Order Item ===================
 
     /**
-     * 更新交易订单项的售后状态
+     * 当售后申请后,更新交易订单项的售后状态
      *
-     * @param id                 交易订单项编号
-     * @param oldAfterSaleStatus 当前售后状态;如果不符,更新后会抛出异常
-     * @param newAfterSaleStatus 目标售后状态
+     * @param id          交易订单项编号
+     * @param afterSaleId 售后单编号
      */
-    default void updateOrderItemAfterSaleStatus(Long id, Integer oldAfterSaleStatus, Integer newAfterSaleStatus) {
-        updateOrderItemAfterSaleStatus(id, oldAfterSaleStatus, newAfterSaleStatus, null, null);
-    }
+    void updateOrderItemWhenAfterSaleCreate(@NotNull Long id, @NotNull Long afterSaleId);
+
 
     /**
-     * 更新交易订单项的售后状态
+     * 当售后完成后,更新交易订单项的售后状态
      *
-     * @param id                 交易订单项编号
-     * @param oldAfterSaleStatus 当前售后状态;如果不符,更新后会抛出异常
-     * @param newAfterSaleStatus 目标售后状态
-     * @param afterSaleId        售后单编号;当订单项发起售后时,必须传递该字段
-     * @param refundPrice        退款金额;当订单项退款成功时,必须传递该值
+     * @param id          交易订单项编号
+     * @param refundPrice 退款金额
      */
-    void updateOrderItemAfterSaleStatus(Long id, Integer oldAfterSaleStatus, Integer newAfterSaleStatus,
-                                        Long afterSaleId, Integer refundPrice);
+    void updateOrderItemWhenAfterSaleSuccess(@NotNull Long id, @NotNull Integer refundPrice);
+
+    /**
+     * 当售后取消(用户取消、管理员驳回、管理员拒绝收货)后,更新交易订单项的售后状态
+     *
+     * @param id 交易订单项编号
+     */
+    void updateOrderItemWhenAfterSaleCancel(@NotNull Long id);
 
     /**
      * 创建订单项的评论
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
index 98abc349a..6f980594b 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
@@ -508,7 +508,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
         for (TradeOrderDO order : orders) {
             try {
                 getSelf().receiveOrderBySystem(order);
-                count ++;
+                count++;
             } catch (Throwable e) {
                 log.error("[autoReceiveOrder][order({}) 自动收货订单异常]", order.getId(), e);
             }
@@ -599,7 +599,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
         for (TradeOrderDO order : orders) {
             try {
                 getSelf().cancelOrderBySystem(order);
-                count ++;
+                count++;
             } catch (Throwable e) {
                 log.error("[autoCancelOrder][order({}) 过期订单异常]", order.getId(), e);
             }
@@ -621,7 +621,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
     /**
      * 取消订单的核心实现
      *
-     * @param order 订单
+     * @param order      订单
      * @param cancelType 取消类型
      */
     private void cancelOrder0(TradeOrderDO order, TradeOrderCancelTypeEnum cancelType) {
@@ -770,67 +770,79 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
 
     // =================== Order Item ===================
 
-    // TODO 疯狂:帮我重构下:
-    // 1. updateOrderItemAfterSaleStatus 拆分成三个方法:发起;同意;拒绝。原因是,职责更清晰,操作日志也更容易记录;
-
     @Override
-    @Transactional(rollbackFor = Exception.class)
-    public void updateOrderItemAfterSaleStatus(Long id, Integer oldAfterSaleStatus, Integer newAfterSaleStatus,
-                                               Long afterSaleId, Integer refundPrice) {
-        // 如果退款成功,则 refundPrice 非空
-        if (Objects.equals(newAfterSaleStatus, TradeOrderItemAfterSaleStatusEnum.SUCCESS.getStatus())
-                && refundPrice == null) {
-            throw new IllegalArgumentException(StrUtil.format("id({}) 退款成功,退款金额不能为空", id));
-        }
-        // 如果退款发起,则 afterSaleId 非空
-        if (Objects.equals(newAfterSaleStatus, TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus())
-                && afterSaleId == null) {
+    public void updateOrderItemWhenAfterSaleCreate(Long id, Long afterSaleId) {
+        if (afterSaleId == null) {
             throw new IllegalArgumentException(StrUtil.format("id({}) 退款发起,售后单编号不能为空", id));
         }
 
+        // 更新订单项
+        updateOrderItemAfterSaleStatus(id, TradeOrderItemAfterSaleStatusEnum.NONE.getStatus(),
+                TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(), afterSaleId);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateOrderItemWhenAfterSaleSuccess(Long id, Integer refundPrice) {
+        if (refundPrice == null) {
+            throw new IllegalArgumentException(StrUtil.format("id({}) 退款成功,退款金额不能为空", id));
+        }
+
+        // 1. 更新订单项
+        updateOrderItemAfterSaleStatus(id, TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(),
+                TradeOrderItemAfterSaleStatusEnum.SUCCESS.getStatus(), null);
+
+        // 2. 计算总的退款金额、退回积分
+        TradeOrderItemDO orderItem = tradeOrderItemMapper.selectById(id);
+        TradeOrderDO order = tradeOrderMapper.selectById(orderItem.getOrderId());
+        Integer orderRefundPrice = order.getRefundPrice() + refundPrice;
+        Integer orderRefundPoint = order.getRefundPoint() + orderItem.getUsePoint();
+        if (isAllOrderItemAfterSaleSuccess(order.getId())) { // 如果都售后成功,则需要取消订单
+            cancelOrderByAfterSale(order, orderRefundPrice, orderRefundPoint);
+        } else { // 如果部分售后,则更新退款金额
+            tradeOrderMapper.updateById(new TradeOrderDO().setId(order.getId())
+                    .setRefundStatus(TradeOrderRefundStatusEnum.PART.getStatus())
+                    .setRefundPrice(orderRefundPrice).setRefundPoint(orderRefundPoint));
+        }
+
+        // TODO 芋艿:这块扣减规则,需要在考虑下
+        // 3.1 回滚数据:增加 SKU 库存
+        productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convert(Collections.singletonList(orderItem)));
+        // 3.2 回滚数据:扣减用户积分(赠送的)
+        reduceUserPoint(order.getUserId(), orderItem.getGivePoint(), MemberPointBizTypeEnum.AFTER_SALE_DEDUCT_GIVE, orderItem.getAfterSaleId());
+        // 3.3 回滚数据:增加用户积分(返还抵扣)
+        addUserPoint(order.getUserId(), orderItem.getUsePoint(), MemberPointBizTypeEnum.AFTER_SALE_REFUND_USED, orderItem.getAfterSaleId());
+        // 3.4 回滚数据:扣减用户经验
+        getSelf().reduceUserExperienceAsync(order.getUserId(), orderRefundPrice, orderItem.getAfterSaleId());
+        // 3.5 回滚数据:更新分佣记录为已失效
+        getSelf().cancelBrokerageAsync(order.getUserId(), id);
+    }
+
+    @Override
+    public void updateOrderItemWhenAfterSaleCancel(Long id) {
+        // 更新订单项
+        updateOrderItemAfterSaleStatus(id, TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(),
+                TradeOrderItemAfterSaleStatusEnum.NONE.getStatus(), null);
+    }
+
+    private void updateOrderItemAfterSaleStatus(Long id, Integer oldAfterSaleStatus, Integer newAfterSaleStatus,
+                                                Long afterSaleId) {
         // 更新订单项
         int updateCount = tradeOrderItemMapper.updateAfterSaleStatus(id, oldAfterSaleStatus, newAfterSaleStatus, afterSaleId);
         if (updateCount <= 0) {
             throw exception(ORDER_ITEM_UPDATE_AFTER_SALE_STATUS_FAIL);
         }
+    }
 
-        // 如果有退款金额,则需要更新订单
-        if (refundPrice == null) {
-            return;
-        }
-        // 计算总的退款金额
-        TradeOrderItemDO orderItem = tradeOrderItemMapper.selectById(id);
-        TradeOrderDO order = tradeOrderMapper.selectById(orderItem.getOrderId());
-        Integer orderRefundPrice = order.getRefundPrice() + refundPrice;
-        // TODO @疯狂:809 到 817 改成:cancelOrderByAfterSale:相当于全部售后成功后,就是要取消胆子;
-        if (isAllOrderItemAfterSaleSuccess(order.getId())) { // 如果都售后成功,则需要取消订单
-            tradeOrderMapper.updateById(new TradeOrderDO().setId(order.getId())
-                    .setRefundStatus(TradeOrderRefundStatusEnum.ALL.getStatus()).setRefundPrice(orderRefundPrice).setRefundPoint(order.getRefundPoint() + orderItem.getUsePoint())
-                    .setCancelType(TradeOrderCancelTypeEnum.AFTER_SALE_CLOSE.getType()).setCancelTime(LocalDateTime.now()));
-
-            // TODO 芋艿:记录订单日志
-
-            // TODO 芋艿:要不要退优惠劵
-
-        } else { // 如果部分售后,则更新退款金额
-            tradeOrderMapper.updateById(new TradeOrderDO().setId(order.getId())
-                    .setRefundStatus(TradeOrderRefundStatusEnum.PART.getStatus()).setRefundPrice(orderRefundPrice));
-        }
-
-        // TODO 芋艿:这块扣减规则,需要在考虑下
-        // 售后成功后,执行数据回滚逻辑
-        if (Objects.equals(newAfterSaleStatus, TradeOrderItemAfterSaleStatusEnum.SUCCESS.getStatus())) {
-            // 增加 SKU 库存
-            productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convert(Collections.singletonList(orderItem)));
-            // 扣减用户积分(赠送的)
-            reduceUserPoint(order.getUserId(), orderItem.getGivePoint(), MemberPointBizTypeEnum.AFTER_SALE_DEDUCT_GIVE, afterSaleId);
-            // 增加用户积分(返还抵扣)
-            addUserPoint(order.getUserId(), orderItem.getUsePoint(), MemberPointBizTypeEnum.AFTER_SALE_REFUND_USED, afterSaleId);
-            // 扣减用户经验
-            getSelf().reduceUserExperienceAsync(order.getUserId(), orderRefundPrice, afterSaleId);
-            // 更新分佣记录为已失效
-            getSelf().cancelBrokerageAsync(order.getUserId(), id);
-        }
+    @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.MEMBER_CANCEL)
+    private void cancelOrderByAfterSale(TradeOrderDO order, Integer orderRefundPrice, Integer refundPoint) {
+        // 1. 更新订单
+        tradeOrderMapper.updateById(new TradeOrderDO().setId(order.getId())
+                .setRefundStatus(TradeOrderRefundStatusEnum.ALL.getStatus())
+                .setRefundPrice(orderRefundPrice).setRefundPoint(refundPoint)
+                .setCancelType(TradeOrderCancelTypeEnum.AFTER_SALE_CLOSE.getType()).setCancelTime(LocalDateTime.now()));
+        // 2. 退还优惠券
+        couponApi.returnUsedCoupon(order.getCouponId());
     }
 
     @Override

From b2c608e7a57fae93fc7d3c382a2ebc03acd475bb Mon Sep 17 00:00:00 2001
From: owen <owen@evolsun.com>
Date: Wed, 27 Sep 2023 21:27:55 +0800
Subject: [PATCH 10/10] =?UTF-8?q?=E4=BA=A4=E6=98=93=EF=BC=9A=E5=8F=91?=
 =?UTF-8?q?=E8=B4=A7=E8=AE=A2=E5=8D=95=E6=97=A5=E5=BF=97?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../order/TradeOrderUpdateServiceImpl.java    | 22 +++++++++----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
index b1b544926..101b7b346 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
@@ -360,7 +360,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
 
     /**
      * 校验交易订单满足被支付的条件
-     *
+     * <p>
      * 1. 交易订单未支付
      * 2. 支付单已支付
      *
@@ -413,7 +413,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.MEMBER_CANCEL)
+    @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.ADMIN_DELIVERY)
     public void deliveryOrder(TradeOrderDeliveryReqVO deliveryReqVO) {
         // 1.1 校验并获得交易订单(可发货)
         TradeOrderDO order = validateOrderDeliverable(deliveryReqVO.getId());
@@ -452,7 +452,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
 
     /**
      * 校验交易订单满足被发货的条件
-     *
+     * <p>
      * 1. 交易订单未发货
      *
      * @param id 交易订单编号
@@ -556,7 +556,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
 
     /**
      * 校验交易订单满足可售货的条件
-     *
+     * <p>
      * 1. 交易订单待收货
      *
      * @param userId 用户编号
@@ -889,7 +889,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
         for (TradeOrderDO order : orders) {
             try {
                 getSelf().createOrderItemCommentBySystemBySystem(order);
-                count ++;
+                count++;
             } catch (Throwable e) {
                 log.error("[createOrderItemCommentBySystem][order({}) 过期订单异常]", order.getId(), e);
             }
@@ -933,7 +933,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
     /**
      * 创建订单项的评论的核心实现
      *
-     * @param orderItem 订单项
+     * @param orderItem   订单项
      * @param createReqVO 评论内容
      * @return 评论编号
      */
@@ -989,14 +989,14 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
 
     /**
      * 创建分销记录
-     *
+     * <p>
      * 目前是支付成功后,就会创建分销记录。
-     *
+     * <p>
      * 业内还有两种做法,可以根据自己的业务调整:
-     *  1. 确认收货后,才创建分销记录
-     *  2. 支付 or 下单成功时,创建分销记录(冻结),确认收货解冻或者 n 天后解冻
+     * 1. 确认收货后,才创建分销记录
+     * 2. 支付 or 下单成功时,创建分销记录(冻结),确认收货解冻或者 n 天后解冻
      *
-     * @param userId 用户编号
+     * @param userId  用户编号
      * @param orderId 订单编号
      */
     @Async