You've already forked FrameTour-BE
feat(pricing): 新增升单检测功能
- 添加升单检测接口和相关 DTO 类 - 实现升单检测逻辑,包括价格汇总、一口价评估和打包优惠评估 - 优化商品列表复制和规范化处理 - 新增 IBundleDiscountService 依赖
This commit is contained in:
@@ -59,6 +59,20 @@ public class PriceCalculationController {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 升单检测:判断是否命中一口价或打包优惠
|
||||
*/
|
||||
@PostMapping("/upgrade-check")
|
||||
public ApiResponse<UpgradeCheckResult> 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询用户可用优惠券
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<ProductItem> purchasedProducts;
|
||||
|
||||
/**
|
||||
* 准备购买的商品列表
|
||||
*/
|
||||
private List<ProductItem> intendingProducts;
|
||||
}
|
||||
@@ -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<ProductItem> purchasedProducts;
|
||||
|
||||
/**
|
||||
* 计划购买商品明细(带计算价)
|
||||
*/
|
||||
private List<ProductItem> intendingProducts;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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<ProductItem> purchasedProducts = cloneProducts(request.getPurchasedProducts());
|
||||
List<ProductItem> 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<ProductItem> 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<ProductItem> 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<ProductItem> 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<ProductItem> 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<ProductItem> cloneProducts(List<ProductItem> source) {
|
||||
List<ProductItem> 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<ProductItem> 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));
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算优惠(券码 + 优惠券)
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user