diff --git a/backend/controllers/tournamentController.js b/backend/controllers/tournamentController.js index 4bfb580..a517233 100644 --- a/backend/controllers/tournamentController.js +++ b/backend/controllers/tournamentController.js @@ -420,6 +420,19 @@ export const resetMatches = async (req, res) => { } }; +export const cleanupOrphanedMatches = async (req, res) => { + const { authcode: token } = req.headers; + const { clubId, tournamentId } = req.body; + try { + const result = await tournamentService.cleanupOrphanedMatches(token, clubId, tournamentId); + emitTournamentChanged(clubId, tournamentId); + res.status(200).json(result); + } catch (err) { + console.error(err); + res.status(500).json({ error: err.message }); + } +}; + export const removeParticipant = async (req, res) => { const { authcode: token } = req.headers; const { clubId, tournamentId, participantId } = req.body; diff --git a/backend/routes/tournamentRoutes.js b/backend/routes/tournamentRoutes.js index 3afdd37..31eb2c8 100644 --- a/backend/routes/tournamentRoutes.js +++ b/backend/routes/tournamentRoutes.js @@ -19,6 +19,7 @@ import { manualAssignGroups, resetGroups, resetMatches, + cleanupOrphanedMatches, removeParticipant, updateParticipantSeeded, setParticipantGaveUp, @@ -61,6 +62,7 @@ router.put('/participant/:clubId/:tournamentId/:participantId/gave-up', authenti router.post('/modus', authenticate, setModus); router.post('/groups/reset', authenticate, resetGroups); router.post('/matches/reset', authenticate, resetMatches); +router.post('/matches/cleanup-orphaned', authenticate, cleanupOrphanedMatches); router.put('/groups', authenticate, createGroups); router.post('/groups/create', authenticate, createGroupsPerClass); router.post('/groups', authenticate, fillGroups); diff --git a/backend/services/tournamentService.js b/backend/services/tournamentService.js index a44dea3..2822efd 100644 --- a/backend/services/tournamentService.js +++ b/backend/services/tournamentService.js @@ -3201,6 +3201,38 @@ Ve // 2. Neues Turnier anlegen await TournamentMatch.destroy({ where }); } + /** + * Entfernt Matches, bei denen mindestens ein Spieler nicht mehr existiert + * (z.B. gelöscht bevor die Aufräum-Logik beim Teilnehmerlöschen eingeführt wurde). + */ + async cleanupOrphanedMatches(userToken, clubId, tournamentId) { + await checkAccess(userToken, clubId); + const tournament = await Tournament.findByPk(tournamentId); + if (!tournament || tournament.clubId != clubId) { + throw new Error('Turnier nicht gefunden'); + } + + const members = await TournamentMember.findAll({ where: { tournamentId }, attributes: ['id'] }); + const externals = await ExternalTournamentParticipant.findAll({ where: { tournamentId }, attributes: ['id'] }); + const validIds = new Set([ + ...members.map(m => m.id), + ...externals.map(e => e.id) + ]); + + const matches = await TournamentMatch.findAll({ where: { tournamentId } }); + let deletedCount = 0; + for (const m of matches) { + const p1Exists = !m.player1Id || validIds.has(m.player1Id); + const p2Exists = !m.player2Id || validIds.has(m.player2Id); + if (!p1Exists || !p2Exists) { + await TournamentResult.destroy({ where: { matchId: m.id } }); + await m.destroy(); + deletedCount++; + } + } + return { deletedCount }; + } + async removeParticipant(userToken, clubId, tournamentId, participantId) { await checkAccess(userToken, clubId); diff --git a/frontend/src/components/tournament/TournamentGroupsTab.vue b/frontend/src/components/tournament/TournamentGroupsTab.vue index 72042d5..83062d4 100644 --- a/frontend/src/components/tournament/TournamentGroupsTab.vue +++ b/frontend/src/components/tournament/TournamentGroupsTab.vue @@ -162,8 +162,14 @@ +