All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m21s
主要变更: - 新增 AssetIdentifier 模型及 Store,统一管理资产标识符(ICCID/IMEI/SN 等) - 新增迁移:asset_identifier 表、order 表新增 asset_identifier 字段、iot_card.virtual_no NOT NULL 约束 - 资产 Handler/Service/Route 全面重构,支持标识符路由查询与解析 - 新增资产历史订单查询接口,支持跨设备/卡/钱包维度的订单聚合 - 设备与物联卡导入任务强制校验虚拟号,缺失时直接拒绝 - Excel 工具函数优化,前端导入指引文档同步更新 - 归档三个 OpenSpec 提案:asset-identifier-standardization、asset-historical-orders、import-mandatory-virtual-no - 更新 OpenAPI 文档及相关 DTO Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
4.4 KiB
4.4 KiB
Requirement: 查询资产本代历史订单
系统 SHALL 提供接口,让管理员查询某资产本代(当前世代)的全部历史订单,支持分页。
API 端点:GET /api/admin/assets/:identifier/orders
请求参数:
:identifier(路径):资产标识符(ICCID 或 VirtualNo),必填page(query):页码,默认 1page_size(query):每页数量,默认 20,最大 100include_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 通过换货链追溯前代资产,返回前代订单,并在响应中区分世代来源。
追溯逻辑:
- 通过
ExchangeOrder.new_asset_id逆向查找当前资产的换货来源 - 得到前代的
old_asset_identifier,查询该标识符的订单 - 递归追溯,最多向前 10 代(安全上限)
响应结构(AssetOrdersResponse):
{
"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 或不返回,节省带宽