# 价格查询系统 (Pricing Module) 开发指南

此文档为pricing包的专用开发指南，提供该模块的详细架构说明和开发最佳实践。

## 模块概览

价格查询系统 (`com.ycwl.basic.pricing`) 是一个独立的业务模块，提供商品定价、优惠券管理、券码管理和价格计算功能。采用分层架构设计，具备完整的CRUD操作、异常处理和数据统计功能。支持优惠券和券码的同时使用，券码优先级更高。

## 目录结构

```
com.ycwl.basic.pricing/
├── controller/          # REST API控制器层
│   ├── CouponManagementController.java    # 优惠券管理API
│   ├── PriceCalculationController.java    # 价格计算API  
│   └── PricingConfigController.java       # 价格配置管理API
├── dto/                # 数据传输对象
│   ├── BundleProductItem.java             # 套餐商品项
│   ├── CouponInfo.java                    # 优惠券信息
│   ├── CouponUseRequest.java              # 优惠券使用请求
│   ├── CouponUseResult.java               # 优惠券使用结果
│   ├── DiscountDetail.java                # 折扣详情
│   ├── PriceCalculationRequest.java       # 价格计算请求
│   ├── PriceCalculationResult.java        # 价格计算结果
│   ├── PriceDetails.java                  # 价格详情
│   ├── ProductItem.java                   # 商品项
│   ├── ProductPriceInfo.java              # 商品价格信息
│   ├── VoucherInfo.java                   # 券码信息
│   ├── DiscountDetectionContext.java      # 优惠检测上下文
│   ├── DiscountInfo.java                  # 优惠信息
│   ├── DiscountResult.java                # 优惠结果
│   └── DiscountCombinationResult.java     # 优惠组合结果
├── entity/             # 数据库实体类
│   ├── BaseEntity.java                    # 基础实体类
│   ├── PriceBundleConfig.java             # 套餐配置
│   ├── PriceCouponClaimRecord.java        # 优惠券领取记录
│   ├── PriceCouponConfig.java             # 优惠券配置
│   ├── PriceProductConfig.java            # 商品价格配置
│   ├── PriceTierConfig.java               # 分层价格配置
│   ├── PriceVoucherBatchConfig.java       # 券码批次配置
│   └── PriceVoucherCode.java              # 券码实体
├── enums/              # 枚举类
│   ├── CouponStatus.java                  # 优惠券状态
│   ├── CouponType.java                    # 优惠券类型
│   ├── ProductType.java                   # 商品类型
│   ├── VoucherDiscountType.java           # 券码优惠类型
│   └── VoucherCodeStatus.java             # 券码状态
├── exception/          # 异常处理
│   ├── CouponInvalidException.java        # 优惠券无效异常
│   ├── PriceCalculationException.java     # 价格计算异常
│   ├── PricingExceptionHandler.java       # 定价异常处理器
│   ├── ProductConfigNotFoundException.java # 商品配置未找到异常
│   ├── VoucherInvalidException.java       # 券码无效异常
│   ├── VoucherAlreadyUsedException.java   # 券码已使用异常
│   ├── VoucherNotClaimableException.java  # 券码不可领取异常
│   └── DiscountDetectionException.java    # 优惠检测异常
├── handler/            # 自定义处理器
│   └── BundleProductListTypeHandler.java  # 套餐商品列表类型处理器
├── mapper/             # MyBatis数据访问层
│   ├── PriceBundleConfigMapper.java
│   ├── PriceCouponClaimRecordMapper.java
│   ├── PriceCouponConfigMapper.java
│   ├── PriceProductConfigMapper.java
│   ├── PriceTierConfigMapper.java
│   ├── PriceVoucherBatchConfigMapper.java
│   └── PriceVoucherCodeMapper.java
└── service/            # 业务逻辑层
    ├── ICouponManagementService.java      # 优惠券管理服务接口
    ├── ICouponService.java                # 优惠券服务接口
    ├── IPriceBundleService.java           # 套餐服务接口
    ├── IPriceCalculationService.java      # 价格计算服务接口
    ├── IPricingManagementService.java     # 定价管理服务接口
    ├── IProductConfigService.java         # 商品配置服务接口
    ├── IVoucherService.java               # 券码服务接口
    ├── IDiscountProvider.java             # 优惠提供者接口
    ├── IDiscountDetectionService.java     # 优惠检测服务接口
    └── impl/                              # 服务实现类
        ├── CouponManagementServiceImpl.java
        ├── CouponServiceImpl.java
        ├── PriceBundleServiceImpl.java
        ├── PriceCalculationServiceImpl.java
        ├── PricingManagementServiceImpl.java
        ├── ProductConfigServiceImpl.java
        ├── VoucherServiceImpl.java
        ├── CouponDiscountProvider.java
        ├── VoucherDiscountProvider.java
        └── DiscountDetectionServiceImpl.java
```

## 核心功能

### 1. 价格计算引擎

#### API端点
- `POST /api/pricing/calculate` - 执行价格计算

#### 计算流程
```java
// 价格计算核心流程
1. 验证PriceCalculationRequest请求参数
2. 加载商品基础配置 (PriceProductConfig)
3. 应用分层定价规则 (PriceTierConfig)
4. 处理套餐商品逻辑 (BundleProductItem)
5. 使用统一优惠检测系统处理券码和优惠券
6. 按优先级应用优惠：券码 > 优惠券
7. 计算最终价格并返回详细结果
```

#### 关键类
- `PriceCalculationService`: 价格计算核心逻辑
- `PriceCalculationRequest`: 计算请求DTO
- `PriceCalculationResult`: 计算结果DTO

### 2. 优惠券管理系统

#### API端点
- `GET /api/pricing/admin/coupons/` - 分页查询优惠券配置
- `POST /api/pricing/admin/coupons/` - 创建优惠券配置
- `PUT /api/pricing/admin/coupons/{id}` - 更新优惠券配置
- `DELETE /api/pricing/admin/coupons/{id}` - 删除优惠券配置
- `GET /api/pricing/admin/coupons/{id}/claims` - 查询优惠券领取记录
- `GET /api/pricing/admin/coupons/{id}/stats` - 获取优惠券统计信息

#### 优惠券类型
```java
public enum CouponType {
    PERCENTAGE,     // 百分比折扣
    FIXED_AMOUNT    // 固定金额减免
}

public enum CouponStatus {
    CLAIMED,        // 已领取
    USED,           // 已使用  
    EXPIRED         // 已过期
}
```

#### 关键特性
- **商品类型限制**: 通过`applicableProducts` JSON字段控制适用商品
- **消费限制**: 支持最小消费金额和最大折扣限制
- **时效性**: 基于时间的有效期控制
- **统计分析**: 完整的使用统计和分析功能

### 3. 商品配置管理

#### API端点
- `GET /api/pricing/config/products` - 查询商品配置
- `POST /api/pricing/config/products` - 创建商品配置
- `PUT /api/pricing/config/products/{id}` - 更新商品配置

#### 商品类型定义
```java
public enum ProductType {
    VLOG_VIDEO("vlog_video", "Vlog视频"),
    RECORDING_SET("recording_set", "录像集"),
    PHOTO_SET("photo_set", "照相集"), 
    PHOTO_PRINT("photo_print", "照片打印"),
    MACHINE_PRINT("machine_print", "一体机打印");
}
```

#### 分层定价
支持基于数量的分层定价策略，通过`PriceTierConfig`实体配置不同数量区间的单价。

## 开发最佳实践

### 1. 添加新商品类型

```java
// 步骤1: 在ProductType枚举中添加新类型
public enum ProductType {
    // 现有类型...
    NEW_PRODUCT("new_product", "新商品类型");
}

// 步骤2: 在数据库中添加default配置
INSERT INTO price_product_config (product_type, base_price, ...) 
VALUES ('new_product', 100.00, ...);

// 步骤3: 添加分层定价配置（可选）
INSERT INTO price_tier_config (product_type, min_quantity, max_quantity, unit_price, ...) 
VALUES ('new_product', 1, 10, 95.00, ...);

// 步骤4: 更新前端产品类型映射
```

### 2. 扩展优惠券类型

```java
// 步骤1: 在CouponType枚举中添加新类型  
public enum CouponType {
    PERCENTAGE,
    FIXED_AMOUNT,
    NEW_COUPON_TYPE    // 新增类型
}

// 步骤2: 在CouponServiceImpl中实现计算逻辑
@Override
public BigDecimal calculateDiscount(CouponConfig coupon, BigDecimal originalPrice) {
    return switch (coupon.getCouponType()) {
        case PERCENTAGE -> calculatePercentageDiscount(coupon, originalPrice);
        case FIXED_AMOUNT -> calculateFixedAmountDiscount(coupon, originalPrice);
        case NEW_COUPON_TYPE -> calculateNewTypeDiscount(coupon, originalPrice);
    };
}

// 步骤3: 更新applicableProducts验证规则
```

### 3. 自定义TypeHandler使用

项目使用MyBatis自定义TypeHandler处理复杂JSON字段：

```java
// BundleProductListTypeHandler处理套餐商品列表
@MappedTypes(List.class)
@MappedJdbcTypes(JdbcType.VARCHAR)
public class BundleProductListTypeHandler extends BaseTypeHandler<List<BundleProductItem>> {
    // JSON序列化/反序列化逻辑
}

// 在实体类中使用
public class SomeEntity {
    @TableField(typeHandler = BundleProductListTypeHandler.class)
    private List<BundleProductItem> bundleProducts;
}
```

### 4. 异常处理模式

```java
// 自定义异常类
public class PriceCalculationException extends RuntimeException {
    public PriceCalculationException(String message) {
        super(message);
    }
}

// 在PricingExceptionHandler中统一处理
@ExceptionHandler(PriceCalculationException.class)
public ApiResponse<String> handlePriceCalculationException(PriceCalculationException e) {
    return ApiResponse.error(ErrorCode.PRICE_CALCULATION_ERROR, e.getMessage());
}
```

### 5. 分页查询实现

```java
// 使用PageHelper实现分页
@Override
public PageInfo<CouponConfig> getCouponsByPage(int pageNum, int pageSize, String status, String name) {
    PageHelper.startPage(pageNum, pageSize);
    
    QueryWrapper<CouponConfig> queryWrapper = new QueryWrapper<>();
    if (StringUtils.hasText(status)) {
        queryWrapper.eq("status", status);
    }
    if (StringUtils.hasText(name)) {
        queryWrapper.like("name", name);
    }
    
    List<CouponConfig> list = couponConfigMapper.selectList(queryWrapper);
    return new PageInfo<>(list);
}
```

## 测试策略

### 1. 单元测试
每个服务类都应有对应的测试类，重点测试：
- 价格计算逻辑的准确性
- 优惠券适用性验证
- 边界条件处理
- 异常场景覆盖

### 2. 集成测试
- 数据库操作测试
- JSON序列化测试
- 分页功能测试
- API端点测试

### 3. 配置验证测试
- `DefaultConfigValidationTest`: 验证default配置的完整性和正确性
- 确保所有ProductType都有对应的基础配置

## 数据库设计

### 核心表结构
- `price_product_config`: 商品价格基础配置
- `price_tier_config`: 分层定价配置
- `price_bundle_config`: 套餐配置
- `price_coupon_config`: 优惠券配置
- `price_coupon_claim_record`: 优惠券领取记录

### 关键字段设计
- JSON字段处理: `applicable_products`使用JSON存储适用商品类型列表
- 时间字段: 统一使用`LocalDateTime`类型
- 价格字段: 使用`BigDecimal`确保精度
- 状态字段: 使用枚举类型确保数据一致性

## 性能优化建议

1. **数据库查询优化**
   - 为常用查询字段添加索引
   - 使用分页查询避免大量数据加载
   - 优化复杂的JOIN查询

2. **缓存策略**
   - 对商品配置进行缓存，减少数据库访问
   - 使用Redis缓存热点优惠券信息

3. **计算性能**
   - 价格计算使用BigDecimal确保精度
   - 批量计算时考虑并行处理

## 安全考虑

1. **输入验证**
   - 严格验证所有输入参数
   - 防止SQL注入和XSS攻击

2. **权限控制**
   - 管理接口需要适当的权限验证
   - 用户只能访问自己的优惠券记录

3. **数据完整性**
   - 使用事务确保数据一致性
   - 关键操作添加审计日志

## 券码管理系统 (Voucher System)

### 1. 核心特性

券码系统是从原`voucher`包迁移而来，现已完全集成到pricing包中，与优惠券系统并行工作。

#### 券码优惠类型
```java
public enum VoucherDiscountType {
    FREE_ALL(0, "全场免费"),      // 所有商品免费，优先级最高且不可叠加
    REDUCE_PRICE(1, "商品降价"),  // 每个商品减免固定金额
    DISCOUNT(2, "商品打折")       // 每个商品按百分比打折
}
```

#### 券码状态流转
```
UNCLAIMED(0) → CLAIMED_UNUSED(1) → USED(2)
   未领取    →    已领取未使用    →   已使用
```

### 2. 数据库表结构

#### 券码批次配置表 (price_voucher_batch_config)
- 批次管理：支持按景区、推客创建券码批次
- 统计功能：实时统计已领取数、已使用数
- 状态控制：支持启用/禁用批次

#### 券码表 (price_voucher_code)
- 唯一性约束：每个券码全局唯一
- 用户限制：同一用户在同一景区只能领取一次券码
- 时间追踪：记录领取时间、使用时间

### 3. 关键业务规则

#### 领取限制
- 同一`faceId`在同一`scenicId`中只能领取一次券码
- 只有启用状态的批次才能领取券码
- 批次必须有可用券码才能成功领取

#### 使用验证
- 券码必须是`CLAIMED_UNUSED`状态才能使用
- 必须验证券码与景区的匹配关系
- 使用后自动更新批次统计数据

## 统一优惠检测系统 (Unified Discount Detection)

### 1. 架构设计

采用策略模式设计的可扩展优惠检测系统，支持多种优惠类型的统一管理和自动优化组合。

#### 核心接口
```java
// 优惠提供者接口
public interface IDiscountProvider {
    String getProviderType();                    // 提供者类型
    int getPriority();                          // 优先级（数字越大越高）
    List<DiscountInfo> detectAvailableDiscounts(); // 检测可用优惠
    DiscountResult applyDiscount();             // 应用优惠
}

// 优惠检测服务接口
public interface IDiscountDetectionService {
    DiscountCombinationResult calculateOptimalCombination(); // 计算最优组合
    DiscountCombinationResult previewOptimalCombination();   // 预览优惠组合
}
```

### 2. 优惠提供者实现

#### VoucherDiscountProvider (优先级: 100)
- 处理券码优惠逻辑
- 支持用户主动输入券码或自动选择最优券码
- 全场免费券码不可与其他优惠叠加

#### CouponDiscountProvider (优先级: 80)
- 处理优惠券优惠逻辑
- 自动选择最优优惠券
- 可与券码叠加使用（除全场免费券码外）

### 3. 优惠应用策略

#### 优先级规则
```
券码优惠 (Priority: 100) → 优惠券优惠 (Priority: 80)
```

#### 叠加逻辑
```java
原价 → 应用券码优惠 → 应用优惠券优惠 → 最终价格

特殊情况：
- 全场免费券码：最终价格直接为0，不再应用其他优惠
- 其他券码类型：可与优惠券叠加使用
```

#### 显示顺序
```
1. 券码优惠 (sortOrder: 1)
2. 限时立减 (sortOrder: 2)  
3. 优惠券优惠 (sortOrder: 3)
4. 一口价优惠 (sortOrder: 4)
```

### 4. 扩展支持

#### 添加新优惠类型
```java
@Component
public class FlashSaleDiscountProvider implements IDiscountProvider {
    @Override
    public String getProviderType() { return "FLASH_SALE"; }
    
    @Override
    public int getPriority() { return 90; } // 介于券码和优惠券之间
    
    // 实现其他方法...
}
```

#### 动态注册
```java
// 系统启动时自动扫描所有IDiscountProvider实现类
// 按优先级排序并注册到DiscountDetectionService中
```

## API接口扩展

### 1. 价格计算接口扩展

#### 新增请求参数
```java
public class PriceCalculationRequest {
    // 原有字段...
    private String voucherCode;        // 用户输入的券码
    private Long scenicId;             // 景区ID
    private Long faceId;               // 用户faceId
    private Boolean autoUseVoucher;    // 是否自动使用券码
    private Boolean previewOnly;       // 是否仅预览优惠
}
```

#### 新增响应字段
```java
public class PriceCalculationResult {
    // 原有字段...
    private VoucherInfo usedVoucher;           // 使用的券码信息
    private List<DiscountInfo> availableDiscounts; // 可用优惠列表（预览模式）
}
```

### 2. 券码管理接口

#### 移动端接口
- `POST /api/pricing/mobile/voucher/claim` - 领取券码
- `GET /api/pricing/mobile/voucher/my-codes` - 我的券码列表

#### 管理端接口
- `POST /api/pricing/admin/voucher/batch/create` - 创建券码批次
- `GET /api/pricing/admin/voucher/batch/list` - 批次列表查询
- `GET /api/pricing/admin/voucher/codes` - 券码列表查询

## 开发最佳实践更新

### 1. 优惠检测开发
```java
// 检测上下文构建
DiscountDetectionContext context = new DiscountDetectionContext();
context.setUserId(userId);
context.setFaceId(faceId);
context.setScenicId(scenicId);
context.setProducts(products);
context.setCurrentAmount(amount);
context.setVoucherCode(voucherCode);

// 使用统一服务检测优惠
DiscountCombinationResult result = discountDetectionService
    .calculateOptimalCombination(context);
```

### 2. 券码服务使用
```java
// 验证券码
VoucherInfo voucherInfo = voucherService.validateAndGetVoucherInfo(
    voucherCode, faceId, scenicId);

// 计算券码优惠
BigDecimal discount = voucherService.calculateVoucherDiscount(
    voucherInfo, context);

// 标记券码已使用
voucherService.markVoucherAsUsed(voucherCode, "订单使用");
```

### 3. 异常处理扩展
```java
// 券码相关异常
try {
    // 券码操作
} catch (VoucherInvalidException e) {
    // 券码无效
} catch (VoucherAlreadyUsedException e) {
    // 券码已使用
} catch (VoucherNotClaimableException e) {
    // 券码不可领取
}
```

## 数据库扩展

### 新增表结构
- `price_voucher_batch_config`: 券码批次配置表
- `price_voucher_code`: 券码表

### 索引优化
```sql
-- 券码查询优化
CREATE INDEX idx_voucher_code ON price_voucher_code(code);
CREATE INDEX idx_face_scenic ON price_voucher_code(face_id, scenic_id);

-- 批次查询优化  
CREATE INDEX idx_scenic_broker ON price_voucher_batch_config(scenic_id, broker_id);
```

### 性能考虑
- 券码表可能数据量较大，考虑按景区分表
- 定期清理已删除的过期数据
- 使用数据完整性检查SQL验证统计数据准确性