# Finanzen Plan 3: WOMS-Oberfläche und Livegang Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Die WOMS-Seite „Finanzen“ zeigt Stripe-Rechnungen und das Finom-Konto nach dem Seitenaufbau der Spec, Projektrechnungen werden in WOMS geschrieben, InvoiceNinja verschwindet aus der Oberfläche, und das Ganze geht kontrolliert live. **Architecture:** WOMS spricht ausschließlich die Portal-Routen `/api/admin/finanzen/*` aus Plan 1 und 2 an (Appwrite-JWT, wiederverwendeter JWT-Cache wie Serverpuls). Reine Formatierungs- und Parse-Logik in `src/lib/finance*.js` (mit `node --test` geprüft), Darstellung in `src/components/finance/*` und drei Seiten. **Tech Stack:** React 18 + Vite (WOMS), vorhandene UI-Bausteine `src/components/ui` (PageHeader, Card, Button, Badge, Tabs, EmptyState, Field, Kpi, AutocompleteInput), `styles/global.css`, `node --test` mit `tests/unit/load.mjs`. **Spec:** `docs/superpowers/specs/2026-10-01-finanzen-design.md` (im Portal-Repo, Worktree `kundenbereich.webklar.com/dev-finanzen`) ## Global Constraints - WOMS-Worktree: `/home/webklar/apps/ticket.webklar.com/dev-finanzen`, Branch `feature/finanzen` von `origin/test`, `node_modules` als Symlink auf `../build/node_modules`. `node_modules` ist in Git getrackt: **nur einzelne Dateien adden, nie `git add -A`**. **Nie pushen** ohne OK des Nutzers (der Webhook stellt jede gepushte Branch live). - Tests: `npm test` (`node --test tests/*.test.mjs tests/unit/*.test.mjs`) grün vor jedem Commit. Reine Logik gehört nach `src/lib/` (ohne Appwrite-Import), damit `loadModule` sie bündeln kann. - Build-Prüfung unter Speichergrenze: `systemd-run --scope -p MemoryMax=1500M -p MemorySwapMax=0 npm run build` im Worktree. - Portal-Basis-URL: `import.meta.env.VITE_PROJECT_ADMIN_URL || 'https://project.webklar.com'`; Auth `Authorization: Bearer ` über `createJwtCache` + `fetchWithRetry` aus `src/lib/serverpulsAuth.js`. Kein `PUT` (Portal-CORS erlaubt GET, POST, PATCH, DELETE). - Nur Admins (`isAdmin` aus `useAuth()`) sehen Menüpunkt und Seiten; Nicht-Admins werden auf `/tickets` umgeleitet. - Darstellung: Beträge de-DE mit `€`, rechtsbündig, Klasse mit `font-variant-numeric: tabular-nums`; Daten `TT.MM.JJJJ`; nie Farbe allein (immer Text oder Symbol dazu); keine Prozent-Veränderungen, keine Diagramme außer optionaler Treppenlinie (nicht in diesem Plan). - Texte für Nutzer auf Deutsch mit echten Umlauten. - InvoiceNinja-Server und `ticket-integrations` bleiben unangetastet. - Seitenblöcke, Aufgabenarten und Zuordnungsregeln exakt wie in der Spec. ## Review Focus - **Rückkehrseite doppelt geladen** (React StrictMode, Neuladen, Zurück-Taste): der Code darf nur einmal an `/bank/rueckkehr` gehen; zweiter Versuch zeigt die Serverantwort verständlich statt „Fehler“. Test in Task 2 (`parseCallback` + Einmal-Schutz in der Seite). - **Betragseingabe** „1.234,50“, „50“, „50,5“, „ 12 “, „-5“, „abc“ → 123450, 5000, 5050, 1200, Fehler, Fehler. Test in Task 1. - **Portal nicht erreichbar oder 403** (Admin ohne 2FA-Sitzung): Seite zeigt eine Fehlerkarte mit Text und „Erneut versuchen“, kein weißer Bildschirm. Test in Task 7 (Smoke). - **Unumkehrbare Zuordnung:** Bestätigen einer Rechnungszuordnung verlangt einen zweiten Klick mit dem Hinweis „wird in Stripe als bezahlt markiert, nicht umkehrbar“. Test in Task 7 (Smoke). - **Kunde ohne E-Mail:** „Rechnung schreiben“ für diesen Kunden ist gesperrt mit Hinweis „E-Mail fehlt“ (Stripe verschickt per Mail). Test in Task 6 (`canInvoice`). --- ### Task 1: Formatierung, Parser und API-Client **Files:** - Create: `src/lib/financeFormat.js` - Create: `src/lib/financeApi.js` - Test: `tests/unit/financeFormat.test.mjs` **Interfaces:** - Produces (`financeFormat.js`, rein): - `euro(cents) → string` (`euro(123456) === '1.234,56 €'`, `euro(null) === '–'`) - `euroWhole(cents) → string` (`euroWhole(10893) === '109 €'`) - `signedEuro(cents) → string` (`'+168,79 €'`, `'−4,49 €'` mit U+2212) - `dateDe('2027-03-29') === '29.03.2027'`; `sinceLabel(iso, now) → 'gerade eben' | 'vor 12 Min.' | 'vor 3 Std.' | 'vor 2 Tagen'` - `parseEuro(text) → number` (Cent) oder wirft `Error('Ungültiger Betrag')`; erlaubt Tausenderpunkte und Dezimalkomma, keine negativen Werte - `INVOICE_STATUS = { entwurf: { label: 'Entwurf', tone: 'muted' }, offen: { label: 'Offen', tone: 'info' }, ueberfaellig: { label: 'Überfällig', tone: 'warn' }, bezahlt: { label: 'Bezahlt', tone: 'ok' }, storniert: { label: 'Storniert', tone: 'muted' }, uneinbringlich: { label: 'Uneinbringlich', tone: 'danger' } }` - `CATEGORY_LABEL = { rechnung: 'Rechnung', 'rechnung-extern': 'Einnahme ohne Stripe-Rechnung', 'stripe-auszahlung': 'Stripe-Auszahlung', fixkosten: 'Fixkosten', privat: 'Privat/Umbuchung', sonstiges: 'Sonstiges', '': 'Offen' }` - `URGENCY = { hoch: { label: 'Dringend', tone: 'danger' }, mittel: { label: 'Bald', tone: 'warn' }, info: { label: 'Hinweis', tone: 'muted' } }` - `AGE_GROUP_LABEL = { 'nicht-faellig': 'Nicht fällig', '1-30': '1–30 Tage', '31-60': '31–60 Tage', 'ueber-60': 'Über 60 Tage' }` - `pageOf(list, page, size = 50) → { items, page, pages }` - `canInvoice(customer) → { ok: boolean, reason?: string }` – `customerStatus` `lead`/`lost` → „Noch kein fester Kunde“; ohne `email` → „E-Mail fehlt“. - Produces (`financeApi.js`): `getOverview()`, `getInvoices({ status, kunde })`, `createInvoice(body)`, `sendInvoice(id)`, `voidInvoice(id)`, `deleteInvoice(id)`, `getTransactions()`, `assignTransaction(id, target)`, `unassignTransaction(id)`, `getBank()`, `refreshBank()`, `startBankAuth()`, `completeBankAuth({ code, state, error })`, `getSettings()`, `patchSettings(patch)`, `getFixedCosts()`, `createFixedCost(body)`, `updateFixedCost(id, patch)`, `deleteFixedCost(id)` – Pfade und Methoden wie in Plan 1/2; Fehler werfen `Error(data.error || 'API-Fehler ')` mit `.status`. - [ ] **Step 1: Worktree anlegen** ```bash cd /home/webklar/apps/ticket.webklar.com && git -C build fetch -q origin && git -C build worktree add ../dev-finanzen -b feature/finanzen origin/test && ln -s ../build/node_modules dev-finanzen/node_modules ``` - [ ] **Step 2: Write the failing tests** in `tests/unit/financeFormat.test.mjs` mit allen Beispielwerten oben (inkl. Review-Focus-Fälle für `parseEuro` und `canInvoice`). - [ ] **Step 3: Run** `npm test` – Expected: FAIL (Modul fehlt). - [ ] **Step 4: Implement** beide Dateien (`financeApi.js` ohne Test, folgt dem Muster von `serverpulsApi.js`). - [ ] **Step 5: Run** `npm test` – Expected: PASS. - [ ] **Step 6: Commit** `git add src/lib/financeFormat.js src/lib/financeApi.js tests/unit/financeFormat.test.mjs && git commit -m "Finanzen: Formatierung, Betragsparser und Portal-API-Client"` ### Task 2: Zugriff nur für Admins, Routen, Rückkehrseite **Files:** - Create: `src/components/AdminRoute.jsx` - Create: `src/lib/bankCallback.js` - Create: `src/pages/BankCallbackPage.jsx` - Modify: `src/App.jsx` (Routen `/finance`, `/finance/einstellungen`, `/finanzen/bank-callback`, alle in `ProtectedRoute` + `AdminRoute`) - Modify: `src/components/Navbar.jsx:64` (Eintrag „Finanzen“ nur bei `isAdmin`) - Test: `tests/unit/bankCallback.test.mjs` **Interfaces:** - Produces: - `AdminRoute({ children })` – wartet auf geladenen Nutzer, sonst `` für Nicht-Admins. - `parseCallback(search) → { code, state, error } | null` (`null`, wenn weder `code` noch `error`); `error_description` wird an `error` angehängt („access_denied: …“). - `BankCallbackPage`: liest `location.search`, ruft `completeBankAuth` genau einmal (`useRef`-Schutz), zeigt „Bankzugang wird eingerichtet …“, dann Erfolg „Bankzugang erneuert, gültig bis TT.MM.JJJJ“ oder die Fehlermeldung des Servers, jeweils mit Knopf „Zu den Finanzen“ (`/finance`). Entfernt danach `code`/`state` aus der Adresszeile (`navigate('/finanzen/bank-callback', { replace: true, state: result })`). - [ ] **Step 1: Write the failing tests:** `parseCallback('?state=a&code=b') → { code: 'b', state: 'a', error: '' }`; `'?state=a&error=access_denied&error_description=abgebrochen' → error 'access_denied: abgebrochen'`; `'' → null`. - [ ] **Step 2: Run** `npm test` – Expected: FAIL. - [ ] **Step 3: Implement** Dateien und Routen. - [ ] **Step 4: Run** `npm test` und den Build-Befehl aus Global Constraints – Expected: PASS, Build ohne Fehler. - [ ] **Step 5: Commit** `git commit -m "Finanzen: nur fuer Admins, Rueckkehrseite fuer die Bankfreigabe"` ### Task 3: Seitenkopf – Statuszeile, Liquidität, Zu erledigen **Files:** - Modify: `src/pages/FinancePage.jsx` (komplett neu, InvoiceNinja-Inhalt entfällt) - Create: `src/components/finance/StatusLine.jsx`, `LiquidityHead.jsx`, `TodoList.jsx` - Create: `src/hooks/useFinanceOverview.js` (`{ data, loading, error, reload }` über `getOverview`) - Modify: `src/styles/global.css` (Klassen `.fin-amount` mit `tabular-nums` und rechtsbündig, `.fin-todo`, `.fin-status`; Farben nur über vorhandene Variablen) **Interfaces:** - Consumes: Task 1, Übersicht-Format aus Plan 2 Task 8. - Produces: - `StatusLine({ stand, onRefresh, refreshing })` – Text nach Spec Block 0; „Aktualisieren“ ruft `refreshBank`, zeigt 429-Meldung des Servers als Hinweis. - `LiquidityHead({ kopf })` – drei `Kpi`: „Kontostand“ (`euroWhole`), „Verfügbar nach Rücklagen“ (`euroWhole`, Unterzeile „Aufschlüsselung“ klappt `kontostand − Steuer-Rücklage − Fixkosten 30 Tage − Mindestpuffer` als Liste auf), „Tiefster Stand in 30 Tagen“ (`sicher: … am TT.MM.` / `erwartet: … am TT.MM.`). - `TodoList({ aufgaben, onAction })` – höchstens 7 Zeilen, danach „Alle N anzeigen“; je Zeile Badge (`URGENCY`), Titel, Text, ein Knopf. `onAction(aktion)` wird in `FinancePage` behandelt: `bank-verbinden` → `startBankAuth()` und `window.location.assign(url)`; `bank-aktualisieren` → Refresh; `rechnung-oeffnen` → `hostedUrl` in neuem Tab bzw. Sprung zum Rechnungsblock; `zuordnen` → öffnet Zuordnen-Dialog (Task 5); `kunde-oeffnen` → `/customers/:id`; `fixkosten-uebernehmen` → Bestätigung, dann `createFixedCost({ name, amountCents, interval, nextDate: , counterpartyMatch: key })`; `einstellungen` → `/finance/einstellungen`. Leerzustand: `EmptyState` „Nichts offen“. - `FinancePage`: Ladezustand, Fehlerkarte mit Meldung und „Erneut versuchen“, danach Blöcke 0–2. - [ ] **Step 1: Implement** Komponenten, Hook, Seite (Blöcke 0–2). - [ ] **Step 2: Run** `npm test` und Build – Expected: PASS. - [ ] **Step 3: Commit** `git commit -m "Finanzen: Statuszeile, Liquiditaetskopf und Aufgabenliste"` ### Task 4: Vorschau, Steuer, Abos **Files:** - Create: `src/components/finance/ForecastTable.jsx`, `TaxCard.jsx`, `AboCard.jsx` - Modify: `src/pages/FinancePage.jsx` (Blöcke 3–5; Desktop: Vorschau links, Steuer rechts; Abos halbe Breite) **Interfaces:** - Produces: - `ForecastTable({ vorschau })` – Spalten Datum, Posten, „sicher“/„erwartet“ (Text, nicht nur Farbe), Betrag (`signedEuro`), Saldo sicher, Saldo erwartet (laufend ab Kontostand); darunter `vorschau.annahme`; leer → „Keine Zahlungen in den nächsten 30 Tagen bekannt.“ - `TaxCard({ steuer })` – Rücklage (`euro`, „30 % von Gewinn 2026“), Kleinunternehmer-Zeile „ von 25.000 € (2026)“ und „Grenze 100.000 €“, Vorjahr nur wenn gesetzt, Gewerbesteuer-Hinweis nur wenn `gewerbesteuerHinweis`, nächste ESt-Vorauszahlung nur wenn gesetzt. - `AboCard({ abos })` – „Abo-Einnahmen pro Monat“, „zahlende Kunden“, „gefährdet“ (nur > 0), Liste Kunde · Produkt · Status · Betrag · nächste Abbuchung; leer → „Noch keine Abos.“ - [ ] **Step 1: Implement.** - [ ] **Step 2: Run** `npm test` und Build – Expected: PASS. - [ ] **Step 3: Commit** `git commit -m "Finanzen: Vorschau, Steuer und Abos"` ### Task 5: Rechnungen, Rechnung schreiben, Buchungen, Zuordnen **Files:** - Create: `src/components/finance/InvoiceTable.jsx`, `InvoiceCreateModal.jsx`, `TransactionTable.jsx`, `AssignDialog.jsx` - Modify: `src/pages/FinancePage.jsx` (Blöcke 6–7, Dialoge) **Interfaces:** - Consumes: Task 1 (`financeApi`, `financeFormat`), `useCustomers` (vorhanden) für die Kundenauswahl. - Produces: - `InvoiceTable({ altersgruppen, customerId = null, onChanged })` – lädt `getInvoices({ status, kunde: customerId })`; Filter `.seg` Offen/Überfällig/Bezahlt/Entwürfe/Alle (Standard Offen); Altersgruppen-Zeile mit Anzahl und Summe; Spalten nach Spec Block 6; Aktionen je Status: Entwurf → „Senden“, „Löschen“ (mit Rückfrage); offen/überfällig → „Zahlungsseite“, „PDF“, „Stornieren“ (Rückfrage, nur `source 'projekt'`); bezahlt → „PDF“, bei `outOfBand` Hinweis „per Überweisung“. Knopf „Rechnung schreiben“. - `InvoiceCreateModal({ open, onClose, onCreated, fixedCustomer = null })` – Kunde (AutocompleteInput über Kunden mit `canInvoice(c).ok`, gesperrte mit Grund), Positionen (Beschreibung, Menge, Einzelpreis in Euro → `parseEuro`), „+ Position“, Zahlungsziel (Standard 14 Tage), Notiz; Summe; Hinweis „Gemäß § 19 UStG ohne Umsatzsteuer.“; Knöpfe „Als Entwurf speichern“ und „Senden“ (`senden: true`); Serverfehler im Dialog anzeigen. - `TransactionTable({ onAssign })` – `getTransactions()`, 50 je Seite (`pageOf`), neueste zuerst; Spalten Datum, Gegenpartei + Verwendungszweck (zweizeilig), Betrag (`signedEuro`), Zuordnung (`CATEGORY_LABEL`, bei `rechnung` mit Nummer); offene Eingänge mit stärkstem Vorschlag als Text und Knopf „Zuordnen“. - `AssignDialog({ transaction, invoices, onClose, onDone })` – Auswahl: Vorschläge, andere offene Rechnung, Stripe-Auszahlung (aus Vorschlägen), Fixkosten (nur Ausgänge, aus `getFixedCosts`), Kategorie (Einnahme ohne Stripe-Rechnung nur bei Eingang, Privat/Umbuchung, Sonstiges), Notiz; bei Rechnung zweistufig: erst „Zuordnen“, dann Bestätigungstext „Die Rechnung wird in Stripe als bezahlt markiert. Das lässt sich nicht rückgängig machen.“ mit „Ja, als bezahlt markieren“; bei bestehender Zuordnung (außer `rechnung`) Knopf „Zuordnung lösen“. Serverfehler (409 Betrag weicht ab usw.) im Dialog. - [ ] **Step 1: Implement.** - [ ] **Step 2: Run** `npm test` und Build – Expected: PASS. - [ ] **Step 3: Commit** `git commit -m "Finanzen: Rechnungen schreiben und verwalten, Buchungen zuordnen"` ### Task 6: Einstellungen, Kunden-Tab, InvoiceNinja aus der Oberfläche **Files:** - Create: `src/pages/FinanceSettingsPage.jsx` - Create: `src/components/finance/CustomerInvoices.jsx` - Modify: `src/pages/CustomerDetailPage.jsx` (Tab „Rechnungen“ → `CustomerInvoices`; Ninja-Sync beim Speichern `:133-156` entfernen; Karte „Rechnungsdaten (für InvoiceNinja)“ → „Rechnungsdaten“; Badge „verknüpft“ entfernen; `syntheticTicket` entfernen) - Modify: `src/components/TicketCard.jsx` (Tab `invoices` und `InvoicePanel` entfernen) - Modify: `src/pages/CustomersPage.jsx` (Untertitel „Sync mit InvoiceNinja“ und Badge „Ninja“ entfernen) - Modify: `src/lib/leads.js` (`ninjaPayload`, `syncCustomerToNinja` und den Ninja-Teil von `upgradeToCustomer` entfernen; `isInvoiceReady` entfernen, falls danach unbenutzt) - Delete: `src/components/InvoicePanel.jsx`, `src/hooks/useInvoices.js`, `src/hooks/useFinanceSummary.js`, `src/lib/integrationsApi.js` (nur wenn `grep -rn integrationsApi src` danach leer ist) **Interfaces:** - Produces: - `FinanceSettingsPage` (`/finance/einstellungen`): Formular Einstellungen (Mindestpuffer und Vorjahresumsatz/ESt-Vorauszahlung in Euro über `parseEuro`, Rücklage in %, Bankverbindungstext) mit „Speichern“; Fixkostenliste (anlegen, bearbeiten, aktiv/inaktiv, löschen mit Rückfrage); Karte „Bankzugang“ (Status, IBAN maskiert, gültig bis, letzter Abruf, Fehler) mit „Jetzt abrufen“ und „Neu verbinden“; Link zurück zu `/finance`. - `CustomerInvoices({ customer })` – `canInvoice(customer)`: nicht ok → `EmptyState` mit Grund und „Daten erfassen“ (wechselt zum Übersicht-Tab); ok → `InvoiceTable({ customerId })` + `InvoiceCreateModal({ fixedCustomer: customer })`. - [ ] **Step 1: Implement** Seite und Komponente. - [ ] **Step 2: Remove** InvoiceNinja-Stellen wie oben; danach `grep -rni "ninja\|invoicePanel\|useInvoices\|useFinanceSummary" src` – Expected: keine Treffer. - [ ] **Step 3: Run** `npm test` und Build – Expected: PASS. - [ ] **Step 4: Commit** (geänderte und gelöschte Dateien einzeln: `git add ` und `git rm `) `git commit -m "Finanzen: Einstellungen, Rechnungen am Kunden, InvoiceNinja aus der Oberflaeche entfernt"` ### Task 7: Oberflächentest (Smoke) gegen Nachbau **Files:** - Create (nicht committen): Scratchpad `finanzen-ui/` mit `fixtures.json` (Übersicht, Rechnungen, Buchungen aus Plan-2-Testdaten) und `smoke.mjs` **Interfaces:** - Consumes: gebautes `dist/` aus dem Worktree. - [ ] **Step 1: Write** `smoke.mjs`: statischer Server für `dist/` (SPA-Fallback auf `index.html`) unter einer festen Herkunft; Playwright (`headless shell`, unter `systemd-run --scope -p MemoryMax=700M`) mit `page.route` für `**/v1/account*` (Admin-Nutzer mit Label `admin`), `**/v1/account/jwt*` und `https://project.webklar.com/api/admin/finanzen/**` aus `fixtures.json`. Prüfungen: 1. `/finance` zeigt „Kontostand“ mit „109 €“, „Zu erledigen“ mit der Aufgabe „Zahlungseingang zuordnen“, Vorschau-Annahmezeile, Rechnungsfilter „Offen“. 2. Zuordnen eines Eingangs zu einer Rechnung: nach erstem Klick erscheint der Satz „lässt sich nicht rückgängig machen“, erst der zweite Klick sendet `POST …/zuordnen` (Anfrage mitzählen). 3. Übersicht antwortet 403 → Fehlerkarte mit „Erneut versuchen“, kein leerer Bildschirm. 4. Nutzer ohne Label `admin` → `/finance` landet auf `/tickets`, Menüpunkt „Finanzen“ fehlt. 5. `/finanzen/bank-callback?state=s&code=c` → genau ein `POST …/bank/rueckkehr`, Text „Bankzugang erneuert, gültig bis 29.03.2027“. 6. Breite 390 px: keine horizontale Scrollleiste auf `/finance`. - [ ] **Step 2: Run** `node smoke.mjs` – Expected: alle 6 Prüfungen „ok“. - [ ] **Step 3:** Gefundene Fehler in den Komponenten beheben, `npm test` + Build, Commit `git commit -m "Finanzen: Korrekturen aus dem Oberflaechentest"` (nur falls Änderungen). ### Task 8: Livegang (jeder Schritt mit OK des Nutzers) **Files:** - Modify: `/home/webklar/apps/docker-compose.yml` (Sicherung vorher: `docker-compose.yml.bak-2026-10-XX-finanzen`) - Move: `/home/webklar/apps/ticket-integrations/enablebanking.pem` und `.pub` → `/home/webklar/apps/secrets/enablebanking/` - [ ] **Step 1: Schlüssel verschieben** (Ordner 700, Schlüssel 600; Inhalt nie ausgeben): ```bash install -d -m 700 /home/webklar/apps/secrets/enablebanking && mv /home/webklar/apps/ticket-integrations/enablebanking.pem /home/webklar/apps/ticket-integrations/enablebanking.pub /home/webklar/apps/secrets/enablebanking/ && chmod 600 /home/webklar/apps/secrets/enablebanking/enablebanking.pem ``` - [ ] **Step 2: Compose** beim Dienst `kundenbereich-server` ergänzen: Volume `- ./secrets/enablebanking:/run/secrets/enablebanking:ro`; Umgebung `- ENABLE_BANKING_APP_ID=b2582270-11bf-4489-affb-1a151f076c07` und `- ENABLE_BANKING_KEY_PATH=/run/secrets/enablebanking/enablebanking.pem`. Prüfen: `docker compose config -q` ohne Fehler. - [ ] **Step 3: Portal live** (OK des Nutzers): `git -C /home/webklar/apps/kundenbereich.webklar.com/dev-finanzen push origin feature/finanzen:main`; warten, bis `build/` auf dem neuen Stand ist (`git -C …/build log -1`); dann `cd /home/webklar/apps && docker compose up -d kundenbereich-server` (übernimmt Volume und Umgebung). Erwartet: `docker logs webklar-kundenbereich-server --since 2m` zeigt den Start ohne Fehler; `curl -s https://project.webklar.com/api/health` → `ok: true`. - [ ] **Step 4: Collections und Sitzung übernehmen** (Werte aus `ticket-integrations/enablebanking-session.txt`, ohne sie auszugeben): ```bash F=/home/webklar/apps/ticket-integrations/enablebanking-session.txt; docker exec -w /app webklar-kundenbereich-server node scripts/setup-finanzen.mjs --session "$(sed -n 's/^session_id=//p' $F)" --account "$(sed -n 's/^account_uid=//p' $F)" --valid-until "$(sed -n 's/^valid_until=//p' $F)" ``` Erwartet: „Sitzung übernommen, gültig bis 29.03.2027“. Danach `enablebanking-session.txt` und `enablebanking-app-id.txt` löschen. Nach höchstens 2 Minuten (Job) oder per `POST /bank/aktualisieren`: 9 Buchungen, Kontostand 108,93 €. - [ ] **Step 5: Stripe-Dashboard** (Nutzer): Rechnungsnummern fortlaufend auf Kontoebene; Zahlarten für Rechnungen Karte und SEPA-Lastschrift; in WOMS-Einstellungen den Bankverbindungstext (IBAN, BIC) eintragen. - [ ] **Step 6: WOMS live** (OK des Nutzers): `git -C /home/webklar/apps/ticket.webklar.com/dev-finanzen push origin feature/finanzen:test`. Erwartet: neues Bundle in `ticket.webklar.com/out` enthält „Verfügbar nach Rücklagen“; Seite lädt für Kenso. - [ ] **Step 7: Daten nachziehen:** Buchung „Rechnung 0005 vom 28.07.2026“ als „Einnahme ohne Stripe-Rechnung“ zuordnen; prüfen, dass Justin das Label `admin` hat (sonst Nutzer fragen). - [ ] **Step 8: Merken:** Memory-Datei `webklar-finanzen.md` mit Livestand, Commits und offenen Punkten aktualisieren (Enable-Banking-Vertrag, Kundenportal-Tab „Rechnungen“ noch InvoiceNinja).