Commit 829bbf58b5aae5b57782a80e0773774669ac86ae

Authored by YunaiV
1 parent 160d619d

price:完成优惠劵的价格计算~

yudao-module-mall/pom.xml
@@ -18,8 +18,8 @@ @@ -18,8 +18,8 @@
18 商城大模块,由 product 商品、market 营销、trade 交易 coupon等组成 18 商城大模块,由 product 商品、market 营销、trade 交易 coupon等组成
19 </description> 19 </description>
20 <modules> 20 <modules>
21 - <module>yudao-module-coupon-api</module>  
22 - <module>yudao-module-coupon-biz</module> 21 +<!-- <module>yudao-module-coupon-api</module>-->
  22 +<!-- <module>yudao-module-coupon-biz</module>-->
23 <module>yudao-module-market-api</module> 23 <module>yudao-module-market-api</module>
24 <module>yudao-module-market-biz</module> 24 <module>yudao-module-market-biz</module>
25 <module>yudao-module-product-api</module> 25 <module>yudao-module-product-api</module>
yudao-module-mall/yudao-module-market-api/src/main/java/cn/iocoder/yudao/module/market/enums/ErrorCodeConstants.java
@@ -9,10 +9,14 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode; @@ -9,10 +9,14 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
9 */ 9 */
10 public interface ErrorCodeConstants { 10 public interface ErrorCodeConstants {
11 11
12 -  
13 - // ========== 促销活动相关 1003001000============ 12 + // ========== 促销活动相关 1003001000 ============ TODO 芋艿:看看是不是要删除掉
14 ErrorCode ACTIVITY_NOT_EXISTS = new ErrorCode(1003001000, "促销活动不存在"); 13 ErrorCode ACTIVITY_NOT_EXISTS = new ErrorCode(1003001000, "促销活动不存在");
15 14
16 - // ========== banner相关 1003002000============  
17 - ErrorCode BANNER_NOT_EXISTS = new ErrorCode(1003002000, "Banner不存在"); 15 + // ========== Banner 相关 1003002000 ============
  16 + ErrorCode BANNER_NOT_EXISTS = new ErrorCode(1003002000, "Banner 不存在");
  17 +
  18 + // ========== Coupon 相关 1003003000 ============
  19 + ErrorCode COUPON_NO_MATCH_SPU = new ErrorCode(1003003000, "优惠劵没有可使用的商品!");
  20 + ErrorCode COUPON_NO_MATCH_MIN_PRICE = new ErrorCode(1003003000, "不满足优惠劵使用的最低金额");
  21 +
18 } 22 }
yudao-module-mall/yudao-module-market-api/src/main/java/cn/iocoder/yudao/module/market/enums/common/PromotionLevelEnum.java
@@ -9,14 +9,17 @@ import java.util.Arrays; @@ -9,14 +9,17 @@ import java.util.Arrays;
9 /** 9 /**
10 * 营销的级别枚举 10 * 营销的级别枚举
11 * 11 *
  12 + * 参考有赞:<a href="https://img01.yzcdn.cn/upload_files/2021/11/02/FhDjUrNDq-G0wjNdYDtgUX09fdGj.png">营销级别</a>
  13 + *
12 * @author 芋道源码 14 * @author 芋道源码
13 */ 15 */
14 @Getter 16 @Getter
15 @AllArgsConstructor 17 @AllArgsConstructor
16 public enum PromotionLevelEnum implements IntArrayValuable { 18 public enum PromotionLevelEnum implements IntArrayValuable {
17 19
18 - ORDER(1, "订单级"), // 多个商品,进行组合后优惠  
19 - SKU(2, "商品级"), // 单个商品,直接优惠 20 + ORDER(1, "订单级"), // 多个商品,进行组合后优惠。例如说:满减送、打包一口价、第二件半价
  21 + SKU(2, "商品级"), // 单个商品,直接优惠。例如说:限时折扣、会员折扣
  22 + COUPON(3, "优惠劵"), // 多个商品,进行组合后优惠。例如说:优惠劵
20 ; 23 ;
21 24
22 public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(PromotionLevelEnum::getLevel).toArray(); 25 public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(PromotionLevelEnum::getLevel).toArray();
yudao-module-mall/yudao-module-market-api/src/main/java/cn/iocoder/yudao/module/market/enums/common/PromotionTypeEnum.java
@@ -19,6 +19,7 @@ public enum PromotionTypeEnum implements IntArrayValuable { @@ -19,6 +19,7 @@ public enum PromotionTypeEnum implements IntArrayValuable {
19 REWARD_ACTIVITY(2, "满减送"), 19 REWARD_ACTIVITY(2, "满减送"),
20 20
21 MEMBER(3, "会员折扣"), 21 MEMBER(3, "会员折扣"),
  22 + COUPON(4, "优惠劵")
22 ; 23 ;
23 24
24 public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(PromotionTypeEnum::getType).toArray(); 25 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/dal/dataobject/coupon/CouponDO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.market.dal.dataobject.coupon;
  2 +
  3 +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import cn.iocoder.yudao.module.market.enums.common.PromotionProductScopeEnum;
  5 +import com.baomidou.mybatisplus.annotation.TableField;
  6 +import com.baomidou.mybatisplus.annotation.TableName;
  7 +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
  8 +import lombok.Data;
  9 +import lombok.EqualsAndHashCode;
  10 +import lombok.experimental.Accessors;
  11 +
  12 +import java.util.Date;
  13 +import java.util.List;
  14 +
  15 +// TODO 芋艿:待完善该实体
  16 +/**
  17 + * 优惠劵 DO
  18 + */
  19 +@TableName("coupon")
  20 +@Data
  21 +@EqualsAndHashCode(callSuper = true)
  22 +@Accessors(chain = true)
  23 +public class CouponDO extends BaseDO {
  24 +
  25 + // ========== 基本信息 BEGIN ==========
  26 + /**
  27 + * 优惠劵编号
  28 + */
  29 + private Long id;
  30 + /**
  31 + * 优惠劵(码)分组编号,{@link CouponTemplateDO} 的 id
  32 + */
  33 + private Integer templateId;
  34 + /**
  35 + * 优惠劵名
  36 + *
  37 + * 冗余自 {@link CouponTemplateDO} 的 title
  38 + *
  39 + * TODO 芋艿,暂时不考虑冗余的更新
  40 + */
  41 + private String title;
  42 +// /**
  43 +// * 核销码
  44 +// */
  45 +// private String verifyCode;
  46 + /**
  47 + * 优惠码状态
  48 + *
  49 + * 1-未使用
  50 + * 2-已使用
  51 + * 3-已失效
  52 + */
  53 + private Integer status;
  54 +
  55 + // ========== 基本信息 END ==========
  56 +
  57 + // ========== 领取情况 BEGIN ==========
  58 + /**
  59 + * 用户编号
  60 + */
  61 + private Integer userId;
  62 + /**
  63 + * 领取类型
  64 + *
  65 + * 1 - 用户主动领取
  66 + * 2 - 后台自动发放
  67 + */
  68 + private Integer takeType;
  69 + // ========== 领取情况 END ==========
  70 +
  71 + // ========== 使用规则 BEGIN ==========
  72 + /**
  73 + * 是否设置满多少金额可用,单位:分
  74 + */
  75 + private Integer priceAvailable;
  76 + /**
  77 + * 生效开始时间
  78 + */
  79 + private Date validStartTime;
  80 + /**
  81 + * 生效结束时间
  82 + */
  83 + private Date validEndTime;
  84 + /**
  85 + * 商品范围
  86 + *
  87 + * 枚举 {@link PromotionProductScopeEnum}
  88 + */
  89 + private Integer productScope;
  90 + /**
  91 + * 商品 SPU 编号的数组
  92 + */
  93 + @TableField(typeHandler = JacksonTypeHandler.class)
  94 + private List<Long> spuIds;
  95 + // ========== 使用规则 END ==========
  96 +
  97 + // ========== 使用效果 BEGIN ==========
  98 + /**
  99 + * 优惠类型
  100 + *
  101 + * 1-代金卷
  102 + * 2-折扣卷
  103 + */
  104 + private Integer preferentialType;
  105 + /**
  106 + * 折扣
  107 + */
  108 + private Integer percentOff;
  109 + /**
  110 + * 优惠金额,单位:分。
  111 + */
  112 + private Integer priceOff;
  113 + /**
  114 + * 折扣上限,仅在 {@link #preferentialType} 等于 2 时生效。
  115 + *
  116 + * 例如,折扣上限为 20 元,当使用 8 折优惠券,订单金额为 1000 元时,最高只可折扣 20 元,而非 80 元。
  117 + */
  118 + private Integer discountPriceLimit;
  119 + // ========== 使用效果 END ==========
  120 +
  121 + // ========== 使用情况 BEGIN ==========
  122 +// /**
  123 +// * 使用订单号
  124 +// */
  125 +// private Integer usedOrderId; // TODO 芋艿,暂时不考虑这个字段
  126 +// /**
  127 +// * 订单中优惠面值,单位:分
  128 +// */
  129 +// private Integer usedPrice; // TODO 芋艿,暂时不考虑这个字段
  130 + /**
  131 + * 使用时间
  132 + */
  133 + private Date usedTime;
  134 +
  135 + // TODO 芋艿,后续要加优惠劵的使用日志,因为下单后,可能会取消。
  136 +
  137 + // ========== 使用情况 END ==========
  138 +
  139 +}
yudao-module-mall/yudao-module-market-biz/src/main/java/cn/iocoder/yudao/module/market/service/banner/BannerServiceImpl.java
@@ -17,7 +17,7 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU @@ -17,7 +17,7 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
17 import static cn.iocoder.yudao.module.market.enums.ErrorCodeConstants.BANNER_NOT_EXISTS; 17 import static cn.iocoder.yudao.module.market.enums.ErrorCodeConstants.BANNER_NOT_EXISTS;
18 18
19 /** 19 /**
20 - * 首页banner 实现类 20 + * 首页 banner 实现类
21 * 21 *
22 * @author xia 22 * @author xia
23 */ 23 */
yudao-module-mall/yudao-module-market-biz/src/main/java/cn/iocoder/yudao/module/market/service/coupon/CouponService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.market.service.coupon;
  2 +
  3 +import cn.iocoder.yudao.module.market.dal.dataobject.coupon.CouponDO;
  4 +
  5 +/**
  6 + * 优惠劵 Service 接口
  7 + *
  8 + * @author 芋道源码
  9 + */
  10 +public interface CouponService {
  11 +
  12 + /**
  13 + * 校验优惠劵,包括状态、有限期
  14 + *
  15 + * 1. 如果校验通过,则返回优惠劵信息
  16 + * 2. 如果校验不通过,则直接抛出业务异常
  17 + *
  18 + * @param id 优惠劵编号
  19 + * @param userId 用户编号
  20 + * @return 优惠劵信息
  21 + */
  22 + CouponDO validCoupon(Long id, Long userId);
  23 +
  24 +}
yudao-module-mall/yudao-module-market-biz/src/main/java/cn/iocoder/yudao/module/market/service/coupon/CouponServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.market.service.coupon;
  2 +
  3 +import cn.iocoder.yudao.module.market.dal.dataobject.coupon.CouponDO;
  4 +import org.springframework.stereotype.Service;
  5 +import org.springframework.validation.annotation.Validated;
  6 +
  7 +/**
  8 + * 优惠劵 Service 实现类
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +@Service
  13 +@Validated
  14 +public class CouponServiceImpl implements CouponService {
  15 +
  16 + // TODO 芋艿:待实现
  17 + @Override
  18 + public CouponDO validCoupon(Long id, Long userId) {
  19 + return null;
  20 + }
  21 +}
yudao-module-mall/yudao-module-market-biz/src/main/java/cn/iocoder/yudao/module/market/service/price/PriceServiceImpl.java
@@ -7,11 +7,14 @@ import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; @@ -7,11 +7,14 @@ import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
7 import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateReqDTO; 7 import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateReqDTO;
8 import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateRespDTO; 8 import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateRespDTO;
9 import cn.iocoder.yudao.module.market.convert.price.PriceConvert; 9 import cn.iocoder.yudao.module.market.convert.price.PriceConvert;
  10 +import cn.iocoder.yudao.module.market.dal.dataobject.coupon.CouponDO;
10 import cn.iocoder.yudao.module.market.dal.dataobject.discount.DiscountProductDO; 11 import cn.iocoder.yudao.module.market.dal.dataobject.discount.DiscountProductDO;
11 import cn.iocoder.yudao.module.market.dal.dataobject.reward.RewardActivityDO; 12 import cn.iocoder.yudao.module.market.dal.dataobject.reward.RewardActivityDO;
12 import cn.iocoder.yudao.module.market.enums.common.PromotionConditionTypeEnum; 13 import cn.iocoder.yudao.module.market.enums.common.PromotionConditionTypeEnum;
13 import cn.iocoder.yudao.module.market.enums.common.PromotionLevelEnum; 14 import cn.iocoder.yudao.module.market.enums.common.PromotionLevelEnum;
  15 +import cn.iocoder.yudao.module.market.enums.common.PromotionProductScopeEnum;
14 import cn.iocoder.yudao.module.market.enums.common.PromotionTypeEnum; 16 import cn.iocoder.yudao.module.market.enums.common.PromotionTypeEnum;
  17 +import cn.iocoder.yudao.module.market.service.coupon.CouponService;
15 import cn.iocoder.yudao.module.market.service.discount.DiscountService; 18 import cn.iocoder.yudao.module.market.service.discount.DiscountService;
16 import cn.iocoder.yudao.module.market.service.reward.RewardService; 19 import cn.iocoder.yudao.module.market.service.reward.RewardService;
17 import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi; 20 import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
@@ -30,6 +33,8 @@ import java.util.function.Supplier; @@ -30,6 +33,8 @@ import java.util.function.Supplier;
30 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 33 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
31 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; 34 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
32 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getSumValue; 35 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getSumValue;
  36 +import static cn.iocoder.yudao.module.market.enums.ErrorCodeConstants.COUPON_NO_MATCH_MIN_PRICE;
  37 +import static cn.iocoder.yudao.module.market.enums.ErrorCodeConstants.COUPON_NO_MATCH_SPU;
33 import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS; 38 import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS;
34 import static java.util.Collections.singletonList; 39 import static java.util.Collections.singletonList;
35 40
@@ -43,7 +48,7 @@ import static java.util.Collections.singletonList; @@ -43,7 +48,7 @@ import static java.util.Collections.singletonList;
43 * TODO 芋艿:进一步完善 48 * TODO 芋艿:进一步完善
44 * 1. 限时折扣:指定金额、减免金额、折扣 49 * 1. 限时折扣:指定金额、减免金额、折扣
45 * 2. 满减送:循环、折扣 50 * 2. 满减送:循环、折扣
46 - * 3. 51 + * 3. 优惠劵:待定
47 * 52 *
48 * @author 芋道源码 53 * @author 芋道源码
49 */ 54 */
@@ -55,6 +60,8 @@ public class PriceServiceImpl implements PriceService { @@ -55,6 +60,8 @@ public class PriceServiceImpl implements PriceService {
55 private DiscountService discountService; 60 private DiscountService discountService;
56 @Resource 61 @Resource
57 private RewardService rewardService; 62 private RewardService rewardService;
  63 + @Resource
  64 + private CouponService couponService;
58 65
59 @Resource 66 @Resource
60 private ProductSkuApi productSkuApi; 67 private ProductSkuApi productSkuApi;
@@ -70,6 +77,8 @@ public class PriceServiceImpl implements PriceService { @@ -70,6 +77,8 @@ public class PriceServiceImpl implements PriceService {
70 calculatePriceForSkuLevel(calculateReqDTO.getUserId(), priceCalculate); 77 calculatePriceForSkuLevel(calculateReqDTO.getUserId(), priceCalculate);
71 // 计算订单级别的价格 78 // 计算订单级别的价格
72 calculatePriceForOrderLevel(calculateReqDTO.getUserId(), priceCalculate); 79 calculatePriceForOrderLevel(calculateReqDTO.getUserId(), priceCalculate);
  80 + // 计算优惠劵级别的价格
  81 + calculatePriceForCouponLevel(calculateReqDTO.getUserId(), calculateReqDTO.getCouponId(), priceCalculate);
73 // 计算【优惠劵】促销 TODO 待实现 82 // 计算【优惠劵】促销 TODO 待实现
74 return priceCalculate; 83 return priceCalculate;
75 } 84 }
@@ -228,9 +237,9 @@ public class PriceServiceImpl implements PriceService { @@ -228,9 +237,9 @@ public class PriceServiceImpl implements PriceService {
228 */ 237 */
229 private RewardActivityDO.Rule getLastMatchRewardActivityRule(RewardActivityDO rewardActivity, 238 private RewardActivityDO.Rule getLastMatchRewardActivityRule(RewardActivityDO rewardActivity,
230 List<PriceCalculateRespDTO.OrderItem> orderItems) { 239 List<PriceCalculateRespDTO.OrderItem> orderItems) {
231 - Integer count = CollectionUtils.getSumValue(orderItems, PriceCalculateRespDTO.OrderItem::getCount, Integer::sum); 240 + Integer count = getSumValue(orderItems, PriceCalculateRespDTO.OrderItem::getCount, Integer::sum);
232 // price 的计算逻辑,使用 orderDividePrice 的原因,主要考虑分摊后,这个才是该 SKU 当前真实的支付总价 241 // price 的计算逻辑,使用 orderDividePrice 的原因,主要考虑分摊后,这个才是该 SKU 当前真实的支付总价
233 - Integer price = CollectionUtils.getSumValue(orderItems, PriceCalculateRespDTO.OrderItem::getOrderDividePrice, Integer::sum); 242 + Integer price = getSumValue(orderItems, PriceCalculateRespDTO.OrderItem::getOrderDividePrice, Integer::sum);
234 assert count != null && price != null; 243 assert count != null && price != null;
235 for (int i = rewardActivity.getRules().size() - 1; i >= 0; i--) { 244 for (int i = rewardActivity.getRules().size() - 1; i >= 0; i--) {
236 RewardActivityDO.Rule rule = rewardActivity.getRules().get(i); 245 RewardActivityDO.Rule rule = rewardActivity.getRules().get(i);
@@ -256,6 +265,65 @@ public class PriceServiceImpl implements PriceService { @@ -256,6 +265,65 @@ public class PriceServiceImpl implements PriceService {
256 return "TODO"; // TODO 芋艿:后面再想想 265 return "TODO"; // TODO 芋艿:后面再想想
257 } 266 }
258 267
  268 + // ========== 计算优惠劵级别的价格 ==========
  269 +
  270 + private void calculatePriceForCouponLevel(Long userId, Long couponId, PriceCalculateRespDTO priceCalculate) {
  271 + // 校验优惠劵
  272 + if (couponId == null) {
  273 + return;
  274 + }
  275 + CouponDO coupon = couponService.validCoupon(couponId, userId);
  276 +
  277 + // 获得匹配的商品 SKU 数组
  278 + List<PriceCalculateRespDTO.OrderItem> orderItems = getMatchCouponOrderItems(priceCalculate, coupon);
  279 + if (CollUtil.isEmpty(orderItems)) {
  280 + throw exception(COUPON_NO_MATCH_SPU);
  281 + }
  282 +
  283 + // 计算是否满足优惠劵的使用金额
  284 + Integer originPrice = getSumValue(orderItems, PriceCalculateRespDTO.OrderItem::getOrderDividePrice, Integer::sum);
  285 + assert originPrice != null;
  286 + if (originPrice < coupon.getPriceAvailable()) {
  287 + throw exception(COUPON_NO_MATCH_MIN_PRICE);
  288 + }
  289 +
  290 + // 计算可以优惠的金额
  291 + priceCalculate.getOrder().setCouponId(couponId);
  292 + Integer couponPrice = getCouponPrice(coupon, originPrice);
  293 + // 分摊金额
  294 + // TODO 芋艿:limit 不能超过最大价格
  295 + List<Integer> couponPartPrices = dividePrice(orderItems, couponPrice);
  296 + // 记录优惠明细
  297 + addPromotion(priceCalculate, orderItems, coupon.getId(), coupon.getTitle(),
  298 + PromotionTypeEnum.COUPON.getType(), PromotionLevelEnum.COUPON.getLevel(), couponPartPrices,
  299 + true, StrUtil.format("优惠劵:省 {} 元", formatPrice(couponPrice)));
  300 + // 修改 SKU 的分摊
  301 + for (int i = 0; i < orderItems.size(); i++) {
  302 + modifyOrderItemOrderPartPriceFromCouponPrice(orderItems.get(i), couponPartPrices.get(i), priceCalculate);
  303 + }
  304 + }
  305 +
  306 + private List<PriceCalculateRespDTO.OrderItem> getMatchCouponOrderItems(PriceCalculateRespDTO priceCalculate,
  307 + CouponDO coupon) {
  308 + if (PromotionProductScopeEnum.ALL.getScope().equals(coupon.getProductScope())) {
  309 + return priceCalculate.getOrder().getItems();
  310 + }
  311 + return CollectionUtils.filterList(priceCalculate.getOrder().getItems(),
  312 + orderItem -> coupon.getSpuIds().contains(orderItem.getSpuId()));
  313 + }
  314 +
  315 + private Integer getCouponPrice(CouponDO coupon, Integer originPrice) {
  316 + // TODO 芋艿 getPreferentialType 的枚举判断
  317 + if (coupon.getPreferentialType().equals(1)) { // 减价
  318 + return coupon.getPriceOff();
  319 + } else if (coupon.getPreferentialType().equals(2)) { // 打折
  320 + Integer couponPrice = originPrice * coupon.getPercentOff() / 100;
  321 + return coupon.getDiscountPriceLimit() == null ? couponPrice
  322 + : Math.min(couponPrice, coupon.getDiscountPriceLimit()); // 优惠上限
  323 + }
  324 + throw new IllegalArgumentException(String.format("优惠劵(%s) 的优惠类型不正确", coupon.toString()));
  325 + }
  326 +
259 // ========== 其它相对通用的方法 ========== 327 // ========== 其它相对通用的方法 ==========
260 328
261 /** 329 /**
@@ -325,7 +393,7 @@ public class PriceServiceImpl implements PriceService { @@ -325,7 +393,7 @@ public class PriceServiceImpl implements PriceService {
325 orderItem -> new PriceCalculateRespDTO.PromotionItem().setSkuId(orderItem.getSkuId()) 393 orderItem -> new PriceCalculateRespDTO.PromotionItem().setSkuId(orderItem.getSkuId())
326 .setOriginalPrice(orderItem.getOrderDividePrice()).setDiscountPrice(0)); 394 .setOriginalPrice(orderItem.getOrderDividePrice()).setDiscountPrice(0));
327 // 创建营销明细 395 // 创建营销明细
328 - Integer originalPrice = CollectionUtils.getSumValue(orderItems, PriceCalculateRespDTO.OrderItem::getOrderDividePrice, Integer::sum); 396 + Integer originalPrice = getSumValue(orderItems, PriceCalculateRespDTO.OrderItem::getOrderDividePrice, Integer::sum);
329 PriceCalculateRespDTO.Promotion promotion = new PriceCalculateRespDTO.Promotion() 397 PriceCalculateRespDTO.Promotion promotion = new PriceCalculateRespDTO.Promotion()
330 .setId(id).setName(name).setType(type).setLevel(level) 398 .setId(id).setName(name).setType(type).setLevel(level)
331 .setOriginalPrice(originalPrice).setDiscountPrice(0) 399 .setOriginalPrice(originalPrice).setDiscountPrice(0)
@@ -357,7 +425,7 @@ public class PriceServiceImpl implements PriceService { @@ -357,7 +425,7 @@ public class PriceServiceImpl implements PriceService {
357 * 本质:分摊 Order 的 discountPrice 价格,到对应的 OrderItem 的 orderPartPrice 价格中 425 * 本质:分摊 Order 的 discountPrice 价格,到对应的 OrderItem 的 orderPartPrice 价格中
358 * 426 *
359 * @param orderItem 订单商品 SKU 427 * @param orderItem 订单商品 SKU
360 - * @param addOrderPartPrice 新增的 428 + * @param addOrderPartPrice 新增的 discountPrice 价格
361 * @param priceCalculate 价格计算结果 429 * @param priceCalculate 价格计算结果
362 */ 430 */
363 private void modifyOrderItemOrderPartPriceFromDiscountPrice(PriceCalculateRespDTO.OrderItem orderItem, Integer addOrderPartPrice, 431 private void modifyOrderItemOrderPartPriceFromDiscountPrice(PriceCalculateRespDTO.OrderItem orderItem, Integer addOrderPartPrice,
@@ -371,6 +439,26 @@ public class PriceServiceImpl implements PriceService { @@ -371,6 +439,26 @@ public class PriceServiceImpl implements PriceService {
371 order.setPayPrice(order.getPayPrice() - addOrderPartPrice); 439 order.setPayPrice(order.getPayPrice() - addOrderPartPrice);
372 } 440 }
373 441
  442 + /**
  443 + * 修改 OrderItem 的 orderPartPrice 价格,同时会修改 Order 的 couponPrice 价格
  444 + *
  445 + * 本质:分摊 Order 的 couponPrice 价格,到对应的 OrderItem 的 orderPartPrice 价格中
  446 + *
  447 + * @param orderItem 订单商品 SKU
  448 + * @param addOrderPartPrice 新增的 couponPrice 价格
  449 + * @param priceCalculate 价格计算结果
  450 + */
  451 + private void modifyOrderItemOrderPartPriceFromCouponPrice(PriceCalculateRespDTO.OrderItem orderItem, Integer addOrderPartPrice,
  452 + PriceCalculateRespDTO priceCalculate) {
  453 + // 设置 OrderItem 价格相关字段
  454 + orderItem.setOrderPartPrice(orderItem.getOrderPartPrice() + addOrderPartPrice);
  455 + orderItem.setOrderDividePrice(orderItem.getPayPrice() - orderItem.getOrderPartPrice());
  456 + // 设置 Order 相关相关字段
  457 + PriceCalculateRespDTO.Order order = priceCalculate.getOrder();
  458 + order.setCouponPrice(order.getCouponPrice() + addOrderPartPrice);
  459 + order.setPayPrice(order.getPayPrice() - addOrderPartPrice);
  460 + }
  461 +
374 private List<Integer> dividePrice(List<PriceCalculateRespDTO.OrderItem> orderItems, Integer price) { 462 private List<Integer> dividePrice(List<PriceCalculateRespDTO.OrderItem> orderItems, Integer price) {
375 List<Integer> prices = new ArrayList<>(orderItems.size()); 463 List<Integer> prices = new ArrayList<>(orderItems.size());
376 Integer total = getSumValue(orderItems, PriceCalculateRespDTO.OrderItem::getOrderDividePrice, Integer::sum); 464 Integer total = getSumValue(orderItems, PriceCalculateRespDTO.OrderItem::getOrderDividePrice, Integer::sum);
yudao-module-mall/yudao-module-market-biz/src/test/java/cn/iocoder/yudao/module/market/service/price/PriceServiceTest.java
@@ -4,11 +4,14 @@ import cn.hutool.core.map.MapUtil; @@ -4,11 +4,14 @@ import cn.hutool.core.map.MapUtil;
4 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; 4 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
5 import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateReqDTO; 5 import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateReqDTO;
6 import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateRespDTO; 6 import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateRespDTO;
  7 +import cn.iocoder.yudao.module.market.dal.dataobject.coupon.CouponDO;
7 import cn.iocoder.yudao.module.market.dal.dataobject.discount.DiscountProductDO; 8 import cn.iocoder.yudao.module.market.dal.dataobject.discount.DiscountProductDO;
8 import cn.iocoder.yudao.module.market.dal.dataobject.reward.RewardActivityDO; 9 import cn.iocoder.yudao.module.market.dal.dataobject.reward.RewardActivityDO;
9 import cn.iocoder.yudao.module.market.enums.common.PromotionConditionTypeEnum; 10 import cn.iocoder.yudao.module.market.enums.common.PromotionConditionTypeEnum;
10 import cn.iocoder.yudao.module.market.enums.common.PromotionLevelEnum; 11 import cn.iocoder.yudao.module.market.enums.common.PromotionLevelEnum;
  12 +import cn.iocoder.yudao.module.market.enums.common.PromotionProductScopeEnum;
11 import cn.iocoder.yudao.module.market.enums.common.PromotionTypeEnum; 13 import cn.iocoder.yudao.module.market.enums.common.PromotionTypeEnum;
  14 +import cn.iocoder.yudao.module.market.service.coupon.CouponService;
12 import cn.iocoder.yudao.module.market.service.discount.DiscountService; 15 import cn.iocoder.yudao.module.market.service.discount.DiscountService;
13 import cn.iocoder.yudao.module.market.service.reward.RewardService; 16 import cn.iocoder.yudao.module.market.service.reward.RewardService;
14 import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi; 17 import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
@@ -45,6 +48,8 @@ public class PriceServiceTest extends BaseMockitoUnitTest { @@ -45,6 +48,8 @@ public class PriceServiceTest extends BaseMockitoUnitTest {
45 @Mock 48 @Mock
46 private RewardService rewardService; 49 private RewardService rewardService;
47 @Mock 50 @Mock
  51 + private CouponService couponService;
  52 + @Mock
48 private ProductSkuApi productSkuApi; 53 private ProductSkuApi productSkuApi;
49 54
50 @Test 55 @Test
@@ -354,4 +359,88 @@ public class PriceServiceTest extends BaseMockitoUnitTest { @@ -354,4 +359,88 @@ public class PriceServiceTest extends BaseMockitoUnitTest {
354 assertEquals(promotionItem012.getDiscountPrice(), 0); 359 assertEquals(promotionItem012.getDiscountPrice(), 0);
355 } 360 }
356 361
  362 + @Test
  363 + public void testCalculatePrice_coupon() {
  364 + // 准备参数
  365 + PriceCalculateReqDTO calculateReqDTO = new PriceCalculateReqDTO().setUserId(randomLongId())
  366 + .setItems(asList(new PriceCalculateReqDTO.Item().setSkuId(10L).setCount(2),
  367 + new PriceCalculateReqDTO.Item().setSkuId(20L).setCount(3),
  368 + new PriceCalculateReqDTO.Item().setSkuId(30L).setCount(4)))
  369 + .setCouponId(1024L);
  370 + // mock 方法(商品 SKU 信息)
  371 + ProductSkuRespDTO productSku01 = randomPojo(ProductSkuRespDTO.class, o -> o.setId(10L).setPrice(100).setSpuId(1L));
  372 + ProductSkuRespDTO productSku02 = randomPojo(ProductSkuRespDTO.class, o -> o.setId(20L).setPrice(50).setSpuId(2L));
  373 + ProductSkuRespDTO productSku03 = randomPojo(ProductSkuRespDTO.class, o -> o.setId(30L).setPrice(30).setSpuId(3L));
  374 + when(productSkuApi.getSkuList(eq(asSet(10L, 20L, 30L)))).thenReturn(asList(productSku01, productSku02, productSku03));
  375 + // mock 方法(优惠劵 Coupon 信息)
  376 + CouponDO coupon = randomPojo(CouponDO.class, o -> o.setId(1024L).setTitle("程序员节")
  377 + .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setSpuIds(asList(1L, 2L))
  378 + .setPriceAvailable(350).setPreferentialType(2).setPercentOff(50).setDiscountPriceLimit(70));
  379 + when(couponService.validCoupon(eq(1024L), eq(calculateReqDTO.getUserId()))).thenReturn(coupon);
  380 +
  381 + // 200 + 150; 350
  382 + //
  383 +
  384 + // 调用
  385 + PriceCalculateRespDTO priceCalculate = priceService.calculatePrice(calculateReqDTO);
  386 + // 断言 Order 部分
  387 + PriceCalculateRespDTO.Order order = priceCalculate.getOrder();
  388 + assertEquals(order.getOriginalPrice(), 470);
  389 + assertEquals(order.getDiscountPrice(), 0);
  390 + assertEquals(order.getPointPrice(), 0);
  391 + assertEquals(order.getDeliveryPrice(), 0);
  392 + assertEquals(order.getPayPrice(), 400);
  393 + assertEquals(order.getCouponId(), 1024L);
  394 + assertEquals(order.getCouponPrice(), 70);
  395 + // 断言 OrderItem 部分
  396 + assertEquals(order.getItems().size(), 3);
  397 + PriceCalculateRespDTO.OrderItem orderItem01 = order.getItems().get(0);
  398 + assertEquals(orderItem01.getSkuId(), 10L);
  399 + assertEquals(orderItem01.getCount(), 2);
  400 + assertEquals(orderItem01.getOriginalPrice(), 200);
  401 + assertEquals(orderItem01.getOriginalUnitPrice(), 100);
  402 + assertEquals(orderItem01.getDiscountPrice(), 0);
  403 + assertEquals(orderItem01.getPayPrice(), 200);
  404 + assertEquals(orderItem01.getOrderPartPrice(), 40);
  405 + assertEquals(orderItem01.getOrderDividePrice(), 160);
  406 + PriceCalculateRespDTO.OrderItem orderItem02 = order.getItems().get(1);
  407 + assertEquals(orderItem02.getSkuId(), 20L);
  408 + assertEquals(orderItem02.getCount(), 3);
  409 + assertEquals(orderItem02.getOriginalPrice(), 150);
  410 + assertEquals(orderItem02.getOriginalUnitPrice(), 50);
  411 + assertEquals(orderItem02.getDiscountPrice(), 0);
  412 + assertEquals(orderItem02.getPayPrice(), 150);
  413 + assertEquals(orderItem02.getOrderPartPrice(), 30);
  414 + assertEquals(orderItem02.getOrderDividePrice(), 120);
  415 + PriceCalculateRespDTO.OrderItem orderItem03 = order.getItems().get(2);
  416 + assertEquals(orderItem03.getSkuId(), 30L);
  417 + assertEquals(orderItem03.getCount(), 4);
  418 + assertEquals(orderItem03.getOriginalPrice(), 120);
  419 + assertEquals(orderItem03.getOriginalUnitPrice(), 30);
  420 + assertEquals(orderItem03.getDiscountPrice(), 0);
  421 + assertEquals(orderItem03.getPayPrice(), 120);
  422 + assertEquals(orderItem03.getOrderPartPrice(), 0);
  423 + assertEquals(orderItem03.getOrderDividePrice(), 120);
  424 + // 断言 Promotion 部分
  425 + assertEquals(priceCalculate.getPromotions().size(), 1);
  426 + PriceCalculateRespDTO.Promotion promotion01 = priceCalculate.getPromotions().get(0);
  427 + assertEquals(promotion01.getId(), 1024L);
  428 + assertEquals(promotion01.getName(), "程序员节");
  429 + assertEquals(promotion01.getType(), PromotionTypeEnum.COUPON.getType());
  430 + assertEquals(promotion01.getLevel(), PromotionLevelEnum.COUPON.getLevel());
  431 + assertEquals(promotion01.getOriginalPrice(), 350);
  432 + assertEquals(promotion01.getDiscountPrice(), 70);
  433 + assertTrue(promotion01.getMeet());
  434 + assertEquals(promotion01.getMeetTip(), "优惠劵:省 0.70 元");
  435 + assertEquals(promotion01.getItems().size(), 2);
  436 + PriceCalculateRespDTO.PromotionItem promotionItem011 = promotion01.getItems().get(0);
  437 + assertEquals(promotionItem011.getSkuId(), 10L);
  438 + assertEquals(promotionItem011.getOriginalPrice(), 200);
  439 + assertEquals(promotionItem011.getDiscountPrice(), 40);
  440 + PriceCalculateRespDTO.PromotionItem promotionItem012 = promotion01.getItems().get(1);
  441 + assertEquals(promotionItem012.getSkuId(), 20L);
  442 + assertEquals(promotionItem012.getOriginalPrice(), 150);
  443 + assertEquals(promotionItem012.getDiscountPrice(), 30);
  444 + }
  445 +
357 } 446 }
yudao-server/pom.xml
@@ -56,11 +56,6 @@ @@ -56,11 +56,6 @@
56 <artifactId>yudao-module-trade-biz</artifactId> 56 <artifactId>yudao-module-trade-biz</artifactId>
57 <version>${revision}</version> 57 <version>${revision}</version>
58 </dependency> 58 </dependency>
59 - <dependency>  
60 - <groupId>cn.iocoder.boot</groupId>  
61 - <artifactId>yudao-module-coupon-biz</artifactId>  
62 - <version>${revision}</version>  
63 - </dependency>  
64 <!-- 数据报表 --> 59 <!-- 数据报表 -->
65 <!-- <dependency>--> 60 <!-- <dependency>-->
66 <!-- <groupId>cn.iocoder.boot</groupId>--> 61 <!-- <groupId>cn.iocoder.boot</groupId>-->