【代码优化】商城:价格计算相关逻辑

This commit is contained in:
YunaiV 2024-09-15 15:38:38 +08:00
parent 2876c7ce16
commit fee7267799
42 changed files with 274 additions and 849 deletions

View File

@ -4,10 +4,6 @@ import cn.hutool.core.collection.CollUtil;
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.member.api.level.MemberLevelApi;
import cn.iocoder.yudao.module.member.api.level.dto.MemberLevelRespDTO;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuDetailRespVO;
import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageReqVO;
import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuRespVO;
@ -51,11 +47,6 @@ public class AppProductSpuController {
@Resource
private ProductBrowseHistoryService productBrowseHistoryService;
@Resource
private MemberLevelApi memberLevelApi;
@Resource
private MemberUserApi memberUserApi;
@GetMapping("/list-by-ids")
@Operation(summary = "获得商品 SPU 列表")
@Parameter(name = "ids", description = "编号列表", required = true)
@ -68,9 +59,6 @@ public class AppProductSpuController {
// 拼接返回
list.forEach(spu -> spu.setSalesCount(spu.getSalesCount() + spu.getVirtualSalesCount()));
List<AppProductSpuRespVO> voList = BeanUtils.toBean(list, AppProductSpuRespVO.class);
// 处理 vip 价格
// MemberLevelRespDTO memberLevel = getMemberLevel();
// voList.forEach(vo -> vo.setVipPrice(calculateVipPrice(vo.getPrice(), memberLevel)));
return success(voList);
}
@ -85,9 +73,6 @@ public class AppProductSpuController {
// 拼接返回
pageResult.getList().forEach(spu -> spu.setSalesCount(spu.getSalesCount() + spu.getVirtualSalesCount()));
PageResult<AppProductSpuRespVO> voPageResult = BeanUtils.toBean(pageResult, AppProductSpuRespVO.class);
// 处理 vip 价格
// MemberLevelRespDTO memberLevel = getMemberLevel();
// voPageResult.getList().forEach(vo -> vo.setVipPrice(calculateVipPrice(vo.getPrice(), memberLevel)));
return success(voPageResult);
}
@ -115,37 +100,7 @@ public class AppProductSpuController {
spu.setSalesCount(spu.getSalesCount() + spu.getVirtualSalesCount());
AppProductSpuDetailRespVO spuVO = BeanUtils.toBean(spu, AppProductSpuDetailRespVO.class)
.setSkus(BeanUtils.toBean(skus, AppProductSpuDetailRespVO.Sku.class));
// 处理 vip 价格
MemberLevelRespDTO memberLevel = getMemberLevel();
spuVO.setVipPrice(calculateVipPrice(spuVO.getPrice(), memberLevel));
return success(spuVO);
}
private MemberLevelRespDTO getMemberLevel() {
Long userId = getLoginUserId();
if (userId == null) {
return null;
}
MemberUserRespDTO user = memberUserApi.getUser(userId);
if (user.getLevelId() == null || user.getLevelId() <= 0) {
return null;
}
return memberLevelApi.getMemberLevel(user.getLevelId());
}
/**
* 计算会员 VIP 优惠价格
*
* @param price 原价
* @param memberLevel 会员等级
* @return 优惠价格
*/
public Integer calculateVipPrice(Integer price, MemberLevelRespDTO memberLevel) {
if (memberLevel == null || memberLevel.getDiscountPercent() == null) {
return null;
}
Integer newPrice = price * memberLevel.getDiscountPercent() / 100;
return price - newPrice;
}
}

View File

@ -46,9 +46,6 @@ public class AppProductSpuDetailRespVO {
@Schema(description = "市场价,单位使用:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer marketPrice;
@Schema(description = "VIP 价格,单位使用:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "968") // 通过会员等级计算出折扣后价格
private Integer vipPrice;
@Schema(description = "库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "666")
private Integer stock;

View File

@ -38,9 +38,6 @@ public class AppProductSpuRespVO {
@Schema(description = "市场价,单位使用:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer marketPrice;
// @Schema(description = "VIP 价格,单位使用:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "968") // 通过会员等级计算出折扣后价格
// private Integer vipPrice;
@Schema(description = "库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "666")
private Integer stock;

View File

@ -46,10 +46,12 @@ public class DiscountProductRespDTO {
* 活动标题
*/
private String activityName;
/**
* 活动开始时间点
*/
private LocalDateTime activityStartTime;
/**
* 活动结束时间点
*
* 冗余 {@link DiscountActivityDO#getEndTime()}
*/
private LocalDateTime activityEndTime;

View File

@ -13,15 +13,6 @@ import java.util.List;
*/
public interface RewardActivityApi {
/**
* 获得当前时间内开启的满减送活动
*
* @param status 状态
* @param dateTime 当前时间即筛选 <= dateTime 的满减送活动
* @return 满减送活动列表
*/
List<RewardActivityMatchRespDTO> getRewardActivityListByStatusAndNow(Integer status, LocalDateTime dateTime);
/**
* 获取指定 spu 编号最近参加的活动每个 spuId 只返回一条记录
*
@ -31,4 +22,5 @@ public interface RewardActivityApi {
* @return 满减送活动列表
*/
List<RewardActivityMatchRespDTO> getRewardActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime);
}

View File

@ -15,7 +15,6 @@ public interface ErrorCodeConstants {
ErrorCode DISCOUNT_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_001_002, "限时折扣活动已关闭,不能修改");
ErrorCode DISCOUNT_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED = new ErrorCode(1_013_001_003, "限时折扣活动未关闭,不能删除");
ErrorCode DISCOUNT_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_001_004, "限时折扣活动已关闭,不能重复关闭");
ErrorCode DISCOUNT_ACTIVITY_TYPE_NOT_EXISTS = new ErrorCode(1_013_001_005, "限时折扣活动类型不存在");
// ========== Banner 相关 1-013-002-000 ============
ErrorCode BANNER_NOT_EXISTS = new ErrorCode(1_013_002_000, "Banner 不存在");
@ -44,7 +43,6 @@ public interface ErrorCodeConstants {
ErrorCode REWARD_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED = new ErrorCode(1_013_006_003, "满减送活动未关闭,不能删除");
ErrorCode REWARD_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_006_004, "满减送活动已关闭,不能重复关闭");
ErrorCode REWARD_ACTIVITY_SCOPE_EXISTS = new ErrorCode(1_013_006_005, "与该时间段已存在的满减送活动商品范围冲突");
ErrorCode REWARD_ACTIVITY_TYPE_NOT_EXISTS = new ErrorCode(1_013_006_006, "满减送活动类型不存在");
// ========== TODO 空着 1-013-007-000 ============

View File

@ -1,12 +1,13 @@
package cn.iocoder.yudao.module.promotion.api.discount;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.promotion.api.discount.dto.DiscountProductRespDTO;
import cn.iocoder.yudao.module.promotion.convert.discount.DiscountActivityConvert;
import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountProductDO;
import cn.iocoder.yudao.module.promotion.service.discount.DiscountActivityService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import jakarta.annotation.Resource;
import java.util.Collection;
import java.util.List;
@ -24,7 +25,8 @@ public class DiscountActivityApiImpl implements DiscountActivityApi {
@Override
public List<DiscountProductRespDTO> getMatchDiscountProductList(Collection<Long> skuIds) {
return discountActivityService.getMatchDiscountProductList(skuIds);
List<DiscountProductDO> list = discountActivityService.getMatchDiscountProductList(skuIds);
return BeanUtils.toBean(list, DiscountProductRespDTO.class);
}
}

View File

@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.promotion.api.reward;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.promotion.api.reward.dto.RewardActivityMatchRespDTO;
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 jakarta.annotation.Resource;
@ -26,15 +25,9 @@ public class RewardActivityApiImpl implements RewardActivityApi {
private RewardActivityService rewardActivityService;
@Override
public List<RewardActivityMatchRespDTO> getRewardActivityListByStatusAndNow(Integer status, LocalDateTime dateTime) {
List<RewardActivityDO> list = rewardActivityService.getRewardActivityListByStatusAndDateTimeLt(status, dateTime);
public List<RewardActivityMatchRespDTO> getRewardActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime) {
List<RewardActivityDO> list = rewardActivityService.getRewardActivityBySpuIdsAndStatusAndDateTimeLt(spuIds, status, dateTime);
return BeanUtils.toBean(list, RewardActivityMatchRespDTO.class);
}
@Override
public List<RewardActivityMatchRespDTO> getRewardActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime) {
List<RewardActivityDO> rewardActivityBySpuIdsAndStatusAndDateTimeLt = rewardActivityService.getRewardActivityBySpuIdsAndStatusAndDateTimeLt(spuIds, status, dateTime);
return RewardActivityConvert.INSTANCE.convertList(rewardActivityBySpuIdsAndStatusAndDateTimeLt);
}
}

View File

@ -2,22 +2,19 @@ package cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTemplateValidityTypeEnum;
import com.fasterxml.jackson.annotation.JsonFormat;
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.Validator;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
@ -40,11 +37,11 @@ public class CouponTemplateBaseVO {
private String description;
@Schema(description = "发行总量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") // -1 - 则表示不限制发放数量
@NotNull(message = "发行总量不能为空", groups = {User.class})
@NotNull(message = "发行总量不能为空")
private Integer totalCount;
@Schema(description = "每人限领个数", requiredMode = Schema.RequiredMode.REQUIRED, example = "66") // -1 - 则表示不限制
@NotNull(message = "每人限领个数不能为空", groups = {User.class})
@NotNull(message = "每人限领个数不能为空")
private Integer takeLimitCount;
@Schema(description = "领取方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@ -92,16 +89,13 @@ public class CouponTemplateBaseVO {
private Integer discountType;
@Schema(description = "折扣百分比", example = "80") // 例如说80% 80
@NotNull(message = "折扣百分比不能为空", groups = {Percent.class})
private Integer discountPercent;
@Schema(description = "优惠金额", example = "10")
@Min(value = 0, message = "优惠金额需要大于等于 0")
@NotNull(message = "优惠金额不能为空", groups = {Price.class})
private Integer discountPrice;
@Schema(description = "折扣上限", example = "100") // 单位仅在 discountType PERCENT 使用
@NotNull(message = "折扣上限不能为空", groups = {Percent.class})
private Integer discountLimitPrice;
@AssertTrue(message = "商品范围编号的数组不能为空")
@ -160,54 +154,4 @@ public class CouponTemplateBaseVO {
|| discountLimitPrice != null;
}
//-------------------------领取方式校验start----------------------------
/**
* 直接领取
*/
public interface User {
}
/**
* 指定发放
*/
public interface Admin {
}
//-------------------------领取方式校验end------------------------------
//-------------------------优惠类型校验start----------------------------
/**
* 满减
*/
public interface Price {
}
/**
* 折扣
*/
public interface Percent {
}
//-------------------------优惠类型校验end------------------------------
public void validate(Validator validator) {
//领取方式校验
if (CouponTakeTypeEnum.USER.getType().equals(takeType)) {
ValidationUtils.validate(validator, this, User.class);
} else if (CouponTakeTypeEnum.ADMIN.getType().equals(takeType)) {
ValidationUtils.validate(validator, this, Admin.class);
}
//优惠类型校验
if (PromotionDiscountTypeEnum.PRICE.getType().equals(discountType)){
ValidationUtils.validate(validator, this, Price.class);
} else if (PromotionDiscountTypeEnum.PERCENT.getType().equals(discountType)) {
ValidationUtils.validate(validator, this, Percent.class);
}
}
}

View File

@ -2,11 +2,9 @@ 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;
@ -151,46 +149,23 @@ public class AppActivityController {
}
private void getRewardActivityList(Collection<Long> spuIds, LocalDateTime now, List<AppActivityRespVO> activityList) {
// TODO @puhui999 3 范围不只 spuId还有 categoryId全部下次 fix
List<RewardActivityDO> rewardActivityList = rewardActivityService.getRewardActivityBySpuIdsAndStatusAndDateTimeLt(
List<RewardActivityDO> rewardActivities = rewardActivityService.getRewardActivityBySpuIdsAndStatusAndDateTimeLt(
spuIds, CommonStatusEnum.ENABLE.getStatus(), now);
if (CollUtil.isEmpty(rewardActivityList)) {
if (CollUtil.isEmpty(rewardActivities)) {
return;
}
Map<Long, Optional<RewardActivityDO>> spuIdAndActivityMap = spuIds.stream()
.collect(Collectors.toMap(
spuId -> spuId,
spuId -> rewardActivityList.stream()
.filter(activity ->
( activity.getProductScopeValues()!=null &&
(activity.getProductScopeValues().contains(spuId) ||
activity.getProductScopeValues().contains(productSpuApi.getSpu(spuId).getCategoryId()))) ||
activity.getProductScope()==1
)
Map<Long, Optional<RewardActivityDO>> spuIdAndActivityMap = spuIds.stream().collect(Collectors.toMap(spuId -> spuId, spuId -> rewardActivities.stream()
.filter(activity -> PromotionProductScopeEnum.isAll(activity.getProductScope())
|| PromotionProductScopeEnum.isSpu(activity.getProductScope()) // 商品范围
&& CollUtil.contains(activity.getProductScopeValues(), spuId)
|| PromotionProductScopeEnum.isCategory(activity.getProductScope()) // 分类范围
&& CollUtil.contains(activity.getProductScopeValues(), productSpuApi.getSpu(spuId).getCategoryId()))
.max(Comparator.comparing(RewardActivityDO::getCreateTime))));
for (Long supId : spuIdAndActivityMap.keySet()) {
if (spuIdAndActivityMap.get(supId).isEmpty()) {
continue;
}
RewardActivityDO rewardActivityDO = spuIdAndActivityMap.get(supId).get();
activityList.add(new AppActivityRespVO(rewardActivityDO.getId(), PromotionTypeEnum.REWARD_ACTIVITY.getType(),
rewardActivityDO.getName(), supId, rewardActivityDO.getStartTime(), rewardActivityDO.getEndTime()));
}
}
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;
}
activityList.add(new AppActivityRespVO(rewardActivity.getId(),
PromotionTypeEnum.REWARD_ACTIVITY.getType(), rewardActivity.getName(), spuId,
rewardActivity.getStartTime(), rewardActivity.getEndTime()));
spuIdAndActivityMap.get(supId).ifPresent(rewardActivity -> activityList.add(
new AppActivityRespVO(rewardActivity.getId(), PromotionTypeEnum.REWARD_ACTIVITY.getType(),
rewardActivity.getName(), supId, rewardActivity.getStartTime(), rewardActivity.getEndTime())));
}
}

View File

@ -20,6 +20,12 @@ public class AppRewardActivityRespVO {
@Schema(description = "活动标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "满啦满啦")
private String name;
@Schema(description = "开始时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime startTime;
@Schema(description = "结束时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime endTime;
@Schema(description = "条件类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer conditionType;
@ -32,10 +38,4 @@ public class AppRewardActivityRespVO {
@Schema(description = "优惠规则的数组")
private List<RewardActivityBaseVO.Rule> rules;
@Schema(description = "开始时间")
private LocalDateTime startTime;
@Schema(description = "结束时间")
private LocalDateTime endTime;
}

View File

@ -29,8 +29,6 @@ public interface CouponConvert {
CouponRespDTO convert(CouponDO bean);
AppCouponMatchRespVO convert2(CouponDO bean);
default CouponDO convert(CouponTemplateDO template, Long userId) {
CouponDO couponDO = new CouponDO()
.setTemplateId(template.getId())

View File

@ -5,7 +5,6 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
import cn.iocoder.yudao.module.promotion.api.discount.dto.DiscountProductRespDTO;
import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.*;
import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountActivityDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountProductDO;
@ -34,9 +33,6 @@ public interface DiscountActivityConvert {
List<DiscountActivityRespVO> convertList(List<DiscountActivityDO> list);
List<DiscountActivityBaseVO.Product> convertList2(List<DiscountProductDO> list);
List<DiscountProductRespDTO> convertList02(List<DiscountProductDO> list);
PageResult<DiscountActivityRespVO> convertPage(PageResult<DiscountActivityDO> page);
default PageResult<DiscountActivityRespVO> convertPage(PageResult<DiscountActivityDO> page,

View File

@ -1,33 +0,0 @@
package cn.iocoder.yudao.module.promotion.convert.reward;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
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.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;
import java.util.List;
/**
* 满减送活动 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);
List<RewardActivityMatchRespDTO> convertList(List<RewardActivityDO> rewardActivityBySpuIdsAndStatusAndDateTimeLt);
}

View File

@ -66,10 +66,16 @@ public class DiscountProductDO extends BaseDO {
*/
private Integer discountPrice;
/**
* 活动标题
*
* 冗余 {@link DiscountActivityDO#getName()}
*/
private String activityName;
/**
* 活动状态
*
* 关联 {@link DiscountActivityDO#getStatus()}
* 冗余 {@link DiscountActivityDO#getStatus()}
*/
private Integer activityStatus;
/**

View File

@ -80,15 +80,6 @@ public interface CouponMapper extends BaseMapperX<CouponDO> {
return convertMap(list, map -> MapUtil.getLong(map, templateIdAlias), map -> MapUtil.getInt(map, countAlias));
}
default List<CouponDO> selectListByUserIdAndStatusAndUsePriceLeAndProductScope(
Long userId, Integer status) {
List<CouponDO> couponDOS = selectList(new LambdaQueryWrapperX<CouponDO>()
.eq(CouponDO::getUserId, userId)
.eq(CouponDO::getStatus, status)
);
return couponDOS;
}
default List<CouponDO> selectListByStatusAndValidEndTimeLe(Integer status, LocalDateTime validEndTime) {
return selectList(new LambdaQueryWrapperX<CouponDO>()
.eq(CouponDO::getStatus, status)

View File

@ -70,7 +70,7 @@ public interface CouponTemplateMapper extends BaseMapperX<CouponTemplateDO> {
.in(CouponTemplateDO::getTakeType, canTakeTypes) // 2. 领取方式一致
.and(ww -> ww.gt(CouponTemplateDO::getValidEndTime, LocalDateTime.now()) // 3.1 未过期
.or().eq(CouponTemplateDO::getValidityType, CouponTemplateValidityTypeEnum.TERM.getType())) // 3.2 领取之后
.apply(" (take_count < total_count OR total_count = -1 or total_count is null)"); // 4. 剩余数量大于 0或者无限领取,或者是指定发放的券
.apply(" (take_count < total_count OR total_count = -1)"); // 4. 剩余数量大于 0或者无限领取
}
return canTakeConsumer;
}

View File

@ -1,12 +1,12 @@
package cn.iocoder.yudao.module.promotion.dal.mysql.discount;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.promotion.api.discount.dto.DiscountProductRespDTO;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountProductDO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@ -19,20 +19,21 @@ import java.util.Map;
@Mapper
public interface DiscountProductMapper extends BaseMapperX<DiscountProductDO> {
default List<DiscountProductDO> selectListBySkuId(Collection<Long> skuIds) {
return selectList(DiscountProductDO::getSkuId, skuIds);
}
default List<DiscountProductDO> selectListByActivityId(Long activityId) {
return selectList(DiscountProductDO::getActivityId, activityId);
}
default List<DiscountProductDO> selectListByActivityId(Collection<Long> activityIds) {
return selectList(DiscountProductDO::getActivityId, activityIds);
default List<DiscountProductDO> selectListBySkuIds(Collection<Long> skuIds) {
return selectList(DiscountProductDO::getSkuId, skuIds);
}
// TODO @zhangshuai逻辑里尽量避免写 join 语句哈你可以看看这个查询有什么办法优化目前的一个思路是分 2 次查询性能也是 ok
List<DiscountProductRespDTO> getMatchDiscountProductList(@Param("skuIds") Collection<Long> skuIds);
default List<DiscountProductDO> selectListByStatusAndDateTimeLt(Collection<Long> skuIds, Integer status, LocalDateTime dateTime) {
return selectList(new LambdaQueryWrapperX<DiscountProductDO>()
.in(DiscountProductDO::getSkuId, skuIds)
.eq(DiscountProductDO::getActivityStatus,status)
.lt(DiscountProductDO::getActivityStartTime, dateTime)
.gt(DiscountProductDO::getActivityEndTime, dateTime));
}
/**
* 查询出指定 spuId spu 参加的活动最接近现在的一条记录多个的话一个 spuId 对应一个最近的活动编号

View File

@ -7,7 +7,6 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityPageReqVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDateTime;
@ -31,34 +30,7 @@ public interface RewardActivityMapper extends BaseMapperX<RewardActivityDO> {
.orderByDesc(RewardActivityDO::getId));
}
default List<RewardActivityDO> selectListBySpuIdsAndStatus(Collection<Long> spuIds, Integer status) {
Function<Collection<Long>, String> productScopeValuesFindInSetFunc = ids -> ids.stream()
.map(id -> StrUtil.format("FIND_IN_SET({}, product_scope_values) ", id))
.collect(Collectors.joining(" OR "));
return selectList(new QueryWrapper<RewardActivityDO>()
.eq("status", status)
.apply(productScopeValuesFindInSetFunc.apply(spuIds)));
}
/**
* 获取指定活动编号的活动列表且
* 开始时间和结束时间小于给定时间 dateTime 的活动列表
*
* @param status 状态
* @param dateTime 指定日期
* @return 活动列表
*/
default List<RewardActivityDO> selectListByStatusAndDateTimeLt(Integer status, LocalDateTime dateTime) {
return selectList(new LambdaQueryWrapperX<RewardActivityDO>()
.eq(RewardActivityDO::getStatus, status)
.lt(RewardActivityDO::getStartTime, dateTime)
.gt(RewardActivityDO::getEndTime, dateTime)// 开始时间 < 指定时间 < 结束时间也就是说获取指定时间段的活动
.orderByAsc(RewardActivityDO::getStartTime)
);
}
default List<RewardActivityDO> getRewardActivityByStatusAndDateTimeLt(Collection<Long> spuIds,Collection<Long> categoryIds, Integer status, LocalDateTime dateTime) {
//拼接通用券查询语句
default List<RewardActivityDO> selectListByStatusAndDateTimeLt(Collection<Long> spuIds, Collection<Long> categoryIds, Integer status, LocalDateTime dateTime) {
Function<Collection<Long>, String> productScopeValuesFindInSetFunc = ids -> ids.stream()
.map(id -> StrUtil.format("FIND_IN_SET({}, product_scope_values) ", id))
.collect(Collectors.joining(" OR "));

View File

@ -1,16 +1,12 @@
package cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillconfig;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
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.seckill.vo.config.SeckillConfigPageReqVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillConfigDO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
@Mapper

View File

@ -279,7 +279,7 @@ public class CouponServiceImpl implements CouponService {
}
}
// 校验领取方式
if (ObjectUtil.notEqual(couponTemplate.getTakeType(), takeType.getValue())) {
if (ObjectUtil.notEqual(couponTemplate.getTakeType(), takeType.getType())) {
throw exception(COUPON_TEMPLATE_CANNOT_TAKE);
}
}

View File

@ -12,11 +12,10 @@ import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
import cn.iocoder.yudao.module.promotion.dal.mysql.coupon.CouponTemplateMapper;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import jakarta.validation.Validator;
import org.springframework.validation.annotation.Validated;
import jakarta.annotation.Resource;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
@ -41,13 +40,9 @@ public class CouponTemplateServiceImpl implements CouponTemplateService {
private ProductCategoryApi productCategoryApi;
@Resource
private ProductSpuApi productSpuApi;
@Resource
private Validator validator;
@Override
public Long createCouponTemplate(CouponTemplateCreateReqVO createReqVO) {
// 校验参数
createReqVO.validate(validator);
// 校验商品范围
validateProductScope(createReqVO.getProductScope(), createReqVO.getProductScopeValues());
// 插入

View File

@ -1,7 +1,6 @@
package cn.iocoder.yudao.module.promotion.service.discount;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.promotion.api.discount.dto.DiscountProductRespDTO;
import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.DiscountActivityCreateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.DiscountActivityPageReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.DiscountActivityUpdateReqVO;
@ -28,7 +27,7 @@ public interface DiscountActivityService {
* @param skuIds SKU 编号数组
* @return 匹配的限时折扣商品
*/
List<DiscountProductRespDTO> getMatchDiscountProductList(Collection<Long> skuIds);
List<DiscountProductDO> getMatchDiscountProductList(Collection<Long> skuIds);
/**
* 创建限时折扣活动

View File

@ -6,7 +6,6 @@ import cn.hutool.core.map.MapUtil;
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.promotion.api.discount.dto.DiscountProductRespDTO;
import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.DiscountActivityBaseVO;
import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.DiscountActivityCreateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.DiscountActivityPageReqVO;
@ -16,8 +15,6 @@ 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.mysql.discount.DiscountActivityMapper;
import cn.iocoder.yudao.module.promotion.dal.mysql.discount.DiscountProductMapper;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionActivityStatusEnum;
import cn.iocoder.yudao.module.promotion.util.PromotionUtils;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -28,7 +25,6 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
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.convertList;
@ -50,8 +46,8 @@ public class DiscountActivityServiceImpl implements DiscountActivityService {
private DiscountProductMapper discountProductMapper;
@Override
public List<DiscountProductRespDTO> getMatchDiscountProductList(Collection<Long> skuIds) {
return discountProductMapper.getMatchDiscountProductList(skuIds);
public List<DiscountProductDO> getMatchDiscountProductList(Collection<Long> skuIds) {
return discountProductMapper.selectListByStatusAndDateTimeLt(skuIds, CommonStatusEnum.ENABLE.getStatus(), LocalDateTime.now());
}
@Override
@ -66,10 +62,10 @@ public class DiscountActivityServiceImpl implements DiscountActivityService {
discountActivityMapper.insert(discountActivity);
// 插入商品
List<DiscountProductDO> discountProducts = BeanUtils.toBean(createReqVO.getProducts(), DiscountProductDO.class,
product -> product.setActivityId(discountActivity.getId()).setActivityStatus(discountActivity.getStatus())
product -> product.setActivityId(discountActivity.getId())
.setActivityName(discountActivity.getName()).setActivityStatus(discountActivity.getStatus())
.setActivityStartTime(createReqVO.getStartTime()).setActivityEndTime(createReqVO.getEndTime()));
discountProductMapper.insertBatch(discountProducts);
// 返回
return discountActivity.getId();
}
@ -85,8 +81,7 @@ public class DiscountActivityServiceImpl implements DiscountActivityService {
validateDiscountActivityProductConflicts(updateReqVO.getId(), updateReqVO.getProducts());
// 更新活动
DiscountActivityDO updateObj = DiscountActivityConvert.INSTANCE.convert(updateReqVO)
.setStatus(PromotionUtils.calculateActivityStatus(updateReqVO.getEndTime()));
DiscountActivityDO updateObj = DiscountActivityConvert.INSTANCE.convert(updateReqVO);
discountActivityMapper.updateById(updateObj);
// 更新商品
updateDiscountProduct(updateReqVO);
@ -101,12 +96,13 @@ public class DiscountActivityServiceImpl implements DiscountActivityService {
discountProductDO -> updateReqVO.getProducts().stream()
.noneMatch(product -> DiscountActivityConvert.INSTANCE.isEquals(discountProductDO, product)));
if (CollUtil.isNotEmpty(deleteIds)) {
discountProductMapper.deleteBatchIds(deleteIds);
discountProductMapper.deleteByIds(deleteIds);
}
// 计算新增的记录
List<DiscountProductDO> newDiscountProducts = convertList(updateReqVO.getProducts(),
product -> DiscountActivityConvert.INSTANCE.convert(product)
.setActivityId(updateReqVO.getId())
.setActivityName(updateReqVO.getName())
.setActivityStartTime(updateReqVO.getStartTime())
.setActivityEndTime(updateReqVO.getEndTime()));
newDiscountProducts.removeIf(product -> dbDiscountProducts.stream().anyMatch(
@ -127,11 +123,9 @@ public class DiscountActivityServiceImpl implements DiscountActivityService {
return;
}
// 查询商品参加的活动
// TODO @zhangshuai下面 121 这个查询是不是不用做呀直接 convert skuId 集合就 ok
List<DiscountProductDO> list = discountProductMapper.selectListByActivityId(id);
// TODO @zhangshuai一般简单的 stream 方法建议是使用 CollectionUtils例如说这里是 convertList 对把
List<Long> skuIds = list.stream().map(item -> item.getSkuId()).collect(Collectors.toList());
List<DiscountProductRespDTO> matchDiscountProductList = getMatchDiscountProductList(skuIds);
List<DiscountProductDO> matchDiscountProductList = discountProductMapper.selectListBySkuIds(
convertSet(list, DiscountProductDO::getSkuId));
if (id != null) { // 排除自己这个活动
matchDiscountProductList.removeIf(product -> id.equals(product.getActivityId()));
}
@ -150,7 +144,7 @@ public class DiscountActivityServiceImpl implements DiscountActivityService {
}
// 更新
DiscountActivityDO updateObj = new DiscountActivityDO().setId(id).setStatus(PromotionActivityStatusEnum.CLOSE.getStatus());
DiscountActivityDO updateObj = new DiscountActivityDO().setId(id).setStatus(CommonStatusEnum.DISABLE.getStatus());
discountActivityMapper.updateById(updateObj);
}

View File

@ -63,15 +63,6 @@ public interface RewardActivityService {
*/
PageResult<RewardActivityDO> getRewardActivityPage(RewardActivityPageReqVO pageReqVO);
/**
* 开始时间 < 指定时间 < 结束时间也就是说获取指定时间段的活动
*
* @param status 状态
* @param dateTime 当前日期时间
* @return 满减送活动列表
*/
List<RewardActivityDO> getRewardActivityListByStatusAndDateTimeLt(Integer status, LocalDateTime dateTime);
/**
* 获取指定 spu 编号最近参加的活动每个 spuId 只返回一条记录
*
@ -80,6 +71,8 @@ public interface RewardActivityService {
* @param dateTime 当前日期时间
* @return 满减送活动列表
*/
List<RewardActivityDO> getRewardActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime);
List<RewardActivityDO> getRewardActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds,
Integer status,
LocalDateTime dateTime);
}

View File

@ -12,22 +12,15 @@ import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivi
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;
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 java.util.stream.Collectors;
import java.util.*;
import static cn.hutool.core.collection.CollUtil.intersectionDistinct;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@ -59,12 +52,8 @@ public class RewardActivityServiceImpl implements RewardActivityService {
validateRewardActivitySpuConflicts(null, createReqVO);
// 插入
RewardActivityDO rewardActivity = RewardActivityConvert.INSTANCE.convert(createReqVO)
.setStatus(
PromotionUtils.calculateActivityStatus(createReqVO.getEndTime()).equals(CommonStatusEnum.DISABLE.getStatus())?
PromotionActivityStatusEnum.WAIT.getStatus():
PromotionActivityStatusEnum.RUN.getStatus()
);
RewardActivityDO rewardActivity = BeanUtils.toBean(createReqVO, RewardActivityDO.class)
.setStatus(CommonStatusEnum.ENABLE.getStatus());
rewardActivityMapper.insert(rewardActivity);
// 返回
return rewardActivity.getId();
@ -83,8 +72,7 @@ public class RewardActivityServiceImpl implements RewardActivityService {
validateRewardActivitySpuConflicts(updateReqVO.getId(), updateReqVO);
// 2. 更新
RewardActivityDO updateObj = BeanUtils.toBean(updateReqVO, RewardActivityDO.class)
.setStatus(PromotionUtils.calculateActivityStatus(updateReqVO.getEndTime()));
RewardActivityDO updateObj = BeanUtils.toBean(updateReqVO, RewardActivityDO.class);
rewardActivityMapper.updateById(updateObj);
}
@ -204,24 +192,17 @@ public class RewardActivityServiceImpl implements RewardActivityService {
return rewardActivityMapper.selectPage(pageReqVO);
}
@Override
public List<RewardActivityDO> getRewardActivityListByStatusAndDateTimeLt(Integer status, LocalDateTime dateTime) {
return rewardActivityMapper.selectListByStatusAndDateTimeLt(status, dateTime);
}
@Override
public List<RewardActivityDO> getRewardActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime) {
List<ProductSpuRespDTO> spuList = productSpuApi.validateSpuList(spuIds);
//查询出商品的分类ids
List<Long> categoryIds = spuList.stream().map(ProductSpuRespDTO::getCategoryId).collect(Collectors.toList());
// 1. 查询出指定 spuId spu 参加的活动
List<RewardActivityDO> rewardActivityList = rewardActivityMapper.getRewardActivityByStatusAndDateTimeLt(spuIds, categoryIds,status,dateTime);
if (CollUtil.isEmpty(rewardActivityList)) {
// 1. 查询商品分类
List<ProductSpuRespDTO> spuList = productSpuApi.getSpuList(spuIds);
if (CollUtil.isEmpty(spuList)) {
return Collections.emptyList();
}
Set<Long> categoryIds = convertSet(spuList, ProductSpuRespDTO::getCategoryId);
// 2. 查询活动详情
return rewardActivityList;
// 2. 查询出指定 spuId spu 参加的活动
return rewardActivityMapper.selectListByStatusAndDateTimeLt(spuIds, categoryIds, status, dateTime);
}
}

View File

@ -23,12 +23,11 @@ import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillConfigDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillProductDO;
import cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillactivity.SeckillActivityMapper;
import cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillactivity.SeckillProductMapper;
import cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillconfig.SeckillConfigMapper;
import jakarta.annotation.Resource;
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.Collection;
import java.util.Collections;
@ -58,8 +57,6 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
@Resource
private SeckillProductMapper seckillProductMapper;
@Resource
private SeckillConfigMapper seckillConfigMapper;
@Resource
private SeckillConfigService seckillConfigService;
@Resource
private ProductSpuApi productSpuApi;
@ -279,7 +276,7 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
}
@Override
public List<SeckillProductDO> getSeckillProductListByActivityId(Collection<Long> activityIds) {
public List<SeckillProductDO> getSeckillProductListByActivityIds(Collection<Long> activityIds) {
return seckillProductMapper.selectListByActivityId(activityIds);
}
@ -339,4 +336,9 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
convertSet(spuIdAndActivityIdMaps, map -> MapUtil.getLong(map, "activityId")), dateTime);
}
@Override
public List<SeckillActivityDO> getSeckillActivityListByIds(Collection<Long> ids) {
return List.of();
}
}

View File

@ -1,25 +0,0 @@
package cn.iocoder.yudao.module.promotion.util;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
import java.time.LocalDateTime;
/**
* 活动工具类
*
* @author 芋道源码
*/
public class PromotionUtils {
/**
* 根据时间计算活动状态
*
* @param endTime 结束时间
* @return 活动状态
*/
public static Integer calculateActivityStatus(LocalDateTime endTime) {
return LocalDateTimeUtils.beforeNow(endTime) ? CommonStatusEnum.DISABLE.getStatus() : CommonStatusEnum.ENABLE.getStatus();
}
}

View File

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.iocoder.yudao.module.promotion.dal.mysql.discount.DiscountProductMapper">
<select id="getMatchDiscountProductList" resultType="cn.iocoder.yudao.module.promotion.api.discount.dto.DiscountProductRespDTO">
SELECT pdp.*,pda.name as activity_name
FROM promotion_discount_product pdp
LEFT JOIN promotion_discount_activity pda
ON pdp.activity_id = pda.id
<where>
<if test="skuIds != null and skuIds.size > 0">
AND pdp.sku_id in
<foreach collection="skuIds" item="skuId" index="index" open="(" close=")" separator=",">
#{skuId}
</foreach>
</if>
AND pda.start_time &lt;= CURRENT_TIME AND pda.end_time &gt;= CURRENT_TIME
AND pda.`status` = 0
AND pda.deleted =0
AND pdp.deleted = 0
</where>
ORDER BY pdp.id DESC
</select>
</mapper>

View File

@ -18,15 +18,8 @@ import org.mockito.InjectMocks;
import org.mockito.Mock;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import static cn.hutool.core.collection.CollUtil.intersectionDistinct;
import static cn.hutool.core.util.RandomUtil.randomEle;
import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.addTime;
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
@ -34,8 +27,6 @@ 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 com.google.common.primitives.Longs.asList;
import static java.util.Collections.singletonList;
import static org.junit.jupiter.api.Assertions.*;
/**
@ -176,90 +167,91 @@ public class RewardActivityServiceImplTest extends BaseMockitoUnitTest {
assertPojoEquals(dbRewardActivity, pageResult.getList().get(0), "rules");
}
@Test
public void testGetRewardActivities_all() {
LocalDateTime now = LocalDateTime.now();
// mock 数据
RewardActivityDO allActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())
.setProductScope(PromotionProductScopeEnum.ALL.getScope()).setStartTime(now.minusDays(1)).setEndTime(now.plusDays(1)));
rewardActivityMapper.insert(allActivity);
RewardActivityDO productActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())
.setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductScopeValues(asList(1L, 2L))
.setStartTime(now.minusDays(1)).setEndTime(now.plusDays(1)));
rewardActivityMapper.insert(productActivity);
// 准备参数
Set<Long> spuIds = asSet(1L, 2L);
// 调用
List<RewardActivityDO> activityList = rewardActivityServiceImpl.getRewardActivityListByStatusAndDateTimeLt(
CommonStatusEnum.ENABLE.getStatus(), now);
List<RewardActivityDO> matchRewardActivityList = filterMatchActivity(spuIds, activityList);
// 断言
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() {
LocalDateTime now = LocalDateTime.now();
// mock 数据
RewardActivityDO productActivity01 = randomPojo(RewardActivityDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())
.setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductScopeValues(asList(1L, 2L))
.setStartTime(now.minusDays(1)).setEndTime(now.plusDays(1)));
rewardActivityMapper.insert(productActivity01);
RewardActivityDO productActivity02 = randomPojo(RewardActivityDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())
.setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductScopeValues(singletonList(3L))
.setStartTime(now.minusDays(1)).setEndTime(now.plusDays(1)));
rewardActivityMapper.insert(productActivity02);
// 准备参数
Set<Long> spuIds = asSet(1L, 2L, 3L);
List<RewardActivityDO> activityList = rewardActivityServiceImpl.getRewardActivityListByStatusAndDateTimeLt(
CommonStatusEnum.ENABLE.getStatus(), now);
List<RewardActivityDO> matchRewardActivityList = filterMatchActivity(spuIds, activityList);
// 断言
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();
}
});
}
/**
* 获得满减送的订单项商品列表
*
* @param spuIds 商品编号
* @param activityList 活动列表
* @return 订单项商品列表
*/
private List<RewardActivityDO> filterMatchActivity(Collection<Long> spuIds, List<RewardActivityDO> activityList) {
List<RewardActivityDO> resultActivityList = new ArrayList<>();
for (RewardActivityDO activity : activityList) {
// 情况一全部商品都可以参与
if (PromotionProductScopeEnum.isAll(activity.getProductScope())) {
resultActivityList.add(activity);
}
// 情况二指定商品参与
if (PromotionProductScopeEnum.isSpu(activity.getProductScope()) &&
!intersectionDistinct(activity.getProductScopeValues(), spuIds).isEmpty()) {
resultActivityList.add(activity);
}
}
return resultActivityList;
}
// TODO 芋艿后续完善单测
// @Test
// public void testGetRewardActivities_all() {
// LocalDateTime now = LocalDateTime.now();
// // mock 数据
// RewardActivityDO allActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())
// .setProductScope(PromotionProductScopeEnum.ALL.getScope()).setStartTime(now.minusDays(1)).setEndTime(now.plusDays(1)));
// rewardActivityMapper.insert(allActivity);
// RewardActivityDO productActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())
// .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductScopeValues(asList(1L, 2L))
// .setStartTime(now.minusDays(1)).setEndTime(now.plusDays(1)));
// rewardActivityMapper.insert(productActivity);
// // 准备参数
// Set<Long> spuIds = asSet(1L, 2L);
//
// // 调用
// List<RewardActivityDO> activityList = rewardActivityServiceImpl.getRewardActivityListByStatusAndDateTimeLt(
// CommonStatusEnum.ENABLE.getStatus(), now);
// List<RewardActivityDO> matchRewardActivityList = filterMatchActivity(spuIds, activityList);
// // 断言
// 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() {
// LocalDateTime now = LocalDateTime.now();
// // mock 数据
// RewardActivityDO productActivity01 = randomPojo(RewardActivityDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())
// .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductScopeValues(asList(1L, 2L))
// .setStartTime(now.minusDays(1)).setEndTime(now.plusDays(1)));
// rewardActivityMapper.insert(productActivity01);
// RewardActivityDO productActivity02 = randomPojo(RewardActivityDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())
// .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductScopeValues(singletonList(3L))
// .setStartTime(now.minusDays(1)).setEndTime(now.plusDays(1)));
// rewardActivityMapper.insert(productActivity02);
// // 准备参数
// Set<Long> spuIds = asSet(1L, 2L, 3L);
//
// List<RewardActivityDO> activityList = rewardActivityServiceImpl.getRewardActivityListByStatusAndDateTimeLt(
// CommonStatusEnum.ENABLE.getStatus(), now);
// List<RewardActivityDO> matchRewardActivityList = filterMatchActivity(spuIds, activityList);
// // 断言
// 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();
// }
// });
// }
//
// /**
// * 获得满减送的订单项商品列表
// *
// * @param spuIds 商品编号
// * @param activityList 活动列表
// * @return 订单项商品列表
// */
// private List<RewardActivityDO> filterMatchActivity(Collection<Long> spuIds, List<RewardActivityDO> activityList) {
// List<RewardActivityDO> resultActivityList = new ArrayList<>();
// for (RewardActivityDO activity : activityList) {
// // 情况一全部商品都可以参与
// if (PromotionProductScopeEnum.isAll(activity.getProductScope())) {
// resultActivityList.add(activity);
// }
// // 情况二指定商品参与
// if (PromotionProductScopeEnum.isSpu(activity.getProductScope()) &&
// !intersectionDistinct(activity.getProductScopeValues(), spuIds).isEmpty()) {
// resultActivityList.add(activity);
// }
// }
// return resultActivityList;
// }
}

View File

@ -44,11 +44,9 @@ import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.DISCOUNT_ACTIVITY_TYPE_NOT_EXISTS;
@Tag(name = "用户 App - 交易订单")
@RestController
@ -314,7 +312,7 @@ public class AppTradeOrderController {
Integer newPrice = price * discountProductRespDTO.getDiscountPercent() / 100;
sku.setPrice(price - newPrice);
}else{
throw exception(DISCOUNT_ACTIVITY_TYPE_NOT_EXISTS);
throw new IllegalArgumentException("限时折扣活动类型不存在");
}
return sku;
}

View File

@ -43,9 +43,10 @@ public interface AfterSaleConvert {
@Mapping(source = "afterSale.orderId", target = "merchantOrderId"),
@Mapping(source = "afterSale.id", target = "merchantRefundId"),
@Mapping(source = "afterSale.applyReason", target = "reason"),
@Mapping(source = "afterSale.refundPrice", target = "price")
@Mapping(source = "afterSale.refundPrice", target = "price"),
@Mapping(source = "orderProperties.payAppKey", target = "appKey")
})
PayRefundCreateReqDTO convert(String userIp, AfterSaleDO afterSale);
PayRefundCreateReqDTO convert(String userIp, AfterSaleDO afterSale, TradeOrderProperties orderProperties);
MemberUserRespVO convert(MemberUserRespDTO bean);

View File

@ -385,9 +385,8 @@ public class AfterSaleServiceImpl implements AfterSaleService {
@Override
public void afterCommit() {
// 创建退款单
PayRefundCreateReqDTO createReqDTO = AfterSaleConvert.INSTANCE.convert(userIp, afterSale)
.setReason(StrUtil.format("退款【{}】", afterSale.getSpuName()));
createReqDTO.setAppKey(tradeOrderProperties.getPayAppKey());
PayRefundCreateReqDTO createReqDTO = AfterSaleConvert.INSTANCE.convert(userIp, afterSale, tradeOrderProperties)
.setReason(StrUtil.format("退款【{}】", afterSale.getSpuName()));;
Long payRefundId = payRefundApi.createRefund(createReqDTO);
// 更新售后单的退款单号
tradeAfterSaleMapper.updateById(new AfterSaleDO().setId(afterSale.getId()).setPayRefundId(payRefundId));

View File

@ -73,11 +73,10 @@ public class TradePriceCalculateRespBO {
*/
private Long bargainActivityId;
/**
* 是否包邮
*/
private Boolean freeDelivery = false;
private Boolean freeDelivery;
/**
* 赠送的优惠劵

View File

@ -121,13 +121,11 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
* @return 是否包邮
*/
private boolean isGlobalExpressFree(TradePriceCalculateRespBO result) {
TradeConfigDO config = tradeConfigService.getTradeConfig();
return result.getFreeDelivery() ||
(config != null
&& Boolean.TRUE.equals(config.getDeliveryExpressFreeEnabled()) // 开启包邮
&& result.getPrice().getPayPrice() >= config.getDeliveryExpressFreePrice()
); // 满足包邮的价格
return config == null
|| Boolean.TRUE.equals(config.getDeliveryExpressFreeEnabled()) // 开启包邮
|| result.getFreeDelivery() //满减包邮
|| result.getPrice().getPayPrice() >= config.getDeliveryExpressFreePrice(); // 满足包邮的价格
}
private void calculateDeliveryPrice(List<OrderItem> selectedSkus,

View File

@ -1,8 +1,8 @@
package cn.iocoder.yudao.module.trade.service.price.calculator;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
import cn.iocoder.yudao.module.member.api.level.MemberLevelApi;
import cn.iocoder.yudao.module.member.api.level.dto.MemberLevelRespDTO;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
@ -14,12 +14,10 @@ import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
import jakarta.annotation.Resource;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import jakarta.annotation.Resource;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
@ -30,6 +28,8 @@ import static cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceC
/**
* 限时折扣的 {@link TradePriceCalculator} 实现类
*
* 由于会员折扣限时折扣是冲突需要选择优惠金额多的所以也放在这里计算
*
* @author 芋道源码
*/
@Component
@ -50,132 +50,89 @@ public class TradeDiscountActivityPriceCalculator implements TradePriceCalculato
return;
}
boolean discount;
boolean vip;
//----------------------------------限时折扣计算-----------------------------------------
// 获得 SKU 对应的限时折扣活动
// 1.1 获得 SKU 对应的限时折扣活动
List<DiscountProductRespDTO> discountProducts = discountActivityApi.getMatchDiscountProductList(
convertSet(result.getItems(), TradePriceCalculateRespBO.OrderItem::getSkuId));
if (CollUtil.isEmpty(discountProducts)) {
discount = false;
}else {
discount = true;
}
Map<Long, DiscountProductRespDTO> discountProductMap = convertMap(discountProducts, DiscountProductRespDTO::getSkuId);
//----------------------------------会员计算-----------------------------------------
MemberLevelRespDTO level;
// 获得用户的会员等级
// 1.2 获得会员等级
MemberUserRespDTO user = memberUserApi.getUser(param.getUserId());
if (user.getLevelId() != null && user.getLevelId() > 0) {
level = memberLevelApi.getMemberLevel(user.getLevelId());
if (level != null && level.getDiscountPercent() != null) {
vip = true;
}else {
vip = false;
}
}else {
level = null;
vip = false;
}
MemberLevelRespDTO level = user != null && user.getLevelId() > 0 ? memberLevelApi.getMemberLevel(user.getLevelId()) : null;
// 2. 计算每个 SKU 的优惠金额
result.getItems().forEach(orderItem -> {
//----------------------------------限时折扣计算-----------------------------------------
DiscountProductRespDTO discountProduct = null;
Integer newDiscountPrice = 0;
if (discount){
// 2.1 计算限时折扣优惠信息
discountProduct = discountProductMap.get(orderItem.getSkuId());
if (discountProduct != null) {
// 2.2 计算优惠金额
Integer newPayPrice = calculatePayPrice(discountProduct, orderItem);
newDiscountPrice = orderItem.getPayPrice() - newPayPrice;
if (!orderItem.getSelected()) {
return;
}
// 2.1 计算限时折扣的优惠金额
DiscountProductRespDTO discountProduct = discountProductMap.get(orderItem.getSkuId());
Integer discountPrice = calculateActivityPrice(discountProduct, orderItem);
// 2.2 计算 VIP 优惠金额
Integer vipPrice = calculateVipPrice(level, orderItem);
if (discountPrice <= 0 && vipPrice <= 0) {
return;
}
//----------------------------------会员计算-----------------------------------------
Integer vipPrice = 0;
if (vip){
// 2.3 计算会员优惠金额
vipPrice = calculateVipPrice(orderItem.getPayPrice(), level.getDiscountPercent());
}
// 2.4 记录优惠明细
// 注意只有在选中的情况下才会记录到优惠明细否则仅仅是更新 SKU 优惠金额用于展示
if (orderItem.getSelected()) {
if (discount && vip){
if(newDiscountPrice > vipPrice){
// 3. 选择优惠金额多的
if (discountPrice > vipPrice) {
TradePriceCalculatorHelper.addPromotion(result, orderItem,
discountProduct.getActivityId(), discountProduct.getActivityName(), PromotionTypeEnum.DISCOUNT_ACTIVITY.getType(),
StrUtil.format("限时折扣:省 {} 元", formatPrice(newDiscountPrice)),
newDiscountPrice);
// 2.5 更新 SKU 优惠金额
orderItem.setDiscountPrice(orderItem.getDiscountPrice() + newDiscountPrice);
StrUtil.format("限时折扣:省 {} 元", formatPrice(discountPrice)),
discountPrice);
// 更新 SKU 优惠金额
orderItem.setDiscountPrice(orderItem.getDiscountPrice() + discountPrice);
} else {
assert level != null;
TradePriceCalculatorHelper.addPromotion(result, orderItem,
level.getId(), level.getName(), PromotionTypeEnum.MEMBER_LEVEL.getType(),
String.format("会员等级折扣:省 %s 元", formatPrice(vipPrice)),
vipPrice);
// 2.5 更新 SKU 的优惠金额
// 更新 SKU 的优惠金额
orderItem.setVipPrice(vipPrice);
}
}else if (discount){
TradePriceCalculatorHelper.addPromotion(result, orderItem,
discountProduct.getActivityId(), discountProduct.getActivityName(), PromotionTypeEnum.DISCOUNT_ACTIVITY.getType(),
StrUtil.format("限时折扣:省 {} 元", formatPrice(newDiscountPrice)),
newDiscountPrice);
// 2.5 更新 SKU 优惠金额
orderItem.setDiscountPrice(orderItem.getDiscountPrice() + newDiscountPrice);
}else if (vip){
TradePriceCalculatorHelper.addPromotion(result, orderItem,
level.getId(), level.getName(), PromotionTypeEnum.MEMBER_LEVEL.getType(),
String.format("会员等级折扣:省 %s 元", formatPrice(vipPrice)),
vipPrice);
// 2.5 更新 SKU 的优惠金额
orderItem.setVipPrice(vipPrice);
}
}
// 4. 分摊优惠
TradePriceCalculatorHelper.recountPayPrice(orderItem);
});
TradePriceCalculatorHelper.recountAllPrice(result);
}
private Integer calculatePayPrice(DiscountProductRespDTO discountProduct,
TradePriceCalculateRespBO.OrderItem orderItem) {
Integer price = orderItem.getPayPrice();
if (PromotionDiscountTypeEnum.PRICE.getType().equals(discountProduct.getDiscountType())) { // 减价
price -= discountProduct.getDiscountPrice() * orderItem.getCount();
} else if (PromotionDiscountTypeEnum.PERCENT.getType().equals(discountProduct.getDiscountType())) { // 打折
price = price * discountProduct.getDiscountPercent() / 100;
} else {
throw new IllegalArgumentException(String.format("优惠活动的商品(%s) 的优惠类型不正确", discountProduct));
}
return price;
});
}
/**
* 计算会员 VIP 优惠价格
* 计算优惠活动的价格
*
* @param price 原价
* @param discountPercent 折扣
* @param discount 优惠活动
* @param orderItem 交易项
* @return 优惠价格
*/
public Integer calculateVipPrice(Integer price, Integer discountPercent) {
if (discountPercent == null) {
private Integer calculateActivityPrice(DiscountProductRespDTO discount,
TradePriceCalculateRespBO.OrderItem orderItem) {
if (discount == null) {
return 0;
}
BigDecimal divide = new BigDecimal(price).multiply(new BigDecimal(discountPercent)).divide(new BigDecimal(100));
Integer newPrice = divide.intValue();
return price - newPrice;
Integer newPrice = orderItem.getPayPrice();
if (PromotionDiscountTypeEnum.PRICE.getType().equals(discount.getDiscountType())) { // 减价
newPrice -= discount.getDiscountPrice() * orderItem.getCount();
} else if (PromotionDiscountTypeEnum.PERCENT.getType().equals(discount.getDiscountType())) { // 打折
newPrice = newPrice * discount.getDiscountPercent() / 100;
} else {
throw new IllegalArgumentException(String.format("优惠活动的商品(%s) 的优惠类型不正确", discount));
}
return orderItem.getPayPrice() - newPrice;
}
/**
* 计算会员 VIP 的优惠价格
*
* @param level 会员等级
* @param orderItem 交易项
* @return 优惠价格
*/
public Integer calculateVipPrice(MemberLevelRespDTO level,
TradePriceCalculateRespBO.OrderItem orderItem) {
if (level == null || level.getDiscountPercent() == null) {
return 0;
}
Integer newPrice = MoneyUtils.calculateRatePrice(orderItem.getPayPrice(), level.getDiscountPercent().doubleValue());
return orderItem.getPayPrice() - newPrice;
}
}

View File

@ -1,93 +0,0 @@
package cn.iocoder.yudao.module.trade.service.price.calculator;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.module.member.api.level.MemberLevelApi;
import cn.iocoder.yudao.module.member.api.level.dto.MemberLevelRespDTO;
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.enums.common.PromotionTypeEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import jakarta.annotation.Resource;
import static cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculatorHelper.formatPrice;
/**
* 会员 VIP 折扣的 {@link TradePriceCalculator} 实现类
*
* @author 芋道源码
*/
@Component
@Order(TradePriceCalculator.ORDER_MEMBER_LEVEL)
public class TradeMemberLevelPriceCalculator implements TradePriceCalculator {
@Resource
private MemberLevelApi memberLevelApi;
@Resource
private MemberUserApi memberUserApi;
/**
* 会员计算迁移到限时优惠计算里
* @param param
* @param result
*/
@Override
public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
// // 0. 只有普通订单才计算该优惠
// if (ObjectUtil.notEqual(result.getType(), TradeOrderTypeEnum.NORMAL.getType())) {
// return;
// }
// // 1. 获得用户的会员等级
// MemberUserRespDTO user = memberUserApi.getUser(param.getUserId());
// if (user.getLevelId() == null || user.getLevelId() <= 0) {
// return;
// }
// MemberLevelRespDTO level = memberLevelApi.getMemberLevel(user.getLevelId());
// if (level == null || level.getDiscountPercent() == null) {
// return;
// }
//
// // 2. 计算每个 SKU 的优惠金额
// result.getItems().forEach(orderItem -> {
// // 2.1 计算优惠金额
// Integer vipPrice = calculateVipPrice(orderItem.getPayPrice(), level.getDiscountPercent());
// if (vipPrice <= 0) {
// return;
// }
//
// // 2.2 记录优惠明细
// if (orderItem.getSelected()) {
// // 注意只有在选中的情况下才会记录到优惠明细否则仅仅是更新 SKU 优惠金额用于展示
// TradePriceCalculatorHelper.addPromotion(result, orderItem,
// level.getId(), level.getName(), PromotionTypeEnum.MEMBER_LEVEL.getType(),
// String.format("会员等级折扣:省 %s 元", formatPrice(vipPrice)),
// vipPrice);
// }
//
// // 2.3 更新 SKU 的优惠金额
// orderItem.setVipPrice(vipPrice);
// TradePriceCalculatorHelper.recountPayPrice(orderItem);
// });
// TradePriceCalculatorHelper.recountAllPrice(result);
}
/**
* 计算会员 VIP 优惠价格
*
* @param price 原价
* @param discountPercent 折扣
* @return 优惠价格
*/
public Integer calculateVipPrice(Integer price, Integer discountPercent) {
if (discountPercent == null) {
return 0;
}
Integer newPrice = price * discountPercent / 100;
return price - newPrice;
}
}

View File

@ -13,8 +13,6 @@ import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
*/
public interface TradePriceCalculator {
int ORDER_MEMBER_LEVEL = 5;
int ORDER_SECKILL_ACTIVITY = 8;
int ORDER_BARGAIN_ACTIVITY = 8;
int ORDER_COMBINATION_ACTIVITY = 8;

View File

@ -23,10 +23,8 @@ import java.util.Comparator;
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.convertSet;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.REWARD_ACTIVITY_TYPE_NOT_EXISTS;
import static cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculatorHelper.formatPrice;
// TODO @puhui999相关的单测建议改一改
@ -81,8 +79,10 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator
Integer newDiscountPrice = rule.getDiscountPrice();
// 2.2 计算分摊的优惠金额
List<Integer> divideDiscountPrices = TradePriceCalculatorHelper.dividePrice(orderItems, newDiscountPrice);
//计算是否包邮
result.setFreeDelivery(rule.getFreeDelivery());
// 2.3 计算是否包邮
if (Boolean.TRUE.equals(rule.getFreeDelivery())) {
result.setFreeDelivery(true);
}
// 3.1 记录使用的优惠劵
result.setCouponId(param.getCouponId());
@ -141,7 +141,8 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator
return filterList(result.getItems(),
orderItem -> CollUtil.contains(rewardActivity.getProductScopeValues(), orderItem.getCategoryId()));
} else {
throw exception(REWARD_ACTIVITY_TYPE_NOT_EXISTS);
throw new IllegalArgumentException(StrUtil.format("满减送活动({})的类型({})不存在",
rewardActivity.getId(), productScope));
}
}

View File

@ -1,118 +0,0 @@
package cn.iocoder.yudao.module.trade.service.price.calculator;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import cn.iocoder.yudao.module.member.api.level.MemberLevelApi;
import cn.iocoder.yudao.module.member.api.level.dto.MemberLevelRespDTO;
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.enums.common.PromotionTypeEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import java.util.ArrayList;
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
/**
* {@link TradeMemberLevelPriceCalculator} 的单元测试类
*
* @author 芋道源码
*/
public class TradeMemberLevelPriceCalculatorTest extends BaseMockitoUnitTest {
@InjectMocks
private TradeMemberLevelPriceCalculator memberLevelPriceCalculator;
@Mock
private MemberLevelApi memberLevelApi;
@Mock
private MemberUserApi memberUserApi;
@Test
public void testCalculate() {
// 准备参数
TradePriceCalculateReqBO param = new TradePriceCalculateReqBO()
.setUserId(1024L)
.setItems(asList(
new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true), // 匹配活动且已选中
new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(false) // 匹配活动但未选中
));
TradePriceCalculateRespBO result = new TradePriceCalculateRespBO()
.setType(TradeOrderTypeEnum.NORMAL.getType())
.setPrice(new TradePriceCalculateRespBO.Price())
.setPromotions(new ArrayList<>())
.setItems(asList(
new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true)
.setPrice(100),
new TradePriceCalculateRespBO.OrderItem().setSkuId(20L).setCount(3).setSelected(false)
.setPrice(50)
));
// 保证价格被初始化上
TradePriceCalculatorHelper.recountPayPrice(result.getItems());
TradePriceCalculatorHelper.recountAllPrice(result);
// mock 方法会员等级
when(memberUserApi.getUser(eq(1024L))).thenReturn(new MemberUserRespDTO().setLevelId(2048L));
when(memberLevelApi.getMemberLevel(eq(2048L))).thenReturn(
new MemberLevelRespDTO().setId(2048L).setName("VIP 会员").setDiscountPercent(60));
// 调用
memberLevelPriceCalculator.calculate(param, result);
// 断言Price 部分
TradePriceCalculateRespBO.Price price = result.getPrice();
assertEquals(price.getTotalPrice(), 200);
assertEquals(price.getDiscountPrice(), 0);
assertEquals(price.getPointPrice(), 0);
assertEquals(price.getDeliveryPrice(), 0);
assertEquals(price.getCouponPrice(), 0);
assertEquals(price.getVipPrice(), 80);
assertEquals(price.getPayPrice(), 120);
assertNull(result.getCouponId());
// 断言SKU 1
assertEquals(result.getItems().size(), 2);
TradePriceCalculateRespBO.OrderItem orderItem01 = result.getItems().get(0);
assertEquals(orderItem01.getSkuId(), 10L);
assertEquals(orderItem01.getCount(), 2);
assertEquals(orderItem01.getPrice(), 100);
assertEquals(orderItem01.getDiscountPrice(), 0);
assertEquals(orderItem01.getDeliveryPrice(), 0);
assertEquals(orderItem01.getCouponPrice(), 0);
assertEquals(orderItem01.getPointPrice(), 0);
assertEquals(orderItem01.getVipPrice(), 80);
assertEquals(orderItem01.getPayPrice(), 120);
// 断言SKU 2
TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1);
assertEquals(orderItem02.getSkuId(), 20L);
assertEquals(orderItem02.getCount(), 3);
assertEquals(orderItem02.getPrice(), 50);
assertEquals(orderItem02.getDiscountPrice(), 0);
assertEquals(orderItem02.getDeliveryPrice(), 0);
assertEquals(orderItem02.getCouponPrice(), 0);
assertEquals(orderItem02.getPointPrice(), 0);
assertEquals(orderItem02.getVipPrice(), 60);
assertEquals(orderItem02.getPayPrice(), 90);
// 断言Promotion 部分
assertEquals(result.getPromotions().size(), 1);
TradePriceCalculateRespBO.Promotion promotion01 = result.getPromotions().get(0);
assertEquals(promotion01.getId(), 2048L);
assertEquals(promotion01.getName(), "VIP 会员");
assertEquals(promotion01.getType(), PromotionTypeEnum.MEMBER_LEVEL.getType());
assertEquals(promotion01.getTotalPrice(), 200);
assertEquals(promotion01.getDiscountPrice(), 80);
assertTrue(promotion01.getMatch());
assertEquals(promotion01.getDescription(), "会员等级折扣:省 0.80 元");
TradePriceCalculateRespBO.PromotionItem promotionItem01 = promotion01.getItems().get(0);
assertEquals(promotion01.getItems().size(), 1);
assertEquals(promotionItem01.getSkuId(), 10L);
assertEquals(promotionItem01.getTotalPrice(), 200);
assertEquals(promotionItem01.getDiscountPrice(), 80);
}
}

View File

@ -11,7 +11,6 @@ import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
import cn.iocoder.yudao.framework.datapermission.core.util.DataPermissionUtils;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import cn.iocoder.yudao.module.infra.api.config.ConfigApi;
import cn.iocoder.yudao.module.infra.api.file.FileApi;
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.AuthRegisterReqVO;
@ -105,7 +104,6 @@ public class AdminUserServiceImpl implements AdminUserService {
AdminUserDO user = BeanUtils.toBean(createReqVO, AdminUserDO.class);
user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启
user.setPassword(encodePassword(createReqVO.getPassword())); // 加密密码
user.setTenantId(TenantContextHolder.getRequiredTenantId());
userMapper.insert(user);
// 2.2 插入关联岗位
if (CollectionUtil.isNotEmpty(user.getPostIds())) {

View File

@ -6,7 +6,7 @@ spring:
# 数据源配置项
autoconfigure:
exclude:
#- org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration # 默认 local 环境,不开启 Quartz 的自动配置
- org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration # 默认 local 环境,不开启 Quartz 的自动配置
- de.codecentric.boot.admin.server.config.AdminServerAutoConfiguration # 禁用 Spring Boot Admin 的 Server 的自动配置
- de.codecentric.boot.admin.server.ui.config.AdminServerUiAutoConfiguration # 禁用 Spring Boot Admin 的 Server UI 的自动配置
- de.codecentric.boot.admin.client.config.SpringBootAdminClientAutoConfiguration # 禁用 Spring Boot Admin 的 Client 的自动配置
@ -45,7 +45,7 @@ spring:
primary: master
datasource:
master:
url: jdbc:mysql://192.168.10.207:3306/specialty?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例
url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例
# url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=true&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true # MySQL Connector/J 5.X 连接的示例
# url: jdbc:postgresql://127.0.0.1:5432/ruoyi-vue-pro # PostgreSQL 连接的示例
# url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例
@ -61,19 +61,19 @@ spring:
# password: SYSDBA001 # DM 连接的示例
# username: root # OpenGauss 连接的示例
# password: Yudao@2024 # OpenGauss 连接的示例
# slave: # 模拟从库,可根据自己需要修改
# lazy: true # 开启懒加载,保证启动速度
# url: jdbc:mysql://192.168.10.207:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true&nullCatalogMeansCurrent=true
# username: root
# password: 123456
slave: # 模拟从库,可根据自己需要修改
lazy: true # 开启懒加载,保证启动速度
url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true&nullCatalogMeansCurrent=true
username: root
password: 123456
# Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
data:
redis:
host: 192.168.10.207 # 地址
host: 127.0.0.1 # 地址
port: 6379 # 端口
database: 0 # 数据库索引
password: 123456 # 密码,建议生产环境开启
# password: dev # 密码,建议生产环境开启
--- #################### 定时任务相关配置 ####################
@ -200,8 +200,8 @@ wx:
# secret: 6f270509224a7ae1296bbf1c8cb97aed
# appid: wxc4598c446f8a9cb3 # 测试号Kongdy 提供的)
# secret: 4a1a04e07f6a4a0751b39c3064a92c8b
appid: wx9a0a5b259d852380 # 测试号puhui 提供的)
secret: 70e65fa9d1a4f2c4e1b2aa8751d3b75e
appid: wx66186af0759f47c9 # 测试号puhui 提供的)
secret: 3218bcbd112cbc614c7264ceb20144ac
config-storage:
type: RedisTemplate # 采用 RedisTemplate 操作 Redis会自动从 Spring 中获取
key-prefix: wa # Redis Key 的前缀