You've already forked FrameTour-BE
feat(pricing): 增加商品和打包配置的优惠券及券码使用限制
- 在 PriceBundleConfig 和 PriceProductConfig 中添加是否可使用优惠券和券码的字段 - 修改 CouponDiscountProvider 和 VoucherDiscountProvider,增加对商品和打包配置的检查 - 更新 PriceCalculationServiceImpl 中的优惠计算逻辑,将一口价改为打包购买 - 调整 DiscountDetail 中的描述和排序顺序,以适应新的优惠方式
This commit is contained in:
@@ -75,15 +75,16 @@ public class DiscountDetail {
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一口价折扣明细
|
||||
* 创建打包购买折扣明细
|
||||
*/
|
||||
public static DiscountDetail createBundleDiscount(BigDecimal discountAmount) {
|
||||
DiscountDetail detail = new DiscountDetail();
|
||||
detail.setDiscountType("BUNDLE");
|
||||
detail.setDiscountName("一口价优惠");
|
||||
detail.setDiscountName("打包购买优惠");
|
||||
detail.setDiscountAmount(discountAmount);
|
||||
detail.setDescription("一口价购买更优惠");
|
||||
detail.setSortOrder(4); // 一口价排在最后
|
||||
detail.setDescription("多商品打包购买更优惠");
|
||||
detail.setSortOrder(5); // 打包购买排在后面
|
||||
return detail;
|
||||
}
|
||||
|
||||
}
|
@@ -79,4 +79,9 @@ public class DiscountInfo {
|
||||
* 优惠券ID(如果是coupon类型)
|
||||
*/
|
||||
private Long couponId;
|
||||
|
||||
/**
|
||||
* 一口价信息(如果是一口价优惠)
|
||||
*/
|
||||
private OnePriceInfo onePriceInfo;
|
||||
}
|
@@ -60,6 +60,16 @@ public class PriceBundleConfig {
|
||||
*/
|
||||
private Boolean isActive;
|
||||
|
||||
/**
|
||||
* 是否可使用优惠券
|
||||
*/
|
||||
private Boolean canUseCoupon;
|
||||
|
||||
/**
|
||||
* 是否可使用券码
|
||||
*/
|
||||
private Boolean canUseVoucher;
|
||||
|
||||
@TableField("create_time")
|
||||
private Date createTime;
|
||||
|
||||
|
@@ -59,6 +59,16 @@ public class PriceProductConfig {
|
||||
*/
|
||||
private Boolean isActive;
|
||||
|
||||
/**
|
||||
* 是否可使用优惠券
|
||||
*/
|
||||
private Boolean canUseCoupon;
|
||||
|
||||
/**
|
||||
* 是否可使用券码
|
||||
*/
|
||||
private Boolean canUseVoucher;
|
||||
|
||||
@TableField("create_time")
|
||||
private Date createTime;
|
||||
|
||||
|
@@ -17,7 +17,7 @@ public interface PriceBundleConfigMapper extends BaseMapper<PriceBundleConfig> {
|
||||
*/
|
||||
@Select("SELECT id, bundle_name, scenic_id, bundle_price, " +
|
||||
"included_products, excluded_products, " +
|
||||
"description, is_active, create_time, update_time " +
|
||||
"description, is_active, can_use_coupon, can_use_voucher, create_time, update_time " +
|
||||
"FROM price_bundle_config WHERE is_active = 1")
|
||||
@Results({
|
||||
@Result(column = "included_products", property = "includedProducts",
|
||||
@@ -32,7 +32,7 @@ public interface PriceBundleConfigMapper extends BaseMapper<PriceBundleConfig> {
|
||||
*/
|
||||
@Select("SELECT id, bundle_name, scenic_id, bundle_price, " +
|
||||
"included_products, excluded_products, " +
|
||||
"description, is_active, create_time, update_time " +
|
||||
"description, is_active, can_use_coupon, can_use_voucher, create_time, update_time " +
|
||||
"FROM price_bundle_config WHERE id = #{id} AND is_active = 1")
|
||||
@Results({
|
||||
@Result(column = "included_products", property = "includedProducts",
|
||||
@@ -49,7 +49,7 @@ public interface PriceBundleConfigMapper extends BaseMapper<PriceBundleConfig> {
|
||||
*/
|
||||
@Select("SELECT id, bundle_name, scenic_id, bundle_price, " +
|
||||
"included_products, excluded_products, " +
|
||||
"description, is_active, create_time, update_time " +
|
||||
"description, is_active, can_use_coupon, can_use_voucher, create_time, update_time " +
|
||||
"FROM price_bundle_config ORDER BY is_active DESC, bundle_name ASC")
|
||||
@Results({
|
||||
@Result(column = "included_products", property = "includedProducts",
|
||||
@@ -63,9 +63,9 @@ public interface PriceBundleConfigMapper extends BaseMapper<PriceBundleConfig> {
|
||||
* 插入一口价配置
|
||||
*/
|
||||
@Insert("INSERT INTO price_bundle_config (bundle_name, scenic_id, bundle_price, included_products, excluded_products, " +
|
||||
"description, is_active, create_time, update_time) VALUES " +
|
||||
"description, is_active, can_use_coupon, can_use_voucher, create_time, update_time) VALUES " +
|
||||
"(#{bundleName}, #{scenicId}, #{bundlePrice}, #{includedProducts,typeHandler=com.ycwl.basic.pricing.handler.BundleProductListTypeHandler}, #{excludedProducts,typeHandler=com.ycwl.basic.pricing.handler.BundleProductListTypeHandler}, " +
|
||||
"#{description}, #{isActive}, NOW(), NOW())")
|
||||
"#{description}, #{isActive}, #{canUseCoupon}, #{canUseVoucher}, NOW(), NOW())")
|
||||
int insertBundleConfig(PriceBundleConfig config);
|
||||
|
||||
/**
|
||||
@@ -73,7 +73,7 @@ public interface PriceBundleConfigMapper extends BaseMapper<PriceBundleConfig> {
|
||||
*/
|
||||
@Update("UPDATE price_bundle_config SET bundle_name = #{bundleName}, scenic_id = #{scenicId}, bundle_price = #{bundlePrice}, " +
|
||||
"included_products = #{includedProducts,typeHandler=com.ycwl.basic.pricing.handler.BundleProductListTypeHandler}, excluded_products = #{excludedProducts,typeHandler=com.ycwl.basic.pricing.handler.BundleProductListTypeHandler}, " +
|
||||
"description = #{description}, is_active = #{isActive}, update_time = NOW() WHERE id = #{id}")
|
||||
"description = #{description}, is_active = #{isActive}, can_use_coupon = #{canUseCoupon}, can_use_voucher = #{canUseVoucher}, update_time = NOW() WHERE id = #{id}")
|
||||
int updateBundleConfig(PriceBundleConfig config);
|
||||
|
||||
/**
|
||||
|
@@ -57,15 +57,15 @@ public interface PriceProductConfigMapper extends BaseMapper<PriceProductConfig>
|
||||
/**
|
||||
* 插入商品价格配置
|
||||
*/
|
||||
@Insert("INSERT INTO price_product_config (product_type, product_id, scenic_id, product_name, base_price, original_price, unit, is_active, create_time, update_time) " +
|
||||
"VALUES (#{productType}, #{productId}, #{scenicId}, #{productName}, #{basePrice}, #{originalPrice}, #{unit}, #{isActive}, NOW(), NOW())")
|
||||
@Insert("INSERT INTO price_product_config (product_type, product_id, scenic_id, product_name, base_price, original_price, unit, is_active, can_use_coupon, can_use_voucher, create_time, update_time) " +
|
||||
"VALUES (#{productType}, #{productId}, #{scenicId}, #{productName}, #{basePrice}, #{originalPrice}, #{unit}, #{isActive}, #{canUseCoupon}, #{canUseVoucher}, NOW(), NOW())")
|
||||
int insertProductConfig(PriceProductConfig config);
|
||||
|
||||
/**
|
||||
* 更新商品价格配置
|
||||
*/
|
||||
@Update("UPDATE price_product_config SET product_id = #{productId}, scenic_id = #{scenicId}, product_name = #{productName}, base_price = #{basePrice}, " +
|
||||
"original_price = #{originalPrice}, unit = #{unit}, is_active = #{isActive}, update_time = NOW() WHERE id = #{id}")
|
||||
"original_price = #{originalPrice}, unit = #{unit}, is_active = #{isActive}, can_use_coupon = #{canUseCoupon}, can_use_voucher = #{canUseVoucher}, update_time = NOW() WHERE id = #{id}")
|
||||
int updateProductConfig(PriceProductConfig config);
|
||||
|
||||
/**
|
||||
|
@@ -29,6 +29,14 @@ public interface IPriceBundleService {
|
||||
*/
|
||||
BigDecimal getBundlePrice(List<ProductItem> products);
|
||||
|
||||
/**
|
||||
* 获取适用的一口价配置
|
||||
*
|
||||
* @param products 商品列表
|
||||
* @return 适用的一口价配置,如果不适用则返回null
|
||||
*/
|
||||
PriceBundleConfig getBundleConfig(List<ProductItem> products);
|
||||
|
||||
/**
|
||||
* 获取所有启用的一口价配置
|
||||
*
|
||||
|
@@ -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; // 默认允许使用优惠券
|
||||
}
|
||||
}
|
@@ -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() {
|
||||
|
@@ -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);
|
||||
|
@@ -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; // 默认允许使用券码
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user