# IoT Agent Commission Management

## Purpose

Manage commission rules and records for IoT agents, supporting three commission types (one-time, long-term, combined), ladder commissions, commission freeze/unfreeze logic, approval workflows, and multi-level agent commission distribution.

This capability supports:
- Agent hierarchy (tree structure) management
- Three commission types: one-time, long-term, combined
- Commission rule configuration (series-based for one-time, package-based for long-term)
- Combined commission with OR-condition unfreezing (time point OR package cycle)
- Ladder commission based on activation/pickup/deposit thresholds
- Commission record lifecycle (frozen → unfreezing → released → invalid)
- Commission unfreeze conditions (activation + real-name + recharge for normal cards; no real-name required for industry cards)
- Commission approval workflow (auto or manual)
- Multi-level agent commission distribution

## Requirements

### Requirement: 代理树形关系

系统 SHALL 管理代理的树形层级关系,每个代理只有一个上级代理。

**agent_hierarchies 表**:
- `id`: 代理关系 ID(主键,BIGINT)
- `agent_id`: 代理用户 ID(BIGINT,唯一)
- `parent_agent_id`: 上级代理用户 ID(BIGINT,可空,NULL 表示顶级代理)
- `level`: 代理层级(INT,1-顶级代理 2-二级代理 ...)
- `path`: 代理路径(VARCHAR(500),如 "1/5/12",用于快速获取整个代理链)
- `created_at`: 创建时间(TIMESTAMP,自动填充)
- `updated_at`: 更新时间(TIMESTAMP,自动填充)

#### Scenario: 创建顶级代理

- **WHEN** 平台创建顶级代理(用户 ID 为 101)
- **THEN** 系统创建代理关系记录,`agent_id` 为 101,`parent_agent_id` 为 NULL,`level` 为 1,`path` 为 "101"

#### Scenario: 创建下级代理

- **WHEN** 顶级代理(ID 为 101)创建下级代理(用户 ID 为 102)
- **THEN** 系统创建代理关系记录,`agent_id` 为 102,`parent_agent_id` 为 101,`level` 为 2,`path` 为 "101/102"

#### Scenario: 查询代理的整个上级链

- **WHEN** 查询代理(ID 为 103,路径为 "101/102/103")的上级链
- **THEN** 系统解析 `path` 字段,返回代理 101(顶级)、102(父级)、103(当前代理)

---

### Requirement: 分佣规则配置

系统 SHALL 支持为代理配置分佣规则,包括一次性分佣、长期分佣和组合分佣。

**commission_rules 表**:
- `id`: 分佣规则 ID(主键,BIGINT)
- `agent_id`: 代理用户 ID(BIGINT)
- `business_type`: 业务类型(VARCHAR(20),"iot_card"-IoT卡 | "number_card"-号卡)
- `commission_type`: 分佣类型(VARCHAR(20),"one_time"-一次性 | "long_term"-长期 | "combined"-组合)
- `series_id`: 套餐系列 ID(BIGINT,可空,**仅一次性分佣使用**,关联 package_series 表)
- `package_id`: 套餐 ID(BIGINT,可空,**仅长期分佣使用**,关联 packages 表)
- `commission_mode`: 分佣模式(VARCHAR(20),"fixed"-固定金额 | "percent"-百分比)
- `commission_value`: 分佣值(DECIMAL(10,4),固定金额或百分比值)
- `freeze_days`: 冻结天数(INT,分佣冻结天数,默认 7)
- `is_ladder`: 是否阶梯分佣(BOOLEAN,默认 false)
- `status`: 规则状态(INT,1-有效 2-无效)
- `created_at`: 创建时间(TIMESTAMP,自动填充)
- `updated_at`: 更新时间(TIMESTAMP,自动填充)

**字段使用规则**:
- **一次性分佣**: 使用 `series_id` 关联套餐系列,`package_id` 为 NULL
- **长期分佣**: 使用 `package_id` 关联具体套餐,`series_id` 为 NULL
- **组合分佣**: 需要创建两条规则记录,一条一次性(使用 `series_id`),一条长期(使用 `package_id`)
- **`series_id` 和 `package_id` 互斥**: 不能同时有值

#### Scenario: 配置一次性分佣规则

- **WHEN** 平台为代理(ID 为 123)配置一次性分佣规则,套餐系列 ID 为 1(月套餐系列),固定金额 5.00 元
- **THEN** 系统创建分佣规则,`agent_id` 为 123,`commission_type` 为 "one_time",`series_id` 为 1,`package_id` 为 NULL,`commission_mode` 为 "fixed",`commission_value` 为 5.00

#### Scenario: 配置长期分佣规则

- **WHEN** 平台为代理(ID 为 123)配置长期分佣规则,套餐 ID 为 3001,百分比 5%
- **THEN** 系统创建分佣规则,`agent_id` 为 123,`commission_type` 为 "long_term",`series_id` 为 NULL,`package_id` 为 3001,`commission_mode` 为 "percent",`commission_value` 为 0.05

#### Scenario: 配置组合分佣规则

- **WHEN** 平台为代理(ID 为 123)配置组合分佣规则,套餐系列 ID 为 1,先一次性分佣 10.00 元,连续在网 3 个月后开始长期分佣(套餐 ID 为 3001)3.00 元/月
- **THEN** 系统创建两条分佣规则:
  - 一条 `commission_type` 为 "one_time",`series_id` 为 1,`package_id` 为 NULL
  - 另一条 `commission_type` 为 "long_term",`series_id` 为 NULL,`package_id` 为 3001,且关联组合条件

#### Scenario: 字段互斥校验

- **WHEN** 平台尝试创建分佣规则,同时设置 `series_id` 为 1 和 `package_id` 为 3001
- **THEN** 系统拒绝创建,返回错误信息"`series_id` 和 `package_id` 不能同时有值"

---

### Requirement: 组合分佣条件配置

系统 SHALL 支持为组合分佣配置解冻条件,包括时间点条件和套餐周期条件。

**commission_combined_conditions 表**:
- `id`: 组合条件 ID(主键,BIGINT)
- `commission_rule_id`: 关联的分佣规则 ID(BIGINT,必须是 commission_type 为 "long_term" 且属于组合分佣的规则)
- `condition_type`: 条件类型(VARCHAR(20),"time_point"-时间点 | "package_cycle"-套餐周期)
- `time_months`: 时间月数(INT,可空,仅当 condition_type 为 "time_point" 时有值,表示实名后多少个月)
- `package_cycle_threshold`: 套餐周期阈值(INT,可空,仅当 condition_type 为 "package_cycle" 时有值,表示使用多少个套餐周期)
- `created_at`: 创建时间(TIMESTAMP,自动填充)
- `updated_at`: 更新时间(TIMESTAMP,自动填充)

**解冻逻辑**: 组合分佣的长期部分,当满足**任一条件**(OR 关系)时开始产生长期分佣。

#### Scenario: 配置时间点条件

- **WHEN** 平台为组合分佣规则(ID 为 501)配置时间点条件,实名后 3 个月开始长期分佣
- **THEN** 系统创建组合条件记录,`commission_rule_id` 为 501,`condition_type` 为 "time_point",`time_months` 为 3

#### Scenario: 配置套餐周期条件

- **WHEN** 平台为组合分佣规则(ID 为 501)配置套餐周期条件,使用 10 个套餐周期后开始长期分佣
- **THEN** 系统创建组合条件记录,`commission_rule_id` 为 501,`condition_type` 为 "package_cycle",`package_cycle_threshold` 为 10

#### Scenario: 同时配置两种条件(OR 关系)

- **WHEN** 平台为组合分佣规则(ID 为 501)同时配置时间点条件(6 个月)和套餐周期条件(10 个周期)
- **THEN** 系统创建两条组合条件记录,长期分佣在任一条件满足时开始

---

### Requirement: 阶梯分佣配置

系统 SHALL 支持阶梯分佣,根据激活量/提货量达到阶梯条件后变更分佣值。

**commission_ladder 表**:
- `id`: 阶梯配置 ID(主键,BIGINT)
- `commission_rule_id`: 关联的分佣规则 ID(BIGINT)
- `ladder_type`: 阶梯类型(VARCHAR(20),"activation"-激活量 | "pickup"-提货量 | "deposit"-保证金)
- `ladder_threshold`: 阶梯阈值(INT,如激活 100 张)
- `commission_mode`: 分佣模式(VARCHAR(20),"fixed"-固定金额 | "percent"-百分比)
- `commission_value`: 分佣值(DECIMAL(10,4),达到阶梯后的分佣值)
- `created_at`: 创建时间(TIMESTAMP,自动填充)
- `updated_at`: 更新时间(TIMESTAMP,自动填充)

#### Scenario: 配置激活量阶梯

- **WHEN** 平台为代理(ID 为 123)配置阶梯分佣,激活 100 张卡后分佣从 5.00 元提升到 8.00 元
- **THEN** 系统创建阶梯配置,`ladder_type` 为 "activation",`ladder_threshold` 为 100,`commission_value` 为 8.00

#### Scenario: 计算阶梯分佣

- **WHEN** 代理(ID 为 123)当月激活量达到 100 张
- **THEN** 系统根据阶梯配置,从第 101 张卡开始使用新的分佣值 8.00 元

---

### Requirement: 分佣记录管理

系统 SHALL 记录每笔分佣,支持冻结、解冻和发放流程。

**commission_records 表**:
- `id`: 分佣记录 ID(主键,BIGINT)
- `agent_id`: 代理用户 ID(BIGINT)
- `order_id`: 订单 ID(BIGINT)
- `commission_rule_id`: 分佣规则 ID(BIGINT)
- `commission_type`: 分佣类型(VARCHAR(20),"one_time" | "long_term" | "combined")
- `amount`: 分佣金额(DECIMAL(10,2),元)
- `status`: 分佣状态(INT,1-冻结 2-解冻中 3-已发放 4-已失效)
- `freeze_until`: 冻结截止时间(TIMESTAMP,可空)
- `released_at`: 发放时间(TIMESTAMP,可空)
- `created_at`: 创建时间(TIMESTAMP,自动填充)
- `updated_at`: 更新时间(TIMESTAMP,自动填充)

#### Scenario: 创建一次性分佣记录

- **WHEN** 订单(ID 为 10001)完成,触发代理(ID 为 123)的一次性分佣 5.00 元,冻结 7 天
- **THEN** 系统创建分佣记录,`agent_id` 为 123,`order_id` 为 10001,`amount` 为 5.00,状态为 1(冻结),`freeze_until` 为 7 天后

#### Scenario: 分佣自动解冻

- **WHEN** 分佣记录(ID 为 1001)的冻结截止时间到达,且满足解冻条件(激活+实名+充值)
- **THEN** 系统将分佣状态从 1(冻结) 变更为 2(解冻中),创建分佣解冻审批记录

#### Scenario: 分佣发放

- **WHEN** 分佣解冻审批通过
- **THEN** 系统将分佣状态从 2(解冻中) 变更为 3(已发放),将分佣金额转入代理钱包,`released_at` 记录发放时间

---

### Requirement: 分佣解冻条件

系统 SHALL 根据分佣类型校验不同的解冻条件。

**一次性分佣解冻条件**:
- 激活(实名状态为已实名;对于行业卡,实名状态可以为未实名)
- 达到累计/首次充值金额
- 冻结天数到达

**长期分佣解冻条件**:
- 激活(实名状态为已实名;对于行业卡,实名状态可以为未实名)
- 达到累计/首次充值金额
- 在网状态正常
- 三无校验通过(通过 Excel 导入解冻)

**组合分佣解冻条件**:
- **一次性部分**: 立即产生并按一次性分佣条件解冻
- **长期部分**: 当满足以下**任一条件**时开始长期分佣(OR 关系):
  - 达到某个时间点之后(例如:实名后 3 个月)
  - **OR** 该 IoT 卡的套餐使用周期数达到阈值(例如:10 个周期)
- **注意**: 套餐周期阈值是针对单张 IoT 卡的,不是设备级别

#### Scenario: 一次性分佣满足解冻条件

- **WHEN** 分佣记录(ID 为 1001)的冻结截止时间到达,用户已实名且已充值
- **THEN** 系统将分佣状态变更为 2(解冻中),创建审批记录

#### Scenario: 长期分佣等待 Excel 导入解冻

- **WHEN** 长期分佣记录等待三无校验
- **THEN** 系统保持分佣状态为 1(冻结),等待平台通过 Excel 导入解冻数据

#### Scenario: 组合分佣时间点条件满足

- **WHEN** 组合分佣规则配置为实名后 3 个月开始长期分佣,IoT 卡已实名 3 个月
- **THEN** 系统开始为该 IoT 卡创建长期分佣记录,即使套餐周期数未达到阈值

#### Scenario: 组合分佣套餐周期条件满足

- **WHEN** 组合分佣规则配置为套餐使用 10 个周期后开始长期分佣,IoT 卡已使用套餐 10 个周期
- **THEN** 系统开始为该 IoT 卡创建长期分佣记录,即使未达到时间点要求

#### Scenario: 组合分佣任一条件满足即开始

- **WHEN** 组合分佣规则配置为"实名后 6 个月 OR 10 个套餐周期",IoT 卡已使用 10 个周期但只实名 2 个月
- **THEN** 系统开始为该 IoT 卡创建长期分佣记录(因为套餐周期条件已满足)

#### Scenario: 行业卡一次性分佣解冻(无需实名)

- **WHEN** 行业卡(card_category 为 "industry")的一次性分佣记录冻结期到达,卡已激活且已充值,但实名状态为未实名
- **THEN** 系统判定解冻条件满足(行业卡无需实名认证),将分佣状态变更为 2(解冻中),创建审批记录

#### Scenario: 行业卡长期分佣解冻(无需实名)

- **WHEN** 行业卡(card_category 为 "industry")的长期分佣记录满足充值金额和在网状态,但实名状态为未实名
- **THEN** 系统判定行业卡无需实名认证,等待三无校验通过后可解冻

---

### Requirement: 分佣解冻审批

系统 SHALL 支持分佣解冻审批流程,审批通过后发放分佣。

**commission_approvals 表**:
- `id`: 审批记录 ID(主键,BIGINT)
- `commission_record_id`: 分佣记录 ID(BIGINT)
- `approval_type`: 审批类型(VARCHAR(20),"auto"-自动 | "manual"-人工)
- `status`: 审批状态(INT,1-待审批 2-已通过 3-已拒绝)
- `approver_id`: 审批人用户 ID(BIGINT,可空)
- `approval_time`: 审批时间(TIMESTAMP,可空)
- `approval_note`: 审批备注(TEXT,可空)
- `created_at`: 创建时间(TIMESTAMP,自动填充)
- `updated_at`: 更新时间(TIMESTAMP,自动填充)

#### Scenario: 创建审批记录

- **WHEN** 分佣记录(ID 为 1001)状态变更为 2(解冻中)
- **THEN** 系统创建审批记录,`commission_record_id` 为 1001,`approval_type` 为 "auto",状态为 1(待审批)

#### Scenario: 审批通过

- **WHEN** 审批人(用户 ID 为 999)审批通过审批记录(ID 为 2001)
- **THEN** 系统将审批状态变更为 2(已通过),分佣记录状态变更为 3(已发放),将分佣金额转入代理钱包

#### Scenario: 审批拒绝

- **WHEN** 审批人拒绝审批记录(ID 为 2001),备注"用户未满足在网条件"
- **THEN** 系统将审批状态变更为 3(已拒绝),分佣记录状态变更为 4(已失效)

---

### Requirement: 分佣模板

系统 SHALL 支持创建分佣模板,存储常用的分佣方案,便于快速配置。

**commission_templates 表**:
- `id`: 模板 ID(主键,BIGINT)
- `template_name`: 模板名称(VARCHAR(255))
- `business_type`: 业务类型(VARCHAR(20),"iot_card"-IoT卡 | "number_card"-号卡)
- `commission_type`: 分佣类型(VARCHAR(20),"one_time" | "long_term" | "combined")
- `commission_mode`: 分佣模式(VARCHAR(20),"fixed" | "percent")
- `commission_value`: 分佣值(DECIMAL(10,4))
- `freeze_days`: 冻结天数(INT)
- `is_ladder`: 是否阶梯分佣(BOOLEAN)
- `created_at`: 创建时间(TIMESTAMP,自动填充)
- `updated_at`: 更新时间(TIMESTAMP,自动填充)

#### Scenario: 创建分佣模板

- **WHEN** 平台创建分佣模板"标准月套餐分佣",业务类型为 IoT 卡,一次性分佣 5.00 元,冻结 7 天
- **THEN** 系统创建模板记录,`template_name` 为 "标准月套餐分佣",`business_type` 为 "iot_card",`commission_type` 为 "one_time",`commission_value` 为 5.00,`freeze_days` 为 7

#### Scenario: 应用分佣模板

- **WHEN** 平台为代理(ID 为 123)应用模板(ID 为 501)
- **THEN** 系统根据模板配置创建分佣规则,`agent_id` 为 123,其他字段从模板复制

---

### Requirement: 多级代理分佣

系统 SHALL 支持多级代理分佣,根据代理路径计算每一级代理的分佣。

**多级分佣规则**:
- 通过代理路径(`path`)获取整个代理链
- 为每一级代理查找对应的分佣规则
- 创建多条分佣记录,每条对应一个代理

#### Scenario: 三级代理分佣

- **WHEN** 订单(ID 为 10001)的代理路径为 "101/102/103",每级代理配置分佣:101(2.00 元)、102(3.00 元)、103(5.00 元)
- **THEN** 系统创建 3 条分佣记录:代理 101 的 2.00 元、代理 102 的 3.00 元、代理 103 的 5.00 元

---

### Requirement: 分佣数据校验

系统 SHALL 对分佣数据进行校验,确保数据完整性和一致性。

**校验规则**:
- 代理 ID(agent_id):必填,≥ 1
- 订单 ID(order_id):必填,≥ 1
- 分佣金额(amount):必填,≥ 0,最多 2 位小数
- 分佣状态(status):必填,枚举值 1-4
- 冻结天数(freeze_days):必填,≥ 0

#### Scenario: 创建分佣记录时金额为负数

- **WHEN** 创建分佣记录,金额为 -5.00
- **THEN** 系统拒绝创建,返回错误信息"分佣金额必须 ≥ 0"

#### Scenario: 创建分佣规则时分佣值无效

- **WHEN** 创建分佣规则,分佣模式为百分比,分佣值为 1.5(超过 100%)
- **THEN** 系统拒绝创建,返回错误信息"百分比分佣值必须在 0-1 之间"
