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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-12 15:44:23 +08:00

23 KiB
Raw Blame History

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)

特点: 订单完成后立即发放佣金

适用场景:

  • 首次激活奖励
  • 推广奖励
  • 快速返佣

示例:

用户购买套餐 → 订单完成 → 立即发放佣金给上级代理

配置示例:

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 流量达标
                              ↓
                          自动解冻发放

配置示例:

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 已发放)

解冻条件数据结构:

{
  "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天后发放)

配置示例:

-- 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元/单

配置示例

-- 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 表示无上限

阶梯计算逻辑

// 伪代码
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 元

示例:

commission_type = 'fixed'
commission_value = 10.00

计算公式:

分佣金额 = commission_value = 10.00 元

2. 百分比 (Percentage)

说明: 按订单金额的 N% 分佣

示例:

commission_type = 'percentage'
commission_value = 0.10 -- 10%

计算公式:

分佣金额 = 订单金额 × commission_value
        = 100.00 × 0.10
        = 10.00 元

分佣记录表

表结构: commission_records

分佣记录表记录每笔分佣的详细信息:

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 流量达标,满足任一条件即可自动解冻。

解冻条件数据结构:

{
  "time_based": {
    "days": 30,
    "deadline": "2025-02-10T00:00:00Z"
  },
  "data_based": {
    "data_mb": 1024,
    "iot_card_id": 12345
  }
}

解冻检查逻辑

// 伪代码
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 // 条件均未满足
}

自动解冻定时任务

// 伪代码
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

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

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()
);

模板数据格式

{
  "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
    }
  ]
}

使用模板创建规则

// 伪代码
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

记录与运营商的月度结算情况:

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 已支付)

结算计算逻辑

// 伪代码
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

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

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()
);

提现规则检查

// 伪代码
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 查询示例

-- 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