Files
kra-new/internal/biz/business/activation.go
T

199 lines
6.3 KiB
Go

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
}