package service import ( "github.com/mhsanaei/3x-ui/v3/internal/database/model" "gorm.io/gorm" ) // adjustGroupBaselinesForRemovedTraffic shifts group baselines down by the clients' // current counters so per-group traffic totals survive a traffic reset or client delete (#5675). // current counters so ListGroups totals survive a traffic reset or client delete (#5675). func adjustGroupBaselinesForRemovedTraffic(tx *gorm.DB, emails []string) error { if len(emails) == 0 { return nil } type groupDelta struct { Name string Up int64 Down int64 } totals := make(map[string]*groupDelta) for _, batch := range chunkStrings(emails, sqlInChunk) { var part []groupDelta if err := tx.Table("clients AS c"). Select("c.group_name AS name, COALESCE(SUM(ct.up), 0) AS up, COALESCE(SUM(ct.down), 0) AS down"). Joins("JOIN client_traffics ct ON ct.email = c.email"). Where("c.group_name <> '' AND c.email IN ?", batch). Group("c.group_name"). Scan(&part).Error; err != nil { return err } for i := range part { if agg, ok := totals[part[i].Name]; ok { agg.Up += part[i].Up agg.Down += part[i].Down } else { totals[part[i].Name] = &part[i] } } } for name, d := range totals { if d.Up == 0 && d.Down == 0 { continue } res := tx.Model(&model.ClientGroup{}).Where("name = ?", name).Updates(map[string]any{ "reset_up": gorm.Expr("reset_up - ?", d.Up), "reset_down": gorm.Expr("reset_down - ?", d.Down), }) if res.Error != nil { return res.Error } if res.RowsAffected == 0 { if err := tx.Create(&model.ClientGroup{Name: name, ResetUp: -d.Up, ResetDown: -d.Down}).Error; err != nil { return err } } } return nil }