## Context

当前 IoT 卡和设备的 Excel 导入依赖列名识别逻辑（`findCardColumns` / `buildDeviceColumnIndex`）。业务侧要求改用中文表头，但设备导入的大多数列名只支持英文，导致中文表头文件解析全部失败。

同时，IoT 卡导入时 `card_category` 写死为 `normal`，无法在导入批次中指定行业卡。

## Goals / Non-Goals

**Goals:**
- Excel 导入不依赖表头内容，按固定列位置读取，支持任意语言表头
- 锁定 IoT 卡和设备导入的列顺序为业务确认版本
- IoT 卡导入支持批次级别的 `card_category`（行业卡/普通卡）

**Non-Goals:**
- 不支持列顺序自定义（固定列位置就是合约）
- 不支持每行独立设置 `card_category`（批次统一即可）
- 不修改导入的其他业务逻辑（校验规则、权限等不变）

## Decisions

### 决策1：按列位置读取，完全移除列名识别

**选择**: 直接按列索引取值，第一行永远跳过（表头行）。

**备选方案**: 在现有映射中添加中文列名别名。

**理由**: 中文别名方案需要为每个字段定义多套名称（中文/英文/变体），维护成本高且仍然脆弱（业务随时可能换叫法）。按位置读取更简单，也更明确——模板即契约，列顺序固定。

### 决策2：card_category 放在请求参数而非 Excel 列

**选择**: 在 `ImportIotCardRequest` 和 `IotCardImportTask` 中加 `card_category` 字段，不在 Excel 里加列。

**理由**: 业务现实是一批卡要么全是行业卡，要么全是普通卡，不会混批。前端用下拉选择比 Excel 里填枚举值更直观，也不影响 Excel 列顺序。

### 决策3：card_category 默认值为 normal

**选择**: 不传时默认 `normal`（普通卡）。

**理由**: 存量接口调用不需要修改，向下兼容。

## Risks / Trade-offs

- **[风险] 模板纪律性**：按位置读取意味着列顺序必须严格匹配。用户如果自行调整 Excel 列顺序，数据会静默读错（没有报错提示）。→ 缓解：提供官方模板下载，文档中明确说明列顺序不可更改。
- **[权衡] 丢失了列名容错性**：现有逻辑允许列顺序任意，改后不再支持。接受此代价，换取代码简单和中文支持。

## Migration Plan

1. 更新 `pkg/utils/excel.go`，替换解析逻辑（按列位置取值，删除 `findCardColumns` / `buildDeviceColumnIndex`）
2. 为 `tb_iot_card_import_task` 创建迁移，新增 `card_category VARCHAR(20) NOT NULL DEFAULT 'normal'` 列
3. 更新 DTO（`ImportIotCardRequest` 加 `card_category`）、model（`IotCardImportTask` 加 `CardCategory`）、service（`CreateImportTask` 将请求字段写入 task）、task handler（创建 IoT 卡时使用 `task.CardCategory` 替代常量）
4. **同步更新导入模板文件**：IoT 卡、设备的 `.xlsx` 模板列顺序需与新实现严格对齐；如模板由前端维护，需在变更交接时通知前端同步

**说明**：卡主表 `tb_iot_card` 早有 `card_category` 字段，本次迁移仅为批次级选择提供"载体列"，让 Worker 从 DB 任务行读取批次类型，而非从 Asynq 载荷传递（保持现有"载荷只带 TaskID"的设计一致性）。

## Open Questions

（无）
