Commit 084d4baba2d9f52e674a4287725e0b26b6f25907
1 parent
a88eda34
price:完成会员价格的计算~
Showing
8 changed files
with
292 additions
and
23 deletions
yudao-module-mall/yudao-module-market-api/src/main/java/cn/iocoder/yudao/module/market/api/price/dto/PriceCalculateRespDTO.java
| @@ -52,7 +52,7 @@ public class PriceCalculateRespDTO { | @@ -52,7 +52,7 @@ public class PriceCalculateRespDTO { | ||
| 52 | */ | 52 | */ |
| 53 | private Integer originalPrice; | 53 | private Integer originalPrice; |
| 54 | /** | 54 | /** |
| 55 | - * 商品优惠(总),单位:分 | 55 | + * 订单优惠(总),单位:分 |
| 56 | * | 56 | * |
| 57 | * 订单级优惠:对主订单的优惠,常见如:订单满 200 元减 10 元;订单满 80 包邮。 | 57 | * 订单级优惠:对主订单的优惠,常见如:订单满 200 元减 10 元;订单满 80 包邮。 |
| 58 | * | 58 | * |
| @@ -101,9 +101,13 @@ public class PriceCalculateRespDTO { | @@ -101,9 +101,13 @@ public class PriceCalculateRespDTO { | ||
| 101 | * 订单商品 SKU | 101 | * 订单商品 SKU |
| 102 | */ | 102 | */ |
| 103 | @Data | 103 | @Data |
| 104 | - public static class OrderItem extends PriceCalculateReqDTO.Item { | 104 | + public static class OrderItem { |
| 105 | 105 | ||
| 106 | /** | 106 | /** |
| 107 | + * SKU 编号 | ||
| 108 | + */ | ||
| 109 | + private Long skuId; | ||
| 110 | + /** | ||
| 107 | * 购买数量 | 111 | * 购买数量 |
| 108 | */ | 112 | */ |
| 109 | private Integer count; | 113 | private Integer count; |
| @@ -141,14 +145,18 @@ public class PriceCalculateRespDTO { | @@ -141,14 +145,18 @@ public class PriceCalculateRespDTO { | ||
| 141 | 145 | ||
| 142 | /** | 146 | /** |
| 143 | * 子订单分摊金额(总),单位:分 | 147 | * 子订单分摊金额(总),单位:分 |
| 144 | - * 需要分摊 {@link Order#discountPrice}、{@link Order#couponPrice} | 148 | + * 需要分摊 {@link Order#discountPrice}、{@link Order#couponPrice}、{@link Order#pointPrice} |
| 145 | * | 149 | * |
| 146 | * 对应 taobao 的 order.part_mjz_discount 字段 | 150 | * 对应 taobao 的 order.part_mjz_discount 字段 |
| 151 | + * 淘宝说明:子订单分摊优惠基础逻辑:一般正常优惠券和满减优惠按照子订单的金额进行分摊,特殊情况如果优惠券是指定商品使用的,只会分摊到对应商品子订单上不分摊。 | ||
| 147 | */ | 152 | */ |
| 148 | private Integer orderPartPrice; | 153 | private Integer orderPartPrice; |
| 149 | /** | 154 | /** |
| 150 | * 分摊后子订单实付金额(总),单位:分 | 155 | * 分摊后子订单实付金额(总),单位:分 |
| 151 | * | 156 | * |
| 157 | + * = {@link #payPrice} | ||
| 158 | + * - {@link #orderPartPrice} | ||
| 159 | + * | ||
| 152 | * 对应 taobao 的 divide_order_fee 字段 | 160 | * 对应 taobao 的 divide_order_fee 字段 |
| 153 | */ | 161 | */ |
| 154 | private Integer orderDividePrice; | 162 | private Integer orderDividePrice; |
| @@ -156,7 +164,7 @@ public class PriceCalculateRespDTO { | @@ -156,7 +164,7 @@ public class PriceCalculateRespDTO { | ||
| 156 | } | 164 | } |
| 157 | 165 | ||
| 158 | /** | 166 | /** |
| 159 | - * 营销活动 | 167 | + * 营销明细 |
| 160 | */ | 168 | */ |
| 161 | @Data | 169 | @Data |
| 162 | public static class Promotion { | 170 | public static class Promotion { |
| @@ -186,11 +194,11 @@ public class PriceCalculateRespDTO { | @@ -186,11 +194,11 @@ public class PriceCalculateRespDTO { | ||
| 186 | /** | 194 | /** |
| 187 | * 计算时的原价(总),单位:分 | 195 | * 计算时的原价(总),单位:分 |
| 188 | */ | 196 | */ |
| 189 | - private Integer beforePrice; | 197 | + private Integer originalPrice; |
| 190 | /** | 198 | /** |
| 191 | * 计算时的优惠(总),单位:分 | 199 | * 计算时的优惠(总),单位:分 |
| 192 | */ | 200 | */ |
| 193 | - private Integer afterPrice; | 201 | + private Integer discountPrice; |
| 194 | /** | 202 | /** |
| 195 | * 匹配的商品 SKU 数组 | 203 | * 匹配的商品 SKU 数组 |
| 196 | */ | 204 | */ |
| @@ -225,11 +233,11 @@ public class PriceCalculateRespDTO { | @@ -225,11 +233,11 @@ public class PriceCalculateRespDTO { | ||
| 225 | /** | 233 | /** |
| 226 | * 计算时的原价(总),单位:分 | 234 | * 计算时的原价(总),单位:分 |
| 227 | */ | 235 | */ |
| 228 | - private Integer beforePrice; | 236 | + private Integer originalPrice; |
| 229 | /** | 237 | /** |
| 230 | * 计算时的优惠(总),单位:分 | 238 | * 计算时的优惠(总),单位:分 |
| 231 | */ | 239 | */ |
| 232 | - private Integer afterPrice; | 240 | + private Integer discountPrice; |
| 233 | 241 | ||
| 234 | } | 242 | } |
| 235 | 243 |
yudao-module-mall/yudao-module-market-api/src/main/java/cn/iocoder/yudao/module/market/enums/common/PromotionTypeEnum.java
| @@ -17,6 +17,8 @@ public enum PromotionTypeEnum implements IntArrayValuable { | @@ -17,6 +17,8 @@ public enum PromotionTypeEnum implements IntArrayValuable { | ||
| 17 | 17 | ||
| 18 | DISCOUNT_ACTIVITY(1, "限时折扣"), | 18 | DISCOUNT_ACTIVITY(1, "限时折扣"), |
| 19 | REWARD_ACTIVITY(2, "满减送"), | 19 | REWARD_ACTIVITY(2, "满减送"), |
| 20 | + | ||
| 21 | + MEMBER(3, "会员折扣"), | ||
| 20 | ; | 22 | ; |
| 21 | 23 | ||
| 22 | public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(PromotionTypeEnum::getType).toArray(); | 24 | public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(PromotionTypeEnum::getType).toArray(); |
yudao-module-mall/yudao-module-market-biz/src/main/java/cn/iocoder/yudao/module/market/convert/price/PriceConvert.java
| @@ -19,20 +19,23 @@ public interface PriceConvert { | @@ -19,20 +19,23 @@ public interface PriceConvert { | ||
| 19 | default PriceCalculateRespDTO convert(PriceCalculateReqDTO calculateReqDTO, List<ProductSkuRespDTO> skuList) { | 19 | default PriceCalculateRespDTO convert(PriceCalculateReqDTO calculateReqDTO, List<ProductSkuRespDTO> skuList) { |
| 20 | // 创建 PriceCalculateRespDTO 对象 | 20 | // 创建 PriceCalculateRespDTO 对象 |
| 21 | PriceCalculateRespDTO priceCalculate = new PriceCalculateRespDTO(); | 21 | PriceCalculateRespDTO priceCalculate = new PriceCalculateRespDTO(); |
| 22 | - priceCalculate.setOrder(new PriceCalculateRespDTO.Order().setOriginalPrice(0).setActivityPrice(0) | ||
| 23 | - .setDeliveryPrice(0).setPayPrice(0).setItems(new ArrayList<>()) | ||
| 24 | - .setCouponId(calculateReqDTO.getCouponId())); | ||
| 25 | - priceCalculate.setPromotions(new ArrayList<>()); | 22 | + // 创建它的 Order 属性 |
| 23 | + PriceCalculateRespDTO.Order order = new PriceCalculateRespDTO.Order().setOriginalPrice(0).setDiscountPrice(0) | ||
| 24 | + .setCouponPrice(0).setPointPrice(0).setDeliveryPrice(0).setPayPrice(0) | ||
| 25 | + .setItems(new ArrayList<>()).setCouponId(calculateReqDTO.getCouponId()); | ||
| 26 | + priceCalculate.setOrder(order).setPromotions(new ArrayList<>()); | ||
| 26 | // 创建它的 OrderItem 属性 | 27 | // 创建它的 OrderItem 属性 |
| 27 | Map<Long, Integer> skuIdCountMap = CollectionUtils.convertMap(calculateReqDTO.getItems(), | 28 | Map<Long, Integer> skuIdCountMap = CollectionUtils.convertMap(calculateReqDTO.getItems(), |
| 28 | PriceCalculateReqDTO.Item::getSkuId, PriceCalculateReqDTO.Item::getCount); | 29 | PriceCalculateReqDTO.Item::getSkuId, PriceCalculateReqDTO.Item::getCount); |
| 29 | skuList.forEach(sku -> { | 30 | skuList.forEach(sku -> { |
| 30 | Integer count = skuIdCountMap.get(sku.getId()); | 31 | Integer count = skuIdCountMap.get(sku.getId()); |
| 31 | - PriceCalculateRespDTO.OrderItem orderItem = new PriceCalculateRespDTO.OrderItem().setCount(count) | ||
| 32 | - .setOriginalUnitPrice(sku.getPrice()).setOriginalPrice(sku.getPrice() * count).setActivityPrice(0); | ||
| 33 | - orderItem.setPayPrice(orderItem.getPayPrice()).setPayUnitPrice(orderItem.getOriginalUnitPrice()) | ||
| 34 | - .setPayPrice(orderItem.getPayPrice()); | 32 | + PriceCalculateRespDTO.OrderItem orderItem = new PriceCalculateRespDTO.OrderItem() |
| 33 | + .setSkuId(sku.getId()).setCount(count).setOriginalUnitPrice(sku.getPrice()) | ||
| 34 | + .setOriginalPrice(sku.getPrice() * count).setDiscountPrice(0).setOrderPartPrice(0); | ||
| 35 | + orderItem.setPayPrice(orderItem.getOriginalPrice()).setOrderDividePrice(orderItem.getOrderDividePrice()); | ||
| 35 | priceCalculate.getOrder().getItems().add(orderItem); | 36 | priceCalculate.getOrder().getItems().add(orderItem); |
| 37 | + // 补充价格信息到 Order 中 | ||
| 38 | + order.setOriginalPrice(order.getOriginalPrice() + orderItem.getOriginalPrice()).setPayPrice(order.getOriginalPrice()); | ||
| 36 | }); | 39 | }); |
| 37 | return priceCalculate; | 40 | return priceCalculate; |
| 38 | } | 41 | } |
yudao-module-mall/yudao-module-market-biz/src/main/java/cn/iocoder/yudao/module/market/service/discount/DiscountService.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.market.service.discount; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.module.market.dal.dataobject.discount.DiscountProductDO; | ||
| 4 | + | ||
| 5 | +import java.util.Collection; | ||
| 6 | +import java.util.Map; | ||
| 7 | + | ||
| 8 | +/** | ||
| 9 | + * 限时折扣 Service 接口 | ||
| 10 | + * | ||
| 11 | + * @author 芋道源码 | ||
| 12 | + */ | ||
| 13 | +public interface DiscountService { | ||
| 14 | + | ||
| 15 | + /** | ||
| 16 | + * 基于指定 SKU 编号数组,获得匹配的限时折扣商品 | ||
| 17 | + * | ||
| 18 | + * 注意,匹配的条件,仅仅是日期符合,并且处于开启状态 | ||
| 19 | + * | ||
| 20 | + * @param skuIds SKU 编号数组 | ||
| 21 | + * @return 匹配的限时折扣商品 | ||
| 22 | + */ | ||
| 23 | + Map<Long, DiscountProductDO> getMatchDiscountProducts(Collection<Long> skuIds); | ||
| 24 | + | ||
| 25 | +} |
yudao-module-mall/yudao-module-market-biz/src/main/java/cn/iocoder/yudao/module/market/service/discount/DiscountServiceImpl.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.market.service.discount; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.module.market.dal.dataobject.discount.DiscountProductDO; | ||
| 4 | +import org.springframework.stereotype.Service; | ||
| 5 | +import org.springframework.validation.annotation.Validated; | ||
| 6 | + | ||
| 7 | +import java.util.Collection; | ||
| 8 | +import java.util.HashMap; | ||
| 9 | +import java.util.Map; | ||
| 10 | + | ||
| 11 | +/** | ||
| 12 | + * 限时折扣 Service 实现类 | ||
| 13 | + * | ||
| 14 | + * @author 芋道源码 | ||
| 15 | + */ | ||
| 16 | +@Service | ||
| 17 | +@Validated | ||
| 18 | +public class DiscountServiceImpl implements DiscountService { | ||
| 19 | + | ||
| 20 | + // TODO 芋艿:待实现 | ||
| 21 | + @Override | ||
| 22 | + public Map<Long, DiscountProductDO> getMatchDiscountProducts(Collection<Long> skuIds) { | ||
| 23 | + Map<Long, DiscountProductDO> products = new HashMap<>(); | ||
| 24 | + products.put(1L, new DiscountProductDO().setPromotionPrice(100)); | ||
| 25 | + products.put(2L, new DiscountProductDO().setPromotionPrice(50)); | ||
| 26 | + return products; | ||
| 27 | + } | ||
| 28 | + | ||
| 29 | +} |
yudao-module-mall/yudao-module-market-biz/src/main/java/cn/iocoder/yudao/module/market/service/discount/package-info.java deleted
yudao-module-mall/yudao-module-market-biz/src/main/java/cn/iocoder/yudao/module/market/service/price/PriceServiceImpl.java
| 1 | package cn.iocoder.yudao.module.market.service.price; | 1 | package cn.iocoder.yudao.module.market.service.price; |
| 2 | 2 | ||
| 3 | +import cn.hutool.core.util.StrUtil; | ||
| 3 | import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | 4 | import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; |
| 4 | import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateReqDTO; | 5 | import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateReqDTO; |
| 5 | import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateRespDTO; | 6 | import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateRespDTO; |
| 6 | import cn.iocoder.yudao.module.market.convert.price.PriceConvert; | 7 | import cn.iocoder.yudao.module.market.convert.price.PriceConvert; |
| 8 | +import cn.iocoder.yudao.module.market.dal.dataobject.discount.DiscountProductDO; | ||
| 9 | +import cn.iocoder.yudao.module.market.enums.common.PromotionLevelEnum; | ||
| 10 | +import cn.iocoder.yudao.module.market.enums.common.PromotionTypeEnum; | ||
| 11 | +import cn.iocoder.yudao.module.market.service.discount.DiscountService; | ||
| 7 | import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi; | 12 | import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi; |
| 8 | import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO; | 13 | import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO; |
| 14 | +import com.google.common.base.Suppliers; | ||
| 9 | import org.springframework.stereotype.Service; | 15 | import org.springframework.stereotype.Service; |
| 10 | import org.springframework.validation.annotation.Validated; | 16 | import org.springframework.validation.annotation.Validated; |
| 11 | 17 | ||
| 12 | import javax.annotation.Resource; | 18 | import javax.annotation.Resource; |
| 13 | import java.util.List; | 19 | import java.util.List; |
| 14 | import java.util.Map; | 20 | import java.util.Map; |
| 21 | +import java.util.function.Supplier; | ||
| 15 | 22 | ||
| 16 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | 23 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 24 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; | ||
| 17 | import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS; | 25 | import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS; |
| 26 | +import static java.util.Collections.singletonList; | ||
| 18 | 27 | ||
| 19 | /** | 28 | /** |
| 20 | * 价格计算 Service 实现类 | 29 | * 价格计算 Service 实现类 |
| 21 | * | 30 | * |
| 31 | + * 优惠计算顺序:min(限时折扣, 会员折扣) > 满减送 > 优惠券。 | ||
| 32 | + * 参考文档: | ||
| 33 | + * 1. <a href="https://help.youzan.com/displaylist/detail_4_4-1-60384">有赞文档:限时折扣、满减送、优惠券哪个优先计算?</a> | ||
| 34 | + * | ||
| 22 | * @author 芋道源码 | 35 | * @author 芋道源码 |
| 23 | */ | 36 | */ |
| 24 | @Service | 37 | @Service |
| @@ -26,22 +39,26 @@ import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_E | @@ -26,22 +39,26 @@ import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_E | ||
| 26 | public class PriceServiceImpl implements PriceService { | 39 | public class PriceServiceImpl implements PriceService { |
| 27 | 40 | ||
| 28 | @Resource | 41 | @Resource |
| 42 | + private DiscountService discountService; | ||
| 43 | + | ||
| 44 | + @Resource | ||
| 29 | private ProductSkuApi productSkuApi; | 45 | private ProductSkuApi productSkuApi; |
| 30 | 46 | ||
| 31 | @Override | 47 | @Override |
| 32 | public PriceCalculateRespDTO calculatePrice(PriceCalculateReqDTO calculateReqDTO) { | 48 | public PriceCalculateRespDTO calculatePrice(PriceCalculateReqDTO calculateReqDTO) { |
| 33 | // 获得商品 SKU 数组 | 49 | // 获得商品 SKU 数组 |
| 34 | - List<ProductSkuRespDTO> skuList = checkProductSkus(calculateReqDTO); | 50 | + List<ProductSkuRespDTO> skuList = checkSkus(calculateReqDTO); |
| 35 | // 初始化 PriceCalculateRespDTO 对象 | 51 | // 初始化 PriceCalculateRespDTO 对象 |
| 36 | PriceCalculateRespDTO priceCalculate = PriceConvert.INSTANCE.convert(calculateReqDTO, skuList); | 52 | PriceCalculateRespDTO priceCalculate = PriceConvert.INSTANCE.convert(calculateReqDTO, skuList); |
| 37 | 53 | ||
| 38 | - // 计算【限时折扣】促销 TODO 待实现 | 54 | + // 计算商品级别的价格 |
| 55 | + calculatePriceForSkuLevel(calculateReqDTO.getUserId(), priceCalculate); | ||
| 39 | // 计算【满减送】促销 TODO 待实现 | 56 | // 计算【满减送】促销 TODO 待实现 |
| 40 | // 计算【优惠劵】促销 TODO 待实现 | 57 | // 计算【优惠劵】促销 TODO 待实现 |
| 41 | return priceCalculate; | 58 | return priceCalculate; |
| 42 | } | 59 | } |
| 43 | 60 | ||
| 44 | - private List<ProductSkuRespDTO> checkProductSkus(PriceCalculateReqDTO calculateReqDTO) { | 61 | + private List<ProductSkuRespDTO> checkSkus(PriceCalculateReqDTO calculateReqDTO) { |
| 45 | // 获得商品 SKU 数组 | 62 | // 获得商品 SKU 数组 |
| 46 | Map<Long, Integer> skuIdCountMap = CollectionUtils.convertMap(calculateReqDTO.getItems(), | 63 | Map<Long, Integer> skuIdCountMap = CollectionUtils.convertMap(calculateReqDTO.getItems(), |
| 47 | PriceCalculateReqDTO.Item::getSkuId, PriceCalculateReqDTO.Item::getCount); | 64 | PriceCalculateReqDTO.Item::getSkuId, PriceCalculateReqDTO.Item::getCount); |
| @@ -58,4 +75,107 @@ public class PriceServiceImpl implements PriceService { | @@ -58,4 +75,107 @@ public class PriceServiceImpl implements PriceService { | ||
| 58 | return skus; | 75 | return skus; |
| 59 | } | 76 | } |
| 60 | 77 | ||
| 78 | + /** | ||
| 79 | + * 计算商品级别的价格,例如说: | ||
| 80 | + * 1. 会员折扣 | ||
| 81 | + * 2. 限时折扣 | ||
| 82 | + * | ||
| 83 | + * 其中,会员折扣、限时折扣取最低价 | ||
| 84 | + * | ||
| 85 | + * @param userId 用户编号 | ||
| 86 | + * @param priceCalculate 价格计算的结果 | ||
| 87 | + */ | ||
| 88 | + private void calculatePriceForSkuLevel(Long userId, PriceCalculateRespDTO priceCalculate) { | ||
| 89 | + // 获取 SKU 级别的所有优惠信息 | ||
| 90 | + Supplier<Double> memberDiscountSupplier = getMemberDiscountSupplier(userId); | ||
| 91 | + Map<Long, DiscountProductDO> discountProducts = discountService.getMatchDiscountProducts( | ||
| 92 | + convertSet(priceCalculate.getOrder().getItems(), PriceCalculateRespDTO.OrderItem::getSkuId)); | ||
| 93 | + | ||
| 94 | + // 处理每个 SKU 的优惠 | ||
| 95 | + priceCalculate.getOrder().getItems().forEach(orderItem -> { | ||
| 96 | + // 获取该 SKU 的优惠信息 | ||
| 97 | + Double memberDiscount = memberDiscountSupplier.get(); | ||
| 98 | + DiscountProductDO discountProduct = discountProducts.get(orderItem.getSkuId()); | ||
| 99 | + if (discountProduct != null // 假设优惠价格更贵,则认为没优惠 | ||
| 100 | + && discountProduct.getPromotionPrice() >= orderItem.getOriginalUnitPrice()) { | ||
| 101 | + discountProduct = null; | ||
| 102 | + } | ||
| 103 | + if (memberDiscount == null && discountProduct == null) { | ||
| 104 | + return; | ||
| 105 | + } | ||
| 106 | + // 计算价格,判断选择哪个折扣 | ||
| 107 | + Integer memberPrice = memberDiscount != null ? (int) (orderItem.getPayPrice() * memberDiscount / 100) : null; | ||
| 108 | + Integer promotionPrice = discountProduct != null ? discountProduct.getPromotionPrice() * orderItem.getCount() : null; | ||
| 109 | + if (memberPrice == null) { | ||
| 110 | + | ||
| 111 | + } else if (promotionPrice == null) { | ||
| 112 | + calculatePriceByMemberDiscount(orderItem, memberDiscount, memberPrice, priceCalculate); | ||
| 113 | + } else if (memberPrice < promotionPrice) { | ||
| 114 | + | ||
| 115 | + } else { | ||
| 116 | + calculatePriceByMemberDiscount(orderItem, memberDiscount, memberPrice, priceCalculate); | ||
| 117 | + } | ||
| 118 | + }); | ||
| 119 | + } | ||
| 120 | + | ||
| 121 | + private void calculatePriceByMemberDiscount(PriceCalculateRespDTO.OrderItem orderItem, | ||
| 122 | + Double memberDiscount, Integer memberPrice, | ||
| 123 | + PriceCalculateRespDTO priceCalculate) { | ||
| 124 | + // 记录优惠明细 | ||
| 125 | + addPromotion(priceCalculate, orderItem, null, | ||
| 126 | + PromotionTypeEnum.MEMBER.getName(), | ||
| 127 | + PromotionTypeEnum.MEMBER.getType(), PromotionLevelEnum.SKU.getLevel(), memberPrice, | ||
| 128 | + true, StrUtil.format("会员折扣:省 {} 元", formatPrice(orderItem.getPayPrice() - memberPrice))); | ||
| 129 | + // 修改 SKU 的优惠 | ||
| 130 | + modifyOrderItemPayPrice(orderItem, memberPrice, priceCalculate); | ||
| 131 | + } | ||
| 132 | + | ||
| 133 | + private void calculatePriceByDiscountActivity(PriceCalculateRespDTO.OrderItem orderItem, | ||
| 134 | + DiscountProductDO discountProduct, Integer promotionPrice, | ||
| 135 | + PriceCalculateRespDTO priceCalculate) { | ||
| 136 | + | ||
| 137 | + } | ||
| 138 | + | ||
| 139 | + private void addPromotion(PriceCalculateRespDTO priceCalculate, PriceCalculateRespDTO.OrderItem orderItem, | ||
| 140 | + Long id, String name, Integer type, Integer level, | ||
| 141 | + Integer newPayPrice, Boolean meet, String meetTip) { | ||
| 142 | + // 创建营销明细 Item | ||
| 143 | + PriceCalculateRespDTO.PromotionItem promotionItem = new PriceCalculateRespDTO.PromotionItem().setSkuId(orderItem.getSkuId()) | ||
| 144 | + .setOriginalPrice(orderItem.getPayPrice()).setDiscountPrice(orderItem.getPayPrice() - newPayPrice); | ||
| 145 | + // 创建营销明细 | ||
| 146 | + PriceCalculateRespDTO.Promotion promotion = new PriceCalculateRespDTO.Promotion() | ||
| 147 | + .setId(id).setName(name).setType(type).setLevel(level) | ||
| 148 | + .setOriginalPrice(promotionItem.getOriginalPrice()).setDiscountPrice(promotionItem.getDiscountPrice()) | ||
| 149 | + .setItems(singletonList(promotionItem)).setMeet(meet).setMeetTip(meetTip); | ||
| 150 | + priceCalculate.getPromotions().add(promotion); | ||
| 151 | + } | ||
| 152 | + | ||
| 153 | + private void modifyOrderItemPayPrice(PriceCalculateRespDTO.OrderItem orderItem, Integer newPayPrice, | ||
| 154 | + PriceCalculateRespDTO priceCalculate) { | ||
| 155 | + int diffPayPrice = orderItem.getPayPrice() - newPayPrice; | ||
| 156 | + // 设置 OrderItem 价格相关字段 | ||
| 157 | + orderItem.setDiscountPrice(orderItem.getDiscountPrice() + diffPayPrice); | ||
| 158 | + orderItem.setPayPrice(newPayPrice); | ||
| 159 | + orderItem.setOrderDividePrice(orderItem.getPayPrice() - orderItem.getOrderPartPrice()); | ||
| 160 | + // 设置 Order 相关相关字段 | ||
| 161 | + priceCalculate.getOrder().setPayPrice(priceCalculate.getOrder().getPayPrice() - diffPayPrice); | ||
| 162 | + } | ||
| 163 | + | ||
| 164 | + // TODO 芋艿:提前实现 | ||
| 165 | + private Supplier<Double> getMemberDiscountSupplier(Long userId) { | ||
| 166 | + return Suppliers.memoize(() -> { | ||
| 167 | + if (userId == 1) { | ||
| 168 | + return 90d; | ||
| 169 | + } | ||
| 170 | + if (userId == 2) { | ||
| 171 | + return 80d; | ||
| 172 | + } | ||
| 173 | + return null; // 无优惠 | ||
| 174 | + }); | ||
| 175 | + } | ||
| 176 | + | ||
| 177 | + private String formatPrice(Integer price) { | ||
| 178 | + return String.format("%.2f", price / 100d); | ||
| 179 | + } | ||
| 180 | + | ||
| 61 | } | 181 | } |
yudao-module-mall/yudao-module-market-biz/src/test/java/cn/iocoder/yudao/module/market/service/price/PriceServiceTest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.market.service.price; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.common.util.collection.SetUtils; | ||
| 4 | +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | ||
| 5 | +import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateReqDTO; | ||
| 6 | +import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateRespDTO; | ||
| 7 | +import cn.iocoder.yudao.module.market.enums.common.PromotionLevelEnum; | ||
| 8 | +import cn.iocoder.yudao.module.market.enums.common.PromotionTypeEnum; | ||
| 9 | +import cn.iocoder.yudao.module.market.service.discount.DiscountService; | ||
| 10 | +import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi; | ||
| 11 | +import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO; | ||
| 12 | +import org.junit.jupiter.api.Test; | ||
| 13 | +import org.mockito.InjectMocks; | ||
| 14 | +import org.mockito.Mock; | ||
| 15 | + | ||
| 16 | +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; | ||
| 17 | +import static java.util.Collections.singletonList; | ||
| 18 | +import static org.junit.jupiter.api.Assertions.*; | ||
| 19 | +import static org.mockito.ArgumentMatchers.eq; | ||
| 20 | +import static org.mockito.Mockito.when; | ||
| 21 | + | ||
| 22 | +/** | ||
| 23 | + * {@link PriceServiceImpl} 的单元测试 | ||
| 24 | + * | ||
| 25 | + * @author 芋道源码 | ||
| 26 | + */ | ||
| 27 | +public class PriceServiceTest extends BaseMockitoUnitTest { | ||
| 28 | + | ||
| 29 | + @InjectMocks | ||
| 30 | + private PriceServiceImpl priceService; | ||
| 31 | + | ||
| 32 | + @Mock | ||
| 33 | + private DiscountService discountService; | ||
| 34 | + @Mock | ||
| 35 | + private ProductSkuApi productSkuApi; | ||
| 36 | + | ||
| 37 | + @Test | ||
| 38 | + public void testCalculatePrice_memberDiscount() { | ||
| 39 | + // 准备参数 | ||
| 40 | + // TODO 芋艿:userId = 1,实现 9 折;后续改成 mock | ||
| 41 | + PriceCalculateReqDTO calculateReqDTO = new PriceCalculateReqDTO().setUserId(1L) | ||
| 42 | + .setItems(singletonList(new PriceCalculateReqDTO.Item().setSkuId(10L).setCount(2))); | ||
| 43 | + // mock 方法(商品 SKU 信息) | ||
| 44 | + ProductSkuRespDTO productSku = randomPojo(ProductSkuRespDTO.class, o -> o.setId(10L).setPrice(100)); | ||
| 45 | + when(productSkuApi.getSkuList(eq(SetUtils.asSet(10L)))).thenReturn(singletonList(productSku)); | ||
| 46 | + | ||
| 47 | + // 调用 | ||
| 48 | + PriceCalculateRespDTO priceCalculate = priceService.calculatePrice(calculateReqDTO); | ||
| 49 | + // 断言 Order 部分 | ||
| 50 | + PriceCalculateRespDTO.Order order = priceCalculate.getOrder(); | ||
| 51 | + assertEquals(order.getOriginalPrice(), 200); | ||
| 52 | + assertEquals(order.getDiscountPrice(), 0); | ||
| 53 | + assertEquals(order.getPointPrice(), 0); | ||
| 54 | + assertEquals(order.getDeliveryPrice(), 0); | ||
| 55 | + assertEquals(order.getPayPrice(), 180); | ||
| 56 | + assertNull(order.getCouponId()); | ||
| 57 | + // 断言 OrderItem 部分 | ||
| 58 | + PriceCalculateRespDTO.OrderItem orderItem = order.getItems().get(0); | ||
| 59 | + assertEquals(order.getItems().size(), 1); | ||
| 60 | + assertEquals(orderItem.getSkuId(), 10L); | ||
| 61 | + assertEquals(orderItem.getCount(), 2); | ||
| 62 | + assertEquals(orderItem.getOriginalPrice(), 200); | ||
| 63 | + assertEquals(orderItem.getOriginalUnitPrice(), 100); | ||
| 64 | + assertEquals(orderItem.getDiscountPrice(), 20); | ||
| 65 | + assertEquals(orderItem.getPayPrice(), 180); | ||
| 66 | + assertEquals(orderItem.getOrderPartPrice(), 0); | ||
| 67 | + assertEquals(orderItem.getOrderDividePrice(), 180); | ||
| 68 | + // 断言 Promotion 部分 | ||
| 69 | + PriceCalculateRespDTO.Promotion promotion = priceCalculate.getPromotions().get(0); | ||
| 70 | + assertEquals(priceCalculate.getPromotions().size(), 1); | ||
| 71 | + assertNull(promotion.getId()); | ||
| 72 | + assertEquals(promotion.getName(), "会员折扣"); | ||
| 73 | + assertEquals(promotion.getType(), PromotionTypeEnum.MEMBER.getType()); | ||
| 74 | + assertEquals(promotion.getLevel(), PromotionLevelEnum.SKU.getLevel()); | ||
| 75 | + assertEquals(promotion.getOriginalPrice(), 200); | ||
| 76 | + assertEquals(promotion.getDiscountPrice(), 20); | ||
| 77 | + assertTrue(promotion.getMeet()); | ||
| 78 | + assertEquals(promotion.getMeetTip(), "会员折扣:省 0.20 元"); | ||
| 79 | + PriceCalculateRespDTO.PromotionItem promotionItem = promotion.getItems().get(0); | ||
| 80 | + assertEquals(promotion.getItems().size(), 1); | ||
| 81 | + assertEquals(promotionItem.getSkuId(), 10L); | ||
| 82 | + assertEquals(promotionItem.getOriginalPrice(), 200); | ||
| 83 | + assertEquals(promotionItem.getDiscountPrice(), 20); | ||
| 84 | + } | ||
| 85 | + | ||
| 86 | +} |