Files
junhong_cmp_fiber/docs/需求规划/账号与佣金管理模块需求规划.md
huang 91c9bbfeb8
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 4m35s
feat: 实现账号与佣金管理模块
新增功能:
- 店铺佣金查询:店铺佣金统计、店铺佣金记录列表、店铺提现记录
- 佣金提现审批:提现申请列表、审批通过、审批拒绝
- 提现配置管理:配置列表、新增配置、获取当前生效配置
- 企业管理:企业列表、创建、更新、删除、获取详情
- 企业卡授权:授权列表、批量授权、批量取消授权、统计
- 客户账号管理:账号列表、创建、更新状态、重置密码
- 我的佣金:佣金统计、佣金记录、提现申请、提现记录

数据库变更:
- 扩展 tb_commission_withdrawal_request 新增提现单号等字段
- 扩展 tb_account 新增 is_primary 字段
- 扩展 tb_commission_record 新增 shop_id、balance_after
- 扩展 tb_commission_withdrawal_setting 新增每日提现次数限制
- 扩展 tb_iot_card、tb_device 新增 shop_id 冗余字段
- 新建 tb_enterprise_card_authorization 企业卡授权表
- 新建 tb_asset_allocation_record 资产分配记录表
- 数据迁移:owner_type 枚举值 agent 统一为 shop

测试:
- 新增 7 个单元测试文件覆盖各服务
- 修复集成测试 Redis 依赖问题
2026-01-21 18:20:44 +08:00

1791 lines
54 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 账号与佣金管理模块需求规划
> 文档版本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绑定管理
- 号卡管理(虚拟产品管理)
- 订单管理(套餐订购、支付流程)
- 数据统计(用量统计、业务报表)
---
*文档结束*