实现资产预计最终到期时间

This commit is contained in:
2026-07-23 13:14:43 +09:00
parent 5dacef57fa
commit 8d65b26e96
17 changed files with 570 additions and 32 deletions

View File

@@ -13,6 +13,7 @@ import (
"github.com/break/junhong_cmp_fiber/internal/gateway"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
packageexpiry "github.com/break/junhong_cmp_fiber/internal/query/packageexpiry"
assetAuditSvc "github.com/break/junhong_cmp_fiber/internal/service/asset_audit"
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
"github.com/break/junhong_cmp_fiber/pkg/constants"
@@ -29,6 +30,11 @@ type IotCardRefresher interface {
RefreshCardDataFromGateway(ctx context.Context, iccid string) error
}
// PackageExpiryResolver 查询资产的套餐最终到期推算结果。
type PackageExpiryResolver interface {
Resolve(ctx context.Context, assetType string, assetID uint) (dto.PackageExpiryEstimate, error)
}
// Service 资产查询与操作服务
type Service struct {
db *gorm.DB
@@ -47,6 +53,12 @@ type Service struct {
gatewayClient *gateway.Client
assetIdentifierStore *postgres.AssetIdentifierStore
assetAuditService assetAuditSvc.OperationLogger
packageExpiryQuery PackageExpiryResolver
}
// SetPackageExpiryQuery 注入套餐最终到期查询,供资产详情统一投影使用。
func (s *Service) SetPackageExpiryQuery(query *packageexpiry.Query) {
s.packageExpiryQuery = query
}
// New 创建资产服务实例
@@ -85,6 +97,7 @@ func New(
gatewayClient: gatewayClient,
assetIdentifierStore: assetIdentifierStore,
assetAuditService: assetAuditService,
packageExpiryQuery: packageexpiry.NewQuery(db),
}
}
@@ -246,6 +259,9 @@ func (s *Service) buildDeviceResolveResponse(ctx context.Context, device *model.
// 查当前主套餐
s.fillPackageInfo(ctx, resp, "device", device.ID)
if err := s.fillPackageExpiryEstimate(ctx, resp, constants.AssetTypeDevice, device.ID); err != nil {
return nil, err
}
activationStatuses, err := s.deviceStore.GetActivationStatusMap(ctx, []uint{device.ID})
if err != nil {
@@ -312,6 +328,9 @@ func (s *Service) buildCardResolveResponse(ctx context.Context, card *model.IotC
// 查当前主套餐
s.fillPackageInfo(ctx, resp, "iot_card", card.ID)
if err := s.fillPackageExpiryEstimate(ctx, resp, constants.AssetTypeIotCard, card.ID); err != nil {
return nil, err
}
// 查 shop 名称
s.fillShopName(ctx, resp)
@@ -349,6 +368,19 @@ func (s *Service) fillPackageInfo(ctx context.Context, resp *dto.AssetResolveRes
resp.EnableVirtualData = usage.EnableVirtualDataSnapshot
}
// fillPackageExpiryEstimate 使用统一 Query 投影资产的最终套餐到期,不允许查询失败降级为无套餐。
func (s *Service) fillPackageExpiryEstimate(ctx context.Context, resp *dto.AssetResolveResponse, assetType string, assetID uint) error {
if s.packageExpiryQuery == nil {
return errors.New(errors.CodeInternalError, "套餐最终到期查询未初始化")
}
estimate, err := s.packageExpiryQuery.Resolve(ctx, assetType, assetID)
if err != nil {
return err
}
resp.PackageExpiryEstimate = estimate
return nil
}
// fillUsageSummary 填充当前世代流量汇总字段。
// 仅在 includeUsageSummary=true 时被调用,无套餐时返回 0.0(非 null
func (s *Service) fillUsageSummary(ctx context.Context, resp *dto.AssetResolveResponse, carrierType string, carrierID uint) {

View File

@@ -11,6 +11,7 @@ import (
"github.com/break/junhong_cmp_fiber/internal/gateway"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
packageexpiry "github.com/break/junhong_cmp_fiber/internal/query/packageexpiry"
"github.com/break/junhong_cmp_fiber/internal/store"
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
"github.com/break/junhong_cmp_fiber/pkg/constants"
@@ -20,21 +21,27 @@ import (
)
type Service struct {
db *gorm.DB
redis *redis.Client
deviceStore *postgres.DeviceStore
deviceSimBindingStore *postgres.DeviceSimBindingStore
iotCardStore *postgres.IotCardStore
shopStore *postgres.ShopStore
assetAllocationRecordStore *postgres.AssetAllocationRecordStore
shopPackageAllocationStore *postgres.ShopPackageAllocationStore
shopSeriesAllocationStore *postgres.ShopSeriesAllocationStore
packageSeriesStore *postgres.PackageSeriesStore
gatewayClient *gateway.Client
assetIdentifierStore *postgres.AssetIdentifierStore
assetAuditService AssetAuditService
enterpriseDeviceAuthStore *postgres.EnterpriseDeviceAuthorizationStore
enterpriseStore *postgres.EnterpriseStore
db *gorm.DB
redis *redis.Client
deviceStore *postgres.DeviceStore
deviceSimBindingStore *postgres.DeviceSimBindingStore
iotCardStore *postgres.IotCardStore
shopStore *postgres.ShopStore
assetAllocationRecordStore *postgres.AssetAllocationRecordStore
shopPackageAllocationStore *postgres.ShopPackageAllocationStore
shopSeriesAllocationStore *postgres.ShopSeriesAllocationStore
packageSeriesStore *postgres.PackageSeriesStore
gatewayClient *gateway.Client
assetIdentifierStore *postgres.AssetIdentifierStore
assetAuditService AssetAuditService
enterpriseDeviceAuthStore *postgres.EnterpriseDeviceAuthorizationStore
enterpriseStore *postgres.EnterpriseStore
packageExpiryQuery *packageexpiry.Query
}
// SetPackageExpiryQuery 注入套餐最终到期查询,供列表使用批量投影。
func (s *Service) SetPackageExpiryQuery(query *packageexpiry.Query) {
s.packageExpiryQuery = query
}
func New(
@@ -55,21 +62,22 @@ func New(
enterpriseStore *postgres.EnterpriseStore,
) *Service {
return &Service{
db: db,
redis: rds,
deviceStore: deviceStore,
deviceSimBindingStore: deviceSimBindingStore,
iotCardStore: iotCardStore,
shopStore: shopStore,
assetAllocationRecordStore: assetAllocationRecordStore,
shopPackageAllocationStore: shopPackageAllocationStore,
shopSeriesAllocationStore: shopSeriesAllocationStore,
packageSeriesStore: packageSeriesStore,
gatewayClient: gatewayClient,
assetIdentifierStore: assetIdentifierStore,
assetAuditService: assetAuditService,
enterpriseDeviceAuthStore: enterpriseDeviceAuthStore,
enterpriseStore: enterpriseStore,
db: db,
redis: rds,
deviceStore: deviceStore,
deviceSimBindingStore: deviceSimBindingStore,
iotCardStore: iotCardStore,
shopStore: shopStore,
assetAllocationRecordStore: assetAllocationRecordStore,
shopPackageAllocationStore: shopPackageAllocationStore,
shopSeriesAllocationStore: shopSeriesAllocationStore,
packageSeriesStore: packageSeriesStore,
gatewayClient: gatewayClient,
assetIdentifierStore: assetIdentifierStore,
assetAuditService: assetAuditService,
enterpriseDeviceAuthStore: enterpriseDeviceAuthStore,
enterpriseStore: enterpriseStore,
packageExpiryQuery: packageexpiry.NewQuery(db),
}
}
@@ -150,10 +158,14 @@ func (s *Service) List(ctx context.Context, req *dto.ListDeviceRequest) (*dto.Li
if err != nil {
return nil, err
}
deviceIDs := s.extractDeviceIDs(devices)
expiryEstimates, err := s.packageExpiryQuery.ResolveBatch(ctx, constants.AssetTypeDevice, deviceIDs)
if err != nil {
return nil, err
}
shopMap := s.loadShopData(ctx, devices)
seriesMap := s.loadSeriesNames(ctx, devices)
deviceIDs := s.extractDeviceIDs(devices)
bindingCounts, err := s.getBindingCounts(ctx, deviceIDs)
if err != nil {
return nil, err
@@ -184,6 +196,7 @@ func (s *Service) List(ctx context.Context, req *dto.ListDeviceRequest) (*dto.Li
list := make([]*dto.DeviceResponse, 0, len(devices))
for _, device := range devices {
item := s.toDeviceResponse(device, shopMap, seriesMap, bindingCounts, activationStatuses)
item.PackageExpiryEstimate = expiryEstimates[device.ID]
if eid, ok := deviceEnterpriseMap[device.ID]; ok {
item.AuthorizedEnterpriseID = &eid
item.AuthorizedEnterpriseName = enterpriseNameMap[eid]

View File

@@ -9,6 +9,7 @@ import (
"github.com/break/junhong_cmp_fiber/internal/gateway"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
packageexpiry "github.com/break/junhong_cmp_fiber/internal/query/packageexpiry"
assetAuditSvc "github.com/break/junhong_cmp_fiber/internal/service/asset_audit"
"github.com/break/junhong_cmp_fiber/internal/store"
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
@@ -73,6 +74,12 @@ type Service struct {
assetAuditService AssetAuditService
enterpriseCardAuthStore *postgres.EnterpriseCardAuthorizationStore
enterpriseStore *postgres.EnterpriseStore
packageExpiryQuery *packageexpiry.Query
}
// SetPackageExpiryQuery 注入套餐最终到期查询,供列表使用批量投影。
func (s *Service) SetPackageExpiryQuery(query *packageexpiry.Query) {
s.packageExpiryQuery = query
}
func New(
@@ -98,6 +105,7 @@ func New(
gatewayClient: gatewayClient,
logger: logger,
assetAuditService: assetAuditService,
packageExpiryQuery: packageexpiry.NewQuery(db),
}
}
@@ -278,6 +286,14 @@ func (s *Service) ListStandalone(ctx context.Context, req *dto.ListStandaloneIot
if err != nil {
return nil, err
}
cardIDs := make([]uint, 0, len(cards))
for _, card := range cards {
cardIDs = append(cardIDs, card.ID)
}
expiryEstimates, err := s.packageExpiryQuery.ResolveBatch(ctx, constants.AssetTypeIotCard, cardIDs)
if err != nil {
return nil, err
}
shopMap := s.loadShopNames(ctx, cards)
//TODO 这里不对,现在已经快照了,这里如果还这样处理明显是浪费的
@@ -318,6 +334,7 @@ func (s *Service) ListStandalone(ctx context.Context, req *dto.ListStandaloneIot
list := make([]*dto.StandaloneIotCardResponse, 0, len(cards))
for _, card := range cards {
item := s.toStandaloneResponse(card, shopMap, seriesMap)
item.PackageExpiryEstimate = expiryEstimates[card.ID]
if eid, ok := cardAuthMap[card.ID]; ok {
item.AuthorizedEnterpriseID = &eid
item.AuthorizedEnterpriseName = enterpriseNameMap[eid]