## MODIFIED Requirements

### Requirement: IoT 卡批量导入

系统 SHALL 支持通过 Excel 文件批量导入 IoT 卡。**[BREAKING]** `virtual_no` 列由可选改为必填：Excel 中 `virtual_no` 列为空的行，系统 MUST 将其记录为失败行并报告原因，不得静默跳过或将其写入数据库。

**必填列（更新后）**：
- `iccid`（必填）：ICCID，电信 19 位/其他 20 位，格式校验
- `virtual_no`（**必填，由可选改为必填**）：虚拟号，全局唯一，1-50 字符
- `msisdn`（可选）：手机号/接入号

**失败原因文本（中文）**：
- ICCID 为空：`"ICCID 不能为空"`
- ICCID 格式错误：`"ICCID 格式错误，应为19-20位数字"`
- VirtualNo 为空：`"虚拟号(virtual_no)不能为空"`（新增）
- VirtualNo 已被占用：`"虚拟号已被占用: <值>"`（已有）
- ICCID 已存在：`"ICCID 已存在: <值>"`（已有）

#### Scenario: 正常导入（ICCID 和 VirtualNo 均有值）
- **WHEN** Excel 中某行 ICCID="898600XXXXX"，virtual_no="CARD-001"
- **THEN** 导入成功，卡记录写入数据库，VirtualNo 和 ICCID 同步注册到 `tb_asset_identifier`

#### Scenario: VirtualNo 为空的行被拒绝
- **WHEN** Excel 中某行 ICCID="898600YYYYY"，virtual_no 列为空或未填写
- **THEN** 该行计入失败，失败原因为"虚拟号(virtual_no)不能为空"
- **THEN** 其他合法行继续导入，不因此行中断

#### Scenario: VirtualNo 重复被拒绝
- **WHEN** Excel 中某行的 virtual_no 与已有卡/设备的 VirtualNo 重复（跨表）
- **THEN** 该行计入失败，原因为"虚拟号已被占用: <值>"

#### Scenario: Excel 文件中无 VirtualNo 列
- **WHEN** Excel 表头中不包含 `virtual_no`/`虚拟号`/`设备号` 等可识别列名
- **THEN** 所有数据行均因 VirtualNo 为空而失败，导入结果中 `fail_count = 总行数`，`success_count = 0`
- **THEN** 返回错误提示："Excel 文件缺少 virtual_no 列，请使用最新模板"

#### Scenario: 导入任务完成后的结果报告
- **WHEN** 导入任务处理完毕
- **THEN** 结果包含：`success_count`、`fail_count`、`skip_count`、失败明细列表（含行号、原因）
- **THEN** VirtualNo 为空的失败行在明细中明确体现行号和原因
