Finanzen: Spec und Plaene 1-3 (Bank, Stripe/Auswertung, WOMS/Livegang)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
30412cb0ea
commit
53b50011e8
4 files changed
+870
No files matched your search
@@ -0,0 +1,196 @@
|
||||
# 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).
|
||||
Reference in new issue
Block a user