## ADDED Requirements

### Requirement: 统一资产解析入口

系统 SHALL 提供统一的资产查找接口，通过任意标识符定位卡或设备，并返回该资产的中等聚合信息。

**API 端点**: `GET /api/admin/assets/resolve/:identifier`

**查找顺序**:
1. 先在 `tb_device` 表查找（匹配 `virtual_no = ? OR imei = ? OR sn = ?`）
2. 未命中则在 `tb_iot_card` 表查找（匹配 `virtual_no = ? OR iccid = ? OR msisdn = ?`）
3. 两表均未命中 → 返回 HTTP 404
4. 找到后应用数据权限过滤，无权限 → 返回 HTTP 403

**数据权限规则**:
- 代理用户：只能查看 `shop_id` 在自己及下级店铺范围内的资产
- 平台用户（SuperAdmin/Platform）：可查看所有资产
- 企业账号：暂不支持此接口，调用时返回 HTTP 403

**响应结构（AssetResolveResponse）**:

*通用字段（device 和 card 均有）*:
- `asset_type`: 资产类型（`"device"` 或 `"card"`）
- `asset_id`: 资产主键 ID
- `virtual_no`: 虚拟号（设备/卡均使用此字段）
- `status`: 资产状态（整型）
- `batch_no`: 批次号
- `shop_id`: 所属店铺 ID（平台库存时为空）
- `shop_name`: 所属店铺名称
- `series_id`: 套餐系列 ID（未绑定时为空）
- `series_name`: 套餐系列名称
- `first_commission_paid`: 一次性佣金是否已发放
- `accumulated_recharge`: 累计充值金额（分）
- `activated_at`: 激活时间（未激活时为空）
- `created_at`: 创建时间
- `updated_at`: 更新时间

*状态与套餐字段（device 和 card 均有）*:
- `real_name_status`: 实名状态（整型）
- `current_package`: 当前套餐名称（无套餐时返回空字符串）
- `package_total_mb`: 真总流量，即 RealDataMB（无套餐时返回 0）
- `package_virtual_mb`: 虚总流量/停机阈值，即 VirtualDataMB（无套餐时返回 0）
- `package_used_mb`: 客户端展示已使用流量（经虚流量换算，见流量计算规则）
- `package_remain_mb`: 客户端展示剩余流量
- `device_protect_status`: 保护期状态（`"none"` / `"stop"` / `"start"`）；card 类型时若绑定的设备有保护期也返回该设备的保护期状态

*绑定关系字段*:
- `iccid`: 仅 card 类型时有值，供前端调用停复机接口使用
- `bound_device_id`: 仅 card 类型且卡绑定了设备时有值
- `bound_device_no`: 绑定设备的虚拟号
- `bound_device_name`: 绑定设备的名称
- `bound_card_count`: 仅 device 类型时有值，绑定卡的总数量
- `cards`: 仅 device 类型时有值，所有绑定卡列表（含未实名、已停用）

*设备专属档案字段（asset_type=device 时有值，card 类型时为空/零值）*:
- `device_name`: 设备名称
- `imei`: IMEI
- `sn`: 序列号
- `device_model`: 设备型号
- `device_type`: 设备类型
- `max_sim_slots`: 最大插槽数
- `manufacturer`: 制造商

*卡专属档案字段（asset_type=card 时有值，device 类型时为空/零值）*:
- `carrier_id`: 运营商 ID
- `carrier_type`: 运营商类型（CMCC/CUCC/CTCC/CBN）
- `carrier_name`: 运营商名称
- `msisdn`: 卡接入号
- `imsi`: IMSI
- `card_category`: 卡业务类型（normal/industry）
- `supplier`: 供应商
- `activation_status`: 激活状态（0-未激活 1-已激活）
- `enable_polling`: 是否参与轮询

**DeviceCardInfo 结构**:
- `iot_card_id`: 卡 ID
- `iccid`: ICCID
- `virtual_no`: 卡的虚拟号
- `real_name_status`: 实名状态
- `network_status`: 网络状态
- `current_month_usage_mb`: 本月已用流量（来自持久化缓存字段）
- `last_sync_at`: 最后与 Gateway 同步时间

**流量展示计算规则**:
- `package_used_mb = current_month_usage_mb × virtual_ratio`
- `package_remain_mb = package_total_mb - package_used_mb`
- 当 `enable_virtual_data = false` 时，`virtual_ratio = 1.0`（无换算）
- 设备级套餐：`current_month_usage_mb` 为所有绑定卡本月用量之和

**特殊情况处理**:
- 卡绑定的设备已被软删除：视为独立卡，不填充绑定信息
- `cards` 列表包含所有状态的绑定卡，不过滤未实名或已停用的卡

#### Scenario: 通过 ICCID 找到卡

- **WHEN** 管理员调用 `GET /api/admin/assets/resolve/89860123456789012345`，ICCID 匹配到一张独立卡
- **THEN** 系统返回 `asset_type="card"`，包含该卡的虚拟号、状态、套餐流量信息，`bound_device_id` 为空

#### Scenario: 通过虚拟号找到设备

- **WHEN** 管理员调用 `GET /api/admin/assets/resolve/GPS-001`，设备表中 `virtual_no = "GPS-001"` 存在
- **THEN** 系统返回 `asset_type="device"`，包含该设备的绑定卡列表（DeviceCardInfo 数组），`bound_card_count` 为绑定卡总数

#### Scenario: 标识符同时命中设备和卡（设备优先）

- **WHEN** `GPS-001` 在 device 表和 iot_card 表均有匹配（virtual_no 相同）
- **THEN** 系统返回设备信息（device 优先），不返回卡信息

#### Scenario: 标识符未命中任何资产

- **WHEN** 管理员查询不存在的标识符 `UNKNOWN-999`
- **THEN** 系统返回 HTTP 404

#### Scenario: 代理用户查询无权限的资产

- **WHEN** 代理用户（shop_id=10）查询属于 shop_id=99（非下级）的设备
- **THEN** 系统返回 HTTP 403，明确提示无权限

#### Scenario: 企业账号调用 resolve

- **WHEN** 企业账号调用 `GET /api/admin/assets/resolve/:identifier`
- **THEN** 系统返回 HTTP 403，提示企业账号暂不支持此接口

#### Scenario: 卡绑定了有停机保护期的设备

- **WHEN** 管理员通过 ICCID 查询某张卡，该卡绑定的设备当前有 stop 保护期
- **THEN** 响应中 `device_protect_status = "stop"`，反映所属设备的保护期状态

#### Scenario: 设备无当前生效套餐

- **WHEN** 管理员查询一台没有购买任何套餐的设备
- **THEN** `current_package = ""`，`package_total_mb = 0`，`package_used_mb = 0`，`package_remain_mb = 0`
