错误的套餐购买限制
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m48s

This commit is contained in:
Break
2026-05-29 09:44:48 +08:00
parent 872769e69e
commit 6a3fffc46c
4 changed files with 63 additions and 40 deletions

View File

@@ -63,15 +63,15 @@ func NewClientAssetHandler(
} }
type resolvedAssetContext struct { type resolvedAssetContext struct {
CustomerID uint CustomerID uint
Identifier string Identifier string
Asset *dto.AssetResolveResponse Asset *dto.AssetResolveResponse
Generation int Generation int
WalletBalance int64 WalletBalance int64
SkipPermissionCtx context.Context SkipPermissionCtx context.Context
IsAgentChannel bool IsAgentChannel bool
SellerShopID uint SellerShopID uint
MainPackageActived bool HasAddonMainPackage bool
} }
// resolveAssetFromIdentifier 统一执行资产解析与归属校验 // resolveAssetFromIdentifier 统一执行资产解析与归属校验
@@ -232,11 +232,16 @@ func (h *ClientAssetHandler) GetAvailablePackages(c *fiber.Ctx) error {
return errors.New(errors.CodeNoAvailablePackage, "当前无可购买套餐") return errors.New(errors.CodeNoAvailablePackage, "当前无可购买套餐")
} }
packagesResult, err := h.assetService.GetPackages(resolved.SkipPermissionCtx, resolved.Asset.AssetType, resolved.Asset.AssetID, 1, 50, nil) if _, err := packagepkg.FindAttachableMainPackageForAddon(
if err != nil { h.db.WithContext(resolved.SkipPermissionCtx),
return err 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 listCtx := resolved.SkipPermissionCtx
if resolved.IsAgentChannel { if resolved.IsAgentChannel {
@@ -512,18 +517,6 @@ func collectPackageIDs(pkgs []*model.Package) []uint {
return ids 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( func buildClientPackageItem(
pkg *model.Package, pkg *model.Package,
resolved *resolvedAssetContext, resolved *resolvedAssetContext,
@@ -537,7 +530,7 @@ func buildClientPackageItem(
} }
isAddon := pkg.PackageType == constants.PackageTypeAddon isAddon := pkg.PackageType == constants.PackageTypeAddon
if isAddon && !resolved.MainPackageActived { if isAddon && !resolved.HasAddonMainPackage {
return dto.ClientPackageItem{}, false return dto.ClientPackageItem{}, false
} }

View File

@@ -2297,14 +2297,8 @@ func (s *Service) activateMainPackage(ctx context.Context, tx *gorm.DB, order *m
// activateAddonPackage 任务 8.5-8.7: 加油包激活逻辑 // 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 { 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) // 任务 8.5-8.6: 检查是否有可挂靠主套餐(待生效、未过期的生效中或已用完
var mainPackage model.PackageUsage mainPackage, err := packagepkg.FindAttachableMainPackageForAddon(tx, carrierType, carrierID, now)
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
if err == gorm.ErrRecordNotFound { if err == gorm.ErrRecordNotFound {
return errors.New(errors.CodeInvalidParam, "必须有主套餐才能购买加油包") return errors.New(errors.CodeInvalidParam, "必须有主套餐才能购买加油包")
} }

View File

@@ -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
}

View File

@@ -588,12 +588,7 @@ func (h *AutoPurchaseHandler) activateAddonPackage(
now time.Time, now time.Time,
) error { ) error {
_ = ctx _ = ctx
var mainPackage model.PackageUsage mainPackage, err := packagepkg.FindAttachableMainPackageForAddon(tx, carrierType, carrierID, now)
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
if err == gorm.ErrRecordNotFound { if err == gorm.ErrRecordNotFound {
return errors.New("必须有主套餐才能购买加油包") return errors.New("必须有主套餐才能购买加油包")
} }