233 lines
11 KiB
Go
233 lines
11 KiB
Go
package business
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
"kra/internal/biz/business"
|
|
)
|
|
|
|
type performanceRepo struct{ data Provider }
|
|
|
|
func NewPerformanceRepo(data Provider) business.PerformanceRepo { return &performanceRepo{data: data} }
|
|
|
|
func performanceChannels(db *gorm.DB, scope business.UserScope, owners []uint, sources []string) *gorm.DB {
|
|
q := db.Table("tab_promote p").Where("p.source_db IN ?", []string{"lx", "hw", "yq"})
|
|
if len(sources) > 0 {
|
|
q = q.Where("p.source_db IN ?", sources)
|
|
}
|
|
if !scope.All || owners != nil {
|
|
bindings := channelBindingQuery(db, scope, business.ChannelFilter{Sources: sources}).Select("b.source_db,b.platform_busier_id")
|
|
if owners != nil {
|
|
bindings = bindings.Where("b.user_id IN ?", owners)
|
|
}
|
|
q = q.Where("(p.source_db,p.busier_id) IN (?)", bindings)
|
|
}
|
|
return q
|
|
}
|
|
func performancePayQuery(db *gorm.DB, scope business.UserScope, owners []uint, sources []string, begin, end time.Time) *gorm.DB {
|
|
keys := performanceChannels(db, scope, owners, sources).Select("p.source_db,p.id")
|
|
return db.Table("tab_sw_report_day r").Where("r.dedup_mode=0 AND r.game_id>0 AND r.stat_date BETWEEN ? AND ?", begin.Format("2006-01-02"), end.Format("2006-01-02")).Where("(r.source_db,r.promote_id) IN (?)", keys)
|
|
}
|
|
func joinEffectiveness(q *gorm.DB) *gorm.DB {
|
|
return q.Joins("LEFT JOIN sys_business_effectiveness_state es ON es.id=1").Joins("LEFT JOIN sys_business_channel_effectiveness e ON e.rule_key=es.rule_key AND e.source_db=p.source_db AND e.promote_id=p.id")
|
|
}
|
|
|
|
type performanceChannelPO struct {
|
|
ContributorID uint
|
|
ContributorName string
|
|
ID, BusierID uint64
|
|
SourceDB string `gorm:"column:source_db"`
|
|
Account, Nickname string
|
|
CreateTime, QualifiedAt *int64
|
|
WindowPay, PeriodPay string
|
|
}
|
|
|
|
func performanceChannelToBiz(po performanceChannelPO) *business.PerformanceChannel {
|
|
return &business.PerformanceChannel{ContributorID: po.ContributorID, ContributorName: po.ContributorName, ChannelKey: business.ChannelKey{ID: po.ID, SourceDB: po.SourceDB}, Account: po.Account, Name: po.Nickname, CreatedAt: po.CreateTime, QualifiedAt: po.QualifiedAt, WindowPay: po.WindowPay, PeriodPay: po.PeriodPay, OwnerIDs: []uint{}}
|
|
}
|
|
|
|
type performanceOwnerPO struct {
|
|
SourceDB string `gorm:"column:source_db"`
|
|
PlatformBusierID uint64
|
|
UserID uint
|
|
Username, NickName string
|
|
}
|
|
|
|
func performanceOwners(db *gorm.DB, scope business.UserScope, sources []string) (map[business.PlatformKey][]performanceOwnerPO, error) {
|
|
var rows []performanceOwnerPO
|
|
if err := channelBindingQuery(db, scope, business.ChannelFilter{Sources: sources}).Select("b.source_db,b.platform_busier_id,b.user_id,u.username,u.nick_name").Order("b.user_id").Scan(&rows).Error; err != nil {
|
|
return nil, business.ErrStorage
|
|
}
|
|
byKey := map[business.PlatformKey][]performanceOwnerPO{}
|
|
for _, row := range rows {
|
|
key := business.PlatformKey{SourceDB: row.SourceDB, BusierID: row.PlatformBusierID}
|
|
byKey[key] = append(byKey[key], row)
|
|
}
|
|
return byKey, nil
|
|
}
|
|
func (r *performanceRepo) Facts(ctx context.Context, scope business.UserScope, ownerIDs []uint, sources []string, begin, end time.Time) (*business.PerformanceFacts, error) {
|
|
var out *business.PerformanceFacts
|
|
err := r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
var err error
|
|
out, err = readPerformanceFacts(tx, scope, ownerIDs, sources, begin, end)
|
|
return err
|
|
})
|
|
return out, storageError(err)
|
|
}
|
|
func readPerformanceFacts(db *gorm.DB, scope business.UserScope, ownerIDs []uint, sources []string, begin, end time.Time) (*business.PerformanceFacts, error) {
|
|
out := &business.PerformanceFacts{Channels: []*business.PerformanceChannel{}, Cash: []business.PerformanceCash{}, Available: true}
|
|
available, err := businessTablesAvailable(db, "tab_promote", "tab_sw_report_day")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out.Available = available
|
|
if !available {
|
|
return out, nil
|
|
}
|
|
owners, err := performanceOwners(db, scope, sources)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var pos []performanceChannelPO
|
|
start, finish := begin.Unix(), min(end.AddDate(0, 0, 1).Unix(), time.Now().Unix()+1)
|
|
channelFacts := joinEffectiveness(performanceChannels(db, scope, ownerIDs, sources)).Where("(p.create_time>=? AND p.create_time<?) OR (e.qualified_at>=? AND e.qualified_at<?)", start, finish, start, finish)
|
|
if err := channelFacts.Select("p.id,p.source_db,p.busier_id,p.account,p.nickname,p.create_time,e.qualified_at,COALESCE(e.window_pay,0) AS window_pay").Scan(&pos).Error; err != nil {
|
|
return nil, business.ErrStorage
|
|
}
|
|
for _, po := range pos {
|
|
channel := performanceChannelToBiz(po)
|
|
for _, owner := range owners[business.PlatformKey{SourceDB: po.SourceDB, BusierID: po.BusierID}] {
|
|
channel.OwnerIDs = append(channel.OwnerIDs, owner.UserID)
|
|
if channel.OwnerID == 0 {
|
|
channel.OwnerID = owner.UserID
|
|
channel.OwnerName = owner.NickName
|
|
if channel.OwnerName == "" {
|
|
channel.OwnerName = owner.Username
|
|
}
|
|
}
|
|
}
|
|
out.Channels = append(out.Channels, channel)
|
|
}
|
|
if begin.After(end) {
|
|
return out, nil
|
|
}
|
|
var cash []struct {
|
|
SourceDB string `gorm:"column:source_db"`
|
|
BusierID uint64
|
|
StatDate, Pay string
|
|
}
|
|
// Aggregate once per SDK business/date, not per channel/date. All channels
|
|
// under the same SDK owner have the same business-tree attribution. This
|
|
// keeps the exact totals while avoiding hundreds of thousands of Go rows.
|
|
visibleChannels := performanceChannels(db, scope, ownerIDs, sources).Select("p.source_db,p.id,p.busier_id")
|
|
if err := db.Table("tab_sw_report_day r").Joins("JOIN (?) owner_p ON owner_p.source_db=r.source_db AND owner_p.id=r.promote_id", visibleChannels).Where("r.dedup_mode=0 AND r.game_id>0 AND r.stat_date BETWEEN ? AND ?", begin.Format("2006-01-02"), end.Format("2006-01-02")).Select("r.source_db,owner_p.busier_id,CAST(r.stat_date AS CHAR) AS stat_date,CAST(SUM(r.total_pay) AS CHAR) AS pay").Group("r.source_db,owner_p.busier_id,r.stat_date").Scan(&cash).Error; err != nil {
|
|
return nil, business.ErrStorage
|
|
}
|
|
for _, row := range cash {
|
|
ids := []uint{}
|
|
for _, owner := range owners[business.PlatformKey{SourceDB: row.SourceDB, BusierID: row.BusierID}] {
|
|
ids = append(ids, owner.UserID)
|
|
}
|
|
out.Cash = append(out.Cash, business.PerformanceCash{ChannelKey: business.ChannelKey{SourceDB: row.SourceDB, ID: row.BusierID}, OwnerIDs: ids, Date: row.StatDate, Pay: row.Pay})
|
|
}
|
|
return out, nil
|
|
}
|
|
func (r *performanceRepo) Details(ctx context.Context, scope business.UserScope, selection business.PerformanceDetailSelection, sources []string, begin, end time.Time, metric string, offset, limit int) ([]*business.PerformanceChannel, int64, error) {
|
|
db := r.data.DB().WithContext(ctx)
|
|
owners := selection.OwnerIDs
|
|
pay := performancePayQuery(db, scope, owners, sources, begin, end).Select("r.source_db,r.promote_id,SUM(r.total_pay) AS pay").Group("r.source_db,r.promote_id")
|
|
needsPay := metric == "pay" || metric == "all"
|
|
q := joinEffectiveness(performanceChannels(db, scope, owners, sources))
|
|
contributorSelect, contributorOrder := "", ""
|
|
if len(selection.ContributorIDs) > 0 {
|
|
// Keep one row per member/platform owner, even if bindings overlap.
|
|
prefix := "(COALESCE(NULLIF(TRIM(c.id_path),''),'0') || ',' || c.id)"
|
|
pattern := "(" + prefix + " || ',%')"
|
|
if db.Dialector.Name() == "mysql" {
|
|
prefix = "CONCAT(COALESCE(NULLIF(TRIM(c.id_path),''),'0'),',',c.id)"
|
|
pattern = "CONCAT(" + prefix + ",',%')"
|
|
}
|
|
contributors := channelBindingQuery(db, scope, business.ChannelFilter{Sources: sources}).
|
|
Joins("JOIN sys_users c ON c.deleted_at IS NULL AND (c.id=u.id OR c.id=u.parent_id OR u.id_path="+prefix+" OR u.id_path LIKE "+pattern+")").
|
|
Where("c.id IN ? AND u.deleted_at IS NULL", selection.ContributorIDs).
|
|
Select("DISTINCT b.source_db,b.platform_busier_id,c.id AS contributor_id,COALESCE(NULLIF(c.nick_name,''),c.username) AS contributor_name")
|
|
if owners != nil {
|
|
contributors = contributors.Where("b.user_id IN ?", owners)
|
|
}
|
|
q = q.Joins("JOIN (?) contribution ON contribution.source_db=p.source_db AND contribution.platform_busier_id=p.busier_id", contributors)
|
|
contributorSelect = ",contribution.contributor_id,contribution.contributor_name"
|
|
contributorOrder = ",contribution.contributor_id"
|
|
}
|
|
if needsPay {
|
|
q = q.Joins("LEFT JOIN (?) pp ON pp.source_db=p.source_db AND pp.promote_id=p.id", pay)
|
|
}
|
|
start, finish := begin.Unix(), min(end.AddDate(0, 0, 1).Unix(), time.Now().Unix()+1)
|
|
switch metric {
|
|
case "open":
|
|
q = q.Where("p.create_time>=? AND p.create_time<?", start, finish)
|
|
case "effective":
|
|
q = q.Where("e.qualified_at>=? AND e.qualified_at<?", start, finish)
|
|
case "pay":
|
|
q = q.Where("COALESCE(pp.pay,0)<>0")
|
|
default:
|
|
q = q.Where("(p.create_time>=? AND p.create_time<?) OR (e.qualified_at>=? AND e.qualified_at<?) OR COALESCE(pp.pay,0)<>0", start, finish, start, finish)
|
|
}
|
|
var total int64
|
|
if err := q.Count(&total).Error; err != nil {
|
|
return nil, 0, business.ErrStorage
|
|
}
|
|
var pos []performanceChannelPO
|
|
paySelect := "'0.00' AS period_pay"
|
|
if needsPay {
|
|
paySelect = "CAST(COALESCE(pp.pay,0) AS CHAR) AS period_pay"
|
|
}
|
|
if err := q.Select("p.id,p.source_db,p.busier_id,p.account,p.nickname,p.create_time,e.qualified_at,CAST(COALESCE(e.window_pay,0) AS CHAR) AS window_pay," + paySelect + contributorSelect).Order("p.create_time DESC,p.source_db,p.id" + contributorOrder).Offset(offset).Limit(limit).Scan(&pos).Error; err != nil {
|
|
return nil, 0, business.ErrStorage
|
|
}
|
|
if !needsPay && len(pos) > 0 {
|
|
keys := [][]any{}
|
|
for _, po := range pos {
|
|
keys = append(keys, []any{po.SourceDB, po.ID})
|
|
}
|
|
var amounts []struct {
|
|
SourceDB string `gorm:"column:source_db"`
|
|
PromoteID uint64
|
|
Pay string
|
|
}
|
|
if err := db.Table("tab_sw_report_day r").Where("r.dedup_mode=0 AND r.game_id>0 AND r.stat_date BETWEEN ? AND ? AND (r.source_db,r.promote_id) IN ?", begin.Format("2006-01-02"), end.Format("2006-01-02"), keys).Select("r.source_db,r.promote_id,CAST(SUM(r.total_pay) AS CHAR) AS pay").Group("r.source_db,r.promote_id").Scan(&amounts).Error; err != nil {
|
|
return nil, 0, business.ErrStorage
|
|
}
|
|
byKey := map[business.ChannelKey]string{}
|
|
for _, a := range amounts {
|
|
byKey[business.ChannelKey{SourceDB: a.SourceDB, ID: a.PromoteID}] = a.Pay
|
|
}
|
|
for i := range pos {
|
|
if value, ok := byKey[business.ChannelKey{SourceDB: pos[i].SourceDB, ID: pos[i].ID}]; ok {
|
|
pos[i].PeriodPay = value
|
|
}
|
|
}
|
|
}
|
|
bindings, err := performanceOwners(db, scope, sources)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
rows := make([]*business.PerformanceChannel, 0, len(pos))
|
|
for _, po := range pos {
|
|
row := performanceChannelToBiz(po)
|
|
for _, owner := range bindings[business.PlatformKey{SourceDB: po.SourceDB, BusierID: po.BusierID}] {
|
|
row.OwnerIDs = append(row.OwnerIDs, owner.UserID)
|
|
if row.OwnerID == 0 {
|
|
row.OwnerID = owner.UserID
|
|
row.OwnerName = owner.NickName
|
|
if row.OwnerName == "" {
|
|
row.OwnerName = owner.Username
|
|
}
|
|
}
|
|
}
|
|
rows = append(rows, row)
|
|
}
|
|
return rows, total, nil
|
|
}
|