diff --git a/src/main/java/com/ycwl/basic/pricing/controller/PriceCalculationController.java b/src/main/java/com/ycwl/basic/pricing/controller/PriceCalculationController.java index 279d6b62..baeb8905 100644 --- a/src/main/java/com/ycwl/basic/pricing/controller/PriceCalculationController.java +++ b/src/main/java/com/ycwl/basic/pricing/controller/PriceCalculationController.java @@ -59,6 +59,20 @@ public class PriceCalculationController { return null; } + /** + * 升单检测:判断是否命中一口价或打包优惠 + */ + @PostMapping("/upgrade-check") + public ApiResponse upgradeCheck(@RequestBody UpgradeCheckRequest request) { + log.info("升单检测请求: scenicId={}, purchased={}, intending={}", + request.getScenicId(), + request.getPurchasedProducts() != null ? request.getPurchasedProducts().size() : 0, + request.getIntendingProducts() != null ? request.getIntendingProducts().size() : 0); + + UpgradeCheckResult result = priceCalculationService.checkUpgrade(request); + return ApiResponse.success(result); + } + /** * 查询用户可用优惠券 */ diff --git a/src/main/java/com/ycwl/basic/pricing/dto/UpgradeBundleDiscountResult.java b/src/main/java/com/ycwl/basic/pricing/dto/UpgradeBundleDiscountResult.java new file mode 100644 index 00000000..b88b84ff --- /dev/null +++ b/src/main/java/com/ycwl/basic/pricing/dto/UpgradeBundleDiscountResult.java @@ -0,0 +1,62 @@ +package com.ycwl.basic.pricing.dto; + +import lombok.Data; + +import java.math.BigDecimal; + +/** + * 升单检测打包优惠结果 + */ +@Data +public class UpgradeBundleDiscountResult { + + /** + * 是否命中打包优惠 + */ + private boolean applicable; + + /** + * 打包配置ID + */ + private Long bundleConfigId; + + /** + * 打包优惠名称 + */ + private String bundleName; + + /** + * 打包优惠描述 + */ + private String bundleDescription; + + /** + * 优惠类型 + */ + private String discountType; + + /** + * 优惠值 + */ + private BigDecimal discountValue; + + /** + * 实际优惠金额 + */ + private BigDecimal discountAmount; + + /** + * 满足条件的最少数量 + */ + private Integer minQuantity; + + /** + * 满足条件的最少金额 + */ + private BigDecimal minAmount; + + /** + * 使用优惠后的预计应付金额 + */ + private BigDecimal estimatedFinalAmount; +} diff --git a/src/main/java/com/ycwl/basic/pricing/dto/UpgradeCheckRequest.java b/src/main/java/com/ycwl/basic/pricing/dto/UpgradeCheckRequest.java new file mode 100644 index 00000000..7b0206e5 --- /dev/null +++ b/src/main/java/com/ycwl/basic/pricing/dto/UpgradeCheckRequest.java @@ -0,0 +1,32 @@ +package com.ycwl.basic.pricing.dto; + +import lombok.Data; + +import java.util.List; + +/** + * 升单检测请求 + */ +@Data +public class UpgradeCheckRequest { + + /** + * 景区ID + */ + private Long scenicId; + + /** + * 用户faceId + */ + private Long faceId; + + /** + * 已购买商品列表 + */ + private List purchasedProducts; + + /** + * 准备购买的商品列表 + */ + private List intendingProducts; +} diff --git a/src/main/java/com/ycwl/basic/pricing/dto/UpgradeCheckResult.java b/src/main/java/com/ycwl/basic/pricing/dto/UpgradeCheckResult.java new file mode 100644 index 00000000..79421b69 --- /dev/null +++ b/src/main/java/com/ycwl/basic/pricing/dto/UpgradeCheckResult.java @@ -0,0 +1,47 @@ +package com.ycwl.basic.pricing.dto; + +import lombok.Data; + +import java.util.List; + +/** + * 升单检测结果 + */ +@Data +public class UpgradeCheckResult { + + /** + * 景区ID + */ + private Long scenicId; + + /** + * 用户faceId + */ + private Long faceId; + + /** + * 价格汇总信息 + */ + private UpgradePriceSummary priceSummary; + + /** + * 一口价检测结果 + */ + private UpgradeOnePriceResult onePriceResult; + + /** + * 打包优惠检测结果 + */ + private UpgradeBundleDiscountResult bundleDiscountResult; + + /** + * 已购买商品明细(带计算价) + */ + private List purchasedProducts; + + /** + * 计划购买商品明细(带计算价) + */ + private List intendingProducts; +} diff --git a/src/main/java/com/ycwl/basic/pricing/dto/UpgradeOnePriceResult.java b/src/main/java/com/ycwl/basic/pricing/dto/UpgradeOnePriceResult.java new file mode 100644 index 00000000..1e38740d --- /dev/null +++ b/src/main/java/com/ycwl/basic/pricing/dto/UpgradeOnePriceResult.java @@ -0,0 +1,52 @@ +package com.ycwl.basic.pricing.dto; + +import lombok.Data; + +import java.math.BigDecimal; + +/** + * 升单检测一口价结果 + */ +@Data +public class UpgradeOnePriceResult { + + /** + * 是否命中一口价规则 + */ + private boolean applicable; + + /** + * 一口价配置ID + */ + private Long bundleConfigId; + + /** + * 一口价名称 + */ + private String bundleName; + + /** + * 一口价描述 + */ + private String description; + + /** + * 适用景区ID + */ + private String scenicId; + + /** + * 一口价金额 + */ + private BigDecimal bundlePrice; + + /** + * 优惠金额(合并小计 - 一口价金额) + */ + private BigDecimal discountAmount; + + /** + * 使用一口价后的预计应付金额 + */ + private BigDecimal estimatedFinalAmount; +} diff --git a/src/main/java/com/ycwl/basic/pricing/dto/UpgradePriceSummary.java b/src/main/java/com/ycwl/basic/pricing/dto/UpgradePriceSummary.java new file mode 100644 index 00000000..71a8edef --- /dev/null +++ b/src/main/java/com/ycwl/basic/pricing/dto/UpgradePriceSummary.java @@ -0,0 +1,42 @@ +package com.ycwl.basic.pricing.dto; + +import lombok.Data; + +import java.math.BigDecimal; + +/** + * 升单检测价格汇总 + */ +@Data +public class UpgradePriceSummary { + + /** + * 已购买原价合计 + */ + private BigDecimal purchasedOriginalAmount = BigDecimal.ZERO; + + /** + * 已购买小计金额 + */ + private BigDecimal purchasedSubtotalAmount = BigDecimal.ZERO; + + /** + * 计划购买原价合计 + */ + private BigDecimal intendingOriginalAmount = BigDecimal.ZERO; + + /** + * 计划购买小计金额 + */ + private BigDecimal intendingSubtotalAmount = BigDecimal.ZERO; + + /** + * 合并后的原价合计 + */ + private BigDecimal combinedOriginalAmount = BigDecimal.ZERO; + + /** + * 合并后的小计金额 + */ + private BigDecimal combinedSubtotalAmount = BigDecimal.ZERO; +} diff --git a/src/main/java/com/ycwl/basic/pricing/service/IPriceCalculationService.java b/src/main/java/com/ycwl/basic/pricing/service/IPriceCalculationService.java index bffa743a..933b5002 100644 --- a/src/main/java/com/ycwl/basic/pricing/service/IPriceCalculationService.java +++ b/src/main/java/com/ycwl/basic/pricing/service/IPriceCalculationService.java @@ -2,6 +2,8 @@ package com.ycwl.basic.pricing.service; import com.ycwl.basic.pricing.dto.PriceCalculationRequest; import com.ycwl.basic.pricing.dto.PriceCalculationResult; +import com.ycwl.basic.pricing.dto.UpgradeCheckRequest; +import com.ycwl.basic.pricing.dto.UpgradeCheckResult; /** * 价格计算服务接口 @@ -15,4 +17,12 @@ public interface IPriceCalculationService { * @return 价格计算结果 */ PriceCalculationResult calculatePrice(PriceCalculationRequest request); + + /** + * 升单检测:综合已购与待购商品,判断是否命中一口价或打包优惠 + * + * @param request 升单检测请求 + * @return 检测结果 + */ + UpgradeCheckResult checkUpgrade(UpgradeCheckRequest request); } \ No newline at end of file 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 cbfee913..af223078 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 @@ -1,6 +1,7 @@ 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.entity.PriceTierConfig; import com.ycwl.basic.pricing.enums.ProductType; @@ -27,6 +28,7 @@ public class PriceCalculationServiceImpl implements IPriceCalculationService { private final IProductConfigService productConfigService; private final ICouponService couponService; private final IPriceBundleService bundleService; + private final IBundleDiscountService bundleDiscountService; private final IDiscountDetectionService discountDetectionService; private final IVoucherService voucherService; @@ -130,6 +132,49 @@ public class PriceCalculationServiceImpl implements IPriceCalculationService { return result; } + @Override + public UpgradeCheckResult checkUpgrade(UpgradeCheckRequest request) { + if (request == null) { + throw new PriceCalculationException("升单检测请求不能为空"); + } + + List purchasedProducts = cloneProducts(request.getPurchasedProducts()); + List intendingProducts = cloneProducts(request.getIntendingProducts()); + + if (purchasedProducts.isEmpty() && intendingProducts.isEmpty()) { + throw new PriceCalculationException("已购和待购商品列表不能同时为空"); + } + + normalizeProducts(purchasedProducts); + normalizeProducts(intendingProducts); + + PriceDetails purchasedDetails = purchasedProducts.isEmpty() + ? new PriceDetails(BigDecimal.ZERO, BigDecimal.ZERO) + : calculateProductsPriceWithOriginal(purchasedProducts); + PriceDetails intendingDetails = intendingProducts.isEmpty() + ? new PriceDetails(BigDecimal.ZERO, BigDecimal.ZERO) + : calculateProductsPriceWithOriginal(intendingProducts); + + List combinedProducts = new ArrayList<>(); + combinedProducts.addAll(purchasedProducts); + combinedProducts.addAll(intendingProducts); + PriceDetails combinedDetails = calculateProductsPriceWithOriginal(combinedProducts); + + UpgradePriceSummary priceSummary = buildPriceSummary(purchasedDetails, intendingDetails, combinedDetails); + UpgradeOnePriceResult onePriceResult = evaluateOnePrice(request.getScenicId(), combinedProducts, combinedDetails); + UpgradeBundleDiscountResult bundleDiscountResult = evaluateBundleDiscount(request.getScenicId(), combinedProducts, combinedDetails); + + UpgradeCheckResult result = new UpgradeCheckResult(); + result.setScenicId(request.getScenicId()); + result.setFaceId(request.getFaceId()); + result.setPriceSummary(priceSummary); + result.setOnePriceResult(onePriceResult); + result.setBundleDiscountResult(bundleDiscountResult); + result.setPurchasedProducts(purchasedProducts); + result.setIntendingProducts(intendingProducts); + return result; + } + private BigDecimal calculateProductsPrice(List products) { BigDecimal totalAmount = BigDecimal.ZERO; @@ -308,6 +353,134 @@ public class PriceCalculationServiceImpl implements IPriceCalculationService { return new ProductPriceInfo(actualPrice, originalPrice); } + private UpgradePriceSummary buildPriceSummary(PriceDetails purchased, PriceDetails intending, PriceDetails combined) { + UpgradePriceSummary summary = new UpgradePriceSummary(); + summary.setPurchasedOriginalAmount(purchased.getOriginalTotalAmount()); + summary.setPurchasedSubtotalAmount(purchased.getTotalAmount()); + summary.setIntendingOriginalAmount(intending.getOriginalTotalAmount()); + summary.setIntendingSubtotalAmount(intending.getTotalAmount()); + summary.setCombinedOriginalAmount(combined.getOriginalTotalAmount()); + summary.setCombinedSubtotalAmount(combined.getTotalAmount()); + return summary; + } + + private UpgradeOnePriceResult evaluateOnePrice(Long scenicId, List combinedProducts, PriceDetails combinedDetails) { + UpgradeOnePriceResult result = new UpgradeOnePriceResult(); + result.setApplicable(false); + + PriceBundleConfig bundleConfig = bundleService.getBundleConfig(combinedProducts); + if (bundleConfig == null || !matchesScenic(bundleConfig.getScenicId(), scenicId)) { + return result; + } + + BigDecimal bundlePrice = bundleConfig.getBundlePrice() != null + ? bundleConfig.getBundlePrice() + : combinedDetails.getTotalAmount(); + BigDecimal discountAmount = combinedDetails.getTotalAmount().subtract(bundlePrice); + if (discountAmount.compareTo(BigDecimal.ZERO) < 0) { + discountAmount = BigDecimal.ZERO; + } + + result.setApplicable(true); + result.setBundleConfigId(bundleConfig.getId()); + result.setBundleName(bundleConfig.getBundleName()); + result.setDescription(bundleConfig.getDescription()); + result.setScenicId(bundleConfig.getScenicId()); + result.setBundlePrice(bundlePrice); + result.setDiscountAmount(discountAmount); + result.setEstimatedFinalAmount(bundlePrice); + return result; + } + + private UpgradeBundleDiscountResult evaluateBundleDiscount(Long scenicId, List combinedProducts, PriceDetails combinedDetails) { + UpgradeBundleDiscountResult result = new UpgradeBundleDiscountResult(); + result.setApplicable(false); + + BundleDiscountInfo bestDiscount = bundleDiscountService.getBestBundleDiscount(combinedProducts, scenicId); + if (bestDiscount == null) { + return result; + } + + BigDecimal discountAmount = bestDiscount.getActualDiscountAmount(); + if (discountAmount == null || discountAmount.compareTo(BigDecimal.ZERO) <= 0) { + discountAmount = bundleDiscountService.calculateBundleDiscount(bestDiscount, combinedProducts); + } + if (discountAmount == null || discountAmount.compareTo(BigDecimal.ZERO) <= 0) { + return result; + } + + result.setApplicable(true); + result.setBundleConfigId(bestDiscount.getBundleConfigId()); + result.setBundleName(bestDiscount.getBundleName()); + result.setBundleDescription(bestDiscount.getBundleDescription()); + result.setDiscountType(bestDiscount.getDiscountType()); + result.setDiscountValue(bestDiscount.getDiscountValue()); + result.setDiscountAmount(discountAmount); + result.setMinQuantity(bestDiscount.getMinQuantity()); + result.setMinAmount(bestDiscount.getMinAmount()); + + BigDecimal finalAmount = combinedDetails.getTotalAmount().subtract(discountAmount); + if (finalAmount.compareTo(BigDecimal.ZERO) < 0) { + finalAmount = BigDecimal.ZERO; + } + result.setEstimatedFinalAmount(finalAmount); + return result; + } + + private List cloneProducts(List source) { + List copies = new ArrayList<>(); + if (source == null) { + return copies; + } + for (ProductItem item : source) { + if (item == null) { + continue; + } + copies.add(cloneProductItem(item)); + } + return copies; + } + + private ProductItem cloneProductItem(ProductItem source) { + ProductItem copy = new ProductItem(); + copy.setProductType(source.getProductType()); + copy.setProductId(source.getProductId()); + copy.setQuantity(source.getQuantity()); + copy.setPurchaseCount(source.getPurchaseCount()); + copy.setOriginalPrice(source.getOriginalPrice()); + copy.setUnitPrice(source.getUnitPrice()); + copy.setSubtotal(source.getSubtotal()); + copy.setScenicId(source.getScenicId()); + return copy; + } + + private void normalizeProducts(List products) { + for (ProductItem product : products) { + if (product.getProductType() == null) { + throw new PriceCalculationException("商品类型不能为空"); + } + if (product.getProductId() == null) { + throw new PriceCalculationException("商品ID不能为空"); + } + if (product.getPurchaseCount() == null) { + product.setPurchaseCount(1); + } + if (product.getQuantity() == null) { + product.setQuantity(1); + } + } + } + + private boolean matchesScenic(String configScenicId, Long scenicId) { + if (scenicId == null) { + return true; + } + if (configScenicId == null || configScenicId.isEmpty()) { + return true; + } + return configScenicId.equals(String.valueOf(scenicId)); + } + /** * 计算优惠(券码 + 优惠券) */