mein-sterntours/dev/entwicklungsplan.md
Phase-1-Rollback-Agent e3dc1afd8e WIP: Sicherheitsnetz vor Phase-1-R\u00fcckbau
Enth\u00e4lt gemischt: Laravel-10-Upgrade + Phase 1 (Contacts-Modul, Duplicats-Commands,
Soft-Delete+Merge-Fields) + Phase 2 Code-Umstellungen (inquiry_id, $table='contacts'/'inquiries')
+ Offers-Modul (Migrationen, Models, offer_id in Booking, offer-Disk in filesystems.php).

Phase 2 + Offers werden im folgenden Commit nach dev/backups/phase2-offers-2026-04-17/
verschoben, damit der Workspace auf Phase-1-only (= Test-System-Stand) reduziert ist
und direkt auf Live deploybar wird.

Tarball-Backup zus\u00e4tzlich unter: ../backups-safety/workspace-pre-phase1-rollback-2026-04-17.tar.gz

Made-with: Cursor
2026-04-17 13:40:31 +00:00

732 lines
50 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Entwicklungsplan mein.sterntours.de (2026)
**Erstellt:** April 2026
**Basis:** [briefings.md](./briefings.md), [customer-bookings/](./customer-bookings/), [audit-april-2025.md](./audit-april-2025.md), [projekt-empfehlungen-2026-04.md](./projekt-empfehlungen-2026-04.md), [frontend-navigation/](./frontend-navigation/)
**Ziel:** Konsolidierung der drei Anwendungen (`mein.sterntours.de` v3, `sterntours.de` Frontend-Backend, `v2.stern-tours.de` Reiseverwaltung) in **ein** modernes Laravel-10-CRM; Ablösung der Altsysteme; neue Kernfunktion „Angebote".
---
## 0. Einordnung und Grundprinzipien
### 0.1 Strategische Leitplanken
- **Ein System als Zielbild:** `mein.sterntours.de` (Laravel 10) wird das einzige Backend. `sterntours.de` bleibt als Frontend-Auslieferung bestehen, das Redaktions-/Admin-Backend darin entfällt. `v2.stern-tours.de` wird komplett abgelöst.
- **Schrittweise, rückwärtskompatibel:** Jede Phase muss produktiv deploybar sein, ohne dass andere Module brechen. Parallelbetrieb alt/neu wo nötig.
- **Migrationen immer mit Rollback:** Jede DB-Migration hat einen `down()`-Pfad, Drop-Migrationen laufen erst nach bestätigter Stabilität.
- **UI-Baseline festlegen:** Bevor die Modernisierung in die Breite geht, wird ein verbindlicher UI/UX-Standard definiert (Listen, Detailseiten, Formulare, Modals, Toolbar, Filter, Inline-Edit, Tabs). Alle neuen Module werden daran ausgerichtet; Altmodule ziehen nach.
- **Business-Logik in Services, nicht in Controllern.** Neue Module nutzen konsequent den bestehenden Schichtenaufbau (Controller → FormRequest → Repository → Service → Model).
### 0.2 Grob-Roadmap (Reihenfolge, high-level)
```
Block A Fundament (Voraussetzung für alles Weitere)
A1 UI/UX-Baseline definieren
A2 Technisches Aufräumen aus Audit (API-Key, Frontend-Tooling-Entscheidung, Pint/Larastan, Queue)
A3 Customer/Lead/Booking-Neustrukturierung Phasen 24 abschließen
Block B Kerngeschäft (sichtbare Verbesserungen für Mitarbeiter)
B1 Backend-Direktbuchung (Reise + Fewo)
B2 Organisations-Tab Buchung überarbeiten
B3 E-Mail-System vereinheitlichen + Auto-Save
B4 Fewo-Doppelbuchung / Belegungs-Bug beheben
B5 Angebots-Modul (zentrale neue Funktion)
Block C Ablösung der Altsysteme
C1 Admin-Controller sterntours.de → mein.sterntours.de migrieren
C2 Navigation + Page-Modell refactoren
C3 CMS-Module vereinheitlichen (Navigation / Benutzerführung)
C4 Reisenverwaltung v2.stern-tours.de migrieren (größtes Modul)
Block D Betrieb & Langfristpflege
D1 Tests, Monitoring, Deployment
D2 Framework-/PHP-Upgrade-Pfad (Laravel 11, PHP 8.3+)
Block E Kundenseitige Self-Service-Funktionen (Zukunftsmodul)
E1 Kundenportal / Kunden-Login (Modul 15)
```
Die genannten Module sind jeweils unten ausführlich beschrieben. Block A ist zwingende Voraussetzung, danach können B und C teilweise parallelisiert werden.
---
## 1. Modul 1 — UI/UX-Baseline (Block A1)
**Ziel:** Einmalig festlegen, wie eine „moderne, einheitliche Seite" im Backend aussieht. Davon leitet sich alles andere ab.
### 1.1 Analyse / Inventar
- Screenshots aller Haupt-Listen (Buchungen, Anfragen, Fewo-Buchungen, Kunden, Kontakte, CMS-Listen) aufnehmen und Abweichungen dokumentieren (Filter oben / Filter als Sidebar / Suchfeld, Inline-Edit ja/nein, Pagination-Stil, Table-Buttons).
- Detailseiten (Anfrage-Detail, Buchung-Detail, Fewo-Buchung-Detail) strukturell vergleichen: Welche Sektionen / Tabs / Boxen existieren, welche sind identisch, welche unterscheiden sich unnötig?
- Typografie, Abstände, Form-Controls, Modal-Varianten auflisten.
### 1.2 Baseline-Definition
- Einheitliches Grid, einheitliche Toolbar (links Filter / Suche, rechts Aktionen).
- Einheitliches DataTable-Pattern (Filter → Spalten → Badge-Logik → Zeilenaktionen).
- Einheitliches Tab-Layout auf Detailseiten (linke Sidebar mit Stammdaten / Zähler, rechte Tab-Area).
- Einheitliche Modals (Bestätigung, Formular, History).
- Einheitliche Status-/Fehler-/Erfolgs-Toasts.
- Komponentenbibliothek als Blade-Components unter `resources/views/components/ui/` (Button, Card, Tabs, Toolbar, FormRow, Modal, Toast).
### 1.3 Pilot-Umsetzung
- Bestehende **Contacts-Liste** (bereits gut, Phase 1 abgeschlossen) als Referenz auf den Baseline-Stand anheben.
- Eine weitere Ansicht (Vorschlag: Buchungsliste) auf die Baseline portieren, um das Pattern zu validieren, bevor die Breite umgestellt wird.
### 1.4 Frontend-Tooling-Migration (parallel, blockiert den Pilot nicht)
**Zielkorridor (Entscheidung Abschnitt 17.7):** Vite + dart-sass + schrittweise Bootstrap 5.
- Laravel Mix 2 → **Vite** als Build-Tool.
- `node-sass`**dart-sass**.
- **Bootstrap 4 → 5 schrittweise:** erst Infrastruktur umstellen (Vite/Sass), Bootstrap 5 als Ziel im gleichen Branch parallel einziehen; Views ziehen **modulweise** nach, nicht in einem Big-Bang-Rewrite.
- Komponentenbibliothek aus 1.2 wird direkt auf Bootstrap 5 aufgesetzt — so zieht jedes neu gebaute Modul (ab Modul 4 aufwärts) automatisch in die neue Welt um.
- Alte Views bleiben in Bootstrap 4 lauffähig, bis ihr jeweiliges Modul umgebaut wird.
- Als eigener Feature-Branch, erst mergen, wenn Pilot-View auf neuer Pipeline läuft und parallel zu Bootstrap-4-Views ko­existieren kann.
**Aufwand:** 23 Wochen (inkl. Komponenten-Library, Pilot-View).
**Abhängigkeiten:** keine. Startmodul.
**Deliverables:** Baseline-Dokument, Komponentenbibliothek, 2 Referenz-Views.
---
## 2. Modul 2 — Technische Hausaufgaben (Block A2)
Aus [audit-april-2025.md](./audit-april-2025.md) und [projekt-empfehlungen-2026-04.md](./projekt-empfehlungen-2026-04.md), bevor größere Module starten. Alle klein / mittel, aber Voraussetzung für einen sauberen Weiterbau.
| # | Maßnahme | Priorität | Aufwand |
|---|----------|-----------|---------|
| 2.1 | Composer-Wildcards (`"*"`) durch feste Versionen ersetzen | Hoch | klein |
| 2.2 | `navigation/cache/clear`-Endpoint absichern (Auth/Secret/IP) | Hoch | klein |
| 2.3 | `booking/import`: GET entfernen, nur POST; Rate-Limit | Hoch | klein |
| 2.4 | `MailDirService`-Nutzung in Booking-/Lead-Services konsolidieren (war im Audit offen) | Mittel | mittel |
| 2.5 | API-Routen auf Klassen-Syntax (`[Controller::class, 'method']`) | Niedrig | klein, laufend |
| 2.6 | Laravel Pint + Larastan + CI-Pipeline | Mittel | mittel |
| 2.7 | PHPUnit: zweite SQLite-Connection für `mysql_stern` + Factories für Customer/Booking/Lead | Mittel | mittel |
| 2.8 | Queue-Worker (Supervisor) + `Mail::queue()` für nicht-kritische Mails | Mittel | mittelgroß |
| 2.9 | `Console\Kernel::schedule()` dokumentieren oder befüllen (keine unsichtbaren Crons) | Mittel | klein |
**Abhängigkeiten:** keine. Läuft parallel zu A1.
---
## 3. Modul 3 — Customer / Lead / Booking konsolidieren (Block A3, in Arbeit)
**Aktueller Stand:** Phase 1 auf Testsystem abgeschlossen, Phasen 24 offen. Siehe [customer-bookings/umsetzung.md](./customer-bookings/umsetzung.md).
### 3.1 Was noch zu tun ist
1. **Phase 1 live deployen** (Duplikat-Merge). Voraussetzung: Backup + Stichprobenprüfung der CSV auf Test.
2. **Phase 2 — Tabellen umbenennen** (`customer``contacts`, `lead``inquiries`, `booking.lead_id``inquiry_id`). Code-Vorarbeit: Model-`$table`, Repositories auf `inquiry_id`, Routen `/lead/``/inquiry/` mit 301-Redirects.
3. **Phase 3 — Participants konsolidieren** (`participants_unified`). Code-Anpassung: `LeadRepository::createBooking()` kopiert keine Teilnehmer mehr; PDF-Generierung auf unified-Tabelle umstellen. **Vor dem Drop** der Alttabellen mindestens eine Woche Parallelbetrieb.
4. **Phase 4 — Communications / Notices / Attachments konsolidieren.** Neue Repositories (`CommunicationRepository`, `NoticeRepository`, `AttachmentRepository`), Views teilen, dann Alt-Tabellen droppen.
### 3.2 Ergänzungen / Optimierungen, die in dieser Phase mitlaufen sollten
Aus eigener Analyse nachgeschoben:
- **Abhängiges Newsletter-Modul** (erwähnt in `umsetzung.md` §Abhängiges Modul): Nach Phase 2 `NewsletterContact`-Model auf `App\Models\Contact` mappen, damit wir keine zwei Model-Pfade für dieselbe Tabelle halten.
- **Passport-/API-Scopes:** Nach Umbenennung der Tabellen sind alle API-Responses, die `customer_id`/`lead_id` ausliefern, zu prüfen (Versionierung `v1` behalten, optional `v2` mit neuen Feldnamen).
- **Volltextsuche für Contacts:** Mit wachsender Kundenhistorie lohnt sich ein DB-Index auf `email`, `name`, `firstname`, `zip` (oder MySQL-Fulltext) — verbessert die bereits eingebaute Schnellsuche.
- **Audit-Log für Merges:** `merge_log`-Tabelle (wer hat wann welchen Contact in welchen gemerged), damit im Support nachvollziehbar bleibt.
- **DSGVO-Lösch-Workflow:** `deleted_at` existiert bereits. Braucht UI für „Kontakt komplett löschen" (mit allen Anfragen/Buchungen), inkl. Sperre, falls aktive Buchungen existieren.
- **Teilnehmer als Stammdaten:** `participants_unified.contact_id` langfristig konsequent setzen — dann können Teilnehmer aus bisherigen Reisen bei neuer Buchung übernommen werden.
**Aufwand:** wie in `umsetzung.md` geschätzt, plus 1 Woche für die Ergänzungen oben.
**Abhängigkeiten:** Phase 2 ist Voraussetzung für das neue Angebots-Modul (Modul 6), weil sonst zwei Namenswelten parallel leben.
---
## 4. Modul 4 — Backend-Direktbuchung (Block B1)
**Briefing-Punkt:** „Es muss auch möglich sein, direkt im System Buchungen anzulegen. Aktuell gehen Mitarbeiter auf die Webseite und lösen dort eine Bestellung aus."
### 4.1 Ist-Analyse
- Alle Buchungen laufen aktuell über das Webseiten-Formular (`sterntours.de/src/AppBundle/Controller` → API-Endpoint `booking/import` auf `mein.sterntours.de`).
- Im Backend gibt es keinen vollständigen „Neue Buchung"-Flow; Anlage erfolgt bisher via Konvertierung aus einem Lead (`LeadRepository::createBooking()`).
### 4.2 Teilschritte
1. **Flow definieren (paper design):**
- Einstieg 1: „Direktbuchung ohne Anfrage" (z. B. aus `/bookings` → „Neue Buchung").
- Einstieg 2: „Buchung aus Angebot" (kommt aus Modul 6).
- Einstieg 3: Bestehend — aus Anfrage (bleibt wie gehabt).
2. **Gemeinsames BookingFormRequest:** Validierung, die sowohl von der Web-API als auch vom Backend-Form genutzt wird (Single Source of Truth).
3. **BookingService::createManual(array $data, ?Inquiry $inquiry = null)** als zentrale Erzeugungsmethode (ersetzt Duplikatlogik zwischen Web-Import und Backend).
4. **UI-Flow im Backend (mehrstufig, nicht ein Riesenformular):**
- Schritt 1: Kontakt wählen oder anlegen (Contact-Autocomplete aus neuer Contacts-Tabelle).
- Schritt 2: Reise / Fewo wählen, Termin, Zimmer-/Personenbelegung.
- Schritt 3: Teilnehmer (aus Kontakt-Historie vorschlagen — hier greift `participants_unified.contact_id`).
- Schritt 4: Leistungen / Optionen / Versicherung.
- Schritt 5: Zahlmodalitäten, Anmerkungen.
- Schritt 6: Übersicht + „Anlegen" + „Anlegen & Bestätigung-Mail senden".
5. **Wiederverwendung des Web-Formulars:** Prüfen, ob Teile des Frontend-Formulars (`sterntours.de`) als Blade-Partials ins Backend gezogen werden können — sonst aber lieber neu auf der UI-Baseline, da das Alt-Frontend auf Twig/Symfony läuft.
6. **E-Mail-Versand optional (Entscheidung Abschnitt 17.4):**
- Letzter Schritt im Backend-Flow bietet zwei Buttons:
- **„Anlegen & Bestätigung senden"** → nutzt dieselbe Mail-Pipeline wie der Web-Import (Buchungsbestätigung an Kunde + interne Mail).
- **„Nur anlegen (keine Mail)"** → legt Buchung an, setzt ein internes Flag `silent_created = 1`, kein Kundenversand.
- Das Flag wird im Audit-Log der Buchung vermerkt, damit später nachvollziehbar ist, warum keine Bestätigung rausging.
- Nachträglicher manueller Mailversand bleibt jederzeit möglich (bestehende Mail-Funktionalität in Buchungs-Detailseite).
7. **Dokumente wie bei Web-Buchung:** Direktbuchung nutzt dieselbe Dokumenten-Logik wie `app/Http/Controllers/BookingController.php` heute (zentral hinterlegte Dokumente über `hasDocument(...)` + freie Uploads über `BookingFileRepository`) — kein Neubau.
### 4.3 Fewo-Variante
- Dasselbe Konzept für Fewo (`TravelUserBookingFewoController`) — mit Verfügbarkeitsprüfung in Echtzeit und harter Kollisions-Sperre (siehe auch Modul 7, Doppelbuchungen).
**Aufwand:** ~4 Wochen (Reisebuchung + Fewo + Tests).
**Abhängigkeiten:** UI-Baseline (Modul 1), Phase 2 Customer/Lead (damit `contact_id`/`inquiry_id` sauber sind).
**Risiko:** Mittel — muss mit Web-API-Import identisch enden, sonst entstehen zwei Wahrheiten.
---
## 5. Modul 5 — Organisations-Tab der Buchung überarbeiten (Block B2)
**Briefing-Punkt:** „Unter dem Punkt Buchungen muss der Reiter Organisation verbessert werden. Es muss von der Benutzerführung einfacher und übersichtlicher werden, eine Organisation einer Reise anzulegen und zu verwalten."
### 5.1 Ist-Analyse (TODO vor Start des Moduls)
- Screenshots + Workflow-Walkthrough des aktuellen Organisations-Tabs dokumentieren.
- Welche Entitäten / Datensätze hängen daran (Transportleistungen, Partner, Zeiten, Dokumente)? Welche Felder sind Pflicht, welche redundant?
- Welche Reibungspunkte berichten die Mitarbeiter (Usability-Interview, 30 min).
### 5.2 Teilschritte
1. **Zieldesign** (aus UI-Baseline abgeleitet): Zeitleisten-/Kacheldarstellung je Leistungsposition (statt langer Tabelle), Inline-Edit für häufige Änderungen.
2. **Refactor im Daten-Layer:** Doppelte Felder identifizieren, konsolidieren (vermutlich analog zu Teilnehmer-Konsolidierung).
3. **UI-Umsetzung** auf Basis der neuen Komponentenbibliothek.
4. **Bulk-Aktionen:** Leistungsblock kopieren (für Gruppen-/Serienreisen), Vorlagen speichern.
5. **PDF-Output** prüfen (Organisationsplan als Dokument).
**Aufwand:** ~23 Wochen.
**Abhängigkeiten:** Modul 1. Kein harter Zusammenhang zu Modul 3, aber wirkt sauberer, wenn Modul 3 Phase 3 (Participants) vorher durch ist.
---
## 6. Modul 6 — Angebote / Offers (Block B5, großer neuer Kernbaustein)
**Briefing-Zitat (Kunde):** „Wir möchten aus unserem System unbedingt einfach, schnell und qualitativ hochwertig Angebote erstellen können … Theoretisch kann ein solches Angebot aussehen wie unser Buchungsauftrag, nur dass Angebot' darüber steht … Wichtig ist uns, dass es einfach zu erstellen ist und man ggf. bei einer sehr hochwertigen Reise auch weiterführenden Text zum Reiseverlauf für die Reisebeschreibung hinzufügen kann … Anschließend wäre es gut, wenn hieraus direkt eine Buchung generiert werden könnte."
### 6.1 Ziel
Aus einer Anfrage (oder direkt) ein **Angebot** erstellen, als PDF ausgeben, an den Kunden versenden und bei Annahme **mit einem Klick in eine Buchung umwandeln**, ohne Daten doppelt zu pflegen.
### 6.2 Datenmodell
Neue Tabellen (Entwurf):
```sql
-- Logischer Angebotsdatensatz (eine "Angebots-Nummer", mehrere Versionen möglich)
CREATE TABLE offers (
id INT PK,
inquiry_id INT NULL, -- aus Anfrage erzeugt
contact_id INT NOT NULL,
booking_id INT NULL, -- gesetzt, sobald Angebot → Buchung wurde
offer_number VARCHAR UNIQUE, -- eigene Nummernkreisführung, z. B. 2026-00123
status ENUM('draft','sent','accepted','declined','expired','withdrawn'),
current_version_id INT NULL, -- FK auf offer_versions.id (aktiv/zuletzt versendet)
created_by INT,
created_at, updated_at
);
-- Jede versendete (und jede danach geänderte) Fassung ist eine eigene Version
CREATE TABLE offer_versions (
id INT PK,
offer_id INT FK,
version_no INT, -- 1, 2, 3, ...
status ENUM('draft','sent','accepted','declined','expired','superseded'),
valid_until DATE NULL,
total_price DECIMAL(10,2),
headline VARCHAR, -- "Persönliches Angebot für Frau XXX"
intro_text TEXT,
itinerary_text LONGTEXT, -- Reiseverlauf, optional WYSIWYG
closing_text TEXT,
template_id INT NULL,
pdf_path VARCHAR NULL, -- generiertes PDF dieser Version
pdf_archived TINYINT(1) DEFAULT 0, -- für spätere Speicher-Bereinigung alter Versionen
sent_at DATETIME NULL,
accepted_at DATETIME NULL,
accepted_via ENUM('customer_link','admin','email') NULL,
created_by INT,
created_at, updated_at,
UNIQUE KEY (offer_id, version_no)
);
CREATE TABLE offer_items (
id INT PK,
offer_version_id INT FK, -- gehört zu einer Version, nicht zum Angebot direkt
position INT,
type ENUM('travel','service','option','discount','insurance','custom'),
title VARCHAR,
description TEXT,
quantity INT DEFAULT 1,
price_per_unit DECIMAL(10,2),
total_price DECIMAL(10,2),
travel_program_id INT NULL,
fewo_lodging_id INT NULL,
metadata JSON
);
CREATE TABLE offer_templates (
id, name, description, headline, intro_text, itinerary_text, closing_text, items_json, ...
);
-- Kundenseitiger Freigabe-Link (Token-URL, Einmal-Link pro Version)
CREATE TABLE offer_access_tokens (
id INT PK,
offer_version_id INT FK,
token VARCHAR(64) UNIQUE,
expires_at DATETIME NULL,
opened_at DATETIME NULL, -- wann der Kunde den Link erstmals geöffnet hat
accepted_at DATETIME NULL,
declined_at DATETIME NULL,
ip_address VARCHAR(45) NULL,
user_agent VARCHAR(255) NULL
);
```
Angebote sind **eigenständig**, nicht Unterdatensatz einer Buchung. Eine Buchung kann per `offer_id` rückverweisen, und ein Angebot hält per `booking_id` die daraus entstandene Buchung. Die Versionierung ist zwingend (Entscheidung aus Abschnitt 17.1): ab dem ersten Versand ist Änderung = neue Version.
### 6.3 Teilschritte
1. **Datenmodell & Migrationen** (`offers`, `offer_versions`, `offer_items`, `offer_templates`, `offer_access_tokens`).
2. **Model / Repository / Service** (`OfferService::createFromInquiry($inquiryId)`, `::createNewVersion(Offer $offer)`, `::send(OfferVersion $v)`, `::convertToBooking(Offer $offer)`).
3. **Vorlagen-Verwaltung:**
- Eigene Liste `/offer-templates`, anlegbar aus einem existierenden Angebot (Button „Als Vorlage speichern").
- Vorlagen pro Reise-Organisation gruppierbar (Briefing: „aus einer der Vorlagen eine Organisation laden").
4. **Angebots-Editor:**
- Einstieg: innerhalb `/inquiries/{id}` Button „Angebot erstellen"; alternativ eigenständiges `/offers/create`.
- Vorlage wählen → Felder werden gefüllt → frei bearbeitbar.
- Positionen (offer_items) mit Drag&Drop, Kopier-Button, Summe live.
- Optionales Feld „Reiseverlauf" als WYSIWYG (TinyMCE/TipTap), ausblendbar für einfache Angebote.
- **Dokumente am Angebot** (analog zum bestehenden Buchungs-Pattern in `app/Http/Controllers/BookingController.php`, der `booking.hasDocument('coupon'|'storno'|…)` für zentral hinterlegte Dokumente und `BookingFileRepository` für freie Uploads nutzt):
- **Zentral hinterlegte Dokumente** (Briefbögen, AGB, Standard-Anhänge pro Reise-Organisation / Reisetyp) werden auswählbar und automatisch als Anhang angeboten.
- **Freie Uploads pro Angebot** (individueller Reiseverlauf, eigener Briefbogen, Bilder) — analog zu `booking_file` über eine `offer_file`-Tabelle bzw. über das in Modul 3 Phase 4 konsolidierte `attachments`-Modul (dann mit `offer_version_id`).
- Ab `sent` werden die zu diesem Zeitpunkt ausgewählten/hochgeladenen Dokumente mit der Version „eingefroren" (zur Version gespeichert, damit spätere Änderungen keine alten PDFs verfälschen).
5. **PDF-Generierung:**
- Neues Template `pdf/offer.blade.php` analog zum bestehenden Buchungsauftrag-PDF.
- Header: „Angebot Nr. 2026-00123 / V2" und / oder „Persönliches Angebot für Frau Musterfrau".
- Gleiche Corporate-Identity-Elemente wie Buchungsauftrag.
- Anlage: zentral ausgewählte Dokumente + frei hochgeladene Dokumente.
6. **Versionierung (fest verankert, Entscheidung Abschnitt 17.1):**
- Solange Status `draft`: Änderungen ändern die aktuelle Version direkt.
- Ab dem ersten `send`: jede Änderung erzeugt eine **neue Version** (V2, V3 …); vorherige Version wird `superseded`.
- Jede Version hält ihr eigenes PDF + ihre eigenen Dokumente + ihre eigenen `offer_items` + ihren eigenen Freigabe-Token.
- **Speicher-/Archivstrategie:** Cron-Job (später, Modul 13) setzt PDFs älterer Versionen `pdf_archived = 1` und verschiebt sie auf kalten Storage; konfigurierbares Retention-Limit (z. B. „nur letzte 3 Versionen im Hot-Storage, ältere PDFs bei Bedarf neu generieren").
7. **Versand-Workflow:**
- Status-Maschine auf `offer_versions`: `draft → sent → accepted/declined/expired/superseded`.
- Versand als E-Mail (PDF im Anhang, Textvorlage im CMS pflegbar — siehe Modul 11).
- Mail-Thread landet im gemeinsamen Communications-Modul (Modul 3 Phase 4).
- Beim Senden wird pro Version **ein neuer `offer_access_token`** erzeugt und in die Mail als Link eingebaut.
8. **Kundenseitiger Freigabe-Link (Entscheidung Abschnitt 17.2):**
- Öffentliche Route `GET /angebot/{token}` auf `sterntours.de` (oder Subdomain `angebote.sterntours.de`), die die Angebotsversion anzeigt (PDF-Embed + Anhänge + Buttons „Annehmen" / „Ablehnen" / „Fragen?").
- Route `POST /angebot/{token}/accept` und `POST /angebot/{token}/decline` mit IP/User-Agent-Protokoll.
- Beim Annehmen: `offer_versions.status = accepted`, `accepted_at = now()`, `accepted_via = 'customer_link'`, Benachrichtigung (Mail + Dashboard-Hinweis) an den zuständigen Mitarbeiter.
- **Admin-Annahme bleibt gleichwertig möglich** (Status händisch auf `accepted` setzen, `accepted_via = 'admin'`), z. B. bei telefonischer Zusage.
9. **Konvertierung Angebot → Buchung:**
- Button „In Buchung übernehmen" im Angebot (nur aktiv bei `accepted`).
- `OfferService::convertToBooking()` legt Buchung + Teilnehmer + Leistungen auf Basis der angenommenen `offer_version` an; nutzt denselben Service wie Modul 4 (Direktbuchung), damit nur eine Erzeugungslogik existiert.
- Angebots-Dokumente (Version-eingefroren) werden automatisch an die Buchung kopiert / verlinkt.
- `booking_id` wird auf `offers` gesetzt.
10. **Übersichtsseite / Listen:** `/offers` mit Filtern (Status, Ablaufdatum, Mitarbeiter, Kontakt, Version), Badges, Bulk-Export.
11. **Zähler / Dashboard:** In der Kontakt- und Anfrage-Übersicht jeweils Badge „X Angebote" analog zum Anfragen-/Buchungs-Badge.
12. **Berechtigungen:** Neue Permissions `offers.read`, `offers.create`, `offers.send`, `offers.accept`.
### 6.4 Ergänzungen aus meiner Sicht (nicht im Briefing, aber sinnvoll)
- **Ablauf-Automatik:** Cron setzt Angebote nach `valid_until` auf `expired`, optional Erinnerungsmail an Mitarbeiter vor Ablauf. Token wird dabei invalidiert.
- **Änderbarkeits-Regel:** Nur `draft`-Versionen sind frei änderbar. Ab `sent` ist Änderung = neue Version.
- **Preiskonsistenz:** `offer_items` greifen — sofern verknüpft — auf dieselben Preisquellen wie das bestehende Reise-/Fewo-System (aus v2 migriert, Modul 12). Keine Doppeltpflege von Preislisten.
- **Anbindung Kundenportal (Modul 15):** Sobald das Kundenportal steht, sieht der eingeloggte Kunde seine Angebote direkt dort — der Token-Link bleibt aber für nicht eingeloggte Kunden weiterhin der Standardweg.
**Aufwand:** ~79 Wochen (großes Modul, durch Versionierung + Freigabe-Link leicht gewachsen).
**Abhängigkeiten:**
- UI-Baseline (Modul 1) muss stehen.
- Modul 3 Phase 2 (inquiries-Tabelle), damit `inquiry_id`-Referenzen sauber sind.
- Modul 3 Phase 4 (attachments-Tabelle) ideal — sonst legen wir eine separate `offer_file`-Tabelle an, die später fusioniert.
- Modul 4 (gemeinsamer BookingService), damit die Konvertierung keinen Copy-Code erzeugt.
- Modul 12 Teil „Reiseprogramme" verfügbar in Laravel, falls `offer_items` auf bestehende Programme referenzieren sollen — zur Not über View-basiertes Legacy-Lesemodell überbrückbar.
---
## 7. Modul 7 — E-Mail-System vereinheitlichen & Auto-Save (Block B3)
**Briefing-Punkte:**
- „Unter dem Punkt Buchungen gibt es E-Mails … wird eine E-Mail geschrieben. Soll diese sofort per Auto-Save als Entwurf gespeichert werden."
- „Auch hier [Fewo] müssen die E-Mails, die dem selben Prinzip die Buchung verfolgen, auch ein Auto-Save bekommen."
- „Da die E-Mails im System grundsätzlich eine einheitliche Programmierung haben, wäre es sinnvoll, diese Skripte zu migrieren."
### 7.1 Teilschritte
1. **Konsolidierung zuerst erledigen** (Modul 3 Phase 4, `communications`-Tabelle). Solange drei verschiedene Mail-Tabellen existieren, ist jede Erweiterung doppelte Arbeit.
2. **Draft-Feld einführen** in `communications`: Status `draft | queued | sent | failed`, plus `autosaved_at`.
3. **Auto-Save im Editor (Frontend):**
- JS-Debounce (alle 3 s) schreibt per `PATCH /communications/{id}/draft` Betreff und Body.
- Anlage eines Drafts beim Öffnen des leeren Editors (liefert `id` zurück).
- Crash-/Reload-Recovery: Beim Öffnen des Mail-Dialogs prüft das Frontend, ob ein bestehender Draft für diesen Kontext existiert (Anfrage / Buchung / Fewo-Buchung) und bietet ihn zum Weiterbearbeiten an.
4. **Vereinheitlichte Modals** auf Basis der UI-Baseline (ein Mail-Modal für alle drei Kontexte).
5. **Queue-basierter Versand** (siehe Modul 2 / 2.8): `Mail::queue()`, mit `send_after`-Feld (geplanter Versand), Retry-Logik.
6. **CustomerFewoMail → Communications migrieren** (Ausnahme, die in Modul 3 Phase 4 ggf. noch offen ist).
7. **Draft-Aufräumcron** (automatische Löschung von Drafts > 30 Tage ohne Aktivität).
**Aufwand:** ~2 Wochen nach Abschluss von Modul 3 Phase 4.
**Abhängigkeiten:** Modul 3 Phase 4.
---
## 8. Modul 8 — Fewo-Doppelbuchungen / Belegung fixen (Block B4)
**Briefing:** „Bei den Buchungen der Ferienwohnungen tauchen immer wieder Fehler auf bei der Belegung … wenn die Daten geändert werden … Doppelbuchungen teilweise im System vorliegen. Ich glaube nur in der Datenbank, das muss natürlich bereinigt werden."
### 8.1 Analyse
- Root-Cause vermutet (aus Briefing): Daten-Änderungen werden nicht synchron in die zweite Speicher-Ebene durchgeschrieben (vermutlich Legacy-Admin in `sterntours.de/AdminController.php` vs. `mein.sterntours.de`).
- Ziel von Modul 9 (Admin-Migration) wird diesen Fehler indirekt mit beheben, der Bug muss aber separat gezielt adressiert werden — sonst blutet er weiter.
### 8.2 Teilschritte
1. **Quellen inventarisieren:** Welche Stellen schreiben `FewoReservation` / Belegungen (Legacy AdminController, Webseite, mein.sterntours Fewo-Buchungen, Fewo-Mail-Import, ggf. iCal-Sync)?
2. **Konflikt-Detektor-Cron schreiben:** Stündlicher Job, der Überlappungen pro `fewo_lodging_id` findet und einen Report ins Dashboard + Slack/Mail liefert.
3. **Datenbestand aufräumen:** Einmaliger Report → manuelle / halb-automatische Bereinigung.
4. **Zentrale Schreiblogik einziehen:** Alle Fewo-Reservierungs-Anlagen / -Änderungen laufen künftig durch einen einzigen `FewoReservationService`, der
- Überlappungen mit Lock prüft (`SELECT … FOR UPDATE`),
- Konsistent in allen relevanten Tabellen schreibt,
- Events feuert (für Cache-Invalidierung, Kalender).
5. **Turnustag-Logik (Entscheidung Abschnitt 17.5):**
- **Kein harter DB-Constraint** (kein `EXCLUSION`-/Unique-Index auf Tages-Ebene), weil An- und Abreisetag legitimerweise überlappen dürfen (Abreise-Vormittag / Anreise-Nachmittag am selben Tag).
- Überlappung wird **im Service** nach klaren Regeln geprüft:
- Konflikt = neue Buchung endet **später** als bestehende beginnt UND neue Buchung beginnt **früher** als bestehende endet, **und** die überlappenden Tage sind keine reinen Wechseltage (Turnustage).
- „Wechseltag" konfigurierbar pro Lodging (Tagestyp: frei / nur Anreise / nur Abreise / gesperrt).
- Der Service liefert bei Konflikt eine sprechende Fehlermeldung („Belegung kollidiert mit Buchung #1234 vom 12.19.06., kein Wechseltag"), statt stumm abzulehnen.
6. **Reservierungs-Audit-Log:** Jede Änderung protokollieren (alter/neuer Zeitraum, User, Quelle).
7. **UI-Kalender für Mitarbeiter:** Visueller Belegungskalender pro Lodging mit farblich markierten Wechseltagen — macht das „überlappen darf, aber nur am Turnustag" für Mitarbeiter sofort sichtbar.
**Aufwand:** ~2 Wochen (Analyse + Fix + Bereinigung).
**Abhängigkeiten:** kann früh gestartet werden; überschneidet sich inhaltlich mit Modul 9 (Admin-Migration) und muss dort mitgedacht werden.
---
## 9. Modul 9 — Migration Admin aus sterntours.de (Block C1)
**Briefing:** „Zusätzlich gibt es ein weiteres Backend `sterntours.de/src/AppBundle/Controller/AdminController.php`. Dieses muss auch sauber in das Hauptbackend `mein.sterntours.de` (v3) übernommen werden, damit wir das alte abstellen können. Damit wird sich dann vermutlich auch der Fehler erledigen. Grundsätzlich darf gerne die Datenstruktur migriert werden und optimiert werden."
### 9.1 Scope-Analyse
`AdminController.php` (~1.300 Zeilen) enthält laut Code u. a.:
- Fewo-Lodging-Verwaltung (CRUD, Bilder, Gruppen).
- Fewo-Preise, Saisons, Reservierungen.
- Fewo-Booking-Requests.
### 9.2 Teilschritte
1. **Inventar:** Jede Route in `AdminController.php` (+ evtl. zugehörige Templates) auflisten, Funktion beschreiben, Äquivalent im Laravel-System suchen.
2. **Datenstruktur-Analyse:** Welche Tabellen werden bedient (`fewo_lodging`, `fewo_price`, `fewo_season`, …)? Welche liegen schon in `mein.sterntours.de`, welche müssen migriert / umbenannt werden? Wo existiert doppelte Datenhaltung (Ursache für Modul 8)?
3. **Zieldesign:** Pro Funktion ein Ticket/Epic (Fewo-Lodging-Verwaltung, Preise, Saisons, …). Jede Funktion zieht auf die UI-Baseline (Modul 1).
4. **Migrationssequenz:**
- a) Read-Only-Schattenansicht in `mein.sterntours.de` bauen, die Daten aus der bestehenden Tabelle anzeigt.
- b) Schreib-Operationen parallel einbauen (Dual-Write-Modus: Legacy + neu), solange beide laufen.
- c) Schreib-Rechte im Legacy-Admin entziehen, sobald die neue UI stabil ist.
- d) Legacy-Admin vollständig deaktivieren (Routing/Login weg).
5. **Integrationstests pro Funktion** (zwingend, da sonst Modul 8 erneut entsteht).
### 9.3 Reihenfolge-Empfehlung innerhalb C1
1. Fewo-Lodging-CRUD (niedriges Risiko, häufigste Aufgabe).
2. Fewo-Preise + Saisons (mittel, zentral für Buchungen).
3. Fewo-Reservierungen (hohes Risiko — muss mit Modul 8 zusammen gedacht werden).
4. Fewo-Booking-Requests + Bilder.
**Aufwand:** ~810 Wochen abhängig von Funktionsumfang.
**Abhängigkeiten:** Modul 1 (UI), Modul 2.8 (Queue) optional, eng mit Modul 8.
---
## 10. Modul 10 — Navigation & Page-Modell refactoren (Block C2)
**Briefing:**
- „Im System, gerade in Fronten `sterntours.de/src/AppBundle/Listener/KernelControllerListener.php`, ist die Struktur der Navigation und der des Seitenbaums sehr eigenwillig und muss verbessert und optimiert werden. `mein.sterntours.de/app/Models/Page.php` — alles liegt in dieser Page-Tabelle und ist sehr schlecht wartbar und undurchschaubar. Hier muss ein deutlich cleaneres Konzept her."
- `https://mein.sterntours.test/navigation-api` ist als Ansatz vorhanden, aber noch nicht gut.
### 10.1 Ist-Zustand (aus navigation.md / frontend-navigation/)
- Ein Symfony `KernelControllerListener` übernimmt Routing-Entscheidungen basierend auf `Page.realUrlPath` / Slug-Traversierung / hartkodierten Redirects / Template-Namen.
- Alles liegt in **einer** Tabelle `page`, die sowohl Inhalte, Reiseprogramme, Fewo-Zuordnungen, Länder, News, Reiseführer referenziert.
- Nested-Set-Logik (`lft`/`rgt`/`lvl`) ist unzuverlässig; derzeit wird sie im Listener bewusst umgangen.
- Es existiert eine Backend-UI unter `/navigation-api`, aber sie ist read-only und deckt das Frontend-Mental-Modell nicht sauber ab.
### 10.2 Zielbild (Vorschlag)
Trennung in klar benannte Entitäten:
```
pages — generische Inhaltsseiten (CMS-Content)
menu_items — reine Navigationsknoten (Titel, Link, Sortierung, parent_id)
routes/redirects — URL-Routing (Slug → Ziel-Entität) inkl. 301-Redirects
content_types — Polymorphe Zuordnung: menu_item → [Page | TravelProgram | FewoLodging | Country | News | TravelGuide | ...]
```
- `menu_items` ist der **einzige** Baum (adjacency list, evtl. mit `nested-set`-Paket gepflegt).
- Jede `menu_item` hat einen `linkable_type` + `linkable_id` (Polymorphie) — so wird klar, was hinter einem Link steht, und das Model `Page` ist nicht mehr das Mega-Modell.
- `routes`-Tabelle auflöst Slug → `linkable`, ersetzt `realUrlPath`-Suche.
### 10.3 Teilschritte
1. **Konzept-Doku** mit allen Alt-Datenfeldern in `page` und Zielzuordnung (welche bleiben auf `pages`, welche wandern auf `menu_items`, welche werden zu polymorphen Zielen).
2. **Neue Tabellen aufbauen (migrativ):** `menu_items`, `routes`, optionale `redirects`.
3. **Datenmigration:** `page``menu_items` + `pages` + `routes` (alles ableitbar; kein Datenverlust).
4. **Laravel-Route-Driver bauen** (für Backend- und API-Lookups).
5. **Symfony-Frontend anpassen:**
- `KernelControllerListener` nutzt eine neue Lookup-API (`GET /api/routes/resolve?path=/foo/bar`) aus `mein.sterntours.de`.
- Legacy-Queries auf `page`-Tabelle entfallen dort.
6. **Neue Backend-UI** unter `/navigation` (nicht mehr `/navigation-api` read-only):
- Drag&Drop-Tree.
- Inline-Edit für Titel / Slug / Sichtbarkeit.
- „Was ist hier verlinkt?" — direkter Sprung zum Content-Editor der Zielentität.
- Live-Preview-Link ins Frontend.
7. **Redirect-Verwaltung** als eigene UI (statt hartkodierter Liste im Listener).
8. **Cache-Strategie** (Events: `RouteChanged` → Cache-Invalidierung; kein manuelles „Cache leeren"-Button mehr als primärer Weg).
**Aufwand:** ~68 Wochen.
**Abhängigkeiten:** Modul 1 (UI-Pattern). Muss vor Modul 11 (CMS-Vereinheitlichung) stehen, damit CMS-Inhalte auf das neue Model angedockt werden.
---
## 11. Modul 11 — CMS-Vereinheitlichung (Block C3)
**Briefing:** Es existieren CMS-Ansätze unter `/cms/feedback`, `/cms/fewo/content`, `/cms/travel_guide/content`, `/iq/content/tree/index`, `/cms/news`, `/cms/answer_question`, `/cms/sidebar`, `/cms/content/infos`, `/cms/content/all`. „Die Einzelmodule können so bleiben. Es muss nur klarer in der Benutzerführung werden und auch aus der Hauptnavigation erreichbar sein und deutlich sein, wo was hingehört."
### 11.1 Teilschritte
1. **Inventar:** Jede dieser Routen inhaltlich beschreiben (Was wird gepflegt? Für welchen Frontend-Bereich?).
2. **Dachstruktur:** Eine neue Haupt-Menü-Gruppe „Inhalte / CMS" im Seitenmenü mit Unterpunkten, die klar benannt sind (nicht mehr `/cms/content/infos` vs. `/cms/content/all`).
3. **Landing-Page** `/content` als Dashboard mit Kacheln pro Bereich + Kurzbeschreibung („Was pflegst du hier?").
4. **Konsistente Listen-/Edit-Views** (Baseline).
5. **Verknüpfung mit Navigation (Modul 10):** Aus der Navigations-UI direkt in den passenden CMS-Bereich springen („Inhalt dieser Seite bearbeiten").
6. **Berechtigungen vereinheitlichen:** Jedes CMS-Submodul hat eine eigene Permission (`cms.feedback`, `cms.news`, …); aktuell ist das teilweise inkonsistent.
7. **Nicht ändern:** Die Einzellogik der Module (Datenmodelle bleiben). Nur UI / Dachnavigation / Permissions / Benennung.
**Aufwand:** ~3 Wochen.
**Abhängigkeiten:** Modul 1, Modul 10 (für Verknüpfungen), Modul 3 (nur inhaltlich, keine Abhängigkeit in der Umsetzung).
---
## 12. Modul 12 — Migration Reiseverwaltung v2.stern-tours.de (Block C4, größtes Einzelmodul)
**Briefing:** „Jetzt kommt noch ein sehr großer Punkt: `v2.stern-tours.de/application/controllers/acp`. Hier werden Reisen angelegt und auch die Reisezeiträume verwaltet. Dieses ist mittlerweile absolut veraltet und auch fehleranfällig. Ein großes neues Modul wird es sein, dieses auf `mein.sterntours.de` zu migrieren — d. h. der komplette Funktionsumfang muss in das Backend einentwickelt und benutzerfreundlich gemacht werden, sowie müssen die gesamten Skripte etc. deutlich optimiert werden."
### 12.1 Umfang (grob aus Controller-Ordner)
```
aegypten_api flight_period travel_country travel_insurance
catalog keyword travel_departure_point travel_option
feedback newsletter travel_destination travel_organizer
travel_arrival_point page travel_discount travel_period
travel_category travel_program travel_period_date travel_period_price
travel_class travel_program_image travel_setting travel_general_notes
wiki welcome
```
Das sind ~25 Entitäten / Verwaltungsbereiche. Viele hängen miteinander zusammen (Program ↔ Period ↔ PeriodDate ↔ PeriodPrice).
### 12.2 Grundstrategie (Entscheidung Abschnitt 17.6)
- **Stück für Stück**, nicht Big Bang.
- Pro Teil-Entität wird die **Datenstruktur überarbeitet und bereinigt** — d. h. sinnvolle Umbauten werden _mitgemacht_, nicht auf später verschoben (1:1-Portierung wäre zu teuer in Nachpflege).
- Während der Migration läuft das Altsystem auf dem Live-Server **weiter** (Parallelbetrieb, Entscheidung Abschnitt 17.8). Jedes fertig migrierte Teil-Modul übernimmt _sofort_ die Hoheit (Schreibrechte), das Altsystem geht für diesen Bereich auf Read-only bzw. Redirect ins neue Backend.
- **Teil-Migration auf Live** ist ausdrücklich vorgesehen: einzelne Entitäten dürfen produktiv auf das neue System umziehen, während andere noch in v2 bleiben. Voraussetzung: bidirektionale Datensicht (View oder Sync) für alle übergreifenden Reports.
### 12.3 Teilschritte (hohes Level — jedes Teil-Modul wird wie ein eigenes Mini-Projekt geführt)
1. **Inventar & Datenmodell-Review:** Pro Entität — Tabellen, Felder, Beziehungen, heute existierende Fehler / Sonderfälle.
2. **Entscheidung pro Tabelle:** Welche Felder bleiben, welche werden umbenannt, welche Tabellen werden zusammengeführt, welche Indizes fehlen. Dokumentiert als kurzes Delta-Dokument pro Entität, bevor die Migration geschrieben wird.
3. **Abhängigkeitsgraph** der Entitäten bauen → Migrationsreihenfolge ableiten.
4. **Schrittweise Migration — empfohlene Reihenfolge:**
- **Stammdaten zuerst** (wenige Abhängigkeiten, einfache CRUD): `travel_country`, `travel_class`, `travel_category`, `travel_organizer`, `travel_departure_point` (+ holiday), `travel_arrival_point`, `travel_destination`, `travel_general_notes`, `travel_insurance` + `travel_insurance_price`, `flight_period`, `travel_option`, `travel_discount`, `travel_setting`, `keyword`.
- **Reiseprogramme:** `travel_program`, `travel_program_image`, `catalog`, `page`-Zuordnungen (hängt mit Modul 10 zusammen).
- **Reisezeiträume:** `travel_period`, `travel_period_date`, `travel_period_price`, `travel_period_price_type` (eng verwoben, gemeinsam migrieren).
- **Sonderfälle / Rand:** `aegypten_api`, `newsletter`, `feedback`, `wiki`, `welcome` (Dashboard).
5. **Pro Teil-Modul:**
- Datenstruktur als Laravel-Migration (mit Rückwärtskompatibilität).
- Model + Repository + Service.
- UI auf Baseline (Listen + Edit).
- Integrationstests.
- Dual-Write während Übergang, bis das Altsystem lesefrei / abgeschaltet ist.
6. **Performance-/Query-Audit:** Laut Briefing „Skripte deutlich optimiert werden". Hier typische Ursachen angehen — N+1-Queries, fehlende Indizes, fehlende Eager Loads, zu viele JOINs in CI-Reports.
7. **Preis-Engine:** Der kritischste Teil. Alle Preisberechnungen (Programm + Periode + Rabatte + Versicherung + Optionen) müssen in **einem** Service gekapselt werden. Dieser wird dann von:
- Modul 6 (Angebote) — für `offer_items`,
- Modul 4 (Direktbuchung) — für Buchungspreise,
- Der Web-Buchungs-API,
- Dem aktuell laufenden Altsystem (während Parallelbetrieb)
genutzt. **Vermeidet eine Neuauflage des heutigen Preischaos.**
8. **Frontend-Adapter:** `sterntours.de` muss zur Anzeige (Preise, Abfahrten) dieselben Daten bekommen. Entweder API-Call ins neue Laravel oder View-Layer in DB, bis das Frontend gegen die neue Quelle läuft.
9. **Abschaltung v2:** Erst wenn alle Datenflüsse migriert sind, Legacy-Read-Zugriffe eliminiert und das Frontend gegen die neue Quelle läuft.
**Aufwand:** ~1420 Wochen. Durch die Entscheidung „Stück für Stück mit Überarbeitung/Bereinigung" (Abschnitt 17.6) eher am oberen Ende, dafür am Ende kein technischer Schuldenberg.
**Abhängigkeiten:** Modul 1 (UI), Modul 2 (Tooling/Tests), Modul 10 (Navigation, weil `travel_program``menu_items` verlinkt werden).
---
## 13. Modul 13 — Betrieb, Tests, Monitoring (Block D1)
Begleitendes Querschnittsmodul.
### 13.1 Teilschritte
1. **Test-Strategie:**
- Pro neues Modul: Unit-Tests für Services, Feature-Tests für kritische Flows (Direktbuchung, Angebot → Buchung, Fewo-Belegung).
- Contract-Tests für alle öffentlichen APIs (Booking-Import, Navigation-API).
2. **CI-Pipeline** (Gitlab/GitHub Actions): Pint, Larastan, PHPUnit, optional Dusk für die wichtigsten Flows.
3. **Queue-Monitoring:** Horizon (wenn Redis) oder Log-Channel + Alert bei Failed Jobs.
4. **Error-Tracking:** Sentry / Flare / Bugsnag (eines davon auswählen).
5. **Deployment-Pipeline:** Entweder Deployer/Envoyer-basiert oder Docker-Build; Maintenance-Mode-Script, Migrations- und Cache-Steps dokumentiert.
6. **Rollback-Pläne** pro Migration (ist in Modul 3 bereits vorbildlich gemacht — Standard für alle neuen Module).
**Aufwand:** laufend, ca. 4 Wochen verteilt über das Gesamtprojekt.
---
## 14. Modul 14 — Framework- & Sprach-Upgrade (Block D2)
Nicht sofort, aber einplanen:
- **Laravel 10 → 11 Upgrade-Pfad:** separater Branch, nach Abschluss von Block A + Teilen B. Nicht während aktiver Alt-Migration (Modul 12).
- **PHP 8.3** in Docker/CI festschreiben, aus `composer.json` die Obergrenze anheben.
- **`jenssegers/date`** entfernen, überall Carbon.
- Composer-Pakete konsolidieren (siehe `projekt-empfehlungen-2026-04.md` §2.3).
**Aufwand:** ~2 Wochen.
---
## 15. Modul 15 — Kundenportal / Kunden-Login (Zukunftsmodul, Block E)
**Briefing (Ergänzung aus den Antworten):** „Ein weiteres Modul für die Zukunft bitte mit aufnehmen: einen Kunden-Login, so dass die Kunden Zugang haben zu ihren eigenen Buchungen und Übersichten."
### 15.1 Ziel
Ein öffentlich erreichbarer, eingeschränkter Kunden-Bereich, in dem sich ein Kontakt mit seiner E-Mail-Adresse anmelden und seine eigenen Daten / Anfragen / Angebote / Buchungen einsehen kann. Klar abgegrenzt vom Mitarbeiter-Backend.
### 15.2 Datenmodell
- **Kein separates `users`-Model** für Kunden — Auth wird an `contacts` aufgehängt, Erweiterung:
```sql
ALTER TABLE contacts
ADD COLUMN password VARCHAR NULL,
ADD COLUMN remember_token VARCHAR(100) NULL,
ADD COLUMN email_verified_at DATETIME NULL,
ADD COLUMN portal_enabled TINYINT(1) DEFAULT 0,
ADD COLUMN last_login_at DATETIME NULL;
CREATE TABLE customer_sessions (
id, contact_id, ip, user_agent, last_active_at, created_at, expires_at
);
```
- Laravel-Auth-Guard `customer` (eigener Guard neben dem Mitarbeiter-Guard `web`).
### 15.3 Teilschritte
1. **Registrierung / Erstanmeldung:**
- Kunde erhält Einladung per Mail (Reiseunterlagen / Buchungsbestätigung enthalten einen „Zugang einrichten"-Link).
- Alternativ: „Passwort setzen" via E-Mail-Verifizierung ausgehend von einer bestehenden Mail-Adresse im `contacts`-Datensatz.
- Kein Self-Signup ohne bestehenden Kontakt — Datenbereinigung würde sonst erneut leiden.
2. **Login / Passwort-Reset / 2FA optional.**
3. **Portal-Views (auf `sterntours.de` oder Subdomain `mein.sterntours.de/portal` — Architekturentscheidung noch offen):**
- Dashboard („Ihre nächste Reise in X Tagen").
- Meine Anfragen.
- **Meine Angebote** (Integration mit Modul 6: eingeloggter Kunde sieht dort dieselben Dokumente wie über den Token-Link, zusätzlich historische Angebote).
- Meine Buchungen (Dokumente, Zahlungsstand, Reiseunterlagen).
- Meine Daten (Adresse, Einwilligungen, Newsletter-Status).
4. **Datenschutz / DSGVO:**
- Nur eigene Daten sichtbar (strenge Scope-Checks in Controllern + Policies).
- Download der eigenen Daten als Export (Art. 15 DSGVO).
- Lösch-Anfrage-Flow (verknüpft mit dem `deleted_at`-Flow aus Modul 3).
5. **Integration mit Token-Links (Modul 6):**
- Ein Token-Link funktioniert weiterhin ohne Login.
- Ist der Kunde eingeloggt und klickt den Link, wird das Angebot in das Portal-Layout eingebettet (nicht doppelte Darstellung).
6. **Permissions & Sicherheit:**
- Separate Passport-Scopes für Portal-API.
- Rate-Limiting auf Login-Endpunkte.
- Audit-Log (wer hat wann was eingesehen) — wichtig bei Angebotsannahmen im Portal.
7. **UI:** Kein Teil der Mitarbeiter-UI-Baseline (andere Zielgruppe). Eigenes, leichtes Layout (Bootstrap 5), das zum `sterntours.de`-Look passt.
### 15.4 Abhängigkeiten
- Modul 3 Phase 2 (`contacts`-Tabelle) ist **zwingende** Voraussetzung — sonst liegen die Kundenlogins auf der alten `customer`-Tabelle.
- Modul 6 (Angebote) sollte mindestens Grundstand haben, damit Portal-Angebotsansicht sinnvoll Inhalt bekommt.
- Profitiert stark von Modul 2.8 (Queue) für Einladungs-/Benachrichtigungsmails.
### 15.5 Einordnung in die Roadmap
- **Nicht Teil der ersten Welle.** Frühester sinnvoller Start: nach Abschluss von Modul 6 + Modul 3 Phase 4.
- Entspricht einem eigenen Block „E — Kundenseitige Self-Service-Funktionen" und kann zeitlich mit Modul 14 (Framework-Upgrade) parallel laufen, wenn Kapazität da ist.
**Aufwand:** ~68 Wochen für MVP (Login + Angebote + Buchungen + Daten), plus 23 Wochen für DSGVO-/Self-Service-Funktionen.
---
## 16. Zusammenfassung — empfohlene Reihenfolge und Parallelisierung
```
Monat 12 A1 UI-Baseline + A2 Technik-Hausaufgaben (+ Start A3 Phase 2)
+ Frontend-Migration Vite/dart-sass/Bootstrap 5 (Modul 1.4) parallel
Monat 23 A3 Customer/Lead/Booking Phasen 2+3 abschließen
Monat 3 A3 Phase 4 + B4 Fewo-Doppelbuchung (Turnustag-Logik) parallel
Monat 4 B1 Direktbuchung (mit Mail-Toggle) + B2 Organisation-Tab
Monat 45 B3 E-Mail-Auto-Save (nach A3 P4)
Monat 57 B5 Angebots-Modul (inkl. Versionierung + Freigabe-Link)
Monat 69 C1 Admin-Migration sterntours.de (zügig, kein harter Cut)
Monat 79 C2 Navigation + C3 CMS-Vereinheitlichung
Monat 816 C4 v2.stern-tours.de Reisenverwaltung — Stück für Stück mit
Teil-Migration auf Live und Parallelbetrieb
Monat ≥10 E1 Kundenportal / Kunden-Login (frühestens nach B5 + A3 P4)
Laufend D1 Betrieb/Tests, D2 Framework-Upgrade gegen Ende
```
Einige Module können parallelisiert werden, wenn Kapazität vorhanden ist. Kritischer Pfad: **A1 → A3 → B5 → C4**. Modul 15 (Portal) liegt off-path und kann flexibel eingeordnet werden.
---
## 17. Entscheidungen (geklärt)
Die acht zuvor offenen Fragen wurden vom Auftraggeber beantwortet. Die Entscheidungen sind in den jeweiligen Modulen bereits eingearbeitet; hier die konsolidierte Referenz:
| # | Thema | Entscheidung | Fundstelle im Plan |
|---|-------|-------------|--------------------|
| 17.1 | **Angebots-Versionen** | Jede Änderung nach dem Versand erzeugt eine neue Version (V2, V3 …). Speicheroptimierung durch spätere Archivierung/Löschung alter Versionen. | Modul 6.2 (`offer_versions`), 6.3.6 (Versionierung), 6.4 (Archivstrategie) |
| 17.2 | **Angebots-Annahme** | Beides: Admin-seitige Statusänderung **und** kundenseitiger Freigabe-Link (Token-URL) mit Bestätigungsseite. | Modul 6.3.8 (`offer_access_tokens`, `/angebot/{token}`) |
| 17.2b | **Kundenportal** | Neues Zukunftsmodul (Modul 15): Kunden-Login mit Zugang zu eigenen Buchungen/Angeboten/Daten. | Modul 15 |
| 17.3 | **Angebots-Dokumente** | Dual-Modell analog zur heutigen Buchungs-Logik in `app/Http/Controllers/BookingController.php`: zentral hinterlegte Dokumente + freie Uploads pro Angebot. | Modul 6.3.4 |
| 17.4 | **Direktbuchung im Backend** | Gesonderter Einstieg mit Auswahl im letzten Schritt: „Anlegen & Bestätigung senden" **oder** „Nur anlegen (keine Mail)". Nachversand bleibt jederzeit manuell möglich. | Modul 4.2.6 |
| 17.5 | **Fewo-Überlappungen** | Kein harter DB-Constraint. Überlappungen werden im `FewoReservationService` nach klaren Turnustag-Regeln geprüft (An-/Abreise am selben Tag legitim). | Modul 8.2.5 |
| 17.6 | **v2-Migrationsstrategie** | Stück für Stück mit Überarbeitung und Bereinigung (keine 1:1-Portierung). | Modul 12.2 |
| 17.7 | **Frontend-Tooling** | Vite + dart-sass + schrittweise Bootstrap 5. Freigegeben. | Modul 1.4 |
| 17.8 | **Abschaltplan Altsysteme** | Parallelbetrieb, bis Migration abgeschlossen. `sterntours.de/AdminController` zügig migrieren (Modul 9). `v2.stern-tours.de` mit Teil-Migrationen auf dem Live-Server, damit der Betrieb ununterbrochen weiterläuft. | Modul 12.2 |
### Direkt daraus folgende neue Bausteine
- **Neue Tabellen:** `offer_versions`, `offer_access_tokens` (Modul 6).
- **Neue Routen (öffentlich):** `/angebot/{token}`, `/angebot/{token}/accept`, `/angebot/{token}/decline` auf `sterntours.de` bzw. Subdomain.
- **Neues Modul 15:** Kundenportal / Kunden-Login (eigener Block E, Zukunftsmodul).
- **Neue Service-Klasse:** `FewoReservationService` mit Turnustag-Logik (Modul 8).
- **Neue UI-Komponente:** Belegungskalender mit Wechseltag-Markierung (Modul 8.2.7).
- **Direktbuchungs-Flag:** `silent_created` auf `bookings` (Modul 4).
---
## 18. Nächste konkrete Schritte (Start der Entwicklung)
Nach dieser Abstimmung kann die Entwicklung starten. Sinnvolle erste Arbeitspakete, jeweils als eigene PRs / Tickets:
1. **A2 Quick Wins** (sofort, blockiert nichts):
- Composer-Wildcards pinnen.
- `navigation/cache/clear` absichern.
- `booking/import` auf POST-only.
- Pint + Larastan + CI aufsetzen.
2. **A3 Phase 1 live deployen** (Customer-Deduplizierung — steht auf Test, braucht Backup + CSV-Review).
3. **A1 UI-Baseline dokumentieren** (Inventar + Baseline-Spec, ohne bereits umzubauen).
4. **Modul 1.4 Frontend-Migration** als eigener Feature-Branch starten (Vite/dart-sass/Bootstrap 5).
5. **A3 Phase 2 vorbereiten** (Code-Anpassungen `Customer::$table`, `Lead::$table`, Repositories auf `inquiry_id`).
Schritt 13 können parallel angegangen werden; Schritt 4 läuft im Hintergrund, bis der Pilot-View auf der neuen Pipeline läuft. Sobald A3 Phase 2 produktiv ist, ist der Weg frei für Modul 4 (Direktbuchung) und anschließend Modul 6 (Angebote).