feat(pricing): 增加商品和打包配置的优惠券及券码使用限制

- 在 PriceBundleConfig 和 PriceProductConfig 中添加是否可使用优惠券和券码的字段
- 修改 CouponDiscountProvider 和 VoucherDiscountProvider,增加对商品和打包配置的检查
- 更新 PriceCalculationServiceImpl 中的优惠计算逻辑,将一口价改为打包购买
- 调整 DiscountDetail 中的描述和排序顺序,以适应新的优惠方式
This commit is contained in:
2025-09-05 11:09:28 +08:00
parent bd077b9252
commit 5210b50adb
11 changed files with 231 additions and 24 deletions

View File

@@ -29,6 +29,14 @@ public interface IPriceBundleService {
*/
BigDecimal getBundlePrice(List<ProductItem> products);
/**
* 获取适用的一口价配置
*
* @param products 商品列表
* @return 适用的一口价配置,如果不适用则返回null
*/
PriceBundleConfig getBundleConfig(List<ProductItem> products);
/**
* 获取所有启用的一口价配置
*

View File

@@ -1,8 +1,12 @@
package com.ycwl.basic.pricing.service.impl;
import com.ycwl.basic.pricing.dto.*;
import com.ycwl.basic.pricing.entity.PriceBundleConfig;
import com.ycwl.basic.pricing.entity.PriceProductConfig;
import com.ycwl.basic.pricing.service.ICouponService;
import com.ycwl.basic.pricing.service.IDiscountProvider;
import com.ycwl.basic.pricing.service.IPriceBundleService;
import com.ycwl.basic.pricing.service.IProductConfigService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@@ -20,6 +24,8 @@ import java.util.List;
public class CouponDiscountProvider implements IDiscountProvider {
private final ICouponService couponService;
private final IProductConfigService productConfigService;
private final IPriceBundleService bundleService;
@Override
public String getProviderType() {
@@ -39,6 +45,12 @@ public class CouponDiscountProvider implements IDiscountProvider {
return discounts;
}
// 检查商品配置和打包配置是否允许使用优惠券
if (!checkCanUseCoupon(context)) {
log.debug("商品配置不允许使用优惠券,跳过优惠券检测");
return discounts;
}
try {
CouponInfo bestCoupon = couponService.selectBestCoupon(
context.getUserId(),
@@ -101,6 +113,70 @@ public class CouponDiscountProvider implements IDiscountProvider {
public boolean canApply(DiscountInfo discountInfo, DiscountDetectionContext context) {
return "COUPON".equals(discountInfo.getDiscountType()) &&
Boolean.TRUE.equals(context.getAutoUseCoupon()) &&
context.getUserId() != null;
context.getUserId() != null &&
checkCanUseCoupon(context);
}
/**
* 检查商品配置和打包配置是否允许使用优惠券
*/
private boolean checkCanUseCoupon(DiscountDetectionContext context) {
if (context.getProducts() == null || context.getProducts().isEmpty()) {
return true; // 如果没有商品信息,默认允许
}
try {
// 检查是否使用了打包购买
BigDecimal bundlePrice = bundleService.getBundlePrice(context.getProducts());
if (bundlePrice != null) {
// 如果使用了打包购买,检查打包配置的优惠券使用开关
PriceBundleConfig bundleConfig = bundleService.getBundleConfig(context.getProducts());
if (bundleConfig != null) {
boolean canUseCoupon = Boolean.TRUE.equals(bundleConfig.getCanUseCoupon());
log.debug("打包配置优惠券开关检查: bundleId={}, canUseCoupon={}", bundleConfig.getId(), canUseCoupon);
return canUseCoupon;
}
}
// 检查单个商品的优惠券使用开关
for (ProductItem product : context.getProducts()) {
String productId = product.getProductId() != null ? product.getProductId() : "default";
try {
PriceProductConfig productConfig = productConfigService.getProductConfig(
product.getProductType().getCode(), productId);
if (productConfig != null) {
if (!Boolean.TRUE.equals(productConfig.getCanUseCoupon())) {
log.debug("商品配置不允许使用优惠券: productType={}, productId={}",
product.getProductType().getCode(), productId);
return false;
}
}
} catch (Exception e) {
// 如果获取具体商品配置失败,尝试获取default配置
try {
PriceProductConfig defaultConfig = productConfigService.getProductConfig(
product.getProductType().getCode(), "default");
if (defaultConfig != null) {
if (!Boolean.TRUE.equals(defaultConfig.getCanUseCoupon())) {
log.debug("商品默认配置不允许使用优惠券: productType={}",
product.getProductType().getCode());
return false;
}
}
} catch (Exception ex) {
log.warn("获取商品配置失败,默认允许使用优惠券: productType={}, productId={}",
product.getProductType().getCode(), productId);
}
}
}
} catch (Exception e) {
log.error("检查优惠券使用开关时发生异常,默认允许使用", e);
}
return true; // 默认允许使用优惠券
}
}

View File

@@ -75,6 +75,27 @@ public class PriceBundleServiceImpl implements IPriceBundleService {
return null;
}
@Override
public PriceBundleConfig getBundleConfig(List<ProductItem> products) {
if (!isBundleApplicable(products)) {
return null;
}
List<PriceBundleConfig> bundles = getActiveBundles();
Set<String> productTypes = new HashSet<>();
for (ProductItem product : products) {
productTypes.add(product.getProductType().getCode());
}
for (PriceBundleConfig bundle : bundles) {
if (isProductsMatchBundle(productTypes, bundle)) {
return bundle;
}
}
return null;
}
@Override
// @Cacheable(value = "active-bundles")
public List<PriceBundleConfig> getActiveBundles() {

View File

@@ -49,13 +49,13 @@ public class PriceCalculationServiceImpl implements IPriceCalculationService {
discountDetails.add(DiscountDetail.createLimitedTimeDiscount(limitedTimeDiscount));
}
// 检查一口价优惠
BigDecimal bundlePrice = bundleService.getBundlePrice(request.getProducts());
if (bundlePrice != null && bundlePrice.compareTo(totalAmount) < 0) {
BigDecimal bundleDiscount = totalAmount.subtract(bundlePrice);
discountDetails.add(DiscountDetail.createBundleDiscount(bundleDiscount));
totalAmount = bundlePrice;
log.info("使用一口价: {}, 优惠: {}", bundlePrice, bundleDiscount);
// 检查打包购买优惠
BigDecimal packagePrice = bundleService.getBundlePrice(request.getProducts());
if (packagePrice != null && packagePrice.compareTo(totalAmount) < 0) {
BigDecimal packageDiscount = totalAmount.subtract(packagePrice);
discountDetails.add(DiscountDetail.createBundleDiscount(packageDiscount));
totalAmount = packagePrice;
log.info("使用打包购买: {}, 优惠: {}", packagePrice, packageDiscount);
}
// 构建价格计算结果
@@ -77,7 +77,7 @@ public class PriceCalculationServiceImpl implements IPriceCalculationService {
// 重新排序
allDiscountDetails.sort(Comparator.comparing(DiscountDetail::getSortOrder));
// 计算总优惠金额(包括限时立减、一口价和其他优惠)
// 计算总优惠金额(包括限时立减、打包购买和其他优惠)
BigDecimal totalDiscountAmount = allDiscountDetails.stream()
.map(DiscountDetail::getDiscountAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
@@ -98,7 +98,7 @@ public class PriceCalculationServiceImpl implements IPriceCalculationService {
} else {
log.warn("优惠计算失败: {}", discountResult.getErrorMessage());
// 降级处理:仅使用基础优惠(限时立减、一口价
// 降级处理:仅使用基础优惠(限时立减、打包购买
BigDecimal totalDiscountAmount = discountDetails.stream()
.map(DiscountDetail::getDiscountAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);

View File

@@ -1,8 +1,12 @@
package com.ycwl.basic.pricing.service.impl;
import com.ycwl.basic.pricing.dto.*;
import com.ycwl.basic.pricing.entity.PriceBundleConfig;
import com.ycwl.basic.pricing.entity.PriceProductConfig;
import com.ycwl.basic.pricing.enums.VoucherDiscountType;
import com.ycwl.basic.pricing.service.IDiscountProvider;
import com.ycwl.basic.pricing.service.IPriceBundleService;
import com.ycwl.basic.pricing.service.IProductConfigService;
import com.ycwl.basic.pricing.service.IVoucherService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -22,6 +26,8 @@ import java.util.List;
public class VoucherDiscountProvider implements IDiscountProvider {
private final IVoucherService voucherService;
private final IProductConfigService productConfigService;
private final IPriceBundleService bundleService;
@Override
public String getProviderType() {
@@ -41,6 +47,12 @@ public class VoucherDiscountProvider implements IDiscountProvider {
return discounts;
}
// 检查商品配置和打包配置是否允许使用券码
if (!checkCanUseVoucher(context)) {
log.debug("商品配置不允许使用券码,跳过券码检测");
return discounts;
}
try {
VoucherInfo voucherInfo = null;
@@ -149,7 +161,8 @@ public class VoucherDiscountProvider implements IDiscountProvider {
return "VOUCHER".equals(discountInfo.getDiscountType()) &&
context.getFaceId() != null &&
context.getScenicId() != null &&
StringUtils.hasText(discountInfo.getVoucherCode());
StringUtils.hasText(discountInfo.getVoucherCode()) &&
checkCanUseVoucher(context);
}
/**
@@ -172,4 +185,67 @@ public class VoucherDiscountProvider implements IDiscountProvider {
// 全场免费券码不可与其他优惠叠加
return voucherInfo.getDiscountType() != VoucherDiscountType.FREE_ALL;
}
/**
* 检查商品配置和打包配置是否允许使用券码
*/
private boolean checkCanUseVoucher(DiscountDetectionContext context) {
if (context.getProducts() == null || context.getProducts().isEmpty()) {
return true; // 如果没有商品信息,默认允许
}
try {
// 检查是否使用了打包购买
BigDecimal bundlePrice = bundleService.getBundlePrice(context.getProducts());
if (bundlePrice != null) {
// 如果使用了打包购买,检查打包配置的券码使用开关
PriceBundleConfig bundleConfig = bundleService.getBundleConfig(context.getProducts());
if (bundleConfig != null) {
boolean canUseVoucher = Boolean.TRUE.equals(bundleConfig.getCanUseVoucher());
log.debug("打包配置券码开关检查: bundleId={}, canUseVoucher={}", bundleConfig.getId(), canUseVoucher);
return canUseVoucher;
}
}
// 检查单个商品的券码使用开关
for (ProductItem product : context.getProducts()) {
String productId = product.getProductId() != null ? product.getProductId() : "default";
try {
PriceProductConfig productConfig = productConfigService.getProductConfig(
product.getProductType().getCode(), productId);
if (productConfig != null) {
if (!Boolean.TRUE.equals(productConfig.getCanUseVoucher())) {
log.debug("商品配置不允许使用券码: productType={}, productId={}",
product.getProductType().getCode(), productId);
return false;
}
}
} catch (Exception e) {
// 如果获取具体商品配置失败,尝试获取default配置
try {
PriceProductConfig defaultConfig = productConfigService.getProductConfig(
product.getProductType().getCode(), "default");
if (defaultConfig != null) {
if (!Boolean.TRUE.equals(defaultConfig.getCanUseVoucher())) {
log.debug("商品默认配置不允许使用券码: productType={}",
product.getProductType().getCode());
return false;
}
}
} catch (Exception ex) {
log.warn("获取商品配置失败,默认允许使用券码: productType={}, productId={}",
product.getProductType().getCode(), productId);
}
}
}
} catch (Exception e) {
log.error("检查券码使用开关时发生异常,默认允许使用", e);
}
return true; // 默认允许使用券码
}
}