!1084 批量优化或修改BUG

Merge pull request !1084 from 痴货/develop-mall
This commit is contained in:
芋道源码 2024-09-15 02:49:35 +00:00 committed by Gitee
commit 241957507c
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
42 changed files with 749 additions and 168 deletions

View File

@ -69,8 +69,8 @@ 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)));
// MemberLevelRespDTO memberLevel = getMemberLevel();
// voList.forEach(vo -> vo.setVipPrice(calculateVipPrice(vo.getPrice(), memberLevel)));
return success(voList);
}
@ -86,8 +86,8 @@ 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)));
// MemberLevelRespDTO memberLevel = getMemberLevel();
// voPageResult.getList().forEach(vo -> vo.setVipPrice(calculateVipPrice(vo.getPrice(), memberLevel)));
return success(voPageResult);
}
@ -142,7 +142,7 @@ public class AppProductSpuController {
*/
public Integer calculateVipPrice(Integer price, MemberLevelRespDTO memberLevel) {
if (memberLevel == null || memberLevel.getDiscountPercent() == null) {
return 0;
return null;
}
Integer newPrice = price * memberLevel.getDiscountPercent() / 100;
return price - newPrice;

View File

@ -38,8 +38,8 @@ 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 = "VIP 价格,单位使用:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "968") // 通过会员等级计算出折扣后价格
// private Integer vipPrice;
@Schema(description = "库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "666")
private Integer stock;

View File

@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.promotion.api.discount.dto;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 限时折扣活动商品 Response DTO
*
@ -44,5 +46,12 @@ public class DiscountProductRespDTO {
* 活动标题
*/
private String activityName;
/**
* 活动结束时间点
*
* 冗余 {@link DiscountActivityDO#getEndTime()}
*/
private LocalDateTime activityEndTime;
}

View File

@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.promotion.api.reward;
import cn.iocoder.yudao.module.promotion.api.reward.dto.RewardActivityMatchRespDTO;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
/**
@ -21,4 +22,13 @@ public interface RewardActivityApi {
*/
List<RewardActivityMatchRespDTO> getRewardActivityListByStatusAndNow(Integer status, LocalDateTime dateTime);
/**
* 获取指定 spu 编号最近参加的活动每个 spuId 只返回一条记录
*
* @param spuIds spu 编号
* @param status 状态
* @param dateTime 当前日期时间
* @return 满减送活动列表
*/
List<RewardActivityMatchRespDTO> getRewardActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime);
}

View File

@ -15,6 +15,7 @@ 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 不存在");
@ -45,6 +46,7 @@ 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

@ -24,7 +24,7 @@ public class DiscountActivityApiImpl implements DiscountActivityApi {
@Override
public List<DiscountProductRespDTO> getMatchDiscountProductList(Collection<Long> skuIds) {
return DiscountActivityConvert.INSTANCE.convertList02(discountActivityService.getMatchDiscountProductList(skuIds));
return discountActivityService.getMatchDiscountProductList(skuIds);
}
}

View File

@ -2,6 +2,7 @@ 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;
@ -9,6 +10,7 @@ import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
/**
@ -29,4 +31,10 @@ public class RewardActivityApiImpl implements RewardActivityApi {
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,9 +2,11 @@ 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;
@ -12,6 +14,7 @@ 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;
@ -37,11 +40,11 @@ public class CouponTemplateBaseVO {
private String description;
@Schema(description = "发行总量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") // -1 - 则表示不限制发放数量
@NotNull(message = "发行总量不能为空")
@NotNull(message = "发行总量不能为空", groups = {User.class})
private Integer totalCount;
@Schema(description = "每人限领个数", requiredMode = Schema.RequiredMode.REQUIRED, example = "66") // -1 - 则表示不限制
@NotNull(message = "每人限领个数不能为空")
@NotNull(message = "每人限领个数不能为空", groups = {User.class})
private Integer takeLimitCount;
@Schema(description = "领取方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@ -89,13 +92,16 @@ 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 = "商品范围编号的数组不能为空")
@ -154,4 +160,54 @@ 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

@ -33,6 +33,7 @@ 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.*;
@ -150,36 +151,32 @@ public class AppActivityController {
}
private void getRewardActivityList(Collection<Long> spuIds, LocalDateTime now, List<AppActivityRespVO> activityList) {
// 1.1 获得所有的活动
List<RewardActivityDO> rewardActivityList = rewardActivityService.getRewardActivityListByStatusAndDateTimeLt(
CommonStatusEnum.ENABLE.getStatus(), now);
// TODO @puhui999 3 范围不只 spuId还有 categoryId全部下次 fix
List<RewardActivityDO> rewardActivityList = rewardActivityService.getRewardActivityBySpuIdsAndStatusAndDateTimeLt(
spuIds, CommonStatusEnum.ENABLE.getStatus(), now);
if (CollUtil.isEmpty(rewardActivityList)) {
return;
}
// 1.2 获得所有的商品信息
List<ProductSpuRespDTO> spuList = productSpuApi.getSpuList(spuIds);
if (CollUtil.isEmpty(spuList)) {
return;
}
// 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);
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
)
.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()));
}
}

View File

@ -60,8 +60,8 @@ public class AppCouponController {
@Operation(summary = "获得匹配指定商品的优惠劵列表", description = "用于下单页,展示优惠劵列表")
public CommonResult<List<AppCouponMatchRespVO>> getMatchCouponList(AppCouponMatchReqVO matchReqVO) {
// todo: 优化优惠金额倒序
List<CouponDO> list = couponService.getMatchCouponList(getLoginUserId(), matchReqVO);
return success(BeanUtils.toBean(list, AppCouponMatchRespVO.class));
List<AppCouponMatchRespVO> list = couponService.getMatchCouponList(getLoginUserId(), matchReqVO);
return success(list);
}
@GetMapping("/page")

View File

@ -4,6 +4,7 @@ import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivi
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "用户 App - 满减送活动 Response VO")
@ -26,9 +27,15 @@ public class AppRewardActivityRespVO {
private Integer productScope;
@Schema(description = "商品 SPU 编号的数组", example = "1,2,3")
private List<Long> productSpuIds;
private List<Long> productScopeValues;
@Schema(description = "优惠规则的数组")
private List<RewardActivityBaseVO.Rule> rules;
@Schema(description = "开始时间")
private LocalDateTime startTime;
@Schema(description = "结束时间")
private LocalDateTime endTime;
}

View File

@ -32,6 +32,8 @@ 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

@ -121,7 +121,10 @@ public interface DiscountActivityConvert {
default boolean isEquals(DiscountProductDO productDO, DiscountProductDO productVO) {
if (ObjectUtil.notEqual(productDO.getSpuId(), productVO.getSpuId())
|| ObjectUtil.notEqual(productDO.getSkuId(), productVO.getSkuId())
|| ObjectUtil.notEqual(productDO.getDiscountType(), productVO.getDiscountType())) {
|| ObjectUtil.notEqual(productDO.getDiscountType(), productVO.getDiscountType())
|| ObjectUtil.notEqual(productDO.getActivityEndTime(), productVO.getActivityEndTime())
|| ObjectUtil.notEqual(productDO.getActivityStartTime(), productVO.getActivityStartTime())
|| ObjectUtil.notEqual(productDO.getActivityStatus(), productVO.getActivityStatus())) {
return false;
}
if (productDO.getDiscountType().equals(PromotionDiscountTypeEnum.PRICE.getType())) {

View File

@ -0,0 +1,33 @@
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

@ -85,19 +85,12 @@ public interface CouponMapper extends BaseMapperX<CouponDO> {
}
default List<CouponDO> selectListByUserIdAndStatusAndUsePriceLeAndProductScope(
Long userId, Integer status, Integer usePrice, List<Long> spuIds, List<Long> categoryIds) {
Function<List<Long>, String> productScopeValuesFindInSetFunc = ids -> ids.stream()
.map(id -> StrUtil.format("FIND_IN_SET({}, product_scope_values) ", id))
.collect(Collectors.joining(" OR "));
return selectList(new LambdaQueryWrapperX<CouponDO>()
Long userId, Integer status) {
List<CouponDO> couponDOS = selectList(new LambdaQueryWrapperX<CouponDO>()
.eq(CouponDO::getUserId, userId)
.eq(CouponDO::getStatus, status)
.le(CouponDO::getUsePrice, usePrice) // 价格小于等于满足价格使用条件
.and(w -> w.eq(CouponDO::getProductScope, PromotionProductScopeEnum.ALL.getScope()) // 商品范围一全部
.or(ww -> ww.eq(CouponDO::getProductScope, PromotionProductScopeEnum.SPU.getScope()) // 商品范围二满足指定商品
.apply(productScopeValuesFindInSetFunc.apply(spuIds)))
.or(ww -> ww.eq(CouponDO::getProductScope, PromotionProductScopeEnum.CATEGORY.getScope()) // 商品范围三满足指定分类
.apply(productScopeValuesFindInSetFunc.apply(categoryIds)))));
);
return couponDOS;
}
default List<CouponDO> selectListByStatusAndValidEndTimeLe(Integer status, LocalDateTime validEndTime) {

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 )"); // 4. 剩余数量大于 0或者无限领取
.apply(" (take_count < total_count OR total_count = -1 or total_count is null)"); // 4. 剩余数量大于 0或者无限领取,或者是指定发放的券
}
return canTakeConsumer;
}

View File

@ -1,6 +1,7 @@
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.module.promotion.dal.dataobject.discount.DiscountProductDO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.apache.ibatis.annotations.Mapper;
@ -31,7 +32,7 @@ public interface DiscountProductMapper extends BaseMapperX<DiscountProductDO> {
}
// TODO @zhangshuai逻辑里尽量避免写 join 语句哈你可以看看这个查询有什么办法优化目前的一个思路是分 2 次查询性能也是 ok
List<DiscountProductDO> getMatchDiscountProductList(@Param("skuIds") Collection<Long> skuIds);
List<DiscountProductRespDTO> getMatchDiscountProductList(@Param("skuIds") Collection<Long> skuIds);
/**
* 查询出指定 spuId spu 参加的活动最接近现在的一条记录多个的话一个 spuId 对应一个最近的活动编号

View File

@ -1,14 +1,20 @@
package cn.iocoder.yudao.module.promotion.dal.mysql.reward;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.promotion.controller.admin.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;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 满减送活动 Mapper
@ -25,13 +31,48 @@ 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)
// 开始时间 < 指定时间dateTime < 结束时间也就是说获取指定时间段的活动
.lt(RewardActivityDO::getStartTime, dateTime).gt(RewardActivityDO::getEndTime, dateTime)
.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) {
//拼接通用券查询语句
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 LambdaQueryWrapperX<RewardActivityDO>()
.eq(RewardActivityDO::getStatus,status)
.lt(RewardActivityDO::getStartTime, dateTime)
.gt(RewardActivityDO::getEndTime, dateTime)
.and(i -> i.eq(RewardActivityDO::getProductScope, PromotionProductScopeEnum.SPU.getScope())
.and(i1 -> i1.apply(productScopeValuesFindInSetFunc.apply(spuIds)))
.or(i1 -> i1.eq(RewardActivityDO::getProductScope, PromotionProductScopeEnum.ALL.getScope()))
.or(i1 -> i1.eq(RewardActivityDO::getProductScope, PromotionProductScopeEnum.CATEGORY.getScope())
.and(i2 -> i2.apply(productScopeValuesFindInSetFunc.apply(categoryIds)))))
.orderByDesc(RewardActivityDO::getId)
);
}
}

View File

@ -51,7 +51,7 @@ public interface SeckillActivityMapper extends BaseMapperX<SeckillActivityDO> {
Assert.isTrue(count > 0);
return update(null, new LambdaUpdateWrapper<SeckillActivityDO>()
.eq(SeckillActivityDO::getId, id)
.gt(SeckillActivityDO::getStock, count)
.ge(SeckillActivityDO::getStock, count)
.setSql("stock = stock - " + count));
}
@ -69,9 +69,11 @@ public interface SeckillActivityMapper extends BaseMapperX<SeckillActivityDO> {
.setSql("stock = stock + " + count));
}
default PageResult<SeckillActivityDO> selectPage(AppSeckillActivityPageReqVO pageReqVO, Integer status) {
default PageResult<SeckillActivityDO> selectPage(AppSeckillActivityPageReqVO pageReqVO, Integer status, LocalDateTime dateTime) {
return selectPage(pageReqVO, new LambdaQueryWrapperX<SeckillActivityDO>()
.eqIfPresent(SeckillActivityDO::getStatus, status)
.lt(SeckillActivityDO::getStartTime, dateTime)
.gt(SeckillActivityDO::getEndTime, dateTime)// 开始时间 < 指定时间 < 结束时间也就是说获取指定时间段的活动
.apply(ObjectUtil.isNotNull(pageReqVO.getConfigId()), "FIND_IN_SET(" + pageReqVO.getConfigId() + ",config_ids) > 0"));
}

View File

@ -1,12 +1,16 @@
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

@ -5,6 +5,7 @@ import cn.hutool.core.map.MapUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponMatchReqVO;
import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponMatchRespVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
@ -178,7 +179,7 @@ public interface CouponService {
* @param matchReqVO 匹配参数
* @return 优惠券列表
*/
List<CouponDO> getMatchCouponList(Long userId, AppCouponMatchReqVO matchReqVO);
List<AppCouponMatchRespVO> getMatchCouponList(Long userId, AppCouponMatchReqVO matchReqVO);
/**
* 获取用户是否可以领取优惠券

View File

@ -13,10 +13,12 @@ import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponMatchReqVO;
import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponMatchRespVO;
import cn.iocoder.yudao.module.promotion.convert.coupon.CouponConvert;
import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
import cn.iocoder.yudao.module.promotion.dal.mysql.coupon.CouponMapper;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
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;
@ -286,6 +288,7 @@ public class CouponServiceImpl implements CouponService {
}
// 校验剩余数量仅在 CouponTakeTypeEnum.USER 用户领取时
if (CouponTakeTypeEnum.isUser(couponTemplate.getTakeCount())
&& couponTemplate.getTotalCount() != null
&& couponTemplate.getTakeCount() + userIds.size() > couponTemplate.getTotalCount()) {
throw exception(COUPON_TEMPLATE_NOT_ENOUGH);
}
@ -308,7 +311,7 @@ public class CouponServiceImpl implements CouponService {
* @param couponTemplate 优惠劵模版
*/
private void removeTakeLimitUser(Set<Long> userIds, CouponTemplateDO couponTemplate) {
if (couponTemplate.getTakeLimitCount() <= 0) {
if (couponTemplate.getTakeLimitCount() == null || couponTemplate.getTakeLimitCount() <= 0) {
return;
}
// 查询已领过券的用户
@ -356,13 +359,45 @@ public class CouponServiceImpl implements CouponService {
}
@Override
public List<CouponDO> getMatchCouponList(Long userId, AppCouponMatchReqVO matchReqVO) {
public List<AppCouponMatchRespVO> getMatchCouponList(Long userId, AppCouponMatchReqVO matchReqVO) {
List<AppCouponMatchRespVO> couponMatchist = new ArrayList<>();
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;
CouponStatusEnum.UNUSED.getStatus());
for (CouponDO couponDO : list) {
AppCouponMatchRespVO appCouponMatchRespVO = CouponConvert.INSTANCE.convert2(couponDO);
Integer productScope = appCouponMatchRespVO.getProductScope();
List<Long> productScopeValues = appCouponMatchRespVO.getProductScopeValues();
Integer usePrice = appCouponMatchRespVO.getUsePrice();
if(matchReqVO.getPrice() < usePrice){
// 价格小于等于满足价格使用条件
appCouponMatchRespVO.setMatch(false);
appCouponMatchRespVO.setDescription("未达到使用门槛");
}else if(!LocalDateTimeUtils.isBetween(appCouponMatchRespVO.getValidStartTime(), appCouponMatchRespVO.getValidEndTime())) {
//判断时间
appCouponMatchRespVO.setMatch(false);
appCouponMatchRespVO.setDescription("使用时间未到");
}else if (PromotionProductScopeEnum.ALL.getScope().equals(productScope)){
appCouponMatchRespVO.setMatch(true);
}else if (PromotionProductScopeEnum.SPU.getScope().equals(productScope)){
boolean spu = new HashSet<>(productScopeValues).containsAll(matchReqVO.getSpuIds());
if(spu){
appCouponMatchRespVO.setMatch(true);
}else {
appCouponMatchRespVO.setMatch(false);
appCouponMatchRespVO.setDescription("与商品不匹配");
}
}else if (PromotionProductScopeEnum.CATEGORY.getScope().equals(productScope)){
boolean category = new HashSet<>(productScopeValues).containsAll(matchReqVO.getCategoryIds());
if(category){
appCouponMatchRespVO.setMatch(true);
}else {
appCouponMatchRespVO.setMatch(false);
appCouponMatchRespVO.setDescription("与商品类型不匹配");
}
}
couponMatchist.add(appCouponMatchRespVO);
}
return couponMatchist;
}
@Override

View File

@ -13,6 +13,7 @@ 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 org.springframework.stereotype.Service;
import jakarta.validation.Validator;
import org.springframework.validation.annotation.Validated;
import jakarta.annotation.Resource;
@ -40,9 +41,13 @@ 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,6 +1,7 @@
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;
@ -27,7 +28,7 @@ public interface DiscountActivityService {
* @param skuIds SKU 编号数组
* @return 匹配的限时折扣商品
*/
List<DiscountProductDO> getMatchDiscountProductList(Collection<Long> skuIds);
List<DiscountProductRespDTO> getMatchDiscountProductList(Collection<Long> skuIds);
/**
* 创建限时折扣活动

View File

@ -6,6 +6,7 @@ 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;
@ -49,7 +50,7 @@ public class DiscountActivityServiceImpl implements DiscountActivityService {
private DiscountProductMapper discountProductMapper;
@Override
public List<DiscountProductDO> getMatchDiscountProductList(Collection<Long> skuIds) {
public List<DiscountProductRespDTO> getMatchDiscountProductList(Collection<Long> skuIds) {
return discountProductMapper.getMatchDiscountProductList(skuIds);
}
@ -130,7 +131,7 @@ public class DiscountActivityServiceImpl implements DiscountActivityService {
List<DiscountProductDO> list = discountProductMapper.selectListByActivityId(id);
// TODO @zhangshuai一般简单的 stream 方法建议是使用 CollectionUtils例如说这里是 convertList 对把
List<Long> skuIds = list.stream().map(item -> item.getSkuId()).collect(Collectors.toList());
List<DiscountProductDO> matchDiscountProductList = getMatchDiscountProductList(skuIds);
List<DiscountProductRespDTO> matchDiscountProductList = getMatchDiscountProductList(skuIds);
if (id != null) { // 排除自己这个活动
matchDiscountProductList.removeIf(product -> id.equals(product.getActivityId()));
}

View File

@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
import jakarta.validation.Valid;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
/**
@ -71,4 +72,14 @@ public interface RewardActivityService {
*/
List<RewardActivityDO> getRewardActivityListByStatusAndDateTimeLt(Integer status, LocalDateTime dateTime);
/**
* 获取指定 spu 编号最近参加的活动每个 spuId 只返回一条记录
*
* @param spuIds SPU 编号数组
* @param status 状态
* @param dateTime 当前日期时间
* @return 满减送活动列表
*/
List<RewardActivityDO> getRewardActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime);
}

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.promotion.service.reward;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
@ -11,8 +12,10 @@ 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;
@ -20,8 +23,11 @@ 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 static cn.hutool.core.collection.CollUtil.intersectionDistinct;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@ -52,9 +58,13 @@ public class RewardActivityServiceImpl implements RewardActivityService {
// 1.2 校验商品是否冲突
validateRewardActivitySpuConflicts(null, createReqVO);
// 2. 插入
RewardActivityDO rewardActivity = BeanUtils.toBean(createReqVO, RewardActivityDO.class)
.setStatus(PromotionUtils.calculateActivityStatus(createReqVO.getEndTime()));
// 插入
RewardActivityDO rewardActivity = RewardActivityConvert.INSTANCE.convert(createReqVO)
.setStatus(
PromotionUtils.calculateActivityStatus(createReqVO.getEndTime()).equals(CommonStatusEnum.DISABLE.getStatus())?
PromotionActivityStatusEnum.WAIT.getStatus():
PromotionActivityStatusEnum.RUN.getStatus()
);
rewardActivityMapper.insert(rewardActivity);
// 返回
return rewardActivity.getId();
@ -199,4 +209,19 @@ public class RewardActivityServiceImpl implements RewardActivityService {
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)) {
return Collections.emptyList();
}
// 2. 查询活动详情
return rewardActivityList;
}
}

View File

@ -110,7 +110,7 @@ public interface SeckillActivityService {
List<SeckillActivityDO> getSeckillActivityListByConfigIdAndStatus(Long configId, Integer status);
/**
* 通过活动时段获取秒杀活动
* 通过活动时段获取开始的秒杀活动
*
* @param pageReqVO 请求
* @return 秒杀活动列表

View File

@ -23,6 +23,7 @@ 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 org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
@ -57,6 +58,8 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
@Resource
private SeckillProductMapper seckillProductMapper;
@Resource
private SeckillConfigMapper seckillConfigMapper;
@Resource
private SeckillConfigService seckillConfigService;
@Resource
private ProductSpuApi productSpuApi;
@ -289,7 +292,7 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
@Override
public PageResult<SeckillActivityDO> getSeckillActivityAppPageByConfigId(AppSeckillActivityPageReqVO pageReqVO) {
return seckillActivityMapper.selectPage(pageReqVO, CommonStatusEnum.ENABLE.getStatus());
return seckillActivityMapper.selectPage(pageReqVO, CommonStatusEnum.ENABLE.getStatus(),LocalDateTime.now());
}
@Override

View File

@ -3,8 +3,8 @@
<mapper namespace="cn.iocoder.yudao.module.promotion.dal.mysql.discount.DiscountProductMapper">
<select id="getMatchDiscountProductList" resultType="cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountProductDO">
SELECT pdp.*
<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
@ -16,9 +16,11 @@
</foreach>
</if>
AND pda.start_time &lt;= CURRENT_TIME AND pda.end_time &gt;= CURRENT_TIME
AND pda.`status` = 20
AND pda.deleted != 1
AND pda.`status` = 0
AND pda.deleted =0
AND pdp.deleted = 0
</where>
ORDER BY pdp.id DESC
</select>
</mapper>

View File

@ -1,9 +1,23 @@
package cn.iocoder.yudao.module.trade.controller.app.order;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
import cn.iocoder.yudao.module.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.pay.api.notify.dto.PayOrderNotifyReqDTO;
import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import cn.iocoder.yudao.module.promotion.api.discount.DiscountActivityApi;
import cn.iocoder.yudao.module.promotion.api.discount.dto.DiscountProductRespDTO;
import cn.iocoder.yudao.module.promotion.api.reward.RewardActivityApi;
import cn.iocoder.yudao.module.promotion.api.reward.dto.RewardActivityMatchRespDTO;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionConditionTypeEnum;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.*;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemCommentCreateReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemRespVO;
@ -27,12 +41,14 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
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
@ -54,6 +70,17 @@ public class AppTradeOrderController {
@Resource
private TradeOrderProperties tradeOrderProperties;
@Resource
private MemberLevelApi memberLevelApi;
@Resource
private MemberUserApi memberUserApi;
@Resource
private DiscountActivityApi discountActivityApi;
@Resource
private RewardActivityApi rewardActivityApi;
@Resource
private ProductSkuApi productKpuApi;
@GetMapping("/settlement")
@Operation(summary = "获得订单结算信息")
@PreAuthenticated
@ -61,6 +88,58 @@ public class AppTradeOrderController {
return success(tradeOrderUpdateService.settlementOrder(getLoginUserId(), settlementReqVO));
}
@GetMapping("/settlementProduct")
@Operation(summary = "获得商品结算信息")
public CommonResult<List<AppTradeProductSettlementRespVO>> settlementProduct(@RequestParam("ids") Set<Long> ids) {
List<AppTradeProductSettlementRespVO> appTradeProductSettlementRespVOS = new ArrayList<>();
MemberLevelRespDTO memberLevel = getMemberLevel();
ids.forEach(spuId -> {
List<AppTradeProductSettlementRespVO.Sku> skus = new ArrayList<>();
List<ProductSkuRespDTO> skuList = productKpuApi.getSkuListBySpuId(Collections.singletonList(spuId));
//查询sku的会员和限时优惠
skuList.forEach(sku -> {
//查询限时优惠价格
AppTradeProductSettlementRespVO.Sku skuDiscount = calculateDiscountPrice(sku.getId(), sku.getPrice());
//查询会员价
AppTradeProductSettlementRespVO.Sku skuVip = calculateVipPrice(sku.getId(), sku.getPrice(), memberLevel);
if(skuDiscount != null && skuVip != null){
if(skuDiscount.getPrice() > skuVip.getPrice()){
skus.add(skuVip);
}else{
skus.add(skuDiscount);
}
}else if(skuDiscount != null){
skus.add(skuDiscount);
}else if(skuVip != null){
skus.add(skuVip);
}
});
AppTradeProductSettlementRespVO.Reward reward = calculateReward(spuId);
AppTradeProductSettlementRespVO respVO = AppTradeProductSettlementRespVO.builder().id(spuId).skus(skus).build();
if(reward != null){
//创建满减活动对象
respVO.setReward(reward);
}
appTradeProductSettlementRespVOS.add(respVO);
});
return success(appTradeProductSettlementRespVOS);
}
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());
}
@PostMapping("/create")
@Operation(summary = "创建订单")
@PreAuthenticated
@ -188,4 +267,78 @@ public class AppTradeOrderController {
return success(tradeOrderUpdateService.createOrderItemCommentByMember(getLoginUserId(), createReqVO));
}
/**
* 计算会员 VIP 优惠价格
*
* @param price 原价
* @param memberLevel 会员等级
* @return 优惠价格
*/
public AppTradeProductSettlementRespVO.Sku calculateVipPrice(Long skuId, Integer price, MemberLevelRespDTO memberLevel) {
if (memberLevel == null || memberLevel.getDiscountPercent() == null) {
return null;
}
Integer newPrice = price * memberLevel.getDiscountPercent() / 100;
return AppTradeProductSettlementRespVO.Sku.builder().
skuId(skuId).
type(PromotionTypeEnum.MEMBER_LEVEL.getType()).
price(newPrice).build();
}
/**
* 计算限时优惠信息
*
* @param price 原价
* @param skuId 商品规格id
* @return 优惠价格
*/
private AppTradeProductSettlementRespVO.Sku calculateDiscountPrice(Long skuId, Integer price) {
if (skuId == null) {
return null;
}
//根据商品id查询限时优惠
List<DiscountProductRespDTO> matchDiscountProductList = discountActivityApi.getMatchDiscountProductList(Collections.singletonList(skuId));
if (matchDiscountProductList != null && !matchDiscountProductList.isEmpty()) {
DiscountProductRespDTO discountProductRespDTO = matchDiscountProductList.get(matchDiscountProductList.size() - 1);
AppTradeProductSettlementRespVO.Sku sku = AppTradeProductSettlementRespVO.Sku.builder().
skuId(skuId).
discountId(discountProductRespDTO.getId()).
type(PromotionTypeEnum.DISCOUNT_ACTIVITY.getType()).
endTime(discountProductRespDTO.getActivityEndTime()).
build();
Integer discountType = discountProductRespDTO.getDiscountType();
if(Objects.equals(PromotionDiscountTypeEnum.PRICE.getType(), discountType)){
sku.setPrice(price - discountProductRespDTO.getDiscountPrice() * 100);
}else if(Objects.equals(PromotionDiscountTypeEnum.PERCENT.getType(), discountType)){
Integer newPrice = price * discountProductRespDTO.getDiscountPercent() / 100;
sku.setPrice(price - newPrice);
}else{
throw exception(DISCOUNT_ACTIVITY_TYPE_NOT_EXISTS);
}
return sku;
}
return null;
}
/**
* 获取第一层满减活动
*
* @param spuId 商品规格id
* @return 优惠价格
*/
private AppTradeProductSettlementRespVO.Reward calculateReward(Long spuId) {
List<RewardActivityMatchRespDTO> matchRewardActivityList = rewardActivityApi.getRewardActivityBySpuIdsAndStatusAndDateTimeLt(Collections.singletonList(spuId), CommonStatusEnum.ENABLE.getStatus(), LocalDateTime.now());
if(matchRewardActivityList != null && !matchRewardActivityList.isEmpty()){
RewardActivityMatchRespDTO rewardActivityMatchRespDTO = matchRewardActivityList.get(matchRewardActivityList.size() - 1);
if(rewardActivityMatchRespDTO != null){
RewardActivityMatchRespDTO.Rule rule = rewardActivityMatchRespDTO.getRules().get(0);
return AppTradeProductSettlementRespVO.Reward.builder().
rewardActivity("" + rule.getLimit() / 100 + (Objects.equals(rewardActivityMatchRespDTO.getConditionType(), PromotionConditionTypeEnum.PRICE.getType())?"":""+"") +rule.getDiscountPrice() / 100)
.id(rewardActivityMatchRespDTO.getId()).build();
}
}
return null;
}
}

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.trade.controller.app.order.vo;
import cn.iocoder.yudao.module.trade.controller.app.base.property.AppProductPropertyValueDetailRespVO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
@ -31,6 +32,13 @@ public class AppTradeOrderSettlementRespVO {
@Schema(description = "总积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
private Integer totalPoint;
/**
* 营销活动数组
*
* 只对应 {@link TradePriceCalculateRespBO.Price#items} 商品匹配的活动
*/
private List<TradePriceCalculateRespBO.Promotion> promotions;
@Schema(description = "购物项")
@Data
public static class Item {

View File

@ -0,0 +1,63 @@
package cn.iocoder.yudao.module.trade.controller.app.order.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "用户 App - 商品结算信息 Response VO")
@Data
@Builder
public class AppTradeProductSettlementRespVO {
@Schema(description = "spu 商品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long id;
@Schema(description = "满减活动对象", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Reward reward;
@Schema(description = "sku 活动信息", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private List<Sku> skus;
/**
* 满减活动
*/
@Data
@Builder
public static class Reward implements Serializable {
@Schema(description = "满减活动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long id;
@Schema(description = "满减活动信息", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private String rewardActivity;
}
/**
* SKU 数组
*/
@Data
@Builder
public static class Sku implements Serializable {
@Schema(description = "商品 SKU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long skuId;
@Schema(description = "价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer price;
@Schema(description = "营销类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer type; // 对应 PromotionTypeEnum 枚举
@Schema(description = "限时优惠id", requiredMode = Schema.RequiredMode.REQUIRED)
private Long discountId;
@Schema(description = "活动结束时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime endTime;
}
}

View File

@ -43,11 +43,9 @@ 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 = "orderProperties.payAppKey", target = "appKey")
@Mapping(source = "afterSale.refundPrice", target = "price")
})
PayRefundCreateReqDTO convert(String userIp, AfterSaleDO afterSale,
TradeOrderProperties orderProperties);
PayRefundCreateReqDTO convert(String userIp, AfterSaleDO afterSale);
MemberUserRespVO convert(MemberUserRespDTO bean);

View File

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

View File

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

View File

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

View File

@ -3,6 +3,10 @@ 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.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.api.discount.DiscountActivityApi;
import cn.iocoder.yudao.module.promotion.api.discount.dto.DiscountProductRespDTO;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum;
@ -14,6 +18,8 @@ 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;
@ -32,6 +38,10 @@ public class TradeDiscountActivityPriceCalculator implements TradePriceCalculato
@Resource
private DiscountActivityApi discountActivityApi;
@Resource
private MemberLevelApi memberLevelApi;
@Resource
private MemberUserApi memberUserApi;
@Override
public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
@ -39,35 +49,101 @@ public class TradeDiscountActivityPriceCalculator implements TradePriceCalculato
if (ObjectUtil.notEqual(result.getType(), TradeOrderTypeEnum.NORMAL.getType())) {
return;
}
boolean discount;
boolean vip;
//----------------------------------限时折扣计算-----------------------------------------
// 获得 SKU 对应的限时折扣活动
List<DiscountProductRespDTO> discountProducts = discountActivityApi.getMatchDiscountProductList(
convertSet(result.getItems(), TradePriceCalculateRespBO.OrderItem::getSkuId));
if (CollUtil.isEmpty(discountProducts)) {
return;
discount = false;
}else {
discount = true;
}
Map<Long, DiscountProductRespDTO> discountProductMap = convertMap(discountProducts, DiscountProductRespDTO::getSkuId);
// 处理每个 SKU 的限时折扣
result.getItems().forEach(orderItem -> {
// 1. 获取该 SKU 的优惠信息
DiscountProductRespDTO discountProduct = discountProductMap.get(orderItem.getSkuId());
if (discountProduct == null) {
return;
}
// 2. 计算优惠金额
Integer newPayPrice = calculatePayPrice(discountProduct, orderItem);
Integer newDiscountPrice = orderItem.getPayPrice() - newPayPrice;
// 3.1 记录优惠明细
if (orderItem.getSelected()) {
// 注意只有在选中的情况下才会记录到优惠明细否则仅仅是更新 SKU 优惠金额用于展示
TradePriceCalculatorHelper.addPromotion(result, orderItem,
discountProduct.getActivityId(), discountProduct.getActivityName(), PromotionTypeEnum.DISCOUNT_ACTIVITY.getType(),
StrUtil.format("限时折扣:省 {} 元", formatPrice(newDiscountPrice)),
newDiscountPrice);
//----------------------------------会员计算-----------------------------------------
MemberLevelRespDTO level;
// 获得用户的会员等级
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;
}
// 3.2 更新 SKU 优惠金额
orderItem.setDiscountPrice(orderItem.getDiscountPrice() + newDiscountPrice);
}else {
level = null;
vip = false;
}
// 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;
}
}
//----------------------------------会员计算-----------------------------------------
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){
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{
TradePriceCalculatorHelper.addPromotion(result, orderItem,
level.getId(), level.getName(), PromotionTypeEnum.MEMBER_LEVEL.getType(),
String.format("会员等级折扣:省 %s 元", formatPrice(vipPrice)),
vipPrice);
// 2.5 更新 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);
}
}
TradePriceCalculatorHelper.recountPayPrice(orderItem);
});
TradePriceCalculatorHelper.recountAllPrice(result);
@ -86,4 +162,20 @@ public class TradeDiscountActivityPriceCalculator implements TradePriceCalculato
return price;
}
/**
* 计算会员 VIP 优惠价格
*
* @param price 原价
* @param discountPercent 折扣
* @return 优惠价格
*/
public Integer calculateVipPrice(Integer price, Integer discountPercent) {
if (discountPercent == null) {
return 0;
}
BigDecimal divide = new BigDecimal(price).multiply(new BigDecimal(discountPercent)).divide(new BigDecimal(100));
Integer newPrice = divide.intValue();
return price - newPrice;
}
}

View File

@ -30,44 +30,49 @@ public class TradeMemberLevelPriceCalculator implements TradePriceCalculator {
@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);
// // 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);
}
/**

View File

@ -23,7 +23,10 @@ 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相关的单测建议改一改
@ -47,14 +50,15 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator
return;
}
// 获得 SKU 对应的满减送活动
List<RewardActivityMatchRespDTO> rewardActivities = rewardActivityApi.getRewardActivityListByStatusAndNow(
CommonStatusEnum.ENABLE.getStatus(), LocalDateTime.now());
List<RewardActivityMatchRespDTO> rewardActivities = rewardActivityApi.getRewardActivityBySpuIdsAndStatusAndDateTimeLt(
convertSet(result.getItems(), TradePriceCalculateRespBO.OrderItem::getSpuId), CommonStatusEnum.ENABLE.getStatus(), LocalDateTime.now());
if (CollUtil.isEmpty(rewardActivities)) {
return;
}
// 处理每个满减送活动
rewardActivities.forEach(rewardActivity -> calculate(param, result, rewardActivity));
// 处理最新的满减送活动
if(!rewardActivities.isEmpty()){
calculate(param, result, rewardActivities.get(0));
}
}
private void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result,
@ -77,6 +81,8 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator
Integer newDiscountPrice = rule.getDiscountPrice();
// 2.2 计算分摊的优惠金额
List<Integer> divideDiscountPrices = TradePriceCalculatorHelper.dividePrice(orderItems, newDiscountPrice);
//计算是否包邮
result.setFreeDelivery(rule.getFreeDelivery());
// 3.1 记录使用的优惠劵
result.setCouponId(param.getCouponId());
@ -119,27 +125,24 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator
/**
* 获得满减送的订单项商品列表
*
* @param result 计算结果
* @param result 计算结果
* @param rewardActivity 满减送活动
* @return 订单项商品列表
*/
private List<TradePriceCalculateRespBO.OrderItem> filterMatchActivityOrderItems(TradePriceCalculateRespBO result,
RewardActivityMatchRespDTO rewardActivity) {
// 情况一全部商品都可以参与
if (PromotionProductScopeEnum.isAll(rewardActivity.getProductScope())) {
Integer productScope = rewardActivity.getProductScope();
if(PromotionProductScopeEnum.isAll(productScope)){
return result.getItems();
}
// 情况二指定商品参与
if (PromotionProductScopeEnum.isSpu(rewardActivity.getProductScope())) {
}else if (PromotionProductScopeEnum.isSpu(productScope)) {
return filterList(result.getItems(),
orderItem -> CollUtil.contains(rewardActivity.getProductScopeValues(), orderItem.getSpuId()));
}
// 情况三指定商品类型参与
if (PromotionProductScopeEnum.isCategory(rewardActivity.getProductScope())) {
}else if (PromotionProductScopeEnum.isCategory(productScope)) {
return filterList(result.getItems(),
orderItem -> CollUtil.contains(rewardActivity.getProductScopeValues(), orderItem.getCategoryId()));
}else{
throw exception(REWARD_ACTIVITY_TYPE_NOT_EXISTS);
}
return List.of();
}
/**

View File

@ -11,6 +11,7 @@ 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;
@ -104,6 +105,7 @@ 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://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://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=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://127.0.0.1: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://192.168.10.207:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true&nullCatalogMeansCurrent=true
# username: root
# password: 123456
# Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
data:
redis:
host: 127.0.0.1 # 地址
host: 192.168.10.207 # 地址
port: 6379 # 端口
database: 0 # 数据库索引
# password: dev # 密码,建议生产环境开启
password: 123456 # 密码,建议生产环境开启
--- #################### 定时任务相关配置 ####################
@ -200,8 +200,8 @@ wx:
# secret: 6f270509224a7ae1296bbf1c8cb97aed
# appid: wxc4598c446f8a9cb3 # 测试号Kongdy 提供的)
# secret: 4a1a04e07f6a4a0751b39c3064a92c8b
appid: wx66186af0759f47c9 # 测试号puhui 提供的)
secret: 3218bcbd112cbc614c7264ceb20144ac
appid: wx9a0a5b259d852380 # 测试号puhui 提供的)
secret: 70e65fa9d1a4f2c4e1b2aa8751d3b75e
config-storage:
type: RedisTemplate # 采用 RedisTemplate 操作 Redis会自动从 Spring 中获取
key-prefix: wa # Redis Key 的前缀