# 账号与佣金管理模块需求规划

> 文档版本：v1.2
> 创建时间：2026-01-21
> 更新时间：2026-01-21
> 状态：✅ 已确认所有核心问题

---

## 一、总体概述

### 1.1 模块范围

本次需求涵盖以下功能模块：

| 模块 | 说明 |
|------|------|
| 账号管理-代理商(店铺)管理 | 查看代理商佣金信息、佣金提现记录、佣金明细 |
| 账号管理-佣金提现 | 佣金提现申请审批流程（放款/拒绝） |
| 佣金提现设置 | 全局提现规则配置（次数、金额、手续费） |
| 账号管理-企业客户管理 | 企业CRUD、卡分配/回收、启用禁用 |
| 账号管理-客户账号管理 | 代理商+企业客户的账号统一管理 |
| 财务-我的账号 | 当前登录账号的佣金数据查询 |

### 1.2 核心概念说明

| 概念 | 系统模型 | 说明 |
|------|----------|------|
| 代理商/店铺 | `Shop` | 同一概念，代码中使用 Shop |
| 代理账号 | `Account` (UserType=3) | 店铺下的员工账号 |
| 店铺主账号 | `Account` (is_primary=true) | 创建店铺时同步创建的账号，每个店铺有且仅有一个 |
| 企业客户 | `Enterprise` | B端企业客户 |
| 企业账号 | `Account` (UserType=4) | 企业的登录账号 |
| 佣金钱包 | `Wallet` (WalletType=commission) | 店铺级别的佣金钱包 |

### 1.3 数据权限规则

| 角色 | 数据可见范围 |
|------|-------------|
| 平台用户 | 全部数据 |
| 代理商用户 | 自己店铺 + 下级店铺 + 归属的企业客户数据 |
| 企业用户 | 仅自己企业数据 |

---

## 二、数据模型变更

### 2.1 需要新增的字段

#### 2.1.1 `tb_commission_withdrawal_request` 表新增字段

| 字段名 | 类型 | 说明 | 是否必填 |
|--------|------|------|----------|
| `withdrawal_no` | varchar(50) | 提现单号（唯一标识，格式：W + 时间戳 + 随机数） | 是 |
| `applicant_id` | uint | 申请人账号ID（店铺下哪个账号提交的） | 是 |
| `shop_id` | uint | 店铺ID（冗余字段，方便查询） | 是 |
| `fee_rate` | int64 | 手续费比率（基点，100=1%，记录申请时的费率快照） | 是 |
| `payment_type` | varchar(20) | 放款类型（manual=人工打款） | 是 |
| `processor_id` | uint | 处理人ID（审批/放款人） | 否 |
| `processed_at` | timestamp | 处理时间 | 否 |
| `remark` | text | 备注 | 否 |

#### 2.1.2 `tb_account` 表新增字段

| 字段名 | 类型 | 说明 | 是否必填 |
|--------|------|------|----------|
| `is_primary` | boolean | 是否为店铺主账号（创建店铺时同步创建的账号） | 否，默认false |

**说明**：
- 每个店铺有且仅有一个主账号（`is_primary=true`）
- 创建店铺时自动创建的账号设置为主账号
- 主账号不可删除，只能禁用
- 未来销售系统会通过账号关联佣金归属

#### 2.1.3 `tb_commission_record` 表新增字段

| 字段名 | 类型 | 说明 | 是否必填 |
|--------|------|------|----------|
| `shop_id` | uint | 店铺ID（冗余字段，佣金主要跟着店铺走） | 是 |

**说明**：
- 佣金本质上跟着店铺走
- 同时保留 `agent_id`（账号ID），未来可支持查看某销售的佣金情况
- 佣金明细查询主要基于 `shop_id`

#### 2.1.4 `tb_commission_withdrawal_setting` 表新增字段

| 字段名 | 类型 | 说明 | 是否必填 |
|--------|------|------|----------|
| `daily_withdrawal_limit` | int | 每日提现次数限制（每个代理商） | 是 |

#### 2.1.5 `tb_iot_card` 表新增字段

| 字段名 | 类型 | 说明 | 是否必填 |
|--------|------|------|----------|
| `shop_id` | uint | 店铺ID（冗余字段，owner_type=shop时等于owner_id） | 否 |

#### 2.1.6 `tb_device` 表新增字段

| 字段名 | 类型 | 说明 | 是否必填 |
|--------|------|------|----------|
| `shop_id` | uint | 店铺ID（冗余字段，owner_type=shop时等于owner_id） | 否 |

#### 2.1.7 `tb_commission_record` 表新增字段（补充）

| 字段名 | 类型 | 说明 | 是否必填 |
|--------|------|------|----------|
| `balance_after` | int64 | 入账后佣金余额（分），创建时计算：本次佣金 + 历史累计佣金 | 是 |

#### 2.1.8 `tb_iot_card` 和 `tb_device` 表 `owner_type` 统一

**当前值**：`platform`, `agent`, `user`, `device`

**统一为**：`platform`, `shop`

| 旧值 | 新值 | 说明 |
|------|------|------|
| `platform` | `platform` | 平台库存（不变） |
| `agent` | `shop` | 代理商持有（统一命名） |
| `user` | 废弃 | 不再使用 |
| `device` | 废弃 | 不再使用 |

**核心设计**：
- 卡/设备只在平台和代理商之间流转
- `owner_type=shop` + `owner_id=店铺ID` 表示代理商持有
- 企业看到的卡是通过"授权"实现的，不改变归属

### 2.2 需要新增的表

#### 2.2.1 `tb_enterprise_card_authorization` 企业-卡授权表（核心）

用于记录企业被授权可见的卡。**这是企业查看卡的唯一途径，不改变卡的归属**。

```sql
CREATE TABLE tb_enterprise_card_authorization (
    id BIGSERIAL PRIMARY KEY,
    enterprise_id BIGINT NOT NULL,                       -- 企业ID
    iot_card_id BIGINT NOT NULL,                         -- 卡ID
    shop_id BIGINT NOT NULL,                             -- 卡所属店铺ID（冗余，方便查询和权限校验）
    authorized_by BIGINT NOT NULL,                       -- 授权人账号ID
    authorized_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),-- 授权时间
    status INT DEFAULT 1,                                -- 1=有效, 0=已回收
    
    creator BIGINT,
    updater BIGINT,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    deleted_at TIMESTAMP WITH TIME ZONE,
    
    CONSTRAINT uk_enterprise_card UNIQUE(enterprise_id, iot_card_id)
);

CREATE INDEX idx_eca_enterprise ON tb_enterprise_card_authorization(enterprise_id, status) WHERE deleted_at IS NULL;
CREATE INDEX idx_eca_card ON tb_enterprise_card_authorization(iot_card_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_eca_shop ON tb_enterprise_card_authorization(shop_id) WHERE deleted_at IS NULL;
```

**核心设计说明**：
- 卡的归属（owner）始终是代理商店铺，不会变成企业
- 企业通过授权表"看到"被授权的卡
- 授权是永久的，回收时更新 `status=0`
- 设备通过卡的授权间接可见（不需要单独的设备授权表）

#### 2.2.2 `tb_asset_allocation_record` 资产分配记录表

用于记录卡/设备在平台和代理商之间流转的历史。

```sql
CREATE TABLE tb_asset_allocation_record (
    id BIGSERIAL PRIMARY KEY,
    allocation_no VARCHAR(50) NOT NULL UNIQUE,           -- 分配单号
    allocation_type VARCHAR(20) NOT NULL,                -- 分配类型：allocate=分配, recall=回收
    asset_type VARCHAR(20) NOT NULL,                     -- 资产类型：iot_card, device
    asset_id BIGINT NOT NULL,                            -- 资产ID
    asset_identifier VARCHAR(50) NOT NULL,               -- 资产标识（ICCID或设备号，方便查询）
    
    from_owner_type VARCHAR(20),                         -- 原归属类型
    from_owner_id BIGINT,                                -- 原归属ID
    to_owner_type VARCHAR(20) NOT NULL,                  -- 目标归属类型：platform/shop
    to_owner_id BIGINT NOT NULL,                         -- 目标归属ID
    
    related_device_id BIGINT,                            -- 关联设备ID（卡分配时如果绑定了设备）
    related_card_ids JSONB,                              -- 关联卡ID列表（设备分配时包含的卡）
    
    operator_id BIGINT NOT NULL,                         -- 操作人ID
    remark TEXT,                                         -- 备注
    
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    deleted_at TIMESTAMP WITH TIME ZONE
);

CREATE INDEX idx_asset_allocation_asset ON tb_asset_allocation_record(asset_type, asset_id);
CREATE INDEX idx_asset_allocation_to_owner ON tb_asset_allocation_record(to_owner_type, to_owner_id);
CREATE INDEX idx_asset_allocation_created ON tb_asset_allocation_record(created_at);
```

### 2.3 卡/设备归属与授权体系（已确认）

#### 2.3.1 核心设计原则

```
┌─────────────────────────────────────────────────────────────────┐
│                    归属 vs 授权 - 核心区别                        │
└─────────────────────────────────────────────────────────────────┘

【归属流转】改变 owner_type/owner_id，涉及真实的资产转移：
    平台 (platform) ──分配──→ 代理商 (shop)

【授权可见】不改变归属，只是让企业"能看到"：
    代理商的卡 ──授权──→ 企业可见（通过授权表）
```

**owner_type 只有两种值**：
- `platform` - 平台库存
- `shop` - 代理商持有

**企业不拥有卡**：
- 企业看到的卡仍然属于代理商
- 企业通过 `tb_enterprise_card_authorization` 表获得可见性
- 授权是永久的，回收时更新状态

#### 2.3.2 数据权限过滤修改

需要修改 `pkg/gorm/callback.go`，对 `tb_iot_card` 表做特殊处理：

```go
// 企业用户查询 IotCard 时的特殊处理
if userType == constants.UserTypeEnterprise && tableName == "tb_iot_card" {
    enterpriseID := middleware.GetEnterpriseIDFromContext(ctx)
    if enterpriseID != 0 {
        // 企业用户只能看到被授权的卡
        tx.Where("id IN (SELECT iot_card_id FROM tb_enterprise_card_authorization WHERE enterprise_id = ? AND status = 1 AND deleted_at IS NULL)", enterpriseID)
    } else {
        tx.Where("1 = 0")
    }
    return
}

// 代理用户查询 IotCard 时，使用 shop_id 字段过滤
if userType == constants.UserTypeAgent && tableName == "tb_iot_card" {
    // 使用新增的 shop_id 冗余字段
    tx.Where("shop_id IN ?", subordinateShopIDs)
    return
}
```

#### 2.3.3 分配/授权规则

| 场景 | 操作 | 说明 |
|------|------|------|
| 平台 → 代理商 | 修改 `owner_type=shop`, `owner_id=shop_id`, `shop_id=shop_id` | 真实的归属转移 |
| 代理商 → 企业 | 创建 `tb_enterprise_card_authorization` 记录 | 只是授权可见，不改变归属 |
| 企业 → 代理商回收 | 更新授权表 `status=0` | 取消授权 |
| 代理商 → 平台回收 | 修改 `owner_type=platform`, `shop_id=NULL` | 同时清理相关授权记录 |

#### 2.3.4 设备可见性（通过卡间接查询）

企业查询设备时，不直接查设备表，而是：
1. 查询企业被授权的卡
2. 通过 `DeviceSimBinding` 表关联到设备
3. 返回设备信息

```sql
-- 企业可见的设备（通过卡间接查询）
SELECT DISTINCT d.*
FROM tb_device d
INNER JOIN tb_device_sim_binding dsb ON d.id = dsb.device_id AND dsb.bind_status = 1
INNER JOIN tb_enterprise_card_authorization eca ON dsb.iot_card_id = eca.iot_card_id
WHERE eca.enterprise_id = ? AND eca.status = 1 AND eca.deleted_at IS NULL;
```

### 2.4 卡/设备分配详细方案（已确认）

#### 2.4.1 分配交互流程

```
┌─────────────────────────────────────────────────────────────────┐
│                    卡分配给企业流程                              │
└─────────────────────────────────────────────────────────────────┘

用户选择ICCID列表 → 调用预检接口 → 展示分配预览 → 用户确认 → 执行分配
                         │
                         ▼
              ┌─────────────────────┐
              │ 检查每张卡是否绑定设备 │
              └──────────┬──────────┘
                         │
         ┌───────────────┼───────────────┐
         │               │               │
    ┌────▼────┐    ┌─────▼─────┐   ┌─────▼─────┐
    │ 未绑定设备 │    │ 绑定了设备  │   │ 卡不存在   │
    │          │    │           │   │ /无权限    │
    └────┬────┘    └─────┬─────┘   └─────┬─────┘
         │               │               │
         │               ▼               │
         │    ┌─────────────────┐        │
         │    │ 查询设备绑定的   │        │
         │    │ 所有卡列表       │        │
         │    └────────┬────────┘        │
         │             │                 │
         ▼             ▼                 ▼
    ┌─────────────────────────────────────────┐
    │           返回分配预览结果               │
    │ - 可直接分配的卡                         │
    │ - 需要整体分配的设备（含所有卡）          │
    │ - 失败的卡（不存在/无权限）              │
    └─────────────────────────────────────────┘
```

#### 2.4.2 新增接口：分配预检

**接口路径**：`POST /api/admin/enterprises/:id/allocate-cards/preview`

**接口说明**：预检要分配的卡，返回分配预览信息供用户确认

**请求参数**：

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| id | uint | 是 | 企业ID（路径参数） |
| iccids | []string | 是 | 需要分配的ICCID列表 |

**返回参数**：

```json
{
  "code": 0,
  "message": "success",
  "data": {
    "standalone_cards": [                         // 可直接分配的卡（未绑定设备）
      {
        "iccid": "89860001234567890123",
        "msisdn": "1440012345678",
        "carrier_name": "中国移动"
      }
    ],
    "device_bundles": [                           // 需要整体分配的设备包
      {
        "device_no": "DEV001",
        "device_name": "测试设备1",
        "trigger_iccid": "89860001234567890124",  // 触发整体分配的卡
        "cards": [                                 // 设备下所有卡
          {
            "iccid": "89860001234567890124",
            "msisdn": "1440012345679",
            "is_trigger": true                    // 是用户选择的卡
          },
          {
            "iccid": "89860001234567890125",
            "msisdn": "1440012345680",
            "is_trigger": false                   // 连带分配的卡
          },
          {
            "iccid": "89860001234567890126",
            "msisdn": "1440012345681",
            "is_trigger": false
          }
        ]
      }
    ],
    "failed_items": [                             // 失败的卡
      {
        "iccid": "89860001234567890127",
        "reason": "卡不存在"
      },
      {
        "iccid": "89860001234567890128",
        "reason": "无权限操作该卡"
      }
    ],
    "summary": {
      "standalone_card_count": 1,
      "device_count": 1,
      "device_card_count": 3,
      "total_card_count": 4,                      // 将要分配的总卡数
      "failed_count": 2
    }
  }
}
```

**前端交互建议**：
1. 用户选择ICCID后，先调用预检接口
2. 展示分配预览：
   - "将分配 1 张独立卡"
   - "将分配 1 台设备（含 3 张卡），因为您选择的卡 xxx 绑定在该设备上"
   - "2 张卡分配失败：xxx（卡不存在）、xxx（无权限）"
3. 用户确认后，调用正式分配接口

#### 2.4.3 授权确认接口调整

**接口路径**：`POST /api/admin/enterprises/:id/allocate-cards`

**请求参数**：

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| id | uint | 是 | 企业ID（路径参数） |
| iccids | []string | 是 | 需要授权的ICCID列表（与预检相同） |
| confirm_device_bundles | bool | 是 | 确认整体授权设备下所有卡（必须为true才执行） |

**说明**：
- 这是"授权"操作，不是"转移归属"
- 授权后卡仍然属于代理商，企业只是能看到和操作
- `confirm_device_bundles=true` 表示用户已确认整体授权设备下所有卡

**接口逻辑调整**：
1. 验证企业存在且归属于当前代理商（或其下级）
2. 验证卡属于当前代理商（`shop_id` 在可见范围内）
3. 如果卡绑定了设备，获取设备下所有卡一起授权
4. **创建授权记录**到 `tb_enterprise_card_authorization` 表（不修改卡的 owner）
5. 返回授权结果

---

## 三、接口设计

### 3.1 账号管理-代理商(店铺)管理

#### 3.1.1 代理商分页列表查询

**接口路径**：`GET /api/admin/shops/commission-summary`

**接口说明**：查询代理商列表及其佣金汇总信息

**请求参数**：

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| page | int | 否 | 页码，默认1 |
| page_size | int | 否 | 每页数量，默认20，最大100 |
| shop_name | string | 否 | 店铺名称（模糊查询） |
| username | string | 否 | 代理商账号用户名（模糊查询） |

**返回参数**：

```json
{
  "code": 0,
  "message": "success",
  "data": {
    "list": [
      {
        "shop_id": 1,
        "shop_name": "某某代理商",
        "shop_code": "SHOP001",
        "username": "agent001",                    // 店铺主账号用户名
        "phone": "13800138000",                    // 店铺主账号手机号
        "total_commission": 100000,                // 总佣金（分）
        "withdrawn_commission": 50000,             // 已提现佣金（分）
        "unwithdraw_commission": 50000,            // 未提现佣金（分）= 总佣金 - 已提现
        "frozen_commission": 10000,                // 冻结中佣金（分）
        "withdrawing_commission": 5000,            // 提现中佣金（分）= 待审批的提现申请金额
        "available_commission": 35000              // 可提现佣金（分）= 未提现 - 冻结 - 提现中
      }
    ],
    "total": 100,
    "page": 1,
    "page_size": 20
  }
}
```

**接口逻辑**：

1. 根据当前用户权限过滤店铺列表（平台看全部，代理看自己+下级）
2. 查询店铺基本信息
3. 关联查询店铺的主账号（`is_primary=true` 的账号）
4. 计算佣金汇总：
   - `total_commission`：从 `Wallet` (shop类型, commission钱包) 的 `balance + frozen_balance` + 已提现金额
   - `withdrawn_commission`：从 `CommissionWithdrawalRequest` 统计 `status=2(已通过)` 的总金额
   - `frozen_commission`：`Wallet.frozen_balance`
   - `withdrawing_commission`：从 `CommissionWithdrawalRequest` 统计 `status=1(待审批)` 的总金额
   - `available_commission`：`Wallet.balance - withdrawing_commission`

**主账号说明**：
- 每个店铺有且仅有一个主账号（`is_primary=true`）
- 创建店铺时自动创建的账号即为主账号
- 这里显示主账号的信息（username、phone）

---

#### 3.1.2 佣金提现分页列表（代理商维度）

**接口路径**：`GET /api/admin/shops/:shop_id/withdrawal-requests`

**接口说明**：查询指定代理商的佣金提现记录

**请求参数**：

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| shop_id | uint | 是 | 店铺ID（路径参数） |
| page | int | 否 | 页码，默认1 |
| page_size | int | 否 | 每页数量，默认20 |
| withdrawal_no | string | 否 | 提现单号（精确查询） |
| start_time | string | 否 | 申请开始时间（ISO8601） |
| end_time | string | 否 | 申请结束时间（ISO8601） |

**返回参数**：

```json
{
  "code": 0,
  "message": "success",
  "data": {
    "list": [
      {
        "id": 1,
        "withdrawal_no": "W20260121143000001",     // 提现单号
        "shop_name": "某某代理商",
        "shop_hierarchy": "上上级代理_上级代理_某某代理商",  // 层级路径：本身往上最多两层（不含平台）
        "applicant_name": "张三",                   // 申请人姓名（账号username）
        "amount": 10000,                           // 提现金额（分）
        "fee_rate": 100,                           // 手续费比率（基点，100=1%）
        "fee": 100,                                // 手续费金额（分）
        "actual_amount": 9900,                     // 实际到账金额（分）
        "status": 1,                               // 状态：1=待审批,2=已通过,3=已拒绝,4=已放款
        "status_name": "待审批",
        "created_at": "2026-01-21T14:30:00+08:00", // 申请时间
        "withdrawal_method": "alipay",             // 收款类型
        "account_name": "张三",                    // 收款人姓名
        "account_number": "zhangsan@alipay.com",   // 支付宝账号
        "payment_type": "manual",                  // 放款类型
        "payment_type_name": "人工打款",
        "processor_name": "管理员",                 // 处理人姓名
        "processed_at": "2026-01-21T15:00:00+08:00", // 处理时间
        "remark": "备注信息"
      }
    ],
    "total": 50,
    "page": 1,
    "page_size": 20
  }
}
```

**接口逻辑**：

1. 验证当前用户有权限查看该店铺
2. 查询 `CommissionWithdrawalRequest` 表，过滤 `shop_id`
3. 关联查询店铺信息、申请人信息、处理人信息
4. 计算店铺层级路径：
   - 从当前店铺往上最多查两层
   - 格式：`上上级_上级_本身`（用下划线分隔）
   - 如果只有一层上级：`上级_本身`
   - 如果是一级代理（无上级）：`本身`
   - 不包含平台

---

#### 3.1.3 佣金明细分页查询（代理商维度）

**接口路径**：`GET /api/admin/shops/:shop_id/commission-records`

**接口说明**：查询指定代理商的佣金入账明细

**请求参数**：

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| shop_id | uint | 是 | 店铺ID（路径参数） |
| page | int | 否 | 页码，默认1 |
| page_size | int | 否 | 每页数量，默认20 |
| commission_type | string | 否 | 佣金类型：one_time/long_term |
| iccid | string | 否 | ICCID（模糊查询） |
| device_no | string | 否 | 设备号（模糊查询） |
| order_no | string | 否 | 订单号（模糊查询） |

**返回参数**：

```json
{
  "code": 0,
  "message": "success",
  "data": {
    "list": [
      {
        "id": 1,
        "shop_name": "某某代理商",
        "order_no": "ORD20260121001",              // 订单号
        "device_no": "DEV001",                     // 设备号（可能为空）
        "iccid": "89860001234567890123",           // ICCID（可能为空）
        "order_created_at": "2026-01-20T10:00:00+08:00", // 下单时间
        "commission_type": "one_time",             // 佣金类型
        "commission_type_name": "一次性佣金",
        "amount": 500,                             // 入账佣金（分）
        "balance_after": 10500,                    // 入账后佣金余额（分）
        "status": 3,                               // 状态：1=冻结,2=解冻中,3=已发放,4=已失效
        "status_name": "已发放",
        "created_at": "2026-01-21T10:00:00+08:00"  // 佣金记录创建时间
      }
    ],
    "total": 200,
    "page": 1,
    "page_size": 20
  }
}
```

**接口逻辑**：

1. 验证当前用户有权限查看该店铺
2. 查询 `CommissionRecord` 表，直接通过 `shop_id` 过滤（已确认新增该字段）
3. 关联查询 `Order` 表获取订单号、下单时间
4. 通过 `Order.iot_card_id` 关联 `IotCard` 获取 ICCID
5. 通过 `Order.device_id` 关联 `Device` 获取设备号
6. `balance_after` 需要从 `WalletTransaction` 中获取

**数据关联说明**：
- `CommissionRecord` 同时存储 `agent_id`（账号ID）和 `shop_id`（店铺ID）
- 佣金明细查询主要基于 `shop_id`
- 保留 `agent_id` 用于未来支持查看某销售的佣金情况

---

### 3.2 账号管理-佣金提现

#### 3.2.1 佣金提现申请分页查询列表

**接口路径**：`GET /api/admin/commission/withdrawal-requests`

**接口说明**：查询所有待处理的佣金提现申请（审批列表）

**请求参数**：

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| page | int | 否 | 页码，默认1 |
| page_size | int | 否 | 每页数量，默认20 |
| status | int | 否 | 状态：1=待审批,2=已通过,3=已拒绝,4=已放款 |
| withdrawal_no | string | 否 | 提现单号（精确查询） |
| shop_name | string | 否 | 店铺名称（模糊查询） |
| start_time | string | 否 | 申请开始时间（ISO8601） |
| end_time | string | 否 | 申请结束时间（ISO8601） |

**返回参数**：

```json
{
  "code": 0,
  "message": "success",
  "data": {
    "list": [
      {
        "id": 1,
        "withdrawal_no": "W20260121143000001",
        "shop_id": 5,
        "shop_name": "某某代理商",
        "shop_hierarchy": "上上级代理_上级代理_某某代理商",  // 本身往上最多两层
        "applicant_id": 10,
        "applicant_name": "张三",
        "amount": 10000,
        "fee_rate": 100,
        "fee": 100,
        "actual_amount": 9900,
        "status": 1,
        "status_name": "待审批",
        "created_at": "2026-01-21T14:30:00+08:00",
        "withdrawal_method": "alipay",
        "withdrawal_method_name": "支付宝",
        "account_name": "张三",
        "account_number": "zhangsan@alipay.com",
        "payment_type": "manual",
        "payment_type_name": "人工打款",
        "processor_id": null,
        "processor_name": null,
        "processed_at": null,
        "remark": null
      }
    ],
    "total": 10,
    "page": 1,
    "page_size": 20
  }
}
```

**接口逻辑**：

1. 根据当前用户权限过滤数据
2. 支持多条件组合查询
3. 按申请时间倒序排列

---

#### 3.2.2 审批通过

**接口路径**：`POST /api/admin/commission/withdrawal-requests/:id/approve`

**接口说明**：审批通过提现申请（实际打款由人工线下完成）

**请求参数**：

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| id | uint | 是 | 提现申请ID（路径参数） |
| payment_type | string | 是 | 放款类型：manual=人工打款 |
| amount | int64 | 否 | 修正后的提现金额（分），不传则使用原金额 |
| withdrawal_method | string | 否 | 修正后的收款类型，不传则使用原值 |
| account_name | string | 否 | 修正后的收款人姓名，不传则使用原值 |
| account_number | string | 否 | 修正后的收款账号，不传则使用原值 |
| remark | string | 否 | 备注 |

**返回参数**：

```json
{
  "code": 0,
  "message": "success",
  "data": {
    "id": 1,
    "withdrawal_no": "W20260121143000001",
    "status": 2,
    "status_name": "已通过",
    "processed_at": "2026-01-21T15:00:00+08:00"
  }
}
```

**接口逻辑**：

1. 验证提现申请存在且状态为待审批
2. 验证当前用户有审批权限
3. 如果修正了金额，重新计算手续费和实际到账金额
4. 更新提现申请状态为已通过（status=2）
5. 从店铺佣金钱包扣除对应金额（解冻并扣除）
6. 记录钱包交易流水
7. 记录处理人和处理时间

**审批流程说明**：
- 审批只有一步：待审批(1) → 已通过(2) 或 已拒绝(3)
- 审批通过后，系统自动扣除佣金
- 实际打款由人工在线下完成（目前只支持人工打款）
- 状态流转：1(待审批) → 2(已通过) → 人工线下打款

---

#### 3.2.3 拒绝（审批拒绝）

**接口路径**：`POST /api/admin/commission/withdrawal-requests/:id/reject`

**接口说明**：拒绝提现申请

**请求参数**：

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| id | uint | 是 | 提现申请ID（路径参数） |
| remark | string | 是 | 拒绝原因 |

**返回参数**：

```json
{
  "code": 0,
  "message": "success",
  "data": {
    "id": 1,
    "withdrawal_no": "W20260121143000001",
    "status": 3,
    "status_name": "已拒绝",
    "processed_at": "2026-01-21T15:00:00+08:00"
  }
}
```

**接口逻辑**：

1. 验证提现申请存在且状态为待审批
2. 验证当前用户有审批权限
3. 更新提现申请状态为已拒绝
4. 解冻店铺佣金钱包中的冻结金额
5. 记录钱包交易流水
6. 记录处理人、处理时间和拒绝原因

---

### 3.3 佣金提现设置

#### 3.3.1 新增佣金提现设置

**接口路径**：`POST /api/admin/commission/withdrawal-settings`

**接口说明**：新增全局佣金提现配置（新配置生效后旧配置自动失效）

**请求参数**：

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| daily_withdrawal_limit | int | 是 | 每日提现次数限制（每个代理商） |
| min_withdrawal_amount | int64 | 是 | 提现最低金额（分） |
| fee_rate | int64 | 是 | 提现手续费比率（基点，100=1%） |

**返回参数**：

```json
{
  "code": 0,
  "message": "success",
  "data": {
    "id": 1,
    "daily_withdrawal_limit": 3,
    "min_withdrawal_amount": 10000,
    "fee_rate": 100,
    "is_active": true,
    "creator_name": "管理员",
    "created_at": "2026-01-21T14:30:00+08:00"
  }
}
```

**接口逻辑**：

1. 验证当前用户有配置权限（平台用户）
2. 将当前生效配置的 `is_active` 设为 false
3. 创建新配置，`is_active` 设为 true
4. 记录创建人

---

#### 3.3.2 分页查询设置记录

**接口路径**：`GET /api/admin/commission/withdrawal-settings`

**接口说明**：查询佣金提现配置历史记录

**请求参数**：

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| page | int | 否 | 页码，默认1 |
| page_size | int | 否 | 每页数量，默认20 |

**返回参数**：

```json
{
  "code": 0,
  "message": "success",
  "data": {
    "list": [
      {
        "id": 2,
        "daily_withdrawal_limit": 3,
        "min_withdrawal_amount": 10000,
        "fee_rate": 100,
        "is_active": true,
        "creator_id": 1,
        "creator_name": "管理员",
        "created_at": "2026-01-21T14:30:00+08:00"
      },
      {
        "id": 1,
        "daily_withdrawal_limit": 5,
        "min_withdrawal_amount": 5000,
        "fee_rate": 50,
        "is_active": false,
        "creator_id": 1,
        "creator_name": "管理员",
        "created_at": "2026-01-01T10:00:00+08:00"
      }
    ],
    "total": 2,
    "page": 1,
    "page_size": 20
  }
}
```

**接口逻辑**：

1. 查询所有配置记录，按创建时间倒序
2. 关联查询创建人姓名

---

#### 3.3.3 获取当前生效配置

**接口路径**：`GET /api/admin/commission/withdrawal-settings/current`

**接口说明**：获取当前生效的提现配置

**返回参数**：

```json
{
  "code": 0,
  "message": "success",
  "data": {
    "id": 2,
    "daily_withdrawal_limit": 3,
    "min_withdrawal_amount": 10000,
    "fee_rate": 100,
    "is_active": true,
    "creator_name": "管理员",
    "created_at": "2026-01-21T14:30:00+08:00"
  }
}
```

---

### 3.4 账号管理-企业客户管理

#### 3.4.1 新增企业

**接口路径**：`POST /api/admin/enterprises`

**接口说明**：创建企业客户，同时自动创建企业账号

**请求参数**：

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| owner_shop_id | uint | 否 | 归属代理商ID，不填则为平台自营 |
| enterprise_name | string | 是 | 企业名称 |
| enterprise_code | string | 是 | 企业编号（唯一） |
| legal_person | string | 否 | 法人代表 |
| contact_name | string | 是 | 联系人姓名 |
| contact_phone | string | 是 | 联系人电话 |
| login_phone | string | 是 | 登录手机号（作为企业账号的登录账号） |
| password | string | 是 | 登录密码 |
| business_license | string | 否 | 营业执照号 |
| province | string | 否 | 省份 |
| city | string | 否 | 城市 |
| district | string | 否 | 区县 |
| address | string | 否 | 详细地址 |

**返回参数**：

```json
{
  "code": 0,
  "message": "success",
  "data": {
    "enterprise": {
      "id": 1,
      "enterprise_name": "某某企业",
      "enterprise_code": "ENT001",
      "owner_shop_id": 5,
      "owner_shop_name": "某某代理商",
      "legal_person": "李四",
      "contact_name": "王五",
      "contact_phone": "13900139000",
      "business_license": "91110000...",
      "province": "北京市",
      "city": "北京市",
      "district": "朝阳区",
      "address": "某某路123号",
      "status": 1,
      "created_at": "2026-01-21T14:30:00+08:00"
    },
    "account": {
      "id": 10,
      "username": "某某企业",
      "phone": "13800138000",
      "user_type": 4,
      "status": 1
    }
  }
}
```

**接口逻辑**：

1. 验证企业编号唯一性
2. 如果指定 `owner_shop_id`，验证店铺存在且当前用户有权限
3. 验证 `login_phone` 在账号表中不存在
4. 开启事务：
   - 创建企业记录
   - 创建企业账号（UserType=4, EnterpriseID=企业ID, Phone=login_phone, Username=企业名称）
5. 提交事务

---

#### 3.4.2 分页查询企业客户

**接口路径**：`GET /api/admin/enterprises`

**接口说明**：查询企业客户列表

**请求参数**：

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| page | int | 否 | 页码，默认1 |
| page_size | int | 否 | 每页数量，默认20 |
| enterprise_name | string | 否 | 企业名称（模糊查询） |
| login_phone | string | 否 | 登录手机号（模糊查询） |
| contact_phone | string | 否 | 联系人电话（模糊查询） |
| owner_shop_id | uint | 否 | 归属代理商ID |
| status | int | 否 | 状态：0=禁用,1=启用 |

**返回参数**：

```json
{
  "code": 0,
  "message": "success",
  "data": {
    "list": [
      {
        "id": 1,
        "enterprise_name": "某某企业",
        "enterprise_code": "ENT001",
        "owner_shop_id": 5,
        "owner_shop_name": "某某代理商",          // NULL时显示"平台自营"
        "contact_name": "王五",
        "contact_phone": "13900139000",
        "login_phone": "13800138000",             // 从关联的Account获取
        "province": "北京市",
        "city": "北京市",
        "district": "朝阳区",
        "address": "某某路123号",
        "status": 1,
        "status_name": "启用",
        "created_at": "2026-01-21T14:30:00+08:00"
      }
    ],
    "total": 50,
    "page": 1,
    "page_size": 20
  }
}
```

**接口逻辑**：

1. 根据当前用户权限过滤：
   - 平台用户：看全部
   - 代理商用户：看 `owner_shop_id` 在自己+下级店铺范围内的企业
2. 关联查询企业账号获取 `login_phone`
3. 关联查询归属店铺名称

---

#### 3.4.3 编辑企业

**接口路径**：`PUT /api/admin/enterprises/:id`

**接口说明**：编辑企业信息（不影响账号）

**请求参数**：

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| id | uint | 是 | 企业ID（路径参数） |
| owner_shop_id | uint | 否 | 归属代理商ID |
| enterprise_name | string | 否 | 企业名称 |
| enterprise_code | string | 否 | 企业编号 |
| legal_person | string | 否 | 法人代表 |
| contact_name | string | 否 | 联系人姓名 |
| contact_phone | string | 否 | 联系人电话 |
| business_license | string | 否 | 营业执照号 |
| province | string | 否 | 省份 |
| city | string | 否 | 城市 |
| district | string | 否 | 区县 |
| address | string | 否 | 详细地址 |

**返回参数**：

```json
{
  "code": 0,
  "message": "success",
  "data": {
    "id": 1,
    "enterprise_name": "某某企业（新）",
    "enterprise_code": "ENT001",
    "updated_at": "2026-01-21T15:00:00+08:00"
  }
}
```

**接口逻辑**：

1. 验证企业存在
2. 验证当前用户有权限编辑该企业
3. 如果修改了 `enterprise_code`，验证唯一性
4. 如果修改了 `owner_shop_id`，验证店铺存在且当前用户有权限
5. 更新企业信息
6. **注意**：修改联系人电话不影响账号的登录手机号

---

#### 3.4.4 分配卡给企业客户

**接口路径**：`POST /api/admin/enterprises/:id/allocate-cards`

**接口说明**：将卡分配给企业客户

**请求参数**：

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| id | uint | 是 | 企业ID（路径参数） |
| iccids | []string | 是 | 需要分配的ICCID列表 |

**返回参数**：

```json
{
  "code": 0,
  "message": "success",
  "data": {
    "success_count": 10,
    "fail_count": 2,
    "failed_items": [
      {
        "iccid": "89860001234567890123",
        "reason": "卡不存在"
      },
      {
        "iccid": "89860001234567890124",
        "reason": "无权限操作该卡"
      }
    ],
    "allocated_devices": [                        // 因卡分配而连带分配的设备
      {
        "device_no": "DEV001",
        "card_count": 4
      }
    ]
  }
}
```

**接口逻辑**：

1. 验证企业存在且当前用户有权限
2. 遍历ICCID列表：
   - 验证卡存在
   - 验证当前用户有权限操作该卡（卡的 owner 在用户可见范围内）
   - 检查卡是否绑定了设备
3. 如果卡绑定了设备：
   - 将整个设备及其所有绑定的卡一起分配
   - 记录到返回的 `allocated_devices` 中
4. 更新卡/设备的 `owner_type=enterprise`, `owner_id=enterprise_id`
5. 创建分配记录到 `tb_asset_allocation_record`

**⚠️ 待确认**：
- 如果卡A绑定在设备X上，设备X还绑定了卡B/C/D，分配卡A时是否要连带分配整个设备和所有卡？
- 我的理解是：是的，需要整体分配，否则会导致归属关系混乱

---

#### 3.4.5 从企业客户回收卡授权

**接口路径**：`POST /api/admin/enterprises/:id/recall-cards`

**接口说明**：取消企业对卡的授权（卡仍属于代理商）

**请求参数**：

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| id | uint | 是 | 企业ID（路径参数） |
| iccids | []string | 是 | 需要回收授权的ICCID列表 |

**返回参数**：

```json
{
  "code": 0,
  "message": "success",
  "data": {
    "success_count": 10,
    "fail_count": 1,
    "failed_items": [
      {
        "iccid": "89860001234567890123",
        "reason": "该卡未授权给此企业"
      }
    ],
    "recalled_devices": [
      {
        "device_no": "DEV001",
        "card_count": 4
      }
    ]
  }
}
```

**接口逻辑**：

1. 验证企业存在且当前用户有权限
2. 遍历ICCID列表：
   - 验证卡存在
   - 验证卡已授权给该企业（授权表中有记录且 status=1）
   - 检查卡是否绑定了设备
3. 如果卡绑定了设备，设备下所有卡的授权一起回收
4. **更新授权记录** `status=0`（不是删除，不是修改卡的 owner）
5. 卡仍然属于代理商，只是企业不再能看到

---

#### 3.4.6 企业客户卡分页查询列表

**接口路径**：`GET /api/admin/enterprises/:id/cards`

**接口说明**：查询企业被授权可见的卡列表

**请求参数**：

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| id | uint | 是 | 企业ID（路径参数） |
| page | int | 否 | 页码，默认1 |
| page_size | int | 否 | 每页数量，默认20 |
| status | int | 否 | 卡状态 |
| carrier_id | uint | 否 | 运营商ID |
| iccid | string | 否 | ICCID（模糊查询） |
| device_no | string | 否 | 设备号（模糊查询） |

**返回参数**：

```json
{
  "code": 0,
  "message": "success",
  "data": {
    "list": [
      {
        "id": 1,
        "iccid": "89860001234567890123",
        "msisdn": "1440012345678",                 // 接入号
        "device_id": 10,
        "device_no": "DEV001",                    // 设备号（可能为空）
        "carrier_id": 1,
        "carrier_name": "中国移动",
        "package_id": 5,
        "package_name": "月租套餐30G",             // 当前套餐名称
        "status": 3,
        "status_name": "已激活",
        "network_status": 1,                       // 网络状态：0=停机,1=开机
        "network_status_name": "开机"
      }
    ],
    "total": 100,
    "page": 1,
    "page_size": 20
  }
}
```

**接口逻辑**：

1. 验证企业存在且当前用户有权限
2. 通过授权表查询企业被授权的卡：
   ```sql
   SELECT c.* FROM tb_iot_card c
   INNER JOIN tb_enterprise_card_authorization eca 
     ON c.id = eca.iot_card_id
   WHERE eca.enterprise_id = ? AND eca.status = 1 AND eca.deleted_at IS NULL
   ```
3. 关联查询设备信息、运营商信息、当前套餐信息

#### 3.4.6.1 企业操作卡 - 停机

**接口路径**：`POST /api/admin/enterprises/:id/cards/:card_id/suspend`

**接口说明**：企业对授权卡执行停机操作

**请求参数**：

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| id | uint | 是 | 企业ID（路径参数） |
| card_id | uint | 是 | 卡ID（路径参数） |

**接口逻辑**：

1. 验证企业存在
2. 验证卡已授权给该企业（授权表中有有效记录）
3. 调用运营商接口执行停机
4. 更新卡的 `network_status = 0`

#### 3.4.6.2 企业操作卡 - 复机

**接口路径**：`POST /api/admin/enterprises/:id/cards/:card_id/resume`

**接口说明**：企业对授权卡执行复机操作

**请求参数**：

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| id | uint | 是 | 企业ID（路径参数） |
| card_id | uint | 是 | 卡ID（路径参数） |

**接口逻辑**：

1. 验证企业存在
2. 验证卡已授权给该企业
3. 调用运营商接口执行复机
4. 更新卡的 `network_status = 1`

---

#### 3.4.7 启用/禁用企业

**接口路径**：`PUT /api/admin/enterprises/:id/status`

**接口说明**：启用或禁用企业

**请求参数**：

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| id | uint | 是 | 企业ID（路径参数） |
| status | int | 是 | 状态：0=禁用,1=启用 |

**返回参数**：

```json
{
  "code": 0,
  "message": "success",
  "data": {
    "id": 1,
    "status": 0,
    "status_name": "禁用"
  }
}
```

**接口逻辑**：

1. 验证企业存在且当前用户有权限
2. 更新企业状态
3. **同步禁用/启用**企业关联的账号

---

#### 3.4.8 修改企业账号密码

**接口路径**：`PUT /api/admin/enterprises/:id/password`

**接口说明**：重置企业账号的登录密码

**请求参数**：

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| id | uint | 是 | 企业ID（路径参数） |
| password | string | 是 | 新密码 |

**返回参数**：

```json
{
  "code": 0,
  "message": "success",
  "data": {
    "id": 1,
    "enterprise_name": "某某企业"
  }
}
```

**接口逻辑**：

1. 验证企业存在且当前用户有权限
2. 查找企业关联的账号
3. 更新账号密码（bcrypt加密）

---

### 3.5 账号管理-客户账号管理

> 说明：统一管理代理商账号和企业账号（UserType=3或4）

#### 3.5.1 分页查询客户账号

**接口路径**：`GET /api/admin/customer-accounts`

**接口说明**：查询代理商和企业的账号列表

**请求参数**：

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| page | int | 否 | 页码，默认1 |
| page_size | int | 否 | 每页数量，默认20 |
| shop_id | uint | 否 | 代理商ID（筛选该代理商及其下级的账号） |
| username | string | 否 | 账号名称（模糊查询） |
| status | int | 否 | 账号状态：0=禁用,1=启用 |
| user_type | int | 否 | 账号类型：3=代理,4=企业 |

**返回参数**：

```json
{
  "code": 0,
  "message": "success",
  "data": {
    "list": [
      {
        "id": 10,
        "username": "张三",
        "phone": "13800138000",
        "user_type": 3,
        "user_type_name": "代理账号",
        "shop_id": 5,
        "shop_name": "某某代理商",
        "enterprise_id": null,
        "enterprise_name": null,
        "status": 1,
        "status_name": "启用",
        "created_at": "2026-01-21T14:30:00+08:00"
      },
      {
        "id": 11,
        "username": "某某企业",
        "phone": "13900139000",
        "user_type": 4,
        "user_type_name": "企业账号",
        "shop_id": null,
        "shop_name": null,
        "enterprise_id": 1,
        "enterprise_name": "某某企业",
        "status": 1,
        "status_name": "启用",
        "created_at": "2026-01-21T14:30:00+08:00"
      }
    ],
    "total": 100,
    "page": 1,
    "page_size": 20
  }
}
```

**接口逻辑**：

1. 过滤条件：`user_type IN (3, 4)`
2. 根据当前用户权限过滤：
   - 平台用户：看全部
   - 代理商用户：看自己店铺+下级店铺的代理账号 + 归属企业的账号
3. 关联查询店铺名称、企业名称

---

#### 3.5.2 新增客户账号

**接口路径**：`POST /api/admin/customer-accounts`

**接口说明**：为代理商新增账号（企业账号通过新增企业时创建）

**请求参数**：

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| shop_id | uint | 是 | 代理商ID |
| username | string | 是 | 账号名称 |
| phone | string | 是 | 登录手机号 |
| password | string | 是 | 登录密码 |
| status | int | 否 | 状态，默认1=启用 |

**返回参数**：

```json
{
  "code": 0,
  "message": "success",
  "data": {
    "id": 12,
    "username": "李四",
    "phone": "13700137000",
    "user_type": 3,
    "shop_id": 5,
    "shop_name": "某某代理商",
    "status": 1,
    "created_at": "2026-01-21T14:30:00+08:00"
  }
}
```

**接口逻辑**：

1. 验证店铺存在且当前用户有权限
2. 验证手机号在账号表中不存在
3. 创建账号（UserType=3, ShopID=shop_id）

---

#### 3.5.3 编辑客户账号

**接口路径**：`PUT /api/admin/customer-accounts/:id`

**接口说明**：编辑客户账号信息

**请求参数**：

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| id | uint | 是 | 账号ID（路径参数） |
| username | string | 否 | 账号名称 |
| phone | string | 否 | 登录手机号 |

**返回参数**：

```json
{
  "code": 0,
  "message": "success",
  "data": {
    "id": 12,
    "username": "李四（新）",
    "phone": "13700137001",
    "updated_at": "2026-01-21T15:00:00+08:00"
  }
}
```

**接口逻辑**：

1. 验证账号存在且类型为代理或企业（3或4）
2. 验证当前用户有权限编辑该账号
3. 如果修改了手机号，验证新手机号不存在
4. 更新账号信息

---

#### 3.5.4 修改客户账号密码

**接口路径**：`PUT /api/admin/customer-accounts/:id/password`

**接口说明**：重置客户账号密码

**请求参数**：

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| id | uint | 是 | 账号ID（路径参数） |
| password | string | 是 | 新密码 |

**返回参数**：

```json
{
  "code": 0,
  "message": "success",
  "data": {
    "id": 12,
    "username": "李四"
  }
}
```

---

#### 3.5.5 启用/禁用客户账号

**接口路径**：`PUT /api/admin/customer-accounts/:id/status`

**接口说明**：启用或禁用客户账号

**请求参数**：

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| id | uint | 是 | 账号ID（路径参数） |
| status | int | 是 | 状态：0=禁用,1=启用 |

**返回参数**：

```json
{
  "code": 0,
  "message": "success",
  "data": {
    "id": 12,
    "status": 0,
    "status_name": "禁用"
  }
}
```

---

### 3.6 财务-我的账号（代理商端）

#### 3.6.1 获取当前账号佣金概览

**接口路径**：`GET /api/admin/my/commission-summary`

**接口说明**：获取当前登录代理账号所属店铺的佣金汇总

**返回参数**：

```json
{
  "code": 0,
  "message": "success",
  "data": {
    "shop_id": 5,
    "shop_name": "某某代理商",
    "total_commission": 100000,
    "withdrawn_commission": 50000,
    "unwithdraw_commission": 50000,
    "frozen_commission": 10000,
    "withdrawing_commission": 5000,
    "available_commission": 35000
  }
}
```

**接口逻辑**：

1. 从当前用户上下文获取 `shop_id`
2. 计算佣金汇总（逻辑同3.1.1）

---

#### 3.6.2 佣金提现申请

**接口路径**：`POST /api/admin/my/withdrawal-requests`

**接口说明**：代理商发起佣金提现申请

**请求参数**：

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| amount | int64 | 是 | 提现金额（分） |
| withdrawal_method | string | 是 | 收款类型：alipay |
| account_name | string | 是 | 收款人姓名 |
| account_number | string | 是 | 支付宝账号 |

**返回参数**：

```json
{
  "code": 0,
  "message": "success",
  "data": {
    "id": 1,
    "withdrawal_no": "W20260121143000001",
    "amount": 10000,
    "fee_rate": 100,
    "fee": 100,
    "actual_amount": 9900,
    "status": 1,
    "status_name": "待审批",
    "created_at": "2026-01-21T14:30:00+08:00"
  }
}
```

**接口逻辑**：

1. 从当前用户上下文获取 `shop_id` 和 `account_id`
2. 获取当前生效的提现配置
3. 验证：
   - 提现金额 >= 最低提现金额
   - 可提现余额 >= 提现金额
   - 今日提现次数 < 每日提现次数限制
4. 计算手续费和实际到账金额
5. 创建提现申请记录
6. 冻结店铺佣金钱包中对应金额
7. 记录钱包交易流水

---

#### 3.6.3 我的提现记录

**接口路径**：`GET /api/admin/my/withdrawal-requests`

**接口说明**：查询当前代理商的提现记录

**请求参数**：

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| page | int | 否 | 页码，默认1 |
| page_size | int | 否 | 每页数量，默认20 |
| status | int | 否 | 状态筛选 |
| start_time | string | 否 | 申请开始时间 |
| end_time | string | 否 | 申请结束时间 |

**返回参数**：

与 3.2.1 相同，但仅返回当前店铺的数据

---

#### 3.6.4 我的佣金明细

**接口路径**：`GET /api/admin/my/commission-records`

**接口说明**：查询当前代理商的佣金入账明细

**请求参数**：

与 3.1.3 相同

**返回参数**：

与 3.1.3 相同，但仅返回当前店铺的数据

---

## 四、接口路径汇总

| 模块 | 方法 | 路径 | 说明 |
|------|------|------|------|
| **代理商管理** | GET | /api/admin/shops/commission-summary | 代理商佣金列表 |
| | GET | /api/admin/shops/:shop_id/withdrawal-requests | 代理商提现记录 |
| | GET | /api/admin/shops/:shop_id/commission-records | 代理商佣金明细 |
| **佣金提现审批** | GET | /api/admin/commission/withdrawal-requests | 提现申请列表 |
| | POST | /api/admin/commission/withdrawal-requests/:id/approve | 审批通过（人工线下打款） |
| | POST | /api/admin/commission/withdrawal-requests/:id/reject | 审批拒绝 |
| **提现设置** | POST | /api/admin/commission/withdrawal-settings | 新增配置 |
| | GET | /api/admin/commission/withdrawal-settings | 配置列表 |
| | GET | /api/admin/commission/withdrawal-settings/current | 当前配置 |
| **企业客户管理** | POST | /api/admin/enterprises | 新增企业 |
| | GET | /api/admin/enterprises | 企业列表 |
| | PUT | /api/admin/enterprises/:id | 编辑企业 |
| | PUT | /api/admin/enterprises/:id/status | 启用禁用 |
| | PUT | /api/admin/enterprises/:id/password | 修改密码 |
| | POST | /api/admin/enterprises/:id/allocate-cards/preview | 授权预检 |
| | POST | /api/admin/enterprises/:id/allocate-cards | 授权卡（确认） |
| | POST | /api/admin/enterprises/:id/recall-cards | 回收授权 |
| | GET | /api/admin/enterprises/:id/cards | 企业授权卡列表 |
| | POST | /api/admin/enterprises/:id/cards/:card_id/suspend | 企业操作-停机 |
| | POST | /api/admin/enterprises/:id/cards/:card_id/resume | 企业操作-复机 |
| **客户账号管理** | GET | /api/admin/customer-accounts | 账号列表 |
| | POST | /api/admin/customer-accounts | 新增账号 |
| | PUT | /api/admin/customer-accounts/:id | 编辑账号 |
| | PUT | /api/admin/customer-accounts/:id/password | 修改密码 |
| | PUT | /api/admin/customer-accounts/:id/status | 启用禁用 |
| **财务-我的账号** | GET | /api/admin/my/commission-summary | 我的佣金概览 |
| | POST | /api/admin/my/withdrawal-requests | 发起提现 |
| | GET | /api/admin/my/withdrawal-requests | 我的提现记录 |
| | GET | /api/admin/my/commission-records | 我的佣金明细 |

---

## 五、已确认事项

### 5.1 核心设计确认

| # | 问题 | 确认结果 |
|---|------|----------|
| 1 | CommissionRecord 存储什么ID？ | **同时存储账号ID和店铺ID**。佣金主要跟着店铺走，但保留账号ID方便未来查看某销售的佣金。 |
| 2 | 店铺主账号如何定义？ | **新增 `is_primary` 字段标记**。创建店铺时同步创建的账号就是主账号。 |
| 3 | 卡绑定设备后如何授权？ | **整个设备及所有卡一起授权**。新增预检接口让用户确认。 |
| 4 | 审批流程几步？ | **只有一步**。审批通过后状态变为"已通过"，实际打款由人工线下完成。 |
| 5 | 代理商层级路径格式？ | **本身往上最多两层**：`上上级_上级_本身` |

### 5.2 卡/设备归属与授权确认

| # | 问题 | 确认结果 |
|---|------|----------|
| 6 | 卡的归属流转范围？ | **只在平台和代理商之间**。企业不拥有卡，只是被授权可见。 |
| 7 | owner_type 统一？ | **改为 `platform` / `shop`**。去掉 `agent`、`user`、`device`。 |
| 8 | 企业看卡的机制？ | **通过授权表** `tb_enterprise_card_authorization`，不改变卡的 owner。 |
| 9 | 设备如何可见？ | **通过卡间接查询**。不需要单独的设备授权表。 |
| 10 | 授权有效期？ | **永久授权**。回收时更新 `status=0`。 |
| 11 | 企业能操作什么？ | **能看、可以停机/复机**。 |

### 5.3 佣金明细确认

| # | 问题 | 确认结果 |
|---|------|----------|
| 12 | "入账后佣金"如何计算？ | **本次佣金 + 历史累计佣金**。在 `CommissionRecord` 创建时计算并存储 `balance_after` 字段。 |

### 5.4 待确认事项

**暂无待确认事项**。如有遗漏请补充。

---

## 六、后续规划提示

本文档仅涵盖账号和佣金相关功能。以下功能待后续规划：

- 物联网卡管理（ICCID增删改查、状态管理、数据同步）
- 设备管理（设备增删改查、SIM绑定管理）
- 号卡管理（虚拟产品管理）
- 订单管理（套餐订购、支付流程）
- 数据统计（用量统计、业务报表）

---

*文档结束*
