trihub-ernaehrung-backseatDevs/docs/booking.md

144 lines
8.5 KiB
Markdown

# Paketbuchung
## Ablauf
Die Angebotsübersicht führt zur Detailseite eines Pakets. Der Buchungsbutton
übergibt die Paket-ID und gegebenenfalls die gewählte Variante als URL-Parameter
an `booking.html`. Bestehende Links mit `#/buchen/:packageId` funktionieren weiterhin.
Booking lädt die Bestellübersicht vom Server. Sie enthält Paket und Variante,
Laufzeit, Leistungen, Abrechnung, Nettopreis, MwSt. und Gesamtbetrag. Die Preise
stammen aus `src/shared/packagesdetails.js`; Preisangaben des Browsers werden
nicht übernommen. Ohne Varianten-ID wird die erste Variante des Pakets gewählt.
Beim Absenden speichert der Server Kundendaten und den vollständigen Bestellstand
in der CSV. Anschließend sendet er eine Bestätigung als Text und HTML mit
Buchungsnummer, Datum, gebuchten Leistungen und Preisen. Änderungen am Katalog
verändern bereits gespeicherte Bestellungen nicht.
Der Zahlungsvorgang ist weiterhin simuliert. Es erfolgt keine Abbuchung und
keine verbindliche Beratungsbuchung. Darauf weisen Formular und E-Mail hin.
## Lokal starten
Voraussetzung ist Node.js 24. Abhängigkeiten mit `npm ci` installieren und die
Werte aus `.env.example` in eine lokale `.env` übernehmen. Eine vorhandene
Konfiguration dabei erhalten.
In separaten Terminals starten:
- `npm run dev:mail`: Mailpit mit SMTP auf Port 1025 und Weboberfläche auf 8025.
- `npm run dev:server`: Buchungs-API auf Port 3000.
- `npm run dev`: Frontend auf Port 5173; `/api` wird an Node weitergeleitet.
Die Angebotsübersicht liegt unter <http://localhost:5173/angebotsuebersicht.html>,
der Mailfänger unter <http://localhost:8025>. Mailpit ist im Dev Container enthalten.
`npm run build` erzeugt die Seiten in `dist/`, einschließlich der Paketdetailseite.
Für ein Deployment muss der Webserver `/api` an den Node-Server weiterleiten.
`npm run start:server` startet die API ohne automatischen Neustart.
## Konfiguration
| Variable | Standardwert | Bedeutung |
| ------------------------ | -------------------------------- | --------------------------------------------------- |
| `HOST` | `127.0.0.1` | Bind-Adresse der API |
| `PORT` | `3000` | API-Port; bei Änderung auch den Vite-Proxy anpassen |
| `SMTP_HOST` | `127.0.0.1` | SMTP-Server |
| `SMTP_PORT` | `1025` lokal, sonst `587` | SMTP-Port |
| `SMTP_SECURE` | `false` | Direktes TLS aktivieren |
| `SMTP_USER`, `SMTP_PASS` | leer | SMTP-Zugangsdaten; beide gemeinsam setzen |
| `SMTP_FROM` | `Tri-Hub <buchung@example.test>` | Absender |
| `SMTP_ALLOW_EXTERNAL` | `false` | Versand über externe SMTP-Server aktivieren |
Externe SMTP-Verbindungen erfordern TLS. Zugangsdaten gehören ausschließlich
in die Serverkonfiguration und dürfen kein `VITE_`-Präfix erhalten.
## API
Der vollständige Vertrag steht in `src/server/swagger.json`.
| Endpunkt | Funktion |
| ---------------------------- | ----------------------------------------------------------------------------------- |
| `GET /api/angebote` | Bestehende Kurzübersicht mit `id`, `name` und `summary` |
| `GET /api/packages` | Kurzübersicht im Swagger-Format |
| `GET /api/packages/{id}` | Paketdetails einschließlich Varianten und Leistungen |
| `POST /api/bookings/preview` | Bestellübersicht für `packageId` und optionale `variantId`; speichert keine Buchung |
| `POST /api/bookings` | Buchung speichern und Bestätigung versenden |
POST-Anfragen benötigen `Content-Type: application/json`. Beim Buchen ist
zusätzlich ein `Idempotency-Key` als UUID v4 erforderlich. Das Formular sendet
`packageId`, gegebenenfalls `variantId`, `name` und `email`.
Alternativ akzeptiert die API das Swagger-Objekt `customer` mit `firstName`,
`lastName`, `email` sowie optional `phone`, `ageGroup` und `notes`. Dabei muss
`acceptedTerms` den Wert `true` haben. Diese Angaben werden ebenfalls gespeichert.
Die Antwort enthält die Buchungsnummer, den Erstellungszeitpunkt, das gebuchte
Paket unter `bookedPackage` und den Versandstatus. Eine neue Buchung mit
SMTP-Annahme erhält HTTP 201, eine Wiederholung HTTP 200. Bei ausstehendem,
fehlgeschlagenem oder unklarem Versand wird HTTP 202 zurückgegeben.
Ungültige Angaben, Pakete und Varianten ergeben HTTP 400; ein unbekanntes Paket
am Detail-Endpunkt HTTP 404. Derselbe Anfrageschlüssel mit anderen Daten ergibt
HTTP 409. JSON-Anfragen sind auf 8 KiB begrenzt. Fehlerantworten enthalten
`error.code`, `error.message` und gegebenenfalls `error.fields`.
## Speicherung und Wiederholungen
Die Buchungen liegen in `src/server/data/bookings.csv`. Neue Buchungsnummern
werden fortlaufend als `TH-000001`, `TH-000002` usw. vergeben. Der höchste
vorhandene Wert bestimmt die nächste Nummer. Die CSV muss deshalb vollständig
erhalten bleiben; ältere UUID-Buchungsnummern werden weiterhin unterstützt.
`bookedPackage` speichert den Bestellstand als JSON, `customer` die optionalen
strukturierten Kundendaten und `acceptedTerms` die zugehörige Zustimmung.
CSV-Dateien mit dem früheren Spaltensatz bleiben lesbar und werden beim nächsten
Speichern erweitert. Für alte Buchungen ohne Bestellstand liefert die API
`bookedPackage: null`.
Lesen, Schreiben und Versand werden innerhalb einer Warteschlange abgearbeitet.
Die Datei wird über eine temporäre Datei atomar ersetzt. Unterstützt wird ein
Backend-Prozess auf einem lokalen Dateisystem. Mehrere Serverinstanzen benötigen
einen gemeinsam abgesicherten Speicher. CSV-Zellen werden gegen die Auswertung
als Tabellenformeln geschützt; Dateien erhalten den Zugriffsmodus 0600.
Wiederholte Anfragen mit demselben Schlüssel und denselben Daten liefern die
vorhandene Buchung. Es wird keine zweite Bestellung angelegt. Die Auswahl einer
anderen Variante mit demselben Schlüssel wird abgelehnt.
Das Formular merkt sich Anfrage und Bestellübersicht je Paket und Variante im
`sessionStorage`. Nach einem Verbindungsfehler bleiben die Angaben gesperrt,
damit dieselbe Anfrage erneut geprüft werden kann. Ein neuer Tab oder gelöschter
Sitzungsspeicher liegt außerhalb dieser Absicherung.
## Versandstatus
| Status | Bedeutung |
| ---------- | ------------------------------------------------------------------------------------------------ |
| `pending` | Buchung gespeichert; Versand noch nicht gestartet. Dieselbe Anfrage kann den Versand fortsetzen. |
| `sending` | Versandabsicht gespeichert. Nach einem Prozessabbruch liefert die API `unknown`. |
| `accepted` | SMTP hat die Nachricht angenommen; die Zustellung ist noch nicht bestätigt. |
| `failed` | Versand fehlgeschlagen oder ausdrücklich abgelehnt. |
| `unknown` | SMTP-Annahme oder anschließende Statusspeicherung blieb unklar. |
Bei `failed` und `unknown` wird nicht automatisch erneut versendet. Vor einem
manuellen Neuversand muss der Mailserver anhand der Buchungsnummer geprüft werden.
CSV und SMTP bilden keine gemeinsame Transaktion.
## Tests
`npm test` prüft Validierung, Preisberechnung, Varianten, CSV-Kompatibilität,
Wiederholungen und Versandfehler. `npm run test:browser` prüft den Weg von der
Angebotsübersicht über die Paketdetails bis zur Bestätigung, einschließlich
Premium-Jahresvariante, Tastaturbedienung und schmalem Bildschirm.
Die Tests verwenden die regulären Paketdaten, temporäre CSV-Dateien und einen
simulierten Mailversand. Playwright benötigt Chromium; die Installation erfolgt
mit `npx playwright install --with-deps chromium`. Die Ports 3000 und 5173 müssen
für die automatisch gestarteten Testserver frei sein.
Vor einem Commit außerdem `npm run format:check`, `npm run build` und
`git diff --check` ausführen. Für eine manuelle Mailprüfung eine Buchung mit einer
Adresse unter `example.test` anlegen und die Bestätigung in Mailpit kontrollieren.