package repository import ( "errors" "strings" "hyapp-admin-server/internal/model" "gorm.io/gorm" ) func (s *Store) ListTeams(keyword string) ([]model.Team, error) { query := s.db.Model(&model.Team{}). Select("admin_teams.*, parent_team.name AS parent_name, COUNT(DISTINCT admin_users.id) AS user_count"). Joins("LEFT JOIN admin_teams parent_team ON parent_team.id = admin_teams.parent_id"). Joins("LEFT JOIN admin_teams child_team ON child_team.parent_id = admin_teams.id"). Joins("LEFT JOIN admin_users ON admin_users.team_id = admin_teams.id OR admin_users.team_id = child_team.id") if keyword := strings.TrimSpace(keyword); keyword != "" { like := "%" + keyword + "%" query = query.Where("admin_teams.name LIKE ? OR CAST(admin_teams.id AS CHAR) LIKE ? OR admin_teams.description LIKE ? OR parent_team.name LIKE ?", like, like, like, like) } var teams []model.Team err := query. Group("admin_teams.id, admin_teams.parent_id, admin_teams.name, admin_teams.description, admin_teams.sort, admin_teams.created_at_ms, admin_teams.updated_at_ms, parent_team.name"). Order("CASE WHEN admin_teams.parent_id IS NULL THEN admin_teams.sort ELSE COALESCE(parent_team.sort, admin_teams.sort) END ASC"). Order("COALESCE(admin_teams.parent_id, admin_teams.id) ASC"). Order("CASE WHEN admin_teams.parent_id IS NULL THEN 0 ELSE 1 END ASC"). Order("admin_teams.sort ASC, admin_teams.id ASC"). Scan(&teams).Error return teams, err } func (s *Store) GetTeam(id uint) (*model.Team, error) { var team model.Team if err := s.db.First(&team, id).Error; err != nil { return nil, err } return &team, nil } func (s *Store) FindTeamByName(name string) (*model.Team, error) { var team model.Team if err := s.db.Where("name = ?", strings.TrimSpace(name)).First(&team).Error; err != nil { return nil, err } return &team, nil } func (s *Store) CreateTeam(team *model.Team) error { if err := normalizeTeam(team); err != nil { return err } if err := s.validateTeamParent(s.db, 0, team.ParentID); err != nil { return err } return s.db.Create(team).Error } func (s *Store) UpdateTeam(id uint, updates model.Team) (*model.Team, error) { if id == 0 { return nil, errors.New("team id is required") } if err := normalizeTeam(&updates); err != nil { return nil, err } err := s.db.Transaction(func(tx *gorm.DB) error { var team model.Team if err := tx.First(&team, id).Error; err != nil { return err } if err := s.validateTeamParent(tx, id, updates.ParentID); err != nil { return err } if err := tx.Model(&team).Updates(map[string]any{ "parent_id": updates.ParentID, "name": updates.Name, "description": updates.Description, "sort": updates.Sort, }).Error; err != nil { return err } // admin_users.team 是旧版字符串字段,仍被历史数据权限和导出兜底读取;团队重命名时同步维护,避免新旧字段长期分叉。 return tx.Model(&model.User{}).Where("team_id = ?", id).Update("team", updates.Name).Error }) if err != nil { return nil, err } return s.GetTeam(id) } func (s *Store) TeamAndChildIDs(id uint) ([]uint, error) { if id == 0 { return nil, errors.New("team id is required") } ids := []uint{id} var childIDs []uint if err := s.db.Model(&model.Team{}).Where("parent_id = ?", id).Order("sort ASC, id ASC").Pluck("id", &childIDs).Error; err != nil { return nil, err } ids = append(ids, childIDs...) return ids, nil } func (s *Store) NormalizeUserTeam(teamID *uint, legacyName string) (*uint, string, error) { name := strings.TrimSpace(legacyName) if teamID != nil { if *teamID == 0 { return nil, "", nil } team, err := s.GetTeam(*teamID) if errors.Is(err, gorm.ErrRecordNotFound) { return nil, "", errors.New("团队不存在") } if err != nil { return nil, "", err } id := team.ID return &id, team.Name, nil } if name == "" { return nil, "", nil } team, err := s.FindTeamByName(name) if err == nil { id := team.ID return &id, team.Name, nil } if errors.Is(err, gorm.ErrRecordNotFound) { // 老客户端可能仍提交自由文本团队;无法匹配到团队表时保留文本,不阻断兼容请求。 return nil, name, nil } return nil, "", err } func normalizeTeam(team *model.Team) error { team.Name = strings.TrimSpace(team.Name) team.Description = strings.TrimSpace(team.Description) if team.ParentID != nil && *team.ParentID == 0 { team.ParentID = nil } if team.Name == "" { return errors.New("团队名称不能为空") } return nil } func (s *Store) validateTeamParent(tx *gorm.DB, currentID uint, parentID *uint) error { if parentID == nil { return nil } if currentID > 0 && *parentID == currentID { return errors.New("子部门不能挂到自己下面") } var parent model.Team if err := tx.First(&parent, *parentID).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return errors.New("父级团队不存在") } return err } if parent.ParentID != nil { return errors.New("暂不支持三级团队") } if currentID > 0 { var childCount int64 if err := tx.Model(&model.Team{}).Where("parent_id = ?", currentID).Count(&childCount).Error; err != nil { return err } // 已有子部门的团队如果再被设为子部门,会形成三级结构;这里提前阻断,保证团队树始终只有团队和子部门两层。 if childCount > 0 { return errors.New("已有子部门的团队不能设为子部门") } } return nil }