package business import ( "context" "math/big" "strings" "time" "github.com/go-kratos/kratos/v3/errors" ) var ( ErrActivationNotFound = errors.NotFound("SW_ACTIVATION_NOT_FOUND", "绑定记录不存在") ErrActivationScope = errors.Forbidden("SW_ACTIVATION_SCOPE", "无权查看该绑定记录") ErrActivationPending = errors.BadRequest("SW_ACTIVATION_PENDING", "审核通过后可查看") ErrActivationSource = errors.BadRequest("SW_ACTIVATION_SOURCE", "OA 渠道激活数据源未接入或暂不可用") ErrActivationMetrics = errors.BadRequest("SW_ACTIVATION_METRICS", "渠道或流水数据尚未接入,暂时无法查询流水明细") ErrActivationInput = errors.BadRequest("SW_ACTIVATION_INPUT", "渠道激活查询参数不正确") ) type Activation struct { ID uint64 PromoteID *uint64 PlatformName, PromoteAccount, BusierAccount, ApplicantOA, Status, AccumAmount string BindAt, UnbindAt, ReachAt *time.Time } func (a *Activation) EndAt() *time.Time { if a.Status == "reached" && a.ReachAt != nil { return a.ReachAt } return a.UnbindAt } type ActivationAccount struct { SourceDB, Account string } type ActivationScope struct { All bool Accounts []ActivationAccount } type ActivationFilter struct { PlatformNames, Statuses []string PromoteAccount, BusierAccount string } type ActivationPage struct { Rows []*Activation Total int64 Available bool } type ActivationSpendQuery struct { PlatformName string PromoteID uint64 Start int64 End *int64 } type ActivationSpendItem struct{ GameName, Amount string } type ActivationSpendDetail struct { Activation *Activation Items []*ActivationSpendItem TotalAmount string } type ActivationRepo interface { OwnAccounts(context.Context, uint) ([]ActivationAccount, error) List(context.Context, ActivationScope, ActivationFilter, ...ListOption) (*ActivationPage, error) Find(context.Context, uint64) (*Activation, error) Spend(context.Context, ActivationSpendQuery) ([]*ActivationSpendItem, error) } type ActivationUsecase struct { repo ActivationRepo accounts *AccountUsecase } func NewActivationUsecase(repo ActivationRepo, accounts *AccountUsecase) *ActivationUsecase { return &ActivationUsecase{repo: repo, accounts: accounts} } func (uc *ActivationUsecase) scope(ctx context.Context) (ActivationScope, error) { actor, _, err := uc.accounts.actor(ctx) if err != nil { return ActivationScope{}, err } if actor.AuthorityID == 888 { return ActivationScope{All: true}, nil } accounts, err := uc.repo.OwnAccounts(ctx, actor.UserID) if err != nil { return ActivationScope{}, err } scope := ActivationScope{} seen := map[ActivationAccount]bool{} for _, account := range accounts { account.Account = strings.TrimSpace(account.Account) account.SourceDB, err = NormalizePlatform(account.SourceDB) key := ActivationAccount{SourceDB: account.SourceDB, Account: strings.ToLower(account.Account)} if err == nil && key.Account != "" && !seen[key] { scope.Accounts = append(scope.Accounts, account) seen[key] = true } } return scope, nil } func (uc *ActivationUsecase) List(ctx context.Context, filter ActivationFilter, options ...ListOption) (*ActivationPage, error) { scope, err := uc.scope(ctx) if err != nil { return nil, err } if !scope.All && filter.BusierAccount != "" { matched := []ActivationAccount{} keyword := strings.ToLower(strings.TrimSpace(filter.BusierAccount)) for _, account := range scope.Accounts { if strings.Contains(strings.ToLower(account.Account), keyword) { matched = append(matched, account) } } scope.Accounts = matched filter.BusierAccount = "" } if !scope.All && len(scope.Accounts) == 0 { return &ActivationPage{Rows: []*Activation{}, Available: true}, nil } for i, status := range filter.Statuses { filter.Statuses[i] = NormalizeActivationStatus(status) } return uc.repo.List(ctx, scope, filter, options...) } func (uc *ActivationUsecase) SpendDetail(ctx context.Context, id uint64) (*ActivationSpendDetail, error) { scope, err := uc.scope(ctx) if err != nil { return nil, err } if !scope.All && len(scope.Accounts) == 0 { return nil, ErrActivationScope } activation, err := uc.repo.Find(ctx, id) if err != nil { return nil, err } if !scope.All { found := false for _, account := range scope.Accounts { if strings.EqualFold(account.SourceDB, strings.TrimSpace(activation.PlatformName)) && strings.EqualFold(account.Account, strings.TrimSpace(activation.BusierAccount)) { found = true break } } if !found { return nil, ErrActivationScope } } if activation.BindAt == nil { return nil, ErrActivationPending } detail := &ActivationSpendDetail{Activation: activation, Items: []*ActivationSpendItem{}, TotalAmount: "0.00"} if strings.TrimSpace(activation.PlatformName) == "" || activation.PromoteID == nil || *activation.PromoteID == 0 { return detail, nil } query := ActivationSpendQuery{PlatformName: activation.PlatformName, PromoteID: *activation.PromoteID, Start: activation.BindAt.Unix()} if end := activation.EndAt(); end != nil { n := end.Unix() query.End = &n } detail.Items, err = uc.repo.Spend(ctx, query) if err != nil { return nil, err } // 金额合计不用浮点数,避免多个游戏相加产生误差或大金额精度损失。 total := new(big.Rat) for _, item := range detail.Items { amount, ok := new(big.Rat).SetString(item.Amount) if !ok { return nil, ErrStorage } item.Amount = amount.FloatString(2) total.Add(total, amount) } detail.TotalAmount = total.FloatString(2) return detail, nil } func NormalizeActivationStatus(status string) string { status = strings.TrimSpace(status) if code, ok := map[string]string{"待审核": "pending", "进行中": "in_progress", "已达标": "reached", "已驳回": "rejected", "已解绑": "unbound"}[status]; ok { return code } return status } func ActivationStatusLabel(status string) string { if label, ok := map[string]string{"pending": "待审核", "in_progress": "进行中", "reached": "已达标", "rejected": "已驳回", "unbound": "已解绑"}[status]; ok { return label } return status }