199 lines
6.3 KiB
Go
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
|
|
}
|