305 lines
8.2 KiB
Go
305 lines
8.2 KiB
Go
package asset
|
|
|
|
import (
|
|
"context"
|
|
"sort"
|
|
|
|
"github.com/break/junhong_cmp_fiber/internal/model"
|
|
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
|
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
const relationshipStatusMasterMissing = "master_missing"
|
|
|
|
// PackageHistoryQuery 读取资产范围内的套餐使用关系。
|
|
type PackageHistoryQuery struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
// PackageHistoryInput 定义已完成入口授权后的套餐历史读取范围。
|
|
type PackageHistoryInput struct {
|
|
AssetType string
|
|
AssetID uint
|
|
Generation *int
|
|
Status *int
|
|
PackageType *string
|
|
Page int
|
|
PageSize int
|
|
}
|
|
|
|
// PackageHistoryResult 保存筛选后的顶层关系组总数和分页后的关系组。
|
|
type PackageHistoryResult struct {
|
|
Total int64
|
|
Items []*PackageHistoryNode
|
|
}
|
|
|
|
// PackageHistoryNode 保存一个套餐使用记录及其可展示关联子项。
|
|
type PackageHistoryNode struct {
|
|
Usage *model.PackageUsage
|
|
Children []*PackageHistoryNode
|
|
RelationshipStatus string
|
|
}
|
|
|
|
type packageUsagePresence struct {
|
|
ID uint
|
|
IotCardID uint
|
|
DeviceID uint
|
|
Generation int
|
|
DeletedAt gorm.DeletedAt
|
|
}
|
|
|
|
// NewPackageHistoryQuery 创建套餐历史关系查询。
|
|
func NewPackageHistoryQuery(db *gorm.DB) *PackageHistoryQuery {
|
|
return &PackageHistoryQuery{db: db}
|
|
}
|
|
|
|
// List 在既有资产和世代范围内读取完整关系,再按整组应用筛选、排序和分页。
|
|
func (q *PackageHistoryQuery) List(ctx context.Context, input PackageHistoryInput) (*PackageHistoryResult, error) {
|
|
query, err := q.baseUsageQuery(ctx, input)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var usages []*model.PackageUsage
|
|
if err := query.Find(&usages).Error; err != nil {
|
|
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询套餐历史失败")
|
|
}
|
|
|
|
usageByID := make(map[uint]*model.PackageUsage, len(usages))
|
|
for _, usage := range usages {
|
|
usageByID[usage.ID] = usage
|
|
}
|
|
|
|
missingMasterIDs := unresolvedMasterIDs(usages, usageByID)
|
|
presentMasters, err := q.lookupMasterUsagePresence(ctx, missingMasterIDs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
items := make([]*PackageHistoryNode, 0, len(usages))
|
|
nodes := make(map[uint]*PackageHistoryNode, len(usages))
|
|
for _, usage := range usages {
|
|
nodes[usage.ID] = &PackageHistoryNode{Usage: usage, Children: make([]*PackageHistoryNode, 0)}
|
|
}
|
|
for _, usage := range usages {
|
|
node := nodes[usage.ID]
|
|
if usage.MasterUsageID == nil {
|
|
items = append(items, node)
|
|
continue
|
|
}
|
|
|
|
master, inRange := nodes[*usage.MasterUsageID]
|
|
if inRange {
|
|
master.Children = append(master.Children, node)
|
|
continue
|
|
}
|
|
if _, exists := presentMasters[*usage.MasterUsageID]; exists {
|
|
return nil, errors.New(errors.CodeDatabaseError, "读取套餐历史关联失败")
|
|
}
|
|
node.RelationshipStatus = relationshipStatusMasterMissing
|
|
items = append(items, node)
|
|
}
|
|
|
|
matchingPackageIDs, err := q.matchingPackageIDs(ctx, input.PackageType, usages)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
items = filterPackageHistoryGroups(items, input.Status, matchingPackageIDs)
|
|
sortPackageHistoryGroups(items)
|
|
total := int64(len(items))
|
|
|
|
return &PackageHistoryResult{
|
|
Total: total,
|
|
Items: paginatePackageHistoryGroups(items, input.Page, input.PageSize),
|
|
}, nil
|
|
}
|
|
|
|
func (q *PackageHistoryQuery) baseUsageQuery(ctx context.Context, input PackageHistoryInput) (*gorm.DB, error) {
|
|
query := q.db.WithContext(ctx).Model(&model.PackageUsage{})
|
|
switch input.AssetType {
|
|
case "card":
|
|
query = query.Where("iot_card_id = ?", input.AssetID)
|
|
case "device":
|
|
query = query.Where("device_id = ?", input.AssetID)
|
|
default:
|
|
return nil, errors.New(errors.CodeInvalidParam, "资产类型非法")
|
|
}
|
|
if input.Generation != nil {
|
|
query = query.Where("generation = ?", *input.Generation)
|
|
}
|
|
return query, nil
|
|
}
|
|
|
|
func (q *PackageHistoryQuery) matchingPackageIDs(ctx context.Context, packageType *string, usages []*model.PackageUsage) (map[uint]struct{}, error) {
|
|
if packageType == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
usagePackageIDs := collectPackageHistoryUsagePackageIDs(usages)
|
|
result := make(map[uint]struct{})
|
|
if len(usagePackageIDs) == 0 {
|
|
return result, nil
|
|
}
|
|
|
|
var ids []uint
|
|
if err := q.db.WithContext(ctx).Model(&model.Package{}).
|
|
Where("id IN ? AND package_type = ?", usagePackageIDs, *packageType).
|
|
Pluck("id", &ids).Error; err != nil {
|
|
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询套餐类型资格失败")
|
|
}
|
|
for _, id := range ids {
|
|
result[id] = struct{}{}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func collectPackageHistoryUsagePackageIDs(usages []*model.PackageUsage) []uint {
|
|
ids := make([]uint, 0, len(usages))
|
|
seen := make(map[uint]struct{}, len(usages))
|
|
for _, usage := range usages {
|
|
if usage == nil || usage.PackageID == 0 {
|
|
continue
|
|
}
|
|
if _, exists := seen[usage.PackageID]; exists {
|
|
continue
|
|
}
|
|
seen[usage.PackageID] = struct{}{}
|
|
ids = append(ids, usage.PackageID)
|
|
}
|
|
return ids
|
|
}
|
|
|
|
func filterPackageHistoryGroups(items []*PackageHistoryNode, status *int, packageIDs map[uint]struct{}) []*PackageHistoryNode {
|
|
filtered := make([]*PackageHistoryNode, 0, len(items))
|
|
for _, item := range items {
|
|
if matchesPackageHistoryUsage(item.Usage, status, packageIDs) {
|
|
filtered = append(filtered, item)
|
|
continue
|
|
}
|
|
for _, child := range item.Children {
|
|
if matchesPackageHistoryUsage(child.Usage, status, packageIDs) {
|
|
filtered = append(filtered, item)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return filtered
|
|
}
|
|
|
|
func matchesPackageHistoryUsage(usage *model.PackageUsage, status *int, packageIDs map[uint]struct{}) bool {
|
|
if status != nil && usage.Status != *status {
|
|
return false
|
|
}
|
|
if packageIDs == nil {
|
|
return true
|
|
}
|
|
_, ok := packageIDs[usage.PackageID]
|
|
return ok
|
|
}
|
|
|
|
func sortPackageHistoryGroups(items []*PackageHistoryNode) {
|
|
for _, item := range items {
|
|
sort.Slice(item.Children, func(i, j int) bool {
|
|
return packageHistoryChildLess(item.Children[i].Usage, item.Children[j].Usage)
|
|
})
|
|
}
|
|
sort.Slice(items, func(i, j int) bool {
|
|
left := items[i].Usage
|
|
right := items[j].Usage
|
|
if left.CreatedAt.Equal(right.CreatedAt) {
|
|
return left.ID > right.ID
|
|
}
|
|
return left.CreatedAt.After(right.CreatedAt)
|
|
})
|
|
}
|
|
|
|
func packageHistoryChildLess(left, right *model.PackageUsage) bool {
|
|
leftBucket := packageHistoryChildBucket(left)
|
|
rightBucket := packageHistoryChildBucket(right)
|
|
if leftBucket != rightBucket {
|
|
return leftBucket < rightBucket
|
|
}
|
|
|
|
leftTime := left.CreatedAt
|
|
rightTime := right.CreatedAt
|
|
if leftBucket == 0 {
|
|
leftTime = *left.ActivatedAt
|
|
rightTime = *right.ActivatedAt
|
|
}
|
|
if leftTime.Equal(rightTime) {
|
|
return left.ID < right.ID
|
|
}
|
|
return leftTime.Before(rightTime)
|
|
}
|
|
|
|
func packageHistoryChildBucket(usage *model.PackageUsage) int {
|
|
if usage.Status == constants.PackageUsageStatusPending {
|
|
return 2
|
|
}
|
|
if usage.ActivatedAt != nil {
|
|
return 0
|
|
}
|
|
return 1
|
|
}
|
|
|
|
func paginatePackageHistoryGroups(items []*PackageHistoryNode, page, pageSize int) []*PackageHistoryNode {
|
|
if len(items) == 0 {
|
|
return make([]*PackageHistoryNode, 0)
|
|
}
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
if pageSize < 1 {
|
|
pageSize = 1
|
|
}
|
|
if page > (len(items)-1)/pageSize+1 {
|
|
return make([]*PackageHistoryNode, 0)
|
|
}
|
|
start := (page - 1) * pageSize
|
|
end := start + pageSize
|
|
if end > len(items) {
|
|
end = len(items)
|
|
}
|
|
return items[start:end]
|
|
}
|
|
|
|
func unresolvedMasterIDs(usages []*model.PackageUsage, usageByID map[uint]*model.PackageUsage) []uint {
|
|
ids := make([]uint, 0)
|
|
seen := make(map[uint]struct{})
|
|
for _, usage := range usages {
|
|
if usage.MasterUsageID == nil {
|
|
continue
|
|
}
|
|
masterID := *usage.MasterUsageID
|
|
if _, found := usageByID[masterID]; found {
|
|
continue
|
|
}
|
|
if _, alreadySeen := seen[masterID]; alreadySeen {
|
|
continue
|
|
}
|
|
seen[masterID] = struct{}{}
|
|
ids = append(ids, masterID)
|
|
}
|
|
return ids
|
|
}
|
|
|
|
func (q *PackageHistoryQuery) lookupMasterUsagePresence(ctx context.Context, ids []uint) (map[uint]packageUsagePresence, error) {
|
|
found := make(map[uint]packageUsagePresence, len(ids))
|
|
if len(ids) == 0 {
|
|
return found, nil
|
|
}
|
|
|
|
var records []packageUsagePresence
|
|
if err := q.db.WithContext(ctx).Unscoped().Model(&model.PackageUsage{}).
|
|
Select("id, iot_card_id, device_id, generation, deleted_at").
|
|
Where("id IN ?", ids).
|
|
Find(&records).Error; err != nil {
|
|
return nil, errors.Wrap(errors.CodeDatabaseError, err, "核对套餐主记录失败")
|
|
}
|
|
for _, record := range records {
|
|
found[record.ID] = record
|
|
}
|
|
return found, nil
|
|
}
|