From 9586822a32471c24f4aa2a40468e70cdfcad92de Mon Sep 17 00:00:00 2001 From: Matthias Hochmeister Date: Wed, 15 Apr 2026 17:40:08 +0200 Subject: [PATCH] fix(sync): scrape AusbildungenListEdit instead of KursteilnehmerListEdit, add selectAlleAnzeige, fix column detection; handle Sachbearbeiter dienstgrad and ignore placeholder handles --- sync/src/db.ts | 3 ++- sync/src/scraper.ts | 13 +++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/sync/src/db.ts b/sync/src/db.ts index 78ca2c3..c795a4c 100644 --- a/sync/src/db.ts +++ b/sync/src/db.ts @@ -40,9 +40,10 @@ function mapDienstgrad(raw: string): string | null { 'ovm': 'Oberverwaltungsmeister', 'hvm': 'Hauptverwaltungsmeister', 'v': 'Verwalter', + 'sb': 'Sachbearbeiter', }; - const normalized = raw.trim().toLowerCase(); + const normalized = raw.trim().toLowerCase().replace(/\*/g, ''); // Direct abbreviation match if (abbrevMap[normalized]) return abbrevMap[normalized]; diff --git a/sync/src/scraper.ts b/sync/src/scraper.ts index 9a9a810..01b438d 100644 --- a/sync/src/scraper.ts +++ b/sync/src/scraper.ts @@ -733,7 +733,7 @@ async function scrapeAusbildungenFromDetailPage( return []; } - const url = `${BASE_URL}/fdisk/module/mgvw/kursteilnehmer/KursteilnehmerListEdit.aspx` + const url = `${BASE_URL}/fdisk/module/mgvw/ausbildungen/AusbildungenListEdit.aspx` + `?search=1&searchid_mitgliedschaften=${idMitgliedschaft}&id_personen=${idPersonen}` + `&id_mitgliedschaften=${idMitgliedschaft}&searchid_personen=${idPersonen}&searchid_maskmode=`; @@ -746,6 +746,9 @@ async function scrapeAusbildungenFromDetailPage( return []; } + // Show all rows (FDISK defaults to 10) + await selectAlleAnzeige(frame); + // Dump HTML for debugging await dumpHtml(frame, `ausbildungen_StNr${member.standesbuchNr}`); @@ -823,7 +826,8 @@ async function scrapeAusbildungenFromDetailPage( const hdr = bestHeaders.map(h => h.toLowerCase()); let kursnummerIdx = hdr.findIndex(h => h.includes('nummer')); let kurzIdx = hdr.findIndex(h => h === 'kurz' || (h.includes('kurz') && !h.includes('name'))); - let kursnameIdx = hdr.findIndex(h => h === 'kurs' || h.includes('ausbildung') || h.includes('bezeichnung')); + // Exclude "kurzbezeichnung" from matching kursname — it already matches kurzIdx above + let kursnameIdx = hdr.findIndex(h => !h.startsWith('kurz') && (h === 'kurs' || h.includes('ausbildung') || h.includes('bezeichnung'))); let datumIdx = hdr.findIndex(h => h.includes('datum') || h.includes('abschluss')); let erfolgscodeIdx = hdr.findIndex(h => h.includes('erfolg') || h.includes('code')); let ablaufIdx = hdr.findIndex(h => h.includes('ablauf') || h.includes('gültig')); @@ -861,8 +865,9 @@ async function scrapeAusbildungenFromDetailPage( for (const row of bestRows) { const kursname = ((kursnameIdx >= 0 ? row.cells[kursnameIdx] : row.cells[0])?.trim()) || ''; if (!kursname) continue; - // Skip header-like rows - if (/kurs|ausbildung|bezeichnung|datensätze|tiefennavigation/i.test(kursname)) continue; + // Skip rows that are column headers or pagination entries (not real course data) + if (/^(kurz|kurzbezeichnung|bezeichnung|tiefennavigation|anzahl)$/i.test(kursname)) continue; + if (/datensätze\s*\d/i.test(kursname)) continue; const rawDatum = datumIdx >= 0 ? row.cells[datumIdx]?.trim() : null; const rawAblauf = ablaufIdx >= 0 ? row.cells[ablaufIdx]?.trim() : null;