# 套餐与佣金业务模型

本文档定义了套餐、套餐系列、佣金的完整业务模型，作为系统改造的规范参考。

---

## 一、核心概念

### 1.1 两种佣金类型

系统只有两种佣金类型：

| 佣金类型 | 触发时机 | 触发次数 | 计算方式 |
|---------|---------|---------|---------|
| **差价佣金** | 每笔订单 | 每单都触发 | 下级成本价 - 自己成本价 |
| **一次性佣金** | 首充/累计充值达标 | 每张卡/设备只触发一次 | 上级给的 - 给下级的 |

### 1.2 实体关系

```
┌─────────────────┐
│   套餐系列       │
│  PackageSeries  │
├─────────────────┤
│ • 系列名称      │
│ • 一次性佣金规则 │ ← 可选配置
└────────┬────────┘
         │ 1:N
         ▼
┌─────────────────┐          ┌─────────────────┐
│     套餐        │          │    卡/设备      │
│    Package      │          │   IoT/Device    │
├─────────────────┤          ├─────────────────┤
│ • 成本价        │          │ • 绑定系列ID    │
│ • 建议售价      │          │ • 累计充值金额  │ ← 按系列累计
│ • 真流量(必填)  │          │ • 是否已首充    │ ← 按系列记录
│ • 虚流量(可选)  │          └────────┬────────┘
│ • 虚流量开关    │                   │
└────────┬────────┘                   │ 分配
         │                            ▼
         │ 分配              ┌─────────────────┐
         ▼                   │      店铺       │
┌─────────────────┐          │      Shop       │
│   套餐分配      │◀─────────┤ • 代理层级      │
│ PkgAllocation   │          │ • 上级店铺ID    │
├─────────────────┤          └─────────────────┘
│ • 店铺ID        │
│ • 套餐ID        │
│ • 成本价(加价后)│
│ • 一次性佣金额  │ ← 给该代理的金额
└─────────────────┘
```

---

## 二、套餐模型

### 2.1 字段定义

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `cost_price` | int64 | 是 | 成本价（平台设置的基础成本价，分） |
| `suggested_price` | int64 | 是 | 建议售价（给代理参考，分） |
| `real_data_mb` | int64 | 是 | 真实流量额度（MB） |
| `enable_virtual_data` | bool | 否 | 是否启用虚流量 |
| `virtual_data_mb` | int64 | 否 | 虚流量额度（启用时必填，≤ 真实流量，MB） |

### 2.2 流量停机判断

```
停机目标值 = enable_virtual_data ? virtual_data_mb : real_data_mb
```

### 2.3 不同用户视角

| 用户类型 | 看到的成本价 | 看到的一次性佣金 |
|---------|-------------|-----------------|
| 平台 | 基础成本价 | 完整规则 |
| 代理A | A的成本价（已加价） | A能拿到的金额 |
| 代理A1 | A1的成本价（再加价） | A1能拿到的金额 |

---

## 三、差价佣金

### 3.1 计算规则

```
平台设置基础成本价: 100
    │
    │ 分配给代理A，设置成本价: 120
    ▼
代理A成本价: 120
    │
    │ 分配给代理A1，设置成本价: 130
    ▼
代理A1成本价: 130
    │
    │ A1销售给客户，售价: 200
    ▼

结果：
  • A1 收入 = 200 - 130 = 70元（销售利润，不是佣金）
  • A 佣金 = 130 - 120 = 10元（差价佣金）
  • 平台收入 = 120元
```

### 3.2 关键区分

- **收入/利润**：末端代理的 `售价 - 自己成本价`
- **差价佣金**：上级代理的 `下级成本价 - 自己成本价`
- **平台收入**：一级代理的成本价

---

## 四、一次性佣金

### 4.1 触发条件

| 条件类型 | 说明 | 强充要求 |
|---------|------|---------|
| `first_recharge` | 首充：该卡/设备在该系列下的第一次充值 | 必须强充 |
| `accumulated_recharge` | 累计充值：累计充值金额达到阈值 | 可选强充 |

### 4.2 规则配置（套餐系列层面）

| 配置项 | 类型 | 说明 |
|--------|------|------|
| `enable` | bool | 是否启用一次性佣金 |
| `trigger_type` | string | 触发类型：`first_recharge` / `accumulated_recharge` |
| `threshold` | int64 | 触发阈值（分）：首充要求金额 或 累计要求金额 |
| `commission_type` | string | 返佣类型：`fixed`(固定) / `tiered`(梯度) |
| `commission_amount` | int64 | 固定返佣金额（fixed类型时） |
| `tiers` | array | 梯度配置（tiered类型时） |
| `validity_type` | string | 时效类型：`permanent` / `fixed_date` / `relative` |
| `validity_value` | string | 时效值（到期日期 或 月数） |
| `enable_force_recharge` | bool | 是否启用强充 |
| `force_calc_type` | string | 强充金额计算：`fixed`(固定) / `dynamic`(动态差额) |
| `force_amount` | int64 | 强充金额（fixed类型时） |

### 4.3 链式分配

一次性佣金在整条代理链上按约定分配：

```
系列规则：首充100返20

分配配置：
  平台给A：20元
  A给A1：8元
  A1给A2：5元

触发首充时：
  A2 获得：5元
  A1 获得：8 - 5 = 3元
  A  获得：20 - 8 = 12元
  ─────────────────────
  合计：20元 ✓
```

### 4.4 首充流程

```
客户购买套餐
    │
    ▼
预检：系列是否启用一次性佣金且为首充？
    │
   否 ───────────────────▶ 正常购买流程
    │
   是
    │
    ▼
该卡/设备在该系列下是否已首充过？
    │
   是 ───────────────────▶ 正常购买流程（不再返佣）
    │
   否
    │
    ▼
计算强充金额 = max(首充要求, 套餐售价)
    │
    ▼
返回提示："需要充值 xxx 元"
    │
    ▼
用户确认 → 创建充值订单(金额=强充金额)
    │
    ▼
用户支付
    │
    ▼
支付成功：
  1. 钱进入钱包
  2. 标记该卡/设备已首充
  3. 自动创建套餐购买订单并完成
  4. 扣款（套餐售价）
  5. 触发一次性佣金，链式分配
```

### 4.5 累计充值流程

```
客户充值（直接充值到钱包）
    │
    ▼
累计充值金额 += 本次充值金额
    │
    ▼
该卡/设备是否已触发过累计充值返佣？
    │
   是 ───────────────────▶ 结束（不再返佣）
    │
   否
    │
    ▼
累计金额 >= 累计要求？
    │
   否 ───────────────────▶ 结束（继续累计）
    │
   是
    │
    ▼
触发一次性佣金，链式分配
标记该卡/设备已触发累计充值返佣
```

**累计规则**：

| 操作类型 | 是否累计 |
|---------|---------|
| 直接充值到钱包 | ✅ 累计 |
| 直接购买套餐（不经过钱包） | ❌ 不累计 |
| 强充购买套餐（先充值再扣款） | ✅ 累计（充值部分） |

---

## 五、梯度佣金

梯度佣金是一次性佣金的进阶版，根据代理销量/销售额动态调整返佣金额。

### 5.1 配置项

| 配置项 | 类型 | 说明 |
|--------|------|------|
| `tier_dimension` | string | 梯度维度：`sales_count`(销量) / `sales_amount`(销售额) |
| `stat_scope` | string | 统计范围：`self`(仅自己) / `self_and_sub`(自己+下级) |
| `tiers` | array | 梯度档位列表 |
| `tiers[].threshold` | int64 | 阈值（销量或销售额） |
| `tiers[].amount` | int64 | 返佣金额（分） |

### 5.2 示例

```
梯度规则（销量维度）：
┌────────────────┬────────────────────────┐
│    销量区间     │    首充100返佣金额     │
├────────────────┼────────────────────────┤
│    >= 0        │         5元            │
├────────────────┼────────────────────────┤
│    >= 100      │        10元            │
├────────────────┼────────────────────────┤
│    >= 200      │        20元            │
└────────────────┴────────────────────────┘

代理A当前销量150单 → 落在 [100, 200) 区间 → 首充返10元
```

### 5.3 梯度升级

```
初始状态：
  代理A 销量150（适用10元档），给A1设置5元
  
  触发时：A1得5元，A得10-5=5元

升级后（A销量达到210）：
  A 适用20元档，A1配置仍为5元
  
  触发时：A1得5元（不变），A得20-5=15元（增量归上级）
```

### 5.4 统计周期

- 统计周期与一次性佣金时效一致
- 只统计该套餐系列下的销量/销售额

---

## 六、约束规则

### 6.1 套餐分配

1. 下级成本价 >= 自己成本价（不能亏本卖）
2. 只能分配自己有权限的套餐给下级
3. 只能分配给直属下级（不能跨级）

### 6.2 一次性佣金分配

4. 给下级的金额 <= 自己能拿到的金额
5. 给下级的金额 >= 0（可以设为0，独吞全部）

### 6.3 流量

6. 虚流量 <= 真实流量

### 6.4 配置修改

7. 修改配置只影响之后的新订单
8. 代理只能修改"给下级多少钱"，不能修改触发规则
9. 平台修改系列规则不影响已分配的代理，需收回重新分配

### 6.5 触发限制

10. 一次性佣金每张卡/设备只触发一次
11. "首充"指该卡/设备在该系列下的第一次充值
12. 累计充值只统计"充值"操作，不统计"直接购买"

---

## 七、操作流程

### 7.1 理想的线性流程

```
1. 创建套餐系列
   └─▶ 可选：配置一次性佣金规则

2. 创建套餐
   └─▶ 归属到系列
   └─▶ 设置成本价、建议售价
   └─▶ 设置真流量（必填）、虚流量（可选）

3. 分配套餐给代理
   └─▶ 设置代理成本价（加价）
   └─▶ 如果系列启用一次性佣金：设置给代理的一次性佣金额度

4. 分配资产（卡/设备）给代理
   └─▶ 资产绑定的套餐系列自动跟着走

5. 代理销售
   └─▶ 客户购买套餐
   └─▶ 差价佣金自动计算并入账给上级
   └─▶ 满足一次性佣金条件时，按链式分配入账
```

---

## 八、与现有代码的差异

详见改造提案：[refactor-commission-package-model](../openspec/changes/refactor-commission-package-model/)
