# Capability: 客户端资产信息

## ADDED Requirements

### Requirement: B1 资产基本信息查询接口

系统 SHALL 提供 `GET /api/c/v1/asset/info?identifier=xxx`，并且 MUST 要求个人客户认证（C 端 Token）。接口 MUST 复用 `asset.Service.Resolve()` 解析标识符，并在调用时使用 `gorm.SkipDataPermission(ctx)` 以绕过 shop_id 数据权限过滤。请求参数 MUST 包含 `identifier`（ICCID、虚拟号、设备号之一）。响应体 SHALL 返回 `asset_type`、`asset_id`、`identifier`、`virtual_no`、`status`、`real_name_status`、`carrier`、`generation`、`wallet_balance`。当存在当前主套餐时，响应体 MUST 额外返回 `current_package`、`current_package_usage_id`、`current_package_activated_at`、`current_package_expires_at` 以及当前套餐流量字段。错误码/消息 MUST 至少包含：`INVALID_PARAM/参数错误`、`FORBIDDEN/无权限操作该资产或资源不存在`、`ASSET_NOT_FOUND/资产不存在`。

#### Scenario: 个人客户查询已绑定资产
- **WHEN** 客户携带有效 Token 调用 `GET /api/c/v1/asset/info?identifier=8986xxxx` 且资产已绑定到本人
- **THEN** 系统返回 200，包含资产基础信息与当前 generation

#### Scenario: 当前主套餐返回开始时间和过期时间
- **GIVEN** 该资产存在一条生效中的主套餐使用记录
- **WHEN** 客户调用 `GET /api/c/v1/asset/info`
- **THEN** 响应体中的 `current_package_activated_at` 等于该主套餐的 `activated_at`
- **AND** 响应体中的 `current_package_expires_at` 等于该主套餐的 `expires_at`

---

### Requirement: B2 可购买套餐列表接口

系统 SHALL 提供 `GET /api/c/v1/asset/packages?identifier=xxx`，并且 MUST 要求个人客户认证。接口 MUST 在归属校验通过后返回可购买套餐列表。价格规则 MUST 为：代理渠道取 `allocation.retail_price`，平台渠道取 `Package.SuggestedRetailPrice`。过滤规则 MUST 同时满足：`Package.status=1`、`shelf_status` 可售、加油包前置主套餐条件成立、`retail_price >= cost_price`。结果 MUST 按展示价格升序。响应体 SHALL 包含 `packages[]`，每项至少含 `package_id`、`package_name`、`package_type`、`retail_price`、`cost_price`、`validity`、`is_addon`。错误码/消息 MUST 至少包含：`INVALID_PARAM/参数错误`、`FORBIDDEN/无权限操作该资产或资源不存在`、`PACKAGE_NOT_AVAILABLE/当前无可购买套餐`。

#### Scenario: 代理渠道价格与过滤生效
- **WHEN** 客户查询可购套餐且其销售链路为代理渠道，部分套餐存在 `retail_price < cost_price`
- **THEN** 系统仅返回可售且满足价格约束的套餐，并按价格升序输出

---

### Requirement: B3 历史套餐列表接口

系统 SHALL 提供 `GET /api/c/v1/asset/package-history?identifier=xxx&page=1&page_size=20`，并且 MUST 要求个人客户认证。接口 MUST 基于标识符解析资产并进行归属校验。查询条件 MUST 自动追加 `generation = 资产当前generation`。请求参数 SHALL 支持 `page`、`page_size`（默认 20，最大 100）。响应体 SHALL 返回 `list[]`、`total`、`page`、`page_size`，列表项复用 `dto.AssetPackageResponse` 结构。错误码/消息 MUST 至少包含：`INVALID_PARAM/参数错误`、`FORBIDDEN/无权限操作该资产或资源不存在`。

#### Scenario: 转手后历史隔离
- **WHEN** 资产已发生转手且存在历史套餐记录
- **THEN** 系统只返回当前 generation 的记录，不返回旧 generation 数据

---

### Requirement: B4 手动刷新接口

系统 SHALL 提供 `POST /api/c/v1/asset/refresh`，并且 MUST 要求个人客户认证。请求体 MUST 包含 `identifier`。当资产为卡时 MUST 调用 Gateway 刷新卡信息；当资产为设备时 MUST 先检查 Redis 冷却窗口，再对设备下卡执行批量刷新。响应体 SHALL 返回 `refresh_type`（`card`/`device`）、`accepted`、`cooldown_seconds`（设备场景）。错误码/消息 MUST 至少包含：`INVALID_PARAM/参数错误`、`FORBIDDEN/无权限操作该资产或资源不存在`、`TOO_MANY_REQUESTS/刷新过于频繁，请稍后重试`、`GATEWAY_ERROR/网关调用失败`。

#### Scenario: 设备刷新冷却拦截
- **WHEN** 客户在冷却时间内重复调用设备刷新
- **THEN** 系统返回频率限制错误并告知剩余冷却时间
