From ef807be89ce128d4920632d1de92f6ee5f40351a Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 4 May 2026 18:18:26 +0800 Subject: [PATCH] 🐞 fix(trade): 修复价格计算 3 处缺陷并补齐单测 --- yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/delivery/DeliveryExpressTemplateFreeDO.java | 4 ++-- yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/bo/DeliveryExpressTemplateRespBO.java | 4 ++-- yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeBargainActivityPriceCalculator.java | 7 +++---- yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java | 2 +- yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java | 3 ++- yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/price/TradePriceServiceImplTest.java | 8 ++------ yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeCouponPriceCalculatorTest.java | 4 +--- yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculatorTest.java | 342 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------------------------------------- yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDiscountActivityPriceCalculatorTest.java | 14 +++++++++----- yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointUsePriceCalculatorTest.java | 7 ++++--- yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculatorTest.java | 378 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 11 files changed, 481 insertions(+), 292 deletions(-) diff --git a/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/delivery/DeliveryExpressTemplateFreeDO.java b/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/delivery/DeliveryExpressTemplateFreeDO.java index 07eef1b..8bf035b 100644 --- a/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/delivery/DeliveryExpressTemplateFreeDO.java +++ b/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/delivery/DeliveryExpressTemplateFreeDO.java @@ -43,14 +43,14 @@ public class DeliveryExpressTemplateFreeDO extends BaseDO { /** * 包邮金额,单位:分 * - * 订单总金额 > 包邮金额时,才免运费 + * 订单总金额 >= 包邮金额时,才免运费 */ private Integer freePrice; /** * 包邮件数 * - * 订单总件数 > 包邮件数时,才免运费 + * 订单总件数 >= 包邮件数时,才免运费 */ private Integer freeCount; diff --git a/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/bo/DeliveryExpressTemplateRespBO.java b/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/bo/DeliveryExpressTemplateRespBO.java index db0af04..212efa5 100644 --- a/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/bo/DeliveryExpressTemplateRespBO.java +++ b/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/bo/DeliveryExpressTemplateRespBO.java @@ -65,14 +65,14 @@ public class DeliveryExpressTemplateRespBO { /** * 包邮金额,单位:分 * - * 订单总金额 > 包邮金额时,才免运费 + * 订单总金额 >= 包邮金额时,才免运费 */ private Integer freePrice; /** * 包邮件数 * - * 订单总件数 > 包邮件数时,才免运费 + * 订单总件数 >= 包邮件数时,才免运费 */ private Integer freeCount; } diff --git a/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeBargainActivityPriceCalculator.java b/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeBargainActivityPriceCalculator.java index ecc4e63..204332c 100644 --- a/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeBargainActivityPriceCalculator.java +++ b/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeBargainActivityPriceCalculator.java @@ -14,7 +14,6 @@ import org.springframework.stereotype.Component; import jakarta.annotation.Resource; -// TODO huihui:单测需要补充 /** * 砍价活动的 {@link TradePriceCalculator} 实现类 * @@ -29,12 +28,12 @@ public class TradeBargainActivityPriceCalculator implements TradePriceCalculator @Override public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) { - // 1. 判断订单类型和是否具有拼团记录编号 + // 1. 判断订单类型和是否具有砍价记录编号 if (ObjectUtil.notEqual(result.getType(), TradeOrderTypeEnum.BARGAIN.getType())) { return; } Assert.isTrue(param.getItems().size() == 1, "砍价时,只允许选择一个商品"); - Assert.isTrue(param.getItems().get(0).getCount() == 1, "砍价时,只允许选择一个商品"); + Assert.isTrue(param.getItems().get(0).getCount() == 1, "砍价时,商品数量只允许为 1"); // 2. 校验是否可以参与砍价 TradePriceCalculateRespBO.OrderItem orderItem = result.getItems().get(0); BargainValidateJoinRespDTO bargainActivity = bargainRecordApi.validateJoinBargain( @@ -44,7 +43,7 @@ public class TradeBargainActivityPriceCalculator implements TradePriceCalculator Integer discountPrice = orderItem.getPayPrice() - bargainActivity.getBargainPrice() * orderItem.getCount(); // TODO 芋艿:极端情况,优惠金额为负数,需要处理 TradePriceCalculatorHelper.addPromotion(result, orderItem, - param.getSeckillActivityId(), bargainActivity.getName(), PromotionTypeEnum.BARGAIN_ACTIVITY.getType(), + bargainActivity.getActivityId(), bargainActivity.getName(), PromotionTypeEnum.BARGAIN_ACTIVITY.getType(), StrUtil.format("砍价活动:省 {} 元", TradePriceCalculatorHelper.formatPrice(discountPrice)), discountPrice); // 3.2 更新 SKU 优惠金额 diff --git a/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java b/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java index b45c22d..cb59203 100644 --- a/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java +++ b/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java @@ -213,7 +213,7 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { } double totalChargeValue = getTotalChargeValue(orderItems, chargeMode); double totalPrice = TradePriceCalculatorHelper.calculateTotalPayPrice(orderItems); - return totalChargeValue <= templateFree.getFreeCount() && totalPrice >= templateFree.getFreePrice(); + return totalChargeValue >= templateFree.getFreeCount() && totalPrice >= templateFree.getFreePrice(); } private double getTotalChargeValue(List orderItems, Integer chargeMode) { diff --git a/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java b/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java index 6585c9d..b74c8ba 100644 --- a/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java +++ b/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.trade.service.price.calculator; import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO; @@ -61,7 +62,7 @@ public class TradePriceCalculatorHelper { // spu 信息 orderItem.setSpuName(spu.getName()).setCategoryId(spu.getCategoryId()) .setDeliveryTypes(spu.getDeliveryTypes()).setDeliveryTemplateId(spu.getDeliveryTemplateId()) - .setGivePoint(spu.getGiveIntegral()).setUsePoint(0); + .setGivePoint(ObjectUtil.defaultIfNull(spu.getGiveIntegral(), 0)).setUsePoint(0); if (StrUtil.isBlank(orderItem.getPicUrl())) { orderItem.setPicUrl(spu.getPicUrl()); } diff --git a/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/price/TradePriceServiceImplTest.java b/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/price/TradePriceServiceImplTest.java index 993a435..a997e64 100644 --- a/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/price/TradePriceServiceImplTest.java +++ b/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/price/TradePriceServiceImplTest.java @@ -6,12 +6,10 @@ import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi; import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO; import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi; import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; -import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum; import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; import cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculator; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; @@ -30,7 +28,6 @@ import static org.mockito.Mockito.when; * * @author 芋道源码 */ -@Disabled // TODO 芋艿:后续 fix 补充的单测 public class TradePriceServiceImplTest extends BaseMockitoUnitTest { @InjectMocks @@ -67,9 +64,8 @@ public class TradePriceServiceImplTest extends BaseMockitoUnitTest { .setValueId(4L).setValueName("黑色"))) ); when(productSkuApi.getSkuList(Mockito.eq(asSet(100L, 200L, 300L)))).thenReturn(skuList); - when(productSpuApi.getSpuList(Mockito.eq(asSet(1001L)))) - .thenReturn(singletonList(new ProductSpuRespDTO().setId(1001L).setName("小菜").setCategoryId(666L) - .setStatus(ProductSpuStatusEnum.ENABLE.getStatus()))); + when(productSpuApi.validateSpuList(Mockito.eq(asSet(1001L)))) + .thenReturn(singletonList(new ProductSpuRespDTO().setId(1001L).setName("小菜").setCategoryId(666L))); // 调用 TradePriceCalculateRespBO calculateRespBO = tradePriceService.calculateOrderPrice(calculateReqBO); diff --git a/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeCouponPriceCalculatorTest.java b/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeCouponPriceCalculatorTest.java index a5a375c..159a5c2 100644 --- a/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeCouponPriceCalculatorTest.java +++ b/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeCouponPriceCalculatorTest.java @@ -11,7 +11,6 @@ import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum; import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; @@ -32,7 +31,6 @@ import static org.mockito.Mockito.when; * * @author 芋道源码 */ -@Disabled // TODO 芋艿:后续修复 public class TradeCouponPriceCalculatorTest extends BaseMockitoUnitTest { @InjectMocks @@ -75,7 +73,7 @@ public class TradeCouponPriceCalculatorTest extends BaseMockitoUnitTest { .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductScopeValues(asList(1L, 2L)) .setUsePrice(350).setDiscountType(PromotionDiscountTypeEnum.PERCENT.getType()) .setDiscountPercent(50).setDiscountLimitPrice(70)) - .setValidStartTime(addTime(Duration.ofDays(1))).setValidEndTime(addTime(Duration.ofDays(2))); + .setValidStartTime(addTime(Duration.ofDays(-1))).setValidEndTime(addTime(Duration.ofDays(1))); when(couponApi.getCouponListByUserId(eq(233L), eq(CouponStatusEnum.UNUSED.getStatus()))) .thenReturn(ListUtil.toList(coupon)); diff --git a/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculatorTest.java b/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculatorTest.java index 3f64db8..64e7617 100644 --- a/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculatorTest.java +++ b/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculatorTest.java @@ -1,29 +1,37 @@ package cn.iocoder.yudao.module.trade.service.price.calculator; import cn.hutool.core.map.MapUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; import cn.iocoder.yudao.module.member.api.address.MemberAddressApi; import cn.iocoder.yudao.module.member.api.address.dto.MemberAddressRespDTO; import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO; +import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryPickUpStoreDO; import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryExpressChargeModeEnum; import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum; import cn.iocoder.yudao.module.trade.service.config.TradeConfigService; import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressTemplateService; +import cn.iocoder.yudao.module.trade.service.delivery.DeliveryPickUpStoreService; import cn.iocoder.yudao.module.trade.service.delivery.bo.DeliveryExpressTemplateRespBO; import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; import java.util.ArrayList; +import java.util.Collections; import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PICK_UP_STORE_NOT_EXISTS; +import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_DELIVERY_PRICE_TEMPLATE_NOT_FOUND; +import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_DELIVERY_PRICE_TYPE_ILLEGAL; import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; @@ -33,15 +41,15 @@ import static org.mockito.Mockito.when; * * @author jason */ -@Disabled // TODO 芋艿:后续修复 -public class TradeDeliveryPriceCalculatorTest extends BaseMockitoUnitTest { +public class TradeDeliveryPriceCalculatorTest extends BaseMockitoUnitTest { @InjectMocks private TradeDeliveryPriceCalculator calculator; @Mock private MemberAddressApi addressApi; - + @Mock + private DeliveryPickUpStoreService deliveryPickUpStoreService; @Mock private DeliveryExpressTemplateService deliveryExpressTemplateService; @Mock @@ -55,7 +63,7 @@ public class TradeDeliveryPriceCalculatorTest extends BaseMockitoUnitTest { private DeliveryExpressTemplateRespBO.Free freeBO; @BeforeEach - public void init(){ + public void init() { // 准备参数 reqBO = new TradePriceCalculateReqBO() .setDeliveryType(DeliveryTypeEnum.EXPRESS.getType()) @@ -71,24 +79,23 @@ public class TradeDeliveryPriceCalculatorTest extends BaseMockitoUnitTest { .setPromotions(new ArrayList<>()) .setItems(asList( new TradePriceCalculateRespBO.OrderItem().setDeliveryTemplateId(1L).setSkuId(10L).setCount(2).setSelected(true) - .setWeight(10d).setVolume(10d).setPrice(100), + .setWeight(10d).setVolume(10d).setPrice(100) + .setDeliveryTypes(asList(DeliveryTypeEnum.EXPRESS.getType(), DeliveryTypeEnum.PICK_UP.getType())), new TradePriceCalculateRespBO.OrderItem().setDeliveryTemplateId(1L).setSkuId(20L).setCount(10).setSelected(true) - .setWeight(10d).setVolume(10d).setPrice(200), + .setWeight(10d).setVolume(10d).setPrice(200) + .setDeliveryTypes(asList(DeliveryTypeEnum.EXPRESS.getType(), DeliveryTypeEnum.PICK_UP.getType())), new TradePriceCalculateRespBO.OrderItem().setDeliveryTemplateId(1L).setSkuId(30L).setCount(1).setSelected(false) .setWeight(10d).setVolume(10d).setPrice(300) + .setDeliveryTypes(asList(DeliveryTypeEnum.EXPRESS.getType(), DeliveryTypeEnum.PICK_UP.getType())) )); // 保证价格被初始化上 TradePriceCalculatorHelper.recountPayPrice(resultBO.getItems()); TradePriceCalculatorHelper.recountAllPrice(resultBO); - // 准备收件地址数据 - MemberAddressRespDTO addressResp = randomPojo(MemberAddressRespDTO.class, item -> item.setAreaId(10)); - when(addressApi.getAddress(eq(10L), eq(1L))).thenReturn(addressResp); - - // 准备运费模板费用配置数据 + // 准备运费模板费用配置数据:起步 10 件 1000 分,续 10 件 2000 分 chargeBO = randomPojo(DeliveryExpressTemplateRespBO.Charge.class, item -> item.setStartCount(10D).setStartPrice(1000).setExtraCount(10D).setExtraPrice(2000)); - // 准备运费模板包邮配置数据:订单总件数 < 包邮件数时 12 < 20 + // 准备运费模板包邮配置数据:默认不包邮(订单总件数 12 不大于 包邮件数 20) freeBO = randomPojo(DeliveryExpressTemplateRespBO.Free.class, item -> item.setFreeCount(20).setFreePrice(100)); // 准备 SP 运费模板数据 @@ -97,99 +104,320 @@ public class TradeDeliveryPriceCalculatorTest extends BaseMockitoUnitTest { .setCharge(chargeBO).setFree(freeBO)); } + // ========== 配送方式校验 ========== + @Test - @DisplayName("全场包邮") - public void testCalculate_expressGlobalFree() { - // mock 方法(全场包邮) - when(tradeConfigService.getTradeConfig()).thenReturn(new TradeConfigDO().setDeliveryExpressFreeEnabled(true) - .setDeliveryExpressFreePrice(2200)); + @DisplayName("配送方式为空:直接返回,不计算运费") + public void testCalculate_deliveryTypeNull() { + // 准备参数 + reqBO.setDeliveryType(null); // 调用 calculator.calculate(reqBO, resultBO); + + // 断言:未计算运费 + assertThat(resultBO.getPrice().getDeliveryPrice()).isEqualTo(0); + } + + @Test + @DisplayName("配送方式与商品不匹配:抛出异常") + public void testCalculate_deliveryTypeMismatch() { + // 准备参数:商品 deliveryTypes 不包含 EXPRESS + resultBO.getItems().forEach(item -> item.setDeliveryTypes(singletonList(DeliveryTypeEnum.PICK_UP.getType()))); + + // 调用并断言异常 + assertServiceException(() -> calculator.calculate(reqBO, resultBO), + PRICE_CALCULATE_DELIVERY_PRICE_TYPE_ILLEGAL); + } + + // ========== 自提模式 ========== + + @Test + @DisplayName("自提:未选择门店时直接返回") + public void testCalculate_pickUp_storeIdNull() { + // 准备参数 + reqBO.setDeliveryType(DeliveryTypeEnum.PICK_UP.getType()).setPickUpStoreId(null); + + // 调用 + calculator.calculate(reqBO, resultBO); + + // 断言:未计算运费 + assertThat(resultBO.getPrice().getDeliveryPrice()).isEqualTo(0); + } + + @Test + @DisplayName("自提:门店不存在时抛出异常") + public void testCalculate_pickUp_storeNotFound() { + // 准备参数 + reqBO.setDeliveryType(DeliveryTypeEnum.PICK_UP.getType()).setPickUpStoreId(99L); + // mock:门店不存在 + when(deliveryPickUpStoreService.getDeliveryPickUpStore(eq(99L))).thenReturn(null); + + // 调用并断言异常 + assertServiceException(() -> calculator.calculate(reqBO, resultBO), + PICK_UP_STORE_NOT_EXISTS); + } + + @Test + @DisplayName("自提:门店被禁用时抛出异常") + public void testCalculate_pickUp_storeDisabled() { + // 准备参数 + reqBO.setDeliveryType(DeliveryTypeEnum.PICK_UP.getType()).setPickUpStoreId(99L); + // mock:门店被禁用 + when(deliveryPickUpStoreService.getDeliveryPickUpStore(eq(99L))).thenReturn( + new DeliveryPickUpStoreDO().setStatus(CommonStatusEnum.DISABLE.getStatus())); + + // 调用并断言异常 + assertServiceException(() -> calculator.calculate(reqBO, resultBO), + PICK_UP_STORE_NOT_EXISTS); + } + + @Test + @DisplayName("自提:门店正常时不计算运费") + public void testCalculate_pickUp_ok() { + // 准备参数 + reqBO.setDeliveryType(DeliveryTypeEnum.PICK_UP.getType()).setPickUpStoreId(99L); + // mock:门店正常 + when(deliveryPickUpStoreService.getDeliveryPickUpStore(eq(99L))).thenReturn( + new DeliveryPickUpStoreDO().setStatus(CommonStatusEnum.ENABLE.getStatus())); + + // 调用 + calculator.calculate(reqBO, resultBO); + + // 断言:未计算运费 + assertThat(resultBO.getPrice().getDeliveryPrice()).isEqualTo(0); + } + + // ========== 快递模式 ========== + + @Test + @DisplayName("快递:收件地址为空时直接返回") + public void testCalculate_express_addressIdNull() { + // 准备参数 + reqBO.setAddressId(null); + + // 调用 + calculator.calculate(reqBO, resultBO); + + // 断言:未计算运费 + assertThat(resultBO.getPrice().getDeliveryPrice()).isEqualTo(0); + } + + @Test + @DisplayName("快递:全场包邮(开关开启 && 总金额满足全场包邮金额)") + public void testCalculate_globalFree() { + // mock:收件地址 + 全场包邮配置 + mockAddress(); + when(tradeConfigService.getTradeConfig()).thenReturn(new TradeConfigDO() + .setDeliveryExpressFreeEnabled(true).setDeliveryExpressFreePrice(2200)); + + // 调用 + calculator.calculate(reqBO, resultBO); + + // 断言 TradePriceCalculateRespBO.Price price = resultBO.getPrice(); assertThat(price) - .extracting("totalPrice","discountPrice","couponPrice","pointPrice","deliveryPrice","payPrice") - .containsExactly(2200, 0, 0, 0, 0, 2200); + .extracting("totalPrice", "discountPrice", "couponPrice", "pointPrice", "deliveryPrice", "payPrice") + .containsExactly(2200, 0, 0, 0, 0, 2200); assertThat(resultBO.getItems()).hasSize(3); - // 断言:SKU1 assertThat(resultBO.getItems().get(0)) - .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice") + .extracting("price", "count", "discountPrice", "couponPrice", "pointPrice", "deliveryPrice", "payPrice") .containsExactly(100, 2, 0, 0, 0, 0, 200); - // 断言:SKU2 assertThat(resultBO.getItems().get(1)) - .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice") + .extracting("price", "count", "discountPrice", "couponPrice", "pointPrice", "deliveryPrice", "payPrice") .containsExactly(200, 10, 0, 0, 0, 0, 2000); - // 断言:SKU3 未选中 assertThat(resultBO.getItems().get(2)) - .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice") + .extracting("price", "count", "discountPrice", "couponPrice", "pointPrice", "deliveryPrice", "payPrice") .containsExactly(300, 1, 0, 0, 0, 0, 300); } @Test - @DisplayName("按件计算运费不包邮的情况") - public void testCalculate_expressTemplateCharge() { - // SKU 1 : 100 * 2 = 200 - // SKU 2 :200 * 10 = 2000 - // 运费 首件 1000 + 续件 2000 = 3000 - // mock 方法 + @DisplayName("快递:活动包邮(freeDelivery=true)时不计算运费") + public void testCalculate_activityFree() { + // 准备参数 + resultBO.setFreeDelivery(true); + // mock:收件地址 + mockAddress(); + + // 调用 + calculator.calculate(reqBO, resultBO); + + // 断言:未计算运费 + assertThat(resultBO.getPrice().getDeliveryPrice()).isEqualTo(0); + } + + @Test + @DisplayName("快递:找不到运费模板时抛出异常") + public void testCalculate_express_templateNotFound() { + // mock:收件地址 + 空模板 + mockAddress(); when(deliveryExpressTemplateService.getExpressTemplateMapByIdsAndArea(eq(asSet(1L)), eq(10))) - .thenReturn(MapUtil.of(1L, templateRespBO)); + .thenReturn(Collections.emptyMap()); + + // 调用并断言异常 + assertServiceException(() -> calculator.calculate(reqBO, resultBO), + PRICE_CALCULATE_DELIVERY_PRICE_TEMPLATE_NOT_FOUND); + } + + @Test + @DisplayName("按件计算运费:不包邮的情况") + public void testCalculate_expressTemplateCharge() { + // SKU 1:100 * 2 = 200 + // SKU 2:200 * 10 = 2000 + // 运费:首件 1000 + 续件 ceil((12-10)/10)=1 倍 * 2000 = 3000 + // mock 方法:默认 freeCount=20,订单 12 件 不大于 20,不包邮 + mockAddress(); + mockTemplate(templateRespBO); // 调用 calculator.calculate(reqBO, resultBO); + // 断言 TradePriceCalculateRespBO.Price price = resultBO.getPrice(); assertThat(price) - .extracting("totalPrice","discountPrice","couponPrice","pointPrice","deliveryPrice","payPrice") - .containsExactly(2200, 0, 0, 0, 3000, 5200); + .extracting("totalPrice", "discountPrice", "couponPrice", "pointPrice", "deliveryPrice", "payPrice") + .containsExactly(2200, 0, 0, 0, 3000, 5200); assertThat(resultBO.getItems()).hasSize(3); - // 断言:SKU1 + // 断言:SKU1 分摊运费 (int)(3000 * 2/12) = 500 assertThat(resultBO.getItems().get(0)) - .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice") + .extracting("price", "count", "discountPrice", "couponPrice", "pointPrice", "deliveryPrice", "payPrice") .containsExactly(100, 2, 0, 0, 0, 500, 700); - // 断言:SKU2 + // 断言:SKU2 分摊运费 3000 - 500 = 2500 assertThat(resultBO.getItems().get(1)) - .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice") + .extracting("price", "count", "discountPrice", "couponPrice", "pointPrice", "deliveryPrice", "payPrice") .containsExactly(200, 10, 0, 0, 0, 2500, 4500); - // 断言:SKU3 未选中 + // 断言:SKU3 未选中,不计运费 assertThat(resultBO.getItems().get(2)) - .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice") + .extracting("price", "count", "discountPrice", "couponPrice", "pointPrice", "deliveryPrice", "payPrice") .containsExactly(300, 1, 0, 0, 0, 0, 300); } @Test - @DisplayName("按件计算运费包邮的情况") + @DisplayName("按件计算运费:包邮(订单总件数 >= 包邮件数 && 总金额 >= 包邮金额)") public void testCalculate_expressTemplateFree() { - // SKU 1 : 100 * 2 = 200 - // SKU 2 :200 * 10 = 2000 - // 运费 0 - // mock 方法 - // 准备运费模板包邮配置数据 包邮 订单总件数 > 包邮件数时 12 > 10 + // SKU 1:100 * 2 = 200 + // SKU 2:200 * 10 = 2000 + // 总件数 12 >= 包邮件数 10 && 总金额 2200 >= 包邮金额 1000,包邮 templateRespBO.setFree(randomPojo(DeliveryExpressTemplateRespBO.Free.class, item -> item.setFreeCount(10).setFreePrice(1000))); - when(deliveryExpressTemplateService.getExpressTemplateMapByIdsAndArea(eq(asSet(1L)), eq(10))) - .thenReturn(MapUtil.of(1L, templateRespBO)); + mockAddress(); + mockTemplate(templateRespBO); // 调用 calculator.calculate(reqBO, resultBO); + // 断言 TradePriceCalculateRespBO.Price price = resultBO.getPrice(); assertThat(price) - .extracting("totalPrice","discountPrice","couponPrice","pointPrice","deliveryPrice","payPrice") - .containsExactly(2200, 0, 0, 0, 0, 2200); + .extracting("totalPrice", "discountPrice", "couponPrice", "pointPrice", "deliveryPrice", "payPrice") + .containsExactly(2200, 0, 0, 0, 0, 2200); assertThat(resultBO.getItems()).hasSize(3); - // 断言:SKU1 assertThat(resultBO.getItems().get(0)) - .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice") + .extracting("price", "count", "discountPrice", "couponPrice", "pointPrice", "deliveryPrice", "payPrice") .containsExactly(100, 2, 0, 0, 0, 0, 200); - // 断言:SKU2 assertThat(resultBO.getItems().get(1)) - .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice") + .extracting("price", "count", "discountPrice", "couponPrice", "pointPrice", "deliveryPrice", "payPrice") .containsExactly(200, 10, 0, 0, 0, 0, 2000); - // 断言:SKU3 未选中 assertThat(resultBO.getItems().get(2)) - .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice") + .extracting("price", "count", "discountPrice", "couponPrice", "pointPrice", "deliveryPrice", "payPrice") .containsExactly(300, 1, 0, 0, 0, 0, 300); } + @Test + @DisplayName("按件计算运费:边界场景(总件数 = 包邮件数 → 包邮)") + public void testCalculate_expressTemplateFree_countBoundary() { + // 总件数 12 等于 包邮件数 12 && 总金额 2200 >= 包邮金额 1000,包邮 + templateRespBO.setFree(randomPojo(DeliveryExpressTemplateRespBO.Free.class, + item -> item.setFreeCount(12).setFreePrice(1000))); + mockAddress(); + mockTemplate(templateRespBO); + + // 调用 + calculator.calculate(reqBO, resultBO); + + // 断言:包邮,运费 = 0 + assertThat(resultBO.getPrice().getDeliveryPrice()).isEqualTo(0); + } + + @Test + @DisplayName("按件计算运费:总金额未达到包邮金额 → 不包邮") + public void testCalculate_expressTemplateFree_priceNotMatch() { + // 总件数 12 > 包邮件数 10,但总金额 2200 < 包邮金额 5000,不包邮 + templateRespBO.setFree(randomPojo(DeliveryExpressTemplateRespBO.Free.class, + item -> item.setFreeCount(10).setFreePrice(5000))); + mockAddress(); + mockTemplate(templateRespBO); + + // 调用 + calculator.calculate(reqBO, resultBO); + + // 断言:仍按运费模板计算 = 3000 + assertThat(resultBO.getPrice().getDeliveryPrice()).isEqualTo(3000); + } + + @Test + @DisplayName("按件计算运费:边界场景(总金额 = 包邮金额 → 包邮)") + public void testCalculate_expressTemplateFree_priceBoundary() { + // 总件数 12 >= 包邮件数 10 && 总金额 2200 等于 包邮金额 2200,包邮 + templateRespBO.setFree(randomPojo(DeliveryExpressTemplateRespBO.Free.class, + item -> item.setFreeCount(10).setFreePrice(2200))); + mockAddress(); + mockTemplate(templateRespBO); + + // 调用 + calculator.calculate(reqBO, resultBO); + + // 断言:包邮,运费 = 0 + assertThat(resultBO.getPrice().getDeliveryPrice()).isEqualTo(0); + } + + @Test + @DisplayName("按重量计算运费") + public void testCalculate_expressTemplate_byWeight() { + // SKU1 weight 10 * count 2 = 20 + // SKU2 weight 10 * count 10 = 100 + // 总重量 120kg + // 运费:首件 1000 + 续件 ceil((120-10)/10)=11 倍 * 2000 = 23000 + templateRespBO.setChargeMode(DeliveryExpressChargeModeEnum.WEIGHT.getType()); + templateRespBO.setFree(randomPojo(DeliveryExpressTemplateRespBO.Free.class, + item -> item.setFreeCount(200).setFreePrice(100))); + mockAddress(); + mockTemplate(templateRespBO); + + // 调用 + calculator.calculate(reqBO, resultBO); + + // 断言 + assertThat(resultBO.getPrice().getDeliveryPrice()).isEqualTo(23000); + } + + @Test + @DisplayName("按体积计算运费") + public void testCalculate_expressTemplate_byVolume() { + // SKU1 volume 10 * count 2 = 20 + // SKU2 volume 10 * count 10 = 100 + // 总体积 120m³,与按重量场景同 + templateRespBO.setChargeMode(DeliveryExpressChargeModeEnum.VOLUME.getType()); + templateRespBO.setFree(randomPojo(DeliveryExpressTemplateRespBO.Free.class, + item -> item.setFreeCount(200).setFreePrice(100))); + mockAddress(); + mockTemplate(templateRespBO); + + // 调用 + calculator.calculate(reqBO, resultBO); + + // 断言 + assertThat(resultBO.getPrice().getDeliveryPrice()).isEqualTo(23000); + } + + private void mockAddress() { + MemberAddressRespDTO addressResp = randomPojo(MemberAddressRespDTO.class, item -> item.setAreaId(10)); + when(addressApi.getAddress(eq(10L), eq(1L))).thenReturn(addressResp); + } + + private void mockTemplate(DeliveryExpressTemplateRespBO template) { + when(deliveryExpressTemplateService.getExpressTemplateMapByIdsAndArea(eq(asSet(1L)), eq(10))) + .thenReturn(MapUtil.of(1L, template)); + } + } diff --git a/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDiscountActivityPriceCalculatorTest.java b/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDiscountActivityPriceCalculatorTest.java index 3e4642b..ef02617 100644 --- a/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDiscountActivityPriceCalculatorTest.java +++ b/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDiscountActivityPriceCalculatorTest.java @@ -1,6 +1,8 @@ package cn.iocoder.yudao.module.trade.service.price.calculator; import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import cn.iocoder.yudao.module.member.api.level.MemberLevelApi; +import cn.iocoder.yudao.module.member.api.user.MemberUserApi; import cn.iocoder.yudao.module.promotion.api.discount.DiscountActivityApi; import cn.iocoder.yudao.module.promotion.api.discount.dto.DiscountProductRespDTO; import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum; @@ -8,7 +10,6 @@ import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum; import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; @@ -27,7 +28,6 @@ import static org.mockito.Mockito.when; * * @author 芋道源码 */ -@Disabled // TODO 芋艿:后续修复 public class TradeDiscountActivityPriceCalculatorTest extends BaseMockitoUnitTest { @InjectMocks @@ -35,6 +35,10 @@ public class TradeDiscountActivityPriceCalculatorTest extends BaseMockitoUnitTes @Mock private DiscountActivityApi discountActivityApi; + @Mock + private MemberLevelApi memberLevelApi; + @Mock + private MemberUserApi memberUserApi; @Test public void testCalculate() { @@ -92,16 +96,16 @@ public class TradeDiscountActivityPriceCalculatorTest extends BaseMockitoUnitTes assertEquals(orderItem01.getCouponPrice(), 0); assertEquals(orderItem01.getPointPrice(), 0); assertEquals(orderItem01.getPayPrice(), 120); - // 断言:SKU 2 + // 断言:SKU 2 未选中,不参与计算 TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1); assertEquals(orderItem02.getSkuId(), 20L); assertEquals(orderItem02.getCount(), 3); assertEquals(orderItem02.getPrice(), 50); - assertEquals(orderItem02.getDiscountPrice(), 60); + assertEquals(orderItem02.getDiscountPrice(), 0); assertEquals(orderItem02.getDeliveryPrice(), 0); assertEquals(orderItem02.getCouponPrice(), 0); assertEquals(orderItem02.getPointPrice(), 0); - assertEquals(orderItem02.getPayPrice(), 90); + assertEquals(orderItem02.getPayPrice(), 150); // 断言:Promotion 部分 assertEquals(result.getPromotions().size(), 1); TradePriceCalculateRespBO.Promotion promotion01 = result.getPromotions().get(0); diff --git a/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointUsePriceCalculatorTest.java b/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointUsePriceCalculatorTest.java index 10850bd..6277173 100644 --- a/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointUsePriceCalculatorTest.java +++ b/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointUsePriceCalculatorTest.java @@ -9,7 +9,6 @@ import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum; import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; @@ -22,13 +21,11 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.when; -// TODO 芋艿:晚点 review /** * {@link TradePointUsePriceCalculator } 的单元测试类 * * @author owen */ -@Disabled // TODO 芋艿:后续修复 public class TradePointUsePriceCalculatorTest extends BaseMockitoUnitTest { @InjectMocks @@ -245,6 +242,10 @@ public class TradePointUsePriceCalculatorTest extends BaseMockitoUnitTest { TradePriceCalculatorHelper.recountPayPrice(result.getItems()); TradePriceCalculatorHelper.recountAllPrice(result); + // mock 方法(会员 信息):不使用积分仍需获取用户初始化 totalPoint + MemberUserRespDTO user = randomPojo(MemberUserRespDTO.class, o -> o.setId(param.getUserId()).setPoint(100)); + when(memberUserApi.getUser(user.getId())).thenReturn(user); + // 调用 tradePointUsePriceCalculator.calculate(param, result); // 断言:没有使用积分 diff --git a/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculatorTest.java b/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculatorTest.java index 725a19e..bb40c45 100644 --- a/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculatorTest.java +++ b/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculatorTest.java @@ -2,10 +2,29 @@ package cn.iocoder.yudao.module.trade.service.price.calculator; import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; import cn.iocoder.yudao.module.promotion.api.reward.RewardActivityApi; +import cn.iocoder.yudao.module.promotion.api.reward.dto.RewardActivityMatchRespDTO; +import cn.iocoder.yudao.module.promotion.enums.common.PromotionConditionTypeEnum; +import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum; +import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; +import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; +import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; +import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; -// TODO 芋艿:后续在修复 +import java.util.ArrayList; +import java.util.LinkedHashMap; + +import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + /** * {@link TradeRewardActivityPriceCalculator} 的单元测试类 * @@ -19,212 +38,155 @@ public class TradeRewardActivityPriceCalculatorTest extends BaseMockitoUnitTest @Mock private RewardActivityApi rewardActivityApi; -// @Test -// public void testCalculate_match() { -// // 准备参数 -// TradePriceCalculateReqBO param = new TradePriceCalculateReqBO() -// .setItems(asList( -// new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true), // 匹配活动 1 -// new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(true), // 匹配活动 1 -// new TradePriceCalculateReqBO.Item().setSkuId(30L).setCount(4).setSelected(true) // 匹配活动 2 -// )); -// TradePriceCalculateRespBO result = new TradePriceCalculateRespBO() -// .setType(TradeOrderTypeEnum.NORMAL.getType()) -// .setPrice(new TradePriceCalculateRespBO.Price()) -// .setPromotions(new ArrayList<>()).setGiveCouponTemplateCounts(new LinkedHashMap<>()) -// .setItems(asList( -// new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true) -// .setPrice(100).setSpuId(1L), -// new TradePriceCalculateRespBO.OrderItem().setSkuId(20L).setCount(3).setSelected(true) -// .setPrice(50).setSpuId(2L), -// new TradePriceCalculateRespBO.OrderItem().setSkuId(30L).setCount(4).setSelected(true) -// .setPrice(30).setSpuId(3L) -// )); -// // 保证价格被初始化上 -// TradePriceCalculatorHelper.recountPayPrice(result.getItems()); -// TradePriceCalculatorHelper.recountAllPrice(result); -// -// // mock 方法(满减送 RewardActivity 信息) -// when(rewardActivityApi.getRewardActivityListByStatusAndNow(CommonStatusEnum.ENABLE.getStatus(), LocalDateTime.now())) -// .thenReturn(asList( -// randomPojo(RewardActivityMatchRespDTO.class, o -> o.setId(1000L).setName("活动 1000 号") -// .setConditionType(PromotionConditionTypeEnum.PRICE.getType()) -// .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductScopeValues(asList(1L, 2L)) -// .setRules(singletonList(new RewardActivityMatchRespDTO.Rule().setLimit(20).setDiscountPrice(70) -// .setFreeDelivery(false)))), -// randomPojo(RewardActivityMatchRespDTO.class, o -> o.setId(2000L).setName("活动 2000 号") -// .setConditionType(PromotionConditionTypeEnum.COUNT.getType()) -// .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductScopeValues(singletonList(3L)) -// .setRules(asList(new RewardActivityMatchRespDTO.Rule().setLimit(1).setDiscountPrice(10) -// .setPoint(50).setFreeDelivery(false), -// new RewardActivityMatchRespDTO.Rule().setLimit(2).setDiscountPrice(60) -// .setPoint(100).setFreeDelivery(false), // 最大可满足,因为是 4 个 -// new RewardActivityMatchRespDTO.Rule().setLimit(10).setDiscountPrice(100) -// .setFreeDelivery(false)))) -// )); -// -// // 调用 -// tradeRewardActivityPriceCalculator.calculate(param, result); -// // 断言 Order 部分 -// TradePriceCalculateRespBO.Price price = result.getPrice(); -// assertEquals(price.getTotalPrice(), 470); -// assertEquals(price.getDiscountPrice(), 130); -// assertEquals(price.getPointPrice(), 0); -// assertEquals(price.getDeliveryPrice(), 0); -// assertEquals(price.getCouponPrice(), 0); -// assertEquals(price.getPayPrice(), 340); -// assertNull(result.getCouponId()); -// // 断言:SKU 1 -// assertEquals(result.getItems().size(), 3); -// TradePriceCalculateRespBO.OrderItem orderItem01 = result.getItems().get(0); -// assertEquals(orderItem01.getSkuId(), 10L); -// assertEquals(orderItem01.getCount(), 2); -// assertEquals(orderItem01.getPrice(), 100); -// assertEquals(orderItem01.getDiscountPrice(), 40); -// assertEquals(orderItem01.getDeliveryPrice(), 0); -// assertEquals(orderItem01.getCouponPrice(), 0); -// assertEquals(orderItem01.getPointPrice(), 0); -// assertEquals(orderItem01.getPayPrice(), 160); -// assertEquals(orderItem01.getGivePoint(), 0); -// // 断言:SKU 2 -// TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1); -// assertEquals(orderItem02.getSkuId(), 20L); -// assertEquals(orderItem02.getCount(), 3); -// assertEquals(orderItem02.getPrice(), 50); -// assertEquals(orderItem02.getDiscountPrice(), 30); -// assertEquals(orderItem02.getDeliveryPrice(), 0); -// assertEquals(orderItem02.getCouponPrice(), 0); -// assertEquals(orderItem02.getPointPrice(), 0); -// assertEquals(orderItem02.getPayPrice(), 120); -// assertEquals(orderItem02.getGivePoint(), 0); -// // 断言:SKU 3 -// TradePriceCalculateRespBO.OrderItem orderItem03 = result.getItems().get(2); -// assertEquals(orderItem03.getSkuId(), 30L); -// assertEquals(orderItem03.getCount(), 4); -// assertEquals(orderItem03.getPrice(), 30); -// assertEquals(orderItem03.getDiscountPrice(), 60); -// assertEquals(orderItem03.getDeliveryPrice(), 0); -// assertEquals(orderItem03.getCouponPrice(), 0); -// assertEquals(orderItem03.getPointPrice(), 0); -// assertEquals(orderItem03.getPayPrice(), 60); -// assertEquals(orderItem03.getGivePoint(), 100); -// // 断言:Promotion 部分(第一个) -// assertEquals(result.getPromotions().size(), 2); -// TradePriceCalculateRespBO.Promotion promotion01 = result.getPromotions().get(0); -// assertEquals(promotion01.getId(), 1000L); -// assertEquals(promotion01.getName(), "活动 1000 号"); -// assertEquals(promotion01.getType(), PromotionTypeEnum.REWARD_ACTIVITY.getType()); -// assertEquals(promotion01.getTotalPrice(), 350); -// assertEquals(promotion01.getDiscountPrice(), 70); -// assertTrue(promotion01.getMatch()); -// assertEquals(promotion01.getDescription(), "满减送:省 0.70 元"); -// assertEquals(promotion01.getItems().size(), 2); -// TradePriceCalculateRespBO.PromotionItem promotionItem011 = promotion01.getItems().get(0); -// assertEquals(promotionItem011.getSkuId(), 10L); -// assertEquals(promotionItem011.getTotalPrice(), 200); -// assertEquals(promotionItem011.getDiscountPrice(), 40); -// TradePriceCalculateRespBO.PromotionItem promotionItem012 = promotion01.getItems().get(1); -// assertEquals(promotionItem012.getSkuId(), 20L); -// assertEquals(promotionItem012.getTotalPrice(), 150); -// assertEquals(promotionItem012.getDiscountPrice(), 30); -// // 断言:Promotion 部分(第二个) -// TradePriceCalculateRespBO.Promotion promotion02 = result.getPromotions().get(1); -// assertEquals(promotion02.getId(), 2000L); -// assertEquals(promotion02.getName(), "活动 2000 号"); -// assertEquals(promotion02.getType(), PromotionTypeEnum.REWARD_ACTIVITY.getType()); -// assertEquals(promotion02.getTotalPrice(), 120); -// assertEquals(promotion02.getDiscountPrice(), 60); -// assertTrue(promotion02.getMatch()); -// assertEquals(promotion02.getDescription(), "满减送:省 0.60 元"); -// TradePriceCalculateRespBO.PromotionItem promotionItem02 = promotion02.getItems().get(0); -// assertEquals(promotion02.getItems().size(), 1); -// assertEquals(promotionItem02.getSkuId(), 30L); -// assertEquals(promotionItem02.getTotalPrice(), 120); -// assertEquals(promotionItem02.getDiscountPrice(), 60); -// } -// -// @Test -// public void testCalculate_notMatch() { -// // 准备参数 -// TradePriceCalculateReqBO param = new TradePriceCalculateReqBO() -// .setItems(asList( -// new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true), -// new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(true), -// new TradePriceCalculateReqBO.Item().setSkuId(30L).setCount(4).setSelected(true) -// )); -// TradePriceCalculateRespBO result = new TradePriceCalculateRespBO() -// .setType(TradeOrderTypeEnum.NORMAL.getType()) -// .setPrice(new TradePriceCalculateRespBO.Price()) -// .setPromotions(new ArrayList<>()) -// .setItems(asList( -// new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true) -// .setPrice(100).setSpuId(1L), -// new TradePriceCalculateRespBO.OrderItem().setSkuId(20L).setCount(3).setSelected(true) -// .setPrice(50).setSpuId(2L) -// )); -// // 保证价格被初始化上 -// TradePriceCalculatorHelper.recountPayPrice(result.getItems()); -// TradePriceCalculatorHelper.recountAllPrice(result); -// -// // mock 方法(限时折扣 DiscountActivity 信息) -// when(rewardActivityApi.getRewardActivityListByStatusAndNow(CommonStatusEnum.ENABLE.getStatus(), LocalDateTime.now())) -// .thenReturn(singletonList( -// randomPojo(RewardActivityMatchRespDTO.class, o -> o.setId(1000L).setName("活动 1000 号") -// .setProductScopeValues(asList(1L, 2L)).setConditionType(PromotionConditionTypeEnum.PRICE.getType()) -// .setRules(singletonList(new RewardActivityMatchRespDTO.Rule().setLimit(351).setDiscountPrice(70)))) -// )); -// -// // 调用 -// tradeRewardActivityPriceCalculator.calculate(param, result); -// // 断言 Order 部分 -// TradePriceCalculateRespBO.Price price = result.getPrice(); -// assertEquals(price.getTotalPrice(), 350); -// assertEquals(price.getDiscountPrice(), 0); -// assertEquals(price.getPointPrice(), 0); -// assertEquals(price.getDeliveryPrice(), 0); -// assertEquals(price.getCouponPrice(), 0); -// assertEquals(price.getPayPrice(), 350); -// assertNull(result.getCouponId()); -// // 断言:SKU 1 -// assertEquals(result.getItems().size(), 2); -// TradePriceCalculateRespBO.OrderItem orderItem01 = result.getItems().get(0); -// assertEquals(orderItem01.getSkuId(), 10L); -// assertEquals(orderItem01.getCount(), 2); -// assertEquals(orderItem01.getPrice(), 100); -// assertEquals(orderItem01.getDiscountPrice(), 0); -// assertEquals(orderItem01.getDeliveryPrice(), 0); -// assertEquals(orderItem01.getCouponPrice(), 0); -// assertEquals(orderItem01.getPointPrice(), 0); -// assertEquals(orderItem01.getPayPrice(), 200); -// // 断言:SKU 2 -// TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1); -// assertEquals(orderItem02.getSkuId(), 20L); -// assertEquals(orderItem02.getCount(), 3); -// assertEquals(orderItem02.getPrice(), 50); -// assertEquals(orderItem02.getDiscountPrice(), 0); -// assertEquals(orderItem02.getDeliveryPrice(), 0); -// assertEquals(orderItem02.getCouponPrice(), 0); -// assertEquals(orderItem02.getPointPrice(), 0); -// assertEquals(orderItem02.getPayPrice(), 150); -// // 断言 Promotion 部分 -// assertEquals(result.getPromotions().size(), 1); -// TradePriceCalculateRespBO.Promotion promotion01 = result.getPromotions().get(0); -// assertEquals(promotion01.getId(), 1000L); -// assertEquals(promotion01.getName(), "活动 1000 号"); -// assertEquals(promotion01.getType(), PromotionTypeEnum.REWARD_ACTIVITY.getType()); -// assertEquals(promotion01.getTotalPrice(), 350); -// assertEquals(promotion01.getDiscountPrice(), 0); -// assertFalse(promotion01.getMatch()); -// assertEquals(promotion01.getDescription(), "TODO"); // TODO 芋艿:后面再想想 -// assertEquals(promotion01.getItems().size(), 2); -// TradePriceCalculateRespBO.PromotionItem promotionItem011 = promotion01.getItems().get(0); -// assertEquals(promotionItem011.getSkuId(), 10L); -// assertEquals(promotionItem011.getTotalPrice(), 200); -// assertEquals(promotionItem011.getDiscountPrice(), 0); -// TradePriceCalculateRespBO.PromotionItem promotionItem012 = promotion01.getItems().get(1); -// assertEquals(promotionItem012.getSkuId(), 20L); -// assertEquals(promotionItem012.getTotalPrice(), 150); -// assertEquals(promotionItem012.getDiscountPrice(), 0); -// } + @Test + public void testCalculate_match() { + // 准备参数:SPU 1、SPU 2 命中活动;SPU 3 不在活动范围 + TradePriceCalculateReqBO param = new TradePriceCalculateReqBO() + .setItems(asList( + new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true), + new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(true), + new TradePriceCalculateReqBO.Item().setSkuId(30L).setCount(4).setSelected(true) + )); + TradePriceCalculateRespBO result = new TradePriceCalculateRespBO() + .setType(TradeOrderTypeEnum.NORMAL.getType()) + .setPrice(new TradePriceCalculateRespBO.Price()) + .setPromotions(new ArrayList<>()) + .setGiveCouponTemplateCounts(new LinkedHashMap<>()) + .setItems(asList( + new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true) + .setPrice(100).setSpuId(1L).setGivePoint(0), + new TradePriceCalculateRespBO.OrderItem().setSkuId(20L).setCount(3).setSelected(true) + .setPrice(50).setSpuId(2L).setGivePoint(0), + new TradePriceCalculateRespBO.OrderItem().setSkuId(30L).setCount(4).setSelected(true) + .setPrice(30).setSpuId(3L).setGivePoint(0) + )); + // 保证价格被初始化上 + TradePriceCalculatorHelper.recountPayPrice(result.getItems()); + TradePriceCalculatorHelper.recountAllPrice(result); + + // mock 方法(满减送活动) + when(rewardActivityApi.getMatchRewardActivityListBySpuIds(eq(asSet(1L, 2L, 3L)))).thenReturn(singletonList( + randomPojo(RewardActivityMatchRespDTO.class, o -> o.setId(1000L).setName("活动 1000 号") + .setConditionType(PromotionConditionTypeEnum.PRICE.getType()) + .setSpuIds(asList(1L, 2L)) + .setRules(asList( + new RewardActivityMatchRespDTO.Rule().setLimit(100).setDiscountPrice(20) + .setPoint(50).setFreeDelivery(false), + new RewardActivityMatchRespDTO.Rule().setLimit(200).setDiscountPrice(50) + .setPoint(100).setFreeDelivery(true)))))); + + // 调用:SKU1+SKU2 总 payPrice=200+150=350, 命中 limit=200 规则 + tradeRewardActivityPriceCalculator.calculate(param, result); + + // 断言:Price 部分(SKU3 仍按原价计入) + TradePriceCalculateRespBO.Price price = result.getPrice(); + assertEquals(price.getTotalPrice(), 470); + assertEquals(price.getDiscountPrice(), 50); + assertEquals(price.getPayPrice(), 420); + // 断言:包邮标记 + assertTrue(result.getFreeDelivery()); + + // 断言:SKU 1 优惠 50 * 200/350 = 28 + TradePriceCalculateRespBO.OrderItem orderItem01 = result.getItems().get(0); + assertEquals(orderItem01.getSkuId(), 10L); + assertEquals(orderItem01.getDiscountPrice(), 28); + assertEquals(orderItem01.getPayPrice(), 172); + assertEquals(orderItem01.getGivePoint(), 57); // 100 * 200/350 + // 断言:SKU 2 优惠 50 - 28 = 22 + TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1); + assertEquals(orderItem02.getSkuId(), 20L); + assertEquals(orderItem02.getDiscountPrice(), 22); + assertEquals(orderItem02.getPayPrice(), 128); + assertEquals(orderItem02.getGivePoint(), 43); // 100 - 57 + // 断言:SKU 3 不在活动范围,不参与 + TradePriceCalculateRespBO.OrderItem orderItem03 = result.getItems().get(2); + assertEquals(orderItem03.getSkuId(), 30L); + assertEquals(orderItem03.getDiscountPrice(), 0); + assertEquals(orderItem03.getPayPrice(), 120); + assertEquals(orderItem03.getGivePoint(), 0); + + // 断言:Promotion 部分 + assertEquals(result.getPromotions().size(), 1); + TradePriceCalculateRespBO.Promotion promotion = result.getPromotions().get(0); + assertEquals(promotion.getId(), 1000L); + assertEquals(promotion.getName(), "活动 1000 号"); + assertEquals(promotion.getType(), PromotionTypeEnum.REWARD_ACTIVITY.getType()); + assertEquals(promotion.getTotalPrice(), 350); + assertEquals(promotion.getDiscountPrice(), 50); + assertTrue(promotion.getMatch()); + assertEquals(promotion.getDescription(), "满减送:省 0.50 元"); + assertEquals(promotion.getItems().size(), 2); + } + + @Test + public void testCalculate_notMatch() { + // 准备参数 + TradePriceCalculateReqBO param = new TradePriceCalculateReqBO() + .setItems(singletonList( + new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true) + )); + TradePriceCalculateRespBO result = new TradePriceCalculateRespBO() + .setType(TradeOrderTypeEnum.NORMAL.getType()) + .setPrice(new TradePriceCalculateRespBO.Price()) + .setPromotions(new ArrayList<>()) + .setGiveCouponTemplateCounts(new LinkedHashMap<>()) + .setItems(singletonList( + new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true) + .setPrice(100).setSpuId(1L).setGivePoint(0) + )); + TradePriceCalculatorHelper.recountPayPrice(result.getItems()); + TradePriceCalculatorHelper.recountAllPrice(result); + + // mock 方法:要求 limit=300,订单总额 200 不满足 + when(rewardActivityApi.getMatchRewardActivityListBySpuIds(eq(asSet(1L)))).thenReturn(singletonList( + randomPojo(RewardActivityMatchRespDTO.class, o -> o.setId(1000L).setName("活动 1000 号") + .setConditionType(PromotionConditionTypeEnum.PRICE.getType()) + .setSpuIds(singletonList(1L)) + .setRules(singletonList(new RewardActivityMatchRespDTO.Rule() + .setLimit(300).setDiscountPrice(70).setDescription("满 3 元减 0.7 元")))))); + + // 调用 + tradeRewardActivityPriceCalculator.calculate(param, result); + + // 断言:未优惠 + TradePriceCalculateRespBO.Price price = result.getPrice(); + assertEquals(price.getTotalPrice(), 200); + assertEquals(price.getDiscountPrice(), 0); + assertEquals(price.getPayPrice(), 200); + + // 断言:促销明细记录为不匹配 + assertEquals(result.getPromotions().size(), 1); + TradePriceCalculateRespBO.Promotion promotion = result.getPromotions().get(0); + assertEquals(promotion.getId(), 1000L); + assertEquals(promotion.getType(), PromotionTypeEnum.REWARD_ACTIVITY.getType()); + assertFalse(promotion.getMatch()); + assertEquals(promotion.getDiscountPrice(), 0); + assertEquals(promotion.getDescription(), "满减送:满 3 元减 0.7 元"); + } + + @Test + public void testCalculate_skipNonNormalOrder() { + // 准备参数:秒杀订单 + TradePriceCalculateReqBO param = new TradePriceCalculateReqBO() + .setItems(singletonList( + new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true) + )); + TradePriceCalculateRespBO result = new TradePriceCalculateRespBO() + .setType(TradeOrderTypeEnum.SECKILL.getType()) + .setPrice(new TradePriceCalculateRespBO.Price()) + .setPromotions(new ArrayList<>()) + .setItems(singletonList( + new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true) + .setPrice(100).setSpuId(1L) + )); + TradePriceCalculatorHelper.recountPayPrice(result.getItems()); + TradePriceCalculatorHelper.recountAllPrice(result); + + // 调用:非普通订单直接跳过,不会调用 API + tradeRewardActivityPriceCalculator.calculate(param, result); + + // 断言:未产生促销 + assertEquals(result.getPromotions().size(), 0); + } } -- libgit2 0.21.4