From 6a3fffc46c32ea181c27c1222168af5d2d8586c7 Mon Sep 17 00:00:00 2001 From: Break Date: Fri, 29 May 2026 09:44:48 +0800 Subject: [PATCH] =?UTF-8?q?=E9=94=99=E8=AF=AF=E7=9A=84=E5=A5=97=E9=A4=90?= =?UTF-8?q?=E8=B4=AD=E4=B9=B0=E9=99=90=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/handler/app/client_asset.go | 45 ++++++++----------- internal/service/order/service.go | 10 +---- .../service/package/addon_main_package.go | 41 +++++++++++++++++ internal/task/auto_purchase.go | 7 +-- 4 files changed, 63 insertions(+), 40 deletions(-) create mode 100644 internal/service/package/addon_main_package.go diff --git a/internal/handler/app/client_asset.go b/internal/handler/app/client_asset.go index b2afead..0f340fe 100644 --- a/internal/handler/app/client_asset.go +++ b/internal/handler/app/client_asset.go @@ -63,15 +63,15 @@ func NewClientAssetHandler( } type resolvedAssetContext struct { - CustomerID uint - Identifier string - Asset *dto.AssetResolveResponse - Generation int - WalletBalance int64 - SkipPermissionCtx context.Context - IsAgentChannel bool - SellerShopID uint - MainPackageActived bool + CustomerID uint + Identifier string + Asset *dto.AssetResolveResponse + Generation int + WalletBalance int64 + SkipPermissionCtx context.Context + IsAgentChannel bool + SellerShopID uint + HasAddonMainPackage bool } // resolveAssetFromIdentifier 统一执行资产解析与归属校验 @@ -232,11 +232,16 @@ func (h *ClientAssetHandler) GetAvailablePackages(c *fiber.Ctx) error { return errors.New(errors.CodeNoAvailablePackage, "当前无可购买套餐") } - packagesResult, err := h.assetService.GetPackages(resolved.SkipPermissionCtx, resolved.Asset.AssetType, resolved.Asset.AssetID, 1, 50, nil) - if err != nil { - return err + if _, err := packagepkg.FindAttachableMainPackageForAddon( + h.db.WithContext(resolved.SkipPermissionCtx), + resolved.Asset.AssetType, + resolved.Asset.AssetID, + time.Now(), + ); err == nil { + resolved.HasAddonMainPackage = true + } else if err != gorm.ErrRecordNotFound { + return errors.Wrap(errors.CodeDatabaseError, err, "查询主套餐失败") } - resolved.MainPackageActived = hasActiveMainPackage(packagesResult.Items) listCtx := resolved.SkipPermissionCtx if resolved.IsAgentChannel { @@ -512,18 +517,6 @@ func collectPackageIDs(pkgs []*model.Package) []uint { return ids } -func hasActiveMainPackage(usages []*dto.AssetPackageResponse) bool { - for _, usage := range usages { - if usage == nil { - continue - } - if usage.PackageType == constants.PackageTypeFormal && usage.Status == constants.PackageUsageStatusActive { - return true - } - } - return false -} - func buildClientPackageItem( pkg *model.Package, resolved *resolvedAssetContext, @@ -537,7 +530,7 @@ func buildClientPackageItem( } isAddon := pkg.PackageType == constants.PackageTypeAddon - if isAddon && !resolved.MainPackageActived { + if isAddon && !resolved.HasAddonMainPackage { return dto.ClientPackageItem{}, false } diff --git a/internal/service/order/service.go b/internal/service/order/service.go index 27bcac3..38d9030 100644 --- a/internal/service/order/service.go +++ b/internal/service/order/service.go @@ -2297,14 +2297,8 @@ func (s *Service) activateMainPackage(ctx context.Context, tx *gorm.DB, order *m // activateAddonPackage 任务 8.5-8.7: 加油包激活逻辑 func (s *Service) activateAddonPackage(ctx context.Context, tx *gorm.DB, order *model.Order, pkg *model.Package, carrierType string, carrierID uint, now time.Time) error { - // 任务 8.5-8.6: 检查是否有主套餐(status IN (0,1)) - var mainPackage model.PackageUsage - err := tx.Where("status IN ?", []int{constants.PackageUsageStatusPending, constants.PackageUsageStatusActive}). - Where("master_usage_id IS NULL"). - Where(carrierType+"_id = ?", carrierID). - Order("priority ASC"). - First(&mainPackage).Error - + // 任务 8.5-8.6: 检查是否有可挂靠主套餐(待生效、未过期的生效中或已用完) + mainPackage, err := packagepkg.FindAttachableMainPackageForAddon(tx, carrierType, carrierID, now) if err == gorm.ErrRecordNotFound { return errors.New(errors.CodeInvalidParam, "必须有主套餐才能购买加油包") } diff --git a/internal/service/package/addon_main_package.go b/internal/service/package/addon_main_package.go new file mode 100644 index 0000000..fd6a105 --- /dev/null +++ b/internal/service/package/addon_main_package.go @@ -0,0 +1,41 @@ +package packagepkg + +import ( + "time" + + "github.com/break/junhong_cmp_fiber/internal/model" + "github.com/break/junhong_cmp_fiber/pkg/constants" + "gorm.io/gorm" +) + +// FindAttachableMainPackageForAddon 查询加油包可挂靠的主套餐。 +// 已用完但未过期的主套餐仍可挂靠;已过期、已失效的主套餐不可挂靠。 +func FindAttachableMainPackageForAddon(tx *gorm.DB, carrierType string, carrierID uint, now time.Time) (*model.PackageUsage, error) { + var mainPackage model.PackageUsage + query := tx.Model(&model.PackageUsage{}). + Where("master_usage_id IS NULL"). + Where( + "(status = ? OR (status IN ? AND (expires_at IS NULL OR expires_at > ?)))", + constants.PackageUsageStatusPending, + []int{ + constants.PackageUsageStatusActive, + constants.PackageUsageStatusDepleted, + }, + now, + ). + Order("priority ASC") + + switch carrierType { + case constants.AssetWalletResourceTypeIotCard, "card": + query = query.Where("iot_card_id = ?", carrierID) + case constants.AssetWalletResourceTypeDevice: + query = query.Where("device_id = ?", carrierID) + default: + return nil, gorm.ErrRecordNotFound + } + + if err := query.First(&mainPackage).Error; err != nil { + return nil, err + } + return &mainPackage, nil +} diff --git a/internal/task/auto_purchase.go b/internal/task/auto_purchase.go index bd6db45..b78da15 100644 --- a/internal/task/auto_purchase.go +++ b/internal/task/auto_purchase.go @@ -588,12 +588,7 @@ func (h *AutoPurchaseHandler) activateAddonPackage( now time.Time, ) error { _ = ctx - var mainPackage model.PackageUsage - err := tx.Where("status IN ?", []int{constants.PackageUsageStatusPending, constants.PackageUsageStatusActive}). - Where("master_usage_id IS NULL"). - Where(carrierType+"_id = ?", carrierID). - Order("priority ASC"). - First(&mainPackage).Error + mainPackage, err := packagepkg.FindAttachableMainPackageForAddon(tx, carrierType, carrierID, now) if err == gorm.ErrRecordNotFound { return errors.New("必须有主套餐才能购买加油包") }