package app import ( "context" "strings" "git.5iot.com/iotlcm-public/iotlcm-app/internal/consts" "git.5iot.com/iotlcm-public/iotlcm-app/internal/dao" "git.5iot.com/iotlcm-public/iotlcm-app/internal/model/entity" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/os/gtime" ) type appRenewPackageContext struct { Card *appCardQueryRow Package *entity.TSimFlowPackageInfo PackageCode string ExpectedCurrentPackageCode string Subscription *entity.TSimCardBindPkg LatestBind *entity.TSimCardBindPkg } type appRenewTargetContext struct { Package *entity.TSimFlowPackageInfo Price *appRenewPriceSnapshot Durations []int } // resolveRenewPackageContext 统一解析“当前可续费套餐”。 // 当前套餐只用于锁定卡片和读取 renewable_package_codes,续费目标套餐由后续步骤单独解析。 func (s *sAppCard) resolveRenewPackageContext(ctx context.Context, card *appCardQueryRow, subscriptionNo string) (*appRenewPackageContext, error) { if card == nil || strings.TrimSpace(card.Iccid) == "" { return nil, gerror.New("卡信息不存在或无权续费") } currentPackageCode := appCardCurrentPackageCode(card) renewCtx := &appRenewPackageContext{ Card: card, ExpectedCurrentPackageCode: currentPackageCode, } subscriptionNo = strings.TrimSpace(subscriptionNo) if subscriptionNo != "" { bind, err := s.loadRenewSubscription(ctx, card, subscriptionNo) if err != nil { return nil, err } if currentPackageCode != "" && strings.TrimSpace(bind.PackageCode) != currentPackageCode { return nil, gerror.New("订购记录不是当前卡片套餐,请刷新后重试") } renewCtx.Subscription = bind } latestBind, err := s.loadLatestRenewBind(ctx, card) if err != nil { return nil, err } renewCtx.LatestBind = latestBind if err = appValidateRenewLatestBind(card, currentPackageCode, latestBind); err != nil { return nil, err } pkg, err := s.findRenewPackageOrNil(ctx, card.TenantId, currentPackageCode) if err != nil { return nil, err } if pkg == nil { return nil, gerror.Newf("当前卡片套餐编码 %s 未配置套餐信息", currentPackageCode) } renewCtx.Package = pkg renewCtx.PackageCode = pkg.PackageCode return renewCtx, nil } func (s *sAppCard) resolveRenewTargetPackages(ctx context.Context, user *entity.SysUser, renewCtx *appRenewPackageContext, requestedPackageCode string) ([]string, []*appRenewTargetContext, *appRenewTargetContext, error) { if renewCtx == nil || renewCtx.Card == nil || renewCtx.Package == nil { return nil, nil, nil, gerror.New("当前套餐信息不完整") } codes := appChangePackageCodes(renewCtx.Package.RenewablePackageCodes) if len(codes) == 0 { return nil, nil, nil, gerror.New("当前套餐未配置可续费套餐") } requestedPackageCode = appRenewRequestedTargetPackageCode(renewCtx, requestedPackageCode, codes) packages, err := s.loadRenewPackagesByCodes(ctx, renewCtx.Card.TenantId, codes) if err != nil { return nil, nil, nil, err } targets := make([]*appRenewTargetContext, 0, len(codes)) var selected *appRenewTargetContext for _, code := range codes { pkg := packages[strings.TrimSpace(code)] if pkg == nil { continue } durations, err := appRenewableDurationYearOptions(pkg) if err != nil { return nil, nil, nil, gerror.Newf("续费套餐%s:%v", pkg.PackageName, err) } price, err := s.resolveRenewUnitPrice(ctx, user, renewCtx.Card, pkg) if err != nil { return nil, nil, nil, err } if price.UnitPrice < 0 { return nil, nil, nil, gerror.Newf("续费套餐%s价格配置不正确", pkg.PackageName) } target := &appRenewTargetContext{ Package: pkg, Price: price, Durations: durations, } targets = append(targets, target) if requestedPackageCode != "" && strings.TrimSpace(pkg.PackageCode) == requestedPackageCode { selected = target } } if len(targets) == 0 { return nil, nil, nil, gerror.New("当前套餐可续费套餐不存在或未配置续费年限") } if requestedPackageCode != "" && selected == nil { return nil, nil, nil, gerror.New("续费套餐不存在") } if selected == nil { selected = targets[0] } return codes, targets, selected, nil } func (s *sAppCard) loadRenewPackagesByCodes(ctx context.Context, tenantId string, codes []string) (map[string]*entity.TSimFlowPackageInfo, error) { result := make(map[string]*entity.TSimFlowPackageInfo, len(codes)) codes = appNormalizePackageCodes(codes) if len(codes) == 0 { return result, nil } var rows []*entity.TSimFlowPackageInfo err := dao.TSimFlowPackageInfo.Ctx(ctx). Where(dao.TSimFlowPackageInfo.Columns().TenantId, strings.TrimSpace(tenantId)). WhereIn(dao.TSimFlowPackageInfo.Columns().PackageCode, codes). Scan(&rows) if err != nil { return nil, gerror.Wrap(err, "查询可续费套餐列表失败") } for _, row := range rows { if row != nil && strings.TrimSpace(row.PackageCode) != "" { result[strings.TrimSpace(row.PackageCode)] = row } } return result, nil } func appRenewRequestedTargetPackageCode(renewCtx *appRenewPackageContext, requestedPackageCode string, renewableCodes []string) string { requestedPackageCode = strings.TrimSpace(requestedPackageCode) if requestedPackageCode == "" { return "" } if appContainsString(renewableCodes, requestedPackageCode) { return requestedPackageCode } if renewCtx != nil && strings.TrimSpace(renewCtx.PackageCode) == requestedPackageCode { return "" } return requestedPackageCode } func appContainsString(values []string, target string) bool { target = strings.TrimSpace(target) if target == "" { return false } for _, value := range values { if strings.TrimSpace(value) == target { return true } } return false } func appValidateRenewLatestBind(card *appCardQueryRow, currentPackageCode string, latestBind *entity.TSimCardBindPkg) error { if card == nil { return gerror.New("卡信息不存在或无权续费") } currentPackageCode = strings.TrimSpace(currentPackageCode) if currentPackageCode == "" || card.PackageStatus == consts.PackageStatusNone { return gerror.Newf("ICCID没有当前套餐,不能续费:%s", card.Iccid) } if latestBind == nil || latestBind.Id == 0 { return gerror.Newf("ICCID没有订购记录,不能续费:%s", card.Iccid) } if strings.TrimSpace(latestBind.PackageCode) != currentPackageCode { return gerror.Newf("ICCID当前套餐与最新订购记录不一致,不能续费:%s", card.Iccid) } baseExpireAt := appRenewBaseExpireTimeFromRow(card, latestBind) if baseExpireAt == nil || baseExpireAt.IsZero() { return gerror.Newf("ICCID最新订购记录没有预计到期时间,不能续费:%s", card.Iccid) } if appRenewExpireAfterAllowedWindow(baseExpireAt) { return gerror.New("距离到期超过6个月,不能续费") } return nil } func (s *sAppCard) loadRenewSubscription(ctx context.Context, card *appCardQueryRow, subscriptionNo string) (*entity.TSimCardBindPkg, error) { var bind *entity.TSimCardBindPkg err := dao.TSimCardBindPkg.Ctx(ctx). Where(dao.TSimCardBindPkg.Columns().SubscriptionNo, strings.TrimSpace(subscriptionNo)). Limit(1). Scan(&bind) if err != nil { return nil, gerror.Wrap(err, "查询订购记录失败") } if bind == nil || bind.Id == 0 || strings.TrimSpace(bind.Iccid) != strings.TrimSpace(card.Iccid) { return nil, gerror.New("订购记录不存在或不属于当前卡片") } return bind, nil } func (s *sAppCard) loadLatestRenewBind(ctx context.Context, card *appCardQueryRow) (*entity.TSimCardBindPkg, error) { var bind *entity.TSimCardBindPkg err := dao.TSimCardBindPkg.Ctx(ctx). Where(dao.TSimCardBindPkg.Columns().Iccid, card.Iccid). OrderDesc(dao.TSimCardBindPkg.Columns().ExpectedExpireTime). OrderDesc(dao.TSimCardBindPkg.Columns().Id). Limit(1). Scan(&bind) if err != nil { return nil, gerror.Wrap(err, "查询最新订购记录失败") } if bind == nil || bind.Id == 0 { return nil, nil } return bind, nil } func (s *sAppCard) findRenewPackageOrNil(ctx context.Context, tenantId string, packageCode string) (*entity.TSimFlowPackageInfo, error) { packageCode = strings.TrimSpace(packageCode) if packageCode == "" { return nil, nil } var pkg *entity.TSimFlowPackageInfo err := dao.TSimFlowPackageInfo.Ctx(ctx). Where(dao.TSimFlowPackageInfo.Columns().TenantId, strings.TrimSpace(tenantId)). Where(dao.TSimFlowPackageInfo.Columns().PackageCode, packageCode). Limit(1). Scan(&pkg) if err != nil { return nil, gerror.Wrap(err, "查询续费套餐失败") } if pkg == nil || pkg.Id == 0 { return nil, nil } return pkg, nil } func (renewCtx *appRenewPackageContext) expireAt() *gtime.Time { if renewCtx == nil { return nil } expireAt := appRenewBaseExpireTimeFromRow(renewCtx.Card, renewCtx.LatestBind) if renewCtx.Subscription != nil && renewCtx.Subscription.ExpectedExpireTime != nil && !renewCtx.Subscription.ExpectedExpireTime.IsZero() && (expireAt == nil || renewCtx.Subscription.ExpectedExpireTime.After(expireAt)) { return renewCtx.Subscription.ExpectedExpireTime } return expireAt }