Files
Webklar-Kundenbereich/docs/superpowers/specs/2026-10-07-buchungskalender-design.md
T

24 KiB
Raw Blame History

Buchungskalender für Unterkünfte (Funktion unterkunft): Design

Stand 2026-10-07. Basis: Portal origin/main 8814c4a (TP2, Webshop, Öffnungszeiten im Pilot). Arbeitszweig feature/unterkunft, Worktree dev-unterkunft. Pfade: P = Portal-Repo, W = WOMS-Repo, D = Website Donau Home (WEBklar/donau-home, lokaler Klon /root/reports/donau-home-website).

Auftrag des Inhabers (2026-10-07): Auf der Donau-Home-Seite (MIMI FEWO, erste Kundin) ersetzt ein Kalender die Felder Anreise/Abreise; der Gast wählt den Zeitraum, gibt Personen, Name, E-Mail, Telefon, Nachricht an. Die Anfrage geht an die Vermieterin; gibt sie frei, bekommt der Gast eine Mail mit Rechnung und zahlt direkt oder vor Ort. Als Funktion im Kundenportal, für alle Kunden. Entscheidungen des Inhabers U1–U7 (Abschnitt 13), alle übrigen von Claude (U8 ff.).


1. Ziel und Abgrenzung

Eine Funktion für Ferienwohnungen, Monteurzimmer und kleine Pensionen: Belegungskalender auf der Website, Anfrage, Freigabe durch den Vermieter, verbindliche Buchung durch den Gast, Rechnung im Namen des Vermieters, Zahlung online (Stripe, direkt an den Vermieter) oder vor Ort, Abgleich mit Booking.com/Airbnb per iCal.

Nicht im Umfang: Meldeschein (Bundesmeldegesetz), Kurtaxe/Gästebeitrag, Anzahlungen und Teilzahlungen, Sofortbuchung ohne Freigabe, saisonale Preise (nur Staffel nach Aufenthaltsdauer), Gutscheine, Channel-Manager-APIs außer iCal, Mehrsprachigkeit (nur Deutsch).

Begriffe: Vermieter = unser Kunde (Mimi). Gast = wer auf der Website anfragt. Unterkunft = buchbare Einheit (Wohnung, Zimmer).

2. Ablauf

  1. Website (Widget): Kalender zeigt belegte Nächte der gewählten Unterkunft (bei nur einer Unterkunft keine Auswahl). Erster Klick Anreise, zweiter Abreise, Spanne markiert, Zusammenfassung „12.–30. Nov. · 18 Nächte“. Belegte Nächte und Vergangenheit nicht wählbar; ein Zeitraum über eine belegte Nacht hinweg ebenso nicht; Mindestnächte werden geprüft. Formular: Personen (1 … Höchstzahl), Firma (optional), Name, E-Mail, Telefon (optional), Nachricht (optional), Datenschutzhinweis. Knopf „Anfrage senden“ (unverbindlich, U6). → Anfrage neu, G16 Eingangsbestätigung an den Gast, K31 Neue Anfrage an den Vermieter.
  2. Portal, Reiter „Buchungen“ → Anfragen: Detail mit vorgerechnetem Preis (Posten editierbar: Betrag, Text, Posten hinzufügen/entfernen), Reservierungsfrist (Standard aus Einstellungen), optionale Nachricht an den Gast. Freigeben → Prüfung unter Sperre, dass der Zeitraum frei ist; Status freigegeben, Nächte als Reservierung gesperrt bis fristAt, G17 an den Gast mit Preis, Stornobedingungen und Buchungslink. Ablehnen (optional mit Text) → abgelehnt, G18.
  3. Buchungsseite https://project.webklar.com/unterkunft/<siteKey>/buchung/<id>?t=<token> (Abschnitt 6): Rechnungsadresse, Zahlart „online“/„vor Ort“, alle Pflichtangaben, Knopf „Zahlungspflichtig buchen“ (U6). → Prüfung unter Sperre (Status freigegeben, Frist nicht abgelaufen, Zeitraum frei); Status gebucht, Rechnungsnummer vergeben, PDF erzeugen, G19 Buchungsbestätigung mit PDF an den Gast, K32 an den Vermieter. Bei „online“ direkt weiter zu Stripe Checkout; Zahlung → bezahlt, G20. Bricht der Gast die Zahlung ab, bleibt die Buchung gebucht (unbezahlt); G19 und die Buchungsseite enthalten „Jetzt online bezahlen“.
  4. Frist abgelaufen (Minuten-Job): freigegeben → abgelaufen, Nächte frei, G21.
  5. Vor Ort bezahlt: Vermieter setzt „bezahlt (vor Ort)“ mit Datum; keine Mail.
  6. Stornierung durch den Vermieter (nur freigegeben, gebucht, bezahlt): Grund, bei Online-Zahlung Erstattung 0 … Gesamtbetrag über Stripe; bei vorhandener Rechnung Stornorechnung (Rechnungskorrektur mit eigener Nummer, voller Betrag negativ); G22 an den Gast mit Stornorechnung. Stornierungen auf Wunsch des Gastes laufen ebenso über den Vermieter (der Gast schreibt ihm; keine Selbststornierung im Widget).

Statusmaschine (nur vorwärts, Übergänge idempotent unter Sperre unterkunft:<unterkunftId>): neu → freigegeben → gebucht → bezahlt; neu → abgelehnt; freigegeben → abgelaufen; freigegeben|gebucht|bezahlt → storniert. zahlart ∈ {online, vorort} wird bei gebucht gesetzt; bezahltVia ∈ {stripe, vorort}.

3. Datenmodell (Appwrite, scripts/unterkunft-setup.mjs, nur Server-Key, Muster shop-setup.mjs)

Alle Dokumente tragen customerId, projectId, featureId (portalFeatures-Dokument der Funktion).

  • ukUnterkuenfte: name, beschreibung (kurz, optional), maxPersonen (1–20), mindestNaechte (1–90), checkIn/checkOut (HH:MM), sort, aktiv, icalImporte (JSON-Array {id, name, url, letzterAbrufAt, letzterErfolgAt, fehler}), icalExportToken (32 Byte, zufällig, erneuerbar).
  • ukPreise (1:1 zur Unterkunft): staffeln (JSON [{abNaechten, preisProNachtCent}], aufsteigend, erste ab 1), reinigungCent, personenInklusive, aufpreisPersonNachtCent, direktrabattProzent (0–50, ganze Zahl), steuerart (kleinunternehmer | regel), ustUebernachtung (Standard 7), ustReinigung (Standard 7), fristTage (1–14, Standard 3), stornobedingungen (Text, Pflicht vor dem Freischalten).
  • ukSperren: unterkunftId, von, bis (Datum, bis = Abreisetag exklusiv, also belegte Nächte von..bis-1), quelle (manuell | ical:<importId> | anfrage:<anfrageId>), notiz (nur manuell, nur Portal). Reservierungen und Buchungen werden nicht als Sperre kopiert, sondern direkt aus ukAnfragen gelesen (Status freigegeben mit fristAt > jetzt, gebucht, bezahlt); ukSperren enthält nur manuell und ical:*.
  • ukAnfragen: nummer (kurz, z. B. A-7K2Q), unterkunftId, von, bis, personen, status, gast (JSON {name, firma, email, telefon, nachricht}), rechnungsadresse (JSON {name, firma, strasse, plz, ort, land}), posten (JSON [{text, menge, einzelCent, ustProzent}], Schnappschuss bei Freigabe), summen (JSON {netto, ust je Satz, brutto}), nachrichtVermieter, fristAt, tokenHash (sha256 des Buchungslink-Tokens), zahlart, bezahltVia, bezahltAt, stripeSessionId, stripePaymentIntentId, erstattetCent, stornoGrund, stornobedingungenText (Schnappschuss bei Freigabe), rechtstexteFassung, ipHash (nur bis Freigabe/Ablehnung, danach entfernt), Zeitstempel je Status.
  • ukRechnungen: anfrageId, art (rechnung | storno), nummer, datum, fileId (Bucket uk-rechnungen), aussteller (JSON-Schnappschuss der Firmendaten), bruttoCent.
  • ukEinstellungen (1 je Funktion): firma, inhaber, strasse, plz, ort, email, telefon, steuernummer/ustId (eins Pflicht), iban/bic/bank (optional, für „vor Ort/Überweisung“-Hinweis), rechnungsPraefix (2–8 Zeichen A–Z0–9, Standard RE), rechnungZaehler (JSON {jahr, n}), akzentfarbe, stripeAccountId, stripeStatus (Muster Webshop), onlineZahlung (an/aus; aus, solange Stripe nicht aktiv).

Rechnungsnummern: <Präfix>-<Jahr>-<n vierstellig>, lückenlos je Funktion und Jahr, vergeben unter Sperre uk-rechnungsnr:<featureId> (keyedLock) mit Lesen–Erhöhen–Schreiben des Zählers vor dem PDF; schlägt das PDF fehl, wird es neu erzeugt (Nummer bleibt). Storno-Rechnungen ziehen aus demselben Zähler.

4. Preisrechnung (server/services/unterkunft/preis.js, rein, ohne I/O)

berechnePreis({ preise, von, bis, personen }) → { naechte, posten, summen }:

  • naechte = Tage zwischen von und bis; Staffel = höchste abNaechten ≤ naechte für alle Nächte.
  • Posten: „Übernachtung, N Nächte à X €“ (ust Übernachtung); falls personen > personenInklusive: „Zusatzperson(en), k × N Nächte à Y €“; „Endreinigung“ (falls > 0, ust Reinigung); „Direktbuchungsrabatt P %“ als negativer Posten auf Übernachtung + Zusatzpersonen (nicht auf Reinigung), mit ust Übernachtung.
  • Beträge sind Bruttobeträge in Cent. Bei regel wird die enthaltene USt je Satz aus der Summe der Posten dieses Satzes herausgerechnet und kaufmännisch auf Cent gerundet; bei kleinunternehmer keine USt.
  • Mimis Änderungen ersetzen die Posten vollständig; summen werden immer serverseitig aus den Posten neu berechnet (summiere(posten, steuerart)), nie aus dem Browser übernommen.

5. Verfügbarkeit und iCal (server/services/unterkunft/belegung.js, ical.js)

  • belegteNaechte(unterkunftId, von, bis): Vereinigung aus ukSperren und aktiven Anfragen (Abschnitt 3). istFrei(...) wird bei Freigabe und Buchung unter der Sperre erneut geprüft (U9). Eine eigene Anfrage zählt bei ihrer eigenen Buchung nicht als belegt.
  • Import (Job alle 15 Minuten, je Import höchstens 1 MB, Zeitlimit 10 s, nur https:, keine privaten/lokalen Ziele — Prüfung der aufgelösten IP gegen private Netze): VEVENT mit DTSTART/DTEND (DATE oder DATE-TIME → Datum), ohne DTEND = 1 Nacht, STATUS:CANCELLED ignorieren, nur Zeitraum heute−1 Tag bis +18 Monate. Erfolgreicher Abruf ersetzt alle Sperren ical:<importId> (erst neue anlegen, dann alte löschen, damit nie eine Lücke entsteht). Fehler: alter Stand bleibt, fehler gesetzt; ab 6 Stunden ohne Erfolg K33 (höchstens einmal je 24 h und Import). Parser selbst geschrieben (Zeilenfaltung, Escapes), keine Abhängigkeit.
  • Export GET /unterkunft/ical/<icalExportToken>.ics: VCALENDAR mit einem VEVENT „Belegt“ je gebuchter/bezahlter Buchung, Reservierung und manueller Sperre (nicht die importierten, sonst Echo), UID stabil, keine Gastdaten. Bei der Booking.com-Anleitung im Portal: Link kopieren, bei Booking.com „Kalender synchronisieren“ eintragen.

6. Website-Widget und öffentliche Schnittstellen

Einbau (Muster Termine/Shop): <div data-webklar-unterkunft="<siteKey>"></div> + <script src="https://project.webklar.com/embed/unterkunft.js" defer></script>. Shadow DOM, Stile über CSS-Variablen (--wk-uk-akzent, --wk-uk-text, --wk-uk-flaeche, --wk-uk-linie, --wk-uk-radius, --wk-uk-schrift), damit jede Seite das Widget anpassen kann. Layout: zwei Spalten (Kalender links, Formular rechts), unter 760 px untereinander. Kalender: ein Monat sichtbar (zwei ab 1100 px Breite), Pfeile, Tastatur (Pfeiltasten, Enter), aria-label je Tag mit Datum und Status, belegte Nächte durchgestrichen/grau, Anreise an einem Tag, an dem nur eine Abreise liegt, ist möglich (Nächte-Logik).

Öffentliche Routen (server/routes/publicUnterkunft.js, Antworten nie mit IDs außer Unterkunft-IDs, keine Gastdaten):

  • GET /api/public/unterkunft/:siteKey → Unterkünfte (id, name, maxPersonen, mindestNaechte, checkIn/Out), Akzentfarbe, Datenschutz-Link-Ziel, Anbietername.
  • GET /api/public/unterkunft/:siteKey/belegung?unterkunft=<id>&von=&bis= (höchstens 18 Monate) → belegte Nächte als Bereiche. Cache 60 s.
  • POST /api/public/unterkunft/:siteKey/anfrage → Prüfung (Pflichtfelder, Längen, E-Mail, Zeitraum frei und gültig, Personen), Honeypot-Feld + Mindest-Ausfüllzeit, Rate-Limit je IP-Hash und Site (Muster Termine). Antwort: Anfragenummer.
  • GET /api/public/unterkunft/:siteKey/buchung/:id?t= und POST …/buchung/:id (Token prüfen gegen tokenHash, zeitkonstanter Vergleich; nach abgelaufen/abgelehnt/storniert nur Statusanzeige) → Buchungsseite.
  • POST …/buchung/:id/zahlung?t= → Stripe Checkout Session auf dem verbundenen Konto (Direct Charge, Muster Webshop: mode: payment, ein Posten „Aufenthalt –, Rechnung “, Betrag = brutto, expires_at 30 min, metadata {ukAnfrageId}, success_url zurück zur Buchungsseite). Abschluss über den bestehenden Connect-Webhook (Weiterleitung nach metadata) und beim Laden der Buchungsseite (Muster Webshop „Danke-Seite schließt selbst ab“).

Buchungsseite (Standalone-Seite im Portal-Design mit Akzentfarbe des Vermieters, § 312j BGB und Art. 246a EGBGB, unmittelbar über dem Knopf): Anbieter (Firma, Anschrift, Kontakt), Unterkunft, An-/Abreise mit Check-in/-out-Zeiten, Personen, alle Posten, Gesamtpreis inkl. USt bzw. Kleinunternehmer-Hinweis, Zahlarten, Stornobedingungen (Schnappschuss), Hinweis „Ein Widerrufsrecht besteht nicht (§ 312g Abs. 2 Nr. 9 BGB).“, Datenschutzhinweis des Vermieters, Frist. Pflichtfelder Rechnungsadresse (Name/Firma, Straße, PLZ, Ort, Land; Vorbelegung aus der Anfrage). Korrektur bis zum Knopf möglich. Knopf „Zahlungspflichtig buchen“; „online“ nur wählbar, wenn onlineZahlung an.

7. Portal für den Vermieter (Funktionen → „Buchungen“, Hash #buchungen)

Nach dem Muster des Webshop-Bereichs; nur sichtbar, wenn die Funktion für das Projekt aktiv ist (Einrichtung: schon in setup nutzbar zum Vorbereiten, Widget erst nach Freischaltung öffentlich).

  • Anfragen: Filter offen (neu, freigegeben) / gebucht / alle; Liste mit Nummer, Zeitraum, Nächten, Gast, Status, Betrag. Detail: Gastangaben, Kalenderausschnitt, Posten-Editor mit Live-Summe, Frist, Nachricht, Freigeben/Ablehnen; bei Buchungen Rechnung herunterladen, „bezahlt (vor Ort)“, Stornieren (mit Erstattungsbetrag bei Online-Zahlung).
  • Kalender: Monatsansicht je Unterkunft, Farben nach Quelle (Direkt gebucht, reserviert, Booking/iCal, manuell); Klick-Klick auf freie Nächte → manuelle Sperre mit Notiz; Klick auf manuelle Sperre → löschen.
  • Unterkünfte & Preise: Anlegen/Bearbeiten/Sortieren/Deaktivieren, Preis-Editor mit Beispielrechnung („7 Nächte, 2 Personen = …“), iCal-Importe (Name + URL, Status letzter Abruf), Export-Link mit Kopieren und Anleitung für Booking.com und Airbnb, „Link erneuern“.
  • Rechnungen: Liste (Nummer, Datum, Gast, Betrag, Art), PDF-Download, Jahres-ZIP-Download.
  • Einstellungen: Firmendaten, Steuer, Rechnungspräfix (änderbar nur vor der ersten Rechnung), Bankverbindung, Akzentfarbe, Stripe verbinden (Wiederverwendung shop/connect.js, ein Stripe-Konto je Kunde: hat der Kunde bereits ein Shop-Konto, wird es angeboten), Online-Zahlung an/aus, Mustertext Stornobedingungen zum Übernehmen.
  • Pflicht vor dem Freischalten (freischaltbar): mindestens eine aktive Unterkunft mit Preisen und Stornobedingungen, Firmendaten vollständig, Steuernummer oder USt-ID.

8. Rechnung (PDF, server/services/unterkunft/rechnungPdf.js)

Mit der vorhandenen PDF-Bibliothek des Portals (Plan prüft, welche; keine neue Abhängigkeit, wenn vermeidbar). Pflichtangaben § 14 Abs. 4 UStG: Name/Anschrift Aussteller und Empfänger, Steuernummer oder USt-ID, Rechnungsdatum, Rechnungsnummer, Leistung („Beherbergung , “), Leistungszeitraum (An–Abreise), Posten, Entgelt netto je Steuersatz, Steuersatz und Steuerbetrag bzw. „Gemäß § 19 UStG wird keine Umsatzsteuer berechnet.“, Gesamtbetrag. Zusätzlich Zahlart und Status („bezahlt am …“ wird nicht nachträglich ins PDF geschrieben; die Rechnung bleibt unverändert). Storno-Rechnung verweist auf die Ursprungsrechnung. Kein WEBklar-Hinweis auf der Rechnung (U10), sie ist die des Vermieters.

9. Bestellung der Funktion bei WEBklar (Register-Eintrag unterkunft)

Muster Webshop: product: 'unterkunftSetup' (19900 Cent, Lookup-Key webklar_unterkunft_einrichtung_19900), priceLabel '199 €', setupEstimate 'meist innerhalb von 1–2 Wochen', requiresWartungsAbo: false, ticketFreischaltung: true, returnHash 'buchungen', returnProjekt: true. Haken: verfuegbar (Schalter UNTERKUNFT_ENABLED aus/pilot/an + UNTERKUNFT_PILOT_EMAILS), bestellpruefung (Anfrage angenommen, Muster Webshop anfrage.js), unterlagenBereit (Leistungsbeschreibung + AVV Buchungskalender), belehrung (gemeinsame), freischaltbar (Abschnitt 7). Anfrageformular der Funktion: Art der Unterkunft, Zahl der Einheiten, Booking.com/Airbnb genutzt ja/nein, Nachricht → K34 an den Kunden, T22 an das Team. Zustimmungen wie Webshop: unterlagen, verantwortung („Ich vermiete in eigenem Namen; für Preise, Stornobedingungen, Rechnungen und Meldepflichten bin ich verantwortlich.“), stripe (nur wenn Online-Zahlung genutzt werden soll; sonst entfällt), beginn, information. Ticket „Buchungskalender einbauen – “ mit siteKey, Snippet, Schritten (Kalender-Bereich anlegen, Snippet, CSS-Variablen an die Seite anpassen, Datenschutz-Absatz einfügen, Testanfrage), Einbauanleitung docs/funktionen/unterkunft-einbau.md. Für Kunden, die die Funktion als Teil ihres Projekts bekommen (Mimi), schaltet das Team sie wie bei den anderen Funktionen manuell frei (vorhandener Weg „ohne Zahlung freischalten“ im WOMS; der Plan prüft, ob es ihn gibt, sonst Skript scripts/unterkunft-freischalten.mjs).

10. WOMS

Wie Webshop: Anfragen der Funktion in der Kunden-Funktionsansicht annehmen/ablehnen; Anzeige des Funktionsstatus; Rechtliches-Karten leistungsbeschreibung-unterkunft und avv-unterkunft (Sortnummern nach den belegten, der Plan prüft setup-legal.mjs). Keine Verwaltung der Buchungen im WOMS (Datenhoheit beim Kunden).

11. Mails (Vorlagen im Stil der bestehenden, server/mail-templates)

Gast (Absender WEBklar-Versandadresse, Anzeigename = Firma des Vermieters, Reply-To = E-Mail des Vermieters): G16 Anfrage eingegangen · G17 Ihre Anfrage ist frei – jetzt buchen (Preis, Frist, Stornobedingungen, Link) · G18 Leider nicht möglich · G19 Buchungsbestätigung (alle Angaben, Stornobedingungen, Rechnungs-PDF, ggf. Bezahl-Link; dauerhafter Datenträger § 312f BGB) · G20 Zahlung eingegangen · G21 Reservierung abgelaufen · G22 Stornierung (Stornorechnung-PDF, Erstattungsbetrag). Vermieter: K31 Neue Buchungsanfrage · K32 Neue Buchung · K33 Abgleich fehlgeschlagen · K34 Ihre Anfrage zum Buchungskalender. Team: T22 Neue Anfrage Buchungskalender. Nummern vor dem Bau gegen main prüfen (Stand: K30, G15, T21 belegt).

12. Datenschutz, Aufbewahrung, Rechtstexte

  • Rollen: Vermieter ist Verantwortlicher für Gastdaten, WEBklar Auftragsverarbeiter → AVV Buchungskalender (Muster AVV Webshop; Unterauftragsverarbeiter Hetzner, Stripe nur als eigener Vertragspartner des Vermieters).
  • Fristen: ipHash weg bei Freigabe/Ablehnung bzw. nach 30 Tagen; Anfragen ohne Buchung (abgelehnt, abgelaufen, neu älter als 60 Tage) nach 6 Monaten anonymisiert; Gastdaten gebuchter Aufenthalte 3 Jahre nach Jahresende der Abreise anonymisiert, Rechnungs-PDFs und Rechnungsadresse bleiben 8 Jahre (§ 147 AO, Buchungsbelege) solange die Funktion läuft; bei Abschaltung: Jahres-ZIP-Download angeboten, Hinweis-Mail, Löschung 90 Tage danach (Muster Webshop). Täglicher Job im bestehenden Aufräum-Muster (retention.js).
  • Datenschutz WEBklar: neuer Abschnitt § 6f (6d Webshop, 6e Öffnungszeiten; Plan prüft). Muster-Absatz für die Website des Vermieters docs/funktionen/unterkunft-datenschutz-absatz.md. Mustertext Stornobedingungen (mit Hinweis „Vorlage, keine Rechtsberatung“).
  • WEBklar-Texte (Fassung 2026-10-07): Leistungsbeschreibung Buchungskalender, AVV Buchungskalender, Kanzlei-Fragen docs/rechtliches/unterkunft-kanzlei-fragen.md (u. a. Button-Lösung über Mail-Link, USt-Satz Endreinigung, Rolle bei Rechnungserstellung im Namen des Vermieters § 14 Abs. 2 S. 4 UStG — Rechnung durch Dritten im Auftrag, Vermieter muss zustimmen → Zustimmung beim Bestellen und Einstellungen-Haken).
  • Upload der Texte in WOMS Rechtliches macht Claude selbst (Feedback des Inhabers), sobald der Inhaber die Texte freigibt.

13. Entscheidungen

Inhaber (2026-10-07): U1 allgemeine Funktion für alle Kunden, Mimi zuerst. U2 Stripe Connect wie Webshop, Geld direkt an den Vermieter, PDF-Rechnung in dessen Namen, Zahlung online oder vor Ort. U3 Preise im Portal hinterlegt, vorgerechnet, vor Freigabe änderbar. U4 iCal-Abgleich in beide Richtungen + manuelle Sperren. U5 mehrere Unterkünfte je Website. U6 Anfrage unverbindlich, Buchung erst per „Zahlungspflichtig buchen“ nach Freigabe, Reservierung bis Frist (Standard 3 Tage). U7 199 € einmalig auf Anfrage, keine laufenden Kosten. Bau in parallelen Strängen. Claude: U8 eigenes Modul statt Terminplaner-Erweiterung oder Fremdsystem. U9 Freigabe und Buchung prüfen Verfügbarkeit unter Sperre erneut. U10 keine WEBklar-Werbung auf der Rechnung. U11 Staffelpreis gilt für alle Nächte. U12 Rabatt nicht auf Reinigung. U13 Stornierung nur durch den Vermieter. U14 Zahlungsabbruch lässt die Buchung bestehen (unbezahlt). U15 ein Stripe-Konto je Kunde für Shop und Buchungen. U16 Widget ohne Fremdbibliothek, Shadow DOM, CSS-Variablen.

14. Website Donau Home (D)

Abschnitt #anfrage: das bisherige Formular wird durch das Widget ersetzt (Kalender links unter Überschrift und Einleitung, Formular rechts; Telefon/E-Mail unter dem Kalender). CSS-Variablen in Palette A (Eiche & Leinen) und Schrift Figtree; Widget-Kalender optisch an den Zeichenstil angelehnt (Rahmen, Akzent Eiche). Bis zur Freischaltung zeigt die Vorschau das Widget über UNTERKUNFT_ENABLED=pilot mit Mimis siteKey. Einleitungstext anpassen („Wählen Sie Ihren Zeitraum im Kalender …“). Kein Push durch Claude.

15. Tests und Livegang

  • Unit: preis.js (Staffeln, Zusatzpersonen, Rabatt, USt-Rundung, Kleinunternehmer), ical.js (Parser inkl. Faltung, DATE/DATE-TIME, CANCELLED, Export), Statusmaschine, Rechnungsnummern unter Parallelität, Belegung/Überschneidung, öffentliche Routen (Validierung, Rate-Limit, Token), Aufbewahrung. Muster der bestehenden Tests (Appwrite-Mock), Node-Läufe mit --max-old-space-size=1024 und Timeout.
  • UI: Playwright-Durchlauf (Muster test/ui/shopTestServer.mjs): Anfrage im Widget → Freigabe im Portal → Buchungsseite → „vor Ort“ → Rechnung; Online-Zahlung gegen Stripe-Attrappe.
  • Livegang (Notiz docs/funktionen/unterkunft-livegang.md): Setup-Skript, Stripe-Preis anlegen, Connect-Webhook-Events prüfen (vorhandener Endpunkt), compose/.env UNTERKUNFT_ENABLED=pilot, UNTERKUNFT_PILOT_EMAILS=kenso.gri@gmail.com, <Mimi>, Push durch den Inhaber (feature/unterkunft:main, nie unter eigenem Namen), Rechtstexte hochladen, Pilot mit Mimi, danach on.

16. Parallele Stränge (für den Plan)

  1. Kern zuerst (seriell, klein): Schema + Setup-Skript, Schalter, Register-Eintrag, preis.js, belegung.js, Statusmaschine mit festen Funktionsschnittstellen und Attrappen. Danach gleichzeitig:
  2. Ablauf + Server: öffentliche Routen, Portal-API, Stripe, Jobs (Frist, iCal, Aufbewahrung), Rechnungsnummern.
  3. Portal-Oberfläche + WOMS.
  4. Widget + Buchungsseite + Donau-Home-Einbau.
  5. Mails, Rechnungs-PDF, Rechtstexte, Einbau- und Livegang-Notiz. Abschluss: Zusammenführen, Gesamttests, Review, UI-Durchlauf.

17. Nachträge aus der Code-Prüfung (2026-10-07, vor dem Plan)

  • N1 (ersetzt U16): Widget nach dem Muster shop.js/termine.js statt Shadow DOM: eigenes <style id="wku-style"> mit @layer wku, Klassenpräfix .wku-, Variablen --wku-akzent, --wku-akzent-text, --wku-hintergrund, --wku-text, --wku-rand, --wku-radius, --wku-schrift, --wku-schrift-titel. Öffentliche API unter /api/public/unterkunft/v1/.
  • N2 (ersetzt den Bucket uk-rechnungen): Keine Datei-Ablage. ukRechnungen.inhalt speichert den vollständigen, unveränderlichen Schnappschuss (Aussteller, Empfänger, Unterkunft, Zeitraum, Posten, Summen, Steuerart, Bezug); das PDF wird bei jedem Abruf daraus deterministisch erzeugt. Eigener minimaler PDF-Writer (Helvetica, WinAnsi inkl. Umlaute und €), keine neue Abhängigkeit.
  • N3: Die Mail-Warteschlange (termine/outbox.js) bekommt ein optionales Feld anhang (JSON-Verweis {art:'ukRechnung', rechnungId}), sendGuestMail einen Parameter attachments; das PDF entsteht beim Versand.
  • N4 (konkretisiert U15): ukEinstellungen.stripeAccountId ist eigen; hat das Projekt ein Webshop-Konto (shopConfig.stripeAccountId), bietet das Portal „vorhandenes Stripe-Konto verwenden“ an. Metadaten wkUk:'1'; account.updated aktualisiert auch ukEinstellungen; erstatten bekommt einen Parameter für das Metadatum webklar.
  • N5: Datenschutz-Abschnitt bleibt § 6f (6e ist für Öffnungszeiten reserviert, noch nicht live).
  • N6: Kein Textform-Nachweis (nicht in TEXTFORM_KEYS); Mimi wird im WOMS über „Freischalten“ (action:'activate') ohne Zahlung freigeschaltet.