parent
4663f32102
commit
2c99a323b9
File diff suppressed because it is too large
Load Diff
@ -1,111 +0,0 @@ |
|||||||
// |
|
||||||
// AppSettings.swift |
|
||||||
// PadelClub |
|
||||||
// |
|
||||||
// Created by Razmig Sarkissian on 26/03/2024. |
|
||||||
// |
|
||||||
|
|
||||||
import Foundation |
|
||||||
import LeStorage |
|
||||||
import SwiftUI |
|
||||||
|
|
||||||
@Observable |
|
||||||
final class AppSettings: MicroStorable { |
|
||||||
|
|
||||||
var lastDataSource: String? = nil |
|
||||||
var didCreateAccount: Bool = false |
|
||||||
var didRegisterAccount: Bool = false |
|
||||||
|
|
||||||
//search tournament stuff |
|
||||||
var tournamentCategories: Set<TournamentCategory.ID> |
|
||||||
var tournamentLevels: Set<TournamentLevel.ID> |
|
||||||
var tournamentAges: Set<FederalTournamentAge.ID> |
|
||||||
var tournamentTypes: Set<FederalTournamentType.ID> |
|
||||||
var startDate: Date |
|
||||||
var endDate: Date |
|
||||||
var city: String |
|
||||||
var distance: Double |
|
||||||
var sortingOption: String |
|
||||||
var nationalCup: Bool |
|
||||||
var dayDuration: Int? |
|
||||||
var dayPeriod: DayPeriod |
|
||||||
|
|
||||||
func lastDataSourceDate() -> Date? { |
|
||||||
guard let lastDataSource else { return nil } |
|
||||||
return URL.importDateFormatter.date(from: lastDataSource) |
|
||||||
} |
|
||||||
|
|
||||||
func localizedLastDataSource() -> String? { |
|
||||||
guard let lastDataSource else { return nil } |
|
||||||
guard let date = URL.importDateFormatter.date(from: lastDataSource) else { return nil } |
|
||||||
|
|
||||||
return date.monthYearFormatted |
|
||||||
} |
|
||||||
|
|
||||||
func resetSearch() { |
|
||||||
tournamentAges = Set() |
|
||||||
tournamentTypes = Set() |
|
||||||
tournamentLevels = Set() |
|
||||||
tournamentCategories = Set() |
|
||||||
city = "" |
|
||||||
distance = 30 |
|
||||||
startDate = Date() |
|
||||||
endDate = Calendar.current.date(byAdding: .month, value: 3, to: Date())! |
|
||||||
sortingOption = "dateDebut+asc" |
|
||||||
nationalCup = false |
|
||||||
dayDuration = nil |
|
||||||
dayPeriod = .all |
|
||||||
} |
|
||||||
|
|
||||||
required init() { |
|
||||||
tournamentAges = Set() |
|
||||||
tournamentTypes = Set() |
|
||||||
tournamentLevels = Set() |
|
||||||
tournamentCategories = Set() |
|
||||||
city = "" |
|
||||||
distance = 30 |
|
||||||
startDate = Date() |
|
||||||
endDate = Calendar.current.date(byAdding: .month, value: 3, to: Date())! |
|
||||||
sortingOption = "dateDebut+asc" |
|
||||||
nationalCup = false |
|
||||||
dayDuration = nil |
|
||||||
dayPeriod = .all |
|
||||||
} |
|
||||||
|
|
||||||
required init(from decoder: Decoder) throws { |
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self) |
|
||||||
lastDataSource = try container.decodeIfPresent(String.self, forKey: ._lastDataSource) |
|
||||||
didCreateAccount = try container.decodeIfPresent(Bool.self, forKey: ._didCreateAccount) ?? false |
|
||||||
didRegisterAccount = try container.decodeIfPresent(Bool.self, forKey: ._didRegisterAccount) ?? false |
|
||||||
tournamentCategories = try container.decodeIfPresent(Set<TournamentCategory.ID>.self, forKey: ._tournamentCategories) ?? Set() |
|
||||||
tournamentLevels = try container.decodeIfPresent(Set<TournamentLevel.ID>.self, forKey: ._tournamentLevels) ?? Set() |
|
||||||
tournamentAges = try container.decodeIfPresent(Set<FederalTournamentAge.ID>.self, forKey: ._tournamentAges) ?? Set() |
|
||||||
tournamentTypes = try container.decodeIfPresent(Set<FederalTournamentType.ID>.self, forKey: ._tournamentTypes) ?? Set() |
|
||||||
startDate = try container.decodeIfPresent(Date.self, forKey: ._startDate) ?? Date() |
|
||||||
endDate = try container.decodeIfPresent(Date.self, forKey: ._endDate) ?? Calendar.current.date(byAdding: .month, value: 3, to: Date())! |
|
||||||
city = try container.decodeIfPresent(String.self, forKey: ._city) ?? "" |
|
||||||
distance = try container.decodeIfPresent(Double.self, forKey: ._distance) ?? 30 |
|
||||||
sortingOption = try container.decodeIfPresent(String.self, forKey: ._sortingOption) ?? "dateDebut+asc" |
|
||||||
nationalCup = try container.decodeIfPresent(Bool.self, forKey: ._nationalCup) ?? false |
|
||||||
dayDuration = try container.decodeIfPresent(Int.self, forKey: ._dayDuration) |
|
||||||
dayPeriod = try container.decodeIfPresent(DayPeriod.self, forKey: ._dayPeriod) ?? .all |
|
||||||
} |
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey { |
|
||||||
case _lastDataSource = "lastDataSource" |
|
||||||
case _didCreateAccount = "didCreateAccount" |
|
||||||
case _didRegisterAccount = "didRegisterAccount" |
|
||||||
case _tournamentCategories = "tournamentCategories" |
|
||||||
case _tournamentLevels = "tournamentLevels" |
|
||||||
case _tournamentAges = "tournamentAges" |
|
||||||
case _tournamentTypes = "tournamentTypes" |
|
||||||
case _startDate = "startDate" |
|
||||||
case _endDate = "endDate" |
|
||||||
case _city = "city" |
|
||||||
case _distance = "distance" |
|
||||||
case _sortingOption = "sortingOption" |
|
||||||
case _nationalCup = "nationalCup" |
|
||||||
case _dayDuration = "dayDuration" |
|
||||||
case _dayPeriod = "dayPeriod" |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,115 +0,0 @@ |
|||||||
// |
|
||||||
// Club.swift |
|
||||||
// PadelClub |
|
||||||
// |
|
||||||
// Created by Laurent Morvillier on 02/02/2024. |
|
||||||
// |
|
||||||
|
|
||||||
import Foundation |
|
||||||
import SwiftUI |
|
||||||
import LeStorage |
|
||||||
|
|
||||||
@Observable |
|
||||||
final class Club: BaseClub { |
|
||||||
|
|
||||||
func clubTitle(_ displayStyle: DisplayStyle = .wide) -> String { |
|
||||||
switch displayStyle { |
|
||||||
case .wide, .title: |
|
||||||
return name |
|
||||||
case .short: |
|
||||||
return acronym |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func shareURL() -> URL? { |
|
||||||
return URL(string: URLs.main.url.appending(path: "?club=\(id)").absoluteString.removingPercentEncoding!) |
|
||||||
} |
|
||||||
|
|
||||||
var customizedCourts: [Court] { |
|
||||||
DataStore.shared.courts.filter { $0.club == self.id }.sorted(by: \.index) |
|
||||||
} |
|
||||||
|
|
||||||
override func deleteDependencies() { |
|
||||||
let customizedCourts = self.customizedCourts |
|
||||||
for customizedCourt in customizedCourts { |
|
||||||
customizedCourt.deleteDependencies() |
|
||||||
} |
|
||||||
DataStore.shared.courts.deleteDependencies(customizedCourts) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
extension Club { |
|
||||||
var isValid: Bool { |
|
||||||
name.isEmpty == false && name.count > 3 |
|
||||||
} |
|
||||||
|
|
||||||
func automaticShortName() -> String { |
|
||||||
name.acronym() |
|
||||||
} |
|
||||||
|
|
||||||
enum AcronymMode: String, CaseIterable { |
|
||||||
case automatic = "Automatique" |
|
||||||
case custom = "Personalisée" |
|
||||||
} |
|
||||||
|
|
||||||
func shortNameMode() -> AcronymMode { |
|
||||||
(acronym.isEmpty || acronym == automaticShortName()) ? .automatic : .custom |
|
||||||
} |
|
||||||
|
|
||||||
func hasTenupId() -> Bool { |
|
||||||
code != nil |
|
||||||
} |
|
||||||
|
|
||||||
func federalLink() -> URL? { |
|
||||||
guard let code else { return nil } |
|
||||||
return URL(string: "https://tenup.fft.fr/club/\(code)") |
|
||||||
} |
|
||||||
|
|
||||||
func courtName(atIndex courtIndex: Int) -> String { |
|
||||||
courtNameIfAvailable(atIndex: courtIndex) ?? Court.courtIndexedTitle(atIndex: courtIndex) |
|
||||||
} |
|
||||||
|
|
||||||
func courtNameIfAvailable(atIndex courtIndex: Int) -> String? { |
|
||||||
customizedCourts.first(where: { $0.index == courtIndex })?.name |
|
||||||
} |
|
||||||
|
|
||||||
func update(fromClub club: Club) { |
|
||||||
self.acronym = club.acronym |
|
||||||
self.name = club.name |
|
||||||
self.phone = club.phone |
|
||||||
self.code = club.code |
|
||||||
self.address = club.address |
|
||||||
self.city = club.city |
|
||||||
self.zipCode = club.zipCode |
|
||||||
self.latitude = club.latitude |
|
||||||
self.longitude = club.longitude |
|
||||||
} |
|
||||||
|
|
||||||
func hasBeenCreated(by creatorId: String?) -> Bool { |
|
||||||
return creatorId == creator || creator == nil || self.relatedUser == creatorId |
|
||||||
} |
|
||||||
|
|
||||||
func isFavorite() -> Bool { |
|
||||||
return DataStore.shared.user.clubs.contains(where: { $0 == self.id }) |
|
||||||
} |
|
||||||
|
|
||||||
static func findOrCreate(name: String, code: String?, city: String? = nil, zipCode: String? = nil) -> Club { |
|
||||||
|
|
||||||
/* |
|
||||||
|
|
||||||
identify a club : code, name, ?? |
|
||||||
|
|
||||||
*/ |
|
||||||
let club: Club? = DataStore.shared.clubs.first(where: { (code == nil && $0.name == name && $0.city == city && $0.zipCode == zipCode) || code != nil && $0.code == code }) |
|
||||||
|
|
||||||
if let club { |
|
||||||
return club |
|
||||||
} else { |
|
||||||
let club = Club(creator: StoreCenter.main.userId, name: name, acronym: name.acronym(), code: code, city: city, zipCode: zipCode) |
|
||||||
club.relatedUser = StoreCenter.main.userId |
|
||||||
return club |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,58 +0,0 @@ |
|||||||
// |
|
||||||
// Court.swift |
|
||||||
// PadelClub |
|
||||||
// |
|
||||||
// Created by Razmig Sarkissian on 23/04/2024. |
|
||||||
// |
|
||||||
|
|
||||||
import Foundation |
|
||||||
import SwiftUI |
|
||||||
import LeStorage |
|
||||||
|
|
||||||
@Observable |
|
||||||
final class Court: BaseCourt { |
|
||||||
|
|
||||||
static func == (lhs: Court, rhs: Court) -> Bool { |
|
||||||
lhs.id == rhs.id |
|
||||||
} |
|
||||||
|
|
||||||
init(index: Int, club: String, name: String? = nil, exitAllowed: Bool = false, indoor: Bool = false) { |
|
||||||
|
|
||||||
super.init() |
|
||||||
|
|
||||||
self.index = index |
|
||||||
self.lastUpdate = Date() |
|
||||||
self.club = club |
|
||||||
self.name = name |
|
||||||
self.exitAllowed = exitAllowed |
|
||||||
self.indoor = indoor |
|
||||||
} |
|
||||||
|
|
||||||
required init(from decoder: Decoder) throws { |
|
||||||
try super.init(from: decoder) |
|
||||||
} |
|
||||||
|
|
||||||
required public init() { |
|
||||||
super.init() |
|
||||||
} |
|
||||||
|
|
||||||
func courtTitle() -> String { |
|
||||||
self.name ?? courtIndexTitle() |
|
||||||
} |
|
||||||
|
|
||||||
func courtIndexTitle() -> String { |
|
||||||
Self.courtIndexedTitle(atIndex: index) |
|
||||||
} |
|
||||||
|
|
||||||
static func courtIndexedTitle(atIndex index: Int) -> String { |
|
||||||
("Terrain #" + (index + 1).formatted()) |
|
||||||
} |
|
||||||
|
|
||||||
func clubObject() -> Club? { |
|
||||||
Store.main.findById(club) |
|
||||||
} |
|
||||||
|
|
||||||
override func deleteDependencies() { |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,316 +0,0 @@ |
|||||||
// |
|
||||||
// User.swift |
|
||||||
// PadelClub |
|
||||||
// |
|
||||||
// Created by Laurent Morvillier on 21/02/2024. |
|
||||||
// |
|
||||||
|
|
||||||
import Foundation |
|
||||||
import LeStorage |
|
||||||
|
|
||||||
enum UserRight: Int, Codable { |
|
||||||
case none = 0 |
|
||||||
case edition = 1 |
|
||||||
case creation = 2 |
|
||||||
} |
|
||||||
|
|
||||||
enum RegistrationPaymentMode: Int, Codable { |
|
||||||
case disabled = 0 |
|
||||||
case corporate = 1 |
|
||||||
case noFee = 2 |
|
||||||
case stripe = 3 |
|
||||||
|
|
||||||
static let stripeFixedFee = 0.25 // Fixed fee in euros |
|
||||||
static let stripePercentageFee = 0.014 // 1.4% |
|
||||||
|
|
||||||
func canEnableOnlinePayment() -> Bool { |
|
||||||
switch self { |
|
||||||
case .disabled: |
|
||||||
return false |
|
||||||
case .corporate: |
|
||||||
return true |
|
||||||
case .noFee: |
|
||||||
return true |
|
||||||
case .stripe: |
|
||||||
return true |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func requiresStripe() -> Bool { |
|
||||||
switch self { |
|
||||||
case .disabled: |
|
||||||
return false |
|
||||||
case .corporate: |
|
||||||
return true |
|
||||||
case .noFee: |
|
||||||
return true |
|
||||||
case .stripe: |
|
||||||
return true |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Observable |
|
||||||
class CustomUser: BaseCustomUser, UserBase { |
|
||||||
|
|
||||||
// static func resourceName() -> String { "users" } |
|
||||||
// static func tokenExemptedMethods() -> [HTTPMethod] { return [.post] } |
|
||||||
// static func filterByStoreIdentifier() -> Bool { return false } |
|
||||||
// static var relationshipNames: [String] = [] |
|
||||||
// |
|
||||||
// public var id: String = Store.randomId() |
|
||||||
// var lastUpdate: Date |
|
||||||
// public var username: String |
|
||||||
// public var email: String |
|
||||||
// var clubs: [String] = [] |
|
||||||
// var umpireCode: String? |
|
||||||
// var licenceId: String? |
|
||||||
// var firstName: String |
|
||||||
// var lastName: String |
|
||||||
// var phone: String? |
|
||||||
// var country: String? |
|
||||||
// |
|
||||||
// var summonsMessageBody : String? = nil |
|
||||||
// var summonsMessageSignature: String? = nil |
|
||||||
// var summonsAvailablePaymentMethods: String? = nil |
|
||||||
// var summonsDisplayFormat: Bool = false |
|
||||||
// var summonsDisplayEntryFee: Bool = false |
|
||||||
// var summonsUseFullCustomMessage: Bool = false |
|
||||||
// var matchFormatsDefaultDuration: [MatchFormat: Int]? = nil |
|
||||||
// var bracketMatchFormatPreference: MatchFormat? |
|
||||||
// var groupStageMatchFormatPreference: MatchFormat? |
|
||||||
// var loserBracketMatchFormatPreference: MatchFormat? |
|
||||||
// var loserBracketMode: LoserBracketMode = .automatic |
|
||||||
// |
|
||||||
// var deviceId: String? |
|
||||||
|
|
||||||
init(username: String, email: String, firstName: String, lastName: String, phone: String?, country: String?, loserBracketMode: LoserBracketMode = .automatic) { |
|
||||||
super.init(username: username, email: email, firstName: firstName, lastName: lastName, phone: phone, country: country, loserBracketMode: loserBracketMode) |
|
||||||
|
|
||||||
|
|
||||||
// self.lastUpdate = Date() |
|
||||||
// self.username = username |
|
||||||
// self.firstName = firstName |
|
||||||
// self.lastName = lastName |
|
||||||
// self.email = email |
|
||||||
// self.phone = phone |
|
||||||
// self.country = country |
|
||||||
// self.loserBracketMode = loserBracketMode |
|
||||||
} |
|
||||||
|
|
||||||
required init(from decoder: Decoder) throws { |
|
||||||
try super.init(from: decoder) |
|
||||||
} |
|
||||||
|
|
||||||
required public init() { |
|
||||||
super.init() |
|
||||||
} |
|
||||||
|
|
||||||
public func uuid() throws -> UUID { |
|
||||||
if let uuid = UUID(uuidString: self.id) { |
|
||||||
return uuid |
|
||||||
} |
|
||||||
throw UUIDError.cantConvertString(string: self.id) |
|
||||||
} |
|
||||||
|
|
||||||
func currentPlayerData() -> ImportedPlayer? { |
|
||||||
guard let licenceId = self.licenceId?.strippedLicense else { return nil } |
|
||||||
let federalContext = PersistenceController.shared.localContainer.viewContext |
|
||||||
let fetchRequest = ImportedPlayer.fetchRequest() |
|
||||||
let predicate = NSPredicate(format: "license == %@", licenceId) |
|
||||||
fetchRequest.predicate = predicate |
|
||||||
return try? federalContext.fetch(fetchRequest).first |
|
||||||
} |
|
||||||
|
|
||||||
func defaultSignature(_ tournament: Tournament?) -> String { |
|
||||||
let fullName = tournament?.umpireCustomContact ?? fullName() |
|
||||||
return "Sportivement,\n\(fullName), votre JAP." |
|
||||||
} |
|
||||||
|
|
||||||
func fullName() -> String { |
|
||||||
[firstName, lastName].joined(separator: " ") |
|
||||||
} |
|
||||||
|
|
||||||
func hasTenupClubs() -> Bool { |
|
||||||
self.clubsObjects().filter({ $0.code != nil }).isEmpty == false |
|
||||||
} |
|
||||||
|
|
||||||
func hasFavoriteClubsAndCreatedClubs() -> Bool { |
|
||||||
clubsObjects(includeCreated: true).isEmpty == false |
|
||||||
} |
|
||||||
|
|
||||||
func setUserClub(_ userClub: Club) { |
|
||||||
self.clubs.insert(userClub.id, at: 0) |
|
||||||
} |
|
||||||
|
|
||||||
func clubsObjects(includeCreated: Bool = false) -> [Club] { |
|
||||||
return DataStore.shared.clubs.filter({ (includeCreated && $0.creator == id) || clubs.contains($0.id) }) |
|
||||||
} |
|
||||||
|
|
||||||
func createdClubsObjectsNotFavorite() -> [Club] { |
|
||||||
return DataStore.shared.clubs.filter({ ($0.creator == id) && clubs.contains($0.id) == false }) |
|
||||||
} |
|
||||||
|
|
||||||
func saveMatchFormatsDefaultDuration(_ matchFormat: MatchFormat, estimatedDuration: Int) { |
|
||||||
if estimatedDuration == matchFormat.defaultEstimatedDuration { |
|
||||||
matchFormatsDefaultDuration?.removeValue(forKey: matchFormat) |
|
||||||
} else { |
|
||||||
matchFormatsDefaultDuration = matchFormatsDefaultDuration ?? [MatchFormat: Int]() |
|
||||||
matchFormatsDefaultDuration?[matchFormat] = estimatedDuration |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func addClub(_ club: Club) { |
|
||||||
if !self.clubs.contains(where: { $0.id == club.id }) { |
|
||||||
self.clubs.append(club.id) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func getSummonsMessageSignature() -> String? { |
|
||||||
if let summonsMessageSignature, summonsMessageSignature.isEmpty == false { |
|
||||||
return summonsMessageSignature |
|
||||||
} else { |
|
||||||
return nil |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func canEnableOnlinePayment() -> Bool { |
|
||||||
registrationPaymentMode.canEnableOnlinePayment() |
|
||||||
} |
|
||||||
// enum CodingKeys: String, CodingKey { |
|
||||||
// case _id = "id" |
|
||||||
// case _lastUpdate = "lastUpdate" |
|
||||||
// case _username = "username" |
|
||||||
// case _email = "email" |
|
||||||
// case _clubs = "clubs" |
|
||||||
// case _umpireCode = "umpireCode" |
|
||||||
// case _licenceId = "licenceId" |
|
||||||
// case _firstName = "firstName" |
|
||||||
// case _lastName = "lastName" |
|
||||||
// case _phone = "phone" |
|
||||||
// case _country = "country" |
|
||||||
// case _summonsMessageBody = "summonsMessageBody" |
|
||||||
// case _summonsMessageSignature = "summonsMessageSignature" |
|
||||||
// case _summonsAvailablePaymentMethods = "summonsAvailablePaymentMethods" |
|
||||||
// case _summonsDisplayFormat = "summonsDisplayFormat" |
|
||||||
// case _summonsDisplayEntryFee = "summonsDisplayEntryFee" |
|
||||||
// case _summonsUseFullCustomMessage = "summonsUseFullCustomMessage" |
|
||||||
// case _matchFormatsDefaultDuration = "matchFormatsDefaultDuration" |
|
||||||
// case _bracketMatchFormatPreference = "bracketMatchFormatPreference" |
|
||||||
// case _groupStageMatchFormatPreference = "groupStageMatchFormatPreference" |
|
||||||
// case _loserBracketMatchFormatPreference = "loserBracketMatchFormatPreference" |
|
||||||
// case _deviceId = "deviceId" |
|
||||||
// case _loserBracketMode = "loserBracketMode" |
|
||||||
// } |
|
||||||
// |
|
||||||
// public required init(from decoder: Decoder) throws { |
|
||||||
// let container = try decoder.container(keyedBy: CodingKeys.self) |
|
||||||
// |
|
||||||
// // Required properties |
|
||||||
// id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId() |
|
||||||
// lastUpdate = try container.decodeIfPresent(Date.self, forKey: ._lastUpdate) ?? Date() |
|
||||||
// username = try container.decode(String.self, forKey: ._username) |
|
||||||
// email = try container.decode(String.self, forKey: ._email) |
|
||||||
// firstName = try container.decode(String.self, forKey: ._firstName) |
|
||||||
// lastName = try container.decode(String.self, forKey: ._lastName) |
|
||||||
// |
|
||||||
// // Optional properties |
|
||||||
// clubs = try container.decodeIfPresent([String].self, forKey: ._clubs) ?? [] |
|
||||||
// umpireCode = try container.decodeIfPresent(String.self, forKey: ._umpireCode) |
|
||||||
// licenceId = try container.decodeIfPresent(String.self, forKey: ._licenceId) |
|
||||||
// phone = try container.decodeIfPresent(String.self, forKey: ._phone) |
|
||||||
// country = try container.decodeIfPresent(String.self, forKey: ._country) |
|
||||||
// |
|
||||||
// // Summons-related properties |
|
||||||
// summonsMessageBody = try container.decodeIfPresent(String.self, forKey: ._summonsMessageBody) |
|
||||||
// summonsMessageSignature = try container.decodeIfPresent(String.self, forKey: ._summonsMessageSignature) |
|
||||||
// summonsAvailablePaymentMethods = try container.decodeIfPresent(String.self, forKey: ._summonsAvailablePaymentMethods) |
|
||||||
// summonsDisplayFormat = try container.decodeIfPresent(Bool.self, forKey: ._summonsDisplayFormat) ?? false |
|
||||||
// summonsDisplayEntryFee = try container.decodeIfPresent(Bool.self, forKey: ._summonsDisplayEntryFee) ?? false |
|
||||||
// summonsUseFullCustomMessage = try container.decodeIfPresent(Bool.self, forKey: ._summonsUseFullCustomMessage) ?? false |
|
||||||
// |
|
||||||
// // Match-related properties |
|
||||||
// matchFormatsDefaultDuration = try container.decodeIfPresent([MatchFormat: Int].self, forKey: ._matchFormatsDefaultDuration) |
|
||||||
// bracketMatchFormatPreference = try container.decodeIfPresent(MatchFormat.self, forKey: ._bracketMatchFormatPreference) |
|
||||||
// groupStageMatchFormatPreference = try container.decodeIfPresent(MatchFormat.self, forKey: ._groupStageMatchFormatPreference) |
|
||||||
// loserBracketMatchFormatPreference = try container.decodeIfPresent(MatchFormat.self, forKey: ._loserBracketMatchFormatPreference) |
|
||||||
// loserBracketMode = try container.decodeIfPresent(LoserBracketMode.self, forKey: ._loserBracketMode) ?? .automatic |
|
||||||
// } |
|
||||||
// |
|
||||||
// func encode(to encoder: Encoder) throws { |
|
||||||
// var container = encoder.container(keyedBy: CodingKeys.self) |
|
||||||
// |
|
||||||
// try container.encode(id, forKey: ._id) |
|
||||||
// try container.encode(lastUpdate, forKey: ._lastUpdate) |
|
||||||
// try container.encode(username, forKey: ._username) |
|
||||||
// try container.encode(email, forKey: ._email) |
|
||||||
// try container.encode(clubs, forKey: ._clubs) |
|
||||||
// |
|
||||||
// try container.encode(umpireCode, forKey: ._umpireCode) |
|
||||||
// try container.encode(licenceId, forKey: ._licenceId) |
|
||||||
// try container.encode(firstName, forKey: ._firstName) |
|
||||||
// try container.encode(lastName, forKey: ._lastName) |
|
||||||
// try container.encode(phone, forKey: ._phone) |
|
||||||
// try container.encode(country, forKey: ._country) |
|
||||||
// try container.encode(summonsMessageBody, forKey: ._summonsMessageBody) |
|
||||||
// try container.encode(summonsMessageSignature, forKey: ._summonsMessageSignature) |
|
||||||
// try container.encode(summonsAvailablePaymentMethods, forKey: ._summonsAvailablePaymentMethods) |
|
||||||
// try container.encode(summonsDisplayFormat, forKey: ._summonsDisplayFormat) |
|
||||||
// try container.encode(summonsDisplayEntryFee, forKey: ._summonsDisplayEntryFee) |
|
||||||
// try container.encode(summonsUseFullCustomMessage, forKey: ._summonsUseFullCustomMessage) |
|
||||||
// |
|
||||||
// try container.encode(matchFormatsDefaultDuration, forKey: ._matchFormatsDefaultDuration) |
|
||||||
// try container.encode(bracketMatchFormatPreference, forKey: ._bracketMatchFormatPreference) |
|
||||||
// try container.encode(groupStageMatchFormatPreference, forKey: ._groupStageMatchFormatPreference) |
|
||||||
// try container.encode(loserBracketMatchFormatPreference, forKey: ._loserBracketMatchFormatPreference) |
|
||||||
// try container.encode(deviceId, forKey: ._deviceId) |
|
||||||
// |
|
||||||
// try container.encode(loserBracketMode, forKey: ._loserBracketMode) |
|
||||||
// } |
|
||||||
|
|
||||||
static func placeHolder() -> CustomUser { |
|
||||||
return CustomUser(username: "", email: "", firstName: "", lastName: "", phone: nil, country: nil) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
class UserCreationForm: CustomUser, UserPasswordBase { |
|
||||||
|
|
||||||
init(user: CustomUser, username: String, password: String, firstName: String, lastName: String, email: String, phone: String?, country: String?) { |
|
||||||
self.password = password |
|
||||||
super.init(username: username, email: email, firstName: firstName, lastName: lastName, phone: phone, country: country) |
|
||||||
|
|
||||||
self.summonsMessageBody = user.summonsMessageBody |
|
||||||
self.summonsMessageSignature = user.summonsMessageSignature |
|
||||||
self.summonsAvailablePaymentMethods = user.summonsAvailablePaymentMethods |
|
||||||
self.summonsDisplayFormat = user.summonsDisplayFormat |
|
||||||
self.summonsDisplayEntryFee = user.summonsDisplayEntryFee |
|
||||||
self.summonsUseFullCustomMessage = user.summonsUseFullCustomMessage |
|
||||||
self.matchFormatsDefaultDuration = user.matchFormatsDefaultDuration |
|
||||||
self.bracketMatchFormatPreference = user.bracketMatchFormatPreference |
|
||||||
self.groupStageMatchFormatPreference = user.groupStageMatchFormatPreference |
|
||||||
self.loserBracketMatchFormatPreference = user.loserBracketMatchFormatPreference |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
required init(from decoder: Decoder) throws { |
|
||||||
fatalError("init(from:) has not been implemented") |
|
||||||
} |
|
||||||
|
|
||||||
required public init() { |
|
||||||
self.password = "" |
|
||||||
super.init() |
|
||||||
} |
|
||||||
|
|
||||||
public var password: String |
|
||||||
|
|
||||||
private enum CodingKeys: String, CodingKey { |
|
||||||
case password |
|
||||||
} |
|
||||||
|
|
||||||
override func encode(to encoder: Encoder) throws { |
|
||||||
try super.encode(to: encoder) |
|
||||||
var container = encoder.container(keyedBy: CodingKeys.self) |
|
||||||
try container.encode(self.password, forKey: .password) |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,403 +0,0 @@ |
|||||||
// |
|
||||||
// DataStore.swift |
|
||||||
// PadelClub |
|
||||||
// |
|
||||||
// Created by Laurent Morvillier on 02/02/2024. |
|
||||||
// |
|
||||||
|
|
||||||
import Foundation |
|
||||||
import LeStorage |
|
||||||
import SwiftUI |
|
||||||
|
|
||||||
class DataStore: ObservableObject { |
|
||||||
|
|
||||||
static let shared = DataStore() |
|
||||||
|
|
||||||
@Published var user: CustomUser = CustomUser.placeHolder() { |
|
||||||
didSet { |
|
||||||
let loggedUser = StoreCenter.main.isAuthenticated |
|
||||||
|
|
||||||
if loggedUser { |
|
||||||
if self.user.id != self.userStorage.item()?.id { |
|
||||||
self.userStorage.setItemNoSync(self.user) |
|
||||||
StoreCenter.main.initialSynchronization(clear: false) |
|
||||||
self._fixMissingClubCreatorIfNecessary(self.clubs) |
|
||||||
self._fixMissingEventCreatorIfNecessary(self.events) |
|
||||||
} |
|
||||||
} else { |
|
||||||
self._temporaryLocalUser.item = self.user |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
fileprivate(set) var tournaments: SyncedCollection<Tournament> |
|
||||||
fileprivate(set) var clubs: SyncedCollection<Club> |
|
||||||
fileprivate(set) var courts: SyncedCollection<Court> |
|
||||||
fileprivate(set) var events: SyncedCollection<Event> |
|
||||||
fileprivate(set) var monthData: StoredCollection<MonthData> |
|
||||||
fileprivate(set) var dateIntervals: SyncedCollection<DateInterval> |
|
||||||
fileprivate(set) var purchases: SyncedCollection<Purchase> |
|
||||||
|
|
||||||
var userStorage: StoredSingleton<CustomUser> |
|
||||||
|
|
||||||
fileprivate var _temporaryLocalUser: OptionalStorage<CustomUser> = OptionalStorage(fileName: "tmp_local_user.json") |
|
||||||
fileprivate(set) var appSettingsStorage: MicroStorage<AppSettings> = MicroStorage(fileName: "appsettings.json") |
|
||||||
|
|
||||||
var appSettings: AppSettings { |
|
||||||
appSettingsStorage.item |
|
||||||
} |
|
||||||
|
|
||||||
init() { |
|
||||||
let store = Store.main |
|
||||||
StoreCenter.main.blackListUserName("apple-test") |
|
||||||
|
|
||||||
// let secureScheme = true |
|
||||||
let domain: String = URLs.activationHost.rawValue |
|
||||||
|
|
||||||
#if DEBUG |
|
||||||
if let secure = PListReader.readBool(plist: "local", key: "secure_server"), |
|
||||||
let domain = PListReader.readString(plist: "local", key: "server_domain") { |
|
||||||
StoreCenter.main.configureURLs(secureScheme: secure, domain: domain) |
|
||||||
} else { |
|
||||||
StoreCenter.main.configureURLs(secureScheme: true, domain: domain) |
|
||||||
} |
|
||||||
#else |
|
||||||
StoreCenter.main.configureURLs(secureScheme: true, domain: domain) |
|
||||||
#endif |
|
||||||
|
|
||||||
StoreCenter.main.logsFailedAPICalls() |
|
||||||
|
|
||||||
var synchronized: Bool = true |
|
||||||
|
|
||||||
#if DEBUG |
|
||||||
if let sync = PListReader.readBool(plist: "local", key: "synchronized") { |
|
||||||
synchronized = sync |
|
||||||
} |
|
||||||
#endif |
|
||||||
|
|
||||||
StoreCenter.main.forceNoSynchronization = !synchronized |
|
||||||
|
|
||||||
|
|
||||||
let indexed: Bool = true |
|
||||||
self.clubs = store.registerSynchronizedCollection(indexed: indexed) |
|
||||||
self.courts = store.registerSynchronizedCollection(indexed: indexed) |
|
||||||
self.tournaments = store.registerSynchronizedCollection(indexed: indexed) |
|
||||||
self.events = store.registerSynchronizedCollection(indexed: indexed) |
|
||||||
self.dateIntervals = store.registerSynchronizedCollection(indexed: indexed) |
|
||||||
self.userStorage = store.registerObject(synchronized: synchronized) |
|
||||||
self.purchases = Store.main.registerSynchronizedCollection(inMemory: true) |
|
||||||
|
|
||||||
self.monthData = store.registerCollection(indexed: indexed) |
|
||||||
|
|
||||||
// Load ApiCallCollection, making them restart at launch and deletable on disconnect |
|
||||||
StoreCenter.main.loadApiCallCollection(type: GroupStage.self) |
|
||||||
StoreCenter.main.loadApiCallCollection(type: Round.self) |
|
||||||
StoreCenter.main.loadApiCallCollection(type: PlayerRegistration.self) |
|
||||||
StoreCenter.main.loadApiCallCollection(type: TeamRegistration.self) |
|
||||||
StoreCenter.main.loadApiCallCollection(type: Match.self) |
|
||||||
StoreCenter.main.loadApiCallCollection(type: TeamScore.self) |
|
||||||
StoreCenter.main.loadApiCallCollection(type: DrawLog.self) |
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(collectionDidLoad), name: NSNotification.Name.CollectionDidLoad, object: nil) |
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(collectionDidUpdate), name: NSNotification.Name.CollectionDidChange, object: nil) |
|
||||||
NotificationCenter.default.addObserver( |
|
||||||
self, |
|
||||||
selector: #selector(_willEnterForegroundNotification), |
|
||||||
name: UIScene.willEnterForegroundNotification, |
|
||||||
object: nil) |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
deinit { |
|
||||||
NotificationCenter.default.removeObserver(self) |
|
||||||
} |
|
||||||
|
|
||||||
func saveUser() { |
|
||||||
if user.username.count > 0 { |
|
||||||
self.userStorage.update() |
|
||||||
} else { |
|
||||||
self._temporaryLocalUser.item = self.user |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@objc func collectionDidLoad(notification: Notification) { |
|
||||||
|
|
||||||
if let userSingleton: StoredSingleton<CustomUser> = notification.object as? StoredSingleton<CustomUser> { |
|
||||||
self.user = userSingleton.item() ?? self._temporaryLocalUser.item ?? CustomUser.placeHolder() |
|
||||||
} else if let clubsCollection: SyncedCollection<Club> = notification.object as? SyncedCollection<Club> { |
|
||||||
self._fixMissingClubCreatorIfNecessary(clubsCollection) |
|
||||||
} else if let eventsCollection: SyncedCollection<Event> = notification.object as? SyncedCollection<Event> { |
|
||||||
self._fixMissingEventCreatorIfNecessary(eventsCollection) |
|
||||||
} |
|
||||||
|
|
||||||
if Store.main.fileCollectionsAllLoaded() { |
|
||||||
AutomaticPatcher.applyAllWhenApplicable() |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
fileprivate func _fixMissingClubCreatorIfNecessary(_ clubsCollection: SyncedCollection<Club>) { |
|
||||||
for club in clubsCollection { |
|
||||||
if let userId = StoreCenter.main.userId, club.creator == nil { |
|
||||||
club.creator = userId |
|
||||||
self.userStorage.item()?.addClub(club) |
|
||||||
self.userStorage.update() |
|
||||||
clubsCollection.writeChangeAndInsertOnServer(instance: club) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
fileprivate func _fixMissingEventCreatorIfNecessary(_ eventsCollection: SyncedCollection<Event>) { |
|
||||||
for event in eventsCollection { |
|
||||||
if let userId = StoreCenter.main.userId, event.creator == nil { |
|
||||||
event.creator = userId |
|
||||||
do { |
|
||||||
try event.insertOnServer() |
|
||||||
} catch { |
|
||||||
Logger.error(error) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@objc func collectionDidUpdate(notification: Notification) { |
|
||||||
self.objectWillChange.send() |
|
||||||
} |
|
||||||
|
|
||||||
@objc func _willEnterForegroundNotification() { |
|
||||||
Task { |
|
||||||
try await self.purchases.loadDataFromServerIfAllowed(clear: true) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func disconnect() { |
|
||||||
|
|
||||||
Task { |
|
||||||
if await StoreCenter.main.hasPendingAPICalls() { |
|
||||||
// todo qu'est ce qu'on fait des API Call ? |
|
||||||
} |
|
||||||
|
|
||||||
do { |
|
||||||
let services = try StoreCenter.main.service() |
|
||||||
try await services.logout() |
|
||||||
} catch { |
|
||||||
Logger.error(error) |
|
||||||
} |
|
||||||
|
|
||||||
DispatchQueue.main.async { |
|
||||||
self._localDisconnect() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
func deleteAccount() { |
|
||||||
|
|
||||||
Task { |
|
||||||
do { |
|
||||||
let services = try StoreCenter.main.service() |
|
||||||
try await services.deleteAccount() |
|
||||||
} catch { |
|
||||||
Logger.error(error) |
|
||||||
} |
|
||||||
|
|
||||||
DispatchQueue.main.async { |
|
||||||
self._localDisconnect() |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
func deleteTournament(_ tournament: Tournament) { |
|
||||||
let event = tournament.eventObject() |
|
||||||
let isLastTournament = event?.tournaments.count == 1 |
|
||||||
self.tournaments.delete(instance: tournament) |
|
||||||
if let event, isLastTournament { |
|
||||||
self.events.delete(instance: event) |
|
||||||
} |
|
||||||
StoreCenter.main.destroyStore(identifier: tournament.id) |
|
||||||
} |
|
||||||
|
|
||||||
fileprivate func _localDisconnect() { |
|
||||||
|
|
||||||
let tournamendIds: [String] = self.tournaments.map { $0.id } |
|
||||||
|
|
||||||
TournamentLibrary.shared.reset() |
|
||||||
|
|
||||||
self.tournaments.reset() |
|
||||||
self.clubs.reset() |
|
||||||
self.courts.reset() |
|
||||||
self.events.reset() |
|
||||||
self.dateIntervals.reset() |
|
||||||
self.userStorage.reset() |
|
||||||
self.purchases.reset() |
|
||||||
|
|
||||||
Guard.main.disconnect() |
|
||||||
|
|
||||||
StoreCenter.main.disconnect() |
|
||||||
|
|
||||||
for tournament in tournamendIds { |
|
||||||
StoreCenter.main.destroyStore(identifier: tournament.id) |
|
||||||
} |
|
||||||
|
|
||||||
self.user = self._temporaryLocalUser.item ?? CustomUser.placeHolder() |
|
||||||
self.user.clubs.removeAll() |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
func tryUserUpdate(userCopy: CustomUser) async throws { |
|
||||||
try await self.userStorage.tryPutBeforeUpdating(userCopy) |
|
||||||
} |
|
||||||
|
|
||||||
// func copyToLocalServer(tournament: Tournament) { |
|
||||||
// |
|
||||||
// Task { |
|
||||||
// do { |
|
||||||
// |
|
||||||
// if let url = PListReader.readString(plist: "local", key: "local_server"), |
|
||||||
// let login = PListReader.readString(plist: "local", key: "username"), |
|
||||||
// let pass = PListReader.readString(plist: "local", key: "password") { |
|
||||||
// let service = Services(url: url) |
|
||||||
// let _: CustomUser = try await service.login(username: login, password: pass) |
|
||||||
// |
|
||||||
// tournament.event = nil |
|
||||||
// _ = try await service.post(tournament) |
|
||||||
// |
|
||||||
// for groupStage in tournament.groupStages() { |
|
||||||
// _ = try await service.post(groupStage) |
|
||||||
// } |
|
||||||
// for round in tournament.rounds() { |
|
||||||
// try await self._insertRoundAndChildren(round: round, service: service) |
|
||||||
// } |
|
||||||
// for teamRegistration in tournament.unsortedTeams() { |
|
||||||
// _ = try await service.post(teamRegistration) |
|
||||||
// for playerRegistration in teamRegistration.unsortedPlayers() { |
|
||||||
// _ = try await service.post(playerRegistration) |
|
||||||
// } |
|
||||||
// } |
|
||||||
// for groupStage in tournament.groupStages() { |
|
||||||
// for match in groupStage._matches() { |
|
||||||
// try await self._insertMatch(match: match, service: service) |
|
||||||
// } |
|
||||||
// } |
|
||||||
// for round in tournament.allRounds() { |
|
||||||
// for match in round._matches() { |
|
||||||
// try await self._insertMatch(match: match, service: service) |
|
||||||
// } |
|
||||||
// } |
|
||||||
// |
|
||||||
// } |
|
||||||
// } catch { |
|
||||||
// Logger.error(error) |
|
||||||
// } |
|
||||||
// } |
|
||||||
// |
|
||||||
// } |
|
||||||
// |
|
||||||
// fileprivate func _insertRoundAndChildren(round: Round, service: Services) async throws { |
|
||||||
// _ = try await service.post(round) |
|
||||||
// for loserRound in round.loserRounds() { |
|
||||||
// try await self._insertRoundAndChildren(round: loserRound, service: service) |
|
||||||
// } |
|
||||||
// } |
|
||||||
// |
|
||||||
// fileprivate func _insertMatch(match: Match, service: Services) async throws { |
|
||||||
// _ = try await service.post(match) |
|
||||||
// for teamScore in match.teamScores { |
|
||||||
// _ = try await service.post(teamScore) |
|
||||||
// } |
|
||||||
// |
|
||||||
// } |
|
||||||
|
|
||||||
// MARK: - Convenience |
|
||||||
|
|
||||||
private var _lastRunningCheckDate: Date? = nil |
|
||||||
private var _cachedRunningMatches: [Match]? = nil |
|
||||||
|
|
||||||
func runningMatches() -> [Match] { |
|
||||||
let dateNow : Date = Date() |
|
||||||
if let lastCheck = _lastRunningCheckDate, |
|
||||||
let cachedMatches = _cachedRunningMatches, |
|
||||||
dateNow.timeIntervalSince(lastCheck) < 5 { |
|
||||||
return cachedMatches |
|
||||||
} |
|
||||||
let lastTournaments = self.tournaments.filter { $0.isDeleted == false && $0.startDate <= dateNow && $0.hasEnded() == false }.sorted(by: \Tournament.startDate, order: .descending).prefix(10) |
|
||||||
|
|
||||||
var runningMatches: [Match] = [] |
|
||||||
for tournament in lastTournaments { |
|
||||||
if let store = tournament.tournamentStore { |
|
||||||
let matches = store.matches.filter { match in |
|
||||||
match.disabled == false && match.isRunning() |
|
||||||
} |
|
||||||
runningMatches.append(contentsOf: matches) |
|
||||||
} |
|
||||||
} |
|
||||||
_lastRunningCheckDate = dateNow |
|
||||||
_cachedRunningMatches = runningMatches |
|
||||||
return _cachedRunningMatches! |
|
||||||
} |
|
||||||
|
|
||||||
private var _lastRunningAndNextCheckDate: Date? = nil |
|
||||||
private var _cachedRunningAndNextMatches: [Match]? = nil |
|
||||||
|
|
||||||
func runningAndNextMatches() -> [Match] { |
|
||||||
let dateNow : Date = Date() |
|
||||||
if let lastCheck = _lastRunningAndNextCheckDate, |
|
||||||
let cachedMatches = _cachedRunningAndNextMatches, |
|
||||||
dateNow.timeIntervalSince(lastCheck) < 5 { |
|
||||||
return cachedMatches |
|
||||||
} |
|
||||||
|
|
||||||
let lastTournaments = self.tournaments.filter { $0.isDeleted == false && $0.startDate <= dateNow && $0.hasEnded() == false }.sorted(by: \Tournament.startDate, order: .descending).prefix(10) |
|
||||||
|
|
||||||
var runningMatches: [Match] = [] |
|
||||||
for tournament in lastTournaments { |
|
||||||
if let store = tournament.tournamentStore { |
|
||||||
let matches = store.matches.filter { match in |
|
||||||
match.disabled == false && match.startDate != nil && match.endDate == nil } |
|
||||||
runningMatches.append(contentsOf: matches) |
|
||||||
} |
|
||||||
} |
|
||||||
_lastRunningAndNextCheckDate = dateNow |
|
||||||
_cachedRunningAndNextMatches = runningMatches |
|
||||||
return _cachedRunningAndNextMatches! |
|
||||||
} |
|
||||||
|
|
||||||
private var _lastEndCheckDate: Date? = nil |
|
||||||
private var _cachedEndMatches: [Match]? = nil |
|
||||||
|
|
||||||
func endMatches() -> [Match] { |
|
||||||
let dateNow : Date = Date() |
|
||||||
|
|
||||||
if let lastCheck = _lastEndCheckDate, |
|
||||||
let cachedMatches = _cachedEndMatches, |
|
||||||
dateNow.timeIntervalSince(lastCheck) < 5 { |
|
||||||
return cachedMatches |
|
||||||
} |
|
||||||
|
|
||||||
let lastTournaments = self.tournaments.filter { $0.isDeleted == false && $0.startDate <= dateNow && $0.hasEnded() == false }.sorted(by: \Tournament.startDate, order: .descending).prefix(10) |
|
||||||
|
|
||||||
var runningMatches: [Match] = [] |
|
||||||
for tournament in lastTournaments { |
|
||||||
if let store = tournament.tournamentStore { |
|
||||||
let matches = store.matches.filter { match in |
|
||||||
match.disabled == false && match.hasEnded() } |
|
||||||
runningMatches.append(contentsOf: matches) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
_lastEndCheckDate = dateNow |
|
||||||
_cachedEndMatches = runningMatches.sorted(by: \.endDate!, order: .descending) |
|
||||||
return _cachedEndMatches! |
|
||||||
} |
|
||||||
|
|
||||||
func subscriptionUnitlyPayedTournaments(after date: Date) -> Int { |
|
||||||
return DataStore.shared.tournaments.count(where: { tournament in |
|
||||||
tournament.creationDate > date && |
|
||||||
tournament.payment == .subscriptionUnit && |
|
||||||
tournament.isCanceled == false && |
|
||||||
tournament.isDeleted == false }) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,76 +0,0 @@ |
|||||||
// |
|
||||||
// DateInterval.swift |
|
||||||
// PadelClub |
|
||||||
// |
|
||||||
// Created by Razmig Sarkissian on 19/04/2024. |
|
||||||
// |
|
||||||
|
|
||||||
import Foundation |
|
||||||
import SwiftUI |
|
||||||
import LeStorage |
|
||||||
|
|
||||||
@Observable |
|
||||||
final class DateInterval: BaseDateInterval { |
|
||||||
|
|
||||||
// static func resourceName() -> String { return "date-intervals" } |
|
||||||
// static func tokenExemptedMethods() -> [HTTPMethod] { return [] } |
|
||||||
// static func filterByStoreIdentifier() -> Bool { return false } |
|
||||||
// static var relationshipNames: [String] = [] |
|
||||||
// |
|
||||||
// var id: String = Store.randomId() |
|
||||||
// var lastUpdate: Date |
|
||||||
// var event: String |
|
||||||
// var courtIndex: Int |
|
||||||
// var startDate: Date |
|
||||||
// var endDate: Date |
|
||||||
|
|
||||||
internal init(event: String, courtIndex: Int, startDate: Date, endDate: Date) { |
|
||||||
super.init(event: event, courtIndex: courtIndex, startDate: startDate, endDate: endDate) |
|
||||||
// self.lastUpdate = Date() |
|
||||||
// self.event = event |
|
||||||
// self.courtIndex = courtIndex |
|
||||||
// self.startDate = startDate |
|
||||||
// self.endDate = endDate |
|
||||||
} |
|
||||||
|
|
||||||
required init(from decoder: any Decoder) throws { |
|
||||||
try super.init(from: decoder) |
|
||||||
} |
|
||||||
|
|
||||||
required public init() { |
|
||||||
super.init() |
|
||||||
} |
|
||||||
|
|
||||||
var range: Range<Date> { |
|
||||||
startDate..<endDate |
|
||||||
} |
|
||||||
|
|
||||||
func isSingleDay() -> Bool { |
|
||||||
Calendar.current.isDate(startDate, inSameDayAs: endDate) |
|
||||||
} |
|
||||||
|
|
||||||
func isDateInside(_ date: Date) -> Bool { |
|
||||||
date >= startDate && date <= endDate |
|
||||||
} |
|
||||||
|
|
||||||
func isDateOutside(_ date: Date) -> Bool { |
|
||||||
date <= startDate && date <= endDate && date >= startDate && date >= endDate |
|
||||||
} |
|
||||||
|
|
||||||
override func deleteDependencies() { |
|
||||||
} |
|
||||||
|
|
||||||
// enum CodingKeys: String, CodingKey { |
|
||||||
// case _id = "id" |
|
||||||
// case _lastUpdate = "lastUpdate" |
|
||||||
// case _event = "event" |
|
||||||
// case _courtIndex = "courtIndex" |
|
||||||
// case _startDate = "startDate" |
|
||||||
// case _endDate = "endDate" |
|
||||||
// } |
|
||||||
|
|
||||||
func insertOnServer() throws { |
|
||||||
DataStore.shared.dateIntervals.writeChangeAndInsertOnServer(instance: self) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,97 +0,0 @@ |
|||||||
// |
|
||||||
// DrawLog.swift |
|
||||||
// PadelClub |
|
||||||
// |
|
||||||
// Created by razmig on 22/10/2024. |
|
||||||
// |
|
||||||
|
|
||||||
import Foundation |
|
||||||
import SwiftUI |
|
||||||
import LeStorage |
|
||||||
|
|
||||||
@Observable |
|
||||||
final class DrawLog: BaseDrawLog, SideStorable { |
|
||||||
|
|
||||||
func tournamentObject() -> Tournament? { |
|
||||||
Store.main.findById(self.tournament) |
|
||||||
} |
|
||||||
|
|
||||||
func computedBracketPosition() -> Int { |
|
||||||
drawMatchIndex * 2 + drawTeamPosition.rawValue |
|
||||||
} |
|
||||||
|
|
||||||
func updateTeamBracketPosition(_ team: TeamRegistration) { |
|
||||||
guard let match = drawMatch() else { return } |
|
||||||
let seedPosition: Int = match.lockAndGetSeedPosition(atTeamPosition: drawTeamPosition) |
|
||||||
team.bracketPosition = seedPosition |
|
||||||
tournamentObject()?.updateTeamScores(in: seedPosition) |
|
||||||
} |
|
||||||
|
|
||||||
func exportedDrawLog() -> String { |
|
||||||
[drawType.localizedDrawType(), drawDate.localizedDate(), localizedDrawLogLabel(), localizedDrawBranch()].filter({ $0.isEmpty == false }).joined(separator: " ") |
|
||||||
} |
|
||||||
|
|
||||||
func localizedDrawSeedLabel() -> String { |
|
||||||
return "\(drawType.localizedDrawType()) #\(drawSeed + 1)" |
|
||||||
} |
|
||||||
|
|
||||||
func localizedDrawLogLabel() -> String { |
|
||||||
return [localizedDrawSeedLabel(), positionLabel()].filter({ $0.isEmpty == false }).joined(separator: " -> ") |
|
||||||
} |
|
||||||
|
|
||||||
func localizedDrawBranch() -> String { |
|
||||||
switch drawType { |
|
||||||
case .seed: |
|
||||||
return drawTeamPosition.localizedBranchLabel() |
|
||||||
default: |
|
||||||
return "" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func drawMatch() -> Match? { |
|
||||||
switch drawType { |
|
||||||
case .seed: |
|
||||||
let roundIndex = RoundRule.roundIndex(fromMatchIndex: drawMatchIndex) |
|
||||||
return tournamentStore?.rounds.first(where: { $0.parent == nil && $0.index == roundIndex })?._matches().first(where: { $0.index == drawMatchIndex }) |
|
||||||
default: |
|
||||||
return nil |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func positionLabel() -> String { |
|
||||||
return drawMatch()?.roundAndMatchTitle() ?? "" |
|
||||||
} |
|
||||||
|
|
||||||
func roundLabel() -> String { |
|
||||||
return drawMatch()?.roundTitle() ?? "" |
|
||||||
} |
|
||||||
|
|
||||||
func matchLabel() -> String { |
|
||||||
return drawMatch()?.matchTitle() ?? "" |
|
||||||
} |
|
||||||
|
|
||||||
var tournamentStore: TournamentStore? { |
|
||||||
return TournamentLibrary.shared.store(tournamentId: self.tournament) |
|
||||||
} |
|
||||||
|
|
||||||
override func deleteDependencies() { |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
enum DrawType: Int, Codable { |
|
||||||
case seed |
|
||||||
case groupStage |
|
||||||
case court |
|
||||||
|
|
||||||
func localizedDrawType() -> String { |
|
||||||
switch self { |
|
||||||
case .seed: |
|
||||||
return "Tête de série" |
|
||||||
case .groupStage: |
|
||||||
return "Poule" |
|
||||||
case .court: |
|
||||||
return "Terrain" |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,100 +0,0 @@ |
|||||||
// |
|
||||||
// Event_v2.swift |
|
||||||
// Padel Tournament |
|
||||||
// |
|
||||||
// Created by razmig on 10/03/2024. |
|
||||||
// |
|
||||||
|
|
||||||
import Foundation |
|
||||||
import LeStorage |
|
||||||
import SwiftUI |
|
||||||
|
|
||||||
@Observable |
|
||||||
final class Event: BaseEvent { |
|
||||||
|
|
||||||
internal init(creator: String? = nil, club: String? = nil, name: String? = nil, tenupId: String? = nil) { |
|
||||||
super.init(creator: creator, club: club, name: name, tenupId: tenupId) |
|
||||||
self.relatedUser = creator |
|
||||||
} |
|
||||||
|
|
||||||
required init(from decoder: Decoder) throws { |
|
||||||
try super.init(from: decoder) |
|
||||||
} |
|
||||||
|
|
||||||
required public init() { |
|
||||||
super.init() |
|
||||||
} |
|
||||||
|
|
||||||
override func deleteDependencies() { |
|
||||||
let tournaments = self.tournaments |
|
||||||
for tournament in tournaments { |
|
||||||
tournament.deleteDependencies() |
|
||||||
} |
|
||||||
|
|
||||||
DataStore.shared.tournaments.deleteDependencies(tournaments) |
|
||||||
|
|
||||||
let courtsUnavailabilities = self.courtsUnavailability |
|
||||||
for courtsUnavailability in courtsUnavailabilities { |
|
||||||
courtsUnavailability.deleteDependencies() |
|
||||||
} |
|
||||||
DataStore.shared.dateIntervals.deleteDependencies(courtsUnavailabilities) |
|
||||||
} |
|
||||||
|
|
||||||
// MARK: - Computed dependencies |
|
||||||
|
|
||||||
var tournaments: [Tournament] { |
|
||||||
DataStore.shared.tournaments.filter { $0.event == self.id && $0.isDeleted == false } |
|
||||||
} |
|
||||||
|
|
||||||
func clubObject() -> Club? { |
|
||||||
guard let club else { return nil } |
|
||||||
return Store.main.findById(club) |
|
||||||
} |
|
||||||
|
|
||||||
var courtsUnavailability: [DateInterval] { |
|
||||||
DataStore.shared.dateIntervals.filter({ $0.event == id }) |
|
||||||
} |
|
||||||
|
|
||||||
// MARK: - |
|
||||||
|
|
||||||
func eventCourtCount() -> Int { |
|
||||||
tournaments.map { $0.courtCount }.max() ?? 2 |
|
||||||
} |
|
||||||
|
|
||||||
func eventStartDate() -> Date { |
|
||||||
tournaments.map { $0.startDate }.min() ?? Date() |
|
||||||
} |
|
||||||
|
|
||||||
func eventDayDuration() -> Int { |
|
||||||
tournaments.map { $0.dayDuration }.max() ?? 1 |
|
||||||
} |
|
||||||
|
|
||||||
func eventTitle() -> String { |
|
||||||
if let name, name.isEmpty == false { |
|
||||||
return name |
|
||||||
} else { |
|
||||||
return "Événement" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func existingBuild(_ build: any TournamentBuildHolder) -> Tournament? { |
|
||||||
tournaments.first(where: { $0.isSameBuild(build) }) |
|
||||||
} |
|
||||||
|
|
||||||
func tournamentsCourtsUsed(exluding tournamentId: String) -> [DateInterval] { |
|
||||||
tournaments.filter { $0.id != tournamentId }.flatMap({ tournament in |
|
||||||
tournament.getPlayedMatchDateIntervals(in: self) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
func insertOnServer() throws { |
|
||||||
DataStore.shared.events.writeChangeAndInsertOnServer(instance: self) |
|
||||||
for tournament in self.tournaments { |
|
||||||
try tournament.insertOnServer() |
|
||||||
} |
|
||||||
for dataInterval in self.courtsUnavailability { |
|
||||||
try dataInterval.insertOnServer() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,150 +0,0 @@ |
|||||||
// Generated by SwiftModelGenerator |
|
||||||
// Do not modify this file manually |
|
||||||
|
|
||||||
import Foundation |
|
||||||
import LeStorage |
|
||||||
import SwiftUI |
|
||||||
|
|
||||||
@Observable |
|
||||||
class BaseClub: SyncedModelObject, SyncedStorable { |
|
||||||
|
|
||||||
static func resourceName() -> String { return "clubs" } |
|
||||||
static func tokenExemptedMethods() -> [HTTPMethod] { return [] } |
|
||||||
static var copyServerResponse: Bool = true |
|
||||||
|
|
||||||
var id: String = Store.randomId() |
|
||||||
var creator: String? = nil |
|
||||||
var name: String = "" |
|
||||||
var acronym: String = "" |
|
||||||
var phone: String? = nil |
|
||||||
var code: String? = nil |
|
||||||
var address: String? = nil |
|
||||||
var city: String? = nil |
|
||||||
var zipCode: String? = nil |
|
||||||
var latitude: Double? = nil |
|
||||||
var longitude: Double? = nil |
|
||||||
var courtCount: Int = 2 |
|
||||||
var broadcastCode: String? = nil |
|
||||||
var timezone: String? = TimeZone.current.identifier |
|
||||||
|
|
||||||
init( |
|
||||||
id: String = Store.randomId(), |
|
||||||
creator: String? = nil, |
|
||||||
name: String = "", |
|
||||||
acronym: String = "", |
|
||||||
phone: String? = nil, |
|
||||||
code: String? = nil, |
|
||||||
address: String? = nil, |
|
||||||
city: String? = nil, |
|
||||||
zipCode: String? = nil, |
|
||||||
latitude: Double? = nil, |
|
||||||
longitude: Double? = nil, |
|
||||||
courtCount: Int = 2, |
|
||||||
broadcastCode: String? = nil, |
|
||||||
timezone: String? = TimeZone.current.identifier |
|
||||||
) { |
|
||||||
super.init() |
|
||||||
self.id = id |
|
||||||
self.creator = creator |
|
||||||
self.name = name |
|
||||||
self.acronym = acronym |
|
||||||
self.phone = phone |
|
||||||
self.code = code |
|
||||||
self.address = address |
|
||||||
self.city = city |
|
||||||
self.zipCode = zipCode |
|
||||||
self.latitude = latitude |
|
||||||
self.longitude = longitude |
|
||||||
self.courtCount = courtCount |
|
||||||
self.broadcastCode = broadcastCode |
|
||||||
self.timezone = timezone |
|
||||||
} |
|
||||||
required public override init() { |
|
||||||
super.init() |
|
||||||
} |
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey { |
|
||||||
case _id = "id" |
|
||||||
case _creator = "creator" |
|
||||||
case _name = "name" |
|
||||||
case _acronym = "acronym" |
|
||||||
case _phone = "phone" |
|
||||||
case _code = "code" |
|
||||||
case _address = "address" |
|
||||||
case _city = "city" |
|
||||||
case _zipCode = "zipCode" |
|
||||||
case _latitude = "latitude" |
|
||||||
case _longitude = "longitude" |
|
||||||
case _courtCount = "courtCount" |
|
||||||
case _broadcastCode = "broadcastCode" |
|
||||||
case _timezone = "timezone" |
|
||||||
} |
|
||||||
|
|
||||||
required init(from decoder: Decoder) throws { |
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self) |
|
||||||
self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId() |
|
||||||
self.creator = try container.decodeIfPresent(String.self, forKey: ._creator) ?? nil |
|
||||||
self.name = try container.decodeIfPresent(String.self, forKey: ._name) ?? "" |
|
||||||
self.acronym = try container.decodeIfPresent(String.self, forKey: ._acronym) ?? "" |
|
||||||
self.phone = try container.decodeIfPresent(String.self, forKey: ._phone) ?? nil |
|
||||||
self.code = try container.decodeIfPresent(String.self, forKey: ._code) ?? nil |
|
||||||
self.address = try container.decodeIfPresent(String.self, forKey: ._address) ?? nil |
|
||||||
self.city = try container.decodeIfPresent(String.self, forKey: ._city) ?? nil |
|
||||||
self.zipCode = try container.decodeIfPresent(String.self, forKey: ._zipCode) ?? nil |
|
||||||
self.latitude = try container.decodeIfPresent(Double.self, forKey: ._latitude) ?? nil |
|
||||||
self.longitude = try container.decodeIfPresent(Double.self, forKey: ._longitude) ?? nil |
|
||||||
self.courtCount = try container.decodeIfPresent(Int.self, forKey: ._courtCount) ?? 2 |
|
||||||
self.broadcastCode = try container.decodeIfPresent(String.self, forKey: ._broadcastCode) ?? nil |
|
||||||
self.timezone = try container.decodeIfPresent(String.self, forKey: ._timezone) ?? TimeZone.current.identifier |
|
||||||
try super.init(from: decoder) |
|
||||||
} |
|
||||||
|
|
||||||
override func encode(to encoder: Encoder) throws { |
|
||||||
var container = encoder.container(keyedBy: CodingKeys.self) |
|
||||||
try container.encode(self.id, forKey: ._id) |
|
||||||
try container.encode(self.creator, forKey: ._creator) |
|
||||||
try container.encode(self.name, forKey: ._name) |
|
||||||
try container.encode(self.acronym, forKey: ._acronym) |
|
||||||
try container.encode(self.phone, forKey: ._phone) |
|
||||||
try container.encode(self.code, forKey: ._code) |
|
||||||
try container.encode(self.address, forKey: ._address) |
|
||||||
try container.encode(self.city, forKey: ._city) |
|
||||||
try container.encode(self.zipCode, forKey: ._zipCode) |
|
||||||
try container.encode(self.latitude, forKey: ._latitude) |
|
||||||
try container.encode(self.longitude, forKey: ._longitude) |
|
||||||
try container.encode(self.courtCount, forKey: ._courtCount) |
|
||||||
try container.encode(self.broadcastCode, forKey: ._broadcastCode) |
|
||||||
try container.encode(self.timezone, forKey: ._timezone) |
|
||||||
try super.encode(to: encoder) |
|
||||||
} |
|
||||||
|
|
||||||
func creatorValue() -> CustomUser? { |
|
||||||
guard let creator = self.creator else { return nil } |
|
||||||
return Store.main.findById(creator) |
|
||||||
} |
|
||||||
|
|
||||||
func copy(from other: any Storable) { |
|
||||||
guard let club = other as? BaseClub else { return } |
|
||||||
self.id = club.id |
|
||||||
self.creator = club.creator |
|
||||||
self.name = club.name |
|
||||||
self.acronym = club.acronym |
|
||||||
self.phone = club.phone |
|
||||||
self.code = club.code |
|
||||||
self.address = club.address |
|
||||||
self.city = club.city |
|
||||||
self.zipCode = club.zipCode |
|
||||||
self.latitude = club.latitude |
|
||||||
self.longitude = club.longitude |
|
||||||
self.courtCount = club.courtCount |
|
||||||
self.broadcastCode = club.broadcastCode |
|
||||||
self.timezone = club.timezone |
|
||||||
} |
|
||||||
|
|
||||||
static func relationships() -> [Relationship] { |
|
||||||
return [ |
|
||||||
Relationship(type: CustomUser.self, keyPath: \BaseClub.creator), |
|
||||||
] |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,93 +0,0 @@ |
|||||||
// Generated by SwiftModelGenerator |
|
||||||
// Do not modify this file manually |
|
||||||
|
|
||||||
import Foundation |
|
||||||
import LeStorage |
|
||||||
import SwiftUI |
|
||||||
|
|
||||||
@Observable |
|
||||||
class BaseCourt: SyncedModelObject, SyncedStorable { |
|
||||||
|
|
||||||
static func resourceName() -> String { return "courts" } |
|
||||||
static func tokenExemptedMethods() -> [HTTPMethod] { return [] } |
|
||||||
static var copyServerResponse: Bool = false |
|
||||||
|
|
||||||
var id: String = Store.randomId() |
|
||||||
var index: Int = 0 |
|
||||||
var club: String = "" |
|
||||||
var name: String? = nil |
|
||||||
var exitAllowed: Bool = false |
|
||||||
var indoor: Bool = false |
|
||||||
|
|
||||||
init( |
|
||||||
id: String = Store.randomId(), |
|
||||||
index: Int = 0, |
|
||||||
club: String = "", |
|
||||||
name: String? = nil, |
|
||||||
exitAllowed: Bool = false, |
|
||||||
indoor: Bool = false |
|
||||||
) { |
|
||||||
super.init() |
|
||||||
self.id = id |
|
||||||
self.index = index |
|
||||||
self.club = club |
|
||||||
self.name = name |
|
||||||
self.exitAllowed = exitAllowed |
|
||||||
self.indoor = indoor |
|
||||||
} |
|
||||||
required public override init() { |
|
||||||
super.init() |
|
||||||
} |
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey { |
|
||||||
case _id = "id" |
|
||||||
case _index = "index" |
|
||||||
case _club = "club" |
|
||||||
case _name = "name" |
|
||||||
case _exitAllowed = "exitAllowed" |
|
||||||
case _indoor = "indoor" |
|
||||||
} |
|
||||||
|
|
||||||
required init(from decoder: Decoder) throws { |
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self) |
|
||||||
self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId() |
|
||||||
self.index = try container.decodeIfPresent(Int.self, forKey: ._index) ?? 0 |
|
||||||
self.club = try container.decodeIfPresent(String.self, forKey: ._club) ?? "" |
|
||||||
self.name = try container.decodeIfPresent(String.self, forKey: ._name) ?? nil |
|
||||||
self.exitAllowed = try container.decodeIfPresent(Bool.self, forKey: ._exitAllowed) ?? false |
|
||||||
self.indoor = try container.decodeIfPresent(Bool.self, forKey: ._indoor) ?? false |
|
||||||
try super.init(from: decoder) |
|
||||||
} |
|
||||||
|
|
||||||
override func encode(to encoder: Encoder) throws { |
|
||||||
var container = encoder.container(keyedBy: CodingKeys.self) |
|
||||||
try container.encode(self.id, forKey: ._id) |
|
||||||
try container.encode(self.index, forKey: ._index) |
|
||||||
try container.encode(self.club, forKey: ._club) |
|
||||||
try container.encode(self.name, forKey: ._name) |
|
||||||
try container.encode(self.exitAllowed, forKey: ._exitAllowed) |
|
||||||
try container.encode(self.indoor, forKey: ._indoor) |
|
||||||
try super.encode(to: encoder) |
|
||||||
} |
|
||||||
|
|
||||||
func clubValue() -> Club? { |
|
||||||
return Store.main.findById(club) |
|
||||||
} |
|
||||||
|
|
||||||
func copy(from other: any Storable) { |
|
||||||
guard let court = other as? BaseCourt else { return } |
|
||||||
self.id = court.id |
|
||||||
self.index = court.index |
|
||||||
self.club = court.club |
|
||||||
self.name = court.name |
|
||||||
self.exitAllowed = court.exitAllowed |
|
||||||
self.indoor = court.indoor |
|
||||||
} |
|
||||||
|
|
||||||
static func relationships() -> [Relationship] { |
|
||||||
return [ |
|
||||||
Relationship(type: Club.self, keyPath: \BaseCourt.club), |
|
||||||
] |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,262 +0,0 @@ |
|||||||
// Generated by SwiftModelGenerator |
|
||||||
// Do not modify this file manually |
|
||||||
|
|
||||||
import Foundation |
|
||||||
import LeStorage |
|
||||||
import SwiftUI |
|
||||||
|
|
||||||
@Observable |
|
||||||
class BaseCustomUser: SyncedModelObject, SyncedStorable { |
|
||||||
|
|
||||||
static func resourceName() -> String { return "users" } |
|
||||||
static func tokenExemptedMethods() -> [HTTPMethod] { return [.post] } |
|
||||||
static var copyServerResponse: Bool = false |
|
||||||
|
|
||||||
var id: String = Store.randomId() |
|
||||||
var username: String = "" |
|
||||||
var email: String = "" |
|
||||||
var clubs: [String] = [] |
|
||||||
var umpireCode: String? = nil |
|
||||||
var licenceId: String? = nil |
|
||||||
var firstName: String = "" |
|
||||||
var lastName: String = "" |
|
||||||
var phone: String? = nil |
|
||||||
var country: String? = nil |
|
||||||
var summonsMessageBody: String? = nil |
|
||||||
var summonsMessageSignature: String? = nil |
|
||||||
var summonsAvailablePaymentMethods: String? = nil |
|
||||||
var summonsDisplayFormat: Bool = false |
|
||||||
var summonsDisplayEntryFee: Bool = false |
|
||||||
var summonsUseFullCustomMessage: Bool = false |
|
||||||
var matchFormatsDefaultDuration: [MatchFormat: Int]? = nil |
|
||||||
var bracketMatchFormatPreference: MatchFormat? = nil |
|
||||||
var groupStageMatchFormatPreference: MatchFormat? = nil |
|
||||||
var loserBracketMatchFormatPreference: MatchFormat? = nil |
|
||||||
var loserBracketMode: LoserBracketMode = .automatic |
|
||||||
var disableRankingFederalRuling: Bool = false |
|
||||||
var deviceId: String? = nil |
|
||||||
var agents: [String] = [] |
|
||||||
var userRole: Int? = nil |
|
||||||
var registrationPaymentMode: RegistrationPaymentMode = RegistrationPaymentMode.disabled |
|
||||||
var umpireCustomMail: String? = nil |
|
||||||
var umpireCustomContact: String? = nil |
|
||||||
var umpireCustomPhone: String? = nil |
|
||||||
var hideUmpireMail: Bool = false |
|
||||||
var hideUmpirePhone: Bool = true |
|
||||||
|
|
||||||
init( |
|
||||||
id: String = Store.randomId(), |
|
||||||
username: String = "", |
|
||||||
email: String = "", |
|
||||||
clubs: [String] = [], |
|
||||||
umpireCode: String? = nil, |
|
||||||
licenceId: String? = nil, |
|
||||||
firstName: String = "", |
|
||||||
lastName: String = "", |
|
||||||
phone: String? = nil, |
|
||||||
country: String? = nil, |
|
||||||
summonsMessageBody: String? = nil, |
|
||||||
summonsMessageSignature: String? = nil, |
|
||||||
summonsAvailablePaymentMethods: String? = nil, |
|
||||||
summonsDisplayFormat: Bool = false, |
|
||||||
summonsDisplayEntryFee: Bool = false, |
|
||||||
summonsUseFullCustomMessage: Bool = false, |
|
||||||
matchFormatsDefaultDuration: [MatchFormat: Int]? = nil, |
|
||||||
bracketMatchFormatPreference: MatchFormat? = nil, |
|
||||||
groupStageMatchFormatPreference: MatchFormat? = nil, |
|
||||||
loserBracketMatchFormatPreference: MatchFormat? = nil, |
|
||||||
loserBracketMode: LoserBracketMode = .automatic, |
|
||||||
disableRankingFederalRuling: Bool = false, |
|
||||||
deviceId: String? = nil, |
|
||||||
agents: [String] = [], |
|
||||||
userRole: Int? = nil, |
|
||||||
registrationPaymentMode: RegistrationPaymentMode = RegistrationPaymentMode.disabled, |
|
||||||
umpireCustomMail: String? = nil, |
|
||||||
umpireCustomContact: String? = nil, |
|
||||||
umpireCustomPhone: String? = nil, |
|
||||||
hideUmpireMail: Bool = false, |
|
||||||
hideUmpirePhone: Bool = true |
|
||||||
) { |
|
||||||
super.init() |
|
||||||
self.id = id |
|
||||||
self.username = username |
|
||||||
self.email = email |
|
||||||
self.clubs = clubs |
|
||||||
self.umpireCode = umpireCode |
|
||||||
self.licenceId = licenceId |
|
||||||
self.firstName = firstName |
|
||||||
self.lastName = lastName |
|
||||||
self.phone = phone |
|
||||||
self.country = country |
|
||||||
self.summonsMessageBody = summonsMessageBody |
|
||||||
self.summonsMessageSignature = summonsMessageSignature |
|
||||||
self.summonsAvailablePaymentMethods = summonsAvailablePaymentMethods |
|
||||||
self.summonsDisplayFormat = summonsDisplayFormat |
|
||||||
self.summonsDisplayEntryFee = summonsDisplayEntryFee |
|
||||||
self.summonsUseFullCustomMessage = summonsUseFullCustomMessage |
|
||||||
self.matchFormatsDefaultDuration = matchFormatsDefaultDuration |
|
||||||
self.bracketMatchFormatPreference = bracketMatchFormatPreference |
|
||||||
self.groupStageMatchFormatPreference = groupStageMatchFormatPreference |
|
||||||
self.loserBracketMatchFormatPreference = loserBracketMatchFormatPreference |
|
||||||
self.loserBracketMode = loserBracketMode |
|
||||||
self.disableRankingFederalRuling = disableRankingFederalRuling |
|
||||||
self.deviceId = deviceId |
|
||||||
self.agents = agents |
|
||||||
self.userRole = userRole |
|
||||||
self.registrationPaymentMode = registrationPaymentMode |
|
||||||
self.umpireCustomMail = umpireCustomMail |
|
||||||
self.umpireCustomContact = umpireCustomContact |
|
||||||
self.umpireCustomPhone = umpireCustomPhone |
|
||||||
self.hideUmpireMail = hideUmpireMail |
|
||||||
self.hideUmpirePhone = hideUmpirePhone |
|
||||||
} |
|
||||||
required public override init() { |
|
||||||
super.init() |
|
||||||
} |
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey { |
|
||||||
case _id = "id" |
|
||||||
case _username = "username" |
|
||||||
case _email = "email" |
|
||||||
case _clubs = "clubs" |
|
||||||
case _umpireCode = "umpireCode" |
|
||||||
case _licenceId = "licenceId" |
|
||||||
case _firstName = "firstName" |
|
||||||
case _lastName = "lastName" |
|
||||||
case _phone = "phone" |
|
||||||
case _country = "country" |
|
||||||
case _summonsMessageBody = "summonsMessageBody" |
|
||||||
case _summonsMessageSignature = "summonsMessageSignature" |
|
||||||
case _summonsAvailablePaymentMethods = "summonsAvailablePaymentMethods" |
|
||||||
case _summonsDisplayFormat = "summonsDisplayFormat" |
|
||||||
case _summonsDisplayEntryFee = "summonsDisplayEntryFee" |
|
||||||
case _summonsUseFullCustomMessage = "summonsUseFullCustomMessage" |
|
||||||
case _matchFormatsDefaultDuration = "matchFormatsDefaultDuration" |
|
||||||
case _bracketMatchFormatPreference = "bracketMatchFormatPreference" |
|
||||||
case _groupStageMatchFormatPreference = "groupStageMatchFormatPreference" |
|
||||||
case _loserBracketMatchFormatPreference = "loserBracketMatchFormatPreference" |
|
||||||
case _loserBracketMode = "loserBracketMode" |
|
||||||
case _disableRankingFederalRuling = "disableRankingFederalRuling" |
|
||||||
case _deviceId = "deviceId" |
|
||||||
case _agents = "agents" |
|
||||||
case _userRole = "userRole" |
|
||||||
case _registrationPaymentMode = "registrationPaymentMode" |
|
||||||
case _umpireCustomMail = "umpireCustomMail" |
|
||||||
case _umpireCustomContact = "umpireCustomContact" |
|
||||||
case _umpireCustomPhone = "umpireCustomPhone" |
|
||||||
case _hideUmpireMail = "hideUmpireMail" |
|
||||||
case _hideUmpirePhone = "hideUmpirePhone" |
|
||||||
} |
|
||||||
|
|
||||||
required init(from decoder: Decoder) throws { |
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self) |
|
||||||
self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId() |
|
||||||
self.username = try container.decodeIfPresent(String.self, forKey: ._username) ?? "" |
|
||||||
self.email = try container.decodeIfPresent(String.self, forKey: ._email) ?? "" |
|
||||||
self.clubs = try container.decodeIfPresent([String].self, forKey: ._clubs) ?? [] |
|
||||||
self.umpireCode = try container.decodeIfPresent(String.self, forKey: ._umpireCode) ?? nil |
|
||||||
self.licenceId = try container.decodeIfPresent(String.self, forKey: ._licenceId) ?? nil |
|
||||||
self.firstName = try container.decodeIfPresent(String.self, forKey: ._firstName) ?? "" |
|
||||||
self.lastName = try container.decodeIfPresent(String.self, forKey: ._lastName) ?? "" |
|
||||||
self.phone = try container.decodeIfPresent(String.self, forKey: ._phone) ?? nil |
|
||||||
self.country = try container.decodeIfPresent(String.self, forKey: ._country) ?? nil |
|
||||||
self.summonsMessageBody = try container.decodeIfPresent(String.self, forKey: ._summonsMessageBody) ?? nil |
|
||||||
self.summonsMessageSignature = try container.decodeIfPresent(String.self, forKey: ._summonsMessageSignature) ?? nil |
|
||||||
self.summonsAvailablePaymentMethods = try container.decodeIfPresent(String.self, forKey: ._summonsAvailablePaymentMethods) ?? nil |
|
||||||
self.summonsDisplayFormat = try container.decodeIfPresent(Bool.self, forKey: ._summonsDisplayFormat) ?? false |
|
||||||
self.summonsDisplayEntryFee = try container.decodeIfPresent(Bool.self, forKey: ._summonsDisplayEntryFee) ?? false |
|
||||||
self.summonsUseFullCustomMessage = try container.decodeIfPresent(Bool.self, forKey: ._summonsUseFullCustomMessage) ?? false |
|
||||||
self.matchFormatsDefaultDuration = try container.decodeIfPresent([MatchFormat: Int].self, forKey: ._matchFormatsDefaultDuration) ?? nil |
|
||||||
self.bracketMatchFormatPreference = try container.decodeIfPresent(MatchFormat.self, forKey: ._bracketMatchFormatPreference) ?? nil |
|
||||||
self.groupStageMatchFormatPreference = try container.decodeIfPresent(MatchFormat.self, forKey: ._groupStageMatchFormatPreference) ?? nil |
|
||||||
self.loserBracketMatchFormatPreference = try container.decodeIfPresent(MatchFormat.self, forKey: ._loserBracketMatchFormatPreference) ?? nil |
|
||||||
self.loserBracketMode = try container.decodeIfPresent(LoserBracketMode.self, forKey: ._loserBracketMode) ?? .automatic |
|
||||||
self.disableRankingFederalRuling = try container.decodeIfPresent(Bool.self, forKey: ._disableRankingFederalRuling) ?? false |
|
||||||
self.deviceId = try container.decodeIfPresent(String.self, forKey: ._deviceId) ?? nil |
|
||||||
self.agents = try container.decodeIfPresent([String].self, forKey: ._agents) ?? [] |
|
||||||
self.userRole = try container.decodeIfPresent(Int.self, forKey: ._userRole) ?? nil |
|
||||||
self.registrationPaymentMode = try container.decodeIfPresent(RegistrationPaymentMode.self, forKey: ._registrationPaymentMode) ?? RegistrationPaymentMode.disabled |
|
||||||
self.umpireCustomMail = try container.decodeIfPresent(String.self, forKey: ._umpireCustomMail) ?? nil |
|
||||||
self.umpireCustomContact = try container.decodeIfPresent(String.self, forKey: ._umpireCustomContact) ?? nil |
|
||||||
self.umpireCustomPhone = try container.decodeIfPresent(String.self, forKey: ._umpireCustomPhone) ?? nil |
|
||||||
self.hideUmpireMail = try container.decodeIfPresent(Bool.self, forKey: ._hideUmpireMail) ?? false |
|
||||||
self.hideUmpirePhone = try container.decodeIfPresent(Bool.self, forKey: ._hideUmpirePhone) ?? true |
|
||||||
try super.init(from: decoder) |
|
||||||
} |
|
||||||
|
|
||||||
override func encode(to encoder: Encoder) throws { |
|
||||||
var container = encoder.container(keyedBy: CodingKeys.self) |
|
||||||
try container.encode(self.id, forKey: ._id) |
|
||||||
try container.encode(self.username, forKey: ._username) |
|
||||||
try container.encode(self.email, forKey: ._email) |
|
||||||
try container.encode(self.clubs, forKey: ._clubs) |
|
||||||
try container.encode(self.umpireCode, forKey: ._umpireCode) |
|
||||||
try container.encode(self.licenceId, forKey: ._licenceId) |
|
||||||
try container.encode(self.firstName, forKey: ._firstName) |
|
||||||
try container.encode(self.lastName, forKey: ._lastName) |
|
||||||
try container.encode(self.phone, forKey: ._phone) |
|
||||||
try container.encode(self.country, forKey: ._country) |
|
||||||
try container.encode(self.summonsMessageBody, forKey: ._summonsMessageBody) |
|
||||||
try container.encode(self.summonsMessageSignature, forKey: ._summonsMessageSignature) |
|
||||||
try container.encode(self.summonsAvailablePaymentMethods, forKey: ._summonsAvailablePaymentMethods) |
|
||||||
try container.encode(self.summonsDisplayFormat, forKey: ._summonsDisplayFormat) |
|
||||||
try container.encode(self.summonsDisplayEntryFee, forKey: ._summonsDisplayEntryFee) |
|
||||||
try container.encode(self.summonsUseFullCustomMessage, forKey: ._summonsUseFullCustomMessage) |
|
||||||
try container.encode(self.matchFormatsDefaultDuration, forKey: ._matchFormatsDefaultDuration) |
|
||||||
try container.encode(self.bracketMatchFormatPreference, forKey: ._bracketMatchFormatPreference) |
|
||||||
try container.encode(self.groupStageMatchFormatPreference, forKey: ._groupStageMatchFormatPreference) |
|
||||||
try container.encode(self.loserBracketMatchFormatPreference, forKey: ._loserBracketMatchFormatPreference) |
|
||||||
try container.encode(self.loserBracketMode, forKey: ._loserBracketMode) |
|
||||||
try container.encode(self.disableRankingFederalRuling, forKey: ._disableRankingFederalRuling) |
|
||||||
try container.encode(self.deviceId, forKey: ._deviceId) |
|
||||||
try container.encode(self.agents, forKey: ._agents) |
|
||||||
try container.encode(self.userRole, forKey: ._userRole) |
|
||||||
try container.encode(self.registrationPaymentMode, forKey: ._registrationPaymentMode) |
|
||||||
try container.encode(self.umpireCustomMail, forKey: ._umpireCustomMail) |
|
||||||
try container.encode(self.umpireCustomContact, forKey: ._umpireCustomContact) |
|
||||||
try container.encode(self.umpireCustomPhone, forKey: ._umpireCustomPhone) |
|
||||||
try container.encode(self.hideUmpireMail, forKey: ._hideUmpireMail) |
|
||||||
try container.encode(self.hideUmpirePhone, forKey: ._hideUmpirePhone) |
|
||||||
try super.encode(to: encoder) |
|
||||||
} |
|
||||||
|
|
||||||
func copy(from other: any Storable) { |
|
||||||
guard let customuser = other as? BaseCustomUser else { return } |
|
||||||
self.id = customuser.id |
|
||||||
self.username = customuser.username |
|
||||||
self.email = customuser.email |
|
||||||
self.clubs = customuser.clubs |
|
||||||
self.umpireCode = customuser.umpireCode |
|
||||||
self.licenceId = customuser.licenceId |
|
||||||
self.firstName = customuser.firstName |
|
||||||
self.lastName = customuser.lastName |
|
||||||
self.phone = customuser.phone |
|
||||||
self.country = customuser.country |
|
||||||
self.summonsMessageBody = customuser.summonsMessageBody |
|
||||||
self.summonsMessageSignature = customuser.summonsMessageSignature |
|
||||||
self.summonsAvailablePaymentMethods = customuser.summonsAvailablePaymentMethods |
|
||||||
self.summonsDisplayFormat = customuser.summonsDisplayFormat |
|
||||||
self.summonsDisplayEntryFee = customuser.summonsDisplayEntryFee |
|
||||||
self.summonsUseFullCustomMessage = customuser.summonsUseFullCustomMessage |
|
||||||
self.matchFormatsDefaultDuration = customuser.matchFormatsDefaultDuration |
|
||||||
self.bracketMatchFormatPreference = customuser.bracketMatchFormatPreference |
|
||||||
self.groupStageMatchFormatPreference = customuser.groupStageMatchFormatPreference |
|
||||||
self.loserBracketMatchFormatPreference = customuser.loserBracketMatchFormatPreference |
|
||||||
self.loserBracketMode = customuser.loserBracketMode |
|
||||||
self.disableRankingFederalRuling = customuser.disableRankingFederalRuling |
|
||||||
self.deviceId = customuser.deviceId |
|
||||||
self.agents = customuser.agents |
|
||||||
self.userRole = customuser.userRole |
|
||||||
self.registrationPaymentMode = customuser.registrationPaymentMode |
|
||||||
self.umpireCustomMail = customuser.umpireCustomMail |
|
||||||
self.umpireCustomContact = customuser.umpireCustomContact |
|
||||||
self.umpireCustomPhone = customuser.umpireCustomPhone |
|
||||||
self.hideUmpireMail = customuser.hideUmpireMail |
|
||||||
self.hideUmpirePhone = customuser.hideUmpirePhone |
|
||||||
} |
|
||||||
|
|
||||||
static func relationships() -> [Relationship] { |
|
||||||
return [] |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,80 +0,0 @@ |
|||||||
// Generated by SwiftModelGenerator |
|
||||||
// Do not modify this file manually |
|
||||||
|
|
||||||
import Foundation |
|
||||||
import LeStorage |
|
||||||
import SwiftUI |
|
||||||
|
|
||||||
@Observable |
|
||||||
class BaseDateInterval: SyncedModelObject, SyncedStorable { |
|
||||||
|
|
||||||
static func resourceName() -> String { return "date-intervals" } |
|
||||||
static func tokenExemptedMethods() -> [HTTPMethod] { return [] } |
|
||||||
static var copyServerResponse: Bool = false |
|
||||||
|
|
||||||
var id: String = Store.randomId() |
|
||||||
var event: String = "" |
|
||||||
var courtIndex: Int = 0 |
|
||||||
var startDate: Date = Date() |
|
||||||
var endDate: Date = Date() |
|
||||||
|
|
||||||
init( |
|
||||||
id: String = Store.randomId(), |
|
||||||
event: String = "", |
|
||||||
courtIndex: Int = 0, |
|
||||||
startDate: Date = Date(), |
|
||||||
endDate: Date = Date() |
|
||||||
) { |
|
||||||
super.init() |
|
||||||
self.id = id |
|
||||||
self.event = event |
|
||||||
self.courtIndex = courtIndex |
|
||||||
self.startDate = startDate |
|
||||||
self.endDate = endDate |
|
||||||
} |
|
||||||
required public override init() { |
|
||||||
super.init() |
|
||||||
} |
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey { |
|
||||||
case _id = "id" |
|
||||||
case _event = "event" |
|
||||||
case _courtIndex = "courtIndex" |
|
||||||
case _startDate = "startDate" |
|
||||||
case _endDate = "endDate" |
|
||||||
} |
|
||||||
|
|
||||||
required init(from decoder: Decoder) throws { |
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self) |
|
||||||
self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId() |
|
||||||
self.event = try container.decodeIfPresent(String.self, forKey: ._event) ?? "" |
|
||||||
self.courtIndex = try container.decodeIfPresent(Int.self, forKey: ._courtIndex) ?? 0 |
|
||||||
self.startDate = try container.decodeIfPresent(Date.self, forKey: ._startDate) ?? Date() |
|
||||||
self.endDate = try container.decodeIfPresent(Date.self, forKey: ._endDate) ?? Date() |
|
||||||
try super.init(from: decoder) |
|
||||||
} |
|
||||||
|
|
||||||
override func encode(to encoder: Encoder) throws { |
|
||||||
var container = encoder.container(keyedBy: CodingKeys.self) |
|
||||||
try container.encode(self.id, forKey: ._id) |
|
||||||
try container.encode(self.event, forKey: ._event) |
|
||||||
try container.encode(self.courtIndex, forKey: ._courtIndex) |
|
||||||
try container.encode(self.startDate, forKey: ._startDate) |
|
||||||
try container.encode(self.endDate, forKey: ._endDate) |
|
||||||
try super.encode(to: encoder) |
|
||||||
} |
|
||||||
|
|
||||||
func copy(from other: any Storable) { |
|
||||||
guard let dateinterval = other as? BaseDateInterval else { return } |
|
||||||
self.id = dateinterval.id |
|
||||||
self.event = dateinterval.event |
|
||||||
self.courtIndex = dateinterval.courtIndex |
|
||||||
self.startDate = dateinterval.startDate |
|
||||||
self.endDate = dateinterval.endDate |
|
||||||
} |
|
||||||
|
|
||||||
static func relationships() -> [Relationship] { |
|
||||||
return [] |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,100 +0,0 @@ |
|||||||
// Generated by SwiftModelGenerator |
|
||||||
// Do not modify this file manually |
|
||||||
|
|
||||||
import Foundation |
|
||||||
import LeStorage |
|
||||||
import SwiftUI |
|
||||||
|
|
||||||
@Observable |
|
||||||
class BaseDrawLog: SyncedModelObject, SyncedStorable { |
|
||||||
|
|
||||||
static func resourceName() -> String { return "draw-logs" } |
|
||||||
static func tokenExemptedMethods() -> [HTTPMethod] { return [] } |
|
||||||
static var copyServerResponse: Bool = false |
|
||||||
|
|
||||||
var id: String = Store.randomId() |
|
||||||
var tournament: String = "" |
|
||||||
var drawDate: Date = Date() |
|
||||||
var drawSeed: Int = 0 |
|
||||||
var drawMatchIndex: Int = 0 |
|
||||||
var drawTeamPosition: TeamPosition = TeamPosition.one |
|
||||||
var drawType: DrawType = DrawType.seed |
|
||||||
|
|
||||||
init( |
|
||||||
id: String = Store.randomId(), |
|
||||||
tournament: String = "", |
|
||||||
drawDate: Date = Date(), |
|
||||||
drawSeed: Int = 0, |
|
||||||
drawMatchIndex: Int = 0, |
|
||||||
drawTeamPosition: TeamPosition = TeamPosition.one, |
|
||||||
drawType: DrawType = DrawType.seed |
|
||||||
) { |
|
||||||
super.init() |
|
||||||
self.id = id |
|
||||||
self.tournament = tournament |
|
||||||
self.drawDate = drawDate |
|
||||||
self.drawSeed = drawSeed |
|
||||||
self.drawMatchIndex = drawMatchIndex |
|
||||||
self.drawTeamPosition = drawTeamPosition |
|
||||||
self.drawType = drawType |
|
||||||
} |
|
||||||
required public override init() { |
|
||||||
super.init() |
|
||||||
} |
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey { |
|
||||||
case _id = "id" |
|
||||||
case _tournament = "tournament" |
|
||||||
case _drawDate = "drawDate" |
|
||||||
case _drawSeed = "drawSeed" |
|
||||||
case _drawMatchIndex = "drawMatchIndex" |
|
||||||
case _drawTeamPosition = "drawTeamPosition" |
|
||||||
case _drawType = "drawType" |
|
||||||
} |
|
||||||
|
|
||||||
required init(from decoder: Decoder) throws { |
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self) |
|
||||||
self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId() |
|
||||||
self.tournament = try container.decodeIfPresent(String.self, forKey: ._tournament) ?? "" |
|
||||||
self.drawDate = try container.decodeIfPresent(Date.self, forKey: ._drawDate) ?? Date() |
|
||||||
self.drawSeed = try container.decodeIfPresent(Int.self, forKey: ._drawSeed) ?? 0 |
|
||||||
self.drawMatchIndex = try container.decodeIfPresent(Int.self, forKey: ._drawMatchIndex) ?? 0 |
|
||||||
self.drawTeamPosition = try container.decodeIfPresent(TeamPosition.self, forKey: ._drawTeamPosition) ?? TeamPosition.one |
|
||||||
self.drawType = try container.decodeIfPresent(DrawType.self, forKey: ._drawType) ?? DrawType.seed |
|
||||||
try super.init(from: decoder) |
|
||||||
} |
|
||||||
|
|
||||||
override func encode(to encoder: Encoder) throws { |
|
||||||
var container = encoder.container(keyedBy: CodingKeys.self) |
|
||||||
try container.encode(self.id, forKey: ._id) |
|
||||||
try container.encode(self.tournament, forKey: ._tournament) |
|
||||||
try container.encode(self.drawDate, forKey: ._drawDate) |
|
||||||
try container.encode(self.drawSeed, forKey: ._drawSeed) |
|
||||||
try container.encode(self.drawMatchIndex, forKey: ._drawMatchIndex) |
|
||||||
try container.encode(self.drawTeamPosition, forKey: ._drawTeamPosition) |
|
||||||
try container.encode(self.drawType, forKey: ._drawType) |
|
||||||
try super.encode(to: encoder) |
|
||||||
} |
|
||||||
|
|
||||||
func tournamentValue() -> Tournament? { |
|
||||||
return Store.main.findById(tournament) |
|
||||||
} |
|
||||||
|
|
||||||
func copy(from other: any Storable) { |
|
||||||
guard let drawlog = other as? BaseDrawLog else { return } |
|
||||||
self.id = drawlog.id |
|
||||||
self.tournament = drawlog.tournament |
|
||||||
self.drawDate = drawlog.drawDate |
|
||||||
self.drawSeed = drawlog.drawSeed |
|
||||||
self.drawMatchIndex = drawlog.drawMatchIndex |
|
||||||
self.drawTeamPosition = drawlog.drawTeamPosition |
|
||||||
self.drawType = drawlog.drawType |
|
||||||
} |
|
||||||
|
|
||||||
static func relationships() -> [Relationship] { |
|
||||||
return [ |
|
||||||
Relationship(type: Tournament.self, keyPath: \BaseDrawLog.tournament), |
|
||||||
] |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,100 +0,0 @@ |
|||||||
// Generated by SwiftModelGenerator |
|
||||||
// Do not modify this file manually |
|
||||||
|
|
||||||
import Foundation |
|
||||||
import LeStorage |
|
||||||
import SwiftUI |
|
||||||
|
|
||||||
@Observable |
|
||||||
class BaseEvent: SyncedModelObject, SyncedStorable { |
|
||||||
|
|
||||||
static func resourceName() -> String { return "events" } |
|
||||||
static func tokenExemptedMethods() -> [HTTPMethod] { return [] } |
|
||||||
static var copyServerResponse: Bool = false |
|
||||||
|
|
||||||
var id: String = Store.randomId() |
|
||||||
var creator: String? = nil |
|
||||||
var club: String? = nil |
|
||||||
var creationDate: Date = Date() |
|
||||||
var name: String? = nil |
|
||||||
var tenupId: String? = nil |
|
||||||
|
|
||||||
init( |
|
||||||
id: String = Store.randomId(), |
|
||||||
creator: String? = nil, |
|
||||||
club: String? = nil, |
|
||||||
creationDate: Date = Date(), |
|
||||||
name: String? = nil, |
|
||||||
tenupId: String? = nil |
|
||||||
) { |
|
||||||
super.init() |
|
||||||
self.id = id |
|
||||||
self.creator = creator |
|
||||||
self.club = club |
|
||||||
self.creationDate = creationDate |
|
||||||
self.name = name |
|
||||||
self.tenupId = tenupId |
|
||||||
} |
|
||||||
required public override init() { |
|
||||||
super.init() |
|
||||||
} |
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey { |
|
||||||
case _id = "id" |
|
||||||
case _creator = "creator" |
|
||||||
case _club = "club" |
|
||||||
case _creationDate = "creationDate" |
|
||||||
case _name = "name" |
|
||||||
case _tenupId = "tenupId" |
|
||||||
} |
|
||||||
|
|
||||||
required init(from decoder: Decoder) throws { |
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self) |
|
||||||
self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId() |
|
||||||
self.creator = try container.decodeIfPresent(String.self, forKey: ._creator) ?? nil |
|
||||||
self.club = try container.decodeIfPresent(String.self, forKey: ._club) ?? nil |
|
||||||
self.creationDate = try container.decodeIfPresent(Date.self, forKey: ._creationDate) ?? Date() |
|
||||||
self.name = try container.decodeIfPresent(String.self, forKey: ._name) ?? nil |
|
||||||
self.tenupId = try container.decodeIfPresent(String.self, forKey: ._tenupId) ?? nil |
|
||||||
try super.init(from: decoder) |
|
||||||
} |
|
||||||
|
|
||||||
override func encode(to encoder: Encoder) throws { |
|
||||||
var container = encoder.container(keyedBy: CodingKeys.self) |
|
||||||
try container.encode(self.id, forKey: ._id) |
|
||||||
try container.encode(self.creator, forKey: ._creator) |
|
||||||
try container.encode(self.club, forKey: ._club) |
|
||||||
try container.encode(self.creationDate, forKey: ._creationDate) |
|
||||||
try container.encode(self.name, forKey: ._name) |
|
||||||
try container.encode(self.tenupId, forKey: ._tenupId) |
|
||||||
try super.encode(to: encoder) |
|
||||||
} |
|
||||||
|
|
||||||
func creatorValue() -> CustomUser? { |
|
||||||
guard let creator = self.creator else { return nil } |
|
||||||
return Store.main.findById(creator) |
|
||||||
} |
|
||||||
|
|
||||||
func clubValue() -> Club? { |
|
||||||
guard let club = self.club else { return nil } |
|
||||||
return Store.main.findById(club) |
|
||||||
} |
|
||||||
|
|
||||||
func copy(from other: any Storable) { |
|
||||||
guard let event = other as? BaseEvent else { return } |
|
||||||
self.id = event.id |
|
||||||
self.creator = event.creator |
|
||||||
self.club = event.club |
|
||||||
self.creationDate = event.creationDate |
|
||||||
self.name = event.name |
|
||||||
self.tenupId = event.tenupId |
|
||||||
} |
|
||||||
|
|
||||||
static func relationships() -> [Relationship] { |
|
||||||
return [ |
|
||||||
Relationship(type: CustomUser.self, keyPath: \BaseEvent.creator), |
|
||||||
Relationship(type: Club.self, keyPath: \BaseEvent.club), |
|
||||||
] |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,107 +0,0 @@ |
|||||||
// Generated by SwiftModelGenerator |
|
||||||
// Do not modify this file manually |
|
||||||
|
|
||||||
import Foundation |
|
||||||
import LeStorage |
|
||||||
import SwiftUI |
|
||||||
|
|
||||||
@Observable |
|
||||||
class BaseGroupStage: SyncedModelObject, SyncedStorable { |
|
||||||
|
|
||||||
static func resourceName() -> String { return "group-stages" } |
|
||||||
static func tokenExemptedMethods() -> [HTTPMethod] { return [] } |
|
||||||
static var copyServerResponse: Bool = false |
|
||||||
|
|
||||||
var id: String = Store.randomId() |
|
||||||
var tournament: String = "" |
|
||||||
var index: Int = 0 |
|
||||||
var size: Int = 0 |
|
||||||
var format: MatchFormat? = nil |
|
||||||
var startDate: Date? = nil |
|
||||||
var name: String? = nil |
|
||||||
var step: Int = 0 |
|
||||||
|
|
||||||
init( |
|
||||||
id: String = Store.randomId(), |
|
||||||
tournament: String = "", |
|
||||||
index: Int = 0, |
|
||||||
size: Int = 0, |
|
||||||
format: MatchFormat? = nil, |
|
||||||
startDate: Date? = nil, |
|
||||||
name: String? = nil, |
|
||||||
step: Int = 0 |
|
||||||
) { |
|
||||||
super.init() |
|
||||||
self.id = id |
|
||||||
self.tournament = tournament |
|
||||||
self.index = index |
|
||||||
self.size = size |
|
||||||
self.format = format |
|
||||||
self.startDate = startDate |
|
||||||
self.name = name |
|
||||||
self.step = step |
|
||||||
} |
|
||||||
required public override init() { |
|
||||||
super.init() |
|
||||||
} |
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey { |
|
||||||
case _id = "id" |
|
||||||
case _tournament = "tournament" |
|
||||||
case _index = "index" |
|
||||||
case _size = "size" |
|
||||||
case _format = "format" |
|
||||||
case _startDate = "startDate" |
|
||||||
case _name = "name" |
|
||||||
case _step = "step" |
|
||||||
} |
|
||||||
|
|
||||||
required init(from decoder: Decoder) throws { |
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self) |
|
||||||
self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId() |
|
||||||
self.tournament = try container.decodeIfPresent(String.self, forKey: ._tournament) ?? "" |
|
||||||
self.index = try container.decodeIfPresent(Int.self, forKey: ._index) ?? 0 |
|
||||||
self.size = try container.decodeIfPresent(Int.self, forKey: ._size) ?? 0 |
|
||||||
self.format = try container.decodeIfPresent(MatchFormat.self, forKey: ._format) ?? nil |
|
||||||
self.startDate = try container.decodeIfPresent(Date.self, forKey: ._startDate) ?? nil |
|
||||||
self.name = try container.decodeIfPresent(String.self, forKey: ._name) ?? nil |
|
||||||
self.step = try container.decodeIfPresent(Int.self, forKey: ._step) ?? 0 |
|
||||||
try super.init(from: decoder) |
|
||||||
} |
|
||||||
|
|
||||||
override func encode(to encoder: Encoder) throws { |
|
||||||
var container = encoder.container(keyedBy: CodingKeys.self) |
|
||||||
try container.encode(self.id, forKey: ._id) |
|
||||||
try container.encode(self.tournament, forKey: ._tournament) |
|
||||||
try container.encode(self.index, forKey: ._index) |
|
||||||
try container.encode(self.size, forKey: ._size) |
|
||||||
try container.encode(self.format, forKey: ._format) |
|
||||||
try container.encode(self.startDate, forKey: ._startDate) |
|
||||||
try container.encode(self.name, forKey: ._name) |
|
||||||
try container.encode(self.step, forKey: ._step) |
|
||||||
try super.encode(to: encoder) |
|
||||||
} |
|
||||||
|
|
||||||
func tournamentValue() -> Tournament? { |
|
||||||
return Store.main.findById(tournament) |
|
||||||
} |
|
||||||
|
|
||||||
func copy(from other: any Storable) { |
|
||||||
guard let groupstage = other as? BaseGroupStage else { return } |
|
||||||
self.id = groupstage.id |
|
||||||
self.tournament = groupstage.tournament |
|
||||||
self.index = groupstage.index |
|
||||||
self.size = groupstage.size |
|
||||||
self.format = groupstage.format |
|
||||||
self.startDate = groupstage.startDate |
|
||||||
self.name = groupstage.name |
|
||||||
self.step = groupstage.step |
|
||||||
} |
|
||||||
|
|
||||||
static func relationships() -> [Relationship] { |
|
||||||
return [ |
|
||||||
Relationship(type: Tournament.self, keyPath: \BaseGroupStage.tournament), |
|
||||||
] |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,156 +0,0 @@ |
|||||||
// Generated by SwiftModelGenerator |
|
||||||
// Do not modify this file manually |
|
||||||
|
|
||||||
import Foundation |
|
||||||
import LeStorage |
|
||||||
import SwiftUI |
|
||||||
|
|
||||||
@Observable |
|
||||||
class BaseMatch: SyncedModelObject, SyncedStorable { |
|
||||||
|
|
||||||
static func resourceName() -> String { return "matches" } |
|
||||||
static func tokenExemptedMethods() -> [HTTPMethod] { return [] } |
|
||||||
static var copyServerResponse: Bool = false |
|
||||||
|
|
||||||
var id: String = Store.randomId() |
|
||||||
var round: String? = nil |
|
||||||
var groupStage: String? = nil |
|
||||||
var startDate: Date? = nil |
|
||||||
var endDate: Date? = nil |
|
||||||
var index: Int = 0 |
|
||||||
var format: MatchFormat? = nil |
|
||||||
var servingTeamId: String? = nil |
|
||||||
var winningTeamId: String? = nil |
|
||||||
var losingTeamId: String? = nil |
|
||||||
var name: String? = nil |
|
||||||
var disabled: Bool = false |
|
||||||
var courtIndex: Int? = nil |
|
||||||
var confirmed: Bool = false |
|
||||||
|
|
||||||
init( |
|
||||||
id: String = Store.randomId(), |
|
||||||
round: String? = nil, |
|
||||||
groupStage: String? = nil, |
|
||||||
startDate: Date? = nil, |
|
||||||
endDate: Date? = nil, |
|
||||||
index: Int = 0, |
|
||||||
format: MatchFormat? = nil, |
|
||||||
servingTeamId: String? = nil, |
|
||||||
winningTeamId: String? = nil, |
|
||||||
losingTeamId: String? = nil, |
|
||||||
name: String? = nil, |
|
||||||
disabled: Bool = false, |
|
||||||
courtIndex: Int? = nil, |
|
||||||
confirmed: Bool = false |
|
||||||
) { |
|
||||||
super.init() |
|
||||||
self.id = id |
|
||||||
self.round = round |
|
||||||
self.groupStage = groupStage |
|
||||||
self.startDate = startDate |
|
||||||
self.endDate = endDate |
|
||||||
self.index = index |
|
||||||
self.format = format |
|
||||||
self.servingTeamId = servingTeamId |
|
||||||
self.winningTeamId = winningTeamId |
|
||||||
self.losingTeamId = losingTeamId |
|
||||||
self.name = name |
|
||||||
self.disabled = disabled |
|
||||||
self.courtIndex = courtIndex |
|
||||||
self.confirmed = confirmed |
|
||||||
} |
|
||||||
required public override init() { |
|
||||||
super.init() |
|
||||||
} |
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey { |
|
||||||
case _id = "id" |
|
||||||
case _round = "round" |
|
||||||
case _groupStage = "groupStage" |
|
||||||
case _startDate = "startDate" |
|
||||||
case _endDate = "endDate" |
|
||||||
case _index = "index" |
|
||||||
case _format = "format" |
|
||||||
case _servingTeamId = "servingTeamId" |
|
||||||
case _winningTeamId = "winningTeamId" |
|
||||||
case _losingTeamId = "losingTeamId" |
|
||||||
case _name = "name" |
|
||||||
case _disabled = "disabled" |
|
||||||
case _courtIndex = "courtIndex" |
|
||||||
case _confirmed = "confirmed" |
|
||||||
} |
|
||||||
|
|
||||||
required init(from decoder: Decoder) throws { |
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self) |
|
||||||
self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId() |
|
||||||
self.round = try container.decodeIfPresent(String.self, forKey: ._round) ?? nil |
|
||||||
self.groupStage = try container.decodeIfPresent(String.self, forKey: ._groupStage) ?? nil |
|
||||||
self.startDate = try container.decodeIfPresent(Date.self, forKey: ._startDate) ?? nil |
|
||||||
self.endDate = try container.decodeIfPresent(Date.self, forKey: ._endDate) ?? nil |
|
||||||
self.index = try container.decodeIfPresent(Int.self, forKey: ._index) ?? 0 |
|
||||||
self.format = try container.decodeIfPresent(MatchFormat.self, forKey: ._format) ?? nil |
|
||||||
self.servingTeamId = try container.decodeIfPresent(String.self, forKey: ._servingTeamId) ?? nil |
|
||||||
self.winningTeamId = try container.decodeIfPresent(String.self, forKey: ._winningTeamId) ?? nil |
|
||||||
self.losingTeamId = try container.decodeIfPresent(String.self, forKey: ._losingTeamId) ?? nil |
|
||||||
self.name = try container.decodeIfPresent(String.self, forKey: ._name) ?? nil |
|
||||||
self.disabled = try container.decodeIfPresent(Bool.self, forKey: ._disabled) ?? false |
|
||||||
self.courtIndex = try container.decodeIfPresent(Int.self, forKey: ._courtIndex) ?? nil |
|
||||||
self.confirmed = try container.decodeIfPresent(Bool.self, forKey: ._confirmed) ?? false |
|
||||||
try super.init(from: decoder) |
|
||||||
} |
|
||||||
|
|
||||||
override func encode(to encoder: Encoder) throws { |
|
||||||
var container = encoder.container(keyedBy: CodingKeys.self) |
|
||||||
try container.encode(self.id, forKey: ._id) |
|
||||||
try container.encode(self.round, forKey: ._round) |
|
||||||
try container.encode(self.groupStage, forKey: ._groupStage) |
|
||||||
try container.encode(self.startDate, forKey: ._startDate) |
|
||||||
try container.encode(self.endDate, forKey: ._endDate) |
|
||||||
try container.encode(self.index, forKey: ._index) |
|
||||||
try container.encode(self.format, forKey: ._format) |
|
||||||
try container.encode(self.servingTeamId, forKey: ._servingTeamId) |
|
||||||
try container.encode(self.winningTeamId, forKey: ._winningTeamId) |
|
||||||
try container.encode(self.losingTeamId, forKey: ._losingTeamId) |
|
||||||
try container.encode(self.name, forKey: ._name) |
|
||||||
try container.encode(self.disabled, forKey: ._disabled) |
|
||||||
try container.encode(self.courtIndex, forKey: ._courtIndex) |
|
||||||
try container.encode(self.confirmed, forKey: ._confirmed) |
|
||||||
try super.encode(to: encoder) |
|
||||||
} |
|
||||||
|
|
||||||
func roundValue() -> Round? { |
|
||||||
guard let round = self.round else { return nil } |
|
||||||
return self.store?.findById(round) |
|
||||||
} |
|
||||||
|
|
||||||
func groupStageValue() -> GroupStage? { |
|
||||||
guard let groupStage = self.groupStage else { return nil } |
|
||||||
return self.store?.findById(groupStage) |
|
||||||
} |
|
||||||
|
|
||||||
func copy(from other: any Storable) { |
|
||||||
guard let match = other as? BaseMatch else { return } |
|
||||||
self.id = match.id |
|
||||||
self.round = match.round |
|
||||||
self.groupStage = match.groupStage |
|
||||||
self.startDate = match.startDate |
|
||||||
self.endDate = match.endDate |
|
||||||
self.index = match.index |
|
||||||
self.format = match.format |
|
||||||
self.servingTeamId = match.servingTeamId |
|
||||||
self.winningTeamId = match.winningTeamId |
|
||||||
self.losingTeamId = match.losingTeamId |
|
||||||
self.name = match.name |
|
||||||
self.disabled = match.disabled |
|
||||||
self.courtIndex = match.courtIndex |
|
||||||
self.confirmed = match.confirmed |
|
||||||
} |
|
||||||
|
|
||||||
static func relationships() -> [Relationship] { |
|
||||||
return [ |
|
||||||
Relationship(type: Round.self, keyPath: \BaseMatch.round), |
|
||||||
Relationship(type: GroupStage.self, keyPath: \BaseMatch.groupStage), |
|
||||||
] |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,163 +0,0 @@ |
|||||||
// Generated by SwiftModelGenerator |
|
||||||
// Do not modify this file manually |
|
||||||
|
|
||||||
import Foundation |
|
||||||
import LeStorage |
|
||||||
import SwiftUI |
|
||||||
|
|
||||||
@Observable |
|
||||||
class BaseMatchScheduler: BaseModelObject, Storable { |
|
||||||
|
|
||||||
static func resourceName() -> String { return "match-scheduler" } |
|
||||||
static func tokenExemptedMethods() -> [HTTPMethod] { return [] } |
|
||||||
static var copyServerResponse: Bool = false |
|
||||||
|
|
||||||
var id: String = Store.randomId() |
|
||||||
var tournament: String = "" |
|
||||||
var timeDifferenceLimit: Int = 5 |
|
||||||
var loserBracketRotationDifference: Int = 0 |
|
||||||
var upperBracketRotationDifference: Int = 1 |
|
||||||
var accountUpperBracketBreakTime: Bool = true |
|
||||||
var accountLoserBracketBreakTime: Bool = false |
|
||||||
var randomizeCourts: Bool = true |
|
||||||
var rotationDifferenceIsImportant: Bool = false |
|
||||||
var shouldHandleUpperRoundSlice: Bool = false |
|
||||||
var shouldEndRoundBeforeStartingNext: Bool = true |
|
||||||
var groupStageChunkCount: Int? = nil |
|
||||||
var overrideCourtsUnavailability: Bool = false |
|
||||||
var shouldTryToFillUpCourtsAvailable: Bool = false |
|
||||||
var courtsAvailable: Set<Int> = Set<Int>() |
|
||||||
var simultaneousStart: Bool = true |
|
||||||
|
|
||||||
init( |
|
||||||
id: String = Store.randomId(), |
|
||||||
tournament: String = "", |
|
||||||
timeDifferenceLimit: Int = 5, |
|
||||||
loserBracketRotationDifference: Int = 0, |
|
||||||
upperBracketRotationDifference: Int = 1, |
|
||||||
accountUpperBracketBreakTime: Bool = true, |
|
||||||
accountLoserBracketBreakTime: Bool = false, |
|
||||||
randomizeCourts: Bool = true, |
|
||||||
rotationDifferenceIsImportant: Bool = false, |
|
||||||
shouldHandleUpperRoundSlice: Bool = false, |
|
||||||
shouldEndRoundBeforeStartingNext: Bool = true, |
|
||||||
groupStageChunkCount: Int? = nil, |
|
||||||
overrideCourtsUnavailability: Bool = false, |
|
||||||
shouldTryToFillUpCourtsAvailable: Bool = false, |
|
||||||
courtsAvailable: Set<Int> = Set<Int>(), |
|
||||||
simultaneousStart: Bool = true |
|
||||||
) { |
|
||||||
super.init() |
|
||||||
self.id = id |
|
||||||
self.tournament = tournament |
|
||||||
self.timeDifferenceLimit = timeDifferenceLimit |
|
||||||
self.loserBracketRotationDifference = loserBracketRotationDifference |
|
||||||
self.upperBracketRotationDifference = upperBracketRotationDifference |
|
||||||
self.accountUpperBracketBreakTime = accountUpperBracketBreakTime |
|
||||||
self.accountLoserBracketBreakTime = accountLoserBracketBreakTime |
|
||||||
self.randomizeCourts = randomizeCourts |
|
||||||
self.rotationDifferenceIsImportant = rotationDifferenceIsImportant |
|
||||||
self.shouldHandleUpperRoundSlice = shouldHandleUpperRoundSlice |
|
||||||
self.shouldEndRoundBeforeStartingNext = shouldEndRoundBeforeStartingNext |
|
||||||
self.groupStageChunkCount = groupStageChunkCount |
|
||||||
self.overrideCourtsUnavailability = overrideCourtsUnavailability |
|
||||||
self.shouldTryToFillUpCourtsAvailable = shouldTryToFillUpCourtsAvailable |
|
||||||
self.courtsAvailable = courtsAvailable |
|
||||||
self.simultaneousStart = simultaneousStart |
|
||||||
} |
|
||||||
required public override init() { |
|
||||||
super.init() |
|
||||||
} |
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey { |
|
||||||
case _id = "id" |
|
||||||
case _tournament = "tournament" |
|
||||||
case _timeDifferenceLimit = "timeDifferenceLimit" |
|
||||||
case _loserBracketRotationDifference = "loserBracketRotationDifference" |
|
||||||
case _upperBracketRotationDifference = "upperBracketRotationDifference" |
|
||||||
case _accountUpperBracketBreakTime = "accountUpperBracketBreakTime" |
|
||||||
case _accountLoserBracketBreakTime = "accountLoserBracketBreakTime" |
|
||||||
case _randomizeCourts = "randomizeCourts" |
|
||||||
case _rotationDifferenceIsImportant = "rotationDifferenceIsImportant" |
|
||||||
case _shouldHandleUpperRoundSlice = "shouldHandleUpperRoundSlice" |
|
||||||
case _shouldEndRoundBeforeStartingNext = "shouldEndRoundBeforeStartingNext" |
|
||||||
case _groupStageChunkCount = "groupStageChunkCount" |
|
||||||
case _overrideCourtsUnavailability = "overrideCourtsUnavailability" |
|
||||||
case _shouldTryToFillUpCourtsAvailable = "shouldTryToFillUpCourtsAvailable" |
|
||||||
case _courtsAvailable = "courtsAvailable" |
|
||||||
case _simultaneousStart = "simultaneousStart" |
|
||||||
} |
|
||||||
|
|
||||||
required init(from decoder: Decoder) throws { |
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self) |
|
||||||
self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId() |
|
||||||
self.tournament = try container.decodeIfPresent(String.self, forKey: ._tournament) ?? "" |
|
||||||
self.timeDifferenceLimit = try container.decodeIfPresent(Int.self, forKey: ._timeDifferenceLimit) ?? 5 |
|
||||||
self.loserBracketRotationDifference = try container.decodeIfPresent(Int.self, forKey: ._loserBracketRotationDifference) ?? 0 |
|
||||||
self.upperBracketRotationDifference = try container.decodeIfPresent(Int.self, forKey: ._upperBracketRotationDifference) ?? 1 |
|
||||||
self.accountUpperBracketBreakTime = try container.decodeIfPresent(Bool.self, forKey: ._accountUpperBracketBreakTime) ?? true |
|
||||||
self.accountLoserBracketBreakTime = try container.decodeIfPresent(Bool.self, forKey: ._accountLoserBracketBreakTime) ?? false |
|
||||||
self.randomizeCourts = try container.decodeIfPresent(Bool.self, forKey: ._randomizeCourts) ?? true |
|
||||||
self.rotationDifferenceIsImportant = try container.decodeIfPresent(Bool.self, forKey: ._rotationDifferenceIsImportant) ?? false |
|
||||||
self.shouldHandleUpperRoundSlice = try container.decodeIfPresent(Bool.self, forKey: ._shouldHandleUpperRoundSlice) ?? false |
|
||||||
self.shouldEndRoundBeforeStartingNext = try container.decodeIfPresent(Bool.self, forKey: ._shouldEndRoundBeforeStartingNext) ?? true |
|
||||||
self.groupStageChunkCount = try container.decodeIfPresent(Int.self, forKey: ._groupStageChunkCount) ?? nil |
|
||||||
self.overrideCourtsUnavailability = try container.decodeIfPresent(Bool.self, forKey: ._overrideCourtsUnavailability) ?? false |
|
||||||
self.shouldTryToFillUpCourtsAvailable = try container.decodeIfPresent(Bool.self, forKey: ._shouldTryToFillUpCourtsAvailable) ?? false |
|
||||||
self.courtsAvailable = try container.decodeIfPresent(Set<Int>.self, forKey: ._courtsAvailable) ?? Set<Int>() |
|
||||||
self.simultaneousStart = try container.decodeIfPresent(Bool.self, forKey: ._simultaneousStart) ?? true |
|
||||||
try super.init(from: decoder) |
|
||||||
} |
|
||||||
|
|
||||||
override func encode(to encoder: Encoder) throws { |
|
||||||
var container = encoder.container(keyedBy: CodingKeys.self) |
|
||||||
try container.encode(self.id, forKey: ._id) |
|
||||||
try container.encode(self.tournament, forKey: ._tournament) |
|
||||||
try container.encode(self.timeDifferenceLimit, forKey: ._timeDifferenceLimit) |
|
||||||
try container.encode(self.loserBracketRotationDifference, forKey: ._loserBracketRotationDifference) |
|
||||||
try container.encode(self.upperBracketRotationDifference, forKey: ._upperBracketRotationDifference) |
|
||||||
try container.encode(self.accountUpperBracketBreakTime, forKey: ._accountUpperBracketBreakTime) |
|
||||||
try container.encode(self.accountLoserBracketBreakTime, forKey: ._accountLoserBracketBreakTime) |
|
||||||
try container.encode(self.randomizeCourts, forKey: ._randomizeCourts) |
|
||||||
try container.encode(self.rotationDifferenceIsImportant, forKey: ._rotationDifferenceIsImportant) |
|
||||||
try container.encode(self.shouldHandleUpperRoundSlice, forKey: ._shouldHandleUpperRoundSlice) |
|
||||||
try container.encode(self.shouldEndRoundBeforeStartingNext, forKey: ._shouldEndRoundBeforeStartingNext) |
|
||||||
try container.encode(self.groupStageChunkCount, forKey: ._groupStageChunkCount) |
|
||||||
try container.encode(self.overrideCourtsUnavailability, forKey: ._overrideCourtsUnavailability) |
|
||||||
try container.encode(self.shouldTryToFillUpCourtsAvailable, forKey: ._shouldTryToFillUpCourtsAvailable) |
|
||||||
try container.encode(self.courtsAvailable, forKey: ._courtsAvailable) |
|
||||||
try container.encode(self.simultaneousStart, forKey: ._simultaneousStart) |
|
||||||
try super.encode(to: encoder) |
|
||||||
} |
|
||||||
|
|
||||||
func tournamentValue() -> Tournament? { |
|
||||||
return Store.main.findById(tournament) |
|
||||||
} |
|
||||||
|
|
||||||
func copy(from other: any Storable) { |
|
||||||
guard let matchscheduler = other as? BaseMatchScheduler else { return } |
|
||||||
self.id = matchscheduler.id |
|
||||||
self.tournament = matchscheduler.tournament |
|
||||||
self.timeDifferenceLimit = matchscheduler.timeDifferenceLimit |
|
||||||
self.loserBracketRotationDifference = matchscheduler.loserBracketRotationDifference |
|
||||||
self.upperBracketRotationDifference = matchscheduler.upperBracketRotationDifference |
|
||||||
self.accountUpperBracketBreakTime = matchscheduler.accountUpperBracketBreakTime |
|
||||||
self.accountLoserBracketBreakTime = matchscheduler.accountLoserBracketBreakTime |
|
||||||
self.randomizeCourts = matchscheduler.randomizeCourts |
|
||||||
self.rotationDifferenceIsImportant = matchscheduler.rotationDifferenceIsImportant |
|
||||||
self.shouldHandleUpperRoundSlice = matchscheduler.shouldHandleUpperRoundSlice |
|
||||||
self.shouldEndRoundBeforeStartingNext = matchscheduler.shouldEndRoundBeforeStartingNext |
|
||||||
self.groupStageChunkCount = matchscheduler.groupStageChunkCount |
|
||||||
self.overrideCourtsUnavailability = matchscheduler.overrideCourtsUnavailability |
|
||||||
self.shouldTryToFillUpCourtsAvailable = matchscheduler.shouldTryToFillUpCourtsAvailable |
|
||||||
self.courtsAvailable = matchscheduler.courtsAvailable |
|
||||||
self.simultaneousStart = matchscheduler.simultaneousStart |
|
||||||
} |
|
||||||
|
|
||||||
static func relationships() -> [Relationship] { |
|
||||||
return [ |
|
||||||
Relationship(type: Tournament.self, keyPath: \BaseMatchScheduler.tournament), |
|
||||||
] |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,122 +0,0 @@ |
|||||||
// Generated by SwiftModelGenerator |
|
||||||
// Do not modify this file manually |
|
||||||
|
|
||||||
import Foundation |
|
||||||
import LeStorage |
|
||||||
import SwiftUI |
|
||||||
|
|
||||||
@Observable |
|
||||||
class BaseMonthData: BaseModelObject, Storable { |
|
||||||
|
|
||||||
static func resourceName() -> String { return "month-data" } |
|
||||||
static func tokenExemptedMethods() -> [HTTPMethod] { return [] } |
|
||||||
static var copyServerResponse: Bool = false |
|
||||||
|
|
||||||
var id: String = Store.randomId() |
|
||||||
var monthKey: String = "" |
|
||||||
var creationDate: Date = Date() |
|
||||||
var maleUnrankedValue: Int? = nil |
|
||||||
var femaleUnrankedValue: Int? = nil |
|
||||||
var maleCount: Int? = nil |
|
||||||
var femaleCount: Int? = nil |
|
||||||
var anonymousCount: Int? = nil |
|
||||||
var incompleteMode: Bool = false |
|
||||||
var dataModelIdentifier: String? = nil |
|
||||||
var fileModelIdentifier: String? = nil |
|
||||||
|
|
||||||
init( |
|
||||||
id: String = Store.randomId(), |
|
||||||
monthKey: String = "", |
|
||||||
creationDate: Date = Date(), |
|
||||||
maleUnrankedValue: Int? = nil, |
|
||||||
femaleUnrankedValue: Int? = nil, |
|
||||||
maleCount: Int? = nil, |
|
||||||
femaleCount: Int? = nil, |
|
||||||
anonymousCount: Int? = nil, |
|
||||||
incompleteMode: Bool = false, |
|
||||||
dataModelIdentifier: String? = nil, |
|
||||||
fileModelIdentifier: String? = nil |
|
||||||
) { |
|
||||||
super.init() |
|
||||||
self.id = id |
|
||||||
self.monthKey = monthKey |
|
||||||
self.creationDate = creationDate |
|
||||||
self.maleUnrankedValue = maleUnrankedValue |
|
||||||
self.femaleUnrankedValue = femaleUnrankedValue |
|
||||||
self.maleCount = maleCount |
|
||||||
self.femaleCount = femaleCount |
|
||||||
self.anonymousCount = anonymousCount |
|
||||||
self.incompleteMode = incompleteMode |
|
||||||
self.dataModelIdentifier = dataModelIdentifier |
|
||||||
self.fileModelIdentifier = fileModelIdentifier |
|
||||||
} |
|
||||||
required public override init() { |
|
||||||
super.init() |
|
||||||
} |
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey { |
|
||||||
case _id = "id" |
|
||||||
case _monthKey = "monthKey" |
|
||||||
case _creationDate = "creationDate" |
|
||||||
case _maleUnrankedValue = "maleUnrankedValue" |
|
||||||
case _femaleUnrankedValue = "femaleUnrankedValue" |
|
||||||
case _maleCount = "maleCount" |
|
||||||
case _femaleCount = "femaleCount" |
|
||||||
case _anonymousCount = "anonymousCount" |
|
||||||
case _incompleteMode = "incompleteMode" |
|
||||||
case _dataModelIdentifier = "dataModelIdentifier" |
|
||||||
case _fileModelIdentifier = "fileModelIdentifier" |
|
||||||
} |
|
||||||
|
|
||||||
required init(from decoder: Decoder) throws { |
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self) |
|
||||||
self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId() |
|
||||||
self.monthKey = try container.decodeIfPresent(String.self, forKey: ._monthKey) ?? "" |
|
||||||
self.creationDate = try container.decodeIfPresent(Date.self, forKey: ._creationDate) ?? Date() |
|
||||||
self.maleUnrankedValue = try container.decodeIfPresent(Int.self, forKey: ._maleUnrankedValue) ?? nil |
|
||||||
self.femaleUnrankedValue = try container.decodeIfPresent(Int.self, forKey: ._femaleUnrankedValue) ?? nil |
|
||||||
self.maleCount = try container.decodeIfPresent(Int.self, forKey: ._maleCount) ?? nil |
|
||||||
self.femaleCount = try container.decodeIfPresent(Int.self, forKey: ._femaleCount) ?? nil |
|
||||||
self.anonymousCount = try container.decodeIfPresent(Int.self, forKey: ._anonymousCount) ?? nil |
|
||||||
self.incompleteMode = try container.decodeIfPresent(Bool.self, forKey: ._incompleteMode) ?? false |
|
||||||
self.dataModelIdentifier = try container.decodeIfPresent(String.self, forKey: ._dataModelIdentifier) ?? nil |
|
||||||
self.fileModelIdentifier = try container.decodeIfPresent(String.self, forKey: ._fileModelIdentifier) ?? nil |
|
||||||
try super.init(from: decoder) |
|
||||||
} |
|
||||||
|
|
||||||
override func encode(to encoder: Encoder) throws { |
|
||||||
var container = encoder.container(keyedBy: CodingKeys.self) |
|
||||||
try container.encode(self.id, forKey: ._id) |
|
||||||
try container.encode(self.monthKey, forKey: ._monthKey) |
|
||||||
try container.encode(self.creationDate, forKey: ._creationDate) |
|
||||||
try container.encode(self.maleUnrankedValue, forKey: ._maleUnrankedValue) |
|
||||||
try container.encode(self.femaleUnrankedValue, forKey: ._femaleUnrankedValue) |
|
||||||
try container.encode(self.maleCount, forKey: ._maleCount) |
|
||||||
try container.encode(self.femaleCount, forKey: ._femaleCount) |
|
||||||
try container.encode(self.anonymousCount, forKey: ._anonymousCount) |
|
||||||
try container.encode(self.incompleteMode, forKey: ._incompleteMode) |
|
||||||
try container.encode(self.dataModelIdentifier, forKey: ._dataModelIdentifier) |
|
||||||
try container.encode(self.fileModelIdentifier, forKey: ._fileModelIdentifier) |
|
||||||
try super.encode(to: encoder) |
|
||||||
} |
|
||||||
|
|
||||||
func copy(from other: any Storable) { |
|
||||||
guard let monthdata = other as? BaseMonthData else { return } |
|
||||||
self.id = monthdata.id |
|
||||||
self.monthKey = monthdata.monthKey |
|
||||||
self.creationDate = monthdata.creationDate |
|
||||||
self.maleUnrankedValue = monthdata.maleUnrankedValue |
|
||||||
self.femaleUnrankedValue = monthdata.femaleUnrankedValue |
|
||||||
self.maleCount = monthdata.maleCount |
|
||||||
self.femaleCount = monthdata.femaleCount |
|
||||||
self.anonymousCount = monthdata.anonymousCount |
|
||||||
self.incompleteMode = monthdata.incompleteMode |
|
||||||
self.dataModelIdentifier = monthdata.dataModelIdentifier |
|
||||||
self.fileModelIdentifier = monthdata.fileModelIdentifier |
|
||||||
} |
|
||||||
|
|
||||||
static func relationships() -> [Relationship] { |
|
||||||
return [] |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,227 +0,0 @@ |
|||||||
// Generated by SwiftModelGenerator |
|
||||||
// Do not modify this file manually |
|
||||||
|
|
||||||
import Foundation |
|
||||||
import LeStorage |
|
||||||
import SwiftUI |
|
||||||
|
|
||||||
@Observable |
|
||||||
class BasePlayerRegistration: SyncedModelObject, SyncedStorable { |
|
||||||
|
|
||||||
static func resourceName() -> String { return "player-registrations" } |
|
||||||
static func tokenExemptedMethods() -> [HTTPMethod] { return [] } |
|
||||||
static var copyServerResponse: Bool = false |
|
||||||
|
|
||||||
var id: String = Store.randomId() |
|
||||||
var teamRegistration: String? = nil |
|
||||||
var firstName: String = "" |
|
||||||
var lastName: String = "" |
|
||||||
var licenceId: String? = nil |
|
||||||
var rank: Int? = nil |
|
||||||
var paymentType: PlayerPaymentType? = nil |
|
||||||
var sex: PlayerSexType? = nil |
|
||||||
var tournamentPlayed: Int? = nil |
|
||||||
var points: Double? = nil |
|
||||||
var clubName: String? = nil |
|
||||||
var ligueName: String? = nil |
|
||||||
var assimilation: String? = nil |
|
||||||
var phoneNumber: String? = nil |
|
||||||
var email: String? = nil |
|
||||||
var birthdate: String? = nil |
|
||||||
var computedRank: Int = 0 |
|
||||||
var source: PlayerRegistration.PlayerDataSource? = nil |
|
||||||
var hasArrived: Bool = false |
|
||||||
var coach: Bool = false |
|
||||||
var captain: Bool = false |
|
||||||
var registeredOnline: Bool = false |
|
||||||
var timeToConfirm: Date? = nil |
|
||||||
var registrationStatus: PlayerRegistration.RegistrationStatus = PlayerRegistration.RegistrationStatus.waiting |
|
||||||
var paymentId: String? = nil |
|
||||||
|
|
||||||
init( |
|
||||||
id: String = Store.randomId(), |
|
||||||
teamRegistration: String? = nil, |
|
||||||
firstName: String = "", |
|
||||||
lastName: String = "", |
|
||||||
licenceId: String? = nil, |
|
||||||
rank: Int? = nil, |
|
||||||
paymentType: PlayerPaymentType? = nil, |
|
||||||
sex: PlayerSexType? = nil, |
|
||||||
tournamentPlayed: Int? = nil, |
|
||||||
points: Double? = nil, |
|
||||||
clubName: String? = nil, |
|
||||||
ligueName: String? = nil, |
|
||||||
assimilation: String? = nil, |
|
||||||
phoneNumber: String? = nil, |
|
||||||
email: String? = nil, |
|
||||||
birthdate: String? = nil, |
|
||||||
computedRank: Int = 0, |
|
||||||
source: PlayerRegistration.PlayerDataSource? = nil, |
|
||||||
hasArrived: Bool = false, |
|
||||||
coach: Bool = false, |
|
||||||
captain: Bool = false, |
|
||||||
registeredOnline: Bool = false, |
|
||||||
timeToConfirm: Date? = nil, |
|
||||||
registrationStatus: PlayerRegistration.RegistrationStatus = PlayerRegistration.RegistrationStatus.waiting, |
|
||||||
paymentId: String? = nil |
|
||||||
) { |
|
||||||
super.init() |
|
||||||
self.id = id |
|
||||||
self.teamRegistration = teamRegistration |
|
||||||
self.firstName = firstName |
|
||||||
self.lastName = lastName |
|
||||||
self.licenceId = licenceId |
|
||||||
self.rank = rank |
|
||||||
self.paymentType = paymentType |
|
||||||
self.sex = sex |
|
||||||
self.tournamentPlayed = tournamentPlayed |
|
||||||
self.points = points |
|
||||||
self.clubName = clubName |
|
||||||
self.ligueName = ligueName |
|
||||||
self.assimilation = assimilation |
|
||||||
self.phoneNumber = phoneNumber |
|
||||||
self.email = email |
|
||||||
self.birthdate = birthdate |
|
||||||
self.computedRank = computedRank |
|
||||||
self.source = source |
|
||||||
self.hasArrived = hasArrived |
|
||||||
self.coach = coach |
|
||||||
self.captain = captain |
|
||||||
self.registeredOnline = registeredOnline |
|
||||||
self.timeToConfirm = timeToConfirm |
|
||||||
self.registrationStatus = registrationStatus |
|
||||||
self.paymentId = paymentId |
|
||||||
} |
|
||||||
required public override init() { |
|
||||||
super.init() |
|
||||||
} |
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey { |
|
||||||
case _id = "id" |
|
||||||
case _teamRegistration = "teamRegistration" |
|
||||||
case _firstName = "firstName" |
|
||||||
case _lastName = "lastName" |
|
||||||
case _licenceId = "licenceId" |
|
||||||
case _rank = "rank" |
|
||||||
case _paymentType = "paymentType" |
|
||||||
case _sex = "sex" |
|
||||||
case _tournamentPlayed = "tournamentPlayed" |
|
||||||
case _points = "points" |
|
||||||
case _clubName = "clubName" |
|
||||||
case _ligueName = "ligueName" |
|
||||||
case _assimilation = "assimilation" |
|
||||||
case _phoneNumber = "phoneNumber" |
|
||||||
case _email = "email" |
|
||||||
case _birthdate = "birthdate" |
|
||||||
case _computedRank = "computedRank" |
|
||||||
case _source = "source" |
|
||||||
case _hasArrived = "hasArrived" |
|
||||||
case _coach = "coach" |
|
||||||
case _captain = "captain" |
|
||||||
case _registeredOnline = "registeredOnline" |
|
||||||
case _timeToConfirm = "timeToConfirm" |
|
||||||
case _registrationStatus = "registrationStatus" |
|
||||||
case _paymentId = "paymentId" |
|
||||||
} |
|
||||||
|
|
||||||
required init(from decoder: Decoder) throws { |
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self) |
|
||||||
self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId() |
|
||||||
self.teamRegistration = try container.decodeIfPresent(String.self, forKey: ._teamRegistration) ?? nil |
|
||||||
self.firstName = try container.decodeIfPresent(String.self, forKey: ._firstName) ?? "" |
|
||||||
self.lastName = try container.decodeIfPresent(String.self, forKey: ._lastName) ?? "" |
|
||||||
self.licenceId = try container.decodeIfPresent(String.self, forKey: ._licenceId) ?? nil |
|
||||||
self.rank = try container.decodeIfPresent(Int.self, forKey: ._rank) ?? nil |
|
||||||
self.paymentType = try container.decodeIfPresent(PlayerPaymentType.self, forKey: ._paymentType) ?? nil |
|
||||||
self.sex = try container.decodeIfPresent(PlayerSexType.self, forKey: ._sex) ?? nil |
|
||||||
self.tournamentPlayed = try container.decodeIfPresent(Int.self, forKey: ._tournamentPlayed) ?? nil |
|
||||||
self.points = try container.decodeIfPresent(Double.self, forKey: ._points) ?? nil |
|
||||||
self.clubName = try container.decodeIfPresent(String.self, forKey: ._clubName) ?? nil |
|
||||||
self.ligueName = try container.decodeIfPresent(String.self, forKey: ._ligueName) ?? nil |
|
||||||
self.assimilation = try container.decodeIfPresent(String.self, forKey: ._assimilation) ?? nil |
|
||||||
self.phoneNumber = try container.decodeIfPresent(String.self, forKey: ._phoneNumber) ?? nil |
|
||||||
self.email = try container.decodeIfPresent(String.self, forKey: ._email) ?? nil |
|
||||||
self.birthdate = try container.decodeIfPresent(String.self, forKey: ._birthdate) ?? nil |
|
||||||
self.computedRank = try container.decodeIfPresent(Int.self, forKey: ._computedRank) ?? 0 |
|
||||||
self.source = try container.decodeIfPresent(PlayerRegistration.PlayerDataSource.self, forKey: ._source) ?? nil |
|
||||||
self.hasArrived = try container.decodeIfPresent(Bool.self, forKey: ._hasArrived) ?? false |
|
||||||
self.coach = try container.decodeIfPresent(Bool.self, forKey: ._coach) ?? false |
|
||||||
self.captain = try container.decodeIfPresent(Bool.self, forKey: ._captain) ?? false |
|
||||||
self.registeredOnline = try container.decodeIfPresent(Bool.self, forKey: ._registeredOnline) ?? false |
|
||||||
self.timeToConfirm = try container.decodeIfPresent(Date.self, forKey: ._timeToConfirm) ?? nil |
|
||||||
self.registrationStatus = try container.decodeIfPresent(PlayerRegistration.RegistrationStatus.self, forKey: ._registrationStatus) ?? PlayerRegistration.RegistrationStatus.waiting |
|
||||||
self.paymentId = try container.decodeIfPresent(String.self, forKey: ._paymentId) ?? nil |
|
||||||
try super.init(from: decoder) |
|
||||||
} |
|
||||||
|
|
||||||
override func encode(to encoder: Encoder) throws { |
|
||||||
var container = encoder.container(keyedBy: CodingKeys.self) |
|
||||||
try container.encode(self.id, forKey: ._id) |
|
||||||
try container.encode(self.teamRegistration, forKey: ._teamRegistration) |
|
||||||
try container.encode(self.firstName, forKey: ._firstName) |
|
||||||
try container.encode(self.lastName, forKey: ._lastName) |
|
||||||
try container.encode(self.licenceId, forKey: ._licenceId) |
|
||||||
try container.encode(self.rank, forKey: ._rank) |
|
||||||
try container.encode(self.paymentType, forKey: ._paymentType) |
|
||||||
try container.encode(self.sex, forKey: ._sex) |
|
||||||
try container.encode(self.tournamentPlayed, forKey: ._tournamentPlayed) |
|
||||||
try container.encode(self.points, forKey: ._points) |
|
||||||
try container.encode(self.clubName, forKey: ._clubName) |
|
||||||
try container.encode(self.ligueName, forKey: ._ligueName) |
|
||||||
try container.encode(self.assimilation, forKey: ._assimilation) |
|
||||||
try container.encode(self.phoneNumber, forKey: ._phoneNumber) |
|
||||||
try container.encode(self.email, forKey: ._email) |
|
||||||
try container.encode(self.birthdate, forKey: ._birthdate) |
|
||||||
try container.encode(self.computedRank, forKey: ._computedRank) |
|
||||||
try container.encode(self.source, forKey: ._source) |
|
||||||
try container.encode(self.hasArrived, forKey: ._hasArrived) |
|
||||||
try container.encode(self.coach, forKey: ._coach) |
|
||||||
try container.encode(self.captain, forKey: ._captain) |
|
||||||
try container.encode(self.registeredOnline, forKey: ._registeredOnline) |
|
||||||
try container.encode(self.timeToConfirm, forKey: ._timeToConfirm) |
|
||||||
try container.encode(self.registrationStatus, forKey: ._registrationStatus) |
|
||||||
try container.encode(self.paymentId, forKey: ._paymentId) |
|
||||||
try super.encode(to: encoder) |
|
||||||
} |
|
||||||
|
|
||||||
func teamRegistrationValue() -> TeamRegistration? { |
|
||||||
guard let teamRegistration = self.teamRegistration else { return nil } |
|
||||||
return Store.main.findById(teamRegistration) |
|
||||||
} |
|
||||||
|
|
||||||
func copy(from other: any Storable) { |
|
||||||
guard let playerregistration = other as? BasePlayerRegistration else { return } |
|
||||||
self.id = playerregistration.id |
|
||||||
self.teamRegistration = playerregistration.teamRegistration |
|
||||||
self.firstName = playerregistration.firstName |
|
||||||
self.lastName = playerregistration.lastName |
|
||||||
self.licenceId = playerregistration.licenceId |
|
||||||
self.rank = playerregistration.rank |
|
||||||
self.paymentType = playerregistration.paymentType |
|
||||||
self.sex = playerregistration.sex |
|
||||||
self.tournamentPlayed = playerregistration.tournamentPlayed |
|
||||||
self.points = playerregistration.points |
|
||||||
self.clubName = playerregistration.clubName |
|
||||||
self.ligueName = playerregistration.ligueName |
|
||||||
self.assimilation = playerregistration.assimilation |
|
||||||
self.phoneNumber = playerregistration.phoneNumber |
|
||||||
self.email = playerregistration.email |
|
||||||
self.birthdate = playerregistration.birthdate |
|
||||||
self.computedRank = playerregistration.computedRank |
|
||||||
self.source = playerregistration.source |
|
||||||
self.hasArrived = playerregistration.hasArrived |
|
||||||
self.coach = playerregistration.coach |
|
||||||
self.captain = playerregistration.captain |
|
||||||
self.registeredOnline = playerregistration.registeredOnline |
|
||||||
self.timeToConfirm = playerregistration.timeToConfirm |
|
||||||
self.registrationStatus = playerregistration.registrationStatus |
|
||||||
self.paymentId = playerregistration.paymentId |
|
||||||
} |
|
||||||
|
|
||||||
static func relationships() -> [Relationship] { |
|
||||||
return [ |
|
||||||
Relationship(type: TeamRegistration.self, keyPath: \BasePlayerRegistration.teamRegistration), |
|
||||||
] |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,99 +0,0 @@ |
|||||||
// Generated by SwiftModelGenerator |
|
||||||
// Do not modify this file manually |
|
||||||
|
|
||||||
import Foundation |
|
||||||
import LeStorage |
|
||||||
|
|
||||||
class BasePurchase: SyncedModelObject, SyncedStorable { |
|
||||||
|
|
||||||
static func resourceName() -> String { return "purchases" } |
|
||||||
static func tokenExemptedMethods() -> [HTTPMethod] { return [] } |
|
||||||
static var copyServerResponse: Bool = false |
|
||||||
|
|
||||||
var id: UInt64 = 0 |
|
||||||
var user: String = "" |
|
||||||
var purchaseDate: Date = Date() |
|
||||||
var productId: String = "" |
|
||||||
var quantity: Int? = nil |
|
||||||
var revocationDate: Date? = nil |
|
||||||
var expirationDate: Date? = nil |
|
||||||
|
|
||||||
init( |
|
||||||
id: UInt64 = 0, |
|
||||||
user: String = "", |
|
||||||
purchaseDate: Date = Date(), |
|
||||||
productId: String = "", |
|
||||||
quantity: Int? = nil, |
|
||||||
revocationDate: Date? = nil, |
|
||||||
expirationDate: Date? = nil |
|
||||||
) { |
|
||||||
super.init() |
|
||||||
self.id = id |
|
||||||
self.user = user |
|
||||||
self.purchaseDate = purchaseDate |
|
||||||
self.productId = productId |
|
||||||
self.quantity = quantity |
|
||||||
self.revocationDate = revocationDate |
|
||||||
self.expirationDate = expirationDate |
|
||||||
} |
|
||||||
required public override init() { |
|
||||||
super.init() |
|
||||||
} |
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey { |
|
||||||
case id = "id" |
|
||||||
case user = "user" |
|
||||||
case purchaseDate = "purchaseDate" |
|
||||||
case productId = "productId" |
|
||||||
case quantity = "quantity" |
|
||||||
case revocationDate = "revocationDate" |
|
||||||
case expirationDate = "expirationDate" |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
required init(from decoder: Decoder) throws { |
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self) |
|
||||||
self.id = try container.decodeIfPresent(UInt64.self, forKey: .id) ?? 0 |
|
||||||
self.user = try container.decodeEncrypted(key: .user) |
|
||||||
self.purchaseDate = try container.decodeIfPresent(Date.self, forKey: .purchaseDate) ?? Date() |
|
||||||
self.productId = try container.decodeIfPresent(String.self, forKey: .productId) ?? "" |
|
||||||
self.quantity = try container.decodeIfPresent(Int.self, forKey: .quantity) ?? nil |
|
||||||
self.revocationDate = try container.decodeIfPresent(Date.self, forKey: .revocationDate) ?? nil |
|
||||||
self.expirationDate = try container.decodeIfPresent(Date.self, forKey: .expirationDate) ?? nil |
|
||||||
try super.init(from: decoder) |
|
||||||
} |
|
||||||
|
|
||||||
override func encode(to encoder: Encoder) throws { |
|
||||||
var container = encoder.container(keyedBy: CodingKeys.self) |
|
||||||
try container.encode(self.id, forKey: .id) |
|
||||||
try container.encodeAndEncryptIfPresent(self.user.data(using: .utf8), forKey: .user) |
|
||||||
try container.encode(self.purchaseDate, forKey: .purchaseDate) |
|
||||||
try container.encode(self.productId, forKey: .productId) |
|
||||||
try container.encode(self.quantity, forKey: .quantity) |
|
||||||
try container.encode(self.revocationDate, forKey: .revocationDate) |
|
||||||
try container.encode(self.expirationDate, forKey: .expirationDate) |
|
||||||
try super.encode(to: encoder) |
|
||||||
} |
|
||||||
|
|
||||||
func userValue() -> CustomUser? { |
|
||||||
return Store.main.findById(user) |
|
||||||
} |
|
||||||
|
|
||||||
func copy(from other: any Storable) { |
|
||||||
guard let purchase = other as? BasePurchase else { return } |
|
||||||
self.id = purchase.id |
|
||||||
self.user = purchase.user |
|
||||||
self.purchaseDate = purchase.purchaseDate |
|
||||||
self.productId = purchase.productId |
|
||||||
self.quantity = purchase.quantity |
|
||||||
self.revocationDate = purchase.revocationDate |
|
||||||
self.expirationDate = purchase.expirationDate |
|
||||||
} |
|
||||||
|
|
||||||
static func relationships() -> [Relationship] { |
|
||||||
return [ |
|
||||||
Relationship(type: CustomUser.self, keyPath: \BasePurchase.user), |
|
||||||
] |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,107 +0,0 @@ |
|||||||
// Generated by SwiftModelGenerator |
|
||||||
// Do not modify this file manually |
|
||||||
|
|
||||||
import Foundation |
|
||||||
import LeStorage |
|
||||||
import SwiftUI |
|
||||||
|
|
||||||
@Observable |
|
||||||
class BaseRound: SyncedModelObject, SyncedStorable { |
|
||||||
|
|
||||||
static func resourceName() -> String { return "rounds" } |
|
||||||
static func tokenExemptedMethods() -> [HTTPMethod] { return [] } |
|
||||||
static var copyServerResponse: Bool = false |
|
||||||
|
|
||||||
var id: String = Store.randomId() |
|
||||||
var tournament: String = "" |
|
||||||
var index: Int = 0 |
|
||||||
var parent: String? = nil |
|
||||||
var format: MatchFormat? = nil |
|
||||||
var startDate: Date? = nil |
|
||||||
var groupStageLoserBracket: Bool = false |
|
||||||
var loserBracketMode: LoserBracketMode = .automatic |
|
||||||
|
|
||||||
init( |
|
||||||
id: String = Store.randomId(), |
|
||||||
tournament: String = "", |
|
||||||
index: Int = 0, |
|
||||||
parent: String? = nil, |
|
||||||
format: MatchFormat? = nil, |
|
||||||
startDate: Date? = nil, |
|
||||||
groupStageLoserBracket: Bool = false, |
|
||||||
loserBracketMode: LoserBracketMode = .automatic |
|
||||||
) { |
|
||||||
super.init() |
|
||||||
self.id = id |
|
||||||
self.tournament = tournament |
|
||||||
self.index = index |
|
||||||
self.parent = parent |
|
||||||
self.format = format |
|
||||||
self.startDate = startDate |
|
||||||
self.groupStageLoserBracket = groupStageLoserBracket |
|
||||||
self.loserBracketMode = loserBracketMode |
|
||||||
} |
|
||||||
required public override init() { |
|
||||||
super.init() |
|
||||||
} |
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey { |
|
||||||
case _id = "id" |
|
||||||
case _tournament = "tournament" |
|
||||||
case _index = "index" |
|
||||||
case _parent = "parent" |
|
||||||
case _format = "format" |
|
||||||
case _startDate = "startDate" |
|
||||||
case _groupStageLoserBracket = "groupStageLoserBracket" |
|
||||||
case _loserBracketMode = "loserBracketMode" |
|
||||||
} |
|
||||||
|
|
||||||
required init(from decoder: Decoder) throws { |
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self) |
|
||||||
self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId() |
|
||||||
self.tournament = try container.decodeIfPresent(String.self, forKey: ._tournament) ?? "" |
|
||||||
self.index = try container.decodeIfPresent(Int.self, forKey: ._index) ?? 0 |
|
||||||
self.parent = try container.decodeIfPresent(String.self, forKey: ._parent) ?? nil |
|
||||||
self.format = try container.decodeIfPresent(MatchFormat.self, forKey: ._format) ?? nil |
|
||||||
self.startDate = try container.decodeIfPresent(Date.self, forKey: ._startDate) ?? nil |
|
||||||
self.groupStageLoserBracket = try container.decodeIfPresent(Bool.self, forKey: ._groupStageLoserBracket) ?? false |
|
||||||
self.loserBracketMode = try container.decodeIfPresent(LoserBracketMode.self, forKey: ._loserBracketMode) ?? .automatic |
|
||||||
try super.init(from: decoder) |
|
||||||
} |
|
||||||
|
|
||||||
override func encode(to encoder: Encoder) throws { |
|
||||||
var container = encoder.container(keyedBy: CodingKeys.self) |
|
||||||
try container.encode(self.id, forKey: ._id) |
|
||||||
try container.encode(self.tournament, forKey: ._tournament) |
|
||||||
try container.encode(self.index, forKey: ._index) |
|
||||||
try container.encode(self.parent, forKey: ._parent) |
|
||||||
try container.encode(self.format, forKey: ._format) |
|
||||||
try container.encode(self.startDate, forKey: ._startDate) |
|
||||||
try container.encode(self.groupStageLoserBracket, forKey: ._groupStageLoserBracket) |
|
||||||
try container.encode(self.loserBracketMode, forKey: ._loserBracketMode) |
|
||||||
try super.encode(to: encoder) |
|
||||||
} |
|
||||||
|
|
||||||
func tournamentValue() -> Tournament? { |
|
||||||
return Store.main.findById(tournament) |
|
||||||
} |
|
||||||
|
|
||||||
func copy(from other: any Storable) { |
|
||||||
guard let round = other as? BaseRound else { return } |
|
||||||
self.id = round.id |
|
||||||
self.tournament = round.tournament |
|
||||||
self.index = round.index |
|
||||||
self.parent = round.parent |
|
||||||
self.format = round.format |
|
||||||
self.startDate = round.startDate |
|
||||||
self.groupStageLoserBracket = round.groupStageLoserBracket |
|
||||||
self.loserBracketMode = round.loserBracketMode |
|
||||||
} |
|
||||||
|
|
||||||
static func relationships() -> [Relationship] { |
|
||||||
return [ |
|
||||||
Relationship(type: Tournament.self, keyPath: \BaseRound.tournament), |
|
||||||
] |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,199 +0,0 @@ |
|||||||
// Generated by SwiftModelGenerator |
|
||||||
// Do not modify this file manually |
|
||||||
|
|
||||||
import Foundation |
|
||||||
import LeStorage |
|
||||||
import SwiftUI |
|
||||||
|
|
||||||
@Observable |
|
||||||
class BaseTeamRegistration: SyncedModelObject, SyncedStorable { |
|
||||||
|
|
||||||
static func resourceName() -> String { return "team-registrations" } |
|
||||||
static func tokenExemptedMethods() -> [HTTPMethod] { return [] } |
|
||||||
static var copyServerResponse: Bool = false |
|
||||||
|
|
||||||
var id: String = Store.randomId() |
|
||||||
var tournament: String = "" |
|
||||||
var groupStage: String? = nil |
|
||||||
var registrationDate: Date? = nil |
|
||||||
var callDate: Date? = nil |
|
||||||
var bracketPosition: Int? = nil |
|
||||||
var groupStagePosition: Int? = nil |
|
||||||
var comment: String? = nil |
|
||||||
var source: String? = nil |
|
||||||
var sourceValue: String? = nil |
|
||||||
var logo: String? = nil |
|
||||||
var name: String? = nil |
|
||||||
var walkOut: Bool = false |
|
||||||
var wildCardBracket: Bool = false |
|
||||||
var wildCardGroupStage: Bool = false |
|
||||||
var weight: Int = 0 |
|
||||||
var lockedWeight: Int? = nil |
|
||||||
var confirmationDate: Date? = nil |
|
||||||
var qualified: Bool = false |
|
||||||
var finalRanking: Int? = nil |
|
||||||
var pointsEarned: Int? = nil |
|
||||||
|
|
||||||
init( |
|
||||||
id: String = Store.randomId(), |
|
||||||
tournament: String = "", |
|
||||||
groupStage: String? = nil, |
|
||||||
registrationDate: Date? = nil, |
|
||||||
callDate: Date? = nil, |
|
||||||
bracketPosition: Int? = nil, |
|
||||||
groupStagePosition: Int? = nil, |
|
||||||
comment: String? = nil, |
|
||||||
source: String? = nil, |
|
||||||
sourceValue: String? = nil, |
|
||||||
logo: String? = nil, |
|
||||||
name: String? = nil, |
|
||||||
walkOut: Bool = false, |
|
||||||
wildCardBracket: Bool = false, |
|
||||||
wildCardGroupStage: Bool = false, |
|
||||||
weight: Int = 0, |
|
||||||
lockedWeight: Int? = nil, |
|
||||||
confirmationDate: Date? = nil, |
|
||||||
qualified: Bool = false, |
|
||||||
finalRanking: Int? = nil, |
|
||||||
pointsEarned: Int? = nil |
|
||||||
) { |
|
||||||
super.init() |
|
||||||
self.id = id |
|
||||||
self.tournament = tournament |
|
||||||
self.groupStage = groupStage |
|
||||||
self.registrationDate = registrationDate |
|
||||||
self.callDate = callDate |
|
||||||
self.bracketPosition = bracketPosition |
|
||||||
self.groupStagePosition = groupStagePosition |
|
||||||
self.comment = comment |
|
||||||
self.source = source |
|
||||||
self.sourceValue = sourceValue |
|
||||||
self.logo = logo |
|
||||||
self.name = name |
|
||||||
self.walkOut = walkOut |
|
||||||
self.wildCardBracket = wildCardBracket |
|
||||||
self.wildCardGroupStage = wildCardGroupStage |
|
||||||
self.weight = weight |
|
||||||
self.lockedWeight = lockedWeight |
|
||||||
self.confirmationDate = confirmationDate |
|
||||||
self.qualified = qualified |
|
||||||
self.finalRanking = finalRanking |
|
||||||
self.pointsEarned = pointsEarned |
|
||||||
} |
|
||||||
required public override init() { |
|
||||||
super.init() |
|
||||||
} |
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey { |
|
||||||
case _id = "id" |
|
||||||
case _tournament = "tournament" |
|
||||||
case _groupStage = "groupStage" |
|
||||||
case _registrationDate = "registrationDate" |
|
||||||
case _callDate = "callDate" |
|
||||||
case _bracketPosition = "bracketPosition" |
|
||||||
case _groupStagePosition = "groupStagePosition" |
|
||||||
case _comment = "comment" |
|
||||||
case _source = "source" |
|
||||||
case _sourceValue = "sourceValue" |
|
||||||
case _logo = "logo" |
|
||||||
case _name = "name" |
|
||||||
case _walkOut = "walkOut" |
|
||||||
case _wildCardBracket = "wildCardBracket" |
|
||||||
case _wildCardGroupStage = "wildCardGroupStage" |
|
||||||
case _weight = "weight" |
|
||||||
case _lockedWeight = "lockedWeight" |
|
||||||
case _confirmationDate = "confirmationDate" |
|
||||||
case _qualified = "qualified" |
|
||||||
case _finalRanking = "finalRanking" |
|
||||||
case _pointsEarned = "pointsEarned" |
|
||||||
} |
|
||||||
|
|
||||||
required init(from decoder: Decoder) throws { |
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self) |
|
||||||
self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId() |
|
||||||
self.tournament = try container.decodeIfPresent(String.self, forKey: ._tournament) ?? "" |
|
||||||
self.groupStage = try container.decodeIfPresent(String.self, forKey: ._groupStage) ?? nil |
|
||||||
self.registrationDate = try container.decodeIfPresent(Date.self, forKey: ._registrationDate) ?? nil |
|
||||||
self.callDate = try container.decodeIfPresent(Date.self, forKey: ._callDate) ?? nil |
|
||||||
self.bracketPosition = try container.decodeIfPresent(Int.self, forKey: ._bracketPosition) ?? nil |
|
||||||
self.groupStagePosition = try container.decodeIfPresent(Int.self, forKey: ._groupStagePosition) ?? nil |
|
||||||
self.comment = try container.decodeIfPresent(String.self, forKey: ._comment) ?? nil |
|
||||||
self.source = try container.decodeIfPresent(String.self, forKey: ._source) ?? nil |
|
||||||
self.sourceValue = try container.decodeIfPresent(String.self, forKey: ._sourceValue) ?? nil |
|
||||||
self.logo = try container.decodeIfPresent(String.self, forKey: ._logo) ?? nil |
|
||||||
self.name = try container.decodeIfPresent(String.self, forKey: ._name) ?? nil |
|
||||||
self.walkOut = try container.decodeIfPresent(Bool.self, forKey: ._walkOut) ?? false |
|
||||||
self.wildCardBracket = try container.decodeIfPresent(Bool.self, forKey: ._wildCardBracket) ?? false |
|
||||||
self.wildCardGroupStage = try container.decodeIfPresent(Bool.self, forKey: ._wildCardGroupStage) ?? false |
|
||||||
self.weight = try container.decodeIfPresent(Int.self, forKey: ._weight) ?? 0 |
|
||||||
self.lockedWeight = try container.decodeIfPresent(Int.self, forKey: ._lockedWeight) ?? nil |
|
||||||
self.confirmationDate = try container.decodeIfPresent(Date.self, forKey: ._confirmationDate) ?? nil |
|
||||||
self.qualified = try container.decodeIfPresent(Bool.self, forKey: ._qualified) ?? false |
|
||||||
self.finalRanking = try container.decodeIfPresent(Int.self, forKey: ._finalRanking) ?? nil |
|
||||||
self.pointsEarned = try container.decodeIfPresent(Int.self, forKey: ._pointsEarned) ?? nil |
|
||||||
try super.init(from: decoder) |
|
||||||
} |
|
||||||
|
|
||||||
override func encode(to encoder: Encoder) throws { |
|
||||||
var container = encoder.container(keyedBy: CodingKeys.self) |
|
||||||
try container.encode(self.id, forKey: ._id) |
|
||||||
try container.encode(self.tournament, forKey: ._tournament) |
|
||||||
try container.encode(self.groupStage, forKey: ._groupStage) |
|
||||||
try container.encode(self.registrationDate, forKey: ._registrationDate) |
|
||||||
try container.encode(self.callDate, forKey: ._callDate) |
|
||||||
try container.encode(self.bracketPosition, forKey: ._bracketPosition) |
|
||||||
try container.encode(self.groupStagePosition, forKey: ._groupStagePosition) |
|
||||||
try container.encode(self.comment, forKey: ._comment) |
|
||||||
try container.encode(self.source, forKey: ._source) |
|
||||||
try container.encode(self.sourceValue, forKey: ._sourceValue) |
|
||||||
try container.encode(self.logo, forKey: ._logo) |
|
||||||
try container.encode(self.name, forKey: ._name) |
|
||||||
try container.encode(self.walkOut, forKey: ._walkOut) |
|
||||||
try container.encode(self.wildCardBracket, forKey: ._wildCardBracket) |
|
||||||
try container.encode(self.wildCardGroupStage, forKey: ._wildCardGroupStage) |
|
||||||
try container.encode(self.weight, forKey: ._weight) |
|
||||||
try container.encode(self.lockedWeight, forKey: ._lockedWeight) |
|
||||||
try container.encode(self.confirmationDate, forKey: ._confirmationDate) |
|
||||||
try container.encode(self.qualified, forKey: ._qualified) |
|
||||||
try container.encode(self.finalRanking, forKey: ._finalRanking) |
|
||||||
try container.encode(self.pointsEarned, forKey: ._pointsEarned) |
|
||||||
try super.encode(to: encoder) |
|
||||||
} |
|
||||||
|
|
||||||
func groupStageValue() -> GroupStage? { |
|
||||||
guard let groupStage = self.groupStage else { return nil } |
|
||||||
return self.store?.findById(groupStage) |
|
||||||
} |
|
||||||
|
|
||||||
func copy(from other: any Storable) { |
|
||||||
guard let teamregistration = other as? BaseTeamRegistration else { return } |
|
||||||
self.id = teamregistration.id |
|
||||||
self.tournament = teamregistration.tournament |
|
||||||
self.groupStage = teamregistration.groupStage |
|
||||||
self.registrationDate = teamregistration.registrationDate |
|
||||||
self.callDate = teamregistration.callDate |
|
||||||
self.bracketPosition = teamregistration.bracketPosition |
|
||||||
self.groupStagePosition = teamregistration.groupStagePosition |
|
||||||
self.comment = teamregistration.comment |
|
||||||
self.source = teamregistration.source |
|
||||||
self.sourceValue = teamregistration.sourceValue |
|
||||||
self.logo = teamregistration.logo |
|
||||||
self.name = teamregistration.name |
|
||||||
self.walkOut = teamregistration.walkOut |
|
||||||
self.wildCardBracket = teamregistration.wildCardBracket |
|
||||||
self.wildCardGroupStage = teamregistration.wildCardGroupStage |
|
||||||
self.weight = teamregistration.weight |
|
||||||
self.lockedWeight = teamregistration.lockedWeight |
|
||||||
self.confirmationDate = teamregistration.confirmationDate |
|
||||||
self.qualified = teamregistration.qualified |
|
||||||
self.finalRanking = teamregistration.finalRanking |
|
||||||
self.pointsEarned = teamregistration.pointsEarned |
|
||||||
} |
|
||||||
|
|
||||||
static func relationships() -> [Relationship] { |
|
||||||
return [ |
|
||||||
Relationship(type: GroupStage.self, keyPath: \BaseTeamRegistration.groupStage), |
|
||||||
] |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,99 +0,0 @@ |
|||||||
// Generated by SwiftModelGenerator |
|
||||||
// Do not modify this file manually |
|
||||||
|
|
||||||
import Foundation |
|
||||||
import LeStorage |
|
||||||
import SwiftUI |
|
||||||
|
|
||||||
@Observable |
|
||||||
class BaseTeamScore: SyncedModelObject, SyncedStorable { |
|
||||||
|
|
||||||
static func resourceName() -> String { return "team-scores" } |
|
||||||
static func tokenExemptedMethods() -> [HTTPMethod] { return [] } |
|
||||||
static var copyServerResponse: Bool = false |
|
||||||
|
|
||||||
var id: String = Store.randomId() |
|
||||||
var match: String = "" |
|
||||||
var teamRegistration: String? = nil |
|
||||||
var score: String? = nil |
|
||||||
var walkOut: Int? = nil |
|
||||||
var luckyLoser: Int? = nil |
|
||||||
|
|
||||||
init( |
|
||||||
id: String = Store.randomId(), |
|
||||||
match: String = "", |
|
||||||
teamRegistration: String? = nil, |
|
||||||
score: String? = nil, |
|
||||||
walkOut: Int? = nil, |
|
||||||
luckyLoser: Int? = nil |
|
||||||
) { |
|
||||||
super.init() |
|
||||||
self.id = id |
|
||||||
self.match = match |
|
||||||
self.teamRegistration = teamRegistration |
|
||||||
self.score = score |
|
||||||
self.walkOut = walkOut |
|
||||||
self.luckyLoser = luckyLoser |
|
||||||
} |
|
||||||
required public override init() { |
|
||||||
super.init() |
|
||||||
} |
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey { |
|
||||||
case _id = "id" |
|
||||||
case _match = "match" |
|
||||||
case _teamRegistration = "teamRegistration" |
|
||||||
case _score = "score" |
|
||||||
case _walkOut = "walkOut" |
|
||||||
case _luckyLoser = "luckyLoser" |
|
||||||
} |
|
||||||
|
|
||||||
required init(from decoder: Decoder) throws { |
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self) |
|
||||||
self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId() |
|
||||||
self.match = try container.decodeIfPresent(String.self, forKey: ._match) ?? "" |
|
||||||
self.teamRegistration = try container.decodeIfPresent(String.self, forKey: ._teamRegistration) ?? nil |
|
||||||
self.score = try container.decodeIfPresent(String.self, forKey: ._score) ?? nil |
|
||||||
self.walkOut = try container.decodeIfPresent(Int.self, forKey: ._walkOut) ?? nil |
|
||||||
self.luckyLoser = try container.decodeIfPresent(Int.self, forKey: ._luckyLoser) ?? nil |
|
||||||
try super.init(from: decoder) |
|
||||||
} |
|
||||||
|
|
||||||
override func encode(to encoder: Encoder) throws { |
|
||||||
var container = encoder.container(keyedBy: CodingKeys.self) |
|
||||||
try container.encode(self.id, forKey: ._id) |
|
||||||
try container.encode(self.match, forKey: ._match) |
|
||||||
try container.encode(self.teamRegistration, forKey: ._teamRegistration) |
|
||||||
try container.encode(self.score, forKey: ._score) |
|
||||||
try container.encode(self.walkOut, forKey: ._walkOut) |
|
||||||
try container.encode(self.luckyLoser, forKey: ._luckyLoser) |
|
||||||
try super.encode(to: encoder) |
|
||||||
} |
|
||||||
|
|
||||||
func matchValue() -> Match? { |
|
||||||
return self.store?.findById(match) |
|
||||||
} |
|
||||||
|
|
||||||
func teamRegistrationValue() -> TeamRegistration? { |
|
||||||
guard let teamRegistration = self.teamRegistration else { return nil } |
|
||||||
return self.store?.findById(teamRegistration) |
|
||||||
} |
|
||||||
|
|
||||||
func copy(from other: any Storable) { |
|
||||||
guard let teamscore = other as? BaseTeamScore else { return } |
|
||||||
self.id = teamscore.id |
|
||||||
self.match = teamscore.match |
|
||||||
self.teamRegistration = teamscore.teamRegistration |
|
||||||
self.score = teamscore.score |
|
||||||
self.walkOut = teamscore.walkOut |
|
||||||
self.luckyLoser = teamscore.luckyLoser |
|
||||||
} |
|
||||||
|
|
||||||
static func relationships() -> [Relationship] { |
|
||||||
return [ |
|
||||||
Relationship(type: Match.self, keyPath: \BaseTeamScore.match), |
|
||||||
Relationship(type: TeamRegistration.self, keyPath: \BaseTeamScore.teamRegistration), |
|
||||||
] |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,590 +0,0 @@ |
|||||||
// Generated by SwiftModelGenerator |
|
||||||
// Do not modify this file manually |
|
||||||
|
|
||||||
import Foundation |
|
||||||
import LeStorage |
|
||||||
import SwiftUI |
|
||||||
|
|
||||||
@Observable |
|
||||||
class BaseTournament: SyncedModelObject, SyncedStorable { |
|
||||||
|
|
||||||
static func resourceName() -> String { return "tournaments" } |
|
||||||
static func tokenExemptedMethods() -> [HTTPMethod] { return [] } |
|
||||||
static var copyServerResponse: Bool = false |
|
||||||
|
|
||||||
var id: String = Store.randomId() |
|
||||||
var event: String? = nil |
|
||||||
var name: String? = nil |
|
||||||
var startDate: Date = Date() |
|
||||||
var endDate: Date? = nil |
|
||||||
var creationDate: Date = Date() |
|
||||||
var isPrivate: Bool = false |
|
||||||
var groupStageFormat: MatchFormat? = nil |
|
||||||
var roundFormat: MatchFormat? = nil |
|
||||||
var loserRoundFormat: MatchFormat? = nil |
|
||||||
var groupStageSortMode: GroupStageOrderingMode = GroupStageOrderingMode.snake |
|
||||||
var groupStageCount: Int = 4 |
|
||||||
var rankSourceDate: Date? = nil |
|
||||||
var dayDuration: Int = 1 |
|
||||||
var teamCount: Int = 24 |
|
||||||
var teamSorting: TeamSortingType = TeamSortingType.inscriptionDate |
|
||||||
var federalCategory: TournamentCategory = TournamentCategory.men |
|
||||||
var federalLevelCategory: TournamentLevel = TournamentLevel.p100 |
|
||||||
var federalAgeCategory: FederalTournamentAge = FederalTournamentAge.senior |
|
||||||
var closedRegistrationDate: Date? = nil |
|
||||||
var groupStageAdditionalQualified: Int = 0 |
|
||||||
var courtCount: Int = 2 |
|
||||||
var prioritizeClubMembers: Bool = false |
|
||||||
var qualifiedPerGroupStage: Int = 1 |
|
||||||
var teamsPerGroupStage: Int = 4 |
|
||||||
var entryFee: Double? = nil |
|
||||||
var payment: TournamentPayment? = nil |
|
||||||
var additionalEstimationDuration: Int = 0 |
|
||||||
var isDeleted: Bool = false |
|
||||||
var isCanceled: Bool = false |
|
||||||
var publishTeams: Bool = false |
|
||||||
var publishSummons: Bool = false |
|
||||||
var publishGroupStages: Bool = false |
|
||||||
var publishBrackets: Bool = false |
|
||||||
var shouldVerifyGroupStage: Bool = false |
|
||||||
var shouldVerifyBracket: Bool = false |
|
||||||
var hideTeamsWeight: Bool = false |
|
||||||
var publishTournament: Bool = false |
|
||||||
var hidePointsEarned: Bool = false |
|
||||||
var publishRankings: Bool = false |
|
||||||
var loserBracketMode: LoserBracketMode = .automatic |
|
||||||
var initialSeedRound: Int = 0 |
|
||||||
var initialSeedCount: Int = 0 |
|
||||||
var enableOnlineRegistration: Bool = false |
|
||||||
var registrationDateLimit: Date? = nil |
|
||||||
var openingRegistrationDate: Date? = nil |
|
||||||
var waitingListLimit: Int? = nil |
|
||||||
var accountIsRequired: Bool = true |
|
||||||
var licenseIsRequired: Bool = true |
|
||||||
var minimumPlayerPerTeam: Int = 2 |
|
||||||
var maximumPlayerPerTeam: Int = 2 |
|
||||||
var information: String? = nil |
|
||||||
var umpireCustomMail: String? = nil |
|
||||||
var umpireCustomContact: String? = nil |
|
||||||
var umpireCustomPhone: String? = nil |
|
||||||
var hideUmpireMail: Bool = false |
|
||||||
var hideUmpirePhone: Bool = true |
|
||||||
var disableRankingFederalRuling: Bool = false |
|
||||||
var teamCountLimit: Bool = true |
|
||||||
var enableOnlinePayment: Bool = false |
|
||||||
var onlinePaymentIsMandatory: Bool = false |
|
||||||
var enableOnlinePaymentRefund: Bool = false |
|
||||||
var refundDateLimit: Date? = nil |
|
||||||
var stripeAccountId: String? = nil |
|
||||||
var enableTimeToConfirm: Bool = false |
|
||||||
var isCorporateTournament: Bool = false |
|
||||||
var isTemplate: Bool = false |
|
||||||
|
|
||||||
init( |
|
||||||
id: String = Store.randomId(), |
|
||||||
event: String? = nil, |
|
||||||
name: String? = nil, |
|
||||||
startDate: Date = Date(), |
|
||||||
endDate: Date? = nil, |
|
||||||
creationDate: Date = Date(), |
|
||||||
isPrivate: Bool = false, |
|
||||||
groupStageFormat: MatchFormat? = nil, |
|
||||||
roundFormat: MatchFormat? = nil, |
|
||||||
loserRoundFormat: MatchFormat? = nil, |
|
||||||
groupStageSortMode: GroupStageOrderingMode = GroupStageOrderingMode.snake, |
|
||||||
groupStageCount: Int = 4, |
|
||||||
rankSourceDate: Date? = nil, |
|
||||||
dayDuration: Int = 1, |
|
||||||
teamCount: Int = 24, |
|
||||||
teamSorting: TeamSortingType = TeamSortingType.inscriptionDate, |
|
||||||
federalCategory: TournamentCategory = TournamentCategory.men, |
|
||||||
federalLevelCategory: TournamentLevel = TournamentLevel.p100, |
|
||||||
federalAgeCategory: FederalTournamentAge = FederalTournamentAge.senior, |
|
||||||
closedRegistrationDate: Date? = nil, |
|
||||||
groupStageAdditionalQualified: Int = 0, |
|
||||||
courtCount: Int = 2, |
|
||||||
prioritizeClubMembers: Bool = false, |
|
||||||
qualifiedPerGroupStage: Int = 1, |
|
||||||
teamsPerGroupStage: Int = 4, |
|
||||||
entryFee: Double? = nil, |
|
||||||
payment: TournamentPayment? = nil, |
|
||||||
additionalEstimationDuration: Int = 0, |
|
||||||
isDeleted: Bool = false, |
|
||||||
isCanceled: Bool = false, |
|
||||||
publishTeams: Bool = false, |
|
||||||
publishSummons: Bool = false, |
|
||||||
publishGroupStages: Bool = false, |
|
||||||
publishBrackets: Bool = false, |
|
||||||
shouldVerifyGroupStage: Bool = false, |
|
||||||
shouldVerifyBracket: 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, |
|
||||||
enableOnlinePayment: Bool = false, |
|
||||||
onlinePaymentIsMandatory: Bool = false, |
|
||||||
enableOnlinePaymentRefund: Bool = false, |
|
||||||
refundDateLimit: Date? = nil, |
|
||||||
stripeAccountId: String? = nil, |
|
||||||
enableTimeToConfirm: Bool = false, |
|
||||||
isCorporateTournament: Bool = false, |
|
||||||
isTemplate: Bool = false |
|
||||||
) { |
|
||||||
super.init() |
|
||||||
self.id = id |
|
||||||
self.event = event |
|
||||||
self.name = name |
|
||||||
self.startDate = startDate |
|
||||||
self.endDate = endDate |
|
||||||
self.creationDate = creationDate |
|
||||||
self.isPrivate = isPrivate |
|
||||||
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 |
|
||||||
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.payment = payment |
|
||||||
self.additionalEstimationDuration = additionalEstimationDuration |
|
||||||
self.isDeleted = isDeleted |
|
||||||
self.isCanceled = isCanceled |
|
||||||
self.publishTeams = publishTeams |
|
||||||
self.publishSummons = publishSummons |
|
||||||
self.publishGroupStages = publishGroupStages |
|
||||||
self.publishBrackets = publishBrackets |
|
||||||
self.shouldVerifyGroupStage = shouldVerifyGroupStage |
|
||||||
self.shouldVerifyBracket = shouldVerifyBracket |
|
||||||
self.hideTeamsWeight = hideTeamsWeight |
|
||||||
self.publishTournament = publishTournament |
|
||||||
self.hidePointsEarned = hidePointsEarned |
|
||||||
self.publishRankings = publishRankings |
|
||||||
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.umpireCustomPhone = umpireCustomPhone |
|
||||||
self.hideUmpireMail = hideUmpireMail |
|
||||||
self.hideUmpirePhone = hideUmpirePhone |
|
||||||
self.disableRankingFederalRuling = disableRankingFederalRuling |
|
||||||
self.teamCountLimit = teamCountLimit |
|
||||||
self.enableOnlinePayment = enableOnlinePayment |
|
||||||
self.onlinePaymentIsMandatory = onlinePaymentIsMandatory |
|
||||||
self.enableOnlinePaymentRefund = enableOnlinePaymentRefund |
|
||||||
self.refundDateLimit = refundDateLimit |
|
||||||
self.stripeAccountId = stripeAccountId |
|
||||||
self.enableTimeToConfirm = enableTimeToConfirm |
|
||||||
self.isCorporateTournament = isCorporateTournament |
|
||||||
self.isTemplate = isTemplate |
|
||||||
} |
|
||||||
required public override init() { |
|
||||||
super.init() |
|
||||||
} |
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey { |
|
||||||
case isCanceled = "isCanceled" |
|
||||||
case payment = "payment" |
|
||||||
case _id = "id" |
|
||||||
case _event = "event" |
|
||||||
case _name = "name" |
|
||||||
case _startDate = "startDate" |
|
||||||
case _endDate = "endDate" |
|
||||||
case _creationDate = "creationDate" |
|
||||||
case _isPrivate = "isPrivate" |
|
||||||
case _groupStageFormat = "groupStageFormat" |
|
||||||
case _roundFormat = "roundFormat" |
|
||||||
case _loserRoundFormat = "loserRoundFormat" |
|
||||||
case _groupStageSortMode = "groupStageSortMode" |
|
||||||
case _groupStageCount = "groupStageCount" |
|
||||||
case _rankSourceDate = "rankSourceDate" |
|
||||||
case _dayDuration = "dayDuration" |
|
||||||
case _teamCount = "teamCount" |
|
||||||
case _teamSorting = "teamSorting" |
|
||||||
case _federalCategory = "federalCategory" |
|
||||||
case _federalLevelCategory = "federalLevelCategory" |
|
||||||
case _federalAgeCategory = "federalAgeCategory" |
|
||||||
case _closedRegistrationDate = "closedRegistrationDate" |
|
||||||
case _groupStageAdditionalQualified = "groupStageAdditionalQualified" |
|
||||||
case _courtCount = "courtCount" |
|
||||||
case _prioritizeClubMembers = "prioritizeClubMembers" |
|
||||||
case _qualifiedPerGroupStage = "qualifiedPerGroupStage" |
|
||||||
case _teamsPerGroupStage = "teamsPerGroupStage" |
|
||||||
case _entryFee = "entryFee" |
|
||||||
case _payment = "globalId" |
|
||||||
case _additionalEstimationDuration = "additionalEstimationDuration" |
|
||||||
case _isDeleted = "isDeleted" |
|
||||||
case _isCanceled = "localId" |
|
||||||
case _publishTeams = "publishTeams" |
|
||||||
case _publishSummons = "publishSummons" |
|
||||||
case _publishGroupStages = "publishGroupStages" |
|
||||||
case _publishBrackets = "publishBrackets" |
|
||||||
case _shouldVerifyGroupStage = "shouldVerifyGroupStage" |
|
||||||
case _shouldVerifyBracket = "shouldVerifyBracket" |
|
||||||
case _hideTeamsWeight = "hideTeamsWeight" |
|
||||||
case _publishTournament = "publishTournament" |
|
||||||
case _hidePointsEarned = "hidePointsEarned" |
|
||||||
case _publishRankings = "publishRankings" |
|
||||||
case _loserBracketMode = "loserBracketMode" |
|
||||||
case _initialSeedRound = "initialSeedRound" |
|
||||||
case _initialSeedCount = "initialSeedCount" |
|
||||||
case _enableOnlineRegistration = "enableOnlineRegistration" |
|
||||||
case _registrationDateLimit = "registrationDateLimit" |
|
||||||
case _openingRegistrationDate = "openingRegistrationDate" |
|
||||||
case _waitingListLimit = "waitingListLimit" |
|
||||||
case _accountIsRequired = "accountIsRequired" |
|
||||||
case _licenseIsRequired = "licenseIsRequired" |
|
||||||
case _minimumPlayerPerTeam = "minimumPlayerPerTeam" |
|
||||||
case _maximumPlayerPerTeam = "maximumPlayerPerTeam" |
|
||||||
case _information = "information" |
|
||||||
case _umpireCustomMail = "umpireCustomMail" |
|
||||||
case _umpireCustomContact = "umpireCustomContact" |
|
||||||
case _umpireCustomPhone = "umpireCustomPhone" |
|
||||||
case _hideUmpireMail = "hideUmpireMail" |
|
||||||
case _hideUmpirePhone = "hideUmpirePhone" |
|
||||||
case _disableRankingFederalRuling = "disableRankingFederalRuling" |
|
||||||
case _teamCountLimit = "teamCountLimit" |
|
||||||
case _enableOnlinePayment = "enableOnlinePayment" |
|
||||||
case _onlinePaymentIsMandatory = "onlinePaymentIsMandatory" |
|
||||||
case _enableOnlinePaymentRefund = "enableOnlinePaymentRefund" |
|
||||||
case _refundDateLimit = "refundDateLimit" |
|
||||||
case _stripeAccountId = "stripeAccountId" |
|
||||||
case _enableTimeToConfirm = "enableTimeToConfirm" |
|
||||||
case _isCorporateTournament = "isCorporateTournament" |
|
||||||
case _isTemplate = "isTemplate" |
|
||||||
} |
|
||||||
|
|
||||||
private static func _decodePayment(container: KeyedDecodingContainer<CodingKeys>) throws -> TournamentPayment? { |
|
||||||
var data = try container.decodeIfPresent(Data.self, forKey: ._payment) |
|
||||||
if data == nil { |
|
||||||
data = try container.decodeIfPresent(Data.self, forKey: .payment) |
|
||||||
} |
|
||||||
|
|
||||||
if let data { |
|
||||||
do { |
|
||||||
let decoded: String = try data.decryptData(pass: CryptoKey.pass.rawValue) |
|
||||||
let sequence = decoded.compactMap { NumberFormatter.standard.number(from: String($0))?.intValue } |
|
||||||
return TournamentPayment(rawValue: sequence[18]) |
|
||||||
} catch { |
|
||||||
Logger.error(error) |
|
||||||
} |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
private func _encodePayment(container: inout KeyedEncodingContainer<CodingKeys>) throws { |
|
||||||
guard let payment else { |
|
||||||
try container.encodeNil(forKey: ._payment) |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
let max: Int = TournamentPayment.allCases.count |
|
||||||
var sequence = (1...18).map { _ in Int.random(in: (0..<max)) } |
|
||||||
sequence.append(payment.rawValue) |
|
||||||
sequence.append(contentsOf: (1...13).map { _ in Int.random(in: (0..<max ))} ) |
|
||||||
|
|
||||||
let stringCombo: [String] = sequence.map { $0.formatted() } |
|
||||||
let joined: String = stringCombo.joined(separator: "") |
|
||||||
if let data = joined.data(using: .utf8) { |
|
||||||
let encryped: Data = try data.encrypt(pass: CryptoKey.pass.rawValue) |
|
||||||
try container.encodeIfPresent(encryped, forKey: ._payment) |
|
||||||
} |
|
||||||
} |
|
||||||
private static func _decodeIscanceled(container: KeyedDecodingContainer<CodingKeys>) throws -> Bool { |
|
||||||
var data = try container.decodeIfPresent(Data.self, forKey: ._isCanceled) |
|
||||||
if data == nil { |
|
||||||
data = try container.decodeIfPresent(Data.self, forKey: .isCanceled) |
|
||||||
} |
|
||||||
if let data { |
|
||||||
do { |
|
||||||
let decoded: String = try data.decryptData(pass: CryptoKey.pass.rawValue) |
|
||||||
let sequence = decoded.compactMap { NumberFormatter.standard.number(from: String($0))?.intValue } |
|
||||||
return Bool.decodeInt(sequence[18]) |
|
||||||
} catch { |
|
||||||
Logger.error(error) |
|
||||||
} |
|
||||||
} |
|
||||||
return false |
|
||||||
} |
|
||||||
|
|
||||||
private func _encodeIscanceled(container: inout KeyedEncodingContainer<CodingKeys>) throws { |
|
||||||
let max: Int = 9 |
|
||||||
var sequence = (1...18).map { _ in Int.random(in: (0...max)) } |
|
||||||
sequence.append(self.isCanceled.encodedValue) |
|
||||||
sequence.append(contentsOf: (1...13).map { _ in Int.random(in: (0...max ))} ) |
|
||||||
|
|
||||||
let stringCombo: [String] = sequence.map { $0.formatted() } |
|
||||||
let joined: String = stringCombo.joined(separator: "") |
|
||||||
if let data = joined.data(using: .utf8) { |
|
||||||
let encryped: Data = try data.encrypt(pass: CryptoKey.pass.rawValue) |
|
||||||
try container.encode(encryped, forKey: ._isCanceled) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
required init(from decoder: Decoder) throws { |
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self) |
|
||||||
self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId() |
|
||||||
self.event = try container.decodeIfPresent(String.self, forKey: ._event) ?? nil |
|
||||||
self.name = try container.decodeIfPresent(String.self, forKey: ._name) ?? nil |
|
||||||
self.startDate = try container.decodeIfPresent(Date.self, forKey: ._startDate) ?? Date() |
|
||||||
self.endDate = try container.decodeIfPresent(Date.self, forKey: ._endDate) ?? nil |
|
||||||
self.creationDate = try container.decodeIfPresent(Date.self, forKey: ._creationDate) ?? Date() |
|
||||||
self.isPrivate = try container.decodeIfPresent(Bool.self, forKey: ._isPrivate) ?? false |
|
||||||
self.groupStageFormat = try container.decodeIfPresent(MatchFormat.self, forKey: ._groupStageFormat) ?? nil |
|
||||||
self.roundFormat = try container.decodeIfPresent(MatchFormat.self, forKey: ._roundFormat) ?? nil |
|
||||||
self.loserRoundFormat = try container.decodeIfPresent(MatchFormat.self, forKey: ._loserRoundFormat) ?? nil |
|
||||||
self.groupStageSortMode = try container.decodeIfPresent(GroupStageOrderingMode.self, forKey: ._groupStageSortMode) ?? GroupStageOrderingMode.snake |
|
||||||
self.groupStageCount = try container.decodeIfPresent(Int.self, forKey: ._groupStageCount) ?? 4 |
|
||||||
self.rankSourceDate = try container.decodeIfPresent(Date.self, forKey: ._rankSourceDate) ?? nil |
|
||||||
self.dayDuration = try container.decodeIfPresent(Int.self, forKey: ._dayDuration) ?? 1 |
|
||||||
self.teamCount = try container.decodeIfPresent(Int.self, forKey: ._teamCount) ?? 24 |
|
||||||
self.teamSorting = try container.decodeIfPresent(TeamSortingType.self, forKey: ._teamSorting) ?? TeamSortingType.inscriptionDate |
|
||||||
self.federalCategory = try container.decodeIfPresent(TournamentCategory.self, forKey: ._federalCategory) ?? TournamentCategory.men |
|
||||||
self.federalLevelCategory = try container.decodeIfPresent(TournamentLevel.self, forKey: ._federalLevelCategory) ?? TournamentLevel.p100 |
|
||||||
self.federalAgeCategory = try container.decodeIfPresent(FederalTournamentAge.self, forKey: ._federalAgeCategory) ?? FederalTournamentAge.senior |
|
||||||
self.closedRegistrationDate = try container.decodeIfPresent(Date.self, forKey: ._closedRegistrationDate) ?? nil |
|
||||||
self.groupStageAdditionalQualified = try container.decodeIfPresent(Int.self, forKey: ._groupStageAdditionalQualified) ?? 0 |
|
||||||
self.courtCount = try container.decodeIfPresent(Int.self, forKey: ._courtCount) ?? 2 |
|
||||||
self.prioritizeClubMembers = try container.decodeIfPresent(Bool.self, forKey: ._prioritizeClubMembers) ?? false |
|
||||||
self.qualifiedPerGroupStage = try container.decodeIfPresent(Int.self, forKey: ._qualifiedPerGroupStage) ?? 1 |
|
||||||
self.teamsPerGroupStage = try container.decodeIfPresent(Int.self, forKey: ._teamsPerGroupStage) ?? 4 |
|
||||||
self.entryFee = try container.decodeIfPresent(Double.self, forKey: ._entryFee) ?? nil |
|
||||||
self.payment = try Self._decodePayment(container: container) |
|
||||||
self.additionalEstimationDuration = try container.decodeIfPresent(Int.self, forKey: ._additionalEstimationDuration) ?? 0 |
|
||||||
self.isDeleted = try container.decodeIfPresent(Bool.self, forKey: ._isDeleted) ?? false |
|
||||||
self.isCanceled = try Self._decodeIscanceled(container: container) |
|
||||||
self.publishTeams = try container.decodeIfPresent(Bool.self, forKey: ._publishTeams) ?? false |
|
||||||
self.publishSummons = try container.decodeIfPresent(Bool.self, forKey: ._publishSummons) ?? false |
|
||||||
self.publishGroupStages = try container.decodeIfPresent(Bool.self, forKey: ._publishGroupStages) ?? false |
|
||||||
self.publishBrackets = try container.decodeIfPresent(Bool.self, forKey: ._publishBrackets) ?? false |
|
||||||
self.shouldVerifyGroupStage = try container.decodeIfPresent(Bool.self, forKey: ._shouldVerifyGroupStage) ?? false |
|
||||||
self.shouldVerifyBracket = try container.decodeIfPresent(Bool.self, forKey: ._shouldVerifyBracket) ?? false |
|
||||||
self.hideTeamsWeight = try container.decodeIfPresent(Bool.self, forKey: ._hideTeamsWeight) ?? false |
|
||||||
self.publishTournament = try container.decodeIfPresent(Bool.self, forKey: ._publishTournament) ?? false |
|
||||||
self.hidePointsEarned = try container.decodeIfPresent(Bool.self, forKey: ._hidePointsEarned) ?? false |
|
||||||
self.publishRankings = try container.decodeIfPresent(Bool.self, forKey: ._publishRankings) ?? false |
|
||||||
self.loserBracketMode = try container.decodeIfPresent(LoserBracketMode.self, forKey: ._loserBracketMode) ?? .automatic |
|
||||||
self.initialSeedRound = try container.decodeIfPresent(Int.self, forKey: ._initialSeedRound) ?? 0 |
|
||||||
self.initialSeedCount = try container.decodeIfPresent(Int.self, forKey: ._initialSeedCount) ?? 0 |
|
||||||
self.enableOnlineRegistration = try container.decodeIfPresent(Bool.self, forKey: ._enableOnlineRegistration) ?? false |
|
||||||
self.registrationDateLimit = try container.decodeIfPresent(Date.self, forKey: ._registrationDateLimit) ?? nil |
|
||||||
self.openingRegistrationDate = try container.decodeIfPresent(Date.self, forKey: ._openingRegistrationDate) ?? nil |
|
||||||
self.waitingListLimit = try container.decodeIfPresent(Int.self, forKey: ._waitingListLimit) ?? nil |
|
||||||
self.accountIsRequired = try container.decodeIfPresent(Bool.self, forKey: ._accountIsRequired) ?? true |
|
||||||
self.licenseIsRequired = try container.decodeIfPresent(Bool.self, forKey: ._licenseIsRequired) ?? true |
|
||||||
self.minimumPlayerPerTeam = try container.decodeIfPresent(Int.self, forKey: ._minimumPlayerPerTeam) ?? 2 |
|
||||||
self.maximumPlayerPerTeam = try container.decodeIfPresent(Int.self, forKey: ._maximumPlayerPerTeam) ?? 2 |
|
||||||
self.information = try container.decodeIfPresent(String.self, forKey: ._information) ?? nil |
|
||||||
self.umpireCustomMail = try container.decodeIfPresent(String.self, forKey: ._umpireCustomMail) ?? nil |
|
||||||
self.umpireCustomContact = try container.decodeIfPresent(String.self, forKey: ._umpireCustomContact) ?? nil |
|
||||||
self.umpireCustomPhone = try container.decodeIfPresent(String.self, forKey: ._umpireCustomPhone) ?? nil |
|
||||||
self.hideUmpireMail = try container.decodeIfPresent(Bool.self, forKey: ._hideUmpireMail) ?? false |
|
||||||
self.hideUmpirePhone = try container.decodeIfPresent(Bool.self, forKey: ._hideUmpirePhone) ?? true |
|
||||||
self.disableRankingFederalRuling = try container.decodeIfPresent(Bool.self, forKey: ._disableRankingFederalRuling) ?? false |
|
||||||
self.teamCountLimit = try container.decodeIfPresent(Bool.self, forKey: ._teamCountLimit) ?? true |
|
||||||
self.enableOnlinePayment = try container.decodeIfPresent(Bool.self, forKey: ._enableOnlinePayment) ?? false |
|
||||||
self.onlinePaymentIsMandatory = try container.decodeIfPresent(Bool.self, forKey: ._onlinePaymentIsMandatory) ?? false |
|
||||||
self.enableOnlinePaymentRefund = try container.decodeIfPresent(Bool.self, forKey: ._enableOnlinePaymentRefund) ?? false |
|
||||||
self.refundDateLimit = try container.decodeIfPresent(Date.self, forKey: ._refundDateLimit) ?? nil |
|
||||||
self.stripeAccountId = try container.decodeIfPresent(String.self, forKey: ._stripeAccountId) ?? nil |
|
||||||
self.enableTimeToConfirm = try container.decodeIfPresent(Bool.self, forKey: ._enableTimeToConfirm) ?? false |
|
||||||
self.isCorporateTournament = try container.decodeIfPresent(Bool.self, forKey: ._isCorporateTournament) ?? false |
|
||||||
self.isTemplate = try container.decodeIfPresent(Bool.self, forKey: ._isTemplate) ?? false |
|
||||||
try super.init(from: decoder) |
|
||||||
} |
|
||||||
|
|
||||||
override func encode(to encoder: Encoder) throws { |
|
||||||
var container = encoder.container(keyedBy: CodingKeys.self) |
|
||||||
try container.encode(self.id, forKey: ._id) |
|
||||||
try container.encode(self.event, forKey: ._event) |
|
||||||
try container.encode(self.name, forKey: ._name) |
|
||||||
try container.encode(self.startDate, forKey: ._startDate) |
|
||||||
try container.encode(self.endDate, forKey: ._endDate) |
|
||||||
try container.encode(self.creationDate, forKey: ._creationDate) |
|
||||||
try container.encode(self.isPrivate, forKey: ._isPrivate) |
|
||||||
try container.encode(self.groupStageFormat, forKey: ._groupStageFormat) |
|
||||||
try container.encode(self.roundFormat, forKey: ._roundFormat) |
|
||||||
try container.encode(self.loserRoundFormat, forKey: ._loserRoundFormat) |
|
||||||
try container.encode(self.groupStageSortMode, forKey: ._groupStageSortMode) |
|
||||||
try container.encode(self.groupStageCount, forKey: ._groupStageCount) |
|
||||||
try container.encode(self.rankSourceDate, forKey: ._rankSourceDate) |
|
||||||
try container.encode(self.dayDuration, forKey: ._dayDuration) |
|
||||||
try container.encode(self.teamCount, forKey: ._teamCount) |
|
||||||
try container.encode(self.teamSorting, forKey: ._teamSorting) |
|
||||||
try container.encode(self.federalCategory, forKey: ._federalCategory) |
|
||||||
try container.encode(self.federalLevelCategory, forKey: ._federalLevelCategory) |
|
||||||
try container.encode(self.federalAgeCategory, forKey: ._federalAgeCategory) |
|
||||||
try container.encode(self.closedRegistrationDate, forKey: ._closedRegistrationDate) |
|
||||||
try container.encode(self.groupStageAdditionalQualified, forKey: ._groupStageAdditionalQualified) |
|
||||||
try container.encode(self.courtCount, forKey: ._courtCount) |
|
||||||
try container.encode(self.prioritizeClubMembers, forKey: ._prioritizeClubMembers) |
|
||||||
try container.encode(self.qualifiedPerGroupStage, forKey: ._qualifiedPerGroupStage) |
|
||||||
try container.encode(self.teamsPerGroupStage, forKey: ._teamsPerGroupStage) |
|
||||||
try container.encode(self.entryFee, forKey: ._entryFee) |
|
||||||
try _encodePayment(container: &container) |
|
||||||
try container.encode(self.additionalEstimationDuration, forKey: ._additionalEstimationDuration) |
|
||||||
try container.encode(self.isDeleted, forKey: ._isDeleted) |
|
||||||
try _encodeIscanceled(container: &container) |
|
||||||
try container.encode(self.publishTeams, forKey: ._publishTeams) |
|
||||||
try container.encode(self.publishSummons, forKey: ._publishSummons) |
|
||||||
try container.encode(self.publishGroupStages, forKey: ._publishGroupStages) |
|
||||||
try container.encode(self.publishBrackets, forKey: ._publishBrackets) |
|
||||||
try container.encode(self.shouldVerifyGroupStage, forKey: ._shouldVerifyGroupStage) |
|
||||||
try container.encode(self.shouldVerifyBracket, forKey: ._shouldVerifyBracket) |
|
||||||
try container.encode(self.hideTeamsWeight, forKey: ._hideTeamsWeight) |
|
||||||
try container.encode(self.publishTournament, forKey: ._publishTournament) |
|
||||||
try container.encode(self.hidePointsEarned, forKey: ._hidePointsEarned) |
|
||||||
try container.encode(self.publishRankings, forKey: ._publishRankings) |
|
||||||
try container.encode(self.loserBracketMode, forKey: ._loserBracketMode) |
|
||||||
try container.encode(self.initialSeedRound, forKey: ._initialSeedRound) |
|
||||||
try container.encode(self.initialSeedCount, forKey: ._initialSeedCount) |
|
||||||
try container.encode(self.enableOnlineRegistration, forKey: ._enableOnlineRegistration) |
|
||||||
try container.encode(self.registrationDateLimit, forKey: ._registrationDateLimit) |
|
||||||
try container.encode(self.openingRegistrationDate, forKey: ._openingRegistrationDate) |
|
||||||
try container.encode(self.waitingListLimit, forKey: ._waitingListLimit) |
|
||||||
try container.encode(self.accountIsRequired, forKey: ._accountIsRequired) |
|
||||||
try container.encode(self.licenseIsRequired, forKey: ._licenseIsRequired) |
|
||||||
try container.encode(self.minimumPlayerPerTeam, forKey: ._minimumPlayerPerTeam) |
|
||||||
try container.encode(self.maximumPlayerPerTeam, forKey: ._maximumPlayerPerTeam) |
|
||||||
try container.encode(self.information, forKey: ._information) |
|
||||||
try container.encode(self.umpireCustomMail, forKey: ._umpireCustomMail) |
|
||||||
try container.encode(self.umpireCustomContact, forKey: ._umpireCustomContact) |
|
||||||
try container.encode(self.umpireCustomPhone, forKey: ._umpireCustomPhone) |
|
||||||
try container.encode(self.hideUmpireMail, forKey: ._hideUmpireMail) |
|
||||||
try container.encode(self.hideUmpirePhone, forKey: ._hideUmpirePhone) |
|
||||||
try container.encode(self.disableRankingFederalRuling, forKey: ._disableRankingFederalRuling) |
|
||||||
try container.encode(self.teamCountLimit, forKey: ._teamCountLimit) |
|
||||||
try container.encode(self.enableOnlinePayment, forKey: ._enableOnlinePayment) |
|
||||||
try container.encode(self.onlinePaymentIsMandatory, forKey: ._onlinePaymentIsMandatory) |
|
||||||
try container.encode(self.enableOnlinePaymentRefund, forKey: ._enableOnlinePaymentRefund) |
|
||||||
try container.encode(self.refundDateLimit, forKey: ._refundDateLimit) |
|
||||||
try container.encode(self.stripeAccountId, forKey: ._stripeAccountId) |
|
||||||
try container.encode(self.enableTimeToConfirm, forKey: ._enableTimeToConfirm) |
|
||||||
try container.encode(self.isCorporateTournament, forKey: ._isCorporateTournament) |
|
||||||
try container.encode(self.isTemplate, forKey: ._isTemplate) |
|
||||||
try super.encode(to: encoder) |
|
||||||
} |
|
||||||
|
|
||||||
func eventValue() -> Event? { |
|
||||||
guard let event = self.event else { return nil } |
|
||||||
return Store.main.findById(event) |
|
||||||
} |
|
||||||
|
|
||||||
func copy(from other: any Storable) { |
|
||||||
guard let tournament = other as? BaseTournament else { return } |
|
||||||
self.id = tournament.id |
|
||||||
self.event = tournament.event |
|
||||||
self.name = tournament.name |
|
||||||
self.startDate = tournament.startDate |
|
||||||
self.endDate = tournament.endDate |
|
||||||
self.creationDate = tournament.creationDate |
|
||||||
self.isPrivate = tournament.isPrivate |
|
||||||
self.groupStageFormat = tournament.groupStageFormat |
|
||||||
self.roundFormat = tournament.roundFormat |
|
||||||
self.loserRoundFormat = tournament.loserRoundFormat |
|
||||||
self.groupStageSortMode = tournament.groupStageSortMode |
|
||||||
self.groupStageCount = tournament.groupStageCount |
|
||||||
self.rankSourceDate = tournament.rankSourceDate |
|
||||||
self.dayDuration = tournament.dayDuration |
|
||||||
self.teamCount = tournament.teamCount |
|
||||||
self.teamSorting = tournament.teamSorting |
|
||||||
self.federalCategory = tournament.federalCategory |
|
||||||
self.federalLevelCategory = tournament.federalLevelCategory |
|
||||||
self.federalAgeCategory = tournament.federalAgeCategory |
|
||||||
self.closedRegistrationDate = tournament.closedRegistrationDate |
|
||||||
self.groupStageAdditionalQualified = tournament.groupStageAdditionalQualified |
|
||||||
self.courtCount = tournament.courtCount |
|
||||||
self.prioritizeClubMembers = tournament.prioritizeClubMembers |
|
||||||
self.qualifiedPerGroupStage = tournament.qualifiedPerGroupStage |
|
||||||
self.teamsPerGroupStage = tournament.teamsPerGroupStage |
|
||||||
self.entryFee = tournament.entryFee |
|
||||||
self.payment = tournament.payment |
|
||||||
self.additionalEstimationDuration = tournament.additionalEstimationDuration |
|
||||||
self.isDeleted = tournament.isDeleted |
|
||||||
self.isCanceled = tournament.isCanceled |
|
||||||
self.publishTeams = tournament.publishTeams |
|
||||||
self.publishSummons = tournament.publishSummons |
|
||||||
self.publishGroupStages = tournament.publishGroupStages |
|
||||||
self.publishBrackets = tournament.publishBrackets |
|
||||||
self.shouldVerifyGroupStage = tournament.shouldVerifyGroupStage |
|
||||||
self.shouldVerifyBracket = tournament.shouldVerifyBracket |
|
||||||
self.hideTeamsWeight = tournament.hideTeamsWeight |
|
||||||
self.publishTournament = tournament.publishTournament |
|
||||||
self.hidePointsEarned = tournament.hidePointsEarned |
|
||||||
self.publishRankings = tournament.publishRankings |
|
||||||
self.loserBracketMode = tournament.loserBracketMode |
|
||||||
self.initialSeedRound = tournament.initialSeedRound |
|
||||||
self.initialSeedCount = tournament.initialSeedCount |
|
||||||
self.enableOnlineRegistration = tournament.enableOnlineRegistration |
|
||||||
self.registrationDateLimit = tournament.registrationDateLimit |
|
||||||
self.openingRegistrationDate = tournament.openingRegistrationDate |
|
||||||
self.waitingListLimit = tournament.waitingListLimit |
|
||||||
self.accountIsRequired = tournament.accountIsRequired |
|
||||||
self.licenseIsRequired = tournament.licenseIsRequired |
|
||||||
self.minimumPlayerPerTeam = tournament.minimumPlayerPerTeam |
|
||||||
self.maximumPlayerPerTeam = tournament.maximumPlayerPerTeam |
|
||||||
self.information = tournament.information |
|
||||||
self.umpireCustomMail = tournament.umpireCustomMail |
|
||||||
self.umpireCustomContact = tournament.umpireCustomContact |
|
||||||
self.umpireCustomPhone = tournament.umpireCustomPhone |
|
||||||
self.hideUmpireMail = tournament.hideUmpireMail |
|
||||||
self.hideUmpirePhone = tournament.hideUmpirePhone |
|
||||||
self.disableRankingFederalRuling = tournament.disableRankingFederalRuling |
|
||||||
self.teamCountLimit = tournament.teamCountLimit |
|
||||||
self.enableOnlinePayment = tournament.enableOnlinePayment |
|
||||||
self.onlinePaymentIsMandatory = tournament.onlinePaymentIsMandatory |
|
||||||
self.enableOnlinePaymentRefund = tournament.enableOnlinePaymentRefund |
|
||||||
self.refundDateLimit = tournament.refundDateLimit |
|
||||||
self.stripeAccountId = tournament.stripeAccountId |
|
||||||
self.enableTimeToConfirm = tournament.enableTimeToConfirm |
|
||||||
self.isCorporateTournament = tournament.isCorporateTournament |
|
||||||
self.isTemplate = tournament.isTemplate |
|
||||||
} |
|
||||||
|
|
||||||
static func relationships() -> [Relationship] { |
|
||||||
return [ |
|
||||||
Relationship(type: Event.self, keyPath: \BaseTournament.event), |
|
||||||
] |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,93 +0,0 @@ |
|||||||
{ |
|
||||||
"models": [ |
|
||||||
{ |
|
||||||
"name": "Club", |
|
||||||
"synchronizable": true, |
|
||||||
"observable": true, |
|
||||||
"copy_server_response": "true", |
|
||||||
"properties": [ |
|
||||||
{ |
|
||||||
"name": "id", |
|
||||||
"type": "String", |
|
||||||
"defaultValue": "Store.randomId()" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "creator", |
|
||||||
"type": "String", |
|
||||||
"optional": true, |
|
||||||
"defaultValue": "nil", |
|
||||||
"foreignKey": "CustomUser" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "name", |
|
||||||
"type": "String", |
|
||||||
"defaultValue": "\"\"" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "acronym", |
|
||||||
"type": "String", |
|
||||||
"defaultValue": "\"\"" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "phone", |
|
||||||
"type": "String", |
|
||||||
"optional": true, |
|
||||||
"defaultValue": "nil" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "code", |
|
||||||
"type": "String", |
|
||||||
"optional": true, |
|
||||||
"defaultValue": "nil" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "address", |
|
||||||
"type": "String", |
|
||||||
"optional": true, |
|
||||||
"defaultValue": "nil" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "city", |
|
||||||
"type": "String", |
|
||||||
"optional": true, |
|
||||||
"defaultValue": "nil" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "zipCode", |
|
||||||
"type": "String", |
|
||||||
"optional": true, |
|
||||||
"defaultValue": "nil" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "latitude", |
|
||||||
"type": "Double", |
|
||||||
"optional": true, |
|
||||||
"defaultValue": "nil" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "longitude", |
|
||||||
"type": "Double", |
|
||||||
"optional": true, |
|
||||||
"defaultValue": "nil" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "courtCount", |
|
||||||
"type": "Int", |
|
||||||
"defaultValue": "2" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "broadcastCode", |
|
||||||
"type": "String", |
|
||||||
"optional": true, |
|
||||||
"defaultValue": "nil" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "timezone", |
|
||||||
"type": "String", |
|
||||||
"optional": true, |
|
||||||
"defaultValue": "TimeZone.current.identifier" |
|
||||||
} |
|
||||||
] |
|
||||||
} |
|
||||||
] |
|
||||||
} |
|
||||||
@ -1,42 +0,0 @@ |
|||||||
{ |
|
||||||
"models": [ |
|
||||||
{ |
|
||||||
"name": "Court", |
|
||||||
"synchronizable": true, |
|
||||||
"observable": true, |
|
||||||
"properties": [ |
|
||||||
{ |
|
||||||
"name": "id", |
|
||||||
"type": "String", |
|
||||||
"defaultValue": "Store.randomId()" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "index", |
|
||||||
"type": "Int" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "club", |
|
||||||
"type": "String", |
|
||||||
"foreignKey": "Club" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "name", |
|
||||||
"type": "String", |
|
||||||
"optional": true, |
|
||||||
"defaultValue": "nil" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "exitAllowed", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "indoor", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
} |
|
||||||
], |
|
||||||
"tokenExemptedMethods": [] |
|
||||||
} |
|
||||||
] |
|
||||||
} |
|
||||||
@ -1,177 +0,0 @@ |
|||||||
{ |
|
||||||
"models": [ |
|
||||||
{ |
|
||||||
"name": "CustomUser", |
|
||||||
"resource_name": "users", |
|
||||||
"synchronizable": true, |
|
||||||
"observable": true, |
|
||||||
"tokenExemptedMethods": ["post"], |
|
||||||
"properties": [ |
|
||||||
{ |
|
||||||
"name": "id", |
|
||||||
"type": "String", |
|
||||||
"defaultValue": "Store.randomId()" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "username", |
|
||||||
"type": "String" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "email", |
|
||||||
"type": "String" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "clubs", |
|
||||||
"type": "[String]", |
|
||||||
"defaultValue": "[]" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "umpireCode", |
|
||||||
"type": "String", |
|
||||||
"optional": true, |
|
||||||
"defaultValue": "nil" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "licenceId", |
|
||||||
"type": "String", |
|
||||||
"optional": true, |
|
||||||
"defaultValue": "nil" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "firstName", |
|
||||||
"type": "String" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "lastName", |
|
||||||
"type": "String" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "phone", |
|
||||||
"type": "String", |
|
||||||
"optional": true, |
|
||||||
"defaultValue": "nil" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "country", |
|
||||||
"type": "String", |
|
||||||
"optional": true, |
|
||||||
"defaultValue": "nil" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "summonsMessageBody", |
|
||||||
"type": "String", |
|
||||||
"optional": true, |
|
||||||
"defaultValue": "nil" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "summonsMessageSignature", |
|
||||||
"type": "String", |
|
||||||
"optional": true, |
|
||||||
"defaultValue": "nil" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "summonsAvailablePaymentMethods", |
|
||||||
"type": "String", |
|
||||||
"optional": true, |
|
||||||
"defaultValue": "nil" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "summonsDisplayFormat", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "summonsDisplayEntryFee", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "summonsUseFullCustomMessage", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "matchFormatsDefaultDuration", |
|
||||||
"type": "[MatchFormat: Int]", |
|
||||||
"optional": true, |
|
||||||
"defaultValue": "nil" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "bracketMatchFormatPreference", |
|
||||||
"type": "MatchFormat", |
|
||||||
"optional": true, |
|
||||||
"defaultValue": "nil" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "groupStageMatchFormatPreference", |
|
||||||
"type": "MatchFormat", |
|
||||||
"optional": true, |
|
||||||
"defaultValue": "nil" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "loserBracketMatchFormatPreference", |
|
||||||
"type": "MatchFormat", |
|
||||||
"optional": true, |
|
||||||
"defaultValue": "nil" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "loserBracketMode", |
|
||||||
"type": "LoserBracketMode", |
|
||||||
"defaultValue": ".automatic" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "disableRankingFederalRuling", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "deviceId", |
|
||||||
"type": "String", |
|
||||||
"optional": true, |
|
||||||
"defaultValue": "nil" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "agents", |
|
||||||
"type": "[String]", |
|
||||||
"defaultValue": "[]" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "userRole", |
|
||||||
"type": "Int", |
|
||||||
"optional": true, |
|
||||||
"defaultValue": "nil" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "registrationPaymentMode", |
|
||||||
"type": "RegistrationPaymentMode", |
|
||||||
"defaultValue": "RegistrationPaymentMode.disabled" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "umpireCustomMail", |
|
||||||
"type": "String", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "umpireCustomContact", |
|
||||||
"type": "String", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "umpireCustomPhone", |
|
||||||
"type": "String", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "hideUmpireMail", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "hideUmpirePhone", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "true" |
|
||||||
} |
|
||||||
] |
|
||||||
} |
|
||||||
] |
|
||||||
} |
|
||||||
@ -1,33 +0,0 @@ |
|||||||
{ |
|
||||||
"models": [ |
|
||||||
{ |
|
||||||
"name": "DateInterval", |
|
||||||
"synchronizable": true, |
|
||||||
"observable": true, |
|
||||||
"properties": [ |
|
||||||
{ |
|
||||||
"name": "id", |
|
||||||
"type": "String", |
|
||||||
"defaultValue": "Store.randomId()" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "event", |
|
||||||
"type": "String" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "courtIndex", |
|
||||||
"type": "Int" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "startDate", |
|
||||||
"type": "Date" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "endDate", |
|
||||||
"type": "Date" |
|
||||||
} |
|
||||||
], |
|
||||||
"tokenExemptedMethods": [] |
|
||||||
} |
|
||||||
] |
|
||||||
} |
|
||||||
@ -1,47 +0,0 @@ |
|||||||
|
|
||||||
{ |
|
||||||
"models": [ |
|
||||||
{ |
|
||||||
"name": "DrawLog", |
|
||||||
"synchronizable": true, |
|
||||||
"sideStorable": true, |
|
||||||
"observable": true, |
|
||||||
"relationshipNames": [], |
|
||||||
"properties": [ |
|
||||||
{ |
|
||||||
"name": "id", |
|
||||||
"type": "String", |
|
||||||
"defaultValue": "Store.randomId()" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "tournament", |
|
||||||
"type": "String", |
|
||||||
"foreignKey": "Tournament" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "drawDate", |
|
||||||
"type": "Date", |
|
||||||
"defaultValue": "Date()" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "drawSeed", |
|
||||||
"type": "Int" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "drawMatchIndex", |
|
||||||
"type": "Int" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "drawTeamPosition", |
|
||||||
"type": "TeamPosition", |
|
||||||
"defaultValue": "TeamPosition.one" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "drawType", |
|
||||||
"type": "DrawType", |
|
||||||
"defaultValue": "DrawType.seed" |
|
||||||
} |
|
||||||
] |
|
||||||
} |
|
||||||
] |
|
||||||
} |
|
||||||
@ -1,48 +0,0 @@ |
|||||||
{ |
|
||||||
"models": [ |
|
||||||
{ |
|
||||||
"name": "Event", |
|
||||||
"synchronizable": true, |
|
||||||
"observable": true, |
|
||||||
"properties": [ |
|
||||||
{ |
|
||||||
"name": "id", |
|
||||||
"type": "String", |
|
||||||
"defaultValue": "Store.randomId()" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "creator", |
|
||||||
"type": "String", |
|
||||||
"optional": true, |
|
||||||
"defaultValue": "nil", |
|
||||||
"foreignKey": "CustomUser" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "club", |
|
||||||
"type": "String", |
|
||||||
"optional": true, |
|
||||||
"defaultValue": "nil", |
|
||||||
"foreignKey": "Club" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "creationDate", |
|
||||||
"type": "Date", |
|
||||||
"defaultValue": "Date()" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "name", |
|
||||||
"type": "String", |
|
||||||
"optional": true, |
|
||||||
"defaultValue": "nil" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "tenupId", |
|
||||||
"type": "String", |
|
||||||
"optional": true, |
|
||||||
"defaultValue": "nil" |
|
||||||
} |
|
||||||
], |
|
||||||
"tokenExemptedMethods": [] |
|
||||||
} |
|
||||||
] |
|
||||||
} |
|
||||||
@ -1,53 +0,0 @@ |
|||||||
{ |
|
||||||
"models": [ |
|
||||||
{ |
|
||||||
"name": "GroupStage", |
|
||||||
"synchronizable": true, |
|
||||||
"observable": true, |
|
||||||
"properties": [ |
|
||||||
{ |
|
||||||
"name": "id", |
|
||||||
"type": "String", |
|
||||||
"defaultValue": "Store.randomId()" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "tournament", |
|
||||||
"type": "String", |
|
||||||
"foreignKey": "Tournament" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "index", |
|
||||||
"type": "Int" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "size", |
|
||||||
"type": "Int" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "format", |
|
||||||
"type": "MatchFormat", |
|
||||||
"optional": true, |
|
||||||
"defaultValue": "nil" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "startDate", |
|
||||||
"type": "Date", |
|
||||||
"optional": true, |
|
||||||
"defaultValue": "nil" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "name", |
|
||||||
"type": "String", |
|
||||||
"optional": true, |
|
||||||
"defaultValue": "nil" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "step", |
|
||||||
"type": "Int", |
|
||||||
"defaultValue": "0" |
|
||||||
} |
|
||||||
], |
|
||||||
"tokenExemptedMethods": [] |
|
||||||
} |
|
||||||
] |
|
||||||
} |
|
||||||
@ -1,83 +0,0 @@ |
|||||||
{ |
|
||||||
"models": [ |
|
||||||
{ |
|
||||||
"name": "Match", |
|
||||||
"synchronizable": true, |
|
||||||
"observable": true, |
|
||||||
"tokenExemptedMethods": [], |
|
||||||
"properties": [ |
|
||||||
{ |
|
||||||
"name": "id", |
|
||||||
"type": "String", |
|
||||||
"defaultValue": "Store.randomId()" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "round", |
|
||||||
"type": "String", |
|
||||||
"optional": true, |
|
||||||
"foreignKey": "Round*" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "groupStage", |
|
||||||
"type": "String", |
|
||||||
"optional": true, |
|
||||||
"foreignKey": "GroupStage*" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "startDate", |
|
||||||
"type": "Date", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "endDate", |
|
||||||
"type": "Date", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "index", |
|
||||||
"type": "Int" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "format", |
|
||||||
"type": "MatchFormat", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "servingTeamId", |
|
||||||
"type": "String", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "winningTeamId", |
|
||||||
"type": "String", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "losingTeamId", |
|
||||||
"type": "String", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "name", |
|
||||||
"type": "String", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "disabled", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "courtIndex", |
|
||||||
"type": "Int", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "confirmed", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
} |
|
||||||
] |
|
||||||
} |
|
||||||
] |
|
||||||
} |
|
||||||
@ -1,91 +0,0 @@ |
|||||||
{ |
|
||||||
"models": [ |
|
||||||
{ |
|
||||||
"name": "MatchScheduler", |
|
||||||
"resource_name": "match-scheduler", |
|
||||||
"synchronizable": false, |
|
||||||
"observable": true, |
|
||||||
"tokenExemptedMethods": [], |
|
||||||
"properties": [ |
|
||||||
{ |
|
||||||
"name": "id", |
|
||||||
"type": "String", |
|
||||||
"defaultValue": "Store.randomId()" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "tournament", |
|
||||||
"type": "String", |
|
||||||
"foreignKey": "Tournament" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "timeDifferenceLimit", |
|
||||||
"type": "Int", |
|
||||||
"defaultValue": "5" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "loserBracketRotationDifference", |
|
||||||
"type": "Int", |
|
||||||
"defaultValue": "0" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "upperBracketRotationDifference", |
|
||||||
"type": "Int", |
|
||||||
"defaultValue": "1" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "accountUpperBracketBreakTime", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "true" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "accountLoserBracketBreakTime", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "randomizeCourts", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "true" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "rotationDifferenceIsImportant", |
|
||||||
"type": "Bool" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "shouldHandleUpperRoundSlice", |
|
||||||
"type": "Bool" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "shouldEndRoundBeforeStartingNext", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "true" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "groupStageChunkCount", |
|
||||||
"type": "Int", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "overrideCourtsUnavailability", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "shouldTryToFillUpCourtsAvailable", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "courtsAvailable", |
|
||||||
"type": "Set<Int>", |
|
||||||
"defaultValue": "Set<Int>()" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "simultaneousStart", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "true" |
|
||||||
} |
|
||||||
] |
|
||||||
} |
|
||||||
] |
|
||||||
} |
|
||||||
@ -1,71 +0,0 @@ |
|||||||
{ |
|
||||||
"models": [ |
|
||||||
{ |
|
||||||
"name": "MonthData", |
|
||||||
"resource_name": "month-data", |
|
||||||
"synchronizable": false, |
|
||||||
"observable": true, |
|
||||||
"tokenExemptedMethods": [], |
|
||||||
"properties": [ |
|
||||||
{ |
|
||||||
"name": "id", |
|
||||||
"type": "String", |
|
||||||
"defaultValue": "Store.randomId()" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "monthKey", |
|
||||||
"type": "String" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "creationDate", |
|
||||||
"type": "Date" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "maleUnrankedValue", |
|
||||||
"type": "Int", |
|
||||||
"optional": true, |
|
||||||
"defaultValue": "nil" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "femaleUnrankedValue", |
|
||||||
"type": "Int", |
|
||||||
"optional": true, |
|
||||||
"defaultValue": "nil" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "maleCount", |
|
||||||
"type": "Int", |
|
||||||
"optional": true, |
|
||||||
"defaultValue": "nil" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "femaleCount", |
|
||||||
"type": "Int", |
|
||||||
"optional": true, |
|
||||||
"defaultValue": "nil" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "anonymousCount", |
|
||||||
"type": "Int", |
|
||||||
"optional": true, |
|
||||||
"defaultValue": "nil" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "incompleteMode", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "dataModelIdentifier", |
|
||||||
"type": "String", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "fileModelIdentifier", |
|
||||||
"type": "String", |
|
||||||
"optional": true |
|
||||||
} |
|
||||||
] |
|
||||||
} |
|
||||||
] |
|
||||||
} |
|
||||||
@ -1,138 +0,0 @@ |
|||||||
{ |
|
||||||
"models": [ |
|
||||||
{ |
|
||||||
"name": "PlayerRegistration", |
|
||||||
"synchronizable": true, |
|
||||||
"sideStorable": true, |
|
||||||
"observable": true, |
|
||||||
"relationshipNames": ["teamRegistration"], |
|
||||||
"properties": [ |
|
||||||
{ |
|
||||||
"name": "id", |
|
||||||
"type": "String", |
|
||||||
"defaultValue": "Store.randomId()" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "teamRegistration", |
|
||||||
"type": "String", |
|
||||||
"optional": true, |
|
||||||
"foreignKey": "TeamRegistration" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "firstName", |
|
||||||
"type": "String" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "lastName", |
|
||||||
"type": "String" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "licenceId", |
|
||||||
"type": "String", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "rank", |
|
||||||
"type": "Int", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "paymentType", |
|
||||||
"type": "PlayerPaymentType", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "sex", |
|
||||||
"type": "PlayerSexType", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "tournamentPlayed", |
|
||||||
"type": "Int", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "points", |
|
||||||
"type": "Double", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "clubName", |
|
||||||
"type": "String", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "ligueName", |
|
||||||
"type": "String", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "assimilation", |
|
||||||
"type": "String", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "phoneNumber", |
|
||||||
"type": "String", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "email", |
|
||||||
"type": "String", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "birthdate", |
|
||||||
"type": "String", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "computedRank", |
|
||||||
"type": "Int", |
|
||||||
"defaultValue": "0" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "source", |
|
||||||
"type": "PlayerRegistration.PlayerDataSource", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "hasArrived", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "coach", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "captain", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "registeredOnline", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "timeToConfirm", |
|
||||||
"type": "Date", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "registrationStatus", |
|
||||||
"type": "PlayerRegistration.RegistrationStatus", |
|
||||||
"choices": "PlayerRegistration.RegistrationStatus", |
|
||||||
"defaultValue": "PlayerRegistration.RegistrationStatus.waiting" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "paymentId", |
|
||||||
"type": "String", |
|
||||||
"optional": true |
|
||||||
} |
|
||||||
] |
|
||||||
} |
|
||||||
] |
|
||||||
} |
|
||||||
@ -1,51 +0,0 @@ |
|||||||
{ |
|
||||||
"models": [ |
|
||||||
{ |
|
||||||
"name": "Purchase", |
|
||||||
"synchronizable": true, |
|
||||||
"properties": [ |
|
||||||
{ |
|
||||||
"name": "id", |
|
||||||
"type": "UInt64", |
|
||||||
"defaultValue": "0" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "user", |
|
||||||
"type": "String", |
|
||||||
"defaultValue": "\"\"", |
|
||||||
"encryption": "standard", |
|
||||||
"foreignKey": "CustomUser" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "purchaseDate", |
|
||||||
"type": "Date" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "productId", |
|
||||||
"type": "String", |
|
||||||
"defaultValue": "\"\"" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "quantity", |
|
||||||
"type": "Int", |
|
||||||
"optional": true, |
|
||||||
"defaultValue": "nil" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "revocationDate", |
|
||||||
"type": "Date", |
|
||||||
"optional": true, |
|
||||||
"defaultValue": "nil" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "expirationDate", |
|
||||||
"type": "Date", |
|
||||||
"optional": true, |
|
||||||
"defaultValue": "nil" |
|
||||||
} |
|
||||||
], |
|
||||||
"tokenExemptedMethods": [], |
|
||||||
"relationshipNames": [] |
|
||||||
} |
|
||||||
] |
|
||||||
} |
|
||||||
@ -1,53 +0,0 @@ |
|||||||
{ |
|
||||||
"models": [ |
|
||||||
{ |
|
||||||
"name": "Round", |
|
||||||
"synchronizable": true, |
|
||||||
"sideStorable": true, |
|
||||||
"observable": true, |
|
||||||
"relationshipNames": [], |
|
||||||
"properties": [ |
|
||||||
{ |
|
||||||
"name": "id", |
|
||||||
"type": "String", |
|
||||||
"defaultValue": "Store.randomId()" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "tournament", |
|
||||||
"type": "String", |
|
||||||
"foreignKey": "Tournament" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "index", |
|
||||||
"type": "Int" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "parent", |
|
||||||
"type": "String", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "format", |
|
||||||
"type": "MatchFormat", |
|
||||||
"optional": true, |
|
||||||
"private": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "startDate", |
|
||||||
"type": "Date", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "groupStageLoserBracket", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "loserBracketMode", |
|
||||||
"type": "LoserBracketMode", |
|
||||||
"defaultValue": ".automatic" |
|
||||||
} |
|
||||||
] |
|
||||||
} |
|
||||||
] |
|
||||||
} |
|
||||||
@ -1,118 +0,0 @@ |
|||||||
{ |
|
||||||
"models": [ |
|
||||||
{ |
|
||||||
"name": "TeamRegistration", |
|
||||||
"synchronizable": true, |
|
||||||
"sideStorable": true, |
|
||||||
"observable": true, |
|
||||||
"relationshipNames": [], |
|
||||||
"properties": [ |
|
||||||
{ |
|
||||||
"name": "id", |
|
||||||
"type": "String", |
|
||||||
"defaultValue": "Store.randomId()" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "tournament", |
|
||||||
"type": "String" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "groupStage", |
|
||||||
"type": "String", |
|
||||||
"optional": true, |
|
||||||
"foreignKey": "GroupStage*" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "registrationDate", |
|
||||||
"type": "Date", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "callDate", |
|
||||||
"type": "Date", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "bracketPosition", |
|
||||||
"type": "Int", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "groupStagePosition", |
|
||||||
"type": "Int", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "comment", |
|
||||||
"type": "String", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "source", |
|
||||||
"type": "String", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "sourceValue", |
|
||||||
"type": "String", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "logo", |
|
||||||
"type": "String", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "name", |
|
||||||
"type": "String", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "walkOut", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "wildCardBracket", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "wildCardGroupStage", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "weight", |
|
||||||
"type": "Int", |
|
||||||
"defaultValue": "0" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "lockedWeight", |
|
||||||
"type": "Int", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "confirmationDate", |
|
||||||
"type": "Date", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "qualified", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "finalRanking", |
|
||||||
"type": "Int", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "pointsEarned", |
|
||||||
"type": "Int", |
|
||||||
"optional": true |
|
||||||
} |
|
||||||
] |
|
||||||
} |
|
||||||
] |
|
||||||
} |
|
||||||
@ -1,44 +0,0 @@ |
|||||||
{ |
|
||||||
"models": [ |
|
||||||
{ |
|
||||||
"name": "TeamScore", |
|
||||||
"synchronizable": true, |
|
||||||
"sideStorable": true, |
|
||||||
"observable": true, |
|
||||||
"relationshipNames": ["match"], |
|
||||||
"properties": [ |
|
||||||
{ |
|
||||||
"name": "id", |
|
||||||
"type": "String", |
|
||||||
"defaultValue": "Store.randomId()" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "match", |
|
||||||
"type": "String", |
|
||||||
"foreignKey": "Match*" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "teamRegistration", |
|
||||||
"type": "String", |
|
||||||
"optional": true, |
|
||||||
"foreignKey": "TeamRegistration*" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "score", |
|
||||||
"type": "String", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "walkOut", |
|
||||||
"type": "Int", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "luckyLoser", |
|
||||||
"type": "Int", |
|
||||||
"optional": true |
|
||||||
} |
|
||||||
] |
|
||||||
} |
|
||||||
] |
|
||||||
} |
|
||||||
@ -1,355 +0,0 @@ |
|||||||
{ |
|
||||||
"models": [ |
|
||||||
{ |
|
||||||
"name": "Tournament", |
|
||||||
"synchronizable": true, |
|
||||||
"copyable": true, |
|
||||||
"observable": true, |
|
||||||
"relationshipNames": [], |
|
||||||
"properties": [ |
|
||||||
{ |
|
||||||
"name": "id", |
|
||||||
"type": "String", |
|
||||||
"defaultValue": "Store.randomId()" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "event", |
|
||||||
"type": "String", |
|
||||||
"optional": true, |
|
||||||
"foreignKey": "Event" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "name", |
|
||||||
"type": "String", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "startDate", |
|
||||||
"type": "Date" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "endDate", |
|
||||||
"type": "Date", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "creationDate", |
|
||||||
"type": "Date", |
|
||||||
"private": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "isPrivate", |
|
||||||
"type": "Bool" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "groupStageFormat", |
|
||||||
"type": "MatchFormat", |
|
||||||
"optional": true, |
|
||||||
"private": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "roundFormat", |
|
||||||
"type": "MatchFormat", |
|
||||||
"optional": true, |
|
||||||
"private": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "loserRoundFormat", |
|
||||||
"type": "MatchFormat", |
|
||||||
"optional": true, |
|
||||||
"private": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "groupStageSortMode", |
|
||||||
"type": "GroupStageOrderingMode", |
|
||||||
"defaultValue": "GroupStageOrderingMode.snake" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "groupStageCount", |
|
||||||
"type": "Int", |
|
||||||
"defaultValue": "4" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "rankSourceDate", |
|
||||||
"type": "Date", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "dayDuration", |
|
||||||
"type": "Int", |
|
||||||
"defaultValue": "1" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "teamCount", |
|
||||||
"type": "Int", |
|
||||||
"defaultValue": "24" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "teamSorting", |
|
||||||
"type": "TeamSortingType", |
|
||||||
"defaultValue": "TeamSortingType.inscriptionDate" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "federalCategory", |
|
||||||
"type": "TournamentCategory", |
|
||||||
"defaultValue": "TournamentCategory.men" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "federalLevelCategory", |
|
||||||
"type": "TournamentLevel", |
|
||||||
"defaultValue": "TournamentLevel.p100" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "federalAgeCategory", |
|
||||||
"type": "FederalTournamentAge", |
|
||||||
"defaultValue": "FederalTournamentAge.senior" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "closedRegistrationDate", |
|
||||||
"type": "Date", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "groupStageAdditionalQualified", |
|
||||||
"type": "Int", |
|
||||||
"defaultValue": "0" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "courtCount", |
|
||||||
"type": "Int", |
|
||||||
"defaultValue": "2" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "prioritizeClubMembers", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "qualifiedPerGroupStage", |
|
||||||
"type": "Int", |
|
||||||
"defaultValue": "1" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "teamsPerGroupStage", |
|
||||||
"type": "Int", |
|
||||||
"defaultValue": "4" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "entryFee", |
|
||||||
"type": "Double", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "payment", |
|
||||||
"type": "TournamentPayment", |
|
||||||
"optional": true, |
|
||||||
"defaultValue": "nil", |
|
||||||
"encryption": "tournament_payment", |
|
||||||
"codingKey": "globalId" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "additionalEstimationDuration", |
|
||||||
"type": "Int", |
|
||||||
"defaultValue": "0" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "isDeleted", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "isCanceled", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false", |
|
||||||
"encryption": "tournament_iscanceled", |
|
||||||
"codingKey": "localId" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "publishTeams", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "publishSummons", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "publishGroupStages", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "publishBrackets", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "shouldVerifyGroupStage", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "shouldVerifyBracket", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "hideTeamsWeight", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "publishTournament", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "hidePointsEarned", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "publishRankings", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "loserBracketMode", |
|
||||||
"type": "LoserBracketMode", |
|
||||||
"defaultValue": ".automatic" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "initialSeedRound", |
|
||||||
"type": "Int", |
|
||||||
"defaultValue": "0" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "initialSeedCount", |
|
||||||
"type": "Int", |
|
||||||
"defaultValue": "0" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "enableOnlineRegistration", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "registrationDateLimit", |
|
||||||
"type": "Date", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "openingRegistrationDate", |
|
||||||
"type": "Date", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "waitingListLimit", |
|
||||||
"type": "Int", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "accountIsRequired", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "true" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "licenseIsRequired", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "true" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "minimumPlayerPerTeam", |
|
||||||
"type": "Int", |
|
||||||
"defaultValue": "2" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "maximumPlayerPerTeam", |
|
||||||
"type": "Int", |
|
||||||
"defaultValue": "2" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "information", |
|
||||||
"type": "String", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "umpireCustomMail", |
|
||||||
"type": "String", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "umpireCustomContact", |
|
||||||
"type": "String", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "umpireCustomPhone", |
|
||||||
"type": "String", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "hideUmpireMail", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "hideUmpirePhone", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "true" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "disableRankingFederalRuling", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "teamCountLimit", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "true" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "enableOnlinePayment", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "onlinePaymentIsMandatory", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "enableOnlinePaymentRefund", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "refundDateLimit", |
|
||||||
"type": "Date", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "stripeAccountId", |
|
||||||
"type": "String", |
|
||||||
"optional": true |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "enableTimeToConfirm", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "isCorporateTournament", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "isTemplate", |
|
||||||
"type": "Bool", |
|
||||||
"defaultValue": "false" |
|
||||||
} |
|
||||||
] |
|
||||||
} |
|
||||||
] |
|
||||||
} |
|
||||||
@ -1,593 +0,0 @@ |
|||||||
import json |
|
||||||
import re |
|
||||||
import os |
|
||||||
from pathlib import Path |
|
||||||
from typing import Dict, List, Any |
|
||||||
import argparse |
|
||||||
import sys |
|
||||||
import logging |
|
||||||
from datetime import datetime |
|
||||||
import inflect |
|
||||||
|
|
||||||
class SwiftModelGenerator: |
|
||||||
def __init__(self, json_data: Dict[str, Any]): |
|
||||||
self.data = json_data |
|
||||||
self.pluralizer = inflect.engine() |
|
||||||
|
|
||||||
def generate_model(self, model_data: Dict[str, Any]) -> str: |
|
||||||
model_name = model_data["name"] |
|
||||||
is_sync = model_data.get("synchronizable", False) |
|
||||||
is_observable = model_data.get("observable", False) |
|
||||||
properties = model_data["properties"] |
|
||||||
|
|
||||||
# Get protocol specific configurations |
|
||||||
resource = self.make_resource_name(model_name) |
|
||||||
resource_name = model_data.get("resource_name", resource) |
|
||||||
token_exempted = model_data.get("tokenExemptedMethods", []) |
|
||||||
copy_server_response = model_data.get("copy_server_response", "false") |
|
||||||
|
|
||||||
lines = ["// Generated by SwiftModelGenerator", "// Do not modify this file manually", ""] |
|
||||||
|
|
||||||
# Import statement |
|
||||||
lines.append("import Foundation") |
|
||||||
lines.append("import LeStorage") |
|
||||||
if is_observable: |
|
||||||
lines.append("import SwiftUI") |
|
||||||
lines.append("") |
|
||||||
|
|
||||||
# Class declaration |
|
||||||
if is_observable: |
|
||||||
lines.append("@Observable") |
|
||||||
protocol = "SyncedStorable" if is_sync else "Storable" |
|
||||||
base_class = "SyncedModelObject" if is_sync else "BaseModelObject" |
|
||||||
lines.append(f"class Base{model_name}: {base_class}, {protocol} {{") |
|
||||||
lines.append("") |
|
||||||
|
|
||||||
# Add SyncedStorable protocol requirements |
|
||||||
lines.extend(self._generate_protocol_requirements(resource_name, token_exempted, copy_server_response)) |
|
||||||
lines.append("") |
|
||||||
|
|
||||||
# Properties |
|
||||||
for prop in properties: |
|
||||||
swift_type = prop["type"] |
|
||||||
if prop.get("optional", False): |
|
||||||
swift_type += "?" |
|
||||||
default_value = prop.get("defaultValue", "nil" if prop.get("optional", False) else self._get_default_value(swift_type)) |
|
||||||
lines.append(f" var {prop['name']}: {swift_type} = {default_value}") |
|
||||||
|
|
||||||
lines.append("") |
|
||||||
|
|
||||||
# Add constructor |
|
||||||
lines.extend(self._generate_constructor(model_name, properties)) |
|
||||||
lines.append("") |
|
||||||
|
|
||||||
# CodingKeys |
|
||||||
lines.extend(self._generate_coding_keys(model_name, properties, is_observable)) |
|
||||||
lines.append("") |
|
||||||
|
|
||||||
# Encryption methods |
|
||||||
encrypted_props = [p for p in properties if "encryption" in p] |
|
||||||
if encrypted_props: |
|
||||||
lines.extend(self._generate_encryption_methods(properties)) |
|
||||||
lines.append("") |
|
||||||
|
|
||||||
# Codable implementation |
|
||||||
lines.extend(self._generate_decoder(model_name, properties, is_observable, is_sync)) |
|
||||||
lines.append("") |
|
||||||
lines.extend(self._generate_encoder(properties, is_observable, is_sync)) |
|
||||||
lines.append("") |
|
||||||
|
|
||||||
# Foreign Key convenience |
|
||||||
foreign_key_methods = self._generate_foreign_key_methods(properties) |
|
||||||
if foreign_key_methods: |
|
||||||
lines.extend(foreign_key_methods) |
|
||||||
# lines.append("") |
|
||||||
|
|
||||||
# Copy method |
|
||||||
lines.extend(self._generate_copy_method(model_name, properties)) |
|
||||||
lines.append("") |
|
||||||
|
|
||||||
# Add relationships function |
|
||||||
lines.extend(self._generate_relationships(model_name, properties)) |
|
||||||
lines.append("") |
|
||||||
|
|
||||||
lines.append("}") |
|
||||||
return "\n".join(lines) |
|
||||||
|
|
||||||
def _generate_constructor(self, model_name: str, properties: List[Dict[str, Any]]) -> List[str]: |
|
||||||
"""Generate a constructor with all properties as parameters with default values.""" |
|
||||||
|
|
||||||
lines = [" init("] |
|
||||||
|
|
||||||
# Generate parameter list |
|
||||||
params = [] |
|
||||||
for prop in properties: |
|
||||||
name = prop['name'] |
|
||||||
type_name = prop['type'] |
|
||||||
is_optional = prop.get("optional", False) |
|
||||||
|
|
||||||
# Always include a default value |
|
||||||
default_value = prop.get("defaultValue") |
|
||||||
if default_value is None: |
|
||||||
if is_optional: |
|
||||||
default_value = "nil" |
|
||||||
else: |
|
||||||
default_value = self._get_default_value(type_name) |
|
||||||
|
|
||||||
# Format the parameter with its type and default value |
|
||||||
param = f" {name}: {type_name}" |
|
||||||
if is_optional: |
|
||||||
param += "?" |
|
||||||
param += f" = {default_value}" |
|
||||||
params.append(param) |
|
||||||
|
|
||||||
# Join parameters with commas |
|
||||||
lines.extend([f"{param}," for param in params[:-1]]) |
|
||||||
lines.append(f"{params[-1]}") # Last parameter without comma |
|
||||||
|
|
||||||
# Constructor body |
|
||||||
lines.extend([ |
|
||||||
" ) {", |
|
||||||
" super.init()", |
|
||||||
]) |
|
||||||
|
|
||||||
# Property assignments |
|
||||||
for prop in properties: |
|
||||||
name = prop['name'] |
|
||||||
lines.append(f" self.{name} = {name}") |
|
||||||
|
|
||||||
lines.append(" }") |
|
||||||
|
|
||||||
lines.extend([ |
|
||||||
" required public override init() {", |
|
||||||
" super.init()", |
|
||||||
" }", |
|
||||||
|
|
||||||
]) |
|
||||||
|
|
||||||
|
|
||||||
return lines |
|
||||||
|
|
||||||
def _generate_foreign_key_methods(self, properties: List[Dict[str, Any]]) -> List[str]: |
|
||||||
lines = [] |
|
||||||
for prop in properties: |
|
||||||
if "foreignKey" in prop: |
|
||||||
foreign_key = prop["foreignKey"] |
|
||||||
prop_name = prop["name"] |
|
||||||
method_name = f"{prop_name}Value" |
|
||||||
is_optional = prop.get("optional", False) |
|
||||||
|
|
||||||
lines.extend([f" func {method_name}() -> {foreign_key.rstrip('*')}? {{"]) |
|
||||||
|
|
||||||
if is_optional: |
|
||||||
lines.extend([ |
|
||||||
f" guard let {prop_name} = self.{prop_name} else {{ return nil }}" |
|
||||||
]) |
|
||||||
|
|
||||||
if foreign_key.endswith("*"): |
|
||||||
foreign_key = foreign_key[:-1] # Remove the asterisk |
|
||||||
lines.extend([ |
|
||||||
f" return self.store?.findById({prop_name})" |
|
||||||
]) |
|
||||||
else: |
|
||||||
lines.extend([ |
|
||||||
f" return Store.main.findById({prop_name})" |
|
||||||
]) |
|
||||||
|
|
||||||
lines.extend([" }", ""]) # Close the method and add a blank line |
|
||||||
return lines |
|
||||||
|
|
||||||
def _generate_coding_keys(self, model_name: str, properties: List[Dict[str, Any]], is_observable: bool) -> List[str]: |
|
||||||
lines = [" enum CodingKeys: String, CodingKey {"] |
|
||||||
|
|
||||||
if model_name == 'Tournament': |
|
||||||
lines.append(" case isCanceled = \"isCanceled\"") |
|
||||||
lines.append(" case payment = \"payment\"") |
|
||||||
|
|
||||||
for prop in properties: |
|
||||||
name = prop['name'] |
|
||||||
# Add underscore prefix to case name if observable |
|
||||||
case_name = f"_{name}" if is_observable else name |
|
||||||
|
|
||||||
# Use custom codingKey if provided, otherwise use the property name |
|
||||||
coding_key_value = prop.get("codingKey", name) |
|
||||||
|
|
||||||
lines.append(f" case {case_name} = \"{coding_key_value}\"") |
|
||||||
|
|
||||||
lines.append(" }") |
|
||||||
return lines |
|
||||||
|
|
||||||
def _generate_encryption_methods(self, properties: List[Dict[str, Any]]) -> List[str]: |
|
||||||
lines = [] |
|
||||||
for prop in properties: |
|
||||||
if "encryption" in prop: |
|
||||||
name = prop['name'] |
|
||||||
enc_type = prop['encryption'] |
|
||||||
if enc_type == "tournament_payment": |
|
||||||
lines.extend([ |
|
||||||
f" private static func _decode{name.capitalize()}(container: KeyedDecodingContainer<CodingKeys>) throws -> TournamentPayment? {{", |
|
||||||
f" var data = try container.decodeIfPresent(Data.self, forKey: ._{name})", |
|
||||||
" if data == nil {", |
|
||||||
" data = try container.decodeIfPresent(Data.self, forKey: .payment)", |
|
||||||
" }", |
|
||||||
" ", |
|
||||||
" if let data {", |
|
||||||
" do {", |
|
||||||
" let decoded: String = try data.decryptData(pass: CryptoKey.pass.rawValue)", |
|
||||||
" let sequence = decoded.compactMap { NumberFormatter.standard.number(from: String($0))?.intValue }", |
|
||||||
" return TournamentPayment(rawValue: sequence[18])", |
|
||||||
" } catch {", |
|
||||||
" Logger.error(error)", |
|
||||||
" }", |
|
||||||
" }", |
|
||||||
" return nil", |
|
||||||
" }", |
|
||||||
"", |
|
||||||
f" private func _encode{name.capitalize()}(container: inout KeyedEncodingContainer<CodingKeys>) throws {{", |
|
||||||
f" guard let {name} else {{", |
|
||||||
f" try container.encodeNil(forKey: ._{name})", |
|
||||||
" return", |
|
||||||
" }", |
|
||||||
" ", |
|
||||||
" let max: Int = TournamentPayment.allCases.count", |
|
||||||
" var sequence = (1...18).map { _ in Int.random(in: (0..<max)) }", |
|
||||||
f" sequence.append({name}.rawValue)", |
|
||||||
" sequence.append(contentsOf: (1...13).map { _ in Int.random(in: (0..<max ))} )", |
|
||||||
"", |
|
||||||
" let stringCombo: [String] = sequence.map { $0.formatted() }", |
|
||||||
" let joined: String = stringCombo.joined(separator: \"\")", |
|
||||||
" if let data = joined.data(using: .utf8) {", |
|
||||||
" let encryped: Data = try data.encrypt(pass: CryptoKey.pass.rawValue)", |
|
||||||
f" try container.encodeIfPresent(encryped, forKey: ._{name})", |
|
||||||
" }", |
|
||||||
" }" |
|
||||||
]) |
|
||||||
elif enc_type == "tournament_iscanceled": |
|
||||||
lines.extend([ |
|
||||||
f" private static func _decode{name.capitalize()}(container: KeyedDecodingContainer<CodingKeys>) throws -> Bool {{", |
|
||||||
f" var data = try container.decodeIfPresent(Data.self, forKey: ._{name})", |
|
||||||
" if data == nil {", |
|
||||||
" data = try container.decodeIfPresent(Data.self, forKey: .isCanceled)", |
|
||||||
" }", |
|
||||||
" if let data {", |
|
||||||
" do {", |
|
||||||
" let decoded: String = try data.decryptData(pass: CryptoKey.pass.rawValue)", |
|
||||||
" let sequence = decoded.compactMap { NumberFormatter.standard.number(from: String($0))?.intValue }", |
|
||||||
" return Bool.decodeInt(sequence[18])", |
|
||||||
" } catch {", |
|
||||||
" Logger.error(error)", |
|
||||||
" }", |
|
||||||
" }", |
|
||||||
" return false", |
|
||||||
" }", |
|
||||||
"", |
|
||||||
f" private func _encode{name.capitalize()}(container: inout KeyedEncodingContainer<CodingKeys>) throws {{", |
|
||||||
" let max: Int = 9", |
|
||||||
" var sequence = (1...18).map { _ in Int.random(in: (0...max)) }", |
|
||||||
f" sequence.append(self.{name}.encodedValue)", |
|
||||||
" sequence.append(contentsOf: (1...13).map { _ in Int.random(in: (0...max ))} )", |
|
||||||
" ", |
|
||||||
" let stringCombo: [String] = sequence.map { $0.formatted() }", |
|
||||||
" let joined: String = stringCombo.joined(separator: \"\")", |
|
||||||
" if let data = joined.data(using: .utf8) {", |
|
||||||
" let encryped: Data = try data.encrypt(pass: CryptoKey.pass.rawValue)", |
|
||||||
f" try container.encode(encryped, forKey: ._{name})", |
|
||||||
" }", |
|
||||||
" }" |
|
||||||
]) |
|
||||||
return lines |
|
||||||
|
|
||||||
def _generate_decoder(self, model_name: str, properties: List[Dict[str, Any]], is_observable: bool, is_sync: bool = False) -> List[str]: |
|
||||||
lines = [" required init(from decoder: Decoder) throws {", |
|
||||||
" let container = try decoder.container(keyedBy: CodingKeys.self)"] |
|
||||||
|
|
||||||
for prop in properties: |
|
||||||
name = prop['name'] |
|
||||||
type_name = prop['type'] |
|
||||||
is_optional = prop.get("optional", False) |
|
||||||
default_value = prop.get("defaultValue", "nil" if is_optional else self._get_default_value(type_name)) |
|
||||||
option = prop.get("option") |
|
||||||
|
|
||||||
# Use the correct case reference based on observable flag |
|
||||||
case_ref = f"_{name}" if is_observable else name |
|
||||||
|
|
||||||
if "encryption" in prop: |
|
||||||
enc_type = prop['encryption'] |
|
||||||
if enc_type == "standard": |
|
||||||
if is_optional: |
|
||||||
lines.append(f" self.{name} = try container.decodeEncryptedIfPresent(key: .{case_ref})") |
|
||||||
else: |
|
||||||
lines.append(f" self.{name} = try container.decodeEncrypted(key: .{case_ref})") |
|
||||||
elif enc_type in ["tournament_payment", "tournament_iscanceled"]: |
|
||||||
lines.append(f" self.{name} = try Self._decode{name.capitalize()}(container: container)") |
|
||||||
else: |
|
||||||
# Handle Date with fractional option |
|
||||||
if type_name == "Date" and option == "fractional": |
|
||||||
if is_optional: |
|
||||||
lines.append(f" if let dateString = try container.decodeIfPresent(String.self, forKey: .{case_ref}) {{") |
|
||||||
lines.append(f" self.{name} = Date.iso8601FractionalFormatter.date(from: dateString)") |
|
||||||
lines.append(" } else {") |
|
||||||
lines.append(f" self.{name} = {default_value}") |
|
||||||
lines.append(" }") |
|
||||||
else: |
|
||||||
lines.append(f" let dateString = try container.decode(String.self, forKey: .{case_ref})") |
|
||||||
lines.append(f" self.{name} = Date.iso8601FractionalFormatter.date(from: dateString) ?? {default_value}") |
|
||||||
else: |
|
||||||
lines.append(f" self.{name} = try container.decodeIfPresent({type_name}.self, forKey: .{case_ref}) ?? {default_value}") |
|
||||||
|
|
||||||
lines.append(" try super.init(from: decoder)") |
|
||||||
|
|
||||||
lines.append(" }") |
|
||||||
return lines |
|
||||||
|
|
||||||
def _generate_encoder(self, properties: List[Dict[str, Any]], is_observable: bool, is_sync: bool = False) -> List[str]: |
|
||||||
lines = [" override func encode(to encoder: Encoder) throws {", |
|
||||||
" var container = encoder.container(keyedBy: CodingKeys.self)"] |
|
||||||
|
|
||||||
for prop in properties: |
|
||||||
name = prop['name'] |
|
||||||
type_name = prop['type'] |
|
||||||
is_optional = prop.get('optional', False) |
|
||||||
option = prop.get("option") |
|
||||||
|
|
||||||
# Use the correct case reference based on observable flag |
|
||||||
case_ref = f"_{name}" if is_observable else name |
|
||||||
|
|
||||||
if "encryption" in prop: |
|
||||||
enc_type = prop['encryption'] |
|
||||||
if enc_type == "standard": |
|
||||||
if is_optional: |
|
||||||
lines.append(f" try container.encodeAndEncryptIfPresent(self.{name}?.data(using: .utf8), forKey: .{case_ref})") |
|
||||||
else: |
|
||||||
lines.append(f" try container.encodeAndEncryptIfPresent(self.{name}.data(using: .utf8), forKey: .{case_ref})") |
|
||||||
elif enc_type in ["tournament_payment", "tournament_iscanceled"]: |
|
||||||
lines.append(f" try _encode{name.capitalize()}(container: &container)") |
|
||||||
else: |
|
||||||
if type_name == "Date" and option == "fractional": |
|
||||||
if is_optional: |
|
||||||
lines.append(f" if let date = self.{name} {{") |
|
||||||
lines.append(f" try container.encode(Date.iso8601FractionalFormatter.string(from: date), forKey: .{case_ref})") |
|
||||||
lines.append(" } else {") |
|
||||||
lines.append(f" try container.encodeNil(forKey: .{case_ref})") |
|
||||||
lines.append(" }") |
|
||||||
else: |
|
||||||
lines.append(f" try container.encode(Date.iso8601FractionalFormatter.string(from: self.{name}), forKey: .{case_ref})") |
|
||||||
else: |
|
||||||
lines.append(f" try container.encode(self.{name}, forKey: .{case_ref})") |
|
||||||
|
|
||||||
lines.append(" try super.encode(to: encoder)") |
|
||||||
lines.append(" }") |
|
||||||
return lines |
|
||||||
|
|
||||||
def _generate_copy_method(self, model_name: str, properties: List[Dict[str, Any]]) -> List[str]: |
|
||||||
|
|
||||||
model_variable = model_name.lower() |
|
||||||
lines = [f" func copy(from other: any Storable) {{"] |
|
||||||
lines.append(f" guard let {model_variable} = other as? Base{model_name} else {{ return }}") |
|
||||||
|
|
||||||
for prop in properties: |
|
||||||
name = prop['name'] |
|
||||||
lines.append(f" self.{name} = {model_variable}.{name}") |
|
||||||
|
|
||||||
lines.append(" }") |
|
||||||
return lines |
|
||||||
|
|
||||||
def _generate_protocol_requirements(self, resource_name: str, token_exempted: List[str], copy_server_response: str) -> List[str]: |
|
||||||
"""Generate the static functions required by SyncedStorable protocol.""" |
|
||||||
# Convert HTTP methods to proper format |
|
||||||
formatted_methods = [f".{method.lower()}" for method in token_exempted] |
|
||||||
methods_str = ", ".join(formatted_methods) if formatted_methods else "" |
|
||||||
|
|
||||||
return [ |
|
||||||
f" static func resourceName() -> String {{ return \"{resource_name}\" }}", |
|
||||||
f" static func tokenExemptedMethods() -> [HTTPMethod] {{ return [{methods_str}] }}", |
|
||||||
f" static var copyServerResponse: Bool = {copy_server_response}", |
|
||||||
] |
|
||||||
|
|
||||||
def _generate_relationships(self, model_name, properties: List[Dict[str, Any]]) -> List[str]: |
|
||||||
# Find all properties with foreign keys |
|
||||||
foreign_key_props = [p for p in properties if "foreignKey" in p] |
|
||||||
|
|
||||||
if not foreign_key_props: |
|
||||||
# If no foreign keys, return empty array |
|
||||||
return [ |
|
||||||
" static func relationships() -> [Relationship] {", |
|
||||||
" return []", |
|
||||||
" }" |
|
||||||
] |
|
||||||
|
|
||||||
lines = [ |
|
||||||
" static func relationships() -> [Relationship] {", |
|
||||||
" return [" |
|
||||||
] |
|
||||||
|
|
||||||
# Generate relationship entries |
|
||||||
for prop in foreign_key_props: |
|
||||||
name = prop["name"] |
|
||||||
foreign_key = prop["foreignKey"].rstrip('*') # Remove asterisk if present |
|
||||||
lines.append(f" Relationship(type: {foreign_key}.self, keyPath: \\Base{model_name}.{name}),") |
|
||||||
|
|
||||||
# Close the array and function |
|
||||||
lines.extend([ |
|
||||||
" ]", |
|
||||||
" }" |
|
||||||
]) |
|
||||||
|
|
||||||
return lines |
|
||||||
|
|
||||||
def _get_default_value(self, type_name: str) -> str: |
|
||||||
"""Get default value for non-optional types""" |
|
||||||
if "String" in type_name: |
|
||||||
return "\"\"" |
|
||||||
elif "Int" in type_name: |
|
||||||
return "0" |
|
||||||
elif "Double" in type_name: |
|
||||||
return "0.0" |
|
||||||
elif "Bool" in type_name: |
|
||||||
return "false" |
|
||||||
elif "Date" in type_name: |
|
||||||
return "Date()" |
|
||||||
else: |
|
||||||
return "nil" # For any other type |
|
||||||
|
|
||||||
def get_output_filename(self, model_name: str) -> str: |
|
||||||
"""Generate the output filename for a model.""" |
|
||||||
return f"Base{model_name}.swift" |
|
||||||
|
|
||||||
def make_resource_name(self, text): |
|
||||||
p = inflect.engine() |
|
||||||
# Split camelCase into words |
|
||||||
words = re.findall('[A-Z][^A-Z]*', text) |
|
||||||
|
|
||||||
if not words: |
|
||||||
words = [text] |
|
||||||
|
|
||||||
words = [word.lower() for word in words] |
|
||||||
words[-1] = p.plural(words[-1]) |
|
||||||
return '-'.join(words) |
|
||||||
|
|
||||||
def process_directory(input_dir: str, output_dir: str, logger: logging.Logger, dry_run: bool = False) -> int: |
|
||||||
"""Process all JSON files in the input directory and generate Swift models.""" |
|
||||||
try: |
|
||||||
input_path = validate_directory(input_dir) |
|
||||||
if not dry_run: |
|
||||||
output_path = validate_directory(output_dir, create=True) |
|
||||||
|
|
||||||
json_files = list(input_path.glob("*.json")) |
|
||||||
if not json_files: |
|
||||||
logger.warning(f"No JSON files found in '{input_dir}'") |
|
||||||
return 0 |
|
||||||
|
|
||||||
logger.info(f"Found {len(json_files)} JSON files to process") |
|
||||||
successful_files = 0 |
|
||||||
|
|
||||||
for json_file in json_files: |
|
||||||
try: |
|
||||||
with open(json_file, 'r') as f: |
|
||||||
json_data = json.load(f) |
|
||||||
|
|
||||||
generator = SwiftModelGenerator(json_data) |
|
||||||
|
|
||||||
# Generate each model in the JSON file |
|
||||||
for model in json_data["models"]: |
|
||||||
model_name = model["name"] |
|
||||||
swift_code = generator.generate_model(model) |
|
||||||
|
|
||||||
if dry_run: |
|
||||||
logger.info(f"Would generate Base{model_name}.swift") |
|
||||||
continue |
|
||||||
|
|
||||||
# Write to output file with Base prefix |
|
||||||
output_file = output_path / generator.get_output_filename(model_name) |
|
||||||
with open(output_file, 'w') as f: |
|
||||||
f.write(swift_code) |
|
||||||
|
|
||||||
logger.info(f"Generated Base{model_name}.swift") |
|
||||||
|
|
||||||
successful_files += 1 |
|
||||||
|
|
||||||
except json.JSONDecodeError as e: |
|
||||||
logger.error(f"Error parsing {json_file.name}: {e}") |
|
||||||
except KeyError as e: |
|
||||||
logger.error(f"Missing required key in {json_file.name}: {e}") |
|
||||||
except Exception as e: |
|
||||||
logger.error(f"Error processing {json_file.name}: {e}") |
|
||||||
|
|
||||||
return successful_files |
|
||||||
|
|
||||||
except Exception as e: |
|
||||||
logger.error(f"Fatal error: {e}") |
|
||||||
return 0 |
|
||||||
|
|
||||||
|
|
||||||
def setup_logging(verbose: bool) -> logging.Logger: |
|
||||||
"""Configure logging based on verbosity level.""" |
|
||||||
logger = logging.getLogger('SwiftModelGenerator') |
|
||||||
handler = logging.StreamHandler() |
|
||||||
|
|
||||||
if verbose: |
|
||||||
logger.setLevel(logging.DEBUG) |
|
||||||
handler.setFormatter(logging.Formatter( |
|
||||||
'%(asctime)s - %(levelname)s - %(message)s' |
|
||||||
)) |
|
||||||
else: |
|
||||||
logger.setLevel(logging.INFO) |
|
||||||
handler.setFormatter(logging.Formatter('%(message)s')) |
|
||||||
|
|
||||||
logger.addHandler(handler) |
|
||||||
return logger |
|
||||||
|
|
||||||
def validate_directory(path: str, create: bool = False) -> Path: |
|
||||||
"""Validate and optionally create a directory.""" |
|
||||||
dir_path = Path(path) |
|
||||||
if dir_path.exists(): |
|
||||||
if not dir_path.is_dir(): |
|
||||||
raise ValueError(f"'{path}' exists but is not a directory") |
|
||||||
elif create: |
|
||||||
dir_path.mkdir(parents=True) |
|
||||||
else: |
|
||||||
raise ValueError(f"Directory '{path}' does not exist") |
|
||||||
return dir_path |
|
||||||
|
|
||||||
def main(): |
|
||||||
parser = argparse.ArgumentParser( |
|
||||||
description="Generate Swift model classes from JSON definitions", |
|
||||||
epilog="Example: %(prog)s -i ./model_definitions -o ../MyProject/Models" |
|
||||||
) |
|
||||||
|
|
||||||
parser.add_argument( |
|
||||||
"-i", "--input-dir", |
|
||||||
required=True, |
|
||||||
help="Directory containing JSON model definitions" |
|
||||||
) |
|
||||||
|
|
||||||
parser.add_argument( |
|
||||||
"-o", "--output-dir", |
|
||||||
required=True, |
|
||||||
help="Directory where Swift files will be generated" |
|
||||||
) |
|
||||||
|
|
||||||
parser.add_argument( |
|
||||||
"-v", "--verbose", |
|
||||||
action="store_true", |
|
||||||
help="Enable verbose output" |
|
||||||
) |
|
||||||
|
|
||||||
parser.add_argument( |
|
||||||
"--dry-run", |
|
||||||
action="store_true", |
|
||||||
help="Show what would be generated without actually creating files" |
|
||||||
) |
|
||||||
|
|
||||||
parser.add_argument( |
|
||||||
"--version", |
|
||||||
action="version", |
|
||||||
version="%(prog)s 1.0.0" |
|
||||||
) |
|
||||||
|
|
||||||
args = parser.parse_args() |
|
||||||
|
|
||||||
# Setup logging |
|
||||||
logger = setup_logging(args.verbose) |
|
||||||
|
|
||||||
# Process the directories |
|
||||||
start_time = datetime.now() |
|
||||||
logger.info("Starting model generation...") |
|
||||||
|
|
||||||
successful_files = process_directory( |
|
||||||
args.input_dir, |
|
||||||
args.output_dir, |
|
||||||
logger, |
|
||||||
args.dry_run |
|
||||||
) |
|
||||||
|
|
||||||
# Report results |
|
||||||
duration = datetime.now() - start_time |
|
||||||
logger.info(f"\nGeneration completed in {duration.total_seconds():.2f} seconds") |
|
||||||
logger.info(f"Successfully processed {successful_files} files") |
|
||||||
|
|
||||||
# Return appropriate exit code |
|
||||||
sys.exit(0 if successful_files > 0 else 1) |
|
||||||
|
|
||||||
if __name__ == "__main__": |
|
||||||
main() |
|
||||||
@ -1,684 +0,0 @@ |
|||||||
// |
|
||||||
// GroupStage.swift |
|
||||||
// Padel Tournament |
|
||||||
// |
|
||||||
// Created by razmig on 10/03/2024. |
|
||||||
// |
|
||||||
|
|
||||||
import Foundation |
|
||||||
import LeStorage |
|
||||||
import Algorithms |
|
||||||
import SwiftUI |
|
||||||
|
|
||||||
@Observable |
|
||||||
final class GroupStage: BaseGroupStage, SideStorable { |
|
||||||
|
|
||||||
var matchFormat: MatchFormat { |
|
||||||
get { |
|
||||||
format ?? .defaultFormatForMatchType(.groupStage) |
|
||||||
} |
|
||||||
set { |
|
||||||
format = newValue |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
var tournamentStore: TournamentStore? { |
|
||||||
return TournamentLibrary.shared.store(tournamentId: self.tournament) |
|
||||||
} |
|
||||||
|
|
||||||
// MARK: - Computed dependencies |
|
||||||
|
|
||||||
func _matches() -> [Match] { |
|
||||||
guard let tournamentStore = self.tournamentStore else { return [] } |
|
||||||
return tournamentStore.matches.filter { $0.groupStage == self.id }.sorted(by: \.index) |
|
||||||
// Store.main.filter { $0.groupStage == self.id } |
|
||||||
} |
|
||||||
|
|
||||||
func tournamentObject() -> Tournament? { |
|
||||||
Store.main.findById(self.tournament) |
|
||||||
} |
|
||||||
|
|
||||||
// MARK: - |
|
||||||
|
|
||||||
func teamAt(groupStagePosition: Int) -> TeamRegistration? { |
|
||||||
if step > 0 { |
|
||||||
return teams().first(where: { $0.groupStagePositionAtStep(step) == groupStagePosition }) |
|
||||||
} |
|
||||||
return teams().first(where: { $0.groupStagePosition == groupStagePosition }) |
|
||||||
} |
|
||||||
|
|
||||||
func groupStageTitle(_ displayStyle: DisplayStyle = .wide) -> String { |
|
||||||
if let name { return name } |
|
||||||
|
|
||||||
var stepLabel = "" |
|
||||||
if step > 0 { |
|
||||||
stepLabel = " (" + (step + 1).ordinalFormatted(feminine: true) + " phase)" |
|
||||||
} |
|
||||||
|
|
||||||
switch displayStyle { |
|
||||||
case .title: |
|
||||||
return "Poule \(index + 1)" + stepLabel |
|
||||||
case .wide: |
|
||||||
return "Poule \(index + 1)" |
|
||||||
case .short: |
|
||||||
return "#\(index + 1)" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
var computedOrder: Int { |
|
||||||
index + step * 100 |
|
||||||
} |
|
||||||
|
|
||||||
func isRunning() -> Bool { // at least a match has started |
|
||||||
_matches().anySatisfy({ $0.isRunning() }) |
|
||||||
} |
|
||||||
|
|
||||||
func hasStarted() -> Bool { // meaning at least one match is over |
|
||||||
_matches().filter { $0.hasEnded() }.isEmpty == false |
|
||||||
} |
|
||||||
|
|
||||||
func hasEnded() -> Bool { |
|
||||||
let _matches = _matches() |
|
||||||
if _matches.isEmpty { return false } |
|
||||||
//guard teams().count == size else { return false } |
|
||||||
return _matches.anySatisfy { $0.hasEnded() == false } == false |
|
||||||
} |
|
||||||
|
|
||||||
fileprivate func _createMatch(index: Int) -> Match { |
|
||||||
let match: Match = Match(groupStage: self.id, |
|
||||||
index: index, |
|
||||||
format: self.matchFormat, |
|
||||||
name: self.localizedMatchUpLabel(for: index)) |
|
||||||
match.store = self.store |
|
||||||
// print("_createMatch(index)", index) |
|
||||||
return match |
|
||||||
} |
|
||||||
|
|
||||||
func removeReturnMatches(onlyLast: Bool = false) { |
|
||||||
|
|
||||||
var returnMatches = _matches().filter({ $0.index >= matchCount }) |
|
||||||
if onlyLast { |
|
||||||
let matchPhaseCount = matchPhaseCount - 1 |
|
||||||
returnMatches = returnMatches.filter({ $0.index >= matchCount * matchPhaseCount }) |
|
||||||
} |
|
||||||
do { |
|
||||||
try self.tournamentStore?.matches.delete(contentOfs: returnMatches) |
|
||||||
} catch { |
|
||||||
Logger.error(error) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
var matchPhaseCount: Int { |
|
||||||
let count = _matches().count |
|
||||||
if matchCount > 0 { |
|
||||||
return count / matchCount |
|
||||||
} else { |
|
||||||
return 0 |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func addReturnMatches() { |
|
||||||
var teamScores = [TeamScore]() |
|
||||||
var matches = [Match]() |
|
||||||
let matchPhaseCount = matchPhaseCount |
|
||||||
for i in 0..<_numberOfMatchesToBuild() { |
|
||||||
let newMatch = self._createMatch(index: i + matchCount * matchPhaseCount) |
|
||||||
// let newMatch = Match(groupStage: self.id, index: i, matchFormat: self.matchFormat, name: localizedMatchUpLabel(for: i)) |
|
||||||
teamScores.append(contentsOf: newMatch.createTeamScores()) |
|
||||||
matches.append(newMatch) |
|
||||||
} |
|
||||||
|
|
||||||
self.tournamentStore?.matches.addOrUpdate(contentOfs: matches) |
|
||||||
self.tournamentStore?.teamScores.addOrUpdate(contentOfs: teamScores) |
|
||||||
} |
|
||||||
|
|
||||||
func buildMatches(keepExistingMatches: Bool = false) { |
|
||||||
var teamScores = [TeamScore]() |
|
||||||
var matches = [Match]() |
|
||||||
clearScoreCache() |
|
||||||
|
|
||||||
if keepExistingMatches == false { |
|
||||||
_removeMatches() |
|
||||||
|
|
||||||
for i in 0..<_numberOfMatchesToBuild() { |
|
||||||
let newMatch = self._createMatch(index: i) |
|
||||||
// let newMatch = Match(groupStage: self.id, index: i, matchFormat: self.matchFormat, name: localizedMatchUpLabel(for: i)) |
|
||||||
teamScores.append(contentsOf: newMatch.createTeamScores()) |
|
||||||
matches.append(newMatch) |
|
||||||
} |
|
||||||
} else { |
|
||||||
for match in _matches() { |
|
||||||
match.resetTeamScores(outsideOf: []) |
|
||||||
teamScores.append(contentsOf: match.createTeamScores()) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
do { |
|
||||||
try self.tournamentStore?.matches.addOrUpdate(contentOfs: matches) |
|
||||||
try self.tournamentStore?.teamScores.addOrUpdate(contentOfs: teamScores) |
|
||||||
} catch { |
|
||||||
Logger.error(error) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func playedMatches() -> [Match] { |
|
||||||
let ordered = _matches() |
|
||||||
let order = _matchOrder() |
|
||||||
let matchCount = max(1, matchCount) |
|
||||||
let count = ordered.count / matchCount |
|
||||||
if ordered.isEmpty == false && ordered.count % order.count == 0 { |
|
||||||
let repeatedArray = (0..<count).flatMap { i in |
|
||||||
order.map { $0 + i * order.count } |
|
||||||
} |
|
||||||
let result = repeatedArray.map { ordered[$0] } |
|
||||||
return result |
|
||||||
} else { |
|
||||||
return ordered |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func orderedIndexOfMatch(_ match: Match) -> Int { |
|
||||||
_matchOrder()[safe: match.index] ?? match.index |
|
||||||
} |
|
||||||
|
|
||||||
func updateGroupStageState() { |
|
||||||
clearScoreCache() |
|
||||||
|
|
||||||
if hasEnded(), let tournament = tournamentObject() { |
|
||||||
|
|
||||||
let teams = teams(true) |
|
||||||
for (index, team) in teams.enumerated() { |
|
||||||
team.qualified = index < tournament.qualifiedPerGroupStage |
|
||||||
if team.bracketPosition != nil && team.qualified == false { |
|
||||||
tournamentObject()?.shouldVerifyBracket = true |
|
||||||
} |
|
||||||
} |
|
||||||
try self.tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: teams) |
|
||||||
if let tournamentObject = tournamentObject() { |
|
||||||
try DataStore.shared.tournaments.addOrUpdate(instance: tournamentObject) |
|
||||||
} |
|
||||||
self.tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: teams) |
|
||||||
|
|
||||||
let groupStagesAreOverAtFirstStep = tournament.groupStagesAreOver(atStep: 0) |
|
||||||
let nextStepGroupStages = tournament.groupStages(atStep: 1) |
|
||||||
let groupStagesAreOverAtSecondStep = tournament.groupStagesAreOver(atStep: 1) |
|
||||||
|
|
||||||
if groupStagesAreOverAtFirstStep, nextStepGroupStages.isEmpty || groupStagesAreOverAtSecondStep == true, tournament.groupStageLoserBracketAreOver(), tournament.rounds().isEmpty { |
|
||||||
tournament.endDate = Date() |
|
||||||
DataStore.shared.tournaments.addOrUpdate(instance: tournament) |
|
||||||
} |
|
||||||
|
|
||||||
tournament.updateTournamentState() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func scoreLabel(forGroupStagePosition groupStagePosition: Int, score: TeamGroupStageScore? = nil) -> (wins: String, losses: String, setsDifference: String?, gamesDifference: String?)? { |
|
||||||
if let scoreData = (score ?? _score(forGroupStagePosition: groupStagePosition, nilIfEmpty: true)) { |
|
||||||
let hideSetDifference = matchFormat.setsToWin == 1 |
|
||||||
let setDifference = scoreData.setDifference.formatted(.number.sign(strategy: .always(includingZero: true))) + " set" + scoreData.setDifference.pluralSuffix |
|
||||||
let gameDifference = scoreData.gameDifference.formatted(.number.sign(strategy: .always(includingZero: true))) + " jeu" + scoreData.gameDifference.localizedPluralSuffix("x") |
|
||||||
return (wins: scoreData.wins.formatted(), losses: scoreData.loses.formatted(), setsDifference: hideSetDifference ? nil : setDifference, gamesDifference: gameDifference) |
|
||||||
// return "\(scoreData.wins)/\(scoreData.loses) " + differenceAsString |
|
||||||
} else { |
|
||||||
return nil |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// func _score(forGroupStagePosition groupStagePosition: Int, nilIfEmpty: Bool = false) -> TeamGroupStageScore? { |
|
||||||
// guard let team = teamAt(groupStagePosition: groupStagePosition) else { return nil } |
|
||||||
// let matches = matches(forGroupStagePosition: groupStagePosition).filter({ $0.hasEnded() }) |
|
||||||
// if matches.isEmpty && nilIfEmpty { return nil } |
|
||||||
// let wins = matches.filter { $0.winningTeamId == team.id }.count |
|
||||||
// let loses = matches.filter { $0.losingTeamId == team.id }.count |
|
||||||
// let differences = matches.compactMap { $0.scoreDifference(groupStagePosition, atStep: step) } |
|
||||||
// let setDifference = differences.map { $0.set }.reduce(0,+) |
|
||||||
// let gameDifference = differences.map { $0.game }.reduce(0,+) |
|
||||||
// return (team, wins, loses, setDifference, gameDifference) |
|
||||||
// /* |
|
||||||
// • 2 points par rencontre gagnée |
|
||||||
// • 1 point par rencontre perdue |
|
||||||
// • -1 point en cas de rencontre perdue par disqualification (scores de 6/0 6/0 attribués aux trois matchs) |
|
||||||
// • -2 points en cas de rencontre perdu par WO (scores de 6/0 6/0 attribués aux trois matchs) |
|
||||||
// */ |
|
||||||
// } |
|
||||||
// |
|
||||||
func matches(forGroupStagePosition groupStagePosition: Int) -> [Match] { |
|
||||||
let combos = Array((0..<size).combinations(ofCount: 2)) |
|
||||||
var matchIndexes = [Int]() |
|
||||||
for (index, combo) in combos.enumerated() { |
|
||||||
if combo.contains(groupStagePosition) { //team is playing |
|
||||||
matchIndexes.append(index) |
|
||||||
} |
|
||||||
} |
|
||||||
return _matches().filter { matchIndexes.contains($0.index%matchCount) } |
|
||||||
} |
|
||||||
|
|
||||||
func initialStartDate(forTeam team: TeamRegistration) -> Date? { |
|
||||||
guard let groupStagePosition = team.groupStagePositionAtStep(step) else { return nil } |
|
||||||
return matches(forGroupStagePosition: groupStagePosition).compactMap({ $0.startDate }).sorted().first ?? startDate |
|
||||||
} |
|
||||||
|
|
||||||
func matchPlayed(by groupStagePosition: Int, againstPosition: Int) -> Match? { |
|
||||||
if groupStagePosition == againstPosition { return nil } |
|
||||||
let combos = Array((0..<size).combinations(ofCount: 2)) |
|
||||||
var matchIndexes = [Int]() |
|
||||||
for (index, combo) in combos.enumerated() { |
|
||||||
if combo.contains(groupStagePosition) && combo.contains(againstPosition) { //teams are playing |
|
||||||
matchIndexes.append(index) |
|
||||||
} |
|
||||||
} |
|
||||||
return _matches().first(where: { matchIndexes.contains($0.index) }) |
|
||||||
} |
|
||||||
|
|
||||||
func availableToStart(playedMatches: [Match], in runningMatches: [Match], checkCanPlay: Bool = true) -> [Match] { |
|
||||||
#if _DEBUG_TIME //DEBUGING TIME |
|
||||||
let start = Date() |
|
||||||
defer { |
|
||||||
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) |
|
||||||
print("func group stage availableToStart", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) |
|
||||||
} |
|
||||||
#endif |
|
||||||
return playedMatches.filter({ $0.isRunning() == false && $0.canBeStarted(inMatches: runningMatches, checkCanPlay: checkCanPlay) }).sorted(by: \.computedStartDateForSorting) |
|
||||||
} |
|
||||||
|
|
||||||
func runningMatches(playedMatches: [Match]) -> [Match] { |
|
||||||
#if _DEBUG_TIME //DEBUGING TIME |
|
||||||
let start = Date() |
|
||||||
defer { |
|
||||||
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) |
|
||||||
print("func group stage runningMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) |
|
||||||
} |
|
||||||
#endif |
|
||||||
return playedMatches.filter({ $0.isRunning() }).sorted(by: \.computedStartDateForSorting) |
|
||||||
} |
|
||||||
|
|
||||||
func readyMatches(playedMatches: [Match]) -> [Match] { |
|
||||||
#if _DEBUG_TIME //DEBUGING TIME |
|
||||||
let start = Date() |
|
||||||
defer { |
|
||||||
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) |
|
||||||
print("func group stage readyMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) |
|
||||||
} |
|
||||||
#endif |
|
||||||
return playedMatches.filter({ $0.isReady() && $0.isRunning() == false && $0.hasEnded() == false }) |
|
||||||
} |
|
||||||
|
|
||||||
func finishedMatches(playedMatches: [Match]) -> [Match] { |
|
||||||
#if _DEBUG_TIME //DEBUGING TIME |
|
||||||
let start = Date() |
|
||||||
defer { |
|
||||||
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) |
|
||||||
print("func group stage finishedMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) |
|
||||||
} |
|
||||||
#endif |
|
||||||
return playedMatches.filter({ $0.hasEnded() }).sorted(by: \.computedEndDateForSorting).reversed() |
|
||||||
} |
|
||||||
|
|
||||||
func isReturnMatchEnabled() -> Bool { |
|
||||||
_matches().count > matchCount |
|
||||||
} |
|
||||||
|
|
||||||
private func _matchOrder() -> [Int] { |
|
||||||
var order: [Int] |
|
||||||
|
|
||||||
switch size { |
|
||||||
case 3: |
|
||||||
order = [1, 2, 0] |
|
||||||
case 4: |
|
||||||
order = [2, 3, 1, 4, 5, 0] |
|
||||||
case 5: |
|
||||||
order = [3, 5, 8, 2, 6, 1, 9, 4, 7, 0] |
|
||||||
case 6: |
|
||||||
order = [4, 7, 9, 3, 6, 11, 2, 8, 10, 1, 13, 5, 12, 14, 0] |
|
||||||
default: |
|
||||||
order = [] |
|
||||||
} |
|
||||||
|
|
||||||
return order |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
func indexOf(_ matchIndex: Int) -> Int { |
|
||||||
_matchOrder().firstIndex(of: matchIndex) ?? matchIndex |
|
||||||
} |
|
||||||
|
|
||||||
func _matchUp(for matchIndex: Int) -> [Int] { |
|
||||||
let combinations = Array((0..<size).combinations(ofCount: 2)) |
|
||||||
return combinations[safe: matchIndex%matchCount] ?? [] |
|
||||||
} |
|
||||||
|
|
||||||
func returnMatchesSuffix(for matchIndex: Int) -> String { |
|
||||||
if matchCount > 0 { |
|
||||||
let count = _matches().count |
|
||||||
if count > matchCount * 2 { |
|
||||||
return " - vague \((matchIndex / matchCount) + 1)" |
|
||||||
} |
|
||||||
|
|
||||||
if matchIndex >= matchCount { |
|
||||||
return " - retour" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return "" |
|
||||||
} |
|
||||||
|
|
||||||
func localizedMatchUpLabel(for matchIndex: Int) -> String { |
|
||||||
let matchUp = _matchUp(for: matchIndex) |
|
||||||
if let index = matchUp.first, let index2 = matchUp.last { |
|
||||||
return "#\(index + 1) vs #\(index2 + 1)" + returnMatchesSuffix(for: matchIndex) |
|
||||||
} else { |
|
||||||
return "--" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
var matchCount: Int { |
|
||||||
(size * (size - 1)) / 2 |
|
||||||
} |
|
||||||
|
|
||||||
func team(teamPosition team: TeamPosition, inMatchIndex matchIndex: Int) -> TeamRegistration? { |
|
||||||
let _teams = _teams(for: matchIndex) |
|
||||||
switch team { |
|
||||||
case .one: |
|
||||||
return _teams.first ?? nil |
|
||||||
case .two: |
|
||||||
return _teams.last ?? nil |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private func _teams(for matchIndex: Int) -> [TeamRegistration?] { |
|
||||||
let combinations = Array(0..<size).combinations(ofCount: 2).map {$0} |
|
||||||
return combinations[safe: matchIndex%matchCount]?.map { teamAt(groupStagePosition: $0) } ?? [] |
|
||||||
} |
|
||||||
|
|
||||||
private func _removeMatches() { |
|
||||||
self.tournamentStore?.matches.delete(contentOfs: _matches()) |
|
||||||
} |
|
||||||
|
|
||||||
private func _numberOfMatchesToBuild() -> Int { |
|
||||||
(size * (size - 1)) / 2 |
|
||||||
} |
|
||||||
|
|
||||||
func unsortedPlayers() -> [PlayerRegistration] { |
|
||||||
unsortedTeams().flatMap({ $0.unsortedPlayers() }) |
|
||||||
} |
|
||||||
|
|
||||||
typealias TeamScoreAreInIncreasingOrder = (TeamGroupStageScore, TeamGroupStageScore) -> Bool |
|
||||||
|
|
||||||
typealias TeamGroupStageScore = (team: TeamRegistration, wins: Int, loses: Int, setDifference: Int, gameDifference: Int) |
|
||||||
|
|
||||||
fileprivate func _headToHead(_ teamPosition: TeamRegistration, _ otherTeam: TeamRegistration) -> Bool { |
|
||||||
let indexes = [teamPosition, otherTeam].compactMap({ $0.groupStagePosition }).sorted() |
|
||||||
let combos = Array((0..<size).combinations(ofCount: 2)) |
|
||||||
let matchIndexes = combos.enumerated().compactMap { $0.element == indexes ? $0.offset : nil } |
|
||||||
let matches = _matches().filter { matchIndexes.contains($0.index) } |
|
||||||
if matches.count > 1 { |
|
||||||
let scoreA = calculateScore(for: teamPosition, matches: matches, groupStagePosition: teamPosition.groupStagePosition!) |
|
||||||
let scoreB = calculateScore(for: otherTeam, matches: matches, groupStagePosition: otherTeam.groupStagePosition!) |
|
||||||
|
|
||||||
let teamsSorted = [scoreA, scoreB].sorted { (lhs, rhs) in |
|
||||||
let predicates: [TeamScoreAreInIncreasingOrder] = [ |
|
||||||
{ $0.wins < $1.wins }, |
|
||||||
{ $0.setDifference < $1.setDifference }, |
|
||||||
{ $0.gameDifference < $1.gameDifference}, |
|
||||||
{ [self] in $0.team.groupStagePositionAtStep(self.step)! > $1.team.groupStagePositionAtStep(self.step)! } |
|
||||||
] |
|
||||||
|
|
||||||
for predicate in predicates { |
|
||||||
if !predicate(lhs, rhs) && !predicate(rhs, lhs) { |
|
||||||
continue |
|
||||||
} |
|
||||||
|
|
||||||
return predicate(lhs, rhs) |
|
||||||
} |
|
||||||
|
|
||||||
return false |
|
||||||
}.map({ $0.team }) |
|
||||||
|
|
||||||
return teamsSorted.first == teamPosition |
|
||||||
} else { |
|
||||||
|
|
||||||
if let matchIndex = combos.firstIndex(of: indexes), let match = _matches().first(where: { $0.index == matchIndex }) { |
|
||||||
return teamPosition.id == match.losingTeamId |
|
||||||
} else { |
|
||||||
return false |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func unsortedTeams() -> [TeamRegistration] { |
|
||||||
guard let tournamentStore = self.tournamentStore else { return [] } |
|
||||||
if step > 0 { |
|
||||||
return tournamentStore.groupStages.filter({ $0.step == step - 1 }).compactMap({ $0.teams(true)[safe: index] }) |
|
||||||
} |
|
||||||
return tournamentStore.teamRegistrations.filter { $0.groupStage == self.id && $0.groupStagePosition != nil } |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
var scoreCache: [Int: TeamGroupStageScore] = [:] |
|
||||||
|
|
||||||
func computedScore(forTeam team: TeamRegistration, step: Int = 0) -> TeamGroupStageScore? { |
|
||||||
guard let groupStagePositionAtStep = team.groupStagePositionAtStep(step) else { |
|
||||||
return nil |
|
||||||
} |
|
||||||
if let cachedScore = scoreCache[groupStagePositionAtStep] { |
|
||||||
return cachedScore |
|
||||||
} else { |
|
||||||
let score = _score(forGroupStagePosition: groupStagePositionAtStep) |
|
||||||
if let score = score { |
|
||||||
scoreCache[groupStagePositionAtStep] = score |
|
||||||
} |
|
||||||
return score |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func teams(_ sortedByScore: Bool = false, scores: [TeamGroupStageScore]? = nil) -> [TeamRegistration] { |
|
||||||
if sortedByScore { |
|
||||||
return unsortedTeams().compactMap({ team in |
|
||||||
// Check cache or use provided scores, otherwise calculate and store in cache |
|
||||||
scores?.first(where: { $0.team.id == team.id }) ?? { |
|
||||||
return computedScore(forTeam: team, step: step) |
|
||||||
}() |
|
||||||
}).sorted { (lhs, rhs) in |
|
||||||
let predicates: [TeamScoreAreInIncreasingOrder] = [ |
|
||||||
{ $0.wins < $1.wins }, |
|
||||||
{ $0.setDifference < $1.setDifference }, |
|
||||||
{ $0.gameDifference < $1.gameDifference}, |
|
||||||
{ self._headToHead($0.team, $1.team) }, |
|
||||||
{ [self] in $0.team.groupStagePositionAtStep(self.step)! > $1.team.groupStagePositionAtStep(self.step)! } |
|
||||||
] |
|
||||||
|
|
||||||
for predicate in predicates { |
|
||||||
if !predicate(lhs, rhs) && !predicate(rhs, lhs) { |
|
||||||
continue |
|
||||||
} |
|
||||||
|
|
||||||
return predicate(lhs, rhs) |
|
||||||
} |
|
||||||
|
|
||||||
return false |
|
||||||
}.map({ $0.team }).reversed() |
|
||||||
} else { |
|
||||||
return unsortedTeams().sorted(by: \TeamRegistration.groupStagePosition!) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func _score(forGroupStagePosition groupStagePosition: Int, nilIfEmpty: Bool = false) -> TeamGroupStageScore? { |
|
||||||
// Check if the score for this position is already cached |
|
||||||
if let cachedScore = scoreCache[groupStagePosition] { |
|
||||||
return cachedScore |
|
||||||
} |
|
||||||
|
|
||||||
guard let team = teamAt(groupStagePosition: groupStagePosition) else { return nil } |
|
||||||
let matches = matches(forGroupStagePosition: groupStagePosition).filter({ $0.hasEnded() }) |
|
||||||
if matches.isEmpty && nilIfEmpty { return nil } |
|
||||||
let score = calculateScore(for: team, matches: matches, groupStagePosition: groupStagePosition) |
|
||||||
scoreCache[groupStagePosition] = score |
|
||||||
return score |
|
||||||
} |
|
||||||
|
|
||||||
private func calculateScore(for team: TeamRegistration, matches: [Match], groupStagePosition: Int) -> TeamGroupStageScore { |
|
||||||
let wins = matches.filter { $0.winningTeamId == team.id }.count |
|
||||||
let loses = matches.filter { $0.losingTeamId == team.id }.count |
|
||||||
let differences = matches.compactMap { $0.scoreDifference(groupStagePosition, atStep: step) } |
|
||||||
let setDifference = differences.map { $0.set }.reduce(0,+) |
|
||||||
let gameDifference = differences.map { $0.game }.reduce(0,+) |
|
||||||
|
|
||||||
return (team, wins, loses, setDifference, gameDifference) |
|
||||||
} |
|
||||||
|
|
||||||
// Clear the cache if necessary, for example when starting a new step or when matches update |
|
||||||
func clearScoreCache() { |
|
||||||
scoreCache.removeAll() |
|
||||||
} |
|
||||||
|
|
||||||
// func teams(_ sortedByScore: Bool = false, scores: [TeamGroupStageScore]? = nil) -> [TeamRegistration] { |
|
||||||
// if sortedByScore { |
|
||||||
// return unsortedTeams().compactMap({ team in |
|
||||||
// scores?.first(where: { $0.team.id == team.id }) ?? _score(forGroupStagePosition: team.groupStagePositionAtStep(step)!) |
|
||||||
// }).sorted { (lhs, rhs) in |
|
||||||
// // Calculate intermediate values once and reuse them |
|
||||||
// let lhsWins = lhs.wins |
|
||||||
// let rhsWins = rhs.wins |
|
||||||
// let lhsSetDifference = lhs.setDifference |
|
||||||
// let rhsSetDifference = rhs.setDifference |
|
||||||
// let lhsGameDifference = lhs.gameDifference |
|
||||||
// let rhsGameDifference = rhs.gameDifference |
|
||||||
// let lhsHeadToHead = self._headToHead(lhs.team, rhs.team) |
|
||||||
// let rhsHeadToHead = self._headToHead(rhs.team, lhs.team) |
|
||||||
// let lhsGroupStagePosition = lhs.team.groupStagePositionAtStep(self.step)! |
|
||||||
// let rhsGroupStagePosition = rhs.team.groupStagePositionAtStep(self.step)! |
|
||||||
// |
|
||||||
// // Define comparison predicates in the same order |
|
||||||
// let predicates: [(Bool, Bool)] = [ |
|
||||||
// (lhsWins < rhsWins, lhsWins > rhsWins), |
|
||||||
// (lhsSetDifference < rhsSetDifference, lhsSetDifference > rhsSetDifference), |
|
||||||
// (lhsGameDifference < rhsGameDifference, lhsGameDifference > rhsGameDifference), |
|
||||||
// (lhsHeadToHead, rhsHeadToHead), |
|
||||||
// (lhsGroupStagePosition > rhsGroupStagePosition, lhsGroupStagePosition < rhsGroupStagePosition) |
|
||||||
// ] |
|
||||||
// |
|
||||||
// // Iterate over predicates and return as soon as a valid comparison is found |
|
||||||
// for (lhsPredicate, rhsPredicate) in predicates { |
|
||||||
// if lhsPredicate { return true } |
|
||||||
// if rhsPredicate { return false } |
|
||||||
// } |
|
||||||
// |
|
||||||
// return false |
|
||||||
// }.map({ $0.team }).reversed() |
|
||||||
// } else { |
|
||||||
// return unsortedTeams().sorted(by: \TeamRegistration.groupStagePosition!) |
|
||||||
// } |
|
||||||
// } |
|
||||||
|
|
||||||
func updateMatchFormat(_ updatedMatchFormat: MatchFormat) { |
|
||||||
self.matchFormat = updatedMatchFormat |
|
||||||
self.updateAllMatchesFormat() |
|
||||||
} |
|
||||||
|
|
||||||
func updateAllMatchesFormat() { |
|
||||||
let playedMatches = playedMatches() |
|
||||||
playedMatches.forEach { match in |
|
||||||
match.matchFormat = matchFormat |
|
||||||
} |
|
||||||
self.tournamentStore?.matches.addOrUpdate(contentOfs: playedMatches) |
|
||||||
} |
|
||||||
|
|
||||||
func pasteData() -> String { |
|
||||||
var data: [String] = [] |
|
||||||
data.append(self.groupStageTitle()) |
|
||||||
teams().forEach { team in |
|
||||||
data.append(team.teamLabelRanked(displayRank: true, displayTeamName: true)) |
|
||||||
} |
|
||||||
|
|
||||||
return data.joined(separator: "\n") |
|
||||||
} |
|
||||||
|
|
||||||
func finalPosition(ofTeam team: TeamRegistration) -> Int? { |
|
||||||
guard hasEnded() else { return nil } |
|
||||||
return teams(true).firstIndex(of: team) |
|
||||||
} |
|
||||||
|
|
||||||
override func deleteDependencies() { |
|
||||||
let matches = self._matches() |
|
||||||
for match in matches { |
|
||||||
match.deleteDependencies() |
|
||||||
} |
|
||||||
self.tournamentStore?.matches.deleteDependencies(matches) |
|
||||||
} |
|
||||||
|
|
||||||
// init(from decoder: Decoder) throws { |
|
||||||
// let container = try decoder.container(keyedBy: CodingKeys.self) |
|
||||||
// |
|
||||||
// id = try container.decode(String.self, forKey: ._id) |
|
||||||
// storeId = try container.decode(String.self, forKey: ._storeId) |
|
||||||
// lastUpdate = try container.decode(Date.self, forKey: ._lastUpdate) |
|
||||||
// tournament = try container.decode(String.self, forKey: ._tournament) |
|
||||||
// index = try container.decode(Int.self, forKey: ._index) |
|
||||||
// size = try container.decode(Int.self, forKey: ._size) |
|
||||||
// format = try container.decodeIfPresent(MatchFormat.self, forKey: ._format) |
|
||||||
// startDate = try container.decodeIfPresent(Date.self, forKey: ._startDate) |
|
||||||
// name = try container.decodeIfPresent(String.self, forKey: ._name) |
|
||||||
// step = try container.decodeIfPresent(Int.self, forKey: ._step) ?? 0 |
|
||||||
// } |
|
||||||
// |
|
||||||
// func encode(to encoder: Encoder) throws { |
|
||||||
// var container = encoder.container(keyedBy: CodingKeys.self) |
|
||||||
// |
|
||||||
// try container.encode(id, forKey: ._id) |
|
||||||
// try container.encode(storeId, forKey: ._storeId) |
|
||||||
// try container.encode(lastUpdate, forKey: ._lastUpdate) |
|
||||||
// try container.encode(tournament, forKey: ._tournament) |
|
||||||
// try container.encode(index, forKey: ._index) |
|
||||||
// try container.encode(size, forKey: ._size) |
|
||||||
// try container.encode(format, forKey: ._format) |
|
||||||
// try container.encode(startDate, forKey: ._startDate) |
|
||||||
// try container.encode(name, forKey: ._name) |
|
||||||
// try container.encode(step, forKey: ._step) |
|
||||||
// } |
|
||||||
|
|
||||||
func insertOnServer() { |
|
||||||
self.tournamentStore?.groupStages.writeChangeAndInsertOnServer(instance: self) |
|
||||||
for match in self._matches() { |
|
||||||
match.insertOnServer() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
extension GroupStage { |
|
||||||
enum CodingKeys: String, CodingKey { |
|
||||||
case _id = "id" |
|
||||||
case _storeId = "storeId" |
|
||||||
case _lastUpdate = "lastUpdate" |
|
||||||
case _tournament = "tournament" |
|
||||||
case _index = "index" |
|
||||||
case _size = "size" |
|
||||||
case _format = "format" |
|
||||||
case _startDate = "startDate" |
|
||||||
case _name = "name" |
|
||||||
case _step = "step" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
extension GroupStage: Selectable { |
|
||||||
func selectionLabel(index: Int) -> String { |
|
||||||
groupStageTitle() |
|
||||||
} |
|
||||||
|
|
||||||
func badgeValue() -> Int? { |
|
||||||
return runningMatches(playedMatches: _matches()).count |
|
||||||
} |
|
||||||
|
|
||||||
func badgeValueColor() -> Color? { |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func badgeImage() -> Badge? { |
|
||||||
if teams().count < size { |
|
||||||
return .xmark |
|
||||||
} else { |
|
||||||
return hasEnded() ? .checkmark : nil |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,888 +0,0 @@ |
|||||||
// |
|
||||||
// MatchScheduler.swift |
|
||||||
// PadelClub |
|
||||||
// |
|
||||||
// Created by Razmig Sarkissian on 08/04/2024. |
|
||||||
// |
|
||||||
|
|
||||||
import Foundation |
|
||||||
import LeStorage |
|
||||||
import SwiftUI |
|
||||||
|
|
||||||
@Observable |
|
||||||
final class MatchScheduler: BaseMatchScheduler, SideStorable { |
|
||||||
// |
|
||||||
// init(tournament: String, |
|
||||||
// timeDifferenceLimit: Int = 5, |
|
||||||
// loserBracketRotationDifference: Int = 0, |
|
||||||
// upperBracketRotationDifference: Int = 1, |
|
||||||
// accountUpperBracketBreakTime: Bool = true, |
|
||||||
// accountLoserBracketBreakTime: Bool = false, |
|
||||||
// randomizeCourts: Bool = true, |
|
||||||
// rotationDifferenceIsImportant: Bool = false, |
|
||||||
// shouldHandleUpperRoundSlice: Bool = false, |
|
||||||
// shouldEndRoundBeforeStartingNext: Bool = true, |
|
||||||
//<<<<<<< HEAD |
|
||||||
// groupStageChunkCount: Int? = nil, overrideCourtsUnavailability: Bool = false, shouldTryToFillUpCourtsAvailable: Bool = false) { |
|
||||||
// super.init() |
|
||||||
//======= |
|
||||||
// groupStageChunkCount: Int? = nil, |
|
||||||
// overrideCourtsUnavailability: Bool = false, |
|
||||||
// shouldTryToFillUpCourtsAvailable: Bool = true, |
|
||||||
// courtsAvailable: Set<Int> = Set<Int>(), |
|
||||||
// simultaneousStart: Bool = true) { |
|
||||||
//>>>>>>> main |
|
||||||
// self.tournament = tournament |
|
||||||
// self.timeDifferenceLimit = timeDifferenceLimit |
|
||||||
// self.loserBracketRotationDifference = loserBracketRotationDifference |
|
||||||
// self.upperBracketRotationDifference = upperBracketRotationDifference |
|
||||||
// self.accountUpperBracketBreakTime = accountUpperBracketBreakTime |
|
||||||
// self.accountLoserBracketBreakTime = accountLoserBracketBreakTime |
|
||||||
// self.randomizeCourts = randomizeCourts |
|
||||||
// self.rotationDifferenceIsImportant = rotationDifferenceIsImportant |
|
||||||
// self.shouldHandleUpperRoundSlice = shouldHandleUpperRoundSlice |
|
||||||
// self.shouldEndRoundBeforeStartingNext = shouldEndRoundBeforeStartingNext |
|
||||||
// self.groupStageChunkCount = groupStageChunkCount |
|
||||||
// self.overrideCourtsUnavailability = overrideCourtsUnavailability |
|
||||||
// self.shouldTryToFillUpCourtsAvailable = shouldTryToFillUpCourtsAvailable |
|
||||||
// self.courtsAvailable = courtsAvailable |
|
||||||
// self.simultaneousStart = simultaneousStart |
|
||||||
// } |
|
||||||
|
|
||||||
var courtsUnavailability: [DateInterval]? { |
|
||||||
guard let event = tournamentObject()?.eventObject() else { return nil } |
|
||||||
return event.courtsUnavailability + (overrideCourtsUnavailability ? [] : event.tournamentsCourtsUsed(exluding: tournament)) |
|
||||||
} |
|
||||||
|
|
||||||
var additionalEstimationDuration : Int { |
|
||||||
return tournamentObject()?.additionalEstimationDuration ?? 0 |
|
||||||
} |
|
||||||
|
|
||||||
var tournamentStore: TournamentStore? { |
|
||||||
return TournamentLibrary.shared.store(tournamentId: self.tournament) |
|
||||||
// TournamentStore.instance(tournamentId: self.tournament) |
|
||||||
} |
|
||||||
|
|
||||||
func tournamentObject() -> Tournament? { |
|
||||||
return Store.main.findById(tournament) |
|
||||||
} |
|
||||||
|
|
||||||
@discardableResult |
|
||||||
func updateGroupStageSchedule(tournament: Tournament, specificGroupStage: GroupStage? = nil, atStep step: Int = 0, startDate: Date? = nil) -> Date { |
|
||||||
let computedGroupStageChunkCount = groupStageChunkCount ?? tournament.getGroupStageChunkValue() |
|
||||||
var groupStages: [GroupStage] = tournament.groupStages(atStep: step) |
|
||||||
if let specificGroupStage { |
|
||||||
groupStages = [specificGroupStage] |
|
||||||
} |
|
||||||
|
|
||||||
let matches = groupStages.flatMap { $0._matches() } |
|
||||||
matches.forEach({ |
|
||||||
$0.removeCourt() |
|
||||||
$0.startDate = nil |
|
||||||
$0.confirmed = false |
|
||||||
}) |
|
||||||
|
|
||||||
var lastDate : Date = startDate ?? tournament.startDate |
|
||||||
let times = Set(groupStages.compactMap { $0.startDate }).sorted() |
|
||||||
|
|
||||||
if let first = times.first { |
|
||||||
if first.isEarlierThan(tournament.startDate) { |
|
||||||
tournament.startDate = first |
|
||||||
do { |
|
||||||
try DataStore.shared.tournaments.addOrUpdate(instance: tournament) |
|
||||||
} catch { |
|
||||||
Logger.error(error) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
times.forEach({ time in |
|
||||||
if lastDate.isEarlierThan(time) { |
|
||||||
lastDate = time |
|
||||||
} |
|
||||||
let groups = groupStages.filter({ $0.startDate == time }) |
|
||||||
let dispatch = groupStageDispatcher(groupStages: groups, startingDate: lastDate) |
|
||||||
|
|
||||||
dispatch.timedMatches.forEach { matchSchedule in |
|
||||||
if let match = matches.first(where: { $0.id == matchSchedule.matchID }) { |
|
||||||
let estimatedDuration = match.matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration) |
|
||||||
let timeIntervalToAdd = (Double(matchSchedule.rotationIndex)) * Double(estimatedDuration) * 60 |
|
||||||
if let startDate = match.groupStageObject?.startDate { |
|
||||||
let matchStartDate = startDate.addingTimeInterval(timeIntervalToAdd) |
|
||||||
match.startDate = matchStartDate |
|
||||||
lastDate = matchStartDate.addingTimeInterval(Double(estimatedDuration) * 60) |
|
||||||
} |
|
||||||
match.setCourt(matchSchedule.courtIndex) |
|
||||||
} |
|
||||||
} |
|
||||||
}) |
|
||||||
|
|
||||||
groupStages.filter({ $0.startDate == nil || times.contains($0.startDate!) == false }).chunked(into: computedGroupStageChunkCount).forEach { groups in |
|
||||||
groups.forEach({ $0.startDate = lastDate }) |
|
||||||
do { |
|
||||||
try self.tournamentStore?.groupStages.addOrUpdate(contentOfs: groups) |
|
||||||
} catch { |
|
||||||
Logger.error(error) |
|
||||||
} |
|
||||||
|
|
||||||
let dispatch = groupStageDispatcher(groupStages: groups, startingDate: lastDate) |
|
||||||
|
|
||||||
dispatch.timedMatches.forEach { matchSchedule in |
|
||||||
if let match = matches.first(where: { $0.id == matchSchedule.matchID }) { |
|
||||||
let estimatedDuration = match.matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration) |
|
||||||
let timeIntervalToAdd = (Double(matchSchedule.rotationIndex)) * Double(estimatedDuration) * 60 |
|
||||||
if let startDate = match.groupStageObject?.startDate { |
|
||||||
let matchStartDate = startDate.addingTimeInterval(timeIntervalToAdd) |
|
||||||
match.startDate = matchStartDate |
|
||||||
lastDate = matchStartDate.addingTimeInterval(Double(estimatedDuration) * 60) |
|
||||||
} |
|
||||||
match.setCourt(matchSchedule.courtIndex) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
do { |
|
||||||
try self.tournamentStore?.matches.addOrUpdate(contentOfs: matches) |
|
||||||
} catch { |
|
||||||
Logger.error(error) |
|
||||||
} |
|
||||||
return lastDate |
|
||||||
} |
|
||||||
|
|
||||||
func groupStageDispatcher(groupStages: [GroupStage], startingDate: Date) -> GroupStageMatchDispatcher { |
|
||||||
|
|
||||||
let _groupStages = groupStages |
|
||||||
|
|
||||||
// Get the maximum count of matches in any group |
|
||||||
let maxMatchesCount = _groupStages.map { $0._matches().count }.max() ?? 0 |
|
||||||
var flattenedMatches = [Match]() |
|
||||||
if simultaneousStart { |
|
||||||
// Flatten matches in a round-robin order by cycling through each group |
|
||||||
flattenedMatches = (0..<maxMatchesCount).flatMap { index in |
|
||||||
_groupStages.compactMap { group in |
|
||||||
// Safely access matches, return nil if index is out of bounds |
|
||||||
let playedMatches = group.playedMatches() |
|
||||||
return playedMatches.indices.contains(index) ? playedMatches[index] : nil |
|
||||||
} |
|
||||||
} |
|
||||||
} else { |
|
||||||
flattenedMatches = _groupStages.flatMap({ $0.playedMatches() }) |
|
||||||
} |
|
||||||
|
|
||||||
var slots = [GroupStageTimeMatch]() |
|
||||||
var availableMatches = flattenedMatches |
|
||||||
var rotationIndex = 0 |
|
||||||
var teamsPerRotation = [Int: [String]]() // Tracks teams assigned to each rotation |
|
||||||
var freeCourtPerRotation = [Int: [Int]]() // Tracks free courts per rotation |
|
||||||
var groupLastRotation = [Int: Int]() // Tracks the last rotation each group was involved in |
|
||||||
let courtsUnavailability = courtsUnavailability |
|
||||||
|
|
||||||
while slots.count < flattenedMatches.count { |
|
||||||
print("Starting rotation \(rotationIndex) with \(availableMatches.count) matches left") |
|
||||||
teamsPerRotation[rotationIndex] = [] |
|
||||||
freeCourtPerRotation[rotationIndex] = [] |
|
||||||
|
|
||||||
let previousRotationBracketIndexes = slots.filter { $0.rotationIndex == rotationIndex - 1 } |
|
||||||
.map { ($0.groupIndex, 1) } |
|
||||||
|
|
||||||
let counts = Dictionary(previousRotationBracketIndexes, uniquingKeysWith: +) |
|
||||||
var rotationMatches = Array(availableMatches.filter({ match in |
|
||||||
// Check if all teams from the match are not already scheduled in the current rotation |
|
||||||
let teamsAvailable = teamsPerRotation[rotationIndex]!.allSatisfy({ !match.containsTeamIndex($0) }) |
|
||||||
if !teamsAvailable { |
|
||||||
print("Match \(match.roundAndMatchTitle()) has teams already scheduled in rotation \(rotationIndex)") |
|
||||||
} |
|
||||||
return teamsAvailable |
|
||||||
})) |
|
||||||
|
|
||||||
if rotationIndex > 0, simultaneousStart == false { |
|
||||||
rotationMatches = rotationMatches.sorted(by: { |
|
||||||
if counts[$0.groupStageObject!.index] ?? 0 == counts[$1.groupStageObject!.index] ?? 0 { |
|
||||||
return $0.groupStageObject!.index < $1.groupStageObject!.index |
|
||||||
} else { |
|
||||||
return counts[$0.groupStageObject!.index] ?? 0 < counts[$1.groupStageObject!.index] ?? 0 |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
courtsAvailable.forEach { courtIndex in |
|
||||||
print("Checking availability for court \(courtIndex) in rotation \(rotationIndex)") |
|
||||||
if let first = rotationMatches.first(where: { match in |
|
||||||
let estimatedDuration = match.matchFormat.getEstimatedDuration(additionalEstimationDuration) |
|
||||||
let timeIntervalToAdd = Double(rotationIndex) * Double(estimatedDuration) * 60 |
|
||||||
let rotationStartDate: Date = startingDate.addingTimeInterval(timeIntervalToAdd) |
|
||||||
|
|
||||||
let courtsUnavailable = courtsUnavailable(startDate: rotationStartDate, duration: match.matchFormat.getEstimatedDuration(additionalEstimationDuration), courtsUnavailability: courtsUnavailability) |
|
||||||
|
|
||||||
if courtsUnavailable.contains(courtIndex) { |
|
||||||
print("Court \(courtIndex) is unavailable at \(rotationStartDate)") |
|
||||||
return false |
|
||||||
} |
|
||||||
|
|
||||||
let teamsAvailable = teamsPerRotation[rotationIndex]!.allSatisfy({ !match.containsTeamIndex($0) }) |
|
||||||
if !teamsAvailable { |
|
||||||
print("Teams from match \(match.roundAndMatchTitle()) are already scheduled in this rotation") |
|
||||||
return false |
|
||||||
} |
|
||||||
|
|
||||||
print("Match \(match.roundAndMatchTitle()) is available for court \(courtIndex) at \(rotationStartDate)") |
|
||||||
return true |
|
||||||
}) { |
|
||||||
let timeMatch = GroupStageTimeMatch(matchID: first.id, rotationIndex: rotationIndex, courtIndex: courtIndex, groupIndex: first.groupStageObject!.index) |
|
||||||
|
|
||||||
print("Scheduled match: \(first.roundAndMatchTitle()) on court \(courtIndex) at rotation \(rotationIndex)") |
|
||||||
|
|
||||||
slots.append(timeMatch) |
|
||||||
teamsPerRotation[rotationIndex]!.append(contentsOf: first.matchUp()) |
|
||||||
rotationMatches.removeAll(where: { $0.id == first.id }) |
|
||||||
availableMatches.removeAll(where: { $0.id == first.id }) |
|
||||||
|
|
||||||
if let index = first.groupStageObject?.index { |
|
||||||
groupLastRotation[index] = rotationIndex |
|
||||||
} |
|
||||||
} else { |
|
||||||
print("No available matches for court \(courtIndex) in rotation \(rotationIndex), adding to free court list") |
|
||||||
freeCourtPerRotation[rotationIndex]!.append(courtIndex) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
rotationIndex += 1 |
|
||||||
} |
|
||||||
|
|
||||||
print("All matches scheduled. Total rotations: \(rotationIndex)") |
|
||||||
|
|
||||||
// Organize slots and ensure courts are randomized or sorted |
|
||||||
var organizedSlots = [GroupStageTimeMatch]() |
|
||||||
for i in 0..<rotationIndex { |
|
||||||
let courtsSorted: [Int] = slots.filter({ $0.rotationIndex == i }).map { $0.courtIndex }.sorted() |
|
||||||
let courts: [Int] = randomizeCourts ? courtsSorted.shuffled() : courtsSorted |
|
||||||
var matches = slots.filter({ $0.rotationIndex == i }).sorted(using: .keyPath(\.groupIndex), .keyPath(\.courtIndex)) |
|
||||||
|
|
||||||
for j in 0..<matches.count { |
|
||||||
matches[j].courtIndex = courts[j] |
|
||||||
organizedSlots.append(matches[j]) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return GroupStageMatchDispatcher( |
|
||||||
timedMatches: organizedSlots, |
|
||||||
freeCourtPerRotation: freeCourtPerRotation, |
|
||||||
rotationCount: rotationIndex, |
|
||||||
groupLastRotation: groupLastRotation |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
func rotationDifference(loserBracket: Bool) -> Int { |
|
||||||
if loserBracket { |
|
||||||
return loserBracketRotationDifference |
|
||||||
} else { |
|
||||||
return upperBracketRotationDifference |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func roundMatchCanBePlayed(_ match: Match, roundObject: Round, slots: [TimeMatch], rotationIndex: Int, targetedStartDate: Date, minimumTargetedEndDate: inout Date) -> Bool { |
|
||||||
print("Evaluating match: \(match.roundAndMatchTitle()) in round: \(roundObject.roundTitle()) with index: \(match.index)") |
|
||||||
|
|
||||||
if let roundStartDate = roundObject.startDate, targetedStartDate < roundStartDate { |
|
||||||
print("Cannot start at \(targetedStartDate), earlier than round start date \(roundStartDate)") |
|
||||||
if targetedStartDate == minimumTargetedEndDate { |
|
||||||
print("Updating minimumTargetedEndDate to roundStartDate: \(roundStartDate)") |
|
||||||
minimumTargetedEndDate = roundStartDate |
|
||||||
} else { |
|
||||||
print("Setting minimumTargetedEndDate to the earlier of \(roundStartDate) and \(minimumTargetedEndDate)") |
|
||||||
minimumTargetedEndDate = min(roundStartDate, minimumTargetedEndDate) |
|
||||||
} |
|
||||||
print("Returning false: Match cannot start earlier than the round start date.") |
|
||||||
return false |
|
||||||
} |
|
||||||
|
|
||||||
let previousMatches = roundObject.precedentMatches(ofMatch: match) |
|
||||||
if previousMatches.isEmpty { |
|
||||||
print("No ancestors matches for this match, returning true. (eg beginning of tournament 1st bracket") |
|
||||||
return true |
|
||||||
} |
|
||||||
|
|
||||||
let previousMatchSlots = slots.filter { previousMatches.map { $0.id }.contains($0.matchID) } |
|
||||||
|
|
||||||
if previousMatchSlots.isEmpty { |
|
||||||
if previousMatches.filter({ !$0.disabled }).allSatisfy({ $0.startDate != nil }) { |
|
||||||
print("All previous matches have start dates, returning true.") |
|
||||||
return true |
|
||||||
} |
|
||||||
print("Some previous matches are pending, returning false.") |
|
||||||
return false |
|
||||||
} |
|
||||||
|
|
||||||
if previousMatches.filter({ !$0.disabled }).count > previousMatchSlots.count { |
|
||||||
if previousMatches.filter({ !$0.disabled }).anySatisfy({ $0.startDate != nil }) { |
|
||||||
print("Some previous matches started, returning true.") |
|
||||||
return true |
|
||||||
} |
|
||||||
print("Not enough previous matches have started, returning false.") |
|
||||||
return false |
|
||||||
} |
|
||||||
|
|
||||||
var includeBreakTime = false |
|
||||||
if accountLoserBracketBreakTime && roundObject.isLoserBracket() { |
|
||||||
includeBreakTime = true |
|
||||||
print("Including break time for loser bracket.") |
|
||||||
} |
|
||||||
|
|
||||||
if accountUpperBracketBreakTime && !roundObject.isLoserBracket() { |
|
||||||
includeBreakTime = true |
|
||||||
print("Including break time for upper bracket.") |
|
||||||
} |
|
||||||
|
|
||||||
let previousMatchIsInPreviousRotation = previousMatchSlots.allSatisfy { |
|
||||||
$0.rotationIndex + rotationDifference(loserBracket: roundObject.isLoserBracket()) < rotationIndex |
|
||||||
} |
|
||||||
|
|
||||||
if previousMatchIsInPreviousRotation { |
|
||||||
print("All previous matches are from earlier rotations, returning true.") |
|
||||||
} else { |
|
||||||
print("Some previous matches are from the current rotation.") |
|
||||||
} |
|
||||||
|
|
||||||
guard let minimumPossibleEndDate = previousMatchSlots.map({ |
|
||||||
$0.estimatedEndDate(includeBreakTime: includeBreakTime) |
|
||||||
}).max() else { |
|
||||||
print("No valid previous match end date, returning \(previousMatchIsInPreviousRotation).") |
|
||||||
return previousMatchIsInPreviousRotation |
|
||||||
} |
|
||||||
|
|
||||||
if targetedStartDate >= minimumPossibleEndDate { |
|
||||||
if rotationDifferenceIsImportant { |
|
||||||
print("Targeted start date is after the minimum possible end date and rotation difference is important, returning \(previousMatchIsInPreviousRotation).") |
|
||||||
return previousMatchIsInPreviousRotation |
|
||||||
} else { |
|
||||||
print("Targeted start date is after the minimum possible end date, returning true.") |
|
||||||
return true |
|
||||||
} |
|
||||||
} else { |
|
||||||
if targetedStartDate == minimumTargetedEndDate { |
|
||||||
print("Updating minimumTargetedEndDate to minimumPossibleEndDate: \(minimumPossibleEndDate)") |
|
||||||
minimumTargetedEndDate = minimumPossibleEndDate |
|
||||||
} else { |
|
||||||
print("Setting minimumTargetedEndDate to the earlier of \(minimumPossibleEndDate) and \(minimumTargetedEndDate)") |
|
||||||
minimumTargetedEndDate = min(minimumPossibleEndDate, minimumTargetedEndDate) |
|
||||||
} |
|
||||||
print("Targeted start date \(targetedStartDate) is before the minimum possible end date, returning false. \(minimumTargetedEndDate)") |
|
||||||
return false |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
func getNextStartDate(fromPreviousRotationSlots slots: [TimeMatch], includeBreakTime: Bool) -> Date? { |
|
||||||
slots.map { $0.estimatedEndDate(includeBreakTime: includeBreakTime) }.min() |
|
||||||
} |
|
||||||
|
|
||||||
func getNextEarliestAvailableDate(from slots: [TimeMatch]) -> [(Int, Date)] { |
|
||||||
let byCourt = Dictionary(grouping: slots, by: { $0.courtIndex }) |
|
||||||
return (byCourt.keys.flatMap { courtIndex in |
|
||||||
let matchesByCourt = byCourt[courtIndex]?.sorted(by: \.startDate) |
|
||||||
let lastMatch = matchesByCourt?.last |
|
||||||
var results = [(Int, Date)]() |
|
||||||
if let courtFreeDate = lastMatch?.estimatedEndDate(includeBreakTime: false) { |
|
||||||
results.append((courtIndex, courtFreeDate)) |
|
||||||
} |
|
||||||
return results |
|
||||||
} |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
func getAvailableCourts(from matches: [Match]) -> [(Int, Date)] { |
|
||||||
let validMatches = matches.filter({ $0.courtIndex != nil && $0.startDate != nil }) |
|
||||||
let byCourt = Dictionary(grouping: validMatches, by: { $0.courtIndex! }) |
|
||||||
return (byCourt.keys.flatMap { court in |
|
||||||
let matchesByCourt = byCourt[court]?.sorted(by: \.startDate!) |
|
||||||
let lastMatch = matchesByCourt?.last |
|
||||||
var results = [(Int, Date)]() |
|
||||||
if let courtFreeDate = lastMatch?.estimatedEndDate(additionalEstimationDuration) { |
|
||||||
results.append((court, courtFreeDate)) |
|
||||||
} |
|
||||||
return results |
|
||||||
} |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
func roundDispatcher(flattenedMatches: [Match], dispatcherStartDate: Date, initialCourts: [Int]?) -> MatchDispatcher { |
|
||||||
var slots = [TimeMatch]() |
|
||||||
var _startDate: Date? |
|
||||||
var rotationIndex = 0 |
|
||||||
var availableMatchs = flattenedMatches.filter({ $0.startDate == nil }) |
|
||||||
let courtsUnavailability = courtsUnavailability |
|
||||||
var issueFound: Bool = false |
|
||||||
|
|
||||||
// Log start of the function |
|
||||||
print("Starting roundDispatcher with \(availableMatchs.count) matches and \(courtsAvailable) courts available") |
|
||||||
|
|
||||||
flattenedMatches.filter { $0.startDate != nil }.sorted(by: \.startDate!).forEach { match in |
|
||||||
if _startDate == nil { |
|
||||||
_startDate = match.startDate |
|
||||||
} else if match.startDate! > _startDate! { |
|
||||||
_startDate = match.startDate |
|
||||||
rotationIndex += 1 |
|
||||||
} |
|
||||||
|
|
||||||
let timeMatch = TimeMatch(matchID: match.id, rotationIndex: rotationIndex, courtIndex: match.courtIndex ?? 0, startDate: match.startDate!, durationLeft: match.matchFormat.getEstimatedDuration(additionalEstimationDuration), minimumBreakTime: match.matchFormat.breakTime.breakTime) |
|
||||||
slots.append(timeMatch) |
|
||||||
} |
|
||||||
|
|
||||||
if !slots.isEmpty { |
|
||||||
rotationIndex += 1 |
|
||||||
} |
|
||||||
|
|
||||||
var freeCourtPerRotation = [Int: [Int]]() |
|
||||||
var courts = initialCourts ?? Array(courtsAvailable) |
|
||||||
var shouldStartAtDispatcherDate = rotationIndex > 0 |
|
||||||
var suitableDate: Date? |
|
||||||
|
|
||||||
while !availableMatchs.isEmpty && !issueFound && rotationIndex < 50 { |
|
||||||
freeCourtPerRotation[rotationIndex] = [] |
|
||||||
let previousRotationSlots = slots.filter({ $0.rotationIndex == rotationIndex - 1 }) |
|
||||||
|
|
||||||
var rotationStartDate: Date |
|
||||||
if previousRotationSlots.isEmpty && rotationIndex > 0 { |
|
||||||
let computedSuitableDate = slots.sorted(by: \.computedEndDateForSorting).last?.computedEndDateForSorting |
|
||||||
print("Previous rotation was empty, find a suitable rotationStartDate \(suitableDate)") |
|
||||||
rotationStartDate = suitableDate ?? computedSuitableDate ?? dispatcherStartDate |
|
||||||
} else { |
|
||||||
rotationStartDate = getNextStartDate(fromPreviousRotationSlots: previousRotationSlots, includeBreakTime: false) ?? dispatcherStartDate |
|
||||||
} |
|
||||||
|
|
||||||
if shouldStartAtDispatcherDate { |
|
||||||
rotationStartDate = dispatcherStartDate |
|
||||||
shouldStartAtDispatcherDate = false |
|
||||||
} else { |
|
||||||
courts = rotationIndex == 0 ? courts : Array(courtsAvailable) |
|
||||||
} |
|
||||||
courts.sort() |
|
||||||
|
|
||||||
// Log courts availability and start date |
|
||||||
print("Courts available at rotation \(rotationIndex): \(courts)") |
|
||||||
print("Rotation start date: \(rotationStartDate)") |
|
||||||
|
|
||||||
// Check for court availability and break time conflicts |
|
||||||
if rotationIndex > 0, let freeCourtPreviousRotation = freeCourtPerRotation[rotationIndex - 1], !freeCourtPreviousRotation.isEmpty { |
|
||||||
print("Handling break time conflicts or waiting for free courts") |
|
||||||
let previousPreviousRotationSlots = slots.filter { $0.rotationIndex == rotationIndex - 2 && freeCourtPreviousRotation.contains($0.courtIndex) } |
|
||||||
var previousEndDate = getNextStartDate(fromPreviousRotationSlots: previousPreviousRotationSlots, includeBreakTime: accountUpperBracketBreakTime) |
|
||||||
var previousEndDateNoBreak = getNextStartDate(fromPreviousRotationSlots: previousPreviousRotationSlots, includeBreakTime: false) |
|
||||||
|
|
||||||
if let courtsUnavailability, previousEndDate != nil { |
|
||||||
previousEndDate = getFirstFreeCourt(startDate: previousEndDate!, duration: 0, courts: courts, courtsUnavailability: courtsUnavailability).earliestFreeDate |
|
||||||
} |
|
||||||
|
|
||||||
if let courtsUnavailability, previousEndDateNoBreak != nil { |
|
||||||
previousEndDateNoBreak = getFirstFreeCourt(startDate: previousEndDateNoBreak!, duration: 0, courts: courts, courtsUnavailability: courtsUnavailability).earliestFreeDate |
|
||||||
} |
|
||||||
|
|
||||||
let noBreakAlreadyTested = previousRotationSlots.anySatisfy { $0.startDate == previousEndDateNoBreak } |
|
||||||
|
|
||||||
if let previousEndDate, let previousEndDateNoBreak { |
|
||||||
let differenceWithBreak = rotationStartDate.timeIntervalSince(previousEndDate) |
|
||||||
let differenceWithoutBreak = rotationStartDate.timeIntervalSince(previousEndDateNoBreak) |
|
||||||
print("Difference with break: \(differenceWithBreak), without break: \(differenceWithoutBreak)") |
|
||||||
|
|
||||||
let timeDifferenceLimitInSeconds = Double(timeDifferenceLimit * 60) |
|
||||||
var difference = differenceWithBreak |
|
||||||
|
|
||||||
if differenceWithBreak <= 0, accountUpperBracketBreakTime == false { |
|
||||||
difference = differenceWithoutBreak |
|
||||||
} else if differenceWithBreak > timeDifferenceLimitInSeconds && differenceWithoutBreak > timeDifferenceLimitInSeconds { |
|
||||||
difference = noBreakAlreadyTested ? differenceWithBreak : max(differenceWithBreak, differenceWithoutBreak) |
|
||||||
} |
|
||||||
|
|
||||||
print("Final difference to evaluate: \(difference)") |
|
||||||
|
|
||||||
if (difference > timeDifferenceLimitInSeconds && rotationStartDate.addingTimeInterval(-difference) != previousEndDate) || difference < 0 { |
|
||||||
print(""" |
|
||||||
Adjusting rotation start: |
|
||||||
- Initial rotationStartDate: \(rotationStartDate) |
|
||||||
- Adjusted by difference: \(difference) |
|
||||||
- Adjusted rotationStartDate: \(rotationStartDate.addingTimeInterval(-difference)) |
|
||||||
- PreviousEndDate: \(previousEndDate) |
|
||||||
""") |
|
||||||
|
|
||||||
courts.removeAll(where: { freeCourtPreviousRotation.contains($0) }) |
|
||||||
freeCourtPerRotation[rotationIndex] = courts |
|
||||||
courts = freeCourtPreviousRotation |
|
||||||
rotationStartDate = rotationStartDate.addingTimeInterval(-difference) |
|
||||||
} |
|
||||||
} |
|
||||||
} else if let firstMatch = availableMatchs.first { |
|
||||||
let duration = firstMatch.matchFormat.getEstimatedDuration(additionalEstimationDuration) |
|
||||||
let courtsUnavailable = courtsUnavailable(startDate: rotationStartDate, duration: duration, courtsUnavailability: courtsUnavailability) |
|
||||||
|
|
||||||
if Array(Set(courtsAvailable).subtracting(Set(courtsUnavailable))).isEmpty { |
|
||||||
print("Issue: All courts unavailable in this rotation") |
|
||||||
if let courtsUnavailability { |
|
||||||
let computedStartDateAndCourts = getFirstFreeCourt(startDate: rotationStartDate, duration: duration, courts: courts, courtsUnavailability: courtsUnavailability) |
|
||||||
rotationStartDate = computedStartDateAndCourts.earliestFreeDate |
|
||||||
courts = computedStartDateAndCourts.availableCourts |
|
||||||
} else { |
|
||||||
issueFound = true |
|
||||||
} |
|
||||||
} else { |
|
||||||
courts = Array(Set(courtsAvailable).subtracting(Set(courtsUnavailable))) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Dispatch courts and schedule matches |
|
||||||
suitableDate = dispatchCourts(courts: courts, availableMatchs: &availableMatchs, slots: &slots, rotationIndex: rotationIndex, rotationStartDate: rotationStartDate, freeCourtPerRotation: &freeCourtPerRotation, courtsUnavailability: courtsUnavailability) |
|
||||||
rotationIndex += 1 |
|
||||||
} |
|
||||||
|
|
||||||
// Organize matches in slots |
|
||||||
var organizedSlots = [TimeMatch]() |
|
||||||
for i in 0..<rotationIndex { |
|
||||||
let courtsSorted = slots.filter { $0.rotationIndex == i }.map { $0.courtIndex }.sorted() |
|
||||||
let courts = randomizeCourts ? courtsSorted.shuffled() : courtsSorted |
|
||||||
var matches = slots.filter { $0.rotationIndex == i }.sorted(using: .keyPath(\.courtIndex)) |
|
||||||
|
|
||||||
for j in 0..<matches.count { |
|
||||||
matches[j].courtIndex = courts[j] |
|
||||||
organizedSlots.append(matches[j]) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
print("Finished roundDispatcher with \(organizedSlots.count) scheduled matches") |
|
||||||
|
|
||||||
return MatchDispatcher(timedMatches: organizedSlots, freeCourtPerRotation: freeCourtPerRotation, rotationCount: rotationIndex, issueFound: issueFound) |
|
||||||
} |
|
||||||
|
|
||||||
func dispatchCourts(courts: [Int], availableMatchs: inout [Match], slots: inout [TimeMatch], rotationIndex: Int, rotationStartDate: Date, freeCourtPerRotation: inout [Int: [Int]], courtsUnavailability: [DateInterval]?) -> Date { |
|
||||||
var matchPerRound = [String: Int]() |
|
||||||
var minimumTargetedEndDate = rotationStartDate |
|
||||||
|
|
||||||
// Log dispatch attempt |
|
||||||
print("Dispatching courts for rotation \(rotationIndex) with start date \(rotationStartDate) and available courts \(courts.sorted())") |
|
||||||
|
|
||||||
for (courtPosition, courtIndex) in courts.sorted().enumerated() { |
|
||||||
if let firstMatch = availableMatchs.first(where: { match in |
|
||||||
print("Trying to find a match for court \(courtIndex) in rotation \(rotationIndex)") |
|
||||||
|
|
||||||
let roundObject = match.roundObject! |
|
||||||
let duration = match.matchFormat.getEstimatedDuration(additionalEstimationDuration) |
|
||||||
|
|
||||||
let courtsUnavailable = courtsUnavailable(startDate: rotationStartDate, duration: duration, courtsUnavailability: courtsUnavailability) |
|
||||||
|
|
||||||
if courtsUnavailable.contains(courtIndex) { |
|
||||||
print("Returning false: Court \(courtIndex) unavailable due to schedule conflicts during \(rotationStartDate).") |
|
||||||
return false |
|
||||||
} |
|
||||||
|
|
||||||
let canBePlayed = roundMatchCanBePlayed(match, roundObject: roundObject, slots: slots, rotationIndex: rotationIndex, targetedStartDate: rotationStartDate, minimumTargetedEndDate: &minimumTargetedEndDate) |
|
||||||
|
|
||||||
if !canBePlayed { |
|
||||||
print("Returning false: Match \(match.roundAndMatchTitle()) can't be played due to constraints.") |
|
||||||
return false |
|
||||||
} |
|
||||||
|
|
||||||
let currentRotationSameRoundMatches = matchPerRound[roundObject.id] ?? 0 |
|
||||||
let roundMatchesCount = roundObject.playedMatches().count |
|
||||||
|
|
||||||
if shouldHandleUpperRoundSlice { |
|
||||||
if roundObject.parent == nil, roundObject.index > 1, currentRotationSameRoundMatches == 0, courts.count - matchPerRound.count < 2 { |
|
||||||
return false |
|
||||||
} |
|
||||||
else if roundObject.parent == nil, roundObject.index == 0, matchPerRound.isEmpty == false { |
|
||||||
return false |
|
||||||
} |
|
||||||
else if roundObject.parent == nil, roundObject.index == 1, currentRotationSameRoundMatches >= 0, courtsAvailable.count > 0 { |
|
||||||
return true |
|
||||||
} |
|
||||||
else if roundObject.parent == nil && roundMatchesCount > courts.count && currentRotationSameRoundMatches >= min(roundMatchesCount / 2, courts.count) { |
|
||||||
print("Returning false: Too many matches already played in the current rotation for round \(roundObject.roundTitle()).") |
|
||||||
return false |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
let indexInRound = match.indexInRound() |
|
||||||
|
|
||||||
|
|
||||||
if shouldTryToFillUpCourtsAvailable == false { |
|
||||||
if roundObject.parent == nil && roundObject.index > 1 && indexInRound == 0, let nextMatch = match.next() { |
|
||||||
|
|
||||||
var nextMinimumTargetedEndDate = minimumTargetedEndDate |
|
||||||
if courtPosition < courts.count - 1 && canBePlayed && roundMatchCanBePlayed(nextMatch, roundObject: roundObject, slots: slots, rotationIndex: rotationIndex, targetedStartDate: rotationStartDate, minimumTargetedEndDate: &nextMinimumTargetedEndDate) { |
|
||||||
print("Returning true: Both current \(match.index) and next match \(nextMatch.index) can be played in rotation \(rotationIndex).") |
|
||||||
return true |
|
||||||
} else { |
|
||||||
print("Returning false: Either current match or next match cannot be played in rotation \(rotationIndex).") |
|
||||||
return false |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
print("Returning true: Match \(match.roundAndMatchTitle()) can be played on court \(courtIndex).") |
|
||||||
return canBePlayed |
|
||||||
}) { |
|
||||||
print("Found match: \(firstMatch.roundAndMatchTitle()) for court \(courtIndex) at \(rotationStartDate)") |
|
||||||
|
|
||||||
matchPerRound[firstMatch.roundObject!.id, default: 0] += 1 |
|
||||||
|
|
||||||
let timeMatch = TimeMatch( |
|
||||||
matchID: firstMatch.id, |
|
||||||
rotationIndex: rotationIndex, |
|
||||||
courtIndex: courtIndex, |
|
||||||
startDate: rotationStartDate, |
|
||||||
durationLeft: firstMatch.matchFormat.getEstimatedDuration(additionalEstimationDuration), |
|
||||||
minimumBreakTime: firstMatch.matchFormat.breakTime.breakTime |
|
||||||
) |
|
||||||
|
|
||||||
slots.append(timeMatch) |
|
||||||
availableMatchs.removeAll(where: { $0.id == firstMatch.id }) |
|
||||||
} else { |
|
||||||
print("No suitable match found for court \(courtIndex) in rotation \(rotationIndex). Adding court to freeCourtPerRotation.") |
|
||||||
freeCourtPerRotation[rotationIndex]?.append(courtIndex) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
if freeCourtPerRotation[rotationIndex]?.count == courtsAvailable.count { |
|
||||||
print("All courts in rotation \(rotationIndex) are free, minimumTargetedEndDate : \(minimumTargetedEndDate)") |
|
||||||
} |
|
||||||
|
|
||||||
if let courtsUnavailability { |
|
||||||
let computedStartDateAndCourts = getFirstFreeCourt(startDate: minimumTargetedEndDate, duration: 0, courts: courts, courtsUnavailability: courtsUnavailability) |
|
||||||
return computedStartDateAndCourts.earliestFreeDate |
|
||||||
} |
|
||||||
|
|
||||||
return minimumTargetedEndDate |
|
||||||
} |
|
||||||
|
|
||||||
@discardableResult func updateBracketSchedule(tournament: Tournament, fromRoundId roundId: String?, fromMatchId matchId: String?, startDate: Date) -> Bool { |
|
||||||
|
|
||||||
let upperRounds: [Round] = tournament.rounds() |
|
||||||
let allMatches: [Match] = tournament.allMatches().filter({ $0.hasEnded() == false && $0.hasStarted() == false }) |
|
||||||
|
|
||||||
var rounds = [Round]() |
|
||||||
|
|
||||||
if let groupStageLoserBracketRound = tournament.groupStageLoserBracket() { |
|
||||||
rounds.append(groupStageLoserBracketRound) |
|
||||||
} |
|
||||||
|
|
||||||
if shouldEndRoundBeforeStartingNext { |
|
||||||
rounds.append(contentsOf: upperRounds.flatMap { |
|
||||||
[$0] + $0.loserRoundsAndChildren() |
|
||||||
}) |
|
||||||
} else { |
|
||||||
rounds.append(contentsOf: upperRounds.map { |
|
||||||
$0 |
|
||||||
} + upperRounds.flatMap { |
|
||||||
$0.loserRoundsAndChildren() |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
let flattenedMatches = rounds.flatMap { round in |
|
||||||
round._matches().filter({ $0.disabled == false && $0.hasEnded() == false && $0.hasStarted() == false }).sorted(by: \.index) |
|
||||||
} |
|
||||||
|
|
||||||
flattenedMatches.forEach({ |
|
||||||
if (roundId == nil && matchId == nil) || $0.startDate?.isEarlierThan(startDate) == false { |
|
||||||
$0.startDate = nil |
|
||||||
$0.removeCourt() |
|
||||||
$0.confirmed = false |
|
||||||
} |
|
||||||
}) |
|
||||||
|
|
||||||
// if let roundId { |
|
||||||
// if let round : Round = Store.main.findById(roundId) { |
|
||||||
// let matches = round._matches().filter({ $0.disabled == false }).sorted(by: \.index) |
|
||||||
// round.resetFromRoundAllMatchesStartDate() |
|
||||||
// flattenedMatches = matches + flattenedMatches |
|
||||||
// } |
|
||||||
// |
|
||||||
// } else if let matchId { |
|
||||||
// if let match : Match = Store.main.findById(matchId) { |
|
||||||
// if let round = match.roundObject { |
|
||||||
// round.resetFromRoundAllMatchesStartDate(from: match) |
|
||||||
// } |
|
||||||
// flattenedMatches = [match] + flattenedMatches |
|
||||||
// } |
|
||||||
// } |
|
||||||
|
|
||||||
if let roundId, let matchId { |
|
||||||
//todo |
|
||||||
if let index = flattenedMatches.firstIndex(where: { $0.round == roundId && $0.id == matchId }) { |
|
||||||
flattenedMatches[index...].forEach { |
|
||||||
$0.startDate = nil |
|
||||||
$0.removeCourt() |
|
||||||
$0.confirmed = false |
|
||||||
} |
|
||||||
} |
|
||||||
} else if let roundId { |
|
||||||
//todo |
|
||||||
if let index = flattenedMatches.firstIndex(where: { $0.round == roundId }) { |
|
||||||
flattenedMatches[index...].forEach { |
|
||||||
$0.startDate = nil |
|
||||||
$0.removeCourt() |
|
||||||
$0.confirmed = false |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
let matches: [Match] = allMatches.filter { $0.startDate?.isEarlierThan(startDate) == true && $0.startDate?.dayInt == startDate.dayInt } |
|
||||||
let usedCourts = getAvailableCourts(from: matches) |
|
||||||
let initialCourts: [Int] = usedCourts.filter { (court, availableDate) in |
|
||||||
availableDate <= startDate |
|
||||||
}.sorted(by: \.1).compactMap { $0.0 } |
|
||||||
|
|
||||||
let courts : [Int]? = initialCourts.isEmpty ? nil : initialCourts |
|
||||||
|
|
||||||
print("initial available courts at beginning: \(courts ?? [])") |
|
||||||
|
|
||||||
let roundDispatch = self.roundDispatcher(flattenedMatches: flattenedMatches, dispatcherStartDate: startDate, initialCourts: courts) |
|
||||||
|
|
||||||
roundDispatch.timedMatches.forEach { matchSchedule in |
|
||||||
if let match = flattenedMatches.first(where: { $0.id == matchSchedule.matchID }) { |
|
||||||
match.startDate = matchSchedule.startDate |
|
||||||
match.setCourt(matchSchedule.courtIndex) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
do { |
|
||||||
try self.tournamentStore?.matches.addOrUpdate(contentOfs: allMatches) |
|
||||||
} catch { |
|
||||||
Logger.error(error) |
|
||||||
} |
|
||||||
|
|
||||||
return roundDispatch.issueFound |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
func courtsUnavailable(startDate: Date, duration: Int, courtsUnavailability: [DateInterval]?) -> [Int] { |
|
||||||
let endDate = startDate.addingTimeInterval(Double(duration) * 60) |
|
||||||
guard let courtsUnavailability else { return [] } |
|
||||||
let groupedBy = Dictionary(grouping: courtsUnavailability, by: { $0.courtIndex }) |
|
||||||
let courts = groupedBy.keys |
|
||||||
return courts.filter { |
|
||||||
courtUnavailable(courtIndex: $0, from: startDate, to: endDate, source: courtsUnavailability) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func courtUnavailable(courtIndex: Int, from startDate: Date, to endDate: Date, source: [DateInterval]) -> Bool { |
|
||||||
let courtLockedSchedule = source.filter({ $0.courtIndex == courtIndex }) |
|
||||||
return courtLockedSchedule.anySatisfy({ dateInterval in |
|
||||||
let range = startDate..<endDate |
|
||||||
return dateInterval.range.overlaps(range) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
func getFirstFreeCourt(startDate: Date, duration: Int, courts: [Int], courtsUnavailability: [DateInterval]) -> (earliestFreeDate: Date, availableCourts: [Int]) { |
|
||||||
var earliestEndDate: Date? |
|
||||||
var availableCourtsAtEarliest: [Int] = [] |
|
||||||
|
|
||||||
// Iterate through each court and find the earliest time it becomes free |
|
||||||
for courtIndex in courts { |
|
||||||
let unavailabilityForCourt = courtsUnavailability.filter { $0.courtIndex == courtIndex } |
|
||||||
var isAvailable = true |
|
||||||
|
|
||||||
for interval in unavailabilityForCourt { |
|
||||||
if interval.startDate <= startDate && interval.endDate > startDate { |
|
||||||
isAvailable = false |
|
||||||
if let currentEarliest = earliestEndDate { |
|
||||||
earliestEndDate = min(currentEarliest, interval.endDate) |
|
||||||
} else { |
|
||||||
earliestEndDate = interval.endDate |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// If the court is available at the start date, add it to the list of available courts |
|
||||||
if isAvailable { |
|
||||||
availableCourtsAtEarliest.append(courtIndex) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// If there are no unavailable courts, return the original start date and all courts |
|
||||||
if let earliestEndDate = earliestEndDate { |
|
||||||
// Find which courts will be available at the earliest free date |
|
||||||
let courtsAvailableAtEarliest = courts.filter { courtIndex in |
|
||||||
let unavailabilityForCourt = courtsUnavailability.filter { $0.courtIndex == courtIndex } |
|
||||||
return unavailabilityForCourt.allSatisfy { $0.endDate <= earliestEndDate } |
|
||||||
} |
|
||||||
return (earliestFreeDate: earliestEndDate, availableCourts: courtsAvailableAtEarliest) |
|
||||||
} else { |
|
||||||
// If no courts were unavailable, all courts are available at the start date |
|
||||||
return (earliestFreeDate: startDate.addingTimeInterval(Double(duration) * 60), availableCourts: courts) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func updateSchedule(tournament: Tournament) -> Bool { |
|
||||||
if tournament.courtCount < courtsAvailable.count { |
|
||||||
courtsAvailable = Set(tournament.courtsAvailable()) |
|
||||||
} |
|
||||||
var lastDate = tournament.startDate |
|
||||||
if tournament.groupStageCount > 0 { |
|
||||||
lastDate = updateGroupStageSchedule(tournament: tournament) |
|
||||||
} |
|
||||||
if tournament.groupStages(atStep: 1).isEmpty == false { |
|
||||||
lastDate = updateGroupStageSchedule(tournament: tournament, atStep: 1, startDate: lastDate) |
|
||||||
} |
|
||||||
return updateBracketSchedule(tournament: tournament, fromRoundId: nil, fromMatchId: nil, startDate: lastDate) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
struct GroupStageTimeMatch { |
|
||||||
let matchID: String |
|
||||||
let rotationIndex: Int |
|
||||||
var courtIndex: Int |
|
||||||
let groupIndex: Int |
|
||||||
} |
|
||||||
|
|
||||||
struct TimeMatch { |
|
||||||
let matchID: String |
|
||||||
let rotationIndex: Int |
|
||||||
var courtIndex: Int |
|
||||||
var startDate: Date |
|
||||||
var durationLeft: Int //in minutes |
|
||||||
var minimumBreakTime: Int //in minutes |
|
||||||
|
|
||||||
func estimatedEndDate(includeBreakTime: Bool) -> Date { |
|
||||||
let minutesToAdd = Double(durationLeft + (includeBreakTime ? minimumBreakTime : 0)) |
|
||||||
return startDate.addingTimeInterval(minutesToAdd * 60.0) |
|
||||||
} |
|
||||||
|
|
||||||
var computedEndDateForSorting: Date { |
|
||||||
estimatedEndDate(includeBreakTime: false) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
struct GroupStageMatchDispatcher { |
|
||||||
let timedMatches: [GroupStageTimeMatch] |
|
||||||
let freeCourtPerRotation: [Int: [Int]] |
|
||||||
let rotationCount: Int |
|
||||||
let groupLastRotation: [Int: Int] |
|
||||||
} |
|
||||||
|
|
||||||
struct MatchDispatcher { |
|
||||||
let timedMatches: [TimeMatch] |
|
||||||
let freeCourtPerRotation: [Int: [Int]] |
|
||||||
let rotationCount: Int |
|
||||||
let issueFound: Bool |
|
||||||
} |
|
||||||
|
|
||||||
extension Match { |
|
||||||
func teamIds() -> [String] { |
|
||||||
return teams().map { $0.id } |
|
||||||
} |
|
||||||
|
|
||||||
func containsTeamId(_ id: String) -> Bool { |
|
||||||
return teamIds().contains(id) |
|
||||||
} |
|
||||||
|
|
||||||
func containsTeamIndex(_ id: String) -> Bool { |
|
||||||
matchUp().contains(id) |
|
||||||
} |
|
||||||
|
|
||||||
func matchUp() -> [String] { |
|
||||||
guard let groupStageObject else { |
|
||||||
return [] |
|
||||||
} |
|
||||||
|
|
||||||
return groupStageObject._matchUp(for: index).map { groupStageObject.id + "_\($0)" } |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,66 +0,0 @@ |
|||||||
// |
|
||||||
// MockData.swift |
|
||||||
// PadelClub |
|
||||||
// |
|
||||||
// Created by Razmig Sarkissian on 20/03/2024. |
|
||||||
// |
|
||||||
|
|
||||||
import Foundation |
|
||||||
|
|
||||||
extension Court { |
|
||||||
static func mock() -> Court { |
|
||||||
Court(index: 0, club: "", name: "Test") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
extension Event { |
|
||||||
static func mock() -> Event { |
|
||||||
Event() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
extension Club { |
|
||||||
static func mock() -> Club { |
|
||||||
Club(name: "AUC", acronym: "AUC") |
|
||||||
} |
|
||||||
|
|
||||||
static func newEmptyInstance() -> Club { |
|
||||||
Club(name: "", acronym: "") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
extension GroupStage { |
|
||||||
static func mock() -> GroupStage { |
|
||||||
GroupStage(tournament: "", index: 0, size: 4) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
extension Round { |
|
||||||
static func mock() -> Round { |
|
||||||
Round(tournament: "", index: 0) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
extension Tournament { |
|
||||||
static func mock() -> Tournament { |
|
||||||
return Tournament(groupStageSortMode: .snake, teamSorting: .inscriptionDate, federalCategory: .men, federalLevelCategory: .p100, federalAgeCategory: .senior) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
extension Match { |
|
||||||
static func mock() -> Match { |
|
||||||
return Match(index: 0) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
extension TeamRegistration { |
|
||||||
static func mock() -> TeamRegistration { |
|
||||||
return TeamRegistration(tournament: "") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
extension PlayerRegistration { |
|
||||||
static func mock() -> PlayerRegistration { |
|
||||||
return PlayerRegistration(firstName: "Raz", lastName: "Shark", sex: .male) |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,53 +0,0 @@ |
|||||||
// |
|
||||||
// PlayerPaymentType.swift |
|
||||||
// PadelClub |
|
||||||
// |
|
||||||
// Created by Laurent Morvillier on 11/02/2025. |
|
||||||
// |
|
||||||
|
|
||||||
import Foundation |
|
||||||
|
|
||||||
enum PlayerPaymentType: Int, CaseIterable, Identifiable, Codable { |
|
||||||
|
|
||||||
init?(rawValue: Int?) { |
|
||||||
guard let value = rawValue else { return nil } |
|
||||||
self.init(rawValue: value) |
|
||||||
} |
|
||||||
|
|
||||||
var id: Self { |
|
||||||
self |
|
||||||
} |
|
||||||
|
|
||||||
case cash = 0 |
|
||||||
case lydia = 1 |
|
||||||
case gift = 2 |
|
||||||
case check = 3 |
|
||||||
case paylib = 4 |
|
||||||
case bankTransfer = 5 |
|
||||||
case clubHouse = 6 |
|
||||||
case creditCard = 7 |
|
||||||
case forfeit = 8 |
|
||||||
|
|
||||||
func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { |
|
||||||
switch self { |
|
||||||
case .check: |
|
||||||
return "Chèque" |
|
||||||
case .cash: |
|
||||||
return "Cash" |
|
||||||
case .lydia: |
|
||||||
return "Lydia" |
|
||||||
case .paylib: |
|
||||||
return "Paylib" |
|
||||||
case .bankTransfer: |
|
||||||
return "Virement" |
|
||||||
case .clubHouse: |
|
||||||
return "Clubhouse" |
|
||||||
case .creditCard: |
|
||||||
return "CB" |
|
||||||
case .forfeit: |
|
||||||
return "Forfait" |
|
||||||
case .gift: |
|
||||||
return "Offert" |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,510 +0,0 @@ |
|||||||
// |
|
||||||
// PlayerRegistration.swift |
|
||||||
// Padel Tournament |
|
||||||
// |
|
||||||
// Created by razmig on 10/03/2024. |
|
||||||
// |
|
||||||
|
|
||||||
import Foundation |
|
||||||
import LeStorage |
|
||||||
|
|
||||||
@Observable |
|
||||||
final class PlayerRegistration: BasePlayerRegistration, SideStorable { |
|
||||||
|
|
||||||
func localizedSourceLabel() -> String { |
|
||||||
switch source { |
|
||||||
case .frenchFederation: |
|
||||||
return "base fédérale" |
|
||||||
case .beachPadel: |
|
||||||
return "beach-padel" |
|
||||||
case nil: |
|
||||||
if registeredOnline { |
|
||||||
return "à vérifier vous-même" |
|
||||||
} else { |
|
||||||
return "créé par vous-même" |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
init(teamRegistration: String? = nil, firstName: String, lastName: String, licenceId: String? = nil, rank: Int? = nil, paymentType: PlayerPaymentType? = nil, sex: PlayerSexType? = nil, tournamentPlayed: Int? = nil, points: Double? = nil, clubName: String? = nil, ligueName: String? = nil, assimilation: String? = nil, phoneNumber: String? = nil, email: String? = nil, birthdate: String? = nil, computedRank: Int = 0, source: PlayerRegistration.PlayerDataSource? = nil, hasArrived: Bool = false, coach: Bool = false, captain: Bool = false, registeredOnline: Bool = false, timeToConfirm: Date? = nil, registrationStatus: PlayerRegistration.RegistrationStatus = PlayerRegistration.RegistrationStatus.waiting, paymentId: String? = nil) { |
|
||||||
super.init() |
|
||||||
self.teamRegistration = teamRegistration |
|
||||||
self.firstName = firstName |
|
||||||
self.lastName = lastName |
|
||||||
self.licenceId = licenceId |
|
||||||
self.rank = rank |
|
||||||
self.paymentType = paymentType |
|
||||||
self.sex = sex |
|
||||||
self.tournamentPlayed = tournamentPlayed |
|
||||||
self.points = points |
|
||||||
self.clubName = clubName |
|
||||||
self.ligueName = ligueName |
|
||||||
self.assimilation = assimilation |
|
||||||
self.phoneNumber = phoneNumber |
|
||||||
self.email = email |
|
||||||
self.birthdate = birthdate |
|
||||||
self.computedRank = computedRank |
|
||||||
self.source = source |
|
||||||
self.hasArrived = hasArrived |
|
||||||
self.coach = coach |
|
||||||
self.captain = captain |
|
||||||
self.registeredOnline = registeredOnline |
|
||||||
self.timeToConfirm = timeToConfirm |
|
||||||
self.registrationStatus = registrationStatus |
|
||||||
self.paymentId = paymentId |
|
||||||
} |
|
||||||
|
|
||||||
internal init(importedPlayer: ImportedPlayer) { |
|
||||||
super.init() |
|
||||||
self.teamRegistration = "" |
|
||||||
self.firstName = (importedPlayer.firstName ?? "").prefixTrimmed(50).capitalized |
|
||||||
self.lastName = (importedPlayer.lastName ?? "").prefixTrimmed(50).uppercased() |
|
||||||
self.licenceId = importedPlayer.license?.prefixTrimmed(50) ?? nil |
|
||||||
self.rank = Int(importedPlayer.rank) |
|
||||||
self.sex = importedPlayer.male ? .male : .female |
|
||||||
self.tournamentPlayed = importedPlayer.tournamentPlayed |
|
||||||
self.points = importedPlayer.getPoints() |
|
||||||
self.clubName = importedPlayer.clubName?.prefixTrimmed(200) |
|
||||||
self.ligueName = importedPlayer.ligueName?.prefixTrimmed(200) |
|
||||||
self.assimilation = importedPlayer.assimilation?.prefixTrimmed(50) |
|
||||||
self.source = .frenchFederation |
|
||||||
self.birthdate = importedPlayer.birthYear?.prefixTrimmed(50) |
|
||||||
} |
|
||||||
|
|
||||||
internal init?(federalData: [String], sex: Int, sexUnknown: Bool) { |
|
||||||
super.init() |
|
||||||
let _lastName = federalData[0].trimmed.uppercased() |
|
||||||
let _firstName = federalData[1].trimmed.capitalized |
|
||||||
if _lastName.isEmpty && _firstName.isEmpty { return nil } |
|
||||||
lastName = _lastName.prefixTrimmed(50) |
|
||||||
firstName = _firstName.prefixTrimmed(50) |
|
||||||
birthdate = federalData[2].formattedAsBirthdate().prefixTrimmed(50) |
|
||||||
licenceId = federalData[3].prefixTrimmed(50) |
|
||||||
clubName = federalData[4].prefixTrimmed(200) |
|
||||||
let stringRank = federalData[5] |
|
||||||
if stringRank.isEmpty { |
|
||||||
rank = nil |
|
||||||
} else { |
|
||||||
rank = Int(stringRank) |
|
||||||
} |
|
||||||
let _email = federalData[6] |
|
||||||
if _email.isEmpty == false { |
|
||||||
self.email = _email.prefixTrimmed(50) |
|
||||||
} |
|
||||||
let _phoneNumber = federalData[7] |
|
||||||
if _phoneNumber.isEmpty == false { |
|
||||||
self.phoneNumber = _phoneNumber.prefixTrimmed(50) |
|
||||||
} |
|
||||||
|
|
||||||
source = .beachPadel |
|
||||||
if sexUnknown { |
|
||||||
if sex == 1 && FileImportManager.shared.foundInWomenData(license: federalData[3]) { |
|
||||||
self.sex = .female |
|
||||||
} else if FileImportManager.shared.foundInMenData(license: federalData[3]) { |
|
||||||
self.sex = .male |
|
||||||
} else { |
|
||||||
self.sex = nil |
|
||||||
} |
|
||||||
} else { |
|
||||||
self.sex = PlayerSexType(rawValue: sex) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
required init(from decoder: any Decoder) throws { |
|
||||||
try super.init(from: decoder) |
|
||||||
} |
|
||||||
|
|
||||||
required public init() { |
|
||||||
super.init() |
|
||||||
} |
|
||||||
|
|
||||||
var tournamentStore: TournamentStore? { |
|
||||||
guard let storeId else { |
|
||||||
fatalError("missing store id for \(String(describing: type(of: self)))") |
|
||||||
} |
|
||||||
return TournamentLibrary.shared.store(tournamentId: storeId) |
|
||||||
// if let store = self.store as? TournamentStore { |
|
||||||
// return store |
|
||||||
// } |
|
||||||
} |
|
||||||
|
|
||||||
var computedAge: Int? { |
|
||||||
if let birthdate { |
|
||||||
let components = birthdate.components(separatedBy: "/") |
|
||||||
if let age = components.last, let ageInt = Int(age) { |
|
||||||
let year = Calendar.current.getSportAge() |
|
||||||
|
|
||||||
if age.count == 2 { //si l'année est sur 2 chiffres dans le fichier |
|
||||||
if ageInt < 23 { |
|
||||||
return year - 2000 - ageInt |
|
||||||
} else { |
|
||||||
return year - 2000 + 100 - ageInt |
|
||||||
} |
|
||||||
} else { //si l'année est représenté sur 4 chiffres |
|
||||||
return year - ageInt |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func pasteData(_ exportFormat: ExportFormat = .rawText) -> String { |
|
||||||
switch exportFormat { |
|
||||||
case .rawText: |
|
||||||
return [firstName.capitalized, lastName.capitalized, licenceId?.computedLicense].compactMap({ $0 }).joined(separator: exportFormat.separator()) |
|
||||||
case .csv: |
|
||||||
return [lastName.uppercased() + " " + firstName.capitalized].joined(separator: exportFormat.separator()) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func isPlaying() -> Bool { |
|
||||||
team()?.isPlaying() == true |
|
||||||
} |
|
||||||
|
|
||||||
func contains(_ searchField: String) -> Bool { |
|
||||||
let nameComponents = searchField.canonicalVersion.split(separator: " ") |
|
||||||
|
|
||||||
if nameComponents.count > 1 { |
|
||||||
let pairs = nameComponents.pairs() |
|
||||||
return pairs.contains(where: { |
|
||||||
(firstName.canonicalVersion.localizedCaseInsensitiveContains(String($0)) && |
|
||||||
lastName.canonicalVersion.localizedCaseInsensitiveContains(String($1))) || |
|
||||||
(firstName.canonicalVersion.localizedCaseInsensitiveContains(String($1)) && |
|
||||||
lastName.canonicalVersion.localizedCaseInsensitiveContains(String($0))) |
|
||||||
}) |
|
||||||
} else { |
|
||||||
return nameComponents.contains { component in |
|
||||||
firstName.canonicalVersion.localizedCaseInsensitiveContains(component) || |
|
||||||
lastName.canonicalVersion.localizedCaseInsensitiveContains(component) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func isSameAs(_ player: PlayerRegistration) -> Bool { |
|
||||||
firstName.trimmedMultiline.canonicalVersion.localizedCaseInsensitiveCompare(player.firstName.trimmedMultiline.canonicalVersion) == .orderedSame && |
|
||||||
lastName.trimmedMultiline.canonicalVersion.localizedCaseInsensitiveCompare(player.lastName.trimmedMultiline.canonicalVersion) == .orderedSame |
|
||||||
} |
|
||||||
|
|
||||||
func tournament() -> Tournament? { |
|
||||||
guard let tournament = team()?.tournament else { return nil } |
|
||||||
return Store.main.findById(tournament) |
|
||||||
} |
|
||||||
|
|
||||||
func team() -> TeamRegistration? { |
|
||||||
guard let teamRegistration else { return nil } |
|
||||||
return self.tournamentStore?.teamRegistrations.findById(teamRegistration) |
|
||||||
} |
|
||||||
|
|
||||||
func isHere() -> Bool { |
|
||||||
hasArrived |
|
||||||
} |
|
||||||
|
|
||||||
func hasPaid() -> Bool { |
|
||||||
paymentType != nil |
|
||||||
} |
|
||||||
|
|
||||||
func playerLabel(_ displayStyle: DisplayStyle = .wide) -> String { |
|
||||||
switch displayStyle { |
|
||||||
case .wide, .title: |
|
||||||
return lastName.trimmed.capitalized + " " + firstName.trimmed.capitalized |
|
||||||
case .short: |
|
||||||
let names = lastName.components(separatedBy: .whitespaces) |
|
||||||
if lastName.components(separatedBy: .whitespaces).count > 1 { |
|
||||||
if let firstLongWord = names.first(where: { $0.count > 3 }) { |
|
||||||
return firstLongWord.trimmed.capitalized.trunc(length: 10) + " " + firstName.trimmed.prefix(1).capitalized + "." |
|
||||||
} |
|
||||||
} |
|
||||||
return lastName.trimmed.capitalized.trunc(length: 10) + " " + firstName.trimmed.prefix(1).capitalized + "." |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func isImported() -> Bool { |
|
||||||
source == .beachPadel |
|
||||||
} |
|
||||||
|
|
||||||
func unrankedOrUnknown() -> Bool { |
|
||||||
source == nil |
|
||||||
} |
|
||||||
|
|
||||||
func isValidLicenseNumber(year: Int) -> Bool { |
|
||||||
guard let licenceId else { return false } |
|
||||||
guard licenceId.isLicenseNumber else { return false } |
|
||||||
guard licenceId.suffix(6) == "(\(year))" else { return false } |
|
||||||
return true |
|
||||||
} |
|
||||||
|
|
||||||
@objc |
|
||||||
var canonicalName: String { |
|
||||||
playerLabel().folding(options: .diacriticInsensitive, locale: .current).lowercased() |
|
||||||
} |
|
||||||
|
|
||||||
func rankLabel(_ displayStyle: DisplayStyle = .wide) -> String { |
|
||||||
if let rank, rank > 0 { |
|
||||||
if rank != computedRank { |
|
||||||
return computedRank.formatted() + " (" + rank.formatted() + ")" |
|
||||||
} else { |
|
||||||
return rank.formatted() |
|
||||||
} |
|
||||||
} else { |
|
||||||
return "non classé" + (isMalePlayer() ? "" : "e") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func updateRank(from sources: [CSVParser], lastRank: Int?) async throws { |
|
||||||
#if DEBUG_TIME |
|
||||||
let start = Date() |
|
||||||
defer { |
|
||||||
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) |
|
||||||
print("func updateRank()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) |
|
||||||
} |
|
||||||
#endif |
|
||||||
|
|
||||||
if let dataFound = try await history(from: sources) { |
|
||||||
await MainActor.run { |
|
||||||
rank = dataFound.rankValue?.toInt() |
|
||||||
points = dataFound.points |
|
||||||
tournamentPlayed = dataFound.tournamentCountValue?.toInt() |
|
||||||
} |
|
||||||
} else if let dataFound = try await historyFromName(from: sources) { |
|
||||||
await MainActor.run { |
|
||||||
rank = dataFound.rankValue?.toInt() |
|
||||||
points = dataFound.points |
|
||||||
tournamentPlayed = dataFound.tournamentCountValue?.toInt() |
|
||||||
} |
|
||||||
} else { |
|
||||||
await MainActor.run { |
|
||||||
rank = lastRank |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func history(from sources: [CSVParser]) async throws -> Line? { |
|
||||||
#if DEBUG_TIME |
|
||||||
let start = Date() |
|
||||||
defer { |
|
||||||
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) |
|
||||||
print("func history()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) |
|
||||||
} |
|
||||||
#endif |
|
||||||
|
|
||||||
guard let license = licenceId?.strippedLicense else { |
|
||||||
return nil // Do NOT call historyFromName here, let updateRank handle it |
|
||||||
} |
|
||||||
|
|
||||||
let filteredSources = sources.filter { $0.maleData == isMalePlayer() } |
|
||||||
|
|
||||||
return await withTaskGroup(of: Line?.self) { group in |
|
||||||
for source in filteredSources { |
|
||||||
group.addTask { |
|
||||||
guard !Task.isCancelled else { return nil } |
|
||||||
return try? await source.first { $0.rawValue.contains(";\(license);") } |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
for await result in group { |
|
||||||
if let result { |
|
||||||
group.cancelAll() // Stop other tasks as soon as we find a match |
|
||||||
return result |
|
||||||
} |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func historyFromName(from sources: [CSVParser]) async throws -> Line? { |
|
||||||
#if DEBUG |
|
||||||
let start = Date() |
|
||||||
defer { |
|
||||||
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) |
|
||||||
print("func historyFromName()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) |
|
||||||
} |
|
||||||
#endif |
|
||||||
|
|
||||||
let filteredSources = sources.filter { $0.maleData == isMalePlayer() } |
|
||||||
let normalizedLastName = lastName.canonicalVersionWithPunctuation |
|
||||||
let normalizedFirstName = firstName.canonicalVersionWithPunctuation |
|
||||||
|
|
||||||
return await withTaskGroup(of: Line?.self) { group in |
|
||||||
for source in filteredSources { |
|
||||||
group.addTask { |
|
||||||
guard !Task.isCancelled else { print("Cancelled"); return nil } |
|
||||||
return try? await source.first { |
|
||||||
let lineValue = $0.rawValue.canonicalVersionWithPunctuation |
|
||||||
return lineValue.contains(";\(normalizedLastName);\(normalizedFirstName);") |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
for await result in group { |
|
||||||
if let result { |
|
||||||
group.cancelAll() // Stop other tasks as soon as we find a match |
|
||||||
return result |
|
||||||
} |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func setComputedRank(in tournament: Tournament) { |
|
||||||
let currentRank = rank ?? tournament.unrankValue(for: isMalePlayer()) ?? 90_000 |
|
||||||
switch tournament.tournamentCategory { |
|
||||||
case .men: |
|
||||||
computedRank = isMalePlayer() ? currentRank : currentRank + PlayerRegistration.addon(for: currentRank, manMax: tournament.maleUnrankedValue ?? 0, womanMax: tournament.femaleUnrankedValue ?? 0) |
|
||||||
default: |
|
||||||
computedRank = currentRank |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func isMalePlayer() -> Bool { |
|
||||||
sex == .male |
|
||||||
} |
|
||||||
|
|
||||||
func validateLicenceId(_ year: Int) { |
|
||||||
if let currentLicenceId = licenceId { |
|
||||||
if currentLicenceId.trimmed.hasSuffix("(\(year-1))") { |
|
||||||
self.licenceId = currentLicenceId.replacingOccurrences(of: "\(year-1)", with: "\(year)") |
|
||||||
} else if let computedLicense = currentLicenceId.strippedLicense?.computedLicense { |
|
||||||
self.licenceId = computedLicense + " (\(year))" |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func hasHomonym() -> Bool { |
|
||||||
let federalContext = PersistenceController.shared.localContainer.viewContext |
|
||||||
let fetchRequest = ImportedPlayer.fetchRequest() |
|
||||||
let predicate = NSPredicate(format: "firstName == %@ && lastName == %@", firstName, lastName) |
|
||||||
fetchRequest.predicate = predicate |
|
||||||
|
|
||||||
do { |
|
||||||
let count = try federalContext.count(for: fetchRequest) |
|
||||||
return count > 1 |
|
||||||
} catch { |
|
||||||
|
|
||||||
} |
|
||||||
return false |
|
||||||
} |
|
||||||
|
|
||||||
func hasPaidOnline() -> Bool { |
|
||||||
registrationStatus == .confirmed && paymentId != nil && paymentType == .creditCard |
|
||||||
} |
|
||||||
|
|
||||||
func hasConfirmed() -> Bool { |
|
||||||
registrationStatus == .confirmed |
|
||||||
} |
|
||||||
|
|
||||||
func confirmRegistration() { |
|
||||||
registrationStatus = .confirmed |
|
||||||
} |
|
||||||
|
|
||||||
enum PlayerDataSource: Int, Codable { |
|
||||||
case frenchFederation = 0 |
|
||||||
case beachPadel = 1 |
|
||||||
} |
|
||||||
|
|
||||||
enum RegistrationStatus: Int, Codable, CaseIterable, Identifiable { |
|
||||||
case waiting = 0 |
|
||||||
case pending = 1 |
|
||||||
case confirmed = 2 |
|
||||||
case canceled = 3 |
|
||||||
|
|
||||||
var id: Int { self.rawValue } |
|
||||||
|
|
||||||
func localizedRegistrationStatus() -> String { |
|
||||||
switch self { |
|
||||||
case .waiting: |
|
||||||
return "En attente" |
|
||||||
case .pending: |
|
||||||
return "En cours" |
|
||||||
case .confirmed: |
|
||||||
return "Confirmé" |
|
||||||
case .canceled: |
|
||||||
return "Annulé" |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
static func addon(for playerRank: Int, manMax: Int, womanMax: Int) -> Int { |
|
||||||
switch playerRank { |
|
||||||
case 0: return 0 |
|
||||||
case womanMax: return manMax - womanMax |
|
||||||
case manMax: return 0 |
|
||||||
default: |
|
||||||
return TournamentCategory.femaleInMaleAssimilationAddition(playerRank) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func insertOnServer() { |
|
||||||
self.tournamentStore?.playerRegistrations.writeChangeAndInsertOnServer(instance: self) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
extension PlayerRegistration: PlayerHolder { |
|
||||||
func getAssimilatedAsMaleRank() -> Int? { |
|
||||||
nil |
|
||||||
} |
|
||||||
|
|
||||||
func getFirstName() -> String { |
|
||||||
firstName |
|
||||||
} |
|
||||||
|
|
||||||
func getLastName() -> String { |
|
||||||
lastName |
|
||||||
} |
|
||||||
|
|
||||||
func getPoints() -> Double? { |
|
||||||
self.points |
|
||||||
} |
|
||||||
|
|
||||||
func getRank() -> Int? { |
|
||||||
rank |
|
||||||
} |
|
||||||
|
|
||||||
func isUnranked() -> Bool { |
|
||||||
rank == nil |
|
||||||
} |
|
||||||
|
|
||||||
func formattedRank() -> String { |
|
||||||
self.rankLabel() |
|
||||||
} |
|
||||||
|
|
||||||
func formattedLicense() -> String { |
|
||||||
if let licenceId { return licenceId.computedLicense } |
|
||||||
return "aucune licence" |
|
||||||
} |
|
||||||
|
|
||||||
var male: Bool { |
|
||||||
isMalePlayer() |
|
||||||
} |
|
||||||
|
|
||||||
func getBirthYear() -> Int? { |
|
||||||
nil |
|
||||||
} |
|
||||||
|
|
||||||
func getProgression() -> Int { |
|
||||||
0 |
|
||||||
} |
|
||||||
|
|
||||||
func getComputedRank() -> Int? { |
|
||||||
computedRank |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
enum PlayerDataSource: Int, Codable { |
|
||||||
case frenchFederation = 0 |
|
||||||
case beachPadel = 1 |
|
||||||
} |
|
||||||
|
|
||||||
enum PlayerSexType: Int, Hashable, CaseIterable, Identifiable, Codable { |
|
||||||
init?(rawValue: Int?) { |
|
||||||
guard let value = rawValue else { return nil } |
|
||||||
self.init(rawValue: value) |
|
||||||
} |
|
||||||
|
|
||||||
var id: Self { |
|
||||||
self |
|
||||||
} |
|
||||||
|
|
||||||
case female = 0 |
|
||||||
case male = 1 |
|
||||||
} |
|
||||||
@ -1,35 +0,0 @@ |
|||||||
# Procédure de création de classe |
|
||||||
Dans Swift: |
|
||||||
- Dans Data > Gen > créer un nouveau fichier json pour la classe |
|
||||||
- Le paramètre "foreignKey" permet de générer une méthode qui récupère l'objet parent. Ajouter une étoile permet d'indiquer que l'on cherche l'objet dans le Store de l'objet et non le Store.main. |
|
||||||
- Pour générer les fichiers, on se place dans le répertoire de generator.py et on lance la commande : python generator.py -i . -o . |
|
||||||
- il faut avoir inflect: pip install inflect |
|
||||||
|
|
||||||
Dans Django: |
|
||||||
- Les modèles de base doivent étendre BaseModel |
|
||||||
- Les modèles stockés dans des répertoires doivent étendre SideStoreModel |
|
||||||
- Les classes d'admin doivent étendre SyncedObjectAdmin |
|
||||||
- Les ForeignKey doivent toujours avoir on_delete=models.SET_NULL |
|
||||||
- Pour se faciliter la vie dans l'admin, on veut que les delete effacent les enfants malgré tout. Il faut donc implémenter la méthode delete_dependencies(self) de la classe |
|
||||||
|
|
||||||
|
|
||||||
# Procédure d'ajout de champ dans une classe |
|
||||||
|
|
||||||
Dans Swift: |
|
||||||
- Ouvrir le fichier .json correspondant à la classe |
|
||||||
- Regénérer la classe, voir ci-dessus pour la commande |
|
||||||
- Ouvrir **ServerDataTests** et ajouter un test sur le champ |
|
||||||
- Pour que les tests sur les dates fonctionnent, on peut tester date.formatted() par exemple |
|
||||||
|
|
||||||
Dans Django: |
|
||||||
- Ajouter le champ dans la classe |
|
||||||
- Si c'est une ForeignKey, *toujours* mettre un related_name sinon la synchro casse |
|
||||||
- Si c'est un champ dans **CustomUser**: |
|
||||||
- Ajouter le champ à la méthode fields_for_update |
|
||||||
- Ajouter le champ dans UserSerializer > create > create_user dans serializers.py |
|
||||||
- L'ajouter aussi dans admin.py si nécéssaire |
|
||||||
- Faire le *makemigrations* + *migrate* |
|
||||||
|
|
||||||
Note: Les nouvelles classes de model doivent étendre BaseModel ou SideStoreModel |
|
||||||
|
|
||||||
Enfin, revenir dans Xcode, ouvrir ServerDataTests et lancer le test mis à jour |
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,779 +0,0 @@ |
|||||||
// |
|
||||||
// TeamRegistration.swift |
|
||||||
// Padel Tournament |
|
||||||
// |
|
||||||
// Created by razmig on 10/03/2024. |
|
||||||
// |
|
||||||
|
|
||||||
import Foundation |
|
||||||
import LeStorage |
|
||||||
import SwiftUI |
|
||||||
|
|
||||||
@Observable |
|
||||||
final class TeamRegistration: BaseTeamRegistration, SideStorable { |
|
||||||
|
|
||||||
// static func resourceName() -> String { "team-registrations" } |
|
||||||
// static func tokenExemptedMethods() -> [HTTPMethod] { return [] } |
|
||||||
// static func filterByStoreIdentifier() -> Bool { return true } |
|
||||||
// static var relationshipNames: [String] = [] |
|
||||||
// |
|
||||||
// var id: String = Store.randomId() |
|
||||||
// var lastUpdate: Date |
|
||||||
// var tournament: String |
|
||||||
// var groupStage: String? |
|
||||||
// var registrationDate: Date? |
|
||||||
// var callDate: Date? |
|
||||||
// var bracketPosition: Int? |
|
||||||
// var groupStagePosition: Int? |
|
||||||
// var comment: String? |
|
||||||
// var source: String? |
|
||||||
// var sourceValue: String? |
|
||||||
// var logo: String? |
|
||||||
// var name: String? |
|
||||||
// |
|
||||||
// var walkOut: Bool = false |
|
||||||
// var wildCardBracket: Bool = false |
|
||||||
// var wildCardGroupStage: Bool = false |
|
||||||
// var weight: Int = 0 |
|
||||||
// var lockedWeight: Int? |
|
||||||
// var confirmationDate: Date? |
|
||||||
// var qualified: Bool = false |
|
||||||
// var finalRanking: Int? |
|
||||||
// var pointsEarned: Int? |
|
||||||
// |
|
||||||
// var storeId: String? = nil |
|
||||||
|
|
||||||
init( |
|
||||||
tournament: String, groupStage: String? = nil, registrationDate: Date? = nil, |
|
||||||
callDate: Date? = nil, bracketPosition: Int? = nil, groupStagePosition: Int? = nil, |
|
||||||
comment: String? = nil, source: String? = nil, sourceValue: String? = nil, |
|
||||||
logo: String? = nil, name: String? = nil, walkOut: Bool = false, |
|
||||||
wildCardBracket: Bool = false, wildCardGroupStage: Bool = false, weight: Int = 0, |
|
||||||
lockedWeight: Int? = nil, confirmationDate: Date? = nil, qualified: Bool = false |
|
||||||
) { |
|
||||||
|
|
||||||
super.init() |
|
||||||
|
|
||||||
// self.storeId = tournament |
|
||||||
self.tournament = tournament |
|
||||||
self.groupStage = groupStage |
|
||||||
self.registrationDate = registrationDate ?? Date() |
|
||||||
self.callDate = callDate |
|
||||||
self.bracketPosition = bracketPosition |
|
||||||
self.groupStagePosition = groupStagePosition |
|
||||||
self.comment = comment |
|
||||||
self.source = source |
|
||||||
self.sourceValue = sourceValue |
|
||||||
self.logo = logo |
|
||||||
self.name = name |
|
||||||
self.walkOut = walkOut |
|
||||||
self.wildCardBracket = wildCardBracket |
|
||||||
self.wildCardGroupStage = wildCardGroupStage |
|
||||||
self.weight = weight |
|
||||||
self.lockedWeight = lockedWeight |
|
||||||
self.confirmationDate = confirmationDate |
|
||||||
self.qualified = qualified |
|
||||||
} |
|
||||||
|
|
||||||
func hasRegisteredOnline() -> Bool { |
|
||||||
players().anySatisfy({ $0.registeredOnline }) |
|
||||||
} |
|
||||||
|
|
||||||
func hasPaidOnline() -> Bool { |
|
||||||
players().anySatisfy({ $0.hasPaidOnline() }) |
|
||||||
} |
|
||||||
|
|
||||||
func hasConfirmed() -> Bool { |
|
||||||
players().allSatisfy({ $0.hasConfirmed() }) |
|
||||||
} |
|
||||||
|
|
||||||
func confirmRegistration() { |
|
||||||
let players = players() |
|
||||||
players.forEach({ $0.confirmRegistration() }) |
|
||||||
tournamentStore?.playerRegistrations.addOrUpdate(contentOfs: players) |
|
||||||
} |
|
||||||
|
|
||||||
func unrankedOrUnknown() -> Bool { |
|
||||||
players().anySatisfy({ $0.source == nil }) |
|
||||||
} |
|
||||||
|
|
||||||
func isOutOfTournament() -> Bool { |
|
||||||
walkOut |
|
||||||
} |
|
||||||
|
|
||||||
required init(from decoder: any Decoder) throws { |
|
||||||
try super.init(from: decoder) |
|
||||||
} |
|
||||||
|
|
||||||
required public init() { |
|
||||||
super.init() |
|
||||||
} |
|
||||||
|
|
||||||
var tournamentStore: TournamentStore? { |
|
||||||
return TournamentLibrary.shared.store(tournamentId: self.tournament) |
|
||||||
} |
|
||||||
|
|
||||||
// MARK: - Computed dependencies |
|
||||||
|
|
||||||
func unsortedPlayers() -> [PlayerRegistration] { |
|
||||||
guard let tournamentStore = self.tournamentStore else { return [] } |
|
||||||
return tournamentStore.playerRegistrations.filter { |
|
||||||
$0.teamRegistration == self.id && $0.coach == false |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// MARK: - |
|
||||||
|
|
||||||
func deleteTeamScores() { |
|
||||||
guard let tournamentStore = self.tournamentStore else { return } |
|
||||||
let ts = tournamentStore.teamScores.filter({ $0.teamRegistration == id }) |
|
||||||
tournamentStore.teamScores.delete(contentOfs: ts) |
|
||||||
} |
|
||||||
|
|
||||||
override func deleteDependencies() { |
|
||||||
let unsortedPlayers = unsortedPlayers() |
|
||||||
for player in unsortedPlayers { |
|
||||||
player.deleteDependencies() |
|
||||||
} |
|
||||||
self.tournamentStore?.playerRegistrations.deleteDependencies(unsortedPlayers) |
|
||||||
|
|
||||||
let teamScores = teamScores() |
|
||||||
for teamScore in teamScores { |
|
||||||
teamScore.deleteDependencies() |
|
||||||
} |
|
||||||
self.tournamentStore?.teamScores.deleteDependencies(teamScores) |
|
||||||
} |
|
||||||
|
|
||||||
func hasArrived(isHere: Bool = false) { |
|
||||||
let unsortedPlayers = unsortedPlayers() |
|
||||||
unsortedPlayers.forEach({ $0.hasArrived = !isHere }) |
|
||||||
self.tournamentStore?.playerRegistrations.addOrUpdate(contentOfs: unsortedPlayers) |
|
||||||
} |
|
||||||
|
|
||||||
func isHere() -> Bool { |
|
||||||
let unsortedPlayers = unsortedPlayers() |
|
||||||
if unsortedPlayers.isEmpty { return false } |
|
||||||
return unsortedPlayers.allSatisfy({ $0.hasArrived }) |
|
||||||
} |
|
||||||
|
|
||||||
func isSeedable() -> Bool { |
|
||||||
bracketPosition == nil && groupStage == nil |
|
||||||
} |
|
||||||
|
|
||||||
func setSeedPosition(inSpot match: Match, slot: TeamPosition?, opposingSeeding: Bool) { |
|
||||||
var teamPosition: TeamPosition { |
|
||||||
if let slot { |
|
||||||
return slot |
|
||||||
} else { |
|
||||||
let matchIndex = match.index |
|
||||||
let seedRound = RoundRule.roundIndex(fromMatchIndex: matchIndex) |
|
||||||
let numberOfMatches = RoundRule.numberOfMatches(forRoundIndex: seedRound) |
|
||||||
let isUpper = |
|
||||||
RoundRule.matchIndexWithinRound(fromMatchIndex: matchIndex) |
|
||||||
< (numberOfMatches / 2) |
|
||||||
var teamPosition = slot ?? (isUpper ? .one : .two) |
|
||||||
if opposingSeeding { |
|
||||||
teamPosition = slot ?? (isUpper ? .two : .one) |
|
||||||
} |
|
||||||
return teamPosition |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
let seedPosition: Int = match.lockAndGetSeedPosition(atTeamPosition: teamPosition) |
|
||||||
tournamentObject()?.resetTeamScores(in: bracketPosition) |
|
||||||
self.bracketPosition = seedPosition |
|
||||||
if groupStagePosition != nil && qualified == false { |
|
||||||
qualified = true |
|
||||||
} |
|
||||||
if let tournament = tournamentObject() { |
|
||||||
if let index = index(in: tournament.selectedSortedTeams()) { |
|
||||||
let drawLog = DrawLog( |
|
||||||
tournament: tournament.id, drawSeed: index, drawMatchIndex: match.index, |
|
||||||
drawTeamPosition: teamPosition, drawType: .seed) |
|
||||||
do { |
|
||||||
try tournamentStore?.drawLogs.addOrUpdate(instance: drawLog) |
|
||||||
} catch { |
|
||||||
Logger.error(error) |
|
||||||
} |
|
||||||
} |
|
||||||
tournament.updateTeamScores(in: bracketPosition) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func expectedSummonDate() -> Date? { |
|
||||||
if let groupStageStartDate = groupStageObject()?.startDate { |
|
||||||
return groupStageStartDate |
|
||||||
} else if let roundMatchStartDate = initialMatch()?.startDate { |
|
||||||
return roundMatchStartDate |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
var initialWeight: Int { |
|
||||||
return lockedWeight ?? weight |
|
||||||
} |
|
||||||
|
|
||||||
func called() -> Bool { |
|
||||||
return callDate != nil |
|
||||||
} |
|
||||||
|
|
||||||
func confirmed() -> Bool { |
|
||||||
return confirmationDate != nil |
|
||||||
} |
|
||||||
|
|
||||||
func getPhoneNumbers() -> [String] { |
|
||||||
return players().compactMap { $0.phoneNumber }.filter({ $0.isEmpty == false }) |
|
||||||
} |
|
||||||
|
|
||||||
func getMail() -> [String] { |
|
||||||
let mails = players().compactMap({ $0.email }) |
|
||||||
return mails |
|
||||||
} |
|
||||||
|
|
||||||
func isImported() -> Bool { |
|
||||||
let unsortedPlayers = unsortedPlayers() |
|
||||||
if unsortedPlayers.isEmpty { return false } |
|
||||||
|
|
||||||
return unsortedPlayers.allSatisfy({ $0.isImported() }) |
|
||||||
} |
|
||||||
|
|
||||||
func isWildCard() -> Bool { |
|
||||||
return wildCardBracket || wildCardGroupStage |
|
||||||
} |
|
||||||
|
|
||||||
func isPlaying() -> Bool { |
|
||||||
return currentMatch() != nil |
|
||||||
} |
|
||||||
|
|
||||||
func currentMatch() -> Match? { |
|
||||||
return teamScores().compactMap { $0.matchObject() }.first(where: { $0.isRunning() }) |
|
||||||
} |
|
||||||
|
|
||||||
func teamScores() -> [TeamScore] { |
|
||||||
guard let tournamentStore = self.tournamentStore else { return [] } |
|
||||||
return tournamentStore.teamScores.filter({ $0.teamRegistration == id }) |
|
||||||
} |
|
||||||
|
|
||||||
func wins() -> [Match] { |
|
||||||
guard let tournamentStore = self.tournamentStore else { return [] } |
|
||||||
return tournamentStore.matches.filter({ $0.winningTeamId == id }) |
|
||||||
} |
|
||||||
|
|
||||||
func loses() -> [Match] { |
|
||||||
guard let tournamentStore = self.tournamentStore else { return [] } |
|
||||||
return tournamentStore.matches.filter({ $0.losingTeamId == id }) |
|
||||||
} |
|
||||||
|
|
||||||
func matches() -> [Match] { |
|
||||||
guard let tournamentStore = self.tournamentStore else { return [] } |
|
||||||
return tournamentStore.matches.filter({ $0.losingTeamId == id || $0.winningTeamId == id }) |
|
||||||
} |
|
||||||
|
|
||||||
var tournamentCategory: TournamentCategory { |
|
||||||
tournamentObject()?.tournamentCategory ?? .men |
|
||||||
} |
|
||||||
|
|
||||||
@objc |
|
||||||
var canonicalName: String { |
|
||||||
players().map { $0.canonicalName }.joined(separator: " ") |
|
||||||
} |
|
||||||
|
|
||||||
func hasMemberOfClub(_ codeClubOrClubName: String?) -> Bool { |
|
||||||
guard let codeClubOrClubName else { return true } |
|
||||||
return unsortedPlayers().anySatisfy({ |
|
||||||
$0.clubName?.contains(codeClubOrClubName) == true |
|
||||||
|| $0.clubName?.contains(codeClubOrClubName) == true |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
func updateWeight(inTournamentCategory tournamentCategory: TournamentCategory) { |
|
||||||
self.setWeight(from: self.players(), inTournamentCategory: tournamentCategory) |
|
||||||
} |
|
||||||
|
|
||||||
func teamLabel( |
|
||||||
_ displayStyle: DisplayStyle = .wide, twoLines: Bool = false, separator: String = "&" |
|
||||||
) -> String { |
|
||||||
if let name { return name } |
|
||||||
return players().map { $0.playerLabel(displayStyle) }.joined( |
|
||||||
separator: twoLines ? "\n" : " \(separator) ") |
|
||||||
} |
|
||||||
|
|
||||||
func teamLabelRanked(displayRank: Bool, displayTeamName: Bool) -> String { |
|
||||||
[ |
|
||||||
displayTeamName ? name : nil, displayRank ? seedIndex() : nil, |
|
||||||
displayTeamName ? (name == nil ? teamLabel() : name) : teamLabel(), |
|
||||||
].compactMap({ $0 }).joined(separator: " ") |
|
||||||
} |
|
||||||
|
|
||||||
func seedIndex() -> String? { |
|
||||||
guard let tournament = tournamentObject() else { return nil } |
|
||||||
guard let index = index(in: tournament.selectedSortedTeams()) else { return nil } |
|
||||||
return "(\(index + 1))" |
|
||||||
} |
|
||||||
|
|
||||||
func index(in teams: [TeamRegistration]) -> Int? { |
|
||||||
return teams.firstIndex(where: { $0.id == id }) |
|
||||||
} |
|
||||||
|
|
||||||
func formattedSeed(in teams: [TeamRegistration]? = nil) -> String { |
|
||||||
let selectedSortedTeams = teams ?? tournamentObject()?.selectedSortedTeams() ?? [] |
|
||||||
if let index = index(in: selectedSortedTeams) { |
|
||||||
return "#\(index + 1)" |
|
||||||
} else { |
|
||||||
return "###" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func contains(_ searchField: String) -> Bool { |
|
||||||
return unsortedPlayers().anySatisfy({ $0.contains(searchField) }) |
|
||||||
|| self.name?.localizedCaseInsensitiveContains(searchField) == true |
|
||||||
} |
|
||||||
|
|
||||||
func containsExactlyPlayerLicenses(_ playerLicenses: [String?]) -> Bool { |
|
||||||
let arrayOfIds: [String] = unsortedPlayers().compactMap({ |
|
||||||
$0.licenceId?.strippedLicense?.canonicalVersion |
|
||||||
}) |
|
||||||
let ids: Set<String> = Set<String>(arrayOfIds.sorted()) |
|
||||||
let searchedIds = Set<String>( |
|
||||||
playerLicenses.compactMap({ $0?.strippedLicense?.canonicalVersion }).sorted()) |
|
||||||
if ids.isEmpty || searchedIds.isEmpty { return false } |
|
||||||
return ids.hashValue == searchedIds.hashValue |
|
||||||
} |
|
||||||
|
|
||||||
func includes(players: [PlayerRegistration]) -> Bool { |
|
||||||
let unsortedPlayers = unsortedPlayers() |
|
||||||
guard players.count == unsortedPlayers.count else { return false } |
|
||||||
return players.allSatisfy { player in |
|
||||||
unsortedPlayers.anySatisfy { _player in |
|
||||||
_player.isSameAs(player) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func includes(player: PlayerRegistration) -> Bool { |
|
||||||
return unsortedPlayers().anySatisfy { _player in |
|
||||||
_player.isSameAs(player) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func canPlay() -> Bool { |
|
||||||
let unsortedPlayers = unsortedPlayers() |
|
||||||
if unsortedPlayers.isEmpty { return false } |
|
||||||
|
|
||||||
return matches().isEmpty == false |
|
||||||
|| unsortedPlayers.allSatisfy({ $0.hasPaid() || $0.hasArrived }) |
|
||||||
} |
|
||||||
|
|
||||||
func availableForSeedPick() -> Bool { |
|
||||||
return groupStage == nil && bracketPosition == nil |
|
||||||
} |
|
||||||
|
|
||||||
func inGroupStage() -> Bool { |
|
||||||
return groupStagePosition != nil |
|
||||||
} |
|
||||||
|
|
||||||
func inRound() -> Bool { |
|
||||||
return bracketPosition != nil |
|
||||||
} |
|
||||||
|
|
||||||
func positionLabel() -> String? { |
|
||||||
if groupStagePosition != nil { return "Poule" } |
|
||||||
if let initialRound = initialRound() { |
|
||||||
return initialRound.roundTitle() |
|
||||||
} else { |
|
||||||
return nil |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func initialRoundColor() -> Color? { |
|
||||||
if walkOut { return Color.logoRed } |
|
||||||
if groupStagePosition != nil || wildCardGroupStage { return Color.blue } |
|
||||||
if let initialRound = initialRound(), let colorHex = RoundRule.colors[safe: initialRound.index] { |
|
||||||
return Color(uiColor: .init(fromHex: colorHex)) |
|
||||||
} else if wildCardBracket { |
|
||||||
return Color.mint |
|
||||||
} else { |
|
||||||
return nil |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func resetGroupeStagePosition() { |
|
||||||
guard let tournamentStore = self.tournamentStore else { return } |
|
||||||
if let groupStage { |
|
||||||
let matches = tournamentStore.matches.filter({ $0.groupStage == groupStage }).map { |
|
||||||
$0.id |
|
||||||
} |
|
||||||
let teamScores = tournamentStore.teamScores.filter({ |
|
||||||
$0.teamRegistration == id && matches.contains($0.match) |
|
||||||
}) |
|
||||||
tournamentStore.teamScores.delete(contentOfs: teamScores) |
|
||||||
} |
|
||||||
//groupStageObject()?._matches().forEach({ $0.updateTeamScores() }) |
|
||||||
groupStage = nil |
|
||||||
groupStagePosition = nil |
|
||||||
} |
|
||||||
|
|
||||||
func resetBracketPosition() { |
|
||||||
guard let tournamentStore = self.tournamentStore else { return } |
|
||||||
let matches = tournamentStore.matches.filter({ $0.groupStage == nil }).map { $0.id } |
|
||||||
let teamScores = tournamentStore.teamScores.filter({ |
|
||||||
$0.teamRegistration == id && matches.contains($0.match) |
|
||||||
}) |
|
||||||
tournamentStore.teamScores.delete(contentOfs: teamScores) |
|
||||||
|
|
||||||
self.bracketPosition = nil |
|
||||||
} |
|
||||||
|
|
||||||
func resetPositions() { |
|
||||||
resetGroupeStagePosition() |
|
||||||
resetBracketPosition() |
|
||||||
} |
|
||||||
|
|
||||||
func pasteData(_ exportFormat: ExportFormat = .rawText, _ index: Int = 0) -> String { |
|
||||||
switch exportFormat { |
|
||||||
case .rawText: |
|
||||||
return [playersPasteData(exportFormat), formattedInscriptionDate(exportFormat), name] |
|
||||||
.compactMap({ $0 }).joined(separator: exportFormat.newLineSeparator()) |
|
||||||
case .csv: |
|
||||||
return [ |
|
||||||
index.formatted(), playersPasteData(exportFormat), |
|
||||||
isWildCard() ? "WC" : weight.formatted(), |
|
||||||
].joined(separator: exportFormat.separator()) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
var computedRegistrationDate: Date { |
|
||||||
return registrationDate ?? .distantFuture |
|
||||||
} |
|
||||||
|
|
||||||
func formattedInscriptionDate(_ exportFormat: ExportFormat = .rawText) -> String? { |
|
||||||
guard let registrationDate else { return nil } |
|
||||||
|
|
||||||
let formattedDate = registrationDate.formatted( |
|
||||||
.dateTime.weekday().day().month().hour().minute()) |
|
||||||
let onlineSuffix = hasRegisteredOnline() ? " en ligne" : "" |
|
||||||
|
|
||||||
switch exportFormat { |
|
||||||
case .rawText: |
|
||||||
return "Inscrit\(onlineSuffix) le \(formattedDate)" |
|
||||||
case .csv: |
|
||||||
return formattedDate |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func formattedSummonDate(_ exportFormat: ExportFormat = .rawText) -> String? { |
|
||||||
|
|
||||||
switch exportFormat { |
|
||||||
case .rawText: |
|
||||||
if let callDate { |
|
||||||
return "Convoqué le " |
|
||||||
+ callDate.formatted(.dateTime.weekday().day().month().hour().minute()) |
|
||||||
} else { |
|
||||||
return nil |
|
||||||
} |
|
||||||
case .csv: |
|
||||||
if let callDate { |
|
||||||
return callDate.formatted(.dateTime.weekday().day().month().hour().minute()) |
|
||||||
} else { |
|
||||||
return nil |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func playersPasteData(_ exportFormat: ExportFormat = .rawText) -> String { |
|
||||||
switch exportFormat { |
|
||||||
case .rawText: |
|
||||||
return players().map { $0.pasteData(exportFormat) }.joined( |
|
||||||
separator: exportFormat.newLineSeparator()) |
|
||||||
case .csv: |
|
||||||
return players().map { |
|
||||||
[$0.pasteData(exportFormat), isWildCard() ? "WC" : $0.computedRank.formatted()] |
|
||||||
.joined(separator: exportFormat.separator()) |
|
||||||
}.joined(separator: exportFormat.separator()) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func updatePlayers( |
|
||||||
_ players: Set<PlayerRegistration>, |
|
||||||
inTournamentCategory tournamentCategory: TournamentCategory |
|
||||||
) { |
|
||||||
let previousPlayers = Set(unsortedPlayers()) |
|
||||||
|
|
||||||
players.forEach { player in |
|
||||||
previousPlayers.forEach { oldPlayer in |
|
||||||
if player.licenceId?.strippedLicense == oldPlayer.licenceId?.strippedLicense, |
|
||||||
player.licenceId?.strippedLicense != nil |
|
||||||
{ |
|
||||||
player.registeredOnline = oldPlayer.registeredOnline |
|
||||||
player.paymentType = oldPlayer.paymentType |
|
||||||
player.paymentId = oldPlayer.paymentId |
|
||||||
player.registrationStatus = oldPlayer.registrationStatus |
|
||||||
player.timeToConfirm = oldPlayer.timeToConfirm |
|
||||||
player.coach = oldPlayer.coach |
|
||||||
player.tournamentPlayed = oldPlayer.tournamentPlayed |
|
||||||
player.points = oldPlayer.points |
|
||||||
player.captain = oldPlayer.captain |
|
||||||
player.assimilation = oldPlayer.assimilation |
|
||||||
player.ligueName = oldPlayer.ligueName |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
let playersToRemove = previousPlayers.subtracting(players) |
|
||||||
self.tournamentStore?.playerRegistrations.delete(contentOfs: Array(playersToRemove)) |
|
||||||
setWeight(from: Array(players), inTournamentCategory: tournamentCategory) |
|
||||||
|
|
||||||
players.forEach { player in |
|
||||||
player.teamRegistration = id |
|
||||||
} |
|
||||||
|
|
||||||
// do { |
|
||||||
// try self.tournamentStore.playerRegistrations.addOrUpdate(contentOfs: players) |
|
||||||
// } catch { |
|
||||||
// Logger.error(error) |
|
||||||
// } |
|
||||||
} |
|
||||||
|
|
||||||
typealias TeamRange = (left: TeamRegistration?, right: TeamRegistration?) |
|
||||||
|
|
||||||
func replacementRange() -> TeamRange? { |
|
||||||
guard let tournamentObject = tournamentObject() else { return nil } |
|
||||||
guard let index = tournamentObject.indexOf(team: self) else { return nil } |
|
||||||
let selectedSortedTeams = tournamentObject.selectedSortedTeams() |
|
||||||
let left = selectedSortedTeams[safe: index - 1] |
|
||||||
let right = selectedSortedTeams[safe: index + 1] |
|
||||||
return (left: left, right: right) |
|
||||||
} |
|
||||||
|
|
||||||
func replacementRangeExtended() -> TeamRange? { |
|
||||||
guard let tournamentObject = tournamentObject() else { return nil } |
|
||||||
guard let groupStagePosition else { return nil } |
|
||||||
let selectedSortedTeams = tournamentObject.selectedSortedTeams() |
|
||||||
var left: TeamRegistration? = nil |
|
||||||
if groupStagePosition == 0 { |
|
||||||
left = tournamentObject.seeds().last |
|
||||||
} else { |
|
||||||
let previousHat = selectedSortedTeams.filter({ |
|
||||||
$0.groupStagePosition == groupStagePosition - 1 |
|
||||||
}).sorted(by: \.weight) |
|
||||||
left = previousHat.last |
|
||||||
} |
|
||||||
var right: TeamRegistration? = nil |
|
||||||
if groupStagePosition == tournamentObject.teamsPerGroupStage - 1 { |
|
||||||
right = nil |
|
||||||
} else { |
|
||||||
let previousHat = selectedSortedTeams.filter({ |
|
||||||
$0.groupStagePosition == groupStagePosition + 1 |
|
||||||
}).sorted(by: \.weight) |
|
||||||
right = previousHat.first |
|
||||||
} |
|
||||||
return (left: left, right: right) |
|
||||||
} |
|
||||||
|
|
||||||
typealias AreInIncreasingOrder = (PlayerRegistration, PlayerRegistration) -> Bool |
|
||||||
|
|
||||||
func players() -> [PlayerRegistration] { |
|
||||||
|
|
||||||
self.unsortedPlayers().sorted { (lhs, rhs) in |
|
||||||
let predicates: [AreInIncreasingOrder] = [ |
|
||||||
{ $0.sex?.rawValue ?? 0 < $1.sex?.rawValue ?? 0 }, |
|
||||||
{ $0.rank ?? Int.max < $1.rank ?? Int.max }, |
|
||||||
{ $0.lastName < $1.lastName }, |
|
||||||
{ $0.firstName < $1.firstName }, |
|
||||||
] |
|
||||||
|
|
||||||
for predicate in predicates { |
|
||||||
if !predicate(lhs, rhs) && !predicate(rhs, lhs) { |
|
||||||
continue |
|
||||||
} |
|
||||||
|
|
||||||
return predicate(lhs, rhs) |
|
||||||
} |
|
||||||
|
|
||||||
return false |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func coaches() -> [PlayerRegistration] { |
|
||||||
guard let store = self.tournamentStore else { return [] } |
|
||||||
return store.playerRegistrations.filter { $0.coach } |
|
||||||
} |
|
||||||
|
|
||||||
func setWeight( |
|
||||||
from players: [PlayerRegistration], |
|
||||||
inTournamentCategory tournamentCategory: TournamentCategory |
|
||||||
) { |
|
||||||
let significantPlayerCount = significantPlayerCount() |
|
||||||
let sortedPlayers = players.sorted(by: \.computedRank, order: .ascending) |
|
||||||
weight = (sortedPlayers.prefix(significantPlayerCount).map { $0.computedRank } + missingPlayerType(inTournamentCategory: tournamentCategory).map { unrankValue(for: $0 == 1 ? true : false ) }).prefix(significantPlayerCount).reduce(0,+) |
|
||||||
} |
|
||||||
|
|
||||||
func significantPlayerCount() -> Int { |
|
||||||
return tournamentObject()?.significantPlayerCount() ?? 2 |
|
||||||
} |
|
||||||
|
|
||||||
func missingPlayerType(inTournamentCategory tournamentCategory: TournamentCategory) -> [Int] { |
|
||||||
let players = unsortedPlayers() |
|
||||||
if players.count >= 2 { return [] } |
|
||||||
let s = players.compactMap { $0.sex?.rawValue } |
|
||||||
var missing = tournamentCategory.mandatoryPlayerType() |
|
||||||
s.forEach { i in |
|
||||||
if let index = missing.firstIndex(of: i) { |
|
||||||
missing.remove(at: index) |
|
||||||
} |
|
||||||
} |
|
||||||
return missing |
|
||||||
} |
|
||||||
|
|
||||||
func unrankValue(for malePlayer: Bool) -> Int { |
|
||||||
return tournamentObject()?.unrankValue(for: malePlayer) ?? 90_000 |
|
||||||
} |
|
||||||
|
|
||||||
func groupStageObject() -> GroupStage? { |
|
||||||
guard let groupStage else { return nil } |
|
||||||
return self.tournamentStore?.groupStages.findById(groupStage) |
|
||||||
} |
|
||||||
|
|
||||||
func initialRound() -> Round? { |
|
||||||
guard let bracketPosition else { return nil } |
|
||||||
let roundIndex = RoundRule.roundIndex(fromMatchIndex: bracketPosition / 2) |
|
||||||
return self.tournamentStore?.rounds.first(where: { $0.index == roundIndex }) |
|
||||||
} |
|
||||||
|
|
||||||
func initialMatch() -> Match? { |
|
||||||
guard let bracketPosition else { return nil } |
|
||||||
guard let initialRoundObject = initialRound() else { return nil } |
|
||||||
return self.tournamentStore?.matches.first(where: { |
|
||||||
$0.round == initialRoundObject.id && $0.index == bracketPosition / 2 |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
func toggleSummonConfirmation() { |
|
||||||
if confirmationDate == nil { confirmationDate = Date() } else { confirmationDate = nil } |
|
||||||
} |
|
||||||
|
|
||||||
func didConfirmSummon() -> Bool { |
|
||||||
confirmationDate != nil |
|
||||||
} |
|
||||||
|
|
||||||
func tournamentObject() -> Tournament? { |
|
||||||
return Store.main.findById(tournament) |
|
||||||
} |
|
||||||
|
|
||||||
func groupStagePositionAtStep(_ step: Int) -> Int? { |
|
||||||
guard let groupStagePosition else { return nil } |
|
||||||
if step == 0 { |
|
||||||
return groupStagePosition |
|
||||||
} else if let groupStageObject = groupStageObject(), groupStageObject.hasEnded() { |
|
||||||
return groupStageObject.index |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func wildcardLabel() -> String? { |
|
||||||
if isWildCard() { |
|
||||||
let wildcardLabel: String = ["Wildcard", (wildCardBracket ? "Tableau" : "Poule")].joined(separator: " ") |
|
||||||
return wildcardLabel |
|
||||||
} else { |
|
||||||
return nil |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
var _cachedRestingTime: (Bool, Date?)? |
|
||||||
|
|
||||||
func restingTime() -> Date? { |
|
||||||
if let _cachedRestingTime { return _cachedRestingTime.1 } |
|
||||||
let restingTime = matches().filter({ $0.hasEnded() }).sorted( |
|
||||||
by: \.computedEndDateForSorting |
|
||||||
).last?.endDate |
|
||||||
_cachedRestingTime = (true, restingTime) |
|
||||||
return restingTime |
|
||||||
} |
|
||||||
|
|
||||||
func resetRestingTime() { |
|
||||||
_cachedRestingTime = nil |
|
||||||
} |
|
||||||
|
|
||||||
var restingTimeForSorting: Date { |
|
||||||
restingTime()! |
|
||||||
} |
|
||||||
|
|
||||||
func teamNameLabel() -> String { |
|
||||||
if let name, name.isEmpty == false { |
|
||||||
return name |
|
||||||
} else { |
|
||||||
return "Toute l'équipe" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func isDifferentPosition(_ drawMatchIndex: Int?) -> Bool { |
|
||||||
if let bracketPosition, let drawMatchIndex { |
|
||||||
return drawMatchIndex != bracketPosition |
|
||||||
} else if bracketPosition != nil { |
|
||||||
return true |
|
||||||
} else if drawMatchIndex != nil { |
|
||||||
return true |
|
||||||
} |
|
||||||
return false |
|
||||||
} |
|
||||||
|
|
||||||
func shouldDisplayRankAndWeight() -> Bool { |
|
||||||
unsortedPlayers().count > 0 |
|
||||||
} |
|
||||||
|
|
||||||
func teamInitialPositionBracket() -> String? { |
|
||||||
let round = initialMatch()?.roundAndMatchTitle() |
|
||||||
if let round { |
|
||||||
return round |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func teamInitialPositionGroupStage() -> String? { |
|
||||||
let groupStage = self.groupStageObject() |
|
||||||
let group = groupStage?.groupStageTitle(.title) |
|
||||||
var groupPositionLabel: String? = nil |
|
||||||
if let finalPosition = groupStage?.finalPosition(ofTeam: self) { |
|
||||||
groupPositionLabel = (finalPosition + 1).ordinalFormatted() |
|
||||||
} else if let groupStagePosition { |
|
||||||
groupPositionLabel = "\(groupStagePosition + 1)" |
|
||||||
} |
|
||||||
|
|
||||||
if let group { |
|
||||||
if let groupPositionLabel { |
|
||||||
return [group, "#\(groupPositionLabel)"].joined(separator: " ") |
|
||||||
} else { |
|
||||||
return group |
|
||||||
} |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func qualifiedStatus(hideBracketStatus: Bool = false) -> String? { |
|
||||||
let teamInitialPositionBracket = teamInitialPositionBracket() |
|
||||||
let groupStageTitle = teamInitialPositionGroupStage() |
|
||||||
|
|
||||||
let base: String? = qualified ? "Qualifié" : nil |
|
||||||
if let groupStageTitle, let teamInitialPositionBracket, hideBracketStatus == false { |
|
||||||
return [base, groupStageTitle, ">", teamInitialPositionBracket].compactMap({ $0 }).joined(separator: " ") |
|
||||||
} else if let groupStageTitle { |
|
||||||
return [base, groupStageTitle].compactMap({ $0 }).joined(separator: " ") |
|
||||||
} else if hideBracketStatus == false { |
|
||||||
return teamInitialPositionBracket |
|
||||||
} |
|
||||||
|
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func insertOnServer() { |
|
||||||
self.tournamentStore?.teamRegistrations.writeChangeAndInsertOnServer(instance: self) |
|
||||||
for playerRegistration in self.unsortedPlayers() { |
|
||||||
playerRegistration.insertOnServer() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
enum TeamDataSource: Int, Codable { |
|
||||||
case beachPadel |
|
||||||
} |
|
||||||
@ -1,117 +0,0 @@ |
|||||||
// |
|
||||||
// TeamScore.swift |
|
||||||
// Padel Tournament |
|
||||||
// |
|
||||||
// Created by razmig on 10/03/2024. |
|
||||||
// |
|
||||||
|
|
||||||
import Foundation |
|
||||||
import LeStorage |
|
||||||
|
|
||||||
@Observable |
|
||||||
final class TeamScore: BaseTeamScore, SideStorable { |
|
||||||
|
|
||||||
|
|
||||||
// static func resourceName() -> String { "team-scores" } |
|
||||||
// static func tokenExemptedMethods() -> [HTTPMethod] { return [] } |
|
||||||
// static func filterByStoreIdentifier() -> Bool { return true } |
|
||||||
// static var relationshipNames: [String] = ["match"] |
|
||||||
// |
|
||||||
// var id: String = Store.randomId() |
|
||||||
// var lastUpdate: Date |
|
||||||
// var match: String |
|
||||||
// var teamRegistration: String? |
|
||||||
// //var playerRegistrations: [String] = [] |
|
||||||
// var score: String? |
|
||||||
// var walkOut: Int? |
|
||||||
// var luckyLoser: Int? |
|
||||||
// |
|
||||||
// var storeId: String? = nil |
|
||||||
|
|
||||||
init(match: String, teamRegistration: String? = nil, score: String? = nil, walkOut: Int? = nil, luckyLoser: Int? = nil) { |
|
||||||
super.init(match: match, teamRegistration: teamRegistration, score: score, walkOut: walkOut, luckyLoser: luckyLoser) |
|
||||||
|
|
||||||
// self.match = match |
|
||||||
// self.teamRegistration = teamRegistration |
|
||||||
//// self.playerRegistrations = playerRegistrations |
|
||||||
// self.score = score |
|
||||||
// self.walkOut = walkOut |
|
||||||
// self.luckyLoser = luckyLoser |
|
||||||
} |
|
||||||
|
|
||||||
init(match: String, team: TeamRegistration?) { |
|
||||||
super.init(match: match) |
|
||||||
if let team { |
|
||||||
self.teamRegistration = team.id |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
required init(from decoder: Decoder) throws { |
|
||||||
try super.init(from: decoder) |
|
||||||
} |
|
||||||
|
|
||||||
required public init() { |
|
||||||
super.init() |
|
||||||
} |
|
||||||
|
|
||||||
var tournamentStore: TournamentStore? { |
|
||||||
guard let storeId else { |
|
||||||
fatalError("missing store id for \(String(describing: type(of: self)))") |
|
||||||
} |
|
||||||
return TournamentLibrary.shared.store(tournamentId: storeId) |
|
||||||
// |
|
||||||
// if let store = self.store as? TournamentStore { |
|
||||||
// return store |
|
||||||
// } |
|
||||||
// fatalError("missing store for \(String(describing: type(of: self)))") |
|
||||||
} |
|
||||||
|
|
||||||
// MARK: - Computed dependencies |
|
||||||
|
|
||||||
func matchObject() -> Match? { |
|
||||||
return self.tournamentStore?.matches.findById(self.match) |
|
||||||
} |
|
||||||
|
|
||||||
var team: TeamRegistration? { |
|
||||||
guard let teamRegistration else { |
|
||||||
return nil |
|
||||||
} |
|
||||||
return self.tournamentStore?.teamRegistrations.findById(teamRegistration) |
|
||||||
} |
|
||||||
|
|
||||||
// MARK: - |
|
||||||
|
|
||||||
func isWalkOut() -> Bool { |
|
||||||
return walkOut != nil |
|
||||||
} |
|
||||||
|
|
||||||
// enum CodingKeys: String, CodingKey { |
|
||||||
// case _id = "id" |
|
||||||
// case _storeId = "storeId" |
|
||||||
// case _lastUpdate = "lastUpdate" |
|
||||||
// case _match = "match" |
|
||||||
// case _teamRegistration = "teamRegistration" |
|
||||||
// //case _playerRegistrations = "playerRegistrations" |
|
||||||
// case _score = "score" |
|
||||||
// case _walkOut = "walkOut" |
|
||||||
// case _luckyLoser = "luckyLoser" |
|
||||||
// } |
|
||||||
// |
|
||||||
// func encode(to encoder: Encoder) throws { |
|
||||||
// var container = encoder.container(keyedBy: CodingKeys.self) |
|
||||||
// |
|
||||||
// try container.encode(id, forKey: ._id) |
|
||||||
// try container.encode(storeId, forKey: ._storeId) |
|
||||||
// try container.encode(lastUpdate, forKey: ._lastUpdate) |
|
||||||
// try container.encode(match, forKey: ._match) |
|
||||||
// try container.encode(teamRegistration, forKey: ._teamRegistration) |
|
||||||
// try container.encode(score, forKey: ._score) |
|
||||||
// try container.encode(walkOut, forKey: ._walkOut) |
|
||||||
// try container.encode(luckyLoser, forKey: ._luckyLoser) |
|
||||||
// } |
|
||||||
|
|
||||||
func insertOnServer() { |
|
||||||
self.tournamentStore?.teamScores.writeChangeAndInsertOnServer(instance: self) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,33 +0,0 @@ |
|||||||
// |
|
||||||
// TournamentLibrary.swift |
|
||||||
// PadelClub |
|
||||||
// |
|
||||||
// Created by Laurent Morvillier on 11/11/2024. |
|
||||||
// |
|
||||||
|
|
||||||
import Foundation |
|
||||||
import LeStorage |
|
||||||
|
|
||||||
class TournamentLibrary { |
|
||||||
|
|
||||||
static let shared: TournamentLibrary = TournamentLibrary() |
|
||||||
|
|
||||||
fileprivate var _stores: [String : TournamentStore] = [:] |
|
||||||
|
|
||||||
func store(tournamentId: String) -> TournamentStore? { |
|
||||||
guard let tournament = DataStore.shared.tournaments.first(where: { $0.id == tournamentId }) else { return nil } |
|
||||||
|
|
||||||
if let store = self._stores[tournamentId] { |
|
||||||
return store |
|
||||||
} |
|
||||||
let store = StoreCenter.main.store(identifier: tournamentId) |
|
||||||
let tournamentStore = TournamentStore(store: store) |
|
||||||
self._stores[tournamentId] = tournamentStore |
|
||||||
return tournamentStore |
|
||||||
} |
|
||||||
|
|
||||||
func reset() { |
|
||||||
self._stores.removeAll() |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,68 +0,0 @@ |
|||||||
// |
|
||||||
// TournamentStore.swift |
|
||||||
// PadelClub |
|
||||||
// |
|
||||||
// Created by Laurent Morvillier on 26/06/2024. |
|
||||||
// |
|
||||||
|
|
||||||
import Foundation |
|
||||||
import LeStorage |
|
||||||
import SwiftUI |
|
||||||
|
|
||||||
class TournamentStore: ObservableObject { |
|
||||||
|
|
||||||
var store: Store |
|
||||||
|
|
||||||
fileprivate(set) var groupStages: SyncedCollection<GroupStage> = SyncedCollection.placeholder() |
|
||||||
fileprivate(set) var matches: SyncedCollection<Match> = SyncedCollection.placeholder() |
|
||||||
fileprivate(set) var teamRegistrations: SyncedCollection<TeamRegistration> = SyncedCollection.placeholder() |
|
||||||
fileprivate(set) var playerRegistrations: SyncedCollection<PlayerRegistration> = SyncedCollection.placeholder() |
|
||||||
fileprivate(set) var rounds: SyncedCollection<Round> = SyncedCollection.placeholder() |
|
||||||
fileprivate(set) var teamScores: SyncedCollection<TeamScore> = SyncedCollection.placeholder() |
|
||||||
|
|
||||||
fileprivate(set) var matchSchedulers: StoredCollection<MatchScheduler> = StoredCollection.placeholder() |
|
||||||
fileprivate(set) var drawLogs: SyncedCollection<DrawLog> = SyncedCollection.placeholder() |
|
||||||
|
|
||||||
// convenience init(tournament: Tournament) { |
|
||||||
// let store = StoreCenter.main.store(identifier: tournament.id) |
|
||||||
// self.init(store: store) |
|
||||||
// self._initialize() |
|
||||||
// } |
|
||||||
|
|
||||||
init(store: Store) { |
|
||||||
self.store = store |
|
||||||
self._initialize() |
|
||||||
} |
|
||||||
|
|
||||||
fileprivate func _initialize() { |
|
||||||
|
|
||||||
let indexed: Bool = true |
|
||||||
|
|
||||||
self.groupStages = self.store.registerSynchronizedCollection(indexed: indexed) |
|
||||||
self.rounds = self.store.registerSynchronizedCollection(indexed: indexed) |
|
||||||
self.teamRegistrations = self.store.registerSynchronizedCollection(indexed: indexed) |
|
||||||
self.playerRegistrations = self.store.registerSynchronizedCollection(indexed: indexed) |
|
||||||
self.matches = self.store.registerSynchronizedCollection(indexed: indexed) |
|
||||||
self.teamScores = self.store.registerSynchronizedCollection(indexed: indexed) |
|
||||||
self.matchSchedulers = self.store.registerCollection(indexed: indexed) |
|
||||||
self.drawLogs = self.store.registerSynchronizedCollection(indexed: indexed) |
|
||||||
|
|
||||||
self.store.loadCollectionsFromServerIfNoFile() |
|
||||||
|
|
||||||
NotificationCenter.default.addObserver( |
|
||||||
self, |
|
||||||
selector: #selector(_leStorageDidSynchronize), |
|
||||||
name: NSNotification.Name.LeStorageDidSynchronize, |
|
||||||
object: nil) |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
@objc func _leStorageDidSynchronize(notification: Notification) { |
|
||||||
// Logger.log("SYNCED > teamRegistrations count = \(self.teamRegistrations.count)") |
|
||||||
} |
|
||||||
|
|
||||||
deinit { |
|
||||||
NotificationCenter.default.removeObserver(self) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,96 +0,0 @@ |
|||||||
// |
|
||||||
// Array+Extensions.swift |
|
||||||
// PadelClub |
|
||||||
// |
|
||||||
// Created by Razmig Sarkissian on 03/03/2024. |
|
||||||
// |
|
||||||
|
|
||||||
import Foundation |
|
||||||
|
|
||||||
extension Array { |
|
||||||
func chunked(into size: Int) -> [[Element]] { |
|
||||||
return stride(from: 0, to: count, by: size).map { |
|
||||||
Array(self[$0 ..< Swift.min($0 + size, count)]) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func anySatisfy(_ p: (Element) -> Bool) -> Bool { |
|
||||||
return first(where: { p($0) }) != nil |
|
||||||
//return !self.allSatisfy { !p($0) } |
|
||||||
} |
|
||||||
|
|
||||||
// Check if the number of elements in the sequence is even |
|
||||||
var isEven: Bool { |
|
||||||
return self.count % 2 == 0 |
|
||||||
} |
|
||||||
|
|
||||||
// Check if the number of elements in the sequence is odd |
|
||||||
var isOdd: Bool { |
|
||||||
return self.count % 2 != 0 |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
extension Array where Element: Equatable { |
|
||||||
|
|
||||||
/// Remove first collection element that is equal to the given `object` or `element`: |
|
||||||
mutating func remove(elements: [Element]) { |
|
||||||
elements.forEach { |
|
||||||
if let index = firstIndex(of: $0) { |
|
||||||
remove(at: index) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
extension Array where Element: CustomStringConvertible { |
|
||||||
func customJoined(separator: String, lastSeparator: String) -> String { |
|
||||||
switch count { |
|
||||||
case 0: |
|
||||||
return "" |
|
||||||
case 1: |
|
||||||
return "\(self[0])" |
|
||||||
case 2: |
|
||||||
return "\(self[0]) \(lastSeparator) \(self[1])" |
|
||||||
default: |
|
||||||
let firstPart = dropLast().map { "\($0)" }.joined(separator: ", ") |
|
||||||
let lastPart = "\(lastSeparator) \(last!)" |
|
||||||
return "\(firstPart) \(lastPart)" |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
extension Dictionary where Key == Int, Value == [String] { |
|
||||||
mutating func setOrAppend(_ element: String?, at key: Int) { |
|
||||||
// Check if the element is nil; do nothing if it is |
|
||||||
guard let element = element else { |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
// Check if the key exists in the dictionary |
|
||||||
if var array = self[key] { |
|
||||||
// If it exists, append the element to the array |
|
||||||
array.append(element) |
|
||||||
self[key] = array |
|
||||||
} else { |
|
||||||
// If it doesn't exist, create a new array with the element |
|
||||||
self[key] = [element] |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
extension Array where Element == String { |
|
||||||
func formatList(maxDisplay: Int = 2) -> [String] { |
|
||||||
// Check if the array has fewer or equal elements than the maximum display limit |
|
||||||
if self.count <= maxDisplay { |
|
||||||
// Join all elements with commas |
|
||||||
return self |
|
||||||
} else { |
|
||||||
// Join only the first `maxDisplay` elements and add "et plus" |
|
||||||
let displayedItems = self.prefix(maxDisplay) |
|
||||||
let remainingCount = self.count - maxDisplay |
|
||||||
return displayedItems.dropLast() + [displayedItems.last! + " et \(remainingCount) de plus"] |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -0,0 +1,25 @@ |
|||||||
|
// |
||||||
|
// Badge+Extensions.swift |
||||||
|
// PadelClub |
||||||
|
// |
||||||
|
// Created by Laurent Morvillier on 15/04/2025. |
||||||
|
// |
||||||
|
|
||||||
|
import Foundation |
||||||
|
import SwiftUI |
||||||
|
import PadelClubData |
||||||
|
|
||||||
|
extension Badge { |
||||||
|
|
||||||
|
func color() -> Color { |
||||||
|
switch self { |
||||||
|
case .checkmark: |
||||||
|
.green |
||||||
|
case .xmark: |
||||||
|
.logoRed |
||||||
|
case .custom(_, let color): |
||||||
|
color |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -1,71 +0,0 @@ |
|||||||
// |
|
||||||
// Calendar+Extensions.swift |
|
||||||
// PadelClub |
|
||||||
// |
|
||||||
// Created by Razmig Sarkissian on 28/03/2024. |
|
||||||
// |
|
||||||
|
|
||||||
import Foundation |
|
||||||
|
|
||||||
extension Calendar { |
|
||||||
func numberOfDaysBetween(_ from: Date?, and to: Date?) -> Int { |
|
||||||
guard let from, let to else { return 0 } |
|
||||||
let fromDate = startOfDay(for: from) |
|
||||||
let toDate = startOfDay(for: to) |
|
||||||
let numberOfDays = dateComponents([.day], from: fromDate, to: toDate) |
|
||||||
|
|
||||||
return numberOfDays.day! // <1> |
|
||||||
} |
|
||||||
|
|
||||||
func isSameDay(date1: Date?, date2: Date?) -> Bool { |
|
||||||
guard let date1, let date2 else { return false } |
|
||||||
|
|
||||||
return numberOfDaysBetween(date1, and: date2) == 0 |
|
||||||
} |
|
||||||
|
|
||||||
func getSportAge() -> Int { |
|
||||||
let currentDate = Date() |
|
||||||
|
|
||||||
// Get the current year |
|
||||||
let currentYear = component(.year, from: currentDate) |
|
||||||
|
|
||||||
// Define the date components for 1st September and 31st December of the current year |
|
||||||
let septemberFirstComponents = DateComponents(year: currentYear, month: 9, day: 1) |
|
||||||
let decemberThirtyFirstComponents = DateComponents(year: currentYear, month: 12, day: 31) |
|
||||||
|
|
||||||
// Get the actual dates for 1st September and 31st December |
|
||||||
let septemberFirst = date(from: septemberFirstComponents)! |
|
||||||
let decemberThirtyFirst = date(from: decemberThirtyFirstComponents)! |
|
||||||
|
|
||||||
// Determine the sport year |
|
||||||
let sportYear: Int |
|
||||||
if currentDate >= septemberFirst && currentDate <= decemberThirtyFirst { |
|
||||||
// If after 1st September and before 31st December, use current year + 1 |
|
||||||
sportYear = currentYear + 1 |
|
||||||
} else { |
|
||||||
// Otherwise, use the current year |
|
||||||
sportYear = currentYear |
|
||||||
} |
|
||||||
return sportYear |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
extension Calendar { |
|
||||||
// Add or subtract months from a date |
|
||||||
func addMonths(_ months: Int, to date: Date) -> Date { |
|
||||||
return self.date(byAdding: .month, value: months, to: date)! |
|
||||||
} |
|
||||||
|
|
||||||
// Generate a list of month start dates between two dates |
|
||||||
func generateMonthRange(startDate: Date, endDate: Date) -> [Date] { |
|
||||||
var dates: [Date] = [] |
|
||||||
var currentDate = startDate |
|
||||||
|
|
||||||
while currentDate <= endDate { |
|
||||||
dates.append(currentDate) |
|
||||||
currentDate = self.addMonths(1, to: currentDate) |
|
||||||
} |
|
||||||
|
|
||||||
return dates |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,45 +0,0 @@ |
|||||||
// |
|
||||||
// KeyedEncodingContainer+Extensions.swift |
|
||||||
// PadelClub |
|
||||||
// |
|
||||||
// Created by Laurent Morvillier on 18/09/2024. |
|
||||||
// |
|
||||||
|
|
||||||
import Foundation |
|
||||||
import LeStorage |
|
||||||
|
|
||||||
extension KeyedDecodingContainer { |
|
||||||
|
|
||||||
func decodeEncrypted(key: Key) throws -> String { |
|
||||||
let data = try self.decode(Data.self, forKey: key) |
|
||||||
return try data.decryptData(pass: CryptoKey.pass.rawValue) |
|
||||||
} |
|
||||||
|
|
||||||
func decodeEncryptedIfPresent(key: Key) throws -> String? { |
|
||||||
let data = try self.decodeIfPresent(Data.self, forKey: key) |
|
||||||
if let data { |
|
||||||
return try data.decryptData(pass: CryptoKey.pass.rawValue) |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
extension KeyedEncodingContainer { |
|
||||||
|
|
||||||
mutating func encodeAndEncrypt(_ value: Data, forKey key: Key) throws { |
|
||||||
let encryped: Data = try value.encrypt(pass: CryptoKey.pass.rawValue) |
|
||||||
try self.encode(encryped, forKey: key) |
|
||||||
} |
|
||||||
|
|
||||||
mutating func encodeAndEncryptIfPresent(_ value: Data?, forKey key: Key) throws { |
|
||||||
guard let value else { |
|
||||||
try encodeNil(forKey: key) |
|
||||||
return |
|
||||||
} |
|
||||||
try self.encodeAndEncrypt(value, forKey: key) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
@ -0,0 +1,22 @@ |
|||||||
|
// |
||||||
|
// CustomUser+Extensions.swift |
||||||
|
// PadelClub |
||||||
|
// |
||||||
|
// Created by Laurent Morvillier on 15/04/2025. |
||||||
|
// |
||||||
|
|
||||||
|
import Foundation |
||||||
|
import PadelClubData |
||||||
|
|
||||||
|
extension CustomUser { |
||||||
|
|
||||||
|
func currentPlayerData() -> ImportedPlayer? { |
||||||
|
guard let licenceId = self.licenceId?.strippedLicense else { return nil } |
||||||
|
let federalContext = PersistenceController.shared.localContainer.viewContext |
||||||
|
let fetchRequest = ImportedPlayer.fetchRequest() |
||||||
|
let predicate = NSPredicate(format: "license == %@", licenceId) |
||||||
|
fetchRequest.predicate = predicate |
||||||
|
return try? federalContext.fetch(fetchRequest).first |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -1,266 +0,0 @@ |
|||||||
// |
|
||||||
// Date+Extensions.swift |
|
||||||
// PadelClub |
|
||||||
// |
|
||||||
// Created by Razmig Sarkissian on 01/03/2024. |
|
||||||
// |
|
||||||
|
|
||||||
import Foundation |
|
||||||
enum TimeOfDay { |
|
||||||
case morning |
|
||||||
case noon |
|
||||||
case afternoon |
|
||||||
case evening |
|
||||||
case night |
|
||||||
|
|
||||||
var hello: String { |
|
||||||
switch self { |
|
||||||
case .morning, .noon, .afternoon: |
|
||||||
return "Bonjour" |
|
||||||
case .evening, .night: |
|
||||||
return "Bonsoir" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
var goodbye: String { |
|
||||||
switch self { |
|
||||||
case .morning, .noon, .afternoon: |
|
||||||
return "Bonne journée" |
|
||||||
case .evening, .night: |
|
||||||
return "Bonne soirée" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
extension Date { |
|
||||||
func withoutSeconds() -> Date { |
|
||||||
let calendar = Calendar.current |
|
||||||
return calendar.date(bySettingHour: calendar.component(.hour, from: self), |
|
||||||
minute: calendar.component(.minute, from: self), |
|
||||||
second: 0, |
|
||||||
of: self)! |
|
||||||
} |
|
||||||
|
|
||||||
func localizedDate() -> String { |
|
||||||
self.formatted(.dateTime.weekday().day().month()) + " à " + self.formattedAsHourMinute() |
|
||||||
} |
|
||||||
|
|
||||||
func formattedAsHourMinute() -> String { |
|
||||||
formatted(.dateTime.hour().minute()) |
|
||||||
} |
|
||||||
|
|
||||||
func formattedAsDate() -> String { |
|
||||||
formatted(.dateTime.weekday().day(.twoDigits).month().year()) |
|
||||||
} |
|
||||||
|
|
||||||
var dateFormatted: String { |
|
||||||
formatted(.dateTime.day(.twoDigits).month(.twoDigits).year(.twoDigits)) |
|
||||||
} |
|
||||||
|
|
||||||
var monthYearFormatted: String { |
|
||||||
formatted(.dateTime.month(.wide).year(.defaultDigits)) |
|
||||||
} |
|
||||||
|
|
||||||
var twoDigitsYearFormatted: String { |
|
||||||
formatted(Date.FormatStyle(date: .numeric, time: .omitted).locale(Locale(identifier: "fr_FR")).year(.twoDigits)) |
|
||||||
} |
|
||||||
|
|
||||||
var timeOfDay: TimeOfDay { |
|
||||||
let hour = Calendar.current.component(.hour, from: self) |
|
||||||
switch hour { |
|
||||||
case 6..<12 : return .morning |
|
||||||
case 12 : return .noon |
|
||||||
case 13..<17 : return .afternoon |
|
||||||
case 17..<22 : return .evening |
|
||||||
default: return .night |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
extension Date { |
|
||||||
func isInCurrentYear() -> Bool { |
|
||||||
let calendar = Calendar.current |
|
||||||
let currentYear = calendar.component(.year, from: Date()) |
|
||||||
let yearOfDate = calendar.component(.year, from: self) |
|
||||||
|
|
||||||
return currentYear == yearOfDate |
|
||||||
} |
|
||||||
|
|
||||||
func get(_ components: Calendar.Component..., calendar: Calendar = Calendar.current) -> DateComponents { |
|
||||||
return calendar.dateComponents(Set(components), from: self) |
|
||||||
} |
|
||||||
|
|
||||||
func get(_ component: Calendar.Component, calendar: Calendar = Calendar.current) -> Int { |
|
||||||
return calendar.component(component, from: self) |
|
||||||
} |
|
||||||
|
|
||||||
var tomorrowAtNine: Date { |
|
||||||
let currentHour = Calendar.current.component(.hour, from: self) |
|
||||||
let startOfDay = Calendar.current.startOfDay(for: self) |
|
||||||
if currentHour < 8 { |
|
||||||
return Calendar.current.date(byAdding: .hour, value: 9, to: startOfDay)! |
|
||||||
} else { |
|
||||||
let date = Calendar.current.date(byAdding: .day, value: 1, to: startOfDay) |
|
||||||
return Calendar.current.date(byAdding: .hour, value: 9, to: date!)! |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func atBeginningOfDay(hourInt: Int = 9) -> Date { |
|
||||||
Calendar.current.date(byAdding: .hour, value: hourInt, to: self.startOfDay)! |
|
||||||
} |
|
||||||
|
|
||||||
static var firstDayOfWeek = Calendar.current.firstWeekday |
|
||||||
static var capitalizedFirstLettersOfWeekdays: [String] = { |
|
||||||
let calendar = Calendar.current |
|
||||||
// let weekdays = calendar.shortWeekdaySymbols |
|
||||||
|
|
||||||
// return weekdays.map { weekday in |
|
||||||
// guard let firstLetter = weekday.first else { return "" } |
|
||||||
// return String(firstLetter).capitalized |
|
||||||
// } |
|
||||||
// Adjusted for the different weekday starts |
|
||||||
var weekdays = calendar.veryShortStandaloneWeekdaySymbols |
|
||||||
if firstDayOfWeek > 1 { |
|
||||||
for _ in 1..<firstDayOfWeek { |
|
||||||
if let first = weekdays.first { |
|
||||||
weekdays.append(first) |
|
||||||
weekdays.removeFirst() |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
return weekdays.map { $0.capitalized } |
|
||||||
}() |
|
||||||
|
|
||||||
static var fullMonthNames: [String] = { |
|
||||||
let dateFormatter = DateFormatter() |
|
||||||
dateFormatter.locale = Locale.current |
|
||||||
|
|
||||||
return (1...12).compactMap { month in |
|
||||||
dateFormatter.setLocalizedDateFormatFromTemplate("MMMM") |
|
||||||
let date = Calendar.current.date(from: DateComponents(year: 2000, month: month, day: 1)) |
|
||||||
return date.map { dateFormatter.string(from: $0) } |
|
||||||
} |
|
||||||
}() |
|
||||||
|
|
||||||
var startOfMonth: Date { |
|
||||||
Calendar.current.dateInterval(of: .month, for: self)!.start |
|
||||||
} |
|
||||||
|
|
||||||
var endOfMonth: Date { |
|
||||||
let lastDay = Calendar.current.dateInterval(of: .month, for: self)!.end |
|
||||||
return Calendar.current.date(byAdding: .day, value: -1, to: lastDay)! |
|
||||||
} |
|
||||||
|
|
||||||
var startOfPreviousMonth: Date { |
|
||||||
let dayInPreviousMonth = Calendar.current.date(byAdding: .month, value: -1, to: self)! |
|
||||||
return dayInPreviousMonth.startOfMonth |
|
||||||
} |
|
||||||
|
|
||||||
var numberOfDaysInMonth: Int { |
|
||||||
Calendar.current.component(.day, from: endOfMonth) |
|
||||||
} |
|
||||||
|
|
||||||
// var sundayBeforeStart: Date { |
|
||||||
// let startOfMonthWeekday = Calendar.current.component(.weekday, from: startOfMonth) |
|
||||||
// let numberFromPreviousMonth = startOfMonthWeekday - 1 |
|
||||||
// return Calendar.current.date(byAdding: .day, value: -numberFromPreviousMonth, to: startOfMonth)! |
|
||||||
// } |
|
||||||
// New to accomodate for different start of week days |
|
||||||
var firstWeekDayBeforeStart: Date { |
|
||||||
let startOfMonthWeekday = Calendar.current.component(.weekday, from: startOfMonth) |
|
||||||
let numberFromPreviousMonth = startOfMonthWeekday - Self.firstDayOfWeek |
|
||||||
return Calendar.current.date(byAdding: .day, value: -numberFromPreviousMonth, to: startOfMonth)! |
|
||||||
} |
|
||||||
|
|
||||||
var calendarDisplayDays: [Date] { |
|
||||||
var days: [Date] = [] |
|
||||||
// Current month days |
|
||||||
for dayOffset in 0..<numberOfDaysInMonth { |
|
||||||
let newDay = Calendar.current.date(byAdding: .day, value: dayOffset, to: startOfMonth) |
|
||||||
days.append(newDay!) |
|
||||||
} |
|
||||||
// previous month days |
|
||||||
for dayOffset in 0..<startOfPreviousMonth.numberOfDaysInMonth { |
|
||||||
let newDay = Calendar.current.date(byAdding: .day, value: dayOffset, to: startOfPreviousMonth) |
|
||||||
days.append(newDay!) |
|
||||||
} |
|
||||||
|
|
||||||
// Fixed to accomodate different weekday starts |
|
||||||
return days.filter { $0 >= firstWeekDayBeforeStart && $0 <= endOfMonth }.sorted(by: <) |
|
||||||
} |
|
||||||
|
|
||||||
var monthInt: Int { |
|
||||||
Calendar.current.component(.month, from: self) |
|
||||||
} |
|
||||||
|
|
||||||
var yearInt: Int { |
|
||||||
Calendar.current.component(.year, from: self) |
|
||||||
} |
|
||||||
|
|
||||||
var dayInt: Int { |
|
||||||
Calendar.current.component(.day, from: self) |
|
||||||
} |
|
||||||
|
|
||||||
var startOfDay: Date { |
|
||||||
Calendar.current.startOfDay(for: self) |
|
||||||
} |
|
||||||
|
|
||||||
func endOfDay() -> Date { |
|
||||||
let calendar = Calendar.current |
|
||||||
return calendar.date(bySettingHour: 23, minute: 59, second: 59, of: self)! |
|
||||||
} |
|
||||||
|
|
||||||
func atNine() -> Date { |
|
||||||
let calendar = Calendar.current |
|
||||||
return calendar.date(bySettingHour: 9, minute: 0, second: 0, of: self)! |
|
||||||
} |
|
||||||
|
|
||||||
func atEightAM() -> Date { |
|
||||||
let calendar = Calendar.current |
|
||||||
return calendar.date(bySettingHour: 8, minute: 0, second: 0, of: self)! |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
extension Date { |
|
||||||
func isEarlierThan(_ date: Date) -> Bool { |
|
||||||
Calendar.current.compare(self, to: date, toGranularity: .minute) == .orderedAscending |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
extension Date { |
|
||||||
func localizedTime() -> String { |
|
||||||
self.formattedAsHourMinute() |
|
||||||
} |
|
||||||
|
|
||||||
func localizedDay() -> String { |
|
||||||
self.formatted(.dateTime.weekday(.wide).day()) |
|
||||||
} |
|
||||||
|
|
||||||
func localizedWeekDay() -> String { |
|
||||||
self.formatted(.dateTime.weekday(.wide)) |
|
||||||
} |
|
||||||
|
|
||||||
func timeElapsedString() -> String { |
|
||||||
let timeInterval = abs(Date().timeIntervalSince(self)) |
|
||||||
let duration = Duration.seconds(timeInterval) |
|
||||||
|
|
||||||
let formatStyle = Duration.UnitsFormatStyle(allowedUnits: [.hours, .minutes], width: .narrow) |
|
||||||
return formatStyle.format(duration) |
|
||||||
} |
|
||||||
|
|
||||||
static var hourMinuteFormatter: DateComponentsFormatter = { |
|
||||||
let formatter = DateComponentsFormatter() |
|
||||||
formatter.allowedUnits = [.hour, .minute] // Customize units |
|
||||||
formatter.unitsStyle = .abbreviated // You can choose .abbreviated or .short |
|
||||||
return formatter |
|
||||||
}() |
|
||||||
|
|
||||||
func truncateMinutesAndSeconds() -> Date { |
|
||||||
let calendar = Calendar.current |
|
||||||
return calendar.date(bySetting: .minute, value: 0, of: self)!.withoutSeconds() |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,43 +0,0 @@ |
|||||||
// |
|
||||||
// FixedWidthInteger+Extensions.swift |
|
||||||
// PadelClub |
|
||||||
// |
|
||||||
// Created by Razmig Sarkissian on 03/03/2024. |
|
||||||
// |
|
||||||
|
|
||||||
import Foundation |
|
||||||
|
|
||||||
public extension FixedWidthInteger { |
|
||||||
func ordinalFormattedSuffix(feminine: Bool = false) -> String { |
|
||||||
switch self { |
|
||||||
case 1: return feminine ? "ère" : "er" |
|
||||||
default: return "ème" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func ordinalFormatted(feminine: Bool = false) -> String { |
|
||||||
return self.formatted() + self.ordinalFormattedSuffix(feminine: feminine) |
|
||||||
} |
|
||||||
|
|
||||||
private var isMany: Bool { |
|
||||||
self > 1 || self < -1 |
|
||||||
} |
|
||||||
|
|
||||||
var pluralSuffix: String { |
|
||||||
return isMany ? "s" : "" |
|
||||||
} |
|
||||||
|
|
||||||
func localizedPluralSuffix(_ plural: String = "s") -> String { |
|
||||||
return isMany ? plural : "" |
|
||||||
} |
|
||||||
|
|
||||||
func formattedAsRawString() -> String { |
|
||||||
String(self) |
|
||||||
} |
|
||||||
|
|
||||||
func durationInHourMinutes() -> String { |
|
||||||
let duration = Duration.seconds(self*60) |
|
||||||
let formatStyle = Duration.UnitsFormatStyle(allowedUnits: [.hours, .minutes], width: .narrow) |
|
||||||
return formatStyle.format(duration) |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,28 +0,0 @@ |
|||||||
// |
|
||||||
// Locale+Extensions.swift |
|
||||||
// PadelClub |
|
||||||
// |
|
||||||
// Created by Laurent Morvillier on 03/04/2024. |
|
||||||
// |
|
||||||
|
|
||||||
import Foundation |
|
||||||
|
|
||||||
extension Locale { |
|
||||||
|
|
||||||
static func countries() -> [String] { |
|
||||||
var countries: [String] = [] |
|
||||||
|
|
||||||
for countryCode in Locale.Region.isoRegions { |
|
||||||
if let countryName = Locale.current.localizedString(forRegionCode: countryCode.identifier) { |
|
||||||
countries.append(countryName) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return countries.sorted() |
|
||||||
} |
|
||||||
|
|
||||||
static func defaultCurrency() -> String { |
|
||||||
// return "EUR" |
|
||||||
Locale.current.currency?.identifier ?? "EUR" |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,34 +1,14 @@ |
|||||||
// |
// |
||||||
// MonthData.swift |
// MonthData+Extensions.swift |
||||||
// PadelClub |
// PadelClub |
||||||
// |
// |
||||||
// Created by Razmig Sarkissian on 18/04/2024. |
// Created by Laurent Morvillier on 15/04/2025. |
||||||
// |
// |
||||||
|
|
||||||
import Foundation |
import Foundation |
||||||
import SwiftUI |
import PadelClubData |
||||||
import LeStorage |
|
||||||
|
|
||||||
@Observable |
extension MonthData { |
||||||
final class MonthData: BaseMonthData { |
|
||||||
|
|
||||||
init(monthKey: String) { |
|
||||||
super.init() |
|
||||||
self.monthKey = monthKey |
|
||||||
self.creationDate = Date() |
|
||||||
} |
|
||||||
|
|
||||||
required init(from decoder: Decoder) throws { |
|
||||||
try super.init(from: decoder) |
|
||||||
} |
|
||||||
|
|
||||||
required public init() { |
|
||||||
super.init() |
|
||||||
} |
|
||||||
|
|
||||||
func total() -> Int { |
|
||||||
return (maleCount ?? 0) + (femaleCount ?? 0) |
|
||||||
} |
|
||||||
|
|
||||||
static func calculateCurrentUnrankedValues(fromDate: Date) async { |
static func calculateCurrentUnrankedValues(fromDate: Date) async { |
||||||
|
|
||||||
@ -1,27 +0,0 @@ |
|||||||
// |
|
||||||
// MySortDescriptor.swift |
|
||||||
// PadelClub |
|
||||||
// |
|
||||||
// Created by Razmig Sarkissian on 26/03/2024. |
|
||||||
// |
|
||||||
|
|
||||||
import Foundation |
|
||||||
|
|
||||||
struct MySortDescriptor<Value> { |
|
||||||
var comparator: (Value, Value) -> ComparisonResult |
|
||||||
} |
|
||||||
|
|
||||||
extension MySortDescriptor { |
|
||||||
static func keyPath<T: Comparable>(_ keyPath: KeyPath<Value, T>) -> Self { |
|
||||||
Self { rootA, rootB in |
|
||||||
let valueA = rootA[keyPath: keyPath] |
|
||||||
let valueB = rootB[keyPath: keyPath] |
|
||||||
|
|
||||||
guard valueA != valueB else { |
|
||||||
return .orderedSame |
|
||||||
} |
|
||||||
|
|
||||||
return valueA < valueB ? .orderedAscending : .orderedDescending |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,20 +0,0 @@ |
|||||||
// |
|
||||||
// NumberFormatter+Extensions.swift |
|
||||||
// PadelClub |
|
||||||
// |
|
||||||
// Created by Razmig Sarkissian on 27/03/2024. |
|
||||||
// |
|
||||||
|
|
||||||
import Foundation |
|
||||||
|
|
||||||
extension NumberFormatter { |
|
||||||
static var ordinal: NumberFormatter = { |
|
||||||
let formatter = NumberFormatter() |
|
||||||
formatter.numberStyle = .ordinal |
|
||||||
return formatter |
|
||||||
}() |
|
||||||
|
|
||||||
static var standard: NumberFormatter = { |
|
||||||
return NumberFormatter() |
|
||||||
}() |
|
||||||
} |
|
||||||
@ -0,0 +1,229 @@ |
|||||||
|
// |
||||||
|
// PlayerRegistration+Extensions.swift |
||||||
|
// PadelClub |
||||||
|
// |
||||||
|
// Created by Laurent Morvillier on 15/04/2025. |
||||||
|
// |
||||||
|
|
||||||
|
import Foundation |
||||||
|
import PadelClubData |
||||||
|
|
||||||
|
extension PlayerRegistration { |
||||||
|
|
||||||
|
convenience init(importedPlayer: ImportedPlayer) { |
||||||
|
self.init() |
||||||
|
self.teamRegistration = "" |
||||||
|
self.firstName = (importedPlayer.firstName ?? "").prefixTrimmed(50).capitalized |
||||||
|
self.lastName = (importedPlayer.lastName ?? "").prefixTrimmed(50).uppercased() |
||||||
|
self.licenceId = importedPlayer.license?.prefixTrimmed(50) ?? nil |
||||||
|
self.rank = Int(importedPlayer.rank) |
||||||
|
self.sex = importedPlayer.male ? .male : .female |
||||||
|
self.tournamentPlayed = importedPlayer.tournamentPlayed |
||||||
|
self.points = importedPlayer.getPoints() |
||||||
|
self.clubName = importedPlayer.clubName?.prefixTrimmed(200) |
||||||
|
self.ligueName = importedPlayer.ligueName?.prefixTrimmed(200) |
||||||
|
self.assimilation = importedPlayer.assimilation?.prefixTrimmed(50) |
||||||
|
self.source = .frenchFederation |
||||||
|
self.birthdate = importedPlayer.birthYear?.prefixTrimmed(50) |
||||||
|
} |
||||||
|
|
||||||
|
convenience init?(federalData: [String], sex: Int, sexUnknown: Bool) { |
||||||
|
self.init() |
||||||
|
let _lastName = federalData[0].trimmed.uppercased() |
||||||
|
let _firstName = federalData[1].trimmed.capitalized |
||||||
|
if _lastName.isEmpty && _firstName.isEmpty { return nil } |
||||||
|
lastName = _lastName.prefixTrimmed(50) |
||||||
|
firstName = _firstName.prefixTrimmed(50) |
||||||
|
birthdate = federalData[2].formattedAsBirthdate().prefixTrimmed(50) |
||||||
|
licenceId = federalData[3].prefixTrimmed(50) |
||||||
|
clubName = federalData[4].prefixTrimmed(200) |
||||||
|
let stringRank = federalData[5] |
||||||
|
if stringRank.isEmpty { |
||||||
|
rank = nil |
||||||
|
} else { |
||||||
|
rank = Int(stringRank) |
||||||
|
} |
||||||
|
let _email = federalData[6] |
||||||
|
if _email.isEmpty == false { |
||||||
|
self.email = _email.prefixTrimmed(50) |
||||||
|
} |
||||||
|
let _phoneNumber = federalData[7] |
||||||
|
if _phoneNumber.isEmpty == false { |
||||||
|
self.phoneNumber = _phoneNumber.prefixTrimmed(50) |
||||||
|
} |
||||||
|
|
||||||
|
source = .beachPadel |
||||||
|
if sexUnknown { |
||||||
|
if sex == 1 && FileImportManager.shared.foundInWomenData(license: federalData[3]) { |
||||||
|
self.sex = .female |
||||||
|
} else if FileImportManager.shared.foundInMenData(license: federalData[3]) { |
||||||
|
self.sex = .male |
||||||
|
} else { |
||||||
|
self.sex = nil |
||||||
|
} |
||||||
|
} else { |
||||||
|
self.sex = PlayerSexType(rawValue: sex) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
extension PlayerRegistration { |
||||||
|
|
||||||
|
func hasHomonym() -> Bool { |
||||||
|
let federalContext = PersistenceController.shared.localContainer.viewContext |
||||||
|
let fetchRequest = ImportedPlayer.fetchRequest() |
||||||
|
let predicate = NSPredicate(format: "firstName == %@ && lastName == %@", firstName, lastName) |
||||||
|
fetchRequest.predicate = predicate |
||||||
|
|
||||||
|
do { |
||||||
|
let count = try federalContext.count(for: fetchRequest) |
||||||
|
return count > 1 |
||||||
|
} catch { |
||||||
|
|
||||||
|
} |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
func updateRank(from sources: [CSVParser], lastRank: Int?) async throws { |
||||||
|
#if DEBUG_TIME |
||||||
|
let start = Date() |
||||||
|
defer { |
||||||
|
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) |
||||||
|
print("func updateRank()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) |
||||||
|
} |
||||||
|
#endif |
||||||
|
|
||||||
|
if let dataFound = try await history(from: sources) { |
||||||
|
rank = dataFound.rankValue?.toInt() |
||||||
|
points = dataFound.points |
||||||
|
tournamentPlayed = dataFound.tournamentCountValue?.toInt() |
||||||
|
} else if let dataFound = try await historyFromName(from: sources) { |
||||||
|
rank = dataFound.rankValue?.toInt() |
||||||
|
points = dataFound.points |
||||||
|
tournamentPlayed = dataFound.tournamentCountValue?.toInt() |
||||||
|
} else { |
||||||
|
rank = lastRank |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func history(from sources: [CSVParser]) async throws -> Line? { |
||||||
|
#if DEBUG_TIME |
||||||
|
let start = Date() |
||||||
|
defer { |
||||||
|
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) |
||||||
|
print("func history()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) |
||||||
|
} |
||||||
|
#endif |
||||||
|
|
||||||
|
guard let license = licenceId?.strippedLicense else { |
||||||
|
return nil // Do NOT call historyFromName here, let updateRank handle it |
||||||
|
} |
||||||
|
|
||||||
|
let filteredSources = sources.filter { $0.maleData == isMalePlayer() } |
||||||
|
|
||||||
|
return await withTaskGroup(of: Line?.self) { group in |
||||||
|
for source in filteredSources { |
||||||
|
group.addTask { |
||||||
|
guard !Task.isCancelled else { return nil } |
||||||
|
return try? await source.first { $0.rawValue.contains(";\(license);") } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for await result in group { |
||||||
|
if let result { |
||||||
|
group.cancelAll() // Stop other tasks as soon as we find a match |
||||||
|
return result |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func historyFromName(from sources: [CSVParser]) async throws -> Line? { |
||||||
|
#if DEBUG |
||||||
|
let start = Date() |
||||||
|
defer { |
||||||
|
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) |
||||||
|
print("func historyFromName()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) |
||||||
|
} |
||||||
|
#endif |
||||||
|
|
||||||
|
let filteredSources = sources.filter { $0.maleData == isMalePlayer() } |
||||||
|
let normalizedLastName = lastName.canonicalVersionWithPunctuation |
||||||
|
let normalizedFirstName = firstName.canonicalVersionWithPunctuation |
||||||
|
|
||||||
|
return await withTaskGroup(of: Line?.self) { group in |
||||||
|
for source in filteredSources { |
||||||
|
group.addTask { |
||||||
|
guard !Task.isCancelled else { print("Cancelled"); return nil } |
||||||
|
return try? await source.first { |
||||||
|
let lineValue = $0.rawValue.canonicalVersionWithPunctuation |
||||||
|
return lineValue.contains(";\(normalizedLastName);\(normalizedFirstName);") |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for await result in group { |
||||||
|
if let result { |
||||||
|
group.cancelAll() // Stop other tasks as soon as we find a match |
||||||
|
return result |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
extension PlayerRegistration: PlayerHolder { |
||||||
|
|
||||||
|
func getAssimilatedAsMaleRank() -> Int? { |
||||||
|
nil |
||||||
|
} |
||||||
|
|
||||||
|
func getFirstName() -> String { |
||||||
|
firstName |
||||||
|
} |
||||||
|
|
||||||
|
func getLastName() -> String { |
||||||
|
lastName |
||||||
|
} |
||||||
|
|
||||||
|
func getPoints() -> Double? { |
||||||
|
self.points |
||||||
|
} |
||||||
|
|
||||||
|
func getRank() -> Int? { |
||||||
|
rank |
||||||
|
} |
||||||
|
|
||||||
|
func isUnranked() -> Bool { |
||||||
|
rank == nil |
||||||
|
} |
||||||
|
|
||||||
|
func formattedRank() -> String { |
||||||
|
self.rankLabel() |
||||||
|
} |
||||||
|
|
||||||
|
func formattedLicense() -> String { |
||||||
|
if let licenceId { return licenceId.computedLicense } |
||||||
|
return "aucune licence" |
||||||
|
} |
||||||
|
|
||||||
|
var male: Bool { |
||||||
|
isMalePlayer() |
||||||
|
} |
||||||
|
|
||||||
|
func getBirthYear() -> Int? { |
||||||
|
nil |
||||||
|
} |
||||||
|
|
||||||
|
func getProgression() -> Int { |
||||||
|
0 |
||||||
|
} |
||||||
|
|
||||||
|
func getComputedRank() -> Int? { |
||||||
|
computedRank |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,33 @@ |
|||||||
|
// |
||||||
|
// Round+Extensions.swift |
||||||
|
// PadelClub |
||||||
|
// |
||||||
|
// Created by Laurent Morvillier on 30/04/2025. |
||||||
|
// |
||||||
|
|
||||||
|
import Foundation |
||||||
|
import PadelClubData |
||||||
|
|
||||||
|
extension Round { |
||||||
|
|
||||||
|
func loserBracketTurns() -> [LoserRound] { |
||||||
|
#if _DEBUG_TIME //DEBUGING TIME |
||||||
|
let start = Date() |
||||||
|
defer { |
||||||
|
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) |
||||||
|
print("func loserBracketTurns()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) |
||||||
|
} |
||||||
|
#endif |
||||||
|
var rounds = [LoserRound]() |
||||||
|
let currentRoundMatchCount = RoundRule.numberOfMatches(forRoundIndex: index) |
||||||
|
let roundCount = RoundRule.numberOfRounds(forTeams: currentRoundMatchCount) |
||||||
|
|
||||||
|
for index in 0..<roundCount { |
||||||
|
let lr = LoserRound(roundIndex: roundCount - index - 1, turnIndex: index, upperBracketRound: self) |
||||||
|
rounds.append(lr) |
||||||
|
} |
||||||
|
|
||||||
|
return rounds |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -1,103 +0,0 @@ |
|||||||
// |
|
||||||
// Sequence+Extensions.swift |
|
||||||
// PadelClub |
|
||||||
// |
|
||||||
// Created by Razmig Sarkissian on 03/03/2024. |
|
||||||
// |
|
||||||
|
|
||||||
import Foundation |
|
||||||
|
|
||||||
extension Collection { |
|
||||||
/// Returns the element at the specified index if it is within bounds, otherwise nil. |
|
||||||
subscript (safe index: Index) -> Element? { |
|
||||||
return indices.contains(index) ? self[index] : nil |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
extension Sequence { |
|
||||||
func sorted<T: Comparable>(by keyPath: KeyPath<Element, T>) -> [Element] { |
|
||||||
return sorted { a, b in |
|
||||||
return a[keyPath: keyPath] < b[keyPath: keyPath] |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
extension Sequence { |
|
||||||
func pairs() -> AnySequence<(Element, Element)> { |
|
||||||
AnySequence(zip(self, self.dropFirst())) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
extension Sequence { |
|
||||||
func concurrentForEach( |
|
||||||
_ operation: @escaping (Element) async throws -> Void |
|
||||||
) async throws { |
|
||||||
try await withThrowingTaskGroup(of: Void.self) { group in |
|
||||||
// First, create all tasks |
|
||||||
for element in self { |
|
||||||
group.addTask { |
|
||||||
try await operation(element) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Then wait for all tasks to complete |
|
||||||
for try await _ in group {} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func concurrentForEach( |
|
||||||
_ operation: @escaping (Element) async -> Void |
|
||||||
) async { |
|
||||||
await withTaskGroup(of: Void.self) { group in |
|
||||||
// First, add all tasks |
|
||||||
for element in self { |
|
||||||
group.addTask { |
|
||||||
await operation(element) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Then wait for all tasks to complete |
|
||||||
for await _ in group {} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
enum SortOrder { |
|
||||||
case ascending |
|
||||||
case descending |
|
||||||
} |
|
||||||
|
|
||||||
extension Sequence { |
|
||||||
func sorted(using descriptors: [MySortDescriptor<Element>], |
|
||||||
order: SortOrder) -> [Element] { |
|
||||||
sorted { valueA, valueB in |
|
||||||
for descriptor in descriptors { |
|
||||||
let result = descriptor.comparator(valueA, valueB) |
|
||||||
|
|
||||||
switch result { |
|
||||||
case .orderedSame: |
|
||||||
// Keep iterating if the two elements are equal, |
|
||||||
// since that'll let the next descriptor determine |
|
||||||
// the sort order: |
|
||||||
break |
|
||||||
case .orderedAscending: |
|
||||||
return order == .ascending |
|
||||||
case .orderedDescending: |
|
||||||
return order == .descending |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// If no descriptor was able to determine the sort |
|
||||||
// order, we'll default to false (similar to when |
|
||||||
// using the '<' operator with the built-in API): |
|
||||||
return false |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
extension Sequence { |
|
||||||
func sorted(using descriptors: MySortDescriptor<Element>...) -> [Element] { |
|
||||||
sorted(using: descriptors, order: .ascending) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@ -0,0 +1,34 @@ |
|||||||
|
// |
||||||
|
// SourceFileManager+Extensions.swift |
||||||
|
// PadelClub |
||||||
|
// |
||||||
|
// Created by Laurent Morvillier on 15/04/2025. |
||||||
|
// |
||||||
|
|
||||||
|
import Foundation |
||||||
|
import LeStorage |
||||||
|
import PadelClubData |
||||||
|
|
||||||
|
extension SourceFileManager { |
||||||
|
|
||||||
|
func exportToCSV(_ prefix: String = "", players: [FederalPlayer], sourceFileType: SourceFile, date: Date) { |
||||||
|
let lastDateString = URL.importDateFormatter.string(from: date) |
||||||
|
let dateString = [prefix, "CLASSEMENT-PADEL", sourceFileType.rawValue, lastDateString].filter({ $0.isEmpty == false }).joined(separator: "-") + "." + "csv" |
||||||
|
|
||||||
|
let documentsUrl:URL = (FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first as URL?)! |
||||||
|
let destinationFileUrl = documentsUrl.appendingPathComponent("\(dateString)") |
||||||
|
var csvText : String = "" |
||||||
|
for player in players { |
||||||
|
csvText.append(player.exportToCSV() + "\n") |
||||||
|
} |
||||||
|
|
||||||
|
do { |
||||||
|
try csvText.write(to: destinationFileUrl, atomically: true, encoding: .utf8) |
||||||
|
print("CSV file exported successfully.") |
||||||
|
} catch { |
||||||
|
print("Error writing CSV file:", error) |
||||||
|
Logger.error(error) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,42 @@ |
|||||||
|
// |
||||||
|
// SpinDrawable+Extensions.swift |
||||||
|
// PadelClub |
||||||
|
// |
||||||
|
// Created by Laurent Morvillier on 15/04/2025. |
||||||
|
// |
||||||
|
|
||||||
|
import Foundation |
||||||
|
import PadelClubData |
||||||
|
|
||||||
|
extension String: SpinDrawable { |
||||||
|
public func segmentLabel(_ displayStyle: DisplayStyle, hideNames: Bool) -> [String] { |
||||||
|
[self] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
extension Match: SpinDrawable { |
||||||
|
public func segmentLabel(_ displayStyle: DisplayStyle, hideNames: Bool) -> [String] { |
||||||
|
let teams = teams() |
||||||
|
if teams.count == 1, hideNames == false { |
||||||
|
return teams.first!.segmentLabel(displayStyle, hideNames: hideNames) |
||||||
|
} else { |
||||||
|
return [roundTitle(), matchTitle(displayStyle)].compactMap { $0 } |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
extension TeamRegistration: SpinDrawable { |
||||||
|
public func segmentLabel(_ displayStyle: DisplayStyle, hideNames: Bool) -> [String] { |
||||||
|
var strings: [String] = [] |
||||||
|
let indexLabel = tournamentObject()?.labelIndexOf(team: self) |
||||||
|
if let indexLabel { |
||||||
|
strings.append(indexLabel) |
||||||
|
if hideNames { |
||||||
|
return strings |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
strings.append(contentsOf: self.players().map { $0.playerLabel(displayStyle) }) |
||||||
|
return strings |
||||||
|
} |
||||||
|
} |
||||||
@ -1,47 +0,0 @@ |
|||||||
// |
|
||||||
// String+Crypto.swift |
|
||||||
// PadelClub |
|
||||||
// |
|
||||||
// Created by Laurent Morvillier on 30/04/2024. |
|
||||||
// |
|
||||||
|
|
||||||
import Foundation |
|
||||||
import CryptoKit |
|
||||||
|
|
||||||
enum CryptoError: Error { |
|
||||||
case invalidUTF8 |
|
||||||
case cantConvertUTF8 |
|
||||||
case invalidBase64String |
|
||||||
case nilSeal |
|
||||||
} |
|
||||||
|
|
||||||
extension Data { |
|
||||||
|
|
||||||
func encrypt(pass: String) throws -> Data { |
|
||||||
let key = try self._createSymmetricKey(fromString: pass) |
|
||||||
let sealedBox = try AES.GCM.seal(self, using: key) |
|
||||||
if let combined = sealedBox.combined { |
|
||||||
return combined |
|
||||||
} |
|
||||||
throw CryptoError.nilSeal |
|
||||||
} |
|
||||||
|
|
||||||
func decryptData(pass: String) throws -> String { |
|
||||||
let key = try self._createSymmetricKey(fromString: pass) |
|
||||||
let sealedBox = try AES.GCM.SealedBox(combined: self) |
|
||||||
let decryptedData = try AES.GCM.open(sealedBox, using: key) |
|
||||||
guard let decryptedMessage = String(data: decryptedData, encoding: .utf8) else { |
|
||||||
throw CryptoError.invalidUTF8 |
|
||||||
} |
|
||||||
return decryptedMessage |
|
||||||
} |
|
||||||
|
|
||||||
fileprivate func _createSymmetricKey(fromString keyString: String) throws -> SymmetricKey { |
|
||||||
guard let keyData = Data(base64Encoded: keyString) else { |
|
||||||
throw CryptoError.invalidBase64String |
|
||||||
} |
|
||||||
return SymmetricKey(data: keyData) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
@ -1,313 +0,0 @@ |
|||||||
// |
|
||||||
// String+Extensions.swift |
|
||||||
// PadelClub |
|
||||||
// |
|
||||||
// Created by Razmig Sarkissian on 01/03/2024. |
|
||||||
// |
|
||||||
|
|
||||||
import Foundation |
|
||||||
|
|
||||||
// MARK: - Trimming and stuff |
|
||||||
extension String { |
|
||||||
func trunc(length: Int, trailing: String = "…") -> String { |
|
||||||
if length <= 0 { return self } |
|
||||||
return (self.count > length) ? self.prefix(length) + trailing : self |
|
||||||
} |
|
||||||
|
|
||||||
func prefixTrimmed(_ length: Int) -> String { |
|
||||||
String(trimmed.prefix(length)) |
|
||||||
} |
|
||||||
|
|
||||||
func prefixMultilineTrimmed(_ length: Int) -> String { |
|
||||||
String(trimmedMultiline.prefix(length)) |
|
||||||
} |
|
||||||
|
|
||||||
var trimmed: String { |
|
||||||
replaceCharactersFromSet(characterSet: .newlines, replacementString: " ").trimmingCharacters(in: .whitespacesAndNewlines) |
|
||||||
} |
|
||||||
|
|
||||||
var trimmedMultiline: String { |
|
||||||
self.trimmingCharacters(in: .whitespacesAndNewlines) |
|
||||||
} |
|
||||||
|
|
||||||
func replaceCharactersFromSet(characterSet: CharacterSet, replacementString: String = "") -> String { |
|
||||||
components(separatedBy: characterSet).joined(separator:replacementString) |
|
||||||
} |
|
||||||
|
|
||||||
var canonicalVersion: String { |
|
||||||
trimmed.replaceCharactersFromSet(characterSet: .punctuationCharacters, replacementString: " ").folding(options: .diacriticInsensitive, locale: .current).lowercased() |
|
||||||
} |
|
||||||
|
|
||||||
var canonicalVersionWithPunctuation: String { |
|
||||||
trimmed.folding(options: .diacriticInsensitive, locale: .current).lowercased() |
|
||||||
} |
|
||||||
|
|
||||||
var removingFirstCharacter: String { |
|
||||||
String(dropFirst()) |
|
||||||
} |
|
||||||
|
|
||||||
func isValidEmail() -> Bool { |
|
||||||
let emailRegEx = "^[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}$" |
|
||||||
let emailPredicate = NSPredicate(format:"SELF MATCHES %@", emailRegEx) |
|
||||||
return emailPredicate.evaluate(with: self) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
// MARK: - Club Name |
|
||||||
extension String { |
|
||||||
func acronym() -> String { |
|
||||||
let acronym = canonicalVersion.replaceCharactersFromSet(characterSet: .whitespacesAndNewlines) |
|
||||||
if acronym.count > 10 { |
|
||||||
return concatenateFirstLetters().uppercased() |
|
||||||
} else { |
|
||||||
return acronym.uppercased() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func concatenateFirstLetters() -> String { |
|
||||||
// Split the input into sentences |
|
||||||
let sentences = self.components(separatedBy: .whitespacesAndNewlines) |
|
||||||
if sentences.count == 1 { |
|
||||||
return String(self.prefix(10)) |
|
||||||
} |
|
||||||
// Extract the first character of each sentence |
|
||||||
let firstLetters = sentences.compactMap { sentence -> Character? in |
|
||||||
let trimmedSentence = sentence.trimmingCharacters(in: .whitespacesAndNewlines) |
|
||||||
if trimmedSentence.count > 2 { |
|
||||||
if let firstCharacter = trimmedSentence.first { |
|
||||||
return firstCharacter |
|
||||||
} |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// Join the first letters together into a string |
|
||||||
let result = String(firstLetters) |
|
||||||
return String(result.prefix(10)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// MARK: - FFT License |
|
||||||
extension String { |
|
||||||
var computedLicense: String { |
|
||||||
if let licenseKey { |
|
||||||
return self + licenseKey |
|
||||||
} else { |
|
||||||
return self |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
var strippedLicense: String? { |
|
||||||
var dropFirst = 0 |
|
||||||
if hasPrefix("0") { |
|
||||||
dropFirst = 1 |
|
||||||
} |
|
||||||
if let match = self.dropFirst(dropFirst).firstMatch(of: /[0-9]{6,8}/) { |
|
||||||
let lic = String(self.dropFirst(dropFirst)[match.range.lowerBound..<match.range.upperBound]) |
|
||||||
return lic |
|
||||||
} else { |
|
||||||
return nil |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
var isLicenseNumber: Bool { |
|
||||||
if let match = self.firstMatch(of: /[0-9]{6,8}[A-Z]/) { |
|
||||||
let lic = String(self[match.range.lowerBound..<match.range.upperBound].dropLast(1)) |
|
||||||
let lastLetter = String(self[match.range.lowerBound..<match.range.upperBound].suffix(1)) |
|
||||||
|
|
||||||
if let lkey = lic.licenseKey { |
|
||||||
return lkey == lastLetter |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return false |
|
||||||
} |
|
||||||
|
|
||||||
var licenseKey: String? { |
|
||||||
if let intValue = Int(self) { |
|
||||||
var value = intValue |
|
||||||
value -= 1 |
|
||||||
value = value % 23 |
|
||||||
let v = UnicodeScalar("A").value |
|
||||||
let i = Int(v) |
|
||||||
if let s = UnicodeScalar(i + value) { |
|
||||||
var c = Character(s) |
|
||||||
if c >= "I" { |
|
||||||
value += 1 |
|
||||||
if let newS = UnicodeScalar(i + value) { |
|
||||||
c = Character(newS) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if c >= "O" { |
|
||||||
value += 1 |
|
||||||
if let newS = UnicodeScalar(i + value) { |
|
||||||
c = Character(newS) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
if c >= "Q" { |
|
||||||
value += 1 |
|
||||||
if let newS = UnicodeScalar(i + value) { |
|
||||||
c = Character(newS) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return String(c) |
|
||||||
} |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func licencesFound() -> [String] { |
|
||||||
// First try to find licenses with format: 5-8 digits followed by optional letter |
|
||||||
let precisePattern = /[1-9][0-9]{5,7}[ ]?[A-Za-z]?/ |
|
||||||
let preciseMatches = self.matches(of: precisePattern) |
|
||||||
let preciseResults = preciseMatches.map { String(self[$0.range]).trimmingCharacters(in: .whitespaces) } |
|
||||||
|
|
||||||
// If we find potential licenses with the precise pattern |
|
||||||
if !preciseResults.isEmpty { |
|
||||||
// Filter to only include those with trailing letters |
|
||||||
let licensesWithLetters = preciseResults.filter { |
|
||||||
let lastChar = $0.last |
|
||||||
return lastChar != nil && lastChar!.isLetter |
|
||||||
} |
|
||||||
|
|
||||||
print("🎫 Found \(preciseResults.count) potential licenses, filtering to \(licensesWithLetters.count) with trailing letters") |
|
||||||
|
|
||||||
// If we have licenses with letters, validate them |
|
||||||
if !licensesWithLetters.isEmpty { |
|
||||||
let validLicenses = licensesWithLetters.filter { $0.isLicenseNumber } |
|
||||||
|
|
||||||
// If we have valid licenses, return the numeric part of each |
|
||||||
if !validLicenses.isEmpty { |
|
||||||
let numericLicenses = validLicenses.map { license -> String in |
|
||||||
// Extract just the numeric part (all characters except the last letter) |
|
||||||
if let lastChar = license.last, lastChar.isLetter { |
|
||||||
return String(license.dropLast()) |
|
||||||
} |
|
||||||
return license |
|
||||||
} |
|
||||||
|
|
||||||
if numericLicenses.isEmpty == false { |
|
||||||
print("🎫 Found valid licenses: \(validLicenses), returning numeric parts: \(numericLicenses)") |
|
||||||
return numericLicenses |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Fallback to just number pattern if we didn't find good matches |
|
||||||
let numberPattern = /[1-9][0-9]{5,7}/ |
|
||||||
let numberMatches = self.matches(of: numberPattern) |
|
||||||
let numberResults = numberMatches.map { String(self[$0.range]) } |
|
||||||
|
|
||||||
print("🎫 Falling back to number-only pattern, found: \(numberResults)") |
|
||||||
return numberResults |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// MARK: - FFT Source Importing |
|
||||||
extension String { |
|
||||||
enum RegexStatic { |
|
||||||
static let mobileNumber = /^(?:\+33|0033|0)[6-7](?:[ .-]?[0-9]{2}){4}$/ |
|
||||||
static let phoneNumber = /^(?:\+33|0033|0)[1-9](?:[ .-]?[0-9]{2}){4}$/ |
|
||||||
} |
|
||||||
|
|
||||||
func isMobileNumber() -> Bool { |
|
||||||
firstMatch(of: RegexStatic.mobileNumber) != nil |
|
||||||
} |
|
||||||
|
|
||||||
func isPhoneNumber() -> Bool { |
|
||||||
firstMatch(of: RegexStatic.phoneNumber) != nil |
|
||||||
} |
|
||||||
|
|
||||||
func cleanSearchText() -> String { |
|
||||||
// Create a character set of all punctuation except slashes and hyphens |
|
||||||
var punctuationToRemove = CharacterSet.punctuationCharacters |
|
||||||
punctuationToRemove.remove(charactersIn: "/-") |
|
||||||
|
|
||||||
// Remove the unwanted punctuation |
|
||||||
return self.components(separatedBy: punctuationToRemove) |
|
||||||
.joined(separator: " ") |
|
||||||
.trimmingCharacters(in: .whitespacesAndNewlines) |
|
||||||
} |
|
||||||
|
|
||||||
//april 04-2024 bug with accent characters / adobe / fft |
|
||||||
mutating func replace(characters: [(Character, Character)]) { |
|
||||||
for (targetChar, replacementChar) in characters { |
|
||||||
self = String(self.map { $0 == targetChar ? replacementChar : $0 }) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// MARK: - Player Names |
|
||||||
extension StringProtocol { |
|
||||||
var firstUppercased: String { prefix(1).uppercased() + dropFirst() } |
|
||||||
var firstCapitalized: String { prefix(1).capitalized + dropFirst() } |
|
||||||
} |
|
||||||
|
|
||||||
// MARK: - todo clean up ?? |
|
||||||
extension LosslessStringConvertible { |
|
||||||
var string: String { .init(self) } |
|
||||||
} |
|
||||||
|
|
||||||
extension String { |
|
||||||
func createFile(_ withName: String = "temp", _ exportedFormat: ExportFormat = .rawText) -> URL { |
|
||||||
let url = FileManager.default.temporaryDirectory |
|
||||||
.appendingPathComponent(withName) |
|
||||||
.appendingPathExtension(exportedFormat.suffix) |
|
||||||
let string = self |
|
||||||
try? FileManager.default.removeItem(at: url) |
|
||||||
try? string.write(to: url, atomically: true, encoding: .utf8) |
|
||||||
return url |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
extension String { |
|
||||||
func toInt() -> Int? { |
|
||||||
Int(self) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
extension String : @retroactive Identifiable { |
|
||||||
public var id: String { self } |
|
||||||
} |
|
||||||
|
|
||||||
extension String { |
|
||||||
/// Parses the birthdate string into a `Date` based on multiple formats. |
|
||||||
/// - Returns: A `Date` object if parsing is successful, or `nil` if the format is unrecognized. |
|
||||||
func parseAsBirthdate() -> Date? { |
|
||||||
let dateFormats = [ |
|
||||||
"yyyy-MM-dd", // Format for "1993-01-31" |
|
||||||
"dd/MM/yyyy", // Format for "27/07/1992" |
|
||||||
"dd/MM/yy" // Format for "27/07/92" |
|
||||||
] |
|
||||||
|
|
||||||
let dateFormatter = DateFormatter() |
|
||||||
dateFormatter.locale = Locale(identifier: "en_US_POSIX") // Ensure consistent parsing |
|
||||||
|
|
||||||
for format in dateFormats { |
|
||||||
dateFormatter.dateFormat = format |
|
||||||
|
|
||||||
if let date = dateFormatter.date(from: self) { |
|
||||||
return date // Return the parsed date if successful |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return nil // Return nil if no format matches |
|
||||||
} |
|
||||||
|
|
||||||
/// Formats the birthdate string into "DD/MM/YYYY". |
|
||||||
/// - Returns: A formatted birthdate string, or the original string if parsing fails. |
|
||||||
func formattedAsBirthdate() -> String { |
|
||||||
if let parsedDate = self.parseAsBirthdate() { |
|
||||||
let outputFormatter = DateFormatter() |
|
||||||
outputFormatter.dateFormat = "dd/MM/yyyy" // Desired output format |
|
||||||
return outputFormatter.string(from: parsedDate) |
|
||||||
} |
|
||||||
return self // Return the original string if parsing fails |
|
||||||
} |
|
||||||
} |
|
||||||
@ -0,0 +1,67 @@ |
|||||||
|
// |
||||||
|
// TeamRegistration+Extensions.swift |
||||||
|
// PadelClub |
||||||
|
// |
||||||
|
// Created by Laurent Morvillier on 15/04/2025. |
||||||
|
// |
||||||
|
|
||||||
|
import Foundation |
||||||
|
import SwiftUI |
||||||
|
import PadelClubData |
||||||
|
|
||||||
|
extension TeamRegistration { |
||||||
|
|
||||||
|
func initialRoundColor() -> Color? { |
||||||
|
if walkOut { return Color.logoRed } |
||||||
|
if groupStagePosition != nil || wildCardGroupStage { return Color.blue } |
||||||
|
if let initialRound = initialRound(), let colorHex = RoundRule.colors[safe: initialRound.index] { |
||||||
|
return Color(uiColor: .init(fromHex: colorHex)) |
||||||
|
} else if wildCardBracket { |
||||||
|
return Color.mint |
||||||
|
} else { |
||||||
|
return nil |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func updateWeight(inTournamentCategory tournamentCategory: TournamentCategory) { |
||||||
|
self.setWeight(from: self.players(), inTournamentCategory: tournamentCategory) |
||||||
|
} |
||||||
|
|
||||||
|
func updatePlayers( |
||||||
|
_ players: Set<PlayerRegistration>, |
||||||
|
inTournamentCategory tournamentCategory: TournamentCategory |
||||||
|
) { |
||||||
|
let previousPlayers = Set(unsortedPlayers()) |
||||||
|
|
||||||
|
players.forEach { player in |
||||||
|
previousPlayers.forEach { oldPlayer in |
||||||
|
if player.licenceId?.strippedLicense == oldPlayer.licenceId?.strippedLicense, |
||||||
|
player.licenceId?.strippedLicense != nil |
||||||
|
{ |
||||||
|
player.registeredOnline = oldPlayer.registeredOnline |
||||||
|
player.coach = oldPlayer.coach |
||||||
|
player.tournamentPlayed = oldPlayer.tournamentPlayed |
||||||
|
player.points = oldPlayer.points |
||||||
|
player.captain = oldPlayer.captain |
||||||
|
player.assimilation = oldPlayer.assimilation |
||||||
|
player.ligueName = oldPlayer.ligueName |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
let playersToRemove = previousPlayers.subtracting(players) |
||||||
|
self.tournamentStore?.playerRegistrations.delete(contentOfs: Array(playersToRemove)) |
||||||
|
setWeight(from: Array(players), inTournamentCategory: tournamentCategory) |
||||||
|
|
||||||
|
players.forEach { player in |
||||||
|
player.teamRegistration = id |
||||||
|
} |
||||||
|
|
||||||
|
// do { |
||||||
|
// try self.tournamentStore.playerRegistrations.addOrUpdate(contentOfs: players) |
||||||
|
// } catch { |
||||||
|
// Logger.error(error) |
||||||
|
// } |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,445 @@ |
|||||||
|
// |
||||||
|
// Tournament+Extensions.swift |
||||||
|
// PadelClub |
||||||
|
// |
||||||
|
// Created by Laurent Morvillier on 15/04/2025. |
||||||
|
// |
||||||
|
|
||||||
|
import Foundation |
||||||
|
import SwiftUI |
||||||
|
import PadelClubData |
||||||
|
import LeStorage |
||||||
|
|
||||||
|
extension Tournament { |
||||||
|
|
||||||
|
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 |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func customizeUsingPreferences() { |
||||||
|
guard let lastTournamentWithSameBuild = DataStore.shared.tournaments.filter({ tournament in |
||||||
|
tournament.tournamentLevel == self.tournamentLevel |
||||||
|
&& tournament.tournamentCategory == self.tournamentCategory |
||||||
|
&& tournament.federalTournamentAge == self.federalTournamentAge |
||||||
|
&& tournament.hasEnded() == true |
||||||
|
&& tournament.isCanceled == false |
||||||
|
&& tournament.isDeleted == false |
||||||
|
}).sorted(by: \.endDate!, order: .descending).first else { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
self.dayDuration = lastTournamentWithSameBuild.dayDuration |
||||||
|
self.teamCount = (lastTournamentWithSameBuild.teamCount / 2) * 2 |
||||||
|
self.enableOnlineRegistration = lastTournamentWithSameBuild.enableOnlineRegistration |
||||||
|
} |
||||||
|
|
||||||
|
func addTeam(_ players: Set<PlayerRegistration>, registrationDate: Date? = nil, name: String? = nil) -> TeamRegistration { |
||||||
|
let team = TeamRegistration(tournament: id, registrationDate: registrationDate, name: name) |
||||||
|
team.setWeight(from: Array(players), inTournamentCategory: tournamentCategory) |
||||||
|
players.forEach { player in |
||||||
|
player.teamRegistration = team.id |
||||||
|
} |
||||||
|
if isAnimation() { |
||||||
|
if team.weight == 0 { |
||||||
|
team.weight = unsortedTeams().count |
||||||
|
} |
||||||
|
} |
||||||
|
return team |
||||||
|
} |
||||||
|
|
||||||
|
func addWildCardIfNeeded(_ count: Int, _ type: MatchType) { |
||||||
|
let currentCount = selectedSortedTeams().filter({ |
||||||
|
if type == .bracket { |
||||||
|
return $0.wildCardBracket |
||||||
|
} else { |
||||||
|
return $0.wildCardGroupStage |
||||||
|
} |
||||||
|
}).count |
||||||
|
|
||||||
|
if currentCount < count { |
||||||
|
let _diff = count - currentCount |
||||||
|
addWildCard(_diff, type) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
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) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func addWildCard(_ count: Int, _ type: MatchType) { |
||||||
|
let wcs = (0..<count).map { _ in |
||||||
|
let team = TeamRegistration(tournament: id) |
||||||
|
if type == .bracket { |
||||||
|
team.wildCardBracket = true |
||||||
|
} else { |
||||||
|
team.wildCardGroupStage = true |
||||||
|
} |
||||||
|
|
||||||
|
team.setWeight(from: [], inTournamentCategory: self.tournamentCategory) |
||||||
|
team.weight += 200_000 |
||||||
|
return team |
||||||
|
} |
||||||
|
|
||||||
|
do { |
||||||
|
try self.tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: wcs) |
||||||
|
} catch { |
||||||
|
Logger.error(error) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func teamsRanked() -> [TeamRegistration] { |
||||||
|
let selected = selectedSortedTeams().filter({ $0.finalRanking != nil }) |
||||||
|
return selected.sorted(by: \.finalRanking!, order: .ascending) |
||||||
|
} |
||||||
|
|
||||||
|
func playersWithoutValidLicense(in players: [PlayerRegistration], isImported: Bool) -> [PlayerRegistration] { |
||||||
|
let licenseYearValidity = self.licenseYearValidity() |
||||||
|
return players.filter({ player in |
||||||
|
if player.isImported() { |
||||||
|
// Player is marked as imported: check if the license is valid |
||||||
|
return !player.isValidLicenseNumber(year: licenseYearValidity) |
||||||
|
} else { |
||||||
|
// Player is not imported: validate license and handle `isImported` flag for non-imported players |
||||||
|
let noLicenseId = player.licenceId == nil || player.licenceId?.isEmpty == true |
||||||
|
let invalidFormattedLicense = player.formattedLicense().isLicenseNumber == false |
||||||
|
|
||||||
|
// If global `isImported` is true, check license number as well |
||||||
|
let invalidLicenseForImportedFlag = isImported && !player.isValidLicenseNumber(year: licenseYearValidity) |
||||||
|
|
||||||
|
return noLicenseId || invalidFormattedLicense || invalidLicenseForImportedFlag |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
func homonyms(in players: [PlayerRegistration]) -> [PlayerRegistration] { |
||||||
|
players.filter({ $0.hasHomonym() }) |
||||||
|
} |
||||||
|
|
||||||
|
func payIfNecessary() throws { |
||||||
|
if self.payment != nil { return } |
||||||
|
if let payment = Guard.main.paymentForNewTournament() { |
||||||
|
self.payment = payment |
||||||
|
DataStore.shared.tournaments.addOrUpdate(instance: self) |
||||||
|
return |
||||||
|
} |
||||||
|
throw PaymentError.cantPayTournament |
||||||
|
} |
||||||
|
|
||||||
|
func cutLabelColor(index: Int?, teamCount: Int?) -> Color { |
||||||
|
guard let index else { return Color.grayNotUniversal } |
||||||
|
let _teamCount = teamCount ?? selectedSortedTeams().count |
||||||
|
let groupStageCut = groupStageCut() |
||||||
|
let bracketCut = bracketCut(teamCount: _teamCount, groupStageCut: groupStageCut) |
||||||
|
if index < bracketCut { |
||||||
|
return Color.mint |
||||||
|
} else if index - bracketCut < groupStageCut && _teamCount > 0 { |
||||||
|
return Color.indigo |
||||||
|
} else { |
||||||
|
return Color.grayNotUniversal |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func isPlayerAgeInadequate(player: PlayerHolder) -> Bool { |
||||||
|
guard let computedAge = player.computedAge else { return false } |
||||||
|
if federalTournamentAge.isAgeValid(age: computedAge) == false { |
||||||
|
return true |
||||||
|
} else { |
||||||
|
return false |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func isPlayerRankInadequate(player: PlayerHolder) -> Bool { |
||||||
|
guard let rank = player.getRank() else { return false } |
||||||
|
let _rank = player.male ? rank : rank + PlayerRegistration.addon(for: rank, manMax: maleUnrankedValue ?? 0, womanMax: femaleUnrankedValue ?? 0) |
||||||
|
if _rank <= tournamentLevel.minimumPlayerRank(category: tournamentCategory, ageCategory: federalTournamentAge) { |
||||||
|
return true |
||||||
|
} else { |
||||||
|
return false |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func inadequatePlayers(in players: [PlayerRegistration]) -> [PlayerRegistration] { |
||||||
|
if startDate.isInCurrentYear() == false { |
||||||
|
return [] |
||||||
|
} |
||||||
|
return players.filter { player in |
||||||
|
return isPlayerRankInadequate(player: player) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func ageInadequatePlayers(in players: [PlayerRegistration]) -> [PlayerRegistration] { |
||||||
|
if startDate.isInCurrentYear() == false { |
||||||
|
return [] |
||||||
|
} |
||||||
|
return players.filter { player in |
||||||
|
return isPlayerAgeInadequate(player: player) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func importTeams(_ teams: [FileImportManager.TeamHolder]) { |
||||||
|
var teamsToImport = [TeamRegistration]() |
||||||
|
let players = players().filter { $0.licenceId != nil } |
||||||
|
teams.forEach { team in |
||||||
|
if let previousTeam = team.previousTeam { |
||||||
|
previousTeam.updatePlayers(team.players, inTournamentCategory: team.tournamentCategory) |
||||||
|
teamsToImport.append(previousTeam) |
||||||
|
} else { |
||||||
|
var registrationDate = team.registrationDate |
||||||
|
if let previousPlayer = players.first(where: { player in |
||||||
|
let ids = team.players.compactMap({ $0.licenceId }) |
||||||
|
return ids.contains(player.licenceId!) |
||||||
|
}), let previousTeamRegistrationDate = previousPlayer.team()?.registrationDate { |
||||||
|
registrationDate = previousTeamRegistrationDate |
||||||
|
} |
||||||
|
let newTeam = addTeam(team.players, registrationDate: registrationDate, name: team.name) |
||||||
|
if isAnimation() { |
||||||
|
if newTeam.weight == 0 { |
||||||
|
newTeam.weight = team.index(in: teams) ?? 0 |
||||||
|
} |
||||||
|
} |
||||||
|
teamsToImport.append(newTeam) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if let tournamentStore = self.tournamentStore { |
||||||
|
tournamentStore.teamRegistrations.addOrUpdate(contentOfs: teamsToImport) |
||||||
|
tournamentStore.playerRegistrations.addOrUpdate(contentOfs: teams.flatMap { $0.players }) |
||||||
|
} |
||||||
|
|
||||||
|
if state() == .build && groupStageCount > 0 && groupStageTeams().isEmpty { |
||||||
|
setGroupStage(randomize: groupStageSortMode == .random) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func registrationIssues(selectedTeams: [TeamRegistration]) async -> Int { |
||||||
|
let players : [PlayerRegistration] = unsortedPlayers() |
||||||
|
let callDateIssue : [TeamRegistration] = selectedTeams.filter { $0.callDate != nil && isStartDateIsDifferentThanCallDate($0) } |
||||||
|
let duplicates : [PlayerRegistration] = duplicates(in: players) |
||||||
|
let problematicPlayers : [PlayerRegistration] = players.filter({ $0.sex == nil }) |
||||||
|
let inadequatePlayers : [PlayerRegistration] = inadequatePlayers(in: players) |
||||||
|
let homonyms = homonyms(in: players) |
||||||
|
let ageInadequatePlayers = ageInadequatePlayers(in: players) |
||||||
|
let isImported = players.anySatisfy({ $0.isImported() }) |
||||||
|
let playersWithoutValidLicense : [PlayerRegistration] = playersWithoutValidLicense(in: players, isImported: isImported) |
||||||
|
let playersMissing : [TeamRegistration] = selectedTeams.filter({ $0.unsortedPlayers().count < 2 }) |
||||||
|
let waitingList : [TeamRegistration] = waitingListTeams(in: selectedTeams, includingWalkOuts: true) |
||||||
|
let waitingListInBracket = waitingList.filter({ $0.bracketPosition != nil }) |
||||||
|
let waitingListInGroupStage = waitingList.filter({ $0.groupStage != nil }) |
||||||
|
|
||||||
|
return callDateIssue.count + duplicates.count + problematicPlayers.count + inadequatePlayers.count + playersWithoutValidLicense.count + playersMissing.count + waitingListInBracket.count + waitingListInGroupStage.count + ageInadequatePlayers.count + homonyms.count |
||||||
|
} |
||||||
|
|
||||||
|
func updateRank(to newDate: Date?, forceRefreshLockWeight: Bool, providedSources: [CSVParser]?) async throws { |
||||||
|
refreshRanking = true |
||||||
|
#if DEBUG_TIME |
||||||
|
let start = Date() |
||||||
|
defer { |
||||||
|
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) |
||||||
|
print("func updateRank()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) |
||||||
|
} |
||||||
|
#endif |
||||||
|
|
||||||
|
guard let newDate else { return } |
||||||
|
rankSourceDate = newDate |
||||||
|
|
||||||
|
// Fetch current month data only once |
||||||
|
var monthData = currentMonthData() |
||||||
|
|
||||||
|
if monthData == nil { |
||||||
|
async let lastRankWoman = SourceFileManager.shared.getUnrankValue(forMale: false, rankSourceDate: rankSourceDate) |
||||||
|
async let lastRankMan = SourceFileManager.shared.getUnrankValue(forMale: true, rankSourceDate: rankSourceDate) |
||||||
|
|
||||||
|
let formatted = URL.importDateFormatter.string(from: newDate) |
||||||
|
let newMonthData = MonthData(monthKey: formatted) |
||||||
|
|
||||||
|
newMonthData.maleUnrankedValue = await lastRankMan |
||||||
|
newMonthData.femaleUnrankedValue = await lastRankWoman |
||||||
|
|
||||||
|
do { |
||||||
|
try DataStore.shared.monthData.addOrUpdate(instance: newMonthData) |
||||||
|
} catch { |
||||||
|
Logger.error(error) |
||||||
|
} |
||||||
|
|
||||||
|
monthData = newMonthData |
||||||
|
} |
||||||
|
|
||||||
|
let lastRankMan = monthData?.maleUnrankedValue |
||||||
|
let lastRankWoman = monthData?.femaleUnrankedValue |
||||||
|
|
||||||
|
var chunkedParsers: [CSVParser] = [] |
||||||
|
if let providedSources { |
||||||
|
chunkedParsers = providedSources |
||||||
|
} else { |
||||||
|
// Fetch only the required files |
||||||
|
let dataURLs = SourceFileManager.shared.allFiles.filter { $0.dateFromPath == newDate } |
||||||
|
guard !dataURLs.isEmpty else { return } // Early return if no files found |
||||||
|
|
||||||
|
let sources = dataURLs.map { CSVParser(url: $0) } |
||||||
|
chunkedParsers = try await chunkAllSources(sources: sources, size: 10000) |
||||||
|
} |
||||||
|
|
||||||
|
let players = unsortedPlayers() |
||||||
|
try await players.concurrentForEach { player in |
||||||
|
let lastRank = (player.sex == .female) ? lastRankWoman : lastRankMan |
||||||
|
try await player.updateRank(from: chunkedParsers, lastRank: lastRank) |
||||||
|
player.setComputedRank(in: self) |
||||||
|
} |
||||||
|
|
||||||
|
if providedSources == nil { |
||||||
|
try chunkedParsers.forEach { chunk in |
||||||
|
try FileManager.default.removeItem(at: chunk.url) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
try tournamentStore?.playerRegistrations.addOrUpdate(contentOfs: players) |
||||||
|
|
||||||
|
let unsortedTeams = unsortedTeams() |
||||||
|
unsortedTeams.forEach { team in |
||||||
|
team.setWeight(from: team.players(), inTournamentCategory: tournamentCategory) |
||||||
|
if forceRefreshLockWeight { |
||||||
|
team.lockedWeight = team.weight |
||||||
|
} |
||||||
|
} |
||||||
|
try tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: unsortedTeams) |
||||||
|
refreshRanking = false |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
extension Tournament { |
||||||
|
static func newEmptyInstance() -> Tournament { |
||||||
|
let lastDataSource: String? = DataStore.shared.appSettings.lastDataSource |
||||||
|
var _mostRecentDateAvailable: Date? { |
||||||
|
guard let lastDataSource else { return nil } |
||||||
|
return URL.importDateFormatter.date(from: lastDataSource) |
||||||
|
} |
||||||
|
|
||||||
|
let rankSourceDate = _mostRecentDateAvailable |
||||||
|
return Tournament(rankSourceDate: rankSourceDate) |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
extension Tournament: FederalTournamentHolder { |
||||||
|
|
||||||
|
func tournamentTitle(_ displayStyle: DisplayStyle, forBuild build: any TournamentBuildHolder) -> String { |
||||||
|
if isAnimation() { |
||||||
|
if let name { |
||||||
|
return name.trunc(length: DeviceHelper.charLength()) |
||||||
|
} else if build.age == .unlisted, build.category == .unlisted { |
||||||
|
return build.level.localizedLevelLabel(.title) |
||||||
|
} else { |
||||||
|
return build.level.localizedLevelLabel(displayStyle) |
||||||
|
} |
||||||
|
} |
||||||
|
return build.level.localizedLevelLabel(displayStyle) |
||||||
|
} |
||||||
|
|
||||||
|
var codeClub: String? { |
||||||
|
club()?.code |
||||||
|
} |
||||||
|
|
||||||
|
var holderId: String { id } |
||||||
|
|
||||||
|
func clubLabel() -> String { |
||||||
|
locationLabel() |
||||||
|
} |
||||||
|
|
||||||
|
func subtitleLabel(forBuild build: any TournamentBuildHolder) -> String { |
||||||
|
if isAnimation() { |
||||||
|
if displayAgeAndCategory(forBuild: build) == false { |
||||||
|
return [build.category.localizedCategoryLabel(ageCategory: build.age), build.age.localizedFederalAgeLabel()].filter({ $0.isEmpty == false }).joined(separator: " ") |
||||||
|
} else if name != nil { |
||||||
|
return build.level.localizedLevelLabel(.title) |
||||||
|
} else { |
||||||
|
return "" |
||||||
|
} |
||||||
|
} else { |
||||||
|
return subtitle() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
var tournaments: [any TournamentBuildHolder] { |
||||||
|
[ |
||||||
|
self |
||||||
|
] |
||||||
|
} |
||||||
|
|
||||||
|
var dayPeriod: DayPeriod { |
||||||
|
let day = startDate.get(.weekday) |
||||||
|
switch day { |
||||||
|
case 2...6: |
||||||
|
return .week |
||||||
|
default: |
||||||
|
return .weekend |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func displayAgeAndCategory(forBuild build: any TournamentBuildHolder) -> Bool { |
||||||
|
if isAnimation() { |
||||||
|
if let name, name.count < DeviceHelper.maxCharacter() { |
||||||
|
return true |
||||||
|
} else if build.age == .unlisted, build.category == .unlisted { |
||||||
|
return true |
||||||
|
} else { |
||||||
|
return DeviceHelper.isBigScreen() |
||||||
|
} |
||||||
|
} |
||||||
|
return true |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
extension Tournament: TournamentBuildHolder { |
||||||
|
public func buildHolderTitle(_ displayStyle: DisplayStyle) -> String { |
||||||
|
tournamentTitle(.short) |
||||||
|
} |
||||||
|
|
||||||
|
public var category: TournamentCategory { |
||||||
|
tournamentCategory |
||||||
|
} |
||||||
|
|
||||||
|
public var level: TournamentLevel { |
||||||
|
tournamentLevel |
||||||
|
} |
||||||
|
|
||||||
|
public var age: FederalTournamentAge { |
||||||
|
federalTournamentAge |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
//extension Tournament { |
||||||
|
// 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 |
||||||
|
// } |
||||||
|
//} |
||||||
@ -1,182 +0,0 @@ |
|||||||
// |
|
||||||
// URL+Extensions.swift |
|
||||||
// PadelClub |
|
||||||
// |
|
||||||
// Created by Razmig Sarkissian on 01/03/2024. |
|
||||||
// |
|
||||||
|
|
||||||
import Foundation |
|
||||||
|
|
||||||
extension URL { |
|
||||||
|
|
||||||
static var savedDateFormatter: DateFormatter = { |
|
||||||
let df = DateFormatter() |
|
||||||
df.dateFormat = "DD/MM/yyyy" |
|
||||||
return df |
|
||||||
}() |
|
||||||
|
|
||||||
static var importDateFormatter: DateFormatter = { |
|
||||||
let df = DateFormatter() |
|
||||||
df.dateFormat = "MM-yyyy" |
|
||||||
return df |
|
||||||
}() |
|
||||||
|
|
||||||
var dateFromPath: Date { |
|
||||||
let found = deletingPathExtension().path().components(separatedBy: "-").suffix(2).joined(separator: "-") |
|
||||||
if let date = URL.importDateFormatter.date(from: found) { |
|
||||||
return date |
|
||||||
} else { |
|
||||||
return Date() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
var index: Int { |
|
||||||
if let i = path().dropLast(12).last?.wholeNumberValue { |
|
||||||
return i |
|
||||||
} |
|
||||||
return 0 |
|
||||||
} |
|
||||||
|
|
||||||
var manData: Bool { |
|
||||||
path().contains("MESSIEURS") |
|
||||||
} |
|
||||||
|
|
||||||
var womanData: Bool { |
|
||||||
path().contains("DAMES") |
|
||||||
} |
|
||||||
|
|
||||||
static var seed: URL? { |
|
||||||
Bundle.main.url(forResource: "SeedData", withExtension: nil) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
extension URL { |
|
||||||
func creationDate() -> Date? { |
|
||||||
// Use FileManager to retrieve the file attributes |
|
||||||
do { |
|
||||||
let fileAttributes = try FileManager.default.attributesOfItem(atPath: self.path()) |
|
||||||
|
|
||||||
// Access the creationDate from the file attributes |
|
||||||
if let creationDate = fileAttributes[.creationDate] as? Date { |
|
||||||
print("File creationDate: \(creationDate)") |
|
||||||
return creationDate |
|
||||||
} else { |
|
||||||
print("creationDate not found.") |
|
||||||
} |
|
||||||
} catch { |
|
||||||
print("Error retrieving file attributes: \(error.localizedDescription)") |
|
||||||
} |
|
||||||
|
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func fftImportingStatus() -> Int? { |
|
||||||
// Read the contents of the file |
|
||||||
guard let fileContents = try? String(contentsOfFile: path(), encoding: .utf8) else { |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// Split the contents by newline characters |
|
||||||
let lines = fileContents.components(separatedBy: .newlines) |
|
||||||
//0 means no need to reimport, just recalc |
|
||||||
//1 or missing means re-import |
|
||||||
if let line = lines.first(where: { |
|
||||||
$0.hasPrefix("import-status:") |
|
||||||
}) { |
|
||||||
return Int(line.replacingOccurrences(of: "import-status:", with: "")) |
|
||||||
} |
|
||||||
|
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func fftImportingMaleUnrankValue() -> Int? { |
|
||||||
// Read the contents of the file |
|
||||||
guard let fileContents = try? String(contentsOfFile: path(), encoding: .utf8) else { |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// Split the contents by newline characters |
|
||||||
let lines = fileContents.components(separatedBy: .newlines) |
|
||||||
|
|
||||||
if let line = lines.first(where: { |
|
||||||
$0.hasPrefix("unrank-male-value:") |
|
||||||
}) { |
|
||||||
return Int(line.replacingOccurrences(of: "unrank-male-value:", with: "")) |
|
||||||
} |
|
||||||
|
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func fileModelIdentifier() -> String? { |
|
||||||
// Read the contents of the file |
|
||||||
guard let fileContents = try? String(contentsOfFile: path(), encoding: .utf8) else { |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// Split the contents by newline characters |
|
||||||
let lines = fileContents.components(separatedBy: .newlines) |
|
||||||
|
|
||||||
if let line = lines.first(where: { |
|
||||||
$0.hasPrefix("file-model-version:") |
|
||||||
}) { |
|
||||||
return line.replacingOccurrences(of: "file-model-version:", with: "") |
|
||||||
} |
|
||||||
|
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func fftImportingUncomplete() -> Int? { |
|
||||||
// Read the contents of the file |
|
||||||
guard let fileContents = try? String(contentsOfFile: path(), encoding: .utf8) else { |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// Split the contents by newline characters |
|
||||||
let lines = fileContents.components(separatedBy: .newlines) |
|
||||||
|
|
||||||
if let line = lines.first(where: { |
|
||||||
$0.hasPrefix("max-players:") |
|
||||||
}) { |
|
||||||
return Int(line.replacingOccurrences(of: "max-players:", with: "")) |
|
||||||
} |
|
||||||
|
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func getUnrankedValue() -> Int? { |
|
||||||
// Read the contents of the file |
|
||||||
guard let fileContents = try? String(contentsOfFile: path(), encoding: .utf8) else { |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// Split the contents by newline characters |
|
||||||
let lines = fileContents.components(separatedBy: .newlines) |
|
||||||
|
|
||||||
// Get the last non-empty line |
|
||||||
var lastLine: String? |
|
||||||
for line in lines.reversed() { |
|
||||||
let trimmedLine = line.trimmingCharacters(in: .whitespacesAndNewlines) |
|
||||||
if !trimmedLine.isEmpty { |
|
||||||
lastLine = trimmedLine |
|
||||||
break |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
guard let rankString = lastLine?.components(separatedBy: ";").dropFirst().first, let rank = Int(rankString) else { |
|
||||||
return nil |
|
||||||
} |
|
||||||
// Define the regular expression pattern |
|
||||||
let pattern = "\\b\(NSRegularExpression.escapedPattern(for: rankString))\\b" |
|
||||||
|
|
||||||
// Create the regular expression object |
|
||||||
guard let regex = try? NSRegularExpression(pattern: pattern) else { |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// Get the matches |
|
||||||
let matches = regex.matches(in: fileContents, range: NSRange(fileContents.startIndex..., in: fileContents)) |
|
||||||
|
|
||||||
// Return the count of matches |
|
||||||
return matches.count + rank - 1 |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,254 +0,0 @@ |
|||||||
// |
|
||||||
// ContactManager.swift |
|
||||||
// Padel Tournament |
|
||||||
// |
|
||||||
// Created by Razmig Sarkissian on 19/09/2023. |
|
||||||
// |
|
||||||
|
|
||||||
import Foundation |
|
||||||
import SwiftUI |
|
||||||
import MessageUI |
|
||||||
import LeStorage |
|
||||||
|
|
||||||
enum ContactManagerError: LocalizedError { |
|
||||||
case mailFailed |
|
||||||
case mailNotSent //no network no error |
|
||||||
case messageFailed |
|
||||||
case messageNotSent //no network no error |
|
||||||
case calendarAccessDenied |
|
||||||
case calendarEventSaveFailed |
|
||||||
case noCalendarAvailable |
|
||||||
case uncalledTeams([TeamRegistration]) |
|
||||||
|
|
||||||
var localizedDescription: String { |
|
||||||
switch self { |
|
||||||
case .mailFailed: |
|
||||||
return "Le mail n'a pas été envoyé" |
|
||||||
case .mailNotSent: |
|
||||||
return "Le mail est dans la boîte d'envoi de l'app Mail. Vérifiez son état dans l'app Mail avant d'essayer de le renvoyer." |
|
||||||
case .messageFailed: |
|
||||||
return "Le SMS n'a pas été envoyé" |
|
||||||
case .messageNotSent: |
|
||||||
return "Le SMS n'a pas été envoyé" |
|
||||||
case .uncalledTeams(let array): |
|
||||||
let verb = array.count > 1 ? "peuvent" : "peut" |
|
||||||
return "Attention, \(array.count) équipe\(array.count.pluralSuffix) ne \(verb) pas être contacté par la méthode choisie" |
|
||||||
case .calendarAccessDenied: |
|
||||||
return "Padel Club n'a pas accès à votre calendrier" |
|
||||||
case .calendarEventSaveFailed: |
|
||||||
return "Padel Club n'a pas réussi à sauver ce tournoi dans votre calendrier" |
|
||||||
case .noCalendarAvailable: |
|
||||||
return "Padel Club n'a pas réussi à trouver un calendrier pour y inscrire ce tournoi" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
static func getNetworkErrorMessage(sentError: ContactManagerError?, networkMonitorConnected: Bool) -> String { |
|
||||||
var errors: [String] = [] |
|
||||||
|
|
||||||
if networkMonitorConnected == false { |
|
||||||
errors.append("L'appareil n'est pas connecté à internet.") |
|
||||||
} |
|
||||||
if let sentError { |
|
||||||
errors.append(sentError.localizedDescription) |
|
||||||
} |
|
||||||
return errors.joined(separator: "\n") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
enum ContactType: Identifiable { |
|
||||||
case mail(date: Date?, recipients: [String]?, bccRecipients: [String]?, body: String?, subject: String?, tournamentBuild: TournamentBuild?) |
|
||||||
case message(date: Date?, recipients: [String]?, body: String?, tournamentBuild: TournamentBuild?) |
|
||||||
|
|
||||||
var id: Int { |
|
||||||
switch self { |
|
||||||
case .message: return 0 |
|
||||||
case .mail: return 1 |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
extension ContactType { |
|
||||||
static let defaultCustomMessage: String = |
|
||||||
""" |
|
||||||
Il est conseillé de vous présenter 10 minutes avant de jouer.\n\nMerci de me confirmer votre présence avec votre nom et de prévenir votre partenaire. |
|
||||||
""" |
|
||||||
static let defaultAvailablePaymentMethods: String = "Règlement possible par chèque ou espèces." |
|
||||||
|
|
||||||
static func callingCustomMessage(source: String? = nil, tournament: Tournament?, startDate: Date?, roundLabel: String) -> String { |
|
||||||
let tournamentCustomMessage = source ?? DataStore.shared.user.summonsMessageBody ?? defaultCustomMessage |
|
||||||
let clubName = tournament?.clubName ?? "" |
|
||||||
|
|
||||||
var text = tournamentCustomMessage |
|
||||||
let date = startDate ?? tournament?.startDate ?? Date() |
|
||||||
|
|
||||||
if let tournament { |
|
||||||
text = text.replacingOccurrences(of: "#titre", with: tournament.tournamentTitle(.title, hideSenior: true)) |
|
||||||
text = text.replacingOccurrences(of: "#prix", with: tournament.entryFeeMessage) |
|
||||||
} |
|
||||||
|
|
||||||
text = text.replacingOccurrences(of: "#club", with: clubName) |
|
||||||
text = text.replacingOccurrences(of: "#manche", with: roundLabel.lowercased()) |
|
||||||
text = text.replacingOccurrences(of: "#jour", with: "\(date.formatted(Date.FormatStyle().weekday(.wide).day().month(.wide)))") |
|
||||||
text = text.replacingOccurrences(of: "#horaire", with: "\(date.formatted(Date.FormatStyle().hour().minute()))") |
|
||||||
|
|
||||||
let signature = DataStore.shared.user.getSummonsMessageSignature() ?? DataStore.shared.user.defaultSignature(tournament) |
|
||||||
|
|
||||||
text = text.replacingOccurrences(of: "#signature", with: signature) |
|
||||||
return text |
|
||||||
} |
|
||||||
|
|
||||||
static func callingMessage(tournament: Tournament?, startDate: Date?, roundLabel: String, matchFormat: MatchFormat?, reSummon: Bool = false) -> String { |
|
||||||
|
|
||||||
let useFullCustomMessage = DataStore.shared.user.summonsUseFullCustomMessage |
|
||||||
|
|
||||||
if useFullCustomMessage { |
|
||||||
return callingCustomMessage(tournament: tournament, startDate: startDate, roundLabel: roundLabel) |
|
||||||
} |
|
||||||
|
|
||||||
let date = startDate ?? tournament?.startDate ?? Date() |
|
||||||
|
|
||||||
let clubName = tournament?.clubName ?? "" |
|
||||||
let message = DataStore.shared.user.summonsMessageBody ?? defaultCustomMessage |
|
||||||
let signature = DataStore.shared.user.getSummonsMessageSignature() ?? DataStore.shared.user.defaultSignature(tournament) |
|
||||||
|
|
||||||
let localizedCalled = "convoqué" + (tournament?.tournamentCategory == .women ? "e" : "") + "s" |
|
||||||
|
|
||||||
var entryFeeMessage: String? { |
|
||||||
(DataStore.shared.user.summonsDisplayEntryFee) ? tournament?.entryFeeMessage : nil |
|
||||||
} |
|
||||||
|
|
||||||
var linkMessage: String? { |
|
||||||
if let tournament, tournament.isPrivate == false, let shareLink = tournament.shareURL(.matches)?.absoluteString { |
|
||||||
return "Vous pourrez suivre tous les résultats de ce tournoi sur le site :\n\n".appending(shareLink) |
|
||||||
} else { |
|
||||||
return nil |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
var computedMessage: String { |
|
||||||
[entryFeeMessage, message, linkMessage].compacted().map { $0.trimmedMultiline }.joined(separator: "\n\n") |
|
||||||
} |
|
||||||
|
|
||||||
let intro = reSummon ? "Suite à des forfaits, vous êtes finalement" : "Vous êtes" |
|
||||||
|
|
||||||
if let tournament { |
|
||||||
return "Bonjour,\n\n\(intro) \(localizedCalled) pour jouer en \(roundLabel.lowercased()) du \(tournament.tournamentTitle(.title, hideSenior: true)) au \(clubName) le \(date.formatted(Date.FormatStyle().weekday(.wide).day().month(.wide))) à \(date.formatted(Date.FormatStyle().hour().minute())).\n\n" + computedMessage + "\n\n\(signature)" |
|
||||||
} else { |
|
||||||
return "Bonjour,\n\n\(intro) \(localizedCalled) \(roundLabel) au \(clubName) le \(date.formatted(Date.FormatStyle().weekday(.wide).day().month(.wide))) à \(date.formatted(Date.FormatStyle().hour().minute())).\n\nMerci de confirmer en répondant à ce message et de prévenir votre partenaire !\n\n\(signature)" |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
struct MessageComposeView: UIViewControllerRepresentable { |
|
||||||
typealias Completion = (_ result: MessageComposeResult) -> Void |
|
||||||
|
|
||||||
static var canSendText: Bool { MFMessageComposeViewController.canSendText() } |
|
||||||
|
|
||||||
let recipients: [String]? |
|
||||||
let body: String? |
|
||||||
let completion: Completion? |
|
||||||
|
|
||||||
func makeUIViewController(context: Context) -> UIViewController { |
|
||||||
guard Self.canSendText else { |
|
||||||
let errorView = ContentUnavailableView("Aucun compte de messagerie", systemImage: "xmark", description: Text("Aucun compte de messagerie n'est configuré sur cet appareil.")) |
|
||||||
return UIHostingController(rootView: errorView) |
|
||||||
} |
|
||||||
|
|
||||||
let controller = MFMessageComposeViewController() |
|
||||||
controller.messageComposeDelegate = context.coordinator |
|
||||||
controller.recipients = recipients |
|
||||||
controller.body = body |
|
||||||
|
|
||||||
return controller |
|
||||||
} |
|
||||||
|
|
||||||
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} |
|
||||||
|
|
||||||
func makeCoordinator() -> Coordinator { |
|
||||||
Coordinator(completion: self.completion) |
|
||||||
} |
|
||||||
|
|
||||||
class Coordinator: NSObject, MFMessageComposeViewControllerDelegate { |
|
||||||
private let completion: Completion? |
|
||||||
|
|
||||||
public init(completion: Completion?) { |
|
||||||
self.completion = completion |
|
||||||
} |
|
||||||
|
|
||||||
public func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) { |
|
||||||
controller.dismiss(animated: true, completion: { |
|
||||||
self.completion?(result) |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
struct MailComposeView: UIViewControllerRepresentable { |
|
||||||
typealias Completion = (_ result: MFMailComposeResult) -> Void |
|
||||||
|
|
||||||
static var canSendMail: Bool { |
|
||||||
if let mailURL = URL(string: "mailto:?to=jap@padelclub.com") { |
|
||||||
let mailConfigured = UIApplication.shared.canOpenURL(mailURL) |
|
||||||
return mailConfigured && MFMailComposeViewController.canSendMail() |
|
||||||
} else { |
|
||||||
return MFMailComposeViewController.canSendMail() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
let recipients: [String]? |
|
||||||
let bccRecipients: [String]? |
|
||||||
let body: String? |
|
||||||
let subject: String? |
|
||||||
var attachmentURL: URL? |
|
||||||
let completion: Completion? |
|
||||||
|
|
||||||
func makeUIViewController(context: Context) -> UIViewController { |
|
||||||
guard Self.canSendMail else { |
|
||||||
let errorView = ContentUnavailableView("Aucun compte mail", systemImage: "xmark", description: Text("Aucun compte mail n'est configuré sur cet appareil.")) |
|
||||||
return UIHostingController(rootView: errorView) |
|
||||||
} |
|
||||||
|
|
||||||
let controller = MFMailComposeViewController() |
|
||||||
controller.mailComposeDelegate = context.coordinator |
|
||||||
controller.setToRecipients(recipients) |
|
||||||
controller.setBccRecipients(bccRecipients) |
|
||||||
if let attachmentURL { |
|
||||||
do { |
|
||||||
let attachmentData = try Data(contentsOf: attachmentURL) |
|
||||||
controller.addAttachmentData(attachmentData, mimeType: "application/zip", fileName: "backup.zip") |
|
||||||
} catch { |
|
||||||
print("Could not attach file: \(error)") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if let body { |
|
||||||
controller.setMessageBody(body, isHTML: false) |
|
||||||
} |
|
||||||
if let subject { |
|
||||||
controller.setSubject(subject) |
|
||||||
} |
|
||||||
|
|
||||||
return controller |
|
||||||
} |
|
||||||
|
|
||||||
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} |
|
||||||
|
|
||||||
func makeCoordinator() -> Coordinator { |
|
||||||
Coordinator(completion: self.completion) |
|
||||||
} |
|
||||||
|
|
||||||
class Coordinator: NSObject, MFMailComposeViewControllerDelegate { |
|
||||||
private let completion: Completion? |
|
||||||
|
|
||||||
public init(completion: Completion?) { |
|
||||||
self.completion = completion |
|
||||||
} |
|
||||||
|
|
||||||
public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) { |
|
||||||
controller.dismiss(animated: true, completion: { |
|
||||||
self.completion?(result) |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,12 +0,0 @@ |
|||||||
// |
|
||||||
// Key.swift |
|
||||||
// PadelClub |
|
||||||
// |
|
||||||
// Created by Laurent Morvillier on 30/04/2024. |
|
||||||
// |
|
||||||
|
|
||||||
import Foundation |
|
||||||
|
|
||||||
enum CryptoKey: String { |
|
||||||
case pass = "Aa9QDV1G5MP9ijF2FTFasibNbS/Zun4qXrubIL2P+Ik=" |
|
||||||
} |
|
||||||
@ -1,64 +0,0 @@ |
|||||||
// |
|
||||||
// DisplayContext.swift |
|
||||||
// PadelClub |
|
||||||
// |
|
||||||
// Created by Razmig Sarkissian on 20/03/2024. |
|
||||||
// |
|
||||||
|
|
||||||
import Foundation |
|
||||||
import UIKit |
|
||||||
|
|
||||||
enum DisplayContext { |
|
||||||
case addition |
|
||||||
case edition |
|
||||||
case lockedForEditing |
|
||||||
case selection |
|
||||||
} |
|
||||||
|
|
||||||
enum DisplayStyle { |
|
||||||
case title |
|
||||||
case wide |
|
||||||
case short |
|
||||||
} |
|
||||||
|
|
||||||
enum SummoningDisplayContext { |
|
||||||
case footer |
|
||||||
case menu |
|
||||||
} |
|
||||||
|
|
||||||
struct DeviceHelper { |
|
||||||
static func isBigScreen() -> Bool { |
|
||||||
switch UIDevice.current.userInterfaceIdiom { |
|
||||||
case .pad: // iPads |
|
||||||
return true |
|
||||||
case .phone: // iPhones (you can add more cases here for large vs small phones) |
|
||||||
if UIScreen.main.bounds.size.width > 375 { // iPhone X, 11, 12, 13 Pro Max etc. |
|
||||||
return true // large phones |
|
||||||
} else { |
|
||||||
return false // smaller phones |
|
||||||
} |
|
||||||
default: |
|
||||||
return false // Other devices (Apple Watch, TV, etc.) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
static func maxCharacter() -> Int { |
|
||||||
switch UIDevice.current.userInterfaceIdiom { |
|
||||||
case .pad: // iPads |
|
||||||
return 30 |
|
||||||
case .phone: // iPhones (you can add more cases here for large vs small phones) |
|
||||||
if UIScreen.main.bounds.size.width > 375 { // iPhone X, 11, 12, 13 Pro Max etc. |
|
||||||
return 15 // large phones |
|
||||||
} else { |
|
||||||
return 9 // smaller phones |
|
||||||
} |
|
||||||
default: |
|
||||||
return 9 // Other devices (Apple Watch, TV, etc.) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
static func charLength() -> Int { |
|
||||||
isBigScreen() ? 0 : 15 |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,37 +0,0 @@ |
|||||||
// |
|
||||||
// ExportFormat.swift |
|
||||||
// PadelClub |
|
||||||
// |
|
||||||
// Created by Razmig Sarkissian on 19/07/2024. |
|
||||||
// |
|
||||||
|
|
||||||
import Foundation |
|
||||||
|
|
||||||
enum ExportFormat: Int, Identifiable, CaseIterable { |
|
||||||
var id: Int { self.rawValue } |
|
||||||
|
|
||||||
case rawText |
|
||||||
case csv |
|
||||||
|
|
||||||
var suffix: String { |
|
||||||
switch self { |
|
||||||
case .rawText: |
|
||||||
return "txt" |
|
||||||
case .csv: |
|
||||||
return "csv" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func separator() -> String { |
|
||||||
switch self { |
|
||||||
case .rawText: |
|
||||||
return " " |
|
||||||
case .csv: |
|
||||||
return ";" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func newLineSeparator(_ count: Int = 1) -> String { |
|
||||||
return Array(repeating: "\n", count: count).joined() |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,28 +0,0 @@ |
|||||||
// |
|
||||||
// NetworkManagerError.swift |
|
||||||
// PadelClub |
|
||||||
// |
|
||||||
// Created by Razmig Sarkissian on 03/03/2024. |
|
||||||
// |
|
||||||
|
|
||||||
import Foundation |
|
||||||
|
|
||||||
enum NetworkManagerError: LocalizedError { |
|
||||||
case maintenance |
|
||||||
case fileNotYetAvailable |
|
||||||
case mailFailed |
|
||||||
case mailNotSent //no network no error |
|
||||||
case messageFailed |
|
||||||
case messageNotSent //no network no error |
|
||||||
case fileNotModified |
|
||||||
case fileNotDownloaded(Int) |
|
||||||
|
|
||||||
var errorDescription: String? { |
|
||||||
switch self { |
|
||||||
case .maintenance: |
|
||||||
return "Le site de la FFT est en maintenance" |
|
||||||
default: |
|
||||||
return String(describing: self) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,47 +0,0 @@ |
|||||||
// |
|
||||||
// PListReader.swift |
|
||||||
// PadelClub |
|
||||||
// |
|
||||||
// Created by Laurent Morvillier on 06/05/2024. |
|
||||||
// |
|
||||||
|
|
||||||
import Foundation |
|
||||||
|
|
||||||
class PListReader { |
|
||||||
|
|
||||||
static func dictionary(plist: String) -> [String: Any]? { |
|
||||||
if let plistPath = Bundle.main.path(forResource: plist, ofType: "plist") { |
|
||||||
// Read plist file into Data |
|
||||||
if let plistData = FileManager.default.contents(atPath: plistPath) { |
|
||||||
do { |
|
||||||
// Deserialize plist data into a dictionary |
|
||||||
if let plistDictionary = try PropertyListSerialization.propertyList(from: plistData, options: [], format: nil) as? [String: Any] { |
|
||||||
return plistDictionary |
|
||||||
} |
|
||||||
} catch { |
|
||||||
print("Error reading plist data: \(error)") |
|
||||||
} |
|
||||||
} else { |
|
||||||
print("Failed to read plist file at path: \(plistPath)") |
|
||||||
} |
|
||||||
} else { |
|
||||||
print("Plist file 'Data.plist' not found in bundle") |
|
||||||
} |
|
||||||
return nil |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
static func readString(plist: String, key: String) -> String? { |
|
||||||
if let dictionary = self.dictionary(plist: plist) { |
|
||||||
return dictionary[key] as? String |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
static func readBool(plist: String, key: String) -> Bool? { |
|
||||||
if let dictionary = self.dictionary(plist: plist) { |
|
||||||
return dictionary[key] as? Bool |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
} |
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,143 +0,0 @@ |
|||||||
// |
|
||||||
// Patcher.swift |
|
||||||
// PadelClub |
|
||||||
// |
|
||||||
// Created by Laurent Morvillier on 21/06/2024. |
|
||||||
// |
|
||||||
|
|
||||||
import Foundation |
|
||||||
import LeStorage |
|
||||||
|
|
||||||
enum ManualPatch: String { |
|
||||||
case disconnect |
|
||||||
|
|
||||||
var id: String { |
|
||||||
return "padelclub.app.manual.patch.\(self.rawValue)" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
class ManualPatcher { |
|
||||||
|
|
||||||
static func patchIfPossible(_ patch: ManualPatch) -> Bool { |
|
||||||
if UserDefaults.standard.value(forKey: patch.id) == nil { |
|
||||||
do { |
|
||||||
Logger.log(">>> Patches \(patch.rawValue)...") |
|
||||||
let result = try self._applyPatch(patch) |
|
||||||
UserDefaults.standard.setValue(true, forKey: patch.id) |
|
||||||
return result |
|
||||||
} catch { |
|
||||||
Logger.error(error) |
|
||||||
} |
|
||||||
} |
|
||||||
return false |
|
||||||
} |
|
||||||
|
|
||||||
fileprivate static func _applyPatch(_ patch: ManualPatch) throws -> Bool { |
|
||||||
switch patch { |
|
||||||
case .disconnect: |
|
||||||
let rawToken = try? StoreCenter.main.rawTokenShouldNotBeUsed() |
|
||||||
if StoreCenter.main.userName != nil || StoreCenter.main.userId != nil || rawToken != nil { |
|
||||||
DataStore.shared.disconnect() |
|
||||||
return true |
|
||||||
} else { |
|
||||||
return false |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
enum PatchError: Error { |
|
||||||
case patchError(message: String) |
|
||||||
} |
|
||||||
|
|
||||||
enum Patch: String, CaseIterable { |
|
||||||
case cleanLogs |
|
||||||
case syncUpgrade |
|
||||||
case updateTournaments |
|
||||||
case cleanPurchaseApiCalls = "cleanPurchaseApiCalls_2" |
|
||||||
|
|
||||||
var id: String { |
|
||||||
return "padelclub.app.patch.\(self.rawValue)" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
class AutomaticPatcher { |
|
||||||
|
|
||||||
static func applyAllWhenApplicable() { |
|
||||||
for patch in Patch.allCases { |
|
||||||
self.patchIfPossible(patch) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
static func patchIfPossible(_ patch: Patch) { |
|
||||||
if UserDefaults.standard.value(forKey: patch.id) == nil { |
|
||||||
do { |
|
||||||
Logger.log(">>> Patches \(patch.rawValue)...") |
|
||||||
try self._applyPatch(patch) |
|
||||||
UserDefaults.standard.setValue(true, forKey: patch.id) |
|
||||||
} catch { |
|
||||||
Logger.error(error) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
fileprivate static func _applyPatch(_ patch: Patch) throws { |
|
||||||
switch patch { |
|
||||||
case .cleanLogs: self._cleanLogs() |
|
||||||
case .syncUpgrade: self._syncUpgrade() |
|
||||||
case .updateTournaments: self._updateTournaments() |
|
||||||
case .cleanPurchaseApiCalls: self._cleanPurchaseApiCalls() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
fileprivate static func _cleanLogs() { |
|
||||||
StoreCenter.main.resetLoggingCollections() |
|
||||||
} |
|
||||||
|
|
||||||
fileprivate static func _syncUpgrade() { |
|
||||||
for tournament in DataStore.shared.tournaments { |
|
||||||
let id = tournament.id |
|
||||||
|
|
||||||
guard let store = TournamentLibrary.shared.store(tournamentId: tournament.id) else { continue } |
|
||||||
|
|
||||||
for round in store.rounds { |
|
||||||
round.storeId = id |
|
||||||
} |
|
||||||
store.rounds.addOrUpdate(contentOfs: store.rounds) |
|
||||||
for groupStage in store.groupStages { |
|
||||||
groupStage.storeId = id |
|
||||||
} |
|
||||||
store.groupStages.addOrUpdate(contentOfs: store.groupStages) |
|
||||||
for teamRegistration in store.teamRegistrations { |
|
||||||
teamRegistration.storeId = id |
|
||||||
} |
|
||||||
store.teamRegistrations.addOrUpdate(contentOfs: store.teamRegistrations) |
|
||||||
for pr in store.playerRegistrations { |
|
||||||
pr.storeId = id |
|
||||||
} |
|
||||||
store.playerRegistrations.addOrUpdate(contentOfs: store.playerRegistrations) |
|
||||||
for match in store.matches { |
|
||||||
match.storeId = id |
|
||||||
} |
|
||||||
store.matches.addOrUpdate(contentOfs: store.matches) |
|
||||||
for ts in store.teamScores { |
|
||||||
ts.storeId = id |
|
||||||
} |
|
||||||
store.teamScores.addOrUpdate(contentOfs: store.teamScores) |
|
||||||
for ms in store.matchSchedulers { |
|
||||||
ms.storeId = id |
|
||||||
} |
|
||||||
store.matchSchedulers.addOrUpdate(contentOfs: store.matchSchedulers) |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
fileprivate static func _updateTournaments() { |
|
||||||
DataStore.shared.tournaments.addOrUpdate(contentOfs: DataStore.shared.tournaments) |
|
||||||
} |
|
||||||
|
|
||||||
fileprivate static func _cleanPurchaseApiCalls() { |
|
||||||
StoreCenter.main.resetApiCalls(type: Purchase.self) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,270 +0,0 @@ |
|||||||
// |
|
||||||
// SourceFileManager.swift |
|
||||||
// PadelClub |
|
||||||
// |
|
||||||
// Created by Razmig Sarkissian on 01/03/2024. |
|
||||||
// |
|
||||||
|
|
||||||
import Foundation |
|
||||||
import LeStorage |
|
||||||
|
|
||||||
class SourceFileManager { |
|
||||||
static let shared = SourceFileManager() |
|
||||||
|
|
||||||
init() { |
|
||||||
createDirectoryIfNeeded(directoryURL: rankingSourceDirectory) |
|
||||||
#if targetEnvironment(simulator) |
|
||||||
createDirectoryIfNeeded(directoryURL: anonymousSourceDirectory) |
|
||||||
#endif |
|
||||||
} |
|
||||||
|
|
||||||
let rankingSourceDirectory : URL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appending(path: "rankings") |
|
||||||
let anonymousSourceDirectory : URL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appending(path: "anonymous") |
|
||||||
|
|
||||||
func createDirectoryIfNeeded(directoryURL: URL) { |
|
||||||
let fileManager = FileManager.default |
|
||||||
do { |
|
||||||
// Check if the directory exists |
|
||||||
if !fileManager.fileExists(atPath: directoryURL.path) { |
|
||||||
// Directory does not exist, create it |
|
||||||
try fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil) |
|
||||||
// print("Directory created at: \(directoryURL)") |
|
||||||
} else { |
|
||||||
// print("Directory already exists at: \(directoryURL)") |
|
||||||
} |
|
||||||
} catch { |
|
||||||
print("Error: \(error)") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
var lastDataSource: String? { |
|
||||||
DataStore.shared.appSettings.lastDataSource |
|
||||||
} |
|
||||||
|
|
||||||
func lastDataSourceDate() -> Date? { |
|
||||||
guard let lastDataSource else { return nil } |
|
||||||
return URL.importDateFormatter.date(from: lastDataSource) |
|
||||||
} |
|
||||||
|
|
||||||
func fetchData() async { |
|
||||||
await fetchData(fromDate: Date()) |
|
||||||
// if let mostRecent = mostRecentDateAvailable, let current = Calendar.current.date(byAdding: .month, value: 1, to: mostRecent), current > mostRecent { |
|
||||||
// await fetchData(fromDate: current) |
|
||||||
// } else { |
|
||||||
// } |
|
||||||
} |
|
||||||
|
|
||||||
func _removeAllData(fromDate current: Date) { |
|
||||||
let lastStringDate = URL.importDateFormatter.string(from: current) |
|
||||||
let files = ["MESSIEURS", "MESSIEURS-2", "MESSIEURS-3", "MESSIEURS-4", "DAMES"] |
|
||||||
files.forEach { fileName in |
|
||||||
NetworkManager.shared.removeRankingData(lastDateString: lastStringDate, fileName: fileName) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func exportToCSV(_ prefix: String = "", players: [FederalPlayer], sourceFileType: SourceFile, date: Date) { |
|
||||||
let lastDateString = URL.importDateFormatter.string(from: date) |
|
||||||
let dateString = [prefix, "CLASSEMENT-PADEL", sourceFileType.rawValue, lastDateString].filter({ $0.isEmpty == false }).joined(separator: "-") + "." + "csv" |
|
||||||
|
|
||||||
let documentsUrl:URL = (FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first as URL?)! |
|
||||||
let destinationFileUrl = documentsUrl.appendingPathComponent("\(dateString)") |
|
||||||
var csvText : String = "" |
|
||||||
for player in players { |
|
||||||
csvText.append(player.exportToCSV() + "\n") |
|
||||||
} |
|
||||||
|
|
||||||
do { |
|
||||||
try csvText.write(to: destinationFileUrl, atomically: true, encoding: .utf8) |
|
||||||
print("CSV file exported successfully.") |
|
||||||
} catch { |
|
||||||
print("Error writing CSV file:", error) |
|
||||||
Logger.error(error) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
actor SourceFileDownloadTracker { |
|
||||||
var _downloadedFileStatus : Int? = nil |
|
||||||
|
|
||||||
func updateIfNecessary(with successState: Int?) { |
|
||||||
if successState != nil && (_downloadedFileStatus == nil || _downloadedFileStatus == 0) { |
|
||||||
_downloadedFileStatus = successState |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func getDownloadedFileStatus() -> Int? { |
|
||||||
return _downloadedFileStatus |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
//return nil if no new files |
|
||||||
//return 1 if new file to import |
|
||||||
//return 0 if new file just to re-calc static data, no need to re-import |
|
||||||
@discardableResult |
|
||||||
func fetchData(fromDate current: Date) async -> Int? { |
|
||||||
let lastStringDate = URL.importDateFormatter.string(from: current) |
|
||||||
|
|
||||||
let files = ["MESSIEURS", "MESSIEURS-2", "MESSIEURS-3", "MESSIEURS-4", "DAMES"] |
|
||||||
|
|
||||||
let sourceFileDownloadTracker = SourceFileDownloadTracker() |
|
||||||
|
|
||||||
do { |
|
||||||
try await withThrowingTaskGroup(of: Void.self) { group in // Mark 1 |
|
||||||
|
|
||||||
for file in files { |
|
||||||
group.addTask { [sourceFileDownloadTracker] in |
|
||||||
let success = try await NetworkManager.shared.downloadRankingData(lastDateString: lastStringDate, fileName: file) |
|
||||||
await sourceFileDownloadTracker.updateIfNecessary(with: success) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
try await group.waitForAll() |
|
||||||
} |
|
||||||
|
|
||||||
// if current < Date() { |
|
||||||
// if let nextCurrent = Calendar.current.date(byAdding: .month, value: 1, to: current) { |
|
||||||
// await fetchData(fromDate: nextCurrent) |
|
||||||
// } |
|
||||||
// } |
|
||||||
} catch { |
|
||||||
print("downloadRankingData", error) |
|
||||||
|
|
||||||
if mostRecentDateAvailable == nil { |
|
||||||
if let previousDate = Calendar.current.date(byAdding: .month, value: -1, to: current) { |
|
||||||
await fetchData(fromDate: previousDate) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
let downloadedFileStatus = await sourceFileDownloadTracker.getDownloadedFileStatus() |
|
||||||
|
|
||||||
return downloadedFileStatus |
|
||||||
} |
|
||||||
|
|
||||||
func getAllFiles(initialDate: String = "08-2022") async { |
|
||||||
let dates = monthsBetweenDates(startDateString: initialDate, endDateString: Date().monthYearFormatted) |
|
||||||
.compactMap { |
|
||||||
URL.importDateFormatter.date(from: $0) |
|
||||||
} |
|
||||||
.filter { date in |
|
||||||
allFiles.contains(where: { $0.dateFromPath == date }) == false |
|
||||||
} |
|
||||||
|
|
||||||
try? await dates.concurrentForEach { date in |
|
||||||
await self.fetchData(fromDate: date) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func monthsBetweenDates(startDateString: String, endDateString: String) -> [String] { |
|
||||||
let dateFormatter = URL.importDateFormatter |
|
||||||
|
|
||||||
guard let startDate = dateFormatter.date(from: startDateString), |
|
||||||
let endDate = dateFormatter.date(from: endDateString) else { |
|
||||||
return [] |
|
||||||
} |
|
||||||
|
|
||||||
var months: [String] = [] |
|
||||||
var currentDate = startDate |
|
||||||
let calendar = Calendar.current |
|
||||||
|
|
||||||
while currentDate <= endDate { |
|
||||||
let monthString = dateFormatter.string(from: currentDate) |
|
||||||
months.append(monthString) |
|
||||||
|
|
||||||
guard let nextMonthDate = calendar.date(byAdding: .month, value: 1, to: currentDate) else { |
|
||||||
break |
|
||||||
} |
|
||||||
currentDate = nextMonthDate |
|
||||||
} |
|
||||||
return months |
|
||||||
} |
|
||||||
|
|
||||||
func getUnrankValue(forMale: Bool, rankSourceDate: Date?) -> Int? { |
|
||||||
let _rankSourceDate = rankSourceDate ?? mostRecentDateAvailable |
|
||||||
let urls = allFiles(forMale).filter { $0.dateFromPath == _rankSourceDate } |
|
||||||
return urls.compactMap { $0.getUnrankedValue() }.sorted().last |
|
||||||
} |
|
||||||
|
|
||||||
var mostRecentDateAvailable: Date? { |
|
||||||
allFiles(false).first?.dateFromPath |
|
||||||
} |
|
||||||
|
|
||||||
func removeAllFilesFromServer() { |
|
||||||
let allFiles = try! FileManager.default.contentsOfDirectory(at: rankingSourceDirectory, includingPropertiesForKeys: nil) |
|
||||||
allFiles.filter { $0.pathExtension == "csv" }.forEach { url in |
|
||||||
try? FileManager.default.removeItem(at: url) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func anonymousFiles() -> [URL] { |
|
||||||
let allJSONFiles = try! FileManager.default.contentsOfDirectory(at: anonymousSourceDirectory, includingPropertiesForKeys: nil).filter({ url in |
|
||||||
url.pathExtension == "csv" |
|
||||||
}) |
|
||||||
return allJSONFiles |
|
||||||
} |
|
||||||
|
|
||||||
func jsonFiles() -> [URL] { |
|
||||||
let allJSONFiles = try! FileManager.default.contentsOfDirectory(at: rankingSourceDirectory, includingPropertiesForKeys: nil).filter({ url in |
|
||||||
url.pathExtension == "json" |
|
||||||
}) |
|
||||||
return allJSONFiles |
|
||||||
} |
|
||||||
|
|
||||||
var allFiles: [URL] { |
|
||||||
let allFiles = try! FileManager.default.contentsOfDirectory(at: rankingSourceDirectory, includingPropertiesForKeys: nil).filter({ url in |
|
||||||
url.pathExtension == "csv" |
|
||||||
}) |
|
||||||
|
|
||||||
return (allFiles + (Bundle.main.urls(forResourcesWithExtension: "csv", subdirectory: nil) ?? [])).sorted { $0.dateFromPath == $1.dateFromPath ? $0.index < $1.index : $0.dateFromPath > $1.dateFromPath } |
|
||||||
} |
|
||||||
|
|
||||||
func allFiles(_ isManPlayer: Bool) -> [URL] { |
|
||||||
allFiles.filter({ url in |
|
||||||
url.path().contains(isManPlayer ? SourceFile.messieurs.rawValue : SourceFile.dames.rawValue) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
func allFilesSortedByDate(_ isManPlayer: Bool) -> [URL] { |
|
||||||
return allFiles(isManPlayer) |
|
||||||
} |
|
||||||
|
|
||||||
static func isDateAfterUrlImportDate(date: Date, dateString: String) -> Bool { |
|
||||||
guard let importDate = URL.importDateFormatter.date(from: dateString) else { |
|
||||||
return false |
|
||||||
} |
|
||||||
|
|
||||||
return importDate.isEarlierThan(date) |
|
||||||
} |
|
||||||
|
|
||||||
static func getSortOption() -> [SortOption] { |
|
||||||
return SortOption.allCases |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
enum SourceFile: String, CaseIterable { |
|
||||||
case dames = "DAMES" |
|
||||||
case messieurs = "MESSIEURS" |
|
||||||
|
|
||||||
var filesFromServer: [URL] { |
|
||||||
let rankingSourceDirectory = SourceFileManager.shared.rankingSourceDirectory |
|
||||||
let allFiles = try! FileManager.default.contentsOfDirectory(at: rankingSourceDirectory, includingPropertiesForKeys: nil) |
|
||||||
return allFiles.filter{$0.pathExtension == "csv" && $0.path().contains(rawValue)} |
|
||||||
} |
|
||||||
|
|
||||||
func currentURLs(importingDate: Date) -> [URL] { |
|
||||||
var files = Bundle.main.urls(forResourcesWithExtension: "csv", subdirectory: nil)?.filter({ url in |
|
||||||
url.path().contains(rawValue) |
|
||||||
}) ?? [] |
|
||||||
|
|
||||||
files.append(contentsOf: filesFromServer) |
|
||||||
return files.filter({ $0.dateFromPath == importingDate }) |
|
||||||
} |
|
||||||
|
|
||||||
var isMan: Bool { |
|
||||||
switch self { |
|
||||||
case .dames: |
|
||||||
return false |
|
||||||
default: |
|
||||||
return true |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,95 +0,0 @@ |
|||||||
// |
|
||||||
// URLs.swift |
|
||||||
// PadelClub |
|
||||||
// |
|
||||||
// Created by Laurent Morvillier on 22/04/2024. |
|
||||||
// |
|
||||||
|
|
||||||
import Foundation |
|
||||||
|
|
||||||
enum URLs: String, Identifiable { |
|
||||||
// case httpScheme = "https://" |
|
||||||
#if DEBUG |
|
||||||
case activationHost = "http://127.0.0.1:8000" |
|
||||||
case main = "http://127.0.0.1:8000/" |
|
||||||
// case api = "https://xlr.alwaysdata.net/roads/" |
|
||||||
#elseif TESTFLIGHT |
|
||||||
case activationHost = "xlr.alwaysdata.net" |
|
||||||
case main = "https://xlr.alwaysdata.net/" |
|
||||||
// case api = "https://xlr.alwaysdata.net/roads/" |
|
||||||
#elseif PRODTEST |
|
||||||
case activationHost = "padelclub.app" |
|
||||||
case main = "https://padelclub.app/" |
|
||||||
// case api = "https://padelclub.app/roads/" |
|
||||||
#else |
|
||||||
case activationHost = "padelclub.app" |
|
||||||
case main = "https://padelclub.app/" |
|
||||||
// case api = "https://padelclub.app/roads/" |
|
||||||
#endif |
|
||||||
|
|
||||||
case subscriptions = "https://apple.co/2Th4vqI" |
|
||||||
case beachPadel = "https://beach-padel.app.fft.fr/beachja/index/" |
|
||||||
//case padelClub = "https://padelclub.app" |
|
||||||
case tenup = "https://tenup.fft.fr" |
|
||||||
case padelCompetitionGeneralGuide = "https://padelclub.app/static/rules/padel-guide-general.pdf" |
|
||||||
case padelCompetitionSpecificGuide = "https://padelclub.app/static/rules/padel-guide-cdc.pdf" |
|
||||||
case padelCompetitionRankingGuide = "https://padelclub.app/static/rules/padel-guide-rankings.pdf" |
|
||||||
case padelRules = "https://padelclub.app/static/rules/padel-rules.pdf" |
|
||||||
case restingDischarge = "https://club.fft.fr/tennisfirmidecazeville/60120370_d/data_1/pdf/fo/formlairededechargederesponsabilitetournoidepadel.pdf" |
|
||||||
case appReview = "https://apps.apple.com/app/padel-club/id6484163558?mt=8&action=write-review" |
|
||||||
case appDescription = "https://padelclub.app/download/" |
|
||||||
case instagram = "https://www.instagram.com/padelclub.app?igsh=bmticnV5YWhpMnBn" |
|
||||||
case appStore = "https://apps.apple.com/app/padel-club/id6484163558" |
|
||||||
case eula = "https://www.apple.com/legal/internet-services/itunes/dev/stdeula/" |
|
||||||
case privacy = "https://padelclub.app/terms-of-use" |
|
||||||
|
|
||||||
var id: String { return self.rawValue } |
|
||||||
|
|
||||||
var url: URL { |
|
||||||
return URL(string: self.rawValue)! |
|
||||||
} |
|
||||||
|
|
||||||
func extend(path: String) -> URL { |
|
||||||
return URL(string: self.rawValue + path)! |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
enum PageLink: String, Identifiable, CaseIterable { |
|
||||||
case info = "Informations" |
|
||||||
case teams = "Équipes" |
|
||||||
case summons = "Convocations" |
|
||||||
case groupStages = "Poules" |
|
||||||
case matches = "Tournoi" |
|
||||||
case rankings = "Classement" |
|
||||||
case broadcast = "Mode TV (Tournoi)" |
|
||||||
case clubBroadcast = "Mode TV (Club)" |
|
||||||
|
|
||||||
var id: String { self.rawValue } |
|
||||||
|
|
||||||
func localizedLabel() -> String { |
|
||||||
rawValue |
|
||||||
} |
|
||||||
|
|
||||||
var path: String { |
|
||||||
switch self { |
|
||||||
case .matches: |
|
||||||
return "" |
|
||||||
case .info: |
|
||||||
return "info" |
|
||||||
case .teams: |
|
||||||
return "teams" |
|
||||||
case .summons: |
|
||||||
return "summons" |
|
||||||
case .rankings: |
|
||||||
return "rankings" |
|
||||||
case .groupStages: |
|
||||||
return "group-stages" |
|
||||||
case .broadcast: |
|
||||||
return "broadcast" |
|
||||||
case .clubBroadcast: |
|
||||||
return "" |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue