Enhance worshipController.js with improved leader mapping and text resolution: Introduce matchEntries for better leader identification, refactor parseNbrSegment to utilize new leaderMaps structure, and add buildWorshipTitleFromPlace for dynamic title generation based on event place names, improving data parsing and clarity in worship planning.
All checks were successful
Deploy miriamgemeinde / deploy (push) Successful in 7s
All checks were successful
Deploy miriamgemeinde / deploy (push) Successful in 7s
This commit is contained in:
@@ -402,6 +402,7 @@ async function parseXlsxToRecords(buffer) {
|
||||
function buildLeaderMaps(leaders) {
|
||||
const codeToName = new Map();
|
||||
const normalizedToName = new Map();
|
||||
const matchEntries = [];
|
||||
for (const leader of leaders || []) {
|
||||
if (!leader?.active) continue;
|
||||
const code = normalizeText(leader.code);
|
||||
@@ -409,6 +410,11 @@ function buildLeaderMaps(leaders) {
|
||||
if (code) {
|
||||
codeToName.set(code, name);
|
||||
normalizedToName.set(code.toLowerCase(), name);
|
||||
matchEntries.push({ key: code.toLowerCase(), name });
|
||||
}
|
||||
if (name) {
|
||||
normalizedToName.set(name.toLowerCase(), name);
|
||||
matchEntries.push({ key: name.toLowerCase(), name });
|
||||
}
|
||||
const aliases = String(leader.aliases || '')
|
||||
.split(',')
|
||||
@@ -416,9 +422,55 @@ function buildLeaderMaps(leaders) {
|
||||
.filter(Boolean);
|
||||
for (const alias of aliases) {
|
||||
normalizedToName.set(alias.toLowerCase(), name);
|
||||
matchEntries.push({ key: alias.toLowerCase(), name });
|
||||
}
|
||||
}
|
||||
return { codeToName, normalizedToName };
|
||||
// Längere Keys zuerst (z.B. ganzer Name vor Kürzel).
|
||||
matchEntries.sort((a, b) => b.key.length - a.key.length);
|
||||
return { codeToName, normalizedToName, matchEntries };
|
||||
}
|
||||
|
||||
function escapeRegex(text) {
|
||||
return String(text || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
}
|
||||
|
||||
function resolveLeaderFromText(text, leaderMaps) {
|
||||
const source = normalizeText(text);
|
||||
const lowerSource = source.toLowerCase();
|
||||
let resolvedName = '';
|
||||
let cleaned = source;
|
||||
|
||||
for (const entry of leaderMaps?.matchEntries || []) {
|
||||
const key = entry.key;
|
||||
if (!key) continue;
|
||||
const boundaryPattern = new RegExp(`(^|\\s|[(/,-])${escapeRegex(key)}($|\\s|[)/,-])`, 'i');
|
||||
if (!boundaryPattern.test(lowerSource)) continue;
|
||||
resolvedName = entry.name;
|
||||
cleaned = cleaned.replace(new RegExp(`\\b${escapeRegex(key)}\\b`, 'ig'), ' ').replace(/\s+/g, ' ').trim();
|
||||
break;
|
||||
}
|
||||
|
||||
return { resolvedName, cleanedText: cleaned };
|
||||
}
|
||||
|
||||
function buildWorshipTitleFromPlace(placeName, fallback = 'Gottesdienst') {
|
||||
const raw = normalizeText(placeName);
|
||||
if (!raw) return fallback;
|
||||
let church = raw;
|
||||
|
||||
// Typische Muster: "Evangelische Kirche Nieder-Eschbach" -> "Nieder-Eschbach"
|
||||
const afterKirche = raw.match(/kirche\s+(.+)$/i);
|
||||
if (afterKirche && afterKirche[1]) {
|
||||
church = normalizeText(afterKirche[1]);
|
||||
} else {
|
||||
const parts = raw.split(/\s*(?:,|\/|->|\(|\)|-)\s*/).filter(Boolean);
|
||||
if (parts.length > 0) {
|
||||
church = normalizeText(parts[parts.length - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!church) return fallback;
|
||||
return `Gottesdienst in ${church}`;
|
||||
}
|
||||
|
||||
function resolveEventPlaceIdFromHeader(eventPlaces, headerCell) {
|
||||
@@ -455,7 +507,7 @@ function splitNbrCellToSegments(cellText) {
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function parseNbrSegment(segment, baseDateUtc, leaderNormalizedMap) {
|
||||
function parseNbrSegment(segment, baseDateUtc, leaderMaps) {
|
||||
const raw = normalizeText(segment);
|
||||
if (!raw) return null;
|
||||
|
||||
@@ -478,19 +530,10 @@ function parseNbrSegment(segment, baseDateUtc, leaderNormalizedMap) {
|
||||
text = normalizeText(text.replace(timeMatch[0], ''));
|
||||
}
|
||||
|
||||
// Officiant: pick the last token that matches a configured leader code/alias.
|
||||
let officiant = '';
|
||||
const tokens = text.split(' ').map((t) => t.trim()).filter(Boolean);
|
||||
for (let i = tokens.length - 1; i >= 0; i--) {
|
||||
const token = tokens[i].replace(/[()]/g, '');
|
||||
const resolved = leaderNormalizedMap.get(token.toLowerCase());
|
||||
if (resolved) {
|
||||
officiant = resolved;
|
||||
tokens.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
const unparsedText = tokens.join(' ').trim();
|
||||
// Gestalter aus beliebigem Teilstring auflösen (Kürzel/Name/Alias).
|
||||
const { resolvedName, cleanedText } = resolveLeaderFromText(text, leaderMaps);
|
||||
const officiant = resolvedName || '';
|
||||
const unparsedText = cleanedText;
|
||||
|
||||
return {
|
||||
dateUtc,
|
||||
@@ -1501,7 +1544,7 @@ async function parseNbrPlanningRecords(records) {
|
||||
|
||||
const eventPlaces = await EventPlace.findAll();
|
||||
const leaders = await WorshipLeader.findAll();
|
||||
const { normalizedToName } = buildLeaderMaps(leaders);
|
||||
const leaderMaps = buildLeaderMaps(leaders);
|
||||
|
||||
// existing worships for change detection
|
||||
const existingWorships = await Worship.findAll({
|
||||
@@ -1574,6 +1617,8 @@ async function parseNbrPlanningRecords(records) {
|
||||
for (const group of groups) {
|
||||
const placeHeader = group.placeHeader;
|
||||
const eventPlaceId = resolveEventPlaceIdFromHeader(eventPlaces, placeHeader);
|
||||
const eventPlace = eventPlaces.find((ep) => String(ep.id) === String(eventPlaceId));
|
||||
const titleFromPlace = buildWorshipTitleFromPlace(eventPlace?.name || placeHeader, 'Gottesdienst');
|
||||
|
||||
const worshipCell = row[group.idx];
|
||||
const music = row[group.musicIdx];
|
||||
@@ -1582,7 +1627,7 @@ async function parseNbrPlanningRecords(records) {
|
||||
if (segments.length === 0) continue;
|
||||
|
||||
for (const seg of segments) {
|
||||
const parsed = parseNbrSegment(seg, baseDateUtc, normalizedToName);
|
||||
const parsed = parseNbrSegment(seg, baseDateUtc, leaderMaps);
|
||||
if (!parsed || !parsed.time) {
|
||||
continue;
|
||||
}
|
||||
@@ -1590,7 +1635,7 @@ async function parseNbrPlanningRecords(records) {
|
||||
date: parsed.dateUtc,
|
||||
dayName,
|
||||
time: parsed.time,
|
||||
title: parsed.title,
|
||||
title: titleFromPlace || parsed.title || 'Gottesdienst',
|
||||
// "Gottesdienst haltend" ist bei uns der "Gestalter" (organizer).
|
||||
organizer: parsed.officiant || '',
|
||||
collection: '',
|
||||
|
||||
Reference in New Issue
Block a user