From e5793fd1510d00c184b0652ab7a23fd8fa1d27ec Mon Sep 17 00:00:00 2001 From: Kenso Grimm Date: Wed, 7 Oct 2026 14:58:50 +0000 Subject: [PATCH] =?UTF-8?q?Buchungskalender:=20Abgleich=20=E2=80=93=20G22/?= =?UTF-8?q?K33-Platzhalter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../K33 Kalenderabgleich fehlgeschlagen.html | 5 +++-- server/services/mailTemplates.js | 1 + server/services/unterkunft/aufbewahrung.js | 8 ++++---- server/services/unterkunft/mails.js | 3 ++- test/mailTemplates.test.js | 2 +- test/unterkunftIcal.test.js | 6 ++++-- 6 files changed, 15 insertions(+), 10 deletions(-) diff --git a/server/mail-templates/K33 Kalenderabgleich fehlgeschlagen.html b/server/mail-templates/K33 Kalenderabgleich fehlgeschlagen.html index afae82c..fd0d0c5 100644 --- a/server/mail-templates/K33 Kalenderabgleich fehlgeschlagen.html +++ b/server/mail-templates/K33 Kalenderabgleich fehlgeschlagen.html @@ -7,7 +7,8 @@ Betreff: Der Kalenderabgleich für {{unterkunft}} funktioniert nicht Absender: „WEBklar“ Empfänger: Kunde (E-Mail des Kundenkontos) · BCC intern - Platzhalter: {{kunde_name}}, {{unterkunft}}, {{portal_url}} + Platzhalter: {{kunde_name}}, {{unterkunft}}, {{quelle}}, {{seit}}, {{portal_url}} + quelle = Name des externen Kalenders (z. B. Booking.com); seit = Datum und Uhrzeit des letzten erfolgreichen Abgleichs bzw. des ersten Fehlers. Der Abgleich mit einem externen Kalender (iCal-Link, z. B. Airbnb oder Booking.com) schlägt wiederholt fehl; belegte Tage von dort fehlen sonst im Buchungskalender. Höchstens einmal je Tag. Texte sind Entwurf für die Mail-Überarbeitung des Inhabers; verbindlich sind nur Platzhalter und Blöcke. --> @@ -34,7 +35,7 @@ a{color:#111111;} - +
Hinweis
Kalenderabgleich fehlgeschlagen
Hallo {{kunde_name}},
wir konnten den externen Kalender von {{unterkunft}} nicht abrufen. Solange das nicht klappt, fehlen die dort belegten Tage im Buchungskalender, und Gäste könnten Zeiträume anfragen, die schon vergeben sind. Bitte prüfe den Kalender-Link in den Einstellungen.
Hallo {{kunde_name}},
der Abgleich deines Kalenders „{{quelle}}“ für {{unterkunft}} schlägt seit {{seit}} fehl. Solange das nicht klappt, fehlen die dort belegten Tage im Buchungskalender, und Gäste könnten Zeiträume anfragen, die schon vergeben sind. Bitte prüfe den Kalender-Link in den Einstellungen.
Kalender prüfen →
Viele Grüße
Dein WEBklar-Team
diff --git a/server/services/mailTemplates.js b/server/services/mailTemplates.js index ecb9deb..bfc66af 100644 --- a/server/services/mailTemplates.js +++ b/server/services/mailTemplates.js @@ -111,6 +111,7 @@ export function renderMail(id, values = {}, blocks = {}) { .replace(/\{\{(\w+)\}\}/g, (_, key) => escapeHtml(valueOf(key)).replace(/\r?\n/g, '
')) // Anrede ohne bekannten Namen .replace(/Guten Tag ,/g, 'Guten Tag,') + .replace(/Hallo ,/g, 'Hallo,') const subject = template.subject .replace(/\{\{(\w+)\}\}/g, (_, key) => valueOf(key)) diff --git a/server/services/unterkunft/aufbewahrung.js b/server/services/unterkunft/aufbewahrung.js index 8968399..ca377d4 100644 --- a/server/services/unterkunft/aufbewahrung.js +++ b/server/services/unterkunft/aufbewahrung.js @@ -23,8 +23,8 @@ import { leereCache } from './kontext.js' const TAG_MS = 24 * 60 * 60 * 1000 const STANDARD_DECKEL = 500 const SEITE = 100 -const ANONYM_JAHRE = 3 -const RECHNUNG_JAHRE = 8 +const ANONYM_JAHRE = () => config.unterkunft.anonymisierenJahre ?? 3 +const RECHNUNG_JAHRE = () => config.unterkunft.rechnungJahre ?? 8 const MARKER = 'Buchungsdaten gelöscht am ' const MARKER_RE = /Buchungsdaten gelöscht am (\d{4}-\d{2}-\d{2})/ const ANFRAGE_ANONYM = { gast: {}, nachrichtVermieter: '', ipHash: '' } @@ -175,7 +175,7 @@ export async function anonymisiere(now, { deckel = config.unterkunft.aufbewahrun [Query.equal('status', ['gebucht', 'bezahlt', 'storniert'])], ['status', 'rechnungId', 'anonymisiertAt', 'bis'], // Abreisejahr Y: faellig, sobald das Berliner Jahr >= Y + 3 + 1 ist (3 Jahre nach Ende des Jahres Y) - (d) => !d.anonymisiertAt && gueltigerTag(d.bis) && gebuchtJahr >= jahrVon(d.bis) + ANONYM_JAHRE + 1 && (d.status !== 'storniert' || hatRechnung(d)), + (d) => !d.anonymisiertAt && gueltigerTag(d.bis) && gebuchtJahr >= jahrVon(d.bis) + ANONYM_JAHRE() + 1 && (d.status !== 'storniert' || hatRechnung(d)), rest, ) await bearbeite('anonymisieren', gebucht, async (d) => { @@ -190,7 +190,7 @@ export async function anonymisiere(now, { deckel = config.unterkunft.aufbewahrun await schritt('rechnungen', async () => { const jahr = jahrVon(heute(new Date(nowMs))) const RE = config.collections.ukRechnungen - const faellig = (r) => gueltigerTag(r.datum) && jahr >= jahrVon(r.datum) + RECHNUNG_JAHRE + 1 + const faellig = (r) => gueltigerTag(r.datum) && jahr >= jahrVon(r.datum) + RECHNUNG_JAHRE() + 1 const gefunden = await sammle(RE, [], ['anfrageId', 'datum'], faellig, rest) if (gefunden.gedeckelt) erg.gedeckelt = true const erledigt = new Set() diff --git a/server/services/unterkunft/mails.js b/server/services/unterkunft/mails.js index 821556a..052149f 100644 --- a/server/services/unterkunft/mails.js +++ b/server/services/unterkunft/mails.js @@ -95,7 +95,7 @@ export async function anGast(art, ctx, anfrage, extra = {}) { const blocks = {} let anhang = null - if (art !== 'G16' && art !== 'G18' && art !== 'G21' && art !== 'G20') values.stornobedingungen = await stornoText(anfrage) + if (art !== 'G16' && art !== 'G18' && art !== 'G21' && art !== 'G20' && art !== 'G22') values.stornobedingungen = await stornoText(anfrage) if (art === 'G17' || art === 'G19' || art === 'G20') values.gesamt = formatEuro(anfrage.summen?.bruttoCent ?? 0) if (art === 'G17' || art === 'G19') values.posten_text = postenText(anfrage.posten) if (art === 'G17') { @@ -164,6 +164,7 @@ export async function anVermieter(template, ctx, values = {}) { zahlart_text: ZAHLART[anfrage.zahlart] ?? LEER, }) } + if (!anfrage) vorgabe.kunde_name = text(ctx?.kundeName) || (await kundenName(ctx)) const alle = { portal_url: portalUrl(ctx), ...vorgabe, ...eigene } if (template === 'T22') { const r = await sendAdminNotice({ template, values: nurPlatzhalter(template, alle) }) diff --git a/test/mailTemplates.test.js b/test/mailTemplates.test.js index 9cd12c5..a00eba8 100644 --- a/test/mailTemplates.test.js +++ b/test/mailTemplates.test.js @@ -1339,7 +1339,7 @@ test('K31–K34, T22: Dateien, Platzhalter, Du-Form', () => { const erwartet = { K31: ['kunde_name', 'gast_name', 'gast_email', 'unterkunft', 'zeitraum', 'naechte', 'personen', 'nummer', 'nachricht_gast', 'portal_url'], K32: ['kunde_name', 'gast_name', 'unterkunft', 'zeitraum', 'naechte', 'personen', 'nummer', 'gesamt', 'zahlart_text', 'portal_url'], - K33: ['kunde_name', 'unterkunft', 'portal_url'], + K33: ['kunde_name', 'unterkunft', 'quelle', 'seit', 'portal_url'], K34: ['kunde_name', 'projekt', 'preis', 'ust_hinweis', 'begruendung', 'portal_url'], T22: ['kunde_name', 'email', 'projekt', 'nachricht', 'woms_url'], } diff --git a/test/unterkunftIcal.test.js b/test/unterkunftIcal.test.js index 613f68b..663c763 100644 --- a/test/unterkunftIcal.test.js +++ b/test/unterkunftIcal.test.js @@ -48,7 +48,7 @@ beforeEach(async () => { sink = mailSink() setMailTransportForTests(sink) aw.seed('portalFeatures', { $id: FEATURE, customerId: 'c1', projectId: 'p1', featureKey: 'unterkunft', siteKey: 'abcdefghijklmnop', state: 'active', enabled: true }) - aw.seed('customers', { $id: 'c1', email: 'mimi@example.com' }) + aw.seed('customers', { $id: 'c1', email: 'mimi@example.com', name: 'Mimi Muster' }) aw.seed('websiteProjects', { $id: 'p1', customerId: 'c1', projectName: 'Donau' }) }) afterEach(() => Object.assign(config.unterkunft, vorher)) @@ -237,7 +237,9 @@ test('K33: erst nach icalFehlerStunden ohne Erfolg, danach hoechstens 1x je 24 h assert.deepEqual(Object.keys(K33()[0]).sort(), ['an', 'anfrageId', 'template']) await warteAufVersand() const k33 = sink.sent.find((m) => /Erste/.test(m.text ?? '')) - assert.ok(k33, 'Mail nennt die Unterkunft (K33 kennt keine Platzhalter quelle/seit)') + assert.ok(k33, 'Mail nennt die Unterkunft') + assert.match(k33.text, /Hallo Mimi Muster,/) + assert.match(k33.text, /„Booking\.com“ für Erste schlägt seit 07\.11\.2026, 06:00 fehl/) assert.deepEqual(k33.to.map((x) => x.address), ['mimi@example.com']) assert.ok(!JSON.stringify(k33).includes('geheim')) assert.ok(!JSON.stringify(K33()[0]).includes('geheim'))