Commit e44c0e668e45e9a7439ea2fe96b02160f79e3ce5
1 parent
e47b3f0a
【功能】完成商品列表价格计算时,需要支持减免金额的功能
Showing
15 changed files
with
350 additions
and
50 deletions
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.java
| ... | ... | @@ -69,8 +69,8 @@ public class AppProductSpuController { |
| 69 | 69 | list.forEach(spu -> spu.setSalesCount(spu.getSalesCount() + spu.getVirtualSalesCount())); |
| 70 | 70 | List<AppProductSpuRespVO> voList = BeanUtils.toBean(list, AppProductSpuRespVO.class); |
| 71 | 71 | // 处理 vip 价格 |
| 72 | - MemberLevelRespDTO memberLevel = getMemberLevel(); | |
| 73 | - voList.forEach(vo -> vo.setVipPrice(calculateVipPrice(vo.getPrice(), memberLevel))); | |
| 72 | +// MemberLevelRespDTO memberLevel = getMemberLevel(); | |
| 73 | +// voList.forEach(vo -> vo.setVipPrice(calculateVipPrice(vo.getPrice(), memberLevel))); | |
| 74 | 74 | return success(voList); |
| 75 | 75 | } |
| 76 | 76 | |
| ... | ... | @@ -86,8 +86,8 @@ public class AppProductSpuController { |
| 86 | 86 | pageResult.getList().forEach(spu -> spu.setSalesCount(spu.getSalesCount() + spu.getVirtualSalesCount())); |
| 87 | 87 | PageResult<AppProductSpuRespVO> voPageResult = BeanUtils.toBean(pageResult, AppProductSpuRespVO.class); |
| 88 | 88 | // 处理 vip 价格 |
| 89 | - MemberLevelRespDTO memberLevel = getMemberLevel(); | |
| 90 | - voPageResult.getList().forEach(vo -> vo.setVipPrice(calculateVipPrice(vo.getPrice(), memberLevel))); | |
| 89 | +// MemberLevelRespDTO memberLevel = getMemberLevel(); | |
| 90 | +// voPageResult.getList().forEach(vo -> vo.setVipPrice(calculateVipPrice(vo.getPrice(), memberLevel))); | |
| 91 | 91 | return success(voPageResult); |
| 92 | 92 | } |
| 93 | 93 | |
| ... | ... | @@ -142,7 +142,7 @@ public class AppProductSpuController { |
| 142 | 142 | */ |
| 143 | 143 | public Integer calculateVipPrice(Integer price, MemberLevelRespDTO memberLevel) { |
| 144 | 144 | if (memberLevel == null || memberLevel.getDiscountPercent() == null) { |
| 145 | - return 0; | |
| 145 | + return null; | |
| 146 | 146 | } |
| 147 | 147 | Integer newPrice = price * memberLevel.getDiscountPercent() / 100; |
| 148 | 148 | return price - newPrice; | ... | ... |
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuRespVO.java
| ... | ... | @@ -38,8 +38,8 @@ public class AppProductSpuRespVO { |
| 38 | 38 | @Schema(description = "市场价,单位使用:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") |
| 39 | 39 | private Integer marketPrice; |
| 40 | 40 | |
| 41 | - @Schema(description = "VIP 价格,单位使用:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "968") // 通过会员等级,计算出折扣后价格 | |
| 42 | - private Integer vipPrice; | |
| 41 | +// @Schema(description = "VIP 价格,单位使用:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "968") // 通过会员等级,计算出折扣后价格 | |
| 42 | +// private Integer vipPrice; | |
| 43 | 43 | |
| 44 | 44 | @Schema(description = "库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "666") |
| 45 | 45 | private Integer stock; | ... | ... |
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/discount/dto/DiscountProductRespDTO.java
| ... | ... | @@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.promotion.api.discount.dto; |
| 2 | 2 | |
| 3 | 3 | import lombok.Data; |
| 4 | 4 | |
| 5 | +import java.time.LocalDateTime; | |
| 6 | + | |
| 5 | 7 | /** |
| 6 | 8 | * 限时折扣活动商品 Response DTO |
| 7 | 9 | * |
| ... | ... | @@ -44,5 +46,12 @@ public class DiscountProductRespDTO { |
| 44 | 46 | * 活动标题 |
| 45 | 47 | */ |
| 46 | 48 | private String activityName; |
| 49 | + /** | |
| 50 | + * 活动结束时间点 | |
| 51 | + * | |
| 52 | + * 冗余 {@link DiscountActivityDO#getEndTime()} | |
| 53 | + */ | |
| 54 | + private LocalDateTime activityEndTime; | |
| 55 | + | |
| 47 | 56 | |
| 48 | 57 | } | ... | ... |
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/reward/RewardActivityApi.java
| ... | ... | @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.promotion.api.reward; |
| 3 | 3 | import cn.iocoder.yudao.module.promotion.api.reward.dto.RewardActivityMatchRespDTO; |
| 4 | 4 | |
| 5 | 5 | import java.time.LocalDateTime; |
| 6 | +import java.util.Collection; | |
| 6 | 7 | import java.util.List; |
| 7 | 8 | |
| 8 | 9 | /** |
| ... | ... | @@ -21,4 +22,13 @@ public interface RewardActivityApi { |
| 21 | 22 | */ |
| 22 | 23 | List<RewardActivityMatchRespDTO> getRewardActivityListByStatusAndNow(Integer status, LocalDateTime dateTime); |
| 23 | 24 | |
| 25 | + /** | |
| 26 | + * 获取指定 spu 编号最近参加的活动,每个 spuId 只返回一条记录 | |
| 27 | + * | |
| 28 | + * @param spuIds spu 编号 | |
| 29 | + * @param status 状态 | |
| 30 | + * @param dateTime 当前日期时间 | |
| 31 | + * @return 满减送活动列表 | |
| 32 | + */ | |
| 33 | + List<RewardActivityMatchRespDTO> getRewardActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime); | |
| 24 | 34 | } | ... | ... |
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java
| ... | ... | @@ -15,6 +15,7 @@ public interface ErrorCodeConstants { |
| 15 | 15 | ErrorCode DISCOUNT_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_001_002, "限时折扣活动已关闭,不能修改"); |
| 16 | 16 | ErrorCode DISCOUNT_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED = new ErrorCode(1_013_001_003, "限时折扣活动未关闭,不能删除"); |
| 17 | 17 | ErrorCode DISCOUNT_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_001_004, "限时折扣活动已关闭,不能重复关闭"); |
| 18 | + ErrorCode DISCOUNT_ACTIVITY_TYPE_NOT_EXISTS = new ErrorCode(1_013_001_005, "限时折扣活动类型不存在"); | |
| 18 | 19 | |
| 19 | 20 | // ========== Banner 相关 1-013-002-000 ============ |
| 20 | 21 | ErrorCode BANNER_NOT_EXISTS = new ErrorCode(1_013_002_000, "Banner 不存在"); | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/reward/RewardActivityApiImpl.java
| ... | ... | @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.promotion.api.reward; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 4 | 4 | import cn.iocoder.yudao.module.promotion.api.reward.dto.RewardActivityMatchRespDTO; |
| 5 | +import cn.iocoder.yudao.module.promotion.convert.reward.RewardActivityConvert; | |
| 5 | 6 | import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO; |
| 6 | 7 | import cn.iocoder.yudao.module.promotion.service.reward.RewardActivityService; |
| 7 | 8 | import jakarta.annotation.Resource; |
| ... | ... | @@ -9,6 +10,7 @@ import org.springframework.stereotype.Service; |
| 9 | 10 | import org.springframework.validation.annotation.Validated; |
| 10 | 11 | |
| 11 | 12 | import java.time.LocalDateTime; |
| 13 | +import java.util.Collection; | |
| 12 | 14 | import java.util.List; |
| 13 | 15 | |
| 14 | 16 | /** |
| ... | ... | @@ -29,4 +31,10 @@ public class RewardActivityApiImpl implements RewardActivityApi { |
| 29 | 31 | return BeanUtils.toBean(list, RewardActivityMatchRespDTO.class); |
| 30 | 32 | } |
| 31 | 33 | |
| 34 | + @Override | |
| 35 | + public List<RewardActivityMatchRespDTO> getRewardActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime) { | |
| 36 | + List<RewardActivityDO> rewardActivityBySpuIdsAndStatusAndDateTimeLt = rewardActivityService.getRewardActivityBySpuIdsAndStatusAndDateTimeLt(spuIds, status, dateTime); | |
| 37 | + return RewardActivityConvert.INSTANCE.convertList(rewardActivityBySpuIdsAndStatusAndDateTimeLt); | |
| 38 | + } | |
| 39 | + | |
| 32 | 40 | } | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/activity/AppActivityController.java
| ... | ... | @@ -33,6 +33,7 @@ import org.springframework.web.bind.annotation.RestController; |
| 33 | 33 | |
| 34 | 34 | import java.time.LocalDateTime; |
| 35 | 35 | import java.util.*; |
| 36 | +import java.util.stream.Collectors; | |
| 36 | 37 | |
| 37 | 38 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 38 | 39 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; |
| ... | ... | @@ -150,36 +151,32 @@ public class AppActivityController { |
| 150 | 151 | } |
| 151 | 152 | |
| 152 | 153 | private void getRewardActivityList(Collection<Long> spuIds, LocalDateTime now, List<AppActivityRespVO> activityList) { |
| 153 | - // 1.1 获得所有的活动 | |
| 154 | - List<RewardActivityDO> rewardActivityList = rewardActivityService.getRewardActivityListByStatusAndDateTimeLt( | |
| 155 | - CommonStatusEnum.ENABLE.getStatus(), now); | |
| 154 | + // TODO @puhui999:有 3 范围,不只 spuId,还有 categoryId,全部,下次 fix | |
| 155 | + List<RewardActivityDO> rewardActivityList = rewardActivityService.getRewardActivityBySpuIdsAndStatusAndDateTimeLt( | |
| 156 | + spuIds, CommonStatusEnum.ENABLE.getStatus(), now); | |
| 156 | 157 | if (CollUtil.isEmpty(rewardActivityList)) { |
| 157 | 158 | return; |
| 158 | 159 | } |
| 159 | - // 1.2 获得所有的商品信息 | |
| 160 | - List<ProductSpuRespDTO> spuList = productSpuApi.getSpuList(spuIds); | |
| 161 | - if (CollUtil.isEmpty(spuList)) { | |
| 162 | - return; | |
| 163 | - } | |
| 164 | 160 | |
| 165 | - // 2. 构建活动 | |
| 166 | - for (RewardActivityDO rewardActivity : rewardActivityList) { | |
| 167 | - // 情况一:所有商品都能参加 | |
| 168 | - if (PromotionProductScopeEnum.isAll(rewardActivity.getProductScope())) { | |
| 169 | - buildAppActivityRespVO(rewardActivity, spuIds, activityList); | |
| 170 | - } | |
| 171 | - // 情况二:指定商品参加 | |
| 172 | - if (PromotionProductScopeEnum.isSpu(rewardActivity.getProductScope())) { | |
| 173 | - List<Long> fSpuIds = spuList.stream().map(ProductSpuRespDTO::getId).filter(id -> | |
| 174 | - rewardActivity.getProductScopeValues().contains(id)).toList(); | |
| 175 | - buildAppActivityRespVO(rewardActivity, fSpuIds, activityList); | |
| 176 | - } | |
| 177 | - // 情况三:指定商品类型参加 | |
| 178 | - if (PromotionProductScopeEnum.isCategory(rewardActivity.getProductScope())) { | |
| 179 | - List<Long> fSpuIds = spuList.stream().filter(spuItem -> rewardActivity.getProductScopeValues() | |
| 180 | - .contains(spuItem.getCategoryId())).map(ProductSpuRespDTO::getId).toList(); | |
| 181 | - buildAppActivityRespVO(rewardActivity, fSpuIds, activityList); | |
| 161 | + Map<Long, Optional<RewardActivityDO>> spuIdAndActivityMap = spuIds.stream() | |
| 162 | + .collect(Collectors.toMap( | |
| 163 | + spuId -> spuId, | |
| 164 | + spuId -> rewardActivityList.stream() | |
| 165 | + .filter(activity -> | |
| 166 | + ( activity.getProductScopeValues()!=null && | |
| 167 | + (activity.getProductScopeValues().contains(spuId) || | |
| 168 | + activity.getProductScopeValues().contains(productSpuApi.getSpu(spuId).getCategoryId()))) || | |
| 169 | + activity.getProductScope()==1 | |
| 170 | + ) | |
| 171 | + .max(Comparator.comparing(RewardActivityDO::getCreateTime)))); | |
| 172 | + for (Long supId : spuIdAndActivityMap.keySet()) { | |
| 173 | + if (spuIdAndActivityMap.get(supId).isEmpty()) { | |
| 174 | + continue; | |
| 182 | 175 | } |
| 176 | + | |
| 177 | + RewardActivityDO rewardActivityDO = spuIdAndActivityMap.get(supId).get(); | |
| 178 | + activityList.add(new AppActivityRespVO(rewardActivityDO.getId(), PromotionTypeEnum.REWARD_ACTIVITY.getType(), | |
| 179 | + rewardActivityDO.getName(), supId, rewardActivityDO.getStartTime(), rewardActivityDO.getEndTime())); | |
| 183 | 180 | } |
| 184 | 181 | } |
| 185 | 182 | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/reward/RewardActivityMapper.java
| 1 | 1 | package cn.iocoder.yudao.module.promotion.dal.mysql.reward; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.util.StrUtil; | |
| 3 | 4 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | 5 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| 5 | 6 | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| 6 | 7 | import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityPageReqVO; |
| 7 | 8 | import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO; |
| 9 | +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | |
| 8 | 10 | import org.apache.ibatis.annotations.Mapper; |
| 9 | 11 | |
| 10 | 12 | import java.time.LocalDateTime; |
| 13 | +import java.util.Collection; | |
| 11 | 14 | import java.util.List; |
| 15 | +import java.util.function.Function; | |
| 16 | +import java.util.stream.Collectors; | |
| 12 | 17 | |
| 13 | 18 | /** |
| 14 | 19 | * 满减送活动 Mapper |
| ... | ... | @@ -25,13 +30,47 @@ public interface RewardActivityMapper extends BaseMapperX<RewardActivityDO> { |
| 25 | 30 | .orderByDesc(RewardActivityDO::getId)); |
| 26 | 31 | } |
| 27 | 32 | |
| 33 | + default List<RewardActivityDO> selectListBySpuIdsAndStatus(Collection<Long> spuIds, Integer status) { | |
| 34 | + Function<Collection<Long>, String> productScopeValuesFindInSetFunc = ids -> ids.stream() | |
| 35 | + .map(id -> StrUtil.format("FIND_IN_SET({}, product_scope_values) ", id)) | |
| 36 | + .collect(Collectors.joining(" OR ")); | |
| 37 | + return selectList(new QueryWrapper<RewardActivityDO>() | |
| 38 | + .eq("status", status) | |
| 39 | + .apply(productScopeValuesFindInSetFunc.apply(spuIds))); | |
| 40 | + } | |
| 41 | + | |
| 42 | + /** | |
| 43 | + * 获取指定活动编号的活动列表且 | |
| 44 | + * 开始时间和结束时间小于给定时间 dateTime 的活动列表 | |
| 45 | + * | |
| 46 | + * @param status 状态 | |
| 47 | + * @param dateTime 指定日期 | |
| 48 | + * @return 活动列表 | |
| 49 | + */ | |
| 28 | 50 | default List<RewardActivityDO> selectListByStatusAndDateTimeLt(Integer status, LocalDateTime dateTime) { |
| 29 | 51 | return selectList(new LambdaQueryWrapperX<RewardActivityDO>() |
| 30 | 52 | .eq(RewardActivityDO::getStatus, status) |
| 31 | - // 开始时间 < 指定时间(dateTime) < 结束时间,也就是说获取指定时间段的活动 | |
| 32 | - .lt(RewardActivityDO::getStartTime, dateTime).gt(RewardActivityDO::getEndTime, dateTime) | |
| 53 | + .lt(RewardActivityDO::getStartTime, dateTime) | |
| 54 | + .gt(RewardActivityDO::getEndTime, dateTime)// 开始时间 < 指定时间 < 结束时间,也就是说获取指定时间段的活动 | |
| 33 | 55 | .orderByAsc(RewardActivityDO::getStartTime) |
| 34 | 56 | ); |
| 35 | 57 | } |
| 36 | 58 | |
| 59 | + default List<RewardActivityDO> getRewardActivityByStatusAndDateTimeLt(Collection<Long> spuIds,Collection<Long> categoryIds, Integer status, LocalDateTime dateTime) { | |
| 60 | + //拼接通用券查询语句 | |
| 61 | + Function<Collection<Long>, String> productScopeValuesFindInSetFunc = ids -> ids.stream() | |
| 62 | + .map(id -> StrUtil.format("FIND_IN_SET({}, product_scope_values) ", id)) | |
| 63 | + .collect(Collectors.joining(" OR ")); | |
| 64 | + return selectList(new LambdaQueryWrapperX<RewardActivityDO>() | |
| 65 | + .eq(RewardActivityDO::getStatus,status) | |
| 66 | + .lt(RewardActivityDO::getStartTime, dateTime) | |
| 67 | + .gt(RewardActivityDO::getEndTime, dateTime) | |
| 68 | + .and(i -> i. eq(RewardActivityDO::getProductScope, 2).and(i1 -> i1.apply(productScopeValuesFindInSetFunc.apply(spuIds)))) | |
| 69 | + .or(i -> i.eq(RewardActivityDO::getProductScope, 1)) | |
| 70 | + .or(i -> i. eq(RewardActivityDO::getProductScope, 3).and(i1 -> i1.apply(productScopeValuesFindInSetFunc.apply(categoryIds)))) | |
| 71 | + .orderByDesc(RewardActivityDO::getId) | |
| 72 | + .last("limit 1") | |
| 73 | + ); | |
| 74 | + } | |
| 75 | + | |
| 37 | 76 | } | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityService.java
| ... | ... | @@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO; |
| 8 | 8 | import jakarta.validation.Valid; |
| 9 | 9 | |
| 10 | 10 | import java.time.LocalDateTime; |
| 11 | +import java.util.Collection; | |
| 11 | 12 | import java.util.List; |
| 12 | 13 | |
| 13 | 14 | /** |
| ... | ... | @@ -71,4 +72,14 @@ public interface RewardActivityService { |
| 71 | 72 | */ |
| 72 | 73 | List<RewardActivityDO> getRewardActivityListByStatusAndDateTimeLt(Integer status, LocalDateTime dateTime); |
| 73 | 74 | |
| 75 | + /** | |
| 76 | + * 获取指定 spu 编号最近参加的活动,每个 spuId 只返回一条记录 | |
| 77 | + * | |
| 78 | + * @param spuIds spu 编号 | |
| 79 | + * @param status 状态 | |
| 80 | + * @param dateTime 当前日期时间 | |
| 81 | + * @return 满减送活动列表 | |
| 82 | + */ | |
| 83 | + List<RewardActivityDO> getRewardActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime); | |
| 84 | + | |
| 74 | 85 | } | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImpl.java
| 1 | 1 | package cn.iocoder.yudao.module.promotion.service.reward; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 3 | 4 | import cn.hutool.core.date.LocalDateTimeUtil; |
| 4 | 5 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 5 | 6 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| ... | ... | @@ -22,8 +23,11 @@ import org.springframework.stereotype.Service; |
| 22 | 23 | import org.springframework.validation.annotation.Validated; |
| 23 | 24 | |
| 24 | 25 | import java.time.LocalDateTime; |
| 26 | +import java.util.Collection; | |
| 27 | +import java.util.Collections; | |
| 25 | 28 | import java.util.List; |
| 26 | 29 | import java.util.Objects; |
| 30 | +import java.util.stream.Collectors; | |
| 27 | 31 | |
| 28 | 32 | import static cn.hutool.core.collection.CollUtil.intersectionDistinct; |
| 29 | 33 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| ... | ... | @@ -205,4 +209,19 @@ public class RewardActivityServiceImpl implements RewardActivityService { |
| 205 | 209 | return rewardActivityMapper.selectListByStatusAndDateTimeLt(status, dateTime); |
| 206 | 210 | } |
| 207 | 211 | |
| 212 | + @Override | |
| 213 | + public List<RewardActivityDO> getRewardActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime) { | |
| 214 | + List<ProductSpuRespDTO> spuList = productSpuApi.validateSpuList(spuIds); | |
| 215 | + //查询出商品的分类ids | |
| 216 | + List<Long> categoryIds = spuList.stream().map(ProductSpuRespDTO::getCategoryId).collect(Collectors.toList()); | |
| 217 | + // 1. 查询出指定 spuId 的 spu 参加的活动 | |
| 218 | + List<RewardActivityDO> rewardActivityList = rewardActivityMapper.getRewardActivityByStatusAndDateTimeLt(spuIds, categoryIds,status,dateTime); | |
| 219 | + if (CollUtil.isEmpty(rewardActivityList)) { | |
| 220 | + return Collections.emptyList(); | |
| 221 | + } | |
| 222 | + | |
| 223 | + // 2. 查询活动详情 | |
| 224 | + return rewardActivityList; | |
| 225 | + } | |
| 226 | + | |
| 208 | 227 | } | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/resources/mapper/discount/DiscountProductMapper.xml
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java
| 1 | 1 | package cn.iocoder.yudao.module.trade.controller.app.order; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | |
| 3 | 4 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 4 | 5 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 5 | 6 | import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated; |
| 7 | +import cn.iocoder.yudao.module.member.api.level.MemberLevelApi; | |
| 8 | +import cn.iocoder.yudao.module.member.api.level.dto.MemberLevelRespDTO; | |
| 9 | +import cn.iocoder.yudao.module.member.api.user.MemberUserApi; | |
| 10 | +import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO; | |
| 6 | 11 | import cn.iocoder.yudao.module.pay.api.notify.dto.PayOrderNotifyReqDTO; |
| 12 | +import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi; | |
| 13 | +import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO; | |
| 14 | +import cn.iocoder.yudao.module.promotion.api.discount.DiscountActivityApi; | |
| 15 | +import cn.iocoder.yudao.module.promotion.api.discount.dto.DiscountProductRespDTO; | |
| 16 | +import cn.iocoder.yudao.module.promotion.api.reward.RewardActivityApi; | |
| 17 | +import cn.iocoder.yudao.module.promotion.api.reward.dto.RewardActivityMatchRespDTO; | |
| 18 | +import cn.iocoder.yudao.module.promotion.enums.common.PromotionConditionTypeEnum; | |
| 19 | +import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum; | |
| 20 | +import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum; | |
| 7 | 21 | import cn.iocoder.yudao.module.trade.controller.app.order.vo.*; |
| 8 | 22 | import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemCommentCreateReqVO; |
| 9 | 23 | import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemRespVO; |
| ... | ... | @@ -27,12 +41,14 @@ import lombok.extern.slf4j.Slf4j; |
| 27 | 41 | import org.springframework.validation.annotation.Validated; |
| 28 | 42 | import org.springframework.web.bind.annotation.*; |
| 29 | 43 | |
| 30 | -import java.util.List; | |
| 31 | -import java.util.Map; | |
| 44 | +import java.time.LocalDateTime; | |
| 45 | +import java.util.*; | |
| 32 | 46 | |
| 47 | +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | |
| 33 | 48 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 34 | 49 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; |
| 35 | 50 | import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; |
| 51 | +import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.DISCOUNT_ACTIVITY_TYPE_NOT_EXISTS; | |
| 36 | 52 | |
| 37 | 53 | @Tag(name = "用户 App - 交易订单") |
| 38 | 54 | @RestController |
| ... | ... | @@ -54,6 +70,17 @@ public class AppTradeOrderController { |
| 54 | 70 | @Resource |
| 55 | 71 | private TradeOrderProperties tradeOrderProperties; |
| 56 | 72 | |
| 73 | + @Resource | |
| 74 | + private MemberLevelApi memberLevelApi; | |
| 75 | + @Resource | |
| 76 | + private MemberUserApi memberUserApi; | |
| 77 | + @Resource | |
| 78 | + private DiscountActivityApi discountActivityApi; | |
| 79 | + @Resource | |
| 80 | + private RewardActivityApi rewardActivityApi; | |
| 81 | + @Resource | |
| 82 | + private ProductSkuApi productKpuApi; | |
| 83 | + | |
| 57 | 84 | @GetMapping("/settlement") |
| 58 | 85 | @Operation(summary = "获得订单结算信息") |
| 59 | 86 | @PreAuthenticated |
| ... | ... | @@ -61,6 +88,51 @@ public class AppTradeOrderController { |
| 61 | 88 | return success(tradeOrderUpdateService.settlementOrder(getLoginUserId(), settlementReqVO)); |
| 62 | 89 | } |
| 63 | 90 | |
| 91 | + @GetMapping("/settlementProduct") | |
| 92 | + @Operation(summary = "获得商品结算信息") | |
| 93 | + public CommonResult<List<AppTradeProductSettlementRespVO>> settlementProduct(@RequestParam("ids") Set<Long> ids) { | |
| 94 | + List<AppTradeProductSettlementRespVO> appTradeProductSettlementRespVOS = new ArrayList<>(); | |
| 95 | + MemberLevelRespDTO memberLevel = getMemberLevel(); | |
| 96 | + ids.forEach(spuId -> { | |
| 97 | + List<AppTradeProductSettlementRespVO.Sku> skus = new ArrayList<>(); | |
| 98 | + List<ProductSkuRespDTO> skuList = productKpuApi.getSkuListBySpuId(Collections.singletonList(spuId)); | |
| 99 | + //查询sku的会员和限时优惠 | |
| 100 | + skuList.forEach(sku -> { | |
| 101 | + //查询限时优惠价格 | |
| 102 | + AppTradeProductSettlementRespVO.Sku skuDiscount = calculateDiscountPrice(sku.getId(), sku.getPrice()); | |
| 103 | + if(skuDiscount != null){ | |
| 104 | + skus.add(skuDiscount); | |
| 105 | + } | |
| 106 | + | |
| 107 | + //查询会员价 | |
| 108 | + AppTradeProductSettlementRespVO.Sku skuVip = calculateVipPrice(sku.getId(), sku.getPrice(), memberLevel); | |
| 109 | + if(skuVip != null){ | |
| 110 | + skus.add(skuVip); | |
| 111 | + } | |
| 112 | + }); | |
| 113 | + AppTradeProductSettlementRespVO.Reward reward = calculateReward(spuId); | |
| 114 | + AppTradeProductSettlementRespVO respVO = AppTradeProductSettlementRespVO.builder().id(spuId).skus(skus).build(); | |
| 115 | + if(reward != null){ | |
| 116 | + //创建满减活动对象 | |
| 117 | + respVO.setReward(reward); | |
| 118 | + } | |
| 119 | + appTradeProductSettlementRespVOS.add(respVO); | |
| 120 | + }); | |
| 121 | + return success(appTradeProductSettlementRespVOS); | |
| 122 | + } | |
| 123 | + | |
| 124 | + private MemberLevelRespDTO getMemberLevel() { | |
| 125 | + Long userId = getLoginUserId(); | |
| 126 | + if (userId == null) { | |
| 127 | + return null; | |
| 128 | + } | |
| 129 | + MemberUserRespDTO user = memberUserApi.getUser(userId); | |
| 130 | + if (user.getLevelId() == null || user.getLevelId() <= 0) { | |
| 131 | + return null; | |
| 132 | + } | |
| 133 | + return memberLevelApi.getMemberLevel(user.getLevelId()); | |
| 134 | + } | |
| 135 | + | |
| 64 | 136 | @PostMapping("/create") |
| 65 | 137 | @Operation(summary = "创建订单") |
| 66 | 138 | @PreAuthenticated |
| ... | ... | @@ -188,4 +260,78 @@ public class AppTradeOrderController { |
| 188 | 260 | return success(tradeOrderUpdateService.createOrderItemCommentByMember(getLoginUserId(), createReqVO)); |
| 189 | 261 | } |
| 190 | 262 | |
| 263 | + /** | |
| 264 | + * 计算会员 VIP 优惠价格 | |
| 265 | + * | |
| 266 | + * @param price 原价 | |
| 267 | + * @param memberLevel 会员等级 | |
| 268 | + * @return 优惠价格 | |
| 269 | + */ | |
| 270 | + public AppTradeProductSettlementRespVO.Sku calculateVipPrice(Long skuId, Integer price, MemberLevelRespDTO memberLevel) { | |
| 271 | + if (memberLevel == null || memberLevel.getDiscountPercent() == null) { | |
| 272 | + return null; | |
| 273 | + } | |
| 274 | + Integer newPrice = price * memberLevel.getDiscountPercent() / 100; | |
| 275 | + return AppTradeProductSettlementRespVO.Sku.builder(). | |
| 276 | + skuId(skuId). | |
| 277 | + type(PromotionTypeEnum.MEMBER_LEVEL.getType()). | |
| 278 | + price(price - newPrice).build(); | |
| 279 | + } | |
| 280 | + | |
| 281 | + /** | |
| 282 | + * 计算限时优惠信息 | |
| 283 | + * | |
| 284 | + * @param price 原价 | |
| 285 | + * @param skuId 商品规格id | |
| 286 | + * @return 优惠价格 | |
| 287 | + */ | |
| 288 | + private AppTradeProductSettlementRespVO.Sku calculateDiscountPrice(Long skuId, Integer price) { | |
| 289 | + if (skuId == null) { | |
| 290 | + return null; | |
| 291 | + } | |
| 292 | + | |
| 293 | + //根据商品id查询限时优惠 | |
| 294 | + List<DiscountProductRespDTO> matchDiscountProductList = discountActivityApi.getMatchDiscountProductList(Collections.singletonList(skuId)); | |
| 295 | + if (matchDiscountProductList != null && !matchDiscountProductList.isEmpty()) { | |
| 296 | + DiscountProductRespDTO discountProductRespDTO = matchDiscountProductList.get(matchDiscountProductList.size() - 1); | |
| 297 | + AppTradeProductSettlementRespVO.Sku sku = AppTradeProductSettlementRespVO.Sku.builder(). | |
| 298 | + skuId(skuId). | |
| 299 | + discountId(discountProductRespDTO.getId()). | |
| 300 | + type(PromotionTypeEnum.DISCOUNT_ACTIVITY.getType()). | |
| 301 | + endTime(discountProductRespDTO.getActivityEndTime()). | |
| 302 | + build(); | |
| 303 | + Integer discountType = discountProductRespDTO.getDiscountType(); | |
| 304 | + if(Objects.equals(PromotionDiscountTypeEnum.PRICE.getType(), discountType)){ | |
| 305 | + sku.setPrice(price - discountProductRespDTO.getDiscountPrice() * 100); | |
| 306 | + }else if(Objects.equals(PromotionDiscountTypeEnum.PERCENT.getType(), discountType)){ | |
| 307 | + Integer newPrice = price * discountProductRespDTO.getDiscountPercent() / 100; | |
| 308 | + sku.setPrice(price - newPrice); | |
| 309 | + }else{ | |
| 310 | + throw exception(DISCOUNT_ACTIVITY_TYPE_NOT_EXISTS); | |
| 311 | + } | |
| 312 | + return sku; | |
| 313 | + } | |
| 314 | + return null; | |
| 315 | + } | |
| 316 | + | |
| 317 | + /** | |
| 318 | + * 获取第一层满减活动 | |
| 319 | + * | |
| 320 | + * @param spuId 商品规格id | |
| 321 | + * @return 优惠价格 | |
| 322 | + */ | |
| 323 | + private AppTradeProductSettlementRespVO.Reward calculateReward(Long spuId) { | |
| 324 | + List<RewardActivityMatchRespDTO> matchRewardActivityList = rewardActivityApi.getRewardActivityBySpuIdsAndStatusAndDateTimeLt(Collections.singletonList(spuId), CommonStatusEnum.ENABLE.getStatus(), LocalDateTime.now()); | |
| 325 | + if(matchRewardActivityList != null && !matchRewardActivityList.isEmpty()){ | |
| 326 | + RewardActivityMatchRespDTO rewardActivityMatchRespDTO = matchRewardActivityList.get(matchRewardActivityList.size() - 1); | |
| 327 | + if(rewardActivityMatchRespDTO != null){ | |
| 328 | + RewardActivityMatchRespDTO.Rule rule = rewardActivityMatchRespDTO.getRules().get(0); | |
| 329 | + return AppTradeProductSettlementRespVO.Reward.builder(). | |
| 330 | + rewardActivity("满" + rule.getLimit() / 100 + (Objects.equals(rewardActivityMatchRespDTO.getConditionType(), PromotionConditionTypeEnum.PRICE.getType())?"元":"件"+"减") +rule.getDiscountPrice() / 100) | |
| 331 | + .rewardId(rewardActivityMatchRespDTO.getId()).build(); | |
| 332 | + } | |
| 333 | + } | |
| 334 | + return null; | |
| 335 | + } | |
| 336 | + | |
| 191 | 337 | } | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeProductSettlementRespVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.trade.controller.app.order.vo; | |
| 2 | + | |
| 3 | +import io.swagger.v3.oas.annotations.media.Schema; | |
| 4 | +import lombok.Builder; | |
| 5 | +import lombok.Data; | |
| 6 | + | |
| 7 | +import java.io.Serializable; | |
| 8 | +import java.time.LocalDateTime; | |
| 9 | +import java.util.List; | |
| 10 | + | |
| 11 | +@Schema(description = "用户 App - 商品结算信息 Response VO") | |
| 12 | +@Data | |
| 13 | +@Builder | |
| 14 | +public class AppTradeProductSettlementRespVO { | |
| 15 | + | |
| 16 | + @Schema(description = "商品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 17 | + private Long id; | |
| 18 | + | |
| 19 | + @Schema(description = "满减活动对象", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 20 | + private Reward reward; | |
| 21 | + | |
| 22 | + @Schema(description = "sku活动信息", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 23 | + private List<Sku> skus; | |
| 24 | + | |
| 25 | + /** | |
| 26 | + * 满减活动 | |
| 27 | + */ | |
| 28 | + @Data | |
| 29 | + @Builder | |
| 30 | + public static class Reward implements Serializable { | |
| 31 | + @Schema(description = "满减活动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 32 | + private Long rewardId; | |
| 33 | + | |
| 34 | + @Schema(description = "满减活动信息", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 35 | + private String rewardActivity; | |
| 36 | + } | |
| 37 | + | |
| 38 | + /** | |
| 39 | + * SKU 数组 | |
| 40 | + */ | |
| 41 | + @Data | |
| 42 | + @Builder | |
| 43 | + public static class Sku implements Serializable { | |
| 44 | + @Schema(description = "商品 SKU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 45 | + private Long skuId; | |
| 46 | + | |
| 47 | + @Schema(description = "价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 48 | + private Integer price; | |
| 49 | + | |
| 50 | + @Schema(description = "营销类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") //PromotionTypeEnum | |
| 51 | + private Integer type; | |
| 52 | + | |
| 53 | + @Schema(description = "限时优惠id", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 54 | + private Long discountId; | |
| 55 | + | |
| 56 | + @Schema(description = "活动结束时间", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 57 | + private LocalDateTime endTime; | |
| 58 | + } | |
| 59 | +} | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java
| ... | ... | @@ -23,6 +23,7 @@ import java.util.Comparator; |
| 23 | 23 | import java.util.List; |
| 24 | 24 | import java.util.Map; |
| 25 | 25 | |
| 26 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; | |
| 26 | 27 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList; |
| 27 | 28 | import static cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculatorHelper.formatPrice; |
| 28 | 29 | |
| ... | ... | @@ -47,8 +48,8 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator |
| 47 | 48 | return; |
| 48 | 49 | } |
| 49 | 50 | // 获得 SKU 对应的满减送活动 |
| 50 | - List<RewardActivityMatchRespDTO> rewardActivities = rewardActivityApi.getRewardActivityListByStatusAndNow( | |
| 51 | - CommonStatusEnum.ENABLE.getStatus(), LocalDateTime.now()); | |
| 51 | + List<RewardActivityMatchRespDTO> rewardActivities = rewardActivityApi.getRewardActivityBySpuIdsAndStatusAndDateTimeLt( | |
| 52 | + convertSet(result.getItems(), TradePriceCalculateRespBO.OrderItem::getSpuId), CommonStatusEnum.ENABLE.getStatus(), LocalDateTime.now()); | |
| 52 | 53 | if (CollUtil.isEmpty(rewardActivities)) { |
| 53 | 54 | return; |
| 54 | 55 | } | ... | ... |
yudao-server/src/main/resources/application-local.yaml
| ... | ... | @@ -6,7 +6,7 @@ spring: |
| 6 | 6 | # 数据源配置项 |
| 7 | 7 | autoconfigure: |
| 8 | 8 | exclude: |
| 9 | - - org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration # 默认 local 环境,不开启 Quartz 的自动配置 | |
| 9 | + #- org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration # 默认 local 环境,不开启 Quartz 的自动配置 | |
| 10 | 10 | - de.codecentric.boot.admin.server.config.AdminServerAutoConfiguration # 禁用 Spring Boot Admin 的 Server 的自动配置 |
| 11 | 11 | - de.codecentric.boot.admin.server.ui.config.AdminServerUiAutoConfiguration # 禁用 Spring Boot Admin 的 Server UI 的自动配置 |
| 12 | 12 | - de.codecentric.boot.admin.client.config.SpringBootAdminClientAutoConfiguration # 禁用 Spring Boot Admin 的 Client 的自动配置 |
| ... | ... | @@ -45,7 +45,7 @@ spring: |
| 45 | 45 | primary: master |
| 46 | 46 | datasource: |
| 47 | 47 | master: |
| 48 | - 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 连接的示例 | |
| 48 | + 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 连接的示例 | |
| 49 | 49 | # 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 连接的示例 |
| 50 | 50 | # url: jdbc:postgresql://127.0.0.1:5432/ruoyi-vue-pro # PostgreSQL 连接的示例 |
| 51 | 51 | # url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例 |
| ... | ... | @@ -61,19 +61,19 @@ spring: |
| 61 | 61 | # password: SYSDBA001 # DM 连接的示例 |
| 62 | 62 | # username: root # OpenGauss 连接的示例 |
| 63 | 63 | # password: Yudao@2024 # OpenGauss 连接的示例 |
| 64 | - slave: # 模拟从库,可根据自己需要修改 | |
| 65 | - lazy: true # 开启懒加载,保证启动速度 | |
| 66 | - url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true&nullCatalogMeansCurrent=true | |
| 67 | - username: root | |
| 68 | - password: 123456 | |
| 64 | +# slave: # 模拟从库,可根据自己需要修改 | |
| 65 | +# lazy: true # 开启懒加载,保证启动速度 | |
| 66 | +# url: jdbc:mysql://192.168.10.207:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true&nullCatalogMeansCurrent=true | |
| 67 | +# username: root | |
| 68 | +# password: 123456 | |
| 69 | 69 | |
| 70 | 70 | # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 |
| 71 | 71 | data: |
| 72 | 72 | redis: |
| 73 | - host: 127.0.0.1 # 地址 | |
| 73 | + host: 192.168.10.207 # 地址 | |
| 74 | 74 | port: 6379 # 端口 |
| 75 | 75 | database: 0 # 数据库索引 |
| 76 | -# password: dev # 密码,建议生产环境开启 | |
| 76 | + password: 123456 # 密码,建议生产环境开启 | |
| 77 | 77 | |
| 78 | 78 | --- #################### 定时任务相关配置 #################### |
| 79 | 79 | |
| ... | ... | @@ -200,8 +200,8 @@ wx: |
| 200 | 200 | # secret: 6f270509224a7ae1296bbf1c8cb97aed |
| 201 | 201 | # appid: wxc4598c446f8a9cb3 # 测试号(Kongdy 提供的) |
| 202 | 202 | # secret: 4a1a04e07f6a4a0751b39c3064a92c8b |
| 203 | - appid: wx66186af0759f47c9 # 测试号(puhui 提供的) | |
| 204 | - secret: 3218bcbd112cbc614c7264ceb20144ac | |
| 203 | + appid: wx9a0a5b259d852380 # 测试号(puhui 提供的) | |
| 204 | + secret: 70e65fa9d1a4f2c4e1b2aa8751d3b75e | |
| 205 | 205 | config-storage: |
| 206 | 206 | type: RedisTemplate # 采用 RedisTemplate 操作 Redis,会自动从 Spring 中获取 |
| 207 | 207 | key-prefix: wa # Redis Key 的前缀 | ... | ... |