trade: 增加创建售后订单的接口
parent
b8d1d31df0
commit
10f2dbc8cd
@ -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;
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,4 +0,0 @@
|
|||||||
package cn.iocoder.yudao.module.trade.controller.app.refund;
|
|
||||||
|
|
||||||
public class TradeRefundController {
|
|
||||||
}
|
|
||||||
@ -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);
|
||||||
|
|
||||||
|
}
|
||||||
@ -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,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.framework.mybatis.core.mapper.BaseMapperX;
|
||||||
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
|
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
|
||||||
@ -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);
|
||||||
|
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue