Commit 241957507c57c6a3300093c1419c9689d458f086
Committed by
Gitee
!1084 批量优化或修改BUG
Merge pull request !1084 from 痴货/develop-mall
Showing
42 changed files
with
746 additions
and
165 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 不存在"); |
| ... | ... | @@ -45,6 +46,7 @@ public interface ErrorCodeConstants { |
| 45 | 46 | ErrorCode REWARD_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED = new ErrorCode(1_013_006_003, "满减送活动未关闭,不能删除"); |
| 46 | 47 | ErrorCode REWARD_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_006_004, "满减送活动已关闭,不能重复关闭"); |
| 47 | 48 | ErrorCode REWARD_ACTIVITY_SCOPE_EXISTS = new ErrorCode(1_013_006_005, "与该时间段已存在的满减送活动商品范围冲突"); |
| 49 | + ErrorCode REWARD_ACTIVITY_TYPE_NOT_EXISTS = new ErrorCode(1_013_006_006, "满减送活动类型不存在"); | |
| 48 | 50 | |
| 49 | 51 | // ========== TODO 空着 1-013-007-000 ============ |
| 50 | 52 | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/discount/DiscountActivityApiImpl.java
| ... | ... | @@ -24,7 +24,7 @@ public class DiscountActivityApiImpl implements DiscountActivityApi { |
| 24 | 24 | |
| 25 | 25 | @Override |
| 26 | 26 | public List<DiscountProductRespDTO> getMatchDiscountProductList(Collection<Long> skuIds) { |
| 27 | - return DiscountActivityConvert.INSTANCE.convertList02(discountActivityService.getMatchDiscountProductList(skuIds)); | |
| 27 | + return discountActivityService.getMatchDiscountProductList(skuIds); | |
| 28 | 28 | } |
| 29 | 29 | |
| 30 | 30 | } | ... | ... |
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/admin/coupon/vo/template/CouponTemplateBaseVO.java
| ... | ... | @@ -2,9 +2,11 @@ package cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | 4 | import cn.hutool.core.util.ObjectUtil; |
| 5 | +import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; | |
| 5 | 6 | import cn.iocoder.yudao.framework.common.validation.InEnum; |
| 6 | 7 | import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum; |
| 7 | 8 | import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum; |
| 9 | +import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum; | |
| 8 | 10 | import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTemplateValidityTypeEnum; |
| 9 | 11 | import com.fasterxml.jackson.annotation.JsonFormat; |
| 10 | 12 | import com.fasterxml.jackson.annotation.JsonIgnore; |
| ... | ... | @@ -12,6 +14,7 @@ import io.swagger.v3.oas.annotations.media.Schema; |
| 12 | 14 | import lombok.Data; |
| 13 | 15 | import org.springframework.format.annotation.DateTimeFormat; |
| 14 | 16 | |
| 17 | +import jakarta.validation.Validator; | |
| 15 | 18 | import jakarta.validation.constraints.AssertTrue; |
| 16 | 19 | import jakarta.validation.constraints.Min; |
| 17 | 20 | import jakarta.validation.constraints.NotNull; |
| ... | ... | @@ -37,11 +40,11 @@ public class CouponTemplateBaseVO { |
| 37 | 40 | private String description; |
| 38 | 41 | |
| 39 | 42 | @Schema(description = "发行总量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") // -1 - 则表示不限制发放数量 |
| 40 | - @NotNull(message = "发行总量不能为空") | |
| 43 | + @NotNull(message = "发行总量不能为空", groups = {User.class}) | |
| 41 | 44 | private Integer totalCount; |
| 42 | 45 | |
| 43 | 46 | @Schema(description = "每人限领个数", requiredMode = Schema.RequiredMode.REQUIRED, example = "66") // -1 - 则表示不限制 |
| 44 | - @NotNull(message = "每人限领个数不能为空") | |
| 47 | + @NotNull(message = "每人限领个数不能为空", groups = {User.class}) | |
| 45 | 48 | private Integer takeLimitCount; |
| 46 | 49 | |
| 47 | 50 | @Schema(description = "领取方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| ... | ... | @@ -89,13 +92,16 @@ public class CouponTemplateBaseVO { |
| 89 | 92 | private Integer discountType; |
| 90 | 93 | |
| 91 | 94 | @Schema(description = "折扣百分比", example = "80") // 例如说,80% 为 80 |
| 95 | + @NotNull(message = "折扣百分比不能为空", groups = {Percent.class}) | |
| 92 | 96 | private Integer discountPercent; |
| 93 | 97 | |
| 94 | 98 | @Schema(description = "优惠金额", example = "10") |
| 95 | 99 | @Min(value = 0, message = "优惠金额需要大于等于 0") |
| 100 | + @NotNull(message = "优惠金额不能为空", groups = {Price.class}) | |
| 96 | 101 | private Integer discountPrice; |
| 97 | 102 | |
| 98 | 103 | @Schema(description = "折扣上限", example = "100") // 单位:分,仅在 discountType 为 PERCENT 使用 |
| 104 | + @NotNull(message = "折扣上限不能为空", groups = {Percent.class}) | |
| 99 | 105 | private Integer discountLimitPrice; |
| 100 | 106 | |
| 101 | 107 | @AssertTrue(message = "商品范围编号的数组不能为空") |
| ... | ... | @@ -154,4 +160,54 @@ public class CouponTemplateBaseVO { |
| 154 | 160 | || discountLimitPrice != null; |
| 155 | 161 | } |
| 156 | 162 | |
| 163 | + //-------------------------领取方式校验start---------------------------- | |
| 164 | + | |
| 165 | + /** | |
| 166 | + * 直接领取 | |
| 167 | + */ | |
| 168 | + public interface User { | |
| 169 | + } | |
| 170 | + | |
| 171 | + /** | |
| 172 | + * 指定发放 | |
| 173 | + */ | |
| 174 | + public interface Admin { | |
| 175 | + } | |
| 176 | + | |
| 177 | + //-------------------------领取方式校验end------------------------------ | |
| 178 | + | |
| 179 | + //-------------------------优惠类型校验start---------------------------- | |
| 180 | + | |
| 181 | + /** | |
| 182 | + * 满减 | |
| 183 | + */ | |
| 184 | + public interface Price { | |
| 185 | + } | |
| 186 | + | |
| 187 | + /** | |
| 188 | + * 折扣 | |
| 189 | + */ | |
| 190 | + public interface Percent { | |
| 191 | + } | |
| 192 | + | |
| 193 | + //-------------------------优惠类型校验end------------------------------ | |
| 194 | + | |
| 195 | + public void validate(Validator validator) { | |
| 196 | + | |
| 197 | + //领取方式校验 | |
| 198 | + if (CouponTakeTypeEnum.USER.getType().equals(takeType)) { | |
| 199 | + ValidationUtils.validate(validator, this, User.class); | |
| 200 | + } else if (CouponTakeTypeEnum.ADMIN.getType().equals(takeType)) { | |
| 201 | + ValidationUtils.validate(validator, this, Admin.class); | |
| 202 | + } | |
| 203 | + | |
| 204 | + //优惠类型校验 | |
| 205 | + if (PromotionDiscountTypeEnum.PRICE.getType().equals(discountType)){ | |
| 206 | + ValidationUtils.validate(validator, this, Price.class); | |
| 207 | + } else if (PromotionDiscountTypeEnum.PERCENT.getType().equals(discountType)) { | |
| 208 | + ValidationUtils.validate(validator, this, Percent.class); | |
| 209 | + } | |
| 210 | + | |
| 211 | + } | |
| 212 | + | |
| 157 | 213 | } | ... | ... |
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/controller/app/coupon/AppCouponController.java
| ... | ... | @@ -60,8 +60,8 @@ public class AppCouponController { |
| 60 | 60 | @Operation(summary = "获得匹配指定商品的优惠劵列表", description = "用于下单页,展示优惠劵列表") |
| 61 | 61 | public CommonResult<List<AppCouponMatchRespVO>> getMatchCouponList(AppCouponMatchReqVO matchReqVO) { |
| 62 | 62 | // todo: 优化:优惠金额倒序 |
| 63 | - List<CouponDO> list = couponService.getMatchCouponList(getLoginUserId(), matchReqVO); | |
| 64 | - return success(BeanUtils.toBean(list, AppCouponMatchRespVO.class)); | |
| 63 | + List<AppCouponMatchRespVO> list = couponService.getMatchCouponList(getLoginUserId(), matchReqVO); | |
| 64 | + return success(list); | |
| 65 | 65 | } |
| 66 | 66 | |
| 67 | 67 | @GetMapping("/page") | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/reward/vo/AppRewardActivityRespVO.java
| ... | ... | @@ -4,6 +4,7 @@ import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivi |
| 4 | 4 | import io.swagger.v3.oas.annotations.media.Schema; |
| 5 | 5 | import lombok.Data; |
| 6 | 6 | |
| 7 | +import java.time.LocalDateTime; | |
| 7 | 8 | import java.util.List; |
| 8 | 9 | |
| 9 | 10 | @Schema(description = "用户 App - 满减送活动 Response VO") |
| ... | ... | @@ -26,9 +27,15 @@ public class AppRewardActivityRespVO { |
| 26 | 27 | private Integer productScope; |
| 27 | 28 | |
| 28 | 29 | @Schema(description = "商品 SPU 编号的数组", example = "1,2,3") |
| 29 | - private List<Long> productSpuIds; | |
| 30 | + private List<Long> productScopeValues; | |
| 30 | 31 | |
| 31 | 32 | @Schema(description = "优惠规则的数组") |
| 32 | 33 | private List<RewardActivityBaseVO.Rule> rules; |
| 33 | 34 | |
| 35 | + @Schema(description = "开始时间") | |
| 36 | + private LocalDateTime startTime; | |
| 37 | + | |
| 38 | + @Schema(description = "结束时间") | |
| 39 | + private LocalDateTime endTime; | |
| 40 | + | |
| 34 | 41 | } | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/coupon/CouponConvert.java
| ... | ... | @@ -32,6 +32,8 @@ public interface CouponConvert { |
| 32 | 32 | |
| 33 | 33 | CouponRespDTO convert(CouponDO bean); |
| 34 | 34 | |
| 35 | + AppCouponMatchRespVO convert2(CouponDO bean); | |
| 36 | + | |
| 35 | 37 | default CouponDO convert(CouponTemplateDO template, Long userId) { |
| 36 | 38 | CouponDO couponDO = new CouponDO() |
| 37 | 39 | .setTemplateId(template.getId()) | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/discount/DiscountActivityConvert.java
| ... | ... | @@ -121,7 +121,10 @@ public interface DiscountActivityConvert { |
| 121 | 121 | default boolean isEquals(DiscountProductDO productDO, DiscountProductDO productVO) { |
| 122 | 122 | if (ObjectUtil.notEqual(productDO.getSpuId(), productVO.getSpuId()) |
| 123 | 123 | || ObjectUtil.notEqual(productDO.getSkuId(), productVO.getSkuId()) |
| 124 | - || ObjectUtil.notEqual(productDO.getDiscountType(), productVO.getDiscountType())) { | |
| 124 | + || ObjectUtil.notEqual(productDO.getDiscountType(), productVO.getDiscountType()) | |
| 125 | + || ObjectUtil.notEqual(productDO.getActivityEndTime(), productVO.getActivityEndTime()) | |
| 126 | + || ObjectUtil.notEqual(productDO.getActivityStartTime(), productVO.getActivityStartTime()) | |
| 127 | + || ObjectUtil.notEqual(productDO.getActivityStatus(), productVO.getActivityStatus())) { | |
| 125 | 128 | return false; |
| 126 | 129 | } |
| 127 | 130 | if (productDO.getDiscountType().equals(PromotionDiscountTypeEnum.PRICE.getType())) { | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/reward/RewardActivityConvert.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.promotion.convert.reward; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 4 | +import cn.iocoder.yudao.module.promotion.api.reward.dto.RewardActivityMatchRespDTO; | |
| 5 | +import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityCreateReqVO; | |
| 6 | +import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityRespVO; | |
| 7 | +import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityUpdateReqVO; | |
| 8 | +import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO; | |
| 9 | +import org.mapstruct.Mapper; | |
| 10 | +import org.mapstruct.factory.Mappers; | |
| 11 | + | |
| 12 | +import java.util.List; | |
| 13 | + | |
| 14 | +/** | |
| 15 | + * 满减送活动 Convert | |
| 16 | + * | |
| 17 | + * @author 芋道源码 | |
| 18 | + */ | |
| 19 | +@Mapper | |
| 20 | +public interface RewardActivityConvert { | |
| 21 | + | |
| 22 | + RewardActivityConvert INSTANCE = Mappers.getMapper(RewardActivityConvert.class); | |
| 23 | + | |
| 24 | + RewardActivityDO convert(RewardActivityCreateReqVO bean); | |
| 25 | + | |
| 26 | + RewardActivityDO convert(RewardActivityUpdateReqVO bean); | |
| 27 | + | |
| 28 | + RewardActivityRespVO convert(RewardActivityDO bean); | |
| 29 | + | |
| 30 | + PageResult<RewardActivityRespVO> convertPage(PageResult<RewardActivityDO> page); | |
| 31 | + | |
| 32 | + List<RewardActivityMatchRespDTO> convertList(List<RewardActivityDO> rewardActivityBySpuIdsAndStatusAndDateTimeLt); | |
| 33 | +} | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java
| ... | ... | @@ -85,19 +85,12 @@ public interface CouponMapper extends BaseMapperX<CouponDO> { |
| 85 | 85 | } |
| 86 | 86 | |
| 87 | 87 | default List<CouponDO> selectListByUserIdAndStatusAndUsePriceLeAndProductScope( |
| 88 | - Long userId, Integer status, Integer usePrice, List<Long> spuIds, List<Long> categoryIds) { | |
| 89 | - Function<List<Long>, String> productScopeValuesFindInSetFunc = ids -> ids.stream() | |
| 90 | - .map(id -> StrUtil.format("FIND_IN_SET({}, product_scope_values) ", id)) | |
| 91 | - .collect(Collectors.joining(" OR ")); | |
| 92 | - return selectList(new LambdaQueryWrapperX<CouponDO>() | |
| 88 | + Long userId, Integer status) { | |
| 89 | + List<CouponDO> couponDOS = selectList(new LambdaQueryWrapperX<CouponDO>() | |
| 93 | 90 | .eq(CouponDO::getUserId, userId) |
| 94 | 91 | .eq(CouponDO::getStatus, status) |
| 95 | - .le(CouponDO::getUsePrice, usePrice) // 价格小于等于,满足价格使用条件 | |
| 96 | - .and(w -> w.eq(CouponDO::getProductScope, PromotionProductScopeEnum.ALL.getScope()) // 商品范围一:全部 | |
| 97 | - .or(ww -> ww.eq(CouponDO::getProductScope, PromotionProductScopeEnum.SPU.getScope()) // 商品范围二:满足指定商品 | |
| 98 | - .apply(productScopeValuesFindInSetFunc.apply(spuIds))) | |
| 99 | - .or(ww -> ww.eq(CouponDO::getProductScope, PromotionProductScopeEnum.CATEGORY.getScope()) // 商品范围三:满足指定分类 | |
| 100 | - .apply(productScopeValuesFindInSetFunc.apply(categoryIds))))); | |
| 92 | + ); | |
| 93 | + return couponDOS; | |
| 101 | 94 | } |
| 102 | 95 | |
| 103 | 96 | default List<CouponDO> selectListByStatusAndValidEndTimeLe(Integer status, LocalDateTime validEndTime) { | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponTemplateMapper.java
| ... | ... | @@ -70,7 +70,7 @@ public interface CouponTemplateMapper extends BaseMapperX<CouponTemplateDO> { |
| 70 | 70 | .in(CouponTemplateDO::getTakeType, canTakeTypes) // 2. 领取方式一致 |
| 71 | 71 | .and(ww -> ww.gt(CouponTemplateDO::getValidEndTime, LocalDateTime.now()) // 3.1 未过期 |
| 72 | 72 | .or().eq(CouponTemplateDO::getValidityType, CouponTemplateValidityTypeEnum.TERM.getType())) // 3.2 领取之后 |
| 73 | - .apply(" (take_count < total_count OR total_count = -1 )"); // 4. 剩余数量大于 0,或者无限领取 | |
| 73 | + .apply(" (take_count < total_count OR total_count = -1 or total_count is null)"); // 4. 剩余数量大于 0,或者无限领取,或者是指定发放的券 | |
| 74 | 74 | } |
| 75 | 75 | return canTakeConsumer; |
| 76 | 76 | } | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/discount/DiscountProductMapper.java
| 1 | 1 | package cn.iocoder.yudao.module.promotion.dal.mysql.discount; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| 4 | +import cn.iocoder.yudao.module.promotion.api.discount.dto.DiscountProductRespDTO; | |
| 4 | 5 | import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountProductDO; |
| 5 | 6 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
| 6 | 7 | import org.apache.ibatis.annotations.Mapper; |
| ... | ... | @@ -31,7 +32,7 @@ public interface DiscountProductMapper extends BaseMapperX<DiscountProductDO> { |
| 31 | 32 | } |
| 32 | 33 | |
| 33 | 34 | // TODO @zhangshuai:逻辑里,尽量避免写 join 语句哈,你可以看看这个查询,有什么办法优化?目前的一个思路,是分 2 次查询,性能也是 ok 的 |
| 34 | - List<DiscountProductDO> getMatchDiscountProductList(@Param("skuIds") Collection<Long> skuIds); | |
| 35 | + List<DiscountProductRespDTO> getMatchDiscountProductList(@Param("skuIds") Collection<Long> skuIds); | |
| 35 | 36 | |
| 36 | 37 | /** |
| 37 | 38 | * 查询出指定 spuId 的 spu 参加的活动最接近现在的一条记录。多个的话,一个 spuId 对应一个最近的活动编号 | ... | ... |
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 cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum; | |
| 10 | +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | |
| 8 | 11 | import org.apache.ibatis.annotations.Mapper; |
| 9 | 12 | |
| 10 | 13 | import java.time.LocalDateTime; |
| 14 | +import java.util.Collection; | |
| 11 | 15 | import java.util.List; |
| 16 | +import java.util.function.Function; | |
| 17 | +import java.util.stream.Collectors; | |
| 12 | 18 | |
| 13 | 19 | /** |
| 14 | 20 | * 满减送活动 Mapper |
| ... | ... | @@ -25,13 +31,48 @@ public interface RewardActivityMapper extends BaseMapperX<RewardActivityDO> { |
| 25 | 31 | .orderByDesc(RewardActivityDO::getId)); |
| 26 | 32 | } |
| 27 | 33 | |
| 34 | + default List<RewardActivityDO> selectListBySpuIdsAndStatus(Collection<Long> spuIds, Integer status) { | |
| 35 | + Function<Collection<Long>, String> productScopeValuesFindInSetFunc = ids -> ids.stream() | |
| 36 | + .map(id -> StrUtil.format("FIND_IN_SET({}, product_scope_values) ", id)) | |
| 37 | + .collect(Collectors.joining(" OR ")); | |
| 38 | + return selectList(new QueryWrapper<RewardActivityDO>() | |
| 39 | + .eq("status", status) | |
| 40 | + .apply(productScopeValuesFindInSetFunc.apply(spuIds))); | |
| 41 | + } | |
| 42 | + | |
| 43 | + /** | |
| 44 | + * 获取指定活动编号的活动列表且 | |
| 45 | + * 开始时间和结束时间小于给定时间 dateTime 的活动列表 | |
| 46 | + * | |
| 47 | + * @param status 状态 | |
| 48 | + * @param dateTime 指定日期 | |
| 49 | + * @return 活动列表 | |
| 50 | + */ | |
| 28 | 51 | default List<RewardActivityDO> selectListByStatusAndDateTimeLt(Integer status, LocalDateTime dateTime) { |
| 29 | 52 | return selectList(new LambdaQueryWrapperX<RewardActivityDO>() |
| 30 | 53 | .eq(RewardActivityDO::getStatus, status) |
| 31 | - // 开始时间 < 指定时间(dateTime) < 结束时间,也就是说获取指定时间段的活动 | |
| 32 | - .lt(RewardActivityDO::getStartTime, dateTime).gt(RewardActivityDO::getEndTime, dateTime) | |
| 54 | + .lt(RewardActivityDO::getStartTime, dateTime) | |
| 55 | + .gt(RewardActivityDO::getEndTime, dateTime)// 开始时间 < 指定时间 < 结束时间,也就是说获取指定时间段的活动 | |
| 33 | 56 | .orderByAsc(RewardActivityDO::getStartTime) |
| 34 | 57 | ); |
| 35 | 58 | } |
| 36 | 59 | |
| 60 | + default List<RewardActivityDO> getRewardActivityByStatusAndDateTimeLt(Collection<Long> spuIds,Collection<Long> categoryIds, Integer status, LocalDateTime dateTime) { | |
| 61 | + //拼接通用券查询语句 | |
| 62 | + Function<Collection<Long>, String> productScopeValuesFindInSetFunc = ids -> ids.stream() | |
| 63 | + .map(id -> StrUtil.format("FIND_IN_SET({}, product_scope_values) ", id)) | |
| 64 | + .collect(Collectors.joining(" OR ")); | |
| 65 | + return selectList(new LambdaQueryWrapperX<RewardActivityDO>() | |
| 66 | + .eq(RewardActivityDO::getStatus,status) | |
| 67 | + .lt(RewardActivityDO::getStartTime, dateTime) | |
| 68 | + .gt(RewardActivityDO::getEndTime, dateTime) | |
| 69 | + .and(i -> i.eq(RewardActivityDO::getProductScope, PromotionProductScopeEnum.SPU.getScope()) | |
| 70 | + .and(i1 -> i1.apply(productScopeValuesFindInSetFunc.apply(spuIds))) | |
| 71 | + .or(i1 -> i1.eq(RewardActivityDO::getProductScope, PromotionProductScopeEnum.ALL.getScope())) | |
| 72 | + .or(i1 -> i1.eq(RewardActivityDO::getProductScope, PromotionProductScopeEnum.CATEGORY.getScope()) | |
| 73 | + .and(i2 -> i2.apply(productScopeValuesFindInSetFunc.apply(categoryIds))))) | |
| 74 | + .orderByDesc(RewardActivityDO::getId) | |
| 75 | + ); | |
| 76 | + } | |
| 77 | + | |
| 37 | 78 | } | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillActivityMapper.java
| ... | ... | @@ -51,7 +51,7 @@ public interface SeckillActivityMapper extends BaseMapperX<SeckillActivityDO> { |
| 51 | 51 | Assert.isTrue(count > 0); |
| 52 | 52 | return update(null, new LambdaUpdateWrapper<SeckillActivityDO>() |
| 53 | 53 | .eq(SeckillActivityDO::getId, id) |
| 54 | - .gt(SeckillActivityDO::getStock, count) | |
| 54 | + .ge(SeckillActivityDO::getStock, count) | |
| 55 | 55 | .setSql("stock = stock - " + count)); |
| 56 | 56 | } |
| 57 | 57 | |
| ... | ... | @@ -69,9 +69,11 @@ public interface SeckillActivityMapper extends BaseMapperX<SeckillActivityDO> { |
| 69 | 69 | .setSql("stock = stock + " + count)); |
| 70 | 70 | } |
| 71 | 71 | |
| 72 | - default PageResult<SeckillActivityDO> selectPage(AppSeckillActivityPageReqVO pageReqVO, Integer status) { | |
| 72 | + default PageResult<SeckillActivityDO> selectPage(AppSeckillActivityPageReqVO pageReqVO, Integer status, LocalDateTime dateTime) { | |
| 73 | 73 | return selectPage(pageReqVO, new LambdaQueryWrapperX<SeckillActivityDO>() |
| 74 | 74 | .eqIfPresent(SeckillActivityDO::getStatus, status) |
| 75 | + .lt(SeckillActivityDO::getStartTime, dateTime) | |
| 76 | + .gt(SeckillActivityDO::getEndTime, dateTime)// 开始时间 < 指定时间 < 结束时间,也就是说获取指定时间段的活动 | |
| 75 | 77 | .apply(ObjectUtil.isNotNull(pageReqVO.getConfigId()), "FIND_IN_SET(" + pageReqVO.getConfigId() + ",config_ids) > 0")); |
| 76 | 78 | } |
| 77 | 79 | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillconfig/SeckillConfigMapper.java
| 1 | 1 | package cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillconfig; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | |
| 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.seckill.vo.config.SeckillConfigPageReqVO; |
| 7 | 8 | import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillConfigDO; |
| 9 | +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | |
| 8 | 10 | import org.apache.ibatis.annotations.Mapper; |
| 9 | 11 | |
| 12 | +import java.time.LocalDateTime; | |
| 13 | +import java.time.format.DateTimeFormatter; | |
| 10 | 14 | import java.util.List; |
| 11 | 15 | |
| 12 | 16 | @Mapper | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java
| ... | ... | @@ -5,6 +5,7 @@ import cn.hutool.core.map.MapUtil; |
| 5 | 5 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 6 | 6 | import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO; |
| 7 | 7 | import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponMatchReqVO; |
| 8 | +import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponMatchRespVO; | |
| 8 | 9 | import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO; |
| 9 | 10 | import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO; |
| 10 | 11 | import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum; |
| ... | ... | @@ -178,7 +179,7 @@ public interface CouponService { |
| 178 | 179 | * @param matchReqVO 匹配参数 |
| 179 | 180 | * @return 优惠券列表 |
| 180 | 181 | */ |
| 181 | - List<CouponDO> getMatchCouponList(Long userId, AppCouponMatchReqVO matchReqVO); | |
| 182 | + List<AppCouponMatchRespVO> getMatchCouponList(Long userId, AppCouponMatchReqVO matchReqVO); | |
| 182 | 183 | |
| 183 | 184 | /** |
| 184 | 185 | * 获取用户是否可以领取优惠券 | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java
| ... | ... | @@ -13,10 +13,12 @@ import cn.iocoder.yudao.module.member.api.user.MemberUserApi; |
| 13 | 13 | import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO; |
| 14 | 14 | import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO; |
| 15 | 15 | import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponMatchReqVO; |
| 16 | +import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponMatchRespVO; | |
| 16 | 17 | import cn.iocoder.yudao.module.promotion.convert.coupon.CouponConvert; |
| 17 | 18 | import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO; |
| 18 | 19 | import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO; |
| 19 | 20 | import cn.iocoder.yudao.module.promotion.dal.mysql.coupon.CouponMapper; |
| 21 | +import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum; | |
| 20 | 22 | import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum; |
| 21 | 23 | import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum; |
| 22 | 24 | import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTemplateValidityTypeEnum; |
| ... | ... | @@ -286,6 +288,7 @@ public class CouponServiceImpl implements CouponService { |
| 286 | 288 | } |
| 287 | 289 | // 校验剩余数量(仅在 CouponTakeTypeEnum.USER 用户领取时) |
| 288 | 290 | if (CouponTakeTypeEnum.isUser(couponTemplate.getTakeCount()) |
| 291 | + && couponTemplate.getTotalCount() != null | |
| 289 | 292 | && couponTemplate.getTakeCount() + userIds.size() > couponTemplate.getTotalCount()) { |
| 290 | 293 | throw exception(COUPON_TEMPLATE_NOT_ENOUGH); |
| 291 | 294 | } |
| ... | ... | @@ -308,7 +311,7 @@ public class CouponServiceImpl implements CouponService { |
| 308 | 311 | * @param couponTemplate 优惠劵模版 |
| 309 | 312 | */ |
| 310 | 313 | private void removeTakeLimitUser(Set<Long> userIds, CouponTemplateDO couponTemplate) { |
| 311 | - if (couponTemplate.getTakeLimitCount() <= 0) { | |
| 314 | + if (couponTemplate.getTakeLimitCount() == null || couponTemplate.getTakeLimitCount() <= 0) { | |
| 312 | 315 | return; |
| 313 | 316 | } |
| 314 | 317 | // 查询已领过券的用户 |
| ... | ... | @@ -356,13 +359,45 @@ public class CouponServiceImpl implements CouponService { |
| 356 | 359 | } |
| 357 | 360 | |
| 358 | 361 | @Override |
| 359 | - public List<CouponDO> getMatchCouponList(Long userId, AppCouponMatchReqVO matchReqVO) { | |
| 362 | + public List<AppCouponMatchRespVO> getMatchCouponList(Long userId, AppCouponMatchReqVO matchReqVO) { | |
| 363 | + List<AppCouponMatchRespVO> couponMatchist = new ArrayList<>(); | |
| 360 | 364 | List<CouponDO> list = couponMapper.selectListByUserIdAndStatusAndUsePriceLeAndProductScope(userId, |
| 361 | - CouponStatusEnum.UNUSED.getStatus(), | |
| 362 | - matchReqVO.getPrice(), matchReqVO.getSpuIds(), matchReqVO.getCategoryIds()); | |
| 363 | - // 兜底逻辑:如果 CouponExpireJob 未执行,status 未变成 EXPIRE ,但是 validEndTime 已经过期了,需要进行过滤 | |
| 364 | - list.removeIf(coupon -> !LocalDateTimeUtils.isBetween(coupon.getValidStartTime(), coupon.getValidEndTime())); | |
| 365 | - return list; | |
| 365 | + CouponStatusEnum.UNUSED.getStatus()); | |
| 366 | + for (CouponDO couponDO : list) { | |
| 367 | + AppCouponMatchRespVO appCouponMatchRespVO = CouponConvert.INSTANCE.convert2(couponDO); | |
| 368 | + Integer productScope = appCouponMatchRespVO.getProductScope(); | |
| 369 | + List<Long> productScopeValues = appCouponMatchRespVO.getProductScopeValues(); | |
| 370 | + Integer usePrice = appCouponMatchRespVO.getUsePrice(); | |
| 371 | + if(matchReqVO.getPrice() < usePrice){ | |
| 372 | + // 价格小于等于,满足价格使用条件 | |
| 373 | + appCouponMatchRespVO.setMatch(false); | |
| 374 | + appCouponMatchRespVO.setDescription("未达到使用门槛"); | |
| 375 | + }else if(!LocalDateTimeUtils.isBetween(appCouponMatchRespVO.getValidStartTime(), appCouponMatchRespVO.getValidEndTime())) { | |
| 376 | + //判断时间 | |
| 377 | + appCouponMatchRespVO.setMatch(false); | |
| 378 | + appCouponMatchRespVO.setDescription("使用时间未到"); | |
| 379 | + }else if (PromotionProductScopeEnum.ALL.getScope().equals(productScope)){ | |
| 380 | + appCouponMatchRespVO.setMatch(true); | |
| 381 | + }else if (PromotionProductScopeEnum.SPU.getScope().equals(productScope)){ | |
| 382 | + boolean spu = new HashSet<>(productScopeValues).containsAll(matchReqVO.getSpuIds()); | |
| 383 | + if(spu){ | |
| 384 | + appCouponMatchRespVO.setMatch(true); | |
| 385 | + }else { | |
| 386 | + appCouponMatchRespVO.setMatch(false); | |
| 387 | + appCouponMatchRespVO.setDescription("与商品不匹配"); | |
| 388 | + } | |
| 389 | + }else if (PromotionProductScopeEnum.CATEGORY.getScope().equals(productScope)){ | |
| 390 | + boolean category = new HashSet<>(productScopeValues).containsAll(matchReqVO.getCategoryIds()); | |
| 391 | + if(category){ | |
| 392 | + appCouponMatchRespVO.setMatch(true); | |
| 393 | + }else { | |
| 394 | + appCouponMatchRespVO.setMatch(false); | |
| 395 | + appCouponMatchRespVO.setDescription("与商品类型不匹配"); | |
| 396 | + } | |
| 397 | + } | |
| 398 | + couponMatchist.add(appCouponMatchRespVO); | |
| 399 | + } | |
| 400 | + return couponMatchist; | |
| 366 | 401 | } |
| 367 | 402 | |
| 368 | 403 | @Override | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponTemplateServiceImpl.java
| ... | ... | @@ -13,6 +13,7 @@ import cn.iocoder.yudao.module.promotion.dal.mysql.coupon.CouponTemplateMapper; |
| 13 | 13 | import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum; |
| 14 | 14 | import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum; |
| 15 | 15 | import org.springframework.stereotype.Service; |
| 16 | +import jakarta.validation.Validator; | |
| 16 | 17 | import org.springframework.validation.annotation.Validated; |
| 17 | 18 | |
| 18 | 19 | import jakarta.annotation.Resource; |
| ... | ... | @@ -40,9 +41,13 @@ public class CouponTemplateServiceImpl implements CouponTemplateService { |
| 40 | 41 | private ProductCategoryApi productCategoryApi; |
| 41 | 42 | @Resource |
| 42 | 43 | private ProductSpuApi productSpuApi; |
| 44 | + @Resource | |
| 45 | + private Validator validator; | |
| 43 | 46 | |
| 44 | 47 | @Override |
| 45 | 48 | public Long createCouponTemplate(CouponTemplateCreateReqVO createReqVO) { |
| 49 | + // 校验参数 | |
| 50 | + createReqVO.validate(validator); | |
| 46 | 51 | // 校验商品范围 |
| 47 | 52 | validateProductScope(createReqVO.getProductScope(), createReqVO.getProductScopeValues()); |
| 48 | 53 | // 插入 | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/discount/DiscountActivityService.java
| 1 | 1 | package cn.iocoder.yudao.module.promotion.service.discount; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | +import cn.iocoder.yudao.module.promotion.api.discount.dto.DiscountProductRespDTO; | |
| 4 | 5 | import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.DiscountActivityCreateReqVO; |
| 5 | 6 | import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.DiscountActivityPageReqVO; |
| 6 | 7 | import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.DiscountActivityUpdateReqVO; |
| ... | ... | @@ -27,7 +28,7 @@ public interface DiscountActivityService { |
| 27 | 28 | * @param skuIds SKU 编号数组 |
| 28 | 29 | * @return 匹配的限时折扣商品 |
| 29 | 30 | */ |
| 30 | - List<DiscountProductDO> getMatchDiscountProductList(Collection<Long> skuIds); | |
| 31 | + List<DiscountProductRespDTO> getMatchDiscountProductList(Collection<Long> skuIds); | |
| 31 | 32 | |
| 32 | 33 | /** |
| 33 | 34 | * 创建限时折扣活动 | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/discount/DiscountActivityServiceImpl.java
| ... | ... | @@ -6,6 +6,7 @@ import cn.hutool.core.map.MapUtil; |
| 6 | 6 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 7 | 7 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 8 | 8 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 9 | +import cn.iocoder.yudao.module.promotion.api.discount.dto.DiscountProductRespDTO; | |
| 9 | 10 | import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.DiscountActivityBaseVO; |
| 10 | 11 | import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.DiscountActivityCreateReqVO; |
| 11 | 12 | import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.DiscountActivityPageReqVO; |
| ... | ... | @@ -49,7 +50,7 @@ public class DiscountActivityServiceImpl implements DiscountActivityService { |
| 49 | 50 | private DiscountProductMapper discountProductMapper; |
| 50 | 51 | |
| 51 | 52 | @Override |
| 52 | - public List<DiscountProductDO> getMatchDiscountProductList(Collection<Long> skuIds) { | |
| 53 | + public List<DiscountProductRespDTO> getMatchDiscountProductList(Collection<Long> skuIds) { | |
| 53 | 54 | return discountProductMapper.getMatchDiscountProductList(skuIds); |
| 54 | 55 | } |
| 55 | 56 | |
| ... | ... | @@ -130,7 +131,7 @@ public class DiscountActivityServiceImpl implements DiscountActivityService { |
| 130 | 131 | List<DiscountProductDO> list = discountProductMapper.selectListByActivityId(id); |
| 131 | 132 | // TODO @zhangshuai:一般简单的 stream 方法,建议是使用 CollectionUtils,例如说这里是 convertList 对把。 |
| 132 | 133 | List<Long> skuIds = list.stream().map(item -> item.getSkuId()).collect(Collectors.toList()); |
| 133 | - List<DiscountProductDO> matchDiscountProductList = getMatchDiscountProductList(skuIds); | |
| 134 | + List<DiscountProductRespDTO> matchDiscountProductList = getMatchDiscountProductList(skuIds); | |
| 134 | 135 | if (id != null) { // 排除自己这个活动 |
| 135 | 136 | matchDiscountProductList.removeIf(product -> id.equals(product.getActivityId())); |
| 136 | 137 | } | ... | ... |
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; |
| ... | ... | @@ -11,8 +12,10 @@ import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivi |
| 11 | 12 | import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityCreateReqVO; |
| 12 | 13 | import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityPageReqVO; |
| 13 | 14 | import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityUpdateReqVO; |
| 15 | +import cn.iocoder.yudao.module.promotion.convert.reward.RewardActivityConvert; | |
| 14 | 16 | import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO; |
| 15 | 17 | import cn.iocoder.yudao.module.promotion.dal.mysql.reward.RewardActivityMapper; |
| 18 | +import cn.iocoder.yudao.module.promotion.enums.common.PromotionActivityStatusEnum; | |
| 16 | 19 | import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum; |
| 17 | 20 | import cn.iocoder.yudao.module.promotion.util.PromotionUtils; |
| 18 | 21 | import jakarta.annotation.Resource; |
| ... | ... | @@ -20,8 +23,11 @@ import org.springframework.stereotype.Service; |
| 20 | 23 | import org.springframework.validation.annotation.Validated; |
| 21 | 24 | |
| 22 | 25 | import java.time.LocalDateTime; |
| 26 | +import java.util.Collection; | |
| 27 | +import java.util.Collections; | |
| 23 | 28 | import java.util.List; |
| 24 | 29 | import java.util.Objects; |
| 30 | +import java.util.stream.Collectors; | |
| 25 | 31 | |
| 26 | 32 | import static cn.hutool.core.collection.CollUtil.intersectionDistinct; |
| 27 | 33 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| ... | ... | @@ -52,9 +58,13 @@ public class RewardActivityServiceImpl implements RewardActivityService { |
| 52 | 58 | // 1.2 校验商品是否冲突 |
| 53 | 59 | validateRewardActivitySpuConflicts(null, createReqVO); |
| 54 | 60 | |
| 55 | - // 2. 插入 | |
| 56 | - RewardActivityDO rewardActivity = BeanUtils.toBean(createReqVO, RewardActivityDO.class) | |
| 57 | - .setStatus(PromotionUtils.calculateActivityStatus(createReqVO.getEndTime())); | |
| 61 | + // 插入 | |
| 62 | + RewardActivityDO rewardActivity = RewardActivityConvert.INSTANCE.convert(createReqVO) | |
| 63 | + .setStatus( | |
| 64 | + PromotionUtils.calculateActivityStatus(createReqVO.getEndTime()).equals(CommonStatusEnum.DISABLE.getStatus())? | |
| 65 | + PromotionActivityStatusEnum.WAIT.getStatus(): | |
| 66 | + PromotionActivityStatusEnum.RUN.getStatus() | |
| 67 | + ); | |
| 58 | 68 | rewardActivityMapper.insert(rewardActivity); |
| 59 | 69 | // 返回 |
| 60 | 70 | return rewardActivity.getId(); |
| ... | ... | @@ -199,4 +209,19 @@ public class RewardActivityServiceImpl implements RewardActivityService { |
| 199 | 209 | return rewardActivityMapper.selectListByStatusAndDateTimeLt(status, dateTime); |
| 200 | 210 | } |
| 201 | 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 | + | |
| 202 | 227 | } | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityService.java
| ... | ... | @@ -110,7 +110,7 @@ public interface SeckillActivityService { |
| 110 | 110 | List<SeckillActivityDO> getSeckillActivityListByConfigIdAndStatus(Long configId, Integer status); |
| 111 | 111 | |
| 112 | 112 | /** |
| 113 | - * 通过活动时段获取秒杀活动 | |
| 113 | + * 通过活动时段获取开始的秒杀活动 | |
| 114 | 114 | * |
| 115 | 115 | * @param pageReqVO 请求 |
| 116 | 116 | * @return 秒杀活动列表 | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityServiceImpl.java
| ... | ... | @@ -23,6 +23,7 @@ import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillConfigDO; |
| 23 | 23 | import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillProductDO; |
| 24 | 24 | import cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillactivity.SeckillActivityMapper; |
| 25 | 25 | import cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillactivity.SeckillProductMapper; |
| 26 | +import cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillconfig.SeckillConfigMapper; | |
| 26 | 27 | import org.springframework.stereotype.Service; |
| 27 | 28 | import org.springframework.transaction.annotation.Transactional; |
| 28 | 29 | import org.springframework.validation.annotation.Validated; |
| ... | ... | @@ -57,6 +58,8 @@ public class SeckillActivityServiceImpl implements SeckillActivityService { |
| 57 | 58 | @Resource |
| 58 | 59 | private SeckillProductMapper seckillProductMapper; |
| 59 | 60 | @Resource |
| 61 | + private SeckillConfigMapper seckillConfigMapper; | |
| 62 | + @Resource | |
| 60 | 63 | private SeckillConfigService seckillConfigService; |
| 61 | 64 | @Resource |
| 62 | 65 | private ProductSpuApi productSpuApi; |
| ... | ... | @@ -289,7 +292,7 @@ public class SeckillActivityServiceImpl implements SeckillActivityService { |
| 289 | 292 | |
| 290 | 293 | @Override |
| 291 | 294 | public PageResult<SeckillActivityDO> getSeckillActivityAppPageByConfigId(AppSeckillActivityPageReqVO pageReqVO) { |
| 292 | - return seckillActivityMapper.selectPage(pageReqVO, CommonStatusEnum.ENABLE.getStatus()); | |
| 295 | + return seckillActivityMapper.selectPage(pageReqVO, CommonStatusEnum.ENABLE.getStatus(),LocalDateTime.now()); | |
| 293 | 296 | } |
| 294 | 297 | |
| 295 | 298 | @Override | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/resources/mapper/discount/DiscountProductMapper.xml
| ... | ... | @@ -3,8 +3,8 @@ |
| 3 | 3 | <mapper namespace="cn.iocoder.yudao.module.promotion.dal.mysql.discount.DiscountProductMapper"> |
| 4 | 4 | |
| 5 | 5 | |
| 6 | - <select id="getMatchDiscountProductList" resultType="cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountProductDO"> | |
| 7 | - SELECT pdp.* | |
| 6 | + <select id="getMatchDiscountProductList" resultType="cn.iocoder.yudao.module.promotion.api.discount.dto.DiscountProductRespDTO"> | |
| 7 | + SELECT pdp.*,pda.name as activity_name | |
| 8 | 8 | FROM promotion_discount_product pdp |
| 9 | 9 | LEFT JOIN promotion_discount_activity pda |
| 10 | 10 | ON pdp.activity_id = pda.id |
| ... | ... | @@ -16,9 +16,11 @@ |
| 16 | 16 | </foreach> |
| 17 | 17 | </if> |
| 18 | 18 | AND pda.start_time <= CURRENT_TIME AND pda.end_time >= CURRENT_TIME |
| 19 | - AND pda.`status` = 20 | |
| 20 | - AND pda.deleted != 1 | |
| 19 | + AND pda.`status` = 0 | |
| 20 | + AND pda.deleted =0 | |
| 21 | + AND pdp.deleted = 0 | |
| 21 | 22 | </where> |
| 23 | + ORDER BY pdp.id DESC | |
| 22 | 24 | </select> |
| 23 | 25 | |
| 24 | 26 | </mapper> | ... | ... |
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,58 @@ 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 | + | |
| 104 | + //查询会员价 | |
| 105 | + AppTradeProductSettlementRespVO.Sku skuVip = calculateVipPrice(sku.getId(), sku.getPrice(), memberLevel); | |
| 106 | + | |
| 107 | + if(skuDiscount != null && skuVip != null){ | |
| 108 | + if(skuDiscount.getPrice() > skuVip.getPrice()){ | |
| 109 | + skus.add(skuVip); | |
| 110 | + }else{ | |
| 111 | + skus.add(skuDiscount); | |
| 112 | + } | |
| 113 | + }else if(skuDiscount != null){ | |
| 114 | + skus.add(skuDiscount); | |
| 115 | + }else if(skuVip != null){ | |
| 116 | + skus.add(skuVip); | |
| 117 | + } | |
| 118 | + | |
| 119 | + }); | |
| 120 | + AppTradeProductSettlementRespVO.Reward reward = calculateReward(spuId); | |
| 121 | + AppTradeProductSettlementRespVO respVO = AppTradeProductSettlementRespVO.builder().id(spuId).skus(skus).build(); | |
| 122 | + if(reward != null){ | |
| 123 | + //创建满减活动对象 | |
| 124 | + respVO.setReward(reward); | |
| 125 | + } | |
| 126 | + appTradeProductSettlementRespVOS.add(respVO); | |
| 127 | + }); | |
| 128 | + return success(appTradeProductSettlementRespVOS); | |
| 129 | + } | |
| 130 | + | |
| 131 | + private MemberLevelRespDTO getMemberLevel() { | |
| 132 | + Long userId = getLoginUserId(); | |
| 133 | + if (userId == null) { | |
| 134 | + return null; | |
| 135 | + } | |
| 136 | + MemberUserRespDTO user = memberUserApi.getUser(userId); | |
| 137 | + if (user.getLevelId() == null || user.getLevelId() <= 0) { | |
| 138 | + return null; | |
| 139 | + } | |
| 140 | + return memberLevelApi.getMemberLevel(user.getLevelId()); | |
| 141 | + } | |
| 142 | + | |
| 64 | 143 | @PostMapping("/create") |
| 65 | 144 | @Operation(summary = "创建订单") |
| 66 | 145 | @PreAuthenticated |
| ... | ... | @@ -188,4 +267,78 @@ public class AppTradeOrderController { |
| 188 | 267 | return success(tradeOrderUpdateService.createOrderItemCommentByMember(getLoginUserId(), createReqVO)); |
| 189 | 268 | } |
| 190 | 269 | |
| 270 | + /** | |
| 271 | + * 计算会员 VIP 优惠价格 | |
| 272 | + * | |
| 273 | + * @param price 原价 | |
| 274 | + * @param memberLevel 会员等级 | |
| 275 | + * @return 优惠价格 | |
| 276 | + */ | |
| 277 | + public AppTradeProductSettlementRespVO.Sku calculateVipPrice(Long skuId, Integer price, MemberLevelRespDTO memberLevel) { | |
| 278 | + if (memberLevel == null || memberLevel.getDiscountPercent() == null) { | |
| 279 | + return null; | |
| 280 | + } | |
| 281 | + Integer newPrice = price * memberLevel.getDiscountPercent() / 100; | |
| 282 | + return AppTradeProductSettlementRespVO.Sku.builder(). | |
| 283 | + skuId(skuId). | |
| 284 | + type(PromotionTypeEnum.MEMBER_LEVEL.getType()). | |
| 285 | + price(newPrice).build(); | |
| 286 | + } | |
| 287 | + | |
| 288 | + /** | |
| 289 | + * 计算限时优惠信息 | |
| 290 | + * | |
| 291 | + * @param price 原价 | |
| 292 | + * @param skuId 商品规格id | |
| 293 | + * @return 优惠价格 | |
| 294 | + */ | |
| 295 | + private AppTradeProductSettlementRespVO.Sku calculateDiscountPrice(Long skuId, Integer price) { | |
| 296 | + if (skuId == null) { | |
| 297 | + return null; | |
| 298 | + } | |
| 299 | + | |
| 300 | + //根据商品id查询限时优惠 | |
| 301 | + List<DiscountProductRespDTO> matchDiscountProductList = discountActivityApi.getMatchDiscountProductList(Collections.singletonList(skuId)); | |
| 302 | + if (matchDiscountProductList != null && !matchDiscountProductList.isEmpty()) { | |
| 303 | + DiscountProductRespDTO discountProductRespDTO = matchDiscountProductList.get(matchDiscountProductList.size() - 1); | |
| 304 | + AppTradeProductSettlementRespVO.Sku sku = AppTradeProductSettlementRespVO.Sku.builder(). | |
| 305 | + skuId(skuId). | |
| 306 | + discountId(discountProductRespDTO.getId()). | |
| 307 | + type(PromotionTypeEnum.DISCOUNT_ACTIVITY.getType()). | |
| 308 | + endTime(discountProductRespDTO.getActivityEndTime()). | |
| 309 | + build(); | |
| 310 | + Integer discountType = discountProductRespDTO.getDiscountType(); | |
| 311 | + if(Objects.equals(PromotionDiscountTypeEnum.PRICE.getType(), discountType)){ | |
| 312 | + sku.setPrice(price - discountProductRespDTO.getDiscountPrice() * 100); | |
| 313 | + }else if(Objects.equals(PromotionDiscountTypeEnum.PERCENT.getType(), discountType)){ | |
| 314 | + Integer newPrice = price * discountProductRespDTO.getDiscountPercent() / 100; | |
| 315 | + sku.setPrice(price - newPrice); | |
| 316 | + }else{ | |
| 317 | + throw exception(DISCOUNT_ACTIVITY_TYPE_NOT_EXISTS); | |
| 318 | + } | |
| 319 | + return sku; | |
| 320 | + } | |
| 321 | + return null; | |
| 322 | + } | |
| 323 | + | |
| 324 | + /** | |
| 325 | + * 获取第一层满减活动 | |
| 326 | + * | |
| 327 | + * @param spuId 商品规格id | |
| 328 | + * @return 优惠价格 | |
| 329 | + */ | |
| 330 | + private AppTradeProductSettlementRespVO.Reward calculateReward(Long spuId) { | |
| 331 | + List<RewardActivityMatchRespDTO> matchRewardActivityList = rewardActivityApi.getRewardActivityBySpuIdsAndStatusAndDateTimeLt(Collections.singletonList(spuId), CommonStatusEnum.ENABLE.getStatus(), LocalDateTime.now()); | |
| 332 | + if(matchRewardActivityList != null && !matchRewardActivityList.isEmpty()){ | |
| 333 | + RewardActivityMatchRespDTO rewardActivityMatchRespDTO = matchRewardActivityList.get(matchRewardActivityList.size() - 1); | |
| 334 | + if(rewardActivityMatchRespDTO != null){ | |
| 335 | + RewardActivityMatchRespDTO.Rule rule = rewardActivityMatchRespDTO.getRules().get(0); | |
| 336 | + return AppTradeProductSettlementRespVO.Reward.builder(). | |
| 337 | + rewardActivity("满" + rule.getLimit() / 100 + (Objects.equals(rewardActivityMatchRespDTO.getConditionType(), PromotionConditionTypeEnum.PRICE.getType())?"元":"件"+"减") +rule.getDiscountPrice() / 100) | |
| 338 | + .id(rewardActivityMatchRespDTO.getId()).build(); | |
| 339 | + } | |
| 340 | + } | |
| 341 | + return null; | |
| 342 | + } | |
| 343 | + | |
| 191 | 344 | } | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderSettlementRespVO.java
| 1 | 1 | package cn.iocoder.yudao.module.trade.controller.app.order.vo; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.module.trade.controller.app.base.property.AppProductPropertyValueDetailRespVO; |
| 4 | +import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; | |
| 4 | 5 | import io.swagger.v3.oas.annotations.media.Schema; |
| 5 | 6 | import jakarta.validation.constraints.NotNull; |
| 6 | 7 | import lombok.AllArgsConstructor; |
| ... | ... | @@ -31,6 +32,13 @@ public class AppTradeOrderSettlementRespVO { |
| 31 | 32 | @Schema(description = "总积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") |
| 32 | 33 | private Integer totalPoint; |
| 33 | 34 | |
| 35 | + /** | |
| 36 | + * 营销活动数组 | |
| 37 | + * | |
| 38 | + * 只对应 {@link TradePriceCalculateRespBO.Price#items} 商品匹配的活动 | |
| 39 | + */ | |
| 40 | + private List<TradePriceCalculateRespBO.Promotion> promotions; | |
| 41 | + | |
| 34 | 42 | @Schema(description = "购物项") |
| 35 | 43 | @Data |
| 36 | 44 | public static class Item { | ... | ... |
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 = "spu 商品编号", 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 | + | |
| 32 | + @Schema(description = "满减活动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 33 | + private Long id; | |
| 34 | + | |
| 35 | + @Schema(description = "满减活动信息", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 36 | + private String rewardActivity; | |
| 37 | + | |
| 38 | + } | |
| 39 | + | |
| 40 | + /** | |
| 41 | + * SKU 数组 | |
| 42 | + */ | |
| 43 | + @Data | |
| 44 | + @Builder | |
| 45 | + public static class Sku implements Serializable { | |
| 46 | + | |
| 47 | + @Schema(description = "商品 SKU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 48 | + private Long skuId; | |
| 49 | + | |
| 50 | + @Schema(description = "价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 51 | + private Integer price; | |
| 52 | + | |
| 53 | + @Schema(description = "营销类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 54 | + private Integer type; // 对应 PromotionTypeEnum 枚举 | |
| 55 | + | |
| 56 | + @Schema(description = "限时优惠id", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 57 | + private Long discountId; | |
| 58 | + | |
| 59 | + @Schema(description = "活动结束时间", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 60 | + private LocalDateTime endTime; | |
| 61 | + | |
| 62 | + } | |
| 63 | +} | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/aftersale/AfterSaleConvert.java
| ... | ... | @@ -43,11 +43,9 @@ public interface AfterSaleConvert { |
| 43 | 43 | @Mapping(source = "afterSale.orderId", target = "merchantOrderId"), |
| 44 | 44 | @Mapping(source = "afterSale.id", target = "merchantRefundId"), |
| 45 | 45 | @Mapping(source = "afterSale.applyReason", target = "reason"), |
| 46 | - @Mapping(source = "afterSale.refundPrice", target = "price"), | |
| 47 | - @Mapping(source = "orderProperties.payAppKey", target = "appKey") | |
| 46 | + @Mapping(source = "afterSale.refundPrice", target = "price") | |
| 48 | 47 | }) |
| 49 | - PayRefundCreateReqDTO convert(String userIp, AfterSaleDO afterSale, | |
| 50 | - TradeOrderProperties orderProperties); | |
| 48 | + PayRefundCreateReqDTO convert(String userIp, AfterSaleDO afterSale); | |
| 51 | 49 | |
| 52 | 50 | MemberUserRespVO convert(MemberUserRespDTO bean); |
| 53 | 51 | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceImpl.java
| ... | ... | @@ -371,8 +371,9 @@ public class AfterSaleServiceImpl implements AfterSaleService { |
| 371 | 371 | @Override |
| 372 | 372 | public void afterCommit() { |
| 373 | 373 | // 创建退款单 |
| 374 | - PayRefundCreateReqDTO createReqDTO = AfterSaleConvert.INSTANCE.convert(userIp, afterSale, tradeOrderProperties) | |
| 374 | + PayRefundCreateReqDTO createReqDTO = AfterSaleConvert.INSTANCE.convert(userIp, afterSale) | |
| 375 | 375 | .setReason(StrUtil.format("退款【{}】", afterSale.getSpuName())); |
| 376 | + createReqDTO.setAppKey(tradeOrderProperties.getPayAppKey()); | |
| 376 | 377 | Long payRefundId = payRefundApi.createRefund(createReqDTO); |
| 377 | 378 | // 更新售后单的退款单号 |
| 378 | 379 | tradeAfterSaleMapper.updateById(new AfterSaleDO().setId(afterSale.getId()).setPayRefundId(payRefundId)); | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java
| ... | ... | @@ -121,10 +121,13 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { |
| 121 | 121 | * @return 是否包邮 |
| 122 | 122 | */ |
| 123 | 123 | private boolean isGlobalExpressFree(TradePriceCalculateRespBO result) { |
| 124 | + | |
| 124 | 125 | TradeConfigDO config = tradeConfigService.getTradeConfig(); |
| 125 | - return config != null | |
| 126 | + return result.getFreeDelivery() || | |
| 127 | + (config != null | |
| 126 | 128 | && Boolean.TRUE.equals(config.getDeliveryExpressFreeEnabled()) // 开启包邮 |
| 127 | - && result.getPrice().getPayPrice() >= config.getDeliveryExpressFreePrice(); // 满足包邮的价格 | |
| 129 | + && result.getPrice().getPayPrice() >= config.getDeliveryExpressFreePrice() | |
| 130 | + ); // 满足包邮的价格 | |
| 128 | 131 | } |
| 129 | 132 | |
| 130 | 133 | private void calculateDeliveryPrice(List<OrderItem> selectedSkus, | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDiscountActivityPriceCalculator.java
| ... | ... | @@ -3,6 +3,10 @@ package cn.iocoder.yudao.module.trade.service.price.calculator; |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | 4 | import cn.hutool.core.util.ObjectUtil; |
| 5 | 5 | import cn.hutool.core.util.StrUtil; |
| 6 | +import cn.iocoder.yudao.module.member.api.level.MemberLevelApi; | |
| 7 | +import cn.iocoder.yudao.module.member.api.level.dto.MemberLevelRespDTO; | |
| 8 | +import cn.iocoder.yudao.module.member.api.user.MemberUserApi; | |
| 9 | +import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO; | |
| 6 | 10 | import cn.iocoder.yudao.module.promotion.api.discount.DiscountActivityApi; |
| 7 | 11 | import cn.iocoder.yudao.module.promotion.api.discount.dto.DiscountProductRespDTO; |
| 8 | 12 | import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum; |
| ... | ... | @@ -14,6 +18,8 @@ import org.springframework.core.annotation.Order; |
| 14 | 18 | import org.springframework.stereotype.Component; |
| 15 | 19 | |
| 16 | 20 | import jakarta.annotation.Resource; |
| 21 | + | |
| 22 | +import java.math.BigDecimal; | |
| 17 | 23 | import java.util.List; |
| 18 | 24 | import java.util.Map; |
| 19 | 25 | |
| ... | ... | @@ -32,6 +38,10 @@ public class TradeDiscountActivityPriceCalculator implements TradePriceCalculato |
| 32 | 38 | |
| 33 | 39 | @Resource |
| 34 | 40 | private DiscountActivityApi discountActivityApi; |
| 41 | + @Resource | |
| 42 | + private MemberLevelApi memberLevelApi; | |
| 43 | + @Resource | |
| 44 | + private MemberUserApi memberUserApi; | |
| 35 | 45 | |
| 36 | 46 | @Override |
| 37 | 47 | public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) { |
| ... | ... | @@ -39,35 +49,101 @@ public class TradeDiscountActivityPriceCalculator implements TradePriceCalculato |
| 39 | 49 | if (ObjectUtil.notEqual(result.getType(), TradeOrderTypeEnum.NORMAL.getType())) { |
| 40 | 50 | return; |
| 41 | 51 | } |
| 52 | + | |
| 53 | + boolean discount; | |
| 54 | + boolean vip; | |
| 55 | + | |
| 56 | + //----------------------------------限时折扣计算----------------------------------------- | |
| 42 | 57 | // 获得 SKU 对应的限时折扣活动 |
| 43 | 58 | List<DiscountProductRespDTO> discountProducts = discountActivityApi.getMatchDiscountProductList( |
| 44 | 59 | convertSet(result.getItems(), TradePriceCalculateRespBO.OrderItem::getSkuId)); |
| 45 | 60 | if (CollUtil.isEmpty(discountProducts)) { |
| 46 | - return; | |
| 61 | + discount = false; | |
| 62 | + }else { | |
| 63 | + discount = true; | |
| 47 | 64 | } |
| 48 | 65 | Map<Long, DiscountProductRespDTO> discountProductMap = convertMap(discountProducts, DiscountProductRespDTO::getSkuId); |
| 49 | 66 | |
| 50 | - // 处理每个 SKU 的限时折扣 | |
| 67 | + | |
| 68 | + | |
| 69 | + //----------------------------------会员计算----------------------------------------- | |
| 70 | + MemberLevelRespDTO level; | |
| 71 | + // 获得用户的会员等级 | |
| 72 | + MemberUserRespDTO user = memberUserApi.getUser(param.getUserId()); | |
| 73 | + if (user.getLevelId() != null && user.getLevelId() > 0) { | |
| 74 | + level = memberLevelApi.getMemberLevel(user.getLevelId()); | |
| 75 | + if (level != null && level.getDiscountPercent() != null) { | |
| 76 | + vip = true; | |
| 77 | + }else { | |
| 78 | + vip = false; | |
| 79 | + } | |
| 80 | + }else { | |
| 81 | + level = null; | |
| 82 | + vip = false; | |
| 83 | + } | |
| 84 | + | |
| 85 | + | |
| 86 | + // 2. 计算每个 SKU 的优惠金额 | |
| 51 | 87 | result.getItems().forEach(orderItem -> { |
| 52 | - // 1. 获取该 SKU 的优惠信息 | |
| 53 | - DiscountProductRespDTO discountProduct = discountProductMap.get(orderItem.getSkuId()); | |
| 54 | - if (discountProduct == null) { | |
| 55 | - return; | |
| 88 | + | |
| 89 | + //----------------------------------限时折扣计算----------------------------------------- | |
| 90 | + DiscountProductRespDTO discountProduct = null; | |
| 91 | + Integer newDiscountPrice = 0; | |
| 92 | + if (discount){ | |
| 93 | + // 2.1 计算限时折扣优惠信息 | |
| 94 | + discountProduct = discountProductMap.get(orderItem.getSkuId()); | |
| 95 | + if (discountProduct != null) { | |
| 96 | + // 2.2 计算优惠金额 | |
| 97 | + Integer newPayPrice = calculatePayPrice(discountProduct, orderItem); | |
| 98 | + newDiscountPrice = orderItem.getPayPrice() - newPayPrice; | |
| 99 | + } | |
| 100 | + } | |
| 101 | + | |
| 102 | + | |
| 103 | + //----------------------------------会员计算----------------------------------------- | |
| 104 | + Integer vipPrice = 0; | |
| 105 | + if (vip){ | |
| 106 | + // 2.3 计算会员优惠金额 | |
| 107 | + vipPrice = calculateVipPrice(orderItem.getPayPrice(), level.getDiscountPercent()); | |
| 56 | 108 | } |
| 57 | - // 2. 计算优惠金额 | |
| 58 | - Integer newPayPrice = calculatePayPrice(discountProduct, orderItem); | |
| 59 | - Integer newDiscountPrice = orderItem.getPayPrice() - newPayPrice; | |
| 60 | 109 | |
| 61 | - // 3.1 记录优惠明细 | |
| 110 | + | |
| 111 | + // 2.4 记录优惠明细 | |
| 112 | + // 注意,只有在选中的情况下,才会记录到优惠明细。否则仅仅是更新 SKU 优惠金额,用于展示 | |
| 62 | 113 | if (orderItem.getSelected()) { |
| 63 | - // 注意,只有在选中的情况下,才会记录到优惠明细。否则仅仅是更新 SKU 优惠金额,用于展示 | |
| 64 | - TradePriceCalculatorHelper.addPromotion(result, orderItem, | |
| 65 | - discountProduct.getActivityId(), discountProduct.getActivityName(), PromotionTypeEnum.DISCOUNT_ACTIVITY.getType(), | |
| 66 | - StrUtil.format("限时折扣:省 {} 元", formatPrice(newDiscountPrice)), | |
| 67 | - newDiscountPrice); | |
| 114 | + if (discount && vip){ | |
| 115 | + if(newDiscountPrice > vipPrice){ | |
| 116 | + TradePriceCalculatorHelper.addPromotion(result, orderItem, | |
| 117 | + discountProduct.getActivityId(), discountProduct.getActivityName(), PromotionTypeEnum.DISCOUNT_ACTIVITY.getType(), | |
| 118 | + StrUtil.format("限时折扣:省 {} 元", formatPrice(newDiscountPrice)), | |
| 119 | + newDiscountPrice); | |
| 120 | + // 2.5 更新 SKU 优惠金额 | |
| 121 | + orderItem.setDiscountPrice(orderItem.getDiscountPrice() + newDiscountPrice); | |
| 122 | + }else{ | |
| 123 | + TradePriceCalculatorHelper.addPromotion(result, orderItem, | |
| 124 | + level.getId(), level.getName(), PromotionTypeEnum.MEMBER_LEVEL.getType(), | |
| 125 | + String.format("会员等级折扣:省 %s 元", formatPrice(vipPrice)), | |
| 126 | + vipPrice); | |
| 127 | + // 2.5 更新 SKU 的优惠金额 | |
| 128 | + orderItem.setVipPrice(vipPrice); | |
| 129 | + } | |
| 130 | + }else if (discount){ | |
| 131 | + TradePriceCalculatorHelper.addPromotion(result, orderItem, | |
| 132 | + discountProduct.getActivityId(), discountProduct.getActivityName(), PromotionTypeEnum.DISCOUNT_ACTIVITY.getType(), | |
| 133 | + StrUtil.format("限时折扣:省 {} 元", formatPrice(newDiscountPrice)), | |
| 134 | + newDiscountPrice); | |
| 135 | + // 2.5 更新 SKU 优惠金额 | |
| 136 | + orderItem.setDiscountPrice(orderItem.getDiscountPrice() + newDiscountPrice); | |
| 137 | + }else if (vip){ | |
| 138 | + TradePriceCalculatorHelper.addPromotion(result, orderItem, | |
| 139 | + level.getId(), level.getName(), PromotionTypeEnum.MEMBER_LEVEL.getType(), | |
| 140 | + String.format("会员等级折扣:省 %s 元", formatPrice(vipPrice)), | |
| 141 | + vipPrice); | |
| 142 | + // 2.5 更新 SKU 的优惠金额 | |
| 143 | + orderItem.setVipPrice(vipPrice); | |
| 144 | + } | |
| 68 | 145 | } |
| 69 | - // 3.2 更新 SKU 优惠金额 | |
| 70 | - orderItem.setDiscountPrice(orderItem.getDiscountPrice() + newDiscountPrice); | |
| 146 | + | |
| 71 | 147 | TradePriceCalculatorHelper.recountPayPrice(orderItem); |
| 72 | 148 | }); |
| 73 | 149 | TradePriceCalculatorHelper.recountAllPrice(result); |
| ... | ... | @@ -86,4 +162,20 @@ public class TradeDiscountActivityPriceCalculator implements TradePriceCalculato |
| 86 | 162 | return price; |
| 87 | 163 | } |
| 88 | 164 | |
| 165 | + /** | |
| 166 | + * 计算会员 VIP 优惠价格 | |
| 167 | + * | |
| 168 | + * @param price 原价 | |
| 169 | + * @param discountPercent 折扣 | |
| 170 | + * @return 优惠价格 | |
| 171 | + */ | |
| 172 | + public Integer calculateVipPrice(Integer price, Integer discountPercent) { | |
| 173 | + if (discountPercent == null) { | |
| 174 | + return 0; | |
| 175 | + } | |
| 176 | + BigDecimal divide = new BigDecimal(price).multiply(new BigDecimal(discountPercent)).divide(new BigDecimal(100)); | |
| 177 | + Integer newPrice = divide.intValue(); | |
| 178 | + return price - newPrice; | |
| 179 | + } | |
| 180 | + | |
| 89 | 181 | } | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeMemberLevelPriceCalculator.java
| ... | ... | @@ -30,44 +30,49 @@ public class TradeMemberLevelPriceCalculator implements TradePriceCalculator { |
| 30 | 30 | @Resource |
| 31 | 31 | private MemberUserApi memberUserApi; |
| 32 | 32 | |
| 33 | + /** | |
| 34 | + * 会员计算迁移到限时优惠计算里 | |
| 35 | + * @param param | |
| 36 | + * @param result | |
| 37 | + */ | |
| 33 | 38 | @Override |
| 34 | 39 | public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) { |
| 35 | - // 0. 只有【普通】订单,才计算该优惠 | |
| 36 | - if (ObjectUtil.notEqual(result.getType(), TradeOrderTypeEnum.NORMAL.getType())) { | |
| 37 | - return; | |
| 38 | - } | |
| 39 | - // 1. 获得用户的会员等级 | |
| 40 | - MemberUserRespDTO user = memberUserApi.getUser(param.getUserId()); | |
| 41 | - if (user.getLevelId() == null || user.getLevelId() <= 0) { | |
| 42 | - return; | |
| 43 | - } | |
| 44 | - MemberLevelRespDTO level = memberLevelApi.getMemberLevel(user.getLevelId()); | |
| 45 | - if (level == null || level.getDiscountPercent() == null) { | |
| 46 | - return; | |
| 47 | - } | |
| 48 | - | |
| 49 | - // 2. 计算每个 SKU 的优惠金额 | |
| 50 | - result.getItems().forEach(orderItem -> { | |
| 51 | - // 2.1 计算优惠金额 | |
| 52 | - Integer vipPrice = calculateVipPrice(orderItem.getPayPrice(), level.getDiscountPercent()); | |
| 53 | - if (vipPrice <= 0) { | |
| 54 | - return; | |
| 55 | - } | |
| 56 | - | |
| 57 | - // 2.2 记录优惠明细 | |
| 58 | - if (orderItem.getSelected()) { | |
| 59 | - // 注意,只有在选中的情况下,才会记录到优惠明细。否则仅仅是更新 SKU 优惠金额,用于展示 | |
| 60 | - TradePriceCalculatorHelper.addPromotion(result, orderItem, | |
| 61 | - level.getId(), level.getName(), PromotionTypeEnum.MEMBER_LEVEL.getType(), | |
| 62 | - String.format("会员等级折扣:省 %s 元", formatPrice(vipPrice)), | |
| 63 | - vipPrice); | |
| 64 | - } | |
| 65 | - | |
| 66 | - // 2.3 更新 SKU 的优惠金额 | |
| 67 | - orderItem.setVipPrice(vipPrice); | |
| 68 | - TradePriceCalculatorHelper.recountPayPrice(orderItem); | |
| 69 | - }); | |
| 70 | - TradePriceCalculatorHelper.recountAllPrice(result); | |
| 40 | +// // 0. 只有【普通】订单,才计算该优惠 | |
| 41 | +// if (ObjectUtil.notEqual(result.getType(), TradeOrderTypeEnum.NORMAL.getType())) { | |
| 42 | +// return; | |
| 43 | +// } | |
| 44 | +// // 1. 获得用户的会员等级 | |
| 45 | +// MemberUserRespDTO user = memberUserApi.getUser(param.getUserId()); | |
| 46 | +// if (user.getLevelId() == null || user.getLevelId() <= 0) { | |
| 47 | +// return; | |
| 48 | +// } | |
| 49 | +// MemberLevelRespDTO level = memberLevelApi.getMemberLevel(user.getLevelId()); | |
| 50 | +// if (level == null || level.getDiscountPercent() == null) { | |
| 51 | +// return; | |
| 52 | +// } | |
| 53 | +// | |
| 54 | +// // 2. 计算每个 SKU 的优惠金额 | |
| 55 | +// result.getItems().forEach(orderItem -> { | |
| 56 | +// // 2.1 计算优惠金额 | |
| 57 | +// Integer vipPrice = calculateVipPrice(orderItem.getPayPrice(), level.getDiscountPercent()); | |
| 58 | +// if (vipPrice <= 0) { | |
| 59 | +// return; | |
| 60 | +// } | |
| 61 | +// | |
| 62 | +// // 2.2 记录优惠明细 | |
| 63 | +// if (orderItem.getSelected()) { | |
| 64 | +// // 注意,只有在选中的情况下,才会记录到优惠明细。否则仅仅是更新 SKU 优惠金额,用于展示 | |
| 65 | +// TradePriceCalculatorHelper.addPromotion(result, orderItem, | |
| 66 | +// level.getId(), level.getName(), PromotionTypeEnum.MEMBER_LEVEL.getType(), | |
| 67 | +// String.format("会员等级折扣:省 %s 元", formatPrice(vipPrice)), | |
| 68 | +// vipPrice); | |
| 69 | +// } | |
| 70 | +// | |
| 71 | +// // 2.3 更新 SKU 的优惠金额 | |
| 72 | +// orderItem.setVipPrice(vipPrice); | |
| 73 | +// TradePriceCalculatorHelper.recountPayPrice(orderItem); | |
| 74 | +// }); | |
| 75 | +// TradePriceCalculatorHelper.recountAllPrice(result); | |
| 71 | 76 | } |
| 72 | 77 | |
| 73 | 78 | /** | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java
| ... | ... | @@ -23,7 +23,10 @@ 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.exception.util.ServiceExceptionUtil.exception; | |
| 27 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; | |
| 26 | 28 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList; |
| 29 | +import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.REWARD_ACTIVITY_TYPE_NOT_EXISTS; | |
| 27 | 30 | import static cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculatorHelper.formatPrice; |
| 28 | 31 | |
| 29 | 32 | // TODO @puhui999:相关的单测,建议改一改 |
| ... | ... | @@ -47,14 +50,15 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator |
| 47 | 50 | return; |
| 48 | 51 | } |
| 49 | 52 | // 获得 SKU 对应的满减送活动 |
| 50 | - List<RewardActivityMatchRespDTO> rewardActivities = rewardActivityApi.getRewardActivityListByStatusAndNow( | |
| 51 | - CommonStatusEnum.ENABLE.getStatus(), LocalDateTime.now()); | |
| 53 | + List<RewardActivityMatchRespDTO> rewardActivities = rewardActivityApi.getRewardActivityBySpuIdsAndStatusAndDateTimeLt( | |
| 54 | + convertSet(result.getItems(), TradePriceCalculateRespBO.OrderItem::getSpuId), CommonStatusEnum.ENABLE.getStatus(), LocalDateTime.now()); | |
| 52 | 55 | if (CollUtil.isEmpty(rewardActivities)) { |
| 53 | 56 | return; |
| 54 | 57 | } |
| 55 | - | |
| 56 | - // 处理每个满减送活动 | |
| 57 | - rewardActivities.forEach(rewardActivity -> calculate(param, result, rewardActivity)); | |
| 58 | + // 处理最新的满减送活动 | |
| 59 | + if(!rewardActivities.isEmpty()){ | |
| 60 | + calculate(param, result, rewardActivities.get(0)); | |
| 61 | + } | |
| 58 | 62 | } |
| 59 | 63 | |
| 60 | 64 | private void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result, |
| ... | ... | @@ -77,6 +81,8 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator |
| 77 | 81 | Integer newDiscountPrice = rule.getDiscountPrice(); |
| 78 | 82 | // 2.2 计算分摊的优惠金额 |
| 79 | 83 | List<Integer> divideDiscountPrices = TradePriceCalculatorHelper.dividePrice(orderItems, newDiscountPrice); |
| 84 | + //计算是否包邮 | |
| 85 | + result.setFreeDelivery(rule.getFreeDelivery()); | |
| 80 | 86 | |
| 81 | 87 | // 3.1 记录使用的优惠劵 |
| 82 | 88 | result.setCouponId(param.getCouponId()); |
| ... | ... | @@ -119,27 +125,24 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator |
| 119 | 125 | /** |
| 120 | 126 | * 获得满减送的订单项(商品)列表 |
| 121 | 127 | * |
| 122 | - * @param result 计算结果 | |
| 128 | + * @param result 计算结果 | |
| 123 | 129 | * @param rewardActivity 满减送活动 |
| 124 | 130 | * @return 订单项(商品)列表 |
| 125 | 131 | */ |
| 126 | 132 | private List<TradePriceCalculateRespBO.OrderItem> filterMatchActivityOrderItems(TradePriceCalculateRespBO result, |
| 127 | 133 | RewardActivityMatchRespDTO rewardActivity) { |
| 128 | - // 情况一:全部商品都可以参与 | |
| 129 | - if (PromotionProductScopeEnum.isAll(rewardActivity.getProductScope())) { | |
| 134 | + Integer productScope = rewardActivity.getProductScope(); | |
| 135 | + if(PromotionProductScopeEnum.isAll(productScope)){ | |
| 130 | 136 | return result.getItems(); |
| 131 | - } | |
| 132 | - // 情况二:指定商品参与 | |
| 133 | - if (PromotionProductScopeEnum.isSpu(rewardActivity.getProductScope())) { | |
| 137 | + }else if (PromotionProductScopeEnum.isSpu(productScope)) { | |
| 134 | 138 | return filterList(result.getItems(), |
| 135 | 139 | orderItem -> CollUtil.contains(rewardActivity.getProductScopeValues(), orderItem.getSpuId())); |
| 136 | - } | |
| 137 | - // 情况三:指定商品类型参与 | |
| 138 | - if (PromotionProductScopeEnum.isCategory(rewardActivity.getProductScope())) { | |
| 140 | + }else if (PromotionProductScopeEnum.isCategory(productScope)) { | |
| 139 | 141 | return filterList(result.getItems(), |
| 140 | 142 | orderItem -> CollUtil.contains(rewardActivity.getProductScopeValues(), orderItem.getCategoryId())); |
| 143 | + }else{ | |
| 144 | + throw exception(REWARD_ACTIVITY_TYPE_NOT_EXISTS); | |
| 141 | 145 | } |
| 142 | - return List.of(); | |
| 143 | 146 | } |
| 144 | 147 | |
| 145 | 148 | /** | ... | ... |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java
| ... | ... | @@ -11,6 +11,7 @@ import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; |
| 11 | 11 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 12 | 12 | import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; |
| 13 | 13 | import cn.iocoder.yudao.framework.datapermission.core.util.DataPermissionUtils; |
| 14 | +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; | |
| 14 | 15 | import cn.iocoder.yudao.module.infra.api.config.ConfigApi; |
| 15 | 16 | import cn.iocoder.yudao.module.infra.api.file.FileApi; |
| 16 | 17 | import cn.iocoder.yudao.module.system.controller.admin.auth.vo.AuthRegisterReqVO; |
| ... | ... | @@ -104,6 +105,7 @@ public class AdminUserServiceImpl implements AdminUserService { |
| 104 | 105 | AdminUserDO user = BeanUtils.toBean(createReqVO, AdminUserDO.class); |
| 105 | 106 | user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启 |
| 106 | 107 | user.setPassword(encodePassword(createReqVO.getPassword())); // 加密密码 |
| 108 | + user.setTenantId(TenantContextHolder.getRequiredTenantId()); | |
| 107 | 109 | userMapper.insert(user); |
| 108 | 110 | // 2.2 插入关联岗位 |
| 109 | 111 | if (CollectionUtil.isNotEmpty(user.getPostIds())) { | ... | ... |
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 的前缀 | ... | ... |