diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 6557f09..bfe0413 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -3306,7 +3306,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0.42; - PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; + PRODUCT_BUNDLE_IDENTIFIER = app.padelclub.dec; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; @@ -3353,7 +3353,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0.42; - PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; + PRODUCT_BUNDLE_IDENTIFIER = app.padelclub.dec; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; diff --git a/PadelClub/Data/PlayerRegistration.swift b/PadelClub/Data/PlayerRegistration.swift index 525423b..80e1822 100644 --- a/PadelClub/Data/PlayerRegistration.swift +++ b/PadelClub/Data/PlayerRegistration.swift @@ -45,10 +45,12 @@ final class PlayerRegistration: ModelObject, Storable { var sourceName: String? var isNveq: Bool = false var isEQConfirmed: Bool? - + var duplicatePlayers: Int? + var isYearValid: Bool? + func localizedSourceLabel() -> String { switch source { - case .frenchFederation, .frenchFederationVerified: + case .frenchFederation, .frenchFederationVerified, .frenchFederationEQVerified: return "Via la base fédérale" case .beachPadel: return "Via le fichier beach-padel" @@ -192,21 +194,29 @@ final class PlayerRegistration: ModelObject, Storable { } func isVerified() -> String { - source == .frenchFederationVerified ? "ok" : "" + switch source { + case .frenchFederationVerified: + return "ok" + case .frenchFederationEQVerified: + return "ok2" + default: + return "" + } } - - var duplicatePlayers: Int? - + func championshipAlerts(tournament: Tournament, allPlayers: [(String, String)], forRegional: Bool) -> [ChampionshipAlert] { var alerts = [ChampionshipAlert]() - if forRegional { + if forRegional, source != .frenchFederationEQVerified { if isNveq == false && (isEQConfirmed == false || isEQConfirmed == nil) { alerts.append(.notEQ(isEQConfirmed, self)) - } } + if isYearValid == nil || isYearValid == false { + alerts.append(.isYearValid(isYearValid, self)) + } + if duplicatePlayers == nil { let teamClubCode = self.team()?.clubCode let strippedLicense = self.licenceId?.strippedLicense @@ -223,7 +233,7 @@ final class PlayerRegistration: ModelObject, Storable { if isUnranked() && source == nil { alerts.append(.unranked(self)) - } else if source != .frenchFederationVerified { + } else if source != .frenchFederationVerified && source != .frenchFederationEQVerified { if tournament.tournamentCategory == .men && isMalePlayer() == false { alerts.append(.playerSexInvalid(self)) } @@ -242,6 +252,8 @@ final class PlayerRegistration: ModelObject, Storable { if isLicenceOK() == false { alerts.append(.playerLicenseInvalid(self)) } + + } return alerts @@ -523,6 +535,10 @@ defer { case _clubCode = "clubCode" case _sourceName = "sourceName" case _isNveq = "isNveq" + case _duplicatePlayers = "duplicatePlayers" + case _isYearValid = "isYearValid" + case _isEQConfirmed = "isEQConfirmed" + } init(from decoder: Decoder) throws { @@ -555,6 +571,10 @@ defer { clubCode = try container.decodeIfPresent(String.self, forKey: ._clubCode) isNveq = try container.decodeIfPresent(Bool.self, forKey: ._isNveq) ?? false sourceName = try container.decodeIfPresent(String.self, forKey: ._sourceName) + isEQConfirmed = try container.decodeIfPresent(Bool.self, forKey: ._isEQConfirmed) + duplicatePlayers = try container.decodeIfPresent(Int.self, forKey: ._duplicatePlayers) + isYearValid = try container.decodeIfPresent(Bool.self, forKey: ._isYearValid) + sourceName = try container.decodeIfPresent(String.self, forKey: ._sourceName) } func encode(to encoder: Encoder) throws { @@ -585,6 +605,9 @@ defer { try container.encode(clubCode, forKey: ._clubCode) try container.encode(sourceName, forKey: ._sourceName) try container.encode(isNveq, forKey: ._isNveq) + try container.encode(isEQConfirmed, forKey: ._isEQConfirmed) + try container.encode(duplicatePlayers, forKey: ._duplicatePlayers) + try container.encode(isYearValid, forKey: ._isYearValid) } enum PlayerDataSource: Int, Codable { @@ -592,6 +615,7 @@ defer { case beachPadel = 1 case onlineRegistration = 2 case frenchFederationVerified = 3 + case frenchFederationEQVerified = 4 } enum PlayerSexType: Int, Hashable, CaseIterable, Identifiable, Codable { diff --git a/PadelClub/Data/TeamRegistration.swift b/PadelClub/Data/TeamRegistration.swift index 091fb90..7a04b13 100644 --- a/PadelClub/Data/TeamRegistration.swift +++ b/PadelClub/Data/TeamRegistration.swift @@ -449,18 +449,18 @@ final class TeamRegistration: ModelObject, Storable { let players = players() - if teamIndex <= 16, players.filter({ $0.isNveq }).count > 2 { + if teamIndex <= 24, players.filter({ $0.isNveq }).count > 2 { alerts.append(.tooManyNVEQ(self)) } - if teamIndex <= 16, players.count > 8 { - alerts.append(.tooManyPlayers(self)) - - } +// if teamIndex <= 24, players.count > 8 { +// alerts.append(.tooManyPlayers(self)) +// +// } players.forEach { pr in - alerts.append(contentsOf: pr.championshipAlerts(tournament: tournament, allPlayers: allPlayers, forRegional: teamIndex <= 16)) + alerts.append(contentsOf: pr.championshipAlerts(tournament: tournament, allPlayers: allPlayers, forRegional: teamIndex <= 24)) } return alerts diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 618edb0..87f1855 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -1692,7 +1692,7 @@ defer { guard let newDate = URL.importDateFormatter.date(from: "08-2024") else { return } let dataURLs = SourceFileManager.shared.allFiles.filter { $0.dateFromPath == newDate } let sources = dataURLs.map { CSVParser(url: $0) } - let teams = selectedSortedTeams().prefix(16) + let teams = selectedSortedTeams().prefix(24) let players = teams.flatMap({ $0.unsortedPlayers() }) try await players.concurrentForEach { player in diff --git a/PadelClub/Utils/FileImportManager.swift b/PadelClub/Utils/FileImportManager.swift index 0eed585..8489135 100644 --- a/PadelClub/Utils/FileImportManager.swift +++ b/PadelClub/Utils/FileImportManager.swift @@ -475,6 +475,7 @@ class FileImportManager { let status : String? = data[safe: 9] let verified : String? = data[safe: 10] let isVerified = verified == "ok" + let isEQVerified = verified == "ok2" if chunkMode == .byColumn { if let licenceId = licenceId?.strippedLicense { @@ -487,7 +488,9 @@ class FileImportManager { player.sourceName = lastName player.isNveq = status == "NVEQ" player.clubCode = found.clubCode - if isVerified { + if isEQVerified { + player.source = .frenchFederationEQVerified + } else if isVerified { player.source = .frenchFederationVerified } return player @@ -495,7 +498,9 @@ class FileImportManager { let player = PlayerRegistration(firstName: firstName, lastName: lastName, licenceId: licenceId, rank: rank, sex: sex, clubName: club, phoneNumber: phoneNumber, email: email) player.sourceName = lastName player.isNveq = status == "NVEQ" - if isVerified { + if isEQVerified { + player.source = .frenchFederationEQVerified + } else if isVerified { player.source = .frenchFederationVerified } if rank == nil { @@ -509,7 +514,9 @@ class FileImportManager { let player = PlayerRegistration(firstName: firstName, lastName: lastName, licenceId: licenceId, rank: rank, sex: sex, clubName: club, phoneNumber: phoneNumber, email: email) player.sourceName = lastName player.isNveq = status == "NVEQ" - if isVerified { + if isEQVerified { + player.source = .frenchFederationEQVerified + } else if isVerified { player.source = .frenchFederationVerified } if rank == nil { @@ -779,9 +786,12 @@ enum ChampionshipAlert: LocalizedError { case playerSexInvalid(PlayerRegistration) case duplicate(Int, PlayerRegistration) case notEQ(Bool?, PlayerRegistration) - + case isYearValid(Bool?, PlayerRegistration) + var errorDescription: String? { switch self { + case .isYearValid(_, let playerRegistration): + return playerRegistration.errorDescription(championshipAlert: self) case .notEQ(_, let playerRegistration): return playerRegistration.errorDescription(championshipAlert: self) case .duplicate(_, let playerRegistration): @@ -816,6 +826,12 @@ extension PlayerRegistration { func errorDescription(championshipAlert: ChampionshipAlert) -> String? { var message = self.playerLabel() + " - " + self.formattedLicense() + " -> " switch championshipAlert { + case .isYearValid(let bool, _): + if bool == nil { + message += "MILLESIME INVERIFIABLE" + } else { + message += "MILLESIME INVALIDE" + } case .notEQ(let bool, _): if bool == nil { message += "EQ INVERIFIABLE" diff --git a/PadelClub/Views/Navigation/MainView.swift b/PadelClub/Views/Navigation/MainView.swift index ee01859..4d5c151 100644 --- a/PadelClub/Views/Navigation/MainView.swift +++ b/PadelClub/Views/Navigation/MainView.swift @@ -176,6 +176,7 @@ struct MainView: View { } private func _checkSourceFileAvailability() async { + return print("dataStore.appSettings.lastDataSource :", dataStore.appSettings.lastDataSource ?? "none") print("check internet") print("check files on internet") diff --git a/PadelClub/Views/Navigation/Umpire/PadelClubView.swift b/PadelClub/Views/Navigation/Umpire/PadelClubView.swift index ca70995..91e07f4 100644 --- a/PadelClub/Views/Navigation/Umpire/PadelClubView.swift +++ b/PadelClub/Views/Navigation/Umpire/PadelClubView.swift @@ -269,7 +269,7 @@ func fetchPlayerData(for licenseID: String) async throws -> [Player]? { request.setValue("XMLHttpRequest", forHTTPHeaderField: "X-Requested-With") // Add cookies if needed (example cookie header value shown, replace with valid cookies) - request.setValue("JSESSIONID=C4B647EB27A036891E29D27FD83CE29B; AWSALB=1btRrmQjbf1VjZH74hBI3+OcfTO/HfJWbf+wm0kpBEVz9jUgvXb+UO4FkcIimuferhUjEfWmENKUS1SYmHCkndtiaR5x89W9uHYi/JTO3zn1ZO2JRCjvEgY7BNJm; AWSALBCORS=1btRrmQjbf1VjZH74hBI3+OcfTO/HfJWbf+wm0kpBEVz9jUgvXb+UO4FkcIimuferhUjEfWmENKUS1SYmHCkndtiaR5x89W9uHYi/JTO3zn1ZO2JRCjvEgY7BNJm; datadome=thpudGMDaIXBt6W8GjURPii9mF0ovaPnNozziskvWhA0uZ7bt~ZX3CICjEZEV5J4GXL151J19_NsLGL5wNT2BZ9NJUbJciEo9YJdFp_OKwlM13_dYqIrJ5rlKW65SPOg; _pcid=%7B%22browserId%22%3A%22m42mi4kbtfuyj367%22%2C%22_t%22%3A%22mjr1fm32%7Cm42mi4r2%22%7D; _pctx=%7Bu%7DN4IgrgzgpgThIC4B2YA2qA05owMoBcBDfSREQpAeyRCwgEt8oBJAE0RXSwH18yBbAFYwAjADN%2BAZgCsAH34AWAEz96CmNJABfIA; _pprv=eyJjb25zZW50Ijp7IjAiOnsibW9kZSI6ImVzc2VudGlhbCJ9LCI3Ijp7Im1vZGUiOiJvcHQtaW4ifX0sInB1cnBvc2VzIjpudWxsLCJfdCI6Im1qcjFmbHdofG00Mm1pNGtoIn0%3D; tc_cj_v2=%5Ecl_%5Dny%5B%5D%5D_mmZZZZZZKQMMORPMMMQNNZZZ%5D777m_iZZZ%22**%22%27%20ZZZKQMMQMPMPMKOPZZZ%5D777%5Ecl_%5Dny%5B%5D%5D_mmZZZZZZKQMPLNJNKJQQJZZZ%5D777m_iZZZ%22**%22%27%20ZZZKQMPLNJNLQOJKZZZ%5D777%5Ecl_%5Dny%5B%5D%5D_mmZZZZZZKQMPLONOQNSNSZZZ%5D; tc_cj_v2_cmp=; tc_cj_v2_med=; SSESS7ba44afc36c80c3faa2b8fa87e7742c5=EFTV9aCrNCaJM7Soo-1OemOQ0qdJXpp7cqiYrMSoQRQ; xtan=-; xtant=1; TCID=124122155494907703483; TCPID=124115115191501043230; xtvrn=$548419$; visid_incap_2712217=PSfJngzoSuiowsuXXhvOu5K+7mUAAAAAQUIPAAAAAAAleL9ldvN/FC1VykkU9ret; SessionStatId=10.91.140.42.1662124965429001", forHTTPHeaderField: "Cookie") + request.setValue("JSESSIONID=6F1BB57625577350AFC8812090D1427C; AWSALB=TJD45ilgPon0e9eo5ZqVQ7BHh5V1rPmc8NrYGnzC+WP3KtCcc07bxAT7DvYmKIsukqkVdTpmw/NM1+0MTrwuP+DlYFXpnpHM6n5AGEIyRzjEIMDXKnl2IwwKI49N; AWSALBCORS=TJD45ilgPon0e9eo5ZqVQ7BHh5V1rPmc8NrYGnzC+WP3KtCcc07bxAT7DvYmKIsukqkVdTpmw/NM1+0MTrwuP+DlYFXpnpHM6n5AGEIyRzjEIMDXKnl2IwwKI49N; datadome=plkXf6gRKVGRESutB228m4fq9z~jOuLzJpYixiEGDAnr2OlZC1Woq4Gg_YtVR0BVC_n1fn~~rEsBk5fwZlfnT~Z9Gn004VInlh5grj7ve6RLBAebWaYGVOLQnKwy88TH; _pcid=%7B%22browserId%22%3A%22m42mi4kbtfuyj367%22%2C%22_t%22%3A%22mjr1fm32%7Cm42mi4r2%22%7D; _pctx=%7Bu%7DN4IgrgzgpgThIC4B2YA2qA05owMoBcBDfSREQpAeyRCwgEt8oBJAE0RXSwH18yBbAFYwAjADN%2BAZgCsAH34AWAEz96CmNJABfIA; _pprv=eyJjb25zZW50Ijp7IjAiOnsibW9kZSI6ImVzc2VudGlhbCJ9LCI3Ijp7Im1vZGUiOiJvcHQtaW4ifX0sInB1cnBvc2VzIjpudWxsLCJfdCI6Im1qcjFmbHdofG00Mm1pNGtoIn0%3D; tc_cj_v2=%5Ecl_%5Dny%5B%5D%5D_mmZZZZZZKQMMORPMMMQNNZZZ%5D777m_iZZZ%22**%22%27%20ZZZKQMMQMPMPMKOPZZZ%5D777%5Ecl_%5Dny%5B%5D%5D_mmZZZZZZKQMPLNJNKJQQJZZZ%5D777m_iZZZ%22**%22%27%20ZZZKQMPLNJNLQOJKZZZ%5D777%5Ecl_%5Dny%5B%5D%5D_mmZZZZZZKQMPNSONSOPQOZZZ%5D; tc_cj_v2_cmp=; tc_cj_v2_med=; SSESS7ba44afc36c80c3faa2b8fa87e7742c5=EFTV9aCrNCaJM7Soo-1OemOQ0qdJXpp7cqiYrMSoQRQ; xtan=-; xtant=1; TCID=124122155494907703483; TCPID=124115115191501043230; xtvrn=$548419$; visid_incap_2712217=PSfJngzoSuiowsuXXhvOu5K+7mUAAAAAQUIPAAAAAAAleL9ldvN/FC1VykkU9ret; SessionStatId=10.91.140.42.1662124965429001", forHTTPHeaderField: "Cookie") let (data, _) = try await URLSession.shared.data(for: request) let decoder = JSONDecoder() @@ -311,6 +311,7 @@ struct Player: Codable { let sexe: String let codeClub: String let dateNaissanceFr: String + let millesimeLicence: Int func birthYear() -> Int? { return Int(dateNaissanceFr.suffix(4)) diff --git a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift index d879276..ace73ee 100644 --- a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift +++ b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift @@ -873,7 +873,6 @@ struct InscriptionManagerView: View { } } } - Button { Task { DispatchQueue.main.async { @@ -898,6 +897,7 @@ struct InscriptionManagerView: View { player.lastName = playerData.nom player.birthdate = playerData.dateNaissanceFr player.clubCode = playerData.codeClub + player.isYearValid = playerData.millesimeLicence == 2025 player.source = .frenchFederation playersToSave.append(player) } @@ -943,6 +943,75 @@ struct InscriptionManagerView: View { } } + Button { + Task { + DispatchQueue.main.async { + gatheringInProgress = true + gathered = 0 + gatheringDone = false + } + + let unrankedUnsourcedChunk = tournament.players().filter { $0.isYearValid == nil }.chunked(into: 100) + + for unrankedUnsourced in unrankedUnsourcedChunk { + await withTaskGroup(of: Void.self) { group in + var playersToSave = [PlayerRegistration]() + for player in unrankedUnsourced { + group.addTask { + do { + if let playerData = try await player.fetchUnrankPlayerData() { + player.lastName = playerData.nom + player.birthdate = playerData.dateNaissanceFr + player.clubCode = playerData.codeClub + player.isYearValid = playerData.millesimeLicence == 2025 + //player.source = .frenchFederation + playersToSave.append(player) + } + DispatchQueue.main.async { + gathered += 1 + print("gathered", gathered) + } + } catch { + print("Error fetching data for player \(player):", error) + } + } + + + do { + try tournamentStore.playerRegistrations.addOrUpdate(contentOfs: playersToSave) + } catch { + Logger.error(error) + } + } + + // Wait for all tasks to complete + await group.waitForAll() + } + + } + + gatheringDone = true + gatheringInProgress = false + } + } label: { + if gatheringInProgress { + LabeledContent { + Text("\(gathered.formatted()) / \(totalUnrankedUnsourced.formatted())") + } label: { + ProgressView("Récupérés", value: gathered, total: totalUnrankedUnsourced) + } + } else { + LabeledContent { + if gatheringDone { + Image(systemName: "checkmark").foregroundStyle(.green) + } + } label: { + Text("Check millesime") + } + } + } + + Button("Recalculer les poids") { tournament.updateWeights() _setHash()