fix: 统一商务表格展示并修正筛选和全量排序

This commit is contained in:
Yvan
2026-11-05 01:48:09 +08:00
parent dc97690eb1
commit 10600d1bc9
38 changed files with 356 additions and 145 deletions
+3 -1
View File
@@ -3,6 +3,7 @@ package business
import (
"context"
"sort"
"strings"
"time"
"golang.org/x/sync/errgroup"
@@ -25,7 +26,8 @@ func analyticsPromotes(db *gorm.DB, scope business.AnalyticsScope, f business.An
if f.PromoteName == "" {
return q
}
hits := analyticsBasePromotes(db, scope, f, "hit").Select("1").Where("hit.account LIKE ?", "%"+f.PromoteName+"%").Where("hit.source_db=" + alias + ".source_db")
keyword := strings.ToLower(strings.TrimSpace(f.PromoteName))
hits := analyticsBasePromotes(db, scope, f, "hit").Select("1").Where("INSTR(LOWER(hit.account),?)>0 OR INSTR(LOWER(hit.nickname),?)>0", keyword, keyword).Where("hit.source_db=" + alias + ".source_db")
if scope.IncludeSub {
root := "CASE WHEN hit.promote_level=1 THEN hit.id WHEN hit.top_promote_id>0 THEN hit.top_promote_id ELSE hit.id END"
hits = hits.Where("(" + alias + ".id=(" + root + ") OR " + alias + ".top_promote_id=(" + root + "))")
+18
View File
@@ -102,6 +102,24 @@ func TestAnalyticsFiltersKeepHierarchyAndSnapshotNames(t *testing.T) {
t.Fatal("不能用当前游戏表名替代报表快照过滤")
}
}
func TestAnalyticsChannelSearchMatchesNicknameAndKeepsScope(t *testing.T) {
db, repo, scope, filter := prepareAnalyticsData(t)
if err := db.Exec("UPDATE tab_promote SET nickname='同名推广' WHERE id=100").Error; err != nil {
t.Fatal(err)
}
scope.Viewer = business.UserScope{SelfID: 9, OnlySelf: true}
filter.PromoteName = "推广"
data, err := repo.Aggregate(context.Background(), business.ChannelAnalytics, scope, filter)
if err != nil || len(data.Rows) != 1 || data.Rows[0].SourceDB != "lx" || data.Rows[0].ID != 100 {
t.Fatalf("nickname search must remain scoped: %+v %v", data, err)
}
filter.PromoteName = "%"
data, err = repo.Aggregate(context.Background(), business.ChannelAnalytics, scope, filter)
if err != nil || len(data.Rows) != 0 {
t.Fatal("wildcard keyword must not broaden the query")
}
}
func TestAnalyticsMissingScopeAndTablesDoNotBroaden(t *testing.T) {
db, repo, scope, filter := prepareAnalyticsData(t)
scope.IncludeSub = false
+2 -1
View File
@@ -86,7 +86,8 @@ func (r *channelRepo) List(ctx context.Context, scope business.UserScope, filter
q = q.Where("p.source_db IN ?", filter.Sources)
}
if filter.Keyword != "" {
q = q.Where("p.account LIKE ?", "%"+filter.Keyword+"%")
keyword := strings.ToLower(strings.TrimSpace(filter.Keyword))
q = q.Where("INSTR(LOWER(p.account),?)>0 OR INSTR(LOWER(p.nickname),?)>0 OR INSTR(LOWER(p.real_name),?)>0", keyword, keyword, keyword)
}
for _, field := range []struct {
column string
+17
View File
@@ -43,6 +43,23 @@ func prepareChannels(t *testing.T) (*gorm.DB, business.ChannelRepo) {
func channelTestScope() business.UserScope {
return business.UserScope{SelfID: 8, IncludeSelf: true, DescendantPath: "0,7,8"}
}
func TestChannelKeywordMatchesNameAndContactWithinScope(t *testing.T) {
db, repo := prepareChannels(t)
if err := db.Exec("UPDATE tab_promote SET nickname='同名公会', real_name='负责人甲' WHERE id=100").Error; err != nil {
t.Fatal(err)
}
for _, keyword := range []string{"公会", "负责人甲"} {
page, err := repo.List(context.Background(), business.UserScope{SelfID: 9, OnlySelf: true}, business.ChannelFilter{Keyword: keyword})
if err != nil || page.Total != 1 || len(page.Rows) != 1 || page.Rows[0].SourceDB != "lx" {
t.Fatalf("keyword must not escape the business scope: %q %+v %v", keyword, page, err)
}
}
page, err := repo.List(context.Background(), channelTestScope(), business.ChannelFilter{Keyword: "%"})
if err != nil || page.Total != 0 {
t.Fatal("keyword wildcards must be treated literally")
}
}
func TestChannelListMatchesBindingsAndLiteralBusinessKeyword(t *testing.T) {
db, repo := prepareChannels(t)
ctx := context.Background()
+2
View File
@@ -287,6 +287,8 @@ func (r *ledgerRepo) Spends(ctx context.Context, scope business.LedgerScope, fil
order := "r.pay_time"
if filter.SortField == "payAmount" {
order = "r.pay_amount"
} else if filter.SortField == "id" {
order = "r.id"
}
if filter.Ascending {
order += " ASC"
+21
View File
@@ -147,3 +147,24 @@ func TestLedgerChannelNicknameAndLiteralFuzzyFilter(t *testing.T) {
t.Fatal("模糊搜索不得扩大商务范围")
}
}
func TestSpendIDSortIsGlobalAndStableAcrossPages(t *testing.T) {
db, repo, scope, filter := prepareLedgerData(t)
if err := db.Exec("UPDATE tab_spend SET pay_time=1999 WHERE id=901").Error; err != nil {
t.Fatal(err)
}
filter.SortField, filter.Ascending = "id", true
first, err := repo.Spends(context.Background(), scope, filter, business.LedgerListOptions{Limit: 2})
if err != nil {
t.Fatal(err)
}
second, err := repo.Spends(context.Background(), scope, filter, business.LedgerListOptions{Offset: 2, Limit: 2})
if err != nil || len(first.Rows) != 2 || len(second.Rows) != 2 || first.Rows[1].ID >= second.Rows[0].ID {
t.Fatal("ascending sort must be applied before pagination")
}
filter.Ascending = false
last, err := repo.Spends(context.Background(), scope, filter, business.LedgerListOptions{Limit: 2})
if err != nil || last.Rows[0].ID != 909 || last.Rows[1].ID != 904 {
t.Fatalf("ID ordering must not fall back to timestamps: %+v %v", last, err)
}
}