|
|
|
|
@ -330,24 +330,24 @@ class Tournament : ModelObject, Storable { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
override func deleteDependencies() throws { |
|
|
|
|
try Store.main.deleteDependencies(items: self.unsortedTeams()) |
|
|
|
|
try Store.main.deleteDependencies(items: self.groupStages()) |
|
|
|
|
try Store.main.deleteDependencies(items: self.rounds()) |
|
|
|
|
try Store.main.deleteDependencies(items: self._matchSchedulers()) |
|
|
|
|
DataStore.shared.teamRegistrations.deleteDependencies(self.unsortedTeams()) |
|
|
|
|
DataStore.shared.groupStages.deleteDependencies(self.groupStages()) |
|
|
|
|
DataStore.shared.rounds.deleteDependencies(self.rounds()) |
|
|
|
|
DataStore.shared.matchSchedulers.deleteDependencies(self._matchSchedulers()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// MARK: - Computed Dependencies |
|
|
|
|
|
|
|
|
|
func unsortedTeams() -> [TeamRegistration] { |
|
|
|
|
Store.main.filter { $0.tournament == self.id } |
|
|
|
|
return Store.main.filter { $0.tournament == self.id } |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func groupStages() -> [GroupStage] { |
|
|
|
|
Store.main.filter { $0.tournament == self.id }.sorted(by: \.index) |
|
|
|
|
return Store.main.filter { $0.tournament == self.id }.sorted(by: \.index) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func allRounds() -> [Round] { |
|
|
|
|
Store.main.filter { $0.tournament == self.id } |
|
|
|
|
return Store.main.filter { $0.tournament == self.id } |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// MARK: - |
|
|
|
|
@ -388,7 +388,7 @@ class Tournament : ModelObject, Storable { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func publishedTeamsDate() -> Date { |
|
|
|
|
startDate |
|
|
|
|
return self.startDate |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func canBePublished() -> Bool { |
|
|
|
|
@ -401,19 +401,22 @@ class Tournament : ModelObject, Storable { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func isTournamentPublished() -> Bool { |
|
|
|
|
(Date() >= publishedTournamentDate() && canBePublished()) || publishTournament |
|
|
|
|
return (Date() >= publishedTournamentDate() && canBePublished()) || publishTournament |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func areTeamsPublished() -> Bool { |
|
|
|
|
Date() >= startDate || publishTeams |
|
|
|
|
return Date() >= startDate || publishTeams |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func areSummonsPublished() -> Bool { |
|
|
|
|
Date() >= startDate || publishSummons |
|
|
|
|
return Date() >= startDate || publishSummons |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func publishedGroupStagesDate() -> Date? { |
|
|
|
|
if let first = groupStages().flatMap({ $0.playedMatches() }).compactMap({ $0.startDate }).sorted().first?.atEightAM() { |
|
|
|
|
fileprivate func _publishedDateFromMatches(_ matches: [Match]) -> Date? { |
|
|
|
|
let startDates: [Date] = matches.compactMap { $0.startDate } |
|
|
|
|
let sortedDates: [Date] = startDates.sorted() |
|
|
|
|
|
|
|
|
|
if let first: Date = sortedDates.first?.atEightAM() { |
|
|
|
|
if first.isEarlierThan(startDate) { |
|
|
|
|
return startDate |
|
|
|
|
} else { |
|
|
|
|
@ -424,6 +427,11 @@ class Tournament : ModelObject, Storable { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func publishedGroupStagesDate() -> Date? { |
|
|
|
|
let matches: [Match] = self.groupStages().flatMap { $0.playedMatches() } |
|
|
|
|
return self._publishedDateFromMatches(matches) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func areGroupStagesPublished() -> Bool { |
|
|
|
|
if publishGroupStages { return true } |
|
|
|
|
if let publishedGroupStagesDate = publishedGroupStagesDate() { |
|
|
|
|
@ -434,15 +442,8 @@ class Tournament : ModelObject, Storable { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func publishedBracketsDate() -> Date? { |
|
|
|
|
if let first = rounds().flatMap({ $0.playedMatches() }).compactMap({ $0.startDate }).sorted().first?.atEightAM() { |
|
|
|
|
if first.isEarlierThan(startDate) { |
|
|
|
|
return startDate |
|
|
|
|
} else { |
|
|
|
|
return first |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
return startDate |
|
|
|
|
} |
|
|
|
|
let matches: [Match] = self.rounds().flatMap { $0.playedMatches() } |
|
|
|
|
return self._publishedDateFromMatches(matches) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func areBracketsPublished() -> Bool { |
|
|
|
|
@ -471,7 +472,7 @@ class Tournament : ModelObject, Storable { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func hasStarted() -> Bool { |
|
|
|
|
startDate <= Date() |
|
|
|
|
return startDate <= Date() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func eventObject() -> Event? { |
|
|
|
|
@ -485,7 +486,7 @@ class Tournament : ModelObject, Storable { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func club() -> Club? { |
|
|
|
|
eventObject()?.clubObject() |
|
|
|
|
return eventObject()?.clubObject() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func locationLabel(_ displayStyle: DisplayStyle = .wide) -> String { |
|
|
|
|
@ -502,7 +503,7 @@ class Tournament : ModelObject, Storable { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func hasEnded() -> Bool { |
|
|
|
|
endDate != nil |
|
|
|
|
return endDate != nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func state() -> Tournament.State { |
|
|
|
|
@ -524,11 +525,11 @@ class Tournament : ModelObject, Storable { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func seededTeams() -> [TeamRegistration] { |
|
|
|
|
selectedSortedTeams().filter({ $0.bracketPosition != nil && $0.groupStagePosition == nil }) |
|
|
|
|
return selectedSortedTeams().filter({ $0.bracketPosition != nil && $0.groupStagePosition == nil }) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func groupStageTeams() -> [TeamRegistration] { |
|
|
|
|
selectedSortedTeams().filter({ $0.groupStagePosition != nil }) |
|
|
|
|
return selectedSortedTeams().filter({ $0.groupStagePosition != nil }) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func groupStageSpots() -> Int { |
|
|
|
|
@ -559,15 +560,15 @@ class Tournament : ModelObject, Storable { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func getRound(atRoundIndex roundIndex: Int) -> Round? { |
|
|
|
|
Store.main.filter(isIncluded: { $0.tournament == id && $0.index == roundIndex }).first |
|
|
|
|
return Store.main.filter(isIncluded: { $0.tournament == id && $0.index == roundIndex }).first |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func availableSeedSpot(inRoundIndex roundIndex: Int) -> [Match] { |
|
|
|
|
getRound(atRoundIndex: roundIndex)?.playedMatches().filter { $0.isEmpty() } ?? [] |
|
|
|
|
return getRound(atRoundIndex: roundIndex)?.playedMatches().filter { $0.isEmpty() } ?? [] |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func availableSeedOpponentSpot(inRoundIndex roundIndex: Int) -> [Match] { |
|
|
|
|
getRound(atRoundIndex: roundIndex)?.playedMatches().filter { $0.hasSpaceLeft() } ?? [] |
|
|
|
|
return getRound(atRoundIndex: roundIndex)?.playedMatches().filter { $0.hasSpaceLeft() } ?? [] |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func availableSeedGroups() -> [SeedInterval] { |
|
|
|
|
@ -1143,7 +1144,8 @@ class Tournament : ModelObject, Storable { |
|
|
|
|
let lastRankWoman = SourceFileManager.shared.getUnrankValue(forMale: false, rankSourceDate: rankSourceDate) |
|
|
|
|
let lastRankMan = SourceFileManager.shared.getUnrankValue(forMale: true, rankSourceDate: rankSourceDate) |
|
|
|
|
await MainActor.run { |
|
|
|
|
let monthData = MonthData(monthKey: URL.importDateFormatter.string(from: newDate)) |
|
|
|
|
let formatted: String = URL.importDateFormatter.string(from: newDate) |
|
|
|
|
let monthData: MonthData = MonthData(monthKey: formatted) |
|
|
|
|
monthData.maleUnrankedValue = lastRankMan |
|
|
|
|
monthData.femaleUnrankedValue = lastRankWoman |
|
|
|
|
do { |
|
|
|
|
@ -1156,7 +1158,7 @@ class Tournament : ModelObject, Storable { |
|
|
|
|
|
|
|
|
|
let lastRankMan = currentMonthData()?.maleUnrankedValue |
|
|
|
|
let lastRankWoman = currentMonthData()?.femaleUnrankedValue |
|
|
|
|
let dataURLs = SourceFileManager.shared.allFiles.filter({ $0.dateFromPath == newDate }) |
|
|
|
|
let dataURLs = SourceFileManager.shared.allFiles.filter { $0.dateFromPath == newDate } |
|
|
|
|
let sources = dataURLs.map { CSVParser(url: $0) } |
|
|
|
|
|
|
|
|
|
try await unsortedPlayers().concurrentForEach { player in |
|
|
|
|
@ -1165,23 +1167,23 @@ class Tournament : ModelObject, Storable { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func missingUnrankedValue() -> Bool { |
|
|
|
|
maleUnrankedValue == nil || femaleUnrankedValue == nil |
|
|
|
|
return maleUnrankedValue == nil || femaleUnrankedValue == nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func findTeam(_ players: [PlayerRegistration]) -> TeamRegistration? { |
|
|
|
|
unsortedTeams().first(where: { $0.includes(players) }) |
|
|
|
|
return unsortedTeams().first(where: { $0.includes(players) }) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func tournamentTitle(_ displayStyle: DisplayStyle = .wide) -> String { |
|
|
|
|
[tournamentLevel.localizedLabel(displayStyle) + " " + tournamentCategory.localizedLabel(displayStyle), displayStyle == .wide ? name : nil].compactMap({ $0 }).joined(separator: " - ") |
|
|
|
|
return [tournamentLevel.localizedLabel(displayStyle) + " " + tournamentCategory.localizedLabel(displayStyle), displayStyle == .wide ? name : nil].compactMap({ $0 }).joined(separator: " - ") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func hideWeight() -> Bool { |
|
|
|
|
tournamentLevel.hideWeight() |
|
|
|
|
return tournamentLevel.hideWeight() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func subtitle(_ displayStyle: DisplayStyle = .wide) -> String { |
|
|
|
|
name ?? "" |
|
|
|
|
return name ?? "" |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func formattedDate(_ displayStyle: DisplayStyle = .wide) -> String { |
|
|
|
|
@ -1194,20 +1196,20 @@ class Tournament : ModelObject, Storable { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func qualifiedFromGroupStage() -> Int { |
|
|
|
|
groupStageCount * qualifiedPerGroupStage |
|
|
|
|
return groupStageCount * qualifiedPerGroupStage |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func availableQualifiedTeams() -> [TeamRegistration] { |
|
|
|
|
unsortedTeams().filter({ $0.qualified && $0.bracketPosition == nil }) |
|
|
|
|
return unsortedTeams().filter({ $0.qualified && $0.bracketPosition == nil }) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func qualifiedTeams() -> [TeamRegistration] { |
|
|
|
|
unsortedTeams().filter({ $0.qualifiedFromGroupStage() }) |
|
|
|
|
return unsortedTeams().filter({ $0.qualifiedFromGroupStage() }) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func moreQualifiedToDraw() -> Int { |
|
|
|
|
max(qualifiedTeams().count - (qualifiedFromGroupStage() + groupStageAdditionalQualified), 0) |
|
|
|
|
return max(qualifiedTeams().count - (qualifiedFromGroupStage() + groupStageAdditionalQualified), 0) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func missingQualifiedFromGroupStages() -> [TeamRegistration] { |
|
|
|
|
@ -1231,7 +1233,7 @@ class Tournament : ModelObject, Storable { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func paymentMethodMessage() -> String? { |
|
|
|
|
DataStore.shared.user.summonsAvailablePaymentMethods ?? ContactType.defaultAvailablePaymentMethods |
|
|
|
|
return DataStore.shared.user.summonsAvailablePaymentMethods ?? ContactType.defaultAvailablePaymentMethods |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var entryFeeMessage: String { |
|
|
|
|
@ -1264,7 +1266,8 @@ class Tournament : ModelObject, Storable { |
|
|
|
|
func cashierStatus() async -> TournamentStatus { |
|
|
|
|
let selectedPlayers = selectedPlayers() |
|
|
|
|
let paid = selectedPlayers.filter({ $0.hasPaid() }) |
|
|
|
|
let label = paid.count.formatted() + " / " + selectedPlayers.count.formatted() + " joueurs encaissés" |
|
|
|
|
// let label = paid.count.formatted() + " / " + selectedPlayers.count.formatted() + " joueurs encaissés" |
|
|
|
|
let label = "\(paid.count.formatted()) / \(selectedPlayers.count.formatted()) joueurs encaissés" |
|
|
|
|
let completion = (Double(paid.count) / Double(selectedPlayers.count)) |
|
|
|
|
let completionLabel = completion.isNaN ? "" : completion.formatted(.percent.precision(.fractionLength(0))) |
|
|
|
|
return TournamentStatus(label: label, completion: completionLabel) |
|
|
|
|
@ -1273,7 +1276,8 @@ class Tournament : ModelObject, Storable { |
|
|
|
|
func scheduleStatus() async -> TournamentStatus { |
|
|
|
|
let allMatches = allMatches() |
|
|
|
|
let ready = allMatches.filter({ $0.startDate != nil }) |
|
|
|
|
let label = ready.count.formatted() + " / " + allMatches.count.formatted() + " matchs programmés" |
|
|
|
|
// let label = ready.count.formatted() + " / " + allMatches.count.formatted() + " matchs programmés" |
|
|
|
|
let label = "\(ready.count.formatted()) / \(allMatches.count.formatted()) matchs programmés" |
|
|
|
|
let completion = (Double(ready.count) / Double(allMatches.count)) |
|
|
|
|
let completionLabel = completion.isNaN ? "" : completion.formatted(.percent.precision(.fractionLength(0))) |
|
|
|
|
return TournamentStatus(label: label, completion: completionLabel) |
|
|
|
|
@ -1282,7 +1286,7 @@ class Tournament : ModelObject, Storable { |
|
|
|
|
func callStatus() async -> TournamentStatus { |
|
|
|
|
let selectedSortedTeams = selectedSortedTeams() |
|
|
|
|
let called = selectedSortedTeams.filter { isStartDateIsDifferentThanCallDate($0) == false } |
|
|
|
|
let label = called.count.formatted() + " / " + selectedSortedTeams.count.formatted() + " convoquées au bon horaire" |
|
|
|
|
let label = "\(called.count.formatted()) / \(selectedSortedTeams.count.formatted()) convoquées au bon horaire" |
|
|
|
|
let completion = (Double(called.count) / Double(selectedSortedTeams.count)) |
|
|
|
|
let completionLabel = completion.isNaN ? "" : completion.formatted(.percent.precision(.fractionLength(0))) |
|
|
|
|
return TournamentStatus(label: label, completion: completionLabel) |
|
|
|
|
@ -1291,7 +1295,7 @@ class Tournament : ModelObject, Storable { |
|
|
|
|
func confirmedSummonStatus() async -> TournamentStatus { |
|
|
|
|
let selectedSortedTeams = selectedSortedTeams() |
|
|
|
|
let called = selectedSortedTeams.filter { $0.confirmationDate != nil } |
|
|
|
|
let label = called.count.formatted() + " / " + selectedSortedTeams.count.formatted() + " confirmées" |
|
|
|
|
let label = "\(called.count.formatted()) / \(selectedSortedTeams.count.formatted()) confirmées" |
|
|
|
|
let completion = (Double(called.count) / Double(selectedSortedTeams.count)) |
|
|
|
|
let completionLabel = completion.isNaN ? "" : completion.formatted(.percent.precision(.fractionLength(0))) |
|
|
|
|
return TournamentStatus(label: label, completion: completionLabel) |
|
|
|
|
@ -1503,7 +1507,7 @@ class Tournament : ModelObject, Storable { |
|
|
|
|
|
|
|
|
|
print("Equipes \(chunks[index].map { $0.weight })") |
|
|
|
|
for (jIndex, _) in chunks[index].enumerated() { |
|
|
|
|
print("Position \(index+1) Poule \(groupStages[jIndex].index)") |
|
|
|
|
print("Position \(index + 1) Poule \(groupStages[jIndex].index)") |
|
|
|
|
chunks[index][jIndex].groupStage = groupStages[jIndex].id |
|
|
|
|
chunks[index][jIndex].groupStagePosition = index |
|
|
|
|
} |
|
|
|
|
@ -1519,11 +1523,11 @@ class Tournament : ModelObject, Storable { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func isFree() -> Bool { |
|
|
|
|
entryFee == nil || entryFee == 0 |
|
|
|
|
return entryFee == nil || entryFee == 0 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func indexOf(team: TeamRegistration) -> Int? { |
|
|
|
|
selectedSortedTeams().firstIndex(where: { $0.id == team.id }) |
|
|
|
|
return selectedSortedTeams().firstIndex(where: { $0.id == team.id }) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func labelIndexOf(team: TeamRegistration) -> String? { |
|
|
|
|
@ -1670,11 +1674,11 @@ class Tournament : ModelObject, Storable { |
|
|
|
|
private let _currentSelectionSorting : [MySortDescriptor<TeamRegistration>] = [.keyPath(\.weight), .keyPath(\.registrationDate!)] |
|
|
|
|
|
|
|
|
|
private func _matchSchedulers() -> [MatchScheduler] { |
|
|
|
|
Store.main.filter(isIncluded: { $0.tournament == self.id }) |
|
|
|
|
return Store.main.filter(isIncluded: { $0.tournament == self.id }) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func matchScheduler() -> MatchScheduler? { |
|
|
|
|
_matchSchedulers().first |
|
|
|
|
return self._matchSchedulers().first |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func currentMonthData() -> MonthData? { |
|
|
|
|
@ -1684,24 +1688,25 @@ class Tournament : ModelObject, Storable { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var maleUnrankedValue: Int? { |
|
|
|
|
currentMonthData()?.maleUnrankedValue |
|
|
|
|
return currentMonthData()?.maleUnrankedValue |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var femaleUnrankedValue: Int? { |
|
|
|
|
currentMonthData()?.femaleUnrankedValue |
|
|
|
|
return currentMonthData()?.femaleUnrankedValue |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func courtNameIfAvailable(atIndex courtIndex: Int) -> String? { |
|
|
|
|
club()?.customizedCourts.first(where: { $0.index == courtIndex })?.name |
|
|
|
|
return club()?.customizedCourts.first(where: { $0.index == courtIndex })?.name |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func courtName(atIndex courtIndex: Int) -> String { |
|
|
|
|
courtNameIfAvailable(atIndex: courtIndex) ?? Court.courtIndexedTitle(atIndex: courtIndex) |
|
|
|
|
return courtNameIfAvailable(atIndex: courtIndex) ?? Court.courtIndexedTitle(atIndex: courtIndex) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func tournamentWinner() -> TeamRegistration? { |
|
|
|
|
let final : Round? = Store.main.filter(isIncluded: { $0.index == 0 && $0.tournament == id && $0.parent == nil }).first |
|
|
|
|
return final?.playedMatches().first?.winner() |
|
|
|
|
let rounds: [Round] = Store.main.filter(isIncluded: { $0.index == 0 && $0.tournament == id && $0.parent == nil }) |
|
|
|
|
// let final: Round? = .first |
|
|
|
|
return rounds.first?.playedMatches().first?.winner() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func getGroupStageChunkValue() -> Int { |
|
|
|
|
|