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

此文档为 pricing 包的专用开发指南，基于当前代码实际结构进行说明与示例。

## 模块概览

`com.ycwl.basic.pricing` 提供商品定价、分层/套餐与一口价配置、优惠券管理、券码管理与使用记录、统一优惠检测与价格计算等能力。采用分层架构（controller/dto/entity/mapper/service），同时整合了 PageHelper 与 MyBatis‑Plus 的分页能力。优惠叠加采用统一检测与排序：券码 > 优惠券 > 一口价（受配置限制）。

## 目录结构（已对齐当前代码）

```
com.ycwl.basic.pricing/
├── controller/                      # REST API 控制器层
│   ├── PriceCalculationController.java     # 价格计算 + 用户可用券查询
│   ├── PricingConfigController.java        # 产品/阶梯/套餐(一口价)配置管理(含 admin 查询)
│   ├── CouponManagementController.java     # 优惠券配置/领取记录/统计（admin）
│   ├── OnePricePurchaseController.java     # 一口价配置管理（admin）
│   ├── VoucherManagementController.java    # 券码批次/券码/移动端领取与查询
│   └── VoucherUsageController.java         # 券码使用记录与统计查询
├── dto/                                # 数据传输对象
│   ├── PriceCalculationRequest.java        # 价格计算请求（含voucherCode/faceId/scenicId...）
│   ├── PriceCalculationResult.java         # 价格计算结果（含usedCoupon/usedVoucher/availableDiscounts）
│   ├── ProductItem.java, ProductPriceInfo.java, PriceDetails.java
│   ├── DiscountDetectionContext.java, DiscountInfo.java, DiscountDetail.java,
│   │   DiscountResult.java, DiscountCombinationResult.java
│   ├── BundleProductItem.java, MobilePriceCalculationRequest.java
│   ├── OnePriceConfigRequest.java, OnePriceInfo.java
│   ├── BundleDiscountInfo.java               # 打包购买优惠信息
│   ├── req/                               # 券码管理请求DTO
│   │   ├── VoucherBatchCreateReq(.java|V2)
│   │   ├── VoucherBatchQueryReq.java, VoucherCodeQueryReq.java, VoucherClaimReq.java
│   │   └── VoucherUsageHistoryReq.java
│   └── resp/                              # 券码管理响应DTO
│       ├── VoucherBatchResp.java, VoucherBatchStatsResp.java
│       ├── VoucherCodeResp.java, VoucherValidationResp.java
│       ├── VoucherUsageRecordResp.java, VoucherUsageStatsResp.java, VoucherUsageSummaryResp.java
│       └── VoucherPrintResp.java
├── entity/                             # 数据库实体类（MyBatis‑Plus）
│   ├── PriceProductConfig.java, PriceTierConfig.java, PriceBundleConfig.java
│   ├── PriceCouponConfig.java, PriceCouponClaimRecord.java
│   ├── PriceVoucherBatchConfig.java, PriceVoucherCode.java, PriceVoucherUsageRecord.java
│   ├── PriceOnePriceConfig.java                 # 一口价配置
│   └── VoucherPrintRecord.java                  # 券码打印记录
├── enums/                              # 枚举类
│   ├── ProductType.java, CouponType.java, CouponStatus.java
│   ├── VoucherDiscountType.java, VoucherCodeStatus.java
├── exception/                          # 统一异常与业务异常
│   ├── PricingExceptionHandler.java, PriceCalculationException.java
│   ├── ProductConfigNotFoundException.java, DiscountDetectionException.java
│   ├── CouponInvalidException.java, VoucherInvalidException.java,
│   ├── VoucherAlreadyUsedException.java, VoucherNotClaimableException.java
├── handler/                            # MyBatis 类型处理器
│   ├── BundleProductListTypeHandler.java  # 套餐商品列表 JSON ↔ 对象列表
│   └── ProductTypeListTypeHandler.java    # 商品类型列表 JSON ↔ 枚举列表
├── mapper/                             # 数据访问接口（多为 MyBatis‑Plus Mapper）
│   ├── PriceProductConfigMapper.java, PriceTierConfigMapper.java, PriceBundleConfigMapper.java
│   ├── PriceCouponConfigMapper.java, PriceCouponClaimRecordMapper.java
│   ├── PriceVoucherBatchConfigMapper.java, PriceVoucherCodeMapper.java, PriceVoucherUsageRecordMapper.java
│   └── PriceOnePriceConfigMapper.java, VoucherPrintRecordMapper.java
└── service/                            # 业务层接口与实现
    ├── IPriceCalculationService.java, IDiscountDetectionService.java, IDiscountProvider.java
    ├── IBundleDiscountService.java              # 打包购买优惠服务接口
    ├── IProductConfigService.java, IPricingManagementService.java, IPriceBundleService.java
    ├── ICouponService.java, ICouponManagementService.java
    ├── IOnePricePurchaseService.java, IVoucherService.java, IVoucherUsageService.java
    ├── VoucherBatchService.java, VoucherCodeService.java, VoucherPrintService.java
    └── impl/
        ├── PriceCalculationServiceImpl.java, DiscountDetectionServiceImpl.java
        ├── ProductConfigServiceImpl.java, PricingManagementServiceImpl.java, PriceBundleServiceImpl.java
        ├── CouponServiceImpl.java, CouponManagementServiceImpl.java, CouponDiscountProvider.java
        ├── VoucherServiceImpl.java, VoucherDiscountProvider.java,
        ├── VoucherBatchServiceImpl.java, VoucherCodeServiceImpl.java, VoucherPrintServiceImpl.java,
        ├── VoucherUsageServiceImpl.java,
        ├── OnePricePurchaseServiceImpl.java, OnePricePurchaseDiscountProvider.java
        └── BundleDiscountServiceImpl.java, BundleDiscountProvider.java  # 打包购买优惠实现
```

## 核心功能

### 1. 价格计算引擎

#### API端点
- `POST /api/pricing/calculate` — 执行价格计算（预览模式默认开启）
- `GET  /api/pricing/coupons/my-coupons` — 查询用户可用优惠券
- `POST /api/pricing/upgrade-check` — 升单检测：综合已购与待购商品，判断是否命中一口价或打包优惠

#### 计算流程
```java
// 价格计算核心流程（IPriceCalculationService / PriceCalculationServiceImpl）
1. 校验 PriceCalculationRequest（包含 voucherCode / scenicId / faceId / autoUseXXX）
2. 加载商品基础配置 (PriceProductConfig)，匹配分层规则 (PriceTierConfig)、套餐等
3. 构造 DiscountDetectionContext，统一检测可用优惠（券码/优惠券/一口价）
4. 按优先级与可叠加规则应用优惠：券码 > 优惠券 > 一口价（受 canUseCoupon/canUseVoucher 控制）
5. 汇总 DiscountDetail，计算 finalAmount / discountAmount 并返回 PriceCalculationResult
```

#### 关键类
- `IPriceCalculationService` / `PriceCalculationServiceImpl`: 价格计算核心逻辑
- `PriceCalculationRequest`: 计算请求DTO
- `PriceCalculationResult`: 计算结果DTO
- `IDiscountDetectionService`: 统一优惠检测与组合

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

#### API端点（管理端，见 `CouponManagementController`）
- `POST   /api/pricing/admin/coupons/configs` — 创建配置
- `PUT    /api/pricing/admin/coupons/configs/{id}` — 更新配置
- `DELETE /api/pricing/admin/coupons/configs/{id}` — 删除配置
- `PUT    /api/pricing/admin/coupons/configs/{id}/status` — 启用/禁用
- `GET    /api/pricing/admin/coupons/configs` — 全量配置（含禁用）
- `GET    /api/pricing/admin/coupons/configs/page` — 分页查询
- `GET    /api/pricing/admin/coupons/claim-records[/*]` — 领取记录列表/分页/按条件查询
- `GET    /api/pricing/admin/coupons/stats/{couponId}[/*]` — 使用统计/明细/概览

#### 枚举定义（简述）
```java
// 实际枚举包含 code/description 字段，并提供 fromCode 工具方法
public enum CouponType { PERCENTAGE("percentage", ...), FIXED_AMOUNT("fixed_amount", ...) }
public enum CouponStatus { CLAIMED("claimed", ...), USED("used", ...), EXPIRED("expired", ...) }
```

#### 关键特性
- 商品类型限制：通过 JSON 字段（结合 `ProductTypeListTypeHandler`）控制适用商品
- 属性门槛：通过 `requiredAttributeKeys`(JSON) 配置，要求在可折扣商品范围内任一商品出现任一属性Key（属性Key为后端与运营约定的字符串）；商品属性由服务端根据商品能力配置（`ProductTypeCapability.metadata.pricingAttributeKeys`）计算写入 `ProductItem.attributeKeys`
- 消费限制：支持最小消费金额、最大折扣限制
- 时效性：基于时间的有效期控制
- **用户领取数量限制**：通过 `userClaimLimit` 字段控制单个用户可领取优惠券的最大数量(v1.0.0新增)
- **库存精细化管理**：区分 `claimedQuantity`(已领取数量) 和 `usedQuantity`(已使用数量)
- 统计分析：完整的使用统计与分析能力

#### 优惠券数量管理机制 (v1.0.0更新)
```java
// PriceCouponConfig 实体字段
private Integer totalQuantity;     // 发行总量(NULL或0表示不限制)
private Integer userClaimLimit;    // 每个用户可领取数量限制(NULL或0表示不限制)
private Integer claimedQuantity;   // 已领取数量(区分于已使用数量)
private Integer usedQuantity;      // 已使用数量(实际消耗时更新)

// 字段语义
totalQuantity:     NULL/0=不限总量,  >0=限制总量
userClaimLimit:    NULL/0=不限用户,  >0=限制单用户
claimedQuantity:   仅在totalQuantity>0时更新
usedQuantity:      实际使用时更新

// 领取流程检查顺序
1. 检查优惠券是否存在且启用
2. 检查有效期
3. 检查总量库存: totalQuantity>0 时,检查 claimedQuantity < totalQuantity
4. 检查用户领取限制: userClaimLimit>0 时,检查 countUserClaims(userId, couponId) < userClaimLimit
5. 创建领取记录并更新 claimedQuantity (仅当totalQuantity>0时)
```

#### 用户领取限制配置示例
```java
// 场景1: 新人专享券,每人限领1张,总量1000张
{
  "couponName": "新人专享券",
  "userClaimLimit": 1,
  "totalQuantity": 1000
}

// 场景2: 活动券,每人限领3张,总量5000张
{
  "couponName": "618促销券",
  "userClaimLimit": 3,
  "totalQuantity": 5000
}

// 场景3: 不限制领取次数,不限制总量
{
  "couponName": "会员专享券",
  "userClaimLimit": null,  // 或 0
  "totalQuantity": null    // 或 0,表示无限量
}

// 场景4: 不限用户次数,但限制总量
{
  "couponName": "限量抢购券",
  "userClaimLimit": null,  // 不限单用户
  "totalQuantity": 500     // 总共500张
}
```

#### 错误码扩展
- `CLAIM_LIMIT_REACHED`: 用户已达到该优惠券的领取上限
- `COUPON_OUT_OF_STOCK`: 优惠券库存不足(基于claimedQuantity检查)
- `ALREADY_CLAIMED`: 用户已领取过(兼容旧逻辑)

### 3. 商品配置管理

#### API端点（摘）
- `GET  /api/pricing/config/products` — 查询商品配置
- `POST /api/pricing/config/products` — 创建商品配置
- `PUT  /api/pricing/config/products/{id}` — 更新商品配置
- `GET  /api/pricing/config/tiers[/*]`、`/bundles[/*]` — 阶梯/一口价配置查询

#### 商品类型定义（对齐实际）
```java
// 实际定义（带 code/description，并提供 fromCode）：
public enum ProductType {
    VLOG_VIDEO("VLOG_VIDEO", "Vlog视频"),
    RECORDING_SET("RECORDING_SET", "录像集"),
    PHOTO_SET("PHOTO_SET", "照相集"),
    PHOTO_PRINT("PHOTO_PRINT", "照片打印"),
    MACHINE_PRINT("MACHINE_PRINT", "一体机打印");
}
```

#### 商品价格配置控制字段
`PriceProductConfig` 实体包含以下优惠控制字段：
- `canUseCoupon`: 是否可使用优惠券
- `canUseVoucher`: 是否可使用券码
- `canUseOnePrice`: 是否可使用一口价优惠（新增）

#### 一口价优惠控制机制
- 当购物车中任何商品的 `canUseOnePrice` 为 `false` 时，将跳过整个购物车的一口价优惠检测
- 配置优先级：具体商品配置 > 商品类型默认配置 > 系统默认（支持）
- 异常情况下默认支持一口价优惠，确保业务流程不受影响

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

## 开发最佳实践

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

```java
// 步骤1: 在 ProductType 枚举中添加新类型（含 code/description）
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 使用

```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;
}

// ProductTypeListTypeHandler 处理商品类型列表（券码可用商品类型）
public class ProductTypeListTypeHandler extends BaseTypeHandler<List<ProductType>> { /* ... */ }
```

### 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.fail(ErrorCode.PRICE_CALCULATION_ERROR, e.getMessage());
}
```

### 5. 分页查询实现

```
// 使用 PageHelper 实现分页（优惠券相关）
@Override
public PageInfo<CouponConfig> getCouponsByPage(int pageNum, int pageSize, String status, String name) {
    PageHelper.startPage(pageNum, pageSize);
    // ...
}
```

```
// 使用 MyBatis‑Plus 的 Page（券码相关）
Page<VoucherBatchResp> page = voucherBatchService.queryBatchList(req);
Page<VoucherCodeResp> page = voucherCodeService.queryCodeList(req);
```

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

### 1. 核心特性

券码系统由原独立模块迁移并完全集成到 pricing 包中，与优惠券系统并行工作。

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

#### 券码状态（简述）
```
UNCLAIMED(0) → CLAIMED_AVAILABLE/CLAIMED_UNUSED(1) → USED(2) → CLAIMED_EXHAUSTED(3) → EXPIRED(4)
```

### 2. 数据库表结构

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

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

### 3. API 概览（对齐当前控制器）

管理端/服务端：`VoucherManagementController`
- `POST   /api/pricing/voucher/batch/create`、`/batch/create/v2` — 创建券码批次
- `POST   /api/pricing/voucher/batch/list` — 批次分页列表
- `GET    /api/pricing/voucher/batch/{id}` — 批次详情
- `GET    /api/pricing/voucher/batch/{id}/stats` — 批次统计
- `PUT    /api/pricing/voucher/batch/{id}/status` — 批次启用/禁用
- `POST   /api/pricing/voucher/codes` — 券码分页列表
- `PUT    /api/pricing/voucher/code/{id}/use` — 标记某券码为已使用
- `GET    /api/pricing/voucher/scenic/{scenicId}/users` — 景区用户券码概览

移动端：`VoucherManagementController`
- `POST   /api/pricing/voucher/mobile/claim` — 领取券码
- `GET    /api/pricing/voucher/mobile/my-codes` — 我的券码列表

使用记录：`VoucherUsageController`
- `POST   /api/pricing/voucher/usage/history` — 使用记录分页
- `GET    /api/pricing/voucher/usage/{voucherCode}/records` — 按券码查询记录
- `GET    /api/pricing/voucher/usage/{voucherCode}/stats` — 券码统计
- `GET    /api/pricing/voucher/usage/user/{faceId}/scenic/{scenicId}` — 用户在景区的记录
- `GET    /api/pricing/voucher/usage/batch/{batchId}/stats` — 批次使用统计

### 4. 关键业务规则

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

#### 使用验证
- 券码必须为可用状态（CLAIMED_AVAILABLE/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. 优惠提供者实现（当前实现与优先级）

#### OnePricePurchaseDiscountProvider (优先级: 120)
- 处理一口价优惠逻辑（景区级统一价格）
- **最高优先级**，优先于所有其他优惠类型
- 商品级别控制：检查购物车中所有商品的 `canUseOnePrice` 配置，任一商品不支持则跳过检测
- 仅当一口价小于当前金额时产生优惠；是否可与券码/优惠券叠加由配置 `canUseCoupon/canUseVoucher` 决定

#### BundleDiscountProvider (优先级: 100)
- 处理打包购买优惠逻辑（多商品组合优惠）
- 支持多种优惠类型：固定减免、百分比折扣、固定价格
- 可配置叠加规则（与优惠券、券码、一口价的组合限制）
- 自动检测购物车中符合条件的商品组合

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

#### CouponDiscountProvider (优先级: 60)
- 处理优惠券优惠逻辑
- **最低优先级**，在所有其他优惠之后应用
- 自动选择最优优惠券

### 3. 优惠应用策略

#### 优先级规则
```
一口价 (120) → 打包购买 (100) → 券码 (80) → 优惠券 (60)
```

#### 叠加逻辑
```java
原价 → 一口价 → 打包购买 → 券码 → 优惠券 → 最终价格

特殊情况：
- 全场免费券码：直接最终价=0，停止后续优惠
- 一口价：可叠加性由配置 canUseCoupon / canUseVoucher 控制；商品级别由 canUseOnePrice 控制参与检测
```

#### 扩展支持

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

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

## 打包购买优惠系统 (Bundle Discount System)

### 1. 核心特性

打包购买优惠系统是新增的优惠类型，支持多商品组合优惠策略，具有第二高优先级（仅次于一口价）。

#### 优惠类型支持
- **FIXED_DISCOUNT**: 固定减免金额（如满2件减50元）
- **PERCENTAGE_DISCOUNT**: 百分比折扣（如多商品组合9折）
- **FIXED_PRICE**: 固定套餐价格（如照片+视频套餐199元）

#### 触发条件
- **商品数量要求**: 最低购买数量限制
- **商品金额要求**: 最低购买金额限制
- **商品类型组合**: 特定商品类型的组合（如照片+视频）

### 2. 业务规则

#### 自动检测规则
```java
// 多商品类型组合优惠
- 条件：购买不同类型商品 >= 2种
- 优惠：9折优惠
- 可叠加：可与优惠券、券码叠加，不可与一口价叠加

// 大批量购买优惠
- 条件：总数量 >= 10件 且 总金额 >= 500元
- 优惠：减免50元
- 可叠加：可与优惠券、券码叠加，不可与一口价叠加

// 特定组合套餐
- 条件：同时购买照片集和Vlog视频
- 优惠：套餐价199元
- 可叠加：不可与其他优惠叠加
```

#### 叠加规则配置
每个打包优惠规则都可以独立配置与其他优惠的叠加关系：
- `canUseWithCoupon`: 是否可与优惠券叠加
- `canUseWithVoucher`: 是否可与券码叠加
- `canUseWithOnePrice`: 是否可与一口价叠加

### 3. 核心接口

#### IBundleDiscountService
```java
// 检测可用的打包优惠
List<BundleDiscountInfo> detectAvailableBundleDiscounts(DiscountDetectionContext context);

// 计算打包优惠金额
BigDecimal calculateBundleDiscount(BundleDiscountInfo bundleDiscount, List<ProductItem> products);

// 获取最优的打包优惠组合
BundleDiscountInfo getBestBundleDiscount(List<ProductItem> products, Long scenicId);
```

#### BundleDiscountProvider
- 实现 `IDiscountProvider` 接口
- 优先级：100（第二高，仅次于一口价的120）
- 自动集成到统一优惠检测系统

### 4. 扩展开发

#### 添加新的打包规则
```java
// 在 BundleDiscountServiceImpl 中添加新规则
private BundleDiscountInfo createNewBundleRule() {
    BundleDiscountInfo bundle = new BundleDiscountInfo();
    bundle.setBundleConfigId(4L);
    bundle.setBundleName("新打包规则");
    bundle.setDiscountType("PERCENTAGE_DISCOUNT");
    bundle.setDiscountValue(new BigDecimal("0.85")); // 8.5折
    bundle.setMinQuantity(5);
    bundle.setMinAmount(new BigDecimal("300"));
    // 配置叠加规则...
    return bundle;
}
```

#### 数据库配置支持
后续可以扩展为从数据库加载打包规则配置，替换当前的硬编码规则。

## 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. 一口价配置管理（OnePrice）

`OnePricePurchaseController`（管理端）：
- `GET    /api/pricing/admin/one-price/` — 分页查询
- `GET    /api/pricing/admin/one-price/all` — 全量查询
- `GET    /api/pricing/admin/one-price/{id}` — 详情
- `POST   /api/pricing/admin/one-price/` — 创建
- `PUT    /api/pricing/admin/one-price/{id}` — 更新
- `DELETE /api/pricing/admin/one-price/{id}` — 删除
- `PUT    /api/pricing/admin/one-price/{id}/status` — 启用/禁用
- `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`: 待购商品列表
- `paidAmount`: 已支付金额（内部代码传入，前端不必传；**必填**，为空直接报错）

#### 检测流程
1. **商品规范化**: 对已购和待购商品进行规范化处理
2. **价格汇总**: 分别计算已购和待购商品的总价格，并合并已支付金额
3. **一口价评估**: 判断合并商品是否满足一口价条件
4. **打包优惠评估**: 检测是否满足打包购买优惠条件
5. **结果汇总**: 生成升单检测结果和建议（补差价 <= 0 视为不可升单）

#### 响应结果 (UpgradeCheckResult)
- `summary`: 价格汇总信息（包含已支付金额）
- `onePriceResult`: 一口价检测结果（含补差价金额）
- `bundleDiscountResult`: 打包优惠检测结果（含补差价金额）
- `bestUpgradeType`: 最优升单类型（ONE_PRICE / BUNDLE_DISCOUNT）
- `bestPayableAmount`: 最低补差价金额

### 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);
}
```

## 测试策略

### 单元测试类型
- **服务层测试**：每个服务类都有对应测试类
  - `PriceBundleServiceTest` - 套餐价格计算测试
  - `ReusableVoucherServiceTest` - 可重复使用券码测试
  - `VoucherTimeRangeTest` - 券码时间范围功能测试
  - `VoucherPrintServiceCodeGenerationTest` - 券码生成测试
- **实体映射测试**：验证数据库映射和JSON序列化
  - `PriceBundleConfigStructureTest` - 实体结构测试
  - `PriceBundleConfigJsonTest` - JSON序列化测试
  - `CouponSwitchFieldsMappingTest` - 字段映射测试
- **类型处理器测试**：验证自定义TypeHandler
  - `BundleProductListTypeHandlerTest` - 套餐商品列表序列化测试
- **配置验证测试**：验证系统配置完整性
  - `DefaultConfigValidationTest` - 验证所有ProductType的default配置
  - `CodeGenerationStandaloneTest` - 独立代码生成测试

### 测试执行命令
```bash
# 运行单个测试类
mvn test -Dtest=VoucherTimeRangeTest
mvn test -Dtest=ReusableVoucherServiceTest
mvn test -Dtest=BundleProductListTypeHandlerTest

# 运行整个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分页集成
- **异常处理**：验证业务异常和全局异常处理器

## 数据库设计

### 核心表结构（摘）
- `price_product_config`: 商品价格基础配置（包含 `can_use_coupon`、`can_use_voucher`、`can_use_one_price` 优惠控制字段）
- `price_tier_config`: 分层定价配置
- `price_bundle_config`: 套餐配置
- `price_coupon_config`: 优惠券配置（**v1.0.0新增**: `user_claim_limit` 用户领取限制, `claimed_quantity` 已领取数量）
- `price_coupon_claim_record`: 优惠券领取记录

### price_coupon_config 表字段说明 (v1.0.0更新)
```sql
-- 核心数量管理字段
total_quantity INT          -- 发行总量(NULL或0表示不限制,>0表示限量)
claimed_quantity INT        -- 已领取数量(v1.0.0新增,领取时+1,仅在total_quantity>0时更新)
used_quantity INT           -- 已使用数量(实际使用时+1)
user_claim_limit INT        -- 每个用户可领取数量限制(v1.0.0新增,NULL或0表示不限制,>0表示限制)

-- 字段关系
-- claimed_quantity >= used_quantity (已领取 >= 已使用)
-- claimed_quantity <= total_quantity (已领取 <= 总量,仅当total_quantity>0时适用)

-- 典型配置组合
-- 1. 无限量发行: total_quantity=NULL/0, user_claim_limit=NULL/0
-- 2. 限量限人: total_quantity=1000, user_claim_limit=1
-- 3. 限量不限人: total_quantity=500, user_claim_limit=NULL/0
-- 4. 不限量但限人: total_quantity=NULL/0, user_claim_limit=3
```

### 新增表结构
- `price_voucher_batch_config`: 券码批次配置表
- `price_voucher_code`: 券码表
- `price_voucher_usage_record`: 券码使用记录表
- `voucher_print_record`: 券码打印记录表
- `price_one_price_config`: 一口价配置表

### 索引优化（示例）
```sql
-- 优惠券领取记录查询优化 (v1.0.0新增)
CREATE INDEX idx_user_coupon ON price_coupon_claim_record(user_id, coupon_id);
CREATE INDEX idx_coupon_status ON price_coupon_claim_record(coupon_id, status);

-- 券码查询优化
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);

-- 使用记录与打印记录查询优化（示例）
CREATE INDEX idx_usage_code ON price_voucher_usage_record(voucher_code);
CREATE INDEX idx_usage_face_scenic ON price_voucher_usage_record(face_id, scenic_id);
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()` 逻辑。

## 版本更新记录

### v1.0.0 (2025-11-16)
**优惠券用户领取数量限制功能**

新增特性:
- ✅ 支持为每个优惠券配置用户领取数量限制 (`userClaimLimit`)
- ✅ 区分已领取数量 (`claimedQuantity`) 和已使用数量 (`usedQuantity`)
- ✅ 新增错误码 `CLAIM_LIMIT_REACHED` 处理超限场景
- ✅ 新增统计方法 `PriceCouponClaimRecordMapper.countUserCouponClaims()`

数据库变更:
- 表 `price_coupon_config` 新增字段 `user_claim_limit INT`
- 表 `price_coupon_config` 新增字段 `claimed_quantity INT`
- 建议索引 `idx_user_coupon ON price_coupon_claim_record(user_id, coupon_id)`

受影响文件:
- `entity/PriceCouponConfig.java`
- `mapper/PriceCouponClaimRecordMapper.java`
- `service/impl/CouponServiceImpl.java`
- `dto/CouponClaimResult.java`
- `dto/CouponInfo.java`

迁移指南: 详见 `docs/coupon-user-claim-limit-guide.md`

