144 lines
8.5 KiB
Markdown
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.
|