Commit 16f5d0f5a4f3d2d0cc6da028271ecceb85b1af7f

Authored by YunaiV
1 parent 734236df

trade:增加优惠劵使用、商品库存的扣减

Showing 20 changed files with 424 additions and 129 deletions
yudao-module-mall/yudao-module-product-api/pom.xml
@@ -22,6 +22,13 @@ @@ -22,6 +22,13 @@
22 <groupId>cn.iocoder.boot</groupId> 22 <groupId>cn.iocoder.boot</groupId>
23 <artifactId>yudao-common</artifactId> 23 <artifactId>yudao-common</artifactId>
24 </dependency> 24 </dependency>
  25 +
  26 + <!-- 参数校验 -->
  27 + <dependency>
  28 + <groupId>org.springframework.boot</groupId>
  29 + <artifactId>spring-boot-starter-validation</artifactId>
  30 + <optional>true</optional>
  31 + </dependency>
25 </dependencies> 32 </dependencies>
26 33
27 -</project>  
28 \ No newline at end of file 34 \ No newline at end of file
  35 +</project>
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApi.java
1 package cn.iocoder.yudao.module.product.api.sku; 1 package cn.iocoder.yudao.module.product.api.sku;
2 2
3 -import cn.iocoder.yudao.module.product.api.sku.dto.SkuDecrementStockBatchReqDTO; 3 +import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
4 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO; 4 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
5 5
6 import java.util.Collection; 6 import java.util.Collection;
@@ -31,11 +31,10 @@ public interface ProductSkuApi { @@ -31,11 +31,10 @@ public interface ProductSkuApi {
31 List<ProductSkuRespDTO> getSkuList(Collection<Long> ids); 31 List<ProductSkuRespDTO> getSkuList(Collection<Long> ids);
32 32
33 /** 33 /**
34 - * 批量扣减 SKU 库存 34 + * 更新 SKU 库存
35 * 35 *
36 - * @param batchReqDTO sku库存信息列表 36 + * @param updateStockReqDTO 更新请求
37 */ 37 */
38 - // TODO @LeeYan9: decrementSkuStockBatch? 啊哈, 动名词;  
39 - void decrementStockBatch(SkuDecrementStockBatchReqDTO batchReqDTO); 38 + void updateSkuStock(ProductSkuUpdateStockReqDTO updateStockReqDTO);
40 39
41 } 40 }
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/SkuDecrementStockBatchReqDTO.java renamed to yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/ProductSkuUpdateStockReqDTO.java
@@ -4,45 +4,44 @@ import lombok.AllArgsConstructor; @@ -4,45 +4,44 @@ import lombok.AllArgsConstructor;
4 import lombok.Data; 4 import lombok.Data;
5 import lombok.NoArgsConstructor; 5 import lombok.NoArgsConstructor;
6 6
  7 +import javax.validation.constraints.NotNull;
7 import java.util.List; 8 import java.util.List;
8 9
9 /** 10 /**
10 - * TODO @LeeYan9: 1) 类注释; 2) Product 开头哈; 11 + * 商品 SKU 更新库存 Request DTO
  12 + *
11 * @author LeeYan9 13 * @author LeeYan9
12 * @since 2022-08-26 14 * @since 2022-08-26
13 */ 15 */
14 @Data 16 @Data
15 @NoArgsConstructor 17 @NoArgsConstructor
16 @AllArgsConstructor 18 @AllArgsConstructor
17 -public class SkuDecrementStockBatchReqDTO { 19 +public class ProductSkuUpdateStockReqDTO {
18 20
19 - // TODO @LeeYan9: 参数校验 21 + /**
  22 + * 商品 SKU
  23 + */
  24 + @NotNull(message = "商品 SKU 不能为空")
20 private List<Item> items; 25 private List<Item> items;
21 26
22 @Data 27 @Data
23 public static class Item { 28 public static class Item {
24 29
25 /** 30 /**
26 - * 商品 SPU 编号,自增 31 + * 商品 SKU 编号
27 */ 32 */
28 - // TODO @LeeYan9: 是不是不用传递哈  
29 - private Long productId; 33 + @NotNull(message = "商品 SKU 编号不能为空")
  34 + private Long id;
30 35
31 /** 36 /**
32 - * 商品 SKU 编号,自增 37 + * 库存变化数量
  38 + *
  39 + * 正数:增加库存
  40 + * 负数:扣减库存
33 */ 41 */
34 - private Long skuId; 42 + @NotNull(message = "库存变化数量不能为空")
  43 + private Integer incCount;
35 44
36 - /**  
37 - * 数量  
38 - */  
39 - private Integer count;  
40 -  
41 - }  
42 -  
43 - // TODO @LeeYan9: 构造方法, 是不是可以满足啦  
44 - public static SkuDecrementStockBatchReqDTO of(List<Item> items) {  
45 - return new SkuDecrementStockBatchReqDTO(items);  
46 } 45 }
47 46
48 } 47 }
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApiImpl.java
1 package cn.iocoder.yudao.module.product.api.sku; 1 package cn.iocoder.yudao.module.product.api.sku;
2 2
3 -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; 3 +import cn.hutool.core.collection.CollUtil;
4 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO; 4 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
5 -import cn.iocoder.yudao.module.product.api.sku.dto.SkuDecrementStockBatchReqDTO; 5 +import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
6 import cn.iocoder.yudao.module.product.convert.sku.ProductSkuConvert; 6 import cn.iocoder.yudao.module.product.convert.sku.ProductSkuConvert;
7 import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO; 7 import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
8 -import cn.iocoder.yudao.module.product.dal.mysql.sku.ProductSkuMapper; 8 +import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
9 import org.springframework.stereotype.Service; 9 import org.springframework.stereotype.Service;
10 -import org.springframework.transaction.annotation.Transactional;  
11 import org.springframework.validation.annotation.Validated; 10 import org.springframework.validation.annotation.Validated;
12 11
13 import javax.annotation.Resource; 12 import javax.annotation.Resource;
@@ -25,7 +24,7 @@ import java.util.List; @@ -25,7 +24,7 @@ import java.util.List;
25 public class ProductSkuApiImpl implements ProductSkuApi { 24 public class ProductSkuApiImpl implements ProductSkuApi {
26 25
27 @Resource 26 @Resource
28 - private ProductSkuMapper productSkuMapper; 27 + private ProductSkuService productSkuService;
29 28
30 @Override 29 @Override
31 public ProductSkuRespDTO getSku(Long id) { 30 public ProductSkuRespDTO getSku(Long id) {
@@ -35,18 +34,16 @@ public class ProductSkuApiImpl implements ProductSkuApi { @@ -35,18 +34,16 @@ public class ProductSkuApiImpl implements ProductSkuApi {
35 34
36 @Override 35 @Override
37 public List<ProductSkuRespDTO> getSkuList(Collection<Long> ids) { 36 public List<ProductSkuRespDTO> getSkuList(Collection<Long> ids) {
38 - // TODO TODO LeeYan9: AllEmpty?  
39 - if (CollectionUtils.isAnyEmpty(ids)) { 37 + if (CollUtil.isEmpty(ids)) {
40 return Collections.emptyList(); 38 return Collections.emptyList();
41 } 39 }
42 - List<ProductSkuDO> productSkuDOList = productSkuMapper.selectBatchIds(ids);  
43 - return ProductSkuConvert.INSTANCE.convertList04(productSkuDOList); 40 + List<ProductSkuDO> skus = productSkuService.getSkuList(ids);
  41 + return ProductSkuConvert.INSTANCE.convertList04(skus);
44 } 42 }
45 43
46 @Override 44 @Override
47 - @Transactional(rollbackFor = Exception.class)  
48 - public void decrementStockBatch(SkuDecrementStockBatchReqDTO batchReqDTO) {  
49 - // TODO @LeeYan9: 最好 Service 去 for 循环;  
50 - productSkuMapper.decrementStockBatch(batchReqDTO.getItems()); 45 + public void updateSkuStock(ProductSkuUpdateStockReqDTO updateStockReqDTO) {
  46 + productSkuService.updateSkuStock(updateStockReqDTO);
51 } 47 }
  48 +
52 } 49 }
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/sku/ProductSkuConvert.java
1 package cn.iocoder.yudao.module.product.convert.sku; 1 package cn.iocoder.yudao.module.product.convert.sku;
2 2
3 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO; 3 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
  4 +import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
4 import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuCreateOrUpdateReqVO; 5 import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuCreateOrUpdateReqVO;
5 import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuOptionRespVO; 6 import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuOptionRespVO;
6 import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuRespVO; 7 import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuRespVO;
@@ -9,7 +10,11 @@ import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO; @@ -9,7 +10,11 @@ import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
9 import org.mapstruct.Mapper; 10 import org.mapstruct.Mapper;
10 import org.mapstruct.factory.Mappers; 11 import org.mapstruct.factory.Mappers;
11 12
  13 +import java.util.HashMap;
12 import java.util.List; 14 import java.util.List;
  15 +import java.util.Map;
  16 +
  17 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
13 18
14 /** 19 /**
15 * 商品 SKU Convert 20 * 商品 SKU Convert
@@ -39,4 +44,26 @@ public interface ProductSkuConvert { @@ -39,4 +44,26 @@ public interface ProductSkuConvert {
39 44
40 List<ProductSkuOptionRespVO> convertList05(List<ProductSkuDO> skus); 45 List<ProductSkuOptionRespVO> convertList05(List<ProductSkuDO> skus);
41 46
  47 + /**
  48 + * 获得 SPU 的库存变化 Map
  49 + *
  50 + * @param items SKU 库存变化
  51 + * @param skus SKU 列表
  52 + * @return SPU 的库存变化 Map
  53 + */
  54 + default Map<Long, Integer> convertSpuStockMap(List<ProductSkuUpdateStockReqDTO.Item> items,
  55 + List<ProductSkuDO> skus) {
  56 + Map<Long, Long> skuIdAndSpuIdMap = convertMap(skus, ProductSkuDO::getId, ProductSkuDO::getSpuId); // SKU 与 SKU 编号的 Map 关系
  57 + Map<Long, Integer> spuIdAndStockMap = new HashMap<>(); // SPU 的库存变化 Map 关系
  58 + items.forEach(item -> {
  59 + Long spuId = skuIdAndSpuIdMap.get(item.getId());
  60 + if (spuId == null) {
  61 + return;
  62 + }
  63 + Integer stock = spuIdAndStockMap.getOrDefault(spuId, 0) + item.getIncCount();
  64 + spuIdAndStockMap.put(spuId, stock);
  65 + });
  66 + return spuIdAndStockMap;
  67 + }
  68 +
42 } 69 }
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/sku/ProductSkuMapper.java
1 package cn.iocoder.yudao.module.product.dal.mysql.sku; 1 package cn.iocoder.yudao.module.product.dal.mysql.sku;
2 2
  3 +import cn.hutool.core.lang.Assert;
3 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
4 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
5 -import cn.iocoder.yudao.module.product.api.sku.dto.SkuDecrementStockBatchReqDTO;  
6 import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO; 6 import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
7 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 7 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
8 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; 8 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
@@ -23,21 +23,37 @@ public interface ProductSkuMapper extends BaseMapperX&lt;ProductSkuDO&gt; { @@ -23,21 +23,37 @@ public interface ProductSkuMapper extends BaseMapperX&lt;ProductSkuDO&gt; {
23 } 23 }
24 24
25 default void deleteBySpuId(Long spuId) { 25 default void deleteBySpuId(Long spuId) {
26 - delete(new LambdaQueryWrapperX<ProductSkuDO>()  
27 - .eqIfPresent(ProductSkuDO::getSpuId, spuId)); 26 + delete(new LambdaQueryWrapperX<ProductSkuDO>().eq(ProductSkuDO::getSpuId, spuId));
28 } 27 }
29 28
30 - default void decrementStockBatch(List<SkuDecrementStockBatchReqDTO.Item> items) {  
31 - for (SkuDecrementStockBatchReqDTO.Item item : items) {  
32 - // 扣减库存 cas 逻辑  
33 - LambdaUpdateWrapper<ProductSkuDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<ProductSkuDO>()  
34 - .setSql(" stock = stock-" + item.getCount())  
35 - .eq(ProductSkuDO::getSpuId, item.getProductId())  
36 - .eq(ProductSkuDO::getId, item.getSkuId())  
37 - .ge(ProductSkuDO::getStock, item.getCount());  
38 - // 执行  
39 - this.update(null, lambdaUpdateWrapper);  
40 - } 29 + /**
  30 + * 更新 SKU 库存(增加)
  31 + *
  32 + * @param id 编号
  33 + * @param incrCount 增加库存(正数)
  34 + */
  35 + default void updateStockIncr(Long id, Integer incrCount) {
  36 + Assert.isTrue(incrCount > 0);
  37 + LambdaUpdateWrapper<ProductSkuDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<ProductSkuDO>()
  38 + .setSql(" stock = stock + " + incrCount)
  39 + .eq(ProductSkuDO::getId, id);
  40 + update(null, lambdaUpdateWrapper);
  41 + }
  42 +
  43 + /**
  44 + * 更新 SKU 库存(减少)
  45 + *
  46 + * @param id 编号
  47 + * @param incrCount 减少库存(负数)
  48 + * @return 更新条数
  49 + */
  50 + default int updateStockDecr(Long id, Integer incrCount) {
  51 + Assert.isTrue(incrCount < 0);
  52 + LambdaUpdateWrapper<ProductSkuDO> updateWrapper = new LambdaUpdateWrapper<ProductSkuDO>()
  53 + .setSql(" stock = stock + " + incrCount) // 负数,所以使用 + 号
  54 + .eq(ProductSkuDO::getId, id)
  55 + .ge(ProductSkuDO::getStock, -incrCount); // cas 逻辑
  56 + return update(null, updateWrapper);
41 } 57 }
42 58
43 default List<ProductSkuDO> selectListByAlarmStock(){ 59 default List<ProductSkuDO> selectListByAlarmStock(){
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/spu/ProductSpuMapper.java
@@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; @@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6 import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuPageReqVO; 6 import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuPageReqVO;
7 import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO; 7 import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
  8 +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
8 import org.apache.ibatis.annotations.Mapper; 9 import org.apache.ibatis.annotations.Mapper;
9 10
10 import java.util.Set; 11 import java.util.Set;
@@ -43,4 +44,17 @@ public interface ProductSpuMapper extends BaseMapperX&lt;ProductSpuDO&gt; { @@ -43,4 +44,17 @@ public interface ProductSpuMapper extends BaseMapperX&lt;ProductSpuDO&gt; {
43 .orderByDesc(ProductSpuDO::getSort)); 44 .orderByDesc(ProductSpuDO::getSort));
44 } 45 }
45 46
  47 + /**
  48 + * 更新商品 SPU 库存
  49 + *
  50 + * @param id 商品 SPU 编号
  51 + * @param incrCount 增加的库存数量
  52 + */
  53 + default void updateStock(Long id, Integer incrCount) {
  54 + LambdaUpdateWrapper<ProductSpuDO> updateWrapper = new LambdaUpdateWrapper<ProductSpuDO>()
  55 + .setSql(" total_stock = total_stock +" + incrCount) // 负数,所以使用 + 号
  56 + .eq(ProductSpuDO::getId, id);
  57 + update(null, updateWrapper);
  58 + }
  59 +
46 } 60 }
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuService.java
1 package cn.iocoder.yudao.module.product.service.sku; 1 package cn.iocoder.yudao.module.product.service.sku;
2 2
  3 +import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
3 import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuCreateOrUpdateReqVO; 4 import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuCreateOrUpdateReqVO;
4 import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO; 5 import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
5 6
@@ -64,7 +65,16 @@ public interface ProductSkuService { @@ -64,7 +65,16 @@ public interface ProductSkuService {
64 * @param spuId SPU 编码 65 * @param spuId SPU 编码
65 * @param skus SKU 的集合 66 * @param skus SKU 的集合
66 */ 67 */
67 - void updateProductSkus(Long spuId, List<ProductSkuCreateOrUpdateReqVO> skus); 68 + void updateSkus(Long spuId, List<ProductSkuCreateOrUpdateReqVO> skus);
  69 +
  70 + /**
  71 + * 更新 SKU 库存(增量)
  72 + *
  73 + * 如果更新的库存不足,会抛出异常
  74 + *
  75 + * @param updateStockReqDTO 更行请求
  76 + */
  77 + void updateSkuStock(ProductSkuUpdateStockReqDTO updateStockReqDTO);
68 78
69 /** 79 /**
70 * 获得商品 sku 集合 80 * 获得商品 sku 集合
@@ -96,4 +106,5 @@ public interface ProductSkuService { @@ -96,4 +106,5 @@ public interface ProductSkuService {
96 */ 106 */
97 List<ProductSkuDO> getSkusByAlarmStock(); 107 List<ProductSkuDO> getSkusByAlarmStock();
98 108
  109 +
99 } 110 }
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceImpl.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.product.service.sku; @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.product.service.sku;
2 2
3 import cn.hutool.core.util.ObjectUtil; 3 import cn.hutool.core.util.ObjectUtil;
4 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; 4 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
  5 +import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
5 import cn.iocoder.yudao.module.product.controller.admin.property.vo.property.ProductPropertyRespVO; 6 import cn.iocoder.yudao.module.product.controller.admin.property.vo.property.ProductPropertyRespVO;
6 import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueRespVO; 7 import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueRespVO;
7 import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuBaseVO; 8 import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuBaseVO;
@@ -13,6 +14,8 @@ import cn.iocoder.yudao.module.product.enums.ErrorCodeConstants; @@ -13,6 +14,8 @@ import cn.iocoder.yudao.module.product.enums.ErrorCodeConstants;
13 import cn.iocoder.yudao.module.product.enums.spu.ProductSpuSpecTypeEnum; 14 import cn.iocoder.yudao.module.product.enums.spu.ProductSpuSpecTypeEnum;
14 import cn.iocoder.yudao.module.product.service.property.ProductPropertyService; 15 import cn.iocoder.yudao.module.product.service.property.ProductPropertyService;
15 import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService; 16 import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService;
  17 +import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
  18 +import org.springframework.context.annotation.Lazy;
16 import org.springframework.stereotype.Service; 19 import org.springframework.stereotype.Service;
17 import org.springframework.transaction.annotation.Transactional; 20 import org.springframework.transaction.annotation.Transactional;
18 import org.springframework.validation.annotation.Validated; 21 import org.springframework.validation.annotation.Validated;
@@ -22,6 +25,7 @@ import java.util.*; @@ -22,6 +25,7 @@ import java.util.*;
22 import java.util.stream.Collectors; 25 import java.util.stream.Collectors;
23 26
24 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 27 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  28 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
25 import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.*; 29 import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.*;
26 30
27 /** 31 /**
@@ -37,15 +41,17 @@ public class ProductSkuServiceImpl implements ProductSkuService { @@ -37,15 +41,17 @@ public class ProductSkuServiceImpl implements ProductSkuService {
37 private ProductSkuMapper productSkuMapper; 41 private ProductSkuMapper productSkuMapper;
38 42
39 @Resource 43 @Resource
  44 + @Lazy // 循环依赖,避免报错
  45 + private ProductSpuService productSpuService;
  46 + @Resource
40 private ProductPropertyService productPropertyService; 47 private ProductPropertyService productPropertyService;
41 -  
42 @Resource 48 @Resource
43 private ProductPropertyValueService productPropertyValueService; 49 private ProductPropertyValueService productPropertyValueService;
44 50
45 @Override 51 @Override
46 public void deleteSku(Long id) { 52 public void deleteSku(Long id) {
47 // 校验存在 53 // 校验存在
48 - this.validateSkuExists(id); 54 + validateSkuExists(id);
49 // 删除 55 // 删除
50 productSkuMapper.deleteById(id); 56 productSkuMapper.deleteById(id);
51 } 57 }
@@ -89,7 +95,7 @@ public class ProductSkuServiceImpl implements ProductSkuService { @@ -89,7 +95,7 @@ public class ProductSkuServiceImpl implements ProductSkuService {
89 // 2. 校验,一个 SKU 下,没有重复的规格。校验方式是,遍历每个 SKU ,看看是否有重复的规格 propertyId 95 // 2. 校验,一个 SKU 下,没有重复的规格。校验方式是,遍历每个 SKU ,看看是否有重复的规格 propertyId
90 Map<Long, ProductPropertyValueRespVO> propertyValueMap = CollectionUtils.convertMap(productPropertyValueService.getPropertyValueListByPropertyId(new ArrayList<>(propertyIds)), ProductPropertyValueRespVO::getId); 96 Map<Long, ProductPropertyValueRespVO> propertyValueMap = CollectionUtils.convertMap(productPropertyValueService.getPropertyValueListByPropertyId(new ArrayList<>(propertyIds)), ProductPropertyValueRespVO::getId);
91 skus.forEach(sku -> { 97 skus.forEach(sku -> {
92 - Set<Long> skuPropertyIds = CollectionUtils.convertSet(sku.getProperties(), propertyItem -> propertyValueMap.get(propertyItem.getValueId()).getPropertyId()); 98 + Set<Long> skuPropertyIds = convertSet(sku.getProperties(), propertyItem -> propertyValueMap.get(propertyItem.getValueId()).getPropertyId());
93 if (skuPropertyIds.size() != sku.getProperties().size()) { 99 if (skuPropertyIds.size() != sku.getProperties().size()) {
94 throw exception(SKU_PROPERTIES_DUPLICATED); 100 throw exception(SKU_PROPERTIES_DUPLICATED);
95 } 101 }
@@ -106,7 +112,7 @@ public class ProductSkuServiceImpl implements ProductSkuService { @@ -106,7 +112,7 @@ public class ProductSkuServiceImpl implements ProductSkuService {
106 // 4. 最后校验,每个 Sku 之间不是重复的 112 // 4. 最后校验,每个 Sku 之间不是重复的
107 Set<Set<Long>> skuAttrValues = new HashSet<>(); // 每个元素,都是一个 Sku 的 attrValueId 集合。这样,通过最外层的 Set ,判断是否有重复的. 113 Set<Set<Long>> skuAttrValues = new HashSet<>(); // 每个元素,都是一个 Sku 的 attrValueId 集合。这样,通过最外层的 Set ,判断是否有重复的.
108 for (ProductSkuCreateOrUpdateReqVO sku : skus) { 114 for (ProductSkuCreateOrUpdateReqVO sku : skus) {
109 - if (!skuAttrValues.add(CollectionUtils.convertSet(sku.getProperties(), ProductSkuBaseVO.Property::getValueId))) { // 添加失败,说明重复 115 + if (!skuAttrValues.add(convertSet(sku.getProperties(), ProductSkuBaseVO.Property::getValueId))) { // 添加失败,说明重复
110 throw exception(ErrorCodeConstants.SPU_SKU_NOT_DUPLICATE); 116 throw exception(ErrorCodeConstants.SPU_SKU_NOT_DUPLICATE);
111 } 117 }
112 } 118 }
@@ -142,7 +148,7 @@ public class ProductSkuServiceImpl implements ProductSkuService { @@ -142,7 +148,7 @@ public class ProductSkuServiceImpl implements ProductSkuService {
142 148
143 @Override 149 @Override
144 @Transactional 150 @Transactional
145 - public void updateProductSkus(Long spuId, List<ProductSkuCreateOrUpdateReqVO> skus) { 151 + public void updateSkus(Long spuId, List<ProductSkuCreateOrUpdateReqVO> skus) {
146 // 查询 SPU 下已经存在的 SKU 的集合 152 // 查询 SPU 下已经存在的 SKU 的集合
147 List<ProductSkuDO> existsSkus = productSkuMapper.selectListBySpuId(spuId); 153 List<ProductSkuDO> existsSkus = productSkuMapper.selectListBySpuId(spuId);
148 // 构建规格与 SKU 的映射关系; 154 // 构建规格与 SKU 的映射关系;
@@ -192,14 +198,27 @@ public class ProductSkuServiceImpl implements ProductSkuService { @@ -192,14 +198,27 @@ public class ProductSkuServiceImpl implements ProductSkuService {
192 } 198 }
193 } 199 }
194 200
195 - public static void main(String[] args) {  
196 - List<Integer> ids = new ArrayList<>();  
197 - ids.add(1);  
198 -  
199 - List<Integer> ids2 = new ArrayList<>();  
200 - ids2.add(2); 201 + @Override
  202 + @Transactional(rollbackFor = Exception.class)
  203 + public void updateSkuStock(ProductSkuUpdateStockReqDTO updateStockReqDTO) {
  204 + // 更新 SKU 库存
  205 + updateStockReqDTO.getItems().forEach(item -> {
  206 + if (item.getIncCount() > 0) {
  207 + productSkuMapper.updateStockIncr(item.getId(), item.getIncCount());
  208 + } else if (item.getIncCount() < 0) {
  209 + int updateStockIncr = productSkuMapper.updateStockDecr(item.getId(), item.getIncCount());
  210 + if (updateStockIncr == 0) {
  211 + throw exception(SKU_STOCK_NOT_ENOUGH);
  212 + }
  213 + }
  214 + });
201 215
202 - System.out.println(ids.equals(ids2)); 216 + // 更新 SPU 库存
  217 + List<ProductSkuDO> skus = productSkuMapper.selectBatchIds(
  218 + convertSet(updateStockReqDTO.getItems(), ProductSkuUpdateStockReqDTO.Item::getId));
  219 + Map<Long, Integer> spuStockIncrCounts = ProductSkuConvert.INSTANCE.convertSpuStockMap(
  220 + updateStockReqDTO.getItems(), skus);
  221 + productSpuService.updateSpuStock(spuStockIncrCounts);
203 } 222 }
204 223
205 } 224 }
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuService.java
@@ -99,4 +99,11 @@ public interface ProductSpuService { @@ -99,4 +99,11 @@ public interface ProductSpuService {
99 */ 99 */
100 PageResult<AppSpuPageRespVO> getSpuPage(AppSpuPageReqVO pageReqVO); 100 PageResult<AppSpuPageRespVO> getSpuPage(AppSpuPageReqVO pageReqVO);
101 101
  102 + /**
  103 + * 更新商品 SPU 库存(增量)
  104 + *
  105 + * @param stockIncrCounts SPU 编号与库存变化(增量)的映射
  106 + */
  107 + void updateSpuStock(Map<Long, Integer> stockIncrCounts);
  108 +
102 } 109 }
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java
@@ -24,6 +24,7 @@ import cn.iocoder.yudao.module.product.service.category.ProductCategoryService; @@ -24,6 +24,7 @@ import cn.iocoder.yudao.module.product.service.category.ProductCategoryService;
24 import cn.iocoder.yudao.module.product.service.property.ProductPropertyService; 24 import cn.iocoder.yudao.module.product.service.property.ProductPropertyService;
25 import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService; 25 import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService;
26 import cn.iocoder.yudao.module.product.service.sku.ProductSkuService; 26 import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
  27 +import org.springframework.context.annotation.Lazy;
27 import org.springframework.stereotype.Service; 28 import org.springframework.stereotype.Service;
28 import org.springframework.transaction.annotation.Transactional; 29 import org.springframework.transaction.annotation.Transactional;
29 import org.springframework.validation.annotation.Validated; 30 import org.springframework.validation.annotation.Validated;
@@ -51,14 +52,12 @@ public class ProductSpuServiceImpl implements ProductSpuService { @@ -51,14 +52,12 @@ public class ProductSpuServiceImpl implements ProductSpuService {
51 private ProductCategoryService categoryService; 52 private ProductCategoryService categoryService;
52 53
53 @Resource 54 @Resource
  55 + @Lazy // 循环依赖,避免报错
54 private ProductSkuService productSkuService; 56 private ProductSkuService productSkuService;
55 -  
56 @Resource 57 @Resource
57 private ProductPropertyService productPropertyService; 58 private ProductPropertyService productPropertyService;
58 -  
59 @Resource 59 @Resource
60 private ProductPropertyValueService productPropertyValueService; 60 private ProductPropertyValueService productPropertyValueService;
61 -  
62 @Resource 61 @Resource
63 private ProductBrandService brandService; 62 private ProductBrandService brandService;
64 63
@@ -106,7 +105,7 @@ public class ProductSpuServiceImpl implements ProductSpuService { @@ -106,7 +105,7 @@ public class ProductSpuServiceImpl implements ProductSpuService {
106 updateObj.setTotalStock(CollectionUtils.getSumValue(skuCreateReqList, ProductSkuCreateOrUpdateReqVO::getStock, Integer::sum)); 105 updateObj.setTotalStock(CollectionUtils.getSumValue(skuCreateReqList, ProductSkuCreateOrUpdateReqVO::getStock, Integer::sum));
107 productSpuMapper.updateById(updateObj); 106 productSpuMapper.updateById(updateObj);
108 // 批量更新 SKU 107 // 批量更新 SKU
109 - productSkuService.updateProductSkus(updateObj.getId(), updateReqVO.getSkus()); 108 + productSkuService.updateSkus(updateObj.getId(), updateReqVO.getSkus());
110 } 109 }
111 110
112 @Override 111 @Override
@@ -210,4 +209,10 @@ public class ProductSpuServiceImpl implements ProductSpuService { @@ -210,4 +209,10 @@ public class ProductSpuServiceImpl implements ProductSpuService {
210 return pageResult; 209 return pageResult;
211 } 210 }
212 211
  212 + @Override
  213 + @Transactional(rollbackFor = Exception.class)
  214 + public void updateSpuStock(Map<Long, Integer> stockIncrCounts) {
  215 + stockIncrCounts.forEach((id, incCount) -> productSpuMapper.updateStock(id, incCount));
  216 + }
  217 +
213 } 218 }
yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.product.service.sku;
  2 +
  3 +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
  4 +import cn.iocoder.yudao.framework.test.core.util.AssertUtils;
  5 +import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
  6 +import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
  7 +import cn.iocoder.yudao.module.product.dal.mysql.sku.ProductSkuMapper;
  8 +import cn.iocoder.yudao.module.product.service.property.ProductPropertyService;
  9 +import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService;
  10 +import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
  11 +import org.junit.jupiter.api.Test;
  12 +import org.springframework.boot.test.mock.mockito.MockBean;
  13 +import org.springframework.context.annotation.Import;
  14 +
  15 +import javax.annotation.Resource;
  16 +
  17 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
  18 +import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_STOCK_NOT_ENOUGH;
  19 +import static java.util.Collections.singletonList;
  20 +import static org.junit.jupiter.api.Assertions.assertEquals;
  21 +import static org.mockito.ArgumentMatchers.argThat;
  22 +import static org.mockito.Mockito.verify;
  23 +
  24 +/**
  25 + * {@link ProductSkuServiceImpl} 的单元测试
  26 + *
  27 + * @author 芋道源码
  28 + */
  29 +@Import(ProductSkuServiceImpl.class)
  30 +public class ProductSkuServiceTest extends BaseDbUnitTest {
  31 +
  32 + @Resource
  33 + private ProductSkuService productSkuService;
  34 +
  35 + @Resource
  36 + private ProductSkuMapper productSkuMapper;
  37 +
  38 + @MockBean
  39 + private ProductSpuService productSpuService;
  40 + @MockBean
  41 + private ProductPropertyService productPropertyService;
  42 + @MockBean
  43 + private ProductPropertyValueService productPropertyValueService;
  44 +
  45 + @Test
  46 + public void testUpdateSkuStock_incrSuccess() {
  47 + // 准备参数
  48 + ProductSkuUpdateStockReqDTO updateStockReqDTO = new ProductSkuUpdateStockReqDTO()
  49 + .setItems(singletonList(new ProductSkuUpdateStockReqDTO.Item().setId(1L).setIncCount(10)));
  50 + // mock 数据
  51 + productSkuMapper.insert(randomPojo(ProductSkuDO.class, o -> o.setId(1L).setSpuId(10L).setStock(20)));
  52 +
  53 + // 调用
  54 + productSkuService.updateSkuStock(updateStockReqDTO);
  55 + // 断言
  56 + ProductSkuDO sku = productSkuMapper.selectById(1L);
  57 + assertEquals(sku.getStock(), 30);
  58 + verify(productSpuService).updateSpuStock(argThat(spuStockIncrCounts -> {
  59 + assertEquals(spuStockIncrCounts.size(), 1);
  60 + assertEquals(spuStockIncrCounts.get(10L), 10);
  61 + return true;
  62 + }));
  63 + }
  64 +
  65 + @Test
  66 + public void testUpdateSkuStock_decrSuccess() {
  67 + // 准备参数
  68 + ProductSkuUpdateStockReqDTO updateStockReqDTO = new ProductSkuUpdateStockReqDTO()
  69 + .setItems(singletonList(new ProductSkuUpdateStockReqDTO.Item().setId(1L).setIncCount(-10)));
  70 + // mock 数据
  71 + productSkuMapper.insert(randomPojo(ProductSkuDO.class, o -> o.setId(1L).setSpuId(10L).setStock(20)));
  72 +
  73 + // 调用
  74 + productSkuService.updateSkuStock(updateStockReqDTO);
  75 + // 断言
  76 + ProductSkuDO sku = productSkuMapper.selectById(1L);
  77 + assertEquals(sku.getStock(), 10);
  78 + verify(productSpuService).updateSpuStock(argThat(spuStockIncrCounts -> {
  79 + assertEquals(spuStockIncrCounts.size(), 1);
  80 + assertEquals(spuStockIncrCounts.get(10L), -10);
  81 + return true;
  82 + }));
  83 + }
  84 +
  85 + @Test
  86 + public void testUpdateSkuStock_decrFail() {
  87 + // 准备参数
  88 + ProductSkuUpdateStockReqDTO updateStockReqDTO = new ProductSkuUpdateStockReqDTO()
  89 + .setItems(singletonList(new ProductSkuUpdateStockReqDTO.Item().setId(1L).setIncCount(-30)));
  90 + // mock 数据
  91 + productSkuMapper.insert(randomPojo(ProductSkuDO.class, o -> o.setId(1L).setSpuId(10L).setStock(20)));
  92 +
  93 + // 调用并断言
  94 + AssertUtils.assertServiceException(() -> productSkuService.updateSkuStock(updateStockReqDTO),
  95 + SKU_STOCK_NOT_ENOUGH);
  96 + }
  97 +
  98 +}
yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/sku/SkuServiceImplTest.java
@@ -13,6 +13,7 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; @@ -13,6 +13,7 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
13 import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS; 13 import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS;
14 import static org.junit.jupiter.api.Assertions.assertNull; 14 import static org.junit.jupiter.api.Assertions.assertNull;
15 15
  16 +// TODO 芋艿:整合到 {@link ProductSkuServiceTest} 中
16 /** 17 /**
17 * {@link ProductSkuServiceImpl} 的单元测试类 18 * {@link ProductSkuServiceImpl} 的单元测试类
18 * 19 *
yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImplTest.java
1 package cn.iocoder.yudao.module.product.service.spu; 1 package cn.iocoder.yudao.module.product.service.spu;
2 2
3 import cn.hutool.core.date.DateUtil; 3 import cn.hutool.core.date.DateUtil;
  4 +import cn.hutool.core.map.MapUtil;
4 import cn.hutool.core.util.RandomUtil; 5 import cn.hutool.core.util.RandomUtil;
5 import cn.iocoder.yudao.framework.common.exception.ServiceException; 6 import cn.iocoder.yudao.framework.common.exception.ServiceException;
6 import cn.iocoder.yudao.framework.common.pojo.PageResult; 7 import cn.iocoder.yudao.framework.common.pojo.PageResult;
@@ -39,6 +40,7 @@ import java.util.stream.Stream; @@ -39,6 +40,7 @@ import java.util.stream.Stream;
39 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; 40 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
40 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; 41 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
41 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; 42 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
  43 +import static org.junit.jupiter.api.Assertions.assertEquals;
42 44
43 // TODO @芋艿:review 下单元测试 45 // TODO @芋艿:review 下单元测试
44 46
@@ -56,19 +58,14 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest { @@ -56,19 +58,14 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest {
56 @Resource 58 @Resource
57 private ProductSpuMapper productSpuMapper; 59 private ProductSpuMapper productSpuMapper;
58 60
59 -  
60 @MockBean 61 @MockBean
61 private ProductSkuServiceImpl productSkuService; 62 private ProductSkuServiceImpl productSkuService;
62 -  
63 @MockBean 63 @MockBean
64 private ProductCategoryServiceImpl categoryService; 64 private ProductCategoryServiceImpl categoryService;
65 -  
66 @MockBean 65 @MockBean
67 private ProductBrandServiceImpl brandService; 66 private ProductBrandServiceImpl brandService;
68 -  
69 @MockBean 67 @MockBean
70 private ProductPropertyService productPropertyService; 68 private ProductPropertyService productPropertyService;
71 -  
72 @MockBean 69 @MockBean
73 private ProductPropertyValueService productPropertyValueService; 70 private ProductPropertyValueService productPropertyValueService;
74 71
@@ -228,7 +225,7 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest { @@ -228,7 +225,7 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest {
228 225
229 PageResult<Object> result = PageResult.empty(); 226 PageResult<Object> result = PageResult.empty();
230 Assertions.assertIterableEquals(result.getList(), spuPage.getList()); 227 Assertions.assertIterableEquals(result.getList(), spuPage.getList());
231 - Assertions.assertEquals(spuPage.getTotal(), result.getTotal()); 228 + assertEquals(spuPage.getTotal(), result.getTotal());
232 } 229 }
233 230
234 @Test 231 @Test
@@ -277,7 +274,7 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest { @@ -277,7 +274,7 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest {
277 274
278 PageResult<ProductSpuRespVO> result = ProductSpuConvert.INSTANCE.convertPage(productSpuMapper.selectPage(productSpuPageReqVO, alarmStockSpuIds)); 275 PageResult<ProductSpuRespVO> result = ProductSpuConvert.INSTANCE.convertPage(productSpuMapper.selectPage(productSpuPageReqVO, alarmStockSpuIds));
279 Assertions.assertIterableEquals(result.getList(), spuPage.getList()); 276 Assertions.assertIterableEquals(result.getList(), spuPage.getList());
280 - Assertions.assertEquals(spuPage.getTotal(), result.getTotal()); 277 + assertEquals(spuPage.getTotal(), result.getTotal());
281 } 278 }
282 279
283 @Test 280 @Test
@@ -328,7 +325,7 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest { @@ -328,7 +325,7 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest {
328 PageResult<ProductSpuRespVO> spuPage = productSpuService.getSpuPage(productSpuPageReqVO); 325 PageResult<ProductSpuRespVO> spuPage = productSpuService.getSpuPage(productSpuPageReqVO);
329 326
330 PageResult<ProductSpuRespVO> result = ProductSpuConvert.INSTANCE.convertPage(productSpuMapper.selectPage(productSpuPageReqVO, (Set<Long>) null)); 327 PageResult<ProductSpuRespVO> result = ProductSpuConvert.INSTANCE.convertPage(productSpuMapper.selectPage(productSpuPageReqVO, (Set<Long>) null));
331 - Assertions.assertEquals(result, spuPage); 328 + assertEquals(result, spuPage);
332 } 329 }
333 330
334 @Test 331 @Test
@@ -354,7 +351,7 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest { @@ -354,7 +351,7 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest {
354 .collect(Collectors.toList()); 351 .collect(Collectors.toList());
355 352
356 Assertions.assertIterableEquals(collect, spuPage.getList()); 353 Assertions.assertIterableEquals(collect, spuPage.getList());
357 - Assertions.assertEquals(spuPage.getTotal(), result.getTotal()); 354 + assertEquals(spuPage.getTotal(), result.getTotal());
358 } 355 }
359 356
360 357
@@ -389,4 +386,19 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest { @@ -389,4 +386,19 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest {
389 return res; 386 return res;
390 } 387 }
391 388
  389 + @Test
  390 + public void testUpdateSpuStock() {
  391 + // 准备参数
  392 + Map<Long, Integer> stockIncrCounts = MapUtil.builder(1L, 10).put(2L, -20).build();
  393 + // mock 方法(数据)
  394 + productSpuMapper.insert(randomPojo(ProductSpuDO.class, o -> o.setId(1L).setTotalStock(20)));
  395 + productSpuMapper.insert(randomPojo(ProductSpuDO.class, o -> o.setId(2L).setTotalStock(30)));
  396 +
  397 + // 调用
  398 + productSpuService.updateSpuStock(stockIncrCounts);
  399 + // 断言
  400 + assertEquals(productSpuService.getSpu(1L).getTotalStock(), 30);
  401 + assertEquals(productSpuService.getSpu(2L).getTotalStock(), 10);
  402 + }
  403 +
392 } 404 }
yudao-module-mall/yudao-module-product-biz/src/test/resources/sql/create_tables.sql
1 CREATE TABLE IF NOT EXISTS `product_sku` ( 1 CREATE TABLE IF NOT EXISTS `product_sku` (
2 -`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',  
3 -`spu_id` bigint NOT NULL COMMENT 'spu编号',  
4 -`tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户编号',  
5 -`name` varchar(128) DEFAULT NULL COMMENT '商品 SKU 名字',  
6 -`properties` varchar(128) DEFAULT NULL COMMENT '规格值数组-json格式, [{propertId: , valueId: }, {propertId: , valueId: }]',  
7 -`price` int NOT NULL DEFAULT '-1' COMMENT '销售价格,单位:分',  
8 -`market_price` int DEFAULT NULL COMMENT '市场价',  
9 -`cost_price` int NOT NULL DEFAULT '-1' COMMENT '成本价,单位: 分',  
10 -`pic_url` varchar(128) NOT NULL COMMENT '图片地址',  
11 -`stock` int DEFAULT NULL COMMENT '库存',  
12 -`warn_stock` int DEFAULT NULL COMMENT '预警库存',  
13 -`volume` double DEFAULT NULL COMMENT '商品体积',  
14 -`weight` double DEFAULT NULL COMMENT '商品重量',  
15 -`bar_code` varchar(64) DEFAULT NULL COMMENT '条形码',  
16 -`status` tinyint DEFAULT NULL COMMENT '状态: 0-正常 1-禁用',  
17 -`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',  
18 -`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',  
19 -`creator` varchar(64) DEFAULT NULL COMMENT '创建人',  
20 -`updater` double DEFAULT NULL COMMENT '更新人',  
21 -`deleted` bit(1) NOT NULL DEFAULT 0 COMMENT '是否删除',  
22 -PRIMARY KEY (`id`) 2 + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
  3 + `spu_id` bigint NOT NULL COMMENT 'spu编号',
  4 + `tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户编号',
  5 + `name` varchar DEFAULT NULL COMMENT '商品 SKU 名字',
  6 + `properties` varchar DEFAULT NULL COMMENT '规格值数组-json格式, [{propertId: , valueId: }, {propertId: , valueId: }]',
  7 + `price` int NOT NULL DEFAULT '-1' COMMENT '销售价格,单位:分',
  8 + `market_price` int DEFAULT NULL COMMENT '市场价',
  9 + `cost_price` int NOT NULL DEFAULT '-1' COMMENT '成本价,单位: 分',
  10 + `pic_url` varchar NOT NULL COMMENT '图片地址',
  11 + `stock` int DEFAULT NULL COMMENT '库存',
  12 + `warn_stock` int DEFAULT NULL COMMENT '预警库存',
  13 + `volume` double DEFAULT NULL COMMENT '商品体积',
  14 + `weight` double DEFAULT NULL COMMENT '商品重量',
  15 + `bar_code` varchar DEFAULT NULL COMMENT '条形码',
  16 + `status` tinyint DEFAULT NULL COMMENT '状态: 0-正常 1-禁用',
  17 + `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  18 + `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  19 + `creator` varchar DEFAULT NULL COMMENT '创建人',
  20 + `updater` varchar DEFAULT NULL COMMENT '更新人',
  21 + `deleted` bit(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
  22 + PRIMARY KEY (`id`)
23 ) COMMENT '商品sku'; 23 ) COMMENT '商品sku';
24 24
25 25
26 CREATE TABLE IF NOT EXISTS `product_spu` ( 26 CREATE TABLE IF NOT EXISTS `product_spu` (
27 -`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',  
28 -`tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户编号',  
29 -`brand_id` bigint DEFAULT NULL COMMENT '商品品牌编号',  
30 -`category_id` bigint NOT NULL COMMENT '分类id',  
31 -`spec_type` int NOT NULL COMMENT '规格类型:0 单规格 1 多规格',  
32 -`code` varchar(128) DEFAULT NULL COMMENT '商品编码',  
33 -`name` varchar(128) NOT NULL COMMENT '商品名称',  
34 -`sell_point` varchar(128) DEFAULT NULL COMMENT '卖点',  
35 -`description` text COMMENT '描述',  
36 -`pic_urls` varchar(1024) DEFAULT '' COMMENT '商品轮播图地址数组,以逗号分隔最多上传15张',  
37 -`video_url` varchar(128) DEFAULT NULL COMMENT '商品视频',  
38 -`market_price` int DEFAULT NULL COMMENT '市场价,单位使用:分',  
39 -`min_price` int DEFAULT NULL COMMENT '最小价格,单位使用:分',  
40 -`max_price` int DEFAULT NULL COMMENT '最大价格,单位使用:分',  
41 -`total_stock` int NOT NULL DEFAULT '0' COMMENT '总库存',  
42 -`show_stock` int DEFAULT '0' COMMENT '是否展示库存',  
43 -`sales_count` int DEFAULT '0' COMMENT '商品销量',  
44 -`virtual_sales_count` int DEFAULT '0' COMMENT '虚拟销量',  
45 -`click_count` int DEFAULT '0' COMMENT '商品点击量',  
46 -`status` bit(1) DEFAULT NULL COMMENT '上下架状态: 0 上架(开启) 1 下架(禁用)-1 回收',  
47 -`sort` int NOT NULL DEFAULT '0' COMMENT '排序字段',  
48 -`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',  
49 -`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',  
50 -`creator` varchar(64) DEFAULT NULL COMMENT '创建人',  
51 -`updater` varchar(64) DEFAULT NULL COMMENT '更新人',  
52 -`deleted` bit(1) NOT NULL DEFAULT 0 COMMENT '是否删除', 27 + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
  28 + `tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户编号',
  29 + `brand_id` bigint DEFAULT NULL COMMENT '商品品牌编号',
  30 + `category_id` bigint NOT NULL COMMENT '分类id',
  31 + `spec_type` int NOT NULL COMMENT '规格类型:0 单规格 1 多规格',
  32 + `code` varchar(128) DEFAULT NULL COMMENT '商品编码',
  33 + `name` varchar(128) NOT NULL COMMENT '商品名称',
  34 + `sell_point` varchar(128) DEFAULT NULL COMMENT '卖点',
  35 + `description` text COMMENT '描述',
  36 + `pic_urls` varchar(1024) DEFAULT '' COMMENT '商品轮播图地址数组,以逗号分隔最多上传15张',
  37 + `video_url` varchar(128) DEFAULT NULL COMMENT '商品视频',
  38 + `market_price` int DEFAULT NULL COMMENT '市场价,单位使用:分',
  39 + `min_price` int DEFAULT NULL COMMENT '最小价格,单位使用:分',
  40 + `max_price` int DEFAULT NULL COMMENT '最大价格,单位使用:分',
  41 + `total_stock` int NOT NULL DEFAULT '0' COMMENT '总库存',
  42 + `show_stock` int DEFAULT '0' COMMENT '是否展示库存',
  43 + `sales_count` int DEFAULT '0' COMMENT '商品销量',
  44 + `virtual_sales_count` int DEFAULT '0' COMMENT '虚拟销量',
  45 + `click_count` int DEFAULT '0' COMMENT '商品点击量',
  46 + `status` bit(1) DEFAULT NULL COMMENT '上下架状态: 0 上架(开启) 1 下架(禁用)-1 回收',
  47 + `sort` int NOT NULL DEFAULT '0' COMMENT '排序字段',
  48 + `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  49 + `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  50 + `creator` varchar DEFAULT NULL COMMENT '创建人',
  51 + `updater` varchar DEFAULT NULL COMMENT '更新人',
  52 + `deleted` bit(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
53 PRIMARY KEY (`id`) 53 PRIMARY KEY (`id`)
54 ) COMMENT '商品spu'; 54 ) COMMENT '商品spu';
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApi.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.api.coupon;
  2 +
  3 +import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO;
  4 +
  5 +import javax.validation.Valid;
  6 +
  7 +/**
  8 + * 优惠劵 API 接口
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +public interface CouponApi {
  13 +
  14 + /**
  15 + * 使用优惠劵
  16 + *
  17 + * @param useReqDTO 使用请求
  18 + */
  19 + void useCoupon(@Valid CouponUseReqDTO useReqDTO);
  20 +
  21 +}
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/dto/CouponUseReqDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.api.coupon.dto;
  2 +
  3 +import lombok.Data;
  4 +
  5 +import javax.validation.constraints.NotNull;
  6 +
  7 +/**
  8 + * 优惠劵使用 Request DTO
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +@Data
  13 +public class CouponUseReqDTO {
  14 +
  15 + /**
  16 + * 优惠劵编号
  17 + */
  18 + @NotNull(message = "优惠劵编号不能为空")
  19 + private Long id;
  20 +
  21 + /**
  22 + * 用户编号
  23 + */
  24 + @NotNull(message = "用户编号不能为空")
  25 + private Long userId;
  26 +
  27 + /**
  28 + * 订单编号
  29 + */
  30 + @NotNull(message = "订单编号不能为空")
  31 + private Long orderId;
  32 +
  33 +}
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApiImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.api.coupon;
  2 +
  3 +
  4 +import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO;
  5 +import cn.iocoder.yudao.module.promotion.service.coupon.CouponService;
  6 +import org.springframework.stereotype.Service;
  7 +
  8 +import javax.annotation.Resource;
  9 +
  10 +/**
  11 + * 优惠劵 API 实现类
  12 + *
  13 + * @author 芋道源码
  14 + */
  15 +@Service
  16 +public class CouponApiImpl implements CouponApi {
  17 +
  18 + @Resource
  19 + private CouponService couponService;
  20 +
  21 + @Override
  22 + public void useCoupon(CouponUseReqDTO useReqDTO) {
  23 + couponService.useCoupon(useReqDTO.getId(), useReqDTO.getUserId(),
  24 + useReqDTO.getOrderId());
  25 + }
  26 +
  27 +}
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java
@@ -45,10 +45,11 @@ public interface CouponService { @@ -45,10 +45,11 @@ public interface CouponService {
45 /** 45 /**
46 * 使用优惠劵 46 * 使用优惠劵
47 * 47 *
48 - * @param id 优惠劵编号  
49 - * @param userId 用户编号 48 + * @param id 优惠劵编号
  49 + * @param userId 用户编号
  50 + * @param orderId 订单编号
50 */ 51 */
51 - void useCoupon(Long id, Long userId); 52 + void useCoupon(Long id, Long userId, Long orderId);
52 53
53 /** 54 /**
54 * 回收优惠劵 55 * 回收优惠劵
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java
@@ -81,12 +81,13 @@ public class CouponServiceImpl implements CouponService { @@ -81,12 +81,13 @@ public class CouponServiceImpl implements CouponService {
81 } 81 }
82 82
83 @Override 83 @Override
84 - public void useCoupon(Long id, Long userId) { 84 + public void useCoupon(Long id, Long userId, Long orderId) {
85 // 校验优惠劵 85 // 校验优惠劵
86 validCoupon(id, userId); 86 validCoupon(id, userId);
87 // 更新状态 87 // 更新状态
88 int updateCount = couponMapper.updateByIdAndStatus(id, CouponStatusEnum.UNUSED.getStatus(), 88 int updateCount = couponMapper.updateByIdAndStatus(id, CouponStatusEnum.UNUSED.getStatus(),
89 - new CouponDO().setStatus(CouponStatusEnum.USED.getStatus()).setUseTime(new Date())); 89 + new CouponDO().setStatus(CouponStatusEnum.USED.getStatus())
  90 + .setUseOrderId(orderId).setUseTime(new Date()));
90 if (updateCount == 0) { 91 if (updateCount == 0) {
91 throw exception(COUPON_STATUS_NOT_UNUSED); 92 throw exception(COUPON_STATUS_NOT_UNUSED);
92 } 93 }