From 187cc47da334ce055b6c31d4e720c90c37353878 Mon Sep 17 00:00:00 2001 From: Laurent Date: Tue, 17 Jun 2025 08:28:51 +0200 Subject: [PATCH 01/12] Adds a noSync way to delete tournaments --- PadelClubData/Data/DataStore.swift | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/PadelClubData/Data/DataStore.swift b/PadelClubData/Data/DataStore.swift index 45ed01e..f81af68 100644 --- a/PadelClubData/Data/DataStore.swift +++ b/PadelClubData/Data/DataStore.swift @@ -186,12 +186,20 @@ public class DataStore: ObservableObject { } - public func deleteTournament(_ tournament: Tournament) { + public func deleteTournament(_ tournament: Tournament, noSync: Bool = false) { let event = tournament.eventObject() let isLastTournament = event?.tournaments.count == 1 - self.tournaments.delete(instance: tournament) - if let event, isLastTournament { - self.events.delete(instance: event) + + if noSync { + self.tournaments.deleteNoSync(instance: tournament, cascading: true) + if let event, isLastTournament { + self.events.deleteNoSync(instance: event, cascading: true) + } + } else { + self.tournaments.delete(instance: tournament) + if let event, isLastTournament { + self.events.delete(instance: event) + } } StoreCenter.main.destroyStore(identifier: tournament.id) } From 7c74762b796e4de7cc51306800ea36f51c9cab94 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Tue, 17 Jun 2025 08:59:41 +0200 Subject: [PATCH 02/12] v1.2.38 remove date remove plannedStartDate --- PadelClubData/Data/Match.swift | 2 +- PadelClubData/Data/PlayerPaymentType.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/PadelClubData/Data/Match.swift b/PadelClubData/Data/Match.swift index da11675..9e2985c 100644 --- a/PadelClubData/Data/Match.swift +++ b/PadelClubData/Data/Match.swift @@ -194,7 +194,7 @@ defer { } public func cleanScheduleAndSave(_ targetStartDate: Date? = nil) { - startDate = targetStartDate ?? startDate + startDate = targetStartDate confirmed = false endDate = nil followingMatch()?.cleanScheduleAndSave(nil) diff --git a/PadelClubData/Data/PlayerPaymentType.swift b/PadelClubData/Data/PlayerPaymentType.swift index 298213f..f3c2d8e 100644 --- a/PadelClubData/Data/PlayerPaymentType.swift +++ b/PadelClubData/Data/PlayerPaymentType.swift @@ -35,9 +35,9 @@ public enum PlayerPaymentType: Int, CaseIterable, Identifiable, Codable { case .cash: return "Cash" case .lydia: - return "Lydia" + return "SumUp" case .paylib: - return "Paylib" + return "Wero" case .bankTransfer: return "Virement" case .clubHouse: From 9e8f7a4ecf821624fde8bcfec2b1a234d3574254 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Fri, 20 Jun 2025 15:12:01 +0200 Subject: [PATCH 03/12] fix planning stuff --- PadelClubData/Data/DataStore.swift | 9 ++++++++- PadelClubData/Data/Match.swift | 15 ++++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/PadelClubData/Data/DataStore.swift b/PadelClubData/Data/DataStore.swift index f81af68..5f1957b 100644 --- a/PadelClubData/Data/DataStore.swift +++ b/PadelClubData/Data/DataStore.swift @@ -106,6 +106,7 @@ public class DataStore: ObservableObject { if Store.main.fileCollectionsAllLoaded() { AutomaticPatcher.applyAllWhenApplicable() + self.resetOngoingCache() } } @@ -321,7 +322,13 @@ public class DataStore: ObservableObject { private var _lastRunningAndNextCheckDate: Date? = nil private var _cachedRunningAndNextMatches: [Match]? = nil - + + public func resetOngoingCache() { + _lastEndCheckDate = nil + _lastRunningCheckDate = nil + _lastRunningAndNextCheckDate = nil + } + public func runningAndNextMatches() -> [Match] { let dateNow : Date = Date() if let lastCheck = _lastRunningAndNextCheckDate, diff --git a/PadelClubData/Data/Match.swift b/PadelClubData/Data/Match.swift index 9e2985c..667eab6 100644 --- a/PadelClubData/Data/Match.swift +++ b/PadelClubData/Data/Match.swift @@ -31,6 +31,15 @@ final public class Match: BaseMatch, SideStorable { plannedStartDate = startDate } + public func updateStartDate(_ date: Date?, keepPlannedStartDate: Bool) { + DataStore.shared.resetOngoingCache() + let cachedPlannedStartDate = self.plannedStartDate + self.startDate = date + if keepPlannedStartDate { + self.plannedStartDate = cachedPlannedStartDate + } + } + // MARK: - public func setMatchName(_ serverName: String?) { @@ -194,7 +203,7 @@ defer { } public func cleanScheduleAndSave(_ targetStartDate: Date? = nil) { - startDate = targetStartDate + updateStartDate(targetStartDate, keepPlannedStartDate: true) confirmed = false endDate = nil followingMatch()?.cleanScheduleAndSave(nil) @@ -652,7 +661,7 @@ defer { public func validateMatch(fromStartDate: Date, toEndDate: Date, fieldSetup: MatchFieldSetup, forced: Bool = false) { if hasEnded() == false { - startDate = fromStartDate + updateStartDate(fromStartDate, keepPlannedStartDate: true) switch fieldSetup { case .fullRandom: @@ -669,7 +678,7 @@ defer { } } else { - startDate = fromStartDate + updateStartDate(fromStartDate, keepPlannedStartDate: true) endDate = toEndDate } From 20ff4508d2ae5487dc7cc2ea6c7c37d0fc8a06ea Mon Sep 17 00:00:00 2001 From: Laurent Date: Mon, 23 Jun 2025 14:49:49 +0200 Subject: [PATCH 04/12] remove licenceId from local user on disconnect --- PadelClubData/Data/DataStore.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/PadelClubData/Data/DataStore.swift b/PadelClubData/Data/DataStore.swift index 5f1957b..7648118 100644 --- a/PadelClubData/Data/DataStore.swift +++ b/PadelClubData/Data/DataStore.swift @@ -229,6 +229,7 @@ public class DataStore: ObservableObject { self.user = self._temporaryLocalUser.item ?? CustomUser.placeHolder() self.user.clubs.removeAll() + self.user.licenceId = nil } From 1459198485cf0658bfb46915f3769c6e33a94476 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Tue, 24 Jun 2025 10:10:12 +0200 Subject: [PATCH 05/12] v1.2.40 --- PadelClubData/Business.swift | 30 +++++++++++++ PadelClubData/Data/Gen/BaseTournament.swift | 9 +++- PadelClubData/Data/Gen/Tournament.json | 5 +++ PadelClubData/Data/Match.swift | 38 +++++++++++++++- PadelClubData/Data/Tournament.swift | 49 ++++++++++++++++++++- 5 files changed, 128 insertions(+), 3 deletions(-) diff --git a/PadelClubData/Business.swift b/PadelClubData/Business.swift index 422ba55..de7193f 100644 --- a/PadelClubData/Business.swift +++ b/PadelClubData/Business.swift @@ -84,6 +84,36 @@ public enum OnlineRegistrationStatus: Int { } } +public enum PaymentStatus { + case notEnabled + case notConfigured // Online payment not set up (no Stripe account) + case optionalPayment // Online payment is optional + case mandatoryRefundEnabled // Online payment is mandatory, and refunds are enabled within the refund period + case mandatoryRefundEnded // Online payment is mandatory, refunds were enabled but the refund period has ended + case mandatoryNoRefund // Online payment is mandatory, but refunds are not enabled + + /** + Returns a localized string representation of the payment status. + This method allows for easy display of the status in the UI. + */ + public func statusLocalized() -> String { + switch self { + case .notEnabled: + return "Paiement en ligne désactivé" + case .notConfigured: + return "Non configuré" + case .optionalPayment: + return "Facultatif" + case .mandatoryRefundEnabled: + return "Obligatoire avec remboursement" + case .mandatoryRefundEnded: + return "Obligatoire, remboursement terminé" + case .mandatoryNoRefund: + return "Obligatoire sans remboursement" + } + } +} + public enum PlayerFilterOption: Int, Hashable, CaseIterable, Identifiable { case all = -1 case male = 1 diff --git a/PadelClubData/Data/Gen/BaseTournament.swift b/PadelClubData/Data/Gen/BaseTournament.swift index cbc2bf0..14f8411 100644 --- a/PadelClubData/Data/Gen/BaseTournament.swift +++ b/PadelClubData/Data/Gen/BaseTournament.swift @@ -83,6 +83,7 @@ public class BaseTournament: SyncedModelObject, SyncedStorable { public var showTeamsInProg: Bool = false public var clubMemberFeeDeduction: Double? = nil public var unregisterDeltaInHours: Int = 24 + public var currencyCode: String? = nil public init( id: String = Store.randomId(), @@ -155,7 +156,8 @@ public class BaseTournament: SyncedModelObject, SyncedStorable { publishProg: Bool = false, showTeamsInProg: Bool = false, clubMemberFeeDeduction: Double? = nil, - unregisterDeltaInHours: Int = 24 + unregisterDeltaInHours: Int = 24, + currencyCode: String? = nil ) { super.init() self.id = id @@ -229,6 +231,7 @@ public class BaseTournament: SyncedModelObject, SyncedStorable { self.showTeamsInProg = showTeamsInProg self.clubMemberFeeDeduction = clubMemberFeeDeduction self.unregisterDeltaInHours = unregisterDeltaInHours + self.currencyCode = currencyCode } required public override init() { super.init() @@ -308,6 +311,7 @@ public class BaseTournament: SyncedModelObject, SyncedStorable { case _showTeamsInProg = "showTeamsInProg" case _clubMemberFeeDeduction = "clubMemberFeeDeduction" case _unregisterDeltaInHours = "unregisterDeltaInHours" + case _currencyCode = "currencyCode" } private static func _decodePayment(container: KeyedDecodingContainer) throws -> TournamentPayment? { @@ -450,6 +454,7 @@ public class BaseTournament: SyncedModelObject, SyncedStorable { self.showTeamsInProg = try container.decodeIfPresent(Bool.self, forKey: ._showTeamsInProg) ?? false self.clubMemberFeeDeduction = try container.decodeIfPresent(Double.self, forKey: ._clubMemberFeeDeduction) ?? nil self.unregisterDeltaInHours = try container.decodeIfPresent(Int.self, forKey: ._unregisterDeltaInHours) ?? 24 + self.currencyCode = try container.decodeIfPresent(String.self, forKey: ._currencyCode) ?? nil try super.init(from: decoder) } @@ -526,6 +531,7 @@ public class BaseTournament: SyncedModelObject, SyncedStorable { try container.encode(self.showTeamsInProg, forKey: ._showTeamsInProg) try container.encode(self.clubMemberFeeDeduction, forKey: ._clubMemberFeeDeduction) try container.encode(self.unregisterDeltaInHours, forKey: ._unregisterDeltaInHours) + try container.encode(self.currencyCode, forKey: ._currencyCode) try super.encode(to: encoder) } @@ -607,6 +613,7 @@ public class BaseTournament: SyncedModelObject, SyncedStorable { self.showTeamsInProg = tournament.showTeamsInProg self.clubMemberFeeDeduction = tournament.clubMemberFeeDeduction self.unregisterDeltaInHours = tournament.unregisterDeltaInHours + self.currencyCode = tournament.currencyCode } public static func relationships() -> [Relationship] { diff --git a/PadelClubData/Data/Gen/Tournament.json b/PadelClubData/Data/Gen/Tournament.json index e058cc5..f5a3b32 100644 --- a/PadelClubData/Data/Gen/Tournament.json +++ b/PadelClubData/Data/Gen/Tournament.json @@ -368,6 +368,11 @@ "name": "unregisterDeltaInHours", "type": "Int", "defaultValue": "24" + }, + { + "name": "currencyCode", + "type": "String", + "optional": true } ] } diff --git a/PadelClubData/Data/Match.swift b/PadelClubData/Data/Match.swift index 667eab6..8deb733 100644 --- a/PadelClubData/Data/Match.swift +++ b/PadelClubData/Data/Match.swift @@ -548,7 +548,7 @@ defer { updateFollowingMatchTeamScore() } - public func setUnfinishedScore(fromMatchDescriptor matchDescriptor: MatchDescriptor) { + public func setUnfinishedScore(fromMatchDescriptor matchDescriptor: MatchDescriptor, walkoutPosition: TeamPosition?) { updateScore(fromMatchDescriptor: matchDescriptor) if endDate == nil { endDate = Date() @@ -558,7 +558,41 @@ defer { } else if let startDate, let endDate, startDate >= endDate { self.startDate = endDate.addingTimeInterval(Double(-getDuration()*60)) } + + if let walkoutPosition { + let teamOne = team(walkoutPosition.otherTeam) + let teamTwo = team(walkoutPosition) + + teamOne?.hasArrived() + teamTwo?.hasArrived() + teamOne?.resetRestingTime() + teamTwo?.resetRestingTime() + + winningTeamId = teamOne?.id + losingTeamId = teamTwo?.id + let teamScoreWalkout = teamScore(walkoutPosition) ?? TeamScore(match: id, team: team(walkoutPosition)) + teamScoreWalkout.walkOut = 0 + let teamScoreWinning = teamScore(walkoutPosition.otherTeam) ?? TeamScore(match: id, team: team(walkoutPosition.otherTeam)) + teamScoreWinning.walkOut = nil + self.tournamentStore?.teamScores.addOrUpdate(contentOfs: [teamScoreWalkout, teamScoreWinning]) + } + + confirmed = true + + groupStageObject?.updateGroupStageState() + roundObject?.updateTournamentState() + if let tournament = currentTournament(), let endDate, let startDate { + if endDate.isEarlierThan(tournament.startDate) { + tournament.startDate = startDate + } + do { + try DataStore.shared.tournaments.addOrUpdate(instance: tournament) + } catch { + Logger.error(error) + } + } + updateFollowingMatchTeamScore() } public func setScore(fromMatchDescriptor matchDescriptor: MatchDescriptor) { @@ -602,8 +636,10 @@ defer { public func updateScore(fromMatchDescriptor matchDescriptor: MatchDescriptor) { let teamScoreOne = teamScore(.one) ?? TeamScore(match: id, team: team(.one)) + teamScoreOne.walkOut = nil teamScoreOne.score = matchDescriptor.teamOneScores.joined(separator: ",") let teamScoreTwo = teamScore(.two) ?? TeamScore(match: id, team: team(.two)) + teamScoreTwo.walkOut = nil teamScoreTwo.score = matchDescriptor.teamTwoScores.joined(separator: ",") if matchDescriptor.teamOneScores.last?.contains("-") == true && matchDescriptor.teamTwoScores.last?.contains("-") == false { diff --git a/PadelClubData/Data/Tournament.swift b/PadelClubData/Data/Tournament.swift index 80e3fd5..d3995c6 100644 --- a/PadelClubData/Data/Tournament.swift +++ b/PadelClubData/Data/Tournament.swift @@ -1220,7 +1220,7 @@ defer { public var entryFeeMessage: String { if let entryFee { - let message: String = "Inscription : \(entryFee.formatted(.currency(code: Locale.defaultCurrency()))) par joueur." + let message: String = "Inscription : \(entryFee.formatted(.currency(code: self.defaultCurrency()))) par joueur." return [message, self._paymentMethodMessage()].compactMap { $0 }.joined(separator: "\n") } else { return "Inscription : gratuite." @@ -2138,6 +2138,45 @@ defer { return .open } + + public func getPaymentStatus() -> PaymentStatus { + if enableOnlinePayment == false { + return .notEnabled + } + + // 1. Check if Stripe account is configured. This is the most fundamental requirement. + if (stripeAccountId == nil || stripeAccountId!.isEmpty) && isCorporateTournament == false { + return .notConfigured + } + + // 2. Check if online payment is mandatory. This determines the main branch of statuses. + if onlinePaymentIsMandatory { + // Payment is mandatory. Now check refund options. + if enableOnlinePaymentRefund { + let now = Date() + if let refundDate = refundDateLimit { + // Refund is enabled and a date limit is set. Check if the date has passed. + if now < refundDate { + return .mandatoryRefundEnabled // Mandatory, refund is currently possible + } else { + return .mandatoryRefundEnded // Mandatory, refund period has ended + } + } else { + // Refund is enabled but no specific date limit is set. Assume it's currently enabled. + return .mandatoryRefundEnabled + } + } else { + // Payment is mandatory, but refunds are explicitly not enabled. + return .mandatoryNoRefund + } + } else { + // Payment is not mandatory, meaning it's optional. + // Features like `clubMemberFeeDeduction` or `enableTimeToConfirm` + // would apply to this optional payment, but don't change its + // overall 'optionalPayment' status for this summary function. + return .optionalPayment + } + } // MARK: - Status public func shouldTournamentBeOver() async -> Bool { @@ -2316,6 +2355,14 @@ defer { groupStages().sorted(by: \.computedStartDateForSorting).first?.startDate } + public func defaultCurrency() -> String { + if let currencyCode = self.currencyCode { + return currencyCode + } else { + return Locale.defaultCurrency() + } + } + // MARK: - func insertOnServer() throws { From 0d106786e95ea0e685807cc41a7448a64756ae88 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Tue, 1 Jul 2025 07:53:43 +0200 Subject: [PATCH 06/12] add contact info --- .../Data/Gen/BasePlayerRegistration.swift | 23 ++++++++++++++++++- .../Data/Gen/PlayerRegistration.json | 15 ++++++++++++ PadelClubData/Data/TeamRegistration.swift | 4 ++-- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/PadelClubData/Data/Gen/BasePlayerRegistration.swift b/PadelClubData/Data/Gen/BasePlayerRegistration.swift index 83dbdf6..762c876 100644 --- a/PadelClubData/Data/Gen/BasePlayerRegistration.swift +++ b/PadelClubData/Data/Gen/BasePlayerRegistration.swift @@ -39,6 +39,9 @@ public class BasePlayerRegistration: SyncedModelObject, SyncedStorable { public var paymentId: String? = nil public var clubCode: String? = nil public var clubMember: Bool = false + public var contactName: String? = nil + public var contactPhoneNumber: String? = nil + public var contactEmail: String? = nil public init( id: String = Store.randomId(), @@ -67,7 +70,10 @@ public class BasePlayerRegistration: SyncedModelObject, SyncedStorable { registrationStatus: PlayerRegistration.RegistrationStatus = PlayerRegistration.RegistrationStatus.waiting, paymentId: String? = nil, clubCode: String? = nil, - clubMember: Bool = false + clubMember: Bool = false, + contactName: String? = nil, + contactPhoneNumber: String? = nil, + contactEmail: String? = nil ) { super.init() self.id = id @@ -97,6 +103,9 @@ public class BasePlayerRegistration: SyncedModelObject, SyncedStorable { self.paymentId = paymentId self.clubCode = clubCode self.clubMember = clubMember + self.contactName = contactName + self.contactPhoneNumber = contactPhoneNumber + self.contactEmail = contactEmail } required public override init() { super.init() @@ -130,6 +139,9 @@ public class BasePlayerRegistration: SyncedModelObject, SyncedStorable { case _paymentId = "paymentId" case _clubCode = "clubCode" case _clubMember = "clubMember" + case _contactName = "contactName" + case _contactPhoneNumber = "contactPhoneNumber" + case _contactEmail = "contactEmail" } required init(from decoder: Decoder) throws { @@ -161,6 +173,9 @@ public class BasePlayerRegistration: SyncedModelObject, SyncedStorable { self.paymentId = try container.decodeIfPresent(String.self, forKey: ._paymentId) ?? nil self.clubCode = try container.decodeIfPresent(String.self, forKey: ._clubCode) ?? nil self.clubMember = try container.decodeIfPresent(Bool.self, forKey: ._clubMember) ?? false + self.contactName = try container.decodeIfPresent(String.self, forKey: ._contactName) ?? nil + self.contactPhoneNumber = try container.decodeIfPresent(String.self, forKey: ._contactPhoneNumber) ?? nil + self.contactEmail = try container.decodeIfPresent(String.self, forKey: ._contactEmail) ?? nil try super.init(from: decoder) } @@ -193,6 +208,9 @@ public class BasePlayerRegistration: SyncedModelObject, SyncedStorable { try container.encode(self.paymentId, forKey: ._paymentId) try container.encode(self.clubCode, forKey: ._clubCode) try container.encode(self.clubMember, forKey: ._clubMember) + try container.encode(self.contactName, forKey: ._contactName) + try container.encode(self.contactPhoneNumber, forKey: ._contactPhoneNumber) + try container.encode(self.contactEmail, forKey: ._contactEmail) try super.encode(to: encoder) } @@ -230,6 +248,9 @@ public class BasePlayerRegistration: SyncedModelObject, SyncedStorable { self.paymentId = playerregistration.paymentId self.clubCode = playerregistration.clubCode self.clubMember = playerregistration.clubMember + self.contactName = playerregistration.contactName + self.contactPhoneNumber = playerregistration.contactPhoneNumber + self.contactEmail = playerregistration.contactEmail } public static func relationships() -> [Relationship] { diff --git a/PadelClubData/Data/Gen/PlayerRegistration.json b/PadelClubData/Data/Gen/PlayerRegistration.json index 5e723f0..047ebd2 100644 --- a/PadelClubData/Data/Gen/PlayerRegistration.json +++ b/PadelClubData/Data/Gen/PlayerRegistration.json @@ -141,6 +141,21 @@ "name": "clubMember", "type": "Bool", "defaultValue": "false" + }, + { + "name": "contactName", + "type": "String", + "optional": true + }, + { + "name": "contactPhoneNumber", + "type": "String", + "optional": true + }, + { + "name": "contactEmail", + "type": "String", + "optional": true } ] } diff --git a/PadelClubData/Data/TeamRegistration.swift b/PadelClubData/Data/TeamRegistration.swift index 441d0ec..2028f28 100644 --- a/PadelClubData/Data/TeamRegistration.swift +++ b/PadelClubData/Data/TeamRegistration.swift @@ -189,11 +189,11 @@ final public class TeamRegistration: BaseTeamRegistration, SideStorable { } public func getPhoneNumbers() -> [String] { - return players().compactMap { $0.phoneNumber }.filter({ $0.isEmpty == false }) + return players().flatMap { [$0.phoneNumber, $0.contactPhoneNumber].compacted() }.filter({ $0.isEmpty == false }) } public func getMail() -> [String] { - let mails = players().compactMap({ $0.email }) + let mails = players().flatMap({ [$0.email, $0.contactEmail].compacted() }) return mails } From e082e42d2f438037e9888d1defcb539d54c2b84c Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Tue, 1 Jul 2025 07:53:43 +0200 Subject: [PATCH 07/12] add contact info --- .../Data/Gen/BasePlayerRegistration.swift | 23 ++++++++++++++++++- .../Data/Gen/PlayerRegistration.json | 15 ++++++++++++ PadelClubData/Data/TeamRegistration.swift | 4 ++-- PadelClubData/Extensions/URL+Extensions.swift | 18 +++++++++++++++ 4 files changed, 57 insertions(+), 3 deletions(-) diff --git a/PadelClubData/Data/Gen/BasePlayerRegistration.swift b/PadelClubData/Data/Gen/BasePlayerRegistration.swift index 83dbdf6..762c876 100644 --- a/PadelClubData/Data/Gen/BasePlayerRegistration.swift +++ b/PadelClubData/Data/Gen/BasePlayerRegistration.swift @@ -39,6 +39,9 @@ public class BasePlayerRegistration: SyncedModelObject, SyncedStorable { public var paymentId: String? = nil public var clubCode: String? = nil public var clubMember: Bool = false + public var contactName: String? = nil + public var contactPhoneNumber: String? = nil + public var contactEmail: String? = nil public init( id: String = Store.randomId(), @@ -67,7 +70,10 @@ public class BasePlayerRegistration: SyncedModelObject, SyncedStorable { registrationStatus: PlayerRegistration.RegistrationStatus = PlayerRegistration.RegistrationStatus.waiting, paymentId: String? = nil, clubCode: String? = nil, - clubMember: Bool = false + clubMember: Bool = false, + contactName: String? = nil, + contactPhoneNumber: String? = nil, + contactEmail: String? = nil ) { super.init() self.id = id @@ -97,6 +103,9 @@ public class BasePlayerRegistration: SyncedModelObject, SyncedStorable { self.paymentId = paymentId self.clubCode = clubCode self.clubMember = clubMember + self.contactName = contactName + self.contactPhoneNumber = contactPhoneNumber + self.contactEmail = contactEmail } required public override init() { super.init() @@ -130,6 +139,9 @@ public class BasePlayerRegistration: SyncedModelObject, SyncedStorable { case _paymentId = "paymentId" case _clubCode = "clubCode" case _clubMember = "clubMember" + case _contactName = "contactName" + case _contactPhoneNumber = "contactPhoneNumber" + case _contactEmail = "contactEmail" } required init(from decoder: Decoder) throws { @@ -161,6 +173,9 @@ public class BasePlayerRegistration: SyncedModelObject, SyncedStorable { self.paymentId = try container.decodeIfPresent(String.self, forKey: ._paymentId) ?? nil self.clubCode = try container.decodeIfPresent(String.self, forKey: ._clubCode) ?? nil self.clubMember = try container.decodeIfPresent(Bool.self, forKey: ._clubMember) ?? false + self.contactName = try container.decodeIfPresent(String.self, forKey: ._contactName) ?? nil + self.contactPhoneNumber = try container.decodeIfPresent(String.self, forKey: ._contactPhoneNumber) ?? nil + self.contactEmail = try container.decodeIfPresent(String.self, forKey: ._contactEmail) ?? nil try super.init(from: decoder) } @@ -193,6 +208,9 @@ public class BasePlayerRegistration: SyncedModelObject, SyncedStorable { try container.encode(self.paymentId, forKey: ._paymentId) try container.encode(self.clubCode, forKey: ._clubCode) try container.encode(self.clubMember, forKey: ._clubMember) + try container.encode(self.contactName, forKey: ._contactName) + try container.encode(self.contactPhoneNumber, forKey: ._contactPhoneNumber) + try container.encode(self.contactEmail, forKey: ._contactEmail) try super.encode(to: encoder) } @@ -230,6 +248,9 @@ public class BasePlayerRegistration: SyncedModelObject, SyncedStorable { self.paymentId = playerregistration.paymentId self.clubCode = playerregistration.clubCode self.clubMember = playerregistration.clubMember + self.contactName = playerregistration.contactName + self.contactPhoneNumber = playerregistration.contactPhoneNumber + self.contactEmail = playerregistration.contactEmail } public static func relationships() -> [Relationship] { diff --git a/PadelClubData/Data/Gen/PlayerRegistration.json b/PadelClubData/Data/Gen/PlayerRegistration.json index 5e723f0..047ebd2 100644 --- a/PadelClubData/Data/Gen/PlayerRegistration.json +++ b/PadelClubData/Data/Gen/PlayerRegistration.json @@ -141,6 +141,21 @@ "name": "clubMember", "type": "Bool", "defaultValue": "false" + }, + { + "name": "contactName", + "type": "String", + "optional": true + }, + { + "name": "contactPhoneNumber", + "type": "String", + "optional": true + }, + { + "name": "contactEmail", + "type": "String", + "optional": true } ] } diff --git a/PadelClubData/Data/TeamRegistration.swift b/PadelClubData/Data/TeamRegistration.swift index 441d0ec..2028f28 100644 --- a/PadelClubData/Data/TeamRegistration.swift +++ b/PadelClubData/Data/TeamRegistration.swift @@ -189,11 +189,11 @@ final public class TeamRegistration: BaseTeamRegistration, SideStorable { } public func getPhoneNumbers() -> [String] { - return players().compactMap { $0.phoneNumber }.filter({ $0.isEmpty == false }) + return players().flatMap { [$0.phoneNumber, $0.contactPhoneNumber].compacted() }.filter({ $0.isEmpty == false }) } public func getMail() -> [String] { - let mails = players().compactMap({ $0.email }) + let mails = players().flatMap({ [$0.email, $0.contactEmail].compacted() }) return mails } diff --git a/PadelClubData/Extensions/URL+Extensions.swift b/PadelClubData/Extensions/URL+Extensions.swift index 4d33dbe..233b5db 100644 --- a/PadelClubData/Extensions/URL+Extensions.swift +++ b/PadelClubData/Extensions/URL+Extensions.swift @@ -143,6 +143,24 @@ public extension URL { return nil } + func fftImportingAnonymous() -> 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("anonymous-players:") + }) { + return Int(line.replacingOccurrences(of: "anonymous-players:", with: "")) + } + + return nil + } + func getUnrankedValue() -> Int? { // Read the contents of the file guard let fileContents = try? String(contentsOfFile: path(), encoding: .utf8) else { From 74907497ba870345863ceec20e59de2d4ff81fd8 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Wed, 9 Jul 2025 17:50:45 +0200 Subject: [PATCH 08/12] fix issue with fft search --- PadelClubData/Utils/NetworkManagerError.swift | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/PadelClubData/Utils/NetworkManagerError.swift b/PadelClubData/Utils/NetworkManagerError.swift index eb91408..ae5a766 100644 --- a/PadelClubData/Utils/NetworkManagerError.swift +++ b/PadelClubData/Utils/NetworkManagerError.swift @@ -16,13 +16,43 @@ public enum NetworkManagerError: LocalizedError { case messageNotSent //no network no error case fileNotModified case fileNotDownloaded(Int) - + case noDataReceived // New: If data is empty or nil + case htmlDecodingFailed // New: If String(data: data, encoding: .utf8) fails + case formBuildIdPrefixNotFound // New: If the prefix regex doesn't match + case formBuildIdSuffixNotFound // New: If the suffix regex doesn't match + case formBuildIdExtractionFailed // New: General parsing failure if other specific errors don't cover it + case apiError(String) // ADDED: General API error with a descriptive message + public var errorDescription: String? { switch self { case .maintenance: - return "Le site de la FFT est en maintenance" - default: - return String(describing: self) + return "Le site de la FFT est en maintenance." + case .fileNotYetAvailable: + return "Le fichier n'est pas encore disponible." + case .mailFailed: + return "L'envoi de l'e-mail a échoué." + case .mailNotSent: + return "L'e-mail n'a pas été envoyé (pas de réseau ou autre)." + case .messageFailed: + return "L'envoi du message a échoué." + case .messageNotSent: + return "Le message n'a pas été envoyé (pas de réseau ou autre)." + case .fileNotModified: + return "Le fichier n'a pas été modifié." + case .fileNotDownloaded(let statusCode): + return "Le fichier n'a pas pu être téléchargé. Code d'état : \(statusCode)." + case .noDataReceived: + return "Aucune donnée n'a été reçue du serveur." + case .htmlDecodingFailed: + return "Échec du décodage de la réponse HTML." + case .formBuildIdPrefixNotFound: + return "Impossible de trouver le début de l'ID du formulaire (form_build_id) dans la page." + case .formBuildIdSuffixNotFound: + return "Impossible de trouver la fin de l'ID du formulaire (form_build_id) dans la page." + case .formBuildIdExtractionFailed: + return "Échec général de l'extraction de l'ID du formulaire (form_build_id)." + case .apiError(let message): + return "Erreur API: \(message)" } } } From 47e15706b9811828a4e99f3828ad6862441e2bd7 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Sun, 17 Aug 2025 09:03:48 +0200 Subject: [PATCH 09/12] update 2026 rules --- PadelClubData/Data/PlayerRegistration.swift | 16 ++------ PadelClubData/Data/TeamRegistration.swift | 4 +- PadelClubData/Data/Tournament.swift | 19 +++++++++ .../Extensions/Date+Extensions.swift | 8 ++++ PadelClubData/ViewModel/PadelRule.swift | 39 +++++++++++++++---- 5 files changed, 64 insertions(+), 22 deletions(-) diff --git a/PadelClubData/Data/PlayerRegistration.swift b/PadelClubData/Data/PlayerRegistration.swift index 880e9c6..9d4511f 100644 --- a/PadelClubData/Data/PlayerRegistration.swift +++ b/PadelClubData/Data/PlayerRegistration.swift @@ -119,7 +119,9 @@ final public class PlayerRegistration: BasePlayerRegistration, SideStorable { public func playerLabel(_ displayStyle: DisplayStyle = .wide) -> String { switch displayStyle { - case .wide, .title: + case .title: + return firstName.trimmed.capitalized + " " + lastName.trimmed.capitalized + case .wide: return lastName.trimmed.capitalized + " " + firstName.trimmed.capitalized case .short: let names = lastName.components(separatedBy: .whitespaces) @@ -170,7 +172,7 @@ final public class PlayerRegistration: BasePlayerRegistration, SideStorable { let currentRank = rank ?? maleUnranked switch tournament.tournamentCategory { case .men: - let addon = PlayerRegistration.addon(for: currentRank, manMax: maleUnranked, womanMax: femaleUnranked) + let addon = tournament.addon(for: currentRank, manMax: maleUnranked, womanMax: femaleUnranked) computedRank = isMalePlayer() ? currentRank : currentRank + addon default: computedRank = currentRank @@ -279,16 +281,6 @@ final public class PlayerRegistration: BasePlayerRegistration, SideStorable { } } - public 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) } diff --git a/PadelClubData/Data/TeamRegistration.swift b/PadelClubData/Data/TeamRegistration.swift index 2028f28..865dfaf 100644 --- a/PadelClubData/Data/TeamRegistration.swift +++ b/PadelClubData/Data/TeamRegistration.swift @@ -261,10 +261,10 @@ final public class TeamRegistration: BaseTeamRegistration, SideStorable { separator: twoLines ? "\n" : " \(separator) ") } - public func teamLabelRanked(displayRank: Bool, displayTeamName: Bool) -> String { + public func teamLabelRanked(displayStyle: DisplayStyle = .wide, displayRank: Bool, displayTeamName: Bool) -> String { [ displayTeamName ? name : nil, displayRank ? seedIndex() : nil, - displayTeamName ? (name == nil ? teamLabel() : name) : teamLabel(), + displayTeamName ? (name == nil ? teamLabel(displayStyle) : name) : teamLabel(displayStyle), ].compactMap({ $0 }).joined(separator: " ") } diff --git a/PadelClubData/Data/Tournament.swift b/PadelClubData/Data/Tournament.swift index d3995c6..d8fdbb4 100644 --- a/PadelClubData/Data/Tournament.swift +++ b/PadelClubData/Data/Tournament.swift @@ -2363,6 +2363,25 @@ defer { } } + public 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, seasonYear: self.startDate.seasonYear()) + } + } + + public func coachingIsAuthorized() -> Bool { + switch startDate.seasonYear() { + case 2026: + return true + default: + return tournamentLevel.coachingIsAuthorized + } + } + // MARK: - func insertOnServer() throws { diff --git a/PadelClubData/Extensions/Date+Extensions.swift b/PadelClubData/Extensions/Date+Extensions.swift index 286b8c5..0db32fc 100644 --- a/PadelClubData/Extensions/Date+Extensions.swift +++ b/PadelClubData/Extensions/Date+Extensions.swift @@ -37,6 +37,14 @@ public enum TimeOfDay { public extension Date { + func seasonYear() -> Int { + if self.monthInt >= 9 { + return self.yearInt + 1 + } + return self.yearInt + } + + func withoutSeconds() -> Date { let calendar = Calendar.current return calendar.date(bySettingHour: calendar.component(.hour, from: self), diff --git a/PadelClubData/ViewModel/PadelRule.swift b/PadelClubData/ViewModel/PadelRule.swift index 8cc6a51..b47dc2e 100644 --- a/PadelClubData/ViewModel/PadelRule.swift +++ b/PadelClubData/ViewModel/PadelRule.swift @@ -876,7 +876,28 @@ public enum TournamentCategory: Int, Hashable, Codable, CaseIterable, Identifiab } } - public static func femaleInMaleAssimilationAddition(_ rank: Int) -> Int { + public static func femaleInMaleAssimilationAddition(_ rank: Int, seasonYear: Int?) -> Int { + switch seasonYear { + case .some(let year): + if year < 2026 { + switch rank { + case 1...10: return 400 + case 11...30: return 1000 + case 31...60: return 2000 + case 61...100: return 3500 + case 101...200: return 10000 + case 201...500: return 15000 + case 501...1000: return 25000 + case 1001...2000: return 35000 + case 2001...3000: return 45000 + default: + return 50000 + } + } + case .none: + break + } + switch rank { case 1...10: return 400 case 11...30: return 1000 @@ -887,8 +908,10 @@ public enum TournamentCategory: Int, Hashable, Codable, CaseIterable, Identifiab case 501...1000: return 25000 case 1001...2000: return 35000 case 2001...3000: return 45000 + case 3001...5000: return 55000 + case 5001...10000: return 70000 default: - return 50000 + return 90000 } } @@ -1499,7 +1522,7 @@ public enum MatchFormat: Int, Hashable, Codable, CaseIterable, Identifiable { public var isFederal: Bool { switch self { - case .megaTie, .twoSetsOfSuperTie, .singleSet, .singleSetDecisivePoint, .singleSetOfFourGames, .singleSetOfFourGamesDecisivePoint: + case .megaTie, .twoSetsOfSuperTie, .singleSet, .singleSetDecisivePoint, .singleSetOfFourGamesDecisivePoint: return false default: return true @@ -1525,11 +1548,11 @@ public enum MatchFormat: Int, Hashable, Codable, CaseIterable, Identifiable { case .twoSetsOfSuperTie: return "G" case .megaTie: - return "F" + return "H" case .singleSet: - return "H1" + return "I1" case .singleSetDecisivePoint: - return "H2" + return "I2" case .twoSetsDecisivePoint: return "A2" case .twoSetsDecisivePointSuperTie: @@ -1539,9 +1562,9 @@ public enum MatchFormat: Int, Hashable, Codable, CaseIterable, Identifiable { case .nineGamesDecisivePoint: return "D2" case .singleSetOfFourGames: - return "I1" + return "F1" case .singleSetOfFourGamesDecisivePoint: - return "I2" + return "F2" } } From a04b1983e8661210bdc61f98292add3356a5aadf Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Sun, 17 Aug 2025 09:54:43 +0200 Subject: [PATCH 10/12] increase number of months in tenup tournament gathering 3 to 4 --- PadelClubData/Extensions/Date+Extensions.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/PadelClubData/Extensions/Date+Extensions.swift b/PadelClubData/Extensions/Date+Extensions.swift index 0db32fc..bc3a7cd 100644 --- a/PadelClubData/Extensions/Date+Extensions.swift +++ b/PadelClubData/Extensions/Date+Extensions.swift @@ -273,3 +273,15 @@ public extension Date { return calendar.date(bySetting: .minute, value: 0, of: self)!.withoutSeconds() } } + +public extension Date { + var startOfCurrentMonth: Date { + let calendar = Calendar.current + return calendar.dateInterval(of: .month, for: self)?.start ?? self + } + + func addingMonths(_ months: Int) -> Date { + let calendar = Calendar.current + return calendar.date(byAdding: .month, value: months, to: self) ?? self + } +} From beaab645b9b287971064bd4374dfb33d0a16f98d Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Tue, 19 Aug 2025 17:36:44 +0200 Subject: [PATCH 11/12] =?UTF-8?q?ajout=20du=20min=20d'=C3=A9quipe=20pour?= =?UTF-8?q?=20homologation=20ajout=20d'une=20option=20pour=20terminer=20le?= =?UTF-8?q?s=20tournois=20ouverts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PadelClubData/Data/Tournament.swift | 4 ++ PadelClubData/ViewModel/PadelRule.swift | 60 +++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/PadelClubData/Data/Tournament.swift b/PadelClubData/Data/Tournament.swift index d8fdbb4..809f965 100644 --- a/PadelClubData/Data/Tournament.swift +++ b/PadelClubData/Data/Tournament.swift @@ -2382,6 +2382,10 @@ defer { } } + public func minimumNumberOfTeams() -> Int { + return federalTournamentAge.minimumNumberOfTeams(inCategory: tournamentCategory, andInLevel: tournamentLevel) + } + // MARK: - func insertOnServer() throws { diff --git a/PadelClubData/ViewModel/PadelRule.swift b/PadelClubData/ViewModel/PadelRule.swift index b47dc2e..2c8a0cb 100644 --- a/PadelClubData/ViewModel/PadelRule.swift +++ b/PadelClubData/ViewModel/PadelRule.swift @@ -166,6 +166,7 @@ public enum TournamentDifficulty { public enum FederalTournamentAge: Int, Hashable, Codable, CaseIterable, Identifiable { case unlisted = 0 + case a09_10 = 100 case a11_12 = 120 case a13_14 = 140 case a15_16 = 160 @@ -184,6 +185,8 @@ public enum FederalTournamentAge: Int, Hashable, Codable, CaseIterable, Identifi switch self { case .unlisted: return (nil, nil) + case .a09_10: + return (year - 10, year - 9) case .a11_12: return (year - 12, year - 11) case .a13_14: @@ -205,6 +208,8 @@ public enum FederalTournamentAge: Int, Hashable, Codable, CaseIterable, Identifi switch self { case .unlisted: return "Animation" + case .a09_10: + return "09/10 ans" case .a11_12: return "11/12 ans" case .a13_14: @@ -241,6 +246,8 @@ public enum FederalTournamentAge: Int, Hashable, Codable, CaseIterable, Identifi public var order: Int { switch self { case .unlisted: + return 8 + case .a09_10: return 7 case .a11_12: return 6 @@ -263,6 +270,8 @@ public enum FederalTournamentAge: Int, Hashable, Codable, CaseIterable, Identifi switch self { case .unlisted: return displayStyle == .title ? "Aucune" : "" + case .a09_10: + return "U10" case .a11_12: return "U12" case .a13_14: @@ -289,14 +298,16 @@ public enum FederalTournamentAge: Int, Hashable, Codable, CaseIterable, Identifi switch self { case .unlisted: return true + case .a09_10: + return age < 11 case .a11_12: return age < 13 case .a13_14: return age < 15 case .a15_16: - return age < 17 + return age < 17 && age >= 11 case .a17_18: - return age < 19 + return age < 19 && age >= 11 case .senior: return age >= 11 case .a45: @@ -310,6 +321,8 @@ public enum FederalTournamentAge: Int, Hashable, Codable, CaseIterable, Identifi switch self { case .unlisted: return false + case .a09_10: + return true case .a11_12: return true case .a13_14: @@ -326,6 +339,42 @@ public enum FederalTournamentAge: Int, Hashable, Codable, CaseIterable, Identifi return false } } + + func minimumNumberOfTeams(inCategory category: TournamentCategory, andInLevel level: TournamentLevel) -> Int { + + if self.isChildCategory() { + if level == .p250, category == .men { + return 8 + } + return 4 + } else { + switch level { + case .p25, .p100, .p250: + if category == .women { + return 4 + } + if level == .p100 { + return 8 + } + return 12 + case .p500: + if category == .women { + return 8 + } + return 16 + case .p1000: + if category == .women { + return 16 + } + return 24 + + case .p1500, .p2000: + return 24 + default: + return -1 + } + } + } } public enum TournamentLevel: Int, Hashable, Codable, CaseIterable, Identifiable { @@ -432,7 +481,12 @@ public enum TournamentLevel: Int, Hashable, Codable, CaseIterable, Identifiable } } - public func minimumPlayerRank(category: TournamentCategory, ageCategory: FederalTournamentAge) -> Int { + public func minimumPlayerRank(category: TournamentCategory, ageCategory: FederalTournamentAge, seasonYear: Int) -> Int { + + if seasonYear == 2026, category == .mix { + return 0 + } + switch self { case .p25: switch ageCategory { From 4579ced7a29e2ee6e60dec8298f490aeed592660 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Wed, 20 Aug 2025 14:09:21 +0200 Subject: [PATCH 12/12] =?UTF-8?q?possiblit=C3=A9=20de=20lancer=20l'horaire?= =?UTF-8?q?=20intelligent=20sur=20tous=20les=20tournois=20d'un=20evenement?= =?UTF-8?q?=20en=20une=20fois?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PadelClubData/Data/MatchScheduler.swift | 3 +-- PadelClubData/Data/Tournament.swift | 17 +++++++++++++++++ PadelClubData/Extensions/Date+Extensions.swift | 4 ++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/PadelClubData/Data/MatchScheduler.swift b/PadelClubData/Data/MatchScheduler.swift index 8999fa0..5f16a7d 100644 --- a/PadelClubData/Data/MatchScheduler.swift +++ b/PadelClubData/Data/MatchScheduler.swift @@ -138,7 +138,6 @@ final public class MatchScheduler: BaseMatchScheduler, SideStorable { } public func groupStageDispatcher(groupStages: [GroupStage], startingDate: Date) -> GroupStageMatchDispatcher { - let _groupStages = groupStages // Get the maximum count of matches in any group @@ -839,7 +838,7 @@ final public class MatchScheduler: BaseMatchScheduler, SideStorable { } - if tournament.dayDuration > 1 && (lastDate.timeOfDay == .evening || errorFormat) { + if tournament.dayDuration > 1 && (lastDate.timeOfDay == .evening || lastDate.timeOfDay == .night || errorFormat) { bracketStartDate = lastDate.tomorrowAtNine } diff --git a/PadelClubData/Data/Tournament.swift b/PadelClubData/Data/Tournament.swift index 809f965..d50bb0f 100644 --- a/PadelClubData/Data/Tournament.swift +++ b/PadelClubData/Data/Tournament.swift @@ -2386,6 +2386,23 @@ defer { return federalTournamentAge.minimumNumberOfTeams(inCategory: tournamentCategory, andInLevel: tournamentLevel) } + public func removeAllDates() { + let allMatches = allMatches() + allMatches.forEach({ + $0.startDate = nil + $0.confirmed = false + }) + self.tournamentStore?.matches.addOrUpdate(contentOfs: allMatches) + + let allGroupStages = groupStages() + allGroupStages.forEach({ $0.startDate = nil }) + self.tournamentStore?.groupStages.addOrUpdate(contentOfs: allGroupStages) + + let allRounds = allRounds() + allRounds.forEach({ $0.startDate = nil }) + self.tournamentStore?.rounds.addOrUpdate(contentOfs: allRounds) + } + // MARK: - func insertOnServer() throws { diff --git a/PadelClubData/Extensions/Date+Extensions.swift b/PadelClubData/Extensions/Date+Extensions.swift index bc3a7cd..a9878f0 100644 --- a/PadelClubData/Extensions/Date+Extensions.swift +++ b/PadelClubData/Extensions/Date+Extensions.swift @@ -214,6 +214,10 @@ public extension Date { Calendar.current.component(.day, from: self) } + var hourInt: Int { + Calendar.current.component(.hour, from: self) + } + var startOfDay: Date { Calendar.current.startOfDay(for: self) }