Browse Source

trade:优化创建订单接口的后端实现

YunaiV 2 years ago
parent
commit
01d10b518c
17 changed files with 310 additions and 139 deletions
  1. 2 2
      yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/ProductSpuApi.java
  2. 3 1
      yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/dto/ProductSpuRespDTO.java
  3. 2 2
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/spu/ProductSpuApiImpl.java
  4. 2 2
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/spu/ProductSpuConvert.java
  5. 3 3
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java
  6. 2 1
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/price/PriceServiceImpl.java
  7. 6 4
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java
  8. 5 2
      yudao-module-mall/yudao-module-trade-biz/pom.xml
  9. 3 2
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderCreateReqVO.java
  10. 38 4
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java
  11. 0 17
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderItemConvert.java
  12. 20 20
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderItemDO.java
  13. 118 74
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderServiceImpl.java
  14. 21 0
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/address/AddressApi.java
  15. 48 0
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/address/dto/AddressRespDTO.java
  16. 28 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/address/AddressApiImpl.java
  17. 9 5
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/address/AddressConvert.java

+ 2 - 2
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/ProductSpuApi.java

@@ -1,6 +1,6 @@
 package cn.iocoder.yudao.module.product.api.spu;
 
-import cn.iocoder.yudao.module.product.api.spu.dto.SpuInfoRespDTO;
+import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
 
 import java.util.Collection;
 import java.util.List;
@@ -19,6 +19,6 @@ public interface ProductSpuApi {
      * @param ids SPU 编号列表
      * @return SPU 数组
      */
-    List<SpuInfoRespDTO> getSpuList(Collection<Long> ids);
+    List<ProductSpuRespDTO> getSpuList(Collection<Long> ids);
 
 }

+ 3 - 1
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/dto/SpuInfoRespDTO.java → yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/dto/ProductSpuRespDTO.java

@@ -9,11 +9,13 @@ import java.util.List;
 
 // TODO @LeeYan9: ProductSpuRespDTO
 /**
+ * 商品 SPU 信息 Response DTO
+ *
  * @author LeeYan9
  * @since 2022-08-26
  */
 @Data
-public class SpuInfoRespDTO {
+public class ProductSpuRespDTO {
 
     /**
      * 商品 SPU 编号,自增

+ 2 - 2
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/spu/ProductSpuApiImpl.java

@@ -1,7 +1,7 @@
 package cn.iocoder.yudao.module.product.api.spu;
 
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
-import cn.iocoder.yudao.module.product.api.spu.dto.SpuInfoRespDTO;
+import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
 import cn.iocoder.yudao.module.product.convert.spu.ProductSpuConvert;
 import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
 import cn.iocoder.yudao.module.product.dal.mysql.spu.ProductSpuMapper;
@@ -27,7 +27,7 @@ public class ProductSpuApiImpl implements ProductSpuApi {
     private ProductSpuMapper productSpuMapper;
 
     @Override
-    public List<SpuInfoRespDTO> getSpuList(Collection<Long> spuIds) {
+    public List<ProductSpuRespDTO> getSpuList(Collection<Long> spuIds) {
         // TODO TODO LeeYan9: AllEmpty?
         if (CollectionUtils.isAnyEmpty(spuIds)) {
             return Collections.emptyList();

+ 2 - 2
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/spu/ProductSpuConvert.java

@@ -1,7 +1,7 @@
 package cn.iocoder.yudao.module.product.convert.spu;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.product.api.spu.dto.SpuInfoRespDTO;
+import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
 import cn.iocoder.yudao.module.product.controller.admin.spu.vo.*;
 import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppSpuPageReqVO;
 import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppSpuPageRespVO;
@@ -35,7 +35,7 @@ public interface ProductSpuConvert {
 
     AppSpuPageRespVO convertAppResp(ProductSpuDO list);
 
-    List<SpuInfoRespDTO> convertList2(List<ProductSpuDO> list);
+    List<ProductSpuRespDTO> convertList2(List<ProductSpuDO> list);
 
     List<ProductSpuSimpleRespVO> convertList02(List<ProductSpuDO> list);
 

+ 3 - 3
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java

@@ -22,17 +22,17 @@ public interface ErrorCodeConstants {
 
     // ========== Coupon 相关 1003003000 ============
     ErrorCode COUPON_NO_MATCH_SPU = new ErrorCode(1003003000, "优惠劵没有可使用的商品!");
-    ErrorCode COUPON_NO_MATCH_MIN_PRICE = new ErrorCode(1003003000, "不满足优惠劵使用的最低金额");
+    ErrorCode COUPON_NO_MATCH_MIN_PRICE = new ErrorCode(1003003000, "所结算的商品中未满足使用的金额");
 
     // ========== 优惠劵模板 1003004000 ==========
     ErrorCode COUPON_TEMPLATE_NOT_EXISTS = new ErrorCode(1003004000, "优惠劵模板不存在");
     ErrorCode COUPON_TEMPLATE_TOTAL_COUNT_TOO_SMALL = new ErrorCode(1003004001, "发放数量不能小于已领取数量({})");
 
     // ========== 优惠劵模板 1003005000 ==========
-    ErrorCode COUPON_NOT_EXISTS = new ErrorCode(1003005000, "优惠不存在");
+    ErrorCode COUPON_NOT_EXISTS = new ErrorCode(1003005000, "优惠不存在");
     ErrorCode COUPON_DELETE_FAIL_USED = new ErrorCode(1003005001, "回收优惠劵失败,优惠劵已被使用");
     ErrorCode COUPON_STATUS_NOT_UNUSED = new ErrorCode(1006003003, "优惠劵不处于待使用状态");
-    ErrorCode COUPON_VALID_TIME_NOT_NOW = new ErrorCode(1006003004, "优惠劵不在有效期内");
+    ErrorCode COUPON_VALID_TIME_NOT_NOW = new ErrorCode(1006003004, "优惠券不在使用时间范围内");
 
     // ========== 满减送活动 1003006000 ==========
     ErrorCode REWARD_ACTIVITY_NOT_EXISTS = new ErrorCode(1003006000, "满减送活动不存在");

+ 2 - 1
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/price/PriceServiceImpl.java

@@ -116,7 +116,8 @@ public class PriceServiceImpl implements PriceService {
                 assert originPrice != null;
                 if (originPrice < coupon.getUsePrice()) {
                     return couponMeetRespDTO.setMeet(false)
-                            .setMeetTip(String.format("差 %s 元可用优惠劵", formatPrice(coupon.getUsePrice() - originPrice)));
+//                            .setMeetTip(String.format("差 %s 元可用优惠劵", formatPrice(coupon.getUsePrice() - originPrice)));
+                            .setMeetTip("所结算的商品中未满足使用的金额");
                 }
             } catch (ServiceException serviceException) {
                 couponMeetRespDTO.setMeet(false);

+ 6 - 4
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java

@@ -12,10 +12,12 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
 public interface ErrorCodeConstants {
 
     // ==========  Order 模块 1-011-000-000 ==========
-    ErrorCode ORDER_SKU_NOT_FOUND = new ErrorCode(1011000001, "商品不存在");
-    ErrorCode ORDER_SPU_NOT_SALE = new ErrorCode(1011000002, "商品不可售卖");
-    ErrorCode ORDER_SKU_NOT_SALE = new ErrorCode(1011000003, "商品Sku不可售卖");
-    ErrorCode ORDER_SKU_STOCK_NOT_ENOUGH = new ErrorCode(1011000004, "商品库存不足");
+    ErrorCode ORDER_CREATE_SKU_NOT_FOUND = new ErrorCode(1011000001, "商品 SKU 不存在");
+    ErrorCode ORDER_CREATE_SPU_NOT_SALE = new ErrorCode(1011000002, "商品 SPU 不可售卖");
+    ErrorCode ORDER_CREATE_SKU_NOT_SALE = new ErrorCode(1011000003, "商品 SKU 不可售卖");
+    ErrorCode ORDER_CREATE_SKU_STOCK_NOT_ENOUGH = new ErrorCode(1011000004, "商品 SKU 库存不足");
+    ErrorCode ORDER_CREATE_SPU_NOT_FOUND = new ErrorCode(1011000005, "商品 SPU 不可售卖");
+    ErrorCode ORDER_CREATE_ADDRESS_NOT_FOUND = new ErrorCode(1011000006, "收货地址不存在");
 
     // ==========  Cart 模块 1-011-001-000 ==========
     ErrorCode CARD_ITEM_NOT_FOUND = new ErrorCode(1001001000, "购物车项不存在");

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

@@ -29,18 +29,21 @@
             <artifactId>yudao-module-product-api</artifactId>
             <version>${revision}</version>
         </dependency>
-
         <dependency>
             <groupId>cn.iocoder.boot</groupId>
             <artifactId>yudao-module-pay-api</artifactId>
             <version>${revision}</version>
         </dependency>
-
         <dependency>
             <groupId>cn.iocoder.boot</groupId>
             <artifactId>yudao-module-promotion-api</artifactId>
             <version>${revision}</version>
         </dependency>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-member-api</artifactId>
+            <version>${revision}</version>
+        </dependency>
 
         <!-- 业务组件 -->
         <dependency>

+ 3 - 2
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderCreateReqVO.java

@@ -5,6 +5,7 @@ import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
 import javax.validation.constraints.Min;
+import javax.validation.constraints.NotEmpty;
 import javax.validation.constraints.NotNull;
 import java.util.List;
 
@@ -14,7 +15,7 @@ public class AppTradeOrderCreateReqVO {
 
     @ApiModelProperty(name = "收件地址编号", required = true, example = "1")
     @NotNull(message = "收件地址不能为空")
-    private Integer addressId;
+    private Long addressId;
 
     @ApiModelProperty(name = "优惠劵编号", example = "1024")
     private Long couponId;
@@ -29,7 +30,7 @@ public class AppTradeOrderCreateReqVO {
     /**
      * 订单商品项列表
      */
-    @NotNull(message = "必须选择购买的商品")
+    @NotEmpty(message = "必须选择购买的商品")
     private List<Item> items;
 
     @ApiModel(value = "订单商品项")

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

@@ -1,24 +1,58 @@
 package cn.iocoder.yudao.module.trade.convert.order;
 
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
+import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
 import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateRespDTO;
 import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO;
 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.enums.order.TradeOrderItemRefundStatusEnum;
 import org.mapstruct.Mapper;
 import org.mapstruct.Mapping;
 import org.mapstruct.Mappings;
 import org.mapstruct.factory.Mappers;
 
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
+
 @Mapper
 public interface TradeOrderConvert {
 
     TradeOrderConvert INSTANCE = Mappers.getMapper(TradeOrderConvert.class);
 
     @Mappings({
-            @Mapping(source = "order.couponId", target = "couponId"),
+            @Mapping(source = "createReqVO.couponId", target = "couponId"),
             @Mapping(target = "remark", ignore = true),
-            @Mapping(source = "createVO.remark", target = "userRemark"),
-            @Mapping(source = "createVO.addressId", target = "receiverAreaId")
+            @Mapping(source = "createReqVO.remark", target = "userRemark"),
+            @Mapping(source = "address.name", target = "receiverName"),
+            @Mapping(source = "address.mobile", target = "receiverMobile"),
+            @Mapping(source = "address.areaId", target = "receiverAreaId"),
+            @Mapping(source = "address.postCode", target = "receiverPostCode"),
+            @Mapping(source = "address.detailAddress", target = "receiverDetailAddress"),
+    })
+    TradeOrderDO convert(Long userId, String clientIp, AppTradeOrderCreateReqVO createReqVO,
+                         PriceCalculateRespDTO.Order order, AddressRespDTO address);
+
+    @Mappings({
+            @Mapping(target = "id", ignore = true),
+            @Mapping(source = "sku.spuId", target = "spuId"),
     })
-    TradeOrderDO convert(AppTradeOrderCreateReqVO createVO, PriceCalculateRespDTO.Order order);
+    TradeOrderItemDO convert(PriceCalculateRespDTO.OrderItem orderItem, ProductSkuRespDTO sku);
+
+    default List<TradeOrderItemDO> convertList(TradeOrderDO tradeOrderDO,
+                                               List<PriceCalculateRespDTO.OrderItem> orderItems, List<ProductSkuRespDTO> skus) {
+        Map<Long, ProductSkuRespDTO> skuMap = convertMap(skus, ProductSkuRespDTO::getId);
+        return CollectionUtils.convertList(orderItems, orderItem -> {
+            TradeOrderItemDO tradeOrderItemDO = convert(orderItem, skuMap.get(orderItem.getSkuId()));
+            tradeOrderItemDO.setOrderId(tradeOrderDO.getId());
+            tradeOrderItemDO.setUserId(tradeOrderDO.getUserId());
+            tradeOrderItemDO.setRefundStatus(TradeOrderItemRefundStatusEnum.NONE.getStatus());
+//            tradeOrderItemDO.setCommented(false);
+            return tradeOrderItemDO;
+        });
+    }
 
 }

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

@@ -1,17 +0,0 @@
-package cn.iocoder.yudao.module.trade.convert.order;
-
-import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateRespDTO;
-import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
-import org.mapstruct.Mapper;
-import org.mapstruct.factory.Mappers;
-
-import java.util.List;
-
-@Mapper
-public interface TradeOrderItemConvert {
-
-    TradeOrderItemConvert INSTANCE = Mappers.getMapper(TradeOrderItemConvert.class);
-
-    List<TradeOrderItemDO> convertList(List<PriceCalculateRespDTO.OrderItem> items);
-
-}

+ 20 - 20
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderItemDO.java

@@ -70,13 +70,13 @@ public class TradeOrderItemDO extends BaseDO {
      * 购买数量
      */
     private Integer count;
-    /**
-     * 是否评论 TODO
-     *
-     * false - 未评论
-     * true - 已评论
-     */
-    private Boolean commented;
+//    /**
+//     * 是否评论 TODO
+//     *
+//     * false - 未评论
+//     * true - 已评论
+//     */
+//    private Boolean commented;
 
     // ========== 价格 + 支付基本信息 ==========
 
@@ -138,19 +138,19 @@ public class TradeOrderItemDO extends BaseDO {
      * 枚举 {@link TradeOrderItemRefundStatusEnum}
      */
     private Integer refundStatus; // TODO 芋艿:可以考虑去查
-    // 如上字段,举个例子:
-    // 假设购买三个,即 stock = 3 。
-    // originPrice = 15
-    // 使用限时折扣(单品优惠)8 折,buyPrice = 12
-    // 开始算总的价格
-    // buyTotal = buyPrice * stock = 12 * 3 = 36
-    // discountTotal ,假设有满减送(分组优惠)满 20 减 10 ,并且使用优惠劵满 1.01 减 1 ,则 discountTotal = 10 + 1 = 11
-    // presentTotal = buyTotal - discountTotal = 24 - 11 = 13
-    // 最终 presentPrice = presentTotal / stock = 13 / 3 = 4.33
-    /**
-     * 退款总金额,单位:分 TODO
-     */
-    private Integer refundTotal;
+//    // 如上字段,举个例子:
+//    // 假设购买三个,即 stock = 3 。
+//    // originPrice = 15
+//    // 使用限时折扣(单品优惠)8 折,buyPrice = 12
+//    // 开始算总的价格
+//    // buyTotal = buyPrice * stock = 12 * 3 = 36
+//    // discountTotal ,假设有满减送(分组优惠)满 20 减 10 ,并且使用优惠劵满 1.01 减 1 ,则 discountTotal = 10 + 1 = 11
+//    // presentTotal = buyTotal - discountTotal = 24 - 11 = 13
+//    // 最终 presentPrice = presentTotal / stock = 13 / 3 = 4.33
+//    /**
+//     * 退款总金额,单位:分 TODO
+//     */
+//    private Integer refundTotal;
 
     /**
      * 商品属性

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

@@ -3,29 +3,27 @@ package cn.iocoder.yudao.module.trade.service.order;
 import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.text.StrBuilder;
 import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.enums.TerminalEnum;
-import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.common.util.string.StrUtils;
-import cn.iocoder.yudao.module.promotion.api.price.PriceApi;
-import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateReqDTO;
-import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateRespDTO;
+import cn.iocoder.yudao.module.member.api.address.AddressApi;
+import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
 import cn.iocoder.yudao.module.pay.api.order.PayOrderApi;
 import cn.iocoder.yudao.module.pay.api.order.PayOrderInfoCreateReqDTO;
 import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
-import cn.iocoder.yudao.module.product.api.sku.dto.SkuDecrementStockBatchReqDTO;
 import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
-import cn.iocoder.yudao.module.product.api.spu.dto.SpuInfoRespDTO;
+import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
 import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
+import cn.iocoder.yudao.module.promotion.api.price.PriceApi;
+import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateReqDTO;
+import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateRespDTO;
 import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO;
 import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO.Item;
 import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
-import cn.iocoder.yudao.module.trade.convert.order.TradeOrderItemConvert;
-import cn.iocoder.yudao.module.trade.convert.pay.PayOrderConvert;
 import cn.iocoder.yudao.module.trade.convert.price.PriceConvert;
-import cn.iocoder.yudao.module.trade.convert.sku.ProductSkuConvert;
 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.TradeOrderMapper;
@@ -43,6 +41,12 @@ import javax.annotation.Resource;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
+import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.ORDER_CREATE_SKU_NOT_SALE;
+import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.ORDER_CREATE_SPU_NOT_FOUND;
 
 /**
  * 交易订单 Service 实现类
@@ -70,66 +74,51 @@ public class TradeOrderServiceImpl implements TradeOrderService {
     private ProductSpuApi productSpuApi;
     @Resource
     private PayOrderApi payOrderApi;
+    @Resource
+    private AddressApi addressApi;
 
     @Resource
     private TradeOrderProperties tradeOrderProperties;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public Long createTradeOrder(Long loginUserId, String clientIp, AppTradeOrderCreateReqVO createReqVO) {
-        List<Item> items = createReqVO.getItems();
-        // 商品SKU检查 sku可售状态,库存
-        List<ProductSkuRespDTO> skuInfos = productSkuApi.getSkuList(CollectionUtils.convertSet(items, Item::getSkuId));
-        Map<Long, ProductSkuRespDTO> skuInfoMap = CollectionUtils.convertMap(skuInfos, ProductSkuRespDTO::getId);
-        checkSaleableAndStockFromSpu(skuInfoMap, items);
-
-        // 商品SPU检查 sku可售状态,库存
-        List<SpuInfoRespDTO> spuInfos = productSpuApi.getSpuList(CollectionUtils.convertSet(skuInfos, ProductSkuRespDTO::getSpuId));
-        checkSaleableFromSpu(spuInfos);
+    public Long createTradeOrder(Long userId, String clientIp, AppTradeOrderCreateReqVO createReqVO) {
+        // 商品 SKU 检查:可售状态、库存
+        List<ProductSkuRespDTO> skus = validateSkuSaleable(createReqVO.getItems());
+        // 商品 SPU 检查:可售状态
+        List<ProductSpuRespDTO> spus = validateSpuSaleable(convertSet(skus, ProductSkuRespDTO::getSpuId));
+        // 用户收件地址的校验
+        AddressRespDTO address = validateAddress(userId, createReqVO.getAddressId());
 
         // 价格计算
-        PriceCalculateReqDTO priceCalculateReqDTO = PriceConvert.INSTANCE.convert(createReqVO, loginUserId);
+        PriceCalculateReqDTO priceCalculateReqDTO = PriceConvert.INSTANCE.convert(createReqVO, userId);
         PriceCalculateRespDTO priceResp = priceApi.calculatePrice(priceCalculateReqDTO);
-        // TODO @LeeYan9: 是可以思考下, 订单的营销优惠记录, 应该记录在哪里, 微信讨论起来!
 
-        // 订单信息记录
-        TradeOrderDO tradeOrderDO = TradeOrderConvert.INSTANCE.convert(createReqVO, priceResp.getOrder());
-        fillTradeOrderInfoFromReqInfo(tradeOrderDO,createReqVO,loginUserId, clientIp); // TODO @LeeYan9: tradeOrderDO, createReqVO, loginUserId, clientIp
-        tradeOrderMapper.insert(tradeOrderDO);
+        // 插入 TradeOrderDO 订单
+        TradeOrderDO tradeOrderDO = createTradeOrder(userId, clientIp, createReqVO, priceResp.getOrder(), address);
+        // 插入 TradeOrderItemDO 订单项
+        createTradeOrderItems(tradeOrderDO, priceResp.getOrder().getItems(), skus);
+
+        // 下单时扣减商品库存 TODO
+//        List<SkuDecrementStockBatchReqDTO.Item> skuDecrementStockItems = ProductSkuConvert.INSTANCE.convert(tradeOrderItems);
+//        productSkuApi.decrementStockBatch(SkuDecrementStockBatchReqDTO.of(skuDecrementStockItems));
+
+        // 删除购物车商品 TODO 芋艿:待实现
 
-        // 订单项信息记录
-        List<TradeOrderItemDO> tradeOrderItems = TradeOrderItemConvert.INSTANCE.convertList(priceResp.getOrder().getItems());
-        // 填充订单项-SKU信息
-        fillItemsInfoFromSkuAndOrder(tradeOrderDO, tradeOrderItems, skuInfoMap);
-        tradeOrderItemMapper.insertBatch(tradeOrderItems);
+        // 扣减积分,抵扣金额 TODO 芋艿:待实现
 
-        // TODO @LeeYan9: 先扣减库存哈; 可能会扣减失败; 毕竟 get 和 update 之间, 会有并发的可能性
-        // 库存扣减
-        List<SkuDecrementStockBatchReqDTO.Item> skuDecrementStockItems = ProductSkuConvert.INSTANCE.convert(tradeOrderItems);
-        productSkuApi.decrementStockBatch(SkuDecrementStockBatchReqDTO.of(skuDecrementStockItems));
+        // 有使用优惠券时更新
+
+        // 增加订单日志 TODO 芋艿:待实现
 
         // 构建预支付请求参数
         // TODO @LeeYan9: 需要更新到订单上
-        PayOrderInfoCreateReqDTO payOrderCreateReqDTO = PayOrderConvert.INSTANCE.convert(tradeOrderDO);
-        fillPayOrderInfoFromItems(payOrderCreateReqDTO, tradeOrderItems);
+//        PayOrderInfoCreateReqDTO payOrderCreateReqDTO = PayOrderConvert.INSTANCE.convert(tradeOrderDO);
+//        fillPayOrderInfoFromItems(payOrderCreateReqDTO, tradeOrderItems);
         // 生成预支付
-        return payOrderApi.createPayOrder(payOrderCreateReqDTO);
-    }
-
-    // TODO @LeeYan9: 填充就好, 不用 from 哈;
-    private void fillTradeOrderInfoFromReqInfo(TradeOrderDO tradeOrderDO, AppTradeOrderCreateReqVO createReqVO,
-                                               Long loginUserId, String clientIp) {
-        tradeOrderDO.setUserId(loginUserId);
-        tradeOrderDO.setUserIp(clientIp);
-        tradeOrderDO.setNo(IdUtil.getSnowflakeNextId() + ""); // TODO @LeeYan9: 思考下, 怎么生成好点哈; 这个是会展示给用户的;
-        tradeOrderDO.setStatus(TradeOrderStatusEnum.WAITING_PAYMENT.getStatus());
-        tradeOrderDO.setType(TradeOrderTypeEnum.NORMAL.getType());
-        tradeOrderDO.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus());
-        tradeOrderDO.setProductCount(CollectionUtils.getSumValue(createReqVO.getItems(), Item::getCount,Integer::sum));
-        // todo 地址&用户信息解析
 
-        // todo 数据来源?
-        tradeOrderDO.setTerminal(TerminalEnum.H5.getTerminal());
+        // TODO @LeeYan9: 是可以思考下, 订单的营销优惠记录, 应该记录在哪里, 微信讨论起来!
+        return tradeOrderDO.getId();
     }
 
     private void fillPayOrderInfoFromItems(PayOrderInfoCreateReqDTO payOrderInfoCreateReqDTO,
@@ -157,7 +146,7 @@ public class TradeOrderServiceImpl implements TradeOrderService {
         payOrderInfoCreateReqDTO.setBody(StrUtils.maxLength(body.subString(1), 128));
     }
 
-    private void fillItemsInfoFromSkuAndOrder(TradeOrderDO tradeOrderDO, List<TradeOrderItemDO> tradeOrderItems,
+    private void xfillItemsInfoFromSkuAndOrder(TradeOrderDO tradeOrderDO, List<TradeOrderItemDO> tradeOrderItems,
                                               Map<Long, ProductSkuRespDTO> spuInfos) {
         for (TradeOrderItemDO tradeOrderItem : tradeOrderItems) {
             // 填充订单信息
@@ -176,32 +165,87 @@ public class TradeOrderServiceImpl implements TradeOrderService {
         }
     }
 
-    private void checkSaleableFromSpu(List<SpuInfoRespDTO> spuInfos) {
-        SpuInfoRespDTO spu = CollectionUtils.findFirst(spuInfos,
-                spuInfoDTO -> !Objects.equals(ProductSpuStatusEnum.ENABLE.getStatus(), spuInfoDTO.getStatus()));
-        if (Objects.isNull(spu)) {
-            throw ServiceExceptionUtil.exception(ErrorCodeConstants.ORDER_SPU_NOT_SALE);
+    /**
+     * 校验商品 SKU 是否可出售
+     *
+     * @param items 商品 SKU
+     * @return 商品 SKU 数组
+     */
+    private List<ProductSkuRespDTO> validateSkuSaleable(List<Item> items) {
+        List<ProductSkuRespDTO> skus = productSkuApi.getSkuList(convertSet(items, Item::getSkuId));
+        // SKU 不存在
+        if (items.size() != skus.size()) {
+            throw exception(ErrorCodeConstants.ORDER_CREATE_SKU_NOT_FOUND);
         }
+        // 校验是否禁用 or 库存不足
+        Map<Long, ProductSkuRespDTO> skuMap = convertMap(skus, ProductSkuRespDTO::getId);
+        items.forEach(item -> {
+            ProductSkuRespDTO sku = skuMap.get(item.getSkuId());
+            // SKU 禁用
+            if (ObjectUtil.notEqual(CommonStatusEnum.ENABLE.getStatus(), sku.getStatus())) {
+                throw exception(ORDER_CREATE_SKU_NOT_SALE);
+            }
+            // SKU 库存不足
+            if (item.getCount() > sku.getStock()) {
+                throw exception(ErrorCodeConstants.ORDER_CREATE_SKU_STOCK_NOT_ENOUGH);
+            }
+        });
+        return skus;
     }
 
-    // TODO @LeeYan9: checkSpuXXX 会不会好点哈? ps: 这个貌似是 sku 哈
-    private void checkSaleableAndStockFromSpu(Map<Long, ProductSkuRespDTO> skuInfoMap,
-                                              List<Item> items) {
-        // sku 不存在
-        if (items.size() != skuInfoMap.size()) {
-            throw ServiceExceptionUtil.exception(ErrorCodeConstants.ORDER_SKU_NOT_FOUND);
+    /**
+     * 校验商品 SPU 是否可出售
+     *
+     * @param spuIds 商品 SPU 编号数组
+     * @return 商品 SPU 数组
+     */
+    private List<ProductSpuRespDTO> validateSpuSaleable(Set<Long> spuIds) {
+        List<ProductSpuRespDTO> spus = productSpuApi.getSpuList(spuIds);
+        // SPU 不存在
+        if (spus.size() != spuIds.size()) {
+            throw exception(ORDER_CREATE_SPU_NOT_FOUND);
         }
-        for (Item item : items) {
-            ProductSkuRespDTO skuInfoDTO = skuInfoMap.get(item.getSkuId());
-            // sku禁用
-            if (!Objects.equals(CommonStatusEnum.ENABLE.getStatus(), skuInfoDTO.getStatus())) {
-                throw ServiceExceptionUtil.exception(ErrorCodeConstants.ORDER_SKU_NOT_SALE);
-            }
-            // sku库存不足
-            if (item.getCount() > skuInfoDTO.getStock()) {
-                throw ServiceExceptionUtil.exception(ErrorCodeConstants.ORDER_SKU_STOCK_NOT_ENOUGH);
-            }
+        // 校验是否存在禁用的 SPU
+        ProductSpuRespDTO spu = CollectionUtils.findFirst(spus,
+                spuDTO -> ObjectUtil.notEqual(ProductSpuStatusEnum.ENABLE.getStatus(), spuDTO.getStatus()));
+        if (Objects.isNull(spu)) {
+            throw exception(ErrorCodeConstants.ORDER_CREATE_SPU_NOT_SALE);
         }
+        return spus;
+    }
 
+    /**
+     * 校验收件地址是否存在
+     *
+     * @param userId 用户编号
+     * @param addressId 收件地址编号
+     * @return 收件地址
+     */
+    private AddressRespDTO validateAddress(Long userId, Long addressId) {
+        AddressRespDTO address = addressApi.getAddress(userId, addressId);
+        if (Objects.isNull(address)) {
+            throw exception(ErrorCodeConstants.ORDER_CREATE_ADDRESS_NOT_FOUND);
+        }
+        return address;
     }
+
+    private TradeOrderDO createTradeOrder(Long userId, String clientIp, AppTradeOrderCreateReqVO createReqVO,
+                                          PriceCalculateRespDTO.Order order, AddressRespDTO address) {
+        TradeOrderDO tradeOrderDO = TradeOrderConvert.INSTANCE.convert(userId, clientIp, createReqVO, order, address);
+        tradeOrderDO.setNo(IdUtil.getSnowflakeNextId() + ""); // TODO @LeeYan9: 思考下, 怎么生成好点哈; 这个是会展示给用户的;
+        tradeOrderDO.setStatus(TradeOrderStatusEnum.WAITING_PAYMENT.getStatus());
+        tradeOrderDO.setType(TradeOrderTypeEnum.NORMAL.getType());
+        tradeOrderDO.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus());
+        tradeOrderDO.setProductCount(getSumValue(order.getItems(),  PriceCalculateRespDTO.OrderItem::getCount, Integer::sum));
+        tradeOrderDO.setTerminal(TerminalEnum.H5.getTerminal()); // todo 数据来源?
+        tradeOrderMapper.insert(tradeOrderDO);
+        return tradeOrderDO;
+    }
+
+    private void createTradeOrderItems(TradeOrderDO tradeOrderDO,
+                                       List<PriceCalculateRespDTO.OrderItem> orderItems, List<ProductSkuRespDTO> skus) {
+        List<TradeOrderItemDO> tradeOrderItemDOs = TradeOrderConvert.INSTANCE.convertList(tradeOrderDO, orderItems, skus);
+        tradeOrderItemMapper.insertBatch(tradeOrderItemDOs);
+    }
+
 }

+ 21 - 0
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/address/AddressApi.java

@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.member.api.address;
+
+import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
+
+/**
+ * 用户收件地址 API 接口
+ *
+ * @author 芋道源码
+ */
+public interface AddressApi {
+
+    /**
+     * 获得用户收件地址
+     *
+     * @param id 收件地址编号
+     * @param userId 用户编号
+     * @return 用户收件地址
+     */
+    AddressRespDTO getAddress(Long id, Long userId);
+
+}

+ 48 - 0
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/address/dto/AddressRespDTO.java

@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.member.api.address.dto;
+
+import lombok.Data;
+
+/**
+ * 用户收件地址 Response DTO
+ *
+ * @author 芋道源码
+ */
+@Data
+public class AddressRespDTO {
+
+    /**
+     * 编号
+     */
+    private Long id;
+    /**
+     * 用户编号
+     */
+    private Long userId;
+    /**
+     * 收件人名称
+     */
+    private String name;
+    /**
+     * 手机号
+     */
+    private String mobile;
+    /**
+     * 地区编号
+     */
+    private Long areaId;
+    /**
+     * 邮编
+     */
+    private String postCode;
+    /**
+     * 收件详细地址
+     */
+    private String detailAddress;
+    /**
+     * 是否默认
+     *
+     * true - 默认收件地址
+     */
+    private Boolean defaulted;
+
+}

+ 28 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/address/AddressApiImpl.java

@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.member.api.address;
+
+import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
+import cn.iocoder.yudao.module.member.convert.address.AddressConvert;
+import cn.iocoder.yudao.module.member.service.address.AddressService;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+
+/**
+ * 用户收件地址 API 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+public class AddressApiImpl implements AddressApi {
+
+    @Resource
+    private AddressService addressService;
+
+    @Override
+    public AddressRespDTO getAddress(Long id, Long userId) {
+        return AddressConvert.INSTANCE.convert02(addressService.getAddress(userId, id));
+    }
+
+}

+ 9 - 5
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/address/AddressConvert.java

@@ -1,13 +1,15 @@
 package cn.iocoder.yudao.module.member.convert.address;
 
-import java.util.*;
-
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-
+import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
+import cn.iocoder.yudao.module.member.controller.app.address.vo.AppAddressCreateReqVO;
+import cn.iocoder.yudao.module.member.controller.app.address.vo.AppAddressRespVO;
+import cn.iocoder.yudao.module.member.controller.app.address.vo.AppAddressUpdateReqVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.address.AddressDO;
 import org.mapstruct.Mapper;
 import org.mapstruct.factory.Mappers;
-import cn.iocoder.yudao.module.member.controller.app.address.vo.*;
-import cn.iocoder.yudao.module.member.dal.dataobject.address.AddressDO;
+
+import java.util.List;
 
 /**
  * 用户收件地址 Convert
@@ -29,4 +31,6 @@ public interface AddressConvert {
 
     PageResult<AppAddressRespVO> convertPage(PageResult<AddressDO> page);
 
+    AddressRespDTO convert02(AddressDO bean);
+
 }