Merge branch 'develop' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into master-jdk17

This commit is contained in:
YunaiV
2024-10-04 19:41:03 +08:00
26 changed files with 496 additions and 19 deletions

View File

@@ -6,13 +6,13 @@ import cn.iocoder.yudao.framework.common.validation.Mobile;
import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import jakarta.validation.Valid;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.List;
@Schema(description = "用户 App - 交易订单结算 Request VO")
@@ -62,6 +62,10 @@ public class AppTradeOrderSettlementReqVO {
@Schema(description = "砍价记录编号", example = "123")
private Long bargainRecordId;
// ========== 积分商城活动相关字段 ==========
@Schema(description = "积分商城活动编号", example = "123")
private Long pointActivityId;
@AssertTrue(message = "活动商品每次只能购买一种规格")
@JsonIgnore
public boolean isValidActivityItems() {

View File

@@ -219,7 +219,8 @@ public interface TradeOrderConvert {
.setSeckillActivityId(settlementReqVO.getSeckillActivityId())
.setBargainRecordId(settlementReqVO.getBargainRecordId())
.setCombinationActivityId(settlementReqVO.getCombinationActivityId())
.setCombinationHeadId(settlementReqVO.getCombinationHeadId());
.setCombinationHeadId(settlementReqVO.getCombinationHeadId())
.setPointActivityId(settlementReqVO.getPointActivityId());
// 商品项的构建
Map<Long, CartDO> cartMap = convertMap(cartList, CartDO::getId);
for (AppTradeOrderSettlementReqVO.Item item : settlementReqVO.getItems()) {

View File

@@ -353,4 +353,11 @@ public class TradeOrderDO extends BaseDO {
*/
private Long combinationRecordId;
/**
* 积分商城活动的编号
*
* 关联 PointActivityDO 的 id 字段
*/
private Long pointActivityId;
}

View File

@@ -42,9 +42,9 @@ public interface TradeOrderQueryService {
/**
* 获得指定用户,指定活动,指定状态的交易订单
*
* @param userId 用户编号
* @param userId 用户编号
* @param combinationActivityId 活动编号
* @param status 订单状态
* @param status 订单状态
* @return 交易订单
*/
TradeOrderDO getOrderByUserIdAndStatusAndCombination(Long userId, Long combinationActivityId, Integer status);

View File

@@ -245,7 +245,10 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
}
// 3. 生成预支付
createPayOrder(order, orderItems);
// 特殊情况:积分兑换时,可能支付金额为零
if (order.getPayPrice() > 0) {
createPayOrder(order, orderItems);
}
// 4. 插入订单日志
TradeOrderLogUtils.setOrderInfo(order.getId(), null, order.getStatus());

View File

@@ -0,0 +1,71 @@
package cn.iocoder.yudao.module.trade.service.order.handler;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.module.promotion.api.point.PointActivityApi;
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.TradeOrderStatusEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Objects;
/**
* 积分商城活动订单的 {@link TradeOrderHandler} 实现类
*
* @author HUIHUI
*/
@Component
public class TradePointOrderHandler implements TradeOrderHandler {
@Resource
private PointActivityApi pointActivityApi;
@Override
public void beforeOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
if (!TradeOrderTypeEnum.isPoint(order.getType())) {
return;
}
// 明确校验一下
Assert.isTrue(orderItems.size() == 1, "积分商城活动兑换商品兑换时,只允许选择一个商品");
// 扣减积分商城活动的库存
pointActivityApi.updatePointStockDecr(order.getPointActivityId(),
orderItems.get(0).getSkuId(), orderItems.get(0).getCount());
// 如果支付金额为 0则直接设置为已支付
if (Objects.equals(order.getPayPrice(), 0)) {
order.setPayStatus(true).setStatus(TradeOrderStatusEnum.UNDELIVERED.getStatus());
}
}
@Override
public void afterCancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
if (!TradeOrderTypeEnum.isPoint(order.getType())) {
return;
}
// 明确校验一下
Assert.isTrue(orderItems.size() == 1, "积分商城活动兑换商品兑换时,只允许选择一个商品");
// 售后的订单项,已经在 afterCancelOrderItem 回滚库存,所以这里不需要重复回滚
orderItems = filterOrderItemListByNoneAfterSale(orderItems);
if (CollUtil.isEmpty(orderItems)) {
return;
}
afterCancelOrderItem(order, orderItems.get(0));
}
@Override
public void afterCancelOrderItem(TradeOrderDO order, TradeOrderItemDO orderItem) {
if (!TradeOrderTypeEnum.isPoint(order.getType())) {
return;
}
// 恢复积分商城活动的库存
pointActivityApi.updatePointStockIncr(order.getPointActivityId(),
orderItem.getSkuId(), orderItem.getCount());
}
}

View File

@@ -69,7 +69,8 @@ public class TradePriceServiceImpl implements TradePriceService {
.buildCalculateResp(calculateReqBO, spuList, skuList);
priceCalculators.forEach(calculator -> calculator.calculate(calculateReqBO, calculateRespBO));
// 2.2 如果最终支付金额小于等于 0则抛出业务异常
if (calculateRespBO.getPrice().getPayPrice() <= 0) {
if (calculateReqBO.getPointActivityId() == null // 积分订单,允许支付金额为 0
&& calculateRespBO.getPrice().getPayPrice() <= 0) {
log.error("[calculatePrice][价格计算不正确,请求 calculateReqDTO({}),结果 priceCalculate({})]",
calculateReqBO, calculateRespBO);
throw exception(PRICE_CALCULATE_PAY_PRICE_ILLEGAL);

View File

@@ -1,11 +1,11 @@
package cn.iocoder.yudao.module.trade.service.price.bo;
import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
import lombok.Data;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.List;
/**
@@ -84,6 +84,12 @@ public class TradePriceCalculateReqBO {
*/
private Long bargainRecordId;
// ========== 积分商城活动相关字段 ==========
/**
* 积分商城活动编号
*/
private Long pointActivityId;
/**
* 商品 SKU
*/

View File

@@ -0,0 +1,94 @@
package cn.iocoder.yudao.module.trade.service.price.calculator;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.promotion.api.point.PointActivityApi;
import cn.iocoder.yudao.module.promotion.api.point.dto.PointValidateJoinRespDTO;
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.order.TradeOrderQueryService;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_POINT_TOTAL_LIMIT_COUNT;
/**
* 积分商城的 {@link TradePriceCalculator} 实现类
*
* @author owen
*/
@Component
@Order(TradePriceCalculator.ORDER_POINT_ACTIVITY)
@Slf4j
public class TradePointActivityPriceCalculator implements TradePriceCalculator {
@Resource
private PointActivityApi pointActivityApi;
@Resource
private MemberUserApi memberUserApi;
@Resource
private TradeOrderQueryService tradeOrderQueryService;
@Override
public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
// 1.1 判断订单类型是否为积分商城活动
if (ObjectUtil.notEqual(result.getType(), TradeOrderTypeEnum.POINT.getType())) {
return;
}
// 1.2 初始化积分
MemberUserRespDTO user = memberUserApi.getUser(param.getUserId());
result.setTotalPoint(user.getPoint()).setUsePoint(0);
// 1.3 校验用户积分余额
if (user.getPoint() == null || user.getPoint() <= 0) {
return;
}
Assert.isTrue(param.getItems().size() == 1, "积分商城兑换商品时,只允许选择一个商品");
// 2. 校验是否可以参与积分商城活动
TradePriceCalculateRespBO.OrderItem orderItem = result.getItems().get(0);
PointValidateJoinRespDTO activity = validateJoinSeckill(
param.getUserId(), param.getPointActivityId(),
orderItem.getSkuId(), orderItem.getCount());
// 3.1 记录优惠明细
int discountPrice = orderItem.getPayPrice(); // 情况一:单使用积分兑换
Assert.isTrue(activity.getPoint() >= 1, "积分商城商品兑换积分必须大于 1");
result.setUsePoint(activity.getPoint() * orderItem.getCount());
orderItem.setUsePoint(activity.getPoint() * orderItem.getCount());
if (activity.getPrice() != null && activity.getPrice() > 0) { // 情况二:积分 + 金额
discountPrice = orderItem.getPayPrice() - activity.getPrice() * orderItem.getCount();
}
// 3.2 记录优惠明细
TradePriceCalculatorHelper.addPromotion(result, orderItem,
param.getPointActivityId(), "积分商城活动", PromotionTypeEnum.POINT.getType(),
StrUtil.format("积分商城活动:省 {} 元", TradePriceCalculatorHelper.formatPrice(discountPrice)),
discountPrice);
// 3.3 更新 SKU 优惠金额
orderItem.setDiscountPrice(orderItem.getDiscountPrice() + discountPrice);
TradePriceCalculatorHelper.recountPayPrice(orderItem);
TradePriceCalculatorHelper.recountAllPrice(result);
}
private PointValidateJoinRespDTO validateJoinSeckill(Long userId, Long activityId, Long skuId, Integer count) {
// 1. 校验是否可以参与积分商城活动
PointValidateJoinRespDTO pointValidateJoinRespDTO = pointActivityApi.validateJoinPointActivity(activityId, skuId, count);
// 2. 校验总限购数量,目前只有 trade 有具体下单的数据,需要交给 trade 价格计算使用
int activityProductCount = tradeOrderQueryService.getSeckillProductCount(userId, activityId);
if (activityProductCount + count > pointValidateJoinRespDTO.getCount()) {
throw exception(PRICE_CALCULATE_POINT_TOTAL_LIMIT_COUNT);
}
return pointValidateJoinRespDTO;
}
}

View File

@@ -1,12 +1,14 @@
package cn.iocoder.yudao.module.trade.service.price.calculator;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.member.api.config.MemberConfigApi;
import cn.iocoder.yudao.module.member.api.config.dto.MemberConfigRespDTO;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
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 jakarta.annotation.Resource;
@@ -37,6 +39,10 @@ public class TradePointUsePriceCalculator implements TradePriceCalculator {
@Override
public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
// 判断订单类型是否不为积分商城活动
if (ObjectUtil.equal(result.getType(), TradeOrderTypeEnum.POINT.getType())) {
return;
}
// 0. 初始化积分
MemberUserRespDTO user = memberUserApi.getUser(param.getUserId());
result.setTotalPoint(user.getPoint()).setUsePoint(0);

View File

@@ -16,6 +16,7 @@ public interface TradePriceCalculator {
int ORDER_SECKILL_ACTIVITY = 8;
int ORDER_BARGAIN_ACTIVITY = 8;
int ORDER_COMBINATION_ACTIVITY = 8;
int ORDER_POINT_ACTIVITY = 8;
int ORDER_DISCOUNT_ACTIVITY = 10;
int ORDER_REWARD_ACTIVITY = 20;

View File

@@ -90,6 +90,9 @@ public class TradePriceCalculatorHelper {
if (param.getBargainRecordId() != null) {
return TradeOrderTypeEnum.BARGAIN.getType();
}
if (param.getPointActivityId() != null) {
return TradeOrderTypeEnum.POINT.getType();
}
return TradeOrderTypeEnum.NORMAL.getType();
}

View File

@@ -8,11 +8,10 @@ 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 jakarta.annotation.Resource;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import jakarta.annotation.Resource;
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;