diff --git a/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordServiceImpl.java b/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordServiceImpl.java index ed314c3..4c3294c 100644 --- a/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordServiceImpl.java +++ b/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordServiceImpl.java @@ -38,7 +38,6 @@ import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getMaxValue; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getMinValue; -import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId; import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.BROKERAGE_WITHDRAW_USER_BALANCE_NOT_ENOUGH; /** @@ -143,7 +142,7 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService { int calculatePrice(Integer basePrice, Integer percent, Integer fixedPrice) { // 1. 优先使用固定佣金 if (fixedPrice != null && fixedPrice >= 0) { - return ObjectUtil.defaultIfNull(fixedPrice, 0); + return fixedPrice; } // 2. 根据比例计算佣金 if (basePrice != null && basePrice > 0 && percent != null && percent > 0) { @@ -329,7 +328,7 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService { return respVO; } // 2.2 校验用户是否有分销资格 - respVO.setEnabled(brokerageUserService.getUserBrokerageEnabled(getLoginUserId())); + respVO.setEnabled(brokerageUserService.getUserBrokerageEnabled(userId)); if (BooleanUtil.isFalse(respVO.getEnabled())) { return respVO; } @@ -339,22 +338,22 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService { return respVO; } - // 3.1 商品单独分佣模式 - Integer fixedMinPrice = 0; - Integer fixedMaxPrice = 0; - Integer spuMinPrice = 0; - Integer spuMaxPrice = 0; + // 3.1 获取商品 SKU 列表 List skuList = productSkuApi.getSkuListBySpuId(ListUtil.of(spuId)); if (BooleanUtil.isTrue(spu.getSubCommissionType())) { - fixedMinPrice = getMinValue(skuList, ProductSkuRespDTO::getFirstBrokeragePrice); - fixedMaxPrice = getMaxValue(skuList, ProductSkuRespDTO::getFirstBrokeragePrice); - // 3.2 全局分佣模式(根据商品价格比例计算) + // 3.2.1 商品独立分销模式:直接取 SKU 固定佣金 + // 注意:固定佣金允许为 0,表示商家主动设为零佣金 + Integer fixedMinPrice = getMinValue(skuList, ProductSkuRespDTO::getFirstBrokeragePrice); + Integer fixedMaxPrice = getMaxValue(skuList, ProductSkuRespDTO::getFirstBrokeragePrice); + respVO.setBrokerageMinPrice(calculatePrice(null, tradeConfig.getBrokerageFirstPercent(), fixedMinPrice)) + .setBrokerageMaxPrice(calculatePrice(null, tradeConfig.getBrokerageFirstPercent(), fixedMaxPrice)); } else { - spuMinPrice = getMinValue(skuList, ProductSkuRespDTO::getPrice); - spuMaxPrice = getMaxValue(skuList, ProductSkuRespDTO::getPrice); + // 3.2.2 全局比例模式:固定佣金传 null,避免被默认值 0 提前拦截 + Integer spuMinPrice = getMinValue(skuList, ProductSkuRespDTO::getPrice); + Integer spuMaxPrice = getMaxValue(skuList, ProductSkuRespDTO::getPrice); + respVO.setBrokerageMinPrice(calculatePrice(spuMinPrice, tradeConfig.getBrokerageFirstPercent(), null)) + .setBrokerageMaxPrice(calculatePrice(spuMaxPrice, tradeConfig.getBrokerageFirstPercent(), null)); } - respVO.setBrokerageMinPrice(calculatePrice(spuMinPrice, tradeConfig.getBrokerageFirstPercent(), fixedMinPrice)); - respVO.setBrokerageMaxPrice(calculatePrice(spuMaxPrice, tradeConfig.getBrokerageFirstPercent(), fixedMaxPrice)); return respVO; } diff --git a/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordServiceImplTest.java b/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordServiceImplTest.java index 141e865..93a9daf 100644 --- a/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordServiceImplTest.java +++ b/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordServiceImplTest.java @@ -1,36 +1,43 @@ package cn.iocoder.yudao.module.trade.service.brokerage; +import cn.hutool.core.collection.ListUtil; import cn.hutool.core.util.NumberUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +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.trade.controller.admin.brokerage.vo.record.BrokerageRecordPageReqVO; +import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.record.AppBrokerageProductPriceRespVO; import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageRecordDO; +import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO; import cn.iocoder.yudao.module.trade.dal.mysql.brokerage.BrokerageRecordMapper; +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum; +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum; import cn.iocoder.yudao.module.trade.service.config.TradeConfigService; import jakarta.annotation.Resource; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.context.annotation.Import; import org.springframework.test.context.bean.override.mockito.MockitoBean; import java.math.RoundingMode; +import java.util.List; -import static cn.hutool.core.util.RandomUtil.randomEle; -import static cn.hutool.core.util.RandomUtil.randomInt; import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime; +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime; import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomInteger; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; -// TODO @芋艿:单测后续看看 /** * {@link BrokerageRecordServiceImpl} 的单元测试类 * * @author owen */ -@Disabled // TODO 芋艿:后续 fix 补充的单测 @Import(BrokerageRecordServiceImpl.class) public class BrokerageRecordServiceImplTest extends BaseDbUnitTest { @@ -43,31 +50,43 @@ public class BrokerageRecordServiceImplTest extends BaseDbUnitTest { private TradeConfigService tradeConfigService; @MockitoBean private BrokerageUserService brokerageUserService; + @MockitoBean + private ProductSpuApi productSpuApi; + @MockitoBean + private ProductSkuApi productSkuApi; @Test - @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 public void testGetBrokerageRecordPage() { // mock 数据 BrokerageRecordDO dbBrokerageRecord = randomPojo(BrokerageRecordDO.class, o -> { // 等会查询到 - o.setUserId(null); - o.setBizType(null); - o.setStatus(null); - o.setCreateTime(null); + o.setUserId(1L); + o.setBizType(BrokerageRecordBizTypeEnum.ORDER.getType()); + o.setStatus(BrokerageRecordStatusEnum.SETTLEMENT.getStatus()); + o.setSourceUserLevel(1); + o.setSourceUserId(100L); + o.setCreateTime(buildTime(2023, 2, 10)); + o.setDeleted(false); }); brokerageRecordMapper.insert(dbBrokerageRecord); // 测试 userId 不匹配 - brokerageRecordMapper.insert(cloneIgnoreId(dbBrokerageRecord, o -> o.setUserId(null))); + brokerageRecordMapper.insert(cloneIgnoreId(dbBrokerageRecord, o -> o.setUserId(2L))); // 测试 bizType 不匹配 - brokerageRecordMapper.insert(cloneIgnoreId(dbBrokerageRecord, o -> o.setBizType(null))); + brokerageRecordMapper.insert(cloneIgnoreId(dbBrokerageRecord, + o -> o.setBizType(BrokerageRecordBizTypeEnum.WITHDRAW.getType()))); // 测试 status 不匹配 - brokerageRecordMapper.insert(cloneIgnoreId(dbBrokerageRecord, o -> o.setStatus(null))); + brokerageRecordMapper.insert(cloneIgnoreId(dbBrokerageRecord, + o -> o.setStatus(BrokerageRecordStatusEnum.CANCEL.getStatus()))); + // 测试 sourceUserLevel 不匹配 + brokerageRecordMapper.insert(cloneIgnoreId(dbBrokerageRecord, o -> o.setSourceUserLevel(2))); // 测试 createTime 不匹配 - brokerageRecordMapper.insert(cloneIgnoreId(dbBrokerageRecord, o -> o.setCreateTime(null))); + brokerageRecordMapper.insert(cloneIgnoreId(dbBrokerageRecord, + o -> o.setCreateTime(buildTime(2023, 3, 1)))); // 准备参数 BrokerageRecordPageReqVO reqVO = new BrokerageRecordPageReqVO(); - reqVO.setUserId(null); - reqVO.setBizType(null); - reqVO.setStatus(null); + reqVO.setUserId(1L); + reqVO.setBizType(BrokerageRecordBizTypeEnum.ORDER.getType()); + reqVO.setStatus(BrokerageRecordStatusEnum.SETTLEMENT.getStatus()); + reqVO.setSourceUserLevel(1); reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); // 调用 @@ -81,37 +100,128 @@ public class BrokerageRecordServiceImplTest extends BaseDbUnitTest { @Test public void testCalculatePrice_useFixedPrice() { // mock 数据 - Integer payPrice = randomInteger(); - Integer percent = randomInt(1, 101); - Integer fixedPrice = randomInt(); + Integer payPrice = 1000; + Integer percent = 10; + Integer fixedPrice = 88; // 调用 int brokerage = brokerageRecordService.calculatePrice(payPrice, percent, fixedPrice); // 断言 - assertEquals(brokerage, fixedPrice); + assertEquals(fixedPrice, brokerage); } @Test public void testCalculatePrice_usePercent() { - // mock 数据 - Integer payPrice = randomInteger(); - Integer percent = randomInt(1, 101); - Integer fixedPrice = randomEle(new Integer[]{0, null}); - System.out.println("fixedPrice=" + fixedPrice); + // mock 数据:fixedPrice 为 null 时,按比例计算佣金 + Integer payPrice = 1000; + Integer percent = 10; + Integer fixedPrice = null; // 调用 int brokerage = brokerageRecordService.calculatePrice(payPrice, percent, fixedPrice); // 断言 - assertEquals(brokerage, NumberUtil.div(NumberUtil.mul(payPrice, percent), 100, 0, RoundingMode.DOWN).intValue()); + assertEquals(NumberUtil.div(NumberUtil.mul(payPrice, percent), 100, 0, + RoundingMode.DOWN).intValue(), brokerage); + } + + @Test + public void testCalculatePrice_fixedPriceIsZero() { + // mock 数据:fixedPrice 为 0 时,仍使用固定佣金,不回退到比例计算 + Integer payPrice = 1000; + Integer percent = 10; + Integer fixedPrice = 0; + // 调用 + int brokerage = brokerageRecordService.calculatePrice(payPrice, percent, fixedPrice); + // 断言 + assertEquals(0, brokerage); } @Test public void testCalculatePrice_equalsZero() { - // mock 数据 + // mock 数据:三个参数均无效时,返回 0 Integer payPrice = null; Integer percent = null; Integer fixedPrice = null; // 调用 int brokerage = brokerageRecordService.calculatePrice(payPrice, percent, fixedPrice); // 断言 - assertEquals(brokerage, 0); + assertEquals(0, brokerage); + } + + @Test + public void testCalculateProductBrokeragePrice_globalPercent() { + // mock:分销功能已开启,比例 10%,当前用户有分销资格 + TradeConfigDO tradeConfig = new TradeConfigDO(); + tradeConfig.setBrokerageEnabled(true); + tradeConfig.setBrokerageFirstPercent(10); + when(tradeConfigService.getTradeConfig()).thenReturn(tradeConfig); + when(brokerageUserService.getUserBrokerageEnabled(100L)).thenReturn(true); + + // mock:商品未开启独立分销(subCommissionType = false),SKU 售价 1000 分 + ProductSpuRespDTO spu = new ProductSpuRespDTO(); + spu.setSubCommissionType(false); + when(productSpuApi.getSpu(1L)).thenReturn(spu); + + ProductSkuRespDTO sku = new ProductSkuRespDTO(); + sku.setPrice(1000); + when(productSkuApi.getSkuListBySpuId(ListUtil.of(1L))).thenReturn(List.of(sku)); + + // 调用 + AppBrokerageProductPriceRespVO result = brokerageRecordService.calculateProductBrokeragePrice(100L, 1L); + + // 断言:按 10% 比例计算,1000 * 10% = 100 分(向下取整) + assertTrue(result.getEnabled()); + assertEquals(100, result.getBrokerageMinPrice()); + assertEquals(100, result.getBrokerageMaxPrice()); + } + + @Test + public void testCalculateProductBrokeragePrice_subCommissionZeroFixed() { + // mock:分销功能已开启,比例 10%,当前用户有分销资格 + TradeConfigDO tradeConfig = new TradeConfigDO(); + tradeConfig.setBrokerageEnabled(true); + tradeConfig.setBrokerageFirstPercent(10); + when(tradeConfigService.getTradeConfig()).thenReturn(tradeConfig); + when(brokerageUserService.getUserBrokerageEnabled(100L)).thenReturn(true); + + // mock:商品开启独立分销(subCommissionType = true),SKU 固定佣金为 0(商家主动设置) + ProductSpuRespDTO spu = new ProductSpuRespDTO(); + spu.setSubCommissionType(true); + when(productSpuApi.getSpu(2L)).thenReturn(spu); + + ProductSkuRespDTO sku = new ProductSkuRespDTO(); + sku.setPrice(1000); + sku.setFirstBrokeragePrice(0); + when(productSkuApi.getSkuListBySpuId(ListUtil.of(2L))).thenReturn(List.of(sku)); + + // 调用 + AppBrokerageProductPriceRespVO result = brokerageRecordService.calculateProductBrokeragePrice(100L, 2L); + + // 断言:独立分销固定佣金为 0,应返回 0,不得回退到全局比例 + assertTrue(result.getEnabled()); + assertEquals(0, result.getBrokerageMinPrice()); + assertEquals(0, result.getBrokerageMaxPrice()); + } + + @Test + public void testCalculateProductBrokeragePrice_subCommissionEmptySkuList() { + // mock:分销功能已开启,比例 10%,当前用户有分销资格 + TradeConfigDO tradeConfig = new TradeConfigDO(); + tradeConfig.setBrokerageEnabled(true); + tradeConfig.setBrokerageFirstPercent(10); + when(tradeConfigService.getTradeConfig()).thenReturn(tradeConfig); + when(brokerageUserService.getUserBrokerageEnabled(100L)).thenReturn(true); + + // mock:商品开启独立分销,但查询不到 SKU 固定佣金 + ProductSpuRespDTO spu = new ProductSpuRespDTO(); + spu.setSubCommissionType(true); + when(productSpuApi.getSpu(3L)).thenReturn(spu); + when(productSkuApi.getSkuListBySpuId(ListUtil.of(3L))).thenReturn(List.of()); + + // 调用 + AppBrokerageProductPriceRespVO result = brokerageRecordService.calculateProductBrokeragePrice(100L, 3L); + + // 断言:独立分销没有固定佣金时,不回退到全局比例 + assertTrue(result.getEnabled()); + assertEquals(0, result.getBrokerageMinPrice()); + assertEquals(0, result.getBrokerageMaxPrice()); } } diff --git a/yudao-module-mall/yudao-module-trade/src/test/resources/sql/create_tables.sql b/yudao-module-mall/yudao-module-trade/src/test/resources/sql/create_tables.sql index 1d7ed24..61ed058 100644 --- a/yudao-module-mall/yudao-module-trade/src/test/resources/sql/create_tables.sql +++ b/yudao-module-mall/yudao-module-trade/src/test/resources/sql/create_tables.sql @@ -173,7 +173,7 @@ CREATE TABLE IF NOT EXISTS "trade_brokerage_user" ) COMMENT '分销用户'; CREATE TABLE IF NOT EXISTS "trade_brokerage_record" ( - "id" int NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, "user_id" bigint NOT NULL, "biz_id" varchar NOT NULL, "biz_type" varchar NOT NULL, @@ -184,6 +184,8 @@ CREATE TABLE IF NOT EXISTS "trade_brokerage_record" "status" varchar NOT NULL, "frozen_days" int NOT NULL, "unfreeze_time" varchar, + "source_user_level" int, + "source_user_id" bigint, "creator" varchar DEFAULT '', "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, "updater" varchar DEFAULT '', @@ -232,4 +234,4 @@ CREATE TABLE IF NOT EXISTS "trade_delivery_express" "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, "deleted" bit NOT NULL DEFAULT FALSE, PRIMARY KEY ("id") -) COMMENT '佣金提现'; \ No newline at end of file +) COMMENT '佣金提现';