diff --git a/backend/controllers/officialTournamentController.js b/backend/controllers/officialTournamentController.js index 9e1db96..8100aa9 100644 --- a/backend/controllers/officialTournamentController.js +++ b/backend/controllers/officialTournamentController.js @@ -29,6 +29,7 @@ export const uploadTournamentPdf = async (req, res) => { venues: JSON.stringify(parsed.austragungsorte || []), competitionTypes: JSON.stringify(parsed.konkurrenztypen || []), registrationDeadlines: JSON.stringify(parsed.meldeschluesse || []), + entryFees: JSON.stringify(parsed.entryFees || {}), }); // competitions persistieren for (const c of parsed.competitions || []) { @@ -111,6 +112,7 @@ export const getParsedTournament = async (req, res) => { austragungsorte: JSON.parse(t.venues || '[]'), konkurrenztypen: JSON.parse(t.competitionTypes || '[]'), meldeschluesse: JSON.parse(t.registrationDeadlines || '[]'), + entryFees: JSON.parse(t.entryFees || '{}'), competitions, }, participation: entries.map(e => ({ @@ -365,6 +367,67 @@ function parseTournamentText(text) { return idx >= 0 ? normLines[idx] : null; }; + // Neue Funktion: Teilnahmegebühren pro Spielklasse extrahieren + const extractEntryFees = () => { + const entryFees = {}; + + // Verschiedene Patterns für Teilnahmegebühren suchen + const feePatterns = [ + // Pattern 1: "Startgeld: U12: 5€, U14: 7€, U16: 10€" + /startgeld\s*:?\s*(.+)/i, + // Pattern 2: "Teilnahmegebühr: U12: 5€, U14: 7€" + /teilnahmegebühr\s*:?\s*(.+)/i, + // Pattern 3: "Gebühr: U12: 5€, U14: 7€" + /gebühr\s*:?\s*(.+)/i, + // Pattern 4: "Einschreibegebühr: U12: 5€, U14: 7€" + /einschreibegebühr\s*:?\s*(.+)/i, + // Pattern 5: "Anmeldegebühr: U12: 5€, U14: 7€" + /anmeldegebühr\s*:?\s*(.+)/i + ]; + + for (const pattern of feePatterns) { + for (let i = 0; i < normLines.length; i++) { + const line = normLines[i]; + const match = line.match(pattern); + if (match) { + const feeText = match[1]; + + // Extrahiere Gebühren aus dem Text + // Unterstützt verschiedene Formate: + // "U12: 5€, U14: 7€, U16: 10€" + // "U12: 5 Euro, U14: 7 Euro" + // "U12 5€, U14 7€" + // "U12: 5,00€, U14: 7,00€" + const feeMatches = feeText.matchAll(/(U\d+|AK\s*\d+)\s*:?\s*(\d+(?:[,.]\d+)?)\s*(?:€|Euro|EUR)?/gi); + + for (const feeMatch of feeMatches) { + const ageClass = feeMatch[1].toUpperCase().replace(/\s+/g, ''); + const amount = feeMatch[2].replace(',', '.'); + const numericAmount = parseFloat(amount); + + if (!isNaN(numericAmount)) { + entryFees[ageClass] = { + amount: numericAmount, + currency: '€', + rawText: feeMatch[0] + }; + } + } + + // Wenn wir Gebühren gefunden haben, brechen wir ab + if (Object.keys(entryFees).length > 0) { + break; + } + } + } + if (Object.keys(entryFees).length > 0) { + break; + } + } + + return entryFees; + }; + const extractBlockAfter = (labels, multiline = false) => { const idx = normLines.findIndex(l => labels.some(lb => l.toLowerCase().startsWith(lb))); if (idx === -1) return multiline ? [] : null; @@ -467,7 +530,26 @@ function parseTournamentText(text) { else if (key.startsWith('austragungssys. vorrunde')) comp.vorrunde = val; else if (key.startsWith('austragungssys. endrunde')) comp.endrunde = val; else if (key.startsWith('max. teilnehmerzahl')) comp.maxTeilnehmer = val; - else if (key === 'startgeld') comp.startgeld = val; + else if (key === 'startgeld') { + comp.startgeld = val; + // Versuche auch spezifische Gebühren für diese Altersklasse zu extrahieren + const ageClassMatch = comp.altersklasseWettbewerb?.match(/(U\d+|AK\s*\d+)/i); + if (ageClassMatch) { + const ageClass = ageClassMatch[1].toUpperCase().replace(/\s+/g, ''); + const feeMatch = val.match(/(\d+(?:[,.]\d+)?)\s*(?:€|Euro|EUR)?/); + if (feeMatch) { + const amount = feeMatch[1].replace(',', '.'); + const numericAmount = parseFloat(amount); + if (!isNaN(numericAmount)) { + comp.entryFeeDetails = { + amount: numericAmount, + currency: '€', + ageClass: ageClass + }; + } + } + } + } } i++; } @@ -515,6 +597,9 @@ function parseTournamentText(text) { } } + // Extrahiere Teilnahmegebühren + const entryFees = extractEntryFees(); + return { title, termin, @@ -526,6 +611,7 @@ function parseTournamentText(text) { startzeiten: {}, competitions, entries, + entryFees, // Neue: Teilnahmegebühren pro Spielklasse debug: { normLines }, }; } diff --git a/backend/models/OfficialTournament.js b/backend/models/OfficialTournament.js index 69b582c..567d6d0 100644 --- a/backend/models/OfficialTournament.js +++ b/backend/models/OfficialTournament.js @@ -11,6 +11,7 @@ const OfficialTournament = sequelize.define('OfficialTournament', { venues: { type: DataTypes.TEXT, allowNull: true }, // JSON.stringify(Array) competitionTypes: { type: DataTypes.TEXT, allowNull: true }, // JSON.stringify(Array) registrationDeadlines: { type: DataTypes.TEXT, allowNull: true }, // JSON.stringify(Array) + entryFees: { type: DataTypes.TEXT, allowNull: true }, // JSON.stringify(Object) - Teilnahmegebühren pro Spielklasse }, { tableName: 'official_tournaments', timestamps: true, diff --git a/frontend/src/components/PDFGenerator.js b/frontend/src/components/PDFGenerator.js index 9345109..1793bf4 100644 --- a/frontend/src/components/PDFGenerator.js +++ b/frontend/src/components/PDFGenerator.js @@ -354,13 +354,15 @@ class PDFGenerator { this.pdf.setFont('helvetica', 'bold'); this.pdf.setFontSize(12); this.pdf.text('Wettbewerb', this.margin, y); - this.pdf.text('Datum', this.margin + 110, y); - this.pdf.text('Startzeit', this.margin + 150, y); + this.pdf.text('Datum', this.margin + 80, y); + this.pdf.text('Startzeit', this.margin + 120, y); + this.pdf.text('Gebühr', this.margin + 160, y); y += 7; for (const r of recommendedRows) { this.pdf.text(r.name || '', this.margin, y); - this.pdf.text(r.date || '–', this.margin + 110, y); - this.pdf.text(r.time || '–', this.margin + 150, y); + this.pdf.text(r.date || '–', this.margin + 80, y); + this.pdf.text(r.time || '–', this.margin + 120, y); + this.pdf.text(r.entryFee || '–', this.margin + 160, y); y += 7; if (y > this.pageHeight) { this.addNewPage(); @@ -381,8 +383,9 @@ class PDFGenerator { this.pdf.setFontSize(12); for (const r of otherRows) { this.pdf.text(r.name || '', this.margin, y); - this.pdf.text(r.date || '–', this.margin + 110, y); - this.pdf.text(r.time || '–', this.margin + 150, y); + this.pdf.text(r.date || '–', this.margin + 80, y); + this.pdf.text(r.time || '–', this.margin + 120, y); + this.pdf.text(r.entryFee || '–', this.margin + 160, y); y += 7; if (y > this.pageHeight) { this.addNewPage(); diff --git a/frontend/src/views/OfficialTournaments.vue b/frontend/src/views/OfficialTournaments.vue index aea31c0..4980838 100644 --- a/frontend/src/views/OfficialTournaments.vue +++ b/frontend/src/views/OfficialTournaments.vue @@ -43,6 +43,15 @@ {{ ak }}: {{ arr.join(', ') }} +
+ Teilnahmegebühren: +
+
+ {{ ageClass }}: + {{ fee.amount }}{{ fee.currency }} +
+
+
@@ -898,13 +907,40 @@ export default { }, competitionsForMember(member) { const comps = (this.parsed && this.parsed.parsedData && this.parsed.parsedData.competitions) ? this.parsed.parsedData.competitions : []; + const entryFees = (this.parsed && this.parsed.parsedData && this.parsed.parsedData.entryFees) ? this.parsed.parsedData.entryFees : {}; const rows = []; for (let idx = 0; idx < comps.length; idx++) { const c = comps[idx]; if (this.isEligibleForCompetition(member, c)) { const title = c.ageClassCompetition || c.altersklasseWettbewerb || ''; const st = this.splitDateTime(c.startTime || c.startzeit || ''); - rows.push({ key: String(idx), name: title, date: st.date, time: st.time, raw: c }); + + // Bestimme die Teilnahmegebühr für diese Altersklasse + let entryFee = ''; + const ageClassMatch = title.match(/(U\d+|AK\s*\d+)/i); + if (ageClassMatch) { + const ageClass = ageClassMatch[1].toUpperCase().replace(/\s+/g, ''); + if (entryFees[ageClass]) { + entryFee = `${entryFees[ageClass].amount}${entryFees[ageClass].currency}`; + } + } + + // Fallback: Verwende startgeld aus der Konkurrenz + if (!entryFee && c.startgeld) { + const feeMatch = c.startgeld.match(/(\d+(?:[,.]\d+)?)\s*(?:€|Euro|EUR)?/); + if (feeMatch) { + entryFee = `${feeMatch[1]}€`; + } + } + + rows.push({ + key: String(idx), + name: title, + date: st.date, + time: st.time, + entryFee: entryFee || '–', + raw: c + }); } } return rows; @@ -1261,6 +1297,35 @@ th, td { border-bottom: 1px solid var(--border-color); padding: 0.5rem; text-ali font-style: italic; font-size: 0.85rem; } + +/* Entry Fees Styling */ +.entry-fees { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin-top: 0.25rem; +} + +.fee-item { + display: flex; + align-items: center; + gap: 0.25rem; + background-color: #f8f9fa; + border: 1px solid #dee2e6; + border-radius: 4px; + padding: 0.25rem 0.5rem; + font-size: 0.9rem; +} + +.age-class { + font-weight: 600; + color: #495057; +} + +.fee-amount { + font-weight: 700; + color: #28a745; +}