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

@@ -4,17 +4,47 @@
### Requirement: D1 创建套餐购买订单接口
系统 SHALL 提供 `POST /api/c/v1/orders/create`,并且 MUST 要求个人客户认证。请求体 MUST 包含 `identifier``package_ids[]``app_type`。接口流程 MUST 按顺序执行:归属校验 → 套餐校验(含加油包前置)→ 实名校验 → OpenID 查询 → 幂等检查 → 强充检查 → 分流创建。实名不满足时 MUST 返回 `NEED_REALNAME`。OpenID 缺失时 MUST 返回 `OPENID_NOT_FOUND`。幂等 MUST 使用 Redis 业务键 + 分布式锁。分流规则 MUST 为:
系统 SHALL 提供 `POST /api/c/v1/orders/create`,并且 MUST 要求个人客户认证。**[BREAKING]** 请求体改为使用资产标识符identifier废弃 `iot_card_id`/`device_id` 主键字段和 `order_type` 字段。请求体 MUST 包含 `identifier``package_ids[]``payment_method`。接口流程 MUST 按顺序执行:归属校验 → 套餐校验(含加油包前置)→ 实名校验 → OpenID 查询 → 幂等检查 → 强充检查 → 分流创建。实名不满足时 MUST 返回 `NEED_REALNAME`。OpenID 缺失时 MUST 返回 `OPENID_NOT_FOUND`。幂等 MUST 使用 Redis 业务键 + 分布式锁。分流规则 MUST 为:
- 无强充:创建套餐订单并返回 `order_type="package"``order``pay_config`
- 需强充:创建充值单并返回 `order_type="recharge"``recharge``pay_config``linked_package_info`
**客户端订单创建请求CreateOrderRequest**
```json
{
"identifier": "string资产标识符ICCID 或 VirtualNo必填1-50字符",
"package_ids": "[uint](套餐 ID 列表必填1-10 个)",
"payment_method": "stringwallet|wechat|alipay必填"
}
```
**废弃字段**
- `iot_card_id`(原单卡购买时必填)
- `device_id`(原设备购买时必填)
- `order_type`(改为系统根据 identifier 解析结果自动填入)
响应体 MUST 包含前端可直接渲染字段。错误码/消息 MUST 至少包含:`INVALID_PARAM/参数错误``FORBIDDEN/无权限操作该资产或资源不存在``NEED_REALNAME/该套餐需实名认证后购买``OPENID_NOT_FOUND/未找到微信授权信息,请先完成授权``IDEMPOTENT_CONFLICT/请求处理中,请勿重复提交``PACKAGE_NOT_AVAILABLE/套餐不可购买`
#### Scenario: 命中强充返回 recharge 结构
- **WHEN** 客户购买套餐触发强充要求
- **THEN** 系统返回 `order_type="recharge"`,包含充值单与关联套餐信息
#### Scenario: 个人客户使用 VirtualNo 购买设备套餐
- **WHEN** 个人客户发送 `{ identifier: "DEV-001", package_ids: [5], payment_method: "wechat" }`
- **THEN** 系统解析 identifier 为设备,创建设备购买订单,微信支付流程正常触发
#### Scenario: 个人客户使用 ICCID 购买单卡套餐
- **WHEN** 个人客户发送 `{ identifier: "898600XXXXX", package_ids: [3], payment_method: "wallet" }`
- **THEN** 系统解析为独立 IoT 卡(`is_standalone = true`),创建单卡购买订单
#### Scenario: 个人客户尝试为绑定设备的卡购买套餐
- **WHEN** 个人客户发送 identifier 对应一张 `is_standalone = false` 的卡
- **THEN** 系统返回错误"该卡已绑定设备,请前往设备页面购买套餐"
#### Scenario: 个人客户只能操作自己绑定的资产
- **WHEN** 个人客户发送的 identifier 对应的资产不属于该客户
- **THEN** 系统返回 HTTP 403"无权限操作该资源"
---
### Requirement: D2 套餐订单列表接口