orderItems = TradeOrderConvert.INSTANCE.convertList(tradeOrderDO, calculateRespBO);
tradeOrderItemMapper.insertBatch(orderItems);
return orderItems;
}
/**
- * 执行创建完创建完订单后的逻辑
+ * 订单创建前,执行前置逻辑
*
+ * @param userId 用户编号
+ * @param createReqVO 创建订单请求
+ * @param calculateRespBO 订单价格计算结果
+ */
+ private void beforeCreateTradeOrder(Long userId, AppTradeOrderCreateReqVO createReqVO,
+ TradePriceCalculateRespBO calculateRespBO) {
+ // 1. 执行订单创建前置处理器
+ TradeBeforeOrderCreateReqBO beforeOrderCreateReqBO = TradeOrderConvert.INSTANCE.convert(createReqVO);
+ beforeOrderCreateReqBO.setOrderType(calculateRespBO.getType());
+ beforeOrderCreateReqBO.setUserId(userId);
+ beforeOrderCreateReqBO.setCount(getSumValue(createReqVO.getItems(), AppTradeOrderSettlementReqVO.Item::getCount, Integer::sum));
+ // TODO @puhui999:这里有个纠结点;handler 的定义是只处理指定类型的订单的拓展逻辑;还是通用的 handler,类似可以处理优惠劵等等
+ tradeOrderHandlers.forEach(handler -> handler.beforeOrderCreate(beforeOrderCreateReqBO));
+
+ // 2. 下单时扣减商品库存
+ productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convertNegative(createReqVO.getItems()));
+ }
+
+ /**
+ * 订单创建后,执行后置逻辑
+ *
* 例如说:优惠劵的扣减、积分的扣减、支付单的创建等等
*
* @param userId 用户编号
* @param createReqVO 创建订单请求
- * @param tradeOrderDO 交易订单
+ * @param order 交易订单
* @param calculateRespBO 订单价格计算结果
*/
private void afterCreateTradeOrder(Long userId, AppTradeOrderCreateReqVO createReqVO,
- TradeOrderDO tradeOrderDO, List orderItems,
+ TradeOrderDO order, List orderItems,
TradePriceCalculateRespBO calculateRespBO) {
- // 执行订单创建后置处理器
- tradeOrderHandlers.forEach(handler -> handler.afterOrderCreate(TradeOrderConvert.INSTANCE.convert(userId, createReqVO, tradeOrderDO, orderItems.get(0))));
+ // 1. 执行订单创建后置处理器
+ // TODO @puhui999:从通用性来说,应该不用 orderItems.get(0)
+ tradeOrderHandlers.forEach(handler -> handler.afterOrderCreate(
+ TradeOrderConvert.INSTANCE.convert(userId, createReqVO, order, orderItems.get(0))));
- // 扣减积分 TODO 芋艿:待实现,需要前置;
- // 这个是不是应该放到支付成功之后?如果支付后的话,可能积分可以重复使用哈。资源类,都要预扣
-
- // 有使用优惠券时更新 TODO 芋艿:需要前置;
+ // 2. 有使用优惠券时更新
+ // 不在前置扣减的原因,是因为优惠劵要记录使用的订单号
if (createReqVO.getCouponId() != null) {
couponApi.useCoupon(new CouponUseReqDTO().setId(createReqVO.getCouponId()).setUserId(userId)
- .setOrderId(tradeOrderDO.getId()));
+ .setOrderId(order.getId()));
}
- // 下单时扣减商品库存
- productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convertNegative(orderItems));
+ // 3. 扣减积分(抵扣)
+ // 不在前置扣减的原因,是因为积分扣减时,需要记录关联业务
+ reduceUserPoint(order.getUserId(), order.getUsePoint(), MemberPointBizTypeEnum.ORDER_USE, order.getId());
- // 删除购物车商品
+ // 4. 删除购物车商品
Set cartIds = convertSet(createReqVO.getItems(), AppTradeOrderSettlementReqVO.Item::getCartId);
if (CollUtil.isNotEmpty(cartIds)) {
cartService.deleteCart(userId, cartIds);
}
- // 生成预支付
- createPayOrder(tradeOrderDO, orderItems, calculateRespBO);
+ // 5. 生成预支付
+ createPayOrder(order, orderItems, calculateRespBO);
- // 增加订单日志 TODO 芋艿:待实现
- }
+ // 6. 插入订单日志
+ TradeOrderLogUtils.setOrderInfo(order.getId(), null, order.getStatus());
+ // TODO @LeeYan9: 是可以思考下, 订单的营销优惠记录, 应该记录在哪里, 微信讨论起来!
+ }
- private void createPayOrder(TradeOrderDO order, List orderItems, TradePriceCalculateRespBO calculateRespBO) {
+ private void createPayOrder(TradeOrderDO order, List orderItems,
+ TradePriceCalculateRespBO calculateRespBO) {
// 创建支付单,用于后续的支付
PayOrderCreateReqDTO payOrderCreateReqDTO = TradeOrderConvert.INSTANCE.convert(
order, orderItems, calculateRespBO, tradeOrderProperties);
@@ -344,6 +335,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
}
// 校验活动
// 1、拼团活动
+ // TODO @puhui999:这块也抽象到 handler 里
if (Objects.equals(TradeOrderTypeEnum.COMBINATION.getType(), order.getType())) {
// 更新拼团状态 TODO puhui999:订单支付失败或订单支付过期删除这条拼团记录
combinationRecordApi.updateRecordStatusToInProgress(order.getUserId(), order.getId(), LocalDateTime.now());
@@ -354,8 +346,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
// TODO 芋艿:OrderLog
- // 增加用户积分
- getSelf().addUserPointAsync(order.getUserId(), order.getPayPrice(), order.getId());
+ // 增加用户积分(赠送)
+ addUserPoint(order.getUserId(), order.getGivePoint(), MemberPointBizTypeEnum.ORDER_REWARD, order.getId());
// 增加用户经验
getSelf().addUserExperienceAsync(order.getUserId(), order.getPayPrice(), order.getId());
// 增加用户佣金
@@ -421,7 +413,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
// 1.1 校验并获得交易订单(可发货)
TradeOrderDO order = validateOrderDeliverable(deliveryReqVO.getId());
// 1.2 校验 deliveryType 是否为快递,是快递才可以发货
- if (ObjectUtil.notEqual(order.getDeliveryType(), DeliveryTypeEnum.EXPRESS.getMode())) {
+ if (ObjectUtil.notEqual(order.getDeliveryType(), DeliveryTypeEnum.EXPRESS.getType())) {
throw exception(ORDER_DELIVERY_FAIL_DELIVERY_TYPE_NOT_EXPRESS);
}
@@ -494,6 +486,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
@Override
@Transactional(rollbackFor = Exception.class)
+ @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.MEMBER_RECEIVE)
public void receiveOrder(Long userId, Long id) {
// 校验并获得交易订单(可收货)
TradeOrderDO order = validateOrderReceivable(userId, id);
@@ -504,7 +497,6 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
if (updateCount == 0) {
throw exception(ORDER_RECEIVE_FAIL_STATUS_NOT_DELIVERED);
}
- // TODO 芋艿:OrderLog
// TODO 芋艿:lili 发送订单变化的消息
@@ -512,7 +504,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
// TODO 芋艿:销售佣金的记录;
- // TODO 芋艿:获得积分;
+ // 插入订单日志
+ TradeOrderLogUtils.setOrderInfo(order.getId(), order.getStatus(), TradeOrderStatusEnum.COMPLETED.getStatus());
}
@Override
@@ -660,11 +653,12 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
return;
}
// 计算总的退款金额
- TradeOrderDO order = tradeOrderMapper.selectById(tradeOrderItemMapper.selectById(id).getOrderId());
+ TradeOrderItemDO orderItem = tradeOrderItemMapper.selectById(id);
+ TradeOrderDO order = tradeOrderMapper.selectById(orderItem.getOrderId());
Integer orderRefundPrice = order.getRefundPrice() + refundPrice;
if (isAllOrderItemAfterSaleSuccess(order.getId())) { // 如果都售后成功,则需要取消订单
tradeOrderMapper.updateById(new TradeOrderDO().setId(order.getId())
- .setRefundStatus(TradeOrderRefundStatusEnum.ALL.getStatus()).setRefundPrice(orderRefundPrice)
+ .setRefundStatus(TradeOrderRefundStatusEnum.ALL.getStatus()).setRefundPrice(orderRefundPrice).setRefundPoint(order.getRefundPoint() + orderItem.getUsePoint())
.setCancelType(TradeOrderCancelTypeEnum.AFTER_SALE_CLOSE.getType()).setCancelTime(LocalDateTime.now()));
// TODO 芋艿:记录订单日志
@@ -675,16 +669,22 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
.setRefundStatus(TradeOrderRefundStatusEnum.PART.getStatus()).setRefundPrice(orderRefundPrice));
}
- // 扣减用户积分
- getSelf().reduceUserPointAsync(order.getUserId(), orderRefundPrice, afterSaleId);
- // 扣减用户经验
- getSelf().reduceUserExperienceAsync(order.getUserId(), orderRefundPrice, afterSaleId);
- // 更新分佣记录为已失效
- getSelf().cancelBrokerageAsync(order.getUserId(), id);
+ // 售后成功后,执行数据回滚逻辑
+ if (Objects.equals(newAfterSaleStatus, TradeOrderItemAfterSaleStatusEnum.SUCCESS.getStatus())) {
+ // 扣减用户积分(赠送的)
+ reduceUserPoint(order.getUserId(), orderItem.getGivePoint(), MemberPointBizTypeEnum.AFTER_SALE_DEDUCT_GIVE, afterSaleId);
+ // 增加用户积分(返还抵扣)
+ addUserPoint(order.getUserId(), orderItem.getUsePoint(), MemberPointBizTypeEnum.AFTER_SALE_REFUND_USED, afterSaleId);
+ // 扣减用户经验
+ getSelf().reduceUserExperienceAsync(order.getUserId(), orderRefundPrice, afterSaleId);
+ // 更新分佣记录为已失效
+ getSelf().cancelBrokerageAsync(order.getUserId(), id);
+ }
}
@Override
@Transactional(rollbackFor = Exception.class)
+ @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.MEMBER_COMMENT)
public Long createOrderItemComment(Long userId, AppTradeOrderItemCommentCreateReqVO createReqVO) {
// 先通过订单项 ID,查询订单项是否存在
TradeOrderItemDO orderItem = tradeOrderItemMapper.selectByIdAndUserId(createReqVO.getOrderItemId(), userId);
@@ -712,24 +712,27 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
List orderItems = tradeOrderItemMapper.selectListByOrderId(order.getId());
if (!anyMatch(orderItems, item -> Objects.equals(item.getCommentStatus(), Boolean.FALSE))) {
tradeOrderMapper.updateById(new TradeOrderDO().setId(order.getId()).setCommentStatus(Boolean.TRUE));
- // TODO 待实现:已完成评价,要不要写一条订单日志?目前 crmeb 会写,有赞可以研究下
+ // 增加订单日志
+ TradeOrderLogUtils.setOrderInfo(order.getId(), order.getStatus(), order.getStatus());
}
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);
}
- // 1.更新 TradeOrderDO 状态为已取消
+ // 2. 更新 TradeOrderDO 状态为已取消
int updateCount = tradeOrderMapper.updateByIdAndStatus(id, order.getStatus(),
new TradeOrderDO().setStatus(TradeOrderStatusEnum.CANCELED.getStatus())
.setCancelTime(LocalDateTime.now())
@@ -738,21 +741,43 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
throw exception(ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID);
}
- // TODO 活动相关库存回滚需要活动 id,活动 id 怎么获取?app 端能否传过来
+ // 3. TODO 活动相关库存回滚需要活动 id,活动 id 怎么获取?app 端能否传过来;回复:从订单里拿呀
tradeOrderHandlers.forEach(handler -> handler.rollback());
- // 2.回滚库存
+ // 4. 回滚库存
List orderItems = tradeOrderItemMapper.selectListByOrderId(id);
productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convert(orderItems));
- // 3.回滚优惠券
- couponApi.returnUsedCoupon(order.getCouponId());
+ // 5. 回滚优惠券
+ if (order.getCouponId() > 0) {
+ couponApi.returnUsedCoupon(order.getCouponId());
+ }
- // 4.回滚积分:积分是支付成功后才增加的吧? 回复:每个项目不同,目前看下来,确认收货貌似更合适,我再看看其它项目的业务选择;
+ // 6. 回滚积分(抵扣的)
+ addUserPoint(order.getUserId(), order.getUsePoint(), MemberPointBizTypeEnum.ORDER_CANCEL, order.getId());
- // TODO 芋艿:OrderLog
+ // 7. 增加订单日志
+ TradeOrderLogUtils.setOrderInfo(order.getId(), order.getStatus(), TradeOrderStatusEnum.CANCELED.getStatus());
+ }
- // TODO 芋艿:lili 发送订单变化的消息
+ @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());
}
/**
@@ -779,28 +804,54 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
memberLevelApi.addExperience(userId, -refundPrice, bizType, String.valueOf(afterSaleId));
}
- @Async
- protected void addUserPointAsync(Long userId, Integer payPrice, Long orderId) {
- int bizType = MemberPointBizTypeEnum.ORDER_BUY.getType();
- memberPointApi.addPoint(userId, payPrice, bizType, String.valueOf(orderId));
+ /**
+ * 添加用户积分
+ *
+ * 目前是支付成功后,就会创建积分记录。
+ *
+ * 业内还有两种做法,可以根据自己的业务调整:
+ * 1. 确认收货后,才创建积分记录
+ * 2. 支付 or 下单成功时,创建积分记录(冻结),确认收货解冻或者 n 天后解冻
+ *
+ * @param userId 用户编号
+ * @param point 增加积分数量
+ * @param bizType 业务编号
+ * @param bizId 业务编号
+ */
+ protected void addUserPoint(Long userId, Integer point, MemberPointBizTypeEnum bizType, Long bizId) {
+ if (point != null && point > 0) {
+ memberPointApi.addPoint(userId, point, bizType.getType(), String.valueOf(bizId));
+ }
}
- @Async
- protected void reduceUserPointAsync(Long userId, Integer refundPrice, Long afterSaleId) {
- int bizType = MemberPointBizTypeEnum.ORDER_CANCEL.getType();
- memberPointApi.addPoint(userId, -refundPrice, bizType, String.valueOf(afterSaleId));
+ protected void reduceUserPoint(Long userId, Integer point, MemberPointBizTypeEnum bizType, Long bizId) {
+ if (point != null && point > 0) {
+ memberPointApi.reducePoint(userId, point, bizType.getType(), String.valueOf(bizId));
+ }
}
-
+ /**
+ * 创建分销记录
+ *
+ * 目前是支付成功后,就会创建分销记录。
+ *
+ * 业内还有两种做法,可以根据自己的业务调整:
+ * 1. 确认收货后,才创建分销记录
+ * 2. 支付 or 下单成功时,创建分销记录(冻结),确认收货解冻或者 n 天后解冻
+ *
+ * @param userId 用户编号
+ * @param orderId 订单编号
+ */
@Async
protected void addBrokerageAsync(Long userId, Long orderId) {
MemberUserRespDTO user = memberUserApi.getUser(userId);
Assert.notNull(user);
-
+ // 每一个订单项,都会去生成分销记录
List orderItems = tradeOrderItemMapper.selectListByOrderId(orderId);
- List list = convertList(orderItems,
- item -> TradeOrderConvert.INSTANCE.convert(user, item, productSkuApi.getSku(item.getSkuId())));
- brokerageRecordService.addBrokerage(userId, BrokerageRecordBizTypeEnum.ORDER, list);
+ List addList = convertList(orderItems,
+ item -> TradeOrderConvert.INSTANCE.convert(user, item,
+ productSpuApi.getSpu(item.getSpuId()), productSkuApi.getSku(item.getSkuId())));
+ brokerageRecordService.addBrokerage(userId, BrokerageRecordBizTypeEnum.ORDER, addList);
}
@Async
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeBargainHandler.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeBargainHandler.java
index 72def21..18a276f 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeBargainHandler.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeBargainHandler.java
@@ -20,14 +20,17 @@ public class TradeBargainHandler implements TradeOrderHandler {
@Resource
private BargainActivityApi bargainActivityApi;
+ // TODO @puhui999:先临时写在这里;在价格计算时,如果是秒杀商品,需要校验如下条件:
+ // 1. 商品存在、库存充足、单次限购;
+ // 2. 活动进行中、时间段符合
+
@Override
public void beforeOrderCreate(TradeBeforeOrderCreateReqBO reqBO) {
// 如果是砍价订单
if (ObjectUtil.notEqual(TradeOrderTypeEnum.BARGAIN.getType(), reqBO.getOrderType())) {
return;
}
-
- // 额外扣减砍价的库存
+ // 扣减砍价活动的库存
bargainActivityApi.updateBargainActivityStock(reqBO.getBargainActivityId(), reqBO.getCount());
}
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCombinationHandler.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCombinationHandler.java
index ad777b3..42a2f81 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCombinationHandler.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCombinationHandler.java
@@ -37,6 +37,11 @@ public class TradeCombinationHandler implements TradeOrderHandler {
@Override
public void afterOrderCreate(TradeAfterOrderCreateReqBO reqBO) {
+ // TODO @puhui999:需要判断下;
+ if (true) {
+ return;
+ }
+
// 创建砍价记录
combinationRecordApi.createCombinationRecord(TradeOrderConvert.INSTANCE.convert(reqBO));
}
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeOrderHandler.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeOrderHandler.java
index b747e35..47426dd 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeOrderHandler.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeOrderHandler.java
@@ -24,9 +24,12 @@ public interface TradeOrderHandler {
*/
void afterOrderCreate(TradeAfterOrderCreateReqBO reqBO);
+ // TODO @puhui999:这个搞成订单取消
/**
* 回滚
*/
void rollback();
+ // TODO @puhui999:再搞个订单项取消哈
+
}
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeSeckillHandler.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeSeckillHandler.java
index ce1dfdb..652717b 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeSeckillHandler.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeSeckillHandler.java
@@ -20,13 +20,17 @@ public class TradeSeckillHandler implements TradeOrderHandler {
@Resource
private SeckillActivityApi seckillActivityApi;
+ // TODO @puhui999:先临时写在这里;在价格计算时,如果是秒杀商品,需要校验如下条件:
+ // 1. 商品存在、库存充足、单次限购;
+ // 2. 活动进行中、时间段符合
+
@Override
public void beforeOrderCreate(TradeBeforeOrderCreateReqBO reqBO) {
// 如果是秒杀订单:额外扣减秒杀的库存;
if (ObjectUtil.notEqual(TradeOrderTypeEnum.SECKILL.getType(), reqBO.getOrderType())) {
return;
}
-
+ // 扣减秒杀活动的库存
seckillActivityApi.updateSeckillStock(reqBO.getSeckillActivityId(), reqBO.getSkuId(), reqBO.getCount());
}
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/TradePriceServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/TradePriceServiceImpl.java
index e211374..a6982c1 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/TradePriceServiceImpl.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/TradePriceServiceImpl.java
@@ -41,7 +41,6 @@ public class TradePriceServiceImpl implements TradePriceService {
@Resource
private List priceCalculators;
- // TODO @疯狂:需要搞个 TradePriceCalculator,计算赠送积分;
@Override
public TradePriceCalculateRespBO calculatePrice(TradePriceCalculateReqBO calculateReqBO) {
// 1.1 获得商品 SKU 数组
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateReqBO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateReqBO.java
index c52f800..a908a93 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateReqBO.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateReqBO.java
@@ -1,8 +1,6 @@
package cn.iocoder.yudao.module.trade.service.price.bo;
import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
-import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
-import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.Valid;
@@ -19,13 +17,6 @@ import java.util.List;
public class TradePriceCalculateReqBO {
/**
- * 订单类型
- *
- * 枚举 {@link TradeOrderTypeEnum}
- */
- private Integer type;
-
- /**
* 用户编号
*
* 对应 MemberUserDO 的 id 编号
@@ -40,11 +31,10 @@ public class TradePriceCalculateReqBO {
private Long couponId;
/**
- * 收货地址编号
- *
- * 对应 MemberAddressDO 的 id 编号
+ * 是否使用积分
*/
- private Long addressId;
+ @NotNull(message = "是否使用积分不能为空")
+ private Boolean pointStatus;
/**
* 配送方式
@@ -52,29 +42,48 @@ public class TradePriceCalculateReqBO {
* 枚举 {@link DeliveryTypeEnum}
*/
private Integer deliveryType;
+ /**
+ * 收货地址编号
+ *
+ * 对应 MemberAddressDO 的 id 编号
+ */
+ private Long addressId;
+ /**
+ * 自提门店编号
+ *
+ * 对应 PickUpStoreDO 的 id 编号
+ */
+ private Long pickUpStoreId;
+
+ /**
+ * 商品 SKU 数组
+ */
+ @NotNull(message = "商品数组不能为空")
+ private List- items;
// ========== 秒杀活动相关字段 ==========
- @Schema(description = "秒杀活动编号", example = "1024")
+ /**
+ * 秒杀活动编号
+ */
private Long seckillActivityId;
// ========== 拼团活动相关字段 ==========
// TODO @puhui999:是不是拼团记录的编号哈?
- @Schema(description = "拼团活动编号", example = "1024")
+ /**
+ * 拼团活动编号
+ */
private Long combinationActivityId;
- @Schema(description = "拼团团长编号", example = "2048")
+ /**
+ * 拼团团长编号
+ */
private Long combinationHeadId;
// ========== 砍价活动相关字段 ==========
- // TODO @puhui999:是不是砍价记录的编号哈?
- @Schema(description = "砍价活动编号", example = "123")
- private Long bargainActivityId;
-
/**
- * 商品 SKU 数组
+ * 砍价活动编号
*/
- @NotNull(message = "商品数组不能为空")
- private List
- items;
+ private Long bargainActivityId;
/**
* 商品 SKU
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java
index 2aee9c6..0527a12 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java
@@ -49,6 +49,16 @@ public class TradePriceCalculateRespBO {
private Long couponId;
/**
+ * 使用的积分
+ */
+ private Integer usePoint;
+
+ /**
+ * 使用的积分
+ */
+ private Integer givePoint;
+
+ /**
* 订单价格
*/
@Data
@@ -85,6 +95,10 @@ public class TradePriceCalculateRespBO {
*/
private Integer pointPrice;
/**
+ * VIP 减免金额,单位:分
+ */
+ private Integer vipPrice;
+ /**
* 秒杀、拼团、砍价活动商品的总金额,单位:分
*
* 基于 {@link OrderItem#getActivityPrice()} ()} * {@link OrderItem#getCount()} 求和
@@ -102,6 +116,7 @@ public class TradePriceCalculateRespBO {
* - {@link #pointPrice}
* - {@link #discountPrice}
* + {@link #deliveryPrice}
+ * - {@link #vipPrice}
*/
private Integer payPrice;
@@ -164,6 +179,14 @@ public class TradePriceCalculateRespBO {
*/
private Integer pointPrice;
/**
+ * 使用的积分
+ */
+ private Integer usePoint;
+ /**
+ * VIP 减免金额,单位:分
+ */
+ private Integer vipPrice;
+ /**
* 秒杀、拼团、砍价活动商品的金额,单位:分
*/
private Integer activityPrice;
@@ -178,6 +201,7 @@ public class TradePriceCalculateRespBO {
* - {@link #pointPrice}
* - {@link #discountPrice}
* + {@link #deliveryPrice}
+ * - {@link #vipPrice}
*/
private Integer payPrice;
@@ -217,6 +241,11 @@ public class TradePriceCalculateRespBO {
*/
private List properties;
+ /**
+ * 使用的积分
+ */
+ private Integer givePoint;
+
}
/**
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeCouponPriceCalculator.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeCouponPriceCalculator.java
index 68955ce..b871186 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeCouponPriceCalculator.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeCouponPriceCalculator.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.trade.service.price.calculator;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi;
import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponRespDTO;
@@ -9,6 +10,7 @@ import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponValidReqDTO;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
+import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
import org.springframework.core.annotation.Order;
@@ -22,6 +24,7 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_NO_MATCH_MIN_PRICE;
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_NO_MATCH_SPU;
+import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_COUPON_NOT_MATCH_NORMAL_ORDER;
/**
* 优惠劵的 {@link TradePriceCalculator} 实现类
@@ -44,6 +47,10 @@ public class TradeCouponPriceCalculator implements TradePriceCalculator {
CouponRespDTO coupon = couponApi.validateCoupon(new CouponValidReqDTO()
.setId(param.getCouponId()).setUserId(param.getUserId()));
Assert.notNull(coupon, "校验通过的优惠劵({}),不能为空", param.getCouponId());
+ // 1.2 只有【普通】订单,才允许使用优惠劵
+ if (ObjectUtil.notEqual(result.getType(), TradeOrderTypeEnum.NORMAL.getType())) {
+ throw exception(PRICE_CALCULATE_COUPON_NOT_MATCH_NORMAL_ORDER);
+ }
// 2.1 获得匹配的商品 SKU 数组
List orderItems = filterMatchCouponOrderItems(result, coupon);
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java
index 5454c45..01796ad 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java
@@ -2,11 +2,17 @@ package cn.iocoder.yudao.module.trade.service.price.calculator;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.module.member.api.address.AddressApi;
import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryPickUpStoreDO;
import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryExpressChargeModeEnum;
import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
+import cn.iocoder.yudao.module.trade.service.config.TradeConfigService;
import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressTemplateService;
+import cn.iocoder.yudao.module.trade.service.delivery.DeliveryPickUpStoreService;
import cn.iocoder.yudao.module.trade.service.delivery.bo.DeliveryExpressTemplateRespBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
@@ -22,8 +28,7 @@ import java.util.Set;
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.PRICE_CALCULATE_DELIVERY_PRICE_USER_ADDR_IS_EMPTY;
-import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_DELIVERY_PRICE_TEMPLATE_NOT_FOUND;
+import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*;
/**
* 运费的 {@link TradePriceCalculator} 实现类
@@ -37,29 +42,60 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
@Resource
private AddressApi addressApi;
+
+ @Resource
+ private DeliveryPickUpStoreService deliveryPickUpStoreService;
@Resource
private DeliveryExpressTemplateService deliveryExpressTemplateService;
+ @Resource
+ private TradeConfigService tradeConfigService;
@Override
public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
- // TODO @芋艿:如果门店自提,需要校验是否开启;
- // 1.1 判断配送方式
- if (param.getDeliveryType() == null || DeliveryTypeEnum.PICK_UP.getMode().equals(param.getDeliveryType())) {
+ if (param.getDeliveryType() == null) {
+ return;
+ }
+ if (DeliveryTypeEnum.PICK_UP.getType().equals(param.getDeliveryType())) {
+ calculateByPickUp(param);
+ } else if (DeliveryTypeEnum.EXPRESS.getType().equals(param.getDeliveryType())) {
+ calculateExpress(param, result);
+ }
+ }
+
+ private void calculateByPickUp(TradePriceCalculateReqBO param) {
+ if (param.getPickUpStoreId() == null) {
+ // 价格计算时,如果为空就不算~最终下单,会校验该字段不允许空
return;
}
+ DeliveryPickUpStoreDO pickUpStore = deliveryPickUpStoreService.getDeliveryPickUpStore(param.getPickUpStoreId());
+ if (pickUpStore == null || CommonStatusEnum.DISABLE.getStatus().equals(pickUpStore.getStatus())) {
+ throw exception(PICK_UP_STORE_NOT_EXISTS);
+ }
+ }
+
+ // ========= 快递发货 ==========
+
+ private void calculateExpress(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
+ // 0. 得到收件地址区域
if (param.getAddressId() == null) {
- throw exception(PRICE_CALCULATE_DELIVERY_PRICE_USER_ADDR_IS_EMPTY);
+ // 价格计算时,如果为空就不算~最终下单,会校验该字段不允许空
+ return;
}
- // 1.2 得到收件地址区域
AddressRespDTO address = addressApi.getAddress(param.getAddressId(), param.getUserId());
Assert.notNull(address, "收件人({})的地址,不能为空", param.getUserId());
- // 2. 过滤出已选中的商品SKU
+ // 情况一:全局包邮
+ if (isGlobalExpressFree(result)) {
+ return;
+ }
+
+ // 情况二:快递模版
+ // 2.1 过滤出已选中的商品 SKU
List selectedItem = filterList(result.getItems(), OrderItem::getSelected);
Set deliveryTemplateIds = convertSet(selectedItem, OrderItem::getDeliveryTemplateId);
Map expressTemplateMap =
deliveryExpressTemplateService.getExpressTemplateMapByIdsAndArea(deliveryTemplateIds, address.getAreaId());
- // 3. 计算配送费用
+ // 2.2 计算配送费用
if (CollUtil.isEmpty(expressTemplateMap)) {
log.error("[calculate][找不到商品 templateIds {} areaId{} 对应的运费模板]", deliveryTemplateIds, address.getAreaId());
throw exception(PRICE_CALCULATE_DELIVERY_PRICE_TEMPLATE_NOT_FOUND);
@@ -67,13 +103,26 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
calculateDeliveryPrice(selectedItem, expressTemplateMap, result);
}
+ /**
+ * 是否全局包邮
+ *
+ * @param result 计算结果
+ * @return 是否包邮
+ */
+ private boolean isGlobalExpressFree(TradePriceCalculateRespBO result) {
+ TradeConfigDO config = tradeConfigService.getTradeConfig();
+ return config != null
+ && Boolean.TRUE.equals(config.getDeliveryExpressFreeEnabled()) // 开启包邮
+ && result.getPrice().getPayPrice() >= config.getDeliveryExpressFreePrice(); // 满足包邮的价格
+ }
+
private void calculateDeliveryPrice(List selectedSkus,
Map expressTemplateMap,
TradePriceCalculateRespBO result) {
// 按商品运费模板来计算商品的运费:相同的运费模板可能对应多条订单商品 SKU
- Map> tplIdItemMap = convertMultiMap(selectedSkus, OrderItem::getDeliveryTemplateId);
+ Map> template2ItemMap = convertMultiMap(selectedSkus, OrderItem::getDeliveryTemplateId);
// 依次计算快递运费
- for (Map.Entry> entry : tplIdItemMap.entrySet()) {
+ for (Map.Entry> entry : template2ItemMap.entrySet()) {
Long templateId = entry.getKey();
List orderItems = entry.getValue();
DeliveryExpressTemplateRespBO templateBO = expressTemplateMap.get(templateId);
@@ -81,26 +130,12 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
log.error("[calculateDeliveryPrice][不能计算快递运费,找不到 templateId({}) 对应的运费模板配置]", templateId);
continue;
}
- // 总件数, 总金额, 总重量, 总体积
- int totalCount = 0;
- int totalPrice = 0;
- double totalWeight = 0;
- double totalVolume = 0;
- for (OrderItem orderItem : orderItems) {
- totalCount += orderItem.getCount();
- totalPrice += orderItem.getPayPrice();
- totalWeight += totalWeight + orderItem.getWeight() * orderItem.getCount();
- totalVolume += totalVolume + orderItem.getVolume() * orderItem.getCount();
- }
- // 优先判断是否包邮. 如果包邮不计算快递运费
- if (isExpressFree(templateBO.getChargeMode(), totalCount, totalWeight,
- totalVolume, totalPrice, templateBO.getFree())) {
+ // 1. 优先判断是否包邮。如果包邮不计算快递运费
+ if (isExpressTemplateFree(orderItems, templateBO.getChargeMode(), templateBO.getFree())) {
continue;
}
- // 计算快递运费
- calculateExpressFeeByChargeMode(totalCount, totalWeight, totalVolume,
- templateBO.getChargeMode(), templateBO.getCharge(), orderItems);
-
+ // 2. 计算快递运费
+ calculateExpressFeeByChargeMode(orderItems, templateBO.getChargeMode(), templateBO.getCharge());
}
TradePriceCalculatorHelper.recountAllPrice(result);
}
@@ -108,73 +143,44 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
/**
* 按配送方式来计算运费
*
- * @param totalCount 总件数
- * @param totalWeight 总重量
- * @param totalVolume 总体积
+ * @param orderItems SKU 商品项目
* @param chargeMode 配送计费方式
* @param templateCharge 快递运费配置
- * @param orderItems SKU 商品项目
*/
- private void calculateExpressFeeByChargeMode(double totalCount, double totalWeight, double totalVolume,
- int chargeMode, DeliveryExpressTemplateRespBO.Charge templateCharge,
- List orderItems) {
+ private void calculateExpressFeeByChargeMode(List orderItems, Integer chargeMode,
+ DeliveryExpressTemplateRespBO.Charge templateCharge) {
if (templateCharge == null) {
log.error("[calculateExpressFeeByChargeMode][计算快递运费时,找不到 SKU({}) 对应的运费模版]", orderItems);
return;
}
- DeliveryExpressChargeModeEnum chargeModeEnum = DeliveryExpressChargeModeEnum.valueOf(chargeMode);
- switch (chargeModeEnum) {
- case PIECE: {
- calculateExpressFee(totalCount, templateCharge, orderItems);
- break;
- }
- case WEIGHT: {
- calculateExpressFee(totalWeight, templateCharge, orderItems);
- break;
- }
- case VOLUME: {
- calculateExpressFee(totalVolume, templateCharge, orderItems);
- break;
- }
- }
- }
-
- /**
- * 计算 SKU 商品快递费用
- *
- * @param total 总件数/总重量/总体积
- * @param templateCharge 快递运费配置
- * @param orderItems SKU 商品项目
- */
- private void calculateExpressFee(double total, DeliveryExpressTemplateRespBO.Charge templateCharge, List orderItems) {
+ double totalChargeValue = getTotalChargeValue(orderItems, chargeMode);
+ // 1. 计算 SKU 商品快递费用
int deliveryPrice;
- if (total <= templateCharge.getStartCount()) {
+ if (totalChargeValue <= templateCharge.getStartCount()) {
deliveryPrice = templateCharge.getStartPrice();
} else {
- double remainWeight = total - templateCharge.getStartCount();
+ double remainWeight = totalChargeValue - templateCharge.getStartCount();
// 剩余重量/ 续件 = 续件的次数. 向上取整
int extraNum = (int) Math.ceil(remainWeight / templateCharge.getExtraCount());
int extraPrice = templateCharge.getExtraPrice() * extraNum;
deliveryPrice = templateCharge.getStartPrice() + extraPrice;
}
- // 分摊快递费用到 SKU. 退费的时候,可能按照 SKU 考虑退费金额
- divideDeliveryPrice(deliveryPrice, orderItems);
- }
- /**
- * 快递运费分摊到每个 SKU 商品上
- *
- * @param deliveryPrice 快递运费
- * @param orderItems SKU 商品
- */
- private void divideDeliveryPrice(int deliveryPrice, List orderItems) {
- // TODO @jason:分摊的话,是不是要按照比例呀?重量、价格、数量等等,
- // 按比例是不是有点复杂。后面看看是否需要;
- // TODO 可以看看别的项目怎么搞的哈。
- int dividePrice = deliveryPrice / orderItems.size();
- for (OrderItem item : orderItems) {
+ // 2. 分摊快递费用到 SKU. 退费的时候,可能按照 SKU 考虑退费金额
+ int remainPrice = deliveryPrice;
+ for (int i = 0; i < orderItems.size(); i++) {
+ TradePriceCalculateRespBO.OrderItem item = orderItems.get(i);
+ int partPrice;
+ double chargeValue = getChargeValue(item, chargeMode);
+ if (i < orderItems.size() - 1) { // 减一的原因,是因为拆分时,如果按照比例,可能会出现.所以最后一个,使用反减
+ partPrice = (int) (deliveryPrice * (chargeValue / totalChargeValue));
+ remainPrice -= partPrice;
+ } else {
+ partPrice = remainPrice;
+ }
+ Assert.isTrue(partPrice >= 0, "分摊金额必须大于等于 0");
// 更新快递运费
- item.setDeliveryPrice(dividePrice);
+ item.setDeliveryPrice(partPrice);
TradePriceCalculatorHelper.recountPayPrice(item);
}
}
@@ -183,42 +189,38 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
* 检查是否包邮
*
* @param chargeMode 配送计费方式
- * @param totalCount 总件数
- * @param totalWeight 总重量
- * @param totalVolume 总体积
- * @param totalPrice 总金额
* @param templateFree 包邮配置
*/
- private boolean isExpressFree(Integer chargeMode, int totalCount, double totalWeight,
- double totalVolume, int totalPrice, DeliveryExpressTemplateRespBO.Free templateFree) {
+ private boolean isExpressTemplateFree(List