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,54 @@
## 1. 前置确认
- [x] 1.1 确认提案一asset-identifier-standardization已完成`tb_order.asset_identifier` 字段存在B 端资产路由已统一为 `:identifier`
## 2. 数据层ExchangeOrder Store 扩展
- [x] 2.1 在 `internal/store/postgres/exchange_order_store.go` 新增 `FindByNewAssetID(ctx, assetType, assetID) (*model.ExchangeOrder, error)` 方法:查询 `WHERE new_asset_id = ? AND new_asset_type = ?`,返回该资产的换货来源记录(找不到则返回 nil表示无前代
## 3. DTO 层:新增响应结构
- [x] 3.1 在 `internal/model/dto/asset_dto.go` 新增以下结构体:
- `AssetOrdersRequest`:路径参数 `identifier`query 参数 `page``page_size``include_previous`
- `AssetOrderItem`单条订单项order_no、order_type、payment_status、payment_status_text、total_amount、payment_method、paid_at、generation、items、created_at
- `GenerationOrders`单代订单块generation、identifier、asset_type、total、page、page_size、items
- `PreviousGenerationOrders`前代订单块generation、identifier、asset_type、exchange_no、exchanged_at、total、items
- `AssetOrdersResponse`完整响应current_generation、previous_generations、truncated
## 4. 服务层GetOrders 方法
- [x] 4.1 在 `internal/service/asset/service.go` 新增 `GetOrders(ctx, identifier, page, pageSize, includePrevious bool) (*dto.AssetOrdersResponse, error)` 方法:
- 调用 `Resolve()` 得到 asset_type 和 asset_id
- 查询本代订单:`orderStore.ListByAssetIdentifier(ctx, identifier, page, pageSize)`
-`includePrevious=true`:循环调用 `exchangeOrderStore.FindByNewAssetID()` 追溯换货链(最多 10 代);每代查询前代 `old_asset_identifier` 对应的订单(最多 20 条,无分页)
- 组装 `AssetOrdersResponse` 返回
- [x] 4.2 在 `internal/store/postgres/order_store.go` 新增 `ListByAssetIdentifier(ctx, identifier, page, pageSize) ([]*model.Order, int64, error)` 方法:查询 `WHERE asset_identifier = ?`,分页,倒序
## 5. Handler 层:新增 Orders Handler
- [x] 5.1 在 `internal/handler/admin/asset.go` 新增 `Orders(c *fiber.Ctx) error` handler
- 从路径参数读取 `identifier`;从 query 读取 `page``page_size``include_previous`
- 调用 `assetService.GetOrders()`
- 返回 `response.Success(c, result)`
- 注释:`// Orders 查询资产历史订单(支持跨代追溯)` + `// GET /api/admin/assets/:identifier/orders`
## 6. 路由层:注册新路由
- [x] 6.1 在 `internal/routes/asset.go` 注册:`assets.Get("/:identifier/orders", h.Asset.Orders)`(注意路由顺序,避免与 `/resolve/:identifier` 冲突)
## 7. 文档生成器更新
- [x] 7.1 在 `cmd/api/docs.go``cmd/gendocs/main.go``handlers` 结构体中确认 `AssetHandler` 已包含新的 `Orders` 方法(检查是否需要更新 handler 注册)
- [x] 7.2 执行 `go run cmd/gendocs/main.go` 重新生成 OpenAPI 文档,验证新接口体现
## 8. 验证
- [x] 8.1 构建验证:`go build ./...` 无编译错误
- [x] 8.2 LSP 诊断:对所有修改文件运行 lsp_diagnostics无错误/警告
- [x] 8.3 接口验证(使用 PostgreSQL MCP + curl
- 查询有订单的资产:`GET /api/admin/assets/:identifier/orders` 返回正确订单列表,含 generation 字段
- 查询无订单资产:返回 `items: [], total: 0`
- 查询不存在的 identifier返回 404
- 查询换货资产(带 include_previous=true`previous_generations` 包含前代订单,附带 exchange_no 和 exchanged_at
- 查询无前代的资产(带 include_previous=true`previous_generations` 为空数组
- 代理查询无权限资产:返回 403