From 8a88c74df28a2a7f94faec148c1d9ab216fe6498 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Thu, 27 Nov 2025 13:55:51 +0800 Subject: [PATCH] =?UTF-8?q?feat(pricing):=20=E6=94=AF=E6=8C=81=E6=99=AF?= =?UTF-8?q?=E5=8C=BA=E7=BB=B4=E5=BA=A6=E7=9A=84=E4=BB=B7=E6=A0=BC=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E5=92=8C=E4=BC=98=E6=83=A0=E7=AD=96=E7=95=A5=E6=8E=A7?= =?UTF-8?q?=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增按景区ID查询商品配置和阶梯价格配置的方法 - 扩展价格计算服务以支持景区级别的优惠策略 - 更新优惠券和代金券提供者以使用景区维度配置 - 修改商品配置服务实现多级查询优先级(景区特定->景区默认->全局特定->全局默认) - 添加商品类型能力服务测试用例 - 增强价格计算逻辑的容错性和向后兼容性 --- .../mapper/PriceProductConfigMapper.java | 8 ++ .../pricing/mapper/PriceTierConfigMapper.java | 12 ++ .../service/IProductConfigService.java | 39 +++++- .../service/impl/CouponDiscountProvider.java | 24 ++-- .../OnePricePurchaseDiscountProvider.java | 37 +++-- .../impl/PriceCalculationServiceImpl.java | 62 ++++----- .../impl/ProductConfigServiceImpl.java | 101 ++++++++++++++ .../service/impl/VoucherDiscountProvider.java | 24 ++-- .../ProductTypeCapabilityServiceTest.java | 127 ++++++++++++++++++ 9 files changed, 357 insertions(+), 77 deletions(-) create mode 100644 src/test/java/com/ycwl/basic/product/service/ProductTypeCapabilityServiceTest.java diff --git a/src/main/java/com/ycwl/basic/pricing/mapper/PriceProductConfigMapper.java b/src/main/java/com/ycwl/basic/pricing/mapper/PriceProductConfigMapper.java index 1f890f24..3cb09c25 100644 --- a/src/main/java/com/ycwl/basic/pricing/mapper/PriceProductConfigMapper.java +++ b/src/main/java/com/ycwl/basic/pricing/mapper/PriceProductConfigMapper.java @@ -33,6 +33,14 @@ public interface PriceProductConfigMapper extends BaseMapper */ @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配置(包含禁用的) diff --git a/src/main/java/com/ycwl/basic/pricing/mapper/PriceTierConfigMapper.java b/src/main/java/com/ycwl/basic/pricing/mapper/PriceTierConfigMapper.java index edbc30d2..cd745477 100644 --- a/src/main/java/com/ycwl/basic/pricing/mapper/PriceTierConfigMapper.java +++ b/src/main/java/com/ycwl/basic/pricing/mapper/PriceTierConfigMapper.java @@ -26,6 +26,18 @@ public interface PriceTierConfigMapper extends BaseMapper { 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); /** * 根据商品类型查询所有阶梯配置 diff --git a/src/main/java/com/ycwl/basic/pricing/service/IProductConfigService.java b/src/main/java/com/ycwl/basic/pricing/service/IProductConfigService.java index c58bd937..e7fc7eff 100644 --- a/src/main/java/com/ycwl/basic/pricing/service/IProductConfigService.java +++ b/src/main/java/com/ycwl/basic/pricing/service/IProductConfigService.java @@ -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); /** * 获取所有启用的商品配置 diff --git a/src/main/java/com/ycwl/basic/pricing/service/impl/CouponDiscountProvider.java b/src/main/java/com/ycwl/basic/pricing/service/impl/CouponDiscountProvider.java index 40c2696a..96d52b9f 100644 --- a/src/main/java/com/ycwl/basic/pricing/service/impl/CouponDiscountProvider.java +++ b/src/main/java/com/ycwl/basic/pricing/service/impl/CouponDiscountProvider.java @@ -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()); } } } diff --git a/src/main/java/com/ycwl/basic/pricing/service/impl/OnePricePurchaseDiscountProvider.java b/src/main/java/com/ycwl/basic/pricing/service/impl/OnePricePurchaseDiscountProvider.java index ed7f0b5b..02b31be2 100644 --- a/src/main/java/com/ycwl/basic/pricing/service/impl/OnePricePurchaseDiscountProvider.java +++ b/src/main/java/com/ycwl/basic/pricing/service/impl/OnePricePurchaseDiscountProvider.java @@ -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 products) { + private boolean areAllProductsSupportOnePrice(List 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); + // 异常情况下默认支持,确保业务流程不受影响 } } diff --git a/src/main/java/com/ycwl/basic/pricing/service/impl/PriceCalculationServiceImpl.java b/src/main/java/com/ycwl/basic/pricing/service/impl/PriceCalculationServiceImpl.java index ac263ae4..6bcf7532 100644 --- a/src/main/java/com/ycwl/basic/pricing/service/impl/PriceCalculationServiceImpl.java +++ b/src/main/java/com/ycwl/basic/pricing/service/impl/PriceCalculationServiceImpl.java @@ -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 products) { + + private PriceDetails calculateProductsPriceWithOriginal(List 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 configs = productConfigService.getProductConfig(productType.getCode()); if (!configs.isEmpty()) { @@ -320,7 +320,7 @@ public class PriceCalculationServiceImpl implements IPriceCalculationService { } } } - + return new ProductPriceInfo(actualPrice, originalPrice); } diff --git a/src/main/java/com/ycwl/basic/pricing/service/impl/ProductConfigServiceImpl.java b/src/main/java/com/ycwl/basic/pricing/service/impl/ProductConfigServiceImpl.java index 0435d057..5b1477e0 100644 --- a/src/main/java/com/ycwl/basic/pricing/service/impl/ProductConfigServiceImpl.java +++ b/src/main/java/com/ycwl/basic/pricing/service/impl/ProductConfigServiceImpl.java @@ -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") diff --git a/src/main/java/com/ycwl/basic/pricing/service/impl/VoucherDiscountProvider.java b/src/main/java/com/ycwl/basic/pricing/service/impl/VoucherDiscountProvider.java index 0137e1e1..6ec833a8 100644 --- a/src/main/java/com/ycwl/basic/pricing/service/impl/VoucherDiscountProvider.java +++ b/src/main/java/com/ycwl/basic/pricing/service/impl/VoucherDiscountProvider.java @@ -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()); } } } diff --git a/src/test/java/com/ycwl/basic/product/service/ProductTypeCapabilityServiceTest.java b/src/test/java/com/ycwl/basic/product/service/ProductTypeCapabilityServiceTest.java new file mode 100644 index 00000000..216f6c12 --- /dev/null +++ b/src/test/java/com/ycwl/basic/product/service/ProductTypeCapabilityServiceTest.java @@ -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()); + } +}