import { promises as fs } from 'fs' import { getServerDataPath } from './paths.js' import { readSpielplanData } from './spielplan-data.js' const CLUB_ID = '43030' function parseCsvRows(content) { const rows = [] let row = [] let value = '' let quoted = false for (let index = 0; index < content.length; index += 1) { const char = content[index] if (char === '"') { if (quoted && content[index + 1] === '"') { value += '"' index += 1 } else { quoted = !quoted } } else if (char === ',' && !quoted) { row.push(value) value = '' } else if ((char === '\n' || char === '\r') && !quoted) { if (char === '\r' && content[index + 1] === '\n') index += 1 row.push(value) if (row.some(cell => cell !== '')) rows.push(row) row = [] value = '' } else { value += char } } row.push(value) if (row.some(cell => cell !== '')) rows.push(row) return rows } function csvCell(value) { const text = String(value ?? '') return /[",\n\r]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text } function romanToNumber(value) { const roman = String(value || '').toUpperCase() const values = { I: 1, V: 5, X: 10 } let result = 0 for (let index = 0; index < roman.length; index += 1) { const current = values[roman[index]] || 0 const next = values[roman[index + 1]] || 0 result += current < next ? -current : current } return result || null } function getCmsTeamCriteria(name, league) { const teamName = String(name || '').trim() const youth = teamName.match(/^jugend\s+j(\d{1,2})(?:\s+([ivx]+|\d+))?\b/i) if (youth) { const rawNumber = youth[2] return { ageClass: `Jugend ${youth[1]}`, teamNumber: rawNumber ? (Number(rawNumber) || romanToNumber(rawNumber)) : null } } // In bestehenden CMS-Daten steht die Altersklasse häufig nur in der Liga, // z. B. Mannschaft "Jugend I" mit Liga "Jugend 13 (J13)". const ageMatch = `${teamName} ${String(league || '')}`.match(/\b(?:jugend\s*|j\s*\(?)(\d{1,2})\b/i) if (ageMatch) { const ordinal = teamName.match(/^jugend\s+([ivx]+|\d+)\b/i)?.[1] return { ageClass: `Jugend ${ageMatch[1]}`, teamNumber: ordinal ? (Number(ordinal) || romanToNumber(ordinal)) : null } } const adult = teamName.match(/^erwachsene\s+(\d+)\b/i) if (adult) return { ageClass: 'Erwachsene', teamNumber: Number(adult[1]) } return null } function collectImportedTeams(rows) { const teams = new Map() for (const row of rows) { for (const side of ['Heim', 'Gast']) { if (String(row[`${side}VereinNr`] || '') !== CLUB_ID) continue const id = String(row[`${side}MannschaftId`] || '').trim() if (!id) continue teams.set(id, { id, ageClass: String(row[`${side}MannschaftAltersklasse`] || '').trim(), teamNumber: Number(row[`${side}MannschaftNr`]) || null }) } } return [...teams.values()] } /** * Adds every league and cup team ID matching a CMS team to its CSV row. * A manually chosen ID is retained if no automatic match can be derived. */ export async function synchronizeMannschaftenSpielplanIds(seasonSlug) { const filePath = getServerDataPath('public-data', `mannschaften_${seasonSlug}.csv`) let content try { content = await fs.readFile(filePath, 'utf8') } catch (error) { if (error?.code === 'ENOENT') return { updatedTeams: 0, skipped: true } throw error } const parsed = parseCsvRows(content) if (parsed.length < 2) return { updatedTeams: 0 } const header = parsed[0] let idColumn = header.indexOf('Spielplan Mannschaft ID') if (idColumn < 0) { header.push('Spielplan Mannschaft ID') idColumn = header.length - 1 } const spielplan = await readSpielplanData({ season: seasonSlug }) const importedTeams = collectImportedTeams(spielplan.data) let updatedTeams = 0 for (const row of parsed.slice(1)) { const criteria = getCmsTeamCriteria(row[0], row[1]) if (!criteria) continue const ids = importedTeams .filter(team => team.ageClass === criteria.ageClass && (!criteria.teamNumber || team.teamNumber === criteria.teamNumber)) .map(team => team.id) .sort((a, b) => Number(a) - Number(b)) if (!ids.length) continue const nextValue = ids.join(',') if (row[idColumn] === nextValue) continue row[idColumn] = nextValue updatedTeams += 1 } if (updatedTeams) { const output = `${parsed.map(row => header.map((_, index) => csvCell(row[index])).join(',')).join('\n')}\n` const temporaryPath = `${filePath}.tmp-${process.pid}-${Date.now()}` await fs.writeFile(temporaryPath, output, 'utf8') await fs.rename(temporaryPath, filePath) } return { updatedTeams, importedTeamCount: importedTeams.length } }