@ -10,14 +10,14 @@ import LeStorage
import SwiftUI
@ Observable
public final class Tournament : BaseTournament {
final public class Tournament : BaseTournament {
// l o c a l v a r i a b l e
public var refreshInProgress : Bool = false
public var lastTeamRefresh : Date ?
public var refreshRanking : Bool = false
func shouldRefreshTeams ( forced : Bool ) -> Bool {
public func shouldRefreshTeams ( forced : Bool ) -> Bool {
if forced {
return true
}
@ -27,98 +27,6 @@ public final class Tournament: BaseTournament {
@ ObservationIgnored
public var navigationPath : [ Screen ] = [ ]
// i n t e r n a l i n i t ( e v e n t : S t r i n g ? = n i l , n a m e : S t r i n g ? = n i l , s t a r t D a t e : D a t e = D a t e ( ) , e n d D a t e : D a t e ? = n i l , c r e a t i o n D a t e : D a t e = D a t e ( ) , i s P r i v a t e : B o o l = f a l s e , g r o u p S t a g e F o r m a t : M a t c h F o r m a t ? = n i l , r o u n d F o r m a t : M a t c h F o r m a t ? = n i l , l o s e r R o u n d F o r m a t : M a t c h F o r m a t ? = n i l , g r o u p S t a g e S o r t M o d e : G r o u p S t a g e O r d e r i n g M o d e , g r o u p S t a g e C o u n t : I n t = 4 , r a n k S o u r c e D a t e : D a t e ? = n i l , d a y D u r a t i o n : I n t = 1 , t e a m C o u n t : I n t = 2 4 , t e a m S o r t i n g : T e a m S o r t i n g T y p e ? = n i l , f e d e r a l C a t e g o r y : T o u r n a m e n t C a t e g o r y , f e d e r a l L e v e l C a t e g o r y : T o u r n a m e n t L e v e l , f e d e r a l A g e C a t e g o r y : F e d e r a l T o u r n a m e n t A g e , c l o s e d R e g i s t r a t i o n D a t e : D a t e ? = n i l , g r o u p S t a g e A d d i t i o n a l Q u a l i f i e d : I n t = 0 , c o u r t C o u n t : I n t = 2 , p r i o r i t i z e C l u b M e m b e r s : B o o l = f a l s e , q u a l i f i e d P e r G r o u p S t a g e : I n t = 1 , t e a m s P e r G r o u p S t a g e : I n t = 4 , e n t r y F e e : D o u b l e ? = n i l , a d d i t i o n a l E s t i m a t i o n D u r a t i o n : I n t = 0 , i s D e l e t e d : B o o l = f a l s e , p u b l i s h T e a m s : B o o l = f a l s e , p u b l i s h S u m m o n s : B o o l = f a l s e , p u b l i s h G r o u p S t a g e s : B o o l = f a l s e , p u b l i s h B r a c k e t s : B o o l = f a l s e , s h o u l d V e r i f y B r a c k e t : B o o l = f a l s e , s h o u l d V e r i f y G r o u p S t a g e : B o o l = f a l s e , h i d e T e a m s W e i g h t : B o o l = f a l s e , p u b l i s h T o u r n a m e n t : B o o l = f a l s e , h i d e P o i n t s E a r n e d : B o o l = f a l s e , p u b l i s h R a n k i n g s : B o o l = f a l s e , l o s e r B r a c k e t M o d e : L o s e r B r a c k e t M o d e = . a u t o m a t i c , i n i t i a l S e e d R o u n d : I n t = 0 , i n i t i a l S e e d C o u n t : I n t = 0 ) {
// s u p e r . i n i t ( )
// }
public init ( event : String ? = nil , name : String ? = nil , startDate : Date = Date ( ) , endDate : Date ? = nil , creationDate : Date = Date ( ) , isPrivate : Bool = true , groupStageFormat : MatchFormat ? = nil , roundFormat : MatchFormat ? = nil , loserRoundFormat : MatchFormat ? = nil , groupStageSortMode : GroupStageOrderingMode , groupStageCount : Int = 4 , rankSourceDate : Date ? = nil , dayDuration : Int = 1 , teamCount : Int = 24 , teamSorting : TeamSortingType ? = nil , federalCategory : TournamentCategory , federalLevelCategory : TournamentLevel , federalAgeCategory : FederalTournamentAge , closedRegistrationDate : Date ? = nil , groupStageAdditionalQualified : Int = 0 , courtCount : Int = 2 , prioritizeClubMembers : Bool = false , qualifiedPerGroupStage : Int = 1 , teamsPerGroupStage : Int = 4 , entryFee : Double ? = nil , additionalEstimationDuration : Int = 0 , isDeleted : Bool = false , publishTeams : Bool = false , publishSummons : Bool = false , publishGroupStages : Bool = false , publishBrackets : Bool = false , shouldVerifyBracket : Bool = false , shouldVerifyGroupStage : Bool = false , hideTeamsWeight : Bool = false , publishTournament : Bool = false , hidePointsEarned : Bool = false , publishRankings : Bool = false , loserBracketMode : LoserBracketMode = . automatic , initialSeedRound : Int = 0 , initialSeedCount : Int = 0 , enableOnlineRegistration : Bool = false , registrationDateLimit : Date ? = nil , openingRegistrationDate : Date ? = nil , waitingListLimit : Int ? = nil , accountIsRequired : Bool = true , licenseIsRequired : Bool = true , minimumPlayerPerTeam : Int = 2 , maximumPlayerPerTeam : Int = 2 , information : String ? = nil ,
umpireCustomMail : String ? = nil ,
umpireCustomContact : String ? = nil ,
umpireCustomPhone : String ? = nil ,
hideUmpireMail : Bool = false ,
hideUmpirePhone : Bool = true ,
disableRankingFederalRuling : Bool = false ,
teamCountLimit : Bool = true
) {
super . init ( )
self . event = event
self . name = name
self . startDate = startDate
self . endDate = endDate
self . creationDate = creationDate
#if DEBUG
self . isPrivate = false
#else
self . isPrivate = isPrivate
#endif
self . groupStageFormat = groupStageFormat
self . roundFormat = roundFormat
self . loserRoundFormat = loserRoundFormat
self . groupStageSortMode = groupStageSortMode
self . groupStageCount = groupStageCount
self . rankSourceDate = rankSourceDate
self . dayDuration = dayDuration
self . teamCount = teamCount
self . teamSorting = teamSorting ? ? federalLevelCategory . defaultTeamSortingType
self . federalCategory = federalCategory
self . federalLevelCategory = federalLevelCategory
self . federalAgeCategory = federalAgeCategory
self . closedRegistrationDate = closedRegistrationDate
self . groupStageAdditionalQualified = groupStageAdditionalQualified
self . courtCount = courtCount
self . prioritizeClubMembers = prioritizeClubMembers
self . qualifiedPerGroupStage = qualifiedPerGroupStage
self . teamsPerGroupStage = teamsPerGroupStage
self . entryFee = entryFee
self . additionalEstimationDuration = additionalEstimationDuration
self . isDeleted = isDeleted
#if DEBUG
self . publishTeams = true
self . publishSummons = true
self . publishBrackets = true
self . publishGroupStages = true
self . publishRankings = true
self . publishTournament = true
#else
self . publishTeams = publishTeams
self . publishSummons = publishSummons
self . publishBrackets = publishBrackets
self . publishGroupStages = publishGroupStages
self . publishRankings = publishRankings
self . publishTournament = publishTournament
#endif
self . shouldVerifyBracket = shouldVerifyBracket
self . shouldVerifyGroupStage = shouldVerifyGroupStage
self . hideTeamsWeight = hideTeamsWeight
self . hidePointsEarned = hidePointsEarned
self . loserBracketMode = loserBracketMode
self . initialSeedRound = initialSeedRound
self . initialSeedCount = initialSeedCount
self . enableOnlineRegistration = enableOnlineRegistration
self . registrationDateLimit = registrationDateLimit
self . openingRegistrationDate = openingRegistrationDate
self . waitingListLimit = waitingListLimit
self . accountIsRequired = accountIsRequired
self . licenseIsRequired = licenseIsRequired
self . minimumPlayerPerTeam = minimumPlayerPerTeam
self . maximumPlayerPerTeam = maximumPlayerPerTeam
self . information = information
self . umpireCustomMail = umpireCustomMail
self . umpireCustomContact = umpireCustomContact
self . disableRankingFederalRuling = disableRankingFederalRuling
self . teamCountLimit = teamCountLimit
}
required init ( from decoder : Decoder ) throws {
try super . init ( from : decoder )
}
required public init ( ) {
super . init ( )
}
public var tournamentStore : TournamentStore ? {
return TournamentLibrary . shared . store ( tournamentId : self . id )
@ -155,6 +63,7 @@ public final class Tournament: BaseTournament {
}
// MARK: - C o m p u t e d D e p e n d e n c i e s
public func unsortedTeams ( ) -> [ TeamRegistration ] {
@ -208,7 +117,7 @@ public final class Tournament: BaseTournament {
return self . startDate
}
func canBePublished ( ) -> Bool {
public func canBePublished ( ) -> Bool {
switch state ( ) {
case . build , . finished , . running :
return unsortedTeams ( ) . count > 3
@ -286,7 +195,7 @@ public final class Tournament: BaseTournament {
return URLs . main . url . appending ( path : " tournament/ \( id ) " ) . appending ( path : pageLink . path )
}
func courtUsed ( runningMatches : [ Match ] ) -> [ Int ] {
public func courtUsed ( runningMatches : [ Match ] ) -> [ Int ] {
#if _DEBUGING_TIME // D E B U G I N G T I M E
let start = Date ( )
defer {
@ -392,7 +301,7 @@ defer {
return seeds ( ) . filter { $0 . isSeedable ( ) }
}
func lastSeedRound ( ) -> Int {
public func lastSeedRound ( ) -> Int {
if let last = seeds ( ) . filter ( { $0 . bracketPosition != nil } ) . last {
return RoundRule . roundIndex ( fromMatchIndex : last . bracketPosition ! / 2 )
} else {
@ -400,16 +309,16 @@ defer {
}
}
func getRound ( atRoundIndex roundIndex : Int ) -> Round ? {
public func getRound ( atRoundIndex roundIndex : Int ) -> Round ? {
return self . tournamentStore ? . rounds . first ( where : { $0 . index = = roundIndex } )
// r e t u r n S t o r e . m a i n . f i l t e r ( i s I n c l u d e d : { $ 0 . t o u r n a m e n t = = i d & & $ 0 . i n d e x = = r o u n d I n d e x } ) . f i r s t
}
func availableSeedSpot ( inRoundIndex roundIndex : Int ) -> [ Match ] {
public func availableSeedSpot ( inRoundIndex roundIndex : Int ) -> [ Match ] {
return getRound ( atRoundIndex : roundIndex ) ? . playedMatches ( ) . filter { $0 . isEmpty ( ) } ? ? [ ]
}
func availableSeedOpponentSpot ( inRoundIndex roundIndex : Int ) -> [ Match ] {
public func availableSeedOpponentSpot ( inRoundIndex roundIndex : Int ) -> [ Match ] {
return getRound ( atRoundIndex : roundIndex ) ? . playedMatches ( ) . filter { $0 . hasSpaceLeft ( ) } ? ? [ ]
}
public func availableSeedGroups ( includeAll : Bool = false ) -> [ SeedInterval ] {
@ -596,28 +505,55 @@ defer {
return groupStages . filter ( { $0 . hasStarted ( ) && $0 . hasEnded ( ) = = false } ) . sorted ( by : \ . index ) . first ? ? groupStages . first
}
func matchesWithSpace ( ) -> [ Match ] {
public func matchesWithSpace ( ) -> [ Match ] {
getActiveRound ( ) ? . playedMatches ( ) . filter ( { $0 . hasSpaceLeft ( ) } ) ? ? [ ]
}
public func getActiveRound ( withSeeds : Bool = false ) -> Round ? {
let rounds : [ Round ] = self . rounds ( )
let unfinishedRounds : [ Round ] = rounds . filter { $0 . hasStarted ( ) && $0 . hasEnded ( ) = = false }
let sortedRounds : [ Round ] = unfinishedRounds . sorted ( by : \ . index ) . reversed ( )
let round = sortedRounds . first ? ? rounds . last ( where : { $0 . hasEnded ( ) } ) ? ? rounds . first
for round in rounds {
let playedMatches = round . playedMatches ( )
if withSeeds {
if round ? . seeds ( ) . isEmpty = = false {
// O p t i m i z a t i o n : I f n o m a t c h e s h a v e s t a r t e d i n t h i s r o u n d , r e t u r n n i l i m m e d i a t e l y
if ! playedMatches . contains ( where : { $0 . hasStarted ( ) } ) {
return round
} else {
return nil
}
} else {
return round
if playedMatches . contains ( where : { $0 . hasStarted ( ) && ! $0 . hasEnded ( ) } ) {
if withSeeds {
if ! round . seeds ( ) . isEmpty {
return round
} else {
return nil
}
} else {
return round
}
}
}
return nil
}
public func getActiveRoundAndStatus ( ) -> ( Round , String ) ? {
let rounds : [ Round ] = self . rounds ( )
for round in rounds {
let playedMatches = round . playedMatches ( )
// O p t i m i z a t i o n : I f n o m a t c h e s h a v e s t a r t e d i n t h i s r o u n d , r e t u r n n i l i m m e d i a t e l y
if ! playedMatches . contains ( where : { $0 . hasStarted ( ) } ) {
return ( round , round . roundStatus ( playedMatches : playedMatches ) )
}
if playedMatches . contains ( where : { $0 . hasStarted ( ) && ! $0 . hasEnded ( ) } ) {
return ( round , round . roundStatus ( playedMatches : playedMatches ) )
}
}
return nil
}
public func getPlayedMatchDateIntervals ( in event : Event ) -> [ DateInterval ] {
let allMatches : [ Match ] = self . allMatches ( ) . filter { $0 . courtIndex != nil && $0 . startDate != nil }
return allMatches . map { match in
@ -634,7 +570,7 @@ defer {
return tournamentStore . matches . filter { $0 . disabled = = false }
}
func _allMatchesIncludingDisabled ( ) -> [ Match ] {
public func _allMatchesIncludingDisabled ( ) -> [ Match ] {
guard let tournamentStore = self . tournamentStore else { return [ ] }
return Array ( tournamentStore . matches )
}
@ -642,7 +578,7 @@ defer {
public func rounds ( ) -> [ Round ] {
guard let tournamentStore = self . tournamentStore else { return [ ] }
let rounds : [ Round ] = tournamentStore . rounds . filter { $0 . isUpperBracket ( ) }
return rounds . sorted ( by : \ . index ) . reversed ( )
return rounds . sorted { $0 . index > $1 . index }
}
public func sortedTeams ( selectedSortedTeams : [ TeamRegistration ] ) -> [ TeamRegistration ] {
@ -655,6 +591,7 @@ defer {
return waitingListTeams ( in : teams , includingWalkOuts : false )
}
public func selectedSortedTeams ( ) -> [ TeamRegistration ] {
#if _DEBUG_TIME // D E B U G I N G T I M E
let start = Date ( )
@ -765,7 +702,7 @@ defer {
public func duplicates ( in players : [ PlayerRegistration ] ) -> [ PlayerRegistration ] {
var duplicates = [ PlayerRegistration ] ( )
Set ( players . compactMap ( { $0 . licenceId } ) ) . forEach { licenceId in
let found = players . filter ( { $0 . licenceId = = licenceId } )
let found = players . filter ( { $0 . licenceId ? . strippedLicense = = licenceId . strippedLicense } )
if found . count > 1 {
duplicates . append ( found . first ! )
}
@ -828,12 +765,6 @@ defer {
}
}
public func getStartDate ( ofSeedIndex seedIndex : Int ? ) -> Date ? {
guard let seedIndex else { return nil }
return selectedSortedTeams ( ) [ safe : seedIndex ] ? . callDate
}
public func maximumCourtsPerGroupSage ( ) -> Int {
if teamsPerGroupStage > 1 {
return min ( teamsPerGroupStage / 2 , courtCount )
@ -841,7 +772,7 @@ defer {
return max ( 1 , courtCount )
}
}
public func isStartDateIsDifferentThanCallDate ( _ team : TeamRegistration , expectedSummonDate : Date ? = nil ) -> Bool {
guard let summonDate = team . callDate else { return true }
let expectedSummonDate : Date ? = team . expectedSummonDate ( ) ? ? expectedSummonDate
@ -900,6 +831,10 @@ defer {
return allMatches . filter ( { $0 . isRunning ( ) = = false && $0 . hasEnded ( ) = = false } ) . sorted ( using : defaultSorting , order : . ascending )
}
public func getStartDate ( ofSeedIndex seedIndex : Int ? ) -> Date ? {
guard let seedIndex else { return nil }
return selectedSortedTeams ( ) [ safe : seedIndex ] ? . callDate
}
public static func finishedMatches ( _ allMatches : [ Match ] , limit : Int ? ) -> [ Match ] {
#if _DEBUG_TIME // D E B U G I N G T I M E
@ -1031,7 +966,7 @@ defer {
let groupStages = groupStages ( )
var baseRank = teamCount - groupStageSpots ( ) + qualifiedPerGroupStage * groupStageCount + groupStageAdditionalQualified
if disableRankingFederalRuling = = false {
if disableRankingFederalRuling = = false , baseRank > 0 {
baseRank += qualifiedPerGroupStage * groupStageCount + groupStageAdditionalQualified - 1
}
let alreadyPlaceTeams = Array ( teams . values . flatMap ( { $0 } ) )
@ -1096,6 +1031,19 @@ defer {
return rankings
}
public func refreshPointsEarned ( assimilationLevel : TournamentLevel ? = nil ) {
guard let tournamentStore = self . tournamentStore else { return }
let tournamentLevel = assimilationLevel ? ? tournamentLevel
let unsortedTeams = unsortedTeams ( )
unsortedTeams . forEach { team in
if let finalRanking = team . finalRanking {
team . pointsEarned = isAnimation ( ) ? nil : tournamentLevel . points ( for : finalRanking - 1 , count : teamCount )
}
}
tournamentStore . teamRegistrations . addOrUpdate ( contentOfs : unsortedTeams )
}
public func lockRegistration ( ) {
closedRegistrationDate = Date ( )
@ -1119,6 +1067,17 @@ defer {
self . tournamentStore ? . teamRegistrations . addOrUpdate ( contentOfs : teams )
}
public func updateWeights ( ) {
let teams = self . unsortedTeams ( )
teams . forEach { team in
let players = team . unsortedPlayers ( )
players . forEach { $0 . setComputedRank ( in : self ) }
team . setWeight ( from : players , inTournamentCategory : tournamentCategory )
self . tournamentStore ? . playerRegistrations . addOrUpdate ( contentOfs : players )
}
self . tournamentStore ? . teamRegistrations . addOrUpdate ( contentOfs : teams )
}
public func missingUnrankedValue ( ) -> Bool {
return maleUnrankedValue = = nil || femaleUnrankedValue = = nil
}
@ -1136,9 +1095,9 @@ defer {
}
}
let displayStyleCategory = hideSenior ? . short : displayStyle
var levelCategory = [ tournamentLevel . localizedLevelLabel ( displayStyle ) , tournamentCategory . localizedLabel ( displayStyle ) ]
var levelCategory = [ tournamentLevel . localizedLevelLabel ( displayStyle ) , tournamentCategory . localizedCategory Label ( displayStyle , ageCategory : federalAgeCategory ) ]
if displayStyle = = . short {
levelCategory = [ tournamentLevel . localizedLevelLabel ( displayStyle ) + tournamentCategory . localizedLabel ( displayStyle ) ]
levelCategory = [ tournamentLevel . localizedLevelLabel ( displayStyle ) + tournamentCategory . localizedCategory Label ( displayStyle , ageCategory : federalAgeCategory ) ]
}
let array = levelCategory + [ federalTournamentAge . localizedFederalAgeLabel ( displayStyleCategory ) ]
let title : String = array . filter ( { $0 . isEmpty = = false } ) . joined ( separator : " " )
@ -1151,10 +1110,10 @@ defer {
public func localizedTournamentType ( ) -> String {
switch tournamentLevel {
case . unlisted :
case . unlisted , . championship :
return tournamentLevel . localizedLevelLabel ( . short )
default :
return tournamentLevel . localizedLevelLabel ( . short ) + tournamentCategory . localizedLabel ( . short )
return tournamentLevel . localizedLevelLabel ( . short ) + tournamentCategory . localizedCategory Label ( . short , ageCategory : federalAgeCategory )
}
}
@ -1185,6 +1144,7 @@ defer {
return groupStageCount * qualifiedPerGroupStage
}
public func availableQualifiedTeams ( ) -> [ TeamRegistration ] {
#if _DEBUG_TIME // D E B U G I N G T I M E
let start = Date ( )
@ -1225,7 +1185,7 @@ defer {
// r e t u r n q u a l i f i e d T e a m s ( ) . c o u n t = = q u a l i f i e d F r o m G r o u p S t a g e ( ) + g r o u p S t a g e A d d i t i o n a l Q u a l i f i e d
}
func groupStageLoserBracketAreOver ( ) -> Bool {
public func groupStageLoserBracketAreOver ( ) -> Bool {
guard let groupStageLoserBracket = groupStageLoserBracket ( ) else {
return true
}
@ -1337,8 +1297,8 @@ defer {
cut = TeamRegistration . TeamRange ( availableSeeds . first , availableSeeds . last )
}
if let round = getActiveRound ( ) {
return ( [ round . roundTitle ( . short ) , round . roundStatus ( ) ] . joined ( separator : " " ) . lowercased ( ) , description , cut )
if let roundAndStatus = getActiveRoundAndStatus ( ) {
return ( [ roundAndStatus . 0 .roundTitle ( . short ) , roundAndStatus . 1 ] . joined ( separator : " " ) . lowercased ( ) , description , cut )
} else {
return ( " " , description , nil )
}
@ -1353,15 +1313,16 @@ defer {
let cut : TeamRegistration . TeamRange ? = isAnimation ( ) ? nil : TeamRegistration . TeamRange ( groupStageTeams . first , groupStageTeams . last )
let runningGroupStages = groupStages ( ) . filter ( { $0 . isRunning ( ) } )
if groupStagesAreOver ( ) { return ( " terminées " , cut ) }
let groupStages = groupStages ( )
let runningGroupStages = groupStages . filter ( { $0 . isRunning ( ) } )
if runningGroupStages . isEmpty {
let ongoingGroupStages = runningGroupStages . filter ( { $0 . hasStarted ( ) && $0 . hasEnded ( ) = = false } )
if ongoingGroupStages . isEmpty = = false {
return ( " Poule " + ongoingGroupStages . count . pluralSuffix + " " + ongoingGroupStages . map { ( $0 . index + 1 ) . formatted ( ) } . joined ( separator : " , " ) + " en cours " , cut )
}
return ( groupStages ( ) . count . formatted ( ) + " poule " + groupStages ( ) . count . pluralSuffix , cut )
return ( groupStages . count . formatted ( ) + " poule " + groupStages . count . pluralSuffix , cut )
} else {
return ( " Poule " + runningGroupStages . count . pluralSuffix + " " + runningGroupStages . map { ( $0 . index + 1 ) . formatted ( ) } . joined ( separator : " , " ) + " en cours " , cut )
}
@ -1393,6 +1354,23 @@ defer {
}
}
public func addEmptyTeamRegistration ( _ count : Int ) {
guard let tournamentStore = self . tournamentStore else { return }
let teams = ( 0. . < count ) . map { _ in
let team = TeamRegistration ( tournament : id )
team . setWeight ( from : [ ] , inTournamentCategory : self . tournamentCategory )
return team
}
do {
try tournamentStore . teamRegistrations . addOrUpdate ( contentOfs : teams )
} catch {
Logger . error ( error )
}
}
public func buildGroupStages ( ) {
guard groupStages ( ) . isEmpty , let tournamentStore = self . tournamentStore else {
return
@ -1597,6 +1575,20 @@ defer {
}
}
public var tournamentCategory : TournamentCategory {
get {
federalCategory
}
set {
if federalCategory != newValue {
federalCategory = newValue
updateWeights ( )
} else {
federalCategory = newValue
}
}
}
public var tournamentLevel : TournamentLevel {
get {
federalLevelCategory
@ -1638,9 +1630,108 @@ defer {
return groupStageMatchFormat
}
}
public func initSettings ( templateTournament : Tournament ? ) {
setupDefaultPrivateSettings ( templateTournament : templateTournament )
setupUmpireSettings ( defaultTournament : nil ) // d e f a u l t i s n o t t e m p l a t e , d e f a u l t i s f o r e v e n t s h a r i n g s e t t i n g s
if let templateTournament {
setupRegistrationSettings ( templateTournament : templateTournament )
}
setupFederalSettings ( )
}
public func setupFederalSettings ( ) {
teamSorting = tournamentLevel . defaultTeamSortingType
groupStageMatchFormat = groupStageSmartMatchFormat ( )
loserBracketMatchFormat = loserBracketSmartMatchFormat ( 5 )
matchFormat = roundSmartMatchFormat ( 5 )
entryFee = tournamentLevel . entryFee
registrationDateLimit = deadline ( for : . inscription )
if enableOnlineRegistration , isAnimation ( ) = = false {
accountIsRequired = true
licenseIsRequired = true
}
}
public func deadline ( for type : TournamentDeadlineType ) -> Date ? {
guard [ . p500 , . p1000 , . p1500 , . p2000 ] . contains ( tournamentLevel ) else { return nil }
let daysOffset = type . daysOffset ( level : tournamentLevel )
if let date = Calendar . current . date ( byAdding : . day , value : daysOffset , to : startDate ) {
let startOfDay = Calendar . current . startOfDay ( for : date )
return Calendar . current . date ( byAdding : type . timeOffset , to : startOfDay )
}
return nil
}
public func setupDefaultPrivateSettings ( templateTournament : Tournament ? ) {
#if DEBUG
self . isPrivate = false
self . publishTeams = true
self . publishSummons = true
self . publishBrackets = true
self . publishGroupStages = true
self . publishRankings = true
self . publishTournament = true
#else
var shouldBePrivate = templateTournament ? . isPrivate ? ? true
if Guard . main . currentPlan = = . monthlyUnlimited {
shouldBePrivate = false
} else if Guard . main . purchasedTransactions . isEmpty = = false {
shouldBePrivate = false
}
self . isPrivate = shouldBePrivate
#endif
}
public func setupUmpireSettings ( defaultTournament : Tournament ? = nil ) {
if let defaultTournament {
self . umpireCustomMail = defaultTournament . umpireCustomMail
self . umpireCustomPhone = defaultTournament . umpireCustomPhone
self . umpireCustomContact = defaultTournament . umpireCustomContact
self . hideUmpireMail = defaultTournament . hideUmpireMail
self . hideUmpirePhone = defaultTournament . hideUmpirePhone
self . disableRankingFederalRuling = defaultTournament . disableRankingFederalRuling
self . loserBracketMode = defaultTournament . loserBracketMode
} else {
let user = DataStore . shared . user
self . umpireCustomMail = user . umpireCustomMail
self . umpireCustomPhone = user . umpireCustomPhone
self . umpireCustomContact = user . umpireCustomContact
self . hideUmpireMail = user . hideUmpireMail
self . hideUmpirePhone = user . hideUmpirePhone
self . disableRankingFederalRuling = user . disableRankingFederalRuling
self . loserBracketMode = user . loserBracketMode
}
}
public func setupRegistrationSettings ( templateTournament : Tournament ) {
self . enableOnlineRegistration = templateTournament . enableOnlineRegistration
self . accountIsRequired = templateTournament . accountIsRequired
self . licenseIsRequired = templateTournament . licenseIsRequired
self . minimumPlayerPerTeam = templateTournament . minimumPlayerPerTeam
self . maximumPlayerPerTeam = templateTournament . maximumPlayerPerTeam
self . waitingListLimit = templateTournament . waitingListLimit
self . teamCountLimit = templateTournament . teamCountLimit
self . enableOnlinePayment = templateTournament . enableOnlinePayment
self . onlinePaymentIsMandatory = templateTournament . onlinePaymentIsMandatory
self . enableOnlinePaymentRefund = templateTournament . enableOnlinePaymentRefund
self . stripeAccountId = templateTournament . stripeAccountId
self . enableTimeToConfirm = templateTournament . enableTimeToConfirm
self . isCorporateTournament = templateTournament . isCorporateTournament
if self . registrationDateLimit = = nil , templateTournament . registrationDateLimit != nil {
self . registrationDateLimit = startDate . truncateMinutesAndSeconds ( )
}
self . openingRegistrationDate = templateTournament . openingRegistrationDate != nil ? creationDate . truncateMinutesAndSeconds ( ) : nil
self . refundDateLimit = templateTournament . enableOnlinePaymentRefund ? startDate . truncateMinutesAndSeconds ( ) : nil
}
public func onlineRegistrationCanBeEnabled ( ) -> Bool {
isAnimation ( ) = = false
true
// i s A n i m a t i o n ( ) = = f a l s e
}
public func roundSmartMatchFormat ( _ roundIndex : Int ) -> MatchFormat {
@ -1662,7 +1753,7 @@ defer {
}
}
func isSameBuild ( _ build : any TournamentBuildHolder ) -> Bool {
public func isSameBuild ( _ build : any TournamentBuildHolder ) -> Bool {
tournamentLevel = = build . level
&& tournamentCategory = = build . category
&& federalTournamentAge = = build . age
@ -1824,7 +1915,7 @@ defer {
}
public func allLoserRoundMatches ( ) -> [ Match ] {
rounds ( ) . flatMap { $0 . loserRoundsAndChildren ( ) . flat Map ( { $0 . _ma tches ( ) } ) }
rounds ( ) . flatMap { $0 . a llL oserRoundMatches( ) }
}
public func seedsCount ( ) -> Int {
@ -1914,47 +2005,44 @@ defer {
}
public func addNewRound ( _ roundIndex : Int ) async {
let round = Round ( tournament : id , index : roundIndex , matchFormat : matchFormat )
let matchCount = RoundRule . numberOfMatches ( forRoundIndex : roundIndex )
let matchStartIndex = RoundRule . matchIndex ( fromRoundIndex : roundIndex )
let nextRound = round . nextRound ( )
var currentIndex = 0
let matches = ( 0. . < matchCount ) . map { index in // 0 i s f i n a l m a t c h
let computedIndex = index + matchStartIndex
let match = Match ( round : round . id , index : computedIndex , format : round . matchFormat )
if let nextRound , let followingMatch = self . tournamentStore ? . matches . first ( where : { $0 . round = = nextRound . id && $0 . index = = ( computedIndex - 1 ) / 2 } ) {
if followingMatch . disabled {
match . disabled = true
} else if computedIndex % 2 = = 1 && followingMatch . team ( . one ) != nil {
// i n d e x d u m a t c h c o u r a n t i m p a i r = p o s i t i o n h a u t d u p r o c h a i n m a t c h
match . disabled = true
} else if computedIndex % 2 = = 0 && followingMatch . team ( . two ) != nil {
// i n d e x d u m a t c h c o u r a n t p a i r = p o s i t i o n b a s s e d u p r o c h a i n m a t c h
match . disabled = true
await MainActor . run {
let round = Round ( tournament : id , index : roundIndex , matchFormat : matchFormat )
let matchCount = RoundRule . numberOfMatches ( forRoundIndex : roundIndex )
let matchStartIndex = RoundRule . matchIndex ( fromRoundIndex : roundIndex )
let nextRound = round . nextRound ( )
let tournamentStore = self . tournamentStore
var currentIndex = 0
let matches = ( 0. . < matchCount ) . map { index in // 0 i s f i n a l m a t c h
let computedIndex = index + matchStartIndex
let match = Match ( round : round . id , index : computedIndex , format : round . matchFormat )
if let nextRound , let followingMatch = tournamentStore ? . matches . first ( where : { $0 . round = = nextRound . id && $0 . index = = ( computedIndex - 1 ) / 2 } ) {
if followingMatch . disabled {
match . disabled = true
} else if computedIndex % 2 = = 1 && followingMatch . team ( . one ) != nil {
// i n d e x d u m a t c h c o u r a n t i m p a i r = p o s i t i o n h a u t d u p r o c h a i n m a t c h
match . disabled = true
} else if computedIndex % 2 = = 0 && followingMatch . team ( . two ) != nil {
// i n d e x d u m a t c h c o u r a n t p a i r = p o s i t i o n b a s s e d u p r o c h a i n m a t c h
match . disabled = true
} else {
match . setMatchName ( Match . setServerTitle ( upperRound : round , matchIndex : currentIndex ) )
currentIndex += 1
}
} else {
match . setMatchName ( Match . setServerTitle ( upperRound : round , matchIndex : currentIndex ) )
currentIndex += 1
}
} else {
match . setMatchName ( Match . setServerTitle ( upperRound : round , matchIndex : currentIndex ) )
currentIndex += 1
return match
}
tournamentStore ? . rounds . addOrUpdate ( instance : round )
tournamentStore ? . matches . addOrUpdate ( contentOfs : matches )
if round . index < 5 {
round . buildLoserBracket ( )
round . loserRounds ( ) . forEach { loserRound in
loserRound . disableUnplayedLoserBracketMatches ( )
}
}
return match
}
do {
try tournamentStore ? . rounds . addOrUpdate ( instance : round )
} catch {
Logger . error ( error )
}
do {
try tournamentStore ? . matches . addOrUpdate ( contentOfs : matches )
} catch {
Logger . error ( error )
}
round . buildLoserBracket ( )
matches . filter { $0 . disabled } . forEach {
$0 . _toggleLoserMatchDisableState ( true )
}
}
@ -2018,11 +2106,19 @@ defer {
// MARK: - S t a t u s
public func shouldTournamentBeOver ( ) async -> Bool {
return false
if hasEnded ( ) {
return true
}
if hasStarted ( ) = = false {
return false
}
if hasStarted ( ) , self . startDate . timeIntervalSinceNow > - 3600 * 24 {
return false
}
if tournamentStore ? . store . fileCollectionsAllLoaded ( ) = = false {
return false
}
#if _DEBUGING_TIME // D E B U G I N G T I M E
#if DEBUG // D E B U G I N G T I M E
let start = Date ( )
defer {
let duration = Duration . milliseconds ( Date ( ) . timeIntervalSince ( start ) * 1_000 )
@ -2057,6 +2153,10 @@ defer {
unsortedTeams ( ) . filter ( { $0 . hasRegisteredOnline ( ) } )
}
public func paidOnlineTeams ( ) -> [ TeamRegistration ] {
unsortedTeams ( ) . filter ( { $0 . hasPaidOnline ( ) } )
}
public func shouldWarnOnlineRegistrationUpdates ( ) -> Bool {
enableOnlineRegistration && onlineTeams ( ) . isEmpty = = false && hasEnded ( ) = = false && hasStarted ( ) = = false
}
@ -2064,12 +2164,17 @@ defer {
public func refreshTeamList ( forced : Bool ) async {
guard StoreCenter . main . isAuthenticated else { return }
guard tournamentStore ? . store . fileCollectionsAllLoaded ( ) = = true else { return }
guard shouldRefreshTeams ( forced : forced ) , refreshInProgress = = false , enableOnlineRegistration , hasEnded ( ) = = false else { return }
guard shouldRefreshTeams ( forced : forced ) , refreshInProgress = = false else { return }
if forced = = false {
guard enableOnlineRegistration , hasEnded ( ) = = false else {
return
}
}
refreshInProgress = true
do {
// t r y a w a i t s e l f . t o u r n a m e n t S t o r e ? . p l a y e r R e g i s t r a t i o n s . l o a d D a t a F r o m S e r v e r I f A l l o w e d ( c l e a r : t r u e )
// t r y a w a i t s e l f . t o u r n a m e n t S t o r e ? . t e a m S c o r e s . l o a d D a t a F r o m S e r v e r I f A l l o w e d ( c l e a r : t r u e )
// t r y a w a i t s e l f . t o u r n a m e n t S t o r e ? . t e a m R e g i s t r a t i o n s . l o a d D a t a F r o m S e r v e r I f A l l o w e d ( c l e a r : t r u e )
try await self . tournamentStore ? . playerRegistrations . loadDataFromServerIfAllowed ( clear : true )
// t r y a w a i t s e l f . t o u r n a m e n t S t o r e ? . t e a m S c o r e s . l o a d D a t a F r o m S e r v e r I f A l l o w e d ( c l e a r : t r u e )
try await self . tournamentStore ? . teamRegistrations . loadDataFromServerIfAllowed ( clear : true )
refreshInProgress = false
lastTeamRefresh = Date ( )
} catch {
@ -2079,6 +2184,11 @@ defer {
}
}
public func mailSubject ( ) -> String {
let subject = [ tournamentTitle ( hideSenior : true ) , formattedDate ( . short ) , clubName ] . compactMap ( { $0 } ) . joined ( separator : " | " )
return subject
}
// MARK: -
func insertOnServer ( ) throws {
@ -2104,42 +2214,12 @@ defer {
}
// MARK: - P a y m e n t s & C r y p t o
public enum PaymentError : Error {
case cantPayTournament
}
// MARK: - R e f a c t o
public var tournamentCategory : TournamentCategory {
get {
federalCategory
}
set {
if federalCategory != newValue {
federalCategory = newValue
updateWeights ( )
} else {
federalCategory = newValue
}
}
}
func updateWeights ( ) {
let teams = self . unsortedTeams ( )
teams . forEach { team in
let players = team . unsortedPlayers ( )
players . forEach { $0 . setComputedRank ( in : self ) }
team . setWeight ( from : players , inTournamentCategory : tournamentCategory )
self . tournamentStore ? . playerRegistrations . addOrUpdate ( contentOfs : players )
}
self . tournamentStore ? . teamRegistrations . addOrUpdate ( contentOfs : teams )
}
}
extension Bool {
@ -2151,7 +2231,7 @@ extension Bool {
return Int . random ( in : ( 5. . . 9 ) )
}
}
static func decodeInt ( _ int : Int ) -> Bool {
public static func decodeInt ( _ int : Int ) -> Bool {
switch int {
case ( 0. . . 4 ) :
return true
@ -2197,6 +2277,17 @@ extension Bool {
// }
// }
public extension Tournament {
static func getTemplateTournament ( ) -> Tournament ? {
return DataStore . shared . tournaments . filter { $0 . isTemplate && $0 . isDeleted = = false } . sorted ( by : \ . startDate , order : . descending ) . first
}
static func fake ( ) -> Tournament {
return Tournament ( event : " Roland Garros " , name : " Magic P100 " , startDate : Date ( ) , endDate : Date ( ) , creationDate : Date ( ) , isPrivate : false , groupStageFormat : . nineGames , roundFormat : nil , loserRoundFormat : nil , groupStageSortMode : . snake , groupStageCount : 4 , rankSourceDate : nil , dayDuration : 2 , teamCount : 24 , teamSorting : . rank , federalCategory : . men , federalLevelCategory : . p100 , federalAgeCategory : . a45 , closedRegistrationDate : nil , groupStageAdditionalQualified : 0 , courtCount : 4 , prioritizeClubMembers : false , qualifiedPerGroupStage : 2 , teamsPerGroupStage : 4 , entryFee : nil )
}
}
// / W a r n i n g : i f t h e e n u m h a s m o r e t h a n 1 0 c a s e s , t h e p a y m e n t a l g o i s b r o k e n
public enum TournamentPayment : Int , CaseIterable {