# Device Import

## Purpose

支持批量导入设备并自动绑定 IoT 卡，用于平台库存管理。导入时设备和卡的绑定关系一次性完成，绑定/解绑接口仅用于后续调整。

## ADDED Requirements

### Requirement: 设备批量导入

系统 SHALL 提供设备批量导入功能，通过 CSV 文件导入设备并自动绑定卡，仅平台用户可操作。

**API 端点**: `POST /api/admin/devices/import`

**请求参数**:
- `batch_no`: 批次号（必填）
- `file_key`: 对象存储文件路径（必填，通过 /storage/upload-url 获取）

**CSV 格式**:
```
device_no,device_name,device_model,device_type,max_sim_slots,manufacturer,iccid_1,iccid_2,iccid_3,iccid_4
DEV-001,GPS追踪器A,GT06N,GPS Tracker,4,Concox,8986001234567890001,8986001234567890002,,
DEV-002,GPS追踪器B,GT06N,GPS Tracker,4,Concox,8986001234567890003,,,
```

**字段说明**:
- `device_no`: 设备号（必填，唯一）
- `device_name`: 设备名称（可选）
- `device_model`: 设备型号（可选）
- `device_type`: 设备类型（可选）
- `max_sim_slots`: 最大插槽数（可选，默认 4，范围 1-4）
- `manufacturer`: 制造商（可选）
- `iccid_1` ~ `iccid_4`: 对应插槽 1-4 的 ICCID（可选，空值表示该插槽无卡）

**导入规则**:
- 导入的设备 shop_id = NULL（平台库存）
- 导入的设备 status = 1（在库）
- 设备号重复则该行跳过
- ICCID 必须已存在于系统中（先导入卡，再导入设备）
- ICCID 不存在则该行失败
- ICCID 已绑定其他设备则该行失败
- 导入通过异步任务处理，立即返回任务 ID

**权限**: 仅平台用户

**响应**:
- `task_id`: 导入任务 ID
- `task_no`: 任务编号
- `message`: 提示信息

#### Scenario: 提交设备导入任务

- **WHEN** 平台管理员上传 CSV 文件并提交导入请求
- **THEN** 系统创建导入任务，返回任务 ID，开始异步处理

#### Scenario: 代理尝试导入设备

- **WHEN** 代理用户尝试导入设备
- **THEN** 系统返回 403 错误，提示"无权限执行此操作"

#### Scenario: 文件格式错误

- **WHEN** 平台管理员上传非 CSV 格式或格式不正确的文件
- **THEN** 系统创建任务但处理失败，任务状态为"失败"，记录错误信息

---

### Requirement: 设备导入任务执行

系统 SHALL 异步执行设备导入任务，逐行处理 CSV 数据。

**处理规则**:
- 逐行解析 CSV 文件
- 对每行数据执行以下校验：
  1. 设备号是否已存在（已存在则跳过）
  2. ICCID 是否存在于系统中（不存在则失败）
  3. ICCID 是否已绑定其他设备（已绑定则失败）
- 校验通过后：
  1. 创建设备记录
  2. 创建设备-卡绑定记录
- 记录处理结果（成功/跳过/失败）

**任务状态**:
- 1: 待处理
- 2: 处理中
- 3: 已完成
- 4: 失败

#### Scenario: 导入成功

- **WHEN** CSV 中所有设备号不重复且 ICCID 有效
- **THEN** 系统创建所有设备和绑定记录，任务状态为"已完成"

#### Scenario: 部分导入成功

- **WHEN** CSV 中部分设备号已存在或部分 ICCID 无效
- **THEN** 系统只导入有效的行，记录跳过和失败的详情，任务状态为"已完成"

#### Scenario: ICCID 不存在

- **WHEN** CSV 中某行的 ICCID 在系统中不存在
- **THEN** 该行导入失败，记录失败原因"ICCID 不存在"

#### Scenario: ICCID 已绑定其他设备

- **WHEN** CSV 中某行的 ICCID 已绑定到其他设备
- **THEN** 该行导入失败，记录失败原因"ICCID 已绑定其他设备"

#### Scenario: 设备号重复

- **WHEN** CSV 中某行的设备号在系统中已存在
- **THEN** 该行被跳过，记录跳过原因"设备号已存在"

---

### Requirement: 设备导入任务列表查询

系统 SHALL 提供设备导入任务列表查询功能，仅平台用户可操作。

**API 端点**: `GET /api/admin/devices/import/tasks`

**查询条件**:
- `status`（可选）: 任务状态 1-4
- `batch_no`（可选）: 批次号，模糊匹配
- `start_time`（可选）: 创建时间起始
- `end_time`（可选）: 创建时间结束

**分页**:
- 默认每页 20 条，最大每页 100 条

**响应字段**:
- `id`: 任务 ID
- `task_no`: 任务编号
- `status`: 任务状态
- `status_text`: 任务状态文本
- `batch_no`: 批次号
- `file_name`: 文件名
- `total_count`: 总数
- `success_count`: 成功数
- `skip_count`: 跳过数
- `fail_count`: 失败数
- `started_at`: 开始时间
- `completed_at`: 完成时间
- `error_message`: 错误信息
- `created_at`: 创建时间

**权限**: 仅平台用户

#### Scenario: 查询导入任务列表

- **WHEN** 平台管理员查询导入任务列表
- **THEN** 系统返回所有导入任务，按创建时间倒序排列

#### Scenario: 按状态筛选任务

- **WHEN** 平台管理员查询状态为 3（已完成）的任务
- **THEN** 系统只返回已完成的任务

#### Scenario: 代理尝试查询导入任务

- **WHEN** 代理用户尝试查询导入任务
- **THEN** 系统返回 403 错误，提示"无权限执行此操作"

---

### Requirement: 设备导入任务详情查询

系统 SHALL 提供设备导入任务详情查询功能，包含跳过和失败记录的详细信息。

**API 端点**: `GET /api/admin/devices/import/tasks/:id`

**响应字段**:
- 包含任务列表的所有字段
- `skipped_items`: 跳过记录详情列表
  - `line`: 行号
  - `device_no`: 设备号
  - `reason`: 跳过原因
- `failed_items`: 失败记录详情列表
  - `line`: 行号
  - `device_no`: 设备号
  - `reason`: 失败原因

**权限**: 仅平台用户

#### Scenario: 查询导入任务详情

- **WHEN** 平台管理员查询导入任务详情（ID=1）
- **THEN** 系统返回任务的完整信息，包括跳过和失败记录详情

#### Scenario: 查询不存在的任务

- **WHEN** 平台管理员查询不存在的任务（ID=999）
- **THEN** 系统返回 404 错误，提示"导入任务不存在"
