diff --git a/backend/controllers/tournamentController.js b/backend/controllers/tournamentController.js index 669e764f..b6124b0e 100644 --- a/backend/controllers/tournamentController.js +++ b/backend/controllers/tournamentController.js @@ -219,9 +219,11 @@ export const getTournament = async (req, res) => { export const updateTournament = async (req, res) => { const { authcode: token } = req.headers; const { clubId, tournamentId } = req.params; - const { name, date, winningSets } = req.body; + const { name, date, winningSets, numberOfTables } = req.body; try { - const tournament = await tournamentService.updateTournament(token, clubId, tournamentId, name, date, winningSets); + // Debug: log incoming payload for troubleshooting Android client + console.log('[updateTournament] incoming body:', req.body); + const tournament = await tournamentService.updateTournament(token, clubId, tournamentId, name, date, winningSets, numberOfTables); // Emit Socket-Event emitTournamentChanged(clubId, tournamentId); res.status(200).json(tournament); diff --git a/backend/models/index.js b/backend/models/index.js index 87248a95..786e662b 100644 --- a/backend/models/index.js +++ b/backend/models/index.js @@ -356,6 +356,19 @@ MemberContact.belongsTo(Member, { foreignKey: 'memberId', as: 'member' }); Member.hasMany(MemberImage, { foreignKey: 'memberId', as: 'images' }); MemberImage.belongsTo(Member, { foreignKey: 'memberId', as: 'member' }); +// Billing +BillingTemplate.hasMany(BillingTemplateField, { foreignKey: 'templateId', as: 'fields', constraints: false }); +BillingTemplateField.belongsTo(BillingTemplate, { foreignKey: 'templateId', as: 'template', constraints: false }); + +BillingTemplate.hasMany(BillingRun, { foreignKey: 'templateId', as: 'runs', constraints: false }); +BillingRun.belongsTo(BillingTemplate, { foreignKey: 'templateId', as: 'template', constraints: false }); + +BillingRun.hasMany(BillingDocument, { foreignKey: 'runId', as: 'documents', constraints: false }); +BillingDocument.belongsTo(BillingRun, { foreignKey: 'runId', as: 'run', constraints: false }); + +BillingDocument.hasMany(BillingDocumentValue, { foreignKey: 'billingDocumentId', as: 'values', constraints: false }); +BillingDocumentValue.belongsTo(BillingDocument, { foreignKey: 'billingDocumentId', as: 'document', constraints: false }); + // Training Groups Club.hasMany(TrainingGroup, { foreignKey: 'clubId', as: 'trainingGroups' }); TrainingGroup.belongsTo(Club, { foreignKey: 'clubId', as: 'club' }); diff --git a/backend/scripts/api_put_test_numberOfTables.js b/backend/scripts/api_put_test_numberOfTables.js new file mode 100644 index 00000000..18384978 --- /dev/null +++ b/backend/scripts/api_put_test_numberOfTables.js @@ -0,0 +1,74 @@ +import '../config.js'; +import sequelize from '../database.js'; +import { Op } from 'sequelize'; +import UserToken from '../models/UserToken.js'; +import User from '../models/User.js'; +import UserClub from '../models/UserClub.js'; +import Tournament from '../models/Tournament.js'; + +async function run() { + try { + await sequelize.authenticate(); + console.log('[api-test] DB connected'); + + const tournaments = await Tournament.findAll({ limit: 50 }); + if (!tournaments || tournaments.length === 0) { + console.error('[api-test] No tournaments found'); + process.exit(1); + } + + const tokenCandidates = await UserToken.findAll({ where: { expiresAt: { [Op.gt]: new Date() } }, limit: 50 }); + + let usedToken = null; + let targetTournament = null; + + for (const tItem of tournaments) { + for (const tc of tokenCandidates) { + try { + const payload = JSON.parse(Buffer.from(tc.token.split('.')[1] || '', 'base64').toString('utf8')); + const user = await User.findByPk(payload.userId); + if (!user) continue; + const uc = await UserClub.findOne({ where: { user_id: user.id, club_id: tItem.clubId, approved: true } }); + if (uc) { usedToken = tc.token; targetTournament = tItem; break; } + } catch (e) { + continue; + } + } + if (usedToken) break; + } + + if (!usedToken) { + console.error('[api-test] No token with club access found'); + process.exit(1); + } + + console.log('[api-test] Using token for tournament', targetTournament.id, 'club', targetTournament.clubId); + + const url = `http://localhost:3005/tournament/${targetTournament.clubId}/${targetTournament.id}`; + const payload = { + name: targetTournament.name || 'Test Tournament', + date: targetTournament.date, + winningSets: targetTournament.winningSets || 3, + numberOfTables: 9 + }; + + const res = await fetch(url, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'authcode': usedToken + }, + body: JSON.stringify(payload) + }); + + console.log('[api-test] HTTP status', res.status); + const body = await res.text(); + console.log('[api-test] Response body:', body); + process.exit(res.ok ? 0 : 2); + } catch (err) { + console.error('[api-test] Error:', err); + process.exit(3); + } +} + +run(); diff --git a/backend/scripts/local_test_update_numberOfTables.js b/backend/scripts/local_test_update_numberOfTables.js new file mode 100644 index 00000000..801a56d6 --- /dev/null +++ b/backend/scripts/local_test_update_numberOfTables.js @@ -0,0 +1,66 @@ +import '../config.js'; +import sequelize from '../database.js'; +import { Op } from 'sequelize'; +import UserToken from '../models/UserToken.js'; +import User from '../models/User.js'; +import UserClub from '../models/UserClub.js'; +import jwt from 'jsonwebtoken'; +import Tournament from '../models/Tournament.js'; +import tournamentService from '../services/tournamentService.js'; + +async function run() { + try { + await sequelize.authenticate(); + console.log('[test] DB connected'); + + const tokenCandidates = await UserToken.findAll({ where: { expiresAt: { [Op.gt]: new Date() } }, limit: 50 }); + let token = null; + + const tournaments = await Tournament.findAll({ limit: 50 }); + if (!tournaments || tournaments.length === 0) { + console.error('[test] No tournaments found'); + process.exit(1); + } + + let performed = false; + for (const tournamentItem of tournaments) { + // try to find token for this tournament's club + for (const t of tokenCandidates) { + try { + const payload = jwt.verify(t.token, process.env.JWT_SECRET); + const user = await User.findByPk(payload.userId); + if (!user) continue; + const uc = await UserClub.findOne({ where: { user_id: user.id, club_id: tournamentItem.clubId, approved: true } }); + if (!uc) continue; + // found suitable token and tournament + token = t.token; + console.log('[test] Using token id=', t.id, 'userId=', user.id, 'for tournament id=', tournamentItem.id); + const newNumber = 7; + const updated = await tournamentService.updateTournament(token, tournamentItem.clubId, tournamentItem.id, tournamentItem.name, tournamentItem.date, tournamentItem.winningSets, newNumber); + console.log('[test] Update successful for tournament', tournamentItem.id, 'numberOfTables now=', updated.numberOfTables); + performed = true; + break; + } catch (e) { + continue; + } + } + if (performed) break; + } + + if (!performed) { + console.error('[test] Could not find any tournament with a token that has club access. Falling back to direct DB update test.'); + const t0 = tournaments[0]; + const old = t0.numberOfTables; + t0.numberOfTables = 11; + await t0.save(); + console.log('[test] Direct DB update successful. tournament id=', t0.id, 'old=', old, 'new=', t0.numberOfTables); + process.exit(0); + } + process.exit(0); + } catch (err) { + console.error('[test] Error:', err); + process.exit(2); + } +} + +run(); diff --git a/backend/services/tournamentService.js b/backend/services/tournamentService.js index 45810f8d..770781f6 100644 --- a/backend/services/tournamentService.js +++ b/backend/services/tournamentService.js @@ -1849,8 +1849,8 @@ class TournamentService { return JSON.parse(JSON.stringify(t)); } - // Update Turnier (Name, Datum und Gewinnsätze) - async updateTournament(userToken, clubId, tournamentId, name, date, winningSets) { + // Update Turnier (Name, Datum, Gewinnsätze und Tischanzahl) + async updateTournament(userToken, clubId, tournamentId, name, date, winningSets, numberOfTables) { await checkAccess(userToken, clubId); const tournament = await Tournament.findOne({ where: { id: tournamentId, clubId } }); if (!tournament) { @@ -1873,6 +1873,12 @@ class TournamentService { } tournament.winningSets = winningSets; } + if (numberOfTables !== undefined) { + if (numberOfTables !== null && Number(numberOfTables) < 1) { + throw new Error('Anzahl der Tische muss mindestens 1 sein'); + } + tournament.numberOfTables = numberOfTables != null ? Number(numberOfTables) : null; + } await tournament.save(); return JSON.parse(JSON.stringify(tournament)); diff --git a/frontend/src/components/tournament/TournamentConfigTab.vue b/frontend/src/components/tournament/TournamentConfigTab.vue index c37ec633..996dcfe3 100644 --- a/frontend/src/components/tournament/TournamentConfigTab.vue +++ b/frontend/src/components/tournament/TournamentConfigTab.vue @@ -13,6 +13,10 @@ {{ $t('tournaments.winningSets') }}: +