feat(tournaments): enhance age eligibility logic and date parsing

- Improved the getCutoffDate method to derive cutoff dates from performance class if not explicitly provided.
- Updated getAgeLimitFromText to handle various age class formats, returning structured age limit objects.
- Introduced getBirthYearFromPerformanceClass to extract birth year information from performance class strings.
- Refined isEligibleByAge to incorporate new eligibility rules based on performance class and age limits, ensuring accurate age validation for tournament participation.
This commit is contained in:
Torsten Schulz (local)
2026-03-10 21:39:50 +01:00
parent 13379d6b24
commit f35e0510e7

View File

@@ -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',