Add participant assignment to groups functionality
Implement a new endpoint to assign participants to specific groups within tournaments. This includes the addition of the `assignParticipantToGroup` function in the tournament controller, which handles the assignment logic and emits relevant events. Update the tournament routes to include this new functionality. Enhance the tournament service to manage group assignments and ensure proper statistics are calculated for participants. Additionally, update the frontend to support adding participants, including external ones, and reflect changes in the UI for group assignments.
This commit is contained in:
@@ -253,6 +253,28 @@ export const manualAssignGroups = async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const assignParticipantToGroup = async (req, res) => {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubId, tournamentId, participantId, groupNumber, isExternal } = req.body;
|
||||
|
||||
try {
|
||||
const groups = await tournamentService.assignParticipantToGroup(
|
||||
token,
|
||||
clubId,
|
||||
tournamentId,
|
||||
participantId,
|
||||
groupNumber,
|
||||
isExternal || false
|
||||
);
|
||||
// Emit Socket-Event
|
||||
emitTournamentChanged(clubId, tournamentId);
|
||||
res.status(200).json(groups);
|
||||
} catch (error) {
|
||||
console.error('Error in assignParticipantToGroup:', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
export const resetGroups = async (req, res) => {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubId, tournamentId } = req.body;
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
deleteTournamentClass,
|
||||
updateParticipantClass,
|
||||
createGroupsPerClass,
|
||||
assignParticipantToGroup,
|
||||
} from '../controllers/tournamentController.js';
|
||||
import { authenticate } from '../middleware/authMiddleware.js';
|
||||
|
||||
@@ -55,12 +56,13 @@ router.post("/match/reopen", authenticate, reopenMatch);
|
||||
router.post('/match/finish', authenticate, finishMatch);
|
||||
router.put('/match/:clubId/:tournamentId/:matchId/active', authenticate, setMatchActive);
|
||||
router.get('/matches/:clubId/:tournamentId', authenticate, getTournamentMatches);
|
||||
router.put('/:clubId/:tournamentId', authenticate, updateTournament);
|
||||
router.get('/:clubId/:tournamentId', authenticate, getTournament);
|
||||
router.get('/:clubId', authenticate, getTournaments);
|
||||
router.post('/knockout', authenticate, startKnockout);
|
||||
router.delete("/matches/knockout", authenticate, deleteKnockoutMatches);
|
||||
router.post('/groups/manual', authenticate, manualAssignGroups);
|
||||
router.put('/participant/group', authenticate, assignParticipantToGroup); // Muss VOR /:clubId/:tournamentId stehen!
|
||||
router.put('/:clubId/:tournamentId', authenticate, updateTournament);
|
||||
router.get('/:clubId/:tournamentId', authenticate, getTournament);
|
||||
router.get('/:clubId', authenticate, getTournaments);
|
||||
router.post('/', authenticate, addTournament);
|
||||
|
||||
// Externe Teilnehmer
|
||||
|
||||
@@ -497,6 +497,12 @@ class TournamentService {
|
||||
]
|
||||
});
|
||||
|
||||
// Lade alle Gruppen-Matches mit Results für Rankings
|
||||
const groupMatches = await TournamentMatch.findAll({
|
||||
where: { tournamentId, round: 'group', isFinished: true },
|
||||
include: [{ model: TournamentResult, as: 'tournamentResults' }]
|
||||
});
|
||||
|
||||
// Gruppiere nach Klassen und nummeriere Gruppen pro Klasse
|
||||
const groupsByClass = {};
|
||||
groups.forEach(g => {
|
||||
@@ -510,25 +516,156 @@ class TournamentService {
|
||||
const result = [];
|
||||
for (const [classKey, classGroups] of Object.entries(groupsByClass)) {
|
||||
classGroups.forEach((g, idx) => {
|
||||
const internalParticipants = (g.tournamentGroupMembers || []).map(m => ({
|
||||
id: m.id,
|
||||
name: `${m.member.firstName} ${m.member.lastName}`,
|
||||
seeded: m.seeded || false,
|
||||
isExternal: false
|
||||
}));
|
||||
// Berechne Rankings für diese Gruppe
|
||||
const stats = {};
|
||||
|
||||
const externalParticipants = (g.externalGroupMembers || []).map(m => ({
|
||||
id: m.id,
|
||||
name: `${m.firstName} ${m.lastName}`,
|
||||
seeded: m.seeded || false,
|
||||
isExternal: true
|
||||
}));
|
||||
// Interne Teilnehmer
|
||||
for (const tm of g.tournamentGroupMembers || []) {
|
||||
stats[tm.id] = {
|
||||
id: tm.id,
|
||||
name: `${tm.member.firstName} ${tm.member.lastName}`,
|
||||
seeded: tm.seeded || false,
|
||||
isExternal: false,
|
||||
points: 0,
|
||||
setsWon: 0,
|
||||
setsLost: 0,
|
||||
pointsWon: 0,
|
||||
pointsLost: 0,
|
||||
pointRatio: 0
|
||||
};
|
||||
}
|
||||
|
||||
// Externe Teilnehmer
|
||||
for (const ext of g.externalGroupMembers || []) {
|
||||
stats[ext.id] = {
|
||||
id: ext.id,
|
||||
name: `${ext.firstName} ${ext.lastName}`,
|
||||
seeded: ext.seeded || false,
|
||||
isExternal: true,
|
||||
points: 0,
|
||||
setsWon: 0,
|
||||
setsLost: 0,
|
||||
pointsWon: 0,
|
||||
pointsLost: 0,
|
||||
pointRatio: 0
|
||||
};
|
||||
}
|
||||
|
||||
// Berechne Statistiken aus Matches
|
||||
for (const m of groupMatches.filter(m => m.groupId === g.id)) {
|
||||
if (!stats[m.player1Id] || !stats[m.player2Id]) continue;
|
||||
const [s1, s2] = m.result.split(':').map(n => parseInt(n, 10));
|
||||
|
||||
if (s1 > s2) {
|
||||
stats[m.player1Id].points += 1; // Sieger bekommt +1
|
||||
stats[m.player2Id].points -= 1; // Verlierer bekommt -1
|
||||
} else if (s2 > s1) {
|
||||
stats[m.player2Id].points += 1; // Sieger bekommt +1
|
||||
stats[m.player1Id].points -= 1; // Verlierer bekommt -1
|
||||
}
|
||||
|
||||
stats[m.player1Id].setsWon += s1;
|
||||
stats[m.player1Id].setsLost += s2;
|
||||
stats[m.player2Id].setsWon += s2;
|
||||
stats[m.player2Id].setsLost += s1;
|
||||
|
||||
// Berechne gespielte Punkte aus tournamentResults
|
||||
if (m.tournamentResults && m.tournamentResults.length > 0) {
|
||||
let p1Points = 0, p2Points = 0;
|
||||
for (const r of m.tournamentResults) {
|
||||
p1Points += r.pointsPlayer1 || 0;
|
||||
p2Points += r.pointsPlayer2 || 0;
|
||||
}
|
||||
stats[m.player1Id].pointsWon += p1Points;
|
||||
stats[m.player1Id].pointsLost += p2Points;
|
||||
stats[m.player2Id].pointsWon += p2Points;
|
||||
stats[m.player2Id].pointsLost += p1Points;
|
||||
}
|
||||
}
|
||||
|
||||
// Berechne Punktverhältnis und absolute Differenz für jeden Spieler
|
||||
Object.values(stats).forEach(s => {
|
||||
const totalPoints = s.pointsWon + s.pointsLost;
|
||||
s.pointRatio = totalPoints > 0 ? s.pointsWon / totalPoints : 0;
|
||||
s.setDiff = s.setsWon - s.setsLost;
|
||||
s.pointsDiff = s.pointsWon - s.pointsLost; // Absolute Differenz der gespielten Punkte
|
||||
});
|
||||
|
||||
// Sortiere nach: Punkte -> Satzverhältnis -> mehr gewonnene Sätze -> absolute Punktdifferenz -> mehr erzielte Spielpunkte -> direkter Vergleich
|
||||
const ranked = Object.values(stats).sort((a, b) => {
|
||||
// 1. Beste Punkte
|
||||
if (b.points !== a.points) return b.points - a.points;
|
||||
// 2. Besseres Satzverhältnis
|
||||
if (b.setDiff !== a.setDiff) return b.setDiff - a.setDiff;
|
||||
// 3. Bei Satzgleichheit: Wer mehr Sätze gewonnen hat
|
||||
if (b.setsWon !== a.setsWon) return b.setsWon - a.setsWon;
|
||||
// 4. Bessere absolute Differenz der gespielten Punkte (höhere Differenz zuerst)
|
||||
if (b.pointsDiff !== a.pointsDiff) return b.pointsDiff - a.pointsDiff;
|
||||
// 5. Bei Spielpunktgleichheit: Wer mehr Spielpunkte erzielt hat
|
||||
if (b.pointsWon !== a.pointsWon) return b.pointsWon - a.pointsWon;
|
||||
// 6. Direkter Vergleich (Sieger weiter oben)
|
||||
const directMatch = groupMatches.find(m =>
|
||||
m.groupId === g.id &&
|
||||
((m.player1Id === a.id && m.player2Id === b.id) ||
|
||||
(m.player1Id === b.id && m.player2Id === a.id))
|
||||
);
|
||||
if (directMatch) {
|
||||
const [s1, s2] = directMatch.result.split(':').map(n => parseInt(n, 10));
|
||||
const aWon = (directMatch.player1Id === a.id && s1 > s2) ||
|
||||
(directMatch.player2Id === a.id && s2 > s1);
|
||||
if (aWon) return -1; // a hat gewonnen -> a kommt weiter oben
|
||||
return 1; // b hat gewonnen -> b kommt weiter oben
|
||||
}
|
||||
// Fallback: Alphabetisch nach Name
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
|
||||
|
||||
// Weise Positionen zu, wobei Spieler mit identischen Werten den gleichen Platz bekommen
|
||||
let currentPosition = 1;
|
||||
const participantsWithPosition = ranked.map((p, i) => {
|
||||
if (i > 0) {
|
||||
const prev = ranked[i - 1];
|
||||
// Prüfe, ob alle Sortierkriterien identisch sind
|
||||
const pointsEqual = prev.points === p.points;
|
||||
const setDiffEqual = prev.setDiff === p.setDiff;
|
||||
const setsWonEqual = prev.setsWon === p.setsWon;
|
||||
const pointsDiffEqual = prev.pointsDiff === p.pointsDiff;
|
||||
const pointsWonEqual = prev.pointsWon === p.pointsWon;
|
||||
|
||||
if (pointsEqual && setDiffEqual && setsWonEqual && pointsDiffEqual && pointsWonEqual) {
|
||||
// Prüfe direkten Vergleich
|
||||
const directMatch = groupMatches.find(m =>
|
||||
m.groupId === g.id &&
|
||||
((m.player1Id === prev.id && m.player2Id === p.id) ||
|
||||
(m.player1Id === p.id && m.player2Id === prev.id))
|
||||
);
|
||||
if (!directMatch || directMatch.result.split(':').map(n => +n)[0] === directMatch.result.split(':').map(n => +n)[1]) {
|
||||
// Gleicher Platz wie Vorgänger (unentschieden oder kein direktes Match)
|
||||
return {
|
||||
...p,
|
||||
position: currentPosition,
|
||||
setDiff: p.setDiff,
|
||||
pointsDiff: p.pointsDiff
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
// Neuer Platz
|
||||
currentPosition = i + 1;
|
||||
return {
|
||||
...p,
|
||||
position: currentPosition,
|
||||
setDiff: p.setDiff,
|
||||
pointsDiff: p.pointsDiff
|
||||
};
|
||||
});
|
||||
|
||||
result.push({
|
||||
groupId: g.id,
|
||||
classId: g.classId,
|
||||
groupNumber: idx + 1, // Nummer innerhalb der Klasse
|
||||
participants: [...internalParticipants, ...externalParticipants]
|
||||
participants: participantsWithPosition
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -752,7 +889,8 @@ class TournamentService {
|
||||
]
|
||||
});
|
||||
const groupMatches = await TournamentMatch.findAll({
|
||||
where: { tournamentId, round: "group", isFinished: true }
|
||||
where: { tournamentId, round: "group", isFinished: true },
|
||||
include: [{ model: TournamentResult, as: "tournamentResults" }]
|
||||
});
|
||||
|
||||
const qualifiers = [];
|
||||
@@ -760,11 +898,11 @@ class TournamentService {
|
||||
const stats = {};
|
||||
// Interne Teilnehmer
|
||||
for (const tm of g.tournamentGroupMembers || []) {
|
||||
stats[tm.id] = { member: tm, points: 0, setsWon: 0, setsLost: 0, isExternal: false };
|
||||
stats[tm.id] = { member: tm, points: 0, setsWon: 0, setsLost: 0, pointsWon: 0, pointsLost: 0, isExternal: false };
|
||||
}
|
||||
// Externe Teilnehmer
|
||||
for (const ext of g.externalGroupMembers || []) {
|
||||
stats[ext.id] = { member: ext, points: 0, setsWon: 0, setsLost: 0, isExternal: true };
|
||||
stats[ext.id] = { member: ext, points: 0, setsWon: 0, setsLost: 0, pointsWon: 0, pointsLost: 0, isExternal: true };
|
||||
}
|
||||
for (const m of groupMatches.filter(m => m.groupId === g.id)) {
|
||||
if (!stats[m.player1Id] || !stats[m.player2Id]) continue;
|
||||
@@ -780,13 +918,55 @@ class TournamentService {
|
||||
stats[m.player1Id].setsLost += p2;
|
||||
stats[m.player2Id].setsWon += p2;
|
||||
stats[m.player2Id].setsLost += p1;
|
||||
|
||||
// Berechne gespielte Punkte aus tournamentResults
|
||||
if (m.tournamentResults && m.tournamentResults.length > 0) {
|
||||
let p1Points = 0, p2Points = 0;
|
||||
for (const r of m.tournamentResults) {
|
||||
p1Points += r.pointsPlayer1 || 0;
|
||||
p2Points += r.pointsPlayer2 || 0;
|
||||
}
|
||||
stats[m.player1Id].pointsWon += p1Points;
|
||||
stats[m.player1Id].pointsLost += p2Points;
|
||||
stats[m.player2Id].pointsWon += p2Points;
|
||||
stats[m.player2Id].pointsLost += p1Points;
|
||||
}
|
||||
}
|
||||
|
||||
// Berechne Punktverhältnis und absolute Differenz für jeden Spieler
|
||||
Object.values(stats).forEach(s => {
|
||||
const totalPoints = s.pointsWon + s.pointsLost;
|
||||
s.pointRatio = totalPoints > 0 ? s.pointsWon / totalPoints : 0;
|
||||
s.pointsDiff = s.pointsWon - s.pointsLost; // Absolute Differenz der gespielten Punkte
|
||||
});
|
||||
|
||||
const ranked = Object.values(stats).sort((a, b) => {
|
||||
const diffA = a.setsWon - a.setsLost;
|
||||
const diffB = b.setsWon - b.setsLost;
|
||||
// 1. Beste Punkte
|
||||
if (b.points !== a.points) return b.points - a.points;
|
||||
// 2. Besseres Satzverhältnis
|
||||
if (diffB !== diffA) return diffB - diffA;
|
||||
// 3. Bei Satzgleichheit: Wer mehr Sätze gewonnen hat
|
||||
if (b.setsWon !== a.setsWon) return b.setsWon - a.setsWon;
|
||||
// 4. Bessere absolute Differenz der gespielten Punkte (höhere Differenz zuerst)
|
||||
if (b.pointsDiff !== a.pointsDiff) return b.pointsDiff - a.pointsDiff;
|
||||
// 5. Bei Spielpunktgleichheit: Wer mehr Spielpunkte erzielt hat
|
||||
if (b.pointsWon !== a.pointsWon) return b.pointsWon - a.pointsWon;
|
||||
// 6. Direkter Vergleich (Sieger weiter oben)
|
||||
const directMatch = groupMatches.find(m =>
|
||||
m.groupId === g.id &&
|
||||
((m.player1Id === a.member.id && m.player2Id === b.member.id) ||
|
||||
(m.player1Id === b.member.id && m.player2Id === a.member.id))
|
||||
);
|
||||
if (directMatch) {
|
||||
const [s1, s2] = directMatch.result.split(":").map(n => parseInt(n, 10));
|
||||
const aWon = (directMatch.player1Id === a.member.id && s1 > s2) ||
|
||||
(directMatch.player2Id === a.member.id && s2 > s1);
|
||||
if (aWon) return -1; // a hat gewonnen -> a kommt weiter oben
|
||||
return 1; // b hat gewonnen -> b kommt weiter oben
|
||||
}
|
||||
// Fallback: Nach ID
|
||||
return a.member.id - b.member.id;
|
||||
});
|
||||
// Füge classId zur Gruppe hinzu
|
||||
@@ -903,8 +1083,25 @@ class TournamentService {
|
||||
throw new Error('Keine Teilnehmer zum Verteilen');
|
||||
}
|
||||
|
||||
// 2) Bestimme, wie viele Gruppen wir anlegen
|
||||
let groupCount;
|
||||
// 2) Hole bestehende Gruppen und erstelle nur neue, wenn nötig
|
||||
const existingGroups = await TournamentGroup.findAll({
|
||||
where: { tournamentId },
|
||||
order: [['id', 'ASC']]
|
||||
});
|
||||
|
||||
// Berechne die maximale groupNumber aus den assignments
|
||||
const maxGroupNumber = assignments.reduce((max, a) => {
|
||||
if (a.groupNumber != null && a.groupNumber > max) {
|
||||
return a.groupNumber;
|
||||
}
|
||||
return max;
|
||||
}, 0);
|
||||
|
||||
// Erstelle nur neue Gruppen, wenn die maximale groupNumber größer ist als die Anzahl der bestehenden Gruppen
|
||||
const existingGroupCount = existingGroups.length;
|
||||
let groupCount = Math.max(existingGroupCount, maxGroupNumber);
|
||||
|
||||
// Wenn numberOfGroups oder maxGroupSize gesetzt sind, verwende diese
|
||||
if (numberOfGroups != null) {
|
||||
groupCount = Number(numberOfGroups);
|
||||
if (isNaN(groupCount) || groupCount < 1) {
|
||||
@@ -915,21 +1112,17 @@ class TournamentService {
|
||||
if (isNaN(sz) || sz < 1) {
|
||||
throw new Error('Ungültige maximale Gruppengröße');
|
||||
}
|
||||
groupCount = Math.ceil(totalMembers / sz);
|
||||
} else {
|
||||
// Fallback auf im Turnier gespeicherte Anzahl
|
||||
groupCount = tournament.numberOfGroups;
|
||||
if (!groupCount || groupCount < 1) {
|
||||
throw new Error('Anzahl Gruppen nicht definiert');
|
||||
}
|
||||
groupCount = Math.max(groupCount, Math.ceil(totalMembers / sz));
|
||||
}
|
||||
|
||||
console.log(`[manualAssignGroups] Existing groups: ${existingGroupCount}, maxGroupNumber: ${maxGroupNumber}, target groupCount: ${groupCount}`);
|
||||
|
||||
// 3) Alte Gruppen löschen und neue anlegen
|
||||
await TournamentGroup.destroy({ where: { tournamentId } });
|
||||
const createdGroups = [];
|
||||
for (let i = 0; i < groupCount; i++) {
|
||||
// 3) Erstelle nur fehlende Gruppen
|
||||
const createdGroups = [...existingGroups];
|
||||
for (let i = existingGroupCount; i < groupCount; i++) {
|
||||
const grp = await TournamentGroup.create({ tournamentId });
|
||||
createdGroups.push(grp);
|
||||
console.log(`[manualAssignGroups] Created new group ${i + 1} with id ${grp.id}`);
|
||||
}
|
||||
|
||||
// 4) Mapping von UI‑Nummer (1…groupCount) auf reale DB‑ID
|
||||
@@ -937,77 +1130,142 @@ class TournamentService {
|
||||
createdGroups.forEach((grp, idx) => {
|
||||
groupMap[idx + 1] = grp.id;
|
||||
});
|
||||
|
||||
console.log(`[manualAssignGroups] Group mapping:`, groupMap);
|
||||
|
||||
// 5) Teilnehmer updaten (sowohl interne als auch externe)
|
||||
await Promise.all(
|
||||
assignments.map(async ({ participantId, groupNumber }) => {
|
||||
const dbGroupId = groupMap[groupNumber];
|
||||
if (!dbGroupId) {
|
||||
assignments.map(async ({ participantId, groupNumber, isExternal }) => {
|
||||
console.log(`[manualAssignGroups] Processing assignment: participantId=${participantId}, groupNumber=${groupNumber}, isExternal=${isExternal}`);
|
||||
console.log(`[manualAssignGroups] Group map:`, JSON.stringify(groupMap));
|
||||
|
||||
// Wenn groupNumber null ist, entferne den Teilnehmer aus der Gruppe (groupId = null)
|
||||
const dbGroupId = groupNumber != null ? groupMap[groupNumber] : null;
|
||||
console.log(`[manualAssignGroups] Calculated dbGroupId: ${dbGroupId} (from groupNumber ${groupNumber})`);
|
||||
|
||||
if (groupNumber != null && !dbGroupId) {
|
||||
throw new Error(`Ungültige Gruppen‑Nummer: ${groupNumber}`);
|
||||
}
|
||||
|
||||
// Prüfe zuerst, ob es ein interner Teilnehmer ist
|
||||
const internalMember = await TournamentMember.findOne({
|
||||
console.log(`[manualAssignGroups] Updating participant ${participantId}, isExternal: ${isExternal}, groupNumber: ${groupNumber}, dbGroupId: ${dbGroupId}`);
|
||||
|
||||
// Prüfe zuerst, ob es ein interner Teilnehmer ist (wenn isExternal nicht explizit true ist)
|
||||
if (isExternal !== true) {
|
||||
const internalMember = await TournamentMember.findOne({
|
||||
where: { id: participantId, tournamentId }
|
||||
});
|
||||
|
||||
if (internalMember) {
|
||||
// Interner Teilnehmer
|
||||
console.log(`[manualAssignGroups] Updating internal member id=${participantId} (memberId=${internalMember.memberId}) to groupId ${dbGroupId}`);
|
||||
await TournamentMember.update(
|
||||
{ groupId: dbGroupId },
|
||||
{ where: { id: participantId, tournamentId } }
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Versuche externen Teilnehmer
|
||||
const externalMember = await ExternalTournamentParticipant.findOne({
|
||||
where: { id: participantId, tournamentId }
|
||||
});
|
||||
|
||||
if (internalMember) {
|
||||
// Interner Teilnehmer
|
||||
return TournamentMember.update(
|
||||
if (externalMember) {
|
||||
// Externer Teilnehmer
|
||||
console.log(`[manualAssignGroups] Updating external member id=${participantId} to groupId ${dbGroupId}`);
|
||||
await ExternalTournamentParticipant.update(
|
||||
{ groupId: dbGroupId },
|
||||
{ where: { id: participantId, tournamentId } }
|
||||
);
|
||||
} else {
|
||||
// Versuche externen Teilnehmer
|
||||
const externalMember = await ExternalTournamentParticipant.findOne({
|
||||
where: { id: participantId, tournamentId }
|
||||
});
|
||||
|
||||
if (externalMember) {
|
||||
// Externer Teilnehmer
|
||||
return ExternalTournamentParticipant.update(
|
||||
{ groupId: dbGroupId },
|
||||
{ where: { id: participantId, tournamentId } }
|
||||
);
|
||||
} else {
|
||||
throw new Error(`Teilnehmer mit ID ${participantId} nicht gefunden`);
|
||||
}
|
||||
throw new Error(`Teilnehmer mit ID ${participantId} nicht gefunden`);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// 6) Ergebnis zurückliefern wie getGroupsWithParticipants
|
||||
const groups = await TournamentGroup.findAll({
|
||||
// 6) Ergebnis zurückliefern wie getGroupsWithParticipants (mit vollständigen Rankings)
|
||||
// Verwende die gleiche Methode wie getGroupsWithParticipants, um konsistente Daten zu liefern
|
||||
return await this.getGroupsWithParticipants(userToken, clubId, tournamentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ordne einen einzelnen Teilnehmer einer Gruppe zu
|
||||
* @param {string} userToken - User authentication token
|
||||
* @param {number} clubId - Club ID
|
||||
* @param {number} tournamentId - Tournament ID
|
||||
* @param {number} participantId - Participant ID
|
||||
* @param {number|null} groupNumber - Group number (1-based) or null to remove from group
|
||||
* @param {boolean} isExternal - Whether the participant is external
|
||||
* @returns {Promise<Object>} Updated groups with participants
|
||||
*/
|
||||
async assignParticipantToGroup(userToken, clubId, tournamentId, participantId, groupNumber, isExternal = false) {
|
||||
console.log(`[assignParticipantToGroup] Called with: participantId=${participantId}, groupNumber=${groupNumber}, isExternal=${isExternal}, tournamentId=${tournamentId}, clubId=${clubId}`);
|
||||
await checkAccess(userToken, clubId);
|
||||
|
||||
const tournament = await Tournament.findByPk(tournamentId);
|
||||
if (!tournament || tournament.clubId != clubId) {
|
||||
throw new Error('Turnier nicht gefunden');
|
||||
}
|
||||
|
||||
// Hole bestehende Gruppen
|
||||
const existingGroups = await TournamentGroup.findAll({
|
||||
where: { tournamentId },
|
||||
include: [{
|
||||
model: TournamentMember,
|
||||
as: 'tournamentGroupMembers',
|
||||
include: [{ model: Member, as: 'member', attributes: ['id', 'firstName', 'lastName'] }]
|
||||
}, {
|
||||
model: ExternalTournamentParticipant,
|
||||
as: 'externalGroupMembers'
|
||||
}],
|
||||
order: [['id', 'ASC']]
|
||||
});
|
||||
console.log(`[assignParticipantToGroup] Found ${existingGroups.length} existing groups`);
|
||||
|
||||
return groups.map(g => {
|
||||
const internalParticipants = (g.tournamentGroupMembers || []).map(m => ({
|
||||
id: m.id,
|
||||
name: `${m.member.firstName} ${m.member.lastName}`,
|
||||
isExternal: false
|
||||
}));
|
||||
// Berechne dbGroupId
|
||||
let dbGroupId = null;
|
||||
if (groupNumber != null) {
|
||||
// Stelle sicher, dass genug Gruppen existieren
|
||||
if (groupNumber > existingGroups.length) {
|
||||
console.log(`[assignParticipantToGroup] Creating ${groupNumber - existingGroups.length} new groups`);
|
||||
// Erstelle fehlende Gruppen
|
||||
for (let i = existingGroups.length; i < groupNumber; i++) {
|
||||
const grp = await TournamentGroup.create({ tournamentId });
|
||||
existingGroups.push(grp);
|
||||
}
|
||||
}
|
||||
// Mapping von groupNumber (1-based) zu groupId
|
||||
dbGroupId = existingGroups[groupNumber - 1].id;
|
||||
console.log(`[assignParticipantToGroup] Mapped groupNumber ${groupNumber} to dbGroupId ${dbGroupId}`);
|
||||
}
|
||||
|
||||
// Aktualisiere den Teilnehmer
|
||||
if (isExternal) {
|
||||
const externalMember = await ExternalTournamentParticipant.findOne({
|
||||
where: { id: participantId, tournamentId }
|
||||
});
|
||||
|
||||
const externalParticipants = (g.externalGroupMembers || []).map(m => ({
|
||||
id: m.id,
|
||||
name: `${m.firstName} ${m.lastName}`,
|
||||
isExternal: true
|
||||
}));
|
||||
if (!externalMember) {
|
||||
throw new Error(`Externer Teilnehmer mit ID ${participantId} nicht gefunden`);
|
||||
}
|
||||
|
||||
return {
|
||||
groupId: g.id,
|
||||
participants: [...internalParticipants, ...externalParticipants]
|
||||
};
|
||||
});
|
||||
console.log(`[assignParticipantToGroup] Updating external member ${participantId} to groupId ${dbGroupId}`);
|
||||
await ExternalTournamentParticipant.update(
|
||||
{ groupId: dbGroupId },
|
||||
{ where: { id: participantId, tournamentId } }
|
||||
);
|
||||
} else {
|
||||
const internalMember = await TournamentMember.findOne({
|
||||
where: { id: participantId, tournamentId }
|
||||
});
|
||||
|
||||
if (!internalMember) {
|
||||
throw new Error(`Interner Teilnehmer mit ID ${participantId} nicht gefunden`);
|
||||
}
|
||||
|
||||
console.log(`[assignParticipantToGroup] Updating internal member ${participantId} (memberId=${internalMember.memberId}) to groupId ${dbGroupId}`);
|
||||
await TournamentMember.update(
|
||||
{ groupId: dbGroupId },
|
||||
{ where: { id: participantId, tournamentId } }
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`[assignParticipantToGroup] Successfully updated participant, loading groups with participants...`);
|
||||
// Lade aktualisierte Gruppen mit Teilnehmern zurück
|
||||
return await this.getGroupsWithParticipants(userToken, clubId, tournamentId);
|
||||
}
|
||||
|
||||
// services/tournamentService.js
|
||||
|
||||
Reference in New Issue
Block a user