## MODIFIED Requirements

### Requirement: 设备批量导入

系统 SHALL 提供设备批量导入功能，通过 Excel 文件导入设备并自动绑定卡，按固定列位置读取，表头行内容不影响解析结果，仅平台用户可操作。

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

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

**Excel 格式**:
- **文件格式**: 仅支持 `.xlsx` (Excel 2007+)
- **Sheet**: 读取第一个sheet，或优先读取名为"导入数据"的sheet
- **表头行**: 第1行固定为表头，永远跳过，内容不限（中文、英文均可）
- **列位置（固定，不可变）**:
  ```
  第1列（索引0）: 虚拟号（必填，全局唯一）
  第2列（索引1）: 设备名称（可选）
  第3列（索引2）: 设备型号（可选）
  第4列（索引3）: 设备类型（可选）
  第5列（索引4）: IMEI（可选）
  第6列（索引5）: 制造商（可选）
  第7列（索引6）: 最大SIM槽数（可选，默认4，范围1-4）
  第8列（索引7）: 卡1 ICCID（可选）
  第9列（索引8）: 卡2 ICCID（可选）
  第10列（索引9）: 卡3 ICCID（可选）
  第11列（索引10）: 卡4 ICCID（可选）
  ```
- **列格式**: 所有列应设置为文本格式（避免数字被转为科学记数法）

**失败原因文本**:
- VirtualNo 为空：`"设备虚拟号(virtual_no)不能为空"`
- MaxSimSlots 越界：`"最大SIM槽数必须在1-4之间"`

**导入规则**:
- 按列索引取值，不识别列名，第一行永远跳过
- 若整行目标列皆为空，视为空行跳过，不计入 total，不计入失败
- MaxSimSlots 为空或 0 回填默认值 4；非空且不在 [1,4] 记录为失败
- 导入的设备 shop_id = NULL（平台库存）
- 导入的设备 status = 1（在库）
- 设备号重复则该行跳过
- ICCID 必须已存在于系统中（先导入卡，再导入设备）
- ICCID 不存在则该行失败
- ICCID 已绑定其他设备则该行失败
- 导入通过异步任务处理，立即返回任务 ID

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

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

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

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

#### Scenario: 中文表头正常导入

- **GIVEN** Excel 文件第1行表头为 `虚拟号 | 设备名称 | 设备型号 | 设备类型 | IMEI | 制造商 | 最大SIM槽数 | 卡1 | 卡2 | 卡3 | 卡4`
- **WHEN** 系统解析该 Excel 文件
- **THEN** 系统跳过第1行，从第2行开始按列位置解析数据

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

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

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

- **WHEN** 平台管理员上传非 Excel 格式(.xlsx)的文件
- **THEN** 系统创建任务但处理失败，任务状态为"失败"，错误信息为"不支持的文件格式 .csv，请上传Excel文件(.xlsx)"

#### Scenario: Excel结构错误

- **WHEN** 平台管理员上传的Excel文件无工作表或无数据行
- **THEN** 系统创建任务但处理失败，任务状态为"失败"，记录相应错误信息

#### Scenario: VirtualNo 为空的行记录为失败

- **WHEN** Excel 中某行第1列（虚拟号）为空
- **THEN** 该行计入失败（`fail_count++`），失败原因为"设备虚拟号(virtual_no)不能为空"
- **THEN** 其他合法行继续导入，不因此行中断

#### Scenario: ICCID 不存在

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

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

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

#### Scenario: 设备号重复

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