All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 4m35s
新增功能: - 店铺佣金查询:店铺佣金统计、店铺佣金记录列表、店铺提现记录 - 佣金提现审批:提现申请列表、审批通过、审批拒绝 - 提现配置管理:配置列表、新增配置、获取当前生效配置 - 企业管理:企业列表、创建、更新、删除、获取详情 - 企业卡授权:授权列表、批量授权、批量取消授权、统计 - 客户账号管理:账号列表、创建、更新状态、重置密码 - 我的佣金:佣金统计、佣金记录、提现申请、提现记录 数据库变更: - 扩展 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 依赖问题
1791 lines
54 KiB
Markdown
1791 lines
54 KiB
Markdown
# 账号与佣金管理模块需求规划
|
||
|
||
> 文档版本: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绑定管理)
|
||
- 号卡管理(虚拟产品管理)
|
||
- 订单管理(套餐订购、支付流程)
|
||
- 数据统计(用量统计、业务报表)
|
||
|
||
---
|
||
|
||
*文档结束*
|