diff --git a/docs/superpowers/specs/2026-10-01-admin-panel-ins-ticketsystem-design.md b/docs/superpowers/specs/2026-10-01-admin-panel-ins-ticketsystem-design.md new file mode 100644 index 0000000..9df57d7 --- /dev/null +++ b/docs/superpowers/specs/2026-10-01-admin-panel-ins-ticketsystem-design.md @@ -0,0 +1,309 @@ +# Admin-Panel ins Ticketsystem – Design + +Stand: 2026-10-01 · Status: vom Nutzer im Chat freigegeben (Abschnitte 1–5), Spec zur Durchsicht +Betrifft zwei Repos: **Portal** `knso/Webklar-Kundenbereich` (project.webklar.com, Branch `main`) und +**WOMS** `JUSN/tickte-system` (ticket.webklar.com, Branch `test`). + +## Ziel + +project.webklar.com ist nur noch die Oberfläche für Kunden. Das Admin-Panel (`public/admin.html`) wird +abgeschafft. Mitarbeiter arbeiten ausschließlich im Ticketsystem (WOMS) und kommen nur noch für zwei Dinge +auf project.webklar.com: eine **Preview** ansehen oder das **Portal eines Kunden so öffnen, wie er es sieht** +(z. B. bei Fernwartung am Telefon), jeweils per Klick aus dem Ticketsystem. + +## Entscheidungen (vom Nutzer) + +| # | Entscheidung | +|---|---| +| E1 | Admin-Panel fällt komplett weg. „Übersicht“ und „Projekte“ des Panels ersatzlos (Projekte gibt es im WOMS). | +| E2 | Kundenansicht: **Variante A**. Erlaubt ist, was den Kunden nichts kostet; gesperrt bleiben Verträge, Geld und Passwort. Jede Aktion wird **protokolliert**. | +| E3 | **Alle Mitarbeiter gleich**: Chat, Gutschriften, Abo-Angebote, E-Mail-Speicher und Kundenansicht für jeden Mitarbeiter, nicht nur Admins. | +| E4 | Rechnungsanalyse (`kalkulation.html`) **erstmal raus**; Code bleibt in Git, kommt zurück, wenn die Leads wieder laufen. | +| E5 | **Alles zu einem Kunden an einem Ort**: neue Tabs auf der WOMS-Kundenseite; Sidebar-Unterpunkte unter „Kunden“ sind Sammellisten. | +| E6 | Technischer **Weg 1**: Der Portal-Server bleibt das Backend (Stripe, SMTP, Dateien), WOMS bekommt die Oberflächen. | +| E7 | Ausnahmen bei den Sammellisten: **Chat** ist ein Messenger mit zwei Spalten; **E-Mail-Speicher** hakt man direkt in der Liste ab. | +| E8 | In der Kundenansicht sind auch **Erklärungen im Namen des Kunden** gesperrt (Chat als Kunde, Gutschrift beantragen, Rückfrage beantworten). In der Praxis wird damit heute nur **News** frei. | +| E9 | Kundenansicht endet nach **2 Stunden**; Protokoll wird nach **12 Monaten** gelöscht; Datenschutzerklärung bekommt dazu einen Satz. | +| E10 | Mitarbeiter-Login auf project.webklar.com wird gesperrt (Hinweis + Link ins Ticketsystem). | +| E11 | Team-Mails: Texte und Design bleiben, nur Links (und Linktext „Admin-Bereich“ → „Ticketsystem“). | +| E12 | Umsetzung in **4 Etappen**; das Admin-Panel bleibt bis Etappe 4 in Betrieb. | + +## Ist-Zustand (kurz) + +- Admin-Panel: Tabs Übersicht, Projekte, Kunden, Chat, Gutschriften, Abo-Angebote, E-Mail-Speicher. Es spricht + `/api/portal-admin/*` mit Cookie-Sitzung (`requireAdminSession`, `server/routes/portalAdmin.js:38`). Jede + Mitarbeiter-Anmeldung im Portal (Eintrag in `employees` **oder** Label `admin`) bekommt diese Sitzung. +- WOMS ruft das Portal heute schon cross-origin mit Appwrite-JWT auf (`/api/admin/*`, `requireAppwriteStaff`, + verlangt Label `admin`): Finanzen, Kunden anlegen/löschen, Projekte, Services, Analyse. +- „Als Kunde ansehen“ gibt es (`POST /api/portal-admin/view-as/:id`); jede Schreib-Route sperrt sich dort einzeln. +- WOMS: Sidebar ist eine flache Liste (`src/components/Navbar.jsx:57-69`), Kundenseite + `src/pages/CustomerDetailPage.jsx` mit Tabs aus `src/lib/customerTabs.js` (Tab nur im React-State). + +## 1. WOMS: Sidebar und Kundenseite + +### Sidebar + +``` +Kunden + Chat ● n ungelesene Kundennachrichten + Gutschriften ● n offene Anträge + Abo-Angebote + E-Mail-Speicher ● n offene Speicher-Aufgaben +``` + +- Einträge in `links` bekommen optional `children`; Unterpunkte sind eingerückt und immer sichtbar. Eingeklappt + erscheinen sie als Symbole unter dem Kunden-Symbol. Zahlen nur, wenn > 0. +- Zahlen kommen aus einem Hook, der **alle 60 s** fragt, solange der Browser-Tab sichtbar ist + (`document.visibilityState`). Fehler lassen die letzte Zahl stehen. +- Der Knopf **„Projekt-Admin“** entfällt (Etappe 4). +- Routen: `/customers/chat` (optional `?kunde=`), `/customers/gutschriften`, `/customers/angebote`, + `/customers/email-speicher`. Statische Segmente gehen in react-router v6 vor `/customers/:id`. + +### Kundenseite + +Tabs: `Übersicht · Tickets · Rechnungen · Projekte · Chat · Abo & Angebote · Gutschriften · Funktionen · Protokoll` + +- „Rechnungen“ bleibt **nur für Admins** (die Finanzen-API ist admin-only). Alle neuen Tabs sehen alle Mitarbeiter. +- „Uebersicht“ wird zu „Übersicht“. +- Der Tab steht in der Adresse: `/customers/?tab=chat|abo|gutschriften|funktionen|protokoll|…`; beim + Gutschriften-Tab zusätzlich `&antrag=` (öffnet den Antrag direkt). Unbekannte Tabs → Übersicht (wie heute + `resolveCustomerTab`). +- Kopfzeile: Knopf **„Kundenansicht öffnen“** (neuer Browser-Tab). Ohne Portal-Zugang + (`portalAccessEnabled && appwriteUserId` fehlt) ausgegraut mit Hinweis „Kein Portal-Zugang“. +- Karte „Portal-Zugang“: Link „Portal öffnen“ (führte nur zum Login) wird durch denselben Knopf ersetzt; neu ist + die Zeile **„Letzter Login“**. + +## 2. WOMS: die Bereiche + +Grundsatz: **Funktionsgleich zum heutigen Admin-Panel**, nur im WOMS-Look. Keine neuen Funktionen. Mails an +Kunden und Stripe-Buchungen bleiben exakt wie heute (gleiche Server-Logik). + +### Chat (Messenger `/customers/chat` und Kunden-Tab „Chat“) + +- Links: Gesprächsliste (ungelesene oben, letzte Nachricht, Zeit) und Feld „Neue Nachricht an Kunde…“ + (Kundenauswahl). Rechts: Gespräch. Im Kunden-Tab steckt dasselbe Gespräch ohne Liste. +- Anhänge wie heute: Bild, Kamerafoto, Datei, Sprachnachricht; Lightbox, Audio-Player, Download. +- Eingabezeile und Sprechblasen: WOMS lädt **`chat-composer.js` und `chat-composer.css` zur Laufzeit von + project.webklar.com** (eine Quelle, kein Auseinanderlaufen) und bettet `ChatComposer.create(...)` / + `ChatComposer.renderMessage(...)` in eine React-Komponente ein. Farben gleicht eine kleine WOMS-CSS an. + Der Composer bekommt eine neue Option für Anfrage-Header (Bearer-JWT beim Hochladen und Entfernen); ohne + die Option verhält er sich wie bisher (Kundenseite unverändert). +- Nach dem Senden zeigt WOMS das Ergebnis des Mail-Hinweises an den Kunden (K17) wie heute im Panel. +- Neue Nachrichten im offenen Gespräch: Abfrage alle 15 s (wie das Panel heute, nur bei sichtbarem Tab); + gelesene Kundennachrichten werden wie heute als gelesen markiert. + +### Gutschriften (Sammelliste und Kunden-Tab) + +- Filter „Offen“ / „Alle“; im Kunden-Tab nur die Anträge dieses Kunden. +- Detail wie heute: Antrag, Nachweise (öffnen), automatische Prüfung, Rechner (Rechnungsbetrag, Rechnung gilt + für, Bezahlt bis, Übernahme ab), weitere Anträge des Kunden, Verlauf, Stripe-Guthaben. +- Aktionen wie heute: „Genehmigen & gutschreiben“, „Rückfrage senden“, „Ablehnen“, „Jetzt in Stripe + gutschreiben“, „In Stripe öffnen“. + +### Abo & Angebote (Sammelliste „Abo-Angebote“ und Kunden-Tab) + +- Kunden-Tab oben: **Abo-Status** (Abo, Leistungsbeginn, nächste Abbuchung, gekündigt zum, Speicher), sichtbar + für alle Mitarbeiter. Darunter die Angebote dieses Kunden und „Neues Angebot“ (Kunde vorbelegt). +- Sammelliste: Filter „Offen & aktiv“ / „Alle“, „Neues Angebot“ mit Kundenauswahl, Zurückziehen, + Mail erneut senden. Coupon in Stripe wie heute. + +### E-Mail-Speicher (nur Sammelliste) + +- Gesamtübersicht, fällige konsoleH-Aufgaben mit Anleitung und Knopf „Erledigt“ direkt in der Zeile, + gescheiterte Änderungen der letzten 30 Tage. + +### Funktionen (Kunden-Tab) + +- Pro Projekt News an/aus, wie heute im Kunden-Tab des Panels. + +### Protokoll (Kunden-Tab) + +- Einträge der Kundenansicht dieses Kunden, neueste zuerst: Zeit, Mitarbeiter, Aktion, Detail. + +### Ersatzlos weg + +Übersicht (Statistik, letzte Logins), Projekt- und Kundentabelle des Panels, Invoice-Ninja-Rechnungsliste +(`GET /customers/:id/invoices`), Kunden-Aktivität (`/customers/:id/activity`, im WOMS-Tab „Tickets“ enthalten), +Kundennummer-Bearbeitung des Panels (Stammdaten bearbeitet WOMS schon selbst). + +## 3. Portal: Kundenansicht und Protokoll + +### Öffnen + +1. WOMS öffnet synchron einen leeren Tab (gegen Pop-up-Sperren), ruft + `POST /api/team/customers/:id/kundenansicht` (Bearer-JWT) und setzt danach die Adresse des Tabs. +2. Der Server prüft: Kunde existiert und hat Portal-Zugang. Er erzeugt einen **Einmal-Link** + (`staffPreviewTokens`, neuer Zweck `kundenansicht`, 60 s, einmal einlösbar) und antwortet mit + `{ openUrl: https://project.webklar.com/api/auth/kundenansicht?token=… }`. +3. `GET /api/auth/kundenansicht` löst das Token ein, setzt die Portal-Sitzung + `{ role:'admin', viewAs:true, viewId, viewExpiresAt, appwriteUserId, adminName, adminEmail, customerId, name, email }` + (`viewId` = zufällige Kennung, `viewExpiresAt` = jetzt + 2 h, Cookie-Laufzeit ebenfalls 2 h) und ein + **Preview-Cookie für genau diesen Kunden** (Rolle Kunde, 2 h), damit Previews aus der Kundenansicht + funktionieren. Protokoll: `geoeffnet`. Weiter auf `/dashboard.html`. +4. Abgelaufenes oder verbrauchtes Token: Hinweisseite „Link abgelaufen, bitte im Ticketsystem erneut öffnen“. + +### Banner und Ende + +- Banner oben: „Kundenansicht: *Kunde* · geöffnet von *Mitarbeiter* · Beenden“. +- „Beenden“: `POST /api/auth/kundenansicht/ende` → Protokoll `beendet`, Portal- und Preview-Cookie löschen, + Weiterleitung auf `https://ticket.webklar.com/customers/` (Basis aus neuer Variable `TICKET_BASE_URL`). +- Nach 2 h: jede Anfrage antwortet `401 { code:'view_expired' }`, Cookies werden gelöscht, Protokoll + `abgelaufen`; das Dashboard zeigt „Kundenansicht abgelaufen – im Ticketsystem erneut öffnen“. + +### Schutz vor Verwechslung (ein Browser = eine Portal-Sitzung) + +- `/api/auth/me` liefert in der Kundenansicht `viewId`. Alle API-Aufrufe des Dashboards (`app.js`, + `funktionen.js`, `analyse-view.js`, Composer) senden den Header `X-Kundenansicht: `. +- Server-Middleware vor allen Kunden-Routen: In einer Kundenansicht mit abweichender Kennung → `409 + { code:'view_replaced', kunde: }`. Schreibende Anfragen **ohne** Kennung → ebenfalls 409. +- Das Dashboard rendert bei 409 nichts Neues, sondern zeigt nur: „Diese Ansicht wurde durch die + Kundenansicht von *B* ersetzt. Tab schließen.“ + +### Was erlaubt ist (fail-closed) + +- Neue zentrale Middleware: In einer Kundenansicht ist **jede nicht-lesende Anfrage gesperrt** (403 + „In der Kundenansicht nicht möglich“), außer einer ausdrücklichen Erlaubnisliste: + - News: anlegen, Bild hochladen, ändern, löschen (`/api/news` POST/PATCH/DELETE; `rejectViewAs` entfällt dort). + - `POST /api/funktionen/analyse/heatmap-link` (nur ein Ansichts-Link, kostet nichts). + - Ende der Kundenansicht und Abmelden. +- Damit bleiben gesperrt: Abo bestellen/verwalten/wechseln, Speicher, News kaufen, Analyse an/aus (AVV-Zustimmung), + Passwort, Chat senden/Anhänge, Gutschrift beantragen/antworten – und **jede künftige Schreib-Route**, bis sie + bewusst auf die Liste kommt. Die bestehenden Einzelsperren bleiben als zweite Sicherung stehen. +- `POST /api/chat/read` fällt unter die Sperre: Die Kundenansicht markiert Team-Nachrichten **nicht** mehr als + „vom Kunden gelesen“ (Korrektur). +- Lesende Anfragen mit Nebenwirkung, die bleiben dürfen: Abgleich offener Wechsel (`GET /api/abo`), + Freischalten fertiger Funktionen und Rechnungs-Links (`GET /api/funktionen`) – reine Synchronisierung. +- Das Dashboard blendet gesperrte Bedienelemente in der Kundenansicht weiter aus bzw. deaktiviert sie (wie heute). + +### Protokoll + +- Neue Appwrite-Collection **`customerViewLog`**: `customerId` (string 64, Pflicht), `staffUserId` (string 64), + `staffName` (string 128), `action` (string 32), `detail` (string 300); Index `customerId` + `$createdAt`. + Nur der Server-Schlüssel schreibt/liest (keine Client-Rechte). +- Aktionen: `geoeffnet`, `beendet`, `abgelaufen`, `news_angelegt`, `news_bild`, `news_geaendert`, + `news_geloescht` (Detail: Titel bzw. Anfang des Textes). +- Geschrieben **vom Server** im Anschluss an die erfolgreiche Aktion; schlägt das Schreiben fehl, wird das + geloggt (Server-Log), die Aktion selbst bleibt gültig. +- Löschen: täglicher Aufräumlauf im Portal-Server, Einträge älter als 12 Monate. +- Anlegen: Setup-Skript mit dem Server-Schlüssel; fehlt dem Schlüssel das Recht, legt der Nutzer die Collection + nach Vorgabe in der Appwrite-Konsole an. + +### Datenschutzerklärung (Portal, Abschnitt 6) + +Der Satz „Um Sie zu unterstützen, können unsere Mitarbeiter Ihr Portal so ansehen, wie Sie es sehen. Eine Nachricht +in Ihrem Namen senden können sie dabei nicht.“ wird ersetzt durch: + +> Um Sie zu unterstützen, können unsere Mitarbeiter Ihr Portal so öffnen, wie Sie es sehen, und dabei für Sie +> News-Beiträge anlegen, ändern oder löschen. Bestellungen, Zahlungen, Änderungen an Ihrem Passwort sowie +> Nachrichten oder Anträge in Ihrem Namen sind dabei ausgeschlossen. Jedes Öffnen und jede Änderung halten wir +> mit Mitarbeiter und Zeitpunkt fest und löschen diese Einträge nach 12 Monaten. Rechtsgrundlage dafür ist +> Art. 6 Abs. 1 lit. f DSGVO; unser berechtigtes Interesse ist die Nachvollziehbarkeit von Zugriffen auf Ihr Konto. + +„Stand“ aktualisieren, PDF-Fassung `docs/WEBklar-Datenschutzerklaerung.pdf` neu erzeugen (wie in d4241d2). +Geht mit Etappe 1 live. + +## 4. Portal: Schnittstelle für das Ticketsystem und Abschaltung + +### Neuer Eingang `/api/team/*` + +- Middleware `requireStaffJwt`: prüft `Authorization: Bearer ` über Appwrite `/account` (wie + `requireAppwriteStaff`), dann `resolveStaffAccount` (Eintrag in `employees` **oder** Label `admin`); sonst 403. + Sie stellt die Mitarbeiter-Identität in derselben Form bereit wie die bisherige Admin-Sitzung + (`appwriteUserId`, `name`, `email`, `isAdmin`), damit die bestehenden Handler unverändert bleiben. + Die 2FA ist damit abgedeckt: Appwrite beantwortet `/account` ohne abgeschlossenen zweiten Faktor mit 401 + (`user_more_factors_required`), die Middleware lässt dann nichts durch. +- Routen (bestehende Logik, neu eingehängt; während Etappe 1–3 parallel zu `/api/portal-admin/*`): + +| Neu unter `/api/team` | Herkunft | +|---|---| +| `GET /chats`, `GET /chats/unread-count`, `GET/POST /chats/:id/messages`, `POST /chats/:id/attachments`, `GET/DELETE /chats/:id/attachments/:att`, `POST /chats/:id/read` | `portalAdmin.js` (Chat-Teil wird eigenes Modul) | +| `/takeover-credits/*` (+ Filter `?customerId=`) | `takeoverAdminRouter` | +| `/offers/*` (+ Filter `?customerId=`) | `offersAdminRouter` | +| `/storage-tasks/*` + neu `GET /storage-tasks/open-count` (ohne Stripe-Abgleich, für die Sidebar-Zahl) | `storageTasksAdminRouter` | +| `/funktionen/:customerId[/news]` | `funktionenAdmin` | +| `GET /customers/:id/abo` | Logik von `/api/admin/customers/:id/abo`, jetzt für alle Mitarbeiter | +| `GET /customers/:id/portal` → `{ hasAccess, lastLoginAt }` | neu (`customerPortalAccess`) | +| `POST /customers/:id/kundenansicht` | neu (ersetzt `view-as`) | +| `GET /customers/:id/protokoll` | neu | +| `POST /preview-link` | Logik von `/api/admin/preview-open`, jetzt für alle Mitarbeiter | + +- **Dateien ohne Header** (Bilder, Sprachnachrichten, Downloads im Chat; Nachweise bei Gutschriften): Die + Antworten enthalten **signierte Links** (HMAC mit dem Server-Geheimnis, gebunden an Kunde + Datei, 15 min + gültig). Die Datei-Routen akzeptieren Bearer-JWT **oder** gültige Signatur. +- CORS: erlaubte Header zusätzlich `X-File-Name`, `X-Attachment-Kind`, `X-Duration-Ms`, `X-Image-Width`, + `X-Image-Height` (Chat-Upload). Herkunft weiter nur `https://ticket.webklar.com` (+ localhost). +- WOMS-Seite: ein gemeinsamer Helfer `teamRequest` nach dem Muster von `src/lib/financeRequest.js` + (JWT-Cache, einmal neu versuchen bei 401, deutsche Fehlertexte). + +### Previews aus dem Ticketsystem + +- Die Preview-Knöpfe im WOMS (`PreviewLinkButton`) sind heute einfache Links und funktionieren nur dank der + Mitarbeiter-Anmeldung im Portal. Sie holen sich ab Etappe 1 einen Einmal-Link über `POST /api/team/preview-link` + (leerer Tab synchron, Adresse danach setzen). + +### Was in Etappe 4 wegfällt + +- `public/admin.html`, `public/admin.js` (und nur dort genutzte Styles), `public/kalkulation.{html,js,css}`. +- `/api/portal-admin/*` komplett, darin auch: overview, projects, customers (Liste, PATCH, activity, invoices), + appointments, `customers/:id/appointment`, `GET customers/:id`, `view-as`, `exit-view-as`. +- `/api/admin/switch-link`, `GET /api/auth/staff-switch`, Zweck `switch` in `staffPreviewTokens`, + `/api/admin/preview-open` (ersetzt durch `/api/team/preview-link`). Die Einlöse-Route + `GET /api/auth/staff-preview` **bleibt**: sie löst auch die Links von `/api/team/preview-link` ein. +- Mitarbeiter-Login: `POST /api/auth/login` erkennt Mitarbeiter wie heute (`resolveStaffAccount`), beendet die + gerade erzeugte Appwrite-Sitzung und antwortet `403 { error:'Mitarbeiter arbeiten im Ticketsystem.', ticketUrl }`; + die Login-Seite zeigt den Hinweis mit Link. `/api/auth/login/mfa`, `/login/mfa/resend`, `staffMfa` und + `startStaffPortalSession` entfallen. Kundenlogin unverändert. +- Alte Mitarbeiter-Sitzungen (Rolle admin ohne Kundenansicht): Dashboard und Login leiten ins Ticketsystem. +- WOMS (vorher live): Knopf „Projekt-Admin“ und `openProjectAdmin()` entfernen. +- Team-Mails: + - Fußzeilen-Link „Admin-Bereich“ in 15 Vorlagen → „Ticketsystem“, `https://ticket.webklar.com/customers`. + - T01 „Neue Chatnachricht“: Knopf „Im Ticketsystem antworten →“ → `/customers/chat?kunde=` + (neuer Platzhalter). + - T10/T11: `admin_link` → `/customers/?tab=gutschriften&antrag=`. + - Code-Kommentare/Texte, die „Admin-Panel“ nennen, werden angepasst. + +## 5. Etappen und Livegang + +Jede Etappe ist für sich livefähig; das Admin-Panel läuft bis Etappe 4 weiter. + +| Etappe | Inhalt | +|---|---| +| **1 Fundament + Kundenansicht** | `/api/team`-Eingang (Middleware, CORS, `teamRequest`), Kundenansicht (Einmal-Link, Sitzung 2 h, Banner, Verwechslungsschutz, fail-closed-Sperre, News frei, Chat-Lesestatus), Protokoll (Collection, Schreiben, Aufräumen, Tab), Datenschutz-Satz, Previews per Einmal-Link, WOMS: Tab in der Adresse, „Übersicht“, Knopf „Kundenansicht öffnen“, Letzter Login, Abo-Status-Route. | +| **2 Chat** | Chat-Routen unter `/api/team`, signierte Datei-Links, Composer-Option für Header, WOMS-Messenger, Kunden-Tab, Sidebar mit Unterpunkten und Chat-Zahl. | +| **3 Gutschriften, Abo & Angebote, E-Mail-Speicher, Funktionen** | Routen unter `/api/team` (+ Kundenfilter, `open-count`), signierte Nachweis-Links, Sammellisten und Kunden-Tabs, restliche Sidebar-Zahlen. | +| **4 Abschalten** | WOMS: „Projekt-Admin“ entfernen (zuerst live). Portal: Admin-Panel, Rechnungsanalyse, `/api/portal-admin`, Switch/Preview-open-Altlasten, Mitarbeiter-Login sperren, Mail-Links. | + +- Reihenfolge je Etappe: **Portal zuerst** (abwärtskompatibel), dann WOMS. Ausnahme Etappe 4: WOMS zuerst. +- Pushen macht der Nutzer (Portal `…:main`, WOMS `…:test`; WOMS deployt jeden gepushten Branch, also nie + Feature-Branches pushen). Danach prüfe ich den Live-Stand lesend. +- Arbeitsorte: Portal-Worktree `dev-admin-umzug` (Branch `feature/admin-umzug`), WOMS-Worktree gleichen Namens. + +## 6. Tests + +- **Portal:** bestehende Unit-Tests (`node --test`) plus neue: + - `requireStaffJwt`: ohne JWT 401, ungültig 401, Nicht-Mitarbeiter 403, Mitarbeiter ohne Label `admin` erlaubt. + - Kundenansicht: Token einmalig und 60 s; Sitzung endet nach 2 h; **jede** Schreib-Route außerhalb der + Erlaubnisliste liefert 403 (Test läuft über die registrierten Express-Routen, damit neue Routen automatisch + erfasst werden); News-Aktionen erlaubt und protokolliert; `POST /api/chat/read` gesperrt. + - Verwechslung: Tab-Kennung A nach Wechsel zu B → 409, keine Daten von B. + - Signierte Links: falscher Kunde, falsche Datei, abgelaufen → 403. + - Aufräumlauf löscht nur Einträge > 12 Monate. + - Abgeschottete Testumgebung (Appwrite-Nachbau, Stripe-Attrappe, Mail-Falle) für Gutschrift/Angebot über + `/api/team` mit Bearer-JWT. +- **WOMS:** Unit-Tests für reine Logik (`customerTabs` mit neuen Tabs/URL, Sidebar-Kinder, signierte-Link-Helfer, + `teamRequest`); Playwright mit nachgebauten Schnittstellen unter Speicherbremse (`systemd-run … MemoryMax`). +- **Nach jedem Livegang:** lesender Live-Check (Routen antworten 401 ohne JWT, CORS-Preflight 204, ausgelieferte + Bundles enthalten die neuen Teile). Keine echten Kundendaten verändern; Testdaten klar benannt und danach löschen. + +## Fehlerbehandlung + +- JWT abgelaufen → `teamRequest` holt ein neues und versucht einmal erneut; danach Fehlermeldung. +- Portal nicht erreichbar → jede Ansicht zeigt eine Fehlerzeile mit „Erneut versuchen“; Sidebar-Zahlen behalten + den letzten Stand. +- Kunde ohne Portal-Zugang → Knopf ausgegraut; Server antwortet zusätzlich 409 „Kein Portal-Zugang“. + +## Nicht Teil dieser Spec + +- Rechnungsanalyse/Kalkulation im WOMS (E4, später eigenes Vorhaben). +- Neue Funktionen in Chat, Gutschriften, Angeboten oder E-Mail-Speicher. +- Änderungen an Kunden-Logins, Kunden-Mails (K01–K19) oder an der WOMS-2FA.