Status: done

## Parent

`.scratch/iot-risk-import-voucher-remark/PRD.md`

## What to build

新增"批量失效订单套餐"导入任务，完整实现 Model → Store → Service → Handler → Route → Worker 全链路，参照现有 IoT 卡导入任务的结构。

**数据模型**（新建 `tb_order_package_invalidate_task`）：
- 参照 `tb_iot_card_import_task` 的基础字段（任务编号、状态、计数、失败/跳过明细、文件存储 key、时间戳等）
- 额外字段：`creator_name varchar(100)`、`remark text`、`voucher_keys jsonb`（存储 `[]string`，最多5个元素）

**上传接口**（创建任务）：
- 接受 CSV 文件（单列 `order_no`）
- 额外参数：`remark`（字符串，可选）和 `voucher_keys`（`[]string`，最多5个，可选）
- 写入 `creator_name` 快照

**Worker 处理逻辑**：
- 逐行读取 `order_no`
- 按 `order_no` 查询 `tb_order` 获取 `order_id`；找不到则记为失败，继续处理
- 查询 `tb_package_usage WHERE order_id = ? AND status NOT IN (3, 4)`，批量更新 `status = 4`（已失效）
- 订单存在但套餐全部已是终态（status 3 或 4）：记为成功
- 不触发退款，不调用网关，不直接停机

**列表/详情接口**：参照现有导入任务接口结构。

## Acceptance criteria

- [ ] Migration 创建 `tb_order_package_invalidate_task` 表，包含所有必要字段
- [ ] 创建任务接口：接受 CSV + remark + voucher_keys（最多5个），写入 creator_name 快照
- [ ] Worker 按行处理：`order_no` 不存在 → 失败并记录原因，继续下一行
- [ ] Worker 按行处理：订单存在且有可失效套餐 → 批量更新 `status=4` → 记为成功
- [ ] Worker 按行处理：订单存在但套餐全为终态 → 记为成功
- [ ] 处理过程中不调用退款接口、不操作网关
- [ ] 列表接口返回 `creator_name`、`remark`、任务状态和计数
- [ ] 详情接口返回失败明细（含 order_no 和原因）
- [ ] 已在路由和文档生成器中注册

## Blocked by

None - can start immediately
