You've already forked FrameTour-BE
Merge branch 'order_update'
# Conflicts: # src/main/java/com/ycwl/basic/pricing/CLAUDE.md
This commit is contained in:
@@ -82,6 +82,7 @@ com.ycwl.basic.pricing/
|
||||
#### API端点
|
||||
- `POST /api/pricing/calculate` — 执行价格计算(预览模式默认开启)
|
||||
- `GET /api/pricing/coupons/my-coupons` — 查询用户可用优惠券
|
||||
- `POST /api/pricing/upgrade-check` — 升单检测:综合已购与待购商品,判断是否命中一口价或打包优惠
|
||||
|
||||
#### 计算流程
|
||||
```java
|
||||
@@ -588,22 +589,146 @@ public class PriceCalculationResult {
|
||||
- `GET /api/pricing/admin/one-price/scenic/{scenicId}` — 按景区查询启用配置
|
||||
- `GET /api/pricing/admin/one-price/check/{scenicId}` — 景区是否适用一口价
|
||||
|
||||
## 升单检测功能 (Upgrade Detection)
|
||||
|
||||
### 1. 功能概述
|
||||
|
||||
升单检测功能是最新新增的功能,用于综合已购商品与待购商品,判断是否满足一口价或打包购买优惠条件,为用户提供购买建议。
|
||||
|
||||
### 2. 核心接口
|
||||
|
||||
#### IPriceCalculationService 升单检测方法
|
||||
```java
|
||||
/**
|
||||
* 升单检测:综合已购与待购商品,判断是否命中一口价或打包优惠
|
||||
* @param request 升单检测请求
|
||||
* @return 升单检测结果
|
||||
*/
|
||||
UpgradeCheckResult checkUpgrade(UpgradeCheckRequest request);
|
||||
```
|
||||
|
||||
#### API 端点
|
||||
- `POST /api/pricing/upgrade-check` — 升单检测接口
|
||||
|
||||
### 3. 检测逻辑
|
||||
|
||||
#### 请求参数 (UpgradeCheckRequest)
|
||||
- `scenicId`: 景区ID
|
||||
- `purchasedProducts`: 已购商品列表
|
||||
- `intendingProducts`: 待购商品列表
|
||||
|
||||
#### 检测流程
|
||||
1. **商品规范化**: 对已购和待购商品进行规范化处理
|
||||
2. **价格汇总**: 分别计算已购和待购商品的总价格
|
||||
3. **一口价评估**: 判断合并商品是否满足一口价条件
|
||||
4. **打包优惠评估**: 检测是否满足打包购买优惠条件
|
||||
5. **结果汇总**: 生成升单检测结果和建议
|
||||
|
||||
#### 响应结果 (UpgradeCheckResult)
|
||||
- `summary`: 价格汇总信息(原价、优惠价、最终价)
|
||||
- `onePriceResult`: 一口价检测结果(如适用)
|
||||
- `bundleResult`: 打包优惠检测结果(如适用)
|
||||
- `upgradeAvailable`: 是否可升单(布尔值)
|
||||
- `savingsAmount`: 升单可节省金额
|
||||
|
||||
### 4. 业务价值
|
||||
|
||||
#### 用户体验提升
|
||||
- 为用户提供购买建议,提高客单价
|
||||
- 自动检测最优购买组合
|
||||
- 清晰展示升单优惠金额
|
||||
|
||||
#### 销售支持
|
||||
- 促进多商品销售
|
||||
- 提高打包购买和一口价利用率
|
||||
- 增加用户购买决策信心
|
||||
|
||||
### 5. 使用场景
|
||||
|
||||
#### 典型场景
|
||||
- 用户已购买照片,建议加购视频享受打包优惠
|
||||
- 用户购买多件商品,建议升级为一口价套餐
|
||||
- 用户购买数量接近打包优惠门槛,建议增加数量
|
||||
|
||||
#### 实现细节
|
||||
```java
|
||||
// 升单检测核心逻辑
|
||||
@Override
|
||||
public UpgradeCheckResult checkUpgrade(UpgradeCheckRequest request) {
|
||||
// 1. 参数验证
|
||||
if (request == null) {
|
||||
throw new PriceCalculationException("升单检测请求不能为空");
|
||||
}
|
||||
|
||||
// 2. 商品规范化
|
||||
List<ProductItem> purchased = normalizeProducts(request.getPurchasedProducts());
|
||||
List<ProductItem> intending = normalizeProducts(request.getIntendingProducts());
|
||||
|
||||
// 3. 合并商品列表
|
||||
List<ProductItem> allProducts = new ArrayList<>();
|
||||
allProducts.addAll(purchased);
|
||||
allProducts.addAll(intending);
|
||||
|
||||
// 4. 价格计算
|
||||
PriceDetails purchasedPrice = calculateProductsPriceWithOriginal(purchased);
|
||||
PriceDetails intendingPrice = calculateProductsPriceWithOriginal(intending);
|
||||
PriceDetails totalPrice = calculateProductsPrice(allProducts);
|
||||
|
||||
// 5. 优惠评估
|
||||
UpgradeOnePriceResult onePriceResult = evaluateOnePrice(request.getScenicId(), allProducts, totalPrice);
|
||||
UpgradeBundleDiscountResult bundleResult = evaluateBundleDiscount(request.getScenicId(), allProducts, totalPrice);
|
||||
|
||||
// 6. 结果汇总
|
||||
return buildUpgradeResult(purchasedPrice, intendingPrice, onePriceResult, bundleResult);
|
||||
}
|
||||
```
|
||||
|
||||
## 测试策略
|
||||
|
||||
### 1. 单元测试
|
||||
建议覆盖:
|
||||
- 价格计算核心流程与边界
|
||||
- 优惠券/券码/一口价适用性与叠加规则
|
||||
- 异常场景与异常处理器
|
||||
### 单元测试类型
|
||||
- **服务层测试**:每个服务类都有对应测试类
|
||||
- `PriceBundleServiceTest` - 套餐价格计算测试
|
||||
- `ReusableVoucherServiceTest` - 可重复使用券码测试
|
||||
- `VoucherTimeRangeTest` - 券码时间范围功能测试
|
||||
- `VoucherPrintServiceCodeGenerationTest` - 券码生成测试
|
||||
- **实体映射测试**:验证数据库映射和JSON序列化
|
||||
- `PriceBundleConfigStructureTest` - 实体结构测试
|
||||
- `PriceBundleConfigJsonTest` - JSON序列化测试
|
||||
- `CouponSwitchFieldsMappingTest` - 字段映射测试
|
||||
- **类型处理器测试**:验证自定义TypeHandler
|
||||
- `BundleProductListTypeHandlerTest` - 套餐商品列表序列化测试
|
||||
- **配置验证测试**:验证系统配置完整性
|
||||
- `DefaultConfigValidationTest` - 验证所有ProductType的default配置
|
||||
- `CodeGenerationStandaloneTest` - 独立代码生成测试
|
||||
|
||||
### 2. 集成测试
|
||||
- 数据库读写与分页
|
||||
- JSON 序列化/反序列化(TypeHandler)
|
||||
- API 端点的入参/出参校验
|
||||
### 测试执行命令
|
||||
```bash
|
||||
# 运行单个测试类
|
||||
mvn test -Dtest=VoucherTimeRangeTest
|
||||
mvn test -Dtest=ReusableVoucherServiceTest
|
||||
mvn test -Dtest=BundleProductListTypeHandlerTest
|
||||
|
||||
### 3. 配置校验
|
||||
- 校验各 ProductType 的默认配置完整性
|
||||
- 关键枚举与配置代码路径的兼容性
|
||||
# 运行整个pricing模块测试
|
||||
mvn test -Dtest="com.ycwl.basic.pricing.*Test"
|
||||
|
||||
# 运行特定分类的测试
|
||||
mvn test -Dtest="com.ycwl.basic.pricing.service.*Test" # 服务层测试
|
||||
mvn test -Dtest="com.ycwl.basic.pricing.handler.*Test" # TypeHandler测试
|
||||
mvn test -Dtest="com.ycwl.basic.pricing.entity.*Test" # 实体测试
|
||||
mvn test -Dtest="com.ycwl.basic.pricing.mapper.*Test" # Mapper测试
|
||||
|
||||
# 运行带详细报告的测试
|
||||
mvn test -Dtest="com.ycwl.basic.pricing.*Test" -Dsurefire.printSummary=true
|
||||
```
|
||||
|
||||
### 重点测试场景
|
||||
- **价格计算核心流程**:验证统一优惠检测和组合逻辑
|
||||
- **可重复使用券码**:验证多次使用、时间间隔、用户限制逻辑
|
||||
- **时间范围控制**:验证券码有效期开始和结束时间
|
||||
- **优惠叠加规则**:验证券码、优惠券、一口价的叠加逻辑
|
||||
- **JSON序列化**:验证复杂对象在数据库中的存储和读取
|
||||
- **分页功能**:验证PageHelper和MyBatis-Plus分页集成
|
||||
- **异常处理**:验证业务异常和全局异常处理器
|
||||
|
||||
## 数据库设计
|
||||
|
||||
@@ -665,11 +790,20 @@ CREATE INDEX idx_print_face_scenic ON voucher_print_record(face_id, scenic_id);
|
||||
- 使用数据完整性检查 SQL 验证统计数据准确性
|
||||
- **优惠券领取记录表查询优化** (v1.0.0): 为 `(user_id, coupon_id)` 添加复合索引以加速用户领取次数统计
|
||||
|
||||
### 关键架构变更
|
||||
|
||||
#### 最近重要更新 (2025-09-18)
|
||||
1. **新增升单检测功能** - 添加了`/api/pricing/upgrade-check`接口,支持已购和待购商品的优惠组合检测
|
||||
2. **新增打包购买优惠功能** - 实现了多商品组合优惠策略,优先级100(仅次于一口价)
|
||||
3. **优惠优先级调整** - 确立了"一口价 > 打包购买 > 券码 > 优惠券"的优先级顺序
|
||||
4. **PrinterServiceImpl重构** - 移除对PriceRepository的依赖,统一使用IPriceCalculationService
|
||||
|
||||
## 兼容性与注意事项
|
||||
|
||||
- 本模块使用 PageHelper(优惠券相关)与 MyBatis‑Plus(券码/一口价等)并存,请根据对应 Service/Mapper 选择分页与查询方式。
|
||||
- 优惠优先级及叠加规则以各 Provider 与业务配置为准,避免在外层重复实现优先级判断逻辑。
|
||||
- 若扩展新的优惠类型,务必实现 `IDiscountProvider` 并在 `IDiscountDetectionService` 中完成注册(当前实现通过组件扫描自动注册并排序)。
|
||||
- 升单检测功能依赖完整的价格计算和优惠检测服务,确保相关依赖正常注入。
|
||||
- **优惠券数量管理** (v1.0.0): 现有代码已调整为领取时更新 `claimedQuantity`,使用时更新 `usedQuantity`。如业务需求不同,请调整 `CouponServiceImpl.claimCoupon()` 和 `CouponServiceImpl.useCoupon()` 逻辑。
|
||||
|
||||
## 版本更新记录
|
||||
|
||||
@@ -38,6 +38,20 @@ public class PriceCalculationController {
|
||||
return ApiResponse.success(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 升单检测:判断是否命中一口价或打包优惠
|
||||
*/
|
||||
@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;
|
||||
@@ -36,6 +37,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;
|
||||
private final IProductTypeCapabilityService productTypeCapabilityService;
|
||||
@@ -159,6 +161,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;
|
||||
|
||||
@@ -390,6 +435,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