Browse Source

商户发起商品审核申请

Yangzw 7 months ago
parent
commit
7fd17cca13
29 changed files with 669 additions and 143 deletions
  1. 25 0
      feifan-module-mall/feifan-module-product-api/src/main/java/cn/newfeifan/mall/module/product/enums/spu/SpuApplyCheckStatusEnum.java
  2. 1 2
      feifan-module-mall/feifan-module-product-biz/src/main/java/cn/newfeifan/mall/module/product/controller/admin/skuapply/vo/SkuApplySaveReqVO.java
  3. 0 6
      feifan-module-mall/feifan-module-product-biz/src/main/java/cn/newfeifan/mall/module/product/controller/admin/spu/vo/ProductSkuSaveReqVO.java
  4. 5 0
      feifan-module-mall/feifan-module-product-biz/src/main/java/cn/newfeifan/mall/module/product/controller/admin/spu/vo/ProductSpuPageReqVO.java
  5. 3 0
      feifan-module-mall/feifan-module-product-biz/src/main/java/cn/newfeifan/mall/module/product/controller/admin/spu/vo/ProductSpuSaveReqVO.java
  6. 6 49
      feifan-module-mall/feifan-module-product-biz/src/main/java/cn/newfeifan/mall/module/product/controller/admin/spuapply/SpuApplyController.java
  7. 3 0
      feifan-module-mall/feifan-module-product-biz/src/main/java/cn/newfeifan/mall/module/product/controller/admin/spuapply/vo/SpuApplyPageReqVO.java
  8. 2 2
      feifan-module-mall/feifan-module-product-biz/src/main/java/cn/newfeifan/mall/module/product/controller/admin/spuapply/vo/SpuApplyRespVO.java
  9. 1 56
      feifan-module-mall/feifan-module-product-biz/src/main/java/cn/newfeifan/mall/module/product/controller/admin/spuapplylog/SpuApplyLogController.java
  10. 33 0
      feifan-module-mall/feifan-module-product-biz/src/main/java/cn/newfeifan/mall/module/product/convert/skuapply/SkuApplyConvert.java
  11. 2 2
      feifan-module-mall/feifan-module-product-biz/src/main/java/cn/newfeifan/mall/module/product/convert/spuapply/SpuApplyConvert.java
  12. 57 2
      feifan-module-mall/feifan-module-product-biz/src/main/java/cn/newfeifan/mall/module/product/dal/dataobject/skuapply/SkuApplyDO.java
  13. 5 0
      feifan-module-mall/feifan-module-product-biz/src/main/java/cn/newfeifan/mall/module/product/dal/dataobject/spu/ProductSpuDO.java
  14. 89 3
      feifan-module-mall/feifan-module-product-biz/src/main/java/cn/newfeifan/mall/module/product/dal/mysql/spuapply/SpuApplyMapper.java
  15. 2 3
      feifan-module-mall/feifan-module-product-biz/src/main/java/cn/newfeifan/mall/module/product/service/sku/ProductSkuService.java
  16. 16 0
      feifan-module-mall/feifan-module-product-biz/src/main/java/cn/newfeifan/mall/module/product/service/skuapply/SkuApplyService.java
  17. 133 0
      feifan-module-mall/feifan-module-product-biz/src/main/java/cn/newfeifan/mall/module/product/service/skuapply/SkuApplyServiceImpl.java
  18. 6 0
      feifan-module-mall/feifan-module-product-biz/src/main/java/cn/newfeifan/mall/module/product/service/spu/ProductSpuService.java
  19. 16 5
      feifan-module-mall/feifan-module-product-biz/src/main/java/cn/newfeifan/mall/module/product/service/spu/ProductSpuServiceImpl.java
  20. 9 0
      feifan-module-mall/feifan-module-product-biz/src/main/java/cn/newfeifan/mall/module/product/service/spuapply/SpuApplyService.java
  21. 200 10
      feifan-module-mall/feifan-module-product-biz/src/main/java/cn/newfeifan/mall/module/product/service/spuapply/SpuApplyServiceImpl.java
  22. 7 0
      feifan-module-system/feifan-module-system-api/src/main/java/cn/newfeifan/mall/module/system/api/sms/dto/code/SmsCodeSendReqDTO.java
  23. 5 0
      feifan-module-system/feifan-module-system-api/src/main/java/cn/newfeifan/mall/module/system/enums/sms/SmsSceneEnum.java
  24. 8 0
      feifan-module-system/feifan-module-system-biz/src/main/java/cn/newfeifan/mall/module/system/service/permission/PermissionService.java
  25. 9 0
      feifan-module-system/feifan-module-system-biz/src/main/java/cn/newfeifan/mall/module/system/service/permission/PermissionServiceImpl.java
  26. 6 0
      feifan-module-system/feifan-module-system-biz/src/main/java/cn/newfeifan/mall/module/system/service/permission/RoleService.java
  27. 6 0
      feifan-module-system/feifan-module-system-biz/src/main/java/cn/newfeifan/mall/module/system/service/permission/RoleServiceImpl.java
  28. 13 3
      feifan-module-system/feifan-module-system-biz/src/main/java/cn/newfeifan/mall/module/system/service/sms/SmsCodeServiceImpl.java
  29. 1 0
      feifan-server/src/main/resources/application.yaml

+ 25 - 0
feifan-module-mall/feifan-module-product-api/src/main/java/cn/newfeifan/mall/module/product/enums/spu/SpuApplyCheckStatusEnum.java

@@ -0,0 +1,25 @@
+package cn.newfeifan.mall.module.product.enums.spu;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 商品申请审核状态表
+ */
+
+@Getter
+@AllArgsConstructor
+public enum SpuApplyCheckStatusEnum {
+
+
+    WAIT_CHECK(0, "待审核"),
+
+    CHECK_PASS(1, "审核通过"),
+
+    CHECK_FAIL(2, "审核不通过"),
+    ;
+
+
+    private final Integer status;
+    private final String mark;
+}

+ 1 - 2
feifan-module-mall/feifan-module-product-biz/src/main/java/cn/newfeifan/mall/module/product/controller/admin/skuapply/vo/SkuApplySaveReqVO.java

@@ -1,6 +1,5 @@
 package cn.newfeifan.mall.module.product.controller.admin.skuapply.vo;
 
-import cn.newfeifan.mall.module.product.controller.admin.spu.vo.ProductSkuSaveReqVO;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.*;
 import java.util.*;
@@ -55,7 +54,7 @@ public class SkuApplySaveReqVO {
     private String mark;
 
     @Schema(description = "属性数组")
-    private List<ProductSkuSaveReqVO.Property> properties;
+    private List<Property> properties;
 
     @Schema(description = "商品属性")
     @Data

+ 0 - 6
feifan-module-mall/feifan-module-product-biz/src/main/java/cn/newfeifan/mall/module/product/controller/admin/spu/vo/ProductSkuSaveReqVO.java

@@ -5,7 +5,6 @@ import lombok.AllArgsConstructor;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
-import javax.validation.constraints.NotEmpty;
 import javax.validation.constraints.NotNull;
 import java.math.BigDecimal;
 import java.util.List;
@@ -13,11 +12,6 @@ import java.util.List;
 @Schema(description = "管理后台 - 商品 SKU 创建/更新 Request VO")
 @Data
 public class ProductSkuSaveReqVO {
-
-    @Schema(description = "商品 SKU 名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "清凉小短袖")
-    @NotEmpty(message = "商品 SKU 名字不能为空")
-    private String name;
-
     @Schema(description = "销售价格,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1999")
     @NotNull(message = "销售价格,单位:分不能为空")
     private Integer price;

+ 5 - 0
feifan-module-mall/feifan-module-product-biz/src/main/java/cn/newfeifan/mall/module/product/controller/admin/spu/vo/ProductSpuPageReqVO.java

@@ -18,6 +18,11 @@ import static cn.newfeifan.mall.framework.common.util.date.DateUtils.FORMAT_YEAR
 @ToString(callSuper = true)
 public class ProductSpuPageReqVO extends PageParam {
 
+    /**
+     * 待审核商品
+     */
+    public static final Integer APPLY_SPU = -1;
+
     /**
      * 出售中商品
      */

+ 3 - 0
feifan-module-mall/feifan-module-product-biz/src/main/java/cn/newfeifan/mall/module/product/controller/admin/spu/vo/ProductSpuSaveReqVO.java

@@ -116,4 +116,7 @@ public class ProductSpuSaveReqVO {
     @Schema(description = "高精度", requiredMode = Schema.RequiredMode.REQUIRED)
     @NotNull(message = "高精度不能为空")
     private Boolean highPrecision;
+
+    @Schema(description = "商品申请id", example = "26655")
+    private Long spuApplyId;
 }

+ 6 - 49
feifan-module-mall/feifan-module-product-biz/src/main/java/cn/newfeifan/mall/module/product/controller/admin/spuapply/SpuApplyController.java

@@ -1,9 +1,5 @@
 package cn.newfeifan.mall.module.product.controller.admin.spuapply;
 
-import cn.newfeifan.mall.module.product.convert.spuapply.SpuApplyConvert;
-import cn.newfeifan.mall.module.product.dal.dataobject.skuapply.SkuApplyDO;
-import cn.newfeifan.mall.module.product.dal.mysql.spuapply.SpuApplyMapper;
-import cn.newfeifan.mall.module.product.service.skuapply.SkuApplyService;
 import org.springframework.web.bind.annotation.*;
 import javax.annotation.Resource;
 import org.springframework.validation.annotation.Validated;
@@ -13,21 +9,11 @@ import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.Operation;
 
 import javax.validation.*;
-import javax.servlet.http.*;
-import java.util.*;
-import java.io.IOException;
-
-import cn.newfeifan.mall.framework.common.pojo.PageParam;
 import cn.newfeifan.mall.framework.common.pojo.PageResult;
 import cn.newfeifan.mall.framework.common.pojo.CommonResult;
 import cn.newfeifan.mall.framework.common.util.object.BeanUtils;
 import static cn.newfeifan.mall.framework.common.pojo.CommonResult.success;
 
-import cn.newfeifan.mall.framework.excel.core.util.ExcelUtils;
-
-import cn.newfeifan.mall.framework.operatelog.core.annotations.OperateLog;
-import static cn.newfeifan.mall.framework.operatelog.core.enums.OperateTypeEnum.*;
-
 import cn.newfeifan.mall.module.product.controller.admin.spuapply.vo.*;
 import cn.newfeifan.mall.module.product.dal.dataobject.spuapply.SpuApplyDO;
 import cn.newfeifan.mall.module.product.service.spuapply.SpuApplyService;
@@ -40,21 +26,17 @@ public class SpuApplyController {
 
     @Resource
     private SpuApplyService spuApplyService;
-    @Resource
-    private SpuApplyMapper spuApplyMapper;
-    @Resource
-    private SkuApplyService skuApplyService;
 
     @PostMapping("/create")
     @Operation(summary = "创建商品spu申请")
-    @PreAuthorize("@ss.hasPermission('product:spu-apply:create')")
+    @PreAuthorize("@ss.hasPermission('product:spu:create')")
     public CommonResult<Long> createSpuApply(@Valid @RequestBody SpuApplySaveReqVO createReqVO) {
         return success(spuApplyService.createSpuApply(createReqVO));
     }
 
     @PutMapping("/update")
     @Operation(summary = "更新商品spu申请")
-    @PreAuthorize("@ss.hasPermission('product:spu-apply:update')")
+    @PreAuthorize("@ss.hasPermission('product:spu:update')")
     public CommonResult<Boolean> updateSpuApply(@Valid @RequestBody SpuApplySaveReqVO updateReqVO) {
         spuApplyService.updateSpuApply(updateReqVO);
         return success(true);
@@ -63,7 +45,7 @@ public class SpuApplyController {
     @DeleteMapping("/delete")
     @Operation(summary = "删除商品spu申请")
     @Parameter(name = "id", description = "编号", required = true)
-    @PreAuthorize("@ss.hasPermission('product:spu-apply:delete')")
+    @PreAuthorize("@ss.hasPermission('product:spu:delete')")
     public CommonResult<Boolean> deleteSpuApply(@RequestParam("id") Long id) {
         spuApplyService.deleteSpuApply(id);
         return success(true);
@@ -72,42 +54,17 @@ public class SpuApplyController {
     @GetMapping("/get-detail")
     @Operation(summary = "获得商品 spu 申请 明细")
     @Parameter(name = "id", description = "编号", required = true, example = "1024")
-    @PreAuthorize("@ss.hasPermission('product:spu-apply:query')")
+    @PreAuthorize("@ss.hasPermission('product:spu:query')")
     public CommonResult<SpuApplyRespVO> getSpuApply(@RequestParam("id") Long id) {
-        // 获得商品 SPU 申请
-        SpuApplyDO spu = spuApplyService.getSpu(id);
-        if (spu == null) {
-            return success(null);
-        }
-        // 查询商品 SKU
-        List<SkuApplyDO> skus = skuApplyService.getSkuListBySpuId(spu.getId());
-
-        String shopName = spuApplyMapper.selectShopNameById(spu.getShopId());
-        CommonResult<SpuApplyRespVO> success = success(SpuApplyConvert.INSTANCE.convert(spu, skus));
-        success.getData().setShopName(shopName);
-
-        return success;
+        return success(spuApplyService.getSpuApplyDetail(id));
     }
 
     @GetMapping("/page")
     @Operation(summary = "获得商品spu申请分页")
-    @PreAuthorize("@ss.hasPermission('product:spu-apply:query')")
+    @PreAuthorize("@ss.hasPermission('product:spu:query')")
     public CommonResult<PageResult<SpuApplyRespVO>> getSpuApplyPage(@Valid SpuApplyPageReqVO pageReqVO) {
         PageResult<SpuApplyDO> pageResult = spuApplyService.getSpuApplyPage(pageReqVO);
         return success(BeanUtils.toBean(pageResult, SpuApplyRespVO.class));
     }
 
-    @GetMapping("/export-excel")
-    @Operation(summary = "导出商品spu申请 Excel")
-    @PreAuthorize("@ss.hasPermission('product:spu-apply:export')")
-    @OperateLog(type = EXPORT)
-    public void exportSpuApplyExcel(@Valid SpuApplyPageReqVO pageReqVO,
-              HttpServletResponse response) throws IOException {
-        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
-        List<SpuApplyDO> list = spuApplyService.getSpuApplyPage(pageReqVO).getList();
-        // 导出 Excel
-        ExcelUtils.write(response, "商品spu申请.xls", "数据", SpuApplyRespVO.class,
-                        BeanUtils.toBean(list, SpuApplyRespVO.class));
-    }
-
 }

+ 3 - 0
feifan-module-mall/feifan-module-product-biz/src/main/java/cn/newfeifan/mall/module/product/controller/admin/spuapply/vo/SpuApplyPageReqVO.java

@@ -128,4 +128,7 @@ public class SpuApplyPageReqVO extends PageParam {
     @Schema(description = "审核状态:默认0表示未审核,1审核通过,2审核不通过", example = "1")
     private Integer checkStatus;
 
+    @Schema(description = "前端请求的tab类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer tabType;
+
 }

+ 2 - 2
feifan-module-mall/feifan-module-product-biz/src/main/java/cn/newfeifan/mall/module/product/controller/admin/spuapply/vo/SpuApplyRespVO.java

@@ -3,7 +3,7 @@ package cn.newfeifan.mall.module.product.controller.admin.spuapply.vo;
 import cn.newfeifan.mall.framework.excel.core.annotations.DictFormat;
 import cn.newfeifan.mall.framework.excel.core.convert.DictConvert;
 import cn.newfeifan.mall.framework.excel.core.convert.MoneyConvert;
-import cn.newfeifan.mall.module.product.controller.admin.spu.vo.ProductSkuRespVO;
+import cn.newfeifan.mall.module.product.controller.admin.skuapply.vo.SkuApplyRespVO;
 import cn.newfeifan.mall.module.product.enums.DictTypeConstants;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.*;
@@ -88,7 +88,7 @@ public class SpuApplyRespVO {
     private Integer stock;
 
     @Schema(description = "SKU 数组")
-    private List<ProductSkuRespVO> skus;
+    private List<SkuApplyRespVO> skus;
 
     // ========== 物流相关字段 =========
 

+ 1 - 56
feifan-module-mall/feifan-module-product-biz/src/main/java/cn/newfeifan/mall/module/product/controller/admin/spuapplylog/SpuApplyLogController.java

@@ -1,31 +1,22 @@
 package cn.newfeifan.mall.module.product.controller.admin.spuapplylog;
 
 import cn.newfeifan.mall.framework.common.pojo.CommonResult;
-import cn.newfeifan.mall.framework.common.pojo.PageParam;
 import cn.newfeifan.mall.framework.common.pojo.PageResult;
 import cn.newfeifan.mall.framework.common.util.object.BeanUtils;
-import cn.newfeifan.mall.framework.excel.core.util.ExcelUtils;
-import cn.newfeifan.mall.framework.operatelog.core.annotations.OperateLog;
 import cn.newfeifan.mall.module.product.controller.admin.spuapplylog.vo.SpuApplyLogPageReqVO;
 import cn.newfeifan.mall.module.product.controller.admin.spuapplylog.vo.SpuApplyLogRespVO;
-import cn.newfeifan.mall.module.product.controller.admin.spuapplylog.vo.SpuApplyLogSaveReqVO;
 import cn.newfeifan.mall.module.product.dal.dataobject.spuapplylog.SpuApplyLogDO;
 import cn.newfeifan.mall.module.product.service.spuapplylog.SpuApplyLogService;
 import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
-import javax.servlet.http.HttpServletResponse;
 import javax.validation.Valid;
-import java.io.IOException;
-import java.util.List;
 
 import static cn.newfeifan.mall.framework.common.pojo.CommonResult.success;
-import static cn.newfeifan.mall.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
 
 @Tag(name = "管理后台 - 商品审核记录")
 @RestController
@@ -36,58 +27,12 @@ public class SpuApplyLogController {
     @Resource
     private SpuApplyLogService spuApplyLogService;
 
-    @PostMapping("/create")
-    @Operation(summary = "创建商品审核记录")
-    @PreAuthorize("@ss.hasPermission('product:spu-apply-log:create')")
-    public CommonResult<Long> createSpuApplyLog(@Valid @RequestBody SpuApplyLogSaveReqVO createReqVO) {
-        return success(spuApplyLogService.createSpuApplyLog(createReqVO));
-    }
-
-    @PutMapping("/update")
-    @Operation(summary = "更新商品审核记录")
-    @PreAuthorize("@ss.hasPermission('product:spu-apply-log:update')")
-    public CommonResult<Boolean> updateSpuApplyLog(@Valid @RequestBody SpuApplyLogSaveReqVO updateReqVO) {
-        spuApplyLogService.updateSpuApplyLog(updateReqVO);
-        return success(true);
-    }
-
-    @DeleteMapping("/delete")
-    @Operation(summary = "删除商品审核记录")
-    @Parameter(name = "id", description = "编号", required = true)
-    @PreAuthorize("@ss.hasPermission('product:spu-apply-log:delete')")
-    public CommonResult<Boolean> deleteSpuApplyLog(@RequestParam("id") Long id) {
-        spuApplyLogService.deleteSpuApplyLog(id);
-        return success(true);
-    }
-
-    @GetMapping("/get")
-    @Operation(summary = "获得商品审核记录")
-    @Parameter(name = "id", description = "编号", required = true, example = "1024")
-    @PreAuthorize("@ss.hasPermission('product:spu-apply-log:query')")
-    public CommonResult<SpuApplyLogRespVO> getSpuApplyLog(@RequestParam("id") Long id) {
-        SpuApplyLogDO spuApplyLog = spuApplyLogService.getSpuApplyLog(id);
-        return success(BeanUtils.toBean(spuApplyLog, SpuApplyLogRespVO.class));
-    }
-
     @GetMapping("/page")
     @Operation(summary = "获得商品审核记录分页")
-    @PreAuthorize("@ss.hasPermission('product:spu-apply-log:query')")
+    @PreAuthorize("@ss.hasPermission('product:spu:query')")
     public CommonResult<PageResult<SpuApplyLogRespVO>> getSpuApplyLogPage(@Valid SpuApplyLogPageReqVO pageReqVO) {
         PageResult<SpuApplyLogDO> pageResult = spuApplyLogService.getSpuApplyLogPage(pageReqVO);
         return success(BeanUtils.toBean(pageResult, SpuApplyLogRespVO.class));
     }
 
-    @GetMapping("/export-excel")
-    @Operation(summary = "导出商品审核记录 Excel")
-    @PreAuthorize("@ss.hasPermission('product:spu-apply-log:export')")
-    @OperateLog(type = EXPORT)
-    public void exportSpuApplyLogExcel(@Valid SpuApplyLogPageReqVO pageReqVO,
-              HttpServletResponse response) throws IOException {
-        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
-        List<SpuApplyLogDO> list = spuApplyLogService.getSpuApplyLogPage(pageReqVO).getList();
-        // 导出 Excel
-        ExcelUtils.write(response, "商品审核记录.xls", "数据", SpuApplyLogRespVO.class,
-                        BeanUtils.toBean(list, SpuApplyLogRespVO.class));
-    }
-
 }

+ 33 - 0
feifan-module-mall/feifan-module-product-biz/src/main/java/cn/newfeifan/mall/module/product/convert/skuapply/SkuApplyConvert.java

@@ -0,0 +1,33 @@
+package cn.newfeifan.mall.module.product.convert.skuapply;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.newfeifan.mall.module.product.dal.dataobject.skuapply.SkuApplyDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+
+/**
+ * 商品 SKUApply Convert
+ *
+ * @author 非繁源码
+ */
+@Mapper
+public interface SkuApplyConvert {
+
+    SkuApplyConvert INSTANCE = Mappers.getMapper(SkuApplyConvert.class);
+
+    default String buildPropertyKey(SkuApplyDO bean) {
+        if (CollUtil.isEmpty(bean.getProperties())) {
+            return StrUtil.EMPTY;
+        }
+        List<SkuApplyDO.Property> properties = new ArrayList<>(bean.getProperties());
+        properties.sort(Comparator.comparing(SkuApplyDO.Property::getValueId));
+        return properties.stream().map(m -> String.valueOf(m.getValueId())).collect(Collectors.joining());
+    }
+
+
+}

+ 2 - 2
feifan-module-mall/feifan-module-product-biz/src/main/java/cn/newfeifan/mall/module/product/convert/spuapply/SpuApplyConvert.java

@@ -2,7 +2,7 @@ package cn.newfeifan.mall.module.product.convert.spuapply;
 
 import cn.newfeifan.mall.framework.common.util.collection.CollectionUtils;
 import cn.newfeifan.mall.framework.common.util.object.BeanUtils;
-import cn.newfeifan.mall.module.product.controller.admin.spu.vo.ProductSkuRespVO;
+import cn.newfeifan.mall.module.product.controller.admin.skuapply.vo.SkuApplyRespVO;
 import cn.newfeifan.mall.module.product.controller.admin.spuapply.vo.SpuApplyRespVO;
 import cn.newfeifan.mall.module.product.dal.dataobject.skuapply.SkuApplyDO;
 import cn.newfeifan.mall.module.product.dal.dataobject.spuapply.SpuApplyDO;
@@ -27,7 +27,7 @@ public interface SpuApplyConvert {
 
     default SpuApplyRespVO convert(SpuApplyDO spu, List<SkuApplyDO> skus) {
         SpuApplyRespVO spuVO = BeanUtils.toBean(spu, SpuApplyRespVO.class);
-        spuVO.setSkus(BeanUtils.toBean(skus, ProductSkuRespVO.class));
+        spuVO.setSkus(BeanUtils.toBean(skus, SkuApplyRespVO.class));
         return spuVO;
     }
 

+ 57 - 2
feifan-module-mall/feifan-module-product-biz/src/main/java/cn/newfeifan/mall/module/product/dal/dataobject/skuapply/SkuApplyDO.java

@@ -1,7 +1,14 @@
 package cn.newfeifan.mall.module.product.dal.dataobject.skuapply;
 
+import cn.newfeifan.mall.framework.common.util.json.JsonUtils;
+import cn.newfeifan.mall.module.product.dal.dataobject.property.ProductPropertyDO;
+import cn.newfeifan.mall.module.product.dal.dataobject.property.ProductPropertyValueDO;
+import cn.newfeifan.mall.module.product.dal.dataobject.sku.ProductSkuDO;
+import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler;
 import lombok.*;
 import java.math.BigDecimal;
+import java.util.List;
+
 import com.baomidou.mybatisplus.annotation.*;
 import cn.newfeifan.mall.framework.mybatis.core.dataobject.BaseDO;
 
@@ -30,9 +37,10 @@ public class SkuApplyDO extends BaseDO {
      */
     private Long spuApplyId;
     /**
-     * 属性数组,JSON 格式 [{propertId: , valueId: }, {propertId: , valueId: }]
+     * 属性数组,JSON 格式
      */
-    private String properties;
+    @TableField(typeHandler = SkuApplyDO.PropertyTypeHandler.class)
+    private List<SkuApplyDO.Property> properties;
     /**
      * 商品价格,单位:分
      */
@@ -106,4 +114,51 @@ public class SkuApplyDO extends BaseDO {
      */
     private BigDecimal highPrecisionSettlementPrice;
 
+    @Data
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class Property {
+
+        /**
+         * 属性编号
+         * 关联 {@link ProductPropertyDO#getId()}
+         */
+        private Long propertyId;
+        /**
+         * 属性名字
+         * 冗余 {@link ProductPropertyDO#getName()}
+         *
+         * 注意:每次属性名字发生变化时,需要更新该冗余
+         */
+        private String propertyName;
+
+        /**
+         * 属性值编号
+         * 关联 {@link ProductPropertyValueDO#getId()}
+         */
+        private Long valueId;
+        /**
+         * 属性值名字
+         * 冗余 {@link ProductPropertyValueDO#getName()}
+         *
+         * 注意:每次属性值名字发生变化时,需要更新该冗余
+         */
+        private String valueName;
+
+    }
+
+    public static class PropertyTypeHandler extends AbstractJsonTypeHandler<Object> {
+
+        @Override
+        protected Object parse(String json) {
+            return JsonUtils.parseArray(json, ProductSkuDO.Property.class);
+        }
+
+        @Override
+        protected String toJson(Object obj) {
+            return JsonUtils.toJsonString(obj);
+        }
+
+    }
+
 }

+ 5 - 0
feifan-module-mall/feifan-module-product-biz/src/main/java/cn/newfeifan/mall/module/product/dal/dataobject/spu/ProductSpuDO.java

@@ -196,4 +196,9 @@ public class ProductSpuDO extends BaseDO {
      */
     private BigDecimal highPrecisionPrice;
 
+    /**
+     * 商品申请id
+     */
+    private Long spuApplyId;
+
 }

+ 89 - 3
feifan-module-mall/feifan-module-product-biz/src/main/java/cn/newfeifan/mall/module/product/dal/mysql/spuapply/SpuApplyMapper.java

@@ -1,10 +1,16 @@
 package cn.newfeifan.mall.module.product.dal.mysql.spuapply;
 
 
+import cn.hutool.core.util.ObjectUtil;
 import cn.newfeifan.mall.framework.common.pojo.PageResult;
 import cn.newfeifan.mall.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.newfeifan.mall.framework.mybatis.core.mapper.BaseMapperX;
+import cn.newfeifan.mall.module.product.controller.admin.spu.vo.ProductSpuPageReqVO;
 import cn.newfeifan.mall.module.product.dal.dataobject.spuapply.SpuApplyDO;
+import cn.newfeifan.mall.module.product.enums.ProductConstants;
+import cn.newfeifan.mall.module.product.enums.spu.ProductSpuStatusEnum;
+import cn.newfeifan.mall.module.product.enums.spu.SpuApplyCheckStatusEnum;
+import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
 import org.apache.ibatis.annotations.Mapper;
 import cn.newfeifan.mall.module.product.controller.admin.spuapply.vo.*;
 import org.apache.ibatis.annotations.Select;
@@ -18,7 +24,8 @@ import org.apache.ibatis.annotations.Select;
 public interface SpuApplyMapper extends BaseMapperX<SpuApplyDO> {
 
     default PageResult<SpuApplyDO> selectPage(SpuApplyPageReqVO reqVO) {
-        return selectPage(reqVO, new LambdaQueryWrapperX<SpuApplyDO>()
+        Integer tabType = reqVO.getTabType();
+        LambdaQueryWrapperX<SpuApplyDO> wrapper = new LambdaQueryWrapperX<SpuApplyDO>()
                 .likeIfPresent(SpuApplyDO::getName, reqVO.getName())
                 .eqIfPresent(SpuApplyDO::getKeyword, reqVO.getKeyword())
                 .eqIfPresent(SpuApplyDO::getIntroduction, reqVO.getIntroduction())
@@ -55,10 +62,89 @@ public interface SpuApplyMapper extends BaseMapperX<SpuApplyDO> {
                 .eqIfPresent(SpuApplyDO::getCheckSystemUserId, reqVO.getCheckSystemUserId())
                 .betweenIfPresent(SpuApplyDO::getCheckTime, reqVO.getCheckTime())
                 .eqIfPresent(SpuApplyDO::getCheckComment, reqVO.getCheckComment())
-                .eqIfPresent(SpuApplyDO::getCheckStatus, reqVO.getCheckStatus())
-                .orderByDesc(SpuApplyDO::getId));
+                .orderByDesc(SpuApplyDO::getId);
+
+        if(reqVO.getCheckStatus().equals(1)){
+            wrapper.ne(SpuApplyDO::getCheckStatus, 0);
+        }else{
+            wrapper.eq(SpuApplyDO::getCheckStatus, 0);
+        }
+        appendTabQuery(tabType, wrapper);
+
+        return selectPage(reqVO, wrapper);
     }
 
     @Select("select name from sale_shop where id=#{id}")
     String selectShopNameById(Long id);
+
+    /**
+     * 添加后台 Tab 选项的查询条件
+     *
+     * @param tabType 标签类型
+     * @param query   查询条件
+     */
+    static void appendTabQuery(Integer tabType, LambdaQueryWrapperX<SpuApplyDO> query) {
+        // 出售中商品
+        if (ObjectUtil.equals(ProductSpuPageReqVO.FOR_SALE, tabType)) {
+            query.eqIfPresent(SpuApplyDO::getStatus, ProductSpuStatusEnum.ENABLE.getStatus());
+        }
+        // 仓储中商品
+        if (ObjectUtil.equals(ProductSpuPageReqVO.IN_WAREHOUSE, tabType)) {
+            query.eqIfPresent(SpuApplyDO::getStatus, ProductSpuStatusEnum.DISABLE.getStatus());
+        }
+        // 已售空商品
+        if (ObjectUtil.equals(ProductSpuPageReqVO.SOLD_OUT, tabType)) {
+            query.eqIfPresent(SpuApplyDO::getStock, 0);
+        }
+        // 警戒库存
+        if (ObjectUtil.equals(ProductSpuPageReqVO.ALERT_STOCK, tabType)) {
+            query.le(SpuApplyDO::getStock, ProductConstants.ALERT_STOCK)
+                    // 如果库存触发警戒库存且状态为回收站的话则不在警戒库存列表展示
+                    .notIn(SpuApplyDO::getStatus, ProductSpuStatusEnum.RECYCLE.getStatus());
+        }
+        // 回收站
+        if (ObjectUtil.equals(ProductSpuPageReqVO.RECYCLE_BIN, tabType)) {
+            query.eqIfPresent(SpuApplyDO::getStatus, ProductSpuStatusEnum.RECYCLE.getStatus());
+        }
+    }
+
+    default Long selectCount(SFunction<SpuApplyDO, ?> field, Object value, ProductSpuPageReqVO pageVO){
+        return selectCount(new LambdaQueryWrapperX<SpuApplyDO>()
+                .eq(field, value)
+                .likeIfPresent(SpuApplyDO::getName, pageVO.getName())
+                .eqIfPresent(SpuApplyDO::getShopId, pageVO.getShopId())
+                .eqIfPresent(SpuApplyDO::getCategoryId, pageVO.getCategoryId())
+                .eqIfPresent(SpuApplyDO::getMerchantId, pageVO.getMerchantId())
+                .betweenIfPresent(SpuApplyDO::getCreateTime, pageVO.getCreateTime())
+                .ne(SpuApplyDO::getCheckStatus, SpuApplyCheckStatusEnum.WAIT_CHECK.getStatus())
+        );
+    }
+
+    default Long selectCount(ProductSpuPageReqVO pageVO) {
+        LambdaQueryWrapperX<SpuApplyDO> queryWrapper = new LambdaQueryWrapperX<>();
+        // 库存小于等于警戒库存
+        queryWrapper
+                .likeIfPresent(SpuApplyDO::getName, pageVO.getName())
+                .eqIfPresent(SpuApplyDO::getShopId, pageVO.getShopId())
+                .eqIfPresent(SpuApplyDO::getCategoryId, pageVO.getCategoryId())
+                .eqIfPresent(SpuApplyDO::getMerchantId, pageVO.getMerchantId())
+                .betweenIfPresent(SpuApplyDO::getCreateTime, pageVO.getCreateTime())
+                .le(SpuApplyDO::getStock, ProductConstants.ALERT_STOCK)
+                // 如果库存触发警戒库存且状态为回收站的话则不计入触发警戒库存的个数
+                .notIn(SpuApplyDO::getStatus, ProductSpuStatusEnum.RECYCLE.getStatus())
+                .ne(SpuApplyDO::getCheckStatus, SpuApplyCheckStatusEnum.WAIT_CHECK.getStatus())
+        ;
+        return selectCount(queryWrapper);
+    }
+
+    default Long selectCount(Integer checkStatus, ProductSpuPageReqVO pageVO){
+        return selectCount(new LambdaQueryWrapperX<SpuApplyDO>()
+                .eq(SpuApplyDO::getCheckStatus, checkStatus)
+                .likeIfPresent(SpuApplyDO::getName, pageVO.getName())
+                .eqIfPresent(SpuApplyDO::getShopId, pageVO.getShopId())
+                .eqIfPresent(SpuApplyDO::getCategoryId, pageVO.getCategoryId())
+                .eqIfPresent(SpuApplyDO::getMerchantId, pageVO.getMerchantId())
+                .betweenIfPresent(SpuApplyDO::getCreateTime, pageVO.getCreateTime())
+        );
+    }
 }

+ 2 - 3
feifan-module-mall/feifan-module-product-biz/src/main/java/cn/newfeifan/mall/module/product/service/sku/ProductSkuService.java

@@ -4,7 +4,6 @@ import cn.newfeifan.mall.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
 import cn.newfeifan.mall.module.product.controller.admin.spu.vo.ProductSkuSaveReqVO;
 import cn.newfeifan.mall.module.product.dal.dataobject.sku.ProductSkuDO;
 
-import javax.validation.constraints.NotNull;
 import java.util.Collection;
 import java.util.List;
 
@@ -42,9 +41,9 @@ public interface ProductSkuService {
      * 对 sku 的组合的属性等进行合法性校验
      *
      * @param list          sku组合的集合
-     * @param highPrecision
+     * @param highPrecision 是否高精度商品
      */
-    void validateSkuList(List<ProductSkuSaveReqVO> list, Boolean specType, @NotNull Boolean highPrecision);
+    void validateSkuList(List<ProductSkuSaveReqVO> list, Boolean specType, Boolean highPrecision);
 
     /**
      * 批量创建 SKU

+ 16 - 0
feifan-module-mall/feifan-module-product-biz/src/main/java/cn/newfeifan/mall/module/product/service/skuapply/SkuApplyService.java

@@ -58,4 +58,20 @@ public interface SkuApplyService {
      * @return 商品sku 申请 集合
      */
     List<SkuApplyDO> getSkuListBySpuId(Long spuApplyId);
+
+    /**
+     * 根据 SPUApply 编号,批量更新它的 SKU 信息
+     *
+     * @param spuApplyId SPU 编码
+     * @param skus  SKU 的集合
+     */
+    void updateSkuList(Long spuApplyId, List<SkuApplySaveReqVO> skus);
+
+    /**
+     * 对 sku 的组合的属性等进行合法性校验
+     *
+     * @param list          sku组合的集合
+     * @param highPrecision 是否高精度商品
+     */
+    void validateSkuList(List<SkuApplySaveReqVO> list, Boolean specType, Boolean highPrecision);
 }

+ 133 - 0
feifan-module-mall/feifan-module-product-biz/src/main/java/cn/newfeifan/mall/module/product/service/skuapply/SkuApplyServiceImpl.java

@@ -1,10 +1,23 @@
 package cn.newfeifan.mall.module.product.service.skuapply;
 
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.newfeifan.mall.module.product.convert.skuapply.SkuApplyConvert;
+import cn.newfeifan.mall.module.product.dal.dataobject.property.ProductPropertyDO;
+import cn.newfeifan.mall.module.product.dal.dataobject.property.ProductPropertyValueDO;
+import cn.newfeifan.mall.module.product.service.property.ProductPropertyService;
+import cn.newfeifan.mall.module.product.service.property.ProductPropertyValueService;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 import javax.annotation.Resource;
+
+import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
+import java.math.BigDecimal;
 import java.util.*;
+import java.util.stream.Collectors;
+
 import cn.newfeifan.mall.module.product.controller.admin.skuapply.vo.*;
 import cn.newfeifan.mall.module.product.dal.dataobject.skuapply.SkuApplyDO;
 import cn.newfeifan.mall.framework.common.pojo.PageResult;
@@ -13,6 +26,8 @@ import cn.newfeifan.mall.framework.common.util.object.BeanUtils;
 import cn.newfeifan.mall.module.product.dal.mysql.skuapply.SkuApplyMapper;
 
 import static cn.newfeifan.mall.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.newfeifan.mall.framework.common.util.collection.CollectionUtils.convertMap;
+import static cn.newfeifan.mall.framework.common.util.collection.CollectionUtils.convertSet;
 import static cn.newfeifan.mall.module.product.enums.ErrorCodeConstants.*;
 
 /**
@@ -27,6 +42,12 @@ public class SkuApplyServiceImpl implements SkuApplyService {
     @Resource
     private SkuApplyMapper skuApplyMapper;
 
+    @Resource
+    @Lazy // 循环依赖,避免报错
+    private ProductPropertyService productPropertyService;
+    @Resource
+    private ProductPropertyValueService productPropertyValueService;
+
     @Override
     public Long createSkuApply(SkuApplySaveReqVO createReqVO) {
         // 插入
@@ -71,4 +92,116 @@ public class SkuApplyServiceImpl implements SkuApplyService {
         return skuApplyMapper.selectListBySpuId(spuApplyId);
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateSkuList(Long spuApplyId, List<SkuApplySaveReqVO> skus) {
+        // 构建属性与 SKU 的映射关系;
+        Map<String, Long> existsSkuMap = convertMap(skuApplyMapper.selectListBySpuId(spuApplyId),
+                SkuApplyConvert.INSTANCE::buildPropertyKey, SkuApplyDO::getId);
+
+        // 拆分三个集合,新插入的、需要更新的、需要删除的
+        List<SkuApplyDO> insertSkus = new ArrayList<>();
+        List<SkuApplyDO> updateSkus = new ArrayList<>();
+        List<SkuApplyDO> allUpdateSkus = BeanUtils.toBean(skus, SkuApplyDO.class, sku -> sku.setSpuApplyId(spuApplyId));
+        allUpdateSkus.forEach(sku -> {
+            String propertiesKey = SkuApplyConvert.INSTANCE.buildPropertyKey(sku);
+            // 1、找得到的,进行更新
+            Long existsSkuId = existsSkuMap.remove(propertiesKey);
+            if (existsSkuId != null) {
+                sku.setId(existsSkuId);
+                updateSkus.add(sku);
+                return;
+            }
+            // 2、找不到,进行插入
+            sku.setSpuApplyId(spuApplyId);
+            insertSkus.add(sku);
+        });
+
+        // 执行最终的批量操作
+        if (CollUtil.isNotEmpty(insertSkus)) {
+            skuApplyMapper.insertBatch(insertSkus);
+        }
+        if (CollUtil.isNotEmpty(updateSkus)) {
+            updateSkus.forEach(sku -> skuApplyMapper.updateById(sku));
+        }
+        if (CollUtil.isNotEmpty(existsSkuMap)) {
+            skuApplyMapper.deleteBatchIds(existsSkuMap.values());
+        }
+    }
+
+    @Override
+    public void validateSkuList(List<SkuApplySaveReqVO> skus, Boolean specType, Boolean highPrecision) {
+        // 0、校验skus是否为空
+        if (CollUtil.isEmpty(skus)) {
+            throw exception(SKU_NOT_EXISTS);
+        }
+
+        // 校验结算价是否大于成本价
+        for (SkuApplySaveReqVO sku : skus) {
+            // 如果是高精度商品
+            if(highPrecision){
+                sku.setSettlementPrice(0);
+                if(sku.getHighPrecisionSettlementPrice().compareTo(sku.getHighPrecisionPrice()) > 0){
+                    throw exception(HIGH_PRECISION_PRICE_NOT_ENOUGH);
+                }
+            }else {
+                sku.setHighPrecisionSettlementPrice(BigDecimal.ZERO);
+                if(sku.getSettlementPrice() > sku.getCostPrice()){
+                    throw exception(HIGH_PRECISION_PRICE_NOT_ENOUGH);
+                }
+            }
+        }
+
+        // 单规格,赋予单规格默认属性
+        if (ObjectUtil.equal(specType, false)) {
+            SkuApplySaveReqVO skuVO = skus.get(0);
+            List<SkuApplySaveReqVO.Property> properties = new ArrayList<>();
+            SkuApplySaveReqVO.Property property = new SkuApplySaveReqVO.Property()
+                    .setPropertyId(ProductPropertyDO.ID_DEFAULT).setPropertyName(ProductPropertyDO.NAME_DEFAULT)
+                    .setValueId(ProductPropertyValueDO.ID_DEFAULT).setValueName(ProductPropertyValueDO.NAME_DEFAULT);
+            properties.add(property);
+            skuVO.setProperties(properties);
+            return; // 单规格不需要后续的校验
+        }
+
+        // 1、校验属性项存在
+        Set<Long> propertyIds = skus.stream().filter(p -> p.getProperties() != null)
+                // 遍历多个 Property 属性
+                .flatMap(p -> p.getProperties().stream())
+                // 将每个 Property 转换成对应的 propertyId,最后形成集合
+                .map(SkuApplySaveReqVO.Property::getPropertyId)
+                .collect(Collectors.toSet());
+        List<ProductPropertyDO> propertyList = productPropertyService.getPropertyList(propertyIds);
+        if (propertyList.size() != propertyIds.size()) {
+            throw exception(PROPERTY_NOT_EXISTS);
+        }
+
+        // 2. 校验,一个 SKU 下,没有重复的属性。校验方式是,遍历每个 SKU ,看看是否有重复的属性 propertyId
+        Map<Long, ProductPropertyValueDO> propertyValueMap = convertMap(productPropertyValueService.getPropertyValueListByPropertyId(propertyIds), ProductPropertyValueDO::getId);
+        skus.forEach(sku -> {
+            Set<Long> skuPropertyIds = convertSet(sku.getProperties(), propertyItem -> propertyValueMap.get(propertyItem.getValueId()).getPropertyId());
+            if (skuPropertyIds.size() != sku.getProperties().size()) {
+                throw exception(SKU_PROPERTIES_DUPLICATED);
+            }
+        });
+
+        // 3. 再校验,每个 Sku 的属性值的数量,是一致的。
+        int attrValueIdsSize = skus.get(0).getProperties().size();
+        for (int i = 1; i < skus.size(); i++) {
+            if (attrValueIdsSize != skus.get(i).getProperties().size()) {
+                throw exception(SPU_ATTR_NUMBERS_MUST_BE_EQUALS);
+            }
+        }
+
+        // 4. 最后校验,每个 Sku 之间不是重复的
+        // 每个元素,都是一个 Sku 的 attrValueId 集合。这样,通过最外层的 Set ,判断是否有重复的.
+        Set<Set<Long>> skuAttrValues = new HashSet<>();
+        for (SkuApplySaveReqVO sku : skus) {
+            // 添加失败,说明重复
+            if (!skuAttrValues.add(convertSet(sku.getProperties(), SkuApplySaveReqVO.Property::getValueId))) {
+                throw exception(SPU_SKU_NOT_DUPLICATE);
+            }
+        }
+    }
+
 }

+ 6 - 0
feifan-module-mall/feifan-module-product-biz/src/main/java/cn/newfeifan/mall/module/product/service/spu/ProductSpuService.java

@@ -140,4 +140,10 @@ public interface ProductSpuService {
     @Async
     void updateBrowseCount(Long id, int incrCount);
 
+    /**
+     * 根据申请id获取商品
+     * @param applyId 申请id
+     * @return spu
+     */
+    ProductSpuDO getSpuByApplyId(Long applyId);
 }

+ 16 - 5
feifan-module-mall/feifan-module-product-biz/src/main/java/cn/newfeifan/mall/module/product/service/spu/ProductSpuServiceImpl.java

@@ -11,7 +11,9 @@ import cn.newfeifan.mall.module.product.controller.admin.spu.vo.*;
 import cn.newfeifan.mall.module.product.controller.app.spu.vo.AppProductSpuPageReqVO;
 import cn.newfeifan.mall.module.product.dal.dataobject.category.ProductCategoryDO;
 import cn.newfeifan.mall.module.product.dal.dataobject.spu.ProductSpuDO;
+import cn.newfeifan.mall.module.product.dal.dataobject.spuapply.SpuApplyDO;
 import cn.newfeifan.mall.module.product.dal.mysql.spu.ProductSpuMapper;
+import cn.newfeifan.mall.module.product.dal.mysql.spuapply.SpuApplyMapper;
 import cn.newfeifan.mall.module.product.enums.spu.ProductSpuStatusEnum;
 import cn.newfeifan.mall.module.product.service.category.ProductCategoryService;
 import cn.newfeifan.mall.module.product.service.sku.ProductSkuService;
@@ -52,6 +54,8 @@ public class ProductSpuServiceImpl implements ProductSpuService {
     private ProductCategoryService categoryService;
     @Resource
     private AdminUserService userService;
+    @Resource
+    private SpuApplyMapper spuApplyMapper;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
@@ -195,6 +199,11 @@ public class ProductSpuServiceImpl implements ProductSpuService {
         productSpuMapper.updateBrowseCount(id , incrCount);
     }
 
+    @Override
+    public ProductSpuDO getSpuByApplyId(Long applyId) {
+        return productSpuMapper.selectOne(ProductSpuDO::getSpuApplyId, applyId);
+    }
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void deleteSpu(Long id) {
@@ -292,22 +301,24 @@ public class ProductSpuServiceImpl implements ProductSpuService {
         pageVO.setMerchantId(userShopDetails.getMerId());
 
         Map<Integer, Long> counts = Maps.newLinkedHashMapWithExpectedSize(5);
+        counts.put(ProductSpuPageReqVO.APPLY_SPU,
+                spuApplyMapper.selectCount(0,pageVO));
 
         // 查询销售中的商品数量
         counts.put(ProductSpuPageReqVO.FOR_SALE,
-                productSpuMapper.selectCount(ProductSpuDO::getStatus, ProductSpuStatusEnum.ENABLE.getStatus(),pageVO));
+                spuApplyMapper.selectCount(SpuApplyDO::getStatus, ProductSpuStatusEnum.ENABLE.getStatus(),pageVO));
         // 查询仓库中的商品数量
         counts.put(ProductSpuPageReqVO.IN_WAREHOUSE,
-                productSpuMapper.selectCount(ProductSpuDO::getStatus, ProductSpuStatusEnum.DISABLE.getStatus(),pageVO));
+                spuApplyMapper.selectCount(SpuApplyDO::getStatus, ProductSpuStatusEnum.DISABLE.getStatus(),pageVO));
         // 查询售空的商品数量
         counts.put(ProductSpuPageReqVO.SOLD_OUT,
-                productSpuMapper.selectCount(ProductSpuDO::getStock, 0,pageVO));
+                spuApplyMapper.selectCount(SpuApplyDO::getStock, 0,pageVO));
         // 查询触发警戒库存的商品数量
         counts.put(ProductSpuPageReqVO.ALERT_STOCK,
-                productSpuMapper.selectCount(pageVO));
+                spuApplyMapper.selectCount(pageVO));
         // 查询回收站中的商品数量
         counts.put(ProductSpuPageReqVO.RECYCLE_BIN,
-                productSpuMapper.selectCount(ProductSpuDO::getStatus, ProductSpuStatusEnum.RECYCLE.getStatus(),pageVO));
+                spuApplyMapper.selectCount(SpuApplyDO::getStatus, ProductSpuStatusEnum.RECYCLE.getStatus(),pageVO));
         return counts;
     }
 

+ 9 - 0
feifan-module-mall/feifan-module-product-biz/src/main/java/cn/newfeifan/mall/module/product/service/spuapply/SpuApplyService.java

@@ -1,10 +1,12 @@
 package cn.newfeifan.mall.module.product.service.spuapply;
 
 import javax.validation.*;
+
 import cn.newfeifan.mall.module.product.controller.admin.spuapply.vo.*;
 import cn.newfeifan.mall.module.product.dal.dataobject.spuapply.SpuApplyDO;
 import cn.newfeifan.mall.framework.common.pojo.PageResult;
 
+
 /**
  * 商品spu申请 Service 接口
  *
@@ -57,4 +59,11 @@ public interface SpuApplyService {
      * @return 商品 SPU 申请
      */
     SpuApplyDO getSpu(Long id);
+
+    /**
+     * 获得商品 SPU 申请详情
+     * @param id id
+     * @return 申请详情
+     */
+    SpuApplyRespVO getSpuApplyDetail(Long id);
 }

+ 200 - 10
feifan-module-mall/feifan-module-product-biz/src/main/java/cn/newfeifan/mall/module/product/service/spuapply/SpuApplyServiceImpl.java

@@ -1,17 +1,28 @@
 package cn.newfeifan.mall.module.product.service.spuapply;
 
 import cn.newfeifan.mall.module.product.controller.admin.skuapply.vo.SkuApplySaveReqVO;
-import cn.newfeifan.mall.module.product.controller.admin.spu.vo.ProductSkuSaveReqVO;
+import cn.newfeifan.mall.module.product.controller.admin.spu.vo.ProductSpuSaveReqVO;
+import cn.newfeifan.mall.module.product.convert.spuapply.SpuApplyConvert;
+import cn.newfeifan.mall.module.product.dal.dataobject.skuapply.SkuApplyDO;
+import cn.newfeifan.mall.module.product.dal.dataobject.spu.ProductSpuDO;
 import cn.newfeifan.mall.module.product.enums.spu.ProductSpuStatusEnum;
-import cn.newfeifan.mall.module.product.service.sku.ProductSkuService;
+import cn.newfeifan.mall.module.product.enums.spu.SpuApplyCheckStatusEnum;
+import cn.newfeifan.mall.module.product.service.category.ProductCategoryService;
 import cn.newfeifan.mall.module.product.service.skuapply.SkuApplyService;
+import cn.newfeifan.mall.module.product.service.spu.ProductSpuService;
+import cn.newfeifan.mall.module.system.api.sms.SmsCodeApi;
+import cn.newfeifan.mall.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
 import cn.newfeifan.mall.module.system.controller.admin.user.vo.user.UserShopDetailsVO;
+import cn.newfeifan.mall.module.system.dal.dataobject.user.AdminUserDO;
+import cn.newfeifan.mall.module.system.enums.sms.SmsSceneEnum;
+import cn.newfeifan.mall.module.system.service.permission.PermissionService;
+import cn.newfeifan.mall.module.system.service.permission.RoleService;
 import cn.newfeifan.mall.module.system.service.user.AdminUserService;
-import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
 
+import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
 import java.util.*;
@@ -24,8 +35,10 @@ import cn.newfeifan.mall.framework.common.util.object.BeanUtils;
 import cn.newfeifan.mall.module.product.dal.mysql.spuapply.SpuApplyMapper;
 
 import static cn.newfeifan.mall.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.newfeifan.mall.framework.common.util.collection.CollectionUtils.getMinValue;
-import static cn.newfeifan.mall.framework.common.util.collection.CollectionUtils.getSumValue;
+import static cn.newfeifan.mall.framework.common.util.collection.CollectionUtils.*;
+import static cn.newfeifan.mall.framework.common.util.servlet.ServletUtils.getClientIP;
+import static cn.newfeifan.mall.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+import static cn.newfeifan.mall.module.product.dal.dataobject.category.ProductCategoryDO.CATEGORY_LEVEL;
 import static cn.newfeifan.mall.module.product.enums.ErrorCodeConstants.*;
 
 /**
@@ -42,24 +55,59 @@ public class SpuApplyServiceImpl implements SpuApplyService {
     @Resource
     private SkuApplyService skuApplyService;
     @Resource
-    @Lazy // 循环依赖,避免报错
-    private ProductSkuService productSkuService;
-    @Resource
     private AdminUserService adminUserService;
+    @Resource
+    private ProductCategoryService categoryService;
+    @Resource
+    private AdminUserService userService;
+    @Resource
+    private ProductSpuService spuService;
+    @Resource
+    private RoleService roleService;
+    @Resource
+    private PermissionService permissionService;
+    @Resource
+    private SmsCodeApi smsCodeApi;
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public Long createSpuApply(SpuApplySaveReqVO createReqVO) {
+// 校验分类、品牌
+        validateCategory(createReqVO.getCategoryId());
+//        brandService.validateProductBrand(createReqVO.getBrandId());
+
+        // 根据商户ID查询对应的店铺ID
+        // todo 后续如果存在多店铺的情况
+
+
         // 校验 SKU
         List<SkuApplySaveReqVO> skuSaveReqList = createReqVO.getSkus();
-        productSkuService.validateSkuList(BeanUtils.toBean(skuSaveReqList, ProductSkuSaveReqVO.class), createReqVO.getSpecType(), createReqVO.getHighPrecision());
+        skuApplyService.validateSkuList(skuSaveReqList, createReqVO.getSpecType(), createReqVO.getHighPrecision());
 
         SpuApplyDO spuApply = BeanUtils.toBean(createReqVO, SpuApplyDO.class);
         // 初始化 SPU 中 SKU 相关属性
         initSpuFromSkus(spuApply, skuSaveReqList);
+
+        //加入商户、店铺信息
+        UserShopDetailsVO userShopDetails = userService.getUserShopDetails();
+
+        spuApply.setShopId(userShopDetails.getShopId());
+        spuApply.setMerchantId(userShopDetails.getMerId());
+
+        for (SkuApplySaveReqVO productSkuSaveReqVO : skuSaveReqList) {
+            productSkuSaveReqVO.setShopId(userShopDetails.getShopId());
+            productSkuSaveReqVO.setMerchantId(userShopDetails.getMerId());
+        }
+
+        spuApply.setApplyMemberUserId(getLoginUserId());
         // 插入 SPU
         spuApplyMapper.insert(spuApply);
         // 插入 SKU
         skuApplyService.createSkuList(spuApply.getId(), skuSaveReqList);
+
+        // 发送短信
+        sendSms(createReqVO.getName());
+
         // 返回
         return spuApply.getId();
     }
@@ -96,12 +144,129 @@ public class SpuApplyServiceImpl implements SpuApplyService {
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public void updateSpuApply(SpuApplySaveReqVO updateReqVO) {
         // 校验存在
         validateSpuApplyExists(updateReqVO.getId());
-        // 更新
+
+        // 校验分类、品牌
+        validateCategory(updateReqVO.getCategoryId());
+
+        // 获取原来的SPU对比
+        SpuApplyRespVO spuApplyDetail = getSpuApplyDetail(updateReqVO.getId());
+
+
+//        brandService.validateProductBrand(updateReqVO.getBrandId());
+        // 校验SKU
+        List<SkuApplySaveReqVO> skuSaveReqList = updateReqVO.getSkus();
+        skuApplyService.validateSkuList(skuSaveReqList, updateReqVO.getSpecType(), updateReqVO.getHighPrecision());
+
+        // 更新 SPU
         SpuApplyDO updateObj = BeanUtils.toBean(updateReqVO, SpuApplyDO.class);
+        initSpuFromSkus(updateObj, skuSaveReqList);
+
+        //最大推广费, 单位: 分  add by Ben
+        Integer maxPromotionFee = 0;
+
+        for (SkuApplySaveReqVO productSkuSaveReqVO : skuSaveReqList) {
+            productSkuSaveReqVO.setShopId(updateReqVO.getShopId());
+            productSkuSaveReqVO.setMerchantId(updateReqVO.getMerchantId());
+
+            Integer skuPromotionFee = productSkuSaveReqVO.getPromotionFee();
+            if (skuPromotionFee > maxPromotionFee)
+                maxPromotionFee = skuPromotionFee;
+        }
+
+        updateObj.setPromotionFee(maxPromotionFee);
+        updateObj.setCheckStatus(SpuApplyCheckStatusEnum.WAIT_CHECK.getStatus());
+
+        // 有部分字段更新了才需要审核,否则不需要审核
+        if (spuApplyDetail.getName().equals(updateReqVO.getName()) &&
+                spuApplyDetail.getProducerArea().equals(updateReqVO.getProducerArea()) &&
+                spuApplyDetail.getKeyword().equals(updateReqVO.getKeyword()) &&
+                spuApplyDetail.getIntroduction().equals(updateReqVO.getIntroduction()) &&
+                spuApplyDetail.getSliderPicUrls().equals(updateReqVO.getSliderPicUrls()) &&
+                spuApplyDetail.getDescription().equals(updateReqVO.getDescription()) &&
+                spuApplyDetail.getHighPrecision().equals(updateReqVO.getHighPrecision()) &&
+                spuApplyDetail.getSkus().size() == updateReqVO.getSkus().size()
+        ) {
+            boolean flag = isFlag(updateReqVO, spuApplyDetail);
+
+            // 如果关键消息没有变动那就不用审核,直接更新到商品中,并且不用发送审核短信
+            if (flag) {
+                updateObj.setCheckStatus(SpuApplyCheckStatusEnum.CHECK_PASS.getStatus());
+
+                // 更新商品
+                ProductSpuSaveReqVO bean = BeanUtils.toBean(updateReqVO, ProductSpuSaveReqVO.class);
+
+                ProductSpuDO spu = spuService.getSpuByApplyId(updateReqVO.getId());
+                bean.setId(spu.getId());
+                spuService.updateSpu(bean);
+            }
+        }
+
         spuApplyMapper.updateById(updateObj);
+        // 批量更新 SKU
+        skuApplyService.updateSkuList(updateObj.getId(), updateReqVO.getSkus());
+
+        // 发送审核短信
+        if (updateObj.getCheckStatus().equals(SpuApplyCheckStatusEnum.WAIT_CHECK.getStatus())) {
+            // 发送短信
+            sendSms(updateReqVO.getName());
+        }
+
+    }
+
+    /**
+     * 发送商品申请短信
+     *
+     * @param spuName 商品名称
+     */
+    private void sendSms(String spuName) {
+        // 商品审核角色编号
+        Long ptSpuCheck = roleService.getPtSpuCheck();
+        // 系统用户ids
+        List<Long> userIds = permissionService.getPtSpuCheckUserIds(ptSpuCheck);
+        // 系统用户s
+        List<AdminUserDO> users = adminUserService.getUserList(userIds);
+
+        for (AdminUserDO user : users) {
+            SmsCodeSendReqDTO smsCodeSendReqDTO = SmsCodeSendReqDTO.builder()
+                    .mobile(user.getMobile())
+                    .scene(SmsSceneEnum.SPU_APPLY_INFORM.getScene())
+                    .createIp(getClientIP())
+                    .name(spuName)
+                    .build();
+            smsCodeApi.sendSmsCode(smsCodeSendReqDTO);
+        }
+    }
+
+    /**
+     * 是否需要审核
+     *
+     * @param updateReqVO    原数据
+     * @param spuApplyDetail 新数据
+     * @return bool
+     */
+    private static boolean isFlag(SpuApplySaveReqVO updateReqVO, SpuApplyRespVO spuApplyDetail) {
+        boolean flag = true;
+        for (int i = 0; i < spuApplyDetail.getSkus().size(); i++) {
+            // 如果是高精度商品
+            if (updateReqVO.getHighPrecision()) {
+                if (!spuApplyDetail.getSkus().get(i).getHighPrecisionPrice().equals(updateReqVO.getSkus().get(i).getHighPrecisionPrice()) ||
+                        !spuApplyDetail.getSkus().get(i).getHighPrecisionSettlementPrice().equals(updateReqVO.getSkus().get(i).getHighPrecisionSettlementPrice())
+                ) {
+                    flag = false;
+                }
+            } else {
+                if (!spuApplyDetail.getSkus().get(i).getSettlementPrice().equals(updateReqVO.getSkus().get(i).getSettlementPrice()) ||
+                        !spuApplyDetail.getSkus().get(i).getCostPrice().equals(updateReqVO.getSkus().get(i).getCostPrice())
+                ) {
+                    flag = false;
+                }
+            }
+        }
+        return flag;
     }
 
     @Override
@@ -118,6 +283,14 @@ public class SpuApplyServiceImpl implements SpuApplyService {
         }
     }
 
+    private void validateCategory(Long id) {
+        categoryService.validateCategory(id);
+        // 校验层级
+        if (categoryService.getCategoryLevel(id) < CATEGORY_LEVEL) {
+            throw exception(SPU_SAVE_FAIL_CATEGORY_LEVEL_ERROR);
+        }
+    }
+
     @Override
     public SpuApplyDO getSpuApply(Long id) {
         return spuApplyMapper.selectById(id);
@@ -137,4 +310,21 @@ public class SpuApplyServiceImpl implements SpuApplyService {
         return spuApplyMapper.selectById(id);
     }
 
+    @Override
+    public SpuApplyRespVO getSpuApplyDetail(Long id) {
+        // 获得商品 SPU 申请
+        SpuApplyDO spu = getSpu(id);
+        if (spu == null) {
+            return null;
+        }
+        // 查询商品 SKU
+        List<SkuApplyDO> skus = skuApplyService.getSkuListBySpuId(spu.getId());
+
+        String shopName = spuApplyMapper.selectShopNameById(spu.getShopId());
+
+        SpuApplyRespVO convert = SpuApplyConvert.INSTANCE.convert(spu, skus);
+        convert.setShopName(shopName);
+        return convert;
+    }
+
 }

+ 7 - 0
feifan-module-system/feifan-module-system-api/src/main/java/cn/newfeifan/mall/module/system/api/sms/dto/code/SmsCodeSendReqDTO.java

@@ -3,6 +3,7 @@ package cn.newfeifan.mall.module.system.api.sms.dto.code;
 import cn.newfeifan.mall.framework.common.validation.InEnum;
 import cn.newfeifan.mall.framework.common.validation.Mobile;
 import cn.newfeifan.mall.module.system.enums.sms.SmsSceneEnum;
+import lombok.Builder;
 import lombok.Data;
 
 import javax.validation.constraints.NotEmpty;
@@ -14,6 +15,7 @@ import javax.validation.constraints.NotNull;
  * @author 非繁源码
  */
 @Data
+@Builder
 public class SmsCodeSendReqDTO {
 
     /**
@@ -34,4 +36,9 @@ public class SmsCodeSendReqDTO {
     @NotEmpty(message = "发送 IP 不能为空")
     private String createIp;
 
+    /**
+     * 名称
+     */
+    private String name;
+
 }

+ 5 - 0
feifan-module-system/feifan-module-system-api/src/main/java/cn/newfeifan/mall/module/system/enums/sms/SmsSceneEnum.java

@@ -20,6 +20,11 @@ public enum SmsSceneEnum implements IntArrayValuable {
     MEMBER_UPDATE_MOBILE(2, "user-update-mobile", "会员用户 - 修改手机"),
     MEMBER_UPDATE_PASSWORD(3, "user-update-password", "会员用户 - 修改密码"),
     MEMBER_RESET_PASSWORD(4, "user-reset-password", "会员用户 - 忘记密码"),
+    MEMBER_WITHDRAWAL_ERROR(6, "user-withdrawal-error", "会员用户 - 提现失败时发送通知给用户"),
+    SPU_APPLY_INFORM(7, "spu-apply-inform", "系统用户 - 商品审核申请通知"),
+    SPU_APPLY_ERROR(8, "spu-apply-error", "系统用户 - 商品审核驳回通知"),
+    SPU_APPLY_SUCCESS(9, "spu-apply-success", "系统用户 - 商品审核通过通知"),
+
 
     ADMIN_MEMBER_LOGIN(21, "admin-sms-login", "后台用户 - 手机号登录");
 

+ 8 - 0
feifan-module-system/feifan-module-system-biz/src/main/java/cn/newfeifan/mall/module/system/service/permission/PermissionService.java

@@ -3,6 +3,7 @@ package cn.newfeifan.mall.module.system.service.permission;
 import cn.newfeifan.mall.module.system.api.permission.dto.DeptDataPermissionRespDTO;
 
 import java.util.Collection;
+import java.util.List;
 import java.util.Set;
 
 import static java.util.Collections.singleton;
@@ -143,4 +144,11 @@ public interface PermissionService {
      */
     DeptDataPermissionRespDTO getDeptDataPermission(Long userId);
 
+    /**
+     * 获取拥有对应角色的系统用户
+     * @param roleId 角色id
+     * @return 系统用户ids
+     */
+    List<Long> getPtSpuCheckUserIds(Long roleId);
+
 }

+ 9 - 0
feifan-module-system/feifan-module-system-biz/src/main/java/cn/newfeifan/mall/module/system/service/permission/PermissionServiceImpl.java

@@ -32,6 +32,7 @@ import org.springframework.transaction.annotation.Transactional;
 import javax.annotation.Resource;
 import java.util.*;
 import java.util.function.Supplier;
+import java.util.stream.Collectors;
 
 import static cn.newfeifan.mall.framework.common.util.collection.CollectionUtils.convertSet;
 import static cn.newfeifan.mall.framework.common.util.json.JsonUtils.toJsonString;
@@ -325,6 +326,14 @@ public class PermissionServiceImpl implements PermissionService {
         return result;
     }
 
+    @Override
+    public List<Long> getPtSpuCheckUserIds(Long roleId) {
+
+        List<UserRoleDO> userRoleDOS = userRoleMapper.selectList(UserRoleDO::getRoleId, roleId);
+
+        return userRoleDOS != null ? userRoleDOS.stream().map(UserRoleDO::getUserId).collect(Collectors.toList()) : null;
+    }
+
     /**
      * 获得自身的代理对象,解决 AOP 生效问题
      *

+ 6 - 0
feifan-module-system/feifan-module-system-biz/src/main/java/cn/newfeifan/mall/module/system/service/permission/RoleService.java

@@ -131,4 +131,10 @@ public interface RoleService {
 
     List<String> getRoleNames(Long userId);
 
+    /**
+     * 获取平台审核角色编号
+     * @return
+     */
+    Long getPtSpuCheck();
+
 }

+ 6 - 0
feifan-module-system/feifan-module-system-biz/src/main/java/cn/newfeifan/mall/module/system/service/permission/RoleServiceImpl.java

@@ -253,4 +253,10 @@ public class RoleServiceImpl implements RoleService {
         return roleMapper.selectByRoleIds(roleIds);
     }
 
+    @Override
+    public Long getPtSpuCheck() {
+        RoleDO ptSpuCheck = roleMapper.selectOne(RoleDO::getCode, "pt_spu_check");
+        return ptSpuCheck == null ? null : ptSpuCheck.getId();
+    }
+
 }

+ 13 - 3
feifan-module-system/feifan-module-system-biz/src/main/java/cn/newfeifan/mall/module/system/service/sms/SmsCodeServiceImpl.java

@@ -15,6 +15,7 @@ import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
 import java.time.LocalDateTime;
+import java.util.HashMap;
 
 import static cn.hutool.core.util.RandomUtil.randomInt;
 import static cn.newfeifan.mall.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -43,11 +44,20 @@ public class SmsCodeServiceImpl implements SmsCodeService {
     public void sendSmsCode(SmsCodeSendReqDTO reqDTO) {
         SmsSceneEnum sceneEnum = SmsSceneEnum.getCodeByScene(reqDTO.getScene());
         Assert.notNull(sceneEnum, "验证码场景({}) 查找不到配置", reqDTO.getScene());
-        // 创建验证码
-        String code = createSmsCode(reqDTO.getMobile(), reqDTO.getScene(), reqDTO.getCreateIp());
+        HashMap<String, Object> map;
+
+        // 提现失败发送的不是验证码
+        if (reqDTO.getScene().equals(SmsSceneEnum.SPU_APPLY_INFORM.getScene())) {
+            map = MapUtil.of("name", reqDTO.getName());
+        } else {
+            // 创建验证码
+            String code = createSmsCode(reqDTO.getMobile(), reqDTO.getScene(), reqDTO.getCreateIp());
+            map = MapUtil.of("code", code);
+        }
+
         // 发送验证码
         smsSendService.sendSingleSms(reqDTO.getMobile(), null, null,
-                sceneEnum.getTemplateCode(), MapUtil.of("code", code));
+                sceneEnum.getTemplateCode(), map);
     }
 
     private String createSmsCode(String mobile, Integer scene, String ip) {

+ 1 - 0
feifan-server/src/main/resources/application.yaml

@@ -64,6 +64,7 @@ flowable:
 # MyBatis Plus 的配置项
 mybatis-plus:
   configuration:
+#    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
     map-underscore-to-camel-case: true # 虽然默认为 true ,但是还是显示去指定下。
   global-config:
     db-config: