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

This commit is contained in:
YunaiV 2022-11-08 00:40:53 +08:00
parent fb45f22533
commit edc5d0f1d0
9 changed files with 202 additions and 9 deletions

View File

@ -135,4 +135,19 @@ public class DateUtils {
return DateUtil.isSameDay(date, new Date());
}
/**
* 判断当前时间是否在该时间范围内
*
* @param startTime 开始时间
* @param endTime 结束时间
* @return 是否
*/
public static boolean isBetween(Date startTime, Date endTime) {
if (startTime == null || endTime == null) {
return false;
}
return startTime.getTime() <= System.currentTimeMillis()
&& endTime.getTime() >= System.currentTimeMillis();
}
}

View File

@ -0,0 +1,35 @@
package cn.iocoder.yudao.module.promotion.api.price.dto;
import lombok.Data;
/**
* 优惠劵的匹配信息 Response DTO
*
* why 放在 price 包下主要获取的时候需要涉及到较多的价格计算逻辑放在 price 可以更好的复用逻辑
*
* @author 芋道源码
*/
@Data
public class CouponMeetRespDTO {
/**
* 优惠劵编号
*/
private Long id;
// ========== 非优惠劵的基本信息字段 ==========
/**
* 是否匹配
*/
private Boolean meet;
/**
* 不匹配的提示 {@link #meet} = true 才有值
*
* 例如说
* 1. 所结算商品没有符合条件的商品
* 2. XXX 元可用优惠劵
* 3. 优惠劵未到使用时间
*/
private String meetTip;
}

View File

@ -31,6 +31,8 @@ public interface ErrorCodeConstants {
// ========== 优惠劵模板 1003005000 ==========
ErrorCode COUPON_NOT_EXISTS = new ErrorCode(1003005000, "优惠劵不存在");
ErrorCode COUPON_DELETE_FAIL_USED = new ErrorCode(1003005001, "回收优惠劵失败,优惠劵已被使用");
ErrorCode COUPON_STATUS_NOT_UNUSED = new ErrorCode(1006003003, "优惠劵不处于待使用状态");
ErrorCode COUPON_VALID_TIME_NOT_NOW = new ErrorCode(1006003004, "优惠劵不在有效期内");
// ========== 满减送活动 1003006000 ==========
ErrorCode REWARD_ACTIVITY_NOT_EXISTS = new ErrorCode(1003006000, "满减送活动不存在");

View File

@ -1,9 +1,11 @@
package cn.iocoder.yudao.module.promotion.convert.price;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.promotion.api.price.dto.CouponMeetRespDTO;
import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateReqDTO;
import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateRespDTO;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@ -42,4 +44,6 @@ public interface PriceConvert {
return priceCalculate;
}
CouponMeetRespDTO convert(CouponDO coupon);
}

View File

@ -9,6 +9,7 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
/**
* 优惠劵 Mapper
@ -27,10 +28,25 @@ public interface CouponMapper extends BaseMapperX<CouponDO> {
.orderByDesc(CouponDO::getId));
}
default List<CouponDO> selectListByUserIdAndStatus(Long userId, Integer status) {
return selectList(new LambdaQueryWrapperX<CouponDO>()
.eq(CouponDO::getUserId, userId).eq(CouponDO::getStatus, status));
}
default CouponDO selectByIdAndUserId(Long id, Long userId) {
return selectOne(new LambdaQueryWrapperX<CouponDO>()
.eq(CouponDO::getId, id).eq(CouponDO::getUserId, userId));
}
default int delete(Long id, Collection<Integer> whereStatuses) {
return update(null, new LambdaUpdateWrapper<CouponDO>()
.eq(CouponDO::getId, id).in(CouponDO::getStatus, whereStatuses)
.set(CouponDO::getDeleted, 1));
}
default int updateByIdAndStatus(Long id, Integer status, CouponDO updateObj) {
return update(updateObj, new LambdaUpdateWrapper<CouponDO>()
.eq(CouponDO::getId, id).eq(CouponDO::getStatus, status));
}
}

View File

@ -4,6 +4,8 @@ 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 java.util.List;
/**
* 优惠劵 Service 接口
*
@ -23,6 +25,15 @@ public interface CouponService {
*/
CouponDO validCoupon(Long id, Long userId);
/**
* 校验优惠劵包括状态有限期
*
* @see #validCoupon(Long, Long) 逻辑相同只是入参不同
*
* @param coupon 优惠劵
*/
void validCoupon(CouponDO coupon);
/**
* 获得优惠劵分页
*
@ -31,6 +42,14 @@ public interface CouponService {
*/
PageResult<CouponDO> getCouponPage(CouponPageReqVO pageReqVO);
/**
* 使用优惠劵
*
* @param id 优惠劵编号
* @param userId 用户编号
*/
void useCoupon(Long id, Long userId);
/**
* 回收优惠劵
*
@ -38,4 +57,13 @@ public interface CouponService {
*/
void deleteCoupon(Long id);
/**
* 获得用户的优惠劵列表
*
* @param userId 用户编号
* @param status 优惠劵状态
* @return 优惠劵列表
*/
List<CouponDO> getCouponList(Long userId, Integer status);
}

View File

@ -1,9 +1,11 @@
package cn.iocoder.yudao.module.promotion.service.coupon;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
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.DateUtils;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.member.api.user.dto.UserRespDTO;
import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
@ -15,11 +17,12 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_DELETE_FAIL_USED;
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_NOT_EXISTS;
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
import static java.util.Arrays.asList;
/**
@ -40,10 +43,26 @@ public class CouponServiceImpl implements CouponService {
@Resource
private MemberUserApi memberUserApi;
// TODO 芋艿待实现
@Override
public CouponDO validCoupon(Long id, Long userId) {
return null;
CouponDO coupon = couponMapper.selectByIdAndUserId(id, userId);
if (coupon == null) {
throw exception(COUPON_NOT_EXISTS);
}
validCoupon(coupon);
return coupon;
}
@Override
public void validCoupon(CouponDO coupon) {
// 校验状态
if (ObjectUtil.notEqual(coupon.getStatus(), CouponStatusEnum.UNUSED.getStatus())) {
throw exception(COUPON_STATUS_NOT_UNUSED);
}
// 校验有效期为避免定时器没跑实际优惠劵已经过期
if (DateUtils.isBetween(coupon.getValidStartTime(), coupon.getValidEndTime())) {
throw exception(COUPON_VALID_TIME_NOT_NOW);
}
}
@Override
@ -61,6 +80,18 @@ public class CouponServiceImpl implements CouponService {
return couponMapper.selectPage(pageReqVO, userIds);
}
@Override
public void useCoupon(Long id, Long userId) {
// 校验优惠劵
validCoupon(id, userId);
// 更新状态
int updateCount = couponMapper.updateByIdAndStatus(id, CouponStatusEnum.UNUSED.getStatus(),
new CouponDO().setStatus(CouponStatusEnum.USED.getStatus()).setUseTime(new Date()));
if (updateCount == 0) {
throw exception(COUPON_STATUS_NOT_UNUSED);
}
}
@Override
@Transactional
public void deleteCoupon(Long id) {
@ -77,6 +108,11 @@ public class CouponServiceImpl implements CouponService {
couponTemplateService.updateCouponTemplateTakeCount(id, -1);
}
@Override
public List<CouponDO> getCouponList(Long userId, Integer status) {
return couponMapper.selectListByUserIdAndStatus(userId, status);
}
private void validateCouponExists(Long id) {
if (couponMapper.selectById(id) == null) {
throw exception(COUPON_NOT_EXISTS);

View File

@ -1,8 +1,11 @@
package cn.iocoder.yudao.module.promotion.service.price;
import cn.iocoder.yudao.module.promotion.api.price.dto.CouponMeetRespDTO;
import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateReqDTO;
import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateRespDTO;
import java.util.List;
/**
* 价格计算 Service 接口
*
@ -14,8 +17,16 @@ public interface PriceService {
* 计算商品的价格
*
* @param calculateReqDTO 价格请求
* @return 价格
* @return 价格
*/
PriceCalculateRespDTO calculatePrice(PriceCalculateReqDTO calculateReqDTO);
/**
* 获得优惠劵的匹配信息列表
*
* @param calculateReqDTO 价格请求
* @return 价格响应
*/
List<CouponMeetRespDTO> getMeetCouponList(PriceCalculateReqDTO calculateReqDTO);
}

View File

@ -3,15 +3,18 @@ package cn.iocoder.yudao.module.promotion.service.price;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import cn.iocoder.yudao.module.promotion.api.price.dto.CouponMeetRespDTO;
import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateReqDTO;
import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateRespDTO;
import cn.iocoder.yudao.module.promotion.convert.price.PriceConvert;
import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
import cn.iocoder.yudao.module.promotion.enums.common.*;
import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum;
import cn.iocoder.yudao.module.promotion.service.coupon.CouponService;
import cn.iocoder.yudao.module.promotion.service.discount.DiscountActivityService;
import cn.iocoder.yudao.module.promotion.service.discount.bo.DiscountProductDetailBO;
@ -22,10 +25,7 @@ import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.function.Supplier;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@ -87,6 +87,52 @@ public class PriceServiceImpl implements PriceService {
return priceCalculate;
}
@Override
public List<CouponMeetRespDTO> getMeetCouponList(PriceCalculateReqDTO calculateReqDTO) {
// 先计算一轮价格
PriceCalculateRespDTO priceCalculate = calculatePrice(calculateReqDTO);
// 获得用户的待使用优惠劵
List<CouponDO> couponList = couponService.getCouponList(calculateReqDTO.getUserId(), CouponStatusEnum.UNUSED.getStatus());
if (CollUtil.isEmpty(couponList)) {
return Collections.emptyList();
}
// 获得优惠劵的匹配信息
return CollectionUtils.convertList(couponList, coupon -> {
CouponMeetRespDTO couponMeetRespDTO = PriceConvert.INSTANCE.convert(coupon);
try {
// 校验优惠劵
couponService.validCoupon(coupon);
// 获得匹配的商品 SKU 数组
List<PriceCalculateRespDTO.OrderItem> orderItems = getMatchCouponOrderItems(priceCalculate, coupon);
if (CollUtil.isEmpty(orderItems)) {
return couponMeetRespDTO.setMeet(false).setMeetTip("所结算商品没有符合条件的商品");
}
// 计算是否满足优惠劵的使用金额
Integer originPrice = getSumValue(orderItems, PriceCalculateRespDTO.OrderItem::getOrderDividePrice, Integer::sum);
assert originPrice != null;
if (originPrice < coupon.getUsePrice()) {
return couponMeetRespDTO.setMeet(false)
.setMeetTip(String.format("差 %s 元可用优惠劵", formatPrice(coupon.getUsePrice() - originPrice)));
}
} catch (ServiceException serviceException) {
couponMeetRespDTO.setMeet(false);
if (serviceException.getCode().equals(COUPON_VALID_TIME_NOT_NOW.getCode())) {
couponMeetRespDTO.setMeetTip("优惠劵未到使用时间");
} else {
log.error("[getMeetCouponList][calculateReqDTO({}) 获得优惠劵匹配信息异常]", calculateReqDTO, serviceException);
couponMeetRespDTO.setMeetTip("优惠劵不满足使用条件");
}
return couponMeetRespDTO;
}
// 满足
return couponMeetRespDTO.setMeet(true);
});
}
private List<ProductSkuRespDTO> checkSkus(PriceCalculateReqDTO calculateReqDTO) {
// 获得商品 SKU 数组
Map<Long, Integer> skuIdCountMap = CollectionUtils.convertMap(calculateReqDTO.getItems(),