Commit 69121bec6ea3361ac7e803e8d8b6434a89bce4c5

Authored by YunaiV
1 parent bac7cf17

fix(mall): 修复优惠券模板限领数量校验误报库存不足

yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponTemplateServiceImpl.java
... ... @@ -52,6 +52,9 @@ public class CouponTemplateServiceImpl implements CouponTemplateService {
52 52  
53 53 @Override
54 54 public Long createCouponTemplate(CouponTemplateCreateReqVO createReqVO) {
  55 + // 校验发放数量不能小于每人限领数量(仅在 CouponTakeTypeEnum.USER 用户领取时)
  56 + validateTotalCountNotLessThanTakeLimitCount(createReqVO.getTakeType(), createReqVO.getTotalCount(),
  57 + createReqVO.getTakeLimitCount());
55 58 // 校验商品范围
56 59 validateProductScope(createReqVO.getProductScope(), createReqVO.getProductScopeValues());
57 60 // 插入
... ... @@ -66,8 +69,11 @@ public class CouponTemplateServiceImpl implements CouponTemplateService {
66 69 public void updateCouponTemplate(CouponTemplateUpdateReqVO updateReqVO) {
67 70 // 校验存在
68 71 CouponTemplateDO couponTemplate = validateCouponTemplateExists(updateReqVO.getId());
  72 + // 校验发放数量不能小于每人限领数量(仅在 CouponTakeTypeEnum.USER 用户领取时)
  73 + validateTotalCountNotLessThanTakeLimitCount(updateReqVO.getTakeType(), updateReqVO.getTotalCount(),
  74 + updateReqVO.getTakeLimitCount());
69 75 // 校验发放数量不能过小(仅在 CouponTakeTypeEnum.USER 用户领取时)
70   - if (CouponTakeTypeEnum.isUser(couponTemplate.getTakeType())
  76 + if (CouponTakeTypeEnum.isUser(updateReqVO.getTakeType())
71 77 && !isTotalCountUnlimited(updateReqVO.getTotalCount()) // 非不限制总发放数量
72 78 && updateReqVO.getTotalCount() < couponTemplate.getTakeCount()) {
73 79 throw exception(COUPON_TEMPLATE_TOTAL_COUNT_TOO_SMALL, couponTemplate.getTakeCount());
... ... @@ -104,6 +110,16 @@ public class CouponTemplateServiceImpl implements CouponTemplateService {
104 110 return couponTemplate;
105 111 }
106 112  
  113 + private void validateTotalCountNotLessThanTakeLimitCount(Integer takeType, Integer totalCount, Integer takeLimitCount) {
  114 + // 修复 https://gitee.com/yudaocode/yudao-mall-uniapp/issues/IJLP6Q 反馈
  115 + if (CouponTakeTypeEnum.isUser(takeType)
  116 + && !isTakeLimitCountUnlimited(takeLimitCount)
  117 + && !isTotalCountUnlimited(totalCount)
  118 + && takeLimitCount > totalCount) {
  119 + throw exception(COUPON_TEMPLATE_NOT_ENOUGH);
  120 + }
  121 + }
  122 +
107 123 private void validateProductScope(Integer productScope, List<Long> productScopeValues) {
108 124 if (Objects.equals(PromotionProductScopeEnum.SPU.getScope(), productScope)) {
109 125 productSpuApi.validateSpuList(productScopeValues);
... ...
yudao-module-mall/yudao-module-promotion/src/test/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponTemplateServiceImplTest.java
... ... @@ -2,7 +2,10 @@ package cn.iocoder.yudao.module.promotion.service.coupon;
2 2  
3 3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
5 6 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
  7 +import cn.iocoder.yudao.module.product.api.category.ProductCategoryApi;
  8 +import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
6 9 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplateCreateReqVO;
7 10 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplatePageReqVO;
8 11 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplateUpdateReqVO;
... ... @@ -10,21 +13,23 @@ import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
10 13 import cn.iocoder.yudao.module.promotion.dal.mysql.coupon.CouponTemplateMapper;
11 14 import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum;
12 15 import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
  16 +import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
13 17 import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTemplateValidityTypeEnum;
14   -import org.junit.jupiter.api.Disabled;
15 18 import org.junit.jupiter.api.Test;
16 19 import org.springframework.context.annotation.Import;
  20 +import org.springframework.test.context.bean.override.mockito.MockitoBean;
17 21  
18 22 import jakarta.annotation.Resource;
19 23 import java.time.LocalDateTime;
  24 +import java.util.function.Consumer;
20 25  
21   -import static cn.hutool.core.util.RandomUtil.randomEle;
22 26 import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
23 27 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
24 28 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
25 29 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
26 30 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
27 31 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
  32 +import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_TEMPLATE_NOT_ENOUGH;
28 33 import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_TEMPLATE_NOT_EXISTS;
29 34 import static org.junit.jupiter.api.Assertions.*;
30 35  
... ... @@ -33,7 +38,6 @@ import static org.junit.jupiter.api.Assertions.*;
33 38 *
34 39 * @author 芋道源码
35 40 */
36   -@Disabled // TODO 芋艿:后续 fix 补充的单测
37 41 @Import(CouponTemplateServiceImpl.class)
38 42 public class CouponTemplateServiceImplTest extends BaseDbUnitTest {
39 43  
... ... @@ -43,13 +47,26 @@ public class CouponTemplateServiceImplTest extends BaseDbUnitTest {
43 47 @Resource
44 48 private CouponTemplateMapper couponTemplateMapper;
45 49  
  50 + @MockitoBean
  51 + private ProductCategoryApi productCategoryApi;
  52 + @MockitoBean
  53 + private ProductSpuApi productSpuApi;
  54 +
46 55 @Test
47 56 public void testCreateCouponTemplate_success() {
48 57 // 准备参数
49   - CouponTemplateCreateReqVO reqVO = randomPojo(CouponTemplateCreateReqVO.class,
50   - o -> o.setProductScope(randomEle(PromotionProductScopeEnum.values()).getScope())
51   - .setValidityType(randomEle(CouponTemplateValidityTypeEnum.values()).getType())
52   - .setDiscountType(randomEle(PromotionDiscountTypeEnum.values()).getType()));
  58 + CouponTemplateCreateReqVO reqVO = randomPojo(CouponTemplateCreateReqVO.class, o -> {
  59 + o.setTotalCount(10);
  60 + o.setTakeLimitCount(2);
  61 + o.setTakeType(CouponTakeTypeEnum.USER.getType());
  62 + o.setProductScope(PromotionProductScopeEnum.ALL.getScope());
  63 + o.setProductScopeValues(null);
  64 + o.setValidityType(CouponTemplateValidityTypeEnum.TERM.getType());
  65 + o.setFixedStartTerm(0);
  66 + o.setFixedEndTerm(7);
  67 + o.setDiscountType(PromotionDiscountTypeEnum.PRICE.getType());
  68 + o.setDiscountPrice(100);
  69 + });
53 70  
54 71 // 调用
55 72 Long couponTemplateId = couponTemplateService.createCouponTemplate(reqVO);
... ... @@ -63,15 +80,21 @@ public class CouponTemplateServiceImplTest extends BaseDbUnitTest {
63 80 @Test
64 81 public void testUpdateCouponTemplate_success() {
65 82 // mock 数据
66   - CouponTemplateDO dbCouponTemplate = randomPojo(CouponTemplateDO.class);
  83 + CouponTemplateDO dbCouponTemplate = randomCouponTemplateDO();
67 84 couponTemplateMapper.insert(dbCouponTemplate);// @Sql: 先插入出一条存在的数据
68 85 // 准备参数
69 86 CouponTemplateUpdateReqVO reqVO = randomPojo(CouponTemplateUpdateReqVO.class, o -> {
70 87 o.setId(dbCouponTemplate.getId()); // 设置更新的 ID
71   - // 其它通用字段
72   - o.setProductScope(randomEle(PromotionProductScopeEnum.values()).getScope())
73   - .setValidityType(randomEle(CouponTemplateValidityTypeEnum.values()).getType())
74   - .setDiscountType(randomEle(PromotionDiscountTypeEnum.values()).getType());
  88 + o.setTotalCount(10);
  89 + o.setTakeLimitCount(2);
  90 + o.setTakeType(CouponTakeTypeEnum.USER.getType());
  91 + o.setProductScope(PromotionProductScopeEnum.ALL.getScope());
  92 + o.setProductScopeValues(null);
  93 + o.setValidityType(CouponTemplateValidityTypeEnum.TERM.getType());
  94 + o.setFixedStartTerm(0);
  95 + o.setFixedEndTerm(7);
  96 + o.setDiscountType(PromotionDiscountTypeEnum.PRICE.getType());
  97 + o.setDiscountPrice(100);
75 98 });
76 99  
77 100 // 调用
... ... @@ -84,16 +107,60 @@ public class CouponTemplateServiceImplTest extends BaseDbUnitTest {
84 107 @Test
85 108 public void testUpdateCouponTemplate_notExists() {
86 109 // 准备参数
87   - CouponTemplateUpdateReqVO reqVO = randomPojo(CouponTemplateUpdateReqVO.class);
  110 + CouponTemplateUpdateReqVO reqVO = randomPojo(CouponTemplateUpdateReqVO.class, o -> o.setId(randomLongId()));
88 111  
89 112 // 调用, 并断言异常
90 113 assertServiceException(() -> couponTemplateService.updateCouponTemplate(reqVO), COUPON_TEMPLATE_NOT_EXISTS);
91 114 }
92 115  
93 116 @Test
  117 + public void testCreateCouponTemplate_takeLimitCountGreaterThanTotalCount() {
  118 + // 准备参数
  119 + CouponTemplateCreateReqVO reqVO = randomPojo(CouponTemplateCreateReqVO.class, o -> {
  120 + o.setTotalCount(1);
  121 + o.setTakeLimitCount(2);
  122 + o.setTakeType(CouponTakeTypeEnum.USER.getType());
  123 + o.setProductScope(PromotionProductScopeEnum.ALL.getScope());
  124 + o.setProductScopeValues(null);
  125 + o.setValidityType(CouponTemplateValidityTypeEnum.TERM.getType());
  126 + o.setFixedStartTerm(0);
  127 + o.setFixedEndTerm(7);
  128 + o.setDiscountType(PromotionDiscountTypeEnum.PRICE.getType());
  129 + o.setDiscountPrice(100);
  130 + });
  131 +
  132 + // 调用,并断言
  133 + assertServiceException(() -> couponTemplateService.createCouponTemplate(reqVO), COUPON_TEMPLATE_NOT_ENOUGH);
  134 + }
  135 +
  136 + @Test
  137 + public void testUpdateCouponTemplate_takeLimitCountGreaterThanTotalCount() {
  138 + // mock 数据
  139 + CouponTemplateDO couponTemplate = randomCouponTemplateDO();
  140 + couponTemplateMapper.insert(couponTemplate);
  141 + // 准备参数
  142 + CouponTemplateUpdateReqVO reqVO = randomPojo(CouponTemplateUpdateReqVO.class, o -> {
  143 + o.setId(couponTemplate.getId()); // 设置更新的 ID
  144 + o.setTotalCount(1);
  145 + o.setTakeLimitCount(2);
  146 + o.setTakeType(CouponTakeTypeEnum.USER.getType());
  147 + o.setProductScope(PromotionProductScopeEnum.ALL.getScope());
  148 + o.setProductScopeValues(null);
  149 + o.setValidityType(CouponTemplateValidityTypeEnum.TERM.getType());
  150 + o.setFixedStartTerm(0);
  151 + o.setFixedEndTerm(7);
  152 + o.setDiscountType(PromotionDiscountTypeEnum.PRICE.getType());
  153 + o.setDiscountPrice(100);
  154 + });
  155 +
  156 + // 调用,并断言
  157 + assertServiceException(() -> couponTemplateService.updateCouponTemplate(reqVO), COUPON_TEMPLATE_NOT_ENOUGH);
  158 + }
  159 +
  160 + @Test
94 161 public void testDeleteCouponTemplate_success() {
95 162 // mock 数据
96   - CouponTemplateDO dbCouponTemplate = randomPojo(CouponTemplateDO.class);
  163 + CouponTemplateDO dbCouponTemplate = randomCouponTemplateDO();
97 164 couponTemplateMapper.insert(dbCouponTemplate);// @Sql: 先插入出一条存在的数据
98 165 // 准备参数
99 166 Long id = dbCouponTemplate.getId();
... ... @@ -146,4 +213,25 @@ public class CouponTemplateServiceImplTest extends BaseDbUnitTest {
146 213 assertPojoEquals(dbCouponTemplate, pageResult.getList().get(0));
147 214 }
148 215  
  216 + @SafeVarargs
  217 + private static CouponTemplateDO randomCouponTemplateDO(Consumer<CouponTemplateDO>... consumers) {
  218 + Consumer<CouponTemplateDO> consumer = o -> {
  219 + o.setStatus(CommonStatusEnum.ENABLE.getStatus());
  220 + o.setTotalCount(10);
  221 + o.setTakeLimitCount(1);
  222 + o.setTakeType(CouponTakeTypeEnum.USER.getType());
  223 + o.setUsePrice(0);
  224 + o.setProductScope(PromotionProductScopeEnum.ALL.getScope());
  225 + o.setProductScopeValues(null);
  226 + o.setValidityType(CouponTemplateValidityTypeEnum.TERM.getType());
  227 + o.setFixedStartTerm(0);
  228 + o.setFixedEndTerm(7);
  229 + o.setDiscountType(PromotionDiscountTypeEnum.PRICE.getType());
  230 + o.setDiscountPrice(100);
  231 + o.setTakeCount(0);
  232 + o.setUseCount(0);
  233 + };
  234 + return randomPojo(CouponTemplateDO.class, ArrayUtils.append(consumer, consumers));
  235 + }
  236 +
149 237 }
... ...
yudao-module-mall/yudao-module-promotion/src/test/resources/sql/create_tables.sql
... ... @@ -23,13 +23,14 @@ CREATE TABLE IF NOT EXISTS &quot;promotion_coupon_template&quot;
23 23 (
24 24 "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
25 25 "name" varchar NOT NULL,
  26 + "description" varchar,
26 27 "status" int NOT NULL,
27 28 "total_count" int NOT NULL,
28 29 "take_limit_count" int NOT NULL,
29 30 "take_type" int NOT NULL,
30 31 "use_price" int NOT NULL,
31 32 "product_scope" int NOT NULL,
32   - "product_spu_ids" varchar,
  33 + "product_scope_values" varchar,
33 34 "validity_type" int NOT NULL,
34 35 "valid_start_time" datetime,
35 36 "valid_end_time" datetime,
... ... @@ -57,11 +58,11 @@ CREATE TABLE IF NOT EXISTS &quot;promotion_coupon&quot;
57 58 "status" int NOT NULL,
58 59 "user_id" bigint NOT NULL,
59 60 "take_type" int NOT NULL,
60   - "useprice" int NOT NULL,
  61 + "use_price" int NOT NULL,
61 62 "valid_start_time" datetime NOT NULL,
62 63 "valid_end_time" datetime NOT NULL,
63 64 "product_scope" int NOT NULL,
64   - "product_spu_ids" varchar,
  65 + "product_scope_values" varchar,
65 66 "discount_type" int NOT NULL,
66 67 "discount_percent" int,
67 68 "discount_price" int,
... ... @@ -112,6 +113,27 @@ CREATE TABLE IF NOT EXISTS &quot;promotion_discount_activity&quot;
112 113 PRIMARY KEY ("id")
113 114 ) COMMENT '限时折扣活动';
114 115  
  116 +CREATE TABLE IF NOT EXISTS "promotion_discount_product"
  117 +(
  118 + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
  119 + "activity_id" bigint NOT NULL,
  120 + "spu_id" bigint NOT NULL,
  121 + "sku_id" bigint NOT NULL,
  122 + "discount_type" int NOT NULL,
  123 + "discount_percent" int,
  124 + "discount_price" int,
  125 + "activity_name" varchar NOT NULL,
  126 + "activity_status" int NOT NULL,
  127 + "activity_start_time" datetime NOT NULL,
  128 + "activity_end_time" datetime NOT NULL,
  129 + "creator" varchar DEFAULT '',
  130 + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  131 + "updater" varchar DEFAULT '',
  132 + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  133 + "deleted" bit NOT NULL DEFAULT FALSE,
  134 + PRIMARY KEY ("id")
  135 +) COMMENT '限时折扣商品';
  136 +
115 137 CREATE TABLE IF NOT EXISTS "promotion_seckill_activity"
116 138 (
117 139 "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
... ... @@ -253,4 +275,4 @@ CREATE TABLE IF NOT EXISTS &quot;promotion_diy_page&quot;
253 275 "deleted" bit NOT NULL DEFAULT FALSE,
254 276 "tenant_id" bigint NOT NULL,
255 277 PRIMARY KEY ("id")
256   -) COMMENT '装修页面';
257 278 \ No newline at end of file
  279 +) COMMENT '装修页面';
... ...