You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
PadelClub/PadelClub/Data/PlayerRegistration.swift

389 lines
12 KiB

//
// PlayerRegistration.swift
// Padel Tournament
//
// Created by razmig on 10/03/2024.
//
import Foundation
import LeStorage
@Observable
class PlayerRegistration: ModelObject, Storable {
static func resourceName() -> String { "player-registrations" }
var id: String = Store.randomId()
var teamRegistration: String?
var firstName: String
var lastName: String
var licenceId: String?
var rank: Int?
var registrationType: PaymentType?
var registrationDate: Date?
var sex: Int
var tournamentPlayed: Int?
var points: Double?
var clubName: String?
var ligueName: String?
var assimilation: String?
var phoneNumber: String?
var email: String?
var birthdate: String?
var weight: Int = 0
var source: PlayerDataSource?
var hasArrived: Bool = false
internal init(teamRegistration: String? = nil, firstName: String, lastName: String, licenceId: String? = nil, rank: Int? = nil, registrationType: PaymentType? = nil, registrationDate: Date? = nil, sex: Int, source: PlayerDataSource? = nil) {
self.teamRegistration = teamRegistration
self.firstName = firstName
self.lastName = lastName
self.licenceId = licenceId
self.rank = rank
self.registrationType = registrationType
self.registrationDate = registrationDate
self.sex = sex
self.source = source
}
internal init(importedPlayer: ImportedPlayer) {
self.teamRegistration = ""
self.firstName = importedPlayer.firstName ?? ""
self.lastName = importedPlayer.lastName ?? ""
self.licenceId = importedPlayer.license ?? nil
self.rank = Int(importedPlayer.rank)
self.sex = importedPlayer.male ? 1 : 0
self.tournamentPlayed = importedPlayer.tournamentPlayed
self.points = importedPlayer.getPoints()
self.clubName = importedPlayer.clubName
self.ligueName = importedPlayer.ligueName
self.assimilation = importedPlayer.assimilation
self.source = .frenchFederation
}
internal init(federalData: [String], sex: Int, sexUnknown: Bool) {
lastName = federalData[0]
firstName = federalData[1]
birthdate = federalData[2]
licenceId = federalData[3]
clubName = federalData[4]
rank = Int(federalData[5])
email = federalData[6]
phoneNumber = federalData[7]
source = .beachPadel
if sexUnknown {
if sex == 1 && FileImportManager.shared.foundInWomenData(license: federalData[3]) {
self.sex = 0
} else if FileImportManager.shared.foundInMenData(license: federalData[3]) {
self.sex = 1
} else {
self.sex = -1
}
} else {
self.sex = sex
}
}
var computedAge: Int? {
if let birthdate {
let components = birthdate.components(separatedBy: "/")
if components.count == 3 {
if let year = Calendar.current.dateComponents([.year], from: Date()).year, let age = components.last, let ageInt = Int(age) {
if age.count == 2 { //si l'année est sur 2 chiffres dans le fichier
if ageInt < 23 {
return year - 2000 - ageInt
} else {
return year - 2000 + 100 - ageInt
}
} else { //si l'année est représenté sur 4 chiffres
return year - ageInt
}
}
}
}
return nil
}
func pasteData() -> String {
[firstName.capitalized, lastName.capitalized, licenceId].compactMap({ $0 }).joined(separator: " ")
}
func isPlaying() -> Bool {
team()?.isPlaying() == true
}
func contains(_ searchField: String) -> Bool {
firstName.localizedCaseInsensitiveContains(searchField) || lastName.localizedCaseInsensitiveContains(searchField)
}
func isSameAs(_ player: PlayerRegistration) -> Bool {
firstName.localizedCaseInsensitiveCompare(player.firstName) == .orderedSame &&
lastName.localizedCaseInsensitiveCompare(player.lastName) == .orderedSame
}
func tournament() -> Tournament? {
guard let tournament = team()?.tournament else { return nil }
return Store.main.findById(tournament)
}
func team() -> TeamRegistration? {
guard let teamRegistration else { return nil }
return Store.main.findById(teamRegistration)
}
func hasPaid() -> Bool {
registrationType != nil
}
func playerLabel(_ displayStyle: DisplayStyle = .wide) -> String {
switch displayStyle {
case .wide:
lastName.trimmed.capitalized + " " + firstName.trimmed.capitalized
case .short:
lastName.trimmed.capitalized + " " + firstName.trimmed.prefix(1).capitalized + "."
}
}
func isImported() -> Bool {
source == .beachPadel
}
func isValidLicenseNumber(year: Int) -> Bool {
guard let licenceId else { return false }
guard licenceId.isLicenseNumber else { return false }
guard licenceId.suffix(6) == "(\(year))" else { return false }
return true
}
@objc
var canonicalName: String {
playerLabel().folding(options: .diacriticInsensitive, locale: .current).lowercased()
}
func rankLabel(_ displayStyle: DisplayStyle = .wide) -> String {
if let rank, rank > 0 {
return rank.formatted()
} else {
return "non classé" + (isMalePlayer() ? "" : "e")
}
}
func getRank() -> Int {
weight
}
@MainActor
func updateRank(from sources: [CSVParser], lastRank: Int) async throws {
if let value = try await history(from: sources)?.rank {
rank = value
} else {
rank = lastRank
}
}
func history(from sources: [CSVParser]) async throws -> Line? {
guard let license = licenceId?.strippedLicense else {
return try await historyFromName(from: sources)
}
return await withTaskGroup(of: Line?.self) { group in
for source in sources.filter({ $0.maleData == isMalePlayer() }) {
group.addTask {
guard !Task.isCancelled else { print("Cancelled"); return nil }
return try? await source.first(where: { line in
line.rawValue.contains(";\(license);")
})
}
}
if let first = await group.first(where: { $0 != nil }) {
group.cancelAll()
return first
} else {
return nil
}
}
}
func historyFromName(from sources: [CSVParser]) async throws -> Line? {
return await withTaskGroup(of: Line?.self) { group in
for source in sources.filter({ $0.maleData == isMalePlayer() }) {
group.addTask { [lastName, firstName] in
guard !Task.isCancelled else { print("Cancelled"); return nil }
return try? await source.first(where: { line in
line.rawValue.canonicalVersionWithPunctuation.contains(";\(lastName.canonicalVersionWithPunctuation);\(firstName.canonicalVersionWithPunctuation);")
})
}
}
if let first = await group.first(where: { $0 != nil }) {
group.cancelAll()
return first
} else {
return nil
}
}
}
func setWeight(in tournament: Tournament) {
let currentRank = rank ?? tournament.unrankValue(for: isMalePlayer()) ?? 100_000
switch tournament.tournamentCategory {
case .men:
weight = isMalePlayer() ? currentRank : currentRank + PlayerRegistration.addon(for: currentRank, manMax: tournament.maleUnrankedValue ?? 0, womanMax: tournament.femaleUnrankedValue ?? 0)
default:
weight = currentRank
}
}
func isMalePlayer() -> Bool {
sex == 1
}
func validateLicenceId(_ year: Int) {
if let currentLicenceId = licenceId {
if currentLicenceId.trimmed.hasSuffix("(\(year-1))") {
self.licenceId = currentLicenceId.replacingOccurrences(of: "\(year-1)", with: "\(year)")
} else if let computedLicense = currentLicenceId.strippedLicense {
self.licenceId = computedLicense + " (\(year))"
}
}
}
enum CodingKeys: String, CodingKey {
case _id = "id"
case _teamRegistration = "teamRegistration"
case _firstName = "firstName"
case _lastName = "lastName"
case _licenceId = "licenceId"
case _rank = "rank"
case _registrationType = "registrationType"
case _registrationDate = "registrationDate"
case _sex = "sex"
case _tournamentPlayed = "tournamentPlayed"
case _points = "points"
case _clubName = "clubName"
case _ligueName = "ligueName"
case _assimilation = "assimilation"
case _birthdate = "birthdate"
case _phoneNumber = "phoneNumber"
case _email = "email"
case _weight = "weight"
case _source = "source"
case _hasArrived = "hasArrived"
}
enum PlayerDataSource: Int, Codable {
case frenchFederation
case beachPadel
}
enum PaymentType: Int, CaseIterable, Identifiable, Codable {
init?(rawValue: Int?) {
guard let value = rawValue else { return nil }
self.init(rawValue: value)
}
var id: Self {
self
}
case cash = 0
case lydia = 1
case gift = 2
case check = 3
case paylib = 4
case bankTransfer = 5
case clubHouse = 6
case creditCard = 7
func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String {
switch self {
case .check:
return "Chèque"
case .cash:
return "Cash"
case .lydia:
return "Lydia"
case .paylib:
return "Paylib"
case .bankTransfer:
return "Virement"
case .clubHouse:
return "Clubhouse"
case .creditCard:
return "CB"
case .gift:
return "Offert"
}
}
}
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
case 1...10: return 400
case 11...30: return 1000
case 31...60: return 2000
case 61...100: return 3000
case 101...200: return 8000
case 201...500: return 12000
default:
return 15000
}
}
}
extension PlayerRegistration: Hashable {
static func == (lhs: PlayerRegistration, rhs: PlayerRegistration) -> Bool {
lhs.id == rhs.id
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}
extension PlayerRegistration: PlayerHolder {
func getFirstName() -> String {
firstName
}
func getLastName() -> String {
lastName
}
func getPoints() -> Double? {
self.points
}
func getRank() -> Int? {
rank
}
func isUnranked() -> Bool {
rank == nil
}
func formattedRank() -> String {
self.rankLabel()
}
func formattedLicense() -> String {
if let licenceId { return licenceId.computedLicense }
return "aucune licence"
}
var male: Bool {
isMalePlayer()
}
}