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) <noreply@anthropic.com>
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Architektur_Planung_Abrechnungssystem.pdf
|
||||||
|
.idea/
|
||||||
101
SPEZIFIKATION.md
Normal file
101
SPEZIFIKATION.md
Normal file
@@ -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
|
||||||
140
UMSETZUNGSPLAN.md
Normal file
140
UMSETZUNGSPLAN.md
Normal file
@@ -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).
|
||||||
Reference in New Issue
Block a user