实现 IoT SIM 管理模块数据模型和数据库结构

- 添加 IoT 核心业务表:运营商、IoT 卡、设备、号卡、套餐、订单等
- 添加分佣系统表:分佣规则、分佣记录、运营商结算等
- 添加轮询和流量管理表:轮询配置、流量使用记录等
- 添加财务和系统管理表:佣金提现、换卡申请等
- 实现完整的 GORM 模型和常量定义
- 添加数据库迁移脚本和详细文档
- 集成 OpenSpec 工作流工具(opsx 命令和 skills)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-12 15:44:23 +08:00
parent 743db126f7
commit 034f00e2e7
48 changed files with 11675 additions and 1 deletions

View File

@@ -0,0 +1,940 @@
# IoT SIM 管理系统 - 分佣系统说明
## 概述
IoT SIM 管理系统实现了一套灵活的多级代理分佣体系,支持三种分佣模式(一次性分佣、长期分佣、组合分佣),支持阶梯奖励机制,支持自动解冻和手动审批,支持 OR 条件解冻逻辑。
---
## 分佣架构
### 多级代理树形结构
```
┌─────────────────────────────────────────────────────────┐
│ 平台(Platform) │
│ Level 0 │
└─────────────────────────────────────────────────────────┘
┌─────────────────┼─────────────────┐
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 一级代理 A │ │ 一级代理 B │ │ 一级代理 C │
│ Level 1 │ │ Level 1 │ │ Level 1 │
│ Path: /A/ │ │ Path: /B/ │ │ Path: /C/ │
└──────────────┘ └──────────────┘ └──────────────┘
│ │
├────┬────┐ ├────┬────┐
▼ ▼ ▼ ▼ ▼ ▼
┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐
│A-1 │ │A-2 │ │A-3 │ │B-1 │ │B-2 │ │B-3 │
│L2 │ │L2 │ │L2 │ │L2 │ │L2 │ │L2 │
└────┘ └────┘ └────┘ └────┘ └────┘ └────┘
```
**代理层级关系表**: `agent_hierarchies`
---
## 三种分佣模式
### 1. 一次性分佣 (One-time Commission)
**特点**: 订单完成后立即发放佣金
**适用场景**:
- 首次激活奖励
- 推广奖励
- 快速返佣
**示例**:
```
用户购买套餐 → 订单完成 → 立即发放佣金给上级代理
```
**配置示例**:
```sql
INSERT INTO commission_rules (
rule_name,
rule_type,
package_series_id,
commission_type,
commission_value,
status
) VALUES (
'一次性分佣-套餐激活奖励',
'one_time',
1, -- 套餐系列 ID
'fixed', -- 固定金额
10.00, -- 10元
1 -- 启用
);
```
**业务流程**:
```
订单创建 → 订单支付 → 订单完成
└─→ 创建分佣记录 (status=1 待发放)
└─→ 自动发放 (status=2 已发放)
```
---
### 2. 长期分佣 (Long-term Commission)
**特点**: 订单完成后冻结佣金,满足解冻条件后发放
**适用场景**:
- 续费奖励
- 留存奖励
- 长期激励
**解冻条件**:
- **时间条件**: 冻结 N 天后自动解冻
- **流量条件**: IoT 卡累计使用 M MB 流量后解冻
- **OR 逻辑**: 时间到期 **OR** 流量达标,满足任一条件即可解冻
**示例**:
```
用户购买套餐 → 订单完成 → 冻结佣金 (30天或1GB流量)
时间到期 OR 流量达标
自动解冻发放
```
**配置示例**:
```sql
INSERT INTO commission_rules (
rule_name,
rule_type,
package_series_id,
commission_type,
commission_value,
freeze_days,
freeze_data_mb,
unfreeze_mode,
status
) VALUES (
'长期分佣-续费奖励',
'long_term',
1,
'percentage', -- 百分比
0.10, -- 10%
30, -- 冻结30天
1024, -- 或使用1GB流量
'auto', -- 自动解冻
1
);
```
**业务流程**:
```
订单创建 → 订单支付 → 订单完成
└─→ 创建分佣记录 (status=3 已冻结)
├─→ 时间检查: 30天后 → 自动解冻 (status=2 已发放)
└─→ 流量检查: 使用1GB流量后 → 自动解冻 (status=2 已发放)
```
**解冻条件数据结构**:
```json
{
"time_based": {
"days": 30,
"deadline": "2025-02-10T00:00:00Z"
},
"data_based": {
"data_mb": 1024,
"iot_card_id": 12345
}
}
```
---
### 3. 组合分佣 (Combined Commission)
**特点**: 同时包含一次性分佣和长期分佣,订单完成后部分立即发放,部分冻结
**适用场景**:
- 首充奖励(立即发放) + 留存奖励(冻结发放)
- 灵活激励机制
**示例**:
```
用户购买套餐 → 订单完成 → 立即发放 5元 + 冻结 10元 (30天后发放)
```
**配置示例**:
```sql
-- 1. 创建组合分佣规则
INSERT INTO commission_rules (
rule_name,
rule_type,
package_series_id,
status
) VALUES (
'组合分佣-首充+留存',
'combined',
1,
1
);
-- 2. 配置一次性条件
INSERT INTO commission_combined_conditions (
rule_id,
condition_type,
commission_type,
commission_value
) VALUES (
1, -- 上面创建的规则 ID
'one_time',
'fixed',
5.00 -- 立即发放 5元
);
-- 3. 配置长期条件
INSERT INTO commission_combined_conditions (
rule_id,
condition_type,
commission_type,
commission_value,
freeze_days,
freeze_data_mb
) VALUES (
1,
'long_term',
'fixed',
10.00, -- 冻结 10元
30, -- 30天
1024 -- 或1GB流量
);
```
**业务流程**:
```
订单创建 → 订单支付 → 订单完成
├─→ 创建一次性分佣记录 (status=1 待发放) → 立即发放 (status=2)
└─→ 创建长期分佣记录 (status=3 已冻结) → 满足条件后解冻
```
---
## 阶梯奖励机制
### 阶梯奖励说明
阶梯奖励允许根据订单数量设置不同的分佣标准,订单数量越多,分佣越高。
**示例配置**:
```
1-10 单: 10元/单
11-50 单: 15元/单
51+ 单: 20元/单
```
### 配置示例
```sql
-- 1. 创建支持阶梯的分佣规则
INSERT INTO commission_rules (
rule_name,
rule_type,
package_series_id,
enable_ladder,
status
) VALUES (
'阶梯分佣-月度订单量',
'one_time',
1,
true, -- 启用阶梯
1
);
-- 2. 配置阶梯奖励
INSERT INTO commission_ladder (rule_id, min_quantity, max_quantity, commission_type, commission_value) VALUES
(1, 1, 10, 'fixed', 10.00),
(1, 11, 50, 'fixed', 15.00),
(1, 51, NULL, 'fixed', 20.00); -- NULL 表示无上限
```
### 阶梯计算逻辑
```go
// 伪代码
func CalculateLadderCommission(agentID uint, ruleID uint, currentMonth string) float64 {
// 1. 查询阶梯配置
ladders := db.FindCommissionLadders(ruleID)
// 2. 统计当月订单数量
orderCount := db.CountOrders(agentID, currentMonth)
// 3. 匹配阶梯
for _, ladder := range ladders {
if orderCount >= ladder.MinQuantity &&
(ladder.MaxQuantity == nil || orderCount <= ladder.MaxQuantity) {
if ladder.CommissionType == "fixed" {
return ladder.CommissionValue
} else if ladder.CommissionType == "percentage" {
orderAmount := db.GetOrderAmount(orderID)
return orderAmount * ladder.CommissionValue
}
}
}
return 0
}
```
---
## 分佣计算方式
### 1. 固定金额 (Fixed)
**说明**: 每笔订单固定分佣 N 元
**示例**:
```sql
commission_type = 'fixed'
commission_value = 10.00
```
**计算公式**:
```
分佣金额 = commission_value = 10.00 元
```
---
### 2. 百分比 (Percentage)
**说明**: 按订单金额的 N% 分佣
**示例**:
```sql
commission_type = 'percentage'
commission_value = 0.10 -- 10%
```
**计算公式**:
```
分佣金额 = 订单金额 × commission_value
= 100.00 × 0.10
= 10.00 元
```
---
## 分佣记录表
### 表结构: `commission_records`
分佣记录表记录每笔分佣的详细信息:
```sql
CREATE TABLE commission_records (
id BIGSERIAL PRIMARY KEY,
order_id BIGINT NOT NULL,
agent_id BIGINT NOT NULL,
rule_id BIGINT NOT NULL,
commission_type VARCHAR(50) NOT NULL,
commission_amount DECIMAL(10,2) NOT NULL,
status INT NOT NULL DEFAULT 1,
freeze_days INT DEFAULT 0,
freeze_data_mb BIGINT DEFAULT 0,
unfreeze_conditions JSONB,
unfrozen_at TIMESTAMPTZ,
distributed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
```
### 状态说明
| status | 状态 | 说明 |
|--------|------|------|
| 1 | 待发放 | 一次性分佣,等待发放 |
| 2 | 已发放 | 已发放到代理账户 |
| 3 | 已冻结 | 长期分佣,冻结中 |
| 4 | 已取消 | 订单取消或退款,分佣取消 |
---
## OR 条件解冻逻辑
### 解冻条件设计
长期分佣支持 **OR 条件解冻**,即时间到期 **OR** 流量达标,满足任一条件即可自动解冻。
**解冻条件数据结构**:
```json
{
"time_based": {
"days": 30,
"deadline": "2025-02-10T00:00:00Z"
},
"data_based": {
"data_mb": 1024,
"iot_card_id": 12345
}
}
```
### 解冻检查逻辑
```go
// 伪代码
func CheckUnfreezeConditions(record *CommissionRecord) bool {
var conditions struct {
TimeBased struct {
Days int `json:"days"`
Deadline time.Time `json:"deadline"`
} `json:"time_based"`
DataBased struct {
DataMB int64 `json:"data_mb"`
IotCardID uint `json:"iot_card_id"`
} `json:"data_based"`
}
json.Unmarshal(record.UnfreezeConditions, &conditions)
// 检查时间条件
if time.Now().After(conditions.TimeBased.Deadline) {
return true // 时间到期,可以解冻
}
// 检查流量条件
if conditions.DataBased.IotCardID > 0 {
card := db.FindIotCardByID(conditions.DataBased.IotCardID)
if card.DataUsageMB >= conditions.DataBased.DataMB {
return true // 流量达标,可以解冻
}
}
return false // 条件均未满足
}
```
### 自动解冻定时任务
```go
// 伪代码
func UnfreezeCommissionTask() {
ticker := time.NewTicker(1 * time.Minute)
defer ticker.Stop()
for range ticker.C {
// 查询所有冻结中的分佣记录
records := db.FindCommissionRecords("status = 3")
for _, record := range records {
if CheckUnfreezeConditions(&record) {
// 解冻
record.Status = 2 // 已发放
record.UnfrozenAt = time.Now()
record.DistributedAt = time.Now()
db.Save(&record)
// 发放到代理账户
DistributeCommission(record.AgentID, record.CommissionAmount)
logger.Info("分佣解冻成功",
zap.Uint("record_id", record.ID),
zap.Uint("agent_id", record.AgentID),
zap.Float64("amount", record.CommissionAmount),
)
}
}
}
}
```
---
## 分佣审批流程
### 自动审批 vs 手动审批
分佣规则的 `unfreeze_mode` 字段控制解冻模式:
- **auto**: 自动解冻,满足条件后自动发放
- **manual**: 手动审批,需要人工审核通过后才能发放
### 手动审批流程
```
订单完成 → 创建分佣记录 (status=3 已冻结)
满足解冻条件
创建审批记录 (approval_status=1 待审批)
审批人审核
├─→ 通过 (approval_status=2) → 发放佣金 (status=2 已发放)
└─→ 拒绝 (approval_status=3) → 取消分佣 (status=4 已取消)
```
### 审批表: `commission_approvals`
```sql
CREATE TABLE commission_approvals (
id BIGSERIAL PRIMARY KEY,
commission_record_id BIGINT UNIQUE NOT NULL,
agent_id BIGINT NOT NULL,
approval_status INT NOT NULL DEFAULT 1,
approver_id BIGINT,
approval_reason TEXT,
approved_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
```
### 审批状态
| approval_status | 状态 | 说明 |
|----------------|------|------|
| 1 | 待审批 | 等待审批人审核 |
| 2 | 已通过 | 审批通过,发放佣金 |
| 3 | 已拒绝 | 审批拒绝,取消分佣 |
---
## 分佣模板
### 模板设计
分佣模板用于快速创建分佣规则,避免重复配置。
**表结构**: `commission_templates`
```sql
CREATE TABLE commission_templates (
id BIGSERIAL PRIMARY KEY,
template_name VARCHAR(255) NOT NULL,
template_data JSONB NOT NULL,
description TEXT,
status INT NOT NULL DEFAULT 1,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
```
### 模板数据格式
```json
{
"rule_type": "combined",
"package_series_id": 1,
"conditions": [
{
"condition_type": "one_time",
"commission_type": "fixed",
"commission_value": 5.00
},
{
"condition_type": "long_term",
"commission_type": "fixed",
"commission_value": 10.00,
"freeze_days": 30,
"freeze_data_mb": 1024
}
]
}
```
### 使用模板创建规则
```go
// 伪代码
func CreateRuleFromTemplate(templateID uint, seriesID uint) error {
template := db.FindTemplateByID(templateID)
var data struct {
RuleType string `json:"rule_type"`
PackageSeriesID uint `json:"package_series_id"`
Conditions []struct {
ConditionType string `json:"condition_type"`
CommissionType string `json:"commission_type"`
CommissionValue float64 `json:"commission_value"`
FreezeDays int `json:"freeze_days"`
FreezeDataMB int64 `json:"freeze_data_mb"`
} `json:"conditions"`
}
json.Unmarshal(template.TemplateData, &data)
// 创建分佣规则
rule := CommissionRule{
RuleName: template.TemplateName,
RuleType: data.RuleType,
PackageSeriesID: seriesID,
Status: 1,
}
db.Create(&rule)
// 创建组合条件
for _, cond := range data.Conditions {
condition := CommissionCombinedCondition{
RuleID: rule.ID,
ConditionType: cond.ConditionType,
CommissionType: cond.CommissionType,
CommissionValue: cond.CommissionValue,
FreezeDays: cond.FreezeDays,
FreezeDataMB: cond.FreezeDataMB,
}
db.Create(&condition)
}
return nil
}
```
---
## 运营商结算
### 结算表: `carrier_settlements`
记录与运营商的月度结算情况:
```sql
CREATE TABLE carrier_settlements (
id BIGSERIAL PRIMARY KEY,
carrier_id BIGINT NOT NULL,
settlement_month VARCHAR(7) NOT NULL,
total_orders INT DEFAULT 0,
total_amount DECIMAL(10,2) DEFAULT 0,
settlement_status INT NOT NULL DEFAULT 1,
settled_at TIMESTAMPTZ,
paid_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
```
### 结算状态
| settlement_status | 状态 | 说明 |
|------------------|------|------|
| 1 | 待结算 | 月度未结束 |
| 2 | 已结算 | 已统计金额 |
| 3 | 已支付 | 已支付给运营商 |
### 月度结算流程
```
每月1号 → 统计上月订单数据
创建结算记录 (settlement_status=1 待结算)
财务审核
确认结算 (settlement_status=2 已结算)
支付运营商 (settlement_status=3 已支付)
```
### 结算计算逻辑
```go
// 伪代码
func GenerateCarrierSettlement(carrierID uint, month string) error {
// 1. 统计上月订单
orders := db.FindOrders("carrier_id = ? AND DATE_FORMAT(completed_at, '%Y-%m') = ?", carrierID, month)
totalOrders := len(orders)
totalAmount := 0.0
for _, order := range orders {
totalAmount += order.Amount
}
// 2. 创建结算记录
settlement := CarrierSettlement{
CarrierID: carrierID,
SettlementMonth: month,
TotalOrders: totalOrders,
TotalAmount: totalAmount,
SettlementStatus: 1, // 待结算
}
db.Create(&settlement)
return nil
}
```
---
## 提现管理
### 提现申请表: `commission_withdrawal_requests`
```sql
CREATE TABLE commission_withdrawal_requests (
id BIGSERIAL PRIMARY KEY,
agent_id BIGINT NOT NULL,
withdrawal_amount DECIMAL(10,2) NOT NULL,
withdrawal_method VARCHAR(20) NOT NULL,
account_info JSONB NOT NULL,
status INT NOT NULL DEFAULT 1,
reviewer_id BIGINT,
review_reason TEXT,
reviewed_at TIMESTAMPTZ,
paid_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
```
### 提现状态
| status | 状态 | 说明 |
|--------|------|------|
| 1 | 待审核 | 等待审核 |
| 2 | 已通过 | 审核通过,等待打款 |
| 3 | 已拒绝 | 审核拒绝 |
| 4 | 已打款 | 已打款到账户 |
| 5 | 已取消 | 用户取消 |
### 提现流程
```
代理提交提现申请 (status=1 待审核)
财务审核
├─→ 通过 (status=2 已通过) → 打款 (status=4 已打款)
└─→ 拒绝 (status=3 已拒绝)
```
### 提现设置表: `commission_withdrawal_settings`
```sql
CREATE TABLE commission_withdrawal_settings (
id BIGSERIAL PRIMARY KEY,
agent_id BIGINT UNIQUE NOT NULL,
min_withdrawal_amount DECIMAL(10,2) DEFAULT 0,
max_withdrawal_amount DECIMAL(10,2) DEFAULT 0,
withdrawal_fee_rate DECIMAL(5,4) DEFAULT 0,
auto_approval_enabled BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
```
### 提现规则检查
```go
// 伪代码
func ValidateWithdrawalRequest(agentID uint, amount float64) error {
setting := db.FindWithdrawalSetting(agentID)
// 检查最小金额
if amount < setting.MinWithdrawalAmount {
return fmt.Errorf("提现金额不能低于 %.2f 元", setting.MinWithdrawalAmount)
}
// 检查最大金额
if setting.MaxWithdrawalAmount > 0 && amount > setting.MaxWithdrawalAmount {
return fmt.Errorf("提现金额不能高于 %.2f 元", setting.MaxWithdrawalAmount)
}
// 检查账户余额
balance := db.GetAgentBalance(agentID)
fee := amount * setting.WithdrawalFeeRate
totalAmount := amount + fee
if balance < totalAmount {
return fmt.Errorf("余额不足,需要 %.2f 元(含手续费 %.2f 元)", totalAmount, fee)
}
return nil
}
```
---
## 分佣业务流程示例
### 示例 1: 一次性分佣
```
1. 用户购买套餐(100元)
2. 订单完成
3. 触发分佣计算
- 规则: 一次性分佣,固定金额 10元
- 创建分佣记录: agent_id=123, commission_amount=10.00, status=1 待发放
4. 自动发放
- 更新分佣记录: status=2 已发放, distributed_at=NOW()
- 更新代理账户余额: balance += 10.00
5. 完成
```
---
### 示例 2: 长期分佣(OR 条件解冻)
```
1. 用户购买套餐(100元)
2. 订单完成
3. 触发分佣计算
- 规则: 长期分佣,10%,冻结30天 OR 使用1GB流量
- 创建分佣记录: agent_id=123, commission_amount=10.00, status=3 已冻结
- 解冻条件: {"time_based": {"days": 30}, "data_based": {"data_mb": 1024}}
4. 定时任务检查解冻条件
- 时间检查: 30天后 → 满足条件 → 解冻
- 流量检查: 使用1GB流量后 → 满足条件 → 解冻
5. 自动解冻
- 更新分佣记录: status=2 已发放, unfrozen_at=NOW(), distributed_at=NOW()
- 更新代理账户余额: balance += 10.00
6. 完成
```
---
### 示例 3: 组合分佣
```
1. 用户购买套餐(100元)
2. 订单完成
3. 触发分佣计算
- 规则: 组合分佣
- 一次性条件: 固定金额 5元
- 长期条件: 固定金额 10元,冻结30天
4. 创建两条分佣记录
- 记录1: agent_id=123, commission_amount=5.00, status=1 待发放
- 记录2: agent_id=123, commission_amount=10.00, status=3 已冻结
5. 立即发放一次性分佣
- 记录1: status=2 已发放
- 代理账户余额: balance += 5.00
6. 30天后自动解冻长期分佣
- 记录2: status=2 已发放
- 代理账户余额: balance += 10.00
7. 完成
```
---
## 监控和统计
### 分佣统计指标
1. **代理分佣总额**
- 待发放金额
- 已发放金额
- 已冻结金额
2. **分佣发放效率**
- 平均发放时长
- 平均解冻时长
3. **提现统计**
- 提现申请数量
- 提现成功率
- 提现金额统计
### SQL 查询示例
```sql
-- 1. 代理分佣总额统计
SELECT
agent_id,
SUM(CASE WHEN status = 1 THEN commission_amount ELSE 0 END) AS pending_amount,
SUM(CASE WHEN status = 2 THEN commission_amount ELSE 0 END) AS distributed_amount,
SUM(CASE WHEN status = 3 THEN commission_amount ELSE 0 END) AS frozen_amount
FROM commission_records
WHERE agent_id = 123
GROUP BY agent_id;
-- 2. 月度分佣统计
SELECT
DATE_FORMAT(created_at, '%Y-%m') AS month,
COUNT(*) AS total_records,
SUM(commission_amount) AS total_amount
FROM commission_records
WHERE agent_id = 123
AND status = 2
GROUP BY DATE_FORMAT(created_at, '%Y-%m')
ORDER BY month DESC;
-- 3. 提现统计
SELECT
status,
COUNT(*) AS request_count,
SUM(withdrawal_amount) AS total_amount
FROM commission_withdrawal_requests
WHERE agent_id = 123
GROUP BY status;
```
---
## 最佳实践
### 1. 合理设置冻结条件
- **过短**: 可能导致代理流失
- **过长**: 影响代理积极性
- **建议**: 根据业务特点和用户留存数据设置合理的冻结期
### 2. 使用 OR 条件解冻
- **优势**: 提高解冻灵活性,代理满足任一条件即可获得佣金
- **示例**: 30天 OR 1GB流量,满足其一即可解冻
### 3. 启用阶梯奖励
- **优势**: 激励代理提高订单量
- **示例**: 月订单量越多,单笔佣金越高
### 4. 定期审查分佣规则
- 定期分析分佣数据,优化分佣规则
- 根据代理反馈调整冻结条件和佣金比例
---
## 总结
IoT SIM 管理系统的分佣系统具有以下特点:
1. **三种分佣模式**: 一次性分佣、长期分佣、组合分佣
2. **阶梯奖励机制**: 支持根据订单数量设置不同的分佣标准
3. **OR 条件解冻**: 时间到期 OR 流量达标,满足任一条件即可解冻
4. **自动 + 手动审批**: 支持自动解冻和手动审批两种模式
5. **分佣模板**: 快速创建分佣规则,避免重复配置
6. **运营商结算**: 记录与运营商的月度结算情况
7. **提现管理**: 完善的提现申请和审批流程
8. **多级代理**: 支持无限层级的代理树形结构
通过灵活配置和使用分佣系统,可以激励代理积极性,提高销售业绩,实现平台与代理的双赢。
---
**文档版本**: v1.0
**最后更新**: 2026-01-12
**维护人员**: Claude Sonnet 4.5