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=? AND e.qualified_at0 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=? AND e.qualified_at0") default: q = q.Where("(p.create_time>=? AND p.create_time=? AND e.qualified_at0", 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 }