diff --git a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/order/TradeOrderOperateTypeEnum.java b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/order/TradeOrderOperateTypeEnum.java index c37958451..f542996cc 100644 --- a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/order/TradeOrderOperateTypeEnum.java +++ b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/order/TradeOrderOperateTypeEnum.java @@ -15,9 +15,10 @@ public enum TradeOrderOperateTypeEnum { MEMBER_CREATE(1, "用户下单"), MEMBER_RECEIVE(30, "用户已收货"), - MEMBER_COMMENT(31, "用户评价"), - MEMBER_CANCEL(40, "手动取消订单"), - SYSTEM_CANCEL(41, "系统取消订单"), + SYSTEM_RECEIVE(31, "到期未收货,系统自动确认收货"), + MEMBER_COMMENT(33, "用户评价"), + MEMBER_CANCEL(40, "取消订单"), + SYSTEM_CANCEL(41, "到期未支付,系统自动取消订单"), // 42 预留:管理员取消订单 MEMBER_DELETE(43, "删除订单"), ; diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java index b1aa44cfa..2fe69fd77 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java @@ -110,7 +110,7 @@ public interface TradeOrderConvert { createReqDTO.setSubject(subject); createReqDTO.setBody(subject); // TODO 芋艿:临时写死 // 订单相关字段 - createReqDTO.setPrice(order.getPayPrice()).setExpireTime(addTime(orderProperties.getExpireTime())); + createReqDTO.setPrice(order.getPayPrice()).setExpireTime(addTime(orderProperties.getPayExpireTime())); return createReqDTO; } @@ -180,7 +180,7 @@ public interface TradeOrderConvert { TradeOrderProperties tradeOrderProperties, DeliveryExpressDO express) { AppTradeOrderDetailRespVO orderVO = convert3(order, orderItems); - orderVO.setPayExpireTime(addTime(tradeOrderProperties.getExpireTime())); + orderVO.setPayExpireTime(addTime(tradeOrderProperties.getPayExpireTime())); if (StrUtil.isNotEmpty(order.getPayChannelCode())) { orderVO.setPayChannelName(DictFrameworkUtils.getDictDataLabel(DictTypeConstants.CHANNEL_CODE, order.getPayChannelCode())); } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderMapper.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderMapper.java index b19940e6d..5d5ba737b 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderMapper.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderMapper.java @@ -61,10 +61,16 @@ public interface TradeOrderMapper extends BaseMapperX { .eq(TradeOrderDO::getUserId, loginUserId)); } - default List selectListByStatusAndCreateTimeLt(Integer status, LocalDateTime expireTime) { + default List selectListByStatusAndCreateTimeLt(Integer status, LocalDateTime createTime) { return selectList(new LambdaUpdateWrapper() .eq(TradeOrderDO::getStatus, status) - .lt(TradeOrderDO::getCreateTime, expireTime)); + .lt(TradeOrderDO::getCreateTime, createTime)); + } + + default List selectListByStatusAndDeliveryTimeLt(Integer status, LocalDateTime deliveryTime) { + return selectList(new LambdaUpdateWrapper() + .eq(TradeOrderDO::getStatus, status) + .lt(TradeOrderDO::getDeliveryTime, deliveryTime)); } } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/order/config/TradeOrderProperties.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/order/config/TradeOrderProperties.java index 4d584f4c9..913eb7a80 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/order/config/TradeOrderProperties.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/order/config/TradeOrderProperties.java @@ -28,6 +28,12 @@ public class TradeOrderProperties { * 支付超时时间 */ @NotNull(message = "支付超时时间不能为空") - private Duration expireTime; + private Duration payExpireTime; + + /** + * 收货超时时间 + */ + @NotNull(message = "收货超时时间不能为空") + private Duration receiveExpireTime; } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/job/order/TradeOrderAutoReceiveJob.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/job/order/TradeOrderAutoReceiveJob.java new file mode 100644 index 000000000..8d7b42b3c --- /dev/null +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/job/order/TradeOrderAutoReceiveJob.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.trade.job.order; + +import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; +import cn.iocoder.yudao.framework.tenant.core.job.TenantJob; +import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * 交易订单的自动收货 Job + * + * @author 芋道源码 + */ +@Component +@TenantJob +public class TradeOrderAutoReceiveJob implements JobHandler { + + @Resource + private TradeOrderUpdateService tradeOrderUpdateService; + + @Override + public String execute(String param) { + int count = tradeOrderUpdateService.autoReceiveOrder(); + return String.format("自动收货 %s 个", count); + } + +} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java index 87ffc457f..f66e0efdb 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java @@ -63,7 +63,14 @@ public interface TradeOrderUpdateService { void receiveOrder(Long userId, Long id); /** - * 【会员】取消订单 + * 【系统】自动收货交易订单 + * + * @return 收货数量 + */ + int autoReceiveOrder(); + + /** + * 【会员】取消交易订单 * * @param userId 用户编号 * @param id 订单编号 diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java index a1e668582..b5eb0479b 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java @@ -492,6 +492,50 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { // 校验并获得交易订单(可收货) TradeOrderDO order = validateOrderReceivable(userId, id); + // 收货订单 + receiveOrder0(order); + } + + @Override + public int autoReceiveOrder() { + // 1. 查询过期的待支付订单 + LocalDateTime expireTime = addTime(tradeOrderProperties.getReceiveExpireTime()); + List orders = tradeOrderMapper.selectListByStatusAndDeliveryTimeLt( + TradeOrderStatusEnum.DELIVERED.getStatus(), expireTime); + if (CollUtil.isEmpty(orders)) { + return 0; + } + + // 2. 遍历执行,逐个取消 + int count = 0; + for (TradeOrderDO order : orders) { + try { + getSelf().autoReceiveOrder(order); + count ++; + } catch (Throwable e) { + log.error("[autoReceiveOrder][order({}) 自动收货订单异常]", order.getId(), e); + } + } + return count; + } + + /** + * 自动收货单个订单 + * + * @param order 订单 + */ + @Transactional(rollbackFor = Exception.class) + @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.SYSTEM_RECEIVE) + public void autoReceiveOrder(TradeOrderDO order) { + receiveOrder0(order); + } + + /** + * 收货订单的核心实现 + * + * @param order 订单 + */ + private void receiveOrder0(TradeOrderDO order) { // 更新 TradeOrderDO 状态为已完成 int updateCount = tradeOrderMapper.updateByIdAndStatus(order.getId(), order.getStatus(), new TradeOrderDO().setStatus(TradeOrderStatusEnum.COMPLETED.getStatus()).setReceiveTime(LocalDateTime.now())); @@ -499,16 +543,139 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { throw exception(ORDER_RECEIVE_FAIL_STATUS_NOT_DELIVERED); } - // TODO 芋艿:lili 发送订单变化的消息 - - // TODO 芋艿:lili 发送商品被购买完成的数据 - - // TODO 芋艿:销售佣金的记录; - // 插入订单日志 TradeOrderLogUtils.setOrderInfo(order.getId(), order.getStatus(), TradeOrderStatusEnum.COMPLETED.getStatus()); } + /** + * 校验交易订单满足可售货的条件 + * + * 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())) { + throw exception(ORDER_RECEIVE_FAIL_STATUS_NOT_DELIVERED); + } + return order; + } + + @Override + @Transactional(rollbackFor = Exception.class) + @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.MEMBER_CANCEL) + public void cancelOrder(Long userId, Long id) { + // 1.1 校验存在 + TradeOrderDO order = tradeOrderMapper.selectOrderByIdAndUserId(id, userId); + if (order == null) { + throw exception(ORDER_NOT_FOUND); + } + // 1.2 校验状态 + if (ObjectUtil.notEqual(order.getStatus(), TradeOrderStatusEnum.UNPAID.getStatus())) { + throw exception(ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID); + } + + // 2. 取消订单 + cancelOrder0(order, TradeOrderCancelTypeEnum.MEMBER_CANCEL); + } + + @Override + public int autoCancelOrder() { + // 1. 查询过期的待支付订单 + LocalDateTime expireTime = addTime(tradeOrderProperties.getPayExpireTime()); + List orders = tradeOrderMapper.selectListByStatusAndCreateTimeLt( + TradeOrderStatusEnum.UNPAID.getStatus(), expireTime); + if (CollUtil.isEmpty(orders)) { + return 0; + } + + // 2. 遍历执行,逐个取消 + int count = 0; + for (TradeOrderDO order : orders) { + try { + getSelf().autoCancelOrder(order); + count ++; + } catch (Throwable e) { + log.error("[autoCancelOrder][order({}) 过期订单异常]", order.getId(), e); + } + } + return count; + } + + /** + * 自动取消单个订单 + * + * @param order 订单 + */ + @Transactional(rollbackFor = Exception.class) + @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.SYSTEM_CANCEL) + public void autoCancelOrder(TradeOrderDO order) { + cancelOrder0(order, TradeOrderCancelTypeEnum.PAY_TIMEOUT); + } + + /** + * 取消订单的核心实现 + * + * @param order 订单 + * @param cancelType 取消类型 + */ + private void cancelOrder0(TradeOrderDO order, TradeOrderCancelTypeEnum cancelType) { + Long id = order.getId(); + // 1. 更新 TradeOrderDO 状态为已取消 + int updateCount = tradeOrderMapper.updateByIdAndStatus(id, order.getStatus(), + new TradeOrderDO().setStatus(TradeOrderStatusEnum.CANCELED.getStatus()) + .setCancelType(cancelType.getType()).setCancelTime(LocalDateTime.now())); + if (updateCount == 0) { + throw exception(ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID); + } + + // 2. TODO 活动相关库存回滚需要活动 id,活动 id 怎么获取?app 端能否传过来;回复:从订单里拿呀 + tradeOrderHandlers.forEach(handler -> handler.rollback()); + + // 3. 回滚库存 + List orderItems = tradeOrderItemMapper.selectListByOrderId(id); + productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convert(orderItems)); + + // 4. 回滚优惠券 + if (order.getCouponId() != null && order.getCouponId() > 0) { + couponApi.returnUsedCoupon(order.getCouponId()); + } + + // 5. 回滚积分(抵扣的) + addUserPoint(order.getUserId(), order.getUsePoint(), MemberPointBizTypeEnum.ORDER_CANCEL, order.getId()); + + // 6. 增加订单日志 + TradeOrderLogUtils.setOrderInfo(order.getId(), order.getStatus(), TradeOrderStatusEnum.CANCELED.getStatus()); + } + + @Override + @Transactional(rollbackFor = Exception.class) + @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.MEMBER_DELETE) + public void deleteOrder(Long userId, Long id) { + // 1.1 校验存在 + TradeOrderDO order = tradeOrderMapper.selectOrderByIdAndUserId(id, userId); + if (order == null) { + throw exception(ORDER_NOT_FOUND); + } + // 1.2 校验状态 + if (ObjectUtil.notEqual(order.getStatus(), TradeOrderStatusEnum.CANCELED.getStatus())) { + throw exception(ORDER_DELETE_FAIL_STATUS_NOT_CANCEL); + } + // 2. 删除订单 + tradeOrderMapper.deleteById(id); + + // 3. 记录日志 + TradeOrderLogUtils.setOrderInfo(order.getId(), order.getStatus(), order.getStatus()); + } + @Override public void updateOrderRemark(TradeOrderRemarkReqVO reqVO) { // 校验并获得交易订单 @@ -604,28 +771,6 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { tradeOrderMapper.updateById(update); } - /** - * 校验交易订单满足可售货的条件 - * - * 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())) { - throw exception(ORDER_RECEIVE_FAIL_STATUS_NOT_DELIVERED); - } - return order; - } - // =================== Order Item =================== @Override @@ -723,113 +868,6 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { return comment; } - @Override - @Transactional(rollbackFor = Exception.class) - @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.MEMBER_CANCEL) - public void cancelOrder(Long userId, Long id) { - // 1.1 校验存在 - TradeOrderDO order = tradeOrderMapper.selectOrderByIdAndUserId(id, userId); - if (order == null) { - throw exception(ORDER_NOT_FOUND); - } - // 1.2 校验状态 - if (ObjectUtil.notEqual(order.getStatus(), TradeOrderStatusEnum.UNPAID.getStatus())) { - throw exception(ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID); - } - - // 2. 取消订单 - cancelOrder0(order, TradeOrderCancelTypeEnum.MEMBER_CANCEL); - } - - @Override - public int autoCancelOrder() { - // 1. 查询过期的待支付订单 - LocalDateTime expireTime = addTime(tradeOrderProperties.getExpireTime()); - List orders = tradeOrderMapper.selectListByStatusAndCreateTimeLt( - TradeOrderStatusEnum.UNPAID.getStatus(), expireTime); - if (CollUtil.isEmpty(orders)) { - return 0; - } - - // 2. 遍历执行,逐个取消 - int count = 0; - for (TradeOrderDO order : orders) { - try { - getSelf().autoCancelOrder(order); - count ++; - } catch (Throwable e) { - log.error("[autoCancelOrder][order({}) 过期订单异常]", order.getId(), e); - } - } - return count; - } - - /** - * 自动取消单个订单 - * - * @param order 订单 - */ - @Transactional(rollbackFor = Exception.class) - @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.SYSTEM_CANCEL) - public void autoCancelOrder(TradeOrderDO order) { - cancelOrder0(order, TradeOrderCancelTypeEnum.PAY_TIMEOUT); - } - - /** - * 取消订单的核心实现 - * - * @param order 订单 - * @param cancelType 取消类型 - */ - private void cancelOrder0(TradeOrderDO order, TradeOrderCancelTypeEnum cancelType) { - Long id = order.getId(); - // 1. 更新 TradeOrderDO 状态为已取消 - int updateCount = tradeOrderMapper.updateByIdAndStatus(id, order.getStatus(), - new TradeOrderDO().setStatus(TradeOrderStatusEnum.CANCELED.getStatus()) - .setCancelType(cancelType.getType()).setCancelTime(LocalDateTime.now())); - if (updateCount == 0) { - throw exception(ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID); - } - - // 2. TODO 活动相关库存回滚需要活动 id,活动 id 怎么获取?app 端能否传过来;回复:从订单里拿呀 - tradeOrderHandlers.forEach(handler -> handler.rollback()); - - // 3. 回滚库存 - List orderItems = tradeOrderItemMapper.selectListByOrderId(id); - productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convert(orderItems)); - - // 4. 回滚优惠券 - if (order.getCouponId() != null && order.getCouponId() > 0) { - couponApi.returnUsedCoupon(order.getCouponId()); - } - - // 5. 回滚积分(抵扣的) - addUserPoint(order.getUserId(), order.getUsePoint(), MemberPointBizTypeEnum.ORDER_CANCEL, order.getId()); - - // 6. 增加订单日志 - TradeOrderLogUtils.setOrderInfo(order.getId(), order.getStatus(), TradeOrderStatusEnum.CANCELED.getStatus()); - } - - @Override - @Transactional(rollbackFor = Exception.class) - @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.MEMBER_DELETE) - public void deleteOrder(Long userId, Long id) { - // 1.1 校验存在 - TradeOrderDO order = tradeOrderMapper.selectOrderByIdAndUserId(id, userId); - if (order == null) { - throw exception(ORDER_NOT_FOUND); - } - // 1.2 校验状态 - if (ObjectUtil.notEqual(order.getStatus(), TradeOrderStatusEnum.CANCELED.getStatus())) { - throw exception(ORDER_DELETE_FAIL_STATUS_NOT_CANCEL); - } - // 2. 删除订单 - tradeOrderMapper.deleteById(id); - - // 3. 记录日志 - TradeOrderLogUtils.setOrderInfo(order.getId(), order.getStatus(), order.getStatus()); - } - /** * 判断指定订单的所有订单项,是不是都售后成功 * diff --git a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceTest.java b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceTest.java index 284ab7ccf..577780bd2 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceTest.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceTest.java @@ -87,7 +87,7 @@ public class TradeOrderUpdateServiceTest extends BaseDbUnitTest { @BeforeEach public void setUp() { when(tradeOrderProperties.getAppId()).thenReturn(888L); - when(tradeOrderProperties.getExpireTime()).thenReturn(Duration.ofDays(1)); + when(tradeOrderProperties.getPayExpireTime()).thenReturn(Duration.ofDays(1)); } @Test diff --git a/yudao-server/src/main/resources/application.yaml b/yudao-server/src/main/resources/application.yaml index 57b12b38f..a89136e42 100644 --- a/yudao-server/src/main/resources/application.yaml +++ b/yudao-server/src/main/resources/application.yaml @@ -203,7 +203,8 @@ yudao: trade: order: app-id: 1 # 商户编号 - expire-time: 2h # 支付的过期时间 + pay-expire-time: 2h # 支付的过期时间 + receive-expire-time: 14d # 收货的过期时间 express: client: kd_niao kd-niao: