151 lines
4.8 KiB
JavaScript
151 lines
4.8 KiB
JavaScript
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(?:mannschaft)?\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(?:mannschaft)?\s*|j\s*\(?)(\d{1,2})\b/i)
|
|
if (ageMatch) {
|
|
const ordinal = teamName.match(/^jugend(?:mannschaft)?\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 }
|
|
}
|