feat(pricing): 优化升单价格计算逻辑支持补差价功能

- 修改 UpgradeBundleDiscountResult 和 UpgradeOnePriceResult 中 estimatedFinalAmount 字段含义为补差价金额
- 在 UpgradeCheckRequest 中新增 paidAmount 字段用于传递已支付金额
- 在 UpgradeCheckResult 中新增 bestUpgradeType 和 bestPayableAmount 字段提供最优升单建议
- 在 UpgradePriceSummary 中新增 paidAmount 字段记录已支付金额
- 更新价格计算服务实现,加入已支付金额处理逻辑
- 新增 normalizeAmount、calculateSupplementAmount 等工具方法确保金额计算精度
- 修复测试代码中的数据类型不匹配问题
This commit is contained in:
2026-01-19 20:25:44 +08:00
parent b1cfef278d
commit 91626626f4
9 changed files with 160 additions and 40 deletions

View File

@@ -616,20 +616,21 @@ UpgradeCheckResult checkUpgrade(UpgradeCheckRequest request);
- `scenicId`: 景区ID
- `purchasedProducts`: 已购商品列表
- `intendingProducts`: 待购商品列表
- `paidAmount`: 已支付金额(内部代码传入,前端不必传)
#### 检测流程
1. **商品规范化**: 对已购和待购商品进行规范化处理
2. **价格汇总**: 分别计算已购和待购商品的总价格
2. **价格汇总**: 分别计算已购和待购商品的总价格,并合并已支付金额
3. **一口价评估**: 判断合并商品是否满足一口价条件
4. **打包优惠评估**: 检测是否满足打包购买优惠条件
5. **结果汇总**: 生成升单检测结果和建议
#### 响应结果 (UpgradeCheckResult)
- `summary`: 价格汇总信息(原价、优惠价、最终价
- `onePriceResult`: 一口价检测结果(如适用
- `bundleResult`: 打包优惠检测结果(如适用
- `upgradeAvailable`: 是否可升单(布尔值
- `savingsAmount`: 升单可节省金额
- `summary`: 价格汇总信息(包含已支付金额
- `onePriceResult`: 一口价检测结果(含补差价金额
- `bundleDiscountResult`: 打包优惠检测结果(含补差价金额
- `bestUpgradeType`: 最优升单类型(ONE_PRICE / BUNDLE_DISCOUNT
- `bestPayableAmount`: 最低补差价金额
### 4. 业务价值

View File

@@ -56,7 +56,7 @@ public class UpgradeBundleDiscountResult {
private BigDecimal minAmount;
/**
* 使用优惠后的预计应付金额
* 使用优惠后的补差价金额(已支付金额已扣减)
*/
private BigDecimal estimatedFinalAmount;
}

View File

@@ -2,6 +2,7 @@ package com.ycwl.basic.pricing.dto;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
/**
@@ -29,4 +30,9 @@ public class UpgradeCheckRequest {
* 准备购买的商品列表
*/
private List<ProductItem> intendingProducts;
/**
* 已支付金额(内部代码传入,前端不必传)
*/
private BigDecimal paidAmount;
}

View File

@@ -2,6 +2,7 @@ package com.ycwl.basic.pricing.dto;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
/**
@@ -35,6 +36,16 @@ public class UpgradeCheckResult {
*/
private UpgradeBundleDiscountResult bundleDiscountResult;
/**
* 最优升单类型(ONE_PRICE / BUNDLE_DISCOUNT)
*/
private String bestUpgradeType;
/**
* 最低补差价金额
*/
private BigDecimal bestPayableAmount;
/**
* 已购买商品明细(带计算价)
*/

View File

@@ -46,7 +46,7 @@ public class UpgradeOnePriceResult {
private BigDecimal discountAmount;
/**
* 使用一口价后的预计应付金额
* 使用一口价后的补差价金额(已支付金额已扣减)
*/
private BigDecimal estimatedFinalAmount;
}

View File

@@ -20,6 +20,11 @@ public class UpgradePriceSummary {
*/
private BigDecimal purchasedSubtotalAmount = BigDecimal.ZERO;
/**
* 已支付金额(用于升单补差)
*/
private BigDecimal paidAmount = BigDecimal.ZERO;
/**
* 计划购买原价合计
*/

View File

@@ -33,6 +33,9 @@ import java.util.Set;
public class PriceCalculationServiceImpl implements IPriceCalculationService {
private static final String CAPABILITY_METADATA_ATTRIBUTE_KEYS = "pricingAttributeKeys";
private static final int AMOUNT_SCALE = 2;
private static final String UPGRADE_TYPE_ONE_PRICE = "ONE_PRICE";
private static final String UPGRADE_TYPE_BUNDLE_DISCOUNT = "BUNDLE_DISCOUNT";
private final IProductConfigService productConfigService;
private final ICouponService couponService;
@@ -166,7 +169,7 @@ public class PriceCalculationServiceImpl implements IPriceCalculationService {
if (request == null) {
throw new PriceCalculationException("升单检测请求不能为空");
}
List<ProductItem> purchasedProducts = cloneProducts(request.getPurchasedProducts());
List<ProductItem> intendingProducts = cloneProducts(request.getIntendingProducts());
@@ -176,29 +179,34 @@ public class PriceCalculationServiceImpl implements IPriceCalculationService {
normalizeProducts(purchasedProducts);
normalizeProducts(intendingProducts);
Long scenicId = request.getScenicId();
PriceDetails purchasedDetails = purchasedProducts.isEmpty()
? new PriceDetails(BigDecimal.ZERO, BigDecimal.ZERO)
: calculateProductsPriceWithOriginal(purchasedProducts);
: calculateProductsPriceWithOriginal(purchasedProducts, scenicId);
PriceDetails intendingDetails = intendingProducts.isEmpty()
? new PriceDetails(BigDecimal.ZERO, BigDecimal.ZERO)
: calculateProductsPriceWithOriginal(intendingProducts);
: calculateProductsPriceWithOriginal(intendingProducts, scenicId);
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);
PriceDetails combinedDetails = calculateProductsPriceWithOriginal(combinedProducts, scenicId);
BigDecimal paidAmount = resolvePaidAmount(request, purchasedDetails);
BigDecimal currentTotalAmount = calculateCurrentTotalAmount(paidAmount, intendingDetails);
UpgradePriceSummary priceSummary = buildPriceSummary(purchasedDetails, intendingDetails, combinedDetails, paidAmount);
UpgradeOnePriceResult onePriceResult = evaluateOnePrice(scenicId, combinedProducts, combinedDetails, paidAmount, currentTotalAmount);
UpgradeBundleDiscountResult bundleDiscountResult = evaluateBundleDiscount(scenicId, combinedProducts, combinedDetails, paidAmount, currentTotalAmount);
UpgradeCheckResult result = new UpgradeCheckResult();
result.setScenicId(request.getScenicId());
result.setScenicId(scenicId);
result.setFaceId(request.getFaceId());
result.setPriceSummary(priceSummary);
result.setOnePriceResult(onePriceResult);
result.setBundleDiscountResult(bundleDiscountResult);
fillBestUpgrade(result, onePriceResult, bundleDiscountResult);
result.setPurchasedProducts(purchasedProducts);
result.setIntendingProducts(intendingProducts);
return result;
@@ -435,7 +443,7 @@ public class PriceCalculationServiceImpl implements IPriceCalculationService {
return new ProductPriceInfo(actualPrice, originalPrice);
}
private UpgradePriceSummary buildPriceSummary(PriceDetails purchased, PriceDetails intending, PriceDetails combined) {
private UpgradePriceSummary buildPriceSummary(PriceDetails purchased, PriceDetails intending, PriceDetails combined, BigDecimal paidAmount) {
UpgradePriceSummary summary = new UpgradePriceSummary();
summary.setPurchasedOriginalAmount(purchased.getOriginalTotalAmount());
summary.setPurchasedSubtotalAmount(purchased.getTotalAmount());
@@ -443,10 +451,15 @@ public class PriceCalculationServiceImpl implements IPriceCalculationService {
summary.setIntendingSubtotalAmount(intending.getTotalAmount());
summary.setCombinedOriginalAmount(combined.getOriginalTotalAmount());
summary.setCombinedSubtotalAmount(combined.getTotalAmount());
summary.setPaidAmount(paidAmount);
return summary;
}
private UpgradeOnePriceResult evaluateOnePrice(Long scenicId, List<ProductItem> combinedProducts, PriceDetails combinedDetails) {
private UpgradeOnePriceResult evaluateOnePrice(Long scenicId,
List<ProductItem> combinedProducts,
PriceDetails combinedDetails,
BigDecimal paidAmount,
BigDecimal currentTotalAmount) {
UpgradeOnePriceResult result = new UpgradeOnePriceResult();
result.setApplicable(false);
@@ -458,23 +471,32 @@ public class PriceCalculationServiceImpl implements IPriceCalculationService {
BigDecimal bundlePrice = bundleConfig.getBundlePrice() != null
? bundleConfig.getBundlePrice()
: combinedDetails.getTotalAmount();
BigDecimal discountAmount = combinedDetails.getTotalAmount().subtract(bundlePrice);
if (discountAmount.compareTo(BigDecimal.ZERO) < 0) {
discountAmount = BigDecimal.ZERO;
BigDecimal normalizedBundlePrice = normalizeAmount(bundlePrice);
BigDecimal normalizedCurrentTotal = normalizeAmount(currentTotalAmount);
BigDecimal normalizedPaidAmount = normalizeAmount(paidAmount);
if (!isUpgradeBeneficial(normalizedCurrentTotal, normalizedBundlePrice)) {
return result;
}
BigDecimal discountAmount = normalizedCurrentTotal.subtract(normalizedBundlePrice);
result.setApplicable(true);
result.setBundleConfigId(bundleConfig.getId());
result.setBundleName(bundleConfig.getBundleName());
result.setDescription(bundleConfig.getDescription());
result.setScenicId(bundleConfig.getScenicId());
result.setBundlePrice(bundlePrice);
result.setBundlePrice(normalizedBundlePrice);
result.setDiscountAmount(discountAmount);
result.setEstimatedFinalAmount(bundlePrice);
result.setEstimatedFinalAmount(calculateSupplementAmount(normalizedBundlePrice, normalizedPaidAmount));
return result;
}
private UpgradeBundleDiscountResult evaluateBundleDiscount(Long scenicId, List<ProductItem> combinedProducts, PriceDetails combinedDetails) {
private UpgradeBundleDiscountResult evaluateBundleDiscount(Long scenicId,
List<ProductItem> combinedProducts,
PriceDetails combinedDetails,
BigDecimal paidAmount,
BigDecimal currentTotalAmount) {
UpgradeBundleDiscountResult result = new UpgradeBundleDiscountResult();
result.setApplicable(false);
@@ -491,21 +513,28 @@ public class PriceCalculationServiceImpl implements IPriceCalculationService {
return result;
}
BigDecimal normalizedPaidAmount = normalizeAmount(paidAmount);
BigDecimal normalizedCurrentTotal = normalizeAmount(currentTotalAmount);
BigDecimal normalizedDiscount = normalizeAmount(discountAmount);
BigDecimal targetTotal = combinedDetails.getTotalAmount().subtract(normalizedDiscount);
if (targetTotal.compareTo(BigDecimal.ZERO) < 0) {
targetTotal = BigDecimal.ZERO;
}
if (!isUpgradeBeneficial(normalizedCurrentTotal, targetTotal)) {
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.setDiscountAmount(normalizedDiscount);
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);
result.setEstimatedFinalAmount(calculateSupplementAmount(targetTotal, normalizedPaidAmount));
return result;
}
@@ -562,6 +591,71 @@ public class PriceCalculationServiceImpl implements IPriceCalculationService {
}
return configScenicId.equals(String.valueOf(scenicId));
}
private BigDecimal resolvePaidAmount(UpgradeCheckRequest request, PriceDetails purchasedDetails) {
BigDecimal paidAmount = request != null ? request.getPaidAmount() : null;
if (paidAmount == null && purchasedDetails != null) {
paidAmount = purchasedDetails.getTotalAmount();
}
return normalizeAmount(paidAmount);
}
private BigDecimal calculateCurrentTotalAmount(BigDecimal paidAmount, PriceDetails intendingDetails) {
BigDecimal intendingAmount = intendingDetails != null ? intendingDetails.getTotalAmount() : BigDecimal.ZERO;
BigDecimal normalizedPaidAmount = normalizeAmount(paidAmount);
BigDecimal normalizedIntendingAmount = normalizeAmount(intendingAmount);
return normalizedPaidAmount.add(normalizedIntendingAmount).setScale(AMOUNT_SCALE, RoundingMode.HALF_UP);
}
private BigDecimal calculateSupplementAmount(BigDecimal targetTotalAmount, BigDecimal paidAmount) {
BigDecimal normalizedTarget = normalizeAmount(targetTotalAmount);
BigDecimal normalizedPaid = normalizeAmount(paidAmount);
BigDecimal supplementAmount = normalizedTarget.subtract(normalizedPaid);
if (supplementAmount.compareTo(BigDecimal.ZERO) < 0) {
supplementAmount = BigDecimal.ZERO;
}
return supplementAmount.setScale(AMOUNT_SCALE, RoundingMode.HALF_UP);
}
private boolean isUpgradeBeneficial(BigDecimal currentTotalAmount, BigDecimal targetTotalAmount) {
BigDecimal normalizedCurrent = normalizeAmount(currentTotalAmount);
BigDecimal normalizedTarget = normalizeAmount(targetTotalAmount);
return normalizedCurrent.compareTo(normalizedTarget) > 0;
}
private BigDecimal normalizeAmount(BigDecimal amount) {
if (amount == null) {
return BigDecimal.ZERO.setScale(AMOUNT_SCALE, RoundingMode.HALF_UP);
}
BigDecimal normalized = amount.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : amount;
return normalized.setScale(AMOUNT_SCALE, RoundingMode.HALF_UP);
}
private void fillBestUpgrade(UpgradeCheckResult result,
UpgradeOnePriceResult onePriceResult,
UpgradeBundleDiscountResult bundleDiscountResult) {
if (result == null) {
return;
}
BigDecimal bestPayableAmount = null;
String bestUpgradeType = null;
if (onePriceResult != null && onePriceResult.isApplicable()) {
bestPayableAmount = onePriceResult.getEstimatedFinalAmount();
bestUpgradeType = UPGRADE_TYPE_ONE_PRICE;
}
if (bundleDiscountResult != null && bundleDiscountResult.isApplicable()) {
BigDecimal bundlePayableAmount = bundleDiscountResult.getEstimatedFinalAmount();
if (bestPayableAmount == null || (bundlePayableAmount != null
&& bundlePayableAmount.compareTo(bestPayableAmount) < 0)) {
bestPayableAmount = bundlePayableAmount;
bestUpgradeType = UPGRADE_TYPE_BUNDLE_DISCOUNT;
}
}
result.setBestPayableAmount(bestPayableAmount);
result.setBestUpgradeType(bestUpgradeType);
}
/**
* 计算优惠(券码 + 优惠券)