fix: 完善商品管理

This commit is contained in:
puhui999 2023-06-01 11:41:26 +08:00
parent e86214015d
commit 8d2bcc57aa
14 changed files with 197 additions and 25 deletions

View File

@ -15,6 +15,7 @@ public interface ErrorCodeConstants {
ErrorCode CATEGORY_PARENT_NOT_FIRST_LEVEL = new ErrorCode(1008001002, "父分类不能是二级分类");
ErrorCode CATEGORY_EXISTS_CHILDREN = new ErrorCode(1008001003, "存在子分类,无法删除");
ErrorCode CATEGORY_DISABLED = new ErrorCode(1008001004, "商品分类({})已禁用,无法使用");
ErrorCode CATEGORY_HAVE_BIND_SPU = new ErrorCode(1008001005, "类别下存在商品,无法删除");
// ========== 商品品牌相关编号 1008002000 ==========
ErrorCode BRAND_NOT_EXISTS = new ErrorCode(1008002000, "品牌不存在");

View File

@ -65,7 +65,7 @@ public class ProductBrandController {
@GetMapping("/list-all-simple")
@Operation(summary = "获取品牌精简信息列表", description = "主要用于前端的下拉选项")
public CommonResult<List<ProductBrandSimpleRespVO>> getSimpleUserList() {
public CommonResult<List<ProductBrandSimpleRespVO>> getSimpleBrandList() {
// 获取品牌列表只要开启状态的
List<ProductBrandDO> list = brandService.getBrandListByStatus(CommonStatusEnum.ENABLE.getStatus());
// 排序后返回给前端

View File

@ -14,8 +14,4 @@ public class ProductPropertyListReqVO {
@Schema(description = "属性名称", example = "颜色")
private String name;
// TODO @puhui999这个查询条件的作用是啥呀
@Schema(description = "属性编号的数组", example = "1,2")
private List<Long> propertyIds;
}

View File

@ -7,7 +7,6 @@ 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;
import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuDetailRespVO;
import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;

View File

@ -1,9 +1,12 @@
package cn.iocoder.yudao.module.product.dal.mysql.sku;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
@ -70,4 +73,22 @@ public interface ProductSkuMapper extends BaseMapperX<ProductSkuDO> {
return selectList(new QueryWrapper<ProductSkuDO>().apply("stock <= warn_stock"));
}
/**
* 更新 sku 属性值时使用的分页查询
*
* @param pageParam 页面参数
* @return {@link PageResult}<{@link ProductSkuDO}>
*/
default PageResult<ProductSkuDO> selectPage(PageParam pageParam) {
return selectPage(pageParam, new LambdaQueryWrapper<ProductSkuDO>().isNotNull(ProductSkuDO::getProperties));
}
/**
* 查询 sku properties 不等于 null 的数量
*
* @return {@link Long}
*/
default Long selectCountByPropertyNotNull() {
return selectCount(new LambdaQueryWrapper<ProductSkuDO>().isNotNull(ProductSkuDO::getProperties));
}
}

View File

@ -7,7 +7,8 @@ import cn.iocoder.yudao.module.product.controller.admin.category.vo.ProductCateg
import cn.iocoder.yudao.module.product.convert.category.ProductCategoryConvert;
import cn.iocoder.yudao.module.product.dal.dataobject.category.ProductCategoryDO;
import cn.iocoder.yudao.module.product.dal.mysql.category.ProductCategoryMapper;
import cn.iocoder.yudao.module.product.enums.ProductConstants;
import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
@ -30,6 +31,9 @@ public class ProductCategoryServiceImpl implements ProductCategoryService {
@Resource
private ProductCategoryMapper productCategoryMapper;
@Resource
@Lazy // 循环依赖避免报错
private ProductSpuService productSpuService;
@Override
public Long createCategory(ProductCategoryCreateReqVO createReqVO) {
@ -63,7 +67,8 @@ public class ProductCategoryServiceImpl implements ProductCategoryService {
if (productCategoryMapper.selectCountByParentId(id) > 0) {
throw exception(CATEGORY_EXISTS_CHILDREN);
}
// TODO 芋艿 补充只有不存在商品才可以删除
// 校验分类是否绑定了 SPU
validateProductCategoryIsHaveBindSpu(id);
// 删除
productCategoryMapper.deleteById(id);
}
@ -91,6 +96,13 @@ public class ProductCategoryServiceImpl implements ProductCategoryService {
}
}
private void validateProductCategoryIsHaveBindSpu(Long id) {
Long count = productSpuService.getSpuCountByCategoryId(id);
if (0 != count) {
throw exception(CATEGORY_HAVE_BIND_SPU);
}
}
@Override
public ProductCategoryDO getCategory(Long id) {
return productCategoryMapper.selectById(id);

View File

@ -10,6 +10,7 @@ import cn.iocoder.yudao.module.product.controller.admin.property.vo.property.Pro
import cn.iocoder.yudao.module.product.convert.property.ProductPropertyConvert;
import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyDO;
import cn.iocoder.yudao.module.product.dal.mysql.property.ProductPropertyMapper;
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;
@ -38,6 +39,9 @@ public class ProductPropertyServiceImpl implements ProductPropertyService {
@Lazy // 延迟加载解决循环依赖问题
private ProductPropertyValueService productPropertyValueService;
@Resource
private ProductSkuService productSkuService;
@Override
@Transactional(rollbackFor = Exception.class)
public Long createProperty(ProductPropertyCreateReqVO createReqVO) {
@ -68,7 +72,8 @@ public class ProductPropertyServiceImpl implements ProductPropertyService {
// 更新
ProductPropertyDO updateObj = ProductPropertyConvert.INSTANCE.convert(updateReqVO);
productPropertyMapper.updateById(updateObj);
// TODO 芋艿更新时需要看看 sku
// TODO 芋艿更新时需要看看 sku fix
productSkuService.updateSkuProperty(updateObj);
}
@Override
@ -94,10 +99,6 @@ public class ProductPropertyServiceImpl implements ProductPropertyService {
@Override
public List<ProductPropertyDO> getPropertyList(ProductPropertyListReqVO listReqVO) {
// 增加使用属性 id 查询
if (CollUtil.isNotEmpty(listReqVO.getPropertyIds())){
return productPropertyMapper.selectBatchIds(listReqVO.getPropertyIds());
}
return productPropertyMapper.selectList(listReqVO);
}

View File

@ -10,6 +10,7 @@ import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyDO
import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyValueDO;
import cn.iocoder.yudao.module.product.dal.mysql.property.ProductPropertyValueMapper;
import cn.iocoder.yudao.module.product.service.property.bo.ProductPropertyValueDetailRespBO;
import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
@ -40,6 +41,10 @@ public class ProductPropertyValueServiceImpl implements ProductPropertyValueServ
@Lazy // 延迟加载避免循环依赖
private ProductPropertyService productPropertyService;
@Resource
@Lazy // 延迟加载避免循环依赖
private ProductSkuService productSkuService;
@Override
public Long createPropertyValue(ProductPropertyValueCreateReqVO createReqVO) {
// 如果已经添加过该属性值直接返回
@ -68,7 +73,8 @@ public class ProductPropertyValueServiceImpl implements ProductPropertyValueServ
// 更新
ProductPropertyValueDO updateObj = ProductPropertyValueConvert.INSTANCE.convert(updateReqVO);
productPropertyValueMapper.updateById(updateObj);
// TODO 芋艿更新时需要看看 sku
// TODO 芋艿更新时需要看看 sku fix
productSkuService.updateSkuPropertyValue(updateObj);
}
@Override

View File

@ -2,6 +2,8 @@ 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.property.ProductPropertyDO;
import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyValueDO;
import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
import org.springframework.lang.Nullable;
@ -115,4 +117,19 @@ public interface ProductSkuService {
*/
List<ProductSkuDO> getSkuListByAlarmStock();
/**
* 更新 sku 属性
*
* @param updateObj 属性对象
* @return int 影响的行数
*/
int updateSkuProperty(ProductPropertyDO updateObj);
/**
* 更新 sku 属性值
*
* @param updateObj 属性值对象
* @return int 影响的行数
*/
int updateSkuPropertyValue(ProductPropertyValueDO updateObj);
}

View File

@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.product.service.sku;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
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.convert.sku.ProductSkuConvert;
@ -42,6 +44,7 @@ public class ProductSkuServiceImpl implements ProductSkuService {
@Lazy // 循环依赖避免报错
private ProductSpuService productSpuService;
@Resource
@Lazy // 循环依赖避免报错
private ProductPropertyService productPropertyService;
@Resource
private ProductPropertyValueService productPropertyValueService;
@ -157,6 +160,84 @@ public class ProductSkuServiceImpl implements ProductSkuService {
return productSkuMapper.selectListByAlarmStock();
}
@Override
public int updateSkuProperty(ProductPropertyDO updateObj) {
// TODO 看了一下数据库有关于 json 字符串的处理怕数据库出现兼容问题这里还是用数据库常规操作来实现
Long count = productSkuMapper.selectCountByPropertyNotNull();
int currentPage = 1;
List<ProductSkuDO> skuDOs = new ArrayList<>();
if (count == 0) {
return 0;
}
int pageSize = 100;
for (int i = 0; i <= count / 100; i++) {
PageParam pageParam = new PageParam().setPageNo(currentPage + i).setPageSize(pageSize);
// 分页查找出 sku 属性不为 null
PageResult<ProductSkuDO> skuPage = productSkuMapper.selectPage(pageParam);
List<ProductSkuDO> records = skuPage.getList();
if (CollUtil.isEmpty(records)) {
break;
}
records.stream()
.filter(sku -> sku.getProperties() != null)
.forEach(sku -> sku.getProperties().forEach(property -> {
if (property.getPropertyId().equals(updateObj.getId())) {
property.setPropertyName(updateObj.getName());
skuDOs.add(sku);
}
}));
}
if (CollUtil.isEmpty(skuDOs)) {
return 0;
}
// 每批处理的大小
int batchSize = 1000;
for (int i = 0; i < skuDOs.size(); i += batchSize) {
List<ProductSkuDO> batchSkuDOs = skuDOs.subList(i, Math.min(i + batchSize, skuDOs.size()));
productSkuMapper.updateBatch(batchSkuDOs, batchSize);
}
return skuDOs.size();
}
@Override
public int updateSkuPropertyValue(ProductPropertyValueDO updateObj) {
// TODO 看了一下数据库有关于 json 字符串的处理怕数据库出现兼容问题这里还是用数据库常规操作来实现
Long count = productSkuMapper.selectCountByPropertyNotNull();
int currentPage = 1;
List<ProductSkuDO> skuDOs = new ArrayList<>();
if (count == 0) {
return 0;
}
int pageSize = 100;
for (int i = 0; i <= count / 100; i++) {
PageParam pageParam = new PageParam().setPageNo(currentPage + i).setPageSize(pageSize);
// 分页查找出 sku 属性不为 null
PageResult<ProductSkuDO> skuPage = productSkuMapper.selectPage(pageParam);
List<ProductSkuDO> records = skuPage.getList();
if (CollUtil.isEmpty(records)) {
break;
}
records.stream()
.filter(sku -> sku.getProperties() != null)
.forEach(sku -> sku.getProperties().forEach(property -> {
if (property.getValueId().equals(updateObj.getId())) {
property.setValueName(updateObj.getName());
skuDOs.add(sku);
}
}));
}
if (CollUtil.isEmpty(skuDOs)) {
return 0;
}
// 每批处理的大小
int batchSize = 1000;
for (int i = 0; i < skuDOs.size(); i += batchSize) {
List<ProductSkuDO> batchSkuDOs = skuDOs.subList(i, Math.min(i + batchSize, skuDOs.size()));
productSkuMapper.updateBatch(batchSkuDOs, batchSize);
}
return skuDOs.size();
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateSkuList(Long spuId, List<ProductSkuCreateOrUpdateReqVO> skus) {

View File

@ -126,4 +126,13 @@ public interface ProductSpuService {
* @return {@link Map}<{@link Integer}, {@link Integer}>
*/
Map<Integer, Long> getTabsCount();
/**
* 通过分类 id 查询 spu 个数
*
* @param id 分类 id
* @return spu
*/
Long getSpuCountByCategoryId(Long id);
}

View File

@ -251,4 +251,9 @@ public class ProductSpuServiceImpl implements ProductSpuService {
return counts;
}
@Override
public Long getSpuCountByCategoryId(Long id) {
return productSpuMapper.selectCount(ProductSpuDO::getCategoryId, id);
}
}

View File

@ -49,13 +49,10 @@ public class ProductSkuServiceTest extends BaseDbUnitTest {
@MockBean
private ProductPropertyValueService productPropertyValueService;
// TODO @puhui999是不是可以删除这 2 方法
public Long generateId() {
return RandomUtil.randomLong(100000, 999999);
}
public int generaInt(){return RandomUtil.randomInt(1,9999999);}
@Test
public void testUpdateSkuList() {
// mock 数据
@ -109,7 +106,14 @@ public class ProductSkuServiceTest extends BaseDbUnitTest {
ProductSkuUpdateStockReqDTO updateStockReqDTO = new ProductSkuUpdateStockReqDTO()
.setItems(singletonList(new ProductSkuUpdateStockReqDTO.Item().setId(1L).setIncrCount(10)));
// mock 数据
productSkuMapper.insert(randomPojo(ProductSkuDO.class, o -> o.setId(1L).setSpuId(10L).setStock(20)));
productSkuMapper.insert(randomPojo(ProductSkuDO.class, o -> {
o.setId(1L).setSpuId(10L).setStock(20);
o.getProperties().forEach(p -> {
// 指定 id 范围 解决 Value too long
p.setPropertyId(generateId());
p.setValueId(generateId());
});
}));
// 调用
productSkuService.updateSkuStock(updateStockReqDTO);
@ -129,7 +133,14 @@ public class ProductSkuServiceTest extends BaseDbUnitTest {
ProductSkuUpdateStockReqDTO updateStockReqDTO = new ProductSkuUpdateStockReqDTO()
.setItems(singletonList(new ProductSkuUpdateStockReqDTO.Item().setId(1L).setIncrCount(-10)));
// mock 数据
productSkuMapper.insert(randomPojo(ProductSkuDO.class, o -> o.setId(1L).setSpuId(10L).setStock(20)));
productSkuMapper.insert(randomPojo(ProductSkuDO.class, o -> {
o.setId(1L).setSpuId(10L).setStock(20);
o.getProperties().forEach(p -> {
// 指定 id 范围 解决 Value too long
p.setPropertyId(generateId());
p.setValueId(generateId());
});
}));
// 调用
productSkuService.updateSkuStock(updateStockReqDTO);
@ -149,8 +160,14 @@ public class ProductSkuServiceTest extends BaseDbUnitTest {
ProductSkuUpdateStockReqDTO updateStockReqDTO = new ProductSkuUpdateStockReqDTO()
.setItems(singletonList(new ProductSkuUpdateStockReqDTO.Item().setId(1L).setIncrCount(-30)));
// mock 数据
productSkuMapper.insert(randomPojo(ProductSkuDO.class, o -> o.setId(1L).setSpuId(10L).setStock(20)));
productSkuMapper.insert(randomPojo(ProductSkuDO.class, o -> {
o.setId(1L).setSpuId(10L).setStock(20);
o.getProperties().forEach(p -> {
// 指定 id 范围 解决 Value too long
p.setPropertyId(generateId());
p.setValueId(generateId());
});
}));
// 调用并断言
AssertUtils.assertServiceException(() -> productSkuService.updateSkuStock(updateStockReqDTO),
SKU_STOCK_NOT_ENOUGH);
@ -158,9 +175,16 @@ public class ProductSkuServiceTest extends BaseDbUnitTest {
@Test
public void testDeleteSku_success() {
ProductSkuDO dbSku = randomPojo(ProductSkuDO.class, o -> {
o.setId(generateId()).setSpuId(generateId());
o.getProperties().forEach(p -> {
// 指定 id 范围 解决 Value too long
p.setPropertyId(generateId());
p.setValueId(generateId());
});
});
// mock 数据
ProductSkuDO dbSku = randomPojo(ProductSkuDO.class);
productSkuMapper.insert(dbSku);// @Sql: 先插入出一条存在的数据
productSkuMapper.insert(dbSku);
// 准备参数
Long id = dbSku.getId();

View File

@ -455,7 +455,7 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest {
public void testUpdateSpuStock() {
// 准备参数
Map<Long, Integer> stockIncrCounts = MapUtil.builder(1L, 10).put(2L, -20).build();
// mock 方法数据 // TODO ProductSpuDO中已没有相关属性
// mock 方法数据
productSpuMapper.insert(randomPojo(ProductSpuDO.class, o ->{
o.setCategoryId(generateId());
o.setBrandId(generateId());
@ -495,7 +495,7 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest {
// 调用
productSpuService.updateSpuStock(stockIncrCounts);
// 断言 // TODO ProductSpuDO中已没有相关属性
// 断言
assertEquals(productSpuService.getSpu(1L).getStock(), 30);
assertEquals(productSpuService.getSpu(2L).getStock(), 10);
}