diff --git a/frontend/src/views/OfficialTournaments.vue b/frontend/src/views/OfficialTournaments.vue index 693fc370..823b64b2 100644 --- a/frontend/src/views/OfficialTournaments.vue +++ b/frontend/src/views/OfficialTournaments.vue @@ -1117,7 +1117,13 @@ export default { return isNaN(d.getTime()) ? null : d; }, getCutoffDate(c) { - return this.parseDateFlexible(c.cutoffDate || c.stichtag || ''); + let val = c.cutoffDate || c.stichtag || ''; + if (!val && (c.performanceClass || c.leistungsklasse)) { + const pc = String(c.performanceClass || c.leistungsklasse || ''); + const m = pc.match(/stichtag\s*:?\s*([0-3]?\d\.[01]?\d\.\d{4})/i); + if (m) val = m[1].trim(); + } + return this.parseDateFlexible(val); }, getMemberBirthDate(member) { return this.parseDateFlexible(member.birthDate || ''); @@ -1128,14 +1134,20 @@ export default { if (fromStart) return this.parseDateFlexible(fromStart[1]); return this.parseDateFlexible(this.parsed?.parsedData?.termin || ''); }, - // Uxx oder AK xx erkennen -> Maximalalter in Jahren + // Uxx, Jugend N, Mädchen N, AK xx erkennen -> { limit, exclusive } + // exclusive=true: Alter < limit (U12); exclusive=false: Alter <= limit (Jugend 12) getAgeLimitFromText(text) { if (!text) return null; - const t = String(text).toUpperCase(); - let m = t.match(/\bU\s*(\d{1,2})\b/); - if (m) return Number(m[1]); - m = t.match(/\bAK\s*(\d{1,2})\b/); - if (m) return Number(m[1]); + const t = String(text); + // Jugend 12 / Mädchen 12: max Alter 12 (Alter <= 12) + let m = t.match(/(?:jugend|mädchen|maedchen)\s+(\d{1,2})\b/i); + if (m) return { limit: Number(m[1]), exclusive: false }; + // U12: unter 12 (Alter < 12) + m = t.match(/\bU\s*(\d{1,2})\b/i); + if (m) return { limit: Number(m[1]), exclusive: true }; + // AK 12 nur wenn nicht in Klammern wie (AK1) - (AK1) ist Kategorie-Nr, nicht Alter + const akMatch = t.match(/\bAK\s*(\d{1,2})\b/i); + if (akMatch && !/\(\s*AK\s*\d+\s*\)/i.test(t)) return { limit: Number(akMatch[1]), exclusive: true }; return null; }, calculateAgeOnDate(birthDate, refDate) { @@ -1152,17 +1164,46 @@ export default { if (/jugend/.test(txt)) return 'both'; return 'both'; }, + getBirthYearFromPerformanceClass(c) { + const pc = c.performanceClass || c.leistungsklasse || ''; + const s = String(pc); + // "Jahrgang 2017 und jünger" -> minBirthYear 2017 (geboren 2017 oder später) + const mUndJünger = s.match(/jahrgang\s+(\d{4})\s+und\s+jünger/i); + if (mUndJünger) return { minYear: Number(mUndJünger[1]) }; + // "Jahrgang 2014" -> exakt Jahr 2014 + const mExact = s.match(/jahrgang\s+(\d{4})\b/i); + if (mExact) return { exact: Number(mExact[1]) }; + return null; + }, isEligibleByAge(member, c) { - const cutoff = this.getCutoffDate(c); const bd = this.getMemberBirthDate(member); - if (cutoff) { - if (!bd || !(bd.getTime() > cutoff.getTime())) return false; // jünger als Stichtag + if (!bd) return false; + + // 1. Jahrgang YYYY (z.B. "Jahrgang 2014") oder "Jahrgang 2017 und jünger" + const birthYearRule = this.getBirthYearFromPerformanceClass(c); + if (birthYearRule != null) { + const memberYear = bd.getFullYear(); + if (birthYearRule.exact != null) return memberYear === birthYearRule.exact; + if (birthYearRule.minYear != null) return memberYear >= birthYearRule.minYear; } - const limit = this.getAgeLimitFromText((c.ageClassCompetition || c.altersklasseWettbewerb || '')); - if (limit != null) { + + // 2. Stichtag (z.B. "01.01.2014 und jünger") -> geboren nach Stichtag + const cutoff = this.getCutoffDate(c); + if (cutoff) { + if (!(bd.getTime() > cutoff.getTime())) return false; // jünger = geboren nach Stichtag + } + + // 3. Alterslimit aus Titel (Jugend 12, U12, AK 12) + const ageLimit = this.getAgeLimitFromText((c.ageClassCompetition || c.altersklasseWettbewerb || '')); + if (ageLimit != null) { const ref = this.getReferenceDate(c); const age = this.calculateAgeOnDate(bd, ref); - if (age == null || !(age < limit)) return false; // Uxx => Alter < xx + if (age == null) return false; + if (ageLimit.exclusive) { + if (!(age < ageLimit.limit)) return false; // U12: Alter < 12 + } else { + if (!(age <= ageLimit.limit)) return false; // Jugend 12: Alter <= 12 + } } return true; }, @@ -1195,50 +1236,7 @@ export default { const yyyy = d.getFullYear(); return `${dd}.${mm}.${yyyy}`; }, - // Eligibility helpers - parseDateFlexible(s) { - if (!s || typeof s !== 'string') return null; - const t = s.trim(); - let m = t.match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})$/); - if (m) { - const d = new Date(Number(m[3]), Number(m[2]) - 1, Number(m[1])); - return isNaN(d.getTime()) ? null : d; - } - m = t.match(/^(\d{4})-(\d{2})-(\d{2})$/); - if (m) { - const d = new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3])); - return isNaN(d.getTime()) ? null : d; - } - const d = new Date(t); - return isNaN(d.getTime()) ? null : d; - }, - getCutoffDate(c) { - return this.parseDateFlexible(c.cutoffDate || c.stichtag || ''); - }, - getMemberBirthDate(member) { - return this.parseDateFlexible(member.birthDate || ''); - }, - getGenderRule(c) { - const txt = `${c.ageClassCompetition || c.altersklasseWettbewerb || ''} ${c.openTo || c.offenFuer || ''}`.toLowerCase(); - if (/(mädchen|weiblich|\bw\b)/.test(txt)) return 'female'; - if (/(jungen|männlich|\bm\b)/.test(txt)) return 'male'; - if (/jugend/.test(txt)) return 'both'; - return 'both'; - }, - isEligibleForCompetition(member, c) { - const rule = this.getGenderRule(c); - const g = member.gender || 'unknown'; - if (rule === 'female' && g !== 'female') return false; - if (rule === 'male' && g !== 'male') return false; - const cutoff = this.getCutoffDate(c); - if (cutoff) { - const bd = this.getMemberBirthDate(member); - if (!bd) return false; - if (!(bd.getTime() > cutoff.getTime())) return false; // jünger als Stichtag => geboren nach Stichtag - } - return true; - }, - + async removeTournament(t) { const confirmed = await this.showConfirm( 'Turnier löschen',