Commit ed9c8d4a7811374816816e26b5a02f057666ec44

Authored by YunaiV
1 parent d884303d

trade:【交易售后】增加售后日志

yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/aftersale/TradeAfterSaleStatusEnum.java
... ... @@ -6,6 +6,8 @@ import lombok.Getter;
6 6  
7 7 import java.util.Arrays;
8 8  
  9 +import static cn.hutool.core.util.ArrayUtil.firstMatch;
  10 +
9 11 /**
10 12 * 售后状态的枚举
11 13 *
... ... @@ -17,15 +19,23 @@ import java.util.Arrays;
17 19 @Getter
18 20 public enum TradeAfterSaleStatusEnum implements IntArrayValuable {
19 21  
20   - APPLY(10,"申请中"), // 【申请售后】
21   - SELLER_AGREE(20, "卖家通过"), // 卖家通过售后;【商品待退货】
22   - BUYER_DELIVERY(30,"待卖家收货"), // 买家已退货,等待卖家收货;【商家待收货】
23   - WAIT_REFUND(40, "等待平台退款"), // 卖家已收货,等待平台退款;等待退款【等待退款】
24   - COMPLETE(50, "完成"), // 完成退款【退款成功】
  22 + APPLY(10,"申请中", // 【申请售后】
  23 + "会员申请退款"),
  24 + SELLER_AGREE(20, "卖家通过", // 卖家通过售后;【商品待退货】
  25 + "商家同意退款"),
  26 + BUYER_DELIVERY(30,"待卖家收货", // 买家已退货,等待卖家收货;【商家待收货】
  27 + "会员填写退货物流信息"),
  28 + WAIT_REFUND(40, "等待平台退款", // 卖家已收货,等待平台退款;等待退款【等待退款】
  29 + "商家收货"),
  30 + COMPLETE(50, "完成", // 完成退款【退款成功】
  31 + "商家确认退款"),
25 32  
26   - BUYER_CANCEL(61, "买家取消售后"), // 【买家取消】
27   - SELLER_DISAGREE(62,"卖家拒绝"), // 卖家拒绝售后;商家拒绝【商家拒绝】
28   - SELLER_REFUSE(63,"卖家拒绝收货"), // 卖家拒绝收货,终止售后;【商家拒收货】
  33 + BUYER_CANCEL(61, "买家取消售后", // 【买家取消】
  34 + "会员取消退款"),
  35 + SELLER_DISAGREE(62,"卖家拒绝", // 卖家拒绝售后;商家拒绝【商家拒绝】
  36 + "商家拒绝退款"),
  37 + SELLER_REFUSE(63,"卖家拒绝收货", // 卖家拒绝收货,终止售后;【商家拒收货】
  38 + "商家拒绝收货"),
29 39 ;
30 40  
31 41 public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(TradeAfterSaleStatusEnum::getStatus).toArray();
... ... @@ -38,10 +48,20 @@ public enum TradeAfterSaleStatusEnum implements IntArrayValuable {
38 48 * 状态名
39 49 */
40 50 private final String name;
  51 + /**
  52 + * 操作内容
  53 + *
  54 + * 目的:记录售后日志的内容
  55 + */
  56 + private final String content;
41 57  
42 58 @Override
43 59 public int[] array() {
44 60 return ARRAYS;
45 61 }
46 62  
  63 + public static TradeAfterSaleStatusEnum valueOf(Integer status) {
  64 + return firstMatch(value -> value.getStatus().equals(status), values());
  65 + }
  66 +
47 67 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/vo/log/TradeAfterSaleLogRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.log;
  2 +
  3 +import io.swagger.annotations.ApiModel;
  4 +import io.swagger.annotations.ApiModelProperty;
  5 +import lombok.Data;
  6 +
  7 +import javax.validation.constraints.NotNull;
  8 +import java.time.LocalDateTime;
  9 +
  10 +@ApiModel("管理后台 - 交易售后日志 Response VO")
  11 +@Data
  12 +public class TradeAfterSaleLogRespVO {
  13 +
  14 + @ApiModelProperty(value = "编号", required = true, example = "20669")
  15 + private Long id;
  16 +
  17 + @ApiModelProperty(value = "用户编号", required = true, example = "22634")
  18 + @NotNull(message = "用户编号不能为空")
  19 + private Long userId;
  20 +
  21 + @ApiModelProperty(value = "用户类型", required = true, example = "2")
  22 + @NotNull(message = "用户类型不能为空")
  23 + private Integer userType;
  24 +
  25 + @ApiModelProperty(value = "售后编号", required = true, example = "3023")
  26 + @NotNull(message = "售后编号不能为空")
  27 + private Long afterSaleId;
  28 +
  29 + @ApiModelProperty(value = "订单编号", required = true, example = "25870")
  30 + @NotNull(message = "订单编号不能为空")
  31 + private Long orderId;
  32 +
  33 + @ApiModelProperty(value = "订单项编号", required = true, example = "23154")
  34 + @NotNull(message = "订单项编号不能为空")
  35 + private Long orderItemId;
  36 +
  37 + @ApiModelProperty(value = "售后状态(之前)", example = "2", notes = "参见 TradeAfterSaleStatusEnum 枚举")
  38 + private Integer beforeStatus;
  39 +
  40 + @ApiModelProperty(value = "售后状态(之后)", required = true, example = "1", notes = "参见 TradeAfterSaleStatusEnum 枚举")
  41 + @NotNull(message = "售后状态(之后)不能为空")
  42 + private Integer afterStatus;
  43 +
  44 + @ApiModelProperty(value = "操作明细", required = true, example = "维权完成,退款金额:¥37776.00")
  45 + @NotNull(message = "操作明细不能为空")
  46 + private String content;
  47 +
  48 + @ApiModelProperty(value = "创建时间", required = true)
  49 + private LocalDateTime createTime;
  50 +
  51 +}
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/aftersale/TradeAfterSaleLogDO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.dal.dataobject.aftersale;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
  4 +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  5 +import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
  6 +import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
  7 +import cn.iocoder.yudao.module.trade.enums.aftersale.TradeAfterSaleStatusEnum;
  8 +import com.baomidou.mybatisplus.annotation.KeySequence;
  9 +import com.baomidou.mybatisplus.annotation.TableId;
  10 +import com.baomidou.mybatisplus.annotation.TableName;
  11 +import lombok.*;
  12 +
  13 +/**
  14 + * 交易售后日志 DO
  15 + *
  16 + * // TODO 可优化:参考淘宝或者有赞:1)增加 action 表示什么操作;2)content 记录每个操作的明细
  17 + *
  18 + * @author 芋道源码
  19 + */
  20 +@TableName("trade_after_sale_log")
  21 +@KeySequence("trade_after_sale_log_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
  22 +@Data
  23 +@EqualsAndHashCode(callSuper = true)
  24 +@ToString(callSuper = true)
  25 +@Builder
  26 +@NoArgsConstructor
  27 +@AllArgsConstructor
  28 +public class TradeAfterSaleLogDO extends BaseDO {
  29 +
  30 + /**
  31 + * 编号
  32 + */
  33 + @TableId
  34 + private Long id;
  35 + /**
  36 + * 用户编号
  37 + *
  38 + * 关联 1:AdminUserDO 的 id 字段
  39 + * 关联 2:MemberUserDO 的 id 字段
  40 + */
  41 + private Long userId;
  42 + /**
  43 + * 用户类型
  44 + *
  45 + * 枚举 {@link UserTypeEnum}
  46 + */
  47 + private Integer userType;
  48 + /**
  49 + * 售后编号
  50 + *
  51 + * 关联 {@link TradeAfterSaleDO#getId()}
  52 + */
  53 + private Long afterSaleId;
  54 + /**
  55 + * 订单编号
  56 + *
  57 + * 关联 {@link TradeOrderDO#getId()}
  58 + */
  59 + private Long orderId;
  60 + /**
  61 + * 订单项编号
  62 + *
  63 + * 关联 {@link TradeOrderItemDO#getId()}
  64 + */
  65 + private Long orderItemId;
  66 + /**
  67 + * 售后状态(之前)
  68 + *
  69 + * 枚举 {@link TradeAfterSaleStatusEnum}
  70 + */
  71 + private Integer beforeStatus;
  72 + /**
  73 + * 售后状态(之后)
  74 + *
  75 + * 枚举 {@link TradeAfterSaleStatusEnum}
  76 + */
  77 + private Integer afterStatus;
  78 + /**
  79 + * 操作明细
  80 + */
  81 + private String content;
  82 +
  83 +}
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/aftersale/TradeAfterSaleLogMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.dal.mysql.aftersale;
  2 +
  3 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  4 +import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.TradeAfterSaleLogDO;
  5 +import org.apache.ibatis.annotations.Mapper;
  6 +
  7 +@Mapper
  8 +public interface TradeAfterSaleLogMapper extends BaseMapperX<TradeAfterSaleLogDO> {
  9 +}
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/TradeAfterSaleServiceImpl.java
... ... @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.trade.service.aftersale;
2 2  
3 3 import cn.hutool.core.util.ObjectUtil;
4 4 import cn.hutool.core.util.RandomUtil;
  5 +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
5 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 7 import cn.iocoder.yudao.module.pay.api.refund.PayRefundApi;
7 8 import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
... ... @@ -12,8 +13,10 @@ import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppTradeAfterSa
12 13 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppTradeAfterSaleDeliveryReqVO;
13 14 import cn.iocoder.yudao.module.trade.convert.aftersale.TradeAfterSaleConvert;
14 15 import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.TradeAfterSaleDO;
  16 +import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.TradeAfterSaleLogDO;
15 17 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
16 18 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
  19 +import cn.iocoder.yudao.module.trade.dal.mysql.aftersale.TradeAfterSaleLogMapper;
17 20 import cn.iocoder.yudao.module.trade.dal.mysql.aftersale.TradeAfterSaleMapper;
18 21 import cn.iocoder.yudao.module.trade.enums.aftersale.TradeAfterSaleStatusEnum;
19 22 import cn.iocoder.yudao.module.trade.enums.aftersale.TradeAfterSaleTypeEnum;
... ... @@ -48,6 +51,8 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService {
48 51  
49 52 @Resource
50 53 private TradeAfterSaleMapper tradeAfterSaleMapper;
  54 + @Resource
  55 + private TradeAfterSaleLogMapper tradeAfterSaleLogMapper;
51 56  
52 57 @Resource
53 58 private PayRefundApi payRefundApi;
... ... @@ -136,7 +141,9 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService {
136 141 TradeOrderItemAfterSaleStatusEnum.NONE.getStatus(),
137 142 TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(), null);
138 143  
139   - // TODO 记录售后日志
  144 + // 记录售后日志
  145 + createAfterSaleLog(orderItem.getUserId(), UserTypeEnum.MEMBER.getValue(),
  146 + afterSale, null, afterSale.getStatus());
140 147  
141 148 // TODO 发送售后消息
142 149 return afterSale;
... ... @@ -156,7 +163,9 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService {
156 163 updateAfterSaleStatus(afterSale.getId(), TradeAfterSaleStatusEnum.APPLY.getStatus(), new TradeAfterSaleDO()
157 164 .setStatus(newStatus).setAuditUserId(userId).setAuditTime(LocalDateTime.now()));
158 165  
159   - // TODO 记录售后日志
  166 + // 记录售后日志
  167 + createAfterSaleLog(userId, UserTypeEnum.ADMIN.getValue(),
  168 + afterSale, afterSale.getStatus(), newStatus);
160 169  
161 170 // TODO 发送售后消息
162 171 }
... ... @@ -173,7 +182,9 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService {
173 182 .setStatus(newStatus).setAuditUserId(userId).setAuditTime(LocalDateTime.now())
174 183 .setAuditReason(auditReqVO.getAuditReason()));
175 184  
176   - // TODO 记录售后日志
  185 + // 记录售后日志
  186 + createAfterSaleLog(userId, UserTypeEnum.ADMIN.getValue(),
  187 + afterSale, afterSale.getStatus(), newStatus);
177 188  
178 189 // TODO 发送售后消息
179 190  
... ... @@ -225,7 +236,9 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService {
225 236 .setLogisticsId(deliveryReqVO.getLogisticsId()).setLogisticsNo(deliveryReqVO.getLogisticsNo())
226 237 .setDeliveryTime(deliveryReqVO.getDeliveryTime()));
227 238  
228   - // TODO 记录售后日志
  239 + // 记录售后日志
  240 + createAfterSaleLog(userId, UserTypeEnum.MEMBER.getValue(),
  241 + afterSale, afterSale.getStatus(), TradeAfterSaleStatusEnum.BUYER_DELIVERY.getStatus());
229 242  
230 243 // TODO 发送售后消息
231 244 }
... ... @@ -240,7 +253,9 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService {
240 253 updateAfterSaleStatus(afterSale.getId(), TradeAfterSaleStatusEnum.BUYER_DELIVERY.getStatus(), new TradeAfterSaleDO()
241 254 .setStatus(TradeAfterSaleStatusEnum.WAIT_REFUND.getStatus()).setReceiveTime(LocalDateTime.now()));
242 255  
243   - // TODO 记录售后日志
  256 + // 记录售后日志
  257 + createAfterSaleLog(userId, UserTypeEnum.ADMIN.getValue(),
  258 + afterSale, afterSale.getStatus(), TradeAfterSaleStatusEnum.WAIT_REFUND.getStatus());
244 259  
245 260 // TODO 发送售后消息
246 261 }
... ... @@ -262,7 +277,9 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService {
262 277 .setStatus(TradeAfterSaleStatusEnum.SELLER_REFUSE.getStatus()).setReceiveTime(LocalDateTime.now())
263 278 .setReceiveReason(confirmReqVO.getRefuseMemo()));
264 279  
265   - // TODO 记录售后日志
  280 + // 记录售后日志
  281 + createAfterSaleLog(userId, UserTypeEnum.ADMIN.getValue(),
  282 + afterSale, afterSale.getStatus(), TradeAfterSaleStatusEnum.SELLER_REFUSE.getStatus());
266 283  
267 284 // TODO 发送售后消息
268 285  
... ... @@ -308,7 +325,9 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService {
308 325 updateAfterSaleStatus(afterSale.getId(), TradeAfterSaleStatusEnum.WAIT_REFUND.getStatus(), new TradeAfterSaleDO()
309 326 .setStatus(TradeAfterSaleStatusEnum.COMPLETE.getStatus()).setRefundTime(LocalDateTime.now()));
310 327  
311   - // TODO 记录售后日志
  328 + // 记录售后日志
  329 + createAfterSaleLog(userId, UserTypeEnum.ADMIN.getValue(),
  330 + afterSale, afterSale.getStatus(), TradeAfterSaleStatusEnum.COMPLETE.getStatus());
312 331  
313 332 // TODO 发送售后消息
314 333  
... ... @@ -332,4 +351,13 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService {
332 351 });
333 352 }
334 353  
  354 + private void createAfterSaleLog(Long userId, Integer userType, TradeAfterSaleDO afterSale,
  355 + Integer beforeStatus, Integer afterStatus) {
  356 + TradeAfterSaleLogDO afterSaleLog = new TradeAfterSaleLogDO().setUserId(userId).setUserType(userType)
  357 + .setAfterSaleId(afterSale.getId()).setOrderId(afterSale.getOrderId())
  358 + .setOrderItemId(afterSale.getOrderItemId()).setBeforeStatus(beforeStatus).setAfterStatus(afterStatus)
  359 + .setContent(TradeAfterSaleStatusEnum.valueOf(afterStatus).getContent());
  360 + tradeAfterSaleLogMapper.insert(afterSaleLog);
  361 + }
  362 +
335 363 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/aftersale/TradeAfterSaleServiceTest.java
1 1 package cn.iocoder.yudao.module.trade.service.aftersale;
2 2  
  3 +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
3 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 5 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
5 6 import cn.iocoder.yudao.module.pay.api.refund.PayRefundApi;
6 7 import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.TradeAfterSalePageReqVO;
7 8 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppTradeAfterSaleCreateReqVO;
8 9 import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.TradeAfterSaleDO;
  10 +import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.TradeAfterSaleLogDO;
9 11 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
10 12 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
  13 +import cn.iocoder.yudao.module.trade.dal.mysql.aftersale.TradeAfterSaleLogMapper;
11 14 import cn.iocoder.yudao.module.trade.dal.mysql.aftersale.TradeAfterSaleMapper;
12 15 import cn.iocoder.yudao.module.trade.enums.aftersale.TradeAfterSaleStatusEnum;
13 16 import cn.iocoder.yudao.module.trade.enums.aftersale.TradeAfterSaleTypeEnum;
... ... @@ -45,6 +48,8 @@ public class TradeAfterSaleServiceTest extends BaseDbUnitTest {
45 48  
46 49 @Resource
47 50 private TradeAfterSaleMapper tradeAfterSaleMapper;
  51 + @Resource
  52 + private TradeAfterSaleLogMapper tradeAfterSaleLogMapper;
48 53  
49 54 @MockBean
50 55 private TradeOrderService tradeOrderService;
... ... @@ -76,7 +81,7 @@ public class TradeAfterSaleServiceTest extends BaseDbUnitTest {
76 81  
77 82 // 调用
78 83 Long afterSaleId = tradeAfterSaleService.createAfterSale(userId, createReqVO);
79   - // 断言
  84 + // 断言(TradeAfterSaleDO)
80 85 TradeAfterSaleDO afterSale = tradeAfterSaleMapper.selectById(afterSaleId);
81 86 assertNotNull(afterSale.getNo());
82 87 assertEquals(afterSale.getStatus(), TradeAfterSaleStatusEnum.APPLY.getStatus());
... ... @@ -91,6 +96,15 @@ public class TradeAfterSaleServiceTest extends BaseDbUnitTest {
91 96 assertNull(afterSale.getLogisticsNo());
92 97 assertNull(afterSale.getDeliveryTime());
93 98 assertNull(afterSale.getReceiveReason());
  99 + // 断言(TradeAfterSaleLogDO)
  100 + TradeAfterSaleLogDO afterSaleLog = tradeAfterSaleLogMapper.selectList().get(0);
  101 + assertEquals(afterSaleLog.getUserId(), userId);
  102 + assertEquals(afterSaleLog.getUserType(), UserTypeEnum.MEMBER.getValue());
  103 + assertEquals(afterSaleLog.getAfterSaleId(), afterSaleId);
  104 + assertPojoEquals(afterSale, orderItem, "id", "creator", "createTime", "updater", "updateTime");
  105 + assertNull(afterSaleLog.getBeforeStatus());
  106 + assertEquals(afterSaleLog.getAfterStatus(), TradeAfterSaleStatusEnum.APPLY.getStatus());
  107 + assertEquals(afterSaleLog.getContent(), TradeAfterSaleStatusEnum.APPLY.getContent());
94 108 }
95 109  
96 110 @Test
... ...
yudao-module-mall/yudao-module-trade-biz/src/test/resources/sql/clean.sql
1 1 DELETE FROM trade_order;
2 2 DELETE FROM trade_order_item;
3 3 DELETE FROM trade_after_sale;
  4 +DELETE FROM trade_after_sale_log;
... ...
yudao-module-mall/yudao-module-trade-biz/src/test/resources/sql/create_tables.sql
... ... @@ -107,3 +107,21 @@ CREATE TABLE IF NOT EXISTS &quot;trade_after_sale&quot; (
107 107 "deleted" bit NOT NULL DEFAULT FALSE,
108 108 PRIMARY KEY ("id")
109 109 ) COMMENT '交易售后表';
  110 +
  111 +CREATE TABLE IF NOT EXISTS "trade_after_sale_log" (
  112 + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
  113 + "user_id" bigint NOT NULL,
  114 + "user_type" int NOT NULL,
  115 + "after_sale_id" bigint NOT NULL,
  116 + "order_id" bigint NOT NULL,
  117 + "order_item_id" bigint NOT NULL,
  118 + "before_status" int,
  119 + "after_status" int NOT NULL,
  120 + "content" varchar NOT NULL,
  121 + "creator" varchar DEFAULT '',
  122 + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  123 + "updater" varchar DEFAULT '',
  124 + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  125 + "deleted" bit NOT NULL DEFAULT FALSE,
  126 + PRIMARY KEY ("id")
  127 +) COMMENT '交易售后日志';
... ...