From 6c050866186fabffbcd4afca59f240e73e9c1179 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Wed, 16 Sep 2026 09:46:38 +0200 Subject: [PATCH] feat(TournamentPlacementsTab): implement knockout round ranking logic for non-qualified players --- .../tournament/TournamentPlacementsTab.vue | 64 ++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/tournament/TournamentPlacementsTab.vue b/frontend/src/components/tournament/TournamentPlacementsTab.vue index a50831a7..5a0b4c0e 100755 --- a/frontend/src/components/tournament/TournamentPlacementsTab.vue +++ b/frontend/src/components/tournament/TournamentPlacementsTab.vue @@ -368,6 +368,69 @@ export default { unique.push(p); } + // Bei einer K.-o.-Endrunde folgen die nicht qualifizierten Spieler + // ihrer Vorrundenplatzierung. Gleiche Gruppenplätze teilen sich + // dabei auch den Endplatz (z. B. beide Gruppendritten auf Platz 5). + const preliminaryRankings = (this.groups || []) + .filter(group => + (group.stageId === null || group.stageId === undefined) + && String(group.classId ?? 'null') === classKey + ) + .flatMap(group => this.groupRankings[group.groupId] || []); + + if (preliminaryRankings.length > 0) { + const normalizedName = (firstName, lastName) => + `${firstName || ''} ${lastName || ''}`.trim().toLocaleLowerCase('de'); + const placedNames = new Set((byClass[classKey] || []) + .map(entry => entry.displayName || normalizedName(entry.member?.firstName, entry.member?.lastName)) + .filter(Boolean) + .map(name => String(name).trim().toLocaleLowerCase('de'))); + const knockoutEntrantNames = new Set((matchesByClass[classKey] || []) + .flatMap(match => [match.player1, match.player2]) + .map(player => player?.member || player) + .map(player => normalizedName(player?.firstName, player?.lastName)) + .filter(Boolean)); + const remainingByGroupPlace = new Map(); + + preliminaryRankings.forEach(ranking => { + const name = String(ranking.name || '').trim(); + const normalizedRankingName = name.toLocaleLowerCase('de'); + if (!name || placedNames.has(normalizedRankingName) || knockoutEntrantNames.has(normalizedRankingName)) return; + const groupPlace = Number(ranking.position); + if (!Number.isFinite(groupPlace) || groupPlace < 1) return; + if (!remainingByGroupPlace.has(groupPlace)) { + remainingByGroupPlace.set(groupPlace, []); + } + remainingByGroupPlace.get(groupPlace).push(ranking); + }); + + let nextPosition = Math.max( + (byClass[classKey] || []).length, + knockoutEntrantNames.size + ) + 1; + [...remainingByGroupPlace.keys()].sort((a, b) => a - b).forEach(groupPlace => { + const rankingsAtPlace = remainingByGroupPlace.get(groupPlace) || []; + rankingsAtPlace.forEach(ranking => { + const nameParts = String(ranking.name || '').trim().split(/\s+/); + (byClass[classKey] ||= []).push({ + position: nextPosition, + member: { + id: ranking.id, + firstName: nameParts.slice(0, -1).join(' '), + lastName: nameParts.slice(-1).join(' '), + }, + displayName: ranking.name, + classId: classKey === 'null' ? null : Number(classKey), + isExternal: ranking.isExternal || false, + }); + }); + nextPosition += rankingsAtPlace.length; + }); + + byClass[classKey] = (byClass[classKey] || []).sort((a, b) => Number(a.position) - Number(b.position)); + return; + } + const maxPos = Math.max(0, ...(byClass[classKey] || []).map(e => Number(e.position) || 0)); let nextPos = maxPos + 1; @@ -765,4 +828,3 @@ table tbody td:first-child { width: 4em; } -