# Design: 设备管理功能

## Context

### 背景

系统已有完整的单卡（IoT Card）管理功能，包括单卡列表、分配、回收、导入。现需要在单卡之上增加设备维度的管理能力。

设备是比单卡更高一层的管理维度：
- 一个设备可绑定 1-4 张 IoT 卡
- 设备和绑定的卡作为一个整体进行分配和回收
- 设备由平台统一管理（导入、绑卡），代理商只能查看和分配

### 现有实现

| 组件 | 状态 | 说明 |
|------|------|------|
| `model/device.go` | ✅ 已有 | Device Model |
| `model/package.go` | ✅ 已有 | DeviceSimBinding Model |
| `tb_device` | ✅ 已有 | 设备表 |
| `tb_device_sim_binding` | ✅ 已有 | 设备卡绑定表 |
| Store/Service/Handler | ❌ 需新增 | 设备业务逻辑 |

### 约束

- 遵循现有分层架构：Handler → Service → Store → Model
- 复用现有的资产分配记录（asset-allocation-record）能力
- 参考现有 ICCID 导入实现异步任务
- 权限控制：导入、绑卡、删除仅平台用户

## Goals / Non-Goals

### Goals

1. 实现设备基础管理（列表、详情、删除）
2. 实现设备导入（CSV 批量导入，自动绑定卡）
3. 实现设备卡绑定管理（绑定、解绑、查询）
4. 实现设备分配/回收（自动同步绑定的卡）
5. 复用现有资产分配记录能力

### Non-Goals

1. ❌ 设备操作（远程重启、改密码、重置）
2. ❌ 设备套餐购买和流量共享
3. ❌ 设备创建/编辑 API（通过导入创建）

## Decisions

### Decision 1: 设备导入时绑定卡

**决策**: 导入设备时必须同时指定要绑定的卡（iccid_1~iccid_4），而非导入设备后再单独绑定。

**原因**:
- 业务流程：平台在外部系统报单后发货，设备和卡是一起出库的
- 减少操作步骤：一次导入完成设备创建和卡绑定
- 数据一致性：避免"空设备"状态

**CSV 格式**:
```csv
device_no,device_name,device_model,device_type,max_sim_slots,manufacturer,iccid_1,iccid_2,iccid_3,iccid_4
```

**备注**: 绑定/解绑 API 仅用于导入后的调整（换卡、补卡）。

### Decision 2: 设备分配时自动同步卡的 shop_id

**决策**: 分配/回收设备时，自动同步修改绑定卡的 shop_id。

**原因**:
- 业务需求：设备和卡作为整体分配，不能分开
- 数据一致性：设备和卡的归属必须一致
- 简化操作：代理商无需感知卡的存在

**实现**:
```go
// 分配设备时
func (s *Service) AllocateDevices(ctx, req) {
    // 1. 更新设备 shop_id
    // 2. 查询设备绑定的所有卡
    // 3. 批量更新卡的 shop_id
    // 4. 创建分配记录（related_card_ids）
}
```

### Decision 3: 导入时卡必须已存在

**决策**: 设备导入时，CSV 中的 ICCID 必须已存在于系统中。

**原因**:
- 数据完整性：卡有运营商、成本价等信息，需要先通过 ICCID 导入
- 业务流程：通常先导入卡，再导入设备绑定卡
- 错误处理：ICCID 不存在时明确报错，便于排查

**备选方案**: 导入时自动创建不存在的卡 → 需要更多字段（运营商等），增加复杂度

### Decision 4: 复用异步任务模式

**决策**: 设备导入使用与 ICCID 导入相同的异步任务模式。

**原因**:
- 一致性：用户体验和代码模式保持一致
- 可靠性：大文件处理不会超时
- 可追溯：任务状态和结果可查询

**实现**:
- 新增 `tb_device_import_task` 表（参考 `tb_iot_card_import_task`）
- 新增 `task/device_import.go` 异步处理器
- 复用 `pkg/queue` 和 `pkg/storage` 能力

### Decision 5: 权限控制策略

**决策**: 设备导入、绑卡、删除仅限平台用户；列表查询、分配回收所有人可用。

| 操作 | 平台用户 | 代理用户 |
|------|---------|---------|
| 设备列表/详情 | ✅ | ✅（数据权限过滤） |
| 设备导入 | ✅ | ❌ |
| 绑卡/解绑 | ✅ | ❌ |
| 删除设备 | ✅ | ❌ |
| 分配设备 | ✅ | ✅（只能给直属下级） |
| 回收设备 | ✅ | ✅（只能回收直属下级） |

**原因**:
- 平台统一管理设备库存和卡绑定关系
- 代理商只需要分配/回收能力

## Risks / Trade-offs

### Risk 1: 导入时卡校验性能

**风险**: 大批量导入时，逐行校验 ICCID 是否存在可能较慢。

**缓解**:
- 批量查询 ICCID 存在性（IN 查询）
- 批量查询 ICCID 绑定状态
- 导入任务异步执行，不阻塞请求

### Risk 2: 设备和卡 shop_id 不一致

**风险**: 如果代码逻辑有 bug，可能导致设备和卡的 shop_id 不一致。

**缓解**:
- 分配/回收使用事务，保证原子性
- 添加集成测试验证一致性
- 考虑后期添加数据一致性检查脚本

### Risk 3: 删除设备时卡的处理

**风险**: 删除设备时，绑定的卡如何处理？

**决策**: 删除设备时自动解绑所有卡，卡的 shop_id 保持不变。

**原因**: 卡是有价值的资产，不应随设备删除而丢失。

## Data Model

### 新增表: tb_device_import_task

```sql
CREATE TABLE tb_device_import_task (
    id BIGSERIAL PRIMARY KEY,
    task_no VARCHAR(50) NOT NULL UNIQUE,
    status INT NOT NULL DEFAULT 1,           -- 1-待处理 2-处理中 3-已完成 4-失败
    batch_no VARCHAR(100),
    file_key VARCHAR(500),
    file_name VARCHAR(255),
    total_count INT DEFAULT 0,
    success_count INT DEFAULT 0,
    skip_count INT DEFAULT 0,
    fail_count INT DEFAULT 0,
    skipped_items JSONB,
    failed_items JSONB,
    error_message TEXT,
    started_at TIMESTAMP,
    completed_at TIMESTAMP,
    created_at TIMESTAMP NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
    deleted_at TIMESTAMP,
    creator BIGINT,
    updater BIGINT
);

CREATE INDEX idx_device_import_task_status ON tb_device_import_task(status);
CREATE INDEX idx_device_import_task_batch_no ON tb_device_import_task(batch_no);
```

### 现有表（无需修改）

- `tb_device`: 设备表
- `tb_device_sim_binding`: 设备卡绑定表
- `tb_asset_allocation_record`: 资产分配记录表（已支持 device 类型）

## API Design

### 设备管理

| 方法 | 路径 | 说明 |
|------|------|------|
| GET | /api/admin/devices | 设备列表 |
| GET | /api/admin/devices/:id | 设备详情 |
| DELETE | /api/admin/devices/:id | 删除设备 |
| GET | /api/admin/devices/:id/cards | 获取绑定的卡 |
| POST | /api/admin/devices/:id/cards | 绑定卡 |
| DELETE | /api/admin/devices/:id/cards/:cardId | 解绑卡 |
| POST | /api/admin/devices/allocate | 批量分配 |
| POST | /api/admin/devices/recall | 批量回收 |

### 设备导入

| 方法 | 路径 | 说明 |
|------|------|------|
| POST | /api/admin/devices/import | 提交导入任务 |
| GET | /api/admin/devices/import/tasks | 导入任务列表 |
| GET | /api/admin/devices/import/tasks/:id | 导入任务详情 |

## Open Questions

1. **设备导入失败后是否支持重试？**
   - 当前设计：不支持，用户需修正 CSV 重新导入
   - 可后续添加：断点续传、失败重试功能

2. **设备和卡 shop_id 不一致时如何修复？**
   - 需要管理员工具或 SQL 脚本修复
   - 建议后续添加数据一致性检查接口
