Commit edc5d0f1d0a17f46a94d7c33382a22543840be29

Authored by YunaiV
1 parent fb45f225

promotion:增加获得优惠劵匹配结果的列表

yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/DateUtils.java
... ... @@ -135,4 +135,19 @@ public class DateUtils {
135 135 return DateUtil.isSameDay(date, new Date());
136 136 }
137 137  
  138 + /**
  139 + * 判断当前时间是否在该时间范围内
  140 + *
  141 + * @param startTime 开始时间
  142 + * @param endTime 结束时间
  143 + * @return 是否
  144 + */
  145 + public static boolean isBetween(Date startTime, Date endTime) {
  146 + if (startTime == null || endTime == null) {
  147 + return false;
  148 + }
  149 + return startTime.getTime() <= System.currentTimeMillis()
  150 + && endTime.getTime() >= System.currentTimeMillis();
  151 + }
  152 +
138 153 }
... ...
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/price/dto/CouponMeetRespDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.api.price.dto;
  2 +
  3 +import lombok.Data;
  4 +
  5 +/**
  6 + * 优惠劵的匹配信息 Response DTO
  7 + *
  8 + * why 放在 price 包下?主要获取的时候,需要涉及到较多的价格计算逻辑,放在 price 可以更好的复用逻辑
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +@Data
  13 +public class CouponMeetRespDTO {
  14 +
  15 + /**
  16 + * 优惠劵编号
  17 + */
  18 + private Long id;
  19 +
  20 + // ========== 非优惠劵的基本信息字段 ==========
  21 + /**
  22 + * 是否匹配
  23 + */
  24 + private Boolean meet;
  25 + /**
  26 + * 不匹配的提示,即 {@link #meet} = true 才有值
  27 + *
  28 + * 例如说:
  29 + * 1. 所结算商品没有符合条件的商品
  30 + * 2. 差 XXX 元可用优惠劵
  31 + * 3. 优惠劵未到使用时间
  32 + */
  33 + private String meetTip;
  34 +
  35 +}
... ...
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java
... ... @@ -31,6 +31,8 @@ public interface ErrorCodeConstants {
31 31 // ========== 优惠劵模板 1003005000 ==========
32 32 ErrorCode COUPON_NOT_EXISTS = new ErrorCode(1003005000, "优惠劵不存在");
33 33 ErrorCode COUPON_DELETE_FAIL_USED = new ErrorCode(1003005001, "回收优惠劵失败,优惠劵已被使用");
  34 + ErrorCode COUPON_STATUS_NOT_UNUSED = new ErrorCode(1006003003, "优惠劵不处于待使用状态");
  35 + ErrorCode COUPON_VALID_TIME_NOT_NOW = new ErrorCode(1006003004, "优惠劵不在有效期内");
34 36  
35 37 // ========== 满减送活动 1003006000 ==========
36 38 ErrorCode REWARD_ACTIVITY_NOT_EXISTS = new ErrorCode(1003006000, "满减送活动不存在");
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/price/PriceConvert.java
1 1 package cn.iocoder.yudao.module.promotion.convert.price;
2 2  
3 3 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
  4 +import cn.iocoder.yudao.module.promotion.api.price.dto.CouponMeetRespDTO;
4 5 import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateReqDTO;
5 6 import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateRespDTO;
6 7 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
  8 +import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
7 9 import org.mapstruct.Mapper;
8 10 import org.mapstruct.factory.Mappers;
9 11  
... ... @@ -42,4 +44,6 @@ public interface PriceConvert {
42 44 return priceCalculate;
43 45 }
44 46  
  47 + CouponMeetRespDTO convert(CouponDO coupon);
  48 +
45 49 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java
... ... @@ -9,6 +9,7 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
9 9 import org.apache.ibatis.annotations.Mapper;
10 10  
11 11 import java.util.Collection;
  12 +import java.util.List;
12 13  
13 14 /**
14 15 * 优惠劵 Mapper
... ... @@ -27,10 +28,25 @@ public interface CouponMapper extends BaseMapperX&lt;CouponDO&gt; {
27 28 .orderByDesc(CouponDO::getId));
28 29 }
29 30  
  31 + default List<CouponDO> selectListByUserIdAndStatus(Long userId, Integer status) {
  32 + return selectList(new LambdaQueryWrapperX<CouponDO>()
  33 + .eq(CouponDO::getUserId, userId).eq(CouponDO::getStatus, status));
  34 + }
  35 +
  36 + default CouponDO selectByIdAndUserId(Long id, Long userId) {
  37 + return selectOne(new LambdaQueryWrapperX<CouponDO>()
  38 + .eq(CouponDO::getId, id).eq(CouponDO::getUserId, userId));
  39 + }
  40 +
30 41 default int delete(Long id, Collection<Integer> whereStatuses) {
31 42 return update(null, new LambdaUpdateWrapper<CouponDO>()
32 43 .eq(CouponDO::getId, id).in(CouponDO::getStatus, whereStatuses)
33 44 .set(CouponDO::getDeleted, 1));
34 45 }
35 46  
  47 + default int updateByIdAndStatus(Long id, Integer status, CouponDO updateObj) {
  48 + return update(updateObj, new LambdaUpdateWrapper<CouponDO>()
  49 + .eq(CouponDO::getId, id).eq(CouponDO::getStatus, status));
  50 + }
  51 +
36 52 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java
... ... @@ -4,6 +4,8 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
5 5 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
6 6  
  7 +import java.util.List;
  8 +
7 9 /**
8 10 * 优惠劵 Service 接口
9 11 *
... ... @@ -24,6 +26,15 @@ public interface CouponService {
24 26 CouponDO validCoupon(Long id, Long userId);
25 27  
26 28 /**
  29 + * 校验优惠劵,包括状态、有限期
  30 + *
  31 + * @see #validCoupon(Long, Long) 逻辑相同,只是入参不同
  32 + *
  33 + * @param coupon 优惠劵
  34 + */
  35 + void validCoupon(CouponDO coupon);
  36 +
  37 + /**
27 38 * 获得优惠劵分页
28 39 *
29 40 * @param pageReqVO 分页查询
... ... @@ -32,10 +43,27 @@ public interface CouponService {
32 43 PageResult<CouponDO> getCouponPage(CouponPageReqVO pageReqVO);
33 44  
34 45 /**
  46 + * 使用优惠劵
  47 + *
  48 + * @param id 优惠劵编号
  49 + * @param userId 用户编号
  50 + */
  51 + void useCoupon(Long id, Long userId);
  52 +
  53 + /**
35 54 * 回收优惠劵
36 55 *
37 56 * @param id 优惠劵编号
38 57 */
39 58 void deleteCoupon(Long id);
40 59  
  60 + /**
  61 + * 获得用户的优惠劵列表
  62 + *
  63 + * @param userId 用户编号
  64 + * @param status 优惠劵状态
  65 + * @return 优惠劵列表
  66 + */
  67 + List<CouponDO> getCouponList(Long userId, Integer status);
  68 +
41 69 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java
1 1 package cn.iocoder.yudao.module.promotion.service.coupon;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.ObjectUtil;
4 5 import cn.hutool.core.util.StrUtil;
5 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 7 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
  8 +import cn.iocoder.yudao.framework.common.util.date.DateUtils;
7 9 import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
8 10 import cn.iocoder.yudao.module.member.api.user.dto.UserRespDTO;
9 11 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
... ... @@ -15,11 +17,12 @@ import org.springframework.transaction.annotation.Transactional;
15 17 import org.springframework.validation.annotation.Validated;
16 18  
17 19 import javax.annotation.Resource;
  20 +import java.util.Date;
  21 +import java.util.List;
18 22 import java.util.Set;
19 23  
20 24 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
21   -import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_DELETE_FAIL_USED;
22   -import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_NOT_EXISTS;
  25 +import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
23 26 import static java.util.Arrays.asList;
24 27  
25 28 /**
... ... @@ -40,10 +43,26 @@ public class CouponServiceImpl implements CouponService {
40 43 @Resource
41 44 private MemberUserApi memberUserApi;
42 45  
43   - // TODO 芋艿:待实现
44 46 @Override
45 47 public CouponDO validCoupon(Long id, Long userId) {
46   - return null;
  48 + CouponDO coupon = couponMapper.selectByIdAndUserId(id, userId);
  49 + if (coupon == null) {
  50 + throw exception(COUPON_NOT_EXISTS);
  51 + }
  52 + validCoupon(coupon);
  53 + return coupon;
  54 + }
  55 +
  56 + @Override
  57 + public void validCoupon(CouponDO coupon) {
  58 + // 校验状态
  59 + if (ObjectUtil.notEqual(coupon.getStatus(), CouponStatusEnum.UNUSED.getStatus())) {
  60 + throw exception(COUPON_STATUS_NOT_UNUSED);
  61 + }
  62 + // 校验有效期;为避免定时器没跑,实际优惠劵已经过期
  63 + if (DateUtils.isBetween(coupon.getValidStartTime(), coupon.getValidEndTime())) {
  64 + throw exception(COUPON_VALID_TIME_NOT_NOW);
  65 + }
47 66 }
48 67  
49 68 @Override
... ... @@ -62,6 +81,18 @@ public class CouponServiceImpl implements CouponService {
62 81 }
63 82  
64 83 @Override
  84 + public void useCoupon(Long id, Long userId) {
  85 + // 校验优惠劵
  86 + validCoupon(id, userId);
  87 + // 更新状态
  88 + int updateCount = couponMapper.updateByIdAndStatus(id, CouponStatusEnum.UNUSED.getStatus(),
  89 + new CouponDO().setStatus(CouponStatusEnum.USED.getStatus()).setUseTime(new Date()));
  90 + if (updateCount == 0) {
  91 + throw exception(COUPON_STATUS_NOT_UNUSED);
  92 + }
  93 + }
  94 +
  95 + @Override
65 96 @Transactional
66 97 public void deleteCoupon(Long id) {
67 98 // 校验存在
... ... @@ -77,6 +108,11 @@ public class CouponServiceImpl implements CouponService {
77 108 couponTemplateService.updateCouponTemplateTakeCount(id, -1);
78 109 }
79 110  
  111 + @Override
  112 + public List<CouponDO> getCouponList(Long userId, Integer status) {
  113 + return couponMapper.selectListByUserIdAndStatus(userId, status);
  114 + }
  115 +
80 116 private void validateCouponExists(Long id) {
81 117 if (couponMapper.selectById(id) == null) {
82 118 throw exception(COUPON_NOT_EXISTS);
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/price/PriceService.java
1 1 package cn.iocoder.yudao.module.promotion.service.price;
2 2  
  3 +import cn.iocoder.yudao.module.promotion.api.price.dto.CouponMeetRespDTO;
3 4 import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateReqDTO;
4 5 import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateRespDTO;
5 6  
  7 +import java.util.List;
  8 +
6 9 /**
7 10 * 价格计算 Service 接口
8 11 *
... ... @@ -14,8 +17,16 @@ public interface PriceService {
14 17 * 计算商品的价格
15 18 *
16 19 * @param calculateReqDTO 价格请求
17   - * @return 价格相应
  20 + * @return 价格响应
18 21 */
19 22 PriceCalculateRespDTO calculatePrice(PriceCalculateReqDTO calculateReqDTO);
20 23  
  24 + /**
  25 + * 获得优惠劵的匹配信息列表
  26 + *
  27 + * @param calculateReqDTO 价格请求
  28 + * @return 价格响应
  29 + */
  30 + List<CouponMeetRespDTO> getMeetCouponList(PriceCalculateReqDTO calculateReqDTO);
  31 +
21 32 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/price/PriceServiceImpl.java
... ... @@ -3,15 +3,18 @@ package cn.iocoder.yudao.module.promotion.service.price;
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.lang.Assert;
5 5 import cn.hutool.core.util.StrUtil;
  6 +import cn.iocoder.yudao.framework.common.exception.ServiceException;
6 7 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
7 8 import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
8 9 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
  10 +import cn.iocoder.yudao.module.promotion.api.price.dto.CouponMeetRespDTO;
9 11 import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateReqDTO;
10 12 import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateRespDTO;
11 13 import cn.iocoder.yudao.module.promotion.convert.price.PriceConvert;
12 14 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
13 15 import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
14 16 import cn.iocoder.yudao.module.promotion.enums.common.*;
  17 +import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum;
15 18 import cn.iocoder.yudao.module.promotion.service.coupon.CouponService;
16 19 import cn.iocoder.yudao.module.promotion.service.discount.DiscountActivityService;
17 20 import cn.iocoder.yudao.module.promotion.service.discount.bo.DiscountProductDetailBO;
... ... @@ -22,10 +25,7 @@ import org.springframework.stereotype.Service;
22 25 import org.springframework.validation.annotation.Validated;
23 26  
24 27 import javax.annotation.Resource;
25   -import java.util.ArrayList;
26   -import java.util.List;
27   -import java.util.Map;
28   -import java.util.Set;
  28 +import java.util.*;
29 29 import java.util.function.Supplier;
30 30  
31 31 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
... ... @@ -87,6 +87,52 @@ public class PriceServiceImpl implements PriceService {
87 87 return priceCalculate;
88 88 }
89 89  
  90 + @Override
  91 + public List<CouponMeetRespDTO> getMeetCouponList(PriceCalculateReqDTO calculateReqDTO) {
  92 + // 先计算一轮价格
  93 + PriceCalculateRespDTO priceCalculate = calculatePrice(calculateReqDTO);
  94 +
  95 + // 获得用户的待使用优惠劵
  96 + List<CouponDO> couponList = couponService.getCouponList(calculateReqDTO.getUserId(), CouponStatusEnum.UNUSED.getStatus());
  97 + if (CollUtil.isEmpty(couponList)) {
  98 + return Collections.emptyList();
  99 + }
  100 +
  101 + // 获得优惠劵的匹配信息
  102 + return CollectionUtils.convertList(couponList, coupon -> {
  103 + CouponMeetRespDTO couponMeetRespDTO = PriceConvert.INSTANCE.convert(coupon);
  104 + try {
  105 + // 校验优惠劵
  106 + couponService.validCoupon(coupon);
  107 +
  108 + // 获得匹配的商品 SKU 数组
  109 + List<PriceCalculateRespDTO.OrderItem> orderItems = getMatchCouponOrderItems(priceCalculate, coupon);
  110 + if (CollUtil.isEmpty(orderItems)) {
  111 + return couponMeetRespDTO.setMeet(false).setMeetTip("所结算商品没有符合条件的商品");
  112 + }
  113 +
  114 + // 计算是否满足优惠劵的使用金额
  115 + Integer originPrice = getSumValue(orderItems, PriceCalculateRespDTO.OrderItem::getOrderDividePrice, Integer::sum);
  116 + assert originPrice != null;
  117 + if (originPrice < coupon.getUsePrice()) {
  118 + return couponMeetRespDTO.setMeet(false)
  119 + .setMeetTip(String.format("差 %s 元可用优惠劵", formatPrice(coupon.getUsePrice() - originPrice)));
  120 + }
  121 + } catch (ServiceException serviceException) {
  122 + couponMeetRespDTO.setMeet(false);
  123 + if (serviceException.getCode().equals(COUPON_VALID_TIME_NOT_NOW.getCode())) {
  124 + couponMeetRespDTO.setMeetTip("优惠劵未到使用时间");
  125 + } else {
  126 + log.error("[getMeetCouponList][calculateReqDTO({}) 获得优惠劵匹配信息异常]", calculateReqDTO, serviceException);
  127 + couponMeetRespDTO.setMeetTip("优惠劵不满足使用条件");
  128 + }
  129 + return couponMeetRespDTO;
  130 + }
  131 + // 满足
  132 + return couponMeetRespDTO.setMeet(true);
  133 + });
  134 + }
  135 +
90 136 private List<ProductSkuRespDTO> checkSkus(PriceCalculateReqDTO calculateReqDTO) {
91 137 // 获得商品 SKU 数组
92 138 Map<Long, Integer> skuIdCountMap = CollectionUtils.convertMap(calculateReqDTO.getItems(),
... ...