You've already forked FrameTour-BE
feat(pricing): 支持景区维度的价格配置和优惠策略控制
- 新增按景区ID查询商品配置和阶梯价格配置的方法 - 扩展价格计算服务以支持景区级别的优惠策略 - 更新优惠券和代金券提供者以使用景区维度配置 - 修改商品配置服务实现多级查询优先级(景区特定->景区默认->全局特定->全局默认) - 添加商品类型能力服务测试用例 - 增强价格计算逻辑的容错性和向后兼容性
This commit is contained in:
@@ -33,6 +33,14 @@ public interface PriceProductConfigMapper extends BaseMapper<PriceProductConfig>
|
||||
*/
|
||||
@Select("SELECT * FROM price_product_config WHERE product_type = #{productType} AND product_id = #{productId} AND is_active = 1")
|
||||
PriceProductConfig selectByProductTypeAndId(String productType, String productId);
|
||||
|
||||
/**
|
||||
* 根据商品类型、商品ID和景区ID查询配置(支持景区维度)
|
||||
*/
|
||||
@Select("SELECT * FROM price_product_config WHERE product_type = #{productType} AND product_id = #{productId} AND scenic_id = #{scenicId} AND is_active = 1")
|
||||
PriceProductConfig selectByProductTypeIdAndScenic(@Param("productType") String productType,
|
||||
@Param("productId") String productId,
|
||||
@Param("scenicId") String scenicId);
|
||||
|
||||
/**
|
||||
* 检查是否存在default配置(包含禁用的)
|
||||
|
||||
@@ -26,6 +26,18 @@ public interface PriceTierConfigMapper extends BaseMapper<PriceTierConfig> {
|
||||
PriceTierConfig selectByProductTypeAndQuantity(@Param("productType") String productType,
|
||||
@Param("productId") String productId,
|
||||
@Param("quantity") Integer quantity);
|
||||
|
||||
/**
|
||||
* 根据商品类型、商品ID、数量和景区ID查询匹配的阶梯价格(支持景区维度)
|
||||
*/
|
||||
@Select("SELECT * FROM price_tier_config WHERE product_type = #{productType} " +
|
||||
"AND product_id = #{productId} AND scenic_id = #{scenicId} " +
|
||||
"AND #{quantity} >= min_quantity AND #{quantity} <= max_quantity " +
|
||||
"AND is_active = 1 ORDER BY sort_order ASC LIMIT 1")
|
||||
PriceTierConfig selectByProductTypeQuantityAndScenic(@Param("productType") String productType,
|
||||
@Param("productId") String productId,
|
||||
@Param("quantity") Integer quantity,
|
||||
@Param("scenicId") String scenicId);
|
||||
|
||||
/**
|
||||
* 根据商品类型查询所有阶梯配置
|
||||
|
||||
@@ -23,22 +23,55 @@ public interface IProductConfigService {
|
||||
|
||||
/**
|
||||
* 根据商品类型和商品ID获取精确配置
|
||||
*
|
||||
*
|
||||
* @param productType 商品类型
|
||||
* @param productId 具体商品ID
|
||||
* @return 商品配置
|
||||
*/
|
||||
PriceProductConfig getProductConfig(String productType, String productId);
|
||||
|
||||
|
||||
/**
|
||||
* 根据商品类型、商品ID和景区ID获取精确配置(支持景区维度的优惠策略控制)
|
||||
*
|
||||
* 查询优先级:
|
||||
* 1. 景区+商品ID: (productType, productId, scenicId)
|
||||
* 2. 景区+默认: (productType, "default", scenicId)
|
||||
* 3. 全局+商品ID: (productType, productId, null)
|
||||
* 4. 全局+默认: (productType, "default", null)
|
||||
*
|
||||
* @param productType 商品类型
|
||||
* @param productId 具体商品ID
|
||||
* @param scenicId 景区ID
|
||||
* @return 商品配置(包含优惠策略控制字段)
|
||||
*/
|
||||
PriceProductConfig getProductConfig(String productType, String productId, Long scenicId);
|
||||
|
||||
/**
|
||||
* 根据商品类型、商品ID和数量获取阶梯价格配置
|
||||
*
|
||||
*
|
||||
* @param productType 商品类型
|
||||
* @param productId 具体商品ID
|
||||
* @param quantity 数量
|
||||
* @return 阶梯价格配置
|
||||
*/
|
||||
PriceTierConfig getTierConfig(String productType, String productId, Integer quantity);
|
||||
|
||||
/**
|
||||
* 根据商品类型、商品ID、数量和景区ID获取阶梯价格配置(支持景区维度)
|
||||
*
|
||||
* 查询优先级:
|
||||
* 1. 景区+商品ID: (productType, productId, quantity, scenicId)
|
||||
* 2. 景区+默认: (productType, "default", quantity, scenicId)
|
||||
* 3. 全局+商品ID: (productType, productId, quantity, null)
|
||||
* 4. 全局+默认: (productType, "default", quantity, null)
|
||||
*
|
||||
* @param productType 商品类型
|
||||
* @param productId 具体商品ID
|
||||
* @param quantity 数量
|
||||
* @param scenicId 景区ID
|
||||
* @return 阶梯价格配置
|
||||
*/
|
||||
PriceTierConfig getTierConfig(String productType, String productId, Integer quantity, Long scenicId);
|
||||
|
||||
/**
|
||||
* 获取所有启用的商品配置
|
||||
|
||||
@@ -138,18 +138,18 @@ public class CouponDiscountProvider implements IDiscountProvider {
|
||||
}
|
||||
}
|
||||
|
||||
// 检查单个商品的优惠券使用开关
|
||||
// 检查单个商品的优惠券使用开关(使用景区维度配置)
|
||||
for (ProductItem product : context.getProducts()) {
|
||||
String productId = product.getProductId() != null ? product.getProductId() : "default";
|
||||
|
||||
|
||||
try {
|
||||
PriceProductConfig productConfig = productConfigService.getProductConfig(
|
||||
product.getProductType().getCode(), productId);
|
||||
|
||||
product.getProductType().getCode(), productId, context.getScenicId());
|
||||
|
||||
if (productConfig != null) {
|
||||
if (!Boolean.TRUE.equals(productConfig.getCanUseCoupon())) {
|
||||
log.debug("商品配置不允许使用优惠券: productType={}, productId={}",
|
||||
product.getProductType().getCode(), productId);
|
||||
log.debug("商品配置不允许使用优惠券: productType={}, productId={}, scenicId={}",
|
||||
product.getProductType().getCode(), productId, context.getScenicId());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -157,18 +157,18 @@ public class CouponDiscountProvider implements IDiscountProvider {
|
||||
// 如果获取具体商品配置失败,尝试获取default配置
|
||||
try {
|
||||
PriceProductConfig defaultConfig = productConfigService.getProductConfig(
|
||||
product.getProductType().getCode(), "default");
|
||||
|
||||
product.getProductType().getCode(), "default", context.getScenicId());
|
||||
|
||||
if (defaultConfig != null) {
|
||||
if (!Boolean.TRUE.equals(defaultConfig.getCanUseCoupon())) {
|
||||
log.debug("商品默认配置不允许使用优惠券: productType={}",
|
||||
product.getProductType().getCode());
|
||||
log.debug("商品默认配置不允许使用优惠券: productType={}, scenicId={}",
|
||||
product.getProductType().getCode(), context.getScenicId());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
log.warn("获取商品配置失败,默认允许使用优惠券: productType={}, productId={}",
|
||||
product.getProductType().getCode(), productId);
|
||||
log.warn("获取商品配置失败,默认允许使用优惠券: productType={}, productId={}, scenicId={}",
|
||||
product.getProductType().getCode(), productId, context.getScenicId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,8 +54,8 @@ public class OnePricePurchaseDiscountProvider implements IDiscountProvider {
|
||||
return discounts;
|
||||
}
|
||||
|
||||
// 检查商品是否支持一口价优惠
|
||||
if (!areAllProductsSupportOnePrice(context.getProducts())) {
|
||||
// 检查商品是否支持一口价优惠(使用景区维度配置)
|
||||
if (!areAllProductsSupportOnePrice(context.getProducts(), context.getScenicId())) {
|
||||
log.debug("存在不支持一口价优惠的商品,跳过一口价检测");
|
||||
return discounts;
|
||||
}
|
||||
@@ -182,49 +182,48 @@ public class OnePricePurchaseDiscountProvider implements IDiscountProvider {
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查购物车中的所有商品是否都支持一口价优惠
|
||||
* 检查购物车中的所有商品是否都支持一口价优惠(使用景区维度配置)
|
||||
*/
|
||||
private boolean areAllProductsSupportOnePrice(List<ProductItem> products) {
|
||||
private boolean areAllProductsSupportOnePrice(List<ProductItem> products, Long scenicId) {
|
||||
if (products == null || products.isEmpty()) {
|
||||
return true; // 空购物车时默认支持
|
||||
}
|
||||
|
||||
for (ProductItem product : products) {
|
||||
try {
|
||||
// 查询商品配置
|
||||
// 查询商品配置(使用景区维度)
|
||||
PriceProductConfig productConfig = productConfigService.getProductConfig(
|
||||
product.getProductType().getCode(), product.getProductId());
|
||||
product.getProductType().getCode(), product.getProductId(), scenicId);
|
||||
|
||||
if (productConfig != null) {
|
||||
// 检查商品是否支持一口价优惠
|
||||
if (Boolean.FALSE.equals(productConfig.getCanUseOnePrice())) {
|
||||
log.debug("商品 {}({}) 不支持一口价优惠",
|
||||
product.getProductType().getCode(), product.getProductId());
|
||||
log.debug("商品 {}({}) 在景区 {} 不支持一口价优惠",
|
||||
product.getProductType().getCode(), product.getProductId(), scenicId);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// 如果找不到具体商品配置,尝试查询 default 配置
|
||||
PriceProductConfig defaultConfig = productConfigService.getProductConfig(
|
||||
product.getProductType().getCode(), "default");
|
||||
product.getProductType().getCode(), "default", scenicId);
|
||||
|
||||
if (defaultConfig != null) {
|
||||
if (Boolean.FALSE.equals(defaultConfig.getCanUseOnePrice())) {
|
||||
log.debug("商品类型 {} 的默认配置不支持一口价优惠",
|
||||
product.getProductType().getCode());
|
||||
log.debug("商品类型 {} 在景区 {} 的默认配置不支持一口价优惠",
|
||||
product.getProductType().getCode(), scenicId);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// 如果既没有具体配置也没有默认配置,默认不支持一口价优惠
|
||||
log.debug("商品 {}({}) 未找到价格配置,默认不支持一口价优惠",
|
||||
product.getProductType().getCode(), product.getProductId());
|
||||
return false;
|
||||
// 如果既没有具体配置也没有默认配置,默认支持(保持向后兼容)
|
||||
log.debug("商品 {}({}) 在景区 {} 未找到价格配置,默认支持一口价优惠",
|
||||
product.getProductType().getCode(), product.getProductId(), scenicId);
|
||||
// 改为默认支持,避免配置缺失导致一口价功能不可用
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("检查商品 {}({}) 一口价优惠支持情况时发生异常,默认不支持",
|
||||
product.getProductType().getCode(), product.getProductId(), e);
|
||||
// 异常情况下默认不支持,避免出现意外情况
|
||||
return false;
|
||||
log.warn("检查商品 {}({}) 在景区 {} 一口价优惠支持情况时发生异常,默认支持",
|
||||
product.getProductType().getCode(), product.getProductId(), scenicId, e);
|
||||
// 异常情况下默认支持,确保业务流程不受影响
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,8 +66,8 @@ public class PriceCalculationServiceImpl implements IPriceCalculationService {
|
||||
product.setQuantity(1);
|
||||
}
|
||||
});
|
||||
// 计算商品价格和原价
|
||||
PriceDetails priceDetails = calculateProductsPriceWithOriginal(request.getProducts());
|
||||
// 计算商品价格和原价(传入景区ID以支持景区级优惠策略控制)
|
||||
PriceDetails priceDetails = calculateProductsPriceWithOriginal(request.getProducts(), request.getScenicId());
|
||||
BigDecimal totalAmount = priceDetails.getTotalAmount();
|
||||
BigDecimal originalTotalAmount = priceDetails.getOriginalTotalAmount();
|
||||
|
||||
@@ -161,27 +161,27 @@ public class PriceCalculationServiceImpl implements IPriceCalculationService {
|
||||
|
||||
return totalAmount.setScale(2, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
private PriceDetails calculateProductsPriceWithOriginal(List<ProductItem> products) {
|
||||
|
||||
private PriceDetails calculateProductsPriceWithOriginal(List<ProductItem> products, Long scenicId) {
|
||||
BigDecimal totalAmount = BigDecimal.ZERO;
|
||||
BigDecimal originalTotalAmount = BigDecimal.ZERO;
|
||||
|
||||
|
||||
for (ProductItem product : products) {
|
||||
// 计算实际价格和原价
|
||||
ProductPriceInfo priceInfo = calculateSingleProductPriceWithOriginal(product);
|
||||
|
||||
// 计算实际价格和原价(传入景区ID)
|
||||
ProductPriceInfo priceInfo = calculateSingleProductPriceWithOriginal(product, scenicId);
|
||||
|
||||
product.setUnitPrice(priceInfo.getActualPrice());
|
||||
product.setOriginalPrice(priceInfo.getOriginalPrice());
|
||||
|
||||
|
||||
BigDecimal subtotal = priceInfo.getActualPrice().multiply(BigDecimal.valueOf(product.getPurchaseCount()));
|
||||
BigDecimal originalSubtotal = priceInfo.getOriginalPrice().multiply(BigDecimal.valueOf(product.getPurchaseCount()));
|
||||
|
||||
|
||||
product.setSubtotal(subtotal);
|
||||
|
||||
|
||||
totalAmount = totalAmount.add(subtotal);
|
||||
originalTotalAmount = originalTotalAmount.add(originalSubtotal);
|
||||
}
|
||||
|
||||
|
||||
return new PriceDetails(
|
||||
totalAmount.setScale(2, RoundingMode.HALF_UP),
|
||||
originalTotalAmount.setScale(2, RoundingMode.HALF_UP)
|
||||
@@ -244,27 +244,27 @@ public class PriceCalculationServiceImpl implements IPriceCalculationService {
|
||||
|
||||
throw new PriceCalculationException("无法计算商品价格: " + productType.getDescription() + ", productId: " + productId);
|
||||
}
|
||||
|
||||
private ProductPriceInfo calculateSingleProductPriceWithOriginal(ProductItem product) {
|
||||
|
||||
private ProductPriceInfo calculateSingleProductPriceWithOriginal(ProductItem product, Long scenicId) {
|
||||
ProductType productType = product.getProductType();
|
||||
String productId = product.getProductId() != null ? product.getProductId() : "default";
|
||||
|
||||
|
||||
BigDecimal actualPrice;
|
||||
BigDecimal originalPrice = null;
|
||||
|
||||
// 优先使用基于product_id的阶梯定价
|
||||
|
||||
// 优先使用基于product_id的阶梯定价(带景区ID)
|
||||
PriceTierConfig tierConfig = productConfigService.getTierConfig(
|
||||
productType.getCode(), productId, product.getQuantity());
|
||||
|
||||
productType.getCode(), productId, product.getQuantity(), scenicId);
|
||||
|
||||
if (tierConfig != null) {
|
||||
actualPrice = tierConfig.getPrice();
|
||||
originalPrice = tierConfig.getOriginalPrice();
|
||||
log.debug("使用阶梯定价: productType={}, productId={}, quantity={}, price={}, originalPrice={}",
|
||||
productType.getCode(), productId, product.getQuantity(), actualPrice, originalPrice);
|
||||
log.debug("使用阶梯定价: productType={}, productId={}, quantity={}, scenicId={}, price={}, originalPrice={}",
|
||||
productType.getCode(), productId, product.getQuantity(), scenicId, actualPrice, originalPrice);
|
||||
} else {
|
||||
// 使用基于product_id的基础配置
|
||||
// 使用基于product_id的基础配置(带景区ID)
|
||||
try {
|
||||
PriceProductConfig baseConfig = productConfigService.getProductConfig(productType.getCode(), productId);
|
||||
PriceProductConfig baseConfig = productConfigService.getProductConfig(productType.getCode(), productId, scenicId);
|
||||
if (baseConfig != null) {
|
||||
actualPrice = baseConfig.getBasePrice();
|
||||
originalPrice = baseConfig.getOriginalPrice();
|
||||
@@ -279,12 +279,12 @@ public class PriceCalculationServiceImpl implements IPriceCalculationService {
|
||||
throw new PriceCalculationException("无法找到具体商品配置");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("未找到具体商品配置: productType={}, productId={}, 尝试使用通用配置",
|
||||
productType, productId);
|
||||
|
||||
// 兜底:使用default配置
|
||||
log.warn("未找到具体商品配置: productType={}, productId={}, scenicId={}, 尝试使用通用配置",
|
||||
productType, productId, scenicId);
|
||||
|
||||
// 兜底:使用default配置(带景区ID)
|
||||
try {
|
||||
PriceProductConfig defaultConfig = productConfigService.getProductConfig(productType.getCode(), "default");
|
||||
PriceProductConfig defaultConfig = productConfigService.getProductConfig(productType.getCode(), "default", scenicId);
|
||||
if (defaultConfig != null) {
|
||||
actualPrice = defaultConfig.getBasePrice();
|
||||
originalPrice = defaultConfig.getOriginalPrice();
|
||||
@@ -299,8 +299,8 @@ public class PriceCalculationServiceImpl implements IPriceCalculationService {
|
||||
throw new PriceCalculationException("无法找到default配置");
|
||||
}
|
||||
} catch (Exception defaultEx) {
|
||||
log.warn("未找到default配置: productType={}", productType.getCode());
|
||||
|
||||
log.warn("未找到default配置: productType={}, scenicId={}", productType.getCode(), scenicId);
|
||||
|
||||
// 最后兜底:使用通用配置(向后兼容)
|
||||
List<PriceProductConfig> configs = productConfigService.getProductConfig(productType.getCode());
|
||||
if (!configs.isEmpty()) {
|
||||
@@ -320,7 +320,7 @@ public class PriceCalculationServiceImpl implements IPriceCalculationService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return new ProductPriceInfo(actualPrice, originalPrice);
|
||||
}
|
||||
|
||||
|
||||
@@ -71,6 +71,107 @@ public class ProductConfigServiceImpl implements IProductConfigService {
|
||||
productType, productId, quantity, config);
|
||||
return config;
|
||||
}
|
||||
|
||||
@Override
|
||||
// @Cacheable(value = "product-config", key = "#productType + '_' + #productId + '_' + #scenicId")
|
||||
public PriceProductConfig getProductConfig(String productType, String productId, Long scenicId) {
|
||||
if (scenicId == null) {
|
||||
// 如果没有景区ID,使用原有逻辑
|
||||
return getProductConfig(productType, productId);
|
||||
}
|
||||
|
||||
// 查询优先级:
|
||||
// 1. 景区+商品ID
|
||||
PriceProductConfig config = productConfigMapper.selectByProductTypeIdAndScenic(
|
||||
productType, productId, scenicId.toString());
|
||||
if (config != null) {
|
||||
log.debug("使用景区特定商品配置: productType={}, productId={}, scenicId={}",
|
||||
productType, productId, scenicId);
|
||||
return config;
|
||||
}
|
||||
|
||||
// 2. 景区+默认
|
||||
if (!"default".equals(productId)) {
|
||||
config = productConfigMapper.selectByProductTypeIdAndScenic(
|
||||
productType, "default", scenicId.toString());
|
||||
if (config != null) {
|
||||
log.debug("使用景区默认配置: productType={}, scenicId={}", productType, scenicId);
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 全局+商品ID (兜底)
|
||||
try {
|
||||
config = productConfigMapper.selectByProductTypeAndId(productType, productId);
|
||||
if (config != null) {
|
||||
log.debug("使用全局商品配置: productType={}, productId={}", productType, productId);
|
||||
return config;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug("全局商品配置未找到: productType={}, productId={}", productType, productId);
|
||||
}
|
||||
|
||||
// 4. 全局+默认 (最后兜底)
|
||||
config = productConfigMapper.selectByProductTypeAndId(productType, "default");
|
||||
if (config != null) {
|
||||
log.debug("使用全局默认配置: productType={}", productType);
|
||||
return config;
|
||||
}
|
||||
|
||||
throw new ProductConfigNotFoundException(
|
||||
String.format("商品配置未找到: productType=%s, productId=%s, scenicId=%s",
|
||||
productType, productId, scenicId));
|
||||
}
|
||||
|
||||
@Override
|
||||
// @Cacheable(value = "tier-config", key = "#productType + '_' + #productId + '_' + #quantity + '_' + #scenicId")
|
||||
public PriceTierConfig getTierConfig(String productType, String productId, Integer quantity, Long scenicId) {
|
||||
if (quantity == null || quantity <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (scenicId == null) {
|
||||
// 如果没有景区ID,使用原有逻辑
|
||||
return getTierConfig(productType, productId, quantity);
|
||||
}
|
||||
|
||||
// 查询优先级:
|
||||
// 1. 景区+商品ID
|
||||
PriceTierConfig config = tierConfigMapper.selectByProductTypeQuantityAndScenic(
|
||||
productType, productId, quantity, scenicId.toString());
|
||||
if (config != null) {
|
||||
log.debug("使用景区特定阶梯定价: productType={}, productId={}, quantity={}, scenicId={}",
|
||||
productType, productId, quantity, scenicId);
|
||||
return config;
|
||||
}
|
||||
|
||||
// 2. 景区+默认
|
||||
if (!"default".equals(productId)) {
|
||||
config = tierConfigMapper.selectByProductTypeQuantityAndScenic(
|
||||
productType, "default", quantity, scenicId.toString());
|
||||
if (config != null) {
|
||||
log.debug("使用景区默认阶梯定价: productType={}, quantity={}, scenicId={}",
|
||||
productType, quantity, scenicId);
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 全局+商品ID (兜底)
|
||||
config = tierConfigMapper.selectByProductTypeAndQuantity(productType, productId, quantity);
|
||||
if (config != null) {
|
||||
log.debug("使用全局阶梯定价: productType={}, productId={}, quantity={}",
|
||||
productType, productId, quantity);
|
||||
return config;
|
||||
}
|
||||
|
||||
// 4. 全局+默认 (最后兜底)
|
||||
config = tierConfigMapper.selectByProductTypeAndQuantity(productType, "default", quantity);
|
||||
if (config != null) {
|
||||
log.debug("使用全局默认阶梯定价: productType={}, quantity={}", productType, quantity);
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
@Override
|
||||
// @Cacheable(value = "active-product-configs")
|
||||
|
||||
@@ -224,18 +224,18 @@ public class VoucherDiscountProvider implements IDiscountProvider {
|
||||
}
|
||||
}
|
||||
|
||||
// 检查单个商品的券码使用开关
|
||||
// 检查单个商品的券码使用开关(使用景区维度配置)
|
||||
for (ProductItem product : context.getProducts()) {
|
||||
String productId = product.getProductId() != null ? product.getProductId() : "default";
|
||||
|
||||
|
||||
try {
|
||||
PriceProductConfig productConfig = productConfigService.getProductConfig(
|
||||
product.getProductType().getCode(), productId);
|
||||
|
||||
product.getProductType().getCode(), productId, context.getScenicId());
|
||||
|
||||
if (productConfig != null) {
|
||||
if (!Boolean.TRUE.equals(productConfig.getCanUseVoucher())) {
|
||||
log.info("商品配置不允许使用券码: productType={}, productId={}",
|
||||
product.getProductType().getCode(), productId);
|
||||
log.info("商品配置不允许使用券码: productType={}, productId={}, scenicId={}",
|
||||
product.getProductType().getCode(), productId, context.getScenicId());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -243,18 +243,18 @@ public class VoucherDiscountProvider implements IDiscountProvider {
|
||||
// 如果获取具体商品配置失败,尝试获取default配置
|
||||
try {
|
||||
PriceProductConfig defaultConfig = productConfigService.getProductConfig(
|
||||
product.getProductType().getCode(), "default");
|
||||
|
||||
product.getProductType().getCode(), "default", context.getScenicId());
|
||||
|
||||
if (defaultConfig != null) {
|
||||
if (!Boolean.TRUE.equals(defaultConfig.getCanUseVoucher())) {
|
||||
log.debug("商品默认配置不允许使用券码: productType={}",
|
||||
product.getProductType().getCode());
|
||||
log.debug("商品默认配置不允许使用券码: productType={}, scenicId={}",
|
||||
product.getProductType().getCode(), context.getScenicId());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
log.warn("获取商品配置失败,默认允许使用券码: productType={}, productId={}",
|
||||
product.getProductType().getCode(), productId);
|
||||
log.warn("获取商品配置失败,默认允许使用券码: productType={}, productId={}, scenicId={}",
|
||||
product.getProductType().getCode(), productId, context.getScenicId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
package com.ycwl.basic.product.service;
|
||||
|
||||
import com.ycwl.basic.product.capability.DuplicateCheckStrategy;
|
||||
import com.ycwl.basic.product.capability.PricingMode;
|
||||
import com.ycwl.basic.product.capability.ProductTypeCapability;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* 商品类型能力服务测试
|
||||
*/
|
||||
@SpringBootTest
|
||||
public class ProductTypeCapabilityServiceTest {
|
||||
|
||||
@Autowired(required = false)
|
||||
private IProductTypeCapabilityService productTypeCapabilityService;
|
||||
|
||||
@Test
|
||||
public void testGetCapability_VLOG_VIDEO() {
|
||||
if (productTypeCapabilityService == null) {
|
||||
System.out.println("服务未注入,跳过测试(可能数据库未迁移)");
|
||||
return;
|
||||
}
|
||||
|
||||
ProductTypeCapability capability = productTypeCapabilityService.getCapability("VLOG_VIDEO");
|
||||
|
||||
assertNotNull(capability);
|
||||
assertEquals("VLOG_VIDEO", capability.getProductType());
|
||||
assertEquals("Vlog视频", capability.getDisplayName());
|
||||
assertEquals("VIDEO", capability.getCategory());
|
||||
assertEquals(PricingMode.FIXED, capability.getPricingModeEnum());
|
||||
assertEquals(false, capability.getAllowDuplicatePurchase());
|
||||
assertEquals(DuplicateCheckStrategy.CHECK_BY_VIDEO_ID, capability.getDuplicateCheckStrategyEnum());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCapability_PHOTO_PRINT() {
|
||||
if (productTypeCapabilityService == null) {
|
||||
System.out.println("服务未注入,跳过测试(可能数据库未迁移)");
|
||||
return;
|
||||
}
|
||||
|
||||
ProductTypeCapability capability = productTypeCapabilityService.getCapability("PHOTO_PRINT");
|
||||
|
||||
assertNotNull(capability);
|
||||
assertEquals("PHOTO_PRINT", capability.getProductType());
|
||||
assertEquals("照片打印", capability.getDisplayName());
|
||||
assertEquals("PRINT", capability.getCategory());
|
||||
assertEquals(PricingMode.QUANTITY_BASED, capability.getPricingModeEnum());
|
||||
assertEquals(true, capability.getAllowDuplicatePurchase());
|
||||
assertEquals(DuplicateCheckStrategy.NO_CHECK, capability.getDuplicateCheckStrategyEnum());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDisplayName() {
|
||||
if (productTypeCapabilityService == null) {
|
||||
System.out.println("服务未注入,跳过测试(可能数据库未迁移)");
|
||||
return;
|
||||
}
|
||||
|
||||
String displayName = productTypeCapabilityService.getDisplayName("RECORDING_SET");
|
||||
assertEquals("录像集", displayName);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAllowDuplicatePurchase() {
|
||||
if (productTypeCapabilityService == null) {
|
||||
System.out.println("服务未注入,跳过测试(可能数据库未迁移)");
|
||||
return;
|
||||
}
|
||||
|
||||
// 视频类不允许重复购买
|
||||
assertFalse(productTypeCapabilityService.allowDuplicatePurchase("VLOG_VIDEO"));
|
||||
|
||||
// 打印类允许重复购买
|
||||
assertTrue(productTypeCapabilityService.allowDuplicatePurchase("PHOTO_PRINT"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetPricingMode() {
|
||||
if (productTypeCapabilityService == null) {
|
||||
System.out.println("服务未注入,跳过测试(可能数据库未迁移)");
|
||||
return;
|
||||
}
|
||||
|
||||
// 视频类固定价格
|
||||
assertEquals(PricingMode.FIXED, productTypeCapabilityService.getPricingMode("VLOG_VIDEO"));
|
||||
|
||||
// 打印类基于数量
|
||||
assertEquals(PricingMode.QUANTITY_BASED, productTypeCapabilityService.getPricingMode("PHOTO_PRINT"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCapability_NotFound_ShouldReturnDefault() {
|
||||
if (productTypeCapabilityService == null) {
|
||||
System.out.println("服务未注入,跳过测试(可能数据库未迁移)");
|
||||
return;
|
||||
}
|
||||
|
||||
ProductTypeCapability capability = productTypeCapabilityService.getCapability("NOT_EXIST_TYPE");
|
||||
|
||||
// 应该返回默认配置而不是 null
|
||||
assertNotNull(capability);
|
||||
assertEquals("NOT_EXIST_TYPE", capability.getProductType());
|
||||
assertEquals("景区商品", capability.getDisplayName());
|
||||
assertEquals(PricingMode.FIXED, capability.getPricingModeEnum());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCapability_NullOrEmpty_ShouldReturnDefault() {
|
||||
if (productTypeCapabilityService == null) {
|
||||
System.out.println("服务未注入,跳过测试(可能数据库未迁移)");
|
||||
return;
|
||||
}
|
||||
|
||||
ProductTypeCapability capability1 = productTypeCapabilityService.getCapability(null);
|
||||
assertNotNull(capability1);
|
||||
assertEquals("景区商品", capability1.getDisplayName());
|
||||
|
||||
ProductTypeCapability capability2 = productTypeCapabilityService.getCapability("");
|
||||
assertNotNull(capability2);
|
||||
assertEquals("景区商品", capability2.getDisplayName());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user