Merge branch 'develop' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into master-jdk17

This commit is contained in:
YunaiV
2024-09-07 08:47:56 +08:00
71 changed files with 1624 additions and 918 deletions

View File

@@ -7,10 +7,12 @@ import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponValidReqDTO;
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 jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import jakarta.annotation.Resource;
import java.util.List;
import java.util.Map;
/**
* 优惠劵 API 实现类
@@ -41,4 +43,14 @@ public class CouponApiImpl implements CouponApi {
return CouponConvert.INSTANCE.convert(coupon);
}
@Override
public List<Long> takeCouponsByAdmin(Map<Long, Integer> giveCoupons, Long userId) {
return couponService.takeCouponsByAdmin(giveCoupons, userId);
}
@Override
public void invalidateCouponsByAdmin(List<Long> giveCouponIds, Long userId) {
couponService.invalidateCouponsByAdmin(giveCouponIds, userId);
}
}

View File

@@ -2,23 +2,22 @@ package cn.iocoder.yudao.module.promotion.controller.admin.reward;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityCreateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityPageReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityRespVO;
import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityUpdateReqVO;
import cn.iocoder.yudao.module.promotion.convert.reward.RewardActivityConvert;
import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
import cn.iocoder.yudao.module.promotion.service.reward.RewardActivityService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 满减送活动")
@@ -69,7 +68,7 @@ public class RewardActivityController {
@PreAuthorize("@ss.hasPermission('promotion:reward-activity:query')")
public CommonResult<RewardActivityRespVO> getRewardActivity(@RequestParam("id") Long id) {
RewardActivityDO rewardActivity = rewardActivityService.getRewardActivity(id);
return success(RewardActivityConvert.INSTANCE.convert(rewardActivity));
return success(BeanUtils.toBean(rewardActivity, RewardActivityRespVO.class));
}
@GetMapping("/page")
@@ -77,7 +76,7 @@ public class RewardActivityController {
@PreAuthorize("@ss.hasPermission('promotion:reward-activity:query')")
public CommonResult<PageResult<RewardActivityRespVO>> getRewardActivityPage(@Valid RewardActivityPageReqVO pageVO) {
PageResult<RewardActivityDO> pageResult = rewardActivityService.getRewardActivityPage(pageVO);
return success(RewardActivityConvert.INSTANCE.convertPage(pageResult));
return success(BeanUtils.toBean(pageResult, RewardActivityRespVO.class));
}
}

View File

@@ -6,18 +6,17 @@ import cn.iocoder.yudao.module.promotion.enums.common.PromotionConditionTypeEnum
import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import jakarta.validation.Valid;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.Future;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
import java.util.Map;
import java.util.Objects;
/**
* 满减送活动 Base VO提供给添加、修改、详细的子 VO 使用
@@ -32,12 +31,10 @@ public class RewardActivityBaseVO {
@Schema(description = "开始时间", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "开始时间不能为空")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime startTime;
@Schema(description = "结束时间", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "结束时间不能为空")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@Future(message = "结束时间必须大于当前时间")
private LocalDateTime endTime;
@@ -54,8 +51,8 @@ public class RewardActivityBaseVO {
@InEnum(value = PromotionProductScopeEnum.class, message = "商品范围必须是 {value}")
private Integer productScope;
@Schema(description = "商品 SPU 编号的数组", example = "1,2,3")
private List<Long> productSpuIds;
@Schema(description = "商品范围编号的数组", example = "[1, 3]")
private List<Long> productScopeValues;
/**
* 优惠规则的数组
@@ -76,24 +73,28 @@ public class RewardActivityBaseVO {
private Integer discountPrice;
@Schema(description = "是否包邮", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
@NotNull(message = "规则是否包邮不能为空")
private Boolean freeDelivery;
@Schema(description = "赠送的积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
@Min(value = 1L, message = "赠送的积分必须大于等于 1")
private Integer point;
@Schema(description = "赠送的优惠劵编号的数组", example = "1,2,3")
private List<Long> couponIds;
@Schema(description = "赠送的优惠劵编号的数组")
private Map<Long, Integer> giveCouponTemplateCounts;
@Schema(description = "赠送的优惠券数量的数组", example = "1,2,3")
private List<Integer> couponCounts;
@AssertTrue(message = "优惠劵和数量必须一一对应")
@AssertTrue(message = "赠送的积分不能小于 0")
@JsonIgnore
public boolean isCouponCountsValid() {
return CollUtil.size(couponCounts) == CollUtil.size(couponCounts);
public boolean isPointValid() {
return point == null || point >= 0;
}
}
@AssertTrue(message = "商品范围编号的数组不能为空")
@JsonIgnore
public boolean isProductScopeValuesValid() {
return Objects.equals(productScope, PromotionProductScopeEnum.ALL.getScope()) // 全部范围时,可以为空
|| CollUtil.isNotEmpty(productScopeValues);
}
}

View File

@@ -2,8 +2,11 @@ package cn.iocoder.yudao.module.promotion.controller.app.activity;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
import cn.iocoder.yudao.module.promotion.controller.app.activity.vo.AppActivityRespVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainActivityDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO;
@@ -11,7 +14,7 @@ import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountActivit
import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountProductDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillActivityDO;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionActivityStatusEnum;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
import cn.iocoder.yudao.module.promotion.service.bargain.BargainActivityService;
import cn.iocoder.yudao.module.promotion.service.combination.CombinationActivityService;
@@ -30,7 +33,6 @@ import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
@@ -52,6 +54,9 @@ public class AppActivityController {
@Resource
private RewardActivityService rewardActivityService;
@Resource
private ProductSpuApi productSpuApi;
@GetMapping("/list-by-spu-id")
@Operation(summary = "获得单个商品,近期参与的每个活动")
@Parameter(name = "spuId", description = "商品编号", required = true)
@@ -87,7 +92,7 @@ public class AppActivityController {
// 4. 限时折扣活动
getDiscountActivities(spuIds, now, activityList);
// 5. 满减送活动
getRewardActivities(spuIds, now, activityList);
getRewardActivityList(spuIds, now, activityList);
return activityList;
}
@@ -144,28 +149,51 @@ public class AppActivityController {
item.getName(), productMap.get(item.getId()), item.getStartTime(), item.getEndTime())));
}
private void getRewardActivities(Collection<Long> spuIds, LocalDateTime now, List<AppActivityRespVO> activityList) {
// TODO @puhui999有 3 范围,不只 spuId还有 categoryId全部
List<RewardActivityDO> rewardActivityList = rewardActivityService.getRewardActivityBySpuIdsAndStatusAndDateTimeLt(
spuIds, PromotionActivityStatusEnum.RUN.getStatus(), now);
private void getRewardActivityList(Collection<Long> spuIds, LocalDateTime now, List<AppActivityRespVO> activityList) {
// 1.1 获得所有的活动
List<RewardActivityDO> rewardActivityList = rewardActivityService.getRewardActivityListByStatusAndDateTimeLt(
CommonStatusEnum.ENABLE.getStatus(), now);
if (CollUtil.isEmpty(rewardActivityList)) {
return;
}
// 1.2 获得所有的商品信息
List<ProductSpuRespDTO> spuList = productSpuApi.getSpuList(spuIds);
if (CollUtil.isEmpty(spuList)) {
return;
}
Map<Long, Optional<RewardActivityDO>> spuIdAndActivityMap = spuIds.stream()
.collect(Collectors.toMap(
spuId -> spuId,
spuId -> rewardActivityList.stream()
.filter(activity -> activity.getProductSpuIds().contains(spuId))
.max(Comparator.comparing(RewardActivityDO::getCreateTime))));
for (Long supId : spuIdAndActivityMap.keySet()) {
if (spuIdAndActivityMap.get(supId).isEmpty()) {
// 2. 构建活动
for (RewardActivityDO rewardActivity : rewardActivityList) {
// 情况一:所有商品都能参加
if (PromotionProductScopeEnum.isAll(rewardActivity.getProductScope())) {
buildAppActivityRespVO(rewardActivity, spuIds, activityList);
}
// 情况二:指定商品参加
if (PromotionProductScopeEnum.isSpu(rewardActivity.getProductScope())) {
List<Long> fSpuIds = spuList.stream().map(ProductSpuRespDTO::getId).filter(id ->
rewardActivity.getProductScopeValues().contains(id)).toList();
buildAppActivityRespVO(rewardActivity, fSpuIds, activityList);
}
// 情况三:指定商品类型参加
if (PromotionProductScopeEnum.isCategory(rewardActivity.getProductScope())) {
List<Long> fSpuIds = spuList.stream().filter(spuItem -> rewardActivity.getProductScopeValues()
.contains(spuItem.getCategoryId())).map(ProductSpuRespDTO::getId).toList();
buildAppActivityRespVO(rewardActivity, fSpuIds, activityList);
}
}
}
private static void buildAppActivityRespVO(RewardActivityDO rewardActivity, Collection<Long> spuIds,
List<AppActivityRespVO> activityList) {
for (Long spuId : spuIds) {
// 校验商品是否已经加入过活动
if (anyMatch(activityList, appActivity -> ObjUtil.equal(appActivity.getId(), rewardActivity.getId()) &&
ObjUtil.equal(appActivity.getSpuId(), spuId))) {
continue;
}
RewardActivityDO rewardActivityDO = spuIdAndActivityMap.get(supId).get();
activityList.add(new AppActivityRespVO(rewardActivityDO.getId(), PromotionTypeEnum.REWARD_ACTIVITY.getType(),
rewardActivityDO.getName(), supId, rewardActivityDO.getStartTime(), rewardActivityDO.getEndTime()));
activityList.add(new AppActivityRespVO(rewardActivity.getId(),
PromotionTypeEnum.REWARD_ACTIVITY.getType(), rewardActivity.getName(), spuId,
rewardActivity.getStartTime(), rewardActivity.getEndTime()));
}
}

View File

@@ -1,29 +0,0 @@
package cn.iocoder.yudao.module.promotion.convert.reward;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityCreateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityRespVO;
import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityUpdateReqVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* 满减送活动 Convert
*
* @author 芋道源码
*/
@Mapper
public interface RewardActivityConvert {
RewardActivityConvert INSTANCE = Mappers.getMapper(RewardActivityConvert.class);
RewardActivityDO convert(RewardActivityCreateReqVO bean);
RewardActivityDO convert(RewardActivityUpdateReqVO bean);
RewardActivityRespVO convert(RewardActivityDO bean);
PageResult<RewardActivityRespVO> convertPage(PageResult<RewardActivityDO> page);
}

View File

@@ -50,7 +50,6 @@ public class CouponDO extends BaseDO {
*
* 枚举 {@link CouponStatusEnum}
*/
// TODO 芋艿:已作废?
private Integer status;
// TODO 芋艿:发放 adminid

View File

@@ -1,8 +1,8 @@
package cn.iocoder.yudao.module.promotion.dal.dataobject.reward;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionActivityStatusEnum;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionConditionTypeEnum;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
@@ -16,6 +16,7 @@ import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
/**
* 满减送活动 DO
@@ -40,7 +41,7 @@ public class RewardActivityDO extends BaseDO {
/**
* 状态
*
* 枚举 {@link PromotionActivityStatusEnum}
* 枚举 {@link CommonStatusEnum}
*/
private Integer status;
/**
@@ -71,7 +72,7 @@ public class RewardActivityDO extends BaseDO {
* 商品 SPU 编号的数组
*/
@TableField(typeHandler = LongListTypeHandler.class)
private List<Long> productSpuIds;
private List<Long> productScopeValues;
/**
* 优惠规则的数组
*/
@@ -104,13 +105,14 @@ public class RewardActivityDO extends BaseDO {
*/
private Integer point;
/**
* 赠送的优惠劵编号的数组
* 赠送的优惠劵
*
* key: 优惠劵模版编号
* value对应的优惠券数量
*
* 目的:用于订单支付后赠送优惠券
*/
private List<Long> couponIds;
/**
* 赠送的优惠券数量的数组
*/
private List<Integer> couponCounts;
private Map<Long, Integer> giveCouponTemplateCounts;
}

View File

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

View File

@@ -27,6 +27,7 @@ import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStat
import cn.iocoder.yudao.module.system.api.social.SocialClientApi;
import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaSubscribeMessageSendReqDTO;
import cn.iocoder.yudao.module.trade.api.order.TradeOrderApi;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderCancelTypeEnum;
import jakarta.annotation.Nullable;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
@@ -37,7 +38,10 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.time.LocalDateTime;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
@@ -335,7 +339,8 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
List<CombinationRecordDO> headAndRecords = updateBatchCombinationRecords(headRecord,
CombinationRecordStatusEnum.FAILED);
// 2. 订单取消
headAndRecords.forEach(item -> tradeOrderApi.cancelPaidOrder(item.getUserId(), item.getOrderId()));
headAndRecords.forEach(item -> tradeOrderApi.cancelPaidOrder(item.getUserId(), item.getOrderId(),
TradeOrderCancelTypeEnum.COMBINATION_CLOSE.getType()));
}
/**

View File

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

View File

@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.promotion.service.coupon;
import cn.hutool.core.collection.CollStreamUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
@@ -19,19 +20,19 @@ 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 jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import jakarta.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
import static java.util.Arrays.asList;
@@ -76,20 +77,6 @@ public class CouponServiceImpl implements CouponService {
}
}
@Override
public PageResult<CouponDO> getCouponPage(CouponPageReqVO pageReqVO) {
// 获得用户编号
if (StrUtil.isNotEmpty(pageReqVO.getNickname())) {
List<MemberUserRespDTO> users = memberUserApi.getUserListByNickname(pageReqVO.getNickname());
if (CollUtil.isEmpty(users)) {
return PageResult.empty();
}
pageReqVO.setUserIds(convertSet(users, MemberUserRespDTO::getId));
}
// 分页查询
return couponMapper.selectPage(pageReqVO);
}
@Override
public void useCoupon(Long id, Long userId, Long orderId) {
// 校验优惠劵
@@ -147,25 +134,8 @@ public class CouponServiceImpl implements CouponService {
}
@Override
public List<CouponDO> getCouponList(Long userId, Integer status) {
return couponMapper.selectListByUserIdAndStatus(userId, status);
}
private CouponDO validateCouponExists(Long id) {
CouponDO coupon = couponMapper.selectById(id);
if (coupon == null) {
throw exception(COUPON_NOT_EXISTS);
}
return coupon;
}
@Override
public Long getUnusedCouponCount(Long userId) {
return couponMapper.selectCountByUserIdAndStatus(userId, CouponStatusEnum.UNUSED.getStatus());
}
@Override
public void takeCoupon(Long templateId, Set<Long> userIds, CouponTakeTypeEnum takeType) {
@Transactional(rollbackFor = Exception.class)
public Map<Long, List<Long>> takeCoupon(Long templateId, Set<Long> userIds, CouponTakeTypeEnum takeType) {
CouponTemplateDO template = couponTemplateService.getCouponTemplate(templateId);
// 1. 过滤掉达到领取限制的用户
removeTakeLimitUser(userIds, template);
@@ -173,10 +143,77 @@ public class CouponServiceImpl implements CouponService {
validateCouponTemplateCanTake(template, userIds, takeType);
// 3. 批量保存优惠劵
couponMapper.insertBatch(convertList(userIds, userId -> CouponConvert.INSTANCE.convert(template, userId)));
List<CouponDO> couponList = convertList(userIds, userId -> CouponConvert.INSTANCE.convert(template, userId));
couponMapper.insertBatch(couponList);
// 3. 增加优惠劵模板的领取数量
// 4. 增加优惠劵模板的领取数量
couponTemplateService.updateCouponTemplateTakeCount(templateId, userIds.size());
return convertMultiMap(couponList, CouponDO::getUserId, CouponDO::getId);
}
@Override
public List<Long> takeCouponsByAdmin(Map<Long, Integer> giveCoupons, Long userId) {
if (CollUtil.isEmpty(giveCoupons)) {
return Collections.emptyList();
}
List<Long> couponIds = new ArrayList<>();
// 循环发放
for (Map.Entry<Long, Integer> entry : giveCoupons.entrySet()) {
try {
for (int i = 0; i < entry.getValue(); i++) {
Map<Long, List<Long>> userCouponIdsMap = getSelf().takeCoupon(entry.getKey(), CollUtil.newHashSet(userId),
CouponTakeTypeEnum.ADMIN);
findAndThen(userCouponIdsMap, userId, couponIds::addAll);
}
} catch (Exception e) {
log.error("[takeCouponsByAdmin][coupon({}) 优惠券发放失败]", entry, e);
}
}
return couponIds;
}
@Override
public void invalidateCouponsByAdmin(List<Long> giveCouponIds, Long userId) {
// 循环收回
for (Long couponId : giveCouponIds) {
try {
getSelf().invalidateCoupon(couponId, userId);
} catch (Exception e) {
log.error("[invalidateCouponsByAdmin][couponId({}) 收回优惠券失败]", couponId, e);
}
}
}
/**
* 【管理员】收回优惠券
*
* @param couponId 模版编号
* @param userId 用户编号
*/
@Transactional(rollbackFor = Exception.class)
public void invalidateCoupon(Long couponId, Long userId) {
// 1.1 校验优惠券
CouponDO coupon = couponMapper.selectByIdAndUserId(couponId, userId);
if (coupon == null) {
throw exception(COUPON_NOT_EXISTS);
}
// 1.2 校验模板
CouponTemplateDO couponTemplate = couponTemplateService.getCouponTemplate(coupon.getTemplateId());
if (couponTemplate == null) {
throw exception(COUPON_TEMPLATE_NOT_EXISTS);
}
// 1.3 校验优惠券是否已经使用,如若使用则先不管
if (ObjUtil.equal(coupon.getStatus(), CouponStatusEnum.USED.getStatus())) {
log.info("[invalidateCoupon][coupon({}) 已经使用,无法作废]", couponId);
return;
}
// 2.1 减少优惠劵模板的领取数量
couponTemplateService.updateCouponTemplateTakeCount(couponTemplate.getId(), -1);
// 2.2 作废优惠劵
couponMapper.deleteById(couponId);
}
@Override
@@ -188,24 +225,6 @@ public class CouponServiceImpl implements CouponService {
}
}
@Override
public Map<Long, Integer> getTakeCountMapByTemplateIds(Collection<Long> templateIds, Long userId) {
if (CollUtil.isEmpty(templateIds)) {
return Collections.emptyMap();
}
return couponMapper.selectCountByUserIdAndTemplateIdIn(userId, templateIds);
}
@Override
public List<CouponDO> getMatchCouponList(Long userId, AppCouponMatchReqVO matchReqVO) {
List<CouponDO> list = couponMapper.selectListByUserIdAndStatusAndUsePriceLeAndProductScope(userId,
CouponStatusEnum.UNUSED.getStatus(),
matchReqVO.getPrice(), matchReqVO.getSpuIds(), matchReqVO.getCategoryIds());
// 兜底逻辑:如果 CouponExpireJob 未执行status 未变成 EXPIRE ,但是 validEndTime 已经过期了,需要进行过滤
list.removeIf(coupon -> !LocalDateTimeUtils.isBetween(coupon.getValidStartTime(), coupon.getValidEndTime()));
return list;
}
@Override
public int expireCoupon() {
// 1. 查询待过期的优惠券
@@ -230,27 +249,6 @@ public class CouponServiceImpl implements CouponService {
return count;
}
@Override
public Map<Long, Boolean> getUserCanCanTakeMap(Long userId, List<CouponTemplateDO> templates) {
// 1. 未登录时,都显示可以领取
Map<Long, Boolean> userCanTakeMap = convertMap(templates, CouponTemplateDO::getId, templateId -> true);
if (userId == null) {
return userCanTakeMap;
}
// 2.1 过滤领取数量无限制的
Set<Long> templateIds = convertSet(templates, CouponTemplateDO::getId, template -> template.getTakeLimitCount() != -1);
// 2.2 检查用户领取的数量是否超过限制
if (CollUtil.isNotEmpty(templateIds)) {
Map<Long, Integer> couponTakeCountMap = this.getTakeCountMapByTemplateIds(templateIds, userId);
for (CouponTemplateDO template : templates) {
Integer takeCount = couponTakeCountMap.get(template.getId());
userCanTakeMap.put(template.getId(), takeCount == null || takeCount < template.getTakeLimitCount());
}
}
return userCanTakeMap;
}
/**
* 过期单个优惠劵
*
@@ -322,11 +320,84 @@ public class CouponServiceImpl implements CouponService {
userIds.removeIf(userId -> MapUtil.getInt(userTakeCountMap, userId, 0) >= couponTemplate.getTakeLimitCount());
}
//======================= 查询相关 =======================
@Override
public Long getUnusedCouponCount(Long userId) {
return couponMapper.selectCountByUserIdAndStatus(userId, CouponStatusEnum.UNUSED.getStatus());
}
@Override
public PageResult<CouponDO> getCouponPage(CouponPageReqVO pageReqVO) {
// 获得用户编号
if (StrUtil.isNotEmpty(pageReqVO.getNickname())) {
List<MemberUserRespDTO> users = memberUserApi.getUserListByNickname(pageReqVO.getNickname());
if (CollUtil.isEmpty(users)) {
return PageResult.empty();
}
pageReqVO.setUserIds(convertSet(users, MemberUserRespDTO::getId));
}
// 分页查询
return couponMapper.selectPage(pageReqVO);
}
@Override
public List<CouponDO> getCouponList(Long userId, Integer status) {
return couponMapper.selectListByUserIdAndStatus(userId, status);
}
@Override
public Map<Long, Integer> getTakeCountMapByTemplateIds(Collection<Long> templateIds, Long userId) {
if (CollUtil.isEmpty(templateIds)) {
return Collections.emptyMap();
}
return couponMapper.selectCountByUserIdAndTemplateIdIn(userId, templateIds);
}
@Override
public List<CouponDO> getMatchCouponList(Long userId, AppCouponMatchReqVO matchReqVO) {
List<CouponDO> list = couponMapper.selectListByUserIdAndStatusAndUsePriceLeAndProductScope(userId,
CouponStatusEnum.UNUSED.getStatus(),
matchReqVO.getPrice(), matchReqVO.getSpuIds(), matchReqVO.getCategoryIds());
// 兜底逻辑:如果 CouponExpireJob 未执行status 未变成 EXPIRE ,但是 validEndTime 已经过期了,需要进行过滤
list.removeIf(coupon -> !LocalDateTimeUtils.isBetween(coupon.getValidStartTime(), coupon.getValidEndTime()));
return list;
}
@Override
public Map<Long, Boolean> getUserCanCanTakeMap(Long userId, List<CouponTemplateDO> templates) {
// 1. 未登录时,都显示可以领取
Map<Long, Boolean> userCanTakeMap = convertMap(templates, CouponTemplateDO::getId, templateId -> true);
if (userId == null) {
return userCanTakeMap;
}
// 2.1 过滤领取数量无限制的
Set<Long> templateIds = convertSet(templates, CouponTemplateDO::getId, template -> template.getTakeLimitCount() != -1);
// 2.2 检查用户领取的数量是否超过限制
if (CollUtil.isNotEmpty(templateIds)) {
Map<Long, Integer> couponTakeCountMap = this.getTakeCountMapByTemplateIds(templateIds, userId);
for (CouponTemplateDO template : templates) {
Integer takeCount = couponTakeCountMap.get(template.getId());
userCanTakeMap.put(template.getId(), takeCount == null || takeCount < template.getTakeLimitCount());
}
}
return userCanTakeMap;
}
@Override
public CouponDO getCoupon(Long userId, Long id) {
return couponMapper.selectByIdAndUserId(id, userId);
}
private CouponDO validateCouponExists(Long id) {
CouponDO coupon = couponMapper.selectById(id);
if (coupon == null) {
throw exception(COUPON_NOT_EXISTS);
}
return coupon;
}
/**
* 获得自身的代理对象,解决 AOP 生效问题
*
@@ -335,4 +406,5 @@ public class CouponServiceImpl implements CouponService {
private CouponServiceImpl getSelf() {
return SpringUtil.getBean(getClass());
}
}

View File

@@ -104,7 +104,10 @@ public class DiscountActivityServiceImpl implements DiscountActivityService {
}
// 计算新增的记录
List<DiscountProductDO> newDiscountProducts = convertList(updateReqVO.getProducts(),
product -> DiscountActivityConvert.INSTANCE.convert(product).setActivityId(updateReqVO.getId()));
product -> DiscountActivityConvert.INSTANCE.convert(product)
.setActivityId(updateReqVO.getId())
.setActivityStartTime(updateReqVO.getStartTime())
.setActivityEndTime(updateReqVO.getEndTime()));
newDiscountProducts.removeIf(product -> dbDiscountProducts.stream().anyMatch(
dbProduct -> DiscountActivityConvert.INSTANCE.isEquals(dbProduct, product))); // 如果匹配到,说明是更新的
if (CollectionUtil.isNotEmpty(newDiscountProducts)) {

View File

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

View File

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

View File

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