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

@@ -6,6 +6,7 @@ import "time"
type AssetResolveResponse struct {
AssetType string `json:"asset_type" description:"资产类型card 或 device"`
AssetID uint `json:"asset_id" description:"资产数据库ID"`
Identifier string `json:"identifier,omitempty" description:"解析时使用的标识符"`
VirtualNo string `json:"virtual_no" description:"虚拟号"`
Status int `json:"status" description:"资产状态"`
BatchNo string `json:"batch_no" description:"批次号"`
@@ -118,7 +119,12 @@ type AssetResolveRequest struct {
Identifier string `path:"identifier" description:"资产标识符(虚拟号/ICCID/IMEI/SN/MSISDN" required:"true"`
}
// AssetTypeIDRequest 资产类型+ID请求路径参数
// AssetIdentifierRequest 资产标识符路径参数请求
type AssetIdentifierRequest struct {
Identifier string `path:"identifier" description:"资产标识符ICCID 或 VirtualNo" required:"true"`
}
// AssetTypeIDRequest 资产类型+ID请求路径参数保留用于钱包等向后兼容场景
type AssetTypeIDRequest struct {
AssetType string `path:"asset_type" description:"资产类型card 或 device" required:"true"`
ID uint `path:"id" description:"资产ID" required:"true"`
@@ -197,12 +203,69 @@ type DeviceGatewayInfo struct {
IMSI *string `json:"imsi,omitempty" description:"IMSI用户标识码"`
}
// AssetOrdersRequest 资产历史订单查询请求
type AssetOrdersRequest struct {
Identifier string `path:"identifier" description:"资产标识符ICCID 或 VirtualNo" required:"true"`
Page int `query:"page" validate:"omitempty,min=1" minimum:"1" description:"页码默认1"`
PageSize int `query:"page_size" validate:"omitempty,min=1,max=100" minimum:"1" maximum:"100" description:"每页条数默认20最大100"`
IncludePrevious bool `query:"include_previous" description:"是否包含前代订单默认false"`
}
// AssetOrderItemDetail 订单中的套餐明细
type AssetOrderItemDetail struct {
PackageName string `json:"package_name" description:"套餐名称快照"`
Quantity int `json:"quantity" description:"数量"`
UnitPrice int64 `json:"unit_price" description:"单价(分)"`
Amount int64 `json:"amount" description:"小计金额(分)"`
}
// AssetOrderItem 资产历史订单条目
type AssetOrderItem struct {
OrderNo string `json:"order_no" description:"订单号"`
OrderType string `json:"order_type" description:"订单类型single_card/device"`
PaymentStatus int `json:"payment_status" description:"支付状态1待支付 2已支付 3已取消 4已退款"`
PaymentStatusText string `json:"payment_status_text" description:"支付状态文本"`
TotalAmount int64 `json:"total_amount" description:"订单金额(分)"`
PaymentMethod string `json:"payment_method" description:"支付方式wallet/wechat/alipay/offline"`
PaidAt *time.Time `json:"paid_at,omitempty" description:"支付时间"`
Generation int `json:"generation" description:"订单所属资产世代编号"`
Items []*AssetOrderItemDetail `json:"items" description:"套餐明细列表"`
CreatedAt time.Time `json:"created_at" description:"订单创建时间"`
}
// GenerationOrders 本代订单块(支持分页)
type GenerationOrders struct {
Generation int `json:"generation" description:"世代编号"`
Identifier string `json:"identifier" description:"资产标识符"`
AssetType string `json:"asset_type" description:"资产类型card 或 device"`
Total int64 `json:"total" description:"总订单数"`
Page int `json:"page" description:"当前页码"`
PageSize int `json:"page_size" description:"每页条数"`
Items []*AssetOrderItem `json:"items" description:"订单列表"`
}
// PreviousGenerationOrders 前代订单块最多20条不分页
type PreviousGenerationOrders struct {
Generation int `json:"generation" description:"前代世代编号"`
Identifier string `json:"identifier" description:"前代资产标识符"`
AssetType string `json:"asset_type" description:"前代资产类型card 或 device"`
ExchangeNo string `json:"exchange_no" description:"换货单号"`
ExchangedAt time.Time `json:"exchanged_at" description:"换货完成时间"`
Total int64 `json:"total" description:"前代总订单数"`
Items []*AssetOrderItem `json:"items" description:"前代订单列表最多20条"`
}
// AssetOrdersResponse 资产历史订单完整响应
type AssetOrdersResponse struct {
CurrentGeneration *GenerationOrders `json:"current_generation" description:"本代订单(含分页)"`
PreviousGenerations []*PreviousGenerationOrders `json:"previous_generations,omitempty" description:"前代订单列表include_previous=true时返回"`
Truncated bool `json:"truncated" description:"是否已截断换货链超过10代追溯上限时为true"`
}
// UpdateAssetPollingStatusRequest 更新资产轮询状态请求
type UpdateAssetPollingStatusRequest struct {
AssetType string `path:"asset_type" description:"资产类型 (iot_card:IoT卡, device:设备)" required:"true"`
ID uint `path:"id" description:"资产ID" required:"true"`
// EnablePolling 是否启用轮询
EnablePolling bool `json:"enable_polling" description:"是否启用轮询 (true:启用, false:禁用)"`
Identifier string `path:"identifier" description:"资产标识符ICCID 或 VirtualNo" required:"true"`
EnablePolling bool `json:"enable_polling" description:"是否启用轮询 (true:启用, false:禁用)"`
}
// UpdateAssetPollingStatusResponse 更新资产轮询状态响应