# 批量换货脚本

该脚本读取两列 CSV，逐行调用 `POST /api/admin/exchanges` 执行换货。脚本固定使用：

- `flow_type=direct`：只支持直接换货，接口创建换货单后会立即完成换货。
- `migrate_data=true`：必须执行全量数据迁移，不能通过参数关闭。

全量迁移由现有换货接口在同一事务中执行，包括钱包余额、套餐使用记录、累计充值字段和资产标签。脚本仅使用 Python 标准库，不需要安装依赖。

默认只预演，必须增加 `--execute` 才会真实换货。

## CSV 格式

首行表头可选，第一列填写旧资产标识，第二列填写新资产标识：

```csv
old_identifier,new_identifier
89860000000000000001,89860000000000000101
89860000000000000002,89860000000000000102
```

支持表头：

- 英文：`old_identifier,new_identifier` 或 `old_asset_identifier,new_asset_identifier`
- 中文：`旧资产标识,新资产标识` 或 `旧资产,新资产`

旧、新资产标识均使用换货接口已有的识别规则：物联网卡支持 ICCID、接入号、虚拟号；设备支持虚拟号、IMEI、SN。

脚本会在请求前拦截：

- 列数不是两列，或任一列为空。
- 同一行新旧资产相同。
- 旧资产重复，或新资产重复。
- 同一资产在本批次中既作为旧资产又作为新资产。该情况会受到执行顺序影响，因此整批终止。

## 预演

物联网卡示例：

```bash
python3 scripts/batch_exchange/batch_exchange.py \
  --base-url https://cmp-api.example.com \
  --csv scripts/batch_exchange/exchanges.example.csv \
  --asset-type iot_card
```

设备批次将 `--asset-type` 改为 `device`。同一个 CSV 批次只能使用一种资产类型；新资产必须与旧资产类型一致，否则接口会拒绝该行。

预演只校验 CSV 并展示最多五条请求示例，不需要 Token，也不会调用接口。输出中会明确显示 `direct` 和 `migrate_data=true`。

## 使用已有 Token 执行

推荐通过环境变量传递 Token，避免进入命令历史：

```bash
JUNHONG_ADMIN_TOKEN='<后台Access Token>' \
python3 scripts/batch_exchange/batch_exchange.py \
  --base-url https://cmp-api.example.com \
  --csv /path/to/exchanges.csv \
  --asset-type iot_card \
  --execute
```

## 使用账号自动登录后执行

未提供 Token 时，可以使用后台账号调用 `/api/admin/login` 自动获取 Token：

```bash
JUNHONG_ADMIN_USERNAME='<后台账号>' \
JUNHONG_ADMIN_PASSWORD='<后台密码>' \
python3 scripts/batch_exchange/batch_exchange.py \
  --base-url https://cmp-api.example.com \
  --csv /path/to/exchanges.csv \
  --asset-type iot_card \
  --execute
```

也可以使用 `--token`、`--username`、`--password` 参数。密码优先通过环境变量或交互输入，避免保存在 Shell 历史中。

## 换货原因和备注

脚本默认使用 `批量直接换货` 作为换货原因。可按整批覆盖原因和备注：

```bash
python3 scripts/batch_exchange/batch_exchange.py \
  --base-url https://cmp-api.example.com \
  --csv /path/to/exchanges.csv \
  --asset-type iot_card \
  --exchange-reason '故障卡批量换货' \
  --remark '2026年7月批次' \
  --execute
```

## 结果文件

默认在输入 CSV 同目录生成：

```text
原文件名_换货结果_YYYYMMDD_HHMMSS.csv
```

结果包含新旧资产标识、成功或失败状态、HTTP 状态码、业务错误码、错误消息、换货单 ID、换货单号、迁移完成状态和迁移余额。每处理一条都会立即刷新文件，中途中断时已完成的结果不会丢失。

为避免覆盖历史结果，`--output` 指定的文件已经存在时脚本会直接报错。

## 其他参数和执行约束

- `--timeout`：单次请求超时秒数，默认 `30`。
- `--interval`：每次换货后的等待秒数，默认 `0.2`。
- `--execute`：显式开启真实换货。

每组资产单独调用一次接口，脚本不会自动重试 POST 请求。接口可能已经成功提交事务但客户端没有收到响应，自动重试可能造成误判；失败项应先查询换货单或资产状态，再决定是否单独重跑。

脚本不会回滚前面已经成功的行。执行前应先预演并确认完整映射；执行后根据结果 CSV 逐条核对失败项。
