mein-sterntours/dev/customer-bookings/phase-2-live-deploy.md
2026-04-22 16:01:27 +02:00

305 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

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.

# Phase-2-Live-Deploy — Anleitung
**Ausgangssituation (nach Phase-1-Live-Deploy am 2026-04-17):**
- Live: Phase 1 ✅ (Code + DB-Migrationen 25, 26)
- Test: Phase 1 ✅ + Phase 2 ✅ (Code + DB-Migrationen 2529, verifiziert)
- Workspace (lokal): Phase 1 + Phase 2 + Offers-Code ready
Dieses Handbuch beschreibt, wie Phase 2 vom Test-Stand auf Live übertragen wird. Phase 2 umfasst zwei atomar zusammengehörige Teile plus eine kleine Ergänzung aus Phase 1:
1. **DB-Schema-Änderungen** (3 Migrationen, breaking):
- `RENAME TABLE customer → contacts`
- `RENAME TABLE lead → inquiries`
- Spalte `booking.lead_id → booking.inquiry_id` (inkl. FK-Index-Neuaufbau)
2. **Application-Code-Änderungen** (23 modifizierte Dateien):
- Models `Customer`, `Contact`, `Lead`, `Booking` mit neuen `$table`-Werten bzw. Spaltennamen
- Repositories, Controllers, Services, Commands, Views — alle Referenzen auf `booking.lead_id``inquiry_id` umgestellt; Raw-SQL auf `contacts`/`inquiries` umgestellt
- Smoke-Test-Fixes (2026-04-17): `Lead::bookings()` mit explizitem FK `inquiry_id`; `orderColumn`-SQL in `ContactController`/`ReportController`/`ReportBookingController`/`ReportLeadsController` auf `contacts.*` gezogen.
3. **Phase-1-Hotfixes — nullable Parameter in den Mail-Dir-Services** (3 modifizierte Dateien):
- `app/Services/Booking.php`, `app/Services/Lead.php`, `app/Services/MailDirService.php`
- `int $mailDirId``?int $mailDirId` bei `getCustomerMailName()` / `getCustomerMailEmails()` / `resolveModel()` (behebt Mail-Dialog-Crash wenn `customer_mails.mail_dir_id` / `lead_mails.mail_dir_id` NULL ist).
- `string $subdir``?string $subdir` bei `setOutputDirs()` / `setOutputDir()` (behebt Mail-Dialog-Crash wenn `$mail_sdir_id` NULL ist, z.B. bei Entwürfen oder Top-Level-Ordnern).
- Fachlich eigentlich Phase-1-Bugs (Laravel-10/PHP-8-strict-typing vs. `null`-Werte aus nullable DB-Spalten), die aber erst durch die Test-Durchläufe am 2026-04-17 sichtbar wurden. Wir liefern die Fixes zusammen mit Phase 2 aus, weil ein zweites separates Wartungsfenster für drei Service-Dateien nicht sinnvoll wäre und die Änderungen keinerlei DB-Abhängigkeit haben (also auch außerhalb der DB-Migration risikoarm sind).
> **Wichtig:** DB und Phase-2-Code müssen atomar gemeinsam live gehen. Zwischen den beiden gibt es kein kompatibles Fenster, deswegen ist ein **Maintenance-Window von ~1020 min Pflicht**. Die 3 Phase-1-Hotfix-Services werden im selben rsync-Schritt mit hochgeladen.
---
## Vorbereitung (am Tag vor dem Deploy)
1. **Timing**: Wartungsfenster einplanen, idealerweise außerhalb der Geschäftszeiten. ~20 Minuten reichen erfahrungsgemäß.
2. **Deploy-Freigabe**: Phase 1 auf Live läuft stabil (mindestens 2448 h), keine Regressions-Meldungen.
3. **Team-Kommunikation**: Mitarbeiter:innen informieren — während des Wartungsfensters keine Mails, keine Buchungen anlegen, keine PDFs drucken.
### Vorab-Kontrolle auf Live (lesend, keine Änderungen)
```bash
# Live-Server: aktueller Stand?
php artisan --version
# → sollte 10.50+ sein (Phase 1 hat Laravel 10 gebracht)
php artisan migrate:status | grep phase1
# → sollte beide phase1-Migrationen mit "Ran" zeigen
mysql -e "SHOW TABLES LIKE 'customer';"
mysql -e "SHOW TABLES LIKE 'lead';"
# → beide müssen noch existieren (Phase 2 NICHT ausgeführt)
mysql -e "SHOW COLUMNS FROM booking LIKE 'lead_id';"
# → lead_id muss existieren
```
Wenn einer dieser Checks nicht das erwartete Ergebnis liefert: **STOP**, klären bevor weitergemacht wird.
### Smoke-Tests auf Test (am Deploy-Tag, vor dem Live-Deploy)
Diese Liste einmal komplett auf `https://mein.sterntours.test` durchgehen. Wenn einer fehlschlägt → Fehler fixen, Live-Deploy verschieben.
- [ ] Login funktioniert, Dashboard lädt
- [ ] **Buchungen**: `/booking` lädt, Filter- und Sortier-Funktionen arbeiten, Anfrage-Nr-Spalte zeigt korrekte Werte
- [ ] **Buchungsdetail** öffnen, alle Tabs laden (Buchung, Mails, Notizen, Dokumente)
- [ ] **PDF-Generierung** aus einer Buchung: Buchungsauftrag, Reisebestätigung, Voucher (Kunde + Agentur), Storno
- [ ] **Anfragen**: `/lead` lädt, einzelne Anfrage öffnen
- [ ] **Anfrage → Buchung erzeugen** (legt `booking.inquiry_id` korrekt an)
- [ ] **Kontakte**: `/contacts` lädt, einzelner Kontakt öffnet, `leads_count` + `bookings_count` zeigen korrekte Werte (testet die Beziehungen über die neuen Tabellennamen)
- [ ] **Kontakt-Duplikate**: `/contact/duplicates` lädt, Zähler stimmen
- [ ] **Mail-Versand** aus einer Buchung (Eintrag in `customer_mails.lead_id` muss identisch zur `booking.inquiry_id` sein)
- [ ] **Mail-Dialog öffnen** bei Buchung und Anfrage (Modal "Neue Mail schreiben" / "Mail anzeigen") — testet den nullable-`mail_dir_id`-Fix in `Services\Booking::getCustomerMailName()` / `Services\Lead::getCustomerMailName()`.
- [ ] **Admin-Reports**: `/admin/report`, `/admin/report-bookings`, `/admin/report-provider`, `/admin/report-leads` laden UND jeweils einmal nach Kundenname sortieren (testet die `orderColumn`-Fixes für `contacts.firstname/name`) + CSV-Export
- [ ] **BookingImport über API** (falls testbar — feuert gegen `/api/booking/import`) legt Datensatz mit korrekter `inquiry_id` an
- [ ] Newsletter-Sync-Command als Dry-Run: `php artisan newsletter:sync-kulturreisen --dry-run` (falls implementiert)
---
## Deploy-Ablauf Live
### Schritt 1 — Upload-Liste generieren (lokal)
Der Phase-2-Delta gegen Phase 1: **3 neue Migrationen + 24 modifizierte Dateien (Phase 2) + 3 Phase-1-Hotfix-Services = 30 Dateien.** Exakte Liste:
```bash
cd /workspace/mein.sterntours.de
cat > /tmp/phase2-files-to-upload.txt <<'EOF'
database/migrations/2025_04_15_200001_phase2_rename_customer_to_contacts.php
database/migrations/2025_04_15_200002_phase2_rename_lead_to_inquiries.php
database/migrations/2025_04_15_200003_phase2_rename_booking_lead_id_to_inquiry_id.php
app/Models/Booking.php
app/Models/Customer.php
app/Models/Contact.php
app/Models/Lead.php
app/Repositories/BookingPDFRepository.php
app/Repositories/LeadRepository.php
app/Repositories/CustomerMailRepository.php
app/Http/Controllers/RequestController.php
app/Http/Controllers/API/BookingController.php
app/Http/Controllers/Admin/ReportController.php
app/Http/Controllers/Admin/ReportBookingController.php
app/Http/Controllers/Admin/ReportProviderController.php
app/Http/Controllers/Admin/ReportLeadsController.php
app/Http/Controllers/LeadController.php
app/Http/Controllers/CustomerController.php
app/Http/Controllers/ContactController.php
app/Services/BookingImport.php
app/Services/Booking.php
app/Services/Lead.php
app/Services/MailDirService.php
app/Console/Commands/SyncNewsletterKulturreisen.php
app/Console/Commands/ContactsFindDuplicates.php
app/Console/Commands/ContactsMergeDuplicates.php
resources/views/customer/mail/modal-show-mail-inner.blade.php
resources/views/contact/index.blade.php
resources/views/pdf/components/booking_head.blade.php
resources/views/pdf/components/booking_header.blade.php
EOF
wc -l /tmp/phase2-files-to-upload.txt
# → sollte 30 zeigen (3 Migrationen + 24 Phase-2-Code + 3 Phase-1-Hotfix-Services)
```
**Erklärung zu den zusätzlichen Dateien gegenüber einer rein strukturellen Phase-2-Liste:**
- `ReportBookingController.php` — Smoke-Test-Fix am 2026-04-17: `orderColumn('customer.firstname/name', …)``contacts.*`. War schon vor dem Fix nicht perfekt, fiele aber beim ersten Sortier-Klick nach Phase-2-Rename hart auf.
- `resources/views/contact/index.blade.php` — Smoke-Test-Fix am 2026-04-17: DataTables-Column-Definition `name: 'customer.id'``name: 'contacts.id'` (+ passender `orderColumn('contacts.id', …)` / `filterColumn('contacts.id', …)` im `ContactController`). Yajra verwendet `name:` als raw SQL-Spalte, wenn kein `orderColumn` matched — deswegen muss der Identifier zwischen Blade und Controller konsistent sein und dem neuen Tabellennamen entsprechen.
- `app/Services/Booking.php`, `Lead.php`, `MailDirService.php` — Phase-1-Hotfix: nullable-Parameter (`?int $mailDirId`, `?string $subdir`). Behebt Laravel-10/PHP-8-strict-typing-Fehler bei Mail-Dialogen mit NULL-Werten aus `customer_mails` / `lead_mails`.
> **Ausdrücklich NICHT mit hochladen:** Phase-3-/Phase-4-Migrationen (5+6 Stück), Offers-Migrationen (7 Stück), Offer-Models (6 Stück), `config/filesystems.php` (offer-Disk). Diese kommen erst mit späteren Modulen und würden auf Live ungewollt Tabellen anlegen oder Code-Erwartungen brechen.
### Schritt 1b — Upload-Liste gegen den Workspace verifizieren (Sanity-Check)
Damit sich hier kein Tippfehler einschleicht, einmal prüfen, dass alle 29 Einträge tatsächlich im Workspace existieren:
```bash
cd /workspace/mein.sterntours.de
MISSING=0
while read f; do
if [[ ! -f "$f" ]]; then
echo "FEHLT: $f"
MISSING=$((MISSING+1))
fi
done < /tmp/phase2-files-to-upload.txt
echo "---"
echo "Fehlend: $MISSING (sollte 0 sein)"
echo "Gesamt: $(wc -l < /tmp/phase2-files-to-upload.txt)"
```
### Schritt 2 — DB-Backup auf Live
```bash
# Auf dem Live-Server, vor dem Deploy
TIMESTAMP=$(date +%Y-%m-%d_%H%M)
mysqldump --single-transaction --routines --triggers \
--databases DEIN_DB_NAME \
| gzip > /backup/live-db-before-phase2-${TIMESTAMP}.sql.gz
# Größe prüfen:
ls -lh /backup/live-db-before-phase2-${TIMESTAMP}.sql.gz
```
Falls `mysqldump` nicht zur Hand: das Hoster-Tool nutzen (phpMyAdmin-Export, Plesk/cPanel-Backup-Feature, etc.).
### Schritt 3 — Maintenance-Mode an
```bash
cd /pfad/zu/mein.sterntours.de
php artisan down --render="errors::503" --secret="phase-2-deploy-$(date +%s)"
# → Secret-URL wird ausgegeben, z.B.:
# https://domain/phase-2-deploy-1746123456
# Damit kannst du während der Wartung selbst noch als Admin auf die Seite.
```
### Schritt 4 — Upload per rsync
```bash
# Vom lokalen Workspace (oder Test-Server) auf Live
rsync -av --files-from=/tmp/phase2-files-to-upload.txt \
/workspace/mein.sterntours.de/ \
user@live-server:/pfad/zu/mein.sterntours.de/
```
Alternative mit scp, falls rsync nicht verfügbar:
```bash
while read f; do
scp "/workspace/mein.sterntours.de/$f" "user@live-server:/pfad/zu/mein.sterntours.de/$f"
done < /tmp/phase2-files-to-upload.txt
```
### Schritt 5 — Code-Caches leeren
Auf dem Live-Server:
```bash
cd /pfad/zu/mein.sterntours.de
php artisan cache:clear
php artisan config:clear
php artisan route:clear
php artisan view:clear
composer dump-autoload --optimize
```
### Schritt 6 — DB-Migrationen gezielt ausführen
> **Wichtig: KEIN `php artisan migrate` ohne `--path`!**
> Wenn man nacktes `migrate` aufruft, werden auch die im Workspace liegenden Phase-3-, Phase-4- und Offer-Migrationen ausgeführt, die noch nicht deployreif sind. Deswegen gezielt per `--path`:
```bash
php artisan migrate --path=database/migrations/2025_04_15_200001_phase2_rename_customer_to_contacts.php --force
php artisan migrate --path=database/migrations/2025_04_15_200002_phase2_rename_lead_to_inquiries.php --force
php artisan migrate --path=database/migrations/2025_04_15_200003_phase2_rename_booking_lead_id_to_inquiry_id.php --force
# Verifikation
php artisan migrate:status | grep phase2
# → alle drei sollten jetzt "Ran" zeigen
```
Erwartete Dauer: wenige Sekunden. `RENAME TABLE` ist in MySQL eine Metadaten-Operation und läuft unabhängig von der Tabellengröße fast instantan. Der Spaltenrename `booking.lead_id → inquiry_id` ist je nach MySQL-Version ebenfalls schnell (MySQL 8+ mit `ALGORITHM=INSTANT`) oder dauert ein paar Sekunden (mit Fallback).
### Schritt 7 — Production-Caches neu aufbauen
```bash
php artisan config:cache
php artisan route:cache
php artisan view:cache
```
### Schritt 8 — Maintenance-Mode aus
```bash
php artisan up
```
### Schritt 9 — Live-Smoke-Tests
Dieselbe Checkliste wie bei Test (oben). Besonders aufpassen:
- PDF-Erzeugung (Voucher einer aktuellen Buchung)
- Mail-Versand aus einer Buchung
- Anfrage-Erstellung + daraus Buchung ableiten
- `/contacts/duplicates` öffnen — zeigt korrekte Counts
Bei Problemen sofort zu **Rollback-Plan** (unten) wechseln.
---
## Rollback-Plan (falls Phase 2 auf Live scheitert)
Der Rollback muss ebenfalls atomar passieren: **Code und DB zusammen zurück.**
### Schritt A — Maintenance-Mode an
```bash
php artisan down --render="errors::503"
```
### Schritt B — DB zurückrollen
Option 1 (sauber, per Migration):
```bash
php artisan migrate:rollback --path=database/migrations/2025_04_15_200003_phase2_rename_booking_lead_id_to_inquiry_id.php --force
php artisan migrate:rollback --path=database/migrations/2025_04_15_200002_phase2_rename_lead_to_inquiries.php --force
php artisan migrate:rollback --path=database/migrations/2025_04_15_200001_phase2_rename_customer_to_contacts.php --force
```
(Die Migrationen haben symmetrische `down()`-Methoden, die die Renames rückgängig machen.)
Option 2 (Notfall, falls `migrate:rollback` fehlschlägt):
```bash
gunzip < /backup/live-db-before-phase2-${TIMESTAMP}.sql.gz | mysql DEIN_DB_NAME
```
### Schritt C — Code zurück
Entweder die pre-Phase-2-Versionen der 22 Dateien zurückspielen (z.B. aus dem DB-Backup-Zeitpunkt-Dateisystem-Backup), oder mit git Revert-Commit:
```bash
# Wenn Live-Server ein Git-Checkout ist:
git checkout <phase-1-commit-id> -- app/ resources/ config/
```
### Schritt D — Caches + Up
```bash
php artisan cache:clear && php artisan config:clear && php artisan view:clear && php artisan route:clear
composer dump-autoload
php artisan config:cache && php artisan route:cache && php artisan view:cache
php artisan up
```
---
## Dokumentation nach erfolgreichem Live-Deploy
1. `dev/customer-bookings/umsetzung.md` updaten: Phase 2 auf Live per 2026-MM-DD.
2. Dieses Handbuch als "durchgeführt" markieren, ggf. Learnings ergänzen.
3. Git-Commit auf Live-Server (falls Git-basiert): mit Tag `deploy/phase-2-live-2026-MM-DD`.
4. Nächster Schritt: Entscheidung, wann Phase 3 (Participants-Unification), Phase 4 (Communications-Unification) oder das Offers-Modul an die Reihe kommen.
---
## Hintergrund: warum das Maintenance-Window nötig ist
Zwischen DB-Rename und Code-Deploy ist der Zustand inkompatibel:
| Zustand | DB hat | Code erwartet | Resultat |
|---|---|---|---|
| Vor Deploy | `customer`, `lead`, `booking.lead_id` | `customer`, `lead`, `booking.lead_id` | OK |
| DB-migriert, Code alt | `contacts`, `inquiries`, `booking.inquiry_id` | `customer`, `lead`, `booking.lead_id` | **Fehler: Table doesn't exist** |
| DB alt, Code neu | `customer`, `lead`, `booking.lead_id` | `contacts`, `inquiries`, `booking.inquiry_id` | **Fehler: Table doesn't exist** |
| Nach Deploy | `contacts`, `inquiries`, `booking.inquiry_id` | `contacts`, `inquiries`, `booking.inquiry_id` | OK |
Deswegen: Maintenance-Mode an → Code + DB gleichzeitig → Maintenance-Mode aus. Alles innerhalb eines Fensters, in dem keine Requests an die App laufen.