# Delta Spec: commission-calculation

## MODIFIED Requirements

### Requirement: 一次性佣金触发检查

系统 SHALL 在更新累计充值金额后立即检查是否触发一次性佣金。**一次性佣金金额从系列分配获取，而非套餐分配**。

**关键变更**：
- 原来从 `ShopPackageAllocation.one_time_commission_amount` 获取佣金金额
- 现在从 `ShopSeriesAllocation.one_time_commission_amount` 获取佣金金额
- 梯度模式下采用链式分配：实际金额 = min(梯度匹配金额, 系列分配的上限)

#### Scenario: 累计达到阈值触发佣金（固定模式）
- **WHEN** 更新累计充值后，累计值 >= 配置阈值
- **AND** 卡/设备的 first_commission_paid = false
- **AND** 系列配置为固定模式（type = "fixed"）
- **THEN** 系统从该卡/设备销售代理的系列分配获取 one_time_commission_amount
- **AND** 发放该金额作为一次性佣金
- **AND** 标记 first_commission_paid = true

#### Scenario: 累计达到阈值触发佣金（梯度模式）
- **WHEN** 更新累计充值后，累计值 >= 配置阈值
- **AND** 卡/设备的 first_commission_paid = false
- **AND** 系列配置为梯度模式（type = "tiered"）
- **AND** 代理销售数量为 50 张，梯度配置：1-30张80元，31-100张100元
- **AND** 代理的系列分配 one_time_commission_amount = 9000（90元上限）
- **THEN** 梯度匹配金额 = 100 元（50张落在31-100档）
- **AND** 实际发放金额 = min(100, 90) = 90 元
- **AND** 标记 first_commission_paid = true

#### Scenario: 累计未达到阈值不触发
- **WHEN** 更新累计充值后，累计值 < 配置阈值
- **THEN** 系统不发放一次性佣金
- **AND** first_commission_paid 保持不变

#### Scenario: 已发放过不重复触发
- **WHEN** 更新累计充值后，累计值 >= 配置阈值
- **AND** 卡/设备的 first_commission_paid = true
- **THEN** 系统不重复发放一次性佣金

---

### Requirement: 一次性佣金链式分配

系统 SHALL 为代理链上的每一级代理计算一次性佣金差价收入。每级代理的收入 = 自己的分配上限 - 下级的分配上限。

#### Scenario: 单级代理
- **WHEN** 一级代理销售卡，系列分配 one_time_commission_amount = 10000（100元）
- **AND** 无下级（终端销售）
- **THEN** 一级代理获得 100 元一次性佣金

#### Scenario: 多级代理链式分配
- **WHEN** 三级代理销售卡，各级系列分配：
  - 平台给一级：one_time_commission_amount = 10000（100元）
  - 一级给二级：one_time_commission_amount = 8000（80元）
  - 二级给三级：one_time_commission_amount = 5000（50元）
- **THEN** 三级获得 50 元
- **AND** 二级获得 30 元（80 - 50）
- **AND** 一级获得 20 元（100 - 80）

#### Scenario: 梯度模式下的链式分配
- **WHEN** 系列配置为梯度模式，梯度匹配金额为 120 元
- **AND** 三级代理的系列分配上限为 50 元，二级为 80 元，一级为 100 元
- **THEN** 三级获得 min(120, 50) = 50 元
- **AND** 二级获得 min(120, 80) - 50 = 30 元
- **AND** 一级获得 min(120, 100) - 80 = 20 元
- **AND** 平台获得 120 - 100 = 20 元

#### Scenario: 某级差价为零
- **WHEN** 某级代理分配的上限等于下级
- **THEN** 该级代理一次性佣金差价为 0，不创建佣金记录

---

### Requirement: 钱包充值触发一次性佣金

钱包充值成功后 SHALL 更新累计充值，并检查是否触发一次性佣金。**佣金金额从系列分配获取**。

#### Scenario: 充值成功更新累计充值
- **WHEN** 卡钱包充值 100 元成功，当前累计充值 200 元
- **THEN** 系统更新卡的 accumulated_recharge 为 300 元

#### Scenario: 充值达到首次充值阈值
- **WHEN** 卡配置为首次充值触发，阈值 100 元，充值 100 元成功，未发放过佣金
- **THEN** 系统从该卡销售代理的系列分配获取 one_time_commission_amount
- **AND** 触发一次性佣金计算，发放佣金，标记 first_commission_paid = true

#### Scenario: 充值达到累计充值阈值
- **WHEN** 卡配置为累计充值触发，阈值 1000 元，充值后累计达到 1000 元，未发放过佣金
- **THEN** 系统从该卡销售代理的系列分配获取 one_time_commission_amount
- **AND** 触发一次性佣金计算，发放佣金，标记 first_commission_paid = true

#### Scenario: 充值未达阈值不触发
- **WHEN** 充值后累计充值未达到阈值
- **THEN** 系统不触发一次性佣金计算

#### Scenario: 已发放过不重复触发
- **WHEN** 卡的一次性佣金已发放过（first_commission_paid = true）
- **THEN** 系统不触发一次性佣金计算

---

## ADDED Requirements

### Requirement: 系列分配查询优化

系统 SHALL 提供通过店铺和系列直接查询系列分配的方法，替代原有的"取第一个"逻辑。

#### Scenario: 直接查询系列分配
- **WHEN** 需要获取代理的一次性佣金配置
- **THEN** 系统通过 shop_id + series_id 直接查询 ShopSeriesAllocation
- **AND** 返回唯一匹配的记录（唯一索引保证）

#### Scenario: 系列分配不存在
- **WHEN** 查询的 shop_id + series_id 组合不存在分配记录
- **THEN** 系统返回 "未找到系列分配配置" 错误
- **AND** 不发放一次性佣金（但不影响成本价差收入计算）

---

### Requirement: enable_one_time_commission 字段查询

系统 SHALL 从 PackageSeries 的顶层字段读取一次性佣金开关状态，支持 SQL 索引查询。

#### Scenario: 检查系列是否启用一次性佣金
- **WHEN** 需要判断是否触发一次性佣金
- **THEN** 系统查询 PackageSeries.enable_one_time_commission 字段
- **AND** 不再解析 one_time_commission_config JSON

#### Scenario: 批量查询启用一次性佣金的系列
- **WHEN** 需要统计启用一次性佣金的系列数量
- **THEN** 系统可使用 `WHERE enable_one_time_commission = true` 直接查询
- **AND** 无需 JSON 解析

---

## REMOVED Requirements

### Requirement: 从套餐分配获取一次性佣金金额

**Reason**: 一次性佣金是系列级概念，应从系列分配获取，而非套餐分配

**Migration**:
- 原查询路径：`ShopPackageAllocation.one_time_commission_amount`
- 新查询路径：`ShopSeriesAllocation.one_time_commission_amount`
- 删除 `ShopPackageAllocationStore.GetByShopAndSeries` 方法
