package cn.iocoder.yudao.module.product.service.spu; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjectUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.product.controller.admin.category.vo.ProductCategoryListReqVO; import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSkuSaveReqVO; import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuPageReqVO; import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuSaveReqVO; import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuUpdateStatusReqVO; import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageReqVO; import cn.iocoder.yudao.module.product.dal.dataobject.category.ProductCategoryDO; import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO; import cn.iocoder.yudao.module.product.dal.mysql.spu.ProductSpuMapper; import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum; import cn.iocoder.yudao.module.product.service.brand.ProductBrandService; import cn.iocoder.yudao.module.product.service.category.ProductCategoryService; import cn.iocoder.yudao.module.product.service.sku.ProductSkuService; import com.google.common.collect.Maps; import jakarta.annotation.Resource; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.product.dal.dataobject.category.ProductCategoryDO.CATEGORY_LEVEL; import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.*; /** * 商品 SPU Service 实现类 * * @author 芋道源码 */ @Service @Validated public class ProductSpuServiceImpl implements ProductSpuService { @Resource private ProductSpuMapper productSpuMapper; @Resource @Lazy // 循环依赖,避免报错 private ProductSkuService productSkuService; @Resource private ProductBrandService brandService; @Resource private ProductCategoryService categoryService; @Override @Transactional(rollbackFor = Exception.class) public Long createSpu(ProductSpuSaveReqVO createReqVO) { // 校验分类、品牌 validateCategory(createReqVO.getCategoryId()); brandService.validateProductBrand(createReqVO.getBrandId()); // 校验 SKU List skuSaveReqList = createReqVO.getSkus(); productSkuService.validateSkuList(skuSaveReqList, createReqVO.getSpecType()); ProductSpuDO spu = BeanUtils.toBean(createReqVO, ProductSpuDO.class); // 初始化 SPU 中 SKU 相关属性 initSpuFromSkus(spu, skuSaveReqList); // 插入 SPU productSpuMapper.insert(spu); // 插入 SKU productSkuService.createSkuList(spu.getId(), skuSaveReqList); // 返回 return spu.getId(); } @Override @Transactional(rollbackFor = Exception.class) public void updateSpu(ProductSpuSaveReqVO updateReqVO) { // 校验 SPU 是否存在 ProductSpuDO spu = validateSpuExists(updateReqVO.getId()); // 校验分类、品牌 validateCategory(updateReqVO.getCategoryId()); brandService.validateProductBrand(updateReqVO.getBrandId()); // 校验SKU List skuSaveReqList = updateReqVO.getSkus(); productSkuService.validateSkuList(skuSaveReqList, updateReqVO.getSpecType()); // 更新 SPU ProductSpuDO updateObj = BeanUtils.toBean(updateReqVO, ProductSpuDO.class).setStatus(spu.getStatus()); initSpuFromSkus(updateObj, skuSaveReqList); productSpuMapper.updateById(updateObj); // 批量更新 SKU productSkuService.updateSkuList(updateObj.getId(), updateReqVO.getSkus()); } /** * 基于 SKU 的信息,初始化 SPU 的信息 * 主要是计数相关的字段,例如说市场价、最大最小价、库存等等 * * @param spu 商品 SPU * @param skus 商品 SKU 数组 */ private void initSpuFromSkus(ProductSpuDO spu, List skus) { // sku 单价最低的商品的价格 spu.setPrice(getMinValue(skus, ProductSkuSaveReqVO::getPrice)); // sku 单价最低的商品的市场价格 spu.setMarketPrice(getMinValue(skus, ProductSkuSaveReqVO::getMarketPrice)); // sku 单价最低的商品的成本价格 spu.setCostPrice(getMinValue(skus, ProductSkuSaveReqVO::getCostPrice)); // skus 库存总数 spu.setStock(getSumValue(skus, ProductSkuSaveReqVO::getStock, Math::addExact)); // 若是 spu 已有状态则不处理 if (spu.getStatus() == null) { spu.setStatus(ProductSpuStatusEnum.ENABLE.getStatus()); // 默认状态为上架 spu.setSalesCount(0); // 默认商品销量 spu.setBrowseCount(0); // 默认商品浏览量 } } /** * 校验商品分类是否合法 * * @param id 商品分类编号 */ private void validateCategory(Long id) { categoryService.validateCategory(id); // 校验层级 if (categoryService.getCategoryLevel(id) < CATEGORY_LEVEL) { throw exception(SPU_SAVE_FAIL_CATEGORY_LEVEL_ERROR); } } @Override public List validateSpuList(Collection ids) { if (CollUtil.isEmpty(ids)) { return Collections.emptyList(); } // 获得商品信息 List list = productSpuMapper.selectByIds(ids); Map spuMap = CollectionUtils.convertMap(list, ProductSpuDO::getId); // 校验 ids.forEach(id -> { ProductSpuDO spu = spuMap.get(id); if (spu == null) { throw exception(SPU_NOT_EXISTS); } if (!ProductSpuStatusEnum.isEnable(spu.getStatus())) { throw exception(SPU_NOT_ENABLE, spu.getName()); } }); return list; } @Override public void updateBrowseCount(Long id, int incrCount) { productSpuMapper.updateBrowseCount(id , incrCount); } @Override @Transactional(rollbackFor = Exception.class) public void deleteSpu(Long id) { // 校验存在 validateSpuExists(id); // 校验商品状态不是回收站不能删除 ProductSpuDO spuDO = productSpuMapper.selectById(id); // 判断 SPU 状态是否为回收站 if (ObjectUtil.notEqual(spuDO.getStatus(), ProductSpuStatusEnum.RECYCLE.getStatus())) { throw exception(SPU_NOT_RECYCLE); } // TODO 芋艿:【可选】参与活动中的商品,不允许删除??? // 删除 SPU productSpuMapper.deleteById(id); // 删除关联的 SKU productSkuService.deleteSkuBySpuId(id); } private ProductSpuDO validateSpuExists(Long id) { ProductSpuDO spuDO = productSpuMapper.selectById(id); if (spuDO == null) { throw exception(SPU_NOT_EXISTS); } return spuDO; } @Override public ProductSpuDO getSpu(Long id) { return productSpuMapper.selectById(id); } @Override public ProductSpuDO getSpu(Long id, boolean includeDeleted) { if (includeDeleted) { return productSpuMapper.selectByIdIncludeDeleted(id); } return getSpu(id); } @Override public List getSpuList(Collection ids) { if (CollUtil.isEmpty(ids)) { return Collections.emptyList(); } Map spuMap = convertMap(productSpuMapper.selectByIds(ids), ProductSpuDO::getId); // 需要按照 ids 顺序返回。例如说:店铺装修选择了 [3, 1, 2] 三个商品,返回结果还是 [3, 1, 2] 这样的顺序 return convertList(ids, spuMap::get); } @Override public List getSpuListByStatus(Integer status) { return productSpuMapper.selectList(ProductSpuDO::getStatus, status); } @Override public PageResult getSpuPage(ProductSpuPageReqVO pageReqVO) { return productSpuMapper.selectPage(pageReqVO); } @Override public PageResult getSpuPage(AppProductSpuPageReqVO pageReqVO) { // 查找时,如果查找某个分类编号,则包含它的子分类。因为顶级分类不包含商品 Set categoryIds = new HashSet<>(); if (pageReqVO.getCategoryId() != null && pageReqVO.getCategoryId() > 0) { categoryIds.add(pageReqVO.getCategoryId()); List categoryChildren = categoryService.getCategoryList(new ProductCategoryListReqVO() .setStatus(CommonStatusEnum.ENABLE.getStatus()).setParentId(pageReqVO.getCategoryId())); categoryIds.addAll(convertList(categoryChildren, ProductCategoryDO::getId)); } if (CollUtil.isNotEmpty(pageReqVO.getCategoryIds())) { categoryIds.addAll(pageReqVO.getCategoryIds()); List categoryChildren = categoryService.getCategoryList(new ProductCategoryListReqVO() .setStatus(CommonStatusEnum.ENABLE.getStatus()).setParentIds(pageReqVO.getCategoryIds())); categoryIds.addAll(convertList(categoryChildren, ProductCategoryDO::getId)); } // 分页查询 return productSpuMapper.selectPage(pageReqVO, categoryIds); } @Override @Transactional(rollbackFor = Exception.class) public void updateSpuStock(Map stockIncrCounts) { stockIncrCounts.forEach((id, incCount) -> productSpuMapper.updateStock(id, incCount)); } @Override @Transactional(rollbackFor = Exception.class) public void updateSpuStatus(ProductSpuUpdateStatusReqVO updateReqVO) { // 校验存在 validateSpuExists(updateReqVO.getId()); // TODO 芋艿:【可选】参与活动中的商品,不允许下架??? // 更新状态 ProductSpuDO productSpuDO = productSpuMapper.selectById(updateReqVO.getId()).setStatus(updateReqVO.getStatus()); productSpuMapper.updateById(productSpuDO); } @Override public Map getTabsCount() { Map counts = Maps.newLinkedHashMapWithExpectedSize(5); // 查询销售中的商品数量 counts.put(ProductSpuPageReqVO.FOR_SALE, productSpuMapper.selectCount(ProductSpuDO::getStatus, ProductSpuStatusEnum.ENABLE.getStatus())); // 查询仓库中的商品数量 counts.put(ProductSpuPageReqVO.IN_WAREHOUSE, productSpuMapper.selectCount(ProductSpuDO::getStatus, ProductSpuStatusEnum.DISABLE.getStatus())); // 查询售空的商品数量 counts.put(ProductSpuPageReqVO.SOLD_OUT, productSpuMapper.selectCount(ProductSpuDO::getStock, 0)); // 查询触发警戒库存的商品数量 counts.put(ProductSpuPageReqVO.ALERT_STOCK, productSpuMapper.selectCount()); // 查询回收站中的商品数量 counts.put(ProductSpuPageReqVO.RECYCLE_BIN, productSpuMapper.selectCount(ProductSpuDO::getStatus, ProductSpuStatusEnum.RECYCLE.getStatus())); return counts; } @Override public Long getSpuCountByCategoryId(Long categoryId) { return productSpuMapper.selectCount(ProductSpuDO::getCategoryId, categoryId); } }