From 10f2dbc8cd7d6f5150b50fff32c3c3ed51aec3c3 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Wed, 16 Nov 2022 00:51:29 +0800 Subject: [PATCH] =?UTF-8?q?trade:=20=E5=A2=9E=E5=8A=A0=E5=88=9B=E5=BB=BA?= =?UTF-8?q?=E5=94=AE=E5=90=8E=E8=AE=A2=E5=8D=95=E7=9A=84=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../trade/enums/ErrorCodeConstants.java | 14 ++- .../aftersale/TradeAfterSaleTypeEnum.java | 12 ++- .../TradeOrderItemAfterSaleStatusEnum.java | 42 ++++++++ .../order/TradeOrderItemRefundStatusEnum.java | 29 ----- .../enums/order/TradeOrderStatusEnum.java | 46 +++++++- .../app/aftersale/AppAfterSaleController.java | 36 +++++++ .../aftersale/vo/AppAfterSaleCreateReqVO.java | 41 +++++++ .../app/order/AppTradeOrderController.java | 8 +- .../app/refund/TradeRefundController.java | 4 - .../aftersale/TradeAfterSaleConvert.java | 25 +++++ .../convert/order/TradeOrderConvert.java | 4 +- .../aftersale/TradeAfterSaleDO.java | 6 +- .../dataobject/order/TradeOrderItemDO.java | 25 ++--- .../mysql/aftersale/TradeAfterSaleMapper.java | 10 ++ .../TradeOrderItemMapper.java | 2 +- .../aftersale/TradeAfterSaleService.java | 23 ++++ .../aftersale/TradeAfterSaleServiceImpl.java | 102 ++++++++++++++++++ .../service/order/TradeOrderService.java | 24 ++++- .../service/order/TradeOrderServiceImpl.java | 26 ++++- .../service/order/TradeOrderServiceTest.java | 12 +-- 20 files changed, 414 insertions(+), 77 deletions(-) create mode 100644 yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/order/TradeOrderItemAfterSaleStatusEnum.java delete mode 100644 yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/order/TradeOrderItemRefundStatusEnum.java create mode 100644 yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/aftersale/AppAfterSaleController.java create mode 100644 yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/aftersale/vo/AppAfterSaleCreateReqVO.java delete mode 100644 yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/refund/TradeRefundController.java create mode 100644 yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/aftersale/TradeAfterSaleConvert.java create mode 100644 yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/aftersale/TradeAfterSaleMapper.java rename yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/{orderitem => order}/TradeOrderItemMapper.java (82%) create mode 100644 yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/TradeAfterSaleService.java create mode 100644 yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/TradeAfterSaleServiceImpl.java diff --git a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java index 8725ac58f..7d31d3cb7 100644 --- a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java +++ b/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, "购物车项不存在"); + } diff --git a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/aftersale/TradeAfterSaleTypeEnum.java b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/aftersale/TradeAfterSaleTypeEnum.java index daa9b0a0c..66e1902f2 100644 --- a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/aftersale/TradeAfterSaleTypeEnum.java +++ b/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; + } + } diff --git a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/order/TradeOrderItemAfterSaleStatusEnum.java b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/order/TradeOrderItemAfterSaleStatusEnum.java new file mode 100644 index 000000000..70d7bf4bc --- /dev/null +++ b/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()); + } + +} diff --git a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/order/TradeOrderItemRefundStatusEnum.java b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/order/TradeOrderItemRefundStatusEnum.java deleted file mode 100644 index 4d81ea9eb..000000000 --- a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/order/TradeOrderItemRefundStatusEnum.java +++ /dev/null @@ -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; - -} diff --git a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/order/TradeOrderStatusEnum.java b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/order/TradeOrderStatusEnum.java index fe0a85f3d..33ddbd662 100644 --- a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/order/TradeOrderStatusEnum.java +++ b/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()); + } + } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/aftersale/AppAfterSaleController.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/aftersale/AppAfterSaleController.java new file mode 100644 index 000000000..789b6e52a --- /dev/null +++ b/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 createAfterSale(@RequestBody AppAfterSaleCreateReqVO createReqVO) { + return success(afterSaleService.createAfterSale(getLoginUserId(), createReqVO)); + } + +} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/aftersale/vo/AppAfterSaleCreateReqVO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/aftersale/vo/AppAfterSaleCreateReqVO.java new file mode 100644 index 000000000..eceba596b --- /dev/null +++ b/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 applyPicUrls; + + @ApiModelProperty(name = "售后类型", required = true, example = "1", notes = "对应 TradeAfterSaleTypeEnum 枚举") + @NotNull(message = "售后类型不能为空") + @InEnum(value = TradeAfterSaleTypeEnum.class, message = "售后类型必须是 {value}") + private Integer type; + +} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java index 4233867e1..64bd1f5f0 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java +++ b/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); } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/refund/TradeRefundController.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/refund/TradeRefundController.java deleted file mode 100644 index aa419190e..000000000 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/refund/TradeRefundController.java +++ /dev/null @@ -1,4 +0,0 @@ -package cn.iocoder.yudao.module.trade.controller.app.refund; - -public class TradeRefundController { -} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/aftersale/TradeAfterSaleConvert.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/aftersale/TradeAfterSaleConvert.java new file mode 100644 index 000000000..8354c1fec --- /dev/null +++ b/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); + +} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java index 54abc9698..4e2b747ff 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java +++ b/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; }); diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/aftersale/TradeAfterSaleDO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/aftersale/TradeAfterSaleDO.java index 2e7187b95..327dc8aeb 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/aftersale/TradeAfterSaleDO.java +++ b/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; /** * 补充描述 */ diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderItemDO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderItemDO.java index 5dfcf4c6a..cbee4ada1 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderItemDO.java +++ b/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 TradeOrderItemRefundStatusEnum} + * 枚举 {@link TradeOrderItemAfterSaleStatusEnum} + * + * @see cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.TradeAfterSaleDO */ - 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; + private Integer afterSaleStatus; /** * 商品属性 diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/aftersale/TradeAfterSaleMapper.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/aftersale/TradeAfterSaleMapper.java new file mode 100644 index 000000000..af729e80c --- /dev/null +++ b/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 { +} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/orderitem/TradeOrderItemMapper.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderItemMapper.java similarity index 82% rename from yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/orderitem/TradeOrderItemMapper.java rename to yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderItemMapper.java index 3a85e0cbb..6565c7310 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/orderitem/TradeOrderItemMapper.java +++ b/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; diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/TradeAfterSaleService.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/TradeAfterSaleService.java new file mode 100644 index 000000000..1e3c55be1 --- /dev/null +++ b/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 { + + /** + * 创建交易售后 + *

+ * 一般是用户发起售后请求 + * + * @param userId 用户编号 + * @param createReqVO 交易售后 Request 信息 + * @return 交易售后编号 + */ + Long createAfterSale(Long userId, AppAfterSaleCreateReqVO createReqVO); + +} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/TradeAfterSaleServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/TradeAfterSaleServiceImpl.java new file mode 100644 index 000000000..3bfd8f7f8 --- /dev/null +++ b/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; + } + +} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderService.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderService.java index f9e635fac..f6f012da4 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderService.java +++ b/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); } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderServiceImpl.java index 95df76afe..1a737a9ef 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderServiceImpl.java +++ b/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 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)); diff --git a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderServiceTest.java b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderServiceTest.java index 669680c3d..13e33d85f 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderServiceTest.java +++ b/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 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() {