feat: 资产套餐历史增加主子层级查询
This commit is contained in:
@@ -6,7 +6,6 @@ package asset
|
||||
import (
|
||||
"context"
|
||||
stderrors "errors"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
@@ -14,6 +13,7 @@ import (
|
||||
infraAudit "github.com/break/junhong_cmp_fiber/internal/infrastructure/audit"
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/model/dto"
|
||||
assetquery "github.com/break/junhong_cmp_fiber/internal/query/asset"
|
||||
packageexpiry "github.com/break/junhong_cmp_fiber/internal/query/packageexpiry"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
@@ -760,11 +760,9 @@ func parseGatewayTime(raw gateway.FlexString) *time.Time {
|
||||
return &t
|
||||
}
|
||||
|
||||
// GetPackages 获取资产的所有套餐列表(支持分页和状态筛选)
|
||||
// callerAccountType: 调用方账号类型,"platform" 时返回成本价(paid_amount),其他类型不返回
|
||||
// page 默认 1,pageSize 默认 50,pageSize 最大 100
|
||||
// GetPackages 获取资产套餐历史层级(支持顶层关系组分页和状态筛选)。
|
||||
// callerAccountType: 调用方账号类型,"platform" 时返回成本价(paid_amount),其他类型不返回。
|
||||
func (s *Service) GetPackages(ctx context.Context, assetType string, id uint, page, pageSize int, status *int, callerAccountType string) (*dto.AssetPackagesResult, error) {
|
||||
// 分页参数边界处理
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
@@ -778,109 +776,122 @@ func (s *Service) GetPackages(ctx context.Context, assetType string, id uint, pa
|
||||
return nil, errors.New(errors.CodeInvalidParam, "套餐状态非法")
|
||||
}
|
||||
|
||||
// assetType 对应 Store 中的 carrierType:card→iot_card, device→device
|
||||
carrierType := assetType
|
||||
if assetType == "card" {
|
||||
carrierType = "iot_card"
|
||||
}
|
||||
|
||||
usages, err := s.packageUsageStore.ListByCarrier(ctx, carrierType, id, status)
|
||||
history, err := assetquery.NewPackageHistoryQuery(s.db).List(ctx, assetquery.PackageHistoryInput{
|
||||
AssetType: assetType,
|
||||
AssetID: id,
|
||||
Status: status,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(errors.CodeInternalError, err, "查询套餐使用记录失败")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 收集所有 PackageID 并批量查询
|
||||
pkgIDSet := make(map[uint]struct{}, len(usages))
|
||||
for _, u := range usages {
|
||||
pkgIDSet[u.PackageID] = struct{}{}
|
||||
}
|
||||
pkgIDs := make([]uint, 0, len(pkgIDSet))
|
||||
for id := range pkgIDSet {
|
||||
pkgIDs = append(pkgIDs, id)
|
||||
}
|
||||
packages, pkgErr := s.packageStore.GetByIDsUnscoped(ctx, pkgIDs)
|
||||
packageIDs := collectPackageHistoryPackageIDs(history.Items)
|
||||
packages, pkgErr := s.packageStore.GetByIDsUnscoped(ctx, packageIDs)
|
||||
if pkgErr != nil {
|
||||
logger.GetAppLogger().Warn("批量查询套餐信息失败,套餐名称可能缺失",
|
||||
zap.Uints("package_ids", pkgIDs),
|
||||
zap.Uints("package_ids", packageIDs),
|
||||
zap.Error(pkgErr))
|
||||
}
|
||||
pkgMap := make(map[uint]*model.Package, len(packages))
|
||||
for _, p := range packages {
|
||||
pkgMap[p.ID] = p
|
||||
packageMap := make(map[uint]*model.Package, len(packages))
|
||||
for _, pkg := range packages {
|
||||
packageMap[pkg.ID] = pkg
|
||||
}
|
||||
|
||||
all := make([]*dto.AssetPackageResponse, 0, len(usages))
|
||||
for _, u := range usages {
|
||||
pkg := pkgMap[u.PackageID]
|
||||
metrics := u.BuildTrafficMetrics()
|
||||
pkgName := u.PackageName
|
||||
pkgType := ""
|
||||
expiryBase := ""
|
||||
if pkg != nil {
|
||||
if pkgName == "" {
|
||||
pkgName = pkg.PackageName
|
||||
}
|
||||
pkgType = pkg.PackageType
|
||||
expiryBase = pkg.ExpiryBase
|
||||
}
|
||||
var paidAmount *int64
|
||||
if callerAccountType == constants.OwnerTypePlatform {
|
||||
paidAmount = u.PaidAmount
|
||||
}
|
||||
item := &dto.AssetPackageResponse{
|
||||
PackageUsageID: u.ID,
|
||||
PackageID: u.PackageID,
|
||||
PackageName: pkgName,
|
||||
PackageType: pkgType,
|
||||
ExpiryBase: expiryBase,
|
||||
OrderID: u.OrderID,
|
||||
OrderNo: u.OrderNo,
|
||||
RefundID: u.RefundID,
|
||||
RefundNo: u.RefundNo,
|
||||
UsageType: u.UsageType,
|
||||
Status: u.Status,
|
||||
StatusName: packageStatusName(u.Status),
|
||||
RealTotalMB: metrics.RealTotalMB,
|
||||
RealUsedMB: metrics.RealUsedMB,
|
||||
VirtualTotalMB: metrics.VirtualTotalMB,
|
||||
VirtualUsedMB: metrics.VirtualUsedMB,
|
||||
ReductionPct: metrics.ReductionPct,
|
||||
EnableVirtualData: u.EnableVirtualDataSnapshot,
|
||||
ActivatedAt: u.ActivatedAt,
|
||||
ExpiresAt: u.ExpiresAt,
|
||||
MasterUsageID: u.MasterUsageID,
|
||||
Priority: u.Priority,
|
||||
PaidAmount: paidAmount,
|
||||
RetailAmount: u.RetailAmount,
|
||||
CreatedAt: u.CreatedAt,
|
||||
}
|
||||
all = append(all, item)
|
||||
items := make([]*dto.AssetPackageHistoryNode, 0, len(history.Items))
|
||||
for _, node := range history.Items {
|
||||
items = append(items, buildAssetPackageHistoryNode(node, packageMap, callerAccountType))
|
||||
}
|
||||
|
||||
// 按 created_at DESC 排序
|
||||
sort.Slice(all, func(i, j int) bool {
|
||||
return all[i].CreatedAt.After(all[j].CreatedAt)
|
||||
})
|
||||
|
||||
total := int64(len(all))
|
||||
offset := (page - 1) * pageSize
|
||||
end := offset + pageSize
|
||||
if offset >= len(all) {
|
||||
offset = len(all)
|
||||
}
|
||||
if end > len(all) {
|
||||
end = len(all)
|
||||
}
|
||||
items := all[offset:end]
|
||||
|
||||
return &dto.AssetPackagesResult{
|
||||
Total: total,
|
||||
Total: history.Total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
Items: items,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func collectPackageHistoryPackageIDs(items []*assetquery.PackageHistoryNode) []uint {
|
||||
ids := make([]uint, 0)
|
||||
seen := make(map[uint]struct{})
|
||||
var collect func(*assetquery.PackageHistoryNode)
|
||||
collect = func(node *assetquery.PackageHistoryNode) {
|
||||
if node == nil || node.Usage == nil {
|
||||
return
|
||||
}
|
||||
if _, exists := seen[node.Usage.PackageID]; !exists {
|
||||
seen[node.Usage.PackageID] = struct{}{}
|
||||
ids = append(ids, node.Usage.PackageID)
|
||||
}
|
||||
for _, child := range node.Children {
|
||||
collect(child)
|
||||
}
|
||||
}
|
||||
for _, item := range items {
|
||||
collect(item)
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
func buildAssetPackageHistoryNode(node *assetquery.PackageHistoryNode, packageMap map[uint]*model.Package, callerAccountType string) *dto.AssetPackageHistoryNode {
|
||||
usage := node.Usage
|
||||
pkg := packageMap[usage.PackageID]
|
||||
metrics := usage.BuildTrafficMetrics()
|
||||
packageName := usage.PackageName
|
||||
packageType := ""
|
||||
expiryBase := ""
|
||||
if pkg != nil {
|
||||
if packageName == "" {
|
||||
packageName = pkg.PackageName
|
||||
}
|
||||
packageType = pkg.PackageType
|
||||
expiryBase = pkg.ExpiryBase
|
||||
}
|
||||
var paidAmount *int64
|
||||
if callerAccountType == constants.OwnerTypePlatform {
|
||||
paidAmount = usage.PaidAmount
|
||||
}
|
||||
|
||||
item := &dto.AssetPackageHistoryNode{
|
||||
PackageUsageID: usage.ID,
|
||||
PackageID: usage.PackageID,
|
||||
PackageName: packageName,
|
||||
PackageType: packageType,
|
||||
ExpiryBase: expiryBase,
|
||||
OrderID: usage.OrderID,
|
||||
OrderNo: usage.OrderNo,
|
||||
RefundID: usage.RefundID,
|
||||
RefundNo: usage.RefundNo,
|
||||
UsageType: usage.UsageType,
|
||||
Status: usage.Status,
|
||||
StatusName: packageStatusName(usage.Status),
|
||||
RealTotalMB: metrics.RealTotalMB,
|
||||
RealUsedMB: metrics.RealUsedMB,
|
||||
VirtualTotalMB: metrics.VirtualTotalMB,
|
||||
VirtualUsedMB: metrics.VirtualUsedMB,
|
||||
ReductionPct: metrics.ReductionPct,
|
||||
EnableVirtualData: usage.EnableVirtualDataSnapshot,
|
||||
ActivatedAt: usage.ActivatedAt,
|
||||
ExpiresAt: usage.ExpiresAt,
|
||||
MasterUsageID: usage.MasterUsageID,
|
||||
Priority: usage.Priority,
|
||||
PaidAmount: paidAmount,
|
||||
RetailAmount: usage.RetailAmount,
|
||||
CreatedAt: usage.CreatedAt,
|
||||
Children: make([]*dto.AssetPackageHistoryNode, 0, len(node.Children)),
|
||||
}
|
||||
if node.RelationshipStatus != "" {
|
||||
item.RelationshipStatus = node.RelationshipStatus
|
||||
item.RelationshipStatusName = "关联主套餐缺失"
|
||||
}
|
||||
for _, child := range node.Children {
|
||||
item.Children = append(item.Children, buildAssetPackageHistoryNode(child, packageMap, callerAccountType))
|
||||
}
|
||||
item.ExpandByDefault = len(item.Children) > 0
|
||||
return item
|
||||
}
|
||||
|
||||
// GetCurrentPackage 获取资产当前生效的主套餐
|
||||
// callerAccountType: 调用方账号类型,"platform" 时返回成本价(paid_amount),其他类型不返回
|
||||
func (s *Service) GetCurrentPackage(ctx context.Context, assetType string, id uint, callerAccountType string) (*dto.AssetPackageResponse, error) {
|
||||
|
||||
Reference in New Issue
Block a user