Files
Webklar-Kundenbereich/docs/superpowers/specs/2026-10-01-finanzen-design.md
T

14 KiB
Raw Blame History

Finanzen: Stripe-Rechnungen und Finom-Konto im Ticketsystem

Stand: 2026-10-01 · Status: Entwurf zur Freigabe

Grundlage: Recherche /root/reports/Finanzübersicht für kleine Webagentur.md (Notizen in /root/research_notes/Finanzübersicht für kleine Webagentur/) und die Machbarkeitsprüfung vom 30.09./01.10.2026 (Enable Banking produktiv getestet: Finom-Konto, Salden, Umsätze).

Ziel

Die Seite „Finanzen“ im Ticketsystem (WOMS, ticket.webklar.com/finance) zeigt nicht mehr InvoiceNinja, sondern Stripe-Rechnungen und das Finom-Geschäftskonto. Sie beantwortet beim wöchentlichen Blick drei Fragen: Wie viel Geld ist wirklich frei? Was muss ich heute tun? Reicht das Geld die nächsten 30 Tage? Projektrechnungen werden direkt aus WOMS über Stripe geschrieben und verschickt.

Erfolg: Kein Zahlungseingang bleibt unzugeordnet, keine überfällige Rechnung bleibt unbemerkt, und der Bankzugang läuft nie unbemerkt ab.

Feststehende Rahmenbedingungen (geprüft)

  • WEBklar GbR, Kleinunternehmer nach § 19 UStG (Impressum, Stripe-Rechnungsfuß im Portal). Daraus folgt: keine Umsatzsteuer auf Rechnungen, keine USt-Voranmeldung, keine USt-Rücklage, keine E-Rechnungspflicht beim Ausstellen – das Stripe-PDF genügt. Einkommensteuer zahlen die Gesellschafter privat; Gewerbesteuer erst über 24.500 € Gewinn.
  • Bank: Enable Banking, App „WEBklar Finanzen“, ID b2582270-11bf-4489-affb-1a151f076c07, Produktion, nur Kontoinformationen (AIS). Schlüssel auf dem Server erzeugt (/home/webklar/apps/ticket-integrations/enablebanking.pem, verlässt den Server nie). Sitzung zum Finom-Konto „Webklar“ besteht, gültig bis 29.03.2027; Sitzungs-ID und Konto-UID in /home/webklar/apps/ticket-integrations/enablebanking-session.txt. Finom liefert bei Eingängen Zahlername, IBAN und Verwendungszweck; Kartenzahlungen haben eine Händlerkategorie (MCC). PSD2-Grenzen: etwa 4 automatische Abrufe pro Tag, Umsätze nur der letzten ~90 Tage, Zugang höchstens 180 Tage.
  • Enable Banking ohne Vertrag ist nur zur Erprobung erlaubt („solely for evaluation purposes or for the personal use of private individuals“, Terms → Restriction of Use). Für den Dauerbetrieb braucht es einen Vertrag (support.api@enablebanking.com). Bauen und testen ist gedeckt.
  • Stripe ist im Kundenportal eingerichtet (Live-Schlüssel, Abos per Karte und SEPA-Lastschrift, Rechnungsfuß § 19 UStG). Bisher hat noch keine Stripe-Auszahlung das Finom-Konto erreicht.
  • InvoiceNinja bleibt installiert und unverändert in Betrieb, verschwindet aber aus der WOMS-Oberfläche. Einzige relevante Altrechnung: 0005 (EWV Software GmbH, 168,79 €), am 05.08.2026 per Überweisung bezahlt und in InvoiceNinja seit 01.10.2026 als bezahlt markiert.
  • Die Kundenstammdaten (customers in woms-database) teilen sich WOMS und Portal; Stripe-Kunden hängen über metadata.webklarCustomerId daran (getOrCreateStripeCustomer im Portal).

Entscheidungen (Standardwerte zur Freigabe)

  1. Backend im Kundenportal-Server unter /api/admin/finanzen/*, geschützt mit requireAppwriteStaff (Appwrite-JWT, Label admin, Sitzung mit zweitem Faktor). Dort liegen schon Stripe, Jobs, Mails, Appwrite-Setup-Skripte und Tests. ticket-integrations bleibt unverändert.
  2. Nur Admins sehen „Finanzen“ (Menüpunkt und Seiten). Justin braucht als Gesellschafter das Label admin (Prüfung beim Livegang).
  3. Schlüssel wandert nach /home/webklar/apps/secrets/enablebanking/ und wird schreibgeschützt in den Portal-Container eingebunden. Die Sitzungs-ID steht in Appwrite (ohne Schlüssel wertlos).
  4. Bankdaten werden dauerhaft gespeichert (Collection bankTransactions), weil die Bank nur 90 Tage liefert. Abruf automatisch alle 6 Stunden (4× täglich) und per Knopf „Aktualisieren“ (höchstens alle 10 Minuten; mit PSU-Kopfzeilen, damit es nicht auf das Tageslimit zählt).
  5. Bankzugang erneuern direkt aus WOMS: Knopf → Finom-Freigabe → Rückkehr auf https://ticket.webklar.com/finanzen/bank-callback (bereits als Redirect-URL registriert). Hinweis in „Zu erledigen“ ab 30 Tagen vor Ablauf, Team-Mail (Vorlage T18) bei 30, 14 und 3 Tagen.
  6. Projektrechnungen entstehen in WOMS über Stripe: Kunde wählen, Positionen (Beschreibung, Menge, Einzelpreis), Zahlungsziel 14 Tage, Versand per Stripe-Mail mit PDF und Zahlungsseite. Zahlarten stellt der Nutzer im Stripe-Dashboard ein (Empfehlung: Karte und SEPA-Lastschrift). Im Rechnungsfuß stehen der § 19-Hinweis und die Bankverbindung mit der Bitte, die Rechnungsnummer als Verwendungszweck anzugeben. Entwürfe lassen sich löschen, versendete Rechnungen nur stornieren.
  7. Zahlungsabgleich als Vorschlag mit Bestätigung, nie still automatisch: Eine Bestätigung markiert die Stripe-Rechnung „außerhalb von Stripe bezahlt“ (paid_out_of_band, nicht umkehrbar) und merkt sich die Bankbuchung. Stripe-Auszahlungen werden der Auszahlung zugeordnet (Umbuchung, kein Umsatz). Teilzahlungen werden nicht unterstützt (Hinweis: in Stripe direkt erfassen).
  8. Fixkosten pflegt der Nutzer als Liste (Name, Betrag, Rhythmus, nächstes Datum, Suchbegriff für die Gegenpartei). Erkannte wiederkehrende Ausgaben werden nur vorgeschlagen (mindestens 3 Buchungen).
  9. Vorschau 30 Tage mit zwei Linien: „nur sicher“ (Kontostand, unterwegs befindliche Stripe-Auszahlungen, Fixkosten, ESt-Vorauszahlungen) und „erwartet“ (zusätzlich offene Rechnungen und Abo-Verlängerungen). Kein Konfidenzkegel, eine Annahmezeile in Klartext.
  10. Steuer: Rücklage = einstellbarer Prozentsatz (Standard 30 %) vom Gewinn des laufenden Jahres (Einnahmen minus Ausgaben laut Bank). Kleinunternehmer-Zähler nach Zahlungseingang: laufendes Jahr gegen 25.000 € (Status im Folgejahr) und 100.000 € (harte Grenze), Vorjahr als Einstellung. ESt-Vorauszahlungen als optionale Einstellung (Betrag je Quartal, fällig 10.3./10.6./10.9./10.12.).
  11. Kundenansicht: Der Kunden-Tab „Rechnungen“ zeigt Stripe-Rechnungen des Kunden und „Rechnung schreiben“. Der Ticket-Tab „Rechnungen“ entfällt.
  12. Nicht im Umfang: Kundenportal-Tab „Rechnungen“ (zeigt weiter InvoiceNinja, eigener Folgeschritt), Stundenguthaben je Kunde, Projektmarge, Kundenkonzentration, Angebote, Teilzahlungen, E-Rechnung, Ausgabenbelege, mehrere Bankkonten.

Seitenaufbau (WOMS /finance)

Reihenfolge von oben nach unten; Blöcke 0–2 stehen im ersten Bildschirm. Beträge in de-DE (1.234,50 €, rechtsbündig, tabular-nums), Daten 01.10.2026, Farbe nie allein als Signal.

Nr. Block Inhalt
0 Statuszeile „Bank: Stand vor 3 Std. · jüngste Buchung 29.09. · Zugang bis 29.03.2027“, „Stripe: Stand …“; bei Fehler „Abruf fehlgeschlagen: …“; Knopf „Aktualisieren“
1 Liquiditätskopf Kontostand (gebucht), „Verfügbar nach Rücklagen“ (Kontostand − Steuer-Rücklage − Fixkosten der nächsten 30 Tage − Mindestpuffer; negativ erlaubt; Aufschlüsselung per Klick), „Tiefster Stand in 30 Tagen“ (nur sicher / erwartet, jeweils mit Datum)
2 Zu erledigen Höchstens 7 Zeilen, je mit genau einer Aktion; Leerzustand „Nichts offen“
3 Vorschau 30 Tage Tabelle nach Datum: Posten, sicher/erwartet, Betrag, laufender Saldo beider Linien; Annahmezeile
4 Steuer Rücklage (Betrag, Prozentsatz, Gewinn lfd. Jahr), Kleinunternehmer-Zähler, nächste ESt-Vorauszahlung (falls eingestellt)
5 Abos Monatliche Abo-Einnahmen (aktive + past_due, Jahrespreise ÷ 12), Anzahl zahlender Kunden, gefährdete Einnahmen (past_due), Liste der Abos
6 Rechnungen Filter Offen / Überfällig / Bezahlt / Entwürfe / Alle; Altersgruppen (nicht fällig, 1–30, 31–60, über 60 Tage) mit Summe und Anzahl; Tabelle Nr., Kunde, Datum, Fällig, Betrag, Offen, Status, Aktionen (PDF, Zahlungsseite, Senden, Stornieren, Löschen); Knopf „Rechnung schreiben“
7 Buchungen Alle gespeicherten Buchungen, neueste zuerst, 50 je Seite: Datum, Gegenpartei + Verwendungszweck, Betrag mit Vorzeichen, Zuordnung (Rechnung, Stripe-Auszahlung, Fixkosten, Einnahme ohne Stripe-Rechnung, Privat/Umbuchung, Sonstiges, offen)

Unterseite /finance/einstellungen: Mindestpuffer, Rücklage-Prozentsatz, Vorjahresumsatz, ESt-Vorauszahlung je Quartal, Bankverbindungstext für den Rechnungsfuß, Fixkostenliste, Bankzugang (Status, „Neu verbinden“).

Mobil: Blöcke gestapelt in derselben Reihenfolge, Tabellen als zweizeilige Zeilen.

„Zu erledigen“ – Arten

Art Wann Aktion Dringlichkeit
bank-zugang Zugang läuft in ≤ 30 Tagen ab oder ist abgelaufen Neu verbinden hoch ab ≤ 14 Tagen
bank-abruf Letzter erfolgreicher Abruf älter als 24 h Aktualisieren mittel
ueberfaellig Offene Rechnung, Fälligkeit < heute Zahlungsseite öffnen / Erinnerung über Stripe senden hoch ab 30 Tagen (Verzug)
zuordnung Eingang (Betrag > 0) ohne Zuordnung Zuordnen (mit Vorschlag, falls vorhanden) mittel
abo-zahlung Abo im Status past_due oder unpaid Kunde öffnen hoch
entwurf Rechnungsentwurf älter als 3 Tage Öffnen info
fixkosten-vorschlag Wiederkehrende Ausgabe erkannt, nicht in der Fixkostenliste Übernehmen info
kleinunternehmer Einnahmen lfd. Jahr ≥ 70 % von 25.000 € Einstellungen mittel, hoch ab 100 %

Sortierung: hoch vor mittel vor info, innerhalb nach Datum (älteste zuerst).

Datenmodell (Appwrite, woms-database)

  • bankConnections – ein Dokument finom: status (aktiv | abgelaufen | fehler), sessionId, accountUid, accountIdHash, ibanMasked, iban, validUntil (ISO), lastSyncAt, lastSyncOk (bool), lastError, lastManualSyncAt, balanceBookedCents, balanceAvailableCents, balanceDate, latestBookingDate, authState, authStartedAt, warnedThresholds (z. B. 30,14).
  • bankTransactions – Dokument-ID = erste 36 Zeichen des SHA-256 aus entry_reference (sonst transaction_id, sonst Datum|Betrag|Gegenpartei|Verwendungszweck): accountUid, status (BOOK/PDNG), bookingDate, valueDate (YYYY-MM-DD), amountCents (Integer, Eingang +, Ausgang −), currency, counterpartyName, counterpartyIban, remittance, mcc, entryReference, category (leer | rechnung | rechnung-extern | stripe-auszahlung | fixkosten | privat | sonstiges), matchedInvoiceId, matchedInvoiceNumber, matchedPayoutId, fixedCostId, note, syncedAt.
  • financeSettings – ein Dokument main: minBufferCents (0), taxReservePercent (30), previousYearRevenueCents (leer), estPrepaymentCents (0), bankDetailsText.
  • financeFixedCosts: name, amountCents (positiv), interval (monat | quartal | jahr), nextDate, counterpartyMatch, active.

Stripe-Rechnungen, -Auszahlungen und Abos werden nicht gespiegelt: Rechnungen und Auszahlungen liest der Server live (Zwischenspeicher 60 s, nach eigenen Änderungen geleert), Abos kommen aus der vorhandenen Collection subscriptions.

Schnittstellen (/api/admin/finanzen)

Methode, Pfad Zweck
GET /uebersicht Alles für Blöcke 0–6 in einer Antwort (Status, Kopf, Aufgaben, Vorschau, Steuer, Abos, Rechnungs-Altersgruppen)
GET /rechnungen?status=&kunde= Stripe-Rechnungen (status: offen, ueberfaellig, bezahlt, entwurf, alle)
POST /rechnungen Rechnung anlegen ({ customerId, items, daysUntilDue, memo, senden })
POST /rechnungen/:id/senden Entwurf festschreiben und senden
POST /rechnungen/:id/stornieren Offene Rechnung stornieren
DELETE /rechnungen/:id Entwurf löschen
GET /buchungen Gespeicherte Buchungen (höchstens 1000, neueste zuerst; die Oberfläche blättert in 50ern) mit Zuordnungsvorschlägen für offene Eingänge
POST /buchungen/:id/zuordnen { invoiceId } oder { payoutId } oder { fixedCostId } oder { category }
DELETE /buchungen/:id/zuordnung Zuordnung lösen (nicht bei Rechnungen, die schon als bezahlt markiert sind)
POST /bank/aktualisieren Sofortabruf
POST /bank/verbinden Freigabe starten → { url }
POST /bank/rueckkehr { code, state } oder { error, state } → neue Sitzung
GET, PATCH /einstellungen Einstellungen
GET, POST /fixkosten; PATCH, DELETE /fixkosten/:id Fixkostenliste

Zuordnungsregeln (Vorschläge, höchstens 3 je Buchung)

  1. Stripe-Auszahlung (hoch): Gegenpartei oder Verwendungszweck enthält „stripe“, Betrag = Betrag einer Auszahlung, Buchungsdatum höchstens 3 Tage von arrival_date entfernt.
  2. Rechnungsnummer (hoch / mittel): normalisierter Verwendungszweck (Großbuchstaben, ohne Leerzeichen und Bindestriche) enthält die normalisierte Stripe-Rechnungsnummer. Betrag = offener Betrag → hoch, sonst mittel mit Hinweis „Betrag weicht ab“.
  3. Betrag und Name (mittel): Betrag = offener Betrag und ein Wort (≥ 4 Zeichen, ohne GmbH, UG, GbR, AG, KG, e.K., und, der, die) aus Firmen- oder Kundenname kommt im Zahlernamen vor.
  4. Nur Betrag (niedrig): Betrag = offener Betrag genau einer offenen Rechnung.

Bestätigen mit Rechnung ist nur möglich, wenn der Betrag dem offenen Betrag entspricht.

Fehlerverhalten

  • Bankabruf scheitert: alte Daten bleiben sichtbar und werden als veraltet markiert; lastError in der Statuszeile; kein Datenverlust.
  • Zugang abgelaufen (Enable Banking 401/403 oder Sitzung EXPIRED): Statuszeile + Aufgabe bank-zugang (hoch), Abrufe pausieren bis zur Neuverbindung.
  • Stripe nicht erreichbar: Übersicht liefert Bankteil trotzdem; Stripe-Blöcke zeigen „Stripe nicht erreichbar“.
  • Rückkehr mit falschem state oder Fehler von Finom: Meldung auf der Rückkehrseite, bestehende Sitzung bleibt gültig.

Livegang (Reihenfolge)

  1. Schlüssel nach secrets/enablebanking/ verschieben, Compose: Mount und Umgebungsvariablen ENABLE_BANKING_APP_ID, ENABLE_BANKING_KEY_PATH.
  2. Setup-Skript legt Collections an und übernimmt die bestehende Sitzung (kein neuer Finom-Login).
  3. Portal main pushen (Nutzer-OK), Container neu starten, erster Abruf prüfen.
  4. Stripe-Dashboard: Rechnungsnummern fortlaufend auf Kontoebene, Zahlarten für Rechnungen (Karte, SEPA-Lastschrift) – Nutzer.
  5. WOMS test pushen (Nutzer-OK).
  6. Buchung „Rechnung 0005“ als „Einnahme ohne Stripe-Rechnung“ zuordnen.
  7. Vor dem Dauerbetrieb: Vertrag mit Enable Banking (Nutzer).