244 lines
7.4 KiB
Go
244 lines
7.4 KiB
Go
package business
|
||
|
||
import (
|
||
"context"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/go-kratos/kratos/v3/errors"
|
||
)
|
||
|
||
var (
|
||
ErrLedgerInput = errors.BadRequest("SW_LEDGER_INPUT", "明细查询参数不正确")
|
||
ErrLedgerUnavailable = errors.BadRequest("SW_LEDGER_UNAVAILABLE", "明细数据尚未接入,无法导出")
|
||
ErrLedgerExportLimit = errors.BadRequest("SW_LEDGER_EXPORT_LIMIT", "导出数据超过10000条,请缩小时间范围或筛选条件")
|
||
ErrGameOptionsUnavailable = errors.BadRequest("SW_GAME_OPTIONS_UNAVAILABLE", "游戏数据尚未接入")
|
||
)
|
||
|
||
type LedgerFilter struct {
|
||
Sources, GameNames []string
|
||
UserID *int64
|
||
PromoteAccount, BusierAccount, PlayerName, OrderNumber string
|
||
PayStatus, PayWay *int
|
||
StartDate, EndDate *time.Time
|
||
BeginUnix, EndUnix int64
|
||
SortField string
|
||
Ascending bool
|
||
}
|
||
type LedgerScope struct {
|
||
Viewer UserScope
|
||
IncludeSub, Unrestricted bool
|
||
}
|
||
type LedgerListOptions struct {
|
||
Offset, Limit int
|
||
RejectAbove int64
|
||
}
|
||
type LedgerNames struct {
|
||
PromoteID *int64
|
||
PromoteAccount, BindingAccount, BindingUserName string
|
||
BusinessPath []string
|
||
}
|
||
|
||
func (n *LedgerNames) DisplayPromote() string {
|
||
if strings.TrimSpace(n.PromoteAccount) != "" {
|
||
return n.PromoteAccount
|
||
}
|
||
if n.PromoteID == nil {
|
||
return "未知渠道"
|
||
}
|
||
if *n.PromoteID == 0 {
|
||
return "官方渠道"
|
||
}
|
||
return "未知渠道(ID:" + strconv.FormatInt(*n.PromoteID, 10) + ")"
|
||
}
|
||
func (n *LedgerNames) DisplayBusiness() string {
|
||
if len(n.BusinessPath) > 0 {
|
||
return strings.Join(n.BusinessPath, "→")
|
||
}
|
||
return (&Channel{BindingAccount: n.BindingAccount, BindingUserName: n.BindingUserName}).BusinessDisplayName()
|
||
}
|
||
|
||
type Registration struct {
|
||
ChannelKey
|
||
LedgerNames
|
||
RegisterTime *int64
|
||
FirstGameID *int64
|
||
FirstGameName, RegisterIP, DeviceName string
|
||
TotalPayAmount *string
|
||
}
|
||
type Spend struct {
|
||
ChannelKey
|
||
LedgerNames
|
||
UserID, GameID *int64
|
||
OrderNumber, GameName, ServerID, ServerName, PlayerName, IP string
|
||
PayAmount *string
|
||
PayTime *int64
|
||
PayWay, PayStatus *int
|
||
}
|
||
type RegistrationPage struct {
|
||
Rows []*Registration
|
||
Total int64
|
||
Available bool
|
||
}
|
||
type SpendPage struct {
|
||
Rows []*Spend
|
||
Total int64
|
||
TotalPayAmount *string
|
||
Available bool
|
||
}
|
||
type GameOption struct {
|
||
SourceDB string
|
||
ID uint64
|
||
Name string
|
||
}
|
||
type LedgerRepo interface {
|
||
Registrations(context.Context, LedgerScope, LedgerFilter, LedgerListOptions) (*RegistrationPage, error)
|
||
Spends(context.Context, LedgerScope, LedgerFilter, LedgerListOptions) (*SpendPage, error)
|
||
Games(context.Context, UserScope, []string, string, int) ([]*GameOption, error)
|
||
}
|
||
type LedgerUsecase struct {
|
||
repo LedgerRepo
|
||
accounts *AccountUsecase
|
||
preferences *UserPreferenceUsecase
|
||
now func() time.Time
|
||
}
|
||
|
||
func NewLedgerUsecase(repo LedgerRepo, accounts *AccountUsecase, preferences *UserPreferenceUsecase) *LedgerUsecase {
|
||
return &LedgerUsecase{repo: repo, accounts: accounts, preferences: preferences, now: time.Now}
|
||
}
|
||
func (uc *LedgerUsecase) prepare(ctx context.Context, filter LedgerFilter) (LedgerScope, LedgerFilter, error) {
|
||
actor, a, err := uc.accounts.actor(ctx)
|
||
if err != nil {
|
||
return LedgerScope{}, filter, err
|
||
}
|
||
pref, err := uc.preferences.Current(ctx)
|
||
if err != nil {
|
||
return LedgerScope{}, filter, err
|
||
}
|
||
viewer := portraitUserScope(a)
|
||
viewer.All = actor.AuthorityID == 888
|
||
scope := LedgerScope{Viewer: viewer, IncludeSub: pref.IncludeSubPromote, Unrestricted: viewer.All && strings.TrimSpace(filter.BusierAccount) == ""}
|
||
begin, end := reportDateDefaults(filter.StartDate, filter.EndDate, 30, uc.now())
|
||
if end.Before(*begin) {
|
||
return scope, filter, ErrLedgerInput
|
||
}
|
||
filter.BeginUnix = begin.Unix()
|
||
filter.EndUnix = end.AddDate(0, 0, 1).Unix() - 1
|
||
filter.StartDate, filter.EndDate = begin, end
|
||
return scope, filter, nil
|
||
}
|
||
func registrationSort(field string) string {
|
||
switch field {
|
||
case "totalPayAmount", "total_pay_amount", "totalPay", "payAmount":
|
||
return "totalPayAmount"
|
||
}
|
||
return "registerTime"
|
||
}
|
||
func spendSort(field string) string {
|
||
switch field {
|
||
case "id":
|
||
return "id"
|
||
case "payAmount", "pay_amount", "amount":
|
||
return "payAmount"
|
||
}
|
||
return "payTime"
|
||
}
|
||
func (uc *LedgerUsecase) Registrations(ctx context.Context, filter LedgerFilter, options LedgerListOptions) (*RegistrationPage, error) {
|
||
scope, filter, err := uc.prepare(ctx, filter)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
filter.SortField = registrationSort(filter.SortField)
|
||
page, err := uc.repo.Registrations(ctx, scope, filter, options)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
for _, row := range page.Rows {
|
||
row.PromoteAccount = row.DisplayPromote()
|
||
if row.TotalPayAmount != nil {
|
||
n, err := portraitNumber(*row.TotalPayAmount)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
row.TotalPayAmount = stringPointer(portraitDecimal(n, 2))
|
||
}
|
||
}
|
||
return page, nil
|
||
}
|
||
func (uc *LedgerUsecase) Spends(ctx context.Context, filter LedgerFilter, options LedgerListOptions) (*SpendPage, error) {
|
||
scope, filter, err := uc.prepare(ctx, filter)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
filter.SortField = spendSort(filter.SortField)
|
||
page, err := uc.repo.Spends(ctx, scope, filter, options)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
for _, row := range page.Rows {
|
||
row.PromoteAccount = row.DisplayPromote()
|
||
if row.PayAmount != nil {
|
||
n, err := portraitNumber(*row.PayAmount)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
row.PayAmount = stringPointer(portraitDecimal(n, 2))
|
||
}
|
||
}
|
||
if page.TotalPayAmount != nil {
|
||
n, err := portraitNumber(*page.TotalPayAmount)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
page.TotalPayAmount = stringPointer(portraitDecimal(n, 2))
|
||
}
|
||
return page, nil
|
||
}
|
||
func (uc *LedgerUsecase) ExportRegistrations(ctx context.Context, filter LedgerFilter) ([]*Registration, error) {
|
||
page, err := uc.Registrations(ctx, filter, LedgerListOptions{Limit: 10000, RejectAbove: 10000})
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if !page.Available {
|
||
return nil, ErrLedgerUnavailable
|
||
}
|
||
if page.Total > 10000 {
|
||
return nil, ErrLedgerExportLimit
|
||
}
|
||
for _, row := range page.Rows {
|
||
if row.TotalPayAmount == nil {
|
||
return nil, ErrLedgerUnavailable
|
||
}
|
||
}
|
||
return page.Rows, nil
|
||
}
|
||
func (uc *LedgerUsecase) ExportSpends(ctx context.Context, filter LedgerFilter) ([]*Spend, error) {
|
||
page, err := uc.Spends(ctx, filter, LedgerListOptions{Limit: 10000, RejectAbove: 10000})
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if !page.Available {
|
||
return nil, ErrLedgerUnavailable
|
||
}
|
||
if page.Total > 10000 {
|
||
return nil, ErrLedgerExportLimit
|
||
}
|
||
return page.Rows, nil
|
||
}
|
||
func (uc *LedgerUsecase) Games(ctx context.Context, sources []string, keyword string, limit int) ([]*GameOption, error) {
|
||
actor, account, err := uc.accounts.actor(ctx)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
viewer := portraitUserScope(account)
|
||
viewer.All = actor.AuthorityID == 888
|
||
if limit <= 0 {
|
||
limit = 20
|
||
}
|
||
if limit > 50 {
|
||
limit = 10000
|
||
}
|
||
return uc.repo.Games(ctx, viewer, sources, strings.TrimSpace(keyword), limit)
|
||
}
|