Der Versionshinweis im README stand noch auf 0.1.0 und wird mitgezogen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fj7mB1PGA1aDHyfdSGHgzD
256 lines
11 KiB
Markdown
256 lines
11 KiB
Markdown
# moneyfy
|
||
|
||
Selbst gehostete Web-Anwendung zur Planung monatlicher Kosten und Einkünfte.
|
||
Single-User, deutschsprachige Oberfläche, ausgelegt für den Betrieb im Homelab
|
||
hinter einem Reverse Proxy.
|
||
|
||
> **Version 0.1.2.** Siehe [CHANGELOG.md](CHANGELOG.md).
|
||
|
||
## Screenshots
|
||
|
||
Die Bilder entstehen aus dem Demo-Datensatz (`make seed-demo`), damit sie ohne
|
||
echte Kontostände auskommen. Ablegen unter `docs/screenshots/`.
|
||
|
||
| | |
|
||
|---|---|
|
||
| <br>**Dashboard** – verfügbar nach Fixkosten, Kategorienring, Zwölf-Monats-Vorschau | <br>**Cashflow-Kalender** – Fälligkeiten je Tag mit Firmenlogo, darunter der Kontostandsverlauf |
|
||
| <br>**Wiederkehrend** – Abos, Verträge und Raten mit Klartext-Rhythmus | <br>**RRULE-Editor** – geführte Auswahl, Expertenmodus und Live-Vorschau der nächsten Termine |
|
||
| <br>**Firmen** – Kachelgrid mit Logo, Markenfarbe und Jahreskosten | <br>**Auswertungen** – laufende Kosten, Jahresvergleich und Export |
|
||
|
||
## Funktionsumfang
|
||
|
||
- Wiederkehrende Zahlungen und Einkünfte über vollständige RFC-5545-RRULEs,
|
||
inklusive Werktagsverschiebung nach NRW-Feiertagen.
|
||
- Preishistorie je Vertrag, sodass vergangene Monate betragstreu bleiben.
|
||
- Verträge mit Mindestlaufzeit, Kündigungsfrist und automatischer Verlängerung.
|
||
- Ratenzahlungen mit Restschuld- und Restratenberechnung.
|
||
- Rücklagenbildung für nicht-monatliche Posten.
|
||
- Firmenlogos und Markenfarben, lokal zwischengespeichert (siehe unten).
|
||
- Einstellbarer Monatsbeginn: Der Monat startet wahlweise am Ersten oder am
|
||
Gehaltstag; alle Monatsansichten folgen diesem Zeitraum (siehe unten).
|
||
- Auswertungen: Monatsübersicht, Cashflow-Kalender, 12-Monats-Forecast,
|
||
Kategorien, Abo-Übersicht, Jahresvergleich, Budgets, Sparziele.
|
||
- Benachrichtigungen per SMTP und Apprise.
|
||
|
||
## Technischer Stack
|
||
|
||
| Bereich | Technologie |
|
||
|---|---|
|
||
| Backend | Python 3.12, FastAPI, SQLAlchemy 2 (async), Alembic, Pydantic v2 |
|
||
| Datenbank | PostgreSQL 17 |
|
||
| Scheduler | APScheduler (in-process) |
|
||
| Frontend | React 18, TypeScript, Vite, TailwindCSS |
|
||
| Charts | Recharts |
|
||
| Tests | pytest + httpx, vitest + testing-library |
|
||
|
||
## Schnellstart (Betrieb)
|
||
|
||
Voraussetzungen: Docker mit Compose-Plugin und ein externes Netz `proxy`.
|
||
|
||
```bash
|
||
mkdir -p /opt/moneyfy && cd /opt/moneyfy
|
||
curl -fsSLO https://git.menzel.center/menzeljonas/moneyfy/raw/branch/main/docker-compose.yml
|
||
curl -fsSLO https://git.menzel.center/menzeljonas/moneyfy/raw/branch/main/.env.example
|
||
cp .env.example .env
|
||
|
||
# Mindestens setzen: POSTGRES_PASSWORD, SECRET_KEY, MONEYFY_ADMIN_PASSWORD
|
||
$EDITOR .env
|
||
|
||
docker login git.menzel.center
|
||
docker compose up -d
|
||
```
|
||
|
||
Das Backend wartet auf die Datenbank, spielt die Migrationen ein und legt beim
|
||
allerersten Start den Kategoriebaum an. Danach läuft die Oberfläche auf
|
||
`http://127.0.0.1:8087`. Die vollständige Einrichtung samt Reverse Proxy und
|
||
Sicherung steht in [docs/deployment.md](docs/deployment.md).
|
||
|
||
## Schnellstart (lokale Entwicklung)
|
||
|
||
Voraussetzungen: Python 3.12, [uv](https://docs.astral.sh/uv/), Node 20+,
|
||
eine erreichbare PostgreSQL-17-Instanz.
|
||
|
||
```bash
|
||
git clone https://git.menzel.center/menzeljonas/moneyfy.git
|
||
cd moneyfy
|
||
cp .env.example backend/.env # DATABASE_URL und SECRET_KEY anpassen
|
||
|
||
make install # Backend-Abhängigkeiten in backend/.venv
|
||
make migrate # Schema anlegen
|
||
make seed # Kategoriebaum und Standardregeln
|
||
make dev # http://localhost:8000/api/docs
|
||
```
|
||
|
||
In einem zweiten Terminal das Frontend, es leitet `/api` an das Backend weiter:
|
||
|
||
```bash
|
||
make fe-install
|
||
make fe-dev # http://localhost:5173
|
||
```
|
||
|
||
Für einen gefüllten Stand:
|
||
|
||
```bash
|
||
make seed-demo # realistischer Beispielhaushalt, Termine relativ zu heute
|
||
make seed-demo-reset # setzt Konten, Firmen, Posten und Buchungen vorher zurück
|
||
```
|
||
|
||
Der Demo-Seed legt drei Konten an, zehn Firmen samt Logos, zwölf wiederkehrende
|
||
Posten (Miete, Strom, Netflix, Spotify, jährliche Kfz-Versicherung, Handyvertrag
|
||
mit Mindestlaufzeit, Kredit über 36 Raten, Gehalt), bestätigt die vergangenen
|
||
Fälligkeiten, ergänzt Buchungen, Budgets und Sparziele.
|
||
|
||
Nützliche Ziele: `make check` (alle Prüfungen), `make test`, `make lint`,
|
||
`make format`, `make fe-lint`, `make fe-test`, `make fe-build`.
|
||
|
||
## Bedienung
|
||
|
||
| Kürzel | Wirkung |
|
||
|---|---|
|
||
| `n` | Neue Buchung |
|
||
| `r` | Neuer wiederkehrender Posten |
|
||
| `/` | Suchfeld der aktuellen Seite |
|
||
| `?` | Übersicht der Kürzel |
|
||
| `Esc` | Dialog schließen |
|
||
|
||
Kürzel greifen nur, solange kein Eingabefeld den Fokus hat.
|
||
|
||
### Monatsbeginn
|
||
|
||
Standardmäßig ist ein Monat der Kalendermonat. Wer nach dem Gehaltseingang
|
||
plant, stellt unter *Einstellungen › Monatsbeginn* den Tag ein, ab dem ein neuer
|
||
Monat zählt. Der Zeitraum läuft dann vom Gehaltstag bis zum Vortag des nächsten
|
||
und heißt nach dem Monat, in dem er beginnt: Mit dem 25. als Beginn umfasst
|
||
„September 2026“ die Tage vom 25.09. bis zum 24.10.
|
||
|
||
Ein Starttag jenseits der Monatslänge rutscht auf den Monatsletzten – die 31
|
||
bedeutet also verlässlich „letzter Tag des Monats“, auch im Februar.
|
||
|
||
Dashboard, Cashflow-Kalender, Budgets, Zwölf-Monats-Vorschau, die
|
||
Kategorienauswertung, der Monatsexport und die Benachrichtigung über
|
||
überschrittene Budgets rechnen mit diesem Zeitraum. Budgets bleiben dabei je
|
||
Monat gepflegt; der Bezeichner ist weiterhin der Monatserste, nur der Schnitt
|
||
verschiebt sich. Die Einstellung gilt für die gesamte Installation und wirkt
|
||
sofort, ohne die Daten anzufassen.
|
||
|
||
## Barrierefreiheit
|
||
|
||
Die Farbtoken sind gegen die WCAG-Kontraste geprüft – in beiden Modi erreicht
|
||
jeder Text mindestens 4,5:1, jedes Bedienelement 3:1. Zwei Werte weichen
|
||
deshalb bewusst von den naheliegenden Tailwind-Stufen ab: `slate-400` als
|
||
Hilfstext käme auf hellem Grund nur auf 2,56:1, und Weiß auf `green-600` nur
|
||
auf 3,30:1.
|
||
|
||
Die Serienfarben der Diagramme sind zusätzlich auf Farbfehlsichtigkeit geprüft
|
||
(siehe [Diagramme](#diagramme)). Jeder Zustand – Budget-Ampel, Versandstatus,
|
||
Vertragswarnung – trägt neben der Farbe ein Symbol und einen Wortlaut.
|
||
|
||
## Konfiguration
|
||
|
||
Alle Einstellungen kommen aus Umgebungsvariablen; `.env.example` enthält die
|
||
vollständige, kommentierte Liste. Die wichtigsten:
|
||
|
||
| Variable | Standard | Bedeutung |
|
||
|---|---|---|
|
||
| `DATABASE_URL` | – | PostgreSQL-DSN, `postgresql://` wird auf asyncpg umgestellt |
|
||
| `SECRET_KEY` | – | Signaturschlüssel für JWTs, mind. 32 Zeichen (`openssl rand -hex 32`) |
|
||
| `MONEYFY_ADMIN_USER` / `MONEYFY_ADMIN_PASSWORD` | `admin` / – | Beim Erststart angelegter Benutzer. Es gibt **kein** eingebautes Standardpasswort; ohne diesen Wert wird niemand angelegt. Die Anlage greift nur, solange die Benutzertabelle leer ist – danach hilft `app.scripts.reset_password`. |
|
||
| `TIMEZONE` | `Europe/Berlin` | Zeitzone der gesamten Anwendung |
|
||
| `HOLIDAY_REGION` | `DE-NW` | Feiertagsregion für Werktagsverschiebungen |
|
||
| `LOGO_STORAGE_DIR` | `/data/logos` | Verzeichnis des Logo-Caches |
|
||
| `LOGODEV_API_KEY` / `BRANDFETCH_API_KEY` | leer | Optionale Logo-Provider, ohne Schlüssel übersprungen |
|
||
| `SMTP_*` | leer | Mailversand für Benachrichtigungen |
|
||
| `APPRISE_URLS` | leer | Komma-separierte Apprise-Ziele |
|
||
| `COOKIE_SECURE` | `true` | Hinter HTTPS `true`, für lokales HTTP `false` |
|
||
| `SCHEDULER_ENABLED` | `true` | Täglicher Benachrichtigungslauf um 07:00 |
|
||
|
||
## Benachrichtigungen
|
||
|
||
Ein Job läuft täglich um 07:00 Uhr (`Europe/Berlin`) im Anwendungsprozess – ohne
|
||
Redis, ohne Celery. Er prüft vier Anlässe: bald fällige Posten, Kündigungsfristen
|
||
30, 14 und 7 Tage vor dem Termin, überschrittene Budgets und Vertragsverlängerungen
|
||
im kommenden Monat.
|
||
|
||
Der Duplikatsschutz hängt am **Zieltag des Ereignisses**, nicht am Versandtag:
|
||
Eine Fälligkeit am 15.03. wird genau einmal gemeldet, unabhängig davon, an
|
||
welchem Tag des Vorlaufs der Job läuft. Ein fehlgeschlagener Versand gilt als
|
||
offen und wird beim nächsten Lauf erneut versucht.
|
||
|
||
Als Kanäle stehen SMTP (HTML-Mail mit eingebetteten Firmenlogos) und Apprise zur
|
||
Verfügung. Über *Einstellungen → Benachrichtigungen* lassen sich Regeln pflegen,
|
||
eine Testnachricht verschicken und das Versandprotokoll einsehen.
|
||
|
||
## Diagramme
|
||
|
||
Die Serienfarben stammen aus einer Palette, die gegen die hellen und dunklen
|
||
Flächen der Anwendung auf Kontrast und Farbfehlsichtigkeit geprüft wurde; ihre
|
||
Reihenfolge ist Teil dieser Absicherung. Grün und Rot bleiben den Vorzeichen im
|
||
Text vorbehalten – als Serienpaar wären sie bei Deuteranopie nicht zu
|
||
unterscheiden. Jedes Diagramm mit mehr als einer Serie führt eine Legende, und
|
||
zu den Balkendiagrammen lässt sich die Wertetabelle aufklappen.
|
||
|
||
Einnahmen, Ausgaben und der kumulierte Kontostand stehen bewusst in zwei
|
||
getrennten Diagrammen untereinander: eine gemeinsame Größenachse würde bei so
|
||
verschiedenen Größenordnungen einen Zusammenhang vortäuschen.
|
||
|
||
## Logos und Markenfarben
|
||
|
||
Jede Firma bekommt ein Bild. Die Provider-Kette arbeitet der Reihe nach:
|
||
|
||
1. **simple-icons** – rund 3.500 Marken, im Repository unter
|
||
`backend/app/assets/simple_icons.json.gz` mitgeliefert. Kein Schlüssel, kein
|
||
Netzzugriff. Neu erzeugen mit `make vendor-icons`.
|
||
2. **logo.dev** – nur mit `LOGODEV_API_KEY`.
|
||
3. **Brandfetch** – nur mit `BRANDFETCH_API_KEY` und hinterlegter Domain.
|
||
4. **Favicon** – über den Google-Dienst, wenn eine Domain bekannt oder aus dem
|
||
Namen ableitbar ist.
|
||
5. **Generierter Avatar** – Buchstaben-Monogramm mit fester Farbe aus dem
|
||
Namens-Hash. Schlägt nie fehl.
|
||
|
||
Findet die Kette einen eindeutigen Treffer im lokalen Katalog, unterbleiben die
|
||
Anfragen nach außen vollständig – die Firmenliste verlässt den Server nicht. Im
|
||
Auswahldialog (`POST /api/merchants/{id}/logo/search`) läuft dagegen bewusst die
|
||
ganze Kette, damit Alternativen zur Wahl stehen.
|
||
|
||
Ausgewählte Logos landen unter `LOGO_STORAGE_DIR`, benannt nach ihrem
|
||
SHA-256-Hash, und werden nur über `GET /api/logos/{id}` ausgeliefert. Beim
|
||
Seitenaufruf entsteht dadurch kein Zugriff auf fremde Dienste. Ein Upload oder
|
||
eine bewusste Auswahl setzt den Status auf `manual` und wird von der Automatik
|
||
nie überschrieben.
|
||
|
||
## Projektstruktur
|
||
|
||
```
|
||
backend/ FastAPI-Anwendung, Modelle, Services, Alembic-Migrationen, Tests
|
||
frontend/ React-Oberfläche (Vite)
|
||
docs/ Betriebs- und Runner-Dokumentation
|
||
.gitea/ Gitea-Actions-Workflows
|
||
```
|
||
|
||
Alternativ lässt sich alles aus dem Quellcode bauen:
|
||
|
||
```bash
|
||
cp docker-compose.override.yml.example docker-compose.override.yml
|
||
docker compose up -d --build
|
||
```
|
||
|
||
Die Override-Datei baut beide Images lokal, bindet den Quellcode ein und öffnet
|
||
die Ports für Backend (8000) und Vite (5173).
|
||
|
||
## Bauen und Ausrollen
|
||
|
||
Zwei Gitea-Workflows unter `.gitea/workflows/`:
|
||
|
||
- **`ci.yml`** läuft bei jedem Push und Pull Request: `ruff`, `pytest`,
|
||
`tsc --noEmit`, `eslint`, `vitest` und der Bundle-Bau.
|
||
- **`build.yml`** läuft auf `main` und bei Tags `v*` und lädt beide Images für
|
||
`linux/amd64` in die Gitea-Registry. Push auf `main` erzeugt `:main` und
|
||
`:sha-<kurz>`, ein Tag `v1.2.3` zusätzlich `:1.2.3`, `:1.2` und `:latest`.
|
||
|
||
Ausgerollt wird **nicht** automatisch. Die Einrichtung des Runners beschreibt
|
||
[docs/runner.md](docs/runner.md), das Ausrollen [docs/deployment.md](docs/deployment.md).
|
||
|
||
## Lizenz
|
||
|
||
[MIT](LICENSE)
|