浏览代码

Merge remote-tracking branch 'origin/feature/mall_product' into feature/mall_product

# Conflicts:
#	yudao-module-mall/yudao-module-product-biz/pom.xml
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.java
YunaiV 1 年之前
父节点
当前提交
4126e37d91
共有 40 个文件被更改,包括 861 次插入289 次删除
  1. 9 0
      yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/dto/ProductSpuRespDTO.java
  2. 6 1
      yudao-module-mall/yudao-module-product-biz/pom.xml
  3. 8 3
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/comment/AppCommentController.java
  4. 49 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/comment/vo/AppCommentPageReqVO.java
  5. 4 3
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/comment/vo/AppCommentRespVO.java
  6. 2 25
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.java
  7. 9 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/property/ProductPropertyDO.java
  8. 9 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/property/ProductPropertyValueDO.java
  9. 35 3
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/comment/ProductCommentMapper.java
  10. 0 22
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/sku/ProductSkuMapper.java
  11. 4 9
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/category/ProductCategoryServiceImpl.java
  12. 12 1
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentService.java
  13. 54 2
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentServiceImpl.java
  14. 2 2
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/property/ProductPropertyServiceImpl.java
  15. 2 4
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/property/ProductPropertyValueServiceImpl.java
  16. 6 6
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuService.java
  17. 45 67
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceImpl.java
  18. 8 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuService.java
  19. 45 15
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java
  20. 50 3
      yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentServiceImplTest.java
  21. 20 0
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApi.java
  22. 92 0
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/order/dto/TradeOrderRespDTO.java
  23. 1 0
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/package-info.java
  24. 5 1
      yudao-module-mall/yudao-module-trade-biz/pom.xml
  25. 28 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApiImpl.java
  26. 1 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/api/package-info.java
  27. 3 3
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/delivery/DeliveryExpressTemplateController.java
  28. 21 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/delivery/vo/expresstemplate/DeliveryExpressTemplateSimpleRespVO.java
  29. 3 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/delivery/DeliveryExpressTemplateConvert.java
  30. 3 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java
  31. 13 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/DeliveryExpressTemplateService.java
  32. 53 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/DeliveryExpressTemplateServiceImpl.java
  33. 45 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/bo/SpuDeliveryExpressTemplateRespBO.java
  34. 60 19
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderServiceImpl.java
  35. 0 9
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateReqBO.java
  36. 78 88
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java
  37. 5 0
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/notify/NotifyMessageSendApi.java
  38. 34 0
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/notify/dto/NotifyTemplateReqDTO.java
  39. 26 0
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/notify/NotifyTemplateTypeEnum.java
  40. 11 1
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/notify/NotifyMessageSendApiImpl.java

+ 9 - 0
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/dto/ProductSpuRespDTO.java

@@ -103,6 +103,15 @@ public class ProductSpuRespDTO {
      */
     private Integer stock;
 
+    // ========== 物流相关字段 =========
+
+    /**
+     * 物流配置模板编号
+     *
+     * 对应 TradeDeliveryExpressTemplateDO 的 id 编号
+     */
+    private Long deliveryTemplateId;
+
     // ========== 统计相关字段 =========
 
     /**

+ 6 - 1
yudao-module-mall/yudao-module-product-biz/pom.xml

@@ -23,7 +23,12 @@
             <artifactId>yudao-module-product-api</artifactId>
             <version>${revision}</version>
         </dependency>
-
+        <!-- TODO 芋艿:看看~~~ -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-trade-api</artifactId>
+            <version>${revision}</version>
+        </dependency>
         <dependency>
             <groupId>cn.iocoder.boot</groupId>
             <artifactId>yudao-module-member-api</artifactId>

+ 8 - 3
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/comment/AppCommentController.java

@@ -9,7 +9,6 @@ import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentCreat
 import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentPageReqVO;
 import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentRespVO;
 import cn.iocoder.yudao.module.product.convert.comment.ProductCommentConvert;
-import cn.iocoder.yudao.module.product.dal.dataobject.comment.ProductCommentDO;
 import cn.iocoder.yudao.module.product.service.comment.ProductCommentService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -18,6 +17,7 @@ import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
 import javax.validation.Valid;
+import java.util.Map;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@@ -37,8 +37,13 @@ public class AppCommentController {
     @GetMapping("/page")
     @Operation(summary = "获得商品评价分页")
     public CommonResult<PageResult<AppCommentRespVO>> getCommentPage(@Valid AppCommentPageReqVO pageVO) {
-        PageResult<ProductCommentDO> pageResult = productCommentService.getCommentPage(pageVO, Boolean.TRUE);
-        return success(ProductCommentConvert.INSTANCE.convertPage02(pageResult));
+        return success(productCommentService.getCommentPage(pageVO, Boolean.TRUE));
+    }
+
+    @GetMapping("/get-count")
+    @Operation(summary = "获得商品评价分页 tab count")
+    public CommonResult<Map<String, Long>> getCommentPage(@Valid Long spuId) {
+        return success(productCommentService.getCommentPageTabsCount(spuId, Boolean.TRUE));
     }
 
     @PostMapping(value = "/create")

+ 49 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/comment/vo/AppCommentPageReqVO.java

@@ -14,8 +14,57 @@ import javax.validation.constraints.NotNull;
 @ToString(callSuper = true)
 public class AppCommentPageReqVO extends PageParam {
 
+    /**
+     * 所有
+     */
+    public static final Integer ALL = 0;
+
+    /**
+     * 所有数量 key
+     */
+    public static final String ALL_COUNT = "allCount";
+
+    /**
+     * 好评
+     */
+    public static final Integer FAVOURABLE_COMMENT = 1;
+
+    /**
+     * 好评数量 key
+     */
+    public static final String FAVOURABLE_COMMENT_COUNT = "favourableCommentCount";
+
+    /**
+     * 中评
+     */
+    public static final Integer MEDIOCRE_COMMENT = 2;
+
+    /**
+     * 中评数量 key
+     */
+    public static final String MEDIOCRE_COMMENT_COUNT = "mediocreCommentCount";
+
+    /**
+     * 差评
+     */
+    public static final Integer NEGATIVE_COMMENT = 3;
+
+    /**
+     * 差评数量 key
+     */
+    public static final String NEGATIVE_COMMENT_COUNT = "negativeCommentCount";
+
+    /**
+     * 默认匿名昵称
+     */
+    public static final String ANONYMOUS_NICKNAME = "匿名用户";
+
     @Schema(description = "商品SPU编号", example = "29502")
     @NotNull(message = "商品SPU编号不能为空")
     private Long spuId;
 
+    @Schema(description = "app 评论页 tab 类型 (0 全部、1 好评、2 中评、3 差评)", example = "0")
+    @NotNull(message = "商品SPU编号不能为空")
+    private Integer type;
+
 }

+ 4 - 3
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/comment/vo/AppCommentRespVO.java

@@ -5,7 +5,6 @@ import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.ToString;
 
-import javax.validation.constraints.NotNull;
 import java.time.LocalDateTime;
 import java.util.List;
 
@@ -27,7 +26,7 @@ public class AppCommentRespVO extends AppCommentBaseVO {
     @Schema(description = "订单项编号", required = true, example = "24965")
     private Long id;
 
-    @Schema(description = "是否匿名:[0:不匿名 1:匿名]", required = true)
+    @Schema(description = "是否匿名", required = true)
     private Boolean anonymous;
 
     @Schema(description = "交易订单编号", required = true, example = "24428")
@@ -36,7 +35,7 @@ public class AppCommentRespVO extends AppCommentBaseVO {
     @Schema(description = "交易订单项编号", required = true, example = "8233")
     private Long orderItemId;
 
-    @Schema(description = "商家是否回复:[1:回复 0:未回复]", required = true)
+    @Schema(description = "商家是否回复", required = true)
     private Boolean replied;
 
     @Schema(description = "回复管理员编号", example = "22212")
@@ -60,4 +59,6 @@ public class AppCommentRespVO extends AppCommentBaseVO {
     @Schema(description = "创建时间", required = true)
     private LocalDateTime createTime;
 
+    @Schema(description = "最终评分", required = true)
+    private Integer finalScore;
 }

+ 2 - 25
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.java

@@ -6,11 +6,7 @@ import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuDetail
 import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageItemRespVO;
 import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageReqVO;
 import cn.iocoder.yudao.module.product.convert.spu.ProductSpuConvert;
-import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
 import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
-import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
-import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService;
-import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
 import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
@@ -23,12 +19,8 @@ import org.springframework.web.bind.annotation.RestController;
 
 import javax.annotation.Resource;
 import javax.validation.Valid;
-import java.util.List;
 
-import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
-import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SPU_NOT_ENABLE;
-import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SPU_NOT_EXISTS;
 
 @Tag(name = "用户 APP - 商品 SPU")
 @RestController
@@ -38,10 +30,6 @@ public class AppProductSpuController {
 
     @Resource
     private ProductSpuService productSpuService;
-    @Resource
-    private ProductSkuService productSkuService;
-    @Resource
-    private ProductPropertyValueService productPropertyValueService;
 
     @GetMapping("/page")
     @Operation(summary = "获得商品 SPU 分页")
@@ -50,23 +38,12 @@ public class AppProductSpuController {
         return success(ProductSpuConvert.INSTANCE.convertPageForGetSpuPage(pageResult));
     }
 
+    // TODO 芋艿:等会看看
     @GetMapping("/get-detail")
     @Operation(summary = "获得商品 SPU 明细")
     @Parameter(name = "id", description = "编号", required = true)
     public CommonResult<AppProductSpuDetailRespVO> getSpuDetail(@RequestParam("id") Long id) {
-        // 获得商品 SPU
-        ProductSpuDO spu = productSpuService.getSpu(id);
-        if (spu == null) {
-            throw exception(SPU_NOT_EXISTS);
-        }
-        if (!ProductSpuStatusEnum.isEnable(spu.getStatus())) {
-            throw exception(SPU_NOT_ENABLE);
-        }
-
-        // 查询商品 SKU
-        List<ProductSkuDO> skus = productSkuService.getSkuListBySpuId(spu.getId());
-        // 拼接
-        return success(ProductSpuConvert.INSTANCE.convertForGetSpuDetail(spu, skus));
+        return success(productSpuService.getAppProductSpuDetail(id));
     }
 
 }

+ 9 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/property/ProductPropertyDO.java

@@ -22,6 +22,15 @@ import lombok.*;
 public class ProductPropertyDO extends BaseDO {
 
     /**
+     * 默认属性id
+     */
+    public static final Long PROPERTY_ID = 0L;
+    /**
+     * 默认属性名字
+     */
+    public static final String PROPERTY_NAME = "默认";
+
+    /**
      * 主键
      */
     @TableId

+ 9 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/property/ProductPropertyValueDO.java

@@ -23,6 +23,15 @@ import lombok.*;
 public class ProductPropertyValueDO extends BaseDO {
 
     /**
+     * 默认属性值id
+     */
+    public static final Long VALUE_ID = 0L;
+    /**
+     * 默认属性值名字
+     */
+    public static final String VALUE_NAME = "默认";
+
+    /**
      * 主键
      */
     @TableId

+ 35 - 3
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/comment/ProductCommentMapper.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.product.dal.mysql.comment;
 
 
+import cn.hutool.core.util.ObjectUtil;
 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;
@@ -34,11 +35,33 @@ public interface ProductCommentMapper extends BaseMapperX<ProductCommentDO> {
                 .orderByDesc(ProductCommentDO::getId));
     }
 
+    static void appendTabQuery(LambdaQueryWrapperX<ProductCommentDO> queryWrapper, Integer type) {
+        // 构建好评查询语句
+        if (ObjectUtil.equal(type, AppCommentPageReqVO.FAVOURABLE_COMMENT)) {
+            // 好评计算 (商品评分星级+服务评分星级) >= 8
+            queryWrapper.apply("(scores + benefit_scores) >= 8");
+        }
+        // 构建中评查询语句
+        if (ObjectUtil.equal(type, AppCommentPageReqVO.MEDIOCRE_COMMENT)) {
+            // 中评计算 (商品评分星级+服务评分星级) > 4 且 (商品评分星级+服务评分星级) < 8
+            queryWrapper.apply("(scores + benefit_scores) > 4 and (scores + benefit_scores) < 8");
+        }
+        // 构建差评查询语句
+        if (ObjectUtil.equal(type, AppCommentPageReqVO.NEGATIVE_COMMENT)) {
+            // 差评计算 (商品评分星级+服务评分星级) <= 4
+            queryWrapper.apply("(scores + benefit_scores) <= 4");
+        }
+    }
+
     default PageResult<ProductCommentDO> selectPage(AppCommentPageReqVO reqVO, Boolean visible) {
-        return selectPage(reqVO, new LambdaQueryWrapperX<ProductCommentDO>()
+        LambdaQueryWrapperX<ProductCommentDO> queryWrapper = new LambdaQueryWrapperX<ProductCommentDO>()
                 .eqIfPresent(ProductCommentDO::getSpuId, reqVO.getSpuId())
-                .eqIfPresent(ProductCommentDO::getVisible, visible)
-                .orderByDesc(ProductCommentDO::getId));
+                .eqIfPresent(ProductCommentDO::getVisible, visible);
+        // 构建评价查询语句
+        appendTabQuery(queryWrapper, reqVO.getType());
+        // 按评价时间排序最新的显示在前面
+        queryWrapper.orderByDesc(ProductCommentDO::getCreateTime);
+        return selectPage(reqVO, queryWrapper);
     }
 
     default void updateCommentVisible(Long id, Boolean visible) {
@@ -74,4 +97,13 @@ public interface ProductCommentMapper extends BaseMapperX<ProductCommentDO> {
         update(null, lambdaUpdateWrapper);
     }
 
+    default Long selectTabCount(Long spuId, Boolean visible, Integer type) {
+        LambdaQueryWrapperX<ProductCommentDO> queryWrapper = new LambdaQueryWrapperX<ProductCommentDO>()
+                .eqIfPresent(ProductCommentDO::getSpuId, spuId)
+                .eqIfPresent(ProductCommentDO::getVisible, visible);
+        // 构建评价查询语句
+        appendTabQuery(queryWrapper, type);
+        return selectCount(queryWrapper);
+    }
+
 }

+ 0 - 22
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/sku/ProductSkuMapper.java

@@ -1,12 +1,9 @@
 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;
@@ -63,23 +60,4 @@ public interface ProductSkuMapper extends BaseMapperX<ProductSkuDO> {
         return selectList(new QueryWrapper<ProductSkuDO>().apply("stock <= warn_stock"));
     }
 
-    // TODO @puhui999:貌似 IN 不出来数据哈。直接全部查询出来,处理就好列;
-    /**
-     * 更新 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));
-    }
 }

+ 4 - 9
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/category/ProductCategoryServiceImpl.java

@@ -68,7 +68,10 @@ public class ProductCategoryServiceImpl implements ProductCategoryService {
             throw exception(CATEGORY_EXISTS_CHILDREN);
         }
         // 校验分类是否绑定了 SPU
-        validateProductCategoryIsHaveBindSpu(id);
+        Long count = productSpuService.getSpuCountByCategoryId(id);
+        if (0 != count) {
+            throw exception(CATEGORY_HAVE_BIND_SPU);
+        }
         // 删除
         productCategoryMapper.deleteById(id);
     }
@@ -96,14 +99,6 @@ public class ProductCategoryServiceImpl implements ProductCategoryService {
         }
     }
 
-    // TODO @puhui999:不用抽方法,因为不太会复用这个方法哈。
-    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);

+ 12 - 1
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentService.java

@@ -7,10 +7,13 @@ import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommen
 import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommentUpdateVisibleReqVO;
 import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentAdditionalReqVO;
 import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentPageReqVO;
+import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentRespVO;
 import cn.iocoder.yudao.module.product.dal.dataobject.comment.ProductCommentDO;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
+import java.util.Map;
+
 /**
  * 商品评论 Service 接口
  *
@@ -50,7 +53,7 @@ public interface ProductCommentService {
      * @param visible 是否可见
      * @return 商品评价分页
      */
-    PageResult<ProductCommentDO> getCommentPage(AppCommentPageReqVO pageVO, Boolean visible);
+    PageResult<AppCommentRespVO> getCommentPage(AppCommentPageReqVO pageVO, Boolean visible);
 
     /**
      * 创建商品评论
@@ -68,4 +71,12 @@ public interface ProductCommentService {
      */
     void additionalComment(MemberUserRespDTO user, AppCommentAdditionalReqVO createReqVO);
 
+    /**
+     * 评论页面标签数
+     *
+     * @param spuId   spu id
+     * @param visible 是否可见
+     * @return 获得商品评价分页 tab count
+     */
+    Map<String, Long> getCommentPageTabsCount(Long spuId, Boolean visible);
 }

+ 54 - 2
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentServiceImpl.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.product.service.comment;
 
+import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
 import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommentPageReqVO;
@@ -7,17 +8,28 @@ import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommen
 import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommentUpdateVisibleReqVO;
 import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentAdditionalReqVO;
 import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentPageReqVO;
+import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentRespVO;
+import cn.iocoder.yudao.module.product.convert.comment.ProductCommentConvert;
 import cn.iocoder.yudao.module.product.dal.dataobject.comment.ProductCommentDO;
+import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
 import cn.iocoder.yudao.module.product.dal.mysql.comment.ProductCommentMapper;
+import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
+import cn.iocoder.yudao.module.trade.api.order.TradeOrderApi;
+import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderRespDTO;
 import org.springframework.stereotype.Service;
 import org.springframework.util.StringUtils;
 import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Objects;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.*;
+import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.ORDER_NOT_FOUND;
 
 /**
  * 商品评论 Service 实现类
@@ -30,6 +42,11 @@ public class ProductCommentServiceImpl implements ProductCommentService {
 
     @Resource
     private ProductCommentMapper productCommentMapper;
+    @Resource
+    private TradeOrderApi tradeOrderApi;
+
+    @Resource
+    private ProductSpuService productSpuService;
 
     @Override
     public PageResult<ProductCommentDO> getCommentPage(ProductCommentPageReqVO pageReqVO) {
@@ -53,13 +70,48 @@ public class ProductCommentServiceImpl implements ProductCommentService {
     }
 
     @Override
-    public PageResult<ProductCommentDO> getCommentPage(AppCommentPageReqVO pageVO, Boolean visible) {
-        return productCommentMapper.selectPage(pageVO, visible);
+    public Map<String, Long> getCommentPageTabsCount(Long spuId, Boolean visible) {
+        Map<String, Long> countMap = new HashMap<>(4);
+        // 查询商品 id = spuId 的所有评论数量
+        countMap.put(AppCommentPageReqVO.ALL_COUNT, productCommentMapper.selectTabCount(spuId, visible, AppCommentPageReqVO.ALL));
+        // 查询商品 id = spuId 的所有好评数量
+        countMap.put(AppCommentPageReqVO.FAVOURABLE_COMMENT_COUNT, productCommentMapper.selectTabCount(spuId, visible, AppCommentPageReqVO.FAVOURABLE_COMMENT));
+        // 查询商品 id = spuId 的所有中评数量
+        countMap.put(AppCommentPageReqVO.MEDIOCRE_COMMENT_COUNT, productCommentMapper.selectTabCount(spuId, visible, AppCommentPageReqVO.MEDIOCRE_COMMENT));
+        // 查询商品 id = spuId 的所有差评数量
+        countMap.put(AppCommentPageReqVO.NEGATIVE_COMMENT_COUNT, productCommentMapper.selectTabCount(spuId, visible, AppCommentPageReqVO.NEGATIVE_COMMENT));
+        return countMap;
+    }
+
+    @Override
+    public PageResult<AppCommentRespVO> getCommentPage(AppCommentPageReqVO pageVO, Boolean visible) {
+        PageResult<AppCommentRespVO> result = ProductCommentConvert.INSTANCE.convertPage02(productCommentMapper.selectPage(pageVO, visible));
+        result.getList().forEach(item -> {
+            // 判断用户是否选择匿名
+            if (ObjectUtil.equal(item.getAnonymous(), true)) {
+                item.setUserNickname(AppCommentPageReqVO.ANONYMOUS_NICKNAME);
+            }
+            // 计算评价最终综合评分 最终星数 = (商品评星 + 服务评星) / 2
+            BigDecimal sumScore = new BigDecimal(item.getScores() + item.getBenefitScores());
+            BigDecimal divide = sumScore.divide(BigDecimal.valueOf(2L), 0, RoundingMode.DOWN);
+            item.setFinalScore(divide.intValue());
+        });
+        return result;
     }
 
     @Override
     public void createComment(ProductCommentDO productComment, Boolean system) {
         if (!system) {
+            // TODO 判断订单是否存在 fix
+            TradeOrderRespDTO order = tradeOrderApi.getOrder(productComment.getOrderId());
+            if (null == order) {
+                throw exception(ORDER_NOT_FOUND);
+            }
+            // TODO 判断 SPU 是否存在 fix
+            ProductSpuDO spu = productSpuService.getSpu(productComment.getSpuId());
+            if (null == spu) {
+                throw exception(SPU_NOT_EXISTS);
+            }
             // 判断当前订单的当前商品用户是否评价过
             ProductCommentDO exist = productCommentMapper.findByUserIdAndOrderIdAndSpuId(productComment.getId(), productComment.getOrderId(), productComment.getSpuId());
             if (null != exist) {

+ 2 - 2
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/property/ProductPropertyServiceImpl.java

@@ -71,8 +71,8 @@ public class ProductPropertyServiceImpl implements ProductPropertyService {
         // 更新
         ProductPropertyDO updateObj = ProductPropertyConvert.INSTANCE.convert(updateReqVO);
         productPropertyMapper.updateById(updateObj);
-        // TODO @puhui:是不是只要传递变量,不传递整个 updateObj 变量哈
-        productSkuService.updateSkuProperty(updateObj);
+        // 更新 sku 相关属性
+        productSkuService.updateSkuProperty(updateObj.getId(), updateObj.getName());
     }
 
     @Override

+ 2 - 4
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/property/ProductPropertyValueServiceImpl.java

@@ -73,10 +73,8 @@ public class ProductPropertyValueServiceImpl implements ProductPropertyValueServ
         // 更新
         ProductPropertyValueDO updateObj = ProductPropertyValueConvert.INSTANCE.convert(updateReqVO);
         productPropertyValueMapper.updateById(updateObj);
-
-        // TODO 芋艿:更新时,需要看看 sku 表 fix
-        // TODO @puhui:是不是只要传递变量,不传递整个 updateObj 变量哈
-        productSkuService.updateSkuPropertyValue(updateObj);
+        // 更新 sku 相关属性
+        productSkuService.updateSkuPropertyValue(updateObj.getId(), updateObj.getName());
     }
 
     @Override

+ 6 - 6
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuService.java

@@ -2,8 +2,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.property.ProductPropertyDO;
-import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyValueDO;
 import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
 
 import java.util.Collection;
@@ -111,16 +109,18 @@ public interface ProductSkuService {
     /**
      * 更新 sku 属性
      *
-     * @param updateObj 属性对象
+     * @param propertyId   属性 id
+     * @param propertyName 属性名
      * @return int 影响的行数
      */
-    int updateSkuProperty(ProductPropertyDO updateObj);
+    int updateSkuProperty(Long propertyId, String propertyName);
 
     /**
      * 更新 sku 属性值
      *
-     * @param updateObj 属性值对象
+     * @param propertyValueId   属性值 id
+     * @param propertyValueName 属性值名字
      * @return int 影响的行数
      */
-    int updateSkuPropertyValue(ProductPropertyValueDO updateObj);
+    int updateSkuPropertyValue(Long propertyValueId, String propertyValueName);
 }

+ 45 - 67
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceImpl.java

@@ -2,9 +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.ProductSkuBaseVO;
 import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuCreateOrUpdateReqVO;
 import cn.iocoder.yudao.module.product.convert.sku.ProductSkuConvert;
 import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyDO;
@@ -80,16 +79,25 @@ public class ProductSkuServiceImpl implements ProductSkuService {
 
     @Override
     public void validateSkuList(List<ProductSkuCreateOrUpdateReqVO> skus, Boolean specType) {
-        // 非多规格,不需要校验
-        if (ObjectUtil.notEqual(specType, true)) {
-            return;
-        }
-
         // 0、校验skus是否为空
         if (CollUtil.isEmpty(skus)) {
             throw exception(SKU_NOT_EXISTS);
         }
-
+        // 单规格处理
+        if (ObjectUtil.equal(specType, false)) {
+            ProductSkuCreateOrUpdateReqVO skuVO = skus.get(0);
+            // 赋予单规格默认属性
+            List<ProductSkuBaseVO.Property> properties = new ArrayList<>();
+            ProductSkuBaseVO.Property property = new ProductSkuBaseVO.Property();
+            property.setPropertyId(ProductPropertyDO.PROPERTY_ID);
+            property.setPropertyName(ProductPropertyDO.PROPERTY_NAME);
+            property.setValueId(ProductPropertyValueDO.VALUE_ID);
+            property.setValueName(ProductPropertyValueDO.VALUE_NAME);
+            properties.add(property);
+            skuVO.setProperties(properties);
+            // 单规格不需要后续的校验
+            return;
+        }
         // 1、校验属性项存在
         Set<Long> propertyIds = skus.stream().filter(p -> p.getProperties() != null)
                 // 遍历多个 Property 属性
@@ -156,81 +164,51 @@ public class ProductSkuServiceImpl implements ProductSkuService {
     }
 
     @Override
-    public int updateSkuProperty(ProductPropertyDO updateObj) {
-        // TODO 看了一下数据库有关于 json 字符串的处理,怕数据库出现兼容问题这里还是用数据库常规操作来实现
-        // TODO @puhui999:直接全部查询处理,批量处理就好列;一般项目的商品不会超过几十万的哈。
-        Long count = productSkuMapper.selectCountByPropertyNotNull();
-        int currentPage = 1;
+    public int updateSkuProperty(Long propertyId, String propertyName) {
+        // 获取所有的 sku
+        List<ProductSkuDO> skuDOList = productSkuMapper.selectList();
+        // 处理后需要更新的 sku
         List<ProductSkuDO> updateSkus = new ArrayList<>();
-        if (count == 0) {
+        if (CollUtil.isEmpty(skuDOList)) {
             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());
-                            updateSkus.add(sku);
-                        }
-                    }));
-        }
+        skuDOList.stream().filter(sku -> sku.getProperties() != null)
+                .forEach(sku -> sku.getProperties().forEach(property -> {
+                    if (property.getPropertyId().equals(propertyId)) {
+                        property.setPropertyName(propertyName);
+                        updateSkus.add(sku);
+                    }
+                }));
         if (CollUtil.isEmpty(updateSkus)) {
             return 0;
         }
-        // TODO @puhui999:貌似 updateBatch 自己会拆分批次,这里不用再拆分了
-        // 每批处理的大小
-        int batchSize = 1000;
-        for (int i = 0; i < updateSkus.size(); i += batchSize) {
-            List<ProductSkuDO> batchSkuDOs = updateSkus.subList(i, Math.min(i + batchSize, updateSkus.size()));
-            productSkuMapper.updateBatch(batchSkuDOs, batchSize);
-        }
+
+        productSkuMapper.updateBatch(updateSkus);
         return updateSkus.size();
     }
 
     @Override
-    public int updateSkuPropertyValue(ProductPropertyValueDO updateObj) {
-        // TODO 看了一下数据库有关于 json 字符串的处理,怕数据库出现兼容问题这里还是用数据库常规操作来实现
-        Long count = productSkuMapper.selectCountByPropertyNotNull();
-        int currentPage = 1;
+    public int updateSkuPropertyValue(Long propertyValueId, String propertyValueName) {
+        // 获取所有的 sku
+        List<ProductSkuDO> skuDOList = productSkuMapper.selectList();
+        // 处理后需要更新的 sku
         List<ProductSkuDO> updateSkus = new ArrayList<>();
-        if (count == 0) {
+        if (CollUtil.isEmpty(skuDOList)) {
             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());
-                            updateSkus.add(sku);
-                        }
-                    }));
-        }
+        skuDOList.stream()
+                .filter(sku -> sku.getProperties() != null)
+                .forEach(sku -> sku.getProperties().forEach(property -> {
+                    if (property.getValueId().equals(propertyValueId)) {
+                        property.setValueName(propertyValueName);
+                        updateSkus.add(sku);
+                    }
+                }));
         if (CollUtil.isEmpty(updateSkus)) {
             return 0;
         }
-        // 每批处理的大小
-        int batchSize = 1000;
-        for (int i = 0; i < updateSkus.size(); i += batchSize) {
-            List<ProductSkuDO> batchSkuDOs = updateSkus.subList(i, Math.min(i + batchSize, updateSkus.size()));
-            productSkuMapper.updateBatch(batchSkuDOs, batchSize);
-        }
+
+        productSkuMapper.updateBatch(updateSkus);
         return updateSkus.size();
     }
 

+ 8 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuService.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.product.service.spu;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.product.controller.admin.spu.vo.*;
+import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuDetailRespVO;
 import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageReqVO;
 import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
 
@@ -135,4 +136,11 @@ public interface ProductSpuService {
      */
     Long getSpuCountByCategoryId(Long id);
 
+    /**
+     * 通过 spu id 获取商品 SPU 明细
+     *
+     * @param id id
+     * @return 用户 App - 商品 SPU 明细
+     */
+    AppProductSpuDetailRespVO getAppProductSpuDetail(Long id);
 }

+ 45 - 15
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java

@@ -7,15 +7,21 @@ import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.module.product.controller.admin.category.vo.ProductCategoryListReqVO;
 import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuCreateOrUpdateReqVO;
 import cn.iocoder.yudao.module.product.controller.admin.spu.vo.*;
+import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuDetailRespVO;
 import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageReqVO;
+import cn.iocoder.yudao.module.product.convert.sku.ProductSkuConvert;
 import cn.iocoder.yudao.module.product.convert.spu.ProductSpuConvert;
 import cn.iocoder.yudao.module.product.dal.dataobject.category.ProductCategoryDO;
+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 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.property.ProductPropertyValueService;
+import cn.iocoder.yudao.module.product.service.property.bo.ProductPropertyValueDetailRespBO;
 import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
 import com.google.common.collect.Maps;
 import org.springframework.context.annotation.Lazy;
@@ -51,6 +57,9 @@ public class ProductSpuServiceImpl implements ProductSpuService {
     private ProductBrandService brandService;
     @Resource
     private ProductCategoryService categoryService;
+    @Resource
+    @Lazy // 循环依赖,避免报错
+    private ProductPropertyValueService productPropertyValueService;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
@@ -141,7 +150,11 @@ public class ProductSpuServiceImpl implements ProductSpuService {
         // 校验存在
         validateSpuExists(id);
         // 校验商品状态不是回收站不能删除
-        validateSpuStatus(id);
+        ProductSpuDO spuDO = productSpuMapper.selectById(id);
+        // 判断 SPU 状态是否为回收站
+        if (ObjectUtil.notEqual(spuDO.getStatus(), ProductSpuStatusEnum.RECYCLE.getStatus())) {
+            throw exception(SPU_NOT_RECYCLE);
+        }
 
         // 删除 SPU
         productSpuMapper.deleteById(id);
@@ -155,20 +168,6 @@ public class ProductSpuServiceImpl implements ProductSpuService {
         }
     }
 
-    /**
-     * 验证 SPU 状态是否为回收站
-     *
-     * @param id id
-     */
-    // TODO puhui999:感觉不用独立出来一个方法,直接在 deleteSpu 方法中校验即可
-    private void validateSpuStatus(Long id) {
-        ProductSpuDO spuDO = productSpuMapper.selectById(id);
-        // 判断 SPU 状态是否为回收站
-        if (ObjectUtil.notEqual(spuDO.getStatus(), ProductSpuStatusEnum.RECYCLE.getStatus())) {
-            throw exception(SPU_NOT_RECYCLE);
-        }
-    }
-
     @Override
     public ProductSpuDO getSpu(Long id) {
         return productSpuMapper.selectById(id);
@@ -263,4 +262,35 @@ public class ProductSpuServiceImpl implements ProductSpuService {
         return productSpuMapper.selectCount(ProductSpuDO::getCategoryId, id);
     }
 
+    @Override
+    public AppProductSpuDetailRespVO getAppProductSpuDetail(Long id) {
+        // 获得商品 SPU
+        ProductSpuDO spu = getSpu(id);
+        if (spu == null) {
+            throw exception(SPU_NOT_EXISTS);
+        }
+        if (!ProductSpuStatusEnum.isEnable(spu.getStatus())) {
+            throw exception(SPU_NOT_ENABLE);
+        }
+
+        // 查询商品 SKU
+        List<ProductSkuDO> skus = productSkuService.getSkuListBySpuId(spu.getId());
+        List<ProductPropertyValueDetailRespBO> propertyValues = new ArrayList<>();
+        // 单规格商品 赋予默认属性值
+        if (ObjectUtil.equal(spu.getSpecType(), false)) {
+            ProductPropertyValueDetailRespBO respBO = new ProductPropertyValueDetailRespBO();
+            respBO.setPropertyId(ProductPropertyDO.PROPERTY_ID);
+            respBO.setPropertyName(ProductPropertyDO.PROPERTY_NAME);
+            respBO.setValueId(ProductPropertyValueDO.VALUE_ID);
+            respBO.setValueName(ProductPropertyValueDO.VALUE_NAME);
+            propertyValues.add(respBO);
+        } else {
+            // 多规格商品则查询商品属性
+            propertyValues = productPropertyValueService
+                    .getPropertyValueDetailList(ProductSkuConvert.INSTANCE.convertPropertyValueIds(skus));
+        }
+        // 拼接
+        return ProductSpuConvert.INSTANCE.convertForGetSpuDetail(spu, skus, propertyValues);
+    }
+
 }

+ 50 - 3
yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentServiceImplTest.java

@@ -11,15 +11,22 @@ import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommen
 import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommentUpdateVisibleReqVO;
 import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentAdditionalReqVO;
 import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentPageReqVO;
+import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentRespVO;
 import cn.iocoder.yudao.module.product.convert.comment.ProductCommentConvert;
 import cn.iocoder.yudao.module.product.dal.dataobject.comment.ProductCommentDO;
 import cn.iocoder.yudao.module.product.dal.mysql.comment.ProductCommentMapper;
 import cn.iocoder.yudao.module.product.enums.comment.ProductCommentScoresEnum;
+import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
+import cn.iocoder.yudao.module.trade.api.order.TradeOrderApi;
 import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.context.annotation.Import;
+import org.springframework.context.annotation.Lazy;
 
 import javax.annotation.Resource;
+import java.time.LocalDateTime;
 import java.util.Date;
+import java.util.Map;
 
 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
@@ -39,8 +46,14 @@ public class ProductCommentServiceImplTest extends BaseDbUnitTest {
     private ProductCommentMapper productCommentMapper;
 
     @Resource
+    @Lazy
     private ProductCommentServiceImpl productCommentService;
 
+    @MockBean
+    private TradeOrderApi tradeOrderApi;
+    @MockBean
+    private ProductSpuService productSpuService;
+
     public String generateNo() {
         return DateUtil.format(new Date(), "yyyyMMddHHmmss") + RandomUtil.randomInt(100000, 999999);
     }
@@ -70,6 +83,23 @@ public class ProductCommentServiceImplTest extends BaseDbUnitTest {
             o.setScores(ProductCommentScoresEnum.FOUR.getScores());
             o.setReplied(Boolean.TRUE);
             o.setVisible(Boolean.TRUE);
+            o.setId(generateId());
+            o.setUserId(generateId());
+            o.setAnonymous(Boolean.TRUE);
+            o.setOrderId(generateId());
+            o.setOrderItemId(generateId());
+            o.setSpuId(generateId());
+            o.setSkuId(generateId());
+            o.setDescriptionScores(ProductCommentScoresEnum.FOUR.getScores());
+            o.setBenefitScores(ProductCommentScoresEnum.FOUR.getScores());
+            o.setDeliveryScores(ProductCommentScoresEnum.FOUR.getScores());
+            o.setContent("真好吃");
+            o.setReplyUserId(generateId());
+            o.setReplyContent("确实");
+            o.setReplyTime(LocalDateTime.now());
+            o.setAdditionalTime(LocalDateTime.now());
+            o.setCreateTime(LocalDateTime.now());
+            o.setUpdateTime(LocalDateTime.now());
         });
         productCommentMapper.insert(productComment);
 
@@ -77,7 +107,7 @@ public class ProductCommentServiceImplTest extends BaseDbUnitTest {
         Long spuId = productComment.getSpuId();
 
         // 测试 userNickname 不匹配
-        productCommentMapper.insert(cloneIgnoreId(productComment, o -> o.setUserNickname("王三")));
+        productCommentMapper.insert(cloneIgnoreId(productComment, o -> o.setUserNickname("王三").setScores(ProductCommentScoresEnum.ONE.getScores())));
         // 测试 orderId 不匹配
         productCommentMapper.insert(cloneIgnoreId(productComment, o -> o.setOrderId(generateId())));
         // 测试 spuId 不匹配
@@ -107,8 +137,25 @@ public class ProductCommentServiceImplTest extends BaseDbUnitTest {
         PageResult<ProductCommentDO> all = productCommentService.getCommentPage(new ProductCommentPageReqVO());
         assertEquals(8, all.getTotal());
 
-        PageResult<ProductCommentDO> visible = productCommentService.getCommentPage(new AppCommentPageReqVO(), Boolean.TRUE);
-        assertEquals(7, visible.getTotal());
+        // 测试获取所有商品分页评论数据
+        PageResult<AppCommentRespVO> result1 = productCommentService.getCommentPage(new AppCommentPageReqVO(), Boolean.TRUE);
+        assertEquals(7, result1.getTotal());
+
+        // 测试获取所有商品分页中评数据
+        PageResult<AppCommentRespVO> result2 = productCommentService.getCommentPage(new AppCommentPageReqVO().setType(AppCommentPageReqVO.MEDIOCRE_COMMENT), Boolean.TRUE);
+        assertEquals(2, result2.getTotal());
+
+        // 测试获取指定 spuId 商品分页中评数据
+        PageResult<AppCommentRespVO> result3 = productCommentService.getCommentPage(new AppCommentPageReqVO().setSpuId(spuId).setType(AppCommentPageReqVO.MEDIOCRE_COMMENT), Boolean.TRUE);
+        assertEquals(2, result3.getTotal());
+
+        // 测试分页 tab count
+        Map<String, Long> tabsCount = productCommentService.getCommentPageTabsCount(spuId, Boolean.TRUE);
+        assertEquals(6, tabsCount.get(AppCommentPageReqVO.ALL_COUNT));
+        assertEquals(4, tabsCount.get(AppCommentPageReqVO.FAVOURABLE_COMMENT_COUNT));
+        assertEquals(2, tabsCount.get(AppCommentPageReqVO.MEDIOCRE_COMMENT_COUNT));
+        assertEquals(0, tabsCount.get(AppCommentPageReqVO.NEGATIVE_COMMENT_COUNT));
+
     }
 
     @Test

+ 20 - 0
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApi.java

@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.trade.api.order;
+
+import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderRespDTO;
+
+/**
+ * 订单 API 接口
+ *
+ * @author HUIHUI
+ */
+public interface TradeOrderApi {
+
+    /**
+     * 获取订单通过订单 id
+     *
+     * @param id id
+     * @return 订单信息 Response DTO
+     */
+    TradeOrderRespDTO getOrder(Long id);
+
+}

+ 92 - 0
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/order/dto/TradeOrderRespDTO.java

@@ -0,0 +1,92 @@
+package cn.iocoder.yudao.module.trade.api.order.dto;
+
+import cn.iocoder.yudao.framework.common.enums.TerminalEnum;
+import cn.iocoder.yudao.module.trade.enums.order.TradeOrderCancelTypeEnum;
+import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
+import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 订单信息 Response DTO
+ *
+ * @author HUIHUI
+ */
+@Data
+public class TradeOrderRespDTO {
+
+    // ========== 订单基本信息 ==========
+    /**
+     * 订单编号,主键自增
+     */
+    private Long id;
+    /**
+     * 订单流水号
+     * <p>
+     * 例如说,1146347329394184195
+     */
+    private String no;
+    /**
+     * 订单类型
+     * <p>
+     * 枚举 {@link TradeOrderTypeEnum}
+     */
+    private Integer type;
+    /**
+     * 订单来源
+     * <p>
+     * 枚举 {@link TerminalEnum}
+     */
+    private Integer terminal;
+    /**
+     * 用户编号
+     * <p>
+     * 关联 MemberUserDO 的 id 编号
+     */
+    private Long userId;
+    /**
+     * 用户 IP
+     */
+    private String userIp;
+    /**
+     * 用户备注
+     */
+    private String userRemark;
+    /**
+     * 订单状态
+     * <p>
+     * 枚举 {@link TradeOrderStatusEnum}
+     */
+    private Integer status;
+    /**
+     * 购买的商品数量
+     */
+    private Integer productCount;
+    /**
+     * 订单完成时间
+     */
+    private LocalDateTime finishTime;
+    /**
+     * 订单取消时间
+     */
+    private LocalDateTime cancelTime;
+    /**
+     * 取消类型
+     * <p>
+     * 枚举 {@link TradeOrderCancelTypeEnum}
+     */
+    private Integer cancelType;
+    /**
+     * 商家备注
+     */
+    private String remark;
+    /**
+     * 是否评价
+     * <p>
+     * true - 已评价
+     * false - 未评价
+     */
+    private Boolean commentStatus;
+
+}

+ 1 - 0
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/package-info.java

@@ -0,0 +1 @@
+package cn.iocoder.yudao.module.trade.api;

+ 5 - 1
yudao-module-mall/yudao-module-trade-biz/pom.xml

@@ -23,7 +23,6 @@
             <artifactId>yudao-module-trade-api</artifactId>
             <version>${revision}</version>
         </dependency>
-
         <dependency>
             <groupId>cn.iocoder.boot</groupId>
             <artifactId>yudao-module-product-api</artifactId>
@@ -44,6 +43,11 @@
             <artifactId>yudao-module-member-api</artifactId>
             <version>${revision}</version>
         </dependency>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-system-api</artifactId>
+            <version>${revision}</version>
+        </dependency>
 
         <!-- 业务组件 -->
         <dependency>

+ 28 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApiImpl.java

@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.trade.api.order;
+
+import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderRespDTO;
+import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
+import cn.iocoder.yudao.module.trade.service.order.TradeOrderService;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+
+/**
+ * 订单 API 接口实现类
+ *
+ * @author HUIHUI
+ */
+@Service
+@Validated
+public class TradeOrderApiImpl implements TradeOrderApi {
+
+    @Resource
+    private TradeOrderService tradeOrderService;
+
+    @Override
+    public TradeOrderRespDTO getOrder(Long id) {
+        return TradeOrderConvert.INSTANCE.convert(tradeOrderService.getOrder(id));
+    }
+
+}

+ 1 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/api/package-info.java

@@ -0,0 +1 @@
+package cn.iocoder.yudao.module.trade.api;

+ 3 - 3
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/delivery/DeliveryExpressTemplateController.java

@@ -70,14 +70,14 @@ public class DeliveryExpressTemplateController {
         return success(DeliveryExpressTemplateConvert.INSTANCE.convertList(list));
     }
 
-    // TODO @puhui999:DeliveryExpressTemplateRespVO 搞个 simple 的哈
+    // TODO @puhui999:DeliveryExpressTemplateRespVO 搞个 simple 的哈 fix
     @GetMapping("/list-all-simple")
     @Operation(summary = "获取快递模版精简信息列表", description = "主要用于前端的下拉选项")
-    public CommonResult<List<DeliveryExpressTemplateRespVO>> getSimpleTemplateList() {
+    public CommonResult<List<DeliveryExpressTemplateSimpleRespVO>> getSimpleTemplateList() {
         // 获取运费模版列表,只要开启状态的
         List<DeliveryExpressTemplateDO> list = deliveryExpressTemplateService.getDeliveryExpressTemplateList();
         // 排序后,返回给前端
-        return success(DeliveryExpressTemplateConvert.INSTANCE.convertList(list));
+        return success(DeliveryExpressTemplateConvert.INSTANCE.convertList1(list));
     }
 
     @GetMapping("/page")

+ 21 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/delivery/vo/expresstemplate/DeliveryExpressTemplateSimpleRespVO.java

@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.expresstemplate;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+
+@Schema(description = "管理后台 - 模版精简信息 Response VO")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class DeliveryExpressTemplateSimpleRespVO {
+
+    @Schema(description = "模版编号", required = true, example = "1024")
+    private Long id;
+
+    @Schema(description = "模板名称", required = true, example = "测试模版")
+    private String name;
+
+}

+ 3 - 1
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/delivery/DeliveryExpressTemplateConvert.java

@@ -28,11 +28,13 @@ public interface DeliveryExpressTemplateConvert {
 
     List<DeliveryExpressTemplateRespVO> convertList(List<DeliveryExpressTemplateDO> list);
 
+    List<DeliveryExpressTemplateSimpleRespVO> convertList1(List<DeliveryExpressTemplateDO> list);
+
     PageResult<DeliveryExpressTemplateRespVO> convertPage(PageResult<DeliveryExpressTemplateDO> page);
 
     default DeliveryExpressTemplateDetailRespVO convert(DeliveryExpressTemplateDO bean,
                                                         List<DeliveryExpressTemplateChargeDO> chargeList,
-                                                        List<DeliveryExpressTemplateFreeDO> freeList){
+                                                        List<DeliveryExpressTemplateFreeDO> freeList) {
         DeliveryExpressTemplateDetailRespVO respVO = convert2(bean);
         respVO.setTemplateCharge(convertTemplateChargeList(chargeList));
         respVO.setTemplateFree(convertTemplateFreeList(freeList));

+ 3 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java

@@ -11,6 +11,7 @@ import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
 import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO;
 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
 import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateReqDTO;
+import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderRespDTO;
 import cn.iocoder.yudao.module.trade.controller.admin.base.member.user.MemberUserRespVO;
 import cn.iocoder.yudao.module.trade.controller.admin.base.product.property.ProductPropertyValueDetailRespVO;
 import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDetailRespVO;
@@ -62,6 +63,8 @@ public interface TradeOrderConvert {
     TradeOrderDO convert(Long userId, String userIp, AppTradeOrderCreateReqVO createReqVO,
                          TradePriceCalculateRespBO calculateRespBO, AddressRespDTO address);
 
+    TradeOrderRespDTO convert(TradeOrderDO orderDO);
+
     default List<TradeOrderItemDO> convertList(TradeOrderDO tradeOrderDO, TradePriceCalculateRespBO calculateRespBO) {
         return CollectionUtils.convertList(calculateRespBO.getItems(), item -> {
             TradeOrderItemDO orderItem = convert(item);

+ 13 - 1
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/DeliveryExpressTemplateService.java

@@ -6,10 +6,12 @@ import cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.expresstemplat
 import cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.expresstemplate.DeliveryExpressTemplatePageReqVO;
 import cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.expresstemplate.DeliveryExpressTemplateUpdateReqVO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateDO;
+import cn.iocoder.yudao.module.trade.service.delivery.bo.SpuDeliveryExpressTemplateRespBO;
 
 import javax.validation.Valid;
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 
 /**
  * 快递运费模板 Service 接口
@@ -73,11 +75,21 @@ public interface DeliveryExpressTemplateService {
 
     /**
      * 校验快递运费模板
-     *
+     * <p>
      * 如果校验不通过,抛出 {@link cn.iocoder.yudao.framework.common.exception.ServiceException} 异常
      *
      * @param templateId 模板编号
      * @return 快递运费模板
      */
     DeliveryExpressTemplateDO validateDeliveryExpressTemplate(Long templateId);
+
+    /**
+     * 基于指定的 SPU 编号数组和收件人地址区域编号. 获取匹配运费模板
+     *
+     * @param ids    SPU 编号列表
+     * @param areaId 区域编号
+     * @return Map (spuId -> 运费模板设置)
+     */
+    Map<Long, SpuDeliveryExpressTemplateRespBO> getExpressTemplateBySpuIdsAndArea(Collection<Long> ids, Integer areaId);
+
 }

+ 53 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/DeliveryExpressTemplateServiceImpl.java

@@ -1,7 +1,10 @@
 package cn.iocoder.yudao.module.trade.service.delivery;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.Assert;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
+import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
 import cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.expresstemplate.*;
 import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateChargeDO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateDO;
@@ -9,6 +12,7 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemp
 import cn.iocoder.yudao.module.trade.dal.mysql.delivery.DeliveryExpressTemplateChargeMapper;
 import cn.iocoder.yudao.module.trade.dal.mysql.delivery.DeliveryExpressTemplateFreeMapper;
 import cn.iocoder.yudao.module.trade.dal.mysql.delivery.DeliveryExpressTemplateMapper;
+import cn.iocoder.yudao.module.trade.service.delivery.bo.SpuDeliveryExpressTemplateRespBO;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
@@ -17,6 +21,7 @@ import javax.annotation.Resource;
 import java.util.*;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
 import static cn.iocoder.yudao.module.trade.convert.delivery.DeliveryExpressTemplateConvert.INSTANCE;
 import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.EXPRESS_TEMPLATE_NAME_DUPLICATE;
@@ -37,6 +42,8 @@ public class DeliveryExpressTemplateServiceImpl implements DeliveryExpressTempla
     private DeliveryExpressTemplateChargeMapper expressTemplateChargeMapper;
     @Resource
     private DeliveryExpressTemplateFreeMapper expressTemplateFreeMapper;
+    @Resource
+    private ProductSpuApi productSpuApi;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
@@ -216,4 +223,50 @@ public class DeliveryExpressTemplateServiceImpl implements DeliveryExpressTempla
         return template;
     }
 
+    @Override
+    public Map<Long, SpuDeliveryExpressTemplateRespBO> getExpressTemplateBySpuIdsAndArea(Collection<Long> spuIds, Integer areaId) {
+        Assert.notNull(areaId, "区域编号 {} 不能为空", areaId);
+        List<ProductSpuRespDTO> spuList = productSpuApi.getSpuList(spuIds);
+        if (CollUtil.isEmpty(spuList)) {
+            return Collections.emptyMap();
+        }
+        Map<Long, ProductSpuRespDTO> spuMap = convertMap(spuList, ProductSpuRespDTO::getDeliveryTemplateId);
+        List<DeliveryExpressTemplateDO> templateList = expressTemplateMapper.selectBatchIds(spuMap.keySet());
+        Map<Long, SpuDeliveryExpressTemplateRespBO> result = new HashMap<>(templateList.size());
+        templateList.forEach(item -> {
+            if (spuMap.containsKey(item.getId())) {
+                ProductSpuRespDTO spuDTO = spuMap.get(item.getId());
+                SpuDeliveryExpressTemplateRespBO bo = new SpuDeliveryExpressTemplateRespBO()
+                        .setSpuId(spuDTO.getId()).setAreaId(areaId)
+                        .setChargeMode(item.getChargeMode())
+                        .setTemplateCharge(findMatchExpressTemplateCharge(item.getId(), areaId))
+                        .setTemplateFree(findMatchExpressTemplateFree(item.getId(), areaId));
+                result.put(spuDTO.getId(), bo);
+            }
+        });
+        return result;
+    }
+
+    private DeliveryExpressTemplateChargeDO findMatchExpressTemplateCharge(Long templateId, Integer areaId) {
+        List<DeliveryExpressTemplateChargeDO> list = expressTemplateChargeMapper.selectListByTemplateId(templateId);
+        for (DeliveryExpressTemplateChargeDO item : list) {
+            // 第一个匹配的返回。 areaId 不能重复
+            if (item.getAreaIds().contains(areaId)) {
+                return item;
+            }
+        }
+        return null;
+    }
+
+    private DeliveryExpressTemplateFreeDO findMatchExpressTemplateFree(Long templateId, Integer areaId) {
+        List<DeliveryExpressTemplateFreeDO> list = expressTemplateFreeMapper.selectListByTemplateId(templateId);
+        for (DeliveryExpressTemplateFreeDO item : list) {
+            // 第一个匹配的返回。 areaId 不能重复
+            if (item.getAreaIds().contains(areaId)) {
+                return item;
+            }
+        }
+        return null;
+    }
+
 }

+ 45 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/bo/SpuDeliveryExpressTemplateRespBO.java

@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.trade.service.delivery.bo;
+
+import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateChargeDO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateFreeDO;
+import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryExpressChargeModeEnum;
+import lombok.Data;
+
+/**
+ * SPU 运费模板配置 Resp BO
+ *
+ * @author jason
+ */
+@Data
+public class SpuDeliveryExpressTemplateRespBO {
+
+    /**
+     * 配送计费方式
+     * <p>
+     * 枚举 {@link DeliveryExpressChargeModeEnum}
+     */
+    private Integer chargeMode;
+
+    /**
+     * 运费模板快递运费设置
+     */
+    private DeliveryExpressTemplateChargeDO templateCharge;
+
+    /**
+     * 运费模板包邮设置
+     */
+    private DeliveryExpressTemplateFreeDO templateFree;
+
+    /**
+     * SPU 编号
+     * <p>
+     * 关联  ProductSpuDO 的 id 编号
+     */
+    private Long spuId;
+
+    /**
+     * 区域编号
+     */
+    private Integer areaId;
+
+}

+ 60 - 19
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderServiceImpl.java

@@ -6,7 +6,9 @@ import cn.hutool.core.util.IdUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.core.KeyValue;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.enums.TerminalEnum;
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 import cn.iocoder.yudao.module.member.api.address.AddressApi;
@@ -21,6 +23,11 @@ import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
 import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi;
 import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO;
+import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi;
+import cn.iocoder.yudao.module.system.api.notify.dto.NotifySendSingleToUserReqDTO;
+import cn.iocoder.yudao.module.system.api.notify.dto.NotifyTemplateReqDTO;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.enums.notify.NotifyTemplateTypeEnum;
 import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO;
 import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderPageReqVO;
 import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO;
@@ -29,6 +36,7 @@ import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettle
 import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementRespVO;
 import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
 import cn.iocoder.yudao.module.trade.dal.dataobject.cart.TradeCartDO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
 import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderItemMapper;
@@ -38,6 +46,7 @@ import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
 import cn.iocoder.yudao.module.trade.enums.order.*;
 import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
 import cn.iocoder.yudao.module.trade.service.cart.TradeCartService;
+import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService;
 import cn.iocoder.yudao.module.trade.service.price.TradePriceService;
 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
@@ -74,6 +83,8 @@ public class TradeOrderServiceImpl implements TradeOrderService {
     private TradeCartService tradeCartService;
     @Resource
     private TradePriceService tradePriceService;
+    @Resource
+    private DeliveryExpressService deliveryExpressService;
 
     @Resource
     private ProductSkuApi productSkuApi;
@@ -85,7 +96,10 @@ public class TradeOrderServiceImpl implements TradeOrderService {
     private CouponApi couponApi;
     @Resource
     private MemberUserApi memberUserApi;
-
+    @Resource
+    private AdminUserApi adminUserApi;
+    @Resource
+    private NotifyMessageSendApi notifyMessageSendApi;
     @Resource
     private TradeOrderProperties tradeOrderProperties;
 
@@ -123,7 +137,7 @@ public class TradeOrderServiceImpl implements TradeOrderService {
     /**
      * 计算订单价格
      *
-     * @param userId 用户编号
+     * @param userId          用户编号
      * @param settlementReqVO 结算信息
      * @return 订单价格
      */
@@ -162,7 +176,7 @@ public class TradeOrderServiceImpl implements TradeOrderService {
     /**
      * 校验收件地址是否存在
      *
-     * @param userId 用户编号
+     * @param userId    用户编号
      * @param addressId 收件地址编号
      * @return 收件地址
      */
@@ -181,7 +195,7 @@ public class TradeOrderServiceImpl implements TradeOrderService {
         order.setStatus(TradeOrderStatusEnum.UNPAID.getStatus());
         order.setType(TradeOrderTypeEnum.NORMAL.getType());
         order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus());
-        order.setProductCount(getSumValue(calculateRespBO.getItems(),  TradePriceCalculateRespBO.OrderItem::getCount, Integer::sum));
+        order.setProductCount(getSumValue(calculateRespBO.getItems(), TradePriceCalculateRespBO.OrderItem::getCount, Integer::sum));
         order.setTerminal(TerminalEnum.H5.getTerminal()); // todo 数据来源?
         // 支付信息
         order.setAdjustPrice(0).setPayed(false);
@@ -201,12 +215,12 @@ public class TradeOrderServiceImpl implements TradeOrderService {
 
     /**
      * 执行创建完创建完订单后的逻辑
-     *
+     * <p>
      * 例如说:优惠劵的扣减、积分的扣减、支付单的创建等等
      *
-     * @param userId 用户编号
-     * @param createReqVO 创建订单请求
-     * @param tradeOrderDO 交易订单
+     * @param userId          用户编号
+     * @param createReqVO     创建订单请求
+     * @param tradeOrderDO    交易订单
      * @param calculateRespBO 订单价格计算结果
      */
     private void afterCreateTradeOrder(Long userId, AppTradeOrderCreateReqVO createReqVO,
@@ -265,11 +279,11 @@ public class TradeOrderServiceImpl implements TradeOrderService {
 
     /**
      * 校验交易订单满足被支付的条件
-     *
+     * <p>
      * 1. 交易订单未支付
      * 2. 支付单已支付
      *
-     * @param id 交易订单编号
+     * @param id         交易订单编号
      * @param payOrderId 支付订单编号
      * @return 交易订单
      */
@@ -324,8 +338,11 @@ public class TradeOrderServiceImpl implements TradeOrderService {
     public void deliveryOrder(Long userId, TradeOrderDeliveryReqVO deliveryReqVO) {
         // 校验并获得交易订单(可发货)
         TradeOrderDO order = validateOrderDeliverable(deliveryReqVO.getId());
-
-        // TODO 芋艿:logisticsId 校验存在
+        // TODO 芋艿:logisticsId 校验存在 发货物流公司 fix
+        DeliveryExpressDO deliveryExpress = deliveryExpressService.getDeliveryExpress(deliveryReqVO.getLogisticsId());
+        if (deliveryExpress == null) {
+            throw exception(DELIVERY_EXPRESS_NOT_EXISTS);
+        }
 
         // 更新 TradeOrderDO 状态为已发货,等待收货
         int updateCount = tradeOrderMapper.updateByIdAndStatus(order.getId(), order.getStatus(),
@@ -338,8 +355,32 @@ public class TradeOrderServiceImpl implements TradeOrderService {
 
         // TODO 芋艿:发送订单变化的消息
 
-        // TODO 芋艿:发送站内信
-
+        // TODO 芋艿:发送站内信 fix
+        // 1、获取模版编码为 order_delivery 的模版,判断是否存在 存在放回 true
+        if (!notifyMessageSendApi.validateNotifyTemplate("order_delivery")) {
+            // 1、1 站内信模版不存在则创建模版
+            NotifyTemplateReqDTO templateReqDTO = new NotifyTemplateReqDTO();
+            templateReqDTO.setName("订单发货通知模版");
+            templateReqDTO.setCode("order_delivery");
+            templateReqDTO.setType(NotifyTemplateTypeEnum.NOTIFICATION_MESSAGE.getType()); // 系统消息
+            // 获取操作用户
+            // AdminUserRespDTO user = adminUserApi.getUser(userId);
+            // templateReqDTO.setNickname(user.getNickname());
+            templateReqDTO.setNickname(UserTypeEnum.ADMIN.getName());
+            templateReqDTO.setContent("订单:{orderId}{msg}");
+            templateReqDTO.setStatus(CommonStatusEnum.ENABLE.getStatus());
+            notifyMessageSendApi.createNotifyTemplate(templateReqDTO);
+        }
+        // 2、构造消息
+        Map<String, Object> msgMap = new HashMap<>();
+        msgMap.put("orderId", deliveryReqVO.getId());
+        msgMap.put("msg", TradeOrderStatusEnum.DELIVERED.getStatus());
+        // 2、发送站内信
+        notifyMessageSendApi.sendSingleMessageToAdmin(
+                new NotifySendSingleToUserReqDTO()
+                        .setUserId(userId)
+                        .setTemplateCode("order_delivery")
+                        .setTemplateParams(msgMap));
         // TODO 芋艿:OrderLog
 
         // TODO 设计:like:是否要单独一个 delivery 发货单表???
@@ -349,7 +390,7 @@ public class TradeOrderServiceImpl implements TradeOrderService {
 
     /**
      * 校验交易订单满足被发货的条件
-     *
+     * <p>
      * 1. 交易订单未发货
      *
      * @param id 交易订单编号
@@ -363,7 +404,7 @@ public class TradeOrderServiceImpl implements TradeOrderService {
         }
         // 校验订单是否是待发货状态
         if (!TradeOrderStatusEnum.isUndelivered(order.getStatus())
-            || ObjectUtil.notEqual(order.getDeliveryStatus(), TradeOrderDeliveryStatusEnum.UNDELIVERED.getStatus())) {
+                || ObjectUtil.notEqual(order.getDeliveryStatus(), TradeOrderDeliveryStatusEnum.UNDELIVERED.getStatus())) {
             throw exception(ORDER_DELIVERY_FAIL_STATUS_NOT_UNDELIVERED);
         }
         return order;
@@ -397,11 +438,11 @@ public class TradeOrderServiceImpl implements TradeOrderService {
 
     /**
      * 校验交易订单满足可售货的条件
-     *
+     * <p>
      * 1. 交易订单待收货
      *
      * @param userId 用户编号
-     * @param id 交易订单编号
+     * @param id     交易订单编号
      * @return 交易订单
      */
     private TradeOrderDO validateOrderReceivable(Long userId, Long id) {
@@ -476,7 +517,7 @@ public class TradeOrderServiceImpl implements TradeOrderService {
     public void updateOrderItemAfterSaleStatus(Long id, Integer oldAfterSaleStatus, Integer newAfterSaleStatus, Integer refundPrice) {
         // 如果退款成功,则 refundPrice 非空
         if (Objects.equals(newAfterSaleStatus, TradeOrderItemAfterSaleStatusEnum.SUCCESS.getStatus())
-            && refundPrice == null) {
+                && refundPrice == null) {
             throw new IllegalArgumentException(StrUtil.format("id({}) 退款成功,退款金额不能为空", id));
         }
 

+ 0 - 9
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateReqBO.java

@@ -1,6 +1,5 @@
 package cn.iocoder.yudao.module.trade.service.price.bo;
 
-import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateDO;
 import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
 import lombok.Data;
@@ -54,14 +53,6 @@ public class TradePriceCalculateReqBO {
     private Integer deliveryType;
 
     /**
-     * 配送模板编号
-     *
-     * 关联 {@link DeliveryExpressTemplateDO#getId()}
-     */
-    // TODO @jason:运费模版,是不是每个 SKU 传入哈
-    private Long templateId;
-
-    /**
      * 商品 SKU 数组
      */
     @NotNull(message = "商品数组不能为空")

+ 78 - 88
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java

@@ -1,17 +1,17 @@
 package cn.iocoder.yudao.module.trade.service.price.calculator;
 
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.Assert;
 import cn.iocoder.yudao.module.member.api.address.AddressApi;
 import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
 import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateChargeDO;
-import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateDO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateFreeDO;
-import cn.iocoder.yudao.module.trade.dal.mysql.delivery.DeliveryExpressTemplateChargeMapper;
-import cn.iocoder.yudao.module.trade.dal.mysql.delivery.DeliveryExpressTemplateFreeMapper;
 import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryExpressChargeModeEnum;
 import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
 import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressTemplateService;
+import cn.iocoder.yudao.module.trade.service.delivery.bo.SpuDeliveryExpressTemplateRespBO;
 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO.OrderItem;
@@ -19,7 +19,6 @@ import org.springframework.core.annotation.Order;
 import org.springframework.stereotype.Component;
 
 import javax.annotation.Resource;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -39,14 +38,8 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
     private AddressApi addressApi;
     @Resource
     private ProductSkuApi productSkuApi;
-
     @Resource
     private DeliveryExpressTemplateService deliveryExpressTemplateService;
-    // TODO @jason:走 Service 哈。Mapper 只允许自己的 Service 调用,保护好数据结构;
-    @Resource
-    private DeliveryExpressTemplateChargeMapper templateChargeMapper;
-    @Resource
-    private DeliveryExpressTemplateFreeMapper templateFreeMapper;
 
     @Override
     public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
@@ -54,66 +47,45 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
         if (param.getDeliveryType() == null || DeliveryTypeEnum.PICK_UP.getMode().equals(param.getDeliveryType())) {
             return;
         }
-
-        if (param.getTemplateId() == null || param.getAddressId() == null) {
+        // 1.2 得到收件地址区域
+        if (param.getAddressId() == null) {
             return;
         }
-        // 1.2 校验运费模板是否存在
-        DeliveryExpressTemplateDO template = deliveryExpressTemplateService.validateDeliveryExpressTemplate(param.getTemplateId());
-
-        // 得到包邮配置
-        List<DeliveryExpressTemplateFreeDO> expressTemplateFreeList = templateFreeMapper.selectListByTemplateId(template.getId());
-        Map<Integer, DeliveryExpressTemplateFreeDO> areaTemplateFreeMap = new HashMap<>();
-        expressTemplateFreeList.forEach(item -> {
-            for (Integer areaId : item.getAreaIds()) {
-                // TODO 需要保证 areaId 不能重复
-                if (!areaTemplateFreeMap.containsKey(areaId)) {
-                    areaTemplateFreeMap.put(areaId, item);
-                }
-            }
-        });
-        // 得到快递运费配置
-        List<DeliveryExpressTemplateChargeDO> expressTemplateChargeList = templateChargeMapper.selectListByTemplateId(template.getId());
-        Map<Integer, DeliveryExpressTemplateChargeDO> areaTemplateChargeMap = new HashMap<>();
-        expressTemplateChargeList.forEach(item -> {
-            for (Integer areaId : item.getAreaIds()) {
-                // areaId 不能重复
-                if (!areaTemplateChargeMap.containsKey(areaId)) {
-                    areaTemplateChargeMap.put(areaId, item);
-                }
-            }
-        });
-        // 得到收件地址区域
         AddressRespDTO address = addressApi.getAddress(param.getAddressId(), param.getUserId());
-        // 1.3 计算快递费用
-        calculateDeliveryPrice(address.getAreaId(), template.getChargeMode(),
-                areaTemplateFreeMap, areaTemplateChargeMap, result);
+        Assert.notNull(address, "收件人({})的地址,不能为空", param.getUserId());
+
+        //1.3 过滤出已选中的商品SKU
+        List<OrderItem> selectedItem = filterList(result.getItems(), OrderItem::getSelected);
+
+        Map<Long, SpuDeliveryExpressTemplateRespBO> spuExpressTemplateMap =
+                deliveryExpressTemplateService.getExpressTemplateBySpuIdsAndArea(
+                        convertSet(selectedItem, OrderItem::getSpuId), address.getAreaId());
+
+        // 1.4 计算配送费用
+        if (CollUtil.isNotEmpty(spuExpressTemplateMap)) {
+            calculateDeliveryPrice(selectedItem, spuExpressTemplateMap, result);
+        }
+
     }
 
-    /**
-     * 校验订单是否满足包邮条件
-     *
-     * @param receiverAreaId        收件人地区的区域编号
-     * @param chargeMode            配送计费方式
-     * @param areaTemplateFreeMap   运费模板包邮区域设置 Map
-     * @param areaTemplateChargeMap 运费模板快递费用设置 Map
-     */
-    private void calculateDeliveryPrice(Integer receiverAreaId,
-                                        Integer chargeMode,
-                                        Map<Integer, DeliveryExpressTemplateFreeDO> areaTemplateFreeMap,
-                                        Map<Integer, DeliveryExpressTemplateChargeDO> areaTemplateChargeMap,
+    private void calculateDeliveryPrice(List<OrderItem> selectedSkus,
+                                        Map<Long, SpuDeliveryExpressTemplateRespBO> spuExpressTemplateMap,
                                         TradePriceCalculateRespBO result) {
-        // 过滤出已选中的商品SKU
-        List<OrderItem> selectedItem = filterList(result.getItems(), OrderItem::getSelected);
-        Set<Long> skuIds = convertSet(selectedItem, OrderItem::getSkuId);
+        Set<Long> skuIds = convertSet(selectedSkus, OrderItem::getSkuId);
         // 得到SKU 详情。得到 重量体积
         Map<Long, ProductSkuRespDTO> skuRespMap = convertMap(productSkuApi.getSkuList(skuIds), ProductSkuRespDTO::getId);
-        // 一个 spuId 可能对应多条订单商品 SKU
-        // TODO @jason:得确认下,按照 sku 算,还是 spu 算;
-        Map<Long, List<OrderItem>> spuIdItemMap = convertMultiMap(selectedItem, OrderItem::getSpuId);
+        // 按spu 来计算商品的运费 一个 spuId 可能对应多条订单商品 SKU,
+        Map<Long, List<OrderItem>> spuIdItemMap = convertMultiMap(selectedSkus, OrderItem::getSpuId);
+
         // 依次计算每个 SPU 的快递运费
         for (Map.Entry<Long, List<OrderItem>> entry : spuIdItemMap.entrySet()) {
+            Long spuId  = entry.getKey();
             List<OrderItem> orderItems = entry.getValue();
+            SpuDeliveryExpressTemplateRespBO templateBO = spuExpressTemplateMap.get(spuId);
+            if (templateBO == null) {
+                // 记录错误日志
+                continue;
+            }
             // 总件数, 总金额, 总重量, 总体积
             int totalCount = 0;
             int totalPrice = 0;
@@ -121,51 +93,67 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
             double totalVolume = 0;
             for (OrderItem orderItem : orderItems) {
                 totalCount += orderItem.getCount();
-                totalPrice += orderItem.getPrice(); // TODO jason:应该按照 payPrice?
+                totalPrice += orderItem.getPayPrice(); // 先按应付总金额来算,后面确认一下
                 ProductSkuRespDTO skuResp = skuRespMap.get(orderItem.getSkuId());
                 if (skuResp != null) {
-                    totalWeight = totalWeight + skuResp.getWeight(); // TODO @jason:* 数量
-                    totalVolume = totalVolume + skuResp.getVolume();
+                    totalWeight = totalWeight + skuResp.getWeight() * orderItem.getCount();
+                    totalVolume = totalVolume + skuResp.getVolume() * orderItem.getCount();
                 }
             }
             // 优先判断是否包邮. 如果包邮不计算快递运费
-            if (areaTemplateFreeMap.containsKey(receiverAreaId) &&
-                    checkExpressFree(chargeMode, totalCount, totalWeight,
-                            totalVolume, totalPrice, areaTemplateFreeMap.get(receiverAreaId))) {
+            if (checkExpressFree(templateBO.getChargeMode(), totalCount, totalWeight,
+                            totalVolume, totalPrice, templateBO.getTemplateFree())) {
                 continue;
             }
-            // 计算快递运费
-            // TODO @jason:貌似也可以抽成 checkExpressFree 类似方法
-            if (areaTemplateChargeMap.containsKey(receiverAreaId)) {
-                DeliveryExpressTemplateChargeDO templateCharge = areaTemplateChargeMap.get(receiverAreaId);
-                DeliveryExpressChargeModeEnum chargeModeEnum = DeliveryExpressChargeModeEnum.valueOf(chargeMode);
-                switch (chargeModeEnum) {
-                    case PIECE: {
-                        calculateExpressFeeBySpu(totalCount, templateCharge, orderItems);
-                        break;
-                    }
-                    case WEIGHT: {
-                        calculateExpressFeeBySpu(totalWeight, templateCharge, orderItems);
-                        break;
-                    }
-                    case VOLUME: {
-                        calculateExpressFeeBySpu(totalVolume, templateCharge, orderItems);
-                        break;
-                    }
-                }
+            if (templateBO.getTemplateCharge() == null) {
+                continue;
             }
+            // 计算快递运费
+            calculateExpressFeeByChargeMode(totalCount, totalWeight, totalVolume,
+                    templateBO.getChargeMode(), templateBO.getTemplateCharge(), orderItems);
+
         }
         TradePriceCalculatorHelper.recountAllPrice(result);
     }
 
     /**
-     * 按 spu 来计算快递费用
+     * 按配送方式来计算运费
+     *
+     * @param totalCount  总件数
+     * @param totalWeight 总重量
+     * @param totalVolume 总体积
+     * @param chargeMode  配送计费方式
+     * @param templateCharge 快递运费配置
+     * @param orderItems SKU 商品项目
+     */
+    private void calculateExpressFeeByChargeMode(double totalCount, double totalWeight, double totalVolume,
+                                                 int chargeMode, DeliveryExpressTemplateChargeDO templateCharge,
+                                                 List<OrderItem> orderItems) {
+        DeliveryExpressChargeModeEnum chargeModeEnum = DeliveryExpressChargeModeEnum.valueOf(chargeMode);
+        switch (chargeModeEnum) {
+            case PIECE: {
+                calculateExpressFee(totalCount, templateCharge, orderItems);
+                break;
+            }
+            case WEIGHT: {
+                calculateExpressFee(totalWeight, templateCharge, orderItems);
+                break;
+            }
+            case VOLUME: {
+                calculateExpressFee(totalVolume, templateCharge, orderItems);
+                break;
+            }
+        }
+    }
+
+    /**
+     * 计算 SKU 商品快递费用
      *
      * @param total          总件数/总重量/总体积
      * @param templateCharge 快递运费配置
      * @param orderItems     SKU 商品项目
      */
-    private void calculateExpressFeeBySpu(double total, DeliveryExpressTemplateChargeDO templateCharge, List<OrderItem> orderItems) {
+    private void calculateExpressFee(double total, DeliveryExpressTemplateChargeDO templateCharge, List<OrderItem> orderItems) {
         int deliveryPrice;
         if (total <= templateCharge.getStartCount()) {
             deliveryPrice = templateCharge.getStartPrice();
@@ -176,8 +164,7 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
             int extraPrice = templateCharge.getExtraPrice() * extraNum;
             deliveryPrice = templateCharge.getStartPrice() + extraPrice;
         }
-        // TODO @芋艿 分摊快递费用到 SKU. 是不是搞复杂了;
-        // TODO @jason:因为退费的时候,可能按照 SKU 考虑退费金额
+        // 分摊快递费用到 SKU. 退费的时候,可能按照 SKU 考虑退费金额
         divideDeliveryPrice(deliveryPrice, orderItems);
     }
 
@@ -207,6 +194,9 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
      */
     private boolean checkExpressFree(Integer chargeMode, int totalCount, double totalWeight,
                                      double totalVolume, int totalPrice, DeliveryExpressTemplateFreeDO templateFree) {
+        if (templateFree == null) {
+            return false;
+        }
         DeliveryExpressChargeModeEnum chargeModeEnum = DeliveryExpressChargeModeEnum.valueOf(chargeMode);
         switch (chargeModeEnum) {
             case PIECE:

+ 5 - 0
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/notify/NotifyMessageSendApi.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.system.api.notify;
 
 import cn.iocoder.yudao.module.system.api.notify.dto.NotifySendSingleToUserReqDTO;
+import cn.iocoder.yudao.module.system.api.notify.dto.NotifyTemplateReqDTO;
 
 import javax.validation.Valid;
 
@@ -27,4 +28,8 @@ public interface NotifyMessageSendApi {
      */
     Long sendSingleMessageToMember(@Valid NotifySendSingleToUserReqDTO reqDTO);
 
+
+    boolean validateNotifyTemplate(String orderDelivery);
+
+    void createNotifyTemplate(NotifyTemplateReqDTO templateReqDTO);
 }

+ 34 - 0
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/notify/dto/NotifyTemplateReqDTO.java

@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.system.api.notify.dto;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
+@Data
+public class NotifyTemplateReqDTO {
+
+    @NotEmpty(message = "模版名称不能为空")
+    private String name;
+
+    @NotNull(message = "模版编码不能为空")
+    private String code;
+
+    @NotNull(message = "模版类型不能为空")
+    private Integer type;
+
+    @NotEmpty(message = "发送人名称不能为空")
+    private String nickname;
+
+    @NotEmpty(message = "模版内容不能为空")
+    private String content;
+
+    @NotNull(message = "状态不能为空")
+    @InEnum(value = CommonStatusEnum.class, message = "状态必须是 {value}")
+    private Integer status;
+
+    private String remark;
+
+}

+ 26 - 0
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/notify/NotifyTemplateTypeEnum.java

@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.system.enums.notify;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 通知模板类型枚举
+ *
+ * @author HUIHUI
+ */
+@Getter
+@AllArgsConstructor
+public enum NotifyTemplateTypeEnum {
+
+    /**
+     * 系统消息
+     */
+    SYSTEM_MESSAGE(2),
+    /**
+     * 通知消息
+     */
+    NOTIFICATION_MESSAGE(1);
+
+    private final Integer type;
+
+}

+ 11 - 1
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/notify/NotifyMessageSendApiImpl.java

@@ -1,7 +1,7 @@
 package cn.iocoder.yudao.module.system.api.notify;
 
 import cn.iocoder.yudao.module.system.api.notify.dto.NotifySendSingleToUserReqDTO;
-import cn.iocoder.yudao.module.system.service.notify.NotifyMessageService;
+import cn.iocoder.yudao.module.system.api.notify.dto.NotifyTemplateReqDTO;
 import cn.iocoder.yudao.module.system.service.notify.NotifySendService;
 import org.springframework.stereotype.Service;
 
@@ -30,4 +30,14 @@ public class NotifyMessageSendApiImpl implements NotifyMessageSendApi {
                 reqDTO.getTemplateCode(), reqDTO.getTemplateParams());
     }
 
+    @Override
+    public boolean validateNotifyTemplate(String orderDelivery) {
+        return false;
+    }
+
+    @Override
+    public void createNotifyTemplate(NotifyTemplateReqDTO templateReqDTO) {
+
+    }
+
 }