Browse Source

trade: 增加创建售后订单的接口

YunaiV 2 years ago
parent
commit
10f2dbc8cd
20 changed files with 415 additions and 78 deletions
  1. 13 1
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java
  2. 11 1
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/aftersale/TradeAfterSaleTypeEnum.java
  3. 42 0
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/order/TradeOrderItemAfterSaleStatusEnum.java
  4. 0 29
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/order/TradeOrderItemRefundStatusEnum.java
  5. 41 5
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/order/TradeOrderStatusEnum.java
  6. 36 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/aftersale/AppAfterSaleController.java
  7. 41 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/aftersale/vo/AppAfterSaleCreateReqVO.java
  8. 4 4
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java
  9. 0 4
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/refund/TradeRefundController.java
  10. 25 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/aftersale/TradeAfterSaleConvert.java
  11. 2 2
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java
  12. 4 2
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/aftersale/TradeAfterSaleDO.java
  13. 9 18
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderItemDO.java
  14. 10 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/aftersale/TradeAfterSaleMapper.java
  15. 1 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderItemMapper.java
  16. 23 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/TradeAfterSaleService.java
  17. 102 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/TradeAfterSaleServiceImpl.java
  18. 22 2
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderService.java
  19. 23 3
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderServiceImpl.java
  20. 6 6
      yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderServiceTest.java

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

@@ -19,7 +19,19 @@ public interface ErrorCodeConstants {
     ErrorCode ORDER_CREATE_SPU_NOT_FOUND = new ErrorCode(1011000005, "商品 SPU 不可售卖");
     ErrorCode ORDER_CREATE_ADDRESS_NOT_FOUND = new ErrorCode(1011000006, "收货地址不存在");
 
+    ErrorCode ORDER_ITEM_NOT_FOUND = new ErrorCode(1011000010, "交易订单项不存在");
+    ErrorCode ORDER_NOT_FOUND = new ErrorCode(1011000010, "交易订单不存在");
+
+    // ==========  After Sale 模块 1-011-000-000 ==========
+    ErrorCode AFTER_SALE_NOT_FOUND = new ErrorCode(1011000100, "售后单不存在");
+    ErrorCode AFTER_SALE_CREATE_FAIL_REFUND_PRICE_ERROR = new ErrorCode(1011000101, "申请退款金额错误");
+    ErrorCode AFTER_SALE_CREATE_FAIL_ORDER_STATUS_CANCELED = new ErrorCode(1011000102, "订单已关闭,无法申请售后");
+    ErrorCode AFTER_SALE_CREATE_FAIL_ORDER_STATUS_NO_PAID = new ErrorCode(1011000103, "订单未支付,无法申请售后");
+    ErrorCode AFTER_SALE_CREATE_FAIL_ORDER_STATUS_NO_DELIVERED = new ErrorCode(1011000104, "订单未发货,无法申请【退货退款】售后");
+    ErrorCode AFTER_SALE_CREATE_FAIL_ORDER_ITEM_APPLIED = new ErrorCode(1011000105, "订单项已申请售后,无法重复申请");
+
     // ==========  Cart 模块 1-011-001-000 ==========
-    ErrorCode CARD_ITEM_NOT_FOUND = new ErrorCode(1001001000, "购物车项不存在");
+    ErrorCode CARD_ITEM_NOT_FOUND = new ErrorCode(1011002000, "购物车项不存在");
+
 
 }

+ 11 - 1
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/aftersale/TradeAfterSaleTypeEnum.java

@@ -1,8 +1,11 @@
 package cn.iocoder.yudao.module.trade.enums.aftersale;
 
+import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
 
+import java.util.Arrays;
+
 /**
  * 交易售后 - 类型
  *
@@ -10,11 +13,13 @@ import lombok.RequiredArgsConstructor;
  */
 @RequiredArgsConstructor
 @Getter
-public enum TradeAfterSaleTypeEnum {
+public enum TradeAfterSaleTypeEnum implements IntArrayValuable {
 
     REFUND(10, "退款"),
     RETURN_AND_REFUND(20, "退货退款");
 
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(TradeAfterSaleTypeEnum::getType).toArray();
+
     /**
      * 状态值
      */
@@ -24,4 +29,9 @@ public enum TradeAfterSaleTypeEnum {
      */
     private final String name;
 
+    @Override
+    public int[] array() {
+        return ARRAYS;
+    }
+
 }

+ 42 - 0
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/order/TradeOrderItemAfterSaleStatusEnum.java

@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.trade.enums.order;
+
+import cn.hutool.core.util.ObjectUtil;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * 交易订单项 - 售后状态
+ *
+ * @author 芋道源码
+ */
+@RequiredArgsConstructor
+@Getter
+public enum TradeOrderItemAfterSaleStatusEnum {
+
+    NONE(0, "未申请"),
+    APPLY(1, "已申请"),
+    SUCCESS(2, "申请成功");
+
+    /**
+     * 状态值
+     */
+    private final Integer status;
+    /**
+     * 状态名
+     */
+    private final String name;
+
+    // TODO 芋艿:EXPIRED 已失效不允许申请售后
+    // TODO 芋艿:PART_AFTER_SALE 部分售后
+
+    /**
+     * 判断指定状态,是否正处于【未申请】状态
+     *
+     * @param status 指定状态
+     * @return 是否
+     */
+    public static boolean isNone(Integer status) {
+        return ObjectUtil.equals(status, NONE.getStatus());
+    }
+
+}

+ 0 - 29
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/order/TradeOrderItemRefundStatusEnum.java

@@ -1,29 +0,0 @@
-package cn.iocoder.yudao.module.trade.enums.order;
-
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-
-/**
- * 交易订单项 - 退款状态
- *
- * @author Sin
- */
-@RequiredArgsConstructor
-@Getter
-public enum TradeOrderItemRefundStatusEnum {
-
-    NONE(0, "未申请退款"),
-    APPLY(1, "申请退款"),
-    WAIT(2, "等待退款"),
-    SUCCESS(3, "退款成功");
-
-    /**
-     * 状态值
-     */
-    private final Integer status;
-    /**
-     * 状态名
-     */
-    private final String name;
-
-}

+ 41 - 5
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/order/TradeOrderStatusEnum.java

@@ -1,5 +1,7 @@
 package cn.iocoder.yudao.module.trade.enums.order;
 
+import cn.hutool.core.util.ObjectUtil;
+import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
 
@@ -12,11 +14,14 @@ import lombok.RequiredArgsConstructor;
 @Getter
 public enum TradeOrderStatusEnum {
 
-    WAITING_PAYMENT(0, "待付款"),
-    WAIT_SHIPMENT(1, "待发货"),
-    ALREADY_SHIPMENT(2, "待收货"),
-    COMPLETED(3, "已完成"),
-    CANCEL(4, "已关闭");
+    UNPAID(0, "未付款"),
+    PAID(10, "已付款"), // 例如说,拼团订单,支付后,需要拼团成功后,才会处于待发货
+    UNDELIVERED(20, "待发货"),
+    DELIVERED(30, "已发货"),
+    COMPLETED(40, "已完成"),
+    CANCELED(50, "已取消");
+
+    // TODO 芋艿: TAKE("待核验"):虚拟订单需要核验商品
 
     /**
      * 状态值
@@ -27,4 +32,35 @@ public enum TradeOrderStatusEnum {
      */
     private final String name;
 
+    /**
+     * 判断指定状态,是否正处于【已取消】状态
+     *
+     * @param status 指定状态
+     * @return 是否
+     */
+    public static boolean isCanceled(Integer status) {
+        return ObjectUtil.equals(status, CANCELED.getStatus());
+    }
+
+    /**
+     * 判断指定状态,是否有过【已付款】状态
+     *
+     * @param status 指定状态
+     * @return 是否
+     */
+    public static boolean havePaid(Integer status) {
+        return ObjectUtils.equalsAny(status, PAID.getStatus(), UNDELIVERED.getStatus(),
+                DELIVERED.getStatus(), COMPLETED.getStatus());
+    }
+
+    /**
+     * 判断指定状态,是否有过【已发货】状态
+     *
+     * @param status 指定状态
+     * @return 是否
+     */
+    public static boolean haveDelivered(Integer status) {
+        return ObjectUtils.equalsAny(status, DELIVERED.getStatus(), COMPLETED.getStatus());
+    }
+
 }

+ 36 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/aftersale/AppAfterSaleController.java

@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.trade.controller.app.aftersale;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleCreateReqVO;
+import cn.iocoder.yudao.module.trade.service.aftersale.TradeAfterSaleService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Api(tags = "用户 App - 交易售后")
+@RestController
+@RequestMapping("/trade/after-sale")
+@Validated
+@Slf4j
+public class AppAfterSaleController {
+
+    @Resource
+    private TradeAfterSaleService afterSaleService;
+
+    @PostMapping(value = "/create")
+    @ApiOperation(value = "申请售后")
+    private CommonResult<Long> createAfterSale(@RequestBody AppAfterSaleCreateReqVO createReqVO) {
+        return success(afterSaleService.createAfterSale(getLoginUserId(), createReqVO));
+    }
+
+}

+ 41 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/aftersale/vo/AppAfterSaleCreateReqVO.java

@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.trade.controller.app.aftersale.vo;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.trade.enums.aftersale.TradeAfterSaleTypeEnum;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@ApiModel("用户 App - 交易售后创建 Request VO")
+@Data
+public class AppAfterSaleCreateReqVO {
+
+    @ApiModelProperty(name = "订单项编号", required = true, example = "1024")
+    @NotNull(message = "订单项编号不能为空")
+    private Long orderItemId;
+
+    @ApiModelProperty(name = "退款金额", required = true, example = "100", notes = "单位:分")
+    @NotNull(message = "退款金额不能为空")
+    @Min(value = 1, message = "退款金额必须大于 0")
+    private Integer applyPrice;
+
+    @ApiModelProperty(name = "申请原因", required = true, example = "1", notes = "使用数据字典枚举,对应 trade_refund_apply_reason 类型")
+    @NotNull(message = "申请原因不能为空")
+    private Integer applyReason;
+
+    @ApiModelProperty(name = "补充描述", example = "商品质量不好")
+    private String applyDescription;
+
+    @ApiModelProperty(name = "补充凭证图片", example = "https://www.iocoder.cn/1.png, https://www.iocoder.cn/2.png")
+    private List<String> applyPicUrls;
+
+    @ApiModelProperty(name = "售后类型", required = true, example = "1", notes = "对应 TradeAfterSaleTypeEnum 枚举")
+    @NotNull(message = "售后类型不能为空")
+    @InEnum(value = TradeAfterSaleTypeEnum.class, message = "售后类型必须是 {value}")
+    private Integer type;
+
+}

+ 4 - 4
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java

@@ -13,22 +13,22 @@ import cn.iocoder.yudao.module.trade.service.order.TradeOrderService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiImplicitParam;
 import io.swagger.annotations.ApiOperation;
-import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
+import javax.annotation.Resource;
 import javax.servlet.http.HttpServletRequest;
 
 @Api(tags = "用户 App - 交易订单")
 @RestController
 @RequestMapping("/trade/order")
-@RequiredArgsConstructor // TODO @LeeYan9: 先统一使用 @Resource 注入哈; 项目只有三层, 依赖注入会存在, 所以使用 @Resource; 也因此, 最好全局保持一致
 @Validated
 @Slf4j
 public class AppTradeOrderController {
 
-    private final TradeOrderService tradeOrderService;
+    @Resource
+    private TradeOrderService tradeOrderService;
 
     @GetMapping("/get-create-info")
     @ApiOperation("基于商品,确认创建订单")
@@ -47,7 +47,7 @@ public class AppTradeOrderController {
         Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
         String clientIp = ServletUtil.getClientIP(servletRequest);
         // 创建交易订单,预支付记录
-        Long orderId = tradeOrderService.createTradeOrder(loginUserId, clientIp, createReqVO);
+        Long orderId = tradeOrderService.createOrder(loginUserId, clientIp, createReqVO);
         return CommonResult.success(orderId);
     }
 

+ 0 - 4
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/refund/TradeRefundController.java

@@ -1,4 +0,0 @@
-package cn.iocoder.yudao.module.trade.controller.app.refund;
-
-public class TradeRefundController {
-}

+ 25 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/aftersale/TradeAfterSaleConvert.java

@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.trade.convert.aftersale;
+
+import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleCreateReqVO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.TradeAfterSaleDO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.Mappings;
+import org.mapstruct.factory.Mappers;
+
+@Mapper
+public interface TradeAfterSaleConvert {
+
+    TradeAfterSaleConvert INSTANCE = Mappers.getMapper(TradeAfterSaleConvert.class);
+
+    @Mappings({
+            @Mapping(target = "id", ignore = true),
+            @Mapping(target = "createTime", ignore = true),
+            @Mapping(target = "updateTime", ignore = true),
+            @Mapping(target = "creator", ignore = true),
+            @Mapping(target = "updater", ignore = true),
+    })
+    TradeAfterSaleDO convert(AppAfterSaleCreateReqVO createReqVO, TradeOrderItemDO tradeOrderItem);
+
+}

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

@@ -11,7 +11,7 @@ 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 cn.iocoder.yudao.module.trade.enums.order.TradeOrderItemAfterSaleStatusEnum;
 import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
 import org.mapstruct.Mapper;
 import org.mapstruct.Mapping;
@@ -55,7 +55,7 @@ public interface TradeOrderConvert {
             TradeOrderItemDO tradeOrderItemDO = convert(orderItem, skuMap.get(orderItem.getSkuId()));
             tradeOrderItemDO.setOrderId(tradeOrderDO.getId());
             tradeOrderItemDO.setUserId(tradeOrderDO.getUserId());
-            tradeOrderItemDO.setRefundStatus(TradeOrderItemRefundStatusEnum.NONE.getStatus()).setRefundTotal(0); // 退款信息
+            tradeOrderItemDO.setAfterSaleStatus(TradeOrderItemAfterSaleStatusEnum.NONE.getStatus()); // 退款信息
 //            tradeOrderItemDO.setCommented(false);
             return tradeOrderItemDO;
         });

+ 4 - 2
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/aftersale/TradeAfterSaleDO.java

@@ -27,7 +27,7 @@ import java.util.List;
 public class TradeAfterSaleDO extends BaseDO {
 
     /**
-     * 售后编号,主键自增n
+     * 售后编号,主键自增
      */
     private Long id;
     /**
@@ -56,8 +56,10 @@ public class TradeAfterSaleDO extends BaseDO {
     private Long userId;
     /**
      * 申请原因
+     *
+     * 使用数据字典枚举,对应 trade_refund_apply_reason 类型
      */
-    private String applyReason;
+    private Integer applyReason;
     /**
      * 补充描述
      */

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

@@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.trade.dal.dataobject.order;
 
 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
-import cn.iocoder.yudao.module.trade.enums.order.TradeOrderItemRefundStatusEnum;
+import cn.iocoder.yudao.module.trade.enums.order.TradeOrderItemAfterSaleStatusEnum;
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableName;
 import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler;
@@ -132,26 +132,17 @@ public class TradeOrderItemDO extends BaseDO {
 
     // ========== 营销基本信息 ==========
 
+    // TODO 芋艿:在捉摸一下
+
     // ========== 退款基本信息 ==========
     /**
-     * 退款状态 TODO
+     * 退款状态
+     *
+     * 枚举 {@link TradeOrderItemAfterSaleStatusEnum}
      *
-     * 枚举 {@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;
+     * @see cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.TradeAfterSaleDO
+     */
+    private Integer afterSaleStatus;
 
     /**
      * 商品属性

+ 10 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/aftersale/TradeAfterSaleMapper.java

@@ -0,0 +1,10 @@
+package cn.iocoder.yudao.module.trade.dal.mysql.aftersale;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.TradeAfterSaleDO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface TradeAfterSaleMapper extends BaseMapperX<TradeAfterSaleDO> {
+}

+ 1 - 1
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/orderitem/TradeOrderItemMapper.java → yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderItemMapper.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.trade.dal.mysql.orderitem;
+package cn.iocoder.yudao.module.trade.dal.mysql.order;
 
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;

+ 23 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/TradeAfterSaleService.java

@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.trade.service.aftersale;
+
+import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleCreateReqVO;
+
+/**
+ * 交易售后 Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface TradeAfterSaleService {
+
+    /**
+     * 创建交易售后
+     * <p>
+     * 一般是用户发起售后请求
+     *
+     * @param userId 用户编号
+     * @param createReqVO 交易售后 Request 信息
+     * @return 交易售后编号
+     */
+    Long createAfterSale(Long userId, AppAfterSaleCreateReqVO createReqVO);
+
+}

+ 102 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/TradeAfterSaleServiceImpl.java

@@ -0,0 +1,102 @@
+package cn.iocoder.yudao.module.trade.service.aftersale;
+
+import cn.hutool.core.util.RandomUtil;
+import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleCreateReqVO;
+import cn.iocoder.yudao.module.trade.convert.aftersale.TradeAfterSaleConvert;
+import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.TradeAfterSaleDO;
+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.aftersale.TradeAfterSaleMapper;
+import cn.iocoder.yudao.module.trade.enums.aftersale.TradeAfterSaleStatusEnum;
+import cn.iocoder.yudao.module.trade.enums.aftersale.TradeAfterSaleTypeEnum;
+import cn.iocoder.yudao.module.trade.enums.order.TradeOrderItemAfterSaleStatusEnum;
+import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
+import cn.iocoder.yudao.module.trade.service.order.TradeOrderService;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*;
+
+/**
+ * 交易售后 Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+public class TradeAfterSaleServiceImpl implements TradeAfterSaleService {
+
+    @Resource
+    private TradeOrderService tradeOrderService;
+
+    @Resource
+    private TradeAfterSaleMapper tradeAfterSaleMapper;
+
+    @Override
+    public Long createAfterSale(Long userId, AppAfterSaleCreateReqVO createReqVO) {
+        // 第一步,前置校验
+        TradeOrderItemDO tradeOrderItem = validateOrderItemApplicable(userId, createReqVO);
+
+        // 第二步,存储交易售后
+        TradeAfterSaleDO afterSale = createAfterSale(createReqVO, tradeOrderItem);
+        return afterSale.getId();
+    }
+
+    private TradeOrderItemDO validateOrderItemApplicable(Long userId, AppAfterSaleCreateReqVO createReqVO) {
+        // 校验订单项存在
+        TradeOrderItemDO orderItem = tradeOrderService.getOrderItem(userId, createReqVO.getOrderItemId());
+        if (orderItem == null) {
+            throw exception(ORDER_ITEM_NOT_FOUND);
+        }
+
+        // 已申请售后,不允许再发起售后申请
+        if (!TradeOrderItemAfterSaleStatusEnum.isNone(orderItem.getAfterSaleStatus())) {
+            throw exception(AFTER_SALE_CREATE_FAIL_ORDER_ITEM_APPLIED);
+        }
+        // TODO 芋艿:超过一定时间,不允许售后
+
+        // 申请的退款金额,不能超过商品的价格
+        if (createReqVO.getApplyPrice() > orderItem.getOrderDividePrice()) {
+            throw exception(AFTER_SALE_CREATE_FAIL_REFUND_PRICE_ERROR);
+        }
+
+        // 校验订单存在
+        TradeOrderDO order = tradeOrderService.getOrder(userId, orderItem.getOrderId());
+        if (order == null) {
+            throw exception(ORDER_NOT_FOUND);
+        }
+        // 已取消,无法发起售后
+        if (TradeOrderStatusEnum.isCanceled(order.getStatus())) {
+            throw exception(AFTER_SALE_CREATE_FAIL_ORDER_STATUS_CANCELED);
+        }
+        // 未支付,无法发起售后
+        if (!TradeOrderStatusEnum.havePaid(order.getStatus())) {
+            throw exception(AFTER_SALE_CREATE_FAIL_ORDER_STATUS_NO_PAID);
+        }
+        // 如果是【退货退款】的情况,需要额外校验是否发货
+        if (createReqVO.getType().equals(TradeAfterSaleTypeEnum.RETURN_AND_REFUND.getType())
+            && !TradeOrderStatusEnum.haveDelivered(order.getStatus())) {
+            throw exception(AFTER_SALE_CREATE_FAIL_ORDER_STATUS_NO_DELIVERED);
+        }
+        return orderItem;
+    }
+
+    private TradeAfterSaleDO createAfterSale(AppAfterSaleCreateReqVO createReqVO,
+                                             TradeOrderItemDO tradeOrderItem) {
+        // 创建售后单
+        TradeAfterSaleDO afterSale = TradeAfterSaleConvert.INSTANCE.convert(createReqVO, tradeOrderItem);
+        afterSale.setNo(RandomUtil.randomString(10)); // TODO 芋艿:优化 no 生成逻辑
+        afterSale.setStatus(TradeAfterSaleStatusEnum.APPLY.getStatus());
+        // TODO 退还积分
+        tradeAfterSaleMapper.insert(afterSale);
+
+        // 更新交易订单项的售后状态 TODO
+
+        // 发送售后消息
+        return afterSale;
+    }
+
+}

+ 22 - 2
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderService.java

@@ -1,6 +1,8 @@
 package cn.iocoder.yudao.module.trade.service.order;
 
 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;
 
 /**
  * 交易订单 Service 接口
@@ -13,11 +15,29 @@ public interface TradeOrderService {
     /**
      * 创建交易订单
      *
-     * @param loginUserId 登录用户
+     * @param userId 登录用户
      * @param userIp 用户 IP 地址
      * @param createReqVO 创建交易订单请求模型
      * @return 交易订单的编号
      */
-    Long createTradeOrder(Long loginUserId, String userIp, AppTradeOrderCreateReqVO createReqVO);
+    Long createOrder(Long userId, String userIp, AppTradeOrderCreateReqVO createReqVO);
+
+    /**
+     * 获得指定用户,指定的交易订单项
+     *
+     * @param userId 用户编号
+     * @param itemId 交易订单项编号
+     * @return 交易订单项
+     */
+    TradeOrderItemDO getOrderItem(Long userId, Long itemId);
+
+    /**
+     * 获得指定用户,指定的交易订单
+     *
+     * @param userId 用户编号
+     * @param orderId 交易订单编号
+     * @return 交易订单
+     */
+    TradeOrderDO getOrder(Long userId, Long orderId);
 
 }

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

@@ -25,7 +25,7 @@ import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
 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;
-import cn.iocoder.yudao.module.trade.dal.mysql.orderitem.TradeOrderItemMapper;
+import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderItemMapper;
 import cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants;
 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderRefundStatusEnum;
 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
@@ -77,7 +77,7 @@ public class TradeOrderServiceImpl implements TradeOrderService {
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public Long createTradeOrder(Long userId, String userIp, AppTradeOrderCreateReqVO createReqVO) {
+    public Long createOrder(Long userId, String userIp, AppTradeOrderCreateReqVO createReqVO) {
         // 商品 SKU 检查:可售状态、库存
         List<ProductSkuRespDTO> skus = validateSkuSaleable(createReqVO.getItems());
         // 商品 SPU 检查:可售状态
@@ -99,6 +99,26 @@ public class TradeOrderServiceImpl implements TradeOrderService {
         return tradeOrderDO.getId();
     }
 
+    @Override
+    public TradeOrderItemDO getOrderItem(Long userId, Long itemId) {
+        TradeOrderItemDO orderItem = tradeOrderItemMapper.selectById(itemId);
+        if (orderItem != null
+                && ObjectUtil.notEqual(orderItem.getUserId(), userId)) {
+            return null;
+        }
+        return orderItem;
+    }
+
+    @Override
+    public TradeOrderDO getOrder(Long userId, Long orderId) {
+        TradeOrderDO order = tradeOrderMapper.selectById(orderId);
+        if (order != null
+                && ObjectUtil.notEqual(order.getUserId(), userId)) {
+            return null;
+        }
+        return order;
+    }
+
     /**
      * 校验商品 SKU 是否可出售
      *
@@ -167,7 +187,7 @@ public class TradeOrderServiceImpl implements TradeOrderService {
                                           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.setStatus(TradeOrderStatusEnum.UNPAID.getStatus());
         tradeOrderDO.setType(TradeOrderTypeEnum.NORMAL.getType());
         tradeOrderDO.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus());
         tradeOrderDO.setProductCount(getSumValue(order.getItems(),  PriceCalculateRespDTO.OrderItem::getCount, Integer::sum));

+ 6 - 6
yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderServiceTest.java

@@ -19,8 +19,8 @@ import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreate
 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;
-import cn.iocoder.yudao.module.trade.dal.mysql.orderitem.TradeOrderItemMapper;
-import cn.iocoder.yudao.module.trade.enums.order.TradeOrderItemRefundStatusEnum;
+import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderItemMapper;
+import cn.iocoder.yudao.module.trade.enums.order.TradeOrderItemAfterSaleStatusEnum;
 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderRefundStatusEnum;
 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
@@ -145,7 +145,7 @@ class TradeOrderServiceTest extends BaseDbUnitTest {
         }))).thenReturn(1000L);
 
         // 调用方法
-        Long tradeOrderId = tradeOrderService.createTradeOrder(userId, userIp, reqVO);
+        Long tradeOrderId = tradeOrderService.createOrder(userId, userIp, reqVO);
         // 断言 TradeOrderDO 订单
         List<TradeOrderDO> tradeOrderDOs = tradeOrderMapper.selectList();
         assertEquals(tradeOrderDOs.size(), 1);
@@ -156,7 +156,7 @@ class TradeOrderServiceTest extends BaseDbUnitTest {
         assertEquals(tradeOrderDO.getTerminal(), TerminalEnum.H5.getTerminal());
         assertEquals(tradeOrderDO.getUserId(), userId);
         assertEquals(tradeOrderDO.getUserIp(), userIp);
-        assertEquals(tradeOrderDO.getStatus(), TradeOrderStatusEnum.WAITING_PAYMENT.getStatus());
+        assertEquals(tradeOrderDO.getStatus(), TradeOrderStatusEnum.UNPAID.getStatus());
         assertEquals(tradeOrderDO.getProductCount(), 7);
         assertNull(tradeOrderDO.getFinishTime());
         assertNull(tradeOrderDO.getCancelTime());
@@ -207,7 +207,7 @@ class TradeOrderServiceTest extends BaseDbUnitTest {
         assertEquals(tradeOrderItemDO01.getPayPrice(), 130);
         assertEquals(tradeOrderItemDO01.getOrderPartPrice(), 7);
         assertEquals(tradeOrderItemDO01.getOrderDividePrice(), 35);
-        assertEquals(tradeOrderItemDO01.getRefundStatus(), TradeOrderItemRefundStatusEnum.NONE.getStatus());
+        assertEquals(tradeOrderItemDO01.getAfterSaleStatus(), TradeOrderItemAfterSaleStatusEnum.NONE.getStatus());
         assertEquals(tradeOrderItemDO01.getRefundTotal(), 0);
         // 断言 TradeOrderItemDO 订单(第 2 个)
         TradeOrderItemDO tradeOrderItemDO02 = tradeOrderItemDOs.get(1);
@@ -228,7 +228,7 @@ class TradeOrderServiceTest extends BaseDbUnitTest {
         assertEquals(tradeOrderItemDO02.getPayPrice(), 40);
         assertEquals(tradeOrderItemDO02.getOrderPartPrice(), 15);
         assertEquals(tradeOrderItemDO02.getOrderDividePrice(), 25);
-        assertEquals(tradeOrderItemDO02.getRefundStatus(), TradeOrderItemRefundStatusEnum.NONE.getStatus());
+        assertEquals(tradeOrderItemDO02.getAfterSaleStatus(), TradeOrderItemAfterSaleStatusEnum.NONE.getStatus());
         assertEquals(tradeOrderItemDO02.getRefundTotal(), 0);
         // 校验调用
         verify(productSkuApi).updateSkuStock(argThat(new ArgumentMatcher<ProductSkuUpdateStockReqDTO>() {