### Requirement: 查询资产本代历史订单 系统 SHALL 提供接口,让管理员查询某资产本代(当前世代)的全部历史订单,支持分页。 **API 端点**:`GET /api/admin/assets/:identifier/orders` **请求参数**: - `:identifier`(路径):资产标识符(ICCID 或 VirtualNo),必填 - `page`(query):页码,默认 1 - `page_size`(query):每页数量,默认 20,最大 100 - `include_previous`(query):是否包含前代订单,布尔值,默认 false **权限规则**: - 代理用户:只能查看数据权限范围内资产的订单 - 平台/超管:可查看所有资产订单 - 企业账号:不支持此接口(返回 403) #### Scenario: 查询本代订单(默认) - **WHEN** 管理员请求 `GET /api/admin/assets/DEV-001/orders` - **THEN** 返回该资产(当前世代)的订单列表,按创建时间倒序,支持分页 - **THEN** 响应中每条订单包含 `generation` 字段,值为资产当前世代编号 #### Scenario: 资产无订单 - **WHEN** 管理员查询一个从未购买过套餐的资产 - **THEN** 返回 `{ items: [], total: 0, page: 1 }`,不返回错误 #### Scenario: identifier 不存在 - **WHEN** 请求的 identifier 无法解析到任何资产 - **THEN** 返回 HTTP 404,错误消息"资产不存在" #### Scenario: 代理查询无权限资产 - **WHEN** 代理用户请求不属于其数据权限范围的资产订单 - **THEN** 返回 HTTP 403,错误消息"无权限操作该资源或资源不存在" ### Requirement: 查询资产跨代历史订单(含前代) 当 `include_previous=true` 时,系统 SHALL 通过换货链追溯前代资产,返回前代订单,并在响应中区分世代来源。 **追溯逻辑**: 1. 通过 `ExchangeOrder.new_asset_id` 逆向查找当前资产的换货来源 2. 得到前代的 `old_asset_identifier`,查询该标识符的订单 3. 递归追溯,最多向前 10 代(安全上限) **响应结构(AssetOrdersResponse)**: ```json { "current_generation": { "generation": 2, "identifier": "DEV-001", "asset_type": "device", "total": 5, "page": 1, "page_size": 20, "items": [ ...订单列表... ] }, "previous_generations": [ { "generation": 1, "identifier": "DEV-OLD-001", "asset_type": "device", "exchange_no": "EXC20260101XXXXXX", "exchanged_at": "2026-01-01T00:00:00Z", "total": 3, "items": [ ...前代订单列表(最近20条)... ] } ], "truncated": false } ``` **每条订单项(AssetOrderItem)包含**: - `order_no`:订单号 - `order_type`:订单类型(single_card / device) - `payment_status`:支付状态 - `payment_status_text`:支付状态文本 - `total_amount`:订单金额(分) - `payment_method`:支付方式 - `paid_at`:支付时间(可空) - `generation`:订单所属资产世代 - `items`:套餐明细列表 - `created_at`:订单创建时间 #### Scenario: 查询换货后资产的全代际订单 - **WHEN** 管理员请求 `GET /api/admin/assets/DEV-001/orders?include_previous=true`,DEV-001 是换货后的新设备(第2代),原设备为 DEV-OLD-001(第1代) - **THEN** `current_generation` 包含 DEV-001 本代的订单(generation=2) - **THEN** `previous_generations[0]` 包含 DEV-OLD-001 的订单(generation=1),附带换货单号和换货时间 - **THEN** `truncated=false`(未超出追溯上限) #### Scenario: 资产本身就是第一代(无前代) - **WHEN** 管理员请求带 `include_previous=true`,但该资产从未经过换货 - **THEN** `previous_generations` 为空数组 `[]` - **THEN** `current_generation` 正常返回本代订单 #### Scenario: 换货链超过追溯上限 - **WHEN** 换货链深度超过 10 代 - **THEN** 追溯在第 10 代截断,`truncated=true` - **THEN** 已追溯到的前代数据正常返回 #### Scenario: 前代订单分页 - **WHEN** 请求带 `include_previous=true` - **THEN** 分页参数(page/page_size)只对 `current_generation` 的订单生效 - **THEN** 前代订单每代最多返回 20 条(不支持前代内分页) #### Scenario: 无 include_previous 时响应不含前代字段 - **WHEN** 管理员请求不带 `include_previous=true`(或传 false) - **THEN** 响应结构中 `previous_generations` 字段为 null 或不返回,节省带宽