diff --git a/yudao-module-mall/yudao-module-product-api/pom.xml b/yudao-module-mall/yudao-module-product-api/pom.xml
index 7eb38008a..123e7c334 100644
--- a/yudao-module-mall/yudao-module-product-api/pom.xml
+++ b/yudao-module-mall/yudao-module-product-api/pom.xml
@@ -22,6 +22,13 @@
cn.iocoder.boot
yudao-common
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+ true
+
-
\ No newline at end of file
+
diff --git a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApi.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApi.java
index d4b3e5b1a..d5d93dc37 100644
--- a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApi.java
+++ b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApi.java
@@ -1,6 +1,6 @@
package cn.iocoder.yudao.module.product.api.sku;
-import cn.iocoder.yudao.module.product.api.sku.dto.SkuDecrementStockBatchReqDTO;
+import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import java.util.Collection;
@@ -31,11 +31,10 @@ public interface ProductSkuApi {
List getSkuList(Collection ids);
/**
- * 批量扣减 SKU 库存
+ * 更新 SKU 库存
*
- * @param batchReqDTO sku库存信息列表
+ * @param updateStockReqDTO 更新请求
*/
- // TODO @LeeYan9: decrementSkuStockBatch? 啊哈, 动名词;
- void decrementStockBatch(SkuDecrementStockBatchReqDTO batchReqDTO);
+ void updateSkuStock(ProductSkuUpdateStockReqDTO updateStockReqDTO);
}
diff --git a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/ProductSkuUpdateStockReqDTO.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/ProductSkuUpdateStockReqDTO.java
new file mode 100644
index 000000000..0b854eeca
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/ProductSkuUpdateStockReqDTO.java
@@ -0,0 +1,47 @@
+package cn.iocoder.yudao.module.product.api.sku.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+/**
+ * 商品 SKU 更新库存 Request DTO
+ *
+ * @author LeeYan9
+ * @since 2022-08-26
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class ProductSkuUpdateStockReqDTO {
+
+ /**
+ * 商品 SKU
+ */
+ @NotNull(message = "商品 SKU 不能为空")
+ private List- items;
+
+ @Data
+ public static class Item {
+
+ /**
+ * 商品 SKU 编号
+ */
+ @NotNull(message = "商品 SKU 编号不能为空")
+ private Long id;
+
+ /**
+ * 库存变化数量
+ *
+ * 正数:增加库存
+ * 负数:扣减库存
+ */
+ @NotNull(message = "库存变化数量不能为空")
+ private Integer incCount;
+
+ }
+
+}
diff --git a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/SkuDecrementStockBatchReqDTO.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/SkuDecrementStockBatchReqDTO.java
deleted file mode 100644
index 5c045d3d5..000000000
--- a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/SkuDecrementStockBatchReqDTO.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package cn.iocoder.yudao.module.product.api.sku.dto;
-
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-import java.util.List;
-
-/**
- * TODO @LeeYan9: 1) 类注释; 2) Product 开头哈;
- * @author LeeYan9
- * @since 2022-08-26
- */
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-public class SkuDecrementStockBatchReqDTO {
-
- // TODO @LeeYan9: 参数校验
- private List
- items;
-
- @Data
- public static class Item {
-
- /**
- * 商品 SPU 编号,自增
- */
- // TODO @LeeYan9: 是不是不用传递哈
- private Long productId;
-
- /**
- * 商品 SKU 编号,自增
- */
- private Long skuId;
-
- /**
- * 数量
- */
- private Integer count;
-
- }
-
- // TODO @LeeYan9: 构造方法, 是不是可以满足啦
- public static SkuDecrementStockBatchReqDTO of(List
- items) {
- return new SkuDecrementStockBatchReqDTO(items);
- }
-
-}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApiImpl.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApiImpl.java
index dc353e2b0..89913c70e 100644
--- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApiImpl.java
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApiImpl.java
@@ -1,13 +1,12 @@
package cn.iocoder.yudao.module.product.api.sku;
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
-import cn.iocoder.yudao.module.product.api.sku.dto.SkuDecrementStockBatchReqDTO;
+import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
import cn.iocoder.yudao.module.product.convert.sku.ProductSkuConvert;
import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
-import cn.iocoder.yudao.module.product.dal.mysql.sku.ProductSkuMapper;
+import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
@@ -25,7 +24,7 @@ import java.util.List;
public class ProductSkuApiImpl implements ProductSkuApi {
@Resource
- private ProductSkuMapper productSkuMapper;
+ private ProductSkuService productSkuService;
@Override
public ProductSkuRespDTO getSku(Long id) {
@@ -35,18 +34,16 @@ public class ProductSkuApiImpl implements ProductSkuApi {
@Override
public List getSkuList(Collection ids) {
- // TODO TODO LeeYan9: AllEmpty?
- if (CollectionUtils.isAnyEmpty(ids)) {
+ if (CollUtil.isEmpty(ids)) {
return Collections.emptyList();
}
- List productSkuDOList = productSkuMapper.selectBatchIds(ids);
- return ProductSkuConvert.INSTANCE.convertList04(productSkuDOList);
+ List skus = productSkuService.getSkuList(ids);
+ return ProductSkuConvert.INSTANCE.convertList04(skus);
}
@Override
- @Transactional(rollbackFor = Exception.class)
- public void decrementStockBatch(SkuDecrementStockBatchReqDTO batchReqDTO) {
- // TODO @LeeYan9: 最好 Service 去 for 循环;
- productSkuMapper.decrementStockBatch(batchReqDTO.getItems());
+ public void updateSkuStock(ProductSkuUpdateStockReqDTO updateStockReqDTO) {
+ productSkuService.updateSkuStock(updateStockReqDTO);
}
+
}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/sku/ProductSkuConvert.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/sku/ProductSkuConvert.java
index c96cd41db..43d34ccba 100755
--- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/sku/ProductSkuConvert.java
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/sku/ProductSkuConvert.java
@@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.product.convert.sku;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
+import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuCreateOrUpdateReqVO;
import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuOptionRespVO;
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;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
/**
* 商品 SKU Convert
@@ -39,4 +44,26 @@ public interface ProductSkuConvert {
List convertList05(List skus);
+ /**
+ * 获得 SPU 的库存变化 Map
+ *
+ * @param items SKU 库存变化
+ * @param skus SKU 列表
+ * @return SPU 的库存变化 Map
+ */
+ default Map convertSpuStockMap(List items,
+ List skus) {
+ Map skuIdAndSpuIdMap = convertMap(skus, ProductSkuDO::getId, ProductSkuDO::getSpuId); // SKU 与 SKU 编号的 Map 关系
+ Map spuIdAndStockMap = new HashMap<>(); // SPU 的库存变化 Map 关系
+ items.forEach(item -> {
+ Long spuId = skuIdAndSpuIdMap.get(item.getId());
+ if (spuId == null) {
+ return;
+ }
+ Integer stock = spuIdAndStockMap.getOrDefault(spuId, 0) + item.getIncCount();
+ spuIdAndStockMap.put(spuId, stock);
+ });
+ return spuIdAndStockMap;
+ }
+
}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/sku/ProductSkuMapper.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/sku/ProductSkuMapper.java
index dfc8fd4ff..3e5a9ce8f 100755
--- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/sku/ProductSkuMapper.java
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/sku/ProductSkuMapper.java
@@ -1,8 +1,8 @@
package cn.iocoder.yudao.module.product.dal.mysql.sku;
+import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
-import cn.iocoder.yudao.module.product.api.sku.dto.SkuDecrementStockBatchReqDTO;
import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
@@ -23,21 +23,37 @@ public interface ProductSkuMapper extends BaseMapperX {
}
default void deleteBySpuId(Long spuId) {
- delete(new LambdaQueryWrapperX()
- .eqIfPresent(ProductSkuDO::getSpuId, spuId));
+ delete(new LambdaQueryWrapperX().eq(ProductSkuDO::getSpuId, spuId));
}
- default void decrementStockBatch(List items) {
- for (SkuDecrementStockBatchReqDTO.Item item : items) {
- // 扣减库存 cas 逻辑
- LambdaUpdateWrapper lambdaUpdateWrapper = new LambdaUpdateWrapper()
- .setSql(" stock = stock-" + item.getCount())
- .eq(ProductSkuDO::getSpuId, item.getProductId())
- .eq(ProductSkuDO::getId, item.getSkuId())
- .ge(ProductSkuDO::getStock, item.getCount());
- // 执行
- this.update(null, lambdaUpdateWrapper);
- }
+ /**
+ * 更新 SKU 库存(增加)
+ *
+ * @param id 编号
+ * @param incrCount 增加库存(正数)
+ */
+ default void updateStockIncr(Long id, Integer incrCount) {
+ Assert.isTrue(incrCount > 0);
+ LambdaUpdateWrapper lambdaUpdateWrapper = new LambdaUpdateWrapper()
+ .setSql(" stock = stock + " + incrCount)
+ .eq(ProductSkuDO::getId, id);
+ update(null, lambdaUpdateWrapper);
+ }
+
+ /**
+ * 更新 SKU 库存(减少)
+ *
+ * @param id 编号
+ * @param incrCount 减少库存(负数)
+ * @return 更新条数
+ */
+ default int updateStockDecr(Long id, Integer incrCount) {
+ Assert.isTrue(incrCount < 0);
+ LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper()
+ .setSql(" stock = stock + " + incrCount) // 负数,所以使用 + 号
+ .eq(ProductSkuDO::getId, id)
+ .ge(ProductSkuDO::getStock, -incrCount); // cas 逻辑
+ return update(null, updateWrapper);
}
default List selectListByAlarmStock(){
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/spu/ProductSpuMapper.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/spu/ProductSpuMapper.java
index eea12f62f..a271b5051 100755
--- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/spu/ProductSpuMapper.java
+++ b/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;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuPageReqVO;
import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.Set;
@@ -43,4 +44,17 @@ public interface ProductSpuMapper extends BaseMapperX {
.orderByDesc(ProductSpuDO::getSort));
}
+ /**
+ * 更新商品 SPU 库存
+ *
+ * @param id 商品 SPU 编号
+ * @param incrCount 增加的库存数量
+ */
+ default void updateStock(Long id, Integer incrCount) {
+ LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper()
+ .setSql(" total_stock = total_stock +" + incrCount) // 负数,所以使用 + 号
+ .eq(ProductSpuDO::getId, id);
+ update(null, updateWrapper);
+ }
+
}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuService.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuService.java
index 8bdfe49d9..1036d8348 100755
--- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuService.java
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuService.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.product.service.sku;
+import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuCreateOrUpdateReqVO;
import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
@@ -64,7 +65,16 @@ public interface ProductSkuService {
* @param spuId SPU 编码
* @param skus SKU 的集合
*/
- void updateProductSkus(Long spuId, List skus);
+ void updateSkus(Long spuId, List skus);
+
+ /**
+ * 更新 SKU 库存(增量)
+ *
+ * 如果更新的库存不足,会抛出异常
+ *
+ * @param updateStockReqDTO 更行请求
+ */
+ void updateSkuStock(ProductSkuUpdateStockReqDTO updateStockReqDTO);
/**
* 获得商品 sku 集合
@@ -96,4 +106,5 @@ public interface ProductSkuService {
*/
List getSkusByAlarmStock();
+
}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceImpl.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceImpl.java
index d9b90b0f3..0a3f7a155 100755
--- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceImpl.java
+++ b/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;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
import cn.iocoder.yudao.module.product.controller.admin.property.vo.property.ProductPropertyRespVO;
import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueRespVO;
import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuBaseVO;
@@ -13,6 +14,8 @@ import cn.iocoder.yudao.module.product.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.product.enums.spu.ProductSpuSpecTypeEnum;
import cn.iocoder.yudao.module.product.service.property.ProductPropertyService;
import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService;
+import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
+import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
@@ -22,6 +25,7 @@ import java.util.*;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.*;
/**
@@ -36,16 +40,18 @@ public class ProductSkuServiceImpl implements ProductSkuService {
@Resource
private ProductSkuMapper productSkuMapper;
+ @Resource
+ @Lazy // 循环依赖,避免报错
+ private ProductSpuService productSpuService;
@Resource
private ProductPropertyService productPropertyService;
-
@Resource
private ProductPropertyValueService productPropertyValueService;
@Override
public void deleteSku(Long id) {
// 校验存在
- this.validateSkuExists(id);
+ validateSkuExists(id);
// 删除
productSkuMapper.deleteById(id);
}
@@ -89,7 +95,7 @@ public class ProductSkuServiceImpl implements ProductSkuService {
// 2. 校验,一个 SKU 下,没有重复的规格。校验方式是,遍历每个 SKU ,看看是否有重复的规格 propertyId
Map propertyValueMap = CollectionUtils.convertMap(productPropertyValueService.getPropertyValueListByPropertyId(new ArrayList<>(propertyIds)), ProductPropertyValueRespVO::getId);
skus.forEach(sku -> {
- Set skuPropertyIds = CollectionUtils.convertSet(sku.getProperties(), propertyItem -> propertyValueMap.get(propertyItem.getValueId()).getPropertyId());
+ Set skuPropertyIds = convertSet(sku.getProperties(), propertyItem -> propertyValueMap.get(propertyItem.getValueId()).getPropertyId());
if (skuPropertyIds.size() != sku.getProperties().size()) {
throw exception(SKU_PROPERTIES_DUPLICATED);
}
@@ -106,7 +112,7 @@ public class ProductSkuServiceImpl implements ProductSkuService {
// 4. 最后校验,每个 Sku 之间不是重复的
Set> skuAttrValues = new HashSet<>(); // 每个元素,都是一个 Sku 的 attrValueId 集合。这样,通过最外层的 Set ,判断是否有重复的.
for (ProductSkuCreateOrUpdateReqVO sku : skus) {
- if (!skuAttrValues.add(CollectionUtils.convertSet(sku.getProperties(), ProductSkuBaseVO.Property::getValueId))) { // 添加失败,说明重复
+ if (!skuAttrValues.add(convertSet(sku.getProperties(), ProductSkuBaseVO.Property::getValueId))) { // 添加失败,说明重复
throw exception(ErrorCodeConstants.SPU_SKU_NOT_DUPLICATE);
}
}
@@ -142,7 +148,7 @@ public class ProductSkuServiceImpl implements ProductSkuService {
@Override
@Transactional
- public void updateProductSkus(Long spuId, List skus) {
+ public void updateSkus(Long spuId, List skus) {
// 查询 SPU 下已经存在的 SKU 的集合
List existsSkus = productSkuMapper.selectListBySpuId(spuId);
// 构建规格与 SKU 的映射关系;
@@ -192,14 +198,27 @@ public class ProductSkuServiceImpl implements ProductSkuService {
}
}
- public static void main(String[] args) {
- List ids = new ArrayList<>();
- ids.add(1);
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void updateSkuStock(ProductSkuUpdateStockReqDTO updateStockReqDTO) {
+ // 更新 SKU 库存
+ updateStockReqDTO.getItems().forEach(item -> {
+ if (item.getIncCount() > 0) {
+ productSkuMapper.updateStockIncr(item.getId(), item.getIncCount());
+ } else if (item.getIncCount() < 0) {
+ int updateStockIncr = productSkuMapper.updateStockDecr(item.getId(), item.getIncCount());
+ if (updateStockIncr == 0) {
+ throw exception(SKU_STOCK_NOT_ENOUGH);
+ }
+ }
+ });
- List ids2 = new ArrayList<>();
- ids2.add(2);
-
- System.out.println(ids.equals(ids2));
+ // 更新 SPU 库存
+ List skus = productSkuMapper.selectBatchIds(
+ convertSet(updateStockReqDTO.getItems(), ProductSkuUpdateStockReqDTO.Item::getId));
+ Map spuStockIncrCounts = ProductSkuConvert.INSTANCE.convertSpuStockMap(
+ updateStockReqDTO.getItems(), skus);
+ productSpuService.updateSpuStock(spuStockIncrCounts);
}
}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuService.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuService.java
index 6ae7e8996..151edbf86 100755
--- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuService.java
+++ b/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 {
*/
PageResult getSpuPage(AppSpuPageReqVO pageReqVO);
+ /**
+ * 更新商品 SPU 库存(增量)
+ *
+ * @param stockIncrCounts SPU 编号与库存变化(增量)的映射
+ */
+ void updateSpuStock(Map stockIncrCounts);
+
}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java
index b332af306..4880d4948 100755
--- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java
+++ b/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;
import cn.iocoder.yudao.module.product.service.property.ProductPropertyService;
import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService;
import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
+import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
@@ -51,14 +52,12 @@ public class ProductSpuServiceImpl implements ProductSpuService {
private ProductCategoryService categoryService;
@Resource
+ @Lazy // 循环依赖,避免报错
private ProductSkuService productSkuService;
-
@Resource
private ProductPropertyService productPropertyService;
-
@Resource
private ProductPropertyValueService productPropertyValueService;
-
@Resource
private ProductBrandService brandService;
@@ -106,7 +105,7 @@ public class ProductSpuServiceImpl implements ProductSpuService {
updateObj.setTotalStock(CollectionUtils.getSumValue(skuCreateReqList, ProductSkuCreateOrUpdateReqVO::getStock, Integer::sum));
productSpuMapper.updateById(updateObj);
// 批量更新 SKU
- productSkuService.updateProductSkus(updateObj.getId(), updateReqVO.getSkus());
+ productSkuService.updateSkus(updateObj.getId(), updateReqVO.getSkus());
}
@Override
@@ -210,4 +209,10 @@ public class ProductSpuServiceImpl implements ProductSpuService {
return pageResult;
}
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void updateSpuStock(Map stockIncrCounts) {
+ stockIncrCounts.forEach((id, incCount) -> productSpuMapper.updateStock(id, incCount));
+ }
+
}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceTest.java b/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceTest.java
new file mode 100644
index 000000000..a94fb18fe
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceTest.java
@@ -0,0 +1,98 @@
+package cn.iocoder.yudao.module.product.service.sku;
+
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+import cn.iocoder.yudao.framework.test.core.util.AssertUtils;
+import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
+import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
+import cn.iocoder.yudao.module.product.dal.mysql.sku.ProductSkuMapper;
+import cn.iocoder.yudao.module.product.service.property.ProductPropertyService;
+import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService;
+import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.Import;
+
+import javax.annotation.Resource;
+
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
+import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_STOCK_NOT_ENOUGH;
+import static java.util.Collections.singletonList;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.verify;
+
+/**
+ * {@link ProductSkuServiceImpl} 的单元测试
+ *
+ * @author 芋道源码
+ */
+@Import(ProductSkuServiceImpl.class)
+public class ProductSkuServiceTest extends BaseDbUnitTest {
+
+ @Resource
+ private ProductSkuService productSkuService;
+
+ @Resource
+ private ProductSkuMapper productSkuMapper;
+
+ @MockBean
+ private ProductSpuService productSpuService;
+ @MockBean
+ private ProductPropertyService productPropertyService;
+ @MockBean
+ private ProductPropertyValueService productPropertyValueService;
+
+ @Test
+ public void testUpdateSkuStock_incrSuccess() {
+ // 准备参数
+ ProductSkuUpdateStockReqDTO updateStockReqDTO = new ProductSkuUpdateStockReqDTO()
+ .setItems(singletonList(new ProductSkuUpdateStockReqDTO.Item().setId(1L).setIncCount(10)));
+ // mock 数据
+ productSkuMapper.insert(randomPojo(ProductSkuDO.class, o -> o.setId(1L).setSpuId(10L).setStock(20)));
+
+ // 调用
+ productSkuService.updateSkuStock(updateStockReqDTO);
+ // 断言
+ ProductSkuDO sku = productSkuMapper.selectById(1L);
+ assertEquals(sku.getStock(), 30);
+ verify(productSpuService).updateSpuStock(argThat(spuStockIncrCounts -> {
+ assertEquals(spuStockIncrCounts.size(), 1);
+ assertEquals(spuStockIncrCounts.get(10L), 10);
+ return true;
+ }));
+ }
+
+ @Test
+ public void testUpdateSkuStock_decrSuccess() {
+ // 准备参数
+ ProductSkuUpdateStockReqDTO updateStockReqDTO = new ProductSkuUpdateStockReqDTO()
+ .setItems(singletonList(new ProductSkuUpdateStockReqDTO.Item().setId(1L).setIncCount(-10)));
+ // mock 数据
+ productSkuMapper.insert(randomPojo(ProductSkuDO.class, o -> o.setId(1L).setSpuId(10L).setStock(20)));
+
+ // 调用
+ productSkuService.updateSkuStock(updateStockReqDTO);
+ // 断言
+ ProductSkuDO sku = productSkuMapper.selectById(1L);
+ assertEquals(sku.getStock(), 10);
+ verify(productSpuService).updateSpuStock(argThat(spuStockIncrCounts -> {
+ assertEquals(spuStockIncrCounts.size(), 1);
+ assertEquals(spuStockIncrCounts.get(10L), -10);
+ return true;
+ }));
+ }
+
+ @Test
+ public void testUpdateSkuStock_decrFail() {
+ // 准备参数
+ ProductSkuUpdateStockReqDTO updateStockReqDTO = new ProductSkuUpdateStockReqDTO()
+ .setItems(singletonList(new ProductSkuUpdateStockReqDTO.Item().setId(1L).setIncCount(-30)));
+ // mock 数据
+ productSkuMapper.insert(randomPojo(ProductSkuDO.class, o -> o.setId(1L).setSpuId(10L).setStock(20)));
+
+ // 调用并断言
+ AssertUtils.assertServiceException(() -> productSkuService.updateSkuStock(updateStockReqDTO),
+ SKU_STOCK_NOT_ENOUGH);
+ }
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/sku/SkuServiceImplTest.java b/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/sku/SkuServiceImplTest.java
index 10ab8a3d1..9dce87a24 100755
--- a/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/sku/SkuServiceImplTest.java
+++ b/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;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS;
import static org.junit.jupiter.api.Assertions.assertNull;
+// TODO 芋艿:整合到 {@link ProductSkuServiceTest} 中
/**
* {@link ProductSkuServiceImpl} 的单元测试类
*
diff --git a/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImplTest.java b/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImplTest.java
index cf4c14649..cd9b2b4e7 100755
--- a/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImplTest.java
+++ b/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImplTest.java
@@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.product.service.spu;
import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.RandomUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
@@ -39,6 +40,7 @@ import java.util.stream.Stream;
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.randomPojo;
+import static org.junit.jupiter.api.Assertions.assertEquals;
// TODO @芋艿:review 下单元测试
@@ -56,19 +58,14 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest {
@Resource
private ProductSpuMapper productSpuMapper;
-
@MockBean
private ProductSkuServiceImpl productSkuService;
-
@MockBean
private ProductCategoryServiceImpl categoryService;
-
@MockBean
private ProductBrandServiceImpl brandService;
-
@MockBean
private ProductPropertyService productPropertyService;
-
@MockBean
private ProductPropertyValueService productPropertyValueService;
@@ -228,7 +225,7 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest {
PageResult