sync3
Laurent 5 months ago
commit c3d3c701bb
  1. 24
      PadelClubData.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
  2. 22
      PadelClubData/Data/Court.swift
  3. 22
      PadelClubData/Data/CustomUser.swift
  4. 3
      PadelClubData/Data/DataStore.swift
  5. 29
      PadelClubData/Data/DateInterval.swift
  6. 2
      PadelClubData/Data/DrawLog.swift
  7. 55
      PadelClubData/Data/Event.swift
  8. 16
      PadelClubData/Data/Gen/BaseMatchScheduler.swift
  9. 16
      PadelClubData/Data/Gen/BasePlayerRegistration.swift
  10. 15
      PadelClubData/Data/Gen/BaseTeamRegistration.swift
  11. 16
      PadelClubData/Data/Gen/BaseTournament.swift
  12. 10
      PadelClubData/Data/Gen/MatchScheduler.json
  13. 10
      PadelClubData/Data/Gen/PlayerRegistration.json
  14. 7
      PadelClubData/Data/Gen/TeamRegistration.json
  15. 10
      PadelClubData/Data/Gen/Tournament.json
  16. 59
      PadelClubData/Data/Match.swift
  17. 119
      PadelClubData/Data/MatchScheduler.swift
  18. 76
      PadelClubData/Data/PlayerRegistration.swift
  19. 48
      PadelClubData/Data/Round.swift
  20. 78
      PadelClubData/Data/TeamRegistration.swift
  21. 25
      PadelClubData/Data/TeamScore.swift
  22. 148
      PadelClubData/Data/Tournament.swift
  23. 4
      PadelClubData/ViewModel/MatchDescriptor.swift
  24. 48
      PadelClubData/ViewModel/PadelRule.swift

@ -0,0 +1,24 @@
{
"originHash" : "86635982bfc3fdf16c7186cd33e77b4dc10a24ff9127aaee5f4f0a3676ae2966",
"pins" : [
{
"identity" : "swift-algorithms",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-algorithms.git",
"state" : {
"revision" : "87e50f483c54e6efd60e885f7f5aa946cee68023",
"version" : "1.2.1"
}
},
{
"identity" : "swift-numerics",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-numerics.git",
"state" : {
"revision" : "e0ec0f5f3af6f3e4d5e7a19d2af26b481acb6ba8",
"version" : "1.0.3"
}
}
],
"version" : 3
}

@ -16,26 +16,6 @@ final public class Court: BaseCourt {
lhs.id == rhs.id
}
public 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()
}
public func courtTitle() -> String {
self.name ?? courtIndexTitle()
}
@ -45,7 +25,7 @@ final public class Court: BaseCourt {
}
public static func courtIndexedTitle(atIndex index: Int) -> String {
("Terrain #" + (index + 1).formatted())
("Piste #" + (index + 1).formatted())
}
public func clubObject() -> Club? {

@ -97,28 +97,6 @@ public class CustomUser: BaseCustomUser, UserBase {
//
// var deviceId: String?
public 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

@ -111,6 +111,9 @@ public class DataStore: ObservableObject {
}
fileprivate func _fixMissingClubCreatorIfNecessary(_ clubsCollection: SyncedCollection<Club>) {
if self.user.clubs.count > 0 { return }
for club in clubsCollection {
if let userId = StoreCenter.main.userId, club.creator == nil {
club.creator = userId

@ -12,35 +12,6 @@ import LeStorage
@Observable
final public 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
public 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
}

@ -91,7 +91,7 @@ public enum DrawType: Int, Codable {
case .groupStage:
return "Poule"
case .court:
return "Terrain"
return "Piste"
}
}
}

@ -40,7 +40,7 @@ final public class Event: BaseEvent {
// MARK: - Computed dependencies
public var tournaments: [Tournament] {
DataStore.shared.tournaments.filter { $0.event == self.id && $0.isDeleted == false }
DataStore.shared.tournaments.filter { $0.event == self.id && $0.isDeleted == false }.sorted(by: \.startDate)
}
public func clubObject() -> Club? {
@ -92,6 +92,59 @@ final public class Event: BaseEvent {
tournaments.filter({ $0.isFree() == false && $0.isCanceled == false && $0.isDeleted == false })
}
public func shareURL() -> URL? {
return URL(string: URLs.main.url.appending(path: "event/\(id)").absoluteString.removingPercentEncoding!)
}
public func formattedDateInterval() -> String {
let tournaments = self.tournaments
guard !tournaments.isEmpty else {
return "" // Handle empty tournament list
}
let firstTournament = tournaments.first!
let lastTournament = tournaments.last!
// Helper to check if two dates are on the same day
func isSameDay(date1: Date, date2: Date) -> Bool {
return Calendar.current.isDate(date1, inSameDayAs: date2)
}
// Scenario 1: Only one tournament
if tournaments.count == 1 {
return firstTournament.startDate.formattedAsDate()
}
// Scenario 2: Multiple tournaments, but all start and end on the same day (single-day event overall)
let overallEventIsSingleDay = isSameDay(date1: firstTournament.startDate, date2: lastTournament.startDate)
if overallEventIsSingleDay {
return firstTournament.startDate.formattedAsDate()
}
// Scenario 3: Multiple tournaments, spanning multiple days (requires "Du ... au ...")
let startDay = firstTournament.startDate.localizedDay()
let endDay = lastTournament.startDate.localizedDay()
let startMonthYear = firstTournament.startDate.monthYearFormatted
let endMonthYear = lastTournament.startDate.monthYearFormatted
let calendar = Calendar.current
let firstStartComponents = calendar.dateComponents([.year, .month], from: firstTournament.startDate)
let lastEndComponents = calendar.dateComponents([.year, .month], from: lastTournament.startDate)
if firstStartComponents.year == lastEndComponents.year && firstStartComponents.month == lastEndComponents.month {
// Same month and year: "Du 13 au 15 Juin 2025"
return "Du \(startDay) au \(endDay) \(startMonthYear)"
} else if firstStartComponents.year == lastEndComponents.year {
// Different months, same year: "Du 13 Juin au 15 Juillet 2025"
return "Du \(startDay) \(startMonthYear) au \(endDay) \(endMonthYear)"
} else {
// Different years: "Du 13 Juin 2025 au 15 Juin 2026"
return "Du \(startDay) \(startMonthYear) au \(endDay) \(endMonthYear)"
}
}
func insertOnServer() throws {
DataStore.shared.events.writeChangeAndInsertOnServer(instance: self)
for tournament in self.tournaments {

@ -29,6 +29,8 @@ public class BaseMatchScheduler: BaseModelObject, Storable {
public var shouldTryToFillUpCourtsAvailable: Bool = false
public var courtsAvailable: Set<Int> = Set<Int>()
public var simultaneousStart: Bool = true
public var groupStageRotationDifference: Int = 0
public var accountGroupStageBreakTime: Bool = false
public init(
id: String = Store.randomId(),
@ -46,7 +48,9 @@ public class BaseMatchScheduler: BaseModelObject, Storable {
overrideCourtsUnavailability: Bool = false,
shouldTryToFillUpCourtsAvailable: Bool = false,
courtsAvailable: Set<Int> = Set<Int>(),
simultaneousStart: Bool = true
simultaneousStart: Bool = true,
groupStageRotationDifference: Int = 0,
accountGroupStageBreakTime: Bool = false
) {
super.init()
self.id = id
@ -65,6 +69,8 @@ public class BaseMatchScheduler: BaseModelObject, Storable {
self.shouldTryToFillUpCourtsAvailable = shouldTryToFillUpCourtsAvailable
self.courtsAvailable = courtsAvailable
self.simultaneousStart = simultaneousStart
self.groupStageRotationDifference = groupStageRotationDifference
self.accountGroupStageBreakTime = accountGroupStageBreakTime
}
required public override init() {
super.init()
@ -87,6 +93,8 @@ public class BaseMatchScheduler: BaseModelObject, Storable {
case _shouldTryToFillUpCourtsAvailable = "shouldTryToFillUpCourtsAvailable"
case _courtsAvailable = "courtsAvailable"
case _simultaneousStart = "simultaneousStart"
case _groupStageRotationDifference = "groupStageRotationDifference"
case _accountGroupStageBreakTime = "accountGroupStageBreakTime"
}
required init(from decoder: Decoder) throws {
@ -107,6 +115,8 @@ public class BaseMatchScheduler: BaseModelObject, Storable {
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
self.groupStageRotationDifference = try container.decodeIfPresent(Int.self, forKey: ._groupStageRotationDifference) ?? 0
self.accountGroupStageBreakTime = try container.decodeIfPresent(Bool.self, forKey: ._accountGroupStageBreakTime) ?? false
try super.init(from: decoder)
}
@ -128,6 +138,8 @@ public class BaseMatchScheduler: BaseModelObject, Storable {
try container.encode(self.shouldTryToFillUpCourtsAvailable, forKey: ._shouldTryToFillUpCourtsAvailable)
try container.encode(self.courtsAvailable, forKey: ._courtsAvailable)
try container.encode(self.simultaneousStart, forKey: ._simultaneousStart)
try container.encode(self.groupStageRotationDifference, forKey: ._groupStageRotationDifference)
try container.encode(self.accountGroupStageBreakTime, forKey: ._accountGroupStageBreakTime)
try super.encode(to: encoder)
}
@ -153,6 +165,8 @@ public class BaseMatchScheduler: BaseModelObject, Storable {
self.shouldTryToFillUpCourtsAvailable = matchscheduler.shouldTryToFillUpCourtsAvailable
self.courtsAvailable = matchscheduler.courtsAvailable
self.simultaneousStart = matchscheduler.simultaneousStart
self.groupStageRotationDifference = matchscheduler.groupStageRotationDifference
self.accountGroupStageBreakTime = matchscheduler.accountGroupStageBreakTime
}
public static func parentRelationships() -> [Relationship] {

@ -38,6 +38,8 @@ public class BasePlayerRegistration: SyncedModelObject, SyncedStorable {
public var timeToConfirm: Date? = nil
public var registrationStatus: PlayerRegistration.RegistrationStatus = PlayerRegistration.RegistrationStatus.waiting
public var paymentId: String? = nil
public var clubCode: String? = nil
public var clubMember: Bool = false
public init(
id: String = Store.randomId(),
@ -64,7 +66,9 @@ public class BasePlayerRegistration: SyncedModelObject, SyncedStorable {
registeredOnline: Bool = false,
timeToConfirm: Date? = nil,
registrationStatus: PlayerRegistration.RegistrationStatus = PlayerRegistration.RegistrationStatus.waiting,
paymentId: String? = nil
paymentId: String? = nil,
clubCode: String? = nil,
clubMember: Bool = false
) {
super.init()
self.id = id
@ -92,6 +96,8 @@ public class BasePlayerRegistration: SyncedModelObject, SyncedStorable {
self.timeToConfirm = timeToConfirm
self.registrationStatus = registrationStatus
self.paymentId = paymentId
self.clubCode = clubCode
self.clubMember = clubMember
}
required public override init() {
super.init()
@ -123,6 +129,8 @@ public class BasePlayerRegistration: SyncedModelObject, SyncedStorable {
case _timeToConfirm = "timeToConfirm"
case _registrationStatus = "registrationStatus"
case _paymentId = "paymentId"
case _clubCode = "clubCode"
case _clubMember = "clubMember"
}
required init(from decoder: Decoder) throws {
@ -152,6 +160,8 @@ public class BasePlayerRegistration: SyncedModelObject, SyncedStorable {
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
self.clubCode = try container.decodeIfPresent(String.self, forKey: ._clubCode) ?? nil
self.clubMember = try container.decodeIfPresent(Bool.self, forKey: ._clubMember) ?? false
try super.init(from: decoder)
}
@ -182,6 +192,8 @@ public class BasePlayerRegistration: SyncedModelObject, SyncedStorable {
try container.encode(self.timeToConfirm, forKey: ._timeToConfirm)
try container.encode(self.registrationStatus, forKey: ._registrationStatus)
try container.encode(self.paymentId, forKey: ._paymentId)
try container.encode(self.clubCode, forKey: ._clubCode)
try container.encode(self.clubMember, forKey: ._clubMember)
try super.encode(to: encoder)
}
@ -217,6 +229,8 @@ public class BasePlayerRegistration: SyncedModelObject, SyncedStorable {
self.timeToConfirm = playerregistration.timeToConfirm
self.registrationStatus = playerregistration.registrationStatus
self.paymentId = playerregistration.paymentId
self.clubCode = playerregistration.clubCode
self.clubMember = playerregistration.clubMember
}
public static func parentRelationships() -> [Relationship] {

@ -16,7 +16,7 @@ public class BaseTeamRegistration: SyncedModelObject, SyncedStorable {
public var id: String = Store.randomId()
public var tournament: String = ""
public var groupStage: String? = nil
public var registrationDate: Date? = nil
public var registrationDate: Date? = Date()
public var callDate: Date? = nil
public var bracketPosition: Int? = nil
public var groupStagePosition: Int? = nil
@ -34,12 +34,13 @@ public class BaseTeamRegistration: SyncedModelObject, SyncedStorable {
public var qualified: Bool = false
public var finalRanking: Int? = nil
public var pointsEarned: Int? = nil
public var uniqueRandomIndex: Int = 0
public init(
id: String = Store.randomId(),
tournament: String = "",
groupStage: String? = nil,
registrationDate: Date? = nil,
registrationDate: Date? = Date(),
callDate: Date? = nil,
bracketPosition: Int? = nil,
groupStagePosition: Int? = nil,
@ -56,7 +57,8 @@ public class BaseTeamRegistration: SyncedModelObject, SyncedStorable {
confirmationDate: Date? = nil,
qualified: Bool = false,
finalRanking: Int? = nil,
pointsEarned: Int? = nil
pointsEarned: Int? = nil,
uniqueRandomIndex: Int = 0
) {
super.init()
self.id = id
@ -80,6 +82,7 @@ public class BaseTeamRegistration: SyncedModelObject, SyncedStorable {
self.qualified = qualified
self.finalRanking = finalRanking
self.pointsEarned = pointsEarned
self.uniqueRandomIndex = uniqueRandomIndex
}
required public override init() {
super.init()
@ -107,6 +110,7 @@ public class BaseTeamRegistration: SyncedModelObject, SyncedStorable {
case _qualified = "qualified"
case _finalRanking = "finalRanking"
case _pointsEarned = "pointsEarned"
case _uniqueRandomIndex = "uniqueRandomIndex"
}
required init(from decoder: Decoder) throws {
@ -114,7 +118,7 @@ public class BaseTeamRegistration: SyncedModelObject, SyncedStorable {
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.registrationDate = try container.decodeIfPresent(Date.self, forKey: ._registrationDate) ?? Date()
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
@ -132,6 +136,7 @@ public class BaseTeamRegistration: SyncedModelObject, SyncedStorable {
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
self.uniqueRandomIndex = try container.decodeIfPresent(Int.self, forKey: ._uniqueRandomIndex) ?? 0
try super.init(from: decoder)
}
@ -158,6 +163,7 @@ public class BaseTeamRegistration: SyncedModelObject, SyncedStorable {
try container.encode(self.qualified, forKey: ._qualified)
try container.encode(self.finalRanking, forKey: ._finalRanking)
try container.encode(self.pointsEarned, forKey: ._pointsEarned)
try container.encode(self.uniqueRandomIndex, forKey: ._uniqueRandomIndex)
try super.encode(to: encoder)
}
@ -193,6 +199,7 @@ public class BaseTeamRegistration: SyncedModelObject, SyncedStorable {
self.qualified = teamregistration.qualified
self.finalRanking = teamregistration.finalRanking
self.pointsEarned = teamregistration.pointsEarned
self.uniqueRandomIndex = teamregistration.uniqueRandomIndex
}
public static func parentRelationships() -> [Relationship] {

@ -82,6 +82,8 @@ public class BaseTournament: SyncedModelObject, SyncedStorable {
public var isTemplate: Bool = false
public var publishProg: Bool = false
public var showTeamsInProg: Bool = false
public var clubMemberFeeDeduction: Double? = nil
public var unregisterDeltaInHours: Int = 24
public init(
id: String = Store.randomId(),
@ -152,7 +154,9 @@ public class BaseTournament: SyncedModelObject, SyncedStorable {
isCorporateTournament: Bool = false,
isTemplate: Bool = false,
publishProg: Bool = false,
showTeamsInProg: Bool = false
showTeamsInProg: Bool = false,
clubMemberFeeDeduction: Double? = nil,
unregisterDeltaInHours: Int = 24
) {
super.init()
self.id = id
@ -224,6 +228,8 @@ public class BaseTournament: SyncedModelObject, SyncedStorable {
self.isTemplate = isTemplate
self.publishProg = publishProg
self.showTeamsInProg = showTeamsInProg
self.clubMemberFeeDeduction = clubMemberFeeDeduction
self.unregisterDeltaInHours = unregisterDeltaInHours
}
required public override init() {
super.init()
@ -301,6 +307,8 @@ public class BaseTournament: SyncedModelObject, SyncedStorable {
case _isTemplate = "isTemplate"
case _publishProg = "publishProg"
case _showTeamsInProg = "showTeamsInProg"
case _clubMemberFeeDeduction = "clubMemberFeeDeduction"
case _unregisterDeltaInHours = "unregisterDeltaInHours"
}
private static func _decodePayment(container: KeyedDecodingContainer<CodingKeys>) throws -> TournamentPayment? {
@ -441,6 +449,8 @@ public class BaseTournament: SyncedModelObject, SyncedStorable {
self.isTemplate = try container.decodeIfPresent(Bool.self, forKey: ._isTemplate) ?? false
self.publishProg = try container.decodeIfPresent(Bool.self, forKey: ._publishProg) ?? false
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
try super.init(from: decoder)
}
@ -515,6 +525,8 @@ public class BaseTournament: SyncedModelObject, SyncedStorable {
try container.encode(self.isTemplate, forKey: ._isTemplate)
try container.encode(self.publishProg, forKey: ._publishProg)
try container.encode(self.showTeamsInProg, forKey: ._showTeamsInProg)
try container.encode(self.clubMemberFeeDeduction, forKey: ._clubMemberFeeDeduction)
try container.encode(self.unregisterDeltaInHours, forKey: ._unregisterDeltaInHours)
try super.encode(to: encoder)
}
@ -594,6 +606,8 @@ public class BaseTournament: SyncedModelObject, SyncedStorable {
self.isTemplate = tournament.isTemplate
self.publishProg = tournament.publishProg
self.showTeamsInProg = tournament.showTeamsInProg
self.clubMemberFeeDeduction = tournament.clubMemberFeeDeduction
self.unregisterDeltaInHours = tournament.unregisterDeltaInHours
}
public static func parentRelationships() -> [Relationship] {

@ -84,6 +84,16 @@
"name": "simultaneousStart",
"type": "Bool",
"defaultValue": "true"
},
{
"name": "groupStageRotationDifference",
"type": "Int",
"defaultValue": "0"
},
{
"name": "accountGroupStageBreakTime",
"type": "Bool",
"defaultValue": "false"
}
]
}

@ -130,6 +130,16 @@
"name": "paymentId",
"type": "String",
"optional": true
},
{
"name": "clubCode",
"type": "String",
"optional": true
},
{
"name": "clubMember",
"type": "Bool",
"defaultValue": "false"
}
]
}

@ -25,6 +25,7 @@
{
"name": "registrationDate",
"type": "Date",
"defaultValue": "Date()",
"optional": true
},
{
@ -111,6 +112,12 @@
"name": "pointsEarned",
"type": "Int",
"optional": true
},
{
"name": "uniqueRandomIndex",
"type": "Int",
"defaultValue": "0",
"optional": false
}
]
}

@ -357,6 +357,16 @@
"name": "showTeamsInProg",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "clubMemberFeeDeduction",
"type": "Double",
"optional": true
},
{
"name": "unregisterDeltaInHours",
"type": "Int",
"defaultValue": "24"
}
]
}

@ -22,25 +22,31 @@ final public class Match: BaseMatch, SideStorable {
public var byeState: Bool = false
public init(round: String? = nil, groupStage: String? = nil, startDate: Date? = nil, endDate: Date? = nil, index: Int, 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(round: round, groupStage: groupStage, startDate: startDate, endDate: endDate, index: index, format: format, servingTeamId: servingTeamId, winningTeamId: winningTeamId, losingTeamId: losingTeamId, name: name, disabled: disabled, courtIndex: courtIndex, confirmed: confirmed)
}
required init(from decoder: Decoder) throws {
try super.init(from: decoder)
}
required public init() {
super.init()
let drawLog = DrawLog()
}
//<<<<<<< HEAD
// public init(round: String? = nil, groupStage: String? = nil, startDate: Date? = nil, endDate: Date? = nil, index: Int, 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(round: round, groupStage: groupStage, startDate: startDate, endDate: endDate, index: index, format: format, servingTeamId: servingTeamId, winningTeamId: winningTeamId, losingTeamId: losingTeamId, name: name, disabled: disabled, courtIndex: courtIndex, confirmed: confirmed)
//
// }
//
// required init(from decoder: Decoder) throws {
// try super.init(from: decoder)
// }
//
// required public init() {
// super.init()
//
// let drawLog = DrawLog()
// }
//
//=======
//>>>>>>> main
// MARK: - DidSet
public override func didSetStartDate() {
if hasStarted() {
return
}
if self.roundValue()?.tournamentObject()?.hasStarted() == false {
plannedStartDate = startDate
} else if self.groupStageValue()?.tournamentObject()?.hasStarted() == false {
@ -127,7 +133,7 @@ defer {
}
#endif
if roundObject?.groupStageLoserBracket == true {
if roundObject?.isGroupStageLoserBracket() == true {
return "\(index)\(index.ordinalFormattedSuffix()) place"
}
if let groupStageObject {
@ -187,13 +193,22 @@ defer {
}
}
public func scoreLabel() -> String {
public func scoreLabel(winnerFirst: Bool = false) -> String {
if hasWalkoutTeam() == true {
return "WO"
}
let scoreOne = teamScore(.one)?.score?.components(separatedBy: ",") ?? []
let scoreTwo = teamScore(.two)?.score?.components(separatedBy: ",") ?? []
let tuples = zip(scoreOne, scoreTwo).map { ($0, $1) }
var tuples = zip(scoreOne, scoreTwo).map { ($0, $1) }
if winnerFirst {
if teamWon(atPosition: .one) {
} else {
tuples = zip(scoreTwo, scoreOne).map { ($0, $1) }
}
}
let scores = tuples.map { $0 + "/" + $1 }.joined(separator: " ")
return scores
}
@ -935,7 +950,7 @@ defer {
public var isLoserBracket: Bool {
if let roundObject {
if roundObject.parent != nil || roundObject.groupStageLoserBracket {
if roundObject.parent != nil || roundObject.isGroupStageLoserBracket() {
return true
}
}
@ -1062,6 +1077,10 @@ defer {
[MatchSpot(match: self, teamPosition: .one), MatchSpot(match: self, teamPosition: .two)]
}
public func initialStartDate() -> Date? {
plannedStartDate ?? startDate
}
func insertOnServer() {
self.tournamentStore?.matches.writeChangeAndInsertOnServer(instance: self)
for teamScore in self.teamScores {

@ -11,44 +11,7 @@ import SwiftUI
@Observable
final public 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))
@ -67,6 +30,14 @@ final public class MatchScheduler: BaseMatchScheduler, SideStorable {
return Store.main.findById(tournament)
}
func getGroupStageRotationDifference() -> Int {
if rotationDifferenceIsImportant {
return groupStageRotationDifference
} else {
return 0
}
}
@discardableResult
public func updateGroupStageSchedule(tournament: Tournament, specificGroupStage: GroupStage? = nil, atStep step: Int = 0, startDate: Date? = nil) -> Date {
let computedGroupStageChunkCount = groupStageChunkCount ?? tournament.getGroupStageChunkValue()
@ -105,8 +76,15 @@ final public class MatchScheduler: BaseMatchScheduler, SideStorable {
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
var estimatedDuration = match.matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration)
if accountGroupStageBreakTime {
estimatedDuration += match.matchFormat.breakTime.breakTime * 60
}
var timeIntervalToAdd = (Double(matchSchedule.rotationIndex)) * Double(estimatedDuration) * 60
timeIntervalToAdd += Double(matchSchedule.rotationIndex) * Double(self.getGroupStageRotationDifference()) * Double(match.matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration)) * 60
if let startDate = match.groupStageObject?.startDate {
let matchStartDate = startDate.addingTimeInterval(timeIntervalToAdd)
match.startDate = matchStartDate
@ -129,8 +107,16 @@ final public class MatchScheduler: BaseMatchScheduler, SideStorable {
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
var estimatedDuration = match.matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration)
if accountGroupStageBreakTime {
estimatedDuration += match.matchFormat.breakTime.breakTime * 60
}
var timeIntervalToAdd = (Double(matchSchedule.rotationIndex)) * Double(estimatedDuration) * 60
timeIntervalToAdd += Double(matchSchedule.rotationIndex) * Double(self.getGroupStageRotationDifference()) * Double(match.matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration)) * 60
if let startDate = match.groupStageObject?.startDate {
let matchStartDate = startDate.addingTimeInterval(timeIntervalToAdd)
match.startDate = matchStartDate
@ -207,8 +193,16 @@ final public class MatchScheduler: BaseMatchScheduler, SideStorable {
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
var estimatedDuration = match.matchFormat.getEstimatedDuration(additionalEstimationDuration)
if accountGroupStageBreakTime {
estimatedDuration += match.matchFormat.breakTime.breakTime * 60
}
var timeIntervalToAdd = (Double(rotationIndex)) * Double(estimatedDuration) * 60
timeIntervalToAdd += Double(rotationIndex) * Double(self.getGroupStageRotationDifference()) * Double(match.matchFormat.getEstimatedDuration(additionalEstimationDuration)) * 60
let rotationStartDate: Date = startingDate.addingTimeInterval(timeIntervalToAdd)
let courtsUnavailable = courtsUnavailable(startDate: rotationStartDate, duration: match.matchFormat.getEstimatedDuration(additionalEstimationDuration), courtsUnavailability: courtsUnavailability)
@ -822,7 +816,31 @@ final public class MatchScheduler: BaseMatchScheduler, SideStorable {
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)
let allMatches = tournament.groupStagesMatches().filter({ $0.hasEnded() == false && $0.hasStarted() == false })
let groupMatchesByDay = tournament.groupMatchesByDay(matches: allMatches)
let countedSet = tournament.matchCountPerDay(matchesByDay: groupMatchesByDay)
var bracketStartDate = lastDate
print("lastDate", lastDate)
var errorFormat = false
let dates = countedSet.keys
if let first = dates.first, let matchesInLastDate = countedSet[first] {
let totalMatches = matchesInLastDate.totalCount()
let count = matchesInLastDate.count(for: tournament.groupStageMatchFormat)
let totalForThisFormat = tournament.groupStageMatchFormat.maximumMatchPerDay(for: totalMatches)
print(totalMatches, count, totalForThisFormat)
errorFormat = count >= totalForThisFormat
print("bracketStartDate", bracketStartDate)
}
if tournament.dayDuration > 1 && (lastDate.timeOfDay == .evening || errorFormat) {
bracketStartDate = lastDate.tomorrowAtNine
}
return updateBracketSchedule(tournament: tournament, fromRoundId: nil, fromMatchId: nil, startDate: bracketStartDate)
}
}
@ -886,3 +904,14 @@ extension Match {
return groupStageObject._matchUp(for: index).map { groupStageObject.id + "_\($0)" }
}
}
// Extension to compute the total count in an NSCountedSet
extension NSCountedSet {
func totalCount() -> Int {
var total = 0
for element in self {
total += self.count(for: element)
}
return total
}
}

@ -24,43 +24,7 @@ final public class PlayerRegistration: BasePlayerRegistration, SideStorable {
return "créé par vous-même"
}
}
}
public 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
}
required init(from decoder: any Decoder) throws {
try super.init(from: decoder)
}
required public init() {
super.init()
}
}
public var tournamentStore: TournamentStore? {
guard let storeId else {
@ -204,6 +168,13 @@ final public class PlayerRegistration: BasePlayerRegistration, SideStorable {
}
}
public func setClubMember(for tournament: Tournament) {
guard let clubCode, clubCode.isEmpty == false, let code = tournament.eventObject()?.clubObject()?.code, code.isEmpty == false else {
return
}
self.clubMember = clubCode.replaceCharactersFromSet(characterSet: .whitespaces).caseInsensitiveCompare(code.replaceCharactersFromSet(characterSet: .whitespaces)) == .orderedSame
}
public func isMalePlayer() -> Bool {
sex == .male
}
@ -230,6 +201,37 @@ final public class PlayerRegistration: BasePlayerRegistration, SideStorable {
registrationStatus = .confirmed
}
public func paidAmount(_ tournament: Tournament, accountForGiftOrForfeit: Bool = false) -> Double {
if accountForGiftOrForfeit == false, paymentType == .gift {
return 0.0
}
if accountForGiftOrForfeit == false, paymentType == .forfeit {
return 0.0
}
if hasPaid(), let entryFee = tournament.entryFee {
if clubMember, let clubMemberFeeDeduction = tournament.clubMemberFeeDeduction {
return entryFee - clubMemberFeeDeduction
} else {
return entryFee
}
} else {
return 0.0
}
}
public func remainingAmount(_ tournament: Tournament) -> Double {
if let entryFee = tournament.entryFee {
if clubMember, let clubMemberFeeDeduction = tournament.clubMemberFeeDeduction {
return entryFee - clubMemberFeeDeduction - paidAmount(tournament, accountForGiftOrForfeit: true)
} else {
return entryFee - paidAmount(tournament, accountForGiftOrForfeit: true)
}
} else {
return 0.0
}
}
public enum PlayerDataSource: Int, Codable {
case frenchFederation = 0
case beachPadel = 1

@ -21,28 +21,6 @@ final public class Round: BaseRound, SideStorable {
@ObservationIgnored
private var _cachedLoserRoundsAndChildren: [Round]?
public init(tournament: String, index: Int, parent: String? = nil, matchFormat: MatchFormat? = nil, startDate: Date? = nil, groupStageLoserBracket: Bool = false, loserBracketMode: LoserBracketMode = .automatic) {
super.init(tournament: tournament, index: index, parent: parent, format: matchFormat, startDate: startDate, groupStageLoserBracket: groupStageLoserBracket, loserBracketMode: loserBracketMode)
// self.lastUpdate = Date()
// self.tournament = tournament
// self.index = index
// self.parent = parent
// self.format = matchFormat
// self.startDate = startDate
// self.groupStageLoserBracket = groupStageLoserBracket
// self.loserBracketMode = loserBracketMode
}
required init(from decoder: any Decoder) throws {
try super.init(from: decoder)
}
required public init() {
super.init()
}
// MARK: - DidSet
public override func didSetStartDate() {
@ -265,7 +243,7 @@ defer {
}
#endif
let parentRound = parentRound
if let parentRound, parentRound.parent == nil, groupStageLoserBracket == false, parentRound.loserBracketMode != .automatic {
if let parentRound, parentRound.parent == nil, isGroupStageLoserBracket() == false, parentRound.loserBracketMode != .automatic {
return nil
}
@ -286,7 +264,7 @@ defer {
#endif
let parentRound = parentRound
if let parentRound, parentRound.parent == nil, groupStageLoserBracket == false, parentRound.loserBracketMode != .automatic {
if let parentRound, parentRound.parent == nil, isGroupStageLoserBracket() == false, parentRound.loserBracketMode != .automatic {
return nil
}
@ -647,7 +625,7 @@ defer {
}
public func roundTitle(_ displayStyle: DisplayStyle = .wide, initialMode: Bool = false) -> String {
if groupStageLoserBracket {
if isGroupStageLoserBracket() {
return "Classement Poules"
}
@ -752,11 +730,11 @@ defer {
}
public func isUpperBracket() -> Bool {
return parent == nil && groupStageLoserBracket == false
return parent == nil && isGroupStageLoserBracket() == false
}
public func isLoserBracket() -> Bool {
return parent != nil || groupStageLoserBracket
return parent != nil || isGroupStageLoserBracket()
}
public func deleteLoserBracket() {
@ -793,7 +771,7 @@ defer {
var titles = [String: String]()
let rounds = (0..<roundCount).map { //index 0 is the final
let round = Round(tournament: tournament, index: $0, matchFormat: loserBracketMatchFormat)
let round = Round(tournament: tournament, index: $0, format: loserBracketMatchFormat)
round.parent = id //parent
//titles[round.id] = round.roundTitle(initialMode: true)
return round
@ -818,6 +796,7 @@ defer {
public func disableUnplayedLoserBracketMatches() {
let m = self._matches()
let roundTitle = roundTitle()
if let previousRound = self.previousRound() {
m.forEach { match in
let prmc = previousRound.previousMatches(previousRound: previousRound, match: match).filter({
@ -839,6 +818,7 @@ defer {
match.disabled = false
}
}
match.setMatchName(roundTitle)
}
} else if let upperRound = self.parentRound {
m.forEach { match in
@ -851,10 +831,12 @@ defer {
} else {
match.disabled = false
}
match.setMatchName(roundTitle)
}
}
tournamentStore?.matches.addOrUpdate(contentOfs: m)
loserRounds().forEach { loserRound in
loserRound.disableUnplayedLoserBracketMatches()
}
@ -903,6 +885,14 @@ defer {
_cachedLoserRoundsAndChildren = nil
}
public func initialStartDate() -> Date? {
plannedStartDate ?? startDate ?? playedMatches().first?.initialStartDate()
}
public func isGroupStageLoserBracket() -> Bool {
groupStageLoserBracket == true
}
public override func deleteDependencies(store: Store, actionOption: ActionOption) {
store.deleteDependencies(type: Match.self, actionOption: actionOption) { $0.round == self.id }

@ -14,38 +14,38 @@ final public class TeamRegistration: BaseTeamRegistration, SideStorable {
@ObservationIgnored
var _cachedRestingTime: (Bool, Date?)?
public 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
}
// public 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
// }
//
public func hasRegisteredOnline() -> Bool {
players().anySatisfy({ $0.registeredOnline })
}
@ -72,14 +72,6 @@ final public class TeamRegistration: BaseTeamRegistration, SideStorable {
walkOut
}
required init(from decoder: any Decoder) throws {
try super.init(from: decoder)
}
required public init() {
super.init()
}
public var tournamentStore: TournamentStore? {
return TournamentLibrary.shared.store(tournamentId: self.tournament)
}
@ -394,6 +386,12 @@ final public class TeamRegistration: BaseTeamRegistration, SideStorable {
].joined(separator: exportFormat.separator())
}
}
public func teamLastNames() -> [String] {
players().map { p in
p.lastName
}
}
public var computedRegistrationDate: Date {
return registrationDate ?? .distantFuture

@ -27,33 +27,14 @@ final public class TeamScore: BaseTeamScore, SideStorable {
// 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)
convenience init(match: String, team: TeamRegistration?) {
self.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)))")

@ -259,10 +259,13 @@ defer {
case .rawText:
return (selectedSortedTeams.compactMap { $0.pasteData(exportFormat) } + ["Liste d'attente"] + waitingListTeams(in: selectedSortedTeams, includingWalkOuts: true).compactMap { $0.pasteData(exportFormat) }).joined(separator: exportFormat.newLineSeparator(2))
case .csv:
let headers = ["", "Nom Prénom", "rang", "Nom Prénom", "rang", "poids"].joined(separator: exportFormat.separator())
let headers = ["", "Nom Prénom", "rang", "Nom Prénom", "rang", "poids", "Paire"].joined(separator: exportFormat.separator())
var teamPaste = [headers]
for (index, team) in selectedSortedTeams.enumerated() {
teamPaste.append(team.pasteData(exportFormat, index + 1))
var teamData = team.pasteData(exportFormat, index + 1)
teamData.append(exportFormat.separator())
teamData.append(team.teamLastNames().joined(separator: " / "))
teamPaste.append(teamData)
}
return teamPaste.joined(separator: exportFormat.newLineSeparator())
}
@ -399,7 +402,8 @@ defer {
case 24...31:
return SeedInterval(first: 25, last: 32)
default:
return nil
let pow = Int(pow(2.0, ceil(log2(Double(alreadySetupSeeds)))))
return SeedInterval(first: pow + 1, last: pow * 2)
}
}
@ -652,7 +656,7 @@ defer {
let defaultSorting : [MySortDescriptor<TeamRegistration>] = _defaultSorting()
let _completeTeams = _teams.sorted(using: defaultSorting, order: .ascending).filter { $0.isWildCard() == false }.prefix(teamCount).sorted(using: [.keyPath(\.initialWeight), .keyPath(\.id)], order: .ascending)
let _completeTeams = _teams.sorted(using: defaultSorting, order: .ascending).filter { $0.isWildCard() == false }.prefix(teamCount).sorted(using: [.keyPath(\.initialWeight), .keyPath(\.uniqueRandomIndex), .keyPath(\.id)], order: .ascending)
let wcGroupStage = _teams.filter { $0.wildCardGroupStage }.sorted(using: _currentSelectionSorting, order: .ascending)
@ -1050,22 +1054,16 @@ defer {
}
}
}
do {
try self.tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: unsortedTeams())
} catch {
Logger.error(error)
}
if self.publishRankings == false {
self.publishRankings = true
do {
try DataStore.shared.tournaments.addOrUpdate(instance: self)
} catch {
Logger.error(error)
if rankings.isEmpty == false {
let teams = unsortedTeams()
self.tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: teams)
if self.publishRankings == false {
self.publishRankings = true
}
DataStore.shared.tournaments.addOrUpdate(instance: self)
}
return rankings
}
@ -1247,19 +1245,11 @@ defer {
}
public func earnings() -> Double {
if let entryFee {
return Double(selectedPlayers().filter { $0.hasPaid() }.count) * entryFee
} else {
return 0.0
}
return selectedPlayers().compactMap { $0.paidAmount(self) }.reduce(0.0, +)
}
public func remainingAmount() -> Double {
if let entryFee {
return Double(selectedPlayers().filter { $0.hasPaid() == false }.count) * entryFee
} else {
return 0.0
}
return selectedPlayers().compactMap { $0.remainingAmount(self) }.reduce(0.0, +)
}
public func paidCompletion() -> Double {
@ -1374,7 +1364,7 @@ defer {
}
public func settingsDescriptionLocalizedLabel() -> String {
[courtCount.formatted() + " terrain\(courtCount.pluralSuffix)", entryFeeMessage].joined(separator: ", ")
[courtCount.formatted() + " piste\(courtCount.pluralSuffix)", entryFeeMessage].joined(separator: ", ")
}
public func structureDescriptionLocalizedLabel() -> String {
@ -1404,7 +1394,7 @@ defer {
guard let tournamentStore = self.tournamentStore else { return }
let teams = (0..<count).map { _ in
let team = TeamRegistration(tournament: id)
let team = TeamRegistration(tournament: id, registrationDate: Date())
team.setWeight(from: [], inTournamentCategory: self.tournamentCategory)
return team
}
@ -1442,7 +1432,7 @@ defer {
let matchCount = RoundRule.numberOfMatches(forTeams: minimalBracketTeamCount ?? bracketTeamCount())
let rounds = (0..<roundCount).map { //index 0 is the final
return Round(tournament: id, index: $0, matchFormat: matchFormat, loserBracketMode: loserBracketMode)
return Round(tournament: id, index: $0, format: matchFormat, loserBracketMode: loserBracketMode)
}
if rounds.isEmpty {
@ -1755,19 +1745,22 @@ defer {
public func setupRegistrationSettings(templateTournament: Tournament) {
self.enableOnlineRegistration = templateTournament.enableOnlineRegistration
self.unregisterDeltaInHours = templateTournament.unregisterDeltaInHours
self.accountIsRequired = templateTournament.accountIsRequired
self.licenseIsRequired = templateTournament.licenseIsRequired
self.minimumPlayerPerTeam = templateTournament.minimumPlayerPerTeam
self.maximumPlayerPerTeam = templateTournament.maximumPlayerPerTeam
self.waitingListLimit = templateTournament.waitingListLimit
self.teamCountLimit = templateTournament.teamCountLimit
self.teamCount = templateTournament.teamCount
self.enableOnlinePayment = templateTournament.enableOnlinePayment
self.onlinePaymentIsMandatory = templateTournament.onlinePaymentIsMandatory
self.enableOnlinePaymentRefund = templateTournament.enableOnlinePaymentRefund
self.stripeAccountId = templateTournament.stripeAccountId
self.enableTimeToConfirm = templateTournament.enableTimeToConfirm
self.isCorporateTournament = templateTournament.isCorporateTournament
self.clubMemberFeeDeduction = templateTournament.clubMemberFeeDeduction
self.unregisterDeltaInHours = templateTournament.unregisterDeltaInHours
if self.registrationDateLimit == nil, templateTournament.registrationDateLimit != nil {
self.registrationDateLimit = startDate.truncateMinutesAndSeconds()
}
@ -1793,9 +1786,9 @@ defer {
private func _defaultSorting() -> [MySortDescriptor<TeamRegistration>] {
switch teamSorting {
case .rank:
[.keyPath(\TeamRegistration.initialWeight), .keyPath(\TeamRegistration.id)]
[.keyPath(\TeamRegistration.initialWeight), .keyPath(\TeamRegistration.uniqueRandomIndex), .keyPath(\TeamRegistration.id)]
case .inscriptionDate:
[.keyPath(\TeamRegistration.registrationDate!), .keyPath(\TeamRegistration.initialWeight), .keyPath(\TeamRegistration.id)]
[.keyPath(\TeamRegistration.registrationDate!), .keyPath(\TeamRegistration.initialWeight), .keyPath(\TeamRegistration.uniqueRandomIndex), .keyPath(\TeamRegistration.id)]
}
}
@ -1805,7 +1798,7 @@ defer {
&& federalTournamentAge == build.age
}
private let _currentSelectionSorting : [MySortDescriptor<TeamRegistration>] = [.keyPath(\.weight), .keyPath(\.id)]
private let _currentSelectionSorting : [MySortDescriptor<TeamRegistration>] = [.keyPath(\.weight), .keyPath(\.uniqueRandomIndex), .keyPath(\.id)]
private func _matchSchedulers() -> [MatchScheduler] {
guard let tournamentStore = self.tournamentStore else { return [] }
@ -1907,7 +1900,7 @@ defer {
}
public func groupStageLoserBracket() -> Round? {
self.tournamentStore?.rounds.first(where: { $0.groupStageLoserBracket })
self.tournamentStore?.rounds.first(where: { $0.isGroupStageLoserBracket() })
}
public func groupStageLoserBracketsInitialPlace() -> Int {
@ -2052,7 +2045,7 @@ defer {
public func addNewRound(_ roundIndex: Int) async {
await MainActor.run {
let round = Round(tournament: id, index: roundIndex, matchFormat: matchFormat)
let round = Round(tournament: id, index: roundIndex, format: matchFormat)
let matchCount = RoundRule.numberOfMatches(forRoundIndex: roundIndex)
let matchStartIndex = RoundRule.matchIndex(fromRoundIndex: roundIndex)
let nextRound = round.nextRound()
@ -2243,6 +2236,87 @@ defer {
return Array(leftInterval...maxSize)
}
public func groupMatchesByDay(matches: [Match]) -> [Date: [Match]] {
var matchesByDay = [Date: [Match]]()
let calendar = Calendar.current
for match in matches {
// Extract day/month/year and create a date with only these components
let components = calendar.dateComponents([.year, .month, .day], from: match.computedStartDateForSorting)
let strippedDate = calendar.date(from: components)!
// Group matches by the strippedDate (only day/month/year)
if matchesByDay[strippedDate] == nil {
matchesByDay[strippedDate] = []
}
let shouldIncludeMatch: Bool
switch match.matchType {
case .groupStage:
shouldIncludeMatch = !matchesByDay[strippedDate]!.filter { $0.groupStage != nil }.compactMap { $0.groupStage }.contains(match.groupStage!)
case .bracket:
shouldIncludeMatch = !matchesByDay[strippedDate]!.filter { $0.round != nil }.compactMap { $0.round }.contains(match.round!)
case .loserBracket:
shouldIncludeMatch = true
}
if shouldIncludeMatch {
matchesByDay[strippedDate]!.append(match)
}
}
return matchesByDay
}
public func matchCountPerDay(matchesByDay: [Date: [Match]]) -> [Date: NSCountedSet] {
let days = matchesByDay.keys
var matchCountPerDay = [Date: NSCountedSet]()
for day in days {
if let matches = matchesByDay[day] {
var groupStageCount = 0
let countedSet = NSCountedSet()
for match in matches {
switch match.matchType {
case .groupStage:
if let groupStage = match.groupStageObject {
if groupStageCount < groupStage.size - 1 {
groupStageCount = groupStage.size - 1
}
}
case .bracket:
countedSet.add(match.matchFormat)
case .loserBracket:
break
}
}
if groupStageCount > 0 {
for _ in 0..<groupStageCount {
countedSet.add(groupStageMatchFormat)
}
}
if let loserRounds = matches.filter({ $0.round != nil }).filter({ $0.roundObject?.parent == nil }).sorted(by: \.computedStartDateForSorting).last?.roundObject?.loserRounds() {
let ids = matches.map { $0.id }
for loserRound in loserRounds {
if let first = loserRound.playedMatches().first {
if ids.contains(first.id) {
countedSet.add(first.matchFormat)
}
}
}
}
matchCountPerDay[day] = countedSet
}
}
return matchCountPerDay
}
// MARK: -
func insertOnServer() throws {

@ -102,8 +102,8 @@ public class MatchDescriptor: ObservableObject {
setDescriptors.compactMap { $0.getValue(teamPosition: .two) }
}
public var scoreTeamOne: Int { setDescriptors.compactMap { $0.winner }.filter { $0 == .one }.count }
public var scoreTeamTwo: Int { setDescriptors.compactMap { $0.winner }.filter { $0 == .two }.count }
public var scoreTeamOne: Int { setDescriptors.filter({ $0.hasEnded }).compactMap { $0.winner }.filter { $0 == .one }.count }
public var scoreTeamTwo: Int { setDescriptors.filter({ $0.hasEnded }).compactMap { $0.winner }.filter { $0 == .two }.count }
public var hasEnded: Bool {
return matchFormat.hasEnded(scoreTeamOne: scoreTeamOne, scoreTeamTwo: scoreTeamTwo)

@ -647,7 +647,7 @@ public enum TournamentLevel: Int, Hashable, Codable, CaseIterable, Identifiable
case _ where count > 28:
return [80,75,72,70,65,63,60,58,55,53,50,48,45,43,40,38,35,33,30,28,25,23,20,18,15,12,10,8,5,3, 1]
default:
return [60,50,40,25,10,5]
return [60,50,40,25,10,5, 1]
}
case .p250:
switch count {
@ -698,33 +698,47 @@ public enum TournamentLevel: Int, Hashable, Codable, CaseIterable, Identifiable
case _ where count > 28:
return [800,750,720,700,650,630,600,580,550,530,500,480,450,430,400,380,350,330,300,280,250,230,200,180,150,120,100,80,50,30, 10]
default:
return [600,500,400,250,100,50]
return [600,500,400,250,100,50, 10]
}
case .p1500:
switch count {
case 9...12:
return [975, 825, 750, 525, 375, 300, 225, 150, 75, 45, 15]
case 13...16:
return [1050, 900, 825, 675, 600, 525, 450, 375, 315, 270, 225, 150, 75, 45, 15]
case 17...20:
return [1125, 975, 900, 825, 750, 675, 600, 525, 450, 375, 345, 300, 270, 225, 180, 150, 75, 45, 15]
case 21...24:
return [1125,1050,975,900,825,750,705,645,600,555,495,450,420,375,345,300,270,225,180,150,75,45,15]
return [1125, 1050, 975, 900, 825, 750, 705, 645, 600, 555, 495, 450, 420, 375, 345, 300, 270, 225, 180, 150, 75, 45, 15]
case 25...28:
return [1200,1125,1050,975,900,825,795,750,720,675,645,600,570,525,495,450,420,375,345,300,270,225,180,150,75,45,15]
return [1200, 1125, 1050, 975, 900, 825, 795, 750, 720, 675, 645, 600, 570, 525, 495, 450, 420, 375, 345, 300, 270, 225, 180, 150, 75, 45, 15]
case _ where count > 28:
return [1200,1125,1080,1050,975,945,900,870,825,795,750,720,675,645,600,570,525,495,450,420,375,345,300,270,225,180,150,120,75,45,15]
return [1200, 1125, 1080, 1050, 975, 945, 900, 870, 825, 795, 750, 720, 675, 645, 600, 570, 525, 495, 450, 420, 375, 345, 300, 270, 225, 180, 150, 120, 75, 45, 15]
default:
return [1125,975,900,825,750,675,600,525,450,375,345,300,270,225,180,150,75,45,15]
}
return [900, 750, 600, 375, 150, 75, 15]
}
case .p2000:
return [1600,1440,1300,1180,1120,1060,1000,880,840,800,760,720,680,640,600,540,520,500,480,460,440,420,400,260,200,40]
switch count {
case 9...12:
return [1300, 1100, 1000, 700, 500, 400, 300, 200, 100, 60, 20]
case 13...16:
return [1400, 1200, 1100, 900, 800, 700, 600, 500, 420, 360, 300, 200, 100, 60, 20]
case 17...20:
return [1500, 1300, 1200, 1100, 1000, 900, 800, 700, 600, 500, 460, 400, 360, 300, 240, 200, 100, 60, 20]
case 21...24:
return [1600, 1400, 1300, 1200, 1100, 1000, 940, 860, 800, 740, 660, 600, 560, 500, 460, 400, 360, 300, 240, 200, 100, 60, 20]
case 25...28:
return [1600, 1500, 1440, 1400, 1300, 1200, 1160, 1100, 1060, 1000, 960, 900, 860, 800, 760, 700, 660, 600, 560, 500, 460, 400, 360, 300, 240, 200, 60]
case _ where count > 28:
return [1600, 1500, 1440, 1400, 1300, 1260, 1200, 1160, 1100, 1060, 1000, 960, 900, 860, 800, 760, 700, 660, 600, 560, 500, 460, 400, 360, 300, 240, 200, 160, 100, 60, 20]
default:
return [1200, 1000, 800, 500, 200, 100, 20]
}
}
}
public var ranges: [PlayersCountRange] {
switch self {
case .p1500:
return [.N16, .N24, .N28, .N32]
case .p2000:
return [.N32]
default:
return PlayersCountRange.allCases
}
return PlayersCountRange.allCases
}
// enum NewBallSystem {
@ -1846,7 +1860,7 @@ public enum AnimationType: Int, CaseIterable, Hashable, Identifiable {
case .playerAnimation:
return "Chaque joueur joue avec quelqu'un de différent à chaque rotation (8 à 12 joueurs)"
case .upAndDown:
return "Les gagnants montent sur le terrain d'à côté, les perdants descendent"
return "Les gagnants montent sur la piste d'à côté, les perdants descendent"
case .brawl:
return "A chaque rotation, les gagnants de la rotation précédente se jouent entre eux"
}

Loading…
Cancel
Save