From 1459198485cf0658bfb46915f3769c6e33a94476 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Tue, 24 Jun 2025 10:10:12 +0200 Subject: [PATCH] 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 {