feat: 资产标识符标准化、资产历史订单查询及导入虚拟号强制验证
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>
This commit is contained in:
2026-04-07 17:39:36 +08:00
parent 7e489a19fb
commit 80c6f6c756
69 changed files with 3039 additions and 851 deletions

View File

@@ -0,0 +1,69 @@
## MODIFIED Requirements
### Requirement: 统一资产解析入口
系统 SHALL 提供统一的资产查找接口,通过任意标识符定位卡或设备,并返回该资产的中等聚合信息。
**API 端点**: `GET /api/admin/assets/resolve/:identifier`
**查找顺序(更新后)**:
1. **主路径**:查 `tb_asset_identifier` WHERE identifier = ? → 命中则得到 asset_type + asset_id直接查对应表取完整记录
2. **Fallback 路径**(注册表未命中时):
- 先查 `tb_device`(匹配 `virtual_no = ? OR imei = ? OR sn = ?`
- 未命中则查 `tb_iot_card`(匹配 `virtual_no = ? OR iccid = ? OR msisdn = ?`
3. 两条路径均未命中 → 返回 HTTP 404
**数据权限规则**:
- 代理用户:只能查看 `shop_id` 在自己及下级店铺范围内的资产
- 平台用户SuperAdmin/Platform可查看所有资产
- 企业账号:暂不支持此接口,调用时返回 HTTP 403
**响应结构AssetResolveResponse**:
*通用字段device 和 card 均有)*
- `asset_type`: 资产类型(`"device"``"card"`
- `asset_id`: 资产主键 ID
- `identifier`: 本次查询所用的标识符(原样回传)
- `virtual_no`: 虚拟号(设备/卡均使用此字段)
- `status`: 资产状态(整型)
- `asset_status`: 业务状态1-在库 2-已销售 3-已换货 4-已停用)
- `generation`: 资产世代编号
- `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``package_used_mb``package_remain_mb`: 套餐流量信息
- `device_protect_status`: 保护期状态
*绑定关系字段*
- `iccid`: 仅 card 类型时有值
- `bound_device_id``bound_device_no``bound_device_name`: 仅 card 类型且绑定设备时有值
- `bound_card_count``cards`: 仅 device 类型时有值
#### Scenario: 通过注册表主路径精确解析
- **WHEN** 管理员输入 identifier 为已存在于 `tb_asset_identifier` 的 VirtualNo 或 ICCID
- **THEN** 系统单次查询注册表命中,直接查对应表返回完整资产信息,响应时间 < 50ms
#### Scenario: Fallback 路径解析 IMEI
- **WHEN** 管理员输入 identifier 为设备 IMEI不在注册表中
- **THEN** 注册表未命中,系统 fallback 查 tb_device 的 imei 字段,找到后返回资产信息
- **THEN** 响应中 `identifier` 字段原样回传该 IMEI 值
#### Scenario: Fallback 路径解析 MSISDN
- **WHEN** 管理员输入 identifier 为 IoT 卡的手机号MSISDN
- **THEN** 注册表未命中fallback 查 tb_iot_card 的 msisdn 字段
- **THEN** 若存在多张卡的 MSISDN 相同返回第一条匹配记录MSISDN 非唯一,存在歧义,记录 warn 日志)
#### Scenario: 标识符完全不存在
- **WHEN** 管理员输入的 identifier 在注册表和 fallback 均未找到
- **THEN** 返回 HTTP 404错误消息"资产不存在"