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

50 KiB
Raw Blame History

Entwicklungsplan mein.sterntours.de (2026)

Erstellt: April 2026 Basis: briefings.md, customer-bookings/, audit-april-2025.md, projekt-empfehlungen-2026-04.md, 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-sassdart-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 und 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.

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 (customercontacts, leadinquiries, booking.lead_idinquiry_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):

-- 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: pagemenu_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_programmenu_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:
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).