# 接口变化说明：资产标识符规范化 + 历史订单 + 导入必填

> **涉及提案**：
> - `asset-identifier-standardization`（BREAKING）
> - `asset-historical-orders`（新增接口）
> - `import-mandatory-virtual-no`（行为变更）
>
> **影响范围**：B 端资产操作、设备管理、订单创建、IoT 卡 / 设备导入

---

## 1. 资产操作接口路由变更

**变更前**：路径参数为数据库主键 ID，需同时传 `asset_type`  
**变更后**：路径参数统一为资产标识符（ICCID 或 VirtualNo），系统内部自动识别资产类型

| 旧路由 | 新路由 |
|---|---|
| `GET /api/admin/assets/:asset_type/:id/realtime-status` | `GET /api/admin/assets/:identifier/realtime-status` |
| `POST /api/admin/assets/:asset_type/:id/refresh` | `POST /api/admin/assets/:identifier/refresh` |
| `GET /api/admin/assets/:asset_type/:id/packages` | `GET /api/admin/assets/:identifier/packages` |
| `GET /api/admin/assets/:asset_type/:id/current-package` | `GET /api/admin/assets/:identifier/current-package` |
| `GET /api/admin/assets/:asset_type/:id/wallet` | `GET /api/admin/assets/:identifier/wallet` |
| `GET /api/admin/assets/:asset_type/:id/wallet/transactions` | `GET /api/admin/assets/:identifier/wallet/transactions` |
| `PATCH /api/admin/assets/:asset_type/:id/polling-status` | `PATCH /api/admin/assets/:identifier/polling-status` |
| `POST /api/admin/assets/device/:device_id/stop` | `POST /api/admin/assets/:identifier/stop` |
| `POST /api/admin/assets/card/:iccid/stop` | `POST /api/admin/assets/:identifier/stop`（已合并） |
| `POST /api/admin/assets/device/:device_id/start` | `POST /api/admin/assets/:identifier/start` |
| `POST /api/admin/assets/card/:iccid/start` | `POST /api/admin/assets/:identifier/start`（已合并） |
| `PATCH /api/admin/devices/:id/deactivate` | `PATCH /api/admin/assets/:identifier/deactivate` |
| `PATCH /api/admin/iot-cards/:id/deactivate` | `PATCH /api/admin/assets/:identifier/deactivate`（已合并） |

**注意事项：**
- `:identifier` 只接受 ICCID 或 VirtualNo（全局唯一标识符）
- `stop` / `start` / `deactivate` 已合并为统一接口，系统内部按资产类型自动分支处理，前端无需区分卡和设备
- IMEI / SN / MSISDN 仍可用于 `GET /api/admin/assets/resolve/:identifier` 搜索，但不适用于操作接口

---

## 2. 设备管理接口路由变更

**变更前**：路径参数为数据库主键 ID  
**变更后**：路径参数改为 VirtualNo（虚拟号）

| 旧路由 | 新路由 |
|---|---|
| `DELETE /api/admin/devices/:id` | `DELETE /api/admin/devices/:virtual_no` |
| `GET /api/admin/devices/:id/cards` | `GET /api/admin/devices/:virtual_no/cards` |
| `POST /api/admin/devices/:id/cards` | `POST /api/admin/devices/:virtual_no/cards` |
| `DELETE /api/admin/devices/:id/cards/:cardId` | `DELETE /api/admin/devices/:virtual_no/cards/:iccid` |

**注意事项：**
- 解绑卡接口第二段路径参数从 `cardId`（数据库 ID）改为 `iccid`（ICCID 字符串）

---

## 3. 创建订单接口请求体变更

### B 端：`POST /api/admin/orders`

**请求体字段变化：**

| 字段 | 旧 | 新 | 说明 |
|---|---|---|---|
| `order_type` | 必填 | **废弃** | 系统根据 `identifier` 解析结果自动填入 |
| `iot_card_id` | 单卡购买必填 | **废弃** | 改用 `identifier` |
| `device_id` | 设备购买必填 | **废弃** | 改用 `identifier` |
| `identifier` | — | **必填** | ICCID 或 VirtualNo，最长 100 字符 |
| `package_ids` | 必填 | 不变 | — |
| `payment_method` | 必填 | 不变 | — |

**变更后请求体示例：**

```json
{
  "identifier": "8986XXXXXXXXXXXXXXXX",[前端接口变更通知.md](%E5%89%8D%E7%AB%AF%E6%8E%A5%E5%8F%A3%E5%8F%98%E6%9B%B4%E9%80%9A%E7%9F%A5.md)
  "package_ids": [5],
  "payment_method": "wallet"
}
```

### C 端：`POST /api/c/v1/orders/create`

同 B 端，废弃 `order_type` / `iot_card_id` / `device_id`，统一使用 `identifier`。C 端原本已有此字段，行为不变。

---

## 4. 订单列表接口新增过滤参数

### `GET /api/admin/orders`

新增可选查询参数：

| 参数 | 类型 | 说明 |
|---|---|---|
| `identifier` | string | 按资产标识符精确过滤，匹配订单的 `asset_identifier` 快照字段 |

**示例：**

```
GET /api/admin/orders?identifier=8986XXXXXXXXXXXXXXXX
GET /api/admin/orders?identifier=DEV-001
```

---

## 5. 响应结构新增字段

### `OrderResponse`（所有订单相关接口响应）

| 新增字段 | 类型 | 说明 |
|---|---|---|
| `asset_identifier` | string | 下单时资产的标识符快照，资产删除后仍可读 |
| `asset_type` | string | 资产类型：`single_card` 或 `device` |

### `AssetResolveResponse`（`GET /api/admin/assets/resolve/:identifier`）

| 新增字段 | 类型 | 说明 |
|---|---|---|
| `identifier` | string | 原样回传本次查询所用的标识符 |

---

## 6. 新增业务规则

### 已绑设备的卡不可单独购买套餐

- **触发条件**：IoT 卡的 `is_standalone = false`（已绑定设备）
- **影响接口**：所有套餐购买入口（B 端后台、C 端 H5）
- **返回错误**：

```json
{
  "code": 40001,
  "msg": "该卡已绑定设备，请前往设备页面购买套餐"
}
```

---

---

## 7. 新增接口：资产历史订单

> **来源提案**：`asset-historical-orders`

### `GET /api/admin/assets/:identifier/orders`

查询某资产本代历史订单，可选通过换货链追溯前代（最多10代）。

**路径参数：**

| 参数 | 说明 |
|---|---|
| `identifier` | 资产标识符（ICCID 或 VirtualNo） |

**查询参数：**

| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `page` | int | 否 | 页码，默认 1 |
| `page_size` | int | 否 | 每页条数，默认 20，最大 100 |
| `include_previous` | bool | 否 | 是否追溯换货前代，默认 false |

**响应示例：**

```json
{
  "code": 0,
  "data": {
    "current_generation": {
      "generation": 2,
      "identifier": "VNO-001",
      "asset_type": "card",
      "total": 3,
      "page": 1,
      "page_size": 20,
      "items": [
        {
          "order_no": "ORD-20250101-001",
          "order_type": "single_card",
          "payment_status": 2,
          "payment_status_text": "已支付",
          "total_amount": 9900,
          "payment_method": "wallet",
          "paid_at": "2025-01-01T10:00:00Z",
          "generation": 2,
          "items": [
            {
              "package_name": "30天1GB套餐",
              "quantity": 1,
              "unit_price": 9900,
              "amount": 9900
            }
          ],
          "created_at": "2025-01-01T09:58:00Z"
        }
      ]
    },
    "previous_generations": [
      {
        "generation": 1,
        "identifier": "VNO-000",
        "asset_type": "card",
        "exchange_no": "EX-20241201-001",
        "exchanged_at": "2024-12-01T10:00:00Z",
        "total": 1,
        "items": [...]
      }
    ],
    "truncated": false
  }
}
```

**注意事项：**
- `previous_generations` 仅在 `include_previous=true` 时返回，否则字段不存在
- `truncated: true` 表示换货链超过 10 代，已截断，前代数据不完整
- 前代订单每代最多返回 20 条（不分页）
- 依赖提案一（`asset-identifier-standardization`）中的 `Order.asset_identifier` 快照字段

---

## 8. 行为变更：导入接口 virtual_no 必填

> **来源提案**：`import-mandatory-virtual-no`（无 Schema 结构变化，仅行为变更）

### `POST /api/admin/iot-cards/import` — BREAKING

| 场景 | 旧行为 | 新行为 |
|---|---|---|
| Excel 缺少 `virtual_no` 列 | 正常导入，卡的 `virtual_no` 存空字符串 | **整批失败**，任务状态=失败，错误："Excel 文件缺少 virtual_no 列，请使用最新模板" |
| 某行 `virtual_no` 为空 | 该行导入成功 | 该行计入 `fail_count`，`failed_items` 含原因："虚拟号(virtual_no)不能为空" |
| `virtual_no` 重复 | 无变化 | 无变化 |

> IoT 卡导入 Excel 模板**必须新增 `virtual_no` 列**，否则全批失败。

### `POST /api/admin/devices/import` — 统计语义变更

| 场景 | 旧行为 | 新行为 |
|---|---|---|
| 某行 `virtual_no` 为空 | 计入 `skip_count`，无失败详情 | 计入 `fail_count`，`failed_items` 含原因："设备虚拟号(virtual_no)不能为空" |

> 前端若有"跳过了 N 行设备"的提示文案，需同步调整为失败处理逻辑。

---

## 前端迁移清单

### asset-identifier-standardization（必须改）
- [ ] 将 `/assets/:asset_type/:id/` 系列 URL 改为 `/assets/:identifier/`，传 ICCID 或 VirtualNo
- [ ] 停复机不再区分卡/设备，统一调 `/assets/:identifier/stop` 和 `/assets/:identifier/start`
- [ ] 停用不再区分 `/iot-cards/:id/deactivate` 和 `/devices/:id/deactivate`，统一调 `/assets/:identifier/deactivate`
- [ ] 设备管理接口将 `/:id` 改为 `/:virtual_no`，解绑卡将 `/:cardId` 改为 `/:iccid`
- [ ] 创建订单请求体移除 `order_type`、`iot_card_id`、`device_id`，改传 `identifier`
- [ ] 订单列表可通过 `?identifier=xxx` 过滤指定资产的历史订单
- [ ] 展示订单详情时可直接读取 `asset_identifier` 字段，无需额外查询资产

### asset-historical-orders（新功能）
- [ ] 资产详情页接入 `GET /api/admin/assets/:identifier/orders` 展示历史订单
- [ ] 换货资产可通过 `?include_previous=true` 追溯前代购买记录

### import-mandatory-virtual-no（需同步）
- [ ] 更新 IoT 卡导入 Excel 模板，必须包含 `virtual_no` 列
- [ ] 导入结果展示逻辑：设备导入"跳过行"统计移至"失败行"，并展示原因
