series overview added

This commit is contained in:
2026-03-15 12:05:47 +11:00
parent ba0844048a
commit af42c16faf
12 changed files with 3133 additions and 1 deletions

View File

@@ -324,3 +324,224 @@ func AutoForfeitUnplayedFixtures(
return len(unplayed), nil
}
// GetPlayoffSeriesByID retrieves a single playoff series with all relations needed
// for the series detail page.
func GetPlayoffSeriesByID(
ctx context.Context,
tx bun.Tx,
seriesID int,
) (*PlayoffSeries, error) {
series := new(PlayoffSeries)
err := tx.NewSelect().
Model(series).
Where("ps.id = ?", seriesID).
Relation("Bracket").
Relation("Bracket.Season").
Relation("Bracket.League").
Relation("Bracket.Series").
Relation("Team1").
Relation("Team2").
Relation("Winner").
Relation("Loser").
Relation("Matches", func(q *bun.SelectQuery) *bun.SelectQuery {
return q.Order("pm.match_number ASC")
}).
Scan(ctx)
if err != nil {
if err.Error() == "sql: no rows in result set" {
return nil, nil
}
return nil, errors.Wrap(err, "tx.NewSelect")
}
return series, nil
}
// CanScheduleSeries checks if the user is a manager of one of the teams in the series.
// Returns (canSchedule, teamID) where teamID is the team the user manages (0 if not a manager).
// Both teams must be assigned for scheduling to be possible.
func CanScheduleSeries(
ctx context.Context,
tx bun.Tx,
series *PlayoffSeries,
user *User,
) (bool, int, error) {
if user == nil || user.Player == nil {
return false, 0, nil
}
if series.Team1ID == nil || series.Team2ID == nil {
return false, 0, nil
}
roster := new(TeamRoster)
err := tx.NewSelect().
Model(roster).
Column("team_id", "is_manager").
Where("team_id IN (?)", bun.In([]int{*series.Team1ID, *series.Team2ID})).
Where("season_id = ?", series.Bracket.SeasonID).
Where("league_id = ?", series.Bracket.LeagueID).
Where("player_id = ?", user.Player.ID).
Scan(ctx)
if err != nil {
if err.Error() == "sql: no rows in result set" {
return false, 0, nil
}
return false, 0, errors.Wrap(err, "tx.NewSelect")
}
if !roster.IsManager {
return false, 0, nil
}
return true, roster.TeamID, nil
}
// GetSeriesTeamRosters returns rosters for both teams in a series.
// Returns map["team1"|"team2"] -> []*PlayerWithPlayStatus
func GetSeriesTeamRosters(
ctx context.Context,
tx bun.Tx,
series *PlayoffSeries,
) (map[string][]*PlayerWithPlayStatus, error) {
if series == nil {
return nil, errors.New("series cannot be nil")
}
rosters := map[string][]*PlayerWithPlayStatus{}
if series.Team1ID != nil {
team1Rosters := []*TeamRoster{}
err := tx.NewSelect().
Model(&team1Rosters).
Where("tr.team_id = ?", *series.Team1ID).
Where("tr.season_id = ?", series.Bracket.SeasonID).
Where("tr.league_id = ?", series.Bracket.LeagueID).
Relation("Player", func(q *bun.SelectQuery) *bun.SelectQuery {
return q.Relation("User")
}).
Scan(ctx)
if err != nil {
return nil, errors.Wrap(err, "tx.NewSelect team1 roster")
}
for _, tr := range team1Rosters {
rosters["team1"] = append(rosters["team1"], &PlayerWithPlayStatus{
Player: tr.Player,
Played: false,
IsManager: tr.IsManager,
})
}
}
if series.Team2ID != nil {
team2Rosters := []*TeamRoster{}
err := tx.NewSelect().
Model(&team2Rosters).
Where("tr.team_id = ?", *series.Team2ID).
Where("tr.season_id = ?", series.Bracket.SeasonID).
Where("tr.league_id = ?", series.Bracket.LeagueID).
Relation("Player", func(q *bun.SelectQuery) *bun.SelectQuery {
return q.Relation("User")
}).
Scan(ctx)
if err != nil {
return nil, errors.Wrap(err, "tx.NewSelect team2 roster")
}
for _, tr := range team2Rosters {
rosters["team2"] = append(rosters["team2"], &PlayerWithPlayStatus{
Player: tr.Player,
Played: false,
IsManager: tr.IsManager,
})
}
}
return rosters, nil
}
// ComputeSeriesPreview computes standings comparison data for the two teams in a series.
// Uses the same logic as ComputeMatchPreview but takes a series instead of a fixture.
func ComputeSeriesPreview(
ctx context.Context,
tx bun.Tx,
series *PlayoffSeries,
) (*MatchPreviewData, error) {
if series == nil || series.Bracket == nil {
return nil, errors.New("series and bracket cannot be nil")
}
seasonID := series.Bracket.SeasonID
leagueID := series.Bracket.LeagueID
// Get all teams in this season+league
allTeams, err := GetTeamsForSeasonLeague(ctx, tx, seasonID, leagueID)
if err != nil {
return nil, errors.Wrap(err, "GetTeamsForSeasonLeague")
}
// Get all allocated fixtures for the season+league
allFixtures, err := GetAllocatedFixtures(ctx, tx, seasonID, leagueID)
if err != nil {
return nil, errors.Wrap(err, "GetAllocatedFixtures")
}
// Get finalized results
allFixtureIDs := make([]int, len(allFixtures))
for i, f := range allFixtures {
allFixtureIDs[i] = f.ID
}
allResultMap, err := GetFinalizedResultsForFixtures(ctx, tx, allFixtureIDs)
if err != nil {
return nil, errors.Wrap(err, "GetFinalizedResultsForFixtures")
}
// Get accepted schedules for ordering recent games
allScheduleMap, err := GetAcceptedSchedulesForFixtures(ctx, tx, allFixtureIDs)
if err != nil {
return nil, errors.Wrap(err, "GetAcceptedSchedulesForFixtures")
}
// Compute leaderboard
leaderboard := ComputeLeaderboard(allTeams, allFixtures, allResultMap)
preview := &MatchPreviewData{
TotalTeams: len(leaderboard),
}
team1ID := 0
team2ID := 0
if series.Team1ID != nil {
team1ID = *series.Team1ID
}
if series.Team2ID != nil {
team2ID = *series.Team2ID
}
for _, entry := range leaderboard {
if entry.Team.ID == team1ID {
preview.HomePosition = entry.Position
preview.HomeRecord = entry.Record
}
if entry.Team.ID == team2ID {
preview.AwayPosition = entry.Position
preview.AwayRecord = entry.Record
}
}
if preview.HomeRecord == nil {
preview.HomeRecord = &TeamRecord{}
}
if preview.AwayRecord == nil {
preview.AwayRecord = &TeamRecord{}
}
// Compute recent games (last 5) for each team
if team1ID > 0 {
preview.HomeRecentGames = ComputeRecentGames(
team1ID, allFixtures, allResultMap, allScheduleMap, 5,
)
}
if team2ID > 0 {
preview.AwayRecentGames = ComputeRecentGames(
team2ID, allFixtures, allResultMap, allScheduleMap, 5,
)
}
return preview, nil
}