Merge remote-tracking branch 'origin/master' into feature/springdoc

# Conflicts:
#	README.md
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/ProductPropertyController.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/ProductPropertyValueController.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/ProductPropertyViewRespVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyAndValueRespVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyBaseVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyCreateReqVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyListReqVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyPageReqVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyRespVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyUpdateReqVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueBaseVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueCreateReqVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValuePageReqVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueRespVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueUpdateReqVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuBaseVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuCreateOrUpdateReqVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/ProductSpuController.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuBaseVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuDetailRespVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuPageReqVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppSpuPageReqVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppSpuPageRespVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppSpuRespVO.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/base/property/AppProductPropertyValueDetailRespVO.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderPageReqVO.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/TradeOrderItemRespVO.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/TradeOrderRespVO.java
#	yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.java
#	yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/notify/vo/PayNotifyOrderReqVO.java
#	yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/notify/vo/PayRefundOrderReqVO.java
#	yudao-server/pom.xml
#	yudao-server/src/main/java/cn/iocoder/yudao/module/shop/controller/app/AppShopOrderController.java
This commit is contained in:
xingyu
2023-01-04 10:48:18 +08:00
471 changed files with 17761 additions and 4522 deletions

View File

@@ -50,6 +50,10 @@
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-ip</artifactId>
</dependency>
<!-- Web 相关 -->
<dependency>

View File

@@ -0,0 +1,4 @@
### 获得交易售后分页 => 成功
GET {{baseUrl}}/trade/after-sale/page?pageNo=1&pageSize=10
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}

View File

@@ -0,0 +1,113 @@
package cn.iocoder.yudao.module.trade.controller.admin.aftersale;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.product.api.property.ProductPropertyValueApi;
import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO;
import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.TradeAfterSaleDisagreeReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.TradeAfterSalePageReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.TradeAfterSaleRefuseReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.TradeAfterSaleRespPageItemVO;
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.service.aftersale.TradeAfterSaleService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Api(tags = "管理后台 - 交易售后")
@RestController
@RequestMapping("/trade/after-sale")
@Validated
@Slf4j
public class TradeAfterSaleController {
@Resource
private TradeAfterSaleService afterSaleService;
@Resource
private MemberUserApi memberUserApi;
@Resource
private ProductPropertyValueApi productPropertyValueApi;
@GetMapping("/page")
@ApiOperation("获得交易售后分页")
@PreAuthorize("@ss.hasPermission('trade:after-sale:query')")
public CommonResult<PageResult<TradeAfterSaleRespPageItemVO>> getAfterSalePage(@Valid TradeAfterSalePageReqVO pageVO) {
// 查询售后
PageResult<TradeAfterSaleDO> pageResult = afterSaleService.getAfterSalePage(pageVO);
if (CollUtil.isEmpty(pageResult.getList())) {
return success(PageResult.empty());
}
// 查询商品属性
List<ProductPropertyValueDetailRespDTO> propertyValueDetails = productPropertyValueApi
.getPropertyValueDetailList(TradeAfterSaleConvert.INSTANCE.convertPropertyValueIds(pageResult.getList()));
// 查询会员
Map<Long, MemberUserRespDTO> memberUsers = memberUserApi.getUserMap(
convertSet(pageResult.getList(), TradeAfterSaleDO::getUserId));
return success(TradeAfterSaleConvert.INSTANCE.convertPage(pageResult, memberUsers, propertyValueDetails));
}
@PutMapping("/agree")
@ApiOperation("同意售后")
@ApiImplicitParam(name = "id", value = "售后编号", required = true, example = "1")
@PreAuthorize("@ss.hasPermission('trade:after-sale:agree')")
public CommonResult<Boolean> agreeAfterSale(@RequestParam("id") Long id) {
afterSaleService.agreeAfterSale(getLoginUserId(), id);
return success(true);
}
@PutMapping("/disagree")
@ApiOperation("拒绝售后")
@PreAuthorize("@ss.hasPermission('trade:after-sale:disagree')")
public CommonResult<Boolean> disagreeAfterSale(@RequestBody TradeAfterSaleDisagreeReqVO confirmReqVO) {
afterSaleService.disagreeAfterSale(getLoginUserId(), confirmReqVO);
return success(true);
}
@PutMapping("/receive")
@ApiOperation("确认收货")
@ApiImplicitParam(name = "id", value = "售后编号", required = true, example = "1")
@PreAuthorize("@ss.hasPermission('trade:after-sale:receive')")
public CommonResult<Boolean> receiveAfterSale(@RequestParam("id") Long id) {
afterSaleService.receiveAfterSale(getLoginUserId(), id);
return success(true);
}
@PutMapping("/refuse")
@ApiOperation("确认收货")
@ApiImplicitParam(name = "id", value = "售后编号", required = true, example = "1")
@PreAuthorize("@ss.hasPermission('trade:after-sale:receive')")
public CommonResult<Boolean> refuseAfterSale(TradeAfterSaleRefuseReqVO refuseReqVO) {
afterSaleService.refuseAfterSale(getLoginUserId(), refuseReqVO);
return success(true);
}
@PostMapping("/refund")
@ApiOperation(value = "确认退款")
@ApiImplicitParam(name = "id", value = "售后编号", required = true, example = "1")
@PreAuthorize("@ss.hasPermission('trade:after-sale:refund')")
public CommonResult<Boolean> refundAfterSale(@RequestParam("id") Long id) {
afterSaleService.refundAfterSale(getLoginUserId(), getClientIP(), id);
return success(true);
}
}

View File

@@ -0,0 +1,119 @@
package cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
import java.util.List;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
/**
* 交易售后 Base VO提供给添加、修改、详细的子 VO 使用
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
*/
@Data
public class TradeAfterSaleBaseVO {
@ApiModelProperty(value = "售后流水号", required = true, example = "202211190847450020500077")
@NotNull(message = "售后流水号不能为空")
private String no;
@ApiModelProperty(value = "售后状态", required = true, example = "10", notes = "参见 TradeAfterSaleStatusEnum 枚举")
@NotNull(message = "售后状态不能为空")
private Integer status;
@ApiModelProperty(value = "售后类型", required = true, example = "20", notes = "参见 TradeAfterSaleTypeEnum 枚举")
@NotNull(message = "售后类型不能为空")
private Integer type;
@ApiModelProperty(value = "售后方式", required = true, example = "10", notes = "参见 TradeAfterSaleWayEnum 枚举")
@NotNull(message = "售后方式不能为空")
private Integer way;
@ApiModelProperty(value = "用户编号", required = true, example = "30337")
@NotNull(message = "用户编号不能为空")
private Long userId;
@ApiModelProperty(value = "申请原因", required = true, example = "不喜欢")
@NotNull(message = "申请原因不能为空")
private String applyReason;
@ApiModelProperty(value = "补充描述", example = "你说的对")
private String applyDescription;
@ApiModelProperty(value = "补充凭证图片", example = "https://www.iocoder.cn/1.png")
private List<String> applyPicUrls;
@ApiModelProperty(value = "订单编号", required = true, example = "18078")
@NotNull(message = "订单编号不能为空")
private Long orderId;
@ApiModelProperty(value = "订单流水号", required = true, example = "2022111917190001")
@NotNull(message = "订单流水号不能为空")
private Long orderNo;
@ApiModelProperty(value = "订单项编号", required = true, example = "572")
@NotNull(message = "订单项编号不能为空")
private Long orderItemId;
@ApiModelProperty(value = "商品 SPU 编号", required = true, example = "2888")
@NotNull(message = "商品 SPU 编号不能为空")
private Long spuId;
@ApiModelProperty(value = "商品 SPU 名称", required = true, example = "李四")
@NotNull(message = "商品 SPU 名称不能为空")
private String spuName;
@ApiModelProperty(value = "商品 SKU 编号", required = true, example = "15657")
@NotNull(message = "商品 SKU 编号不能为空")
private Long skuId;
@ApiModelProperty(value = "商品图片", example = "https://www.iocoder.cn/2.png")
private String picUrl;
@ApiModelProperty(value = "购买数量", required = true, example = "20012")
@NotNull(message = "购买数量不能为空")
private Integer count;
@ApiModelProperty(value = "审批时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime auditTime;
@ApiModelProperty(value = "审批人", example = "30835")
private Long auditUserId;
@ApiModelProperty(value = "审批备注", example = "不香")
private String auditReason;
@ApiModelProperty(value = "退款金额,单位:分", required = true, example = "18077")
@NotNull(message = "退款金额,单位:分不能为空")
private Integer refundPrice;
@ApiModelProperty(value = "支付退款编号", example = "10271")
private Long payRefundId;
@ApiModelProperty(value = "退款时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime refundTime;
@ApiModelProperty(value = "退货物流公司编号", example = "10")
private Long logisticsId;
@ApiModelProperty(value = "退货物流单号", example = "610003952009")
private String logisticsNo;
@ApiModelProperty(value = "退货时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime deliveryTime;
@ApiModelProperty(value = "收货时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime receiveTime;
@ApiModelProperty(value = "收货备注", example = "不喜欢")
private String receiveReason;
}

View File

@@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@ApiModel("管理后台 - 交易售后拒绝 Request VO")
@Data
public class TradeAfterSaleDisagreeReqVO {
@ApiModelProperty(value = "售后编号", required = true, example = "1024")
@NotNull(message = "售后编号不能为空")
private Long id;
@ApiModelProperty(value = "审批备注", required = true, example = "你猜")
@NotEmpty(message = "审批备注不能为空")
private String auditReason;
}

View File

@@ -0,0 +1,50 @@
package cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.validation.InEnum;
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.aftersale.TradeAfterSaleWayEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@ApiModel("管理后台 - 交易售后分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class TradeAfterSalePageReqVO extends PageParam {
@ApiModelProperty(value = "售后流水号", example = "202211190847450020500077", notes = "模糊匹配")
private String no;
@ApiModelProperty(value = "售后状态", example = "10", notes = "参见 TradeAfterSaleStatusEnum 枚举")
@InEnum(value = TradeAfterSaleStatusEnum.class, message = "售后状态必须是 {value}")
private Integer status;
@ApiModelProperty(value = "售后类型", example = "20", notes = "参见 TradeAfterSaleTypeEnum 枚举")
@InEnum(value = TradeAfterSaleTypeEnum.class, message = "售后类型必须是 {value}")
private Integer type;
@ApiModelProperty(value = "售后方式", example = "10", notes = "参见 TradeAfterSaleWayEnum 枚举")
@InEnum(value = TradeAfterSaleWayEnum.class, message = "售后方式必须是 {value}")
private Integer way;
@ApiModelProperty(value = "订单编号", example = "18078", notes = "模糊匹配")
private String orderNo;
@ApiModelProperty(value = "商品 SPU 名称", example = "李四", notes = "模糊匹配")
private String spuName;
@ApiModelProperty(value = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@@ -0,0 +1,21 @@
package cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
@ApiModel("管理后台 - 交易售后拒绝收货 Request VO")
@Data
public class TradeAfterSaleRefuseReqVO {
@ApiModelProperty(value = "售后编号", required = true, example = "1024")
@NotNull(message = "售后编号不能为空")
private Long id;
@ApiModelProperty(value = "收货备注", required = true, example = "你猜")
@NotNull(message = "收货备注不能为空")
private String refuseMemo;
}

View File

@@ -0,0 +1,36 @@
package cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo;
import cn.iocoder.yudao.module.trade.controller.admin.base.member.user.MemberUserRespVO;
import cn.iocoder.yudao.module.trade.controller.admin.base.product.property.ProductPropertyValueDetailRespVO;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.LocalDateTime;
import java.util.List;
@ApiModel("管理后台 - 交易售后分页的每一条记录 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class TradeAfterSaleRespPageItemVO extends TradeAfterSaleBaseVO {
@ApiModelProperty(value = "售后编号", required = true, example = "27630")
private Long id;
@ApiModelProperty(value = "创建时间", required = true)
private LocalDateTime createTime;
/**
* 商品属性数组
*/
private List<ProductPropertyValueDetailRespVO> properties;
/**
* 用户信息
*/
private MemberUserRespVO user;
}

View File

@@ -0,0 +1,51 @@
package cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.log;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
@ApiModel("管理后台 - 交易售后日志 Response VO")
@Data
public class TradeAfterSaleLogRespVO {
@ApiModelProperty(value = "编号", required = true, example = "20669")
private Long id;
@ApiModelProperty(value = "用户编号", required = true, example = "22634")
@NotNull(message = "用户编号不能为空")
private Long userId;
@ApiModelProperty(value = "用户类型", required = true, example = "2")
@NotNull(message = "用户类型不能为空")
private Integer userType;
@ApiModelProperty(value = "售后编号", required = true, example = "3023")
@NotNull(message = "售后编号不能为空")
private Long afterSaleId;
@ApiModelProperty(value = "订单编号", required = true, example = "25870")
@NotNull(message = "订单编号不能为空")
private Long orderId;
@ApiModelProperty(value = "订单项编号", required = true, example = "23154")
@NotNull(message = "订单项编号不能为空")
private Long orderItemId;
@ApiModelProperty(value = "售后状态(之前)", example = "2", notes = "参见 TradeAfterSaleStatusEnum 枚举")
private Integer beforeStatus;
@ApiModelProperty(value = "售后状态(之后)", required = true, example = "1", notes = "参见 TradeAfterSaleStatusEnum 枚举")
@NotNull(message = "售后状态(之后)不能为空")
private Integer afterStatus;
@ApiModelProperty(value = "操作明细", required = true, example = "维权完成退款金额¥37776.00")
@NotNull(message = "操作明细不能为空")
private String content;
@ApiModelProperty(value = "创建时间", required = true)
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,4 @@
/**
* 占位符,可忽略
*/
package cn.iocoder.yudao.module.trade.controller.admin.base.member;

View File

@@ -0,0 +1,20 @@
package cn.iocoder.yudao.module.trade.controller.admin.base.member.user;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ApiModel("管理后台 - 会员用户 Response VO")
@Data
public class MemberUserRespVO {
@ApiModelProperty(value = "用户 ID", required = true, example = "1")
private Long id;
@ApiModelProperty(value = "用户昵称", required = true, example = "芋道源码")
private String nickname;
@ApiModelProperty(value = "用户头像", example = "https://www.iocoder.cn/xxx.png")
private String avatar;
}

View File

@@ -0,0 +1,4 @@
/**
* 放置该模块通用的 VO 类
*/
package cn.iocoder.yudao.module.trade.controller.admin.base;

View File

@@ -0,0 +1,23 @@
package cn.iocoder.yudao.module.trade.controller.admin.base.product.property;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ApiModel("管理后台 - 商品属性值的明细 Response VO")
@Data
public class ProductPropertyValueDetailRespVO {
@ApiModelProperty(value = "属性的编号", required = true, example = "1")
private Long propertyId;
@ApiModelProperty(value = "属性的名称", required = true, example = "颜色")
private String propertyName;
@ApiModelProperty(value = "属性值的编号", required = true, example = "1024")
private Long valueId;
@ApiModelProperty(value = "属性值的名称", required = true, example = "红色")
private String valueName;
}

View File

@@ -0,0 +1,9 @@
### 获得交易订单分页 => 成功
GET {{baseUrl}}/trade/order/page?pageNo=1&pageSize=10
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
### 获得交易订单分页 => 成功
GET {{baseUrl}}/trade/order/get-detail?id=21
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}

View File

@@ -0,0 +1,93 @@
package cn.iocoder.yudao.module.trade.controller.admin.order;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.product.api.property.ProductPropertyValueApi;
import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDetailRespVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderPageItemRespVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderPageReqVO;
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.service.order.TradeOrderService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Api(tags = "管理后台 - 交易订单")
@RestController
@RequestMapping("/trade/order")
@Validated
@Slf4j
public class TradeOrderController {
@Resource
private TradeOrderService tradeOrderService;
@Resource
private ProductPropertyValueApi productPropertyValueApi;
@Resource
private MemberUserApi memberUserApi;
@GetMapping("/page")
@ApiOperation("获得交易订单分页")
@PreAuthorize("@ss.hasPermission('trade:order:query')")
public CommonResult<PageResult<TradeOrderPageItemRespVO>> getOrderPage(TradeOrderPageReqVO reqVO) {
// 查询订单
PageResult<TradeOrderDO> pageResult = tradeOrderService.getOrderPage(reqVO);
if (CollUtil.isEmpty(pageResult.getList())) {
return success(PageResult.empty());
}
// 查询订单项
List<TradeOrderItemDO> orderItems = tradeOrderService.getOrderItemListByOrderId(
convertSet(pageResult.getList(), TradeOrderDO::getId));
// 查询商品属性
List<ProductPropertyValueDetailRespDTO> propertyValueDetails = productPropertyValueApi
.getPropertyValueDetailList(TradeOrderConvert.INSTANCE.convertPropertyValueIds(orderItems));
// 最终组合
return success(TradeOrderConvert.INSTANCE.convertPage(pageResult, orderItems, propertyValueDetails));
}
@GetMapping("/get-detail")
@ApiOperation("获得交易订单详情")
@ApiImplicitParam(name = "id", value = "订单编号", required = true, example = "1")
@PreAuthorize("@ss.hasPermission('trade:order:query')")
public CommonResult<TradeOrderDetailRespVO> getOrderDetail(@RequestParam("id") Long id) {
// 查询订单
TradeOrderDO order = tradeOrderService.getOrder(id);
// 查询订单项
List<TradeOrderItemDO> orderItems = tradeOrderService.getOrderItemListByOrderId(id);
// 查询商品属性
List<ProductPropertyValueDetailRespDTO> propertyValueDetails = productPropertyValueApi
.getPropertyValueDetailList(TradeOrderConvert.INSTANCE.convertPropertyValueIds(orderItems));
// 查询会员
MemberUserRespDTO user = memberUserApi.getUser(order.getUserId());
// 最终组合
return success(TradeOrderConvert.INSTANCE.convert(order, orderItems, propertyValueDetails, user));
}
@PostMapping("/delivery")
@ApiOperation("发货订单")
@PreAuthorize("@ss.hasPermission('trade:order:delivery')")
public CommonResult<Boolean> deliveryOrder(@RequestBody TradeOrderDeliveryReqVO deliveryReqVO) {
tradeOrderService.deliveryOrder(getLoginUserId(), deliveryReqVO);
return success(true);
}
}

View File

@@ -0,0 +1,145 @@
package cn.iocoder.yudao.module.trade.controller.admin.order.vo;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.Date;
/**
* 交易订单 Base VO提供给添加、修改、详细的子 VO 使用
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
*/
@Data
public class TradeOrderBaseVO {
// ========== 订单基本信息 ==========
@ApiModelProperty(value = "订单编号", required = true, example = "1024")
private Long id;
@ApiModelProperty(value = "订单流水号", required = true, example = "1146347329394184195")
private String no;
@ApiModelProperty(value = "创建时间", required = true, notes = "下单时间")
private Date createTime;
@ApiModelProperty(value = "订单类型", required = true, example = "1", notes = "参见 TradeOrderTypeEnum 枚举")
private Integer type;
@ApiModelProperty(value = "订单来源", required = true, example = "1", notes = "参见 TerminalEnum 枚举")
private Integer terminal;
@ApiModelProperty(value = "用户编号", required = true, example = "2048")
private Long userId;
@ApiModelProperty(value = "用户 IP", required = true, example = "127.0.0.1")
private String userIp;
@ApiModelProperty(value = "用户备注", required = true, example = "你猜")
private String userRemark;
@ApiModelProperty(value = "订单状态", required = true, example = "1", notes = "参见 TradeOrderStatusEnum 枚举")
private Integer status;
@ApiModelProperty(value = "购买的商品数量", required = true, example = "10")
private Integer productCount;
@ApiModelProperty(value = "订单完成时间")
private LocalDateTime finishTime;
@ApiModelProperty(value = "订单取消时间")
private LocalDateTime cancelTime;
@ApiModelProperty(value = "取消类型", example = "10", notes = "参见 TradeOrderCancelTypeEnum 枚举")
private Integer cancelType;
@ApiModelProperty(value = "商家备注", example = "你猜一下")
private String remark;
// ========== 价格 + 支付基本信息 ==========
@ApiModelProperty(value = "支付订单编号", required = true, example = "1024")
private Long payOrderId;
@ApiModelProperty(value = "是否已支付", required = true, example = "true")
private Boolean payed;
@ApiModelProperty(value = "付款时间")
private LocalDateTime payTime;
@ApiModelProperty(value = "支付渠道", required = true, example = "wx_lite", notes = "参见 PayChannelEnum 枚举")
private String payChannelCode;
@ApiModelProperty(value = "商品原价(总)", required = true, example = "1000", notes = "单位:分")
private Integer originalPrice;
@ApiModelProperty(value = "订单原价(总)", required = true, example = "1000", notes = "单位:分")
private Integer orderPrice;
@ApiModelProperty(value = "订单优惠(总)", required = true, example = "100", notes = "单位:分")
private Integer discountPrice;
@ApiModelProperty(value = "运费金额", required = true, example = "100", notes = "单位:分")
private Integer deliveryPrice;
@ApiModelProperty(value = "订单调价(总)", required = true, example = "100", notes = "单位:分")
private Integer adjustPrice;
@ApiModelProperty(value = "应付金额(总)", required = true, example = "1000", notes = "单位:分")
private Integer payPrice;
// ========== 收件 + 物流基本信息 ==========
@ApiModelProperty(value = "配送模板编号", example = "1024")
private Long deliveryTemplateId;
@ApiModelProperty(value = "发货物流公司编号", example = "1024")
private Long logisticsId;
@ApiModelProperty(value = "发货物流单号", example = "1024")
private String logisticsNo;
@ApiModelProperty(value = "发货状态", required = true, example = "1", notes = "参见 TradeOrderDeliveryStatusEnum 枚举")
private Integer deliveryStatus;
@ApiModelProperty(value = "发货时间")
private LocalDateTime deliveryTime;
@ApiModelProperty(value = "收货时间")
private LocalDateTime receiveTime;
@ApiModelProperty(value = "收件人名称", required = true, example = "张三")
private String receiverName;
@ApiModelProperty(value = "收件人手机", required = true, example = "13800138000")
private String receiverMobile;
@ApiModelProperty(value = "收件人地区编号", required = true, example = "110000")
private Integer receiverAreaId;
@ApiModelProperty(value = "收件人邮编", required = true, example = "100000")
private Integer receiverPostCode;
@ApiModelProperty(value = "收件人详细地址", required = true, example = "中关村大街 1 号")
private String receiverDetailAddress;
// ========== 售后基本信息 ==========
@ApiModelProperty(value = "售后状态", example = "1", notes = "参见 TradeOrderAfterSaleStatusEnum 枚举")
private Integer afterSaleStatus;
@ApiModelProperty(value = "退款金额", required = true, example = "100", notes = "单位:分")
private Integer refundPrice;
// ========== 营销基本信息 ==========
@ApiModelProperty(value = "优惠劵编号", example = "1024")
private Long couponId;
@ApiModelProperty(value = "优惠劵减免金额", required = true, example = "100", notes = "单位:分")
private Integer couponPrice;
@ApiModelProperty(value = "积分抵扣的金额", required = true, example = "100", notes = "单位:分")
private Integer pointPrice;
}

View File

@@ -0,0 +1,26 @@
package cn.iocoder.yudao.module.trade.controller.admin.order.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@ApiModel("管理后台 - 订单发货 Request VO")
@Data
public class TradeOrderDeliveryReqVO {
@ApiModelProperty(name = "订单编号", required = true, example = "1024")
@NotNull(message = "订单编号不能为空")
private Long id;
@ApiModelProperty(name = "发货物流公司编号", required = true, example = "1")
@NotNull(message = "发货物流公司编号不能为空")
private Long logisticsId;
@ApiModelProperty(name = "发货物流单号", required = true, example = "SF123456789")
@NotEmpty(message = "发货物流单号不能为空")
private String logisticsNo;
}

View File

@@ -0,0 +1,39 @@
package cn.iocoder.yudao.module.trade.controller.admin.order.vo;
import cn.iocoder.yudao.module.trade.controller.admin.base.member.user.MemberUserRespVO;
import cn.iocoder.yudao.module.trade.controller.admin.base.product.property.ProductPropertyValueDetailRespVO;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@ApiModel("管理后台 - 交易订单的详情 Response VO")
@Data
public class TradeOrderDetailRespVO extends TradeOrderBaseVO {
@ApiModelProperty(value = "收件人地区名字", required = true, example = "上海 上海市 普陀区")
private String receiverAreaName;
/**
* 订单项列表
*/
private List<Item> items;
/**
* 用户信息
*/
private MemberUserRespVO user;
@ApiModel("管理后台 - 交易订单的详情的订单项目")
@Data
public static class Item extends TradeOrderItemBaseVO {
/**
* 属性数组
*/
private List<ProductPropertyValueDetailRespVO> properties;
}
}

View File

@@ -0,0 +1,70 @@
package cn.iocoder.yudao.module.trade.controller.admin.order.vo;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 交易订单项 Base VO提供给添加、修改、详细的子 VO 使用
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
*/
@Data
public class TradeOrderItemBaseVO {
// ========== 订单项基本信息 ==========
@ApiModelProperty(value = "编号", required = true, example = "1")
private Long id;
@ApiModelProperty(value = "用户编号", required = true, example = "1")
private Long userId;
@ApiModelProperty(value = "订单编号", required = true, example = "1")
private Long orderId;
// ========== 商品基本信息 ==========
@ApiModelProperty(value = "商品 SPU 编号", required = true, example = "1")
private Long spuId;
@ApiModelProperty(value = "商品 SPU 名称", required = true, example = "芋道源码")
private String spuName;
@ApiModelProperty(value = "商品 SKU 编号", required = true, example = "1")
private Long skuId;
@ApiModelProperty(value = "商品图片", required = true, example = "https://www.iocoder.cn/1.png")
private String picUrl;
@ApiModelProperty(value = "购买数量", required = true, example = "1")
private Integer count;
// ========== 价格 + 支付基本信息 ==========
@ApiModelProperty(value = "商品原价(总)", required = true, example = "100", notes = "单位:分")
private Integer originalPrice;
@ApiModelProperty(value = "商品原价(单)", required = true, example = "100", notes = "单位:分")
private Integer originalUnitPrice;
@ApiModelProperty(value = "商品优惠(总)", required = true, example = "100", notes = "单位:分")
private Integer discountPrice;
@ApiModelProperty(value = "商品实付金额(总)", required = true, example = "100", notes = "单位:分")
private Integer payPrice;
@ApiModelProperty(value = "子订单分摊金额(总)", required = true, example = "100", notes = "单位:分")
private Integer orderPartPrice;
@ApiModelProperty(value = "分摊后子订单实付金额(总)", required = true, example = "100", notes = "单位:分")
private Integer orderDividePrice;
// ========== 营销基本信息 ==========
// TODO 芋艿:在捉摸一下
// ========== 售后基本信息 ==========
@ApiModelProperty(value = "售后状态", required = true, example = "1", notes = "参见 TradeOrderItemAfterSaleStatusEnum 枚举类")
private Integer afterSaleStatus;
}

View File

@@ -0,0 +1,33 @@
package cn.iocoder.yudao.module.trade.controller.admin.order.vo;
import cn.iocoder.yudao.module.trade.controller.admin.base.product.property.ProductPropertyValueDetailRespVO;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@ApiModel("管理后台 - 交易订单的分页项 Response VO")
@Data
public class TradeOrderPageItemRespVO extends TradeOrderBaseVO {
@ApiModelProperty(value = "收件人地区名字", required = true, example = "上海 上海市 普陀区")
private String receiverAreaName;
/**
* 订单项列表
*/
private List<Item> items;
@ApiModel("管理后台 - 交易订单的分页项的订单项目")
@Data
public static class Item extends TradeOrderItemBaseVO {
/**
* 属性数组
*/
private List<ProductPropertyValueDetailRespVO> properties;
}
}

View File

@@ -0,0 +1,54 @@
package cn.iocoder.yudao.module.trade.controller.admin.order.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.framework.common.validation.Mobile;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@ApiModel("管理后台 - 交易订单的分页 Request VO")
@Data
public class TradeOrderPageReqVO extends PageParam {
@ApiModelProperty(value = "订单号", example = "88888888", notes = "模糊匹配")
private String no;
@ApiModelProperty(value = "用户编号", example = "1024")
private Long userId;
@ApiModelProperty(value = "用户昵称", example = "小王", notes = "模糊匹配")
private String userNickname;
@ApiModelProperty(value = "用户手机号", example = "小王", notes = "精准匹配")
@Mobile
private String userMobile;
@ApiModelProperty(value = "收件人名称", example = "小红", notes = "模糊匹配")
private String receiverName;
@ApiModelProperty(value = "收件人手机", example = "1560", notes = "模糊匹配")
@Mobile
private String receiverMobile;
@ApiModelProperty(value = "订单类型", example = "1", notes = "参见 TradeOrderTypeEnum 枚举")
private Integer type;
@ApiModelProperty(value = "订单状态", example = "1", notes = "参见 TradeOrderStatusEnum 枚举")
@InEnum(value = TradeOrderStatusEnum.class, message = "订单状态必须是 {value}")
private Integer status;
@ApiModelProperty(value = "支付渠道", example = "wx_lite")
private String payChannelCode;
@ApiModelProperty(value = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@@ -0,0 +1,50 @@
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.AppTradeAfterSaleCreateReqVO;
import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppTradeAfterSaleDeliveryReqVO;
import cn.iocoder.yudao.module.trade.service.aftersale.TradeAfterSaleService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
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 AppTradeAfterSaleController {
@Resource
private TradeAfterSaleService afterSaleService;
@PostMapping(value = "/create")
@ApiOperation(value = "申请售后")
public CommonResult<Long> createAfterSale(@RequestBody AppTradeAfterSaleCreateReqVO createReqVO) {
return success(afterSaleService.createAfterSale(getLoginUserId(), createReqVO));
}
@PostMapping(value = "/delivery")
@ApiOperation(value = "退回货物")
public CommonResult<Boolean> deliveryAfterSale(@RequestBody AppTradeAfterSaleDeliveryReqVO deliveryReqVO) {
afterSaleService.deliveryAfterSale(getLoginUserId(), deliveryReqVO);
return success(true);
}
@DeleteMapping(value = "/cancel")
@ApiOperation(value = "取消售后")
@ApiImplicitParam(name = "id", value = "售后编号", required = true, example = "1")
public CommonResult<Boolean> cancelAfterSale(@RequestParam("id") Long id) {
afterSaleService.cancelAfterSale(getLoginUserId(), id);
return success(true);
}
}

View File

@@ -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.TradeAfterSaleWayEnum;
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 AppTradeAfterSaleCreateReqVO {
@ApiModelProperty(name = "订单项编号", required = true, example = "1024")
@NotNull(message = "订单项编号不能为空")
private Long orderItemId;
@ApiModelProperty(name = "售后方式", required = true, example = "1", notes = "对应 TradeAfterSaleWayEnum 枚举")
@NotNull(message = "售后方式不能为空")
@InEnum(value = TradeAfterSaleWayEnum.class, message = "售后方式必须是 {value}")
private Integer way;
@ApiModelProperty(name = "退款金额", required = true, example = "100", notes = "单位:分")
@NotNull(message = "退款金额不能为空")
@Min(value = 1, message = "退款金额必须大于 0")
private Integer refundPrice;
@ApiModelProperty(name = "申请原因", required = true, example = "1", notes = "使用数据字典枚举,对应 trade_refund_apply_reason 类型")
@NotNull(message = "申请原因不能为空")
private String 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;
}

View File

@@ -0,0 +1,31 @@
package cn.iocoder.yudao.module.trade.controller.app.aftersale.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
@ApiModel("用户 App - 交易售后退回货物 Request VO")
@Data
public class AppTradeAfterSaleDeliveryReqVO {
@ApiModelProperty(name = "售后编号", required = true, example = "1024")
@NotNull(message = "售后编号不能为空")
private Long id;
@ApiModelProperty(name = "退货物流公司编号", required = true, example = "1")
@NotNull(message = "退货物流公司编号不能为空")
private Long logisticsId;
@ApiModelProperty(name = "退货物流单号", required = true, example = "SF123456789")
@NotNull(message = "退货物流单号不能为空")
private String logisticsNo;
@ApiModelProperty(name = "退货时间", required = true)
@NotEmpty(message = "退货时间不能为空")
private LocalDateTime deliveryTime;
}

View File

@@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.trade.controller.app.base.property;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "用户 App - 规格 + 规格值 Response VO")
@Schema(description ="用户 App - 商品属性值的明细 Response VO")
@Data
public class AppProductPropertyValueDetailRespVO {

View File

@@ -27,7 +27,7 @@ public class AppProductSkuBaseRespVO {
private Integer stock;
/**
* 规格数组
* 属性数组
*/
private List<AppProductPropertyValueDetailRespVO> properties;

View File

@@ -8,24 +8,30 @@ GET {{shop-api-base-url}}/trade-order/confirm-create-order-info-from-cart
Content-Type: application/x-www-form-urlencoded
Authorization: Bearer {{user-access-token}}
### /trade-order/confirm-create-order-info-from-cart 基于商品,创建订单
POST {{shop-api-base-url}}/trade-order/create
### /trade-order/create 基于商品,创建订单
POST {{appApi}}/trade/order/create
Content-Type: application/json
Authorization: Bearer {{user-access-token}}
Authorization: Bearer {{appToken}}
tenant-id: {{appTenentId}}
{
"userAddressId": 19,
"addressId": 21,
"remark": "我是备注",
"orderItems": [
"fromCart": false,
"items": [
{
"skuId": 3,
"quantity": 1
"skuId": 29,
"count": 1
}
]
}
### /trade-order/page 获得订单交易分页
GET {{shop-api-base-url}}/trade-order/page?status=1&pageNo=1&pageSize=10
Content-Type: application/x-www-form-urlencoded
### 获得订单交易分页
GET {{appApi}}/trade/order/page?pageNo=1&pageSize=10
Authorization: Bearer {{appToken}}
tenant-id: {{appTenentId}}
###
### 获得订单交易的详细
GET {{appApi}}/trade/order/get-detail?id=21
Authorization: Bearer {{appToken}}
tenant-id: {{appTenentId}}

View File

@@ -4,36 +4,46 @@ import cn.hutool.extra.servlet.ServletUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderGetCreateInfoRespVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.TradeOrderPageReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.TradeOrderRespVO;
import cn.iocoder.yudao.module.pay.api.notify.dto.PayOrderNotifyReqDTO;
import cn.iocoder.yudao.module.product.api.property.ProductPropertyValueApi;
import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.*;
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.service.order.TradeOrderService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
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;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "用户 App - 交易订单")
@RestController
@RequestMapping("/trade/order")
@RequiredArgsConstructor // TODO @LeeYan9: 先统一使用 @Resource 注入哈; 项目只有三层, 依赖注入会存在, 所以使用 @Resource; 也因此, 最好全局保持一致
@Validated
@Slf4j
public class AppTradeOrderController {
private final TradeOrderService tradeOrderService;
@Resource
private TradeOrderService tradeOrderService;
@Resource
private ProductPropertyValueApi productPropertyValueApi;
@GetMapping("/get-create-info")
@Operation(summary = "基于商品,确认创建订单")
@PreAuthenticated
public CommonResult<AppTradeOrderGetCreateInfoRespVO> getTradeOrderCreateInfo(AppTradeOrderCreateReqVO createReqVO) {
public CommonResult<AppTradeOrderGetCreateInfoRespVO> getOrderCreateInfo(AppTradeOrderCreateReqVO createReqVO) {
// return success(tradeOrderService.getOrderConfirmCreateInfo(UserSecurityContextHolder.getUserId(), skuId, quantity, couponCardId));
return null;
}
@@ -41,29 +51,52 @@ public class AppTradeOrderController {
@PostMapping("/create")
@Operation(summary = "创建订单")
@PreAuthenticated
public CommonResult<Long> createTradeOrder(@RequestBody AppTradeOrderCreateReqVO createReqVO,
HttpServletRequest servletRequest) {
public CommonResult<Long> createOrder(@RequestBody AppTradeOrderCreateReqVO createReqVO,
HttpServletRequest servletRequest) {
// 获取登录用户、用户 IP 地址
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
Long loginUserId = getLoginUserId();
String clientIp = ServletUtil.getClientIP(servletRequest);
// 创建交易订单,预支付记录
Long orderId = tradeOrderService.createTradeOrder(loginUserId, clientIp, createReqVO);
return CommonResult.success(orderId);
Long orderId = tradeOrderService.createOrder(loginUserId, clientIp, createReqVO);
return success(orderId);
}
@GetMapping("/get")
@PostMapping("/update-paid")
@Operation(summary = "更新订单为已支付", notes = "由 pay-module 支付服务,进行回调,可见 PayNotifyJob")
public CommonResult<Boolean> updateOrderPaid(@RequestBody PayOrderNotifyReqDTO notifyReqDTO) {
tradeOrderService.updateOrderPaid(Long.valueOf(notifyReqDTO.getMerchantOrderId()),
notifyReqDTO.getPayOrderId());
return success(true);
}
@GetMapping("/get-detail")
@Operation(summary = "获得交易订单")
@Parameter(name = "tradeOrderId", description = "交易订单编号", required = true)
public CommonResult<TradeOrderRespVO> getTradeOrder(@RequestParam("tradeOrderId") Integer tradeOrderId) {
// return success(tradeOrderService.getTradeOrder(tradeOrderId));
return null;
@Parameter(name = "id", description = "交易订单编号", required = true)
public CommonResult<AppTradeOrderDetailRespVO> getOrder(@RequestParam("id") Long id) {
// 查询订单
TradeOrderDO order = tradeOrderService.getOrder(getLoginUserId(), id);
// 查询订单项
List<TradeOrderItemDO> orderItems = tradeOrderService.getOrderItemListByOrderId(order.getId());
// 查询商品属性
List<ProductPropertyValueDetailRespDTO> propertyValueDetails = productPropertyValueApi
.getPropertyValueDetailList(TradeOrderConvert.INSTANCE.convertPropertyValueIds(orderItems));
// 最终组合
return success(TradeOrderConvert.INSTANCE.convert02(order, orderItems, propertyValueDetails));
}
@GetMapping("/page")
@Operation(summary = "获得订单交易分页")
public CommonResult<PageResult<TradeOrderRespVO>> pageTradeOrder(TradeOrderPageReqVO pageVO) {
// return success(tradeOrderService.pageTradeOrder(UserSecurityContextHolder.getUserId(), pageVO));
return null;
public CommonResult<PageResult<AppTradeOrderPageItemRespVO>> getOrderPage(AppTradeOrderPageReqVO reqVO) {
// 查询订单
PageResult<TradeOrderDO> pageResult = tradeOrderService.getOrderPage(getLoginUserId(), reqVO);
// 查询订单项
List<TradeOrderItemDO> orderItems = tradeOrderService.getOrderItemListByOrderId(
convertSet(pageResult.getList(), TradeOrderDO::getId));
// 查询商品属性
List<ProductPropertyValueDetailRespDTO> propertyValueDetails = productPropertyValueApi
.getPropertyValueDetailList(TradeOrderConvert.INSTANCE.convertPropertyValueIds(orderItems));
// 最终组合
return success(TradeOrderConvert.INSTANCE.convertPage02(pageResult, orderItems, propertyValueDetails));
}
}

View File

@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.trade.controller.app.order.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@@ -30,6 +31,7 @@ public class AppTradeOrderCreateReqVO {
* 订单商品项列表
*/
@NotEmpty(message = "必须选择购买的商品")
@Valid
private List<Item> items;
@Schema(description = "订单商品项")

View File

@@ -0,0 +1,150 @@
package cn.iocoder.yudao.module.trade.controller.app.order.vo;
import cn.iocoder.yudao.module.trade.controller.app.base.property.AppProductPropertyValueDetailRespVO;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
@ApiModel("用户 App - 订单交易的明细 Response VO")
@Data
public class AppTradeOrderDetailRespVO {
// ========== 订单基本信息 ==========
@ApiModelProperty(value = "订单编号", required = true, example = "1024")
private Long id;
@ApiModelProperty(value = "订单流水号", required = true, example = "1146347329394184195")
private String no;
@ApiModelProperty(value = "创建时间", required = true, notes = "下单时间")
private Date createTime;
@ApiModelProperty(value = "用户备注", required = true, example = "你猜")
private String userRemark;
@ApiModelProperty(value = "订单状态", required = true, example = "1", notes = "参见 TradeOrderStatusEnum 枚举")
private Integer status;
@ApiModelProperty(value = "购买的商品数量", required = true, example = "10")
private Integer productCount;
@ApiModelProperty(value = "订单完成时间")
private LocalDateTime finishTime;
@ApiModelProperty(value = "订单取消时间")
private LocalDateTime cancelTime;
// ========== 价格 + 支付基本信息 ==========
@ApiModelProperty(value = "支付订单编号", required = true, example = "1024")
private Long payOrderId;
@ApiModelProperty(value = "付款时间")
private LocalDateTime payTime;
@ApiModelProperty(value = "商品原价(总)", required = true, example = "1000", notes = "单位:分")
private Integer originalPrice;
@ApiModelProperty(value = "订单原价(总)", required = true, example = "1000", notes = "单位:分")
private Integer orderPrice;
@ApiModelProperty(value = "订单优惠(总)", required = true, example = "100", notes = "单位:分")
private Integer discountPrice;
@ApiModelProperty(value = "运费金额", required = true, example = "100", notes = "单位:分")
private Integer deliveryPrice;
@ApiModelProperty(value = "订单调价(总)", required = true, example = "100", notes = "单位:分")
private Integer adjustPrice;
@ApiModelProperty(value = "应付金额(总)", required = true, example = "1000", notes = "单位:分")
private Integer payPrice;
// ========== 收件 + 物流基本信息 ==========
@ApiModelProperty(value = "发货物流单号", example = "1024")
private String logisticsNo;
@ApiModelProperty(value = "发货时间")
private LocalDateTime deliveryTime;
@ApiModelProperty(value = "收货时间")
private LocalDateTime receiveTime;
@ApiModelProperty(value = "收件人名称", required = true, example = "张三")
private String receiverName;
@ApiModelProperty(value = "收件人手机", required = true, example = "13800138000")
private String receiverMobile;
@ApiModelProperty(value = "收件人地区编号", required = true, example = "110000")
private Integer receiverAreaId;
@ApiModelProperty(value = "收件人地区名字", required = true, example = "上海 上海市 普陀区")
private String receiverAreaName;
@ApiModelProperty(value = "收件人邮编", required = true, example = "100000")
private Integer receiverPostCode;
@ApiModelProperty(value = "收件人详细地址", required = true, example = "中关村大街 1 号")
private String receiverDetailAddress;
// ========== 售后基本信息 ==========
// ========== 营销基本信息 ==========
@ApiModelProperty(value = "优惠劵编号", example = "1024")
private Long couponId;
@ApiModelProperty(value = "优惠劵减免金额", required = true, example = "100", notes = "单位:分")
private Integer couponPrice;
@ApiModelProperty(value = "积分抵扣的金额", required = true, example = "100", notes = "单位:分")
private Integer pointPrice;
/**
* 订单项数组
*/
private List<Item> items;
@ApiModel("用户 App - 交易订单的分页项的订单项目")
@Data
public static class Item {
@ApiModelProperty(value = "编号", required = true, example = "1")
private Long id;
@ApiModelProperty(value = "商品 SPU 编号", required = true, example = "1")
private Long spuId;
@ApiModelProperty(value = "商品 SPU 名称", required = true, example = "芋道源码")
private String spuName;
@ApiModelProperty(value = "商品 SKU 编号", required = true, example = "1")
private Long skuId;
@ApiModelProperty(value = "商品图片", required = true, example = "https://www.iocoder.cn/1.png")
private String picUrl;
@ApiModelProperty(value = "购买数量", required = true, example = "1")
private Integer count;
@ApiModelProperty(value = "商品原价(总)", required = true, example = "100", notes = "单位:分")
private Integer originalPrice;
@ApiModelProperty(value = "商品原价(单)", required = true, example = "100", notes = "单位:分")
private Integer originalUnitPrice;
/**
* 属性数组
*/
private List<AppProductPropertyValueDetailRespVO> properties;
}
}

View File

@@ -55,7 +55,7 @@ public class AppTradeOrderGetCreateInfoRespVO {
*/
private String picURL;
// /**
// * 规格值数组
// * 属性数组
// */
// private List<ProductAttrKeyValueRespVO> attrs; // TODO 后面改下
/**

View File

@@ -0,0 +1,66 @@
package cn.iocoder.yudao.module.trade.controller.app.order.vo;
import cn.iocoder.yudao.module.trade.controller.app.base.property.AppProductPropertyValueDetailRespVO;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@ApiModel("用户 App - 订单交易的分页项 Response VO")
@Data
public class AppTradeOrderPageItemRespVO {
@ApiModelProperty(value = "订单编号", required = true, example = "1024")
private Long id;
@ApiModelProperty(value = "订单流水号", required = true, example = "1146347329394184195")
private String no;
@ApiModelProperty(value = "订单状态", required = true, example = "1", notes = "参见 TradeOrderStatusEnum 枚举")
private Integer status;
@ApiModelProperty(value = "购买的商品数量", required = true, example = "10")
private Integer productCount;
/**
* 订单项数组
*/
private List<Item> items;
@ApiModel("用户 App - 交易订单的明细的订单项目")
@Data
public static class Item {
@ApiModelProperty(value = "编号", required = true, example = "1")
private Long id;
@ApiModelProperty(value = "商品 SPU 编号", required = true, example = "1")
private Long spuId;
@ApiModelProperty(value = "商品 SPU 名称", required = true, example = "芋道源码")
private String spuName;
@ApiModelProperty(value = "商品 SKU 编号", required = true, example = "1")
private Long skuId;
@ApiModelProperty(value = "商品图片", required = true, example = "https://www.iocoder.cn/1.png")
private String picUrl;
@ApiModelProperty(value = "购买数量", required = true, example = "1")
private Integer count;
@ApiModelProperty(value = "商品原价(总)", required = true, example = "100", notes = "单位:分")
private Integer originalPrice;
@ApiModelProperty(value = "商品原价(单)", required = true, example = "100", notes = "单位:分")
private Integer originalUnitPrice;
/**
* 属性数组
*/
private List<AppProductPropertyValueDetailRespVO> properties;
}
}

View File

@@ -1,16 +1,18 @@
package cn.iocoder.yudao.module.trade.controller.app.order.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
// TODO 芋艿字段优化
@Schema(description = "交易订单分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class TradeOrderPageReqVO extends PageParam {
public class AppTradeOrderPageReqVO extends PageParam {
@Schema(description = "订单状态-参见 TradeOrderStatusEnum 枚举", example = "1")
private Integer orderStatus;
@InEnum(value = TradeOrderStatusEnum.class, message = "订单状态必须是 {value}")
private Integer status;
}

View File

@@ -1,52 +0,0 @@
package cn.iocoder.yudao.module.trade.controller.app.order.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "交易订单项 Response VO")
@Data
public class TradeOrderItemRespVO {
@Schema(description = "id自增长", required = true)
private Integer id;
@Schema(description = "订单编号", required = true)
private Integer orderId;
@Schema(description = "订单项状态", required = true)
private Integer status;
@Schema(description = "商品 SKU 编号", required = true)
private Integer skuId;
@Schema(description = "商品 SPU 编号", required = true)
private Integer spuId;
@Schema(description = "商品名字", required = true)
private String skuName;
@Schema(description = "图片名字", required = true)
private String skuImage;
@Schema(description = "商品数量", required = true)
private Integer quantity;
@Schema(description = "原始单价,单位:分", required = true)
private Integer originPrice;
@Schema(description = "购买单价,单位:分", required = true)
private Integer buyPrice;
@Schema(description = "最终价格,单位:分", required = true)
private Integer presentPrice;
@Schema(description = "购买总金额,单位:分", required = true)
private Integer buyTotal;
@Schema(description = "优惠总金额,单位:分", required = true)
private Integer discountTotal;
@Schema(description = "最终总金额,单位:分", required = true)
private Integer presentTotal;
@Schema(description = "退款总金额,单位:分", required = true)
private Integer refundTotal;
@Schema(description = "物流id")
private Integer logisticsId;
@Schema(description = "售后状态", required = true)
private Integer afterSaleStatus;
@Schema(description = "售后订单编号")
private Integer afterSaleOrderId;
@Schema(description = "创建时间", required = true)
private LocalDateTime createTime;
}

View File

@@ -1,71 +0,0 @@
package cn.iocoder.yudao.module.trade.controller.app.order.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.time.LocalDateTime;
import java.util.*;
@Schema(description = "订单交易 Response VO")
@Data
public class TradeOrderRespVO {
@Schema(description = "订单编号", required = true)
private Integer id;
@Schema(description = "用户编号", required = true)
private Integer userId;
@Schema(description = "订单单号", required = true)
private String orderNo;
@Schema(description = "订单状态", required = true)
private Integer orderStatus;
@Schema(description = "备注")
private String remark;
@Schema(description = "订单结束时间")
private LocalDateTime endTime;
@Schema(description = "订单金额(总金额),单位:分", required = true)
private Integer buyPrice;
@Schema(description = "优惠总金额,单位:分", required = true)
private Integer discountPrice;
@Schema(description = "物流金额,单位:分", required = true)
private Integer logisticsPrice;
@Schema(description = "最终金额,单位:分", required = true)
private Integer presentPrice;
@Schema(description = "支付金额,单位:分", required = true)
private Integer payPrice;
@Schema(description = "退款金额,单位:分", required = true)
private Integer refundPrice;
@Schema(description = "付款时间")
private LocalDateTime payTime;
@Schema(description = "支付订单编号")
private Integer payTransactionId;
@Schema(description = "支付渠道")
private Integer payChannel;
@Schema(description = "配送类型", required = true)
private Integer deliveryType;
@Schema(description = "发货时间")
private LocalDateTime deliveryTime;
@Schema(description = "收货时间")
private LocalDateTime receiveTime;
@Schema(description = "收件人名称", required = true)
private String receiverName;
@Schema(description = "手机号", required = true)
private String receiverMobile;
@Schema(description = "地区编码", required = true)
private Integer receiverAreaCode;
@Schema(description = "收件详细地址", required = true)
private String receiverDetailAddress;
@Schema(description = "售后状态", required = true)
private Integer afterSaleStatus;
@Schema(description = "优惠劵编号")
private Integer couponCardId;
@Schema(description = "创建时间", required = true)
private LocalDateTime createTime;
/**
* 订单项数组
*
* // TODO 芋艿,后续考虑怎么优化下,目前是内嵌了别的 dto
*/
private List<TradeOrderItemRespVO> orderItems;
}

View File

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

View File

@@ -0,0 +1,89 @@
package cn.iocoder.yudao.module.trade.convert.aftersale;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO;
import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.TradeAfterSaleRespPageItemVO;
import cn.iocoder.yudao.module.trade.controller.admin.base.member.user.MemberUserRespVO;
import cn.iocoder.yudao.module.trade.controller.admin.base.product.property.ProductPropertyValueDetailRespVO;
import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppTradeAfterSaleCreateReqVO;
import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.TradeAfterSaleDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.util.*;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
@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(AppTradeAfterSaleCreateReqVO createReqVO, TradeOrderItemDO tradeOrderItem);
@Mappings({
@Mapping(source = "afterSale.orderId", target = "merchantOrderId"),
@Mapping(source = "afterSale.applyReason", target = "reason"),
@Mapping(source = "afterSale.refundPrice", target = "amount")
})
PayRefundCreateReqDTO convert(String userIp, TradeAfterSaleDO afterSale,
TradeOrderProperties orderProperties);
MemberUserRespVO convert(MemberUserRespDTO bean);
PageResult<TradeAfterSaleRespPageItemVO> convertPage(PageResult<TradeAfterSaleDO> page);
default PageResult<TradeAfterSaleRespPageItemVO> convertPage(PageResult<TradeAfterSaleDO> pageResult,
Map<Long, MemberUserRespDTO> memberUsers, List<ProductPropertyValueDetailRespDTO> propertyValueDetails) {
PageResult<TradeAfterSaleRespPageItemVO> pageVOResult = convertPage(pageResult);
// 处理会员 + 商品属性等关联信息
Map<Long, ProductPropertyValueDetailRespDTO> propertyValueDetailMap = convertMap(propertyValueDetails, ProductPropertyValueDetailRespDTO::getValueId);
for (int i = 0; i < pageResult.getList().size(); i++) {
TradeAfterSaleRespPageItemVO afterSaleVO = pageVOResult.getList().get(i);
TradeAfterSaleDO afterSaleDO = pageResult.getList().get(i);
// 会员
afterSaleVO.setUser(convert(memberUsers.get(afterSaleDO.getUserId())));
// 商品属性
if (CollUtil.isNotEmpty(afterSaleDO.getProperties())) {
afterSaleVO.setProperties(new ArrayList<>(afterSaleDO.getProperties().size()));
// 遍历每个 properties设置到 TradeOrderPageItemRespVO.Item 中
afterSaleDO.getProperties().forEach(property -> {
ProductPropertyValueDetailRespDTO propertyValueDetail = propertyValueDetailMap.get(property.getValueId());
if (propertyValueDetail == null) {
return;
}
afterSaleVO.getProperties().add(convert(propertyValueDetail));
});
}
}
return pageVOResult;
}
ProductPropertyValueDetailRespVO convert(ProductPropertyValueDetailRespDTO bean);
default Set<Long> convertPropertyValueIds(List<TradeAfterSaleDO> list) {
if (CollUtil.isEmpty(list)) {
return new HashSet<>();
}
return list.stream().filter(item -> item.getProperties() != null)
.flatMap(p -> p.getProperties().stream()) // 遍历多个 Property 属性
.map(TradeOrderItemDO.Property::getValueId) // 将每个 Property 转换成对应的 propertyId最后形成集合
.collect(Collectors.toSet());
}
}

View File

@@ -1,27 +1,40 @@
package cn.iocoder.yudao.module.trade.convert.order;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
import cn.iocoder.yudao.module.pay.api.order.PayOrderInfoCreateReqDTO;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
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.admin.base.member.user.MemberUserRespVO;
import cn.iocoder.yudao.module.trade.controller.admin.base.product.property.ProductPropertyValueDetailRespVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDetailRespVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderPageItemRespVO;
import cn.iocoder.yudao.module.trade.controller.app.base.property.AppProductPropertyValueDetailRespVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderDetailRespVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderPageItemRespVO;
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;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.addTime;
@Mapper
@@ -56,7 +69,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;
});
@@ -72,9 +85,9 @@ public interface TradeOrderConvert {
ProductSkuUpdateStockReqDTO.Item convert(TradeOrderItemDO bean);
List<ProductSkuUpdateStockReqDTO.Item> convertList(List<TradeOrderItemDO> list);
default PayOrderInfoCreateReqDTO convert(TradeOrderDO tradeOrderDO, List<TradeOrderItemDO> tradeOrderItemDOs,
List<ProductSpuRespDTO> spus, TradeOrderProperties tradeOrderProperties) {
PayOrderInfoCreateReqDTO createReqDTO = new PayOrderInfoCreateReqDTO()
default PayOrderCreateReqDTO convert(TradeOrderDO tradeOrderDO, List<TradeOrderItemDO> tradeOrderItemDOs,
List<ProductSpuRespDTO> spus, TradeOrderProperties tradeOrderProperties) {
PayOrderCreateReqDTO createReqDTO = new PayOrderCreateReqDTO()
.setAppId(tradeOrderProperties.getAppId()).setUserIp(tradeOrderDO.getUserIp());
// 商户相关字段
createReqDTO.setMerchantOrderId(String.valueOf(tradeOrderDO.getId()));
@@ -88,4 +101,141 @@ public interface TradeOrderConvert {
return createReqDTO;
}
default Set<Long> convertPropertyValueIds(List<TradeOrderItemDO> list) {
if (CollUtil.isEmpty(list)) {
return new HashSet<>();
}
return list.stream().filter(item -> item.getProperties() != null)
.flatMap(p -> p.getProperties().stream()) // 遍历多个 Property 属性
.map(TradeOrderItemDO.Property::getValueId) // 将每个 Property 转换成对应的 propertyId最后形成集合
.collect(Collectors.toSet());
}
default PageResult<TradeOrderPageItemRespVO> convertPage(PageResult<TradeOrderDO> pageResult, List<TradeOrderItemDO> orderItems,
List<ProductPropertyValueDetailRespDTO> propertyValueDetails) {
Map<Long, List<TradeOrderItemDO>> orderItemMap = convertMultiMap(orderItems, TradeOrderItemDO::getOrderId);
Map<Long, ProductPropertyValueDetailRespDTO> propertyValueDetailMap = convertMap(propertyValueDetails, ProductPropertyValueDetailRespDTO::getValueId);
// 转化 List
List<TradeOrderPageItemRespVO> orderVOs = CollectionUtils.convertList(pageResult.getList(), order -> {
List<TradeOrderItemDO> xOrderItems = orderItemMap.get(order.getId());
TradeOrderPageItemRespVO orderVO = convert(order, xOrderItems);
if (CollUtil.isNotEmpty(xOrderItems)) {
// 处理商品属性
for (int i = 0; i < xOrderItems.size(); i++) {
List<TradeOrderItemDO.Property> properties = xOrderItems.get(i).getProperties();
if (CollUtil.isEmpty(properties)) {
continue;
}
TradeOrderPageItemRespVO.Item item = orderVO.getItems().get(i);
item.setProperties(new ArrayList<>(properties.size()));
// 遍历每个 properties设置到 TradeOrderPageItemRespVO.Item 中
properties.forEach(property -> {
ProductPropertyValueDetailRespDTO propertyValueDetail = propertyValueDetailMap.get(property.getValueId());
if (propertyValueDetail == null) {
return;
}
item.getProperties().add(convert(propertyValueDetail));
});
}
}
// 处理收货地址
orderVO.setReceiverAreaName(AreaUtils.format(order.getReceiverAreaId()));
return orderVO;
});
return new PageResult<>(orderVOs, pageResult.getTotal());
}
TradeOrderPageItemRespVO convert(TradeOrderDO order, List<TradeOrderItemDO> items);
ProductPropertyValueDetailRespVO convert(ProductPropertyValueDetailRespDTO bean);
default TradeOrderDetailRespVO convert(TradeOrderDO order, List<TradeOrderItemDO> orderItems,
List<ProductPropertyValueDetailRespDTO> propertyValueDetails, MemberUserRespDTO user) {
TradeOrderDetailRespVO orderVO = convert2(order, orderItems);
// 处理商品属性
Map<Long, ProductPropertyValueDetailRespDTO> propertyValueDetailMap = convertMap(propertyValueDetails, ProductPropertyValueDetailRespDTO::getValueId);
for (int i = 0; i < orderItems.size(); i++) {
List<TradeOrderItemDO.Property> properties = orderItems.get(i).getProperties();
if (CollUtil.isEmpty(properties)) {
continue;
}
TradeOrderDetailRespVO.Item item = orderVO.getItems().get(i);
item.setProperties(new ArrayList<>(properties.size()));
// 遍历每个 properties设置到 TradeOrderPageItemRespVO.Item 中
properties.forEach(property -> {
ProductPropertyValueDetailRespDTO propertyValueDetail = propertyValueDetailMap.get(property.getValueId());
if (propertyValueDetail == null) {
return;
}
item.getProperties().add(convert(propertyValueDetail));
});
}
// 处理收货地址
orderVO.setReceiverAreaName(AreaUtils.format(order.getReceiverAreaId()));
// 处理用户信息
orderVO.setUser(convert(user));
return orderVO;
}
TradeOrderDetailRespVO convert2(TradeOrderDO order, List<TradeOrderItemDO> items);
MemberUserRespVO convert(MemberUserRespDTO bean);
default PageResult<AppTradeOrderPageItemRespVO> convertPage02(PageResult<TradeOrderDO> pageResult, List<TradeOrderItemDO> orderItems,
List<ProductPropertyValueDetailRespDTO> propertyValueDetails) {
Map<Long, List<TradeOrderItemDO>> orderItemMap = convertMultiMap(orderItems, TradeOrderItemDO::getOrderId);
Map<Long, ProductPropertyValueDetailRespDTO> propertyValueDetailMap = convertMap(propertyValueDetails, ProductPropertyValueDetailRespDTO::getValueId);
// 转化 List
List<AppTradeOrderPageItemRespVO> orderVOs = CollectionUtils.convertList(pageResult.getList(), order -> {
List<TradeOrderItemDO> xOrderItems = orderItemMap.get(order.getId());
AppTradeOrderPageItemRespVO orderVO = convert02(order, xOrderItems);
if (CollUtil.isNotEmpty(xOrderItems)) {
// 处理商品属性
for (int i = 0; i < xOrderItems.size(); i++) {
List<TradeOrderItemDO.Property> properties = xOrderItems.get(i).getProperties();
if (CollUtil.isEmpty(properties)) {
continue;
}
AppTradeOrderPageItemRespVO.Item item = orderVO.getItems().get(i);
item.setProperties(new ArrayList<>(properties.size()));
// 遍历每个 properties设置到 TradeOrderPageItemRespVO.Item 中
properties.forEach(property -> {
ProductPropertyValueDetailRespDTO propertyValueDetail = propertyValueDetailMap.get(property.getValueId());
if (propertyValueDetail == null) {
return;
}
item.getProperties().add(convert02(propertyValueDetail));
});
}
}
return orderVO;
});
return new PageResult<>(orderVOs, pageResult.getTotal());
}
AppTradeOrderPageItemRespVO convert02(TradeOrderDO order, List<TradeOrderItemDO> items);
AppProductPropertyValueDetailRespVO convert02(ProductPropertyValueDetailRespDTO bean);
default AppTradeOrderDetailRespVO convert02(TradeOrderDO order, List<TradeOrderItemDO> orderItems,
List<ProductPropertyValueDetailRespDTO> propertyValueDetails) {
AppTradeOrderDetailRespVO orderVO = convert3(order, orderItems);
// 处理商品属性
Map<Long, ProductPropertyValueDetailRespDTO> propertyValueDetailMap = convertMap(propertyValueDetails, ProductPropertyValueDetailRespDTO::getValueId);
for (int i = 0; i < orderItems.size(); i++) {
List<TradeOrderItemDO.Property> properties = orderItems.get(i).getProperties();
if (CollUtil.isEmpty(properties)) {
continue;
}
AppTradeOrderDetailRespVO.Item item = orderVO.getItems().get(i);
item.setProperties(new ArrayList<>(properties.size()));
// 遍历每个 properties设置到 TradeOrderPageItemRespVO.Item 中
properties.forEach(property -> {
ProductPropertyValueDetailRespDTO propertyValueDetail = propertyValueDetailMap.get(property.getValueId());
if (propertyValueDetail == null) {
return;
}
item.getProperties().add(convert02(propertyValueDetail));
});
}
// 处理收货地址
orderVO.setReceiverAreaName(AreaUtils.format(order.getReceiverAreaId()));
return orderVO;
}
AppTradeOrderDetailRespVO convert3(TradeOrderDO order, List<TradeOrderItemDO> items);
}

View File

@@ -0,0 +1,201 @@
package cn.iocoder.yudao.module.trade.dal.dataobject.aftersale;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
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.aftersale.TradeAfterSaleStatusEnum;
import cn.iocoder.yudao.module.trade.enums.aftersale.TradeAfterSaleTypeEnum;
import cn.iocoder.yudao.module.trade.enums.aftersale.TradeAfterSaleWayEnum;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
import java.util.List;
/**
* 交易售后,用于处理 {@link TradeOrderDO} 交易订单的退款退货流程
*
* @author 芋道源码
*/
@TableName(value = "trade_after_sale", autoResultMap = true)
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
public class TradeAfterSaleDO extends BaseDO {
/**
* 售后编号,主键自增
*/
private Long id;
/**
* 售后流水号
*
* 例如说1146347329394184195
*/
private String no;
/**
* 退款状态
*
* 枚举 {@link TradeAfterSaleStatusEnum}
*/
private Integer status;
/**
* 售后方式
*
* 枚举 {@link TradeAfterSaleWayEnum}
*/
private Integer way;
/**
* 售后类型
*
* 枚举 {@link TradeAfterSaleTypeEnum}
*/
private Integer type;
/**
* 用户编号
*
* 关联 MemberUserDO 的 id 编号
*/
private Long userId;
/**
* 申请原因
*
* type = 退款,对应 trade_after_sale_refund_reason 类型
* type = 退货退款,对应 trade_after_sale_refund_and_return_reason 类型
*/
private String applyReason;
/**
* 补充描述
*/
private String applyDescription;
/**
* 补充凭证图片
*
* 数组,以逗号分隔
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private List<String> applyPicUrls;
// ========== 交易订单相关 ==========
/**
* 交易订单编号
*
* 关联 {@link TradeOrderDO#getId()}
*/
private Long orderId;
/**
* 订单流水号
*
* 冗余 {@link TradeOrderDO#getNo()}
*/
private String orderNo;
/**
* 交易订单项编号
*
* 关联 {@link TradeOrderItemDO#getId()}
*/
private Long orderItemId;
/**
* 商品 SPU 编号
*
* 关联 ProductSpuDO 的 id 字段
* 冗余 {@link TradeOrderItemDO#getSpuId()}
*/
private Long spuId;
/**
* 商品 SPU 名称
*
* 关联 ProductSkuDO 的 name 字段
* 冗余 {@link TradeOrderItemDO#getSpuName()}
*/
private String spuName;
/**
* 商品 SKU 编号
*
* 关联 ProductSkuDO 的编号
*/
private Long skuId;
/**
* 属性数组JSON 格式
*
* 冗余 {@link TradeOrderItemDO#getProperties()}
*/
@TableField(typeHandler = TradeOrderItemDO.PropertyTypeHandler.class)
private List<TradeOrderItemDO.Property> properties;
/**
* 商品图片
*
* 冗余 {@link TradeOrderItemDO#getPicUrl()}
*/
private String picUrl;
/**
* 退货商品数量
*/
private Integer count;
// ========== 审批相关 ==========
/**
* 审批时间
*/
private LocalDateTime auditTime;
/**
* 审批人
*
* 关联 AdminUserDO 的 id 编号
*/
private Long auditUserId;
/**
* 审批备注
*
* 注意,只有审批不通过才会填写
*/
private String auditReason;
// ========== 退款相关 ==========
/**
* 退款金额,单位:分。
*/
private Integer refundPrice;
/**
* 支付退款编号
*
* 对接 pay-module-biz 支付服务的退款订单编号,即 PayRefundDO 的 id 编号
*/
private Long payRefundId;
/**
* 退款时间
*/
private LocalDateTime refundTime;
// ========== 退货相关 ==========
/**
* 退货物流公司编号
*
* 关联 LogisticsDO 的 id 编号
*/
private Long logisticsId;
/**
* 退货物流单号
*/
private String logisticsNo;
/**
* 退货时间
*/
private LocalDateTime deliveryTime;
/**
* 收货时间
*/
private LocalDateTime receiveTime;
/**
* 收货备注
*
* 注意,只有拒绝收货才会填写
*/
private String receiveReason;
}

View File

@@ -0,0 +1,83 @@
package cn.iocoder.yudao.module.trade.dal.dataobject.aftersale;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
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.aftersale.TradeAfterSaleStatusEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
/**
* 交易售后日志 DO
*
* // TODO 可优化参考淘宝或者有赞1增加 action 表示什么操作2content 记录每个操作的明细
*
* @author 芋道源码
*/
@TableName("trade_after_sale_log")
@KeySequence("trade_after_sale_log_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TradeAfterSaleLogDO extends BaseDO {
/**
* 编号
*/
@TableId
private Long id;
/**
* 用户编号
*
* 关联 1AdminUserDO 的 id 字段
* 关联 2MemberUserDO 的 id 字段
*/
private Long userId;
/**
* 用户类型
*
* 枚举 {@link UserTypeEnum}
*/
private Integer userType;
/**
* 售后编号
*
* 关联 {@link TradeAfterSaleDO#getId()}
*/
private Long afterSaleId;
/**
* 订单编号
*
* 关联 {@link TradeOrderDO#getId()}
*/
private Long orderId;
/**
* 订单项编号
*
* 关联 {@link TradeOrderItemDO#getId()}
*/
private Long orderItemId;
/**
* 售后状态(之前)
*
* 枚举 {@link TradeAfterSaleStatusEnum}
*/
private Integer beforeStatus;
/**
* 售后状态(之后)
*
* 枚举 {@link TradeAfterSaleStatusEnum}
*/
private Integer afterStatus;
/**
* 操作明细
*/
private String content;
}

View File

@@ -4,7 +4,8 @@ import cn.iocoder.yudao.framework.common.enums.TerminalEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateRespDTO.OrderItem;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderCancelTypeEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderRefundStatusEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderAfterSaleStatusEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderDeliveryStatusEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
@@ -44,9 +45,9 @@ public class TradeOrderDO extends BaseDO {
*
* 枚举 {@link TradeOrderTypeEnum}
*/
private Integer type; // TODO order_promotion_type
private Integer type;
/**
* 订单来源终端
* 订单来源
*
* 枚举 {@link TerminalEnum}
*/
@@ -71,7 +72,6 @@ public class TradeOrderDO extends BaseDO {
* 枚举 {@link TradeOrderStatusEnum}
*/
private Integer status;
// TODO 芋艿:要不要存储 prod_name 购买的商品名门?
/**
* 购买的商品数量
*/
@@ -96,6 +96,17 @@ public class TradeOrderDO extends BaseDO {
private String remark;
// ========== 价格 + 支付基本信息 ==========
// 价格文档 - 淘宝https://open.taobao.com/docV3.htm?docId=108471&docType=1
// 价格文档 - 京东到家https://openo2o.jddj.com/api/getApiDetail/182/4d1494c5e7ac4679bfdaaed950c5bc7f.htm
// 价格文档 - 有赞https://doc.youzanyun.com/detail/API/0/906
/**
* 支付订单编号
*
* 对接 pay-module-biz 支付服务的支付订单编号,即 PayOrderDO 的 id 编号
*/
private Long payOrderId;
/**
* 是否已支付
*
@@ -107,11 +118,12 @@ public class TradeOrderDO extends BaseDO {
* 付款时间
*/
private LocalDateTime payTime;
// ========== 价格 + 支付基本信息 ==========
// 价格文档 - 淘宝https://open.taobao.com/docV3.htm?docId=108471&docType=1
// 价格文档 - 京东到家https://openo2o.jddj.com/api/getApiDetail/182/4d1494c5e7ac4679bfdaaed950c5bc7f.htm
// 价格文档 - 有赞https://doc.youzanyun.com/detail/API/0/906
/**
* 支付渠道
*
* 对应 PayChannelEnum 枚举
*/
private String payChannelCode;
/**
* 商品原价(总),单位:分
@@ -157,18 +169,6 @@ public class TradeOrderDO extends BaseDO {
* + {@link #adjustPrice}
*/
private Integer payPrice;
/**
* 支付订单编号
*
* 对接 pay-module-biz 支付服务的支付订单编号,即 PayOrderDO 的 id 编号
*/
private Long payOrderId;
/**
* 支付成功的支付渠道
*
* 对应 PayChannelEnum 枚举
*/
private Integer payChannel;
// ========== 收件 + 物流基本信息 ==========
/**
@@ -178,20 +178,24 @@ public class TradeOrderDO extends BaseDO {
*/
private Long deliveryTemplateId;
/**
* 物流公司
* 发货物流公司
*/
private String expressNo;
private Long logisticsId;
/**
* 发货物流单号
*/
private String logisticsNo;
/**
* 发货状态
*
* true - 已发货
* false - 未发货
* 枚举 {@link TradeOrderDeliveryStatusEnum}
*/
private Boolean deliveryStatus;
private Integer deliveryStatus;
/**
* 发货时间
*/
private LocalDateTime deliveryTime;
/**
* 收货时间
*/
@@ -207,7 +211,7 @@ public class TradeOrderDO extends BaseDO {
/**
* 收件人地区编号
*/
private Long receiverAreaId;
private Integer receiverAreaId;
/**
* 收件人邮编
*/
@@ -217,13 +221,13 @@ public class TradeOrderDO extends BaseDO {
*/
private String receiverDetailAddress;
// ========== 退款基本信息 ==========
// ========== 售后基本信息 ==========
/**
* 退款状态
* 收货状态
*
* 枚举 {@link TradeOrderRefundStatusEnum}
* 枚举 {@link TradeOrderAfterSaleStatusEnum}
*/
private Integer refundStatus;
private Integer afterSaleStatus;
/**
* 退款金额,单位:分
*

View File

@@ -2,7 +2,8 @@ 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.dal.dataobject.aftersale.TradeAfterSaleDO;
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;
@@ -10,6 +11,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.List;
/**
@@ -41,13 +43,19 @@ public class TradeOrderItemDO extends BaseDO {
*/
private Long orderId;
// ========== 商品基本信息 ==========
// ========== 商品基本信息; 冗余较多字段,减少关联查询 ==========
/**
* 商品 SPU 编号
*
* 关联 ProductSkuDO 的 spuId 编号
*/
private Long spuId;
/**
* 商品 SPU 名称
*
* 冗余 ProductSkuDO 的 spuName 编号
*/
private String spuName;
/**
* 商品 SKU 编号
*
@@ -55,14 +63,12 @@ public class TradeOrderItemDO extends BaseDO {
*/
private Long skuId;
/**
* 规格值数组JSON 格式
* 属性数组JSON 格式
*
* 冗余 ProductSkuDO 的 properties 字段
*/
@TableField(typeHandler = PropertyTypeHandler.class)
private List<Property> properties;
/**
* 商品名称
*/
private String name;
/**
* 商品图片
*/
@@ -132,32 +138,23 @@ public class TradeOrderItemDO extends BaseDO {
// ========== 营销基本信息 ==========
// ========== 退款基本信息 ==========
// TODO 芋艿:在捉摸一下
// ========== 售后基本信息 ==========
/**
* 退款状态 TODO
* 售后状态
*
* 枚举 {@link TradeOrderItemRefundStatusEnum}
* 枚举 {@link TradeOrderItemAfterSaleStatusEnum}
*
* @see 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;
/**
* 商品属性
*/
@Data
public static class Property {
public static class Property implements Serializable {
/**
* 属性编号

View File

@@ -1,148 +0,0 @@
package cn.iocoder.yudao.module.trade.dal.dataobject.refund;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
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.TradeOrderRefundStatusEnum;
import cn.iocoder.yudao.module.trade.enums.refund.TradeRefundTypeEnum;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
import java.util.List;
/**
* 交易退款,用于处理 {@link TradeOrderDO} 交易订单的退货换流程
*/
// TODO 芋艿:需要调整下每个字段的命名;未完全实现;
@TableName(value = "trade_refund")
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
public class TradeRefundDO extends BaseDO {
/**
* 交易退款编号,主键自增
*/
@Deprecated
private Long id;
/**
* 退款流水号
*
* 例如说1146347329394184195
*/
private String sn;
/**
* 退款状态
*
* 枚举 {@link TradeOrderRefundStatusEnum}
*/
private Integer status;
/**
* 用户编号
*
* 关联 MemberUserDO 的 id 编号
*/
private Long userId;
/**
* 用户手机
*/
private String userMobile;
/**
* 申请类型
*
* 枚举 {@link TradeRefundTypeEnum}
*/
private Integer type;
/**
* 用户售后说明
*/
private String reasonMemo; // buyer_msg
/**
* 用户售后凭证图片的地址数组
*
* 数组,以逗号分隔
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private List<String> reasonPicUrls; // photo_files
// ========== 商家相关 ==========
/**
* 商家处理时间
*/
private LocalDateTime handleTime; // handel_time
/**
* 商家拒绝理由
*/
private String rejectReasonMemo; // seller_msg
// ========== 交易订单相关 ==========
/**
* 交易订单编号
*
* 外键 {@link TradeOrderDO#getId()}
*/
private Long tradeOrderId;
/**
* 交易订单项编号
*
* 关联 {@link TradeOrderItemDO#getId()}
* 如果全部退款,则该值设置为 0 即可
*/
private Long tradeOrderItemId;
/**
* 商品 SKU 编号
*/
@Deprecated
private Integer skuId;
/**
* 退货商品数量
*/
private Integer stock; // goods_num
// ========== 退款相关 ==========
/**
* 退款金额,单位:分。
*/
private Integer refundPrice; // refund_amount
/**
* 支付退款编号
*
* 对接 pay-module-biz 支付服务的退款订单编号,即 PayRefundDO 的 id 编号
*/
private Long payRefundId;
// TODO 芋艿看看是否有必要冗余order_number、order_amount、flow_trade_no、out_refund_no、pay_type、return_money_sts、refund_time
// ========== 退货相关 ==========
/**
* 退货物流公司编号
*
* 关联 ExpressDO 的 id 编号
*/
private Long returnExpressId; // express_name
/**
* 退货物流单号
*/
private String returnExpressNo; // express_no
/**
* 退货时间
*/
private LocalDateTime returnDate; // ship_time
// ========== 收获相关 ==========
/**
* 收获备注
*/
private String receiveMemo; // receive_message
/**
* 收货时间
*/
private LocalDateTime receiveDate; // receive_time
}

View File

@@ -0,0 +1,9 @@
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.TradeAfterSaleLogDO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface TradeAfterSaleLogMapper extends BaseMapperX<TradeAfterSaleLogDO> {
}

View File

@@ -0,0 +1,35 @@
package cn.iocoder.yudao.module.trade.dal.mysql.aftersale;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.TradeAfterSalePageReqVO;
import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.TradeAfterSaleDO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface TradeAfterSaleMapper extends BaseMapperX<TradeAfterSaleDO> {
default PageResult<TradeAfterSaleDO> selectPage(TradeAfterSalePageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<TradeAfterSaleDO>()
.likeIfPresent(TradeAfterSaleDO::getNo, reqVO.getNo())
.eqIfPresent(TradeAfterSaleDO::getStatus, reqVO.getStatus())
.eqIfPresent(TradeAfterSaleDO::getType, reqVO.getType())
.eqIfPresent(TradeAfterSaleDO::getWay, reqVO.getWay())
.likeIfPresent(TradeAfterSaleDO::getOrderNo, reqVO.getOrderNo())
.likeIfPresent(TradeAfterSaleDO::getSpuName, reqVO.getSpuName())
.betweenIfPresent(TradeAfterSaleDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(TradeAfterSaleDO::getId));
}
default int updateByIdAndStatus(Long id, Integer status, TradeAfterSaleDO update) {
return update(update, new LambdaUpdateWrapper<TradeAfterSaleDO>()
.eq(TradeAfterSaleDO::getId, id).eq(TradeAfterSaleDO::getStatus, status));
}
default TradeAfterSaleDO selectByPayRefundId(Long payRefundId) {
return selectOne(TradeAfterSaleDO::getPayRefundId, payRefundId);
}
}

View File

@@ -0,0 +1,27 @@
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;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
@Mapper
public interface TradeOrderItemMapper extends BaseMapperX<TradeOrderItemDO> {
default int updateAfterSaleStatus(Long id, Integer oldAfterSaleStatus, Integer newAfterSaleStatus) {
return update(new TradeOrderItemDO().setAfterSaleStatus(newAfterSaleStatus),
new LambdaUpdateWrapper<>(new TradeOrderItemDO().setId(id).setAfterSaleStatus(oldAfterSaleStatus)));
}
default List<TradeOrderItemDO> selectListByOrderId(Long orderId) {
return selectList(TradeOrderItemDO::getOrderId, orderId);
}
default List<TradeOrderItemDO> selectListByOrderId(Collection<Long> orderIds) {
return selectList(TradeOrderItemDO::getOrderId, orderIds);
}
}

View File

@@ -1,9 +1,46 @@
package cn.iocoder.yudao.module.trade.dal.mysql.order;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderPageReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderPageReqVO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.Set;
@Mapper
public interface TradeOrderMapper extends BaseMapperX<TradeOrderDO> {
default int updateByIdAndStatus(Long id, Integer status, TradeOrderDO update) {
return update(update, new LambdaUpdateWrapper<TradeOrderDO>()
.eq(TradeOrderDO::getId, id).eq(TradeOrderDO::getStatus, status));
}
default TradeOrderDO selectByIdAndUserId(Long id, Long userId) {
return selectOne(TradeOrderDO::getId, id, TradeOrderDO::getUserId, userId);
}
default PageResult<TradeOrderDO> selectPage(TradeOrderPageReqVO reqVO, Set<Long> userIds) {
return selectPage(reqVO, new LambdaQueryWrapperX<TradeOrderDO>()
.likeIfPresent(TradeOrderDO::getNo, reqVO.getNo())
.eqIfPresent(TradeOrderDO::getUserId, reqVO.getUserId())
.inIfPresent(TradeOrderDO::getUserId, userIds)
.likeIfPresent(TradeOrderDO::getReceiverName, reqVO.getReceiverName())
.likeIfPresent(TradeOrderDO::getReceiverMobile, reqVO.getReceiverMobile())
.eqIfPresent(TradeOrderDO::getType, reqVO.getType())
.eqIfPresent(TradeOrderDO::getStatus, reqVO.getStatus())
.eqIfPresent(TradeOrderDO::getPayChannelCode, reqVO.getPayChannelCode())
.betweenIfPresent(TradeOrderDO::getCreateTime, reqVO.getCreateTime()));
}
default PageResult<TradeOrderDO> selectPage(AppTradeOrderPageReqVO reqVO, Long userId) {
return selectPage(reqVO, new LambdaQueryWrapperX<TradeOrderDO>()
.eq(TradeOrderDO::getUserId, userId)
.eqIfPresent(TradeOrderDO::getStatus, reqVO.getStatus())
.orderByDesc(TradeOrderDO::getId)); // TODO 芋艿:未来不同的 status不同的排序
}
}

View File

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

View File

@@ -0,0 +1,94 @@
package cn.iocoder.yudao.module.trade.service.aftersale;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.TradeAfterSaleDisagreeReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.TradeAfterSalePageReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.TradeAfterSaleRefuseReqVO;
import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppTradeAfterSaleCreateReqVO;
import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppTradeAfterSaleDeliveryReqVO;
import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.TradeAfterSaleDO;
/**
* 交易售后 Service 接口
*
* @author 芋道源码
*/
public interface TradeAfterSaleService {
/**
* 获得交易售后分页
*
* @param pageReqVO 分页查询
* @return 交易售后分页
*/
PageResult<TradeAfterSaleDO> getAfterSalePage(TradeAfterSalePageReqVO pageReqVO);
/**
* 【会员】创建交易售后
* <p>
* 一般是用户发起售后请求
*
* @param userId 会员用户编号
* @param createReqVO 创建 Request 信息
* @return 交易售后编号
*/
Long createAfterSale(Long userId, AppTradeAfterSaleCreateReqVO createReqVO);
/**
* 【管理员】同意交易售后
*
* @param userId 管理员用户编号
* @param id 交易售后编号
*/
void agreeAfterSale(Long userId, Long id);
/**
* 【管理员】拒绝交易售后
*
* @param userId 管理员用户编号
* @param auditReqVO 审批 Request 信息
*/
void disagreeAfterSale(Long userId, TradeAfterSaleDisagreeReqVO auditReqVO);
/**
* 【会员】退回货物
*
* @param userId 会员用户编号
* @param deliveryReqVO 退货 Request 信息
*/
void deliveryAfterSale(Long userId, AppTradeAfterSaleDeliveryReqVO deliveryReqVO);
/**
* 【管理员】确认收货
*
* @param userId 管理员编号
* @param id 交易售后编号
*/
void receiveAfterSale(Long userId, Long id);
/**
* 【管理员】拒绝收货
*
* @param userId 管理员用户编号
* @param refuseReqVO 拒绝收货 Request 信息
*/
void refuseAfterSale(Long userId, TradeAfterSaleRefuseReqVO refuseReqVO);
/**
* 【管理员】确认退款
*
* @param userId 管理员用户编号
* @param userIp 管理员用户 IP
* @param id 售后编号
*/
void refundAfterSale(Long userId, String userIp, Long id);
/**
* 【会员】取消售后
*
* @param userId 会员用户编号
* @param id 交易售后编号
*/
void cancelAfterSale(Long userId, Long id);
}

View File

@@ -0,0 +1,392 @@
package cn.iocoder.yudao.module.trade.service.aftersale;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.module.pay.api.refund.PayRefundApi;
import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.TradeAfterSaleDisagreeReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.TradeAfterSalePageReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.TradeAfterSaleRefuseReqVO;
import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppTradeAfterSaleCreateReqVO;
import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppTradeAfterSaleDeliveryReqVO;
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.aftersale.TradeAfterSaleLogDO;
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.TradeAfterSaleLogMapper;
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.aftersale.TradeAfterSaleWayEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderItemAfterSaleStatusEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
import cn.iocoder.yudao.module.trade.service.order.TradeOrderService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.time.LocalDateTime;
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;
@Resource
private TradeAfterSaleLogMapper tradeAfterSaleLogMapper;
@Resource
private PayRefundApi payRefundApi;
@Resource
private TradeOrderProperties tradeOrderProperties;
@Override
public PageResult<TradeAfterSaleDO> getAfterSalePage(TradeAfterSalePageReqVO pageReqVO) {
return tradeAfterSaleMapper.selectPage(pageReqVO);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Long createAfterSale(Long userId, AppTradeAfterSaleCreateReqVO createReqVO) {
// 第一步,前置校验
TradeOrderItemDO tradeOrderItem = validateOrderItemApplicable(userId, createReqVO);
// 第二步,存储交易售后
TradeAfterSaleDO afterSale = createAfterSale(createReqVO, tradeOrderItem);
return afterSale.getId();
}
/**
* 校验交易订单项是否可以申请售后
*
* @param userId 用户编号
* @param createReqVO 售后创建信息
* @return 交易订单项
*/
private TradeOrderItemDO validateOrderItemApplicable(Long userId, AppTradeAfterSaleCreateReqVO 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);
}
// 申请的退款金额,不能超过商品的价格
if (createReqVO.getRefundPrice() > 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);
}
// TODO 芋艿:超过一定时间,不允许售后
// 已取消,无法发起售后
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.getWay().equals(TradeAfterSaleWayEnum.RETURN_AND_REFUND.getWay())
&& !TradeOrderStatusEnum.haveDelivered(order.getStatus())) {
throw exception(AFTER_SALE_CREATE_FAIL_ORDER_STATUS_NO_DELIVERED);
}
return orderItem;
}
private TradeAfterSaleDO createAfterSale(AppTradeAfterSaleCreateReqVO createReqVO,
TradeOrderItemDO orderItem) {
// 创建售后单
TradeAfterSaleDO afterSale = TradeAfterSaleConvert.INSTANCE.convert(createReqVO, orderItem);
afterSale.setNo(RandomUtil.randomString(10)); // TODO 芋艿:优化 no 生成逻辑
afterSale.setStatus(TradeAfterSaleStatusEnum.APPLY.getStatus());
// 标记是售中还是售后
TradeOrderDO order = tradeOrderService.getOrder(orderItem.getUserId(), orderItem.getOrderId());
afterSale.setOrderNo(order.getNo()); // 记录 orderNo 订单流水,方便后续检索
afterSale.setType(TradeOrderStatusEnum.isCompleted(order.getStatus())
? TradeAfterSaleTypeEnum.AFTER_SALE.getType() : TradeAfterSaleTypeEnum.IN_SALE.getType());
// TODO 退还积分
tradeAfterSaleMapper.insert(afterSale);
// 更新交易订单项的售后状态
tradeOrderService.updateOrderItemAfterSaleStatus(orderItem.getId(),
TradeOrderItemAfterSaleStatusEnum.NONE.getStatus(),
TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(), null);
// 记录售后日志
createAfterSaleLog(orderItem.getUserId(), UserTypeEnum.MEMBER.getValue(),
afterSale, null, afterSale.getStatus());
// TODO 发送售后消息
return afterSale;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void agreeAfterSale(Long userId, Long id) {
// 校验售后单存在,并状态未审批
TradeAfterSaleDO afterSale = validateAfterSaleAuditable(id);
// 更新售后单的状态
// 情况一:退款:标记为 WAIT_REFUND 状态。后续等退款发起成功后,在标记为 COMPLETE 状态
// 情况二:退货退款:需要等用户退货后,才能发起退款
Integer newStatus = afterSale.getType().equals(TradeAfterSaleWayEnum.REFUND.getWay()) ?
TradeAfterSaleStatusEnum.WAIT_REFUND.getStatus() : TradeAfterSaleStatusEnum.SELLER_AGREE.getStatus();
updateAfterSaleStatus(afterSale.getId(), TradeAfterSaleStatusEnum.APPLY.getStatus(), new TradeAfterSaleDO()
.setStatus(newStatus).setAuditUserId(userId).setAuditTime(LocalDateTime.now()));
// 记录售后日志
createAfterSaleLog(userId, UserTypeEnum.ADMIN.getValue(),
afterSale, afterSale.getStatus(), newStatus);
// TODO 发送售后消息
}
@Override
@Transactional(rollbackFor = Exception.class)
public void disagreeAfterSale(Long userId, TradeAfterSaleDisagreeReqVO auditReqVO) {
// 校验售后单存在,并状态未审批
TradeAfterSaleDO afterSale = validateAfterSaleAuditable(auditReqVO.getId());
// 更新售后单的状态
Integer newStatus = TradeAfterSaleStatusEnum.SELLER_DISAGREE.getStatus();
updateAfterSaleStatus(afterSale.getId(), TradeAfterSaleStatusEnum.APPLY.getStatus(), new TradeAfterSaleDO()
.setStatus(newStatus).setAuditUserId(userId).setAuditTime(LocalDateTime.now())
.setAuditReason(auditReqVO.getAuditReason()));
// 记录售后日志
createAfterSaleLog(userId, UserTypeEnum.ADMIN.getValue(),
afterSale, afterSale.getStatus(), newStatus);
// TODO 发送售后消息
// 更新交易订单项的售后状态为【未申请】
tradeOrderService.updateOrderItemAfterSaleStatus(afterSale.getOrderItemId(),
TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(),
TradeOrderItemAfterSaleStatusEnum.NONE.getStatus(), null);
}
/**
* 校验售后单是否可审批(同意售后、拒绝售后)
*
* @param id 售后编号
* @return 售后单
*/
private TradeAfterSaleDO validateAfterSaleAuditable(Long id) {
TradeAfterSaleDO afterSale = tradeAfterSaleMapper.selectById(id);
if (afterSale == null) {
throw exception(AFTER_SALE_NOT_FOUND);
}
if (ObjectUtil.notEqual(afterSale.getStatus(), TradeAfterSaleStatusEnum.APPLY.getStatus())) {
throw exception(AFTER_SALE_AUDIT_FAIL_STATUS_NOT_APPLY);
}
return afterSale;
}
private void updateAfterSaleStatus(Long id, Integer status, TradeAfterSaleDO updateObj) {
int updateCount = tradeAfterSaleMapper.updateByIdAndStatus(id, status, updateObj);
if (updateCount == 0) {
throw exception(AFTER_SALE_UPDATE_STATUS_FAIL);
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deliveryAfterSale(Long userId, AppTradeAfterSaleDeliveryReqVO deliveryReqVO) {
// 校验售后单存在,并状态未退货
TradeAfterSaleDO afterSale = tradeAfterSaleMapper.selectById(deliveryReqVO.getId());
if (afterSale == null) {
throw exception(AFTER_SALE_NOT_FOUND);
}
if (ObjectUtil.notEqual(afterSale.getStatus(), TradeAfterSaleStatusEnum.SELLER_AGREE.getStatus())) {
throw exception(AFTER_SALE_DELIVERY_FAIL_STATUS_NOT_SELLER_AGREE);
}
// 更新售后单的物流信息
updateAfterSaleStatus(afterSale.getId(), TradeAfterSaleStatusEnum.SELLER_AGREE.getStatus(), new TradeAfterSaleDO()
.setStatus(TradeAfterSaleStatusEnum.BUYER_DELIVERY.getStatus())
.setLogisticsId(deliveryReqVO.getLogisticsId()).setLogisticsNo(deliveryReqVO.getLogisticsNo())
.setDeliveryTime(deliveryReqVO.getDeliveryTime()));
// 记录售后日志
createAfterSaleLog(userId, UserTypeEnum.MEMBER.getValue(),
afterSale, afterSale.getStatus(), TradeAfterSaleStatusEnum.BUYER_DELIVERY.getStatus());
// TODO 发送售后消息
}
@Override
@Transactional(rollbackFor = Exception.class)
public void receiveAfterSale(Long userId, Long id) {
// 校验售后单存在,并状态为已退货
TradeAfterSaleDO afterSale = validateAfterSaleReceivable(id);
// 更新售后单的状态
updateAfterSaleStatus(afterSale.getId(), TradeAfterSaleStatusEnum.BUYER_DELIVERY.getStatus(), new TradeAfterSaleDO()
.setStatus(TradeAfterSaleStatusEnum.WAIT_REFUND.getStatus()).setReceiveTime(LocalDateTime.now()));
// 记录售后日志
createAfterSaleLog(userId, UserTypeEnum.ADMIN.getValue(),
afterSale, afterSale.getStatus(), TradeAfterSaleStatusEnum.WAIT_REFUND.getStatus());
// TODO 发送售后消息
}
@Override
@Transactional(rollbackFor = Exception.class)
public void refuseAfterSale(Long userId, TradeAfterSaleRefuseReqVO refuseReqVO) {
// 校验售后单存在,并状态为已退货
TradeAfterSaleDO afterSale = tradeAfterSaleMapper.selectById(refuseReqVO.getId());
if (afterSale == null) {
throw exception(AFTER_SALE_NOT_FOUND);
}
if (ObjectUtil.notEqual(afterSale.getStatus(), TradeAfterSaleStatusEnum.BUYER_DELIVERY.getStatus())) {
throw exception(AFTER_SALE_CONFIRM_FAIL_STATUS_NOT_BUYER_DELIVERY);
}
// 更新售后单的状态
updateAfterSaleStatus(afterSale.getId(), TradeAfterSaleStatusEnum.BUYER_DELIVERY.getStatus(), new TradeAfterSaleDO()
.setStatus(TradeAfterSaleStatusEnum.SELLER_REFUSE.getStatus()).setReceiveTime(LocalDateTime.now())
.setReceiveReason(refuseReqVO.getRefuseMemo()));
// 记录售后日志
createAfterSaleLog(userId, UserTypeEnum.ADMIN.getValue(),
afterSale, afterSale.getStatus(), TradeAfterSaleStatusEnum.SELLER_REFUSE.getStatus());
// TODO 发送售后消息
// 更新交易订单项的售后状态为【未申请】
tradeOrderService.updateOrderItemAfterSaleStatus(afterSale.getOrderItemId(),
TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(),
TradeOrderItemAfterSaleStatusEnum.NONE.getStatus(), null);
}
/**
* 校验售后单是否可收货,即处于买家已发货
*
* @param id 售后编号
* @return 售后单
*/
private TradeAfterSaleDO validateAfterSaleReceivable(Long id) {
TradeAfterSaleDO afterSale = tradeAfterSaleMapper.selectById(id);
if (afterSale == null) {
throw exception(AFTER_SALE_NOT_FOUND);
}
if (ObjectUtil.notEqual(afterSale.getStatus(), TradeAfterSaleStatusEnum.BUYER_DELIVERY.getStatus())) {
throw exception(AFTER_SALE_CONFIRM_FAIL_STATUS_NOT_BUYER_DELIVERY);
}
return afterSale;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void refundAfterSale(Long userId, String userIp, Long id) {
// 校验售后单的状态,并状态待退款
TradeAfterSaleDO afterSale = tradeAfterSaleMapper.selectByPayRefundId(id);
if (afterSale == null) {
throw exception(AFTER_SALE_NOT_FOUND);
}
if (ObjectUtil.notEqual(afterSale.getStatus(), TradeAfterSaleStatusEnum.WAIT_REFUND.getStatus())) {
throw exception(AFTER_SALE_REFUND_FAIL_STATUS_NOT_WAIT_REFUND);
}
// 发起退款单。注意,需要在事务提交后,再进行发起,避免重复发起
createPayRefund(userIp, afterSale);
// 更新售后单的状态为【已完成】
updateAfterSaleStatus(afterSale.getId(), TradeAfterSaleStatusEnum.WAIT_REFUND.getStatus(), new TradeAfterSaleDO()
.setStatus(TradeAfterSaleStatusEnum.COMPLETE.getStatus()).setRefundTime(LocalDateTime.now()));
// 记录售后日志
createAfterSaleLog(userId, UserTypeEnum.ADMIN.getValue(),
afterSale, afterSale.getStatus(), TradeAfterSaleStatusEnum.COMPLETE.getStatus());
// TODO 发送售后消息
// 更新交易订单项的售后状态为【已完成】
tradeOrderService.updateOrderItemAfterSaleStatus(afterSale.getOrderItemId(),
TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(),
TradeOrderItemAfterSaleStatusEnum.SUCCESS.getStatus(), afterSale.getRefundPrice());
}
private void createPayRefund(String userIp, TradeAfterSaleDO afterSale) {
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
// 创建退款单
PayRefundCreateReqDTO createReqDTO = TradeAfterSaleConvert.INSTANCE.convert(userIp, afterSale, tradeOrderProperties);
Long payRefundId = payRefundApi.createPayRefund(createReqDTO);
// 更新售后单的退款单号
tradeAfterSaleMapper.updateById(new TradeAfterSaleDO().setId(afterSale.getId()).setPayRefundId(payRefundId));
}
});
}
@Override
public void cancelAfterSale(Long userId, Long id) {
// 校验售后单的状态,并状态待退款
TradeAfterSaleDO afterSale = tradeAfterSaleMapper.selectByPayRefundId(id);
if (afterSale == null) {
throw exception(AFTER_SALE_NOT_FOUND);
}
if (ObjectUtils.equalsAny(afterSale.getStatus(), TradeAfterSaleStatusEnum.APPLY.getStatus(),
TradeAfterSaleStatusEnum.SELLER_AGREE.getStatus())) {
throw exception(AFTER_SALE_CANCEL_FAIL_STATUS_NOT_APPLY_OR_AGREE);
}
// 更新售后单的状态为【已取消】
updateAfterSaleStatus(afterSale.getId(), afterSale.getStatus(), new TradeAfterSaleDO()
.setStatus(TradeAfterSaleStatusEnum.BUYER_CANCEL.getStatus()));
// 记录售后日志
createAfterSaleLog(userId, UserTypeEnum.MEMBER.getValue(),
afterSale, afterSale.getStatus(), TradeAfterSaleStatusEnum.BUYER_CANCEL.getStatus());
// TODO 发送售后消息
// 更新交易订单项的售后状态为【未申请】
tradeOrderService.updateOrderItemAfterSaleStatus(afterSale.getOrderItemId(),
TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(),
TradeOrderItemAfterSaleStatusEnum.NONE.getStatus(), null);
}
private void createAfterSaleLog(Long userId, Integer userType, TradeAfterSaleDO afterSale,
Integer beforeStatus, Integer afterStatus) {
TradeAfterSaleLogDO afterSaleLog = new TradeAfterSaleLogDO().setUserId(userId).setUserType(userType)
.setAfterSaleId(afterSale.getId()).setOrderId(afterSale.getOrderId())
.setOrderItemId(afterSale.getOrderItemId()).setBeforeStatus(beforeStatus).setAfterStatus(afterStatus)
.setContent(TradeAfterSaleStatusEnum.valueOf(afterStatus).getContent());
tradeAfterSaleLogMapper.insert(afterSaleLog);
}
}

View File

@@ -1,6 +1,17 @@
package cn.iocoder.yudao.module.trade.service.order;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderPageReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderPageReqVO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
import java.util.Collection;
import java.util.List;
import static java.util.Collections.singleton;
/**
* 交易订单 Service 接口
@@ -10,14 +21,122 @@ import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreate
*/
public interface TradeOrderService {
// =================== Order ===================
/**
* 创建交易订单
* 【会员】创建交易订单
*
* @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 id 交易订单编号
* @param payOrderId 支付订单编号
*/
void updateOrderPaid(Long id, Long payOrderId);
/**
* 【管理员】发货交易订单
*
* @param userId 管理员编号
* @param deliveryReqVO 发货请求
*/
void deliveryOrder(Long userId, TradeOrderDeliveryReqVO deliveryReqVO);
/**
* 【会员】收货交易订单
*
* @param userId 用户编号
* @param id 订单编号
*/
void receiveOrder(Long userId, Long id);
/**
* 获得指定编号的交易订单
*
* @param id 交易订单编号
* @return 交易订单
*/
TradeOrderDO getOrder(Long id);
/**
* 获得指定用户,指定的交易订单
*
* @param userId 用户编号
* @param id 交易订单编号
* @return 交易订单
*/
TradeOrderDO getOrder(Long userId, Long id);
/**
* 【管理员】获得交易订单分页
*
* @param reqVO 分页请求
* @return 交易订单
*/
PageResult<TradeOrderDO> getOrderPage(TradeOrderPageReqVO reqVO);
/**
* 【会员】获得交易订单分页
*
* @param userId 用户编号
* @param reqVO 分页请求
* @return 交易订单
*/
PageResult<TradeOrderDO> getOrderPage(Long userId, AppTradeOrderPageReqVO reqVO);
// =================== Order Item ===================
/**
* 获得指定用户,指定的交易订单项
*
* @param userId 用户编号
* @param itemId 交易订单项编号
* @return 交易订单项
*/
TradeOrderItemDO getOrderItem(Long userId, Long itemId);
/**
* 更新交易订单项的售后状态
*
* @param id 交易订单项编号
* @param oldAfterSaleStatus 当前售后状态;如果不符,更新后会抛出异常
* @param newAfterSaleStatus 目标售后状态
* @param refundPrice 退款金额;当订单项退款成功时,必须传递该值
*/
void updateOrderItemAfterSaleStatus(Long id, Integer oldAfterSaleStatus,
Integer newAfterSaleStatus, Integer refundPrice);
/**
* 根据交易订单项编号数组,查询交易订单项
*
* @param ids 交易订单项编号数组
* @return 交易订单项数组
*/
List<TradeOrderItemDO> getOrderItemList(Collection<Long> ids);
/**
* 根据交易订单编号,查询交易订单项
*
* @param orderId 交易订单编号
* @return 交易订单项数组
*/
default List<TradeOrderItemDO> getOrderItemListByOrderId(Long orderId) {
return getOrderItemListByOrderId(singleton(orderId));
}
/**
* 根据交易订单编号数组,查询交易订单项
*
* @param orderIds 交易订单编号数组
* @return 交易订单项数组
*/
List<TradeOrderItemDO> getOrderItemListByOrderId(Collection<Long> orderIds);
}

View File

@@ -1,14 +1,23 @@
package cn.iocoder.yudao.module.trade.service.order;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.core.KeyValue;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.enums.TerminalEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.module.member.api.address.AddressApi;
import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.pay.api.order.PayOrderApi;
import cn.iocoder.yudao.module.pay.api.order.PayOrderInfoCreateReqDTO;
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO;
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
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.ProductSkuUpdateStockReqDTO;
@@ -19,31 +28,31 @@ import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi;
import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO;
import cn.iocoder.yudao.module.promotion.api.price.PriceApi;
import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateRespDTO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderPageReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO.Item;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderPageReqVO;
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.TradeOrderItemMapper;
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.ErrorCodeConstants;
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;
import cn.iocoder.yudao.module.trade.enums.order.*;
import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.time.LocalDateTime;
import java.util.*;
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;
import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.PAY_ORDER_NOT_FOUND;
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*;
/**
* 交易订单 Service 实现类
@@ -52,6 +61,7 @@ import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.ORDER_CREAT
* @since 2022-08-26
*/
@Service
@Slf4j
public class TradeOrderServiceImpl implements TradeOrderService {
@Resource
@@ -71,13 +81,17 @@ public class TradeOrderServiceImpl implements TradeOrderService {
private AddressApi addressApi;
@Resource
private CouponApi couponApi;
@Resource
private MemberUserApi memberUserApi;
@Resource
private TradeOrderProperties tradeOrderProperties;
// =================== Order ===================
@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 检查:可售状态
@@ -109,7 +123,7 @@ public class TradeOrderServiceImpl implements TradeOrderService {
List<ProductSkuRespDTO> skus = productSkuApi.getSkuList(convertSet(items, Item::getSkuId));
// SKU 不存在
if (items.size() != skus.size()) {
throw exception(ErrorCodeConstants.ORDER_CREATE_SKU_NOT_FOUND);
throw exception(ORDER_CREATE_SKU_NOT_FOUND);
}
// 校验是否禁用 or 库存不足
Map<Long, ProductSkuRespDTO> skuMap = convertMap(skus, ProductSkuRespDTO::getId);
@@ -167,14 +181,14 @@ 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.setAfterSaleStatus(TradeOrderAfterSaleStatusEnum.NONE.getStatus());
tradeOrderDO.setProductCount(getSumValue(order.getItems(), PriceCalculateRespDTO.OrderItem::getCount, Integer::sum));
tradeOrderDO.setTerminal(TerminalEnum.H5.getTerminal()); // todo 数据来源?
tradeOrderDO.setAdjustPrice(0).setPayed(false); // 支付信息
tradeOrderDO.setDeliveryStatus(false); // 物流信息
tradeOrderDO.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus()).setRefundPrice(0); // 退款信息
tradeOrderDO.setDeliveryStatus(TradeOrderDeliveryStatusEnum.UNDELIVERED.getStatus()); // 物流信息
tradeOrderDO.setAfterSaleStatus(TradeOrderAfterSaleStatusEnum.NONE.getStatus()).setRefundPrice(0); // 退款信息
tradeOrderMapper.insert(tradeOrderDO);
return tradeOrderDO;
}
@@ -220,12 +234,297 @@ public class TradeOrderServiceImpl implements TradeOrderService {
private void createPayOrder(TradeOrderDO tradeOrderDO, List<TradeOrderItemDO> tradeOrderItemDOs,
List<ProductSpuRespDTO> spus) {
// 创建支付单,用于后续的支付
PayOrderInfoCreateReqDTO payOrderCreateReqDTO = TradeOrderConvert.INSTANCE.convert(
PayOrderCreateReqDTO payOrderCreateReqDTO = TradeOrderConvert.INSTANCE.convert(
tradeOrderDO, tradeOrderItemDOs, spus, tradeOrderProperties);
Long payOrderId = payOrderApi.createPayOrder(payOrderCreateReqDTO);
Long payOrderId = payOrderApi.createOrder(payOrderCreateReqDTO);
// 更新到交易单上
tradeOrderMapper.updateById(new TradeOrderDO().setId(tradeOrderDO.getId()).setPayOrderId(payOrderId));
}
@Override
public void updateOrderPaid(Long id, Long payOrderId) {
// 校验并获得交易订单(可支付)
KeyValue<TradeOrderDO, PayOrderRespDTO> orderResult = validateOrderPayable(id, payOrderId);
TradeOrderDO order = orderResult.getKey();
PayOrderRespDTO payOrder = orderResult.getValue();
// 更新 TradeOrderDO 状态为已支付,等待发货
int updateCount = tradeOrderMapper.updateByIdAndStatus(id, order.getStatus(),
new TradeOrderDO().setStatus(TradeOrderStatusEnum.UNDELIVERED.getStatus()).setPayed(true)
.setPayTime(LocalDateTime.now()).setPayChannelCode(payOrder.getChannelCode()));
if (updateCount == 0) {
throw exception(ORDER_UPDATE_PAID_STATUS_NOT_UNPAID);
}
// TODO 芋艿:发送订单变化的消息
// TODO 芋艿:发送站内信
// TODO 芋艿OrderLog
}
/**
* 校验交易订单满足被支付的条件
*
* 1. 交易订单未支付
* 2. 支付单已支付
*
* @param id 交易订单编号
* @param payOrderId 支付订单编号
* @return 交易订单
*/
private KeyValue<TradeOrderDO, PayOrderRespDTO> validateOrderPayable(Long id, Long payOrderId) {
// 校验订单是否存在
TradeOrderDO order = tradeOrderMapper.selectById(id);
if (order == null) {
throw exception(ORDER_NOT_FOUND);
}
// 校验订单未支付
if (!TradeOrderStatusEnum.isUnpaid(order.getStatus()) || order.getPayed()) {
log.error("[validateOrderPaid][order({}) 不处于待支付状态请进行处理order 数据是:{}]",
id, JsonUtils.toJsonString(order));
throw exception(ORDER_UPDATE_PAID_STATUS_NOT_UNPAID);
}
// 校验支付订单匹配
if (ObjectUtil.notEqual(order.getPayOrderId(), payOrderId)) { // 支付单号
log.error("[validateOrderPaid][order({}) 支付单不匹配({})请进行处理order 数据是:{}]",
id, payOrderId, JsonUtils.toJsonString(order));
throw exception(ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR);
}
// 校验支付单是否存在
PayOrderRespDTO payOrder = payOrderApi.getOrder(payOrderId);
if (payOrder == null) {
log.error("[validateOrderPaid][order({}) payOrder({}) 不存在,请进行处理!]", id, payOrderId);
throw exception(PAY_ORDER_NOT_FOUND);
}
// 校验支付单已支付
if (!PayOrderStatusEnum.isSuccess(payOrder.getStatus())) {
log.error("[validateOrderPaid][order({}) payOrder({}) 未支付请进行处理payOrder 数据是:{}]",
id, payOrderId, JsonUtils.toJsonString(payOrder));
throw exception(ORDER_UPDATE_PAID_FAIL_PAY_ORDER_STATUS_NOT_SUCCESS);
}
// 校验支付金额一致
if (ObjectUtil.notEqual(payOrder.getAmount(), order.getPayPrice())) {
log.error("[validateOrderPaid][order({}) payOrder({}) 支付金额不匹配请进行处理order 数据是:{}payOrder 数据是:{}]",
id, payOrderId, JsonUtils.toJsonString(order), JsonUtils.toJsonString(payOrder));
throw exception(ORDER_UPDATE_PAID_FAIL_PAY_PRICE_NOT_MATCH);
}
// 校验支付订单匹配(二次)
if (ObjectUtil.notEqual(payOrder.getMerchantOrderId(), id.toString())) {
log.error("[validateOrderPaid][order({}) 支付单不匹配({})请进行处理payOrder 数据是:{}]",
id, payOrderId, JsonUtils.toJsonString(payOrder));
throw exception(ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR);
}
return new KeyValue<>(order, payOrder);
}
// TODO 芋艿:如果无需发货,需要怎么存储?
@Override
public void deliveryOrder(Long userId, TradeOrderDeliveryReqVO deliveryReqVO) {
// 校验并获得交易订单(可发货)
TradeOrderDO order = validateOrderDeliverable(deliveryReqVO.getId());
// TODO 芋艿logisticsId 校验存在
// 更新 TradeOrderDO 状态为已发货,等待收货
int updateCount = tradeOrderMapper.updateByIdAndStatus(order.getId(), order.getStatus(),
new TradeOrderDO().setStatus(TradeOrderStatusEnum.DELIVERED.getStatus())
.setLogisticsId(deliveryReqVO.getLogisticsId()).setLogisticsNo(deliveryReqVO.getLogisticsNo())
.setDeliveryStatus(TradeOrderDeliveryStatusEnum.DELIVERED.getStatus()).setDeliveryTime(LocalDateTime.now()));
if (updateCount == 0) {
throw exception(ORDER_DELIVERY_FAIL_STATUS_NOT_UNDELIVERED);
}
// TODO 芋艿:发送订单变化的消息
// TODO 芋艿:发送站内信
// TODO 芋艿OrderLog
// TODO 设计like是否要单独一个 delivery 发货单表???
// TODO 设计niu要不要支持一个订单下多个 order item 单独发货,类似有赞
// TODO 设计lili是不是发货后才支持售后
}
/**
* 校验交易订单满足被发货的条件
*
* 1. 交易订单未发货
*
* @param id 交易订单编号
* @return 交易订单
*/
private TradeOrderDO validateOrderDeliverable(Long id) {
// 校验订单是否存在
TradeOrderDO order = tradeOrderMapper.selectById(id);
if (order == null) {
throw exception(ORDER_NOT_FOUND);
}
// 校验订单是否是待发货状态
if (!TradeOrderStatusEnum.isUndelivered(order.getStatus())
|| ObjectUtil.notEqual(order.getDeliveryStatus(), TradeOrderDeliveryStatusEnum.UNDELIVERED.getStatus())) {
throw exception(ORDER_DELIVERY_FAIL_STATUS_NOT_UNDELIVERED);
}
return order;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void receiveOrder(Long userId, Long id) {
// 校验并获得交易订单(可收货)
TradeOrderDO order = validateOrderReceivable(userId, id);
// 更新 TradeOrderDO 状态为已完成
int updateCount = tradeOrderMapper.updateByIdAndStatus(order.getId(), order.getStatus(),
new TradeOrderDO().setStatus(TradeOrderStatusEnum.COMPLETED.getStatus())
.setDeliveryStatus(TradeOrderDeliveryStatusEnum.RECEIVED.getStatus()).setReceiveTime(LocalDateTime.now()));
if (updateCount == 0) {
throw exception(ORDER_RECEIVE_FAIL_STATUS_NOT_DELIVERED);
}
// TODO 芋艿OrderLog
// TODO 芋艿lili 发送订单变化的消息
// TODO 芋艿lili 发送商品被购买完成的数据
}
@Override
public TradeOrderDO getOrder(Long id) {
return tradeOrderMapper.selectById(id);
}
/**
* 校验交易订单满足可售货的条件
*
* 1. 交易订单待收货
*
* @param userId 用户编号
* @param id 交易订单编号
* @return 交易订单
*/
private TradeOrderDO validateOrderReceivable(Long userId, Long id) {
// 校验订单是否存在
TradeOrderDO order = tradeOrderMapper.selectByIdAndUserId(id, userId);
if (order == null) {
throw exception(ORDER_NOT_FOUND);
}
// 校验订单是否是待收货状态
if (!TradeOrderStatusEnum.isDelivered(order.getStatus())
|| ObjectUtil.notEqual(order.getDeliveryStatus(), TradeOrderDeliveryStatusEnum.DELIVERED.getStatus())) {
throw exception(ORDER_RECEIVE_FAIL_STATUS_NOT_DELIVERED);
}
return order;
}
@Override
public TradeOrderDO getOrder(Long userId, Long id) {
TradeOrderDO order = tradeOrderMapper.selectById(id);
if (order != null
&& ObjectUtil.notEqual(order.getUserId(), userId)) {
return null;
}
return order;
}
@Override
public PageResult<TradeOrderDO> getOrderPage(TradeOrderPageReqVO reqVO) {
// 获得 userId 相关的查询
Set<Long> userIds = new HashSet<>();
if (StrUtil.isNotEmpty(reqVO.getUserMobile())) {
MemberUserRespDTO user = memberUserApi.getUserByMobile(reqVO.getUserMobile());
if (user == null) { // 没查询到用户,说明肯定也没他的订单
return new PageResult<>();
}
userIds.add(user.getId());
}
if (StrUtil.isNotEmpty(reqVO.getUserNickname())) {
List<MemberUserRespDTO> users = memberUserApi.getUserListByNickname(reqVO.getUserNickname());
if (CollUtil.isEmpty(users)) { // 没查询到用户,说明肯定也没他的订单
return new PageResult<>();
}
userIds.addAll(convertSet(users, MemberUserRespDTO::getId));
}
// 分页查询
return tradeOrderMapper.selectPage(reqVO, userIds);
}
@Override
public PageResult<TradeOrderDO> getOrderPage(Long userId, AppTradeOrderPageReqVO reqVO) {
return tradeOrderMapper.selectPage(reqVO, userId);
}
// =================== Order Item ===================
@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 void updateOrderItemAfterSaleStatus(Long id, Integer oldAfterSaleStatus, Integer newAfterSaleStatus, Integer refundPrice) {
// 如果退款成功,则 refundPrice 非空
if (Objects.equals(newAfterSaleStatus, TradeOrderItemAfterSaleStatusEnum.SUCCESS.getStatus())
&& refundPrice == null) {
throw new IllegalArgumentException(StrUtil.format("id({}) 退款成功,退款金额不能为空", id));
}
// 更新订单项
int updateCount = tradeOrderItemMapper.updateAfterSaleStatus(id, oldAfterSaleStatus, newAfterSaleStatus);
if (updateCount <= 0) {
throw exception(ORDER_ITEM_UPDATE_AFTER_SALE_STATUS_FAIL);
}
// 如果有退款金额,则需要更新订单
if (refundPrice == null) {
return;
}
// 计算总的退款金额
TradeOrderDO order = tradeOrderMapper.selectById(tradeOrderItemMapper.selectById(id).getOrderId());
Integer orderRefundPrice = order.getRefundPrice() + refundPrice;
if (isAllOrderItemAfterSaleSuccess(order.getId())) { // 如果都售后成功,则需要取消订单
tradeOrderMapper.updateById(new TradeOrderDO().setId(order.getId())
.setAfterSaleStatus(TradeOrderAfterSaleStatusEnum.ALL.getStatus()).setRefundPrice(orderRefundPrice)
.setCancelType(TradeOrderCancelTypeEnum.AFTER_SALE_CLOSE.getType()).setCancelTime(LocalDateTime.now()));
// TODO 芋艿:记录订单日志
// TODO 芋艿:站内信?
} else { // 如果部分售后,则更新退款金额
tradeOrderMapper.updateById(new TradeOrderDO().setId(order.getId())
.setAfterSaleStatus(TradeOrderAfterSaleStatusEnum.PART.getStatus()).setRefundPrice(orderRefundPrice));
}
// TODO 芋艿:未来如果有分佣,需要更新相关分佣订单为已失效
}
@Override
public List<TradeOrderItemDO> getOrderItemList(Collection<Long> ids) {
return tradeOrderItemMapper.selectBatchIds(ids);
}
@Override
public List<TradeOrderItemDO> getOrderItemListByOrderId(Collection<Long> orderIds) {
return tradeOrderItemMapper.selectListByOrderId(orderIds);
}
/**
* 判断指定订单的所有订单项,是不是都售后成功
*
* @param id 订单编号
* @return 是否都售后成功
*/
private boolean isAllOrderItemAfterSaleSuccess(Long id) {
List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(id);
return orderItems.stream().allMatch(orderItem -> Objects.equals(orderItem.getAfterSaleStatus(),
TradeOrderItemAfterSaleStatusEnum.SUCCESS.getStatus()));
}
}

View File

@@ -0,0 +1,154 @@
package cn.iocoder.yudao.module.trade.service.aftersale;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.pay.api.refund.PayRefundApi;
import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.TradeAfterSalePageReqVO;
import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppTradeAfterSaleCreateReqVO;
import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.TradeAfterSaleDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.TradeAfterSaleLogDO;
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.TradeAfterSaleLogMapper;
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.aftersale.TradeAfterSaleWayEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderItemAfterSaleStatusEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
import cn.iocoder.yudao.module.trade.service.order.TradeOrderService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
/**
* {@link TradeAfterSaleService} 的单元测试
*
* @author 芋道源码
*/
@Import(TradeAfterSaleServiceImpl.class)
public class TradeAfterSaleServiceTest extends BaseDbUnitTest {
@Resource
private TradeAfterSaleServiceImpl tradeAfterSaleService;
@Resource
private TradeAfterSaleMapper tradeAfterSaleMapper;
@Resource
private TradeAfterSaleLogMapper tradeAfterSaleLogMapper;
@MockBean
private TradeOrderService tradeOrderService;
@MockBean
private PayRefundApi payRefundApi;
@MockBean
private TradeOrderProperties tradeOrderProperties;
@Test
public void testCreateAfterSale() {
// 准备参数
Long userId = 1024L;
AppTradeAfterSaleCreateReqVO createReqVO = new AppTradeAfterSaleCreateReqVO()
.setOrderItemId(1L).setRefundPrice(100).setWay(TradeAfterSaleWayEnum.RETURN_AND_REFUND.getWay())
.setApplyReason("退钱").setApplyDescription("快退")
.setApplyPicUrls(asList("https://www.baidu.com/1.png", "https://www.baidu.com/2.png"));
// mock 方法(交易订单项)
TradeOrderItemDO orderItem = randomPojo(TradeOrderItemDO.class, o -> {
o.setOrderId(111L).setUserId(userId).setOrderDividePrice(200);
o.setAfterSaleStatus(TradeOrderItemAfterSaleStatusEnum.NONE.getStatus());
});
when(tradeOrderService.getOrderItem(eq(1024L), eq(1L)))
.thenReturn(orderItem);
// mock 方法(交易订单)
TradeOrderDO order = randomPojo(TradeOrderDO.class, o -> o.setStatus(TradeOrderStatusEnum.DELIVERED.getStatus())
.setNo("202211301234"));
when(tradeOrderService.getOrder(eq(1024L), eq(111L))).thenReturn(order);
// 调用
Long afterSaleId = tradeAfterSaleService.createAfterSale(userId, createReqVO);
// 断言TradeAfterSaleDO
TradeAfterSaleDO afterSale = tradeAfterSaleMapper.selectById(afterSaleId);
assertNotNull(afterSale.getNo());
assertEquals(afterSale.getStatus(), TradeAfterSaleStatusEnum.APPLY.getStatus());
assertEquals(afterSale.getType(), TradeAfterSaleTypeEnum.IN_SALE.getType());
assertPojoEquals(afterSale, createReqVO);
assertEquals(afterSale.getUserId(), 1024L);
assertPojoEquals(afterSale, orderItem, "id", "creator", "createTime", "updater", "updateTime");
assertEquals(afterSale.getOrderNo(), "202211301234");
assertNull(afterSale.getPayRefundId());
assertNull(afterSale.getRefundTime());
assertNull(afterSale.getLogisticsId());
assertNull(afterSale.getLogisticsNo());
assertNull(afterSale.getDeliveryTime());
assertNull(afterSale.getReceiveReason());
// 断言TradeAfterSaleLogDO
TradeAfterSaleLogDO afterSaleLog = tradeAfterSaleLogMapper.selectList().get(0);
assertEquals(afterSaleLog.getUserId(), userId);
assertEquals(afterSaleLog.getUserType(), UserTypeEnum.MEMBER.getValue());
assertEquals(afterSaleLog.getAfterSaleId(), afterSaleId);
assertPojoEquals(afterSale, orderItem, "id", "creator", "createTime", "updater", "updateTime");
assertNull(afterSaleLog.getBeforeStatus());
assertEquals(afterSaleLog.getAfterStatus(), TradeAfterSaleStatusEnum.APPLY.getStatus());
assertEquals(afterSaleLog.getContent(), TradeAfterSaleStatusEnum.APPLY.getContent());
}
@Test
public void testGetAfterSalePage() {
// mock 数据
TradeAfterSaleDO dbAfterSale = randomPojo(TradeAfterSaleDO.class, o -> { // 等会查询到
o.setNo("202211190847450020500077");
o.setStatus(TradeAfterSaleStatusEnum.APPLY.getStatus());
o.setWay(TradeAfterSaleWayEnum.RETURN_AND_REFUND.getWay());
o.setType(TradeAfterSaleTypeEnum.IN_SALE.getType());
o.setOrderNo("202211190847450020500011");
o.setSpuName("芋艿");
o.setCreateTime(buildTime(2022, 1, 15));
});
tradeAfterSaleMapper.insert(dbAfterSale);
// 测试 no 不匹配
tradeAfterSaleMapper.insert(cloneIgnoreId(dbAfterSale, o -> o.setNo("202211190847450020500066")));
// 测试 status 不匹配
tradeAfterSaleMapper.insert(cloneIgnoreId(dbAfterSale, o -> o.setStatus(TradeAfterSaleStatusEnum.SELLER_REFUSE.getStatus())));
// 测试 way 不匹配
tradeAfterSaleMapper.insert(cloneIgnoreId(dbAfterSale, o -> o.setWay(TradeAfterSaleWayEnum.REFUND.getWay())));
// 测试 type 不匹配
tradeAfterSaleMapper.insert(cloneIgnoreId(dbAfterSale, o -> o.setType(TradeAfterSaleTypeEnum.AFTER_SALE.getType())));
// 测试 orderNo 不匹配
tradeAfterSaleMapper.insert(cloneIgnoreId(dbAfterSale, o -> o.setOrderNo("202211190847450020500022")));
// 测试 spuName 不匹配
tradeAfterSaleMapper.insert(cloneIgnoreId(dbAfterSale, o -> o.setSpuName("土豆")));
// 测试 createTime 不匹配
tradeAfterSaleMapper.insert(cloneIgnoreId(dbAfterSale, o -> o.setCreateTime(buildTime(2022, 1, 20))));
// 准备参数
TradeAfterSalePageReqVO reqVO = new TradeAfterSalePageReqVO();
reqVO.setNo("20221119084745002050007");
reqVO.setStatus(TradeAfterSaleStatusEnum.APPLY.getStatus());
reqVO.setWay(TradeAfterSaleWayEnum.RETURN_AND_REFUND.getWay());
reqVO.setType(TradeAfterSaleTypeEnum.IN_SALE.getType());
reqVO.setOrderNo("20221119084745002050001");
reqVO.setSpuName("");
reqVO.setCreateTime(new LocalDateTime[]{buildTime(2022, 1, 1), buildTime(2022, 1, 16)});
// 调用
PageResult<TradeAfterSaleDO> pageResult = tradeAfterSaleService.getAfterSalePage(reqVO);
// 断言
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
assertPojoEquals(dbAfterSale, pageResult.getList().get(0));
}
}

View File

@@ -6,29 +6,27 @@ import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
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.dto.PayOrderRespDTO;
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
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.ProductSkuUpdateStockReqDTO;
import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
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.coupon.CouponApi;
import cn.iocoder.yudao.module.promotion.api.price.PriceApi;
import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateRespDTO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO;
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.dal.mysql.order.TradeOrderItemMapper;
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.enums.order.TradeOrderRefundStatusEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
import cn.iocoder.yudao.module.trade.enums.order.*;
import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderConfig;
import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatcher;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
@@ -38,6 +36,8 @@ import java.util.Arrays;
import java.util.List;
import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static java.util.Collections.singletonList;
import static org.junit.jupiter.api.Assertions.*;
@@ -53,7 +53,7 @@ import static org.mockito.Mockito.when;
* @since 2022-09-07
*/
@Import({TradeOrderServiceImpl.class, TradeOrderConfig.class})
class TradeOrderServiceTest extends BaseDbUnitTest {
public class TradeOrderServiceTest extends BaseDbUnitTest {
@Resource
private TradeOrderServiceImpl tradeOrderService;
@@ -111,7 +111,7 @@ class TradeOrderServiceTest extends BaseDbUnitTest {
// mock 方法(用户收件地址的校验)
AddressRespDTO addressRespDTO = new AddressRespDTO().setId(10L).setUserId(userId).setName("芋艿")
.setMobile("15601691300").setAreaId(3306L).setPostCode("85757").setDetailAddress("土豆村");
when(addressApi.getAddress(eq(userId), eq(10L))).thenReturn(addressRespDTO);
when(addressApi.getAddress(eq(10L), eq(userId))).thenReturn(addressRespDTO);
// mock 方法(价格计算)
PriceCalculateRespDTO.OrderItem priceOrderItem01 = new PriceCalculateRespDTO.OrderItem()
.setSpuId(11L).setSkuId(1L).setCount(3).setOriginalPrice(150).setOriginalUnitPrice(50)
@@ -133,7 +133,7 @@ class TradeOrderServiceTest extends BaseDbUnitTest {
return true;
}))).thenReturn(new PriceCalculateRespDTO().setOrder(priceOrder));
// mock 方法(创建支付单)
when(payOrderApi.createPayOrder(argThat(createReqDTO -> {
when(payOrderApi.createOrder(argThat(createReqDTO -> {
assertEquals(createReqDTO.getAppId(), 888L);
assertEquals(createReqDTO.getUserIp(), userIp);
assertNotNull(createReqDTO.getMerchantOrderId()); // 由于 tradeOrderId 后生成,只能校验非空
@@ -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());
@@ -171,18 +171,18 @@ class TradeOrderServiceTest extends BaseDbUnitTest {
assertEquals(tradeOrderDO.getAdjustPrice(), 0);
assertEquals(tradeOrderDO.getPayPrice(), 80);
assertEquals(tradeOrderDO.getPayOrderId(), 1000L);
assertNull(tradeOrderDO.getPayChannel());
assertNull(tradeOrderDO.getPayChannelCode());
assertNull(tradeOrderDO.getDeliveryTemplateId());
assertNull(tradeOrderDO.getExpressNo());
assertFalse(tradeOrderDO.getDeliveryStatus());
assertNull(tradeOrderDO.getLogisticsId());
assertEquals(tradeOrderDO.getDeliveryStatus(), TradeOrderDeliveryStatusEnum.UNDELIVERED.getStatus());
assertNull(tradeOrderDO.getDeliveryTime());
assertNull(tradeOrderDO.getReceiveTime());
assertEquals(tradeOrderDO.getReceiverName(), "芋艿");
assertEquals(tradeOrderDO.getReceiverMobile(), "15601691300");
assertEquals(tradeOrderDO.getReceiverAreaId(), 3306L);
assertEquals(tradeOrderDO.getReceiverAreaId(), 3306);
assertEquals(tradeOrderDO.getReceiverPostCode(), 85757);
assertEquals(tradeOrderDO.getReceiverDetailAddress(), "土豆村");
assertEquals(tradeOrderDO.getRefundStatus(), TradeOrderRefundStatusEnum.NONE.getStatus());
assertEquals(tradeOrderDO.getAfterSaleStatus(), TradeOrderAfterSaleStatusEnum.NONE.getStatus());
assertEquals(tradeOrderDO.getRefundPrice(), 0);
assertEquals(tradeOrderDO.getCouponPrice(), 30);
assertEquals(tradeOrderDO.getPointPrice(), 10);
@@ -198,7 +198,7 @@ class TradeOrderServiceTest extends BaseDbUnitTest {
assertEquals(tradeOrderItemDO01.getProperties().size(), 1);
assertEquals(tradeOrderItemDO01.getProperties().get(0).getPropertyId(), 111L);
assertEquals(tradeOrderItemDO01.getProperties().get(0).getValueId(), 222L);
assertEquals(tradeOrderItemDO01.getName(), sku01.getName());
assertEquals(tradeOrderItemDO01.getSpuName(), sku01.getSpuName());
assertEquals(tradeOrderItemDO01.getPicUrl(), sku01.getPicUrl());
assertEquals(tradeOrderItemDO01.getCount(), 3);
assertEquals(tradeOrderItemDO01.getOriginalPrice(), 150);
@@ -207,8 +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.getRefundTotal(), 0);
assertEquals(tradeOrderItemDO01.getAfterSaleStatus(), TradeOrderItemAfterSaleStatusEnum.NONE.getStatus());
// 断言 TradeOrderItemDO 订单(第 2 个)
TradeOrderItemDO tradeOrderItemDO02 = tradeOrderItemDOs.get(1);
assertNotNull(tradeOrderItemDO02.getId());
@@ -219,7 +218,7 @@ class TradeOrderServiceTest extends BaseDbUnitTest {
assertEquals(tradeOrderItemDO02.getProperties().size(), 1);
assertEquals(tradeOrderItemDO02.getProperties().get(0).getPropertyId(), 333L);
assertEquals(tradeOrderItemDO02.getProperties().get(0).getValueId(), 444L);
assertEquals(tradeOrderItemDO02.getName(), sku02.getName());
assertEquals(tradeOrderItemDO02.getSpuName(), sku02.getSpuName());
assertEquals(tradeOrderItemDO02.getPicUrl(), sku02.getPicUrl());
assertEquals(tradeOrderItemDO02.getCount(), 4);
assertEquals(tradeOrderItemDO02.getOriginalPrice(), 80);
@@ -228,21 +227,15 @@ class TradeOrderServiceTest extends BaseDbUnitTest {
assertEquals(tradeOrderItemDO02.getPayPrice(), 40);
assertEquals(tradeOrderItemDO02.getOrderPartPrice(), 15);
assertEquals(tradeOrderItemDO02.getOrderDividePrice(), 25);
assertEquals(tradeOrderItemDO02.getRefundStatus(), TradeOrderItemRefundStatusEnum.NONE.getStatus());
assertEquals(tradeOrderItemDO02.getRefundTotal(), 0);
assertEquals(tradeOrderItemDO02.getAfterSaleStatus(), TradeOrderItemAfterSaleStatusEnum.NONE.getStatus());
// 校验调用
verify(productSkuApi).updateSkuStock(argThat(new ArgumentMatcher<ProductSkuUpdateStockReqDTO>() {
@Override
public boolean matches(ProductSkuUpdateStockReqDTO updateStockReqDTO) {
assertEquals(updateStockReqDTO.getItems().size(), 2);
assertEquals(updateStockReqDTO.getItems().get(0).getId(), 1L);
assertEquals(updateStockReqDTO.getItems().get(0).getIncrCount(), 3);
assertEquals(updateStockReqDTO.getItems().get(1).getId(), 2L);
assertEquals(updateStockReqDTO.getItems().get(1).getIncrCount(), 4);
return true;
}
verify(productSkuApi).updateSkuStock(argThat(updateStockReqDTO -> {
assertEquals(updateStockReqDTO.getItems().size(), 2);
assertEquals(updateStockReqDTO.getItems().get(0).getId(), 1L);
assertEquals(updateStockReqDTO.getItems().get(0).getIncrCount(), 3);
assertEquals(updateStockReqDTO.getItems().get(1).getId(), 2L);
assertEquals(updateStockReqDTO.getItems().get(1).getIncrCount(), 4);
return true;
}));
verify(couponApi).useCoupon(argThat(reqDTO -> {
assertEquals(reqDTO.getId(), reqVO.getCouponId());
@@ -250,11 +243,78 @@ class TradeOrderServiceTest extends BaseDbUnitTest {
assertEquals(reqDTO.getOrderId(), tradeOrderId);
return true;
}));
// //mock 支付订单信息
// when(payOrderApi.createPayOrder(any())).thenReturn(1L);
}
// //价格
// assertEquals(calculateRespDTO.getOrder().getItems().get(0).getPresentPrice(), tradeOrderItemDO.getPresentPrice());
@Test
public void testUpdateOrderPaid() {
// mock 数据TradeOrder
TradeOrderDO order = randomPojo(TradeOrderDO.class, o -> {
o.setId(1L).setStatus(TradeOrderStatusEnum.UNPAID.getStatus());
o.setPayOrderId(10L).setPayed(false).setPayPrice(100).setPayTime(null);
});
tradeOrderMapper.insert(order);
// 准备参数
Long id = 1L;
Long payOrderId = 10L;
// mock 方法(支付单)
when(payOrderApi.getOrder(eq(10L))).thenReturn(randomPojo(PayOrderRespDTO.class,
o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()).setChannelCode("wx_pub")
.setMerchantOrderId("1")).setAmount(100));
// 调用
tradeOrderService.updateOrderPaid(id, payOrderId);
// 断言
TradeOrderDO dbOrder = tradeOrderMapper.selectById(id);
assertEquals(dbOrder.getStatus(), TradeOrderStatusEnum.UNDELIVERED.getStatus());
assertTrue(dbOrder.getPayed());
assertNotNull(dbOrder.getPayTime());
assertEquals(dbOrder.getPayChannelCode(), "wx_pub");
}
@Test
public void testDeliveryOrder() {
// mock 数据TradeOrder
TradeOrderDO order = randomPojo(TradeOrderDO.class, o -> {
o.setId(1L).setStatus(TradeOrderStatusEnum.UNDELIVERED.getStatus());
o.setLogisticsId(null).setLogisticsNo(null).setDeliveryTime(null)
.setDeliveryStatus(TradeOrderDeliveryStatusEnum.UNDELIVERED.getStatus());
});
tradeOrderMapper.insert(order);
// 准备参数
TradeOrderDeliveryReqVO deliveryReqVO = new TradeOrderDeliveryReqVO().setId(1L)
.setLogisticsId(10L).setLogisticsNo("100");
// mock 方法(支付单)
// 调用
tradeOrderService.deliveryOrder(randomLongId(), deliveryReqVO);
// 断言
TradeOrderDO dbOrder = tradeOrderMapper.selectById(1L);
assertEquals(dbOrder.getStatus(), TradeOrderStatusEnum.DELIVERED.getStatus());
assertEquals(dbOrder.getDeliveryStatus(), TradeOrderDeliveryStatusEnum.DELIVERED.getStatus());
assertPojoEquals(dbOrder, deliveryReqVO);
assertNotNull(dbOrder.getDeliveryTime());
}
@Test
public void testReceiveOrder() {
// mock 数据TradeOrder
TradeOrderDO order = randomPojo(TradeOrderDO.class, o -> {
o.setId(1L).setUserId(10L).setStatus(TradeOrderStatusEnum.DELIVERED.getStatus());
o.setDeliveryStatus(TradeOrderDeliveryStatusEnum.DELIVERED.getStatus()).setReceiveTime(null);
});
tradeOrderMapper.insert(order);
// 准备参数
Long id = 1L;
Long userId = 10L;
// mock 方法(支付单)
// 调用
tradeOrderService.receiveOrder(userId, id);
// 断言
TradeOrderDO dbOrder = tradeOrderMapper.selectById(1L);
assertEquals(dbOrder.getStatus(), TradeOrderStatusEnum.COMPLETED.getStatus());
assertEquals(dbOrder.getDeliveryStatus(), TradeOrderDeliveryStatusEnum.RECEIVED.getStatus());
assertNotNull(dbOrder.getReceiveTime());
}
}

View File

@@ -9,7 +9,7 @@ spring:
# 数据源配置项
datasource:
name: ruoyi-vue-pro
url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false; # MODE 使用 MySQL 模式DATABASE_TO_UPPER 配置表和字段使用小写
url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=value; # MODE 使用 MySQL 模式DATABASE_TO_UPPER 配置表和字段使用小写
driver-class-name: org.h2.Driver
username: sa
password:

View File

@@ -1,2 +1,4 @@
DELETE FROM trade_order;
DELETE FROM trade_order_item;
DELETE FROM trade_order_item;
DELETE FROM trade_after_sale;
DELETE FROM trade_after_sale_log;

View File

@@ -20,11 +20,12 @@ CREATE TABLE IF NOT EXISTS "trade_order" (
"delivery_price" int NOT NULL,
"adjust_price" int NOT NULL,
"pay_price" int NOT NULL,
"pay_order_id" int,
"pay_channel" int,
"delivery_template_id" int,
"express_no" int,
"delivery_status" bit NOT NULL,
"pay_order_id" bigint,
"pay_channel_code" varchar,
"delivery_template_id" bigint,
"logistics_id" bigint,
"logistics_no" varchar,
"delivery_status" smallint NOT NULL,
"delivery_time" datetime,
"receive_time" datetime,
"receiver_name" varchar NOT NULL,
@@ -32,7 +33,7 @@ CREATE TABLE IF NOT EXISTS "trade_order" (
"receiver_area_id" int NOT NULL,
"receiver_post_code" int,
"receiver_detail_address" varchar NOT NULL,
"refund_status" int NOT NULL,
"after_sale_status" int NOT NULL,
"refund_price" int NOT NULL,
"coupon_id" bigint NOT NULL,
"coupon_price" int NOT NULL,
@@ -50,9 +51,9 @@ CREATE TABLE IF NOT EXISTS "trade_order_item" (
"user_id" bigint NOT NULL,
"order_id" bigint NOT NULL,
"spu_id" bigint NOT NULL,
"spu_name" varchar NOT NULL,
"sku_id" bigint NOT NULL,
"properties" varchar,
"name" varchar NOT NULL,
"pic_url" varchar,
"count" int NOT NULL,
"original_price" int NOT NULL,
@@ -61,8 +62,7 @@ CREATE TABLE IF NOT EXISTS "trade_order_item" (
"pay_price" int NOT NULL,
"order_part_price" int NOT NULL,
"order_divide_price" int NOT NULL,
"refund_status" int NOT NULL,
"refund_total" int NOT NULL,
"after_sale_status" int NOT NULL,
"creator" varchar DEFAULT '',
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar DEFAULT '',
@@ -70,3 +70,59 @@ CREATE TABLE IF NOT EXISTS "trade_order_item" (
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT '交易订单明细表';
CREATE TABLE IF NOT EXISTS "trade_after_sale" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"no" varchar NOT NULL,
"status" int NOT NULL,
"type" int NOT NULL,
"way" int NOT NULL,
"user_id" bigint NOT NULL,
"apply_reason" varchar NOT NULL,
"apply_description" varchar,
"apply_pic_urls" varchar,
"order_id" bigint NOT NULL,
"order_no" varchar NOT NULL,
"order_item_id" bigint NOT NULL,
"spu_id" bigint NOT NULL,
"spu_name" varchar NOT NULL,
"sku_id" bigint NOT NULL,
"properties" varchar,
"pic_url" varchar,
"count" int NOT NULL,
"audit_time" varchar,
"audit_user_id" bigint,
"audit_reason" varchar,
"refund_price" int NOT NULL,
"pay_refund_id" bigint,
"refund_time" varchar,
"logistics_id" bigint,
"logistics_no" varchar,
"delivery_time" varchar,
"receive_time" varchar,
"receive_reason" varchar,
"creator" varchar DEFAULT '',
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar DEFAULT '',
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT '交易售后表';
CREATE TABLE IF NOT EXISTS "trade_after_sale_log" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"user_id" bigint NOT NULL,
"user_type" int NOT NULL,
"after_sale_id" bigint NOT NULL,
"order_id" bigint NOT NULL,
"order_item_id" bigint NOT NULL,
"before_status" int,
"after_status" int NOT NULL,
"content" varchar NOT NULL,
"creator" varchar DEFAULT '',
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar DEFAULT '',
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT '交易售后日志';