Merge remote-tracking branch 'origin/feature/mall_product' into feature/mall_product

# Conflicts:
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderMapper.java
This commit is contained in:
owen
2023-10-04 09:12:48 +08:00
35 changed files with 334 additions and 304 deletions

View File

@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.trade.controller.app.order.vo;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.framework.common.validation.Mobile;
import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
@@ -67,7 +68,7 @@ public class AppTradeOrderSettlementReqVO {
@JsonIgnore
public boolean isValidActivityItems() {
// 校验是否是活动订单
if (seckillActivityId == null && combinationActivityId == null && combinationHeadId == null) {
if (ObjUtil.isAllEmpty(seckillActivityId, combinationActivityId, combinationHeadId)) {
return true;
}
// 校验订单项是否超出

View File

@@ -14,7 +14,7 @@ import java.util.List;
public class AppTradeOrderSettlementRespVO {
@Schema(description = "交易类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") // 对应 TradeOrderTypeEnum 枚举
private Integer type = 1; // TODO 芋艿:改成计算
private Integer type;
@Schema(description = "购物项数组", requiredMode = Schema.RequiredMode.REQUIRED)
private List<Item> items;
@@ -75,6 +75,9 @@ public class AppTradeOrderSettlementRespVO {
@Schema(description = "商品原价(总),单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "500")
private Integer totalPrice;
@Schema(description = "订单优惠(总),单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "66")
private Integer discountPrice;
@Schema(description = "运费金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "50")
private Integer deliveryPrice;

View File

@@ -211,7 +211,8 @@ public interface TradeOrderConvert {
.setCouponId(settlementReqVO.getCouponId()).setPointStatus(settlementReqVO.getPointStatus())
.setDeliveryType(settlementReqVO.getDeliveryType()).setAddressId(settlementReqVO.getAddressId())
.setPickUpStoreId(settlementReqVO.getPickUpStoreId())
.setItems(new ArrayList<>(settlementReqVO.getItems().size()));
.setItems(new ArrayList<>(settlementReqVO.getItems().size()))
.setSeckillActivityId(settlementReqVO.getSeckillActivityId());
// 商品项的构建
Map<Long, CartDO> cartMap = convertMap(cartList, CartDO::getId);
for (AppTradeOrderSettlementReqVO.Item item : settlementReqVO.getItems()) {

View File

@@ -288,4 +288,11 @@ public class TradeOrderDO extends BaseDO {
*/
private Integer vipPrice;
/**
* 秒杀活动编号
*
* 关联 SeckillActivityDO 的 id 字段
*/
private Long seckillActivityId;
}

View File

@@ -1,13 +1,19 @@
package cn.iocoder.yudao.module.trade.dal.mysql.order;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
@Mapper
public interface TradeOrderItemMapper extends BaseMapperX<TradeOrderItemDO> {
@@ -38,4 +44,13 @@ public interface TradeOrderItemMapper extends BaseMapperX<TradeOrderItemDO> {
.eq(TradeOrderItemDO::getCommentStatus, commentStatus));
}
default int selectProductSumByOrderId(@Param("orderIds") Set<Long> orderIds) {
// SQL sum 查询
List<Map<String, Object>> result = selectMaps(new QueryWrapper<TradeOrderItemDO>()
.select("SUM(count) AS sumCount")
.in("order_id", orderIds)); // 只计算选中的
// 获得数量
return CollUtil.getFirst(result) != null ? MapUtil.getInt(result.get(0), "sumCount") : 0;
}
}

View File

@@ -86,6 +86,12 @@ public interface TradeOrderMapper extends BaseMapperX<TradeOrderDO> {
.eq(TradeOrderDO::getCommentStatus, commentStatus));
}
default List<TradeOrderDO> selectListByUserIdAndSeckillActivityId(Long userId, Long seckillActivityId) {
return selectList(new LambdaUpdateWrapper<>(TradeOrderDO.class)
.eq(TradeOrderDO::getUserId, userId)
.eq(TradeOrderDO::getSeckillActivityId, seckillActivityId));
}
default TradeOrderSummaryRespDTO selectSummaryByPayTimeBetween(LocalDateTime beginTime, LocalDateTime endTime) {
return BeanUtil.copyProperties(CollUtil.get(selectMaps(MPJWrappers.<TradeOrderDO>lambdaJoin()
.selectCount(TradeOrderDO::getId, TradeOrderSummaryRespDTO::getOrderPayCount)

View File

@@ -84,6 +84,15 @@ public interface TradeOrderQueryService {
*/
List<ExpressTrackRespDTO> getExpressTrackList(Long id);
/**
* 【会员】在指定秒杀活动下,用户购买的商品数量
*
* @param userId 用户编号
* @param activityId 活动编号
* @return 秒杀商品数量
*/
int getSeckillProductCount(Long userId, Long activityId);
// =================== Order Item ===================
/**

View File

@@ -14,6 +14,7 @@ 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.enums.order.TradeOrderStatusEnum;
import cn.iocoder.yudao.module.trade.framework.delivery.core.client.ExpressClientFactory;
import cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.ExpressTrackQueryReqDTO;
import cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.ExpressTrackRespDTO;
@@ -122,6 +123,18 @@ public class TradeOrderQueryServiceImpl implements TradeOrderQueryService {
return getExpressTrackList(order);
}
@Override
public int getSeckillProductCount(Long userId, Long activityId) {
// 获得订单列表
List<TradeOrderDO> orders = tradeOrderMapper.selectListByUserIdAndSeckillActivityId(userId, activityId);
orders.removeIf(order -> TradeOrderStatusEnum.isCanceled(order.getStatus())); // 过滤掉【已取消】的订单
if (CollUtil.isEmpty(orders)) {
return 0;
}
// 获得订单项列表
return tradeOrderItemMapper.selectProductSumByOrderId(convertSet(orders, TradeOrderDO::getId));
}
// TODO @puhui999可以加个 spring 缓存30 分钟;主要考虑及时性要求不高,但是每次调用需要钱;
/**
* 获得订单的物流轨迹

View File

@@ -14,7 +14,7 @@ import javax.annotation.Resource;
* @author HUIHUI
*/
@Component
public class TradeBargainHandler extends TradeOrderDefaultHandler {
public class TradeBargainHandler implements TradeOrderHandler {
@Resource
private BargainActivityApi bargainActivityApi;

View File

@@ -17,7 +17,7 @@ import javax.annotation.Resource;
* @author HUIHUI
*/
@Component
public class TradeCombinationHandler extends TradeOrderDefaultHandler {
public class TradeCombinationHandler implements TradeOrderHandler {
@Resource
private CombinationRecordApi combinationRecordApi;

View File

@@ -1,34 +0,0 @@
package cn.iocoder.yudao.module.trade.service.order.handler;
import cn.iocoder.yudao.module.trade.service.order.bo.TradeAfterOrderCreateReqBO;
import cn.iocoder.yudao.module.trade.service.order.bo.TradeAfterPayOrderReqBO;
import cn.iocoder.yudao.module.trade.service.order.bo.TradeBeforeOrderCreateReqBO;
/**
* 订单活动特殊逻辑处理器 handler 默认抽象实现类
*
* @author HUIHUI
*/
public abstract class TradeOrderDefaultHandler implements TradeOrderHandler {
@Override
public void beforeOrderCreate(TradeBeforeOrderCreateReqBO reqBO) {
}
@Override
public void afterOrderCreate(TradeAfterOrderCreateReqBO reqBO) {
}
@Override
public void afterPayOrder(TradeAfterPayOrderReqBO reqBO) {
}
@Override
public void cancelOrder() {
}
}

View File

@@ -17,25 +17,25 @@ public interface TradeOrderHandler {
*
* @param reqBO 请求
*/
void beforeOrderCreate(TradeBeforeOrderCreateReqBO reqBO);
default void beforeOrderCreate(TradeBeforeOrderCreateReqBO reqBO) {}
/**
* 订单创建后
*
* @param reqBO 请求
*/
void afterOrderCreate(TradeAfterOrderCreateReqBO reqBO);
default void afterOrderCreate(TradeAfterOrderCreateReqBO reqBO) {}
/**
* 支付订单后
*
* @param reqBO 请求
*/
void afterPayOrder(TradeAfterPayOrderReqBO reqBO);
default void afterPayOrder(TradeAfterPayOrderReqBO reqBO) {}
/**
* 订单取消
*/
void cancelOrder();
default void cancelOrder() {}
}

View File

@@ -14,7 +14,7 @@ import javax.annotation.Resource;
* @author HUIHUI
*/
@Component
public class TradeSeckillHandler extends TradeOrderDefaultHandler {
public class TradeSeckillHandler implements TradeOrderHandler {
@Resource
private SeckillActivityApi seckillActivityApi;

View File

@@ -98,19 +98,9 @@ public class TradePriceCalculateRespBO {
* VIP 减免金额,单位:分
*/
private Integer vipPrice;
/**
* 秒杀、拼团、砍价活动商品的总金额,单位:分
*
* 基于 {@link OrderItem#getActivityPrice()} ()} * {@link OrderItem#getCount()} 求和
*/
private Integer activityPrice;
/**
* 最终购买金额(总),单位:分
*
* ==========活动情况===========
* = {@link #activityPrice}
* + {@link #deliveryPrice}
* ==========正常情况===========
* = {@link #totalPrice}
* - {@link #couponPrice}
* - {@link #pointPrice}
@@ -186,16 +176,9 @@ public class TradePriceCalculateRespBO {
* VIP 减免金额,单位:分
*/
private Integer vipPrice;
/**
* 秒杀、拼团、砍价活动商品的金额,单位:分
*/
private Integer activityPrice;
/**
* 应付金额(总),单位:分
* ==========活动情况===========
* = {@link #activityPrice} * {@link #count}
* + {@link #deliveryPrice}
* ==========正常情况===========
*
* = {@link #price} * {@link #count}
* - {@link #couponPrice}
* - {@link #pointPrice}

View File

@@ -105,9 +105,6 @@ public class TradePriceCalculatorHelper {
if (!item.getSelected()) {
return;
}
// TODO puhui: 需要在这里计算活动的价格
// ========== 一、活动情况 ==========
// ========== 二、正常情况 ==========
price.setTotalPrice(price.getTotalPrice() + item.getPrice() * item.getCount());
price.setDiscountPrice(price.getDiscountPrice() + item.getDiscountPrice());
price.setDeliveryPrice(price.getDeliveryPrice() + item.getDeliveryPrice());

View File

@@ -1,19 +1,22 @@
package cn.iocoder.yudao.module.trade.service.price.calculator;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.promotion.api.seckill.SeckillActivityApi;
import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillActivityProductRespDTO;
import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillValidateJoinRespDTO;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService;
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;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_SECKILL_TOTAL_LIMIT_COUNT;
// TODO huihui单测需要补充
/**
* 秒杀活动的 {@link TradePriceCalculator} 实现类
*
@@ -24,22 +27,45 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
public class TradeSeckillActivityPriceCalculator implements TradePriceCalculator {
@Resource
private SeckillActivityApi activityApi;
private SeckillActivityApi seckillActivityApi;
@Resource
private TradeOrderQueryService tradeOrderQueryService;
@Override
public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
// 1判断订单类型和是否具有秒杀活动编号
// 1. 判断订单类型和是否具有秒杀活动编号
if (param.getSeckillActivityId() == null) {
return;
}
// 2、获取秒杀活动商品信息
List<SeckillActivityProductRespDTO> productList = activityApi.getSeckillActivityProductList(param.getSeckillActivityId(), convertSet(param.getItems(),
TradePriceCalculateReqBO.Item::getSkuId));
Map<Long, SeckillActivityProductRespDTO> productMap = convertMap(productList, SeckillActivityProductRespDTO::getSkuId);
result.getItems().forEach(item -> {
SeckillActivityProductRespDTO product = productMap.get(item.getSkuId());
item.setActivityPrice(product.getSeckillPrice()); // 设置活动金额
});
Assert.isTrue(param.getItems().size() == 1, "秒杀时,只允许选择一个商品");
// 2. 校验是否可以参与秒杀
TradePriceCalculateRespBO.OrderItem orderItem = result.getItems().get(0);
SeckillValidateJoinRespDTO seckillActivity = validateJoinSeckill(
param.getUserId(), param.getSeckillActivityId(),
orderItem.getSkuId(), orderItem.getCount());
// 3.1 记录优惠明细
Integer discountPrice = orderItem.getPayPrice() - seckillActivity.getSeckillPrice() * orderItem.getCount();
TradePriceCalculatorHelper.addPromotion(result, orderItem,
param.getSeckillActivityId(), seckillActivity.getName(), PromotionTypeEnum.SECKILL_ACTIVITY.getType(),
StrUtil.format("秒杀活动:省 {} 元", TradePriceCalculatorHelper.formatPrice(discountPrice)),
discountPrice);
// 3.2 更新 SKU 优惠金额
orderItem.setDiscountPrice(orderItem.getDiscountPrice() + discountPrice);
TradePriceCalculatorHelper.recountPayPrice(orderItem);
TradePriceCalculatorHelper.recountAllPrice(result);
}
private SeckillValidateJoinRespDTO validateJoinSeckill(Long userId, Long activityId, Long skuId, Integer count) {
// 1. 校验是否可以参与秒杀
SeckillValidateJoinRespDTO seckillActivity = seckillActivityApi.validateJoinSeckill(activityId, skuId, count);
// 2. 校验总限购数量,目前只有 trade 有具体下单的数据,需要交给 trade 价格计算使用
int seckillProductCount = tradeOrderQueryService.getSeckillProductCount(userId, activityId);
if (seckillProductCount + count > seckillActivity.getTotalLimitCount()) {
throw exception(PRICE_CALCULATE_SECKILL_TOTAL_LIMIT_COUNT);
}
return seckillActivity;
}
}