package main import ( "encoding/json" "fmt" "net/http/httptest" "path/filepath" "strconv" "strings" "testing" "time" "github.com/gin-gonic/gin" "github.com/glebarez/sqlite" "gorm.io/gorm" biz "kra/internal/biz/business" "kra/internal/biz/system" data "kra/internal/data/business" systemdata "kra/internal/data/system" "kra/internal/server/handler" "kra/internal/server/router" service "kra/internal/service/business" ) func TestBusinessManagementHTTPGroupsGoalsAndEffectivenessAgree(t *testing.T) { db, err := gorm.Open(sqlite.Open(filepath.Join(t.TempDir(), "management.db")), &gorm.Config{}) if err != nil { t.Fatal(err) } pool, _ := db.DB() t.Cleanup(func() { _ = pool.Close() }) for _, step := range append(systemdata.Migrations(), data.Migrations()...) { if err := step.Migrate(db); err != nil { t.Fatal(err) } } for _, sql := range []string{ "INSERT INTO sys_users(id,uuid,username,nick_name,level,parent_id,id_path,enable) VALUES(1,'root','root','root',0,0,'0',1),(7,'owner','owner','甲组织',1,0,'0',1),(8,'branch','branch','甲分部',2,7,'0,7',1),(9,'worker','worker','甲成员',3,8,'0,7,8',1),(10,'other','other','外部组织',1,0,'0',1)", "INSERT INTO sys_user_bind_platform(user_id,source_db,platform_busier_id,platform_busier_account,status) VALUES(9,'lx',14,'sdk','0'),(10,'hw',20,'outside','0')", "CREATE TABLE tab_promote(id INTEGER,source_db TEXT,account TEXT,nickname TEXT,parent_name TEXT,real_name TEXT,promote_level INTEGER,register_type INTEGER,status INTEGER,lock_status INTEGER,parent_id INTEGER,top_promote_id INTEGER,busier_id INTEGER,create_time INTEGER,last_login_time INTEGER)", "CREATE TABLE tab_spend(id INTEGER,source_db TEXT,user_id INTEGER,promote_id INTEGER,pay_amount DECIMAL(20,2),pay_time INTEGER,pay_status INTEGER)", "CREATE TABLE tab_user(id INTEGER,source_db TEXT,promote_id INTEGER,puid INTEGER,idcard TEXT)", "CREATE TABLE tab_promote_apply(source_db TEXT,promote_id INTEGER,game_id INTEGER,status INTEGER)", "CREATE TABLE tab_sw_report_day(source_db TEXT,promote_id INTEGER,game_id INTEGER,game_name TEXT,dedup_mode INTEGER,stat_date DATE,total_pay DECIMAL(20,2))", "CREATE TABLE tab_sw_report_day_key(source_db TEXT,promote_id INTEGER,game_id INTEGER,game_name TEXT,dedup_mode INTEGER,stat_date DATE,metric INTEGER,identity_key TEXT)", } { if err := db.Exec(sql).Error; err != nil { t.Fatal(err) } } loc := time.FixedZone("Asia/Shanghai", 8*3600) now := time.Now().In(loc) month := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, loc) previous := month.AddDate(0, 0, -1).Unix() stamp := month.Unix() + 1 if err := db.Exec("INSERT INTO tab_promote VALUES(100,'lx','channel','渠道甲','','',1,0,1,0,0,0,14,?,?),(101,'hw','outside','渠道乙','','',1,0,1,0,0,0,20,?,?),(102,'lx','new','新渠道','','',1,0,1,0,0,0,14,?,?)", previous, stamp, stamp, stamp, stamp, stamp).Error; err != nil { t.Fatal(err) } if err := db.Exec("INSERT INTO tab_spend VALUES(1,'lx',1,100,3500,?,1),(2,'hw',1,101,99999,?,1),(3,'lx',1,102,99999,?,0)", stamp, stamp, stamp).Error; err != nil { t.Fatal(err) } if err := db.Exec("INSERT INTO tab_sw_report_day VALUES('lx',100,1,'game',0,?,3500),('hw',101,1,'game',0,?,99999)", month.Format("2006-01-02"), month.Format("2006-01-02")).Error; err != nil { t.Fatal(err) } provider := businessHTTPData{db} accounts := biz.NewAccountUsecase(data.NewAccountRepo(provider)) preferences := biz.NewUserPreferenceUsecase(data.NewUserPreferenceRepo(provider)) settings := data.NewOtherSettingsRepo(provider) effective := biz.NewEffectivenessUsecase(data.NewEffectivenessRepo(provider), settings) teams := biz.NewTeamUsecase(data.NewTeamRepo(provider), accounts) goalRepo := data.NewGoalRepo(provider) performance := biz.NewPerformanceUsecase(data.NewPerformanceRepo(provider), teams, effective, goalRepo) goals := biz.NewGoalUsecase(goalRepo, performance) dashboard := biz.NewDashboardUsecase(data.NewDashboardRepo(provider), biz.NewWorkbenchUsecase(data.NewWorkbenchRepo(provider), accounts, preferences), effective) engine := gin.New() engine.Use(func(c *gin.Context) { id, _ := strconv.ParseUint(c.GetHeader("X-Test-User"), 10, 64) role := uint(901) if id == 1 { role = 888 } else if id == 8 { role = 902 } else if id == 9 { role = 903 } c.Request = c.Request.WithContext(system.NewActorContext(c.Request.Context(), system.Actor{UserID: uint(id), AuthorityID: role})) c.Next() }) router.RegisterBusinessPerformance(engine.Group(""), handler.NewBusinessPerformance(service.NewBusinessPerformanceService(teams, performance, goals, dashboard))) router.RegisterOtherSettings(engine.Group(""), handler.NewOtherSettings(service.NewOtherSettingsService(biz.NewOtherSettingsUsecase(settings, effective)))) channels := biz.NewChannelUsecase(data.NewChannelRepo(provider), nil, accounts, preferences, nil, effective) engine.GET("/sw/promote/list", handler.NewChannel(service.NewChannelService(channels)).List) call := func(user int, method, path, body string, code int) map[string]any { t.Helper() req := httptest.NewRequest(method, path, strings.NewReader(body)) req.Header.Set("Content-Type", "application/json") req.Header.Set("X-Test-User", strconv.Itoa(user)) rec := httptest.NewRecorder() engine.ServeHTTP(rec, req) var value map[string]any if err := json.Unmarshal(rec.Body.Bytes(), &value); err != nil { t.Fatalf("%s: %s", path, rec.Body.String()) } if value["code"] != float64(code) { t.Fatalf("%s: %+v", path, value) } result, _ := value["data"].(map[string]any) return result } view := call(7, "GET", "/sw/groups", "", 0) if len(view["members"].([]any)) != 3 { t.Fatal("不得包含其他一级组织") } view = call(7, "PUT", "/sw/groups", `{"revision":0,"groups":[{"name":"甲组","memberIds":[8,9]}]}`, 0) if len(view["groups"].([]any)) != 1 { t.Fatal("小组保存失败") } call(8, "PUT", "/sw/groups", `{"revision":1,"groups":[]}`, 7) call(7, "PUT", "/sw/goals", fmt.Sprintf(`{"kind":"person","targetId":9,"month":%q,"open":0,"effective":1,"amount":3000}`, month.Format("2006-01")), 0) call(9, "PUT", "/sw/goals", fmt.Sprintf(`{"kind":"person","targetId":9,"month":%q,"open":100}`, month.Format("2006-01")), 7) query := fmt.Sprintf("/sw/performance?year=%d&beginMonth=%d&endMonth=%d", now.Year(), now.Month(), now.Month()) stats := call(7, "GET", query, "", 0) total := stats["total"].(map[string]any) if total["open"] != float64(1) || total["effective"] != float64(1) || total["pay"] != "3500.00" { t.Fatalf("权限范围/开端与达标月份/流水错误: %+v", total) } groupID := uint64(view["groups"].([]any)[0].(map[string]any)["id"].(float64)) details := call(7, "GET", fmt.Sprintf("/sw/performance/details?kind=team&targetId=%d&begin=%s&end=%s&metric=effective", groupID, month.Format("2006-01-02"), now.Format("2006-01-02")), "", 0) if details["total"] != float64(2) || len(details["rows"].([]any)) != 2 { t.Fatal("team contribution drilldown must agree with summed member performance") } details = call(7, "GET", fmt.Sprintf("/sw/performance/details?kind=group&targetId=%d&memberIds=9&begin=%s&end=%s&metric=effective", groupID, month.Format("2006-01-02"), now.Format("2006-01-02")), "", 0) if details["total"] != float64(1) { t.Fatal("filtered matrix subtotal must return unique channels") } filtered := call(7, "GET", query+"&groupId=999", "", 0) if filtered["matrixTotal"].(map[string]any)["open"] != float64(0) || filtered["total"].(map[string]any)["open"] != float64(1) { t.Fatal("matrix filter must not corrupt scope cards") } overview := call(7, "GET", fmt.Sprintf("/sw/workbench/overview?sourceDbList=lx&period=custom&begin=%s&end=%s", month.Format("2006-01-02"), now.Format("2006-01-02")), "", 0) cards := overview["cards"].(map[string]any) if len(cards) != 8 || cards["lockedPromote"].(map[string]any)["value"] != nil || cards["terminatedPromote"].(map[string]any)["compareLabel"] != "事件数据未接入" { t.Fatal("event cards must be visible without fabricating historical counts") } mine := call(9, "GET", "/sw/goals/mine", "", 0) if mine["actual"].(map[string]any)["pay"] != "3500.00" { t.Fatal("首页目标必须与绩效同源") } listed := call(7, "GET", "/sw/promote/list?sourceDb=lx&effective=true", "", 0) if listed["total"] != float64(1) || listed["rows"].([]any)[0].(map[string]any)["effective"] != true { t.Fatal("渠道有效端筛选未接通") } call(7, "GET", fmt.Sprintf("/sw/performance/details?kind=person&targetId=10&begin=%s&end=%s&metric=effective", month.Format("2006-01-02"), now.Format("2006-01-02")), "", 7) call(1, "PUT", "/sw/settings/effectiveness", `{"amount":4000,"days":30}`, 0) stats = call(7, "GET", query, "", 0) if stats["total"].(map[string]any)["effective"] != float64(0) { t.Fatal("修改规则后绩效未重算") } listed = call(7, "GET", "/sw/promote/list?sourceDb=lx&effective=true", "", 0) if listed["total"] != float64(0) { t.Fatal("渠道列表仍然使用旧规则") } call(7, "PUT", "/sw/business/profile", `{"userId":9,"realName":"新负责人","phoneNumber":"13900000009","hireDate":"2025-01-02","employment":"probation"}`, 0) call(7, "PUT", "/sw/goals", fmt.Sprintf(`{"kind":"person","targetId":9,"month":%q,"open":null,"effective":null,"amount":null}`, month.Format("2006-01")), 0) if call(9, "GET", "/sw/goals/mine", "", 0) != nil { t.Fatal("清空目标后首页应隐藏目标卡") } }