From 7b60be2933fc34866464b1fecf891405c9c31250 Mon Sep 17 00:00:00 2001 From: Matthias Mintert Date: Sat, 11 Jul 2026 00:22:16 +0200 Subject: [PATCH] Add architecture spec and implementation plan Transcribe the original architecture PDF into SPEZIFIKATION.md and add UMSETZUNGSPLAN.md with the refined implementation plan (ZUGFeRD on-the-fly invoicing, uv + Docker Compose, grouped Leistungserfassung). Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitignore | 2 + SPEZIFIKATION.md | 101 +++++++++++++++++++++++++++++++++ UMSETZUNGSPLAN.md | 140 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 243 insertions(+) create mode 100644 .gitignore create mode 100644 SPEZIFIKATION.md create mode 100644 UMSETZUNGSPLAN.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cfd0af6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Architektur_Planung_Abrechnungssystem.pdf +.idea/ diff --git a/SPEZIFIKATION.md b/SPEZIFIKATION.md new file mode 100644 index 0000000..aa5d29a --- /dev/null +++ b/SPEZIFIKATION.md @@ -0,0 +1,101 @@ +# System- & Architekturplanung + +**Django Abrechnungssystem für Therapie- und Sozialdienste** + +> Diese Datei ist die Transkription der ursprünglichen Spezifikation +> (`Architektur_Planung_Abrechnungssystem.pdf`) nach Markdown. Verfeinerungen und +> Umsetzungsentscheidungen siehe [`UMSETZUNGSPLAN.md`](./UMSETZUNGSPLAN.md). + +## 1. Projektübersicht & Zielsetzung + +Das zu entwickelnde System ist eine webbasierte Abrechnungs- und Verwaltungssoftware +(basierend auf dem Django-Framework) für ein Unternehmen im therapeutischen oder +sozialen Sektor. Es verwaltet Auftraggeber, Therapeuten, Teilnehmer sowie die +erbrachten Leistungen und generiert daraus monatliche E-Rechnungen. + +**Kernziel:** Komfortable, automatisierte Abrechnung bei gleichzeitiger rechtlicher +Absicherung des Softwareentwicklers. Dies wird durch das „On-the-fly"-Generatorkonzept +erreicht, bei dem keine fertigen Rechnungsdokumente dauerhaft in der Datenbank +gespeichert werden. + +## 2. Rechtliche Rahmenbedingungen + +- **E-Rechnungspflicht (ab 2025):** B2B-Rechnungen in Deutschland erfordern ein + maschinenlesbares Format. Das System nutzt das **ZUGFeRD-Format** (PDF-Dokument mit + eingebetteter XML-Datei nach EN 16931). +- **GoBD & Revisionssicherheit:** Um die Haftung des Entwicklers bezüglich der + Manipulationssicherheit von Rechnungsdokumenten zu minimieren, fungiert die Software + als Konvertierungswerkzeug. Die finale revisionssichere Ablage der exportierten PDFs + obliegt dem Nutzer. +- **Lückenlose Nummernkreise:** Zur Erfüllung der buchhalterischen Pflichten führt das + System ein Metadaten-Journal (`InvoiceLog`), welches die Vergabe fortlaufender + Rechnungsnummern und Stornierungen dokumentiert. + +## 3. Systemarchitektur & Datenmodell + +Das Datenmodell ist so strukturiert, dass es die dreidimensionale Beziehung zwischen +Auftraggebern (Rechnungsempfänger), Teilnehmern (Patienten/Klienten) und Therapeuten +(Leistungserbringer) abbildet. + +| Entität (Model) | Beschreibung & Funktion | +|-------------------|-------------------------| +| **Auftraggeber** | Bezahler der Leistungen (Ämter, Firmen, Privatzahler). Besitzt Stammdaten und empfängt die ZUGFeRD-Rechnungen. | +| **Teilnehmer** | Die Person, die die Dienstleistung in Anspruch nimmt. Ist fest einem Auftraggeber zugeordnet. | +| **Therapeut** | Der Leistungserbringer (z.B. mit Namenskürzel). Basis für die Leistungsstatistiken. | +| **TherapySession**| Das „Logbuch" der erbrachten Leistungen. Speichert Datum, Stundenanzahl und Stundensatz der jeweiligen Therapieeinheit. Verknüpft Therapeut und Teilnehmer. | +| **InvoiceLog** | Das Rechnungsjournal. Speichert keine Dokumente, sondern nur Metadaten: Rechnungsnummer, Datum, Auftraggeber, Gesamtbetrag und den Status (GÜLTIG/STORNIERT). | + +## 4. Kernprozesse + +### 4.1 Leistungserfassung (Dateneingabe) + +Um den Flaschenhals der manuellen Dateneingabe aus Papierlisten zu beseitigen, nutzt +das System Django `ModelFormSets`. Dies ermöglicht eine tabellarische Bulk-Eingabe +(ähnlich Excel), bei der eine Verwaltungskraft mehrere Therapie-Sitzungen für einen +Therapeuten und Monat in einer einzigen Maske erfassen und speichern kann. + +### 4.2 Rechnungslegung (On-the-fly Generierung) + +Der monatliche Abrechnungsprozess verläuft wie folgt: + +- Das System filtert alle nicht abgerechneten `TherapySession`-Einträge eines + Auftraggebers. +- Es zieht die nächste freie Rechnungsnummer und erstellt einen Eintrag im + `InvoiceLog`. +- Die Leistungen werden mit dieser Rechnungsnummer verknüpft (Feld `abgerechnet_mit`). +- Der ZUGFeRD-Generator baut die XML-Struktur auf und bettet sie in ein via WeasyPrint + generiertes PDF ein. +- Das Dokument wird direkt dem Nutzer als Download ausgeliefert oder per E-Mail + versendet, **ohne** als Datei auf dem Server verbleiben zu müssen. + +### 4.3 Storno- und Korrektur-Workflow + +Sollte eine Rechnung fehlerhaft sein (z.B. vergessene Stunden): + +- Die bestehende Rechnung wird im System auf „STORNIERT" gesetzt. +- Ein Storno-Beleg mit neuer, eigener Belegnummer wird generiert. +- Die zugehörigen `TherapySession`-Einträge werden wieder freigegeben (Status „nicht + abgerechnet"). +- Nach Korrektur der Leistungsdaten wird eine neue, reguläre Rechnung mit der nächsten + fortlaufenden Nummer generiert. + +## 5. Controlling & Statistiken + +Das System beinhaltet ein Dashboard für Management und Steuerberater. Die Aggregation +der Daten erfolgt dynamisch über das Django ORM auf Basis der erbrachten Leistungen +(`TherapySession`). So wird eine dreidimensionale Auswertung (Umsatz & Stunden) +realisiert, ohne auf persistente Rechnungsdokumente zurückgreifen zu müssen: + +- **Nach Auftraggeber:** Identifikation der größten Umsatzbringer (z.B. spezifische + Ämter). +- **Nach Teilnehmer:** Übersicht der erbrachten Stunden pro Klient. +- **Nach Therapeut:** Leistungskontrolle und Grundlage für interne Provisionen/ + Gehaltsabrechnungen. + +## 6. Technologiestack + +- **Backend:** Python / Django +- **Datenbank:** PostgreSQL (empfohlen) oder SQLite (für Entwicklung) +- **PDF-Generierung:** WeasyPrint (HTML/CSS zu PDF) +- **E-Rechnung:** factur-x / python-zugferd (für die XML-Struktur) +- **Frontend:** Django Templates, optional django-crispy-forms / django-tables2 \ No newline at end of file diff --git a/UMSETZUNGSPLAN.md b/UMSETZUNGSPLAN.md new file mode 100644 index 0000000..a51c84d --- /dev/null +++ b/UMSETZUNGSPLAN.md @@ -0,0 +1,140 @@ +# Umsetzungsplan: Django ZUGFeRD Abrechnungssystem (Therapie-/Sozialdienste) + +> Ursprüngliche Spezifikation: [`SPEZIFIKATION.md`](./SPEZIFIKATION.md). +> Dieser Plan verfeinert die Spezifikation um die im Interview getroffenen +> Entscheidungen und ist die Grundlage für die Implementierung. + +## Context + +Greenfield-Projekt. Ziel laut [`SPEZIFIKATION.md`](./SPEZIFIKATION.md): eine Django-Web-App, +die Auftraggeber, Therapeuten, Teilnehmer und erbrachte Leistungen verwaltet und daraus +**monatliche deutsche E-Rechnungen on-the-fly** als **ZUGFeRD**-Dokumente erzeugt (PDF/A-3 +mit eingebettetem EN-16931-CII-XML). Zentraler rechtlicher Treiber: keine fertigen +Rechnungsdokumente werden auf dem Server gespeichert (Minimierung der Entwicklerhaftung); +nur ein Metadaten-Journal (`InvoiceLog`) hält einen lückenlosen Nummernkreis und die +Stornohistorie. Dokumente werden dem Nutzer als Download gestreamt. + +**Bestätigte Entscheidungen (aus dem Interview):** +- USt: **USt-frei nach §4 UStG** → CII-Steuerkategorie `E`, 0%, mit Befreiungsgrund-Text. +- Rechnungslayout: **sauberes, an DIN 5008 orientiertes Template** mit Platzhalter-Logo/ + Firmendaten. +- Umfang: **vollständiges End-to-End-MVP**. +- Auth: **einfacher Django-Login**, wenige interne Nutzer. + +## Tech stack +- Django 5.2 LTS, PostgreSQL, Settings über `django-environ`. +- **Packaging & Runtime: `uv`** (`pyproject.toml` + `uv.lock`, `uv sync` / `uv run`). +- **Containerisiert: Docker Compose** (`web` + `db` Postgres). +- **WeasyPrint** (HTML/CSS → PDF, `pdf_variant='pdf/a-3b'`). +- **drafthorse** (EN-16931-CII-XML bauen) + **factur-x** (XML einbetten → ZUGFeRD PDF/A-3). +- django-crispy-forms + crispy-bootstrap5, django-tables2 (lt. Spezifikation). +- pytest-django für Tests. +- ⚠️ Python-Version in `pyproject.toml` auf **3.13** pinnen (Django 5.2/WeasyPrint auf + 3.12/3.13 validiert; Host hat 3.14). `uv` stellt 3.13 unabhängig vom Host bereit und + umgeht so das Kompatibilitätsrisiko. + +## Docker & uv +- **`pyproject.toml`** deklariert alle Deps; `uv.lock` pinnt sie; `.python-version` → 3.13. +- **`Dockerfile`** (multi-stage): Builder installiert Deps via `uv sync --frozen`; Runtime + ist ein schlankes Python-3.13-Image plus WeasyPrint-Systembibliotheken (`libpango-1.0-0`, + `libpangocairo-1.0-0`, `libcairo2`, `libgdk-pixbuf-2.0-0`, Fonts). Start via + `uv run gunicorn`. `.dockerignore` schließt venv/pyc/git aus. +- **`docker-compose.yml`**: `db` (postgres:16, named volume) + `web` (build ., depends_on + db, `.env` für Secrets/DB-URL, Port 8000). `entrypoint.sh` wartet auf DB → `migrate` → + `collectstatic` → startet gunicorn (bzw. `runserver` im Dev-Override). +- **`docker-compose.override.yml`** (Dev): Source bind-mount, `runserver`, DEBUG=1. + +## Project structure (single app for simplicity) +``` +manage.py, pyproject.toml, uv.lock, .python-version, .env(.example) +Dockerfile, .dockerignore, docker-compose.yml, docker-compose.override.yml, entrypoint.sh +config/ settings.py, urls.py, wsgi.py +abrechnung/ + models.py admin.py forms.py + views.py # entry, invoicing, storno, dashboard views + services/numbering.py # gapless invoice-number allocation + services/invoicing.py # on-the-fly generation + storno orchestration + services/zugferd.py # HTML->PDF/A-3 + CII XML build + embed + templates/abrechnung/ # invoice.html (WeasyPrint) + UI pages + tests/ +templates/base.html static/ +``` + +## Data model (`abrechnung/models.py`) +- **Firma** (seller singleton): name, Anschrift, USt-IdNr/Steuernr, IBAN/BIC, Kontakt, + Logo, `ust_befreiungsgrund` (default §4-text). Used for ZUGFeRD Seller + header. +- **Auftraggeber** (buyer/payer): name, Anschrift, Typ (Amt/Firma/Privat), optional + USt-IdNr, E-Mail, optional Leitweg-ID. → ZUGFeRD Buyer. +- **Teilnehmer**: Name, optional Aktenzeichen/Geburtsdatum, FK → Auftraggeber, + **FK → Therapeut (zugeordneter Therapeut)**. Each Teilnehmer has one assigned + therapist; drives the grouped capture flow and prefills `Leistung.therapeut`. +- **Therapeut**: Name, Kürzel, `aktiv`. +- **Leistungsart** (admin-configurable): `bezeichnung` (z.B. Sitzung, Vorbereitung, + Elterngespräch, Beratung), optional `standard_stundensatz`, `aktiv`. Used to prefill + rates and to group statistics. +- **Leistung** (renamed from `TherapySession` — covers sessions *and* preparation, + Elterngespräche, Beratung etc.): FK Therapeut, FK Teilnehmer, FK Leistungsart, + `datum`, `stundenanzahl` (Decimal), `stundensatz` (Decimal, prefilled from + Leistungsart), optional Beschreibung, `abgerechnet_mit` (FK InvoiceLog, null) = + billing link. Auftraggeber derived via Teilnehmer. +- **InvoiceLog** (journal, no documents): `rechnungsnummer` (unique), `datum`, + FK Auftraggeber, `gesamtbetrag`, `status` (GÜLTIG/STORNIERT), `typ` + (RECHNUNG/STORNO), `storno_von` (self-FK), `created_at`, `created_by`. + +## Core processes +1. **Leistungserfassung — grouped by Therapeut → Monatsblatt pro Teilnehmer (Spez. 4.1)** + — the primary Excel-like capture view. **Higher level:** select **Therapeut + Monat**. + The page then renders one **collapsible Monatsblatt section per Teilnehmer** assigned + to that Therapeut. Each section is a spreadsheet-style, row-per-entry editable table + (`modelformset_factory(Leistung)`) with columns **Datum · Leistungsart · Stunden · + Satz · Beschreibung** (Therapeut is the group context, so no per-row Therapeut column; + `Leistung.therapeut` is set from the section). Stundensatz prefilled from the chosen + Leistungsart; add/remove rows, keyboard-friendly cell nav, live per-Teilnehmer and + per-Therapeut monthly totals. Existing entries for that Therapeut+Monat pre-load for + editing. Lightweight formset + minimal JS (row cloning, tab/enter nav, total calc); + no heavy grid dependency. +2. **Rechnungslegung (on-the-fly, Spez. 4.2)** — `services/invoicing.py` in a single + `transaction.atomic`: filter unbilled Leistungen of an Auftraggeber → allocate next + number (`numbering.py`, `select_for_update` on a counter row → guaranteed gapless) + → create `InvoiceLog` → set `abgerechnet_mit` on Leistungen → `zugferd.py` renders + `invoice.html` via WeasyPrint to PDF/A-3b, drafthorse builds CII XML (category `E`, + 0%, exemption reason), factur-x embeds it → `FileResponse` download, **nothing + persisted to disk**. +3. **Storno/Korrektur (Spez. 4.3)** — set original `status=STORNIERT`; create STORNO + `InvoiceLog` (new number, references original, negative total) + generate its + ZUGFeRD doc; release Leistungen (`abgerechnet_mit=NULL`) so a corrected regular + invoice can be re-billed. +4. **Dashboard (Spez. 5)** — ORM aggregation over `Leistung` + (`annotate`/`Sum` of hours & hours×rate) grouped **by Auftraggeber / Teilnehmer / + Therapeut** (+ optional by Leistungsart), with month filter; rendered via + django-tables2. + +## Auth +Django built-in auth, `LoginRequiredMixin` on all views, a login template; users +created via admin/superuser. No custom roles for MVP. + +## Files to create (primary) +`config/settings.py`, `config/urls.py`; `abrechnung/models.py`, `admin.py`, `forms.py`, +`views.py`, `services/{numbering,invoicing,zugferd}.py`, +`templates/abrechnung/invoice.html` + UI templates (incl. `monatsblatt.html` capture +grid), `templates/base.html`, `static/abrechnung/monatsblatt.js` (row cloning / cell +nav / live totals), `abrechnung/tests/*`, `pyproject.toml` + `uv.lock`, `.env.example`, +`Dockerfile`, `docker-compose.yml`(+override), `entrypoint.sh`, `.dockerignore`. + +## Verification +- `pytest` covering: gapless numbering under concurrency (`select_for_update`), + invoice generation returns a PDF whose embedded XML parses as valid EN 16931 CII + (category E, exemption reason present, totals match `InvoiceLog`), storno releases + Leistungen & writes correct journal entries, dashboard aggregation numbers. +- `docker compose up` builds and runs cleanly (web + db); entrypoint migrates. +- Manual E2E: create superuser + Firma/Auftraggeber/Therapeut/Teilnehmer (assigned to a + Therapeut)/Leistungsart → open Leistungserfassung for that **Therapeut + Monat** → + capture Leistungen across their Teilnehmer sections (grid) → generate invoice → + download PDF → validate ZUGFeRD (`facturx-check` / mustangproject) → run storno → + confirm Leistungen freed → open dashboard. + +## Assumptions (flag if wrong) +- Single-tenant (one billing Firma). Invoice currency EUR. All MVP services USt-frei + (no mixed-rate invoices). ZUGFeRD profile: EN 16931 (COMFORT). +- Each Teilnehmer has exactly one assigned Therapeut; a Leistung's therapist is that + assigned Therapeut (no per-row substitution in the MVP grid). \ No newline at end of file