## MODIFIED Requirements

### Requirement: 导入任务实体定义
系统 SHALL 定义 IoT 卡导入任务(IotCardImportTask)实体，用于跟踪 IoT 卡批量导入的进度和结果。

**实体字段**:

**任务信息**:
- `id`: 任务 ID（主键，BIGINT）
- `task_no`: 任务编号（VARCHAR(50)，唯一，格式: IMP-YYYYMMDD-XXXXXX）
- `status`: 任务状态（INT，1-待处理 2-处理中 3-已完成 4-失败）

**导入参数**:
- `carrier_id`: 运营商 ID（BIGINT，必填）
- `carrier_type`: 运营商类型（VARCHAR(10)，CMCC/CUCC/CTCC/CBN）
- `batch_no`: 批次号（VARCHAR(100)，可选）
- `file_name`: 原始文件名（VARCHAR(255)，可选）

**待导入数据**:
- `card_list`: 待导入卡列表（JSONB，结构: [{iccid, msisdn}]，替代原 iccid_list）

**进度统计**:
- `total_count`: 总数（INT，CSV 文件总行数）
- `success_count`: 成功数（INT，成功导入的卡数量）
- `skip_count`: 跳过数（INT，因重复等原因跳过的数量）
- `fail_count`: 失败数（INT，因格式错误等原因失败的数量）

**结果详情**:
- `skipped_items`: 跳过记录详情（JSONB，结构: [{line, iccid, msisdn, reason}]）
- `failed_items`: 失败记录详情（JSONB，结构: [{line, iccid, msisdn, reason}]）

**时间和错误**:
- `started_at`: 开始处理时间（TIMESTAMP，可空）
- `completed_at`: 完成时间（TIMESTAMP，可空）
- `error_message`: 任务级错误信息（TEXT，可空，如文件解析失败等）

**系统字段**:
- `shop_id`: 店铺 ID（BIGINT，可空，记录发起导入的店铺）
- `created_at`: 创建时间（TIMESTAMP，自动填充）
- `updated_at`: 更新时间（TIMESTAMP，自动填充）
- `creator`: 创建人 ID（BIGINT）
- `updater`: 更新人 ID（BIGINT）

#### Scenario: 创建导入任务

- **GIVEN** 管理员上传包含 ICCID 和 MSISDN 两列的 CSV 文件
- **WHEN** 系统解析 CSV 并创建导入任务
- **THEN** 系统创建导入任务记录，`card_list` 包含 [{iccid, msisdn}] 结构，`status` 为 1（待处理）

---

### Requirement: IotCard 写入时同步填充 ICCID 双列
`processBatch()` 在初始化 `model.IotCard` 时，SHALL 调用 `utils.SplitICCID` 赋值 `ICCID19` 和 `ICCID20` 两个字段。

- `ICCID19 string` = `iccid19`（`SplitICCID` 返回值第一个，所有合法卡必填）
- `ICCID20 *string` = `iccid20`（`SplitICCID` 返回值第二个，**指针类型**；19 位卡返回 `nil`，GORM 写入 SQL NULL；禁止赋空字符串 `""`）
- 若 `SplitICCID` 返回 `iccid19 == ""`（异常长度 ICCID），不写入数据库，标记为 fail（此情况在 `ValidateICCID` 长度校验后理论上不应出现，作防御性保留）

`SplitICCID` 函数签名：
```go
func SplitICCID(iccid string) (iccid19 string, iccid20 *string)
// 19 位：返回 (iccid, nil)
// 20 位：返回 (iccid[:19], &iccid)
// 其他：返回 ("", nil)
```

#### Scenario: 导入 19 位卡时填充双列
- **WHEN** 批量导入时某张卡的 ICCID 长度为 19 位
- **THEN** 写入数据库时 `iccid_19` = ICCID 原值，`iccid_20` 为 **SQL NULL**（通过 `*string` 赋值 `nil` 实现）

#### Scenario: 导入 20 位卡时填充双列
- **WHEN** 批量导入时某张卡的 ICCID 长度为 20 位
- **THEN** 写入数据库时 `iccid_19` = ICCID 前 19 位，`iccid_20` = ICCID 原值（通过 `*string` 赋非 nil 指针实现）
