From 312a1d9d8afcca91d096649258e4de143554ee84 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Wed, 11 Mar 2026 17:21:29 +0100 Subject: [PATCH] refactor(clickTtPlayerRegistrationService): enhance application search and link handling - Introduced a new function to normalize comparable text for improved matching during application searches. - Updated the _openApplicationAfterSearch method to accept member data, enhancing the accuracy of application link retrieval based on expected name and birth date. - Improved error handling to provide clearer feedback when no matching application entry is found, ensuring better user guidance during the registration process. --- .../clickTtPlayerRegistrationService.js | 62 ++++++++++++++++--- 1 file changed, 52 insertions(+), 10 deletions(-) diff --git a/backend/services/clickTtPlayerRegistrationService.js b/backend/services/clickTtPlayerRegistrationService.js index 06d5917e..b8dda633 100644 --- a/backend/services/clickTtPlayerRegistrationService.js +++ b/backend/services/clickTtPlayerRegistrationService.js @@ -37,6 +37,14 @@ function escapeRegExp(value) { return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } +function normalizeComparableText(value) { + return String(value || '') + .normalize('NFKC') + .replace(/\s+/g, ' ') + .trim() + .toLowerCase(); +} + function buildDebugHtmlPath() { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); return path.join(process.cwd(), 'tmp', `clicktt-debug-${timestamp}.html`); @@ -113,7 +121,7 @@ class ClickTtPlayerRegistrationService { await this._clickByText(page, 'Personen suchen', trace); await page.waitForLoadState('domcontentloaded'); - await this._openApplicationAfterSearch(page, trace); + await this._openApplicationAfterSearch(page, memberJson, trace); await this._fillApplicationForm(page, memberJson, primaryEmail, trace); await this._clickByText(page, 'Weiter >>', trace); await page.waitForLoadState('domcontentloaded'); @@ -472,9 +480,41 @@ class ClickTtPlayerRegistrationService { } } - async _openApplicationAfterSearch(page, trace) { - const explicitApplicationLink = page.getByText(/Spielberechtigung beantragen/i).first(); - if (await explicitApplicationLink.count()) { + async _openApplicationAfterSearch(page, member, trace) { + const expectedName = normalizeComparableText(`${member.lastName}, ${member.firstName}`); + const expectedBirthDate = formatGermanDate(member.birthDate); + + const explicitApplicationHref = await page.locator('table.result-set tr').evaluateAll((rows, criteria) => { + const normalize = (value) => String(value || '') + .normalize('NFKC') + .replace(/\s+/g, ' ') + .trim() + .toLowerCase(); + + for (const row of rows) { + const link = row.querySelector('a'); + const strong = row.querySelector('strong'); + const cells = Array.from(row.querySelectorAll('td')); + const linkText = normalize(link?.textContent || ''); + const nameText = normalize(strong?.textContent || ''); + const birthDateText = normalize(cells[1]?.textContent || ''); + + if (!link || !strong) continue; + if (!linkText.includes('spielberechtigung beantragen')) continue; + if (nameText !== criteria.expectedName) continue; + if (birthDateText !== normalize(criteria.expectedBirthDate)) continue; + + return link.getAttribute('href'); + } + + return null; + }, { + expectedName, + expectedBirthDate + }); + + if (explicitApplicationHref) { + const explicitApplicationLink = page.locator(`a[href="${explicitApplicationHref}"]`).first(); let dialogSeen = false; page.once('dialog', async (dialog) => { dialogSeen = true; @@ -488,7 +528,9 @@ class ClickTtPlayerRegistrationService { this._trace(trace, 'step', { name: 'click', label: 'Spielberechtigung beantragen', - selector: 'text=/Spielberechtigung beantragen/i' + selector: `a[href="${explicitApplicationHref}"]`, + expectedName: `${member.lastName}, ${member.firstName}`, + expectedBirthDate }); await explicitApplicationLink.click(); await page.waitForLoadState('domcontentloaded'); @@ -501,12 +543,12 @@ class ClickTtPlayerRegistrationService { return; } - const confirmLocator = page.locator('[onclick*="confirm("]').first(); - if (await confirmLocator.count()) { - const text = sanitizePageText( - await confirmLocator.innerText().catch(() => '') || await confirmLocator.getAttribute('value').catch(() => '') || '' + const searchResultsText = sanitizePageText(await page.locator('table.result-set').innerText().catch(() => '')); + if (searchResultsText) { + throw new HttpError( + `Kein exakt passender Click-TT-Sucheintrag für "${member.lastName}, ${member.firstName}" mit Geburtsdatum ${expectedBirthDate} gefunden`, + 409 ); - throw new HttpError(`Click-TT-Suchergebnis mit Confirm gefunden, aber kein klickbares Antragselement per Label erkannt${text ? `: ${text}` : ''}`, 409); } }