Files
kra-new/internal/data/business/activation_metrics.go
T

193 lines
5.8 KiB
Go

package business
import (
"context"
"gorm.io/gorm"
"kra/internal/biz/business"
"math/big"
"sort"
"time"
)
func (r *activationRepo) applyTransferEnd(ctx context.Context, row *business.Activation) error {
if row.BindAt == nil || row.PromoteID == nil || (row.Status != "reached" && row.ReachAt == nil) {
return nil
}
db, err := r.databases.Database(row.PlatformName)
if err != nil || db == nil {
return nil
}
db = db.WithContext(ctx)
ok, err := businessTableAvailable(db, "tab_promote_business_event")
if err != nil {
return err
}
if !ok {
return nil
}
begin := *row.BindAt
if row.ReachAt != nil && row.ReachAt.After(begin) {
begin = *row.ReachAt
}
q := db.Table("tab_promote_business_event").Where("kind='transfer' AND promote_id=? AND occurred_at>=?", *row.PromoteID, begin)
if row.BusierID > 0 {
q = q.Where("old_busier_id=?", row.BusierID)
} else {
q = q.Where("LOWER(old_busier_account)=LOWER(?)", row.BusierAccount)
}
var rows []struct{ OccurredAt time.Time }
if err := q.Select("occurred_at").Order("occurred_at,id").Limit(1).Find(&rows).Error; err != nil {
return business.ErrActivationSource
}
if len(rows) > 0 {
row.EndOnTransfer(rows[0].OccurredAt)
}
return nil
}
func activationMetricQuery(row *business.Activation) business.ActivationSpendQuery {
q := business.ActivationSpendQuery{PlatformName: row.PlatformName, Channels: row.Channels, Start: row.BindAt.Unix()}
if row.PromoteID != nil {
q.PromoteID = *row.PromoteID
}
if end := row.EndAt(); end != nil {
n := end.Unix()
q.End = &n
}
return q
}
func sumActivationMoney(values []string) (string, error) {
total := new(big.Rat)
for _, v := range values {
n, ok := new(big.Rat).SetString(v)
if !ok {
return "", business.ErrStorage
}
total.Add(total, n)
}
return total.FloatString(2), nil
}
// 激活数据与 OA 使用同一 SDK 数据源,避免商务报表副本延迟/测试副本不同导致金额不一致。
func activationRoots(q business.ActivationSpendQuery) map[string][]uint64 {
roots := map[string][]uint64{}
keys := q.Channels
if len(keys) == 0 {
keys = []business.ChannelKey{{SourceDB: q.PlatformName, ID: q.PromoteID}}
}
for _, key := range keys {
if key.ID > 0 {
roots[key.SourceDB] = append(roots[key.SourceDB], key.ID)
}
}
return roots
}
func (r *activationRepo) activationMetricsAvailable(ctx context.Context, q business.ActivationSpendQuery) (bool, error) {
for source := range activationRoots(q) {
db, err := r.databases.Database(source)
if err != nil || db == nil {
return false, nil
}
ok, err := businessTablesAvailable(db.WithContext(ctx), "tab_promote", "tab_spend")
if err != nil {
return false, err
}
if !ok {
return false, nil
}
}
return true, nil
}
func nativeActivationTree(db *gorm.DB, roots []uint64) *gorm.DB {
return db.Table("tab_promote p").Where("p.id IN ? OR p.top_promote_id IN ? OR p.parent_id IN ?", roots, roots, roots)
}
func (r *activationRepo) Spend(ctx context.Context, q business.ActivationSpendQuery) ([]*business.ActivationSpendItem, error) {
out := []*business.ActivationSpendItem{}
for source, roots := range activationRoots(q) {
db, err := r.databases.Database(source)
if err != nil || db == nil {
return nil, business.ErrActivationMetrics
}
db = db.WithContext(ctx)
tree := nativeActivationTree(db, roots)
var n int64
if err := tree.Count(&n).Error; err != nil {
return nil, business.ErrActivationMetrics
}
if n == 0 {
continue
}
query := db.Table("tab_spend s").Where("s.pay_status=1 AND s.pay_time>=?", q.Start).Where("EXISTS (?)", tree.Select("1").Where("p.id=s.promote_id"))
if q.End != nil {
query = query.Where("s.pay_time<=?", *q.End)
}
var rows []struct {
GameID uint64
GameName, Amount string
}
if err := query.Select("s.game_id,COALESCE(MAX(s.game_name),'') AS game_name,COALESCE(SUM(s.pay_amount),0) AS amount").Group("s.game_id").Order("s.game_id").Scan(&rows).Error; err != nil {
return nil, business.ErrActivationMetrics
}
for _, row := range rows {
out = append(out, &business.ActivationSpendItem{PlatformName: source, GameName: row.GameName, Amount: row.Amount})
}
}
sort.SliceStable(out, func(i, j int) bool {
a, okA := new(big.Rat).SetString(out[i].Amount)
b, okB := new(big.Rat).SetString(out[j].Amount)
if okA && okB && a.Cmp(b) != 0 {
return a.Cmp(b) > 0
}
if out[i].PlatformName != out[j].PlatformName {
return out[i].PlatformName < out[j].PlatformName
}
return out[i].GameName < out[j].GameName
})
return out, nil
}
func (r *activationRepo) registered(ctx context.Context, q business.ActivationSpendQuery, dedup bool) (*int64, error) {
total := int64(0)
identities := map[string]bool{}
for source, roots := range activationRoots(q) {
db, err := r.databases.Database(source)
if err != nil || db == nil {
return nil, nil
}
db = db.WithContext(ctx)
ok, err := businessTablesAvailable(db, "tab_user", "tab_promote")
if err != nil {
return nil, err
}
if !ok {
return nil, nil
}
cast := "CAST(r.register_time AS INTEGER)"
if db.Dialector.Name() == "mysql" {
cast = "CAST(r.register_time AS UNSIGNED)"
}
query := db.Table("tab_user r").Where("r.puid=0 AND "+cast+">=?", q.Start).Where("EXISTS (?)", nativeActivationTree(db, roots).Select("1").Where("p.id=r.promote_id"))
if q.End != nil {
query = query.Where(cast+"<=?", *q.End)
}
if dedup {
var cards []string
if err := query.Where("TRIM(COALESCE(r.idcard,''))<>''").Distinct("TRIM(r.idcard)").Pluck("TRIM(r.idcard)", &cards).Error; err != nil {
return nil, business.ErrActivationMetrics
}
for _, card := range cards {
identities[card] = true
}
} else {
var count int64
if err := query.Distinct("r.id").Count(&count).Error; err != nil {
return nil, business.ErrActivationMetrics
}
total += count
}
}
if dedup {
total = int64(len(identities))
}
return &total, nil
}