package business import ( "context" "gorm.io/gorm" databusiness "kra/internal/data/business" datasystem "kra/internal/data/system" datatask "kra/internal/data/task" "kra/internal/modules/surface" "kra/pkg/database/migration" "kra/pkg/module" "sort" ) func Definition() module.Definition { performanceMenu := module.Menu{Name: "sw_BusinessPerformance", Path: "BusinessPerformance", ParentName: "sw_DataAnalysis", Component: "view/sw/BusinessPerformance.vue", Title: "商务绩效统计", Icon: "DataLine", Sort: 7} settingsMenu := module.Menu{Name: "sw_OtherSettings", Path: "OtherSettings", Component: "view/sw/OtherSettings.vue", Title: "其他设置", Icon: databusiness.MenuIcon("sw_OtherSettings"), Sort: 5} migrations := append(databusiness.Migrations(), migration.Step{ID: "202611040016_business_other_settings_surface", Migrate: func(db *gorm.DB) error { // First-install menus are created by the normal seed flow. if !db.Migrator().HasTable("sys_authorities") { return nil } var admins int64 if err := db.Table("sys_authorities").Where("authority_id=888 AND deleted_at IS NULL").Count(&admins).Error; err != nil { return err } if admins == 0 { return nil } return db.Transaction(func(tx *gorm.DB) error { menu := settingsMenu var alert struct{ Sort int } result := tx.Table("sys_base_menus").Select("sort").Where("name=? AND parent_id=0 AND deleted_at IS NULL", "sw_AlertCenter").Limit(1).Find(&alert) if result.Error != nil { return result.Error } if result.RowsAffected > 0 { menu.Sort = alert.Sort + 1 } var collisions int64 if err := tx.Table("sys_base_menus").Where("parent_id=0 AND deleted_at IS NULL AND name<>? AND sort=?", menu.Name, menu.Sort).Count(&collisions).Error; err != nil { return err } if collisions > 0 { if err := tx.Table("sys_base_menus").Where("parent_id=0 AND deleted_at IS NULL AND name<>? AND sort>=?", menu.Name, menu.Sort).Update("sort", gorm.Expr("sort + 1")).Error; err != nil { return err } } return datasystem.EnsureAdminSurface(tx, module.Surface{ Menus: []module.Menu{menu}, APIs: []module.API{ {Path: "/sw/settings/effectiveness", Method: "GET", Group: "其他设置", Description: "获取有效端判定参数"}, {Path: "/sw/settings/effectiveness", Method: "PUT", Group: "其他设置", Description: "保存有效端判定参数"}, }, }, 888) }) }}) managementAPIs := []module.API{} for _, api := range surface.APIsForPrefix("/sw/", 0) { switch api.Path { case "/sw/groups", "/sw/business/profile", "/sw/business/management", "/sw/performance", "/sw/performance/details", "/sw/performance/export", "/sw/goals", "/sw/goals/mine", "/sw/workbench/overview": managementAPIs = append(managementAPIs, api) } } tasks := []module.TimedTask{{Name: "ChannelEffectiveness", Description: "增量刷新渠道有效端", Spec: "*/1 * * * *", MethodName: "channelEffectivenessTask", Enabled: true}, {Name: "ChannelEffectivenessRecheck", Description: "每日核对有效端历史流水", Spec: "0 3 * * *", MethodName: "channelEffectivenessFullTask", Enabled: true}} migrations = append(migrations, migration.Step{ID: "202611040019_business_performance_surface", Migrate: func(db *gorm.DB) error { if !db.Migrator().HasTable("sys_authorities") { return nil } var count int64 if err := db.Table("sys_authorities").Where("authority_id=888 AND deleted_at IS NULL").Count(&count).Error; err != nil { return err } if count == 0 { return nil } return db.Transaction(func(tx *gorm.DB) error { if err := datasystem.EnsureAdminSurface(tx, module.Surface{Menus: []module.Menu{performanceMenu}, APIs: managementAPIs}, 888); err != nil { return err } var roles []uint if err := tx.Table("sys_authority_menus a").Joins("JOIN sys_base_menus m ON m.id=a.sys_base_menu_id").Where("m.name=? AND m.deleted_at IS NULL", "sw_BusinessList").Distinct().Pluck("a.sys_authority_authority_id", &roles).Error; err != nil { return err } var menuIDs []uint if err := tx.Table("sys_base_menus").Where("name IN ? AND deleted_at IS NULL", []string{"sw_DataAnalysis", performanceMenu.Name}).Pluck("id", &menuIDs).Error; err != nil { return err } for _, role := range roles { for _, id := range menuIDs { var exists int64 if err := tx.Table("sys_authority_menus").Where("sys_authority_authority_id=? AND sys_base_menu_id=?", role, id).Count(&exists).Error; err != nil { return err } if exists == 0 { if err := tx.Table("sys_authority_menus").Create(map[string]any{"sys_authority_authority_id": role, "sys_base_menu_id": id}).Error; err != nil { return err } } } } if err := datasystem.GrantMenuAPIs(tx, "sw_BusinessList", managementAPIs); err != nil { return err } return datatask.SeedDefaults(context.Background(), tx, tasks) }) }}) migrations = append(migrations, migration.Step{ID: "202611050021_business_alert_surface", Migrate: migrateAlertSurface}) definition := module.Definition{Name: "business", Migrations: migrations, Prerequisites: databusiness.CheckPrerequisites, ValidateSchema: databusiness.CheckSchema, TimedTasks: append([]module.TimedTask{alertDetectionTask}, tasks...), Surface: module.Surface{ Menus: []module.Menu{ alertExemptionMenu, performanceMenu, settingsMenu, {Name: "sw_RankingAnalysis", Path: "RankingAnalysis", ParentName: "sw_DataAnalysis", Component: "view/sw/RankingAnalysis.vue", Title: "排行榜", Icon: databusiness.MenuIcon("sw_RankingAnalysis"), Sort: 6}, {Name: "sw_ServerAnalysis", Path: "ServerAnalysis", ParentName: "sw_DataAnalysis", Component: "view/sw/ServerAnalysis.vue", Title: "区服分析", Icon: databusiness.MenuIcon("sw_ServerAnalysis"), Sort: 5}, {Name: "sw_ChannelDataStatistics", Path: "ChannelDataStatistics", ParentName: "sw_DataAnalysis", Component: "view/sw/ChannelDataStatistics.vue", Title: "渠道数据统计", Icon: databusiness.MenuIcon("sw_ChannelDataStatistics"), Sort: 1}, {Name: "sw_GameAnalytics", Path: "GameAnalytics", ParentName: "sw_DataAnalysis", Component: "view/sw/GameAnalytics.vue", Title: "游戏数据", Icon: databusiness.MenuIcon("sw_GameAnalytics"), Sort: 4}, {Name: "sw_DataAnalysis", Path: "sw-DataAnalysis", Component: "view/routerHolder.vue", Title: "数据分析", Icon: databusiness.MenuIcon("sw_DataAnalysis"), Sort: 3}, {Name: "sw_RegistrationDetails", Path: "RegistrationDetails", ParentName: "sw_DataAnalysis", Component: "view/sw/RegistrationDetails.vue", Title: "注册明细", Icon: databusiness.MenuIcon("sw_RegistrationDetails"), Sort: 3}, {Name: "sw_RechargeDetails", Path: "RechargeDetails", ParentName: "sw_DataAnalysis", Component: "view/sw/RechargeDetails.vue", Title: "充值明细", Icon: databusiness.MenuIcon("sw_RechargeDetails"), Sort: 2}, {Name: "sw_ChannelList", Path: "ChannelList", ParentName: "sw_BusMgmt", Component: "view/sw/ChannelList.vue", Title: "渠道列表", Icon: databusiness.MenuIcon("sw_ChannelList"), Sort: 2}, {Name: "sw_ChannelActivationList", Path: "ChannelActivationList", ParentName: "sw_BusMgmt", Component: "view/sw/ChannelActivationList.vue", Title: "渠道激活", Icon: databusiness.MenuIcon("sw_ChannelActivationList"), Sort: 3}, {Name: "sw_AlertList", Path: "AlertList", ParentName: "sw_AlertCenter", Component: "view/sw/AlertList.vue", Title: "预警列表", Icon: databusiness.MenuIcon("sw_AlertList"), Sort: 1}, {Name: "sw_AlertCenter", Path: "sw-AlertCenter", Component: "view/routerHolder.vue", Title: "预警中心", Icon: databusiness.MenuIcon("sw_AlertCenter"), Sort: 4}, {Name: "sw_AlertRules", Path: "AlertRules", ParentName: "sw_AlertCenter", Component: "view/sw/AlertRules.vue", Title: "预警规则", Icon: databusiness.MenuIcon("sw_AlertRules"), Sort: 2}, {Name: "sw_BusMgmt", Path: "sw-BusMgmt", Component: "view/routerHolder.vue", Title: "商务管理", Icon: databusiness.MenuIcon("sw_BusMgmt"), Sort: 2}, {Name: "sw_BusinessList", Path: "BusinessList", ParentName: "sw_BusMgmt", Component: "view/sw/BusinessList.vue", Title: "商务列表", Icon: databusiness.MenuIcon("sw_BusinessList"), Sort: 1}, }, APIs: surface.APIsForPrefix("/sw/", 0), }} // Parent menus must exist before their children in first-install seeding. sort.SliceStable(definition.Surface.Menus, func(i, j int) bool { return definition.Surface.Menus[i].ParentName == "" && definition.Surface.Menus[j].ParentName != "" }) return definition }