feat: 完善小组绩效与有效端统计并修复审查问题
This commit is contained in:
@@ -0,0 +1,232 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user