Files
duffyduckandClaude Opus 5 b343e89fbf Belege zu Buchungen: Durchsuchen, Ziehen-und-Ablegen, Einfügen
Zu jeder Buchung lässt sich ein Beleg hinterlegen, im Formular oder
nachträglich über die Buchungsliste.

Der Medientyp wird aus der Dateisignatur bestimmt, nicht aus Endung oder
Browserangabe: eine als rechnung.pdf benannte Textdatei wird abgewiesen. SVG ist
ausgeschlossen, weil es Skripte enthalten kann, die beim Anzeigen ausgeführt
würden. Ausgeliefert wird mit nosniff und enger Content-Security-Policy.

Zur Aufbewahrungspflicht nach §147 AO werden Belege nie überschrieben und nie
gelöscht. Beim Ersetzen bleibt die alte Datei liegen; das Lösen der Verknüpfung
entfernt nur den Verweis. Der Dateiname trägt den SHA-256-Anfang, die Buchung
den vollständigen Hash.

Ablage unter data/belege/<mandant>/<jahr>/ – je Mandant getrennt und im selben
Bind-Mount wie die Datenbanken, sodass ein Backup des data-Ordners Buchungen und
Belege gemeinsam erfasst.

Bisher fehlte jede Möglichkeit, bestehende Mandantendateien um neue Spalten zu
erweitern: CREATE TABLE IF NOT EXISTS lässt vorhandene Tabellen unverändert. Neu
hinzugekommene Spalten werden jetzt beim Öffnen additiv ergänzt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 22:28:16 +02:00

344 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# open-eur-webapp
Einnahmen-Überschuss-Rechnung nach §4 Abs. 3 EStG für Einzelunternehmen und
Freiberufler mit Kleinunternehmerregelung, Umsatzsteuer-Voranmeldung,
BWA, ELSTER-Anbindung und DATEV-/GoBD-Export.
Die Anwendung läuft lokal: ein Node-Prozess, eine SQLite-Datei, keine
Cloud-Anbindung.
```bash
npm install
npm run seed # Beispieldaten mit Regimewechsel 2025 -> 2026
npm run dev # Backend (3311) + Frontend (5173) im Entwicklungsmodus
```
Für den Dauerbetrieb:
```bash
npm run build
npm start # liefert API und Oberfläche auf http://127.0.0.1:3311
```
`npm test` prüft die Steuerlogik (Regimewechsel, EÜR, UStVA, AfA,
Steuernummernumsetzung).
`npm run seed` bricht ab, wenn bereits Buchungen vorhanden sind sonst würden
die Beispieldaten ein zweites Mal eingespielt und alle Auswertungen verdoppeln.
`npm run seed -- reset` setzt die Bewegungsdaten zurück und legt sie neu an;
dabei gehen auch selbst erfasste Buchungen verloren.
## Betrieb mit Docker
```bash
cp .env.beispiel .env # optional, für ELSTER
export UID GID=$(id -g) # damit ./data dem eigenen Konto gehört
docker-compose up -d --build
```
Danach läuft die Anwendung auf <http://localhost:3311>.
**Keine benannten Volumes.** Der einzige Mount ist `./data:/app/data` die
Buchhaltung liegt als gewöhnliche SQLite-Dateien im Projektverzeichnis und lässt
sich sichern, kopieren und einsehen, ohne Docker zu bemühen. Der Container läuft
mit der UID des aufrufenden Kontos, deshalb gehören die Dateien nicht root.
Weitere Festlegungen:
- **Port nur auf 127.0.0.1 veröffentlicht.** Die Anwendung kennt keine
Benutzeranmeldung, und hier liegen Steuernummer und Umsätze. Für den Zugriff
aus dem Netz gehört ein Reverse Proxy mit Authentifizierung davor.
- Das Dateisystem des Containers ist schreibgeschützt (`read_only`), beschreibbar
ist nur `/app/data` und ein `tmpfs` unter `/tmp`.
- `.env` wird **nicht** ins Image gebacken. `docker-compose` liest die Datei für
die Variablenersetzung und reicht die Werte als Umgebungsvariablen weiter.
- **ERiC ist nicht enthalten** und darf es auch nicht sein. Wer echt übermitteln
will, hebt in `docker-compose.yml` die Zeilen für `/opt/eric` und das
Zertifikat auf und setzt die Pfade in `.env`.
Beim Start meldet der Container, woher die Konfiguration stammt und ob ERiC
eingebunden ist:
```
open-eur-webapp läuft auf http://localhost:3311
Konfiguration aus der Prozessumgebung.
ELSTER: Simulationsmodus, es wird nichts übermittelt (…).
Mandanten: standard aktiv ohne Angabe: standard
```
Ohne Docker bindet sich der Server weiterhin an `127.0.0.1`; im Container setzt
`HOST=0.0.0.0`, weil die Portfreigabe den Prozess sonst nicht erreicht.
## Mandanten
Jeder Mandant hat **eine eigene SQLite-Datei** unter `data/mandanten/`. Das ist
Absicht: bei einer gemeinsamen Datenbank mit `mandant_id`-Spalte genügt ein
einziges vergessenes `WHERE`, damit Testbuchungen in der echten EÜR oder in
einer ELSTER-Übermittlung auftauchen. Getrennte Dateien machen das strukturell
unmöglich.
Umgeschaltet wird oben in der Seitenleiste. Die Anwendung lädt dabei neu, damit
keine Formularstände des vorherigen Mandanten stehen bleiben.
**Testmandanten** sind als solche markiert, auf jeder Seite sichtbar
gekennzeichnet und **können nichts an das Finanzamt übermitteln** es sei denn,
`ELSTER_TESTMERKER` ist gesetzt, dann sind Probeübermittlungen ohne steuerliche
Wirkung möglich.
```bash
npm run seed -- --mandant=test # legt einen Testmandanten an und befüllt ihn
```
Technisch hängt der Mandant am Request: Header `X-Mandant`, für Download-Links
(die keine Header setzen können) alternativ `?mandant=<bezeichner>`. Der Server
führt den Handler in einem AsyncLocalStorage-Kontext aus, sodass jeder
Datenbankzugriff automatisch auf der richtigen Datei landet.
Beim Entfernen eines Mandanten wird die Datei **nicht gelöscht**, sondern mit
Zeitstempel beiseitegelegt (`…​.sqlite.entfernt-<zeit>`).
## Kontenrahmen: SKR03 und SKR04
Beide stehen zur Wahl **beim Anlegen eines Mandanten**, danach nicht mehr.
Kontonummern und Bezeichnungen stammen aus den amtlichen DATEV-Kontenrahmen
(Stand 2026); die Zuordnung zu EÜR-Posten, BWA-Gruppen und UStVA-Kennzahlen ist
fachliche Auslegung dieser Anwendung und gehört vor dem ersten Export mit der
Steuerkanzlei abgestimmt.
Der Wechsel ist gesperrt, weil dieselbe Kontonummer in beiden Rahmen etwas
anderes bedeutet:
| Konto | SKR03 | SKR04 |
|---|---|---|
| 1800 | Privatentnahmen allgemein | **Bank** |
| 2100 | Zinsen und ähnliche Aufwendungen | Privatentnahmen allgemein |
| 4830 | Abschreibungen auf Sachanlagen | Sonstige betriebliche Erträge |
| 4970 | Nebenkosten des Geldverkehrs | Zinserträge |
Aus demselben Grund ist das Standard-Gegenkonto rahmenabhängig (SKR03 `1200`,
SKR04 `1800`) und das Feld `kontenrahmen` in den Stammdaten schreibgeschützt
sonst könnte der DATEV-Export einen Rahmen melden, den die Buchungszeilen nicht
verwenden.
## Belege
Zu jeder Buchung lässt sich ein Beleg hinterlegen per Durchsuchen-Schaltfläche,
Ziehen-und-Ablegen oder <kbd>Strg</kbd>+<kbd>V</kbd>. Nachträglich geht es über
die Schaltfläche „+ Beleg" in der Buchungsliste.
**Zum Einfügen mit Strg+V:** Screenshots liegen als Bild in der Zwischenablage
und funktionieren überall. Beim Kopieren einer *Datei* im Dateimanager hängt es
vom System ab: Windows und macOS legen die Datei selbst ab, dann klappt es auch
für PDFs. Unter Linux liefern viele Dateimanager nur einen `file://`-Verweis, den
der Browser aus Sicherheitsgründen nicht lesen darf dort bleibt
Ziehen-und-Ablegen der verlässliche Weg für PDFs.
Zulässig sind PDF, JPEG, PNG, WebP, TIFF und HEIC bis 20 MB. **Der Typ wird aus
der Dateisignatur bestimmt, nicht aus Endung oder Browserangabe** eine als
`rechnung.pdf` benannte Textdatei wird abgewiesen. SVG ist ausgeschlossen, weil
es Skripte enthalten kann, die beim Anzeigen ausgeführt würden.
Die Dateien liegen unter `data/belege/<mandant>/<jahr>/` und damit im selben
Bind-Mount wie die Datenbanken; ein Backup des `data`-Ordners erfasst Buchungen
und Belege gemeinsam. Zur Aufbewahrungspflicht (§147 AO): Belege werden nie
überschrieben und nie gelöscht. Wird ein Beleg ersetzt oder die Verknüpfung
gelöst, bleibt die Datei erhalten der Dateiname enthält den SHA-256-Anfang, die
Buchung den vollständigen Hash.
## Anlagevermögen oder Sofortabzug
Ob eine Ausgabe abzuschreiben ist, entscheidet das **Konto** (Kontotyp `anlage`),
nicht der Betrag eine Werbekampagne für 3.000 € bleibt sofort abziehbar, ein
Drucker für 900 € nicht. Eine Buchung auf ein Anlagekonto wird deshalb immer
aktiviert; ein stiller Vollabzug ist nicht möglich.
Der Betrag entscheidet erst über die **Methode** (bei Kleinunternehmern zählt der
Bruttobetrag, weil die Umsatzsteuer dann zu den Anschaffungskosten gehört):
| Anschaffungskosten | Behandlung |
|---|---|
| ≤ 800 € | GWG, im Anschaffungsjahr voll abgesetzt (§6 Abs. 2 EStG) |
| ≤ 1.000 € | Sammelposten, über 5 Jahre aufgelöst (§6 Abs. 2a EStG) |
| > 1.000 € | linear über die Nutzungsdauer (§7 Abs. 1 EStG) Nutzungsdauer ist Pflichtangabe |
Buchungen auf Anlagekonten ohne hinterlegtes Anlagegut (etwa aus einer früheren
Fassung) werden in der EÜR gesondert als prüfbedürftig ausgewiesen. Die Zahlen
werden dabei bewusst *nicht* stillschweigend korrigiert.
## Konfiguration
`.env.beispiel` nach `.env` kopieren und anpassen. Die Datei wird beim Start
automatisch gelesen; bereits gesetzte Umgebungsvariablen haben Vorrang.
**Relative Pfade darin beziehen sich immer auf das Projektverzeichnis**, nicht
auf das Verzeichnis, aus dem der Prozess gestartet wurde sonst entstünden je
nach Startort verschiedene Datenbanken.
Ohne `.env` läuft alles außer der ELSTER-Übermittlung; diese arbeitet dann im
Simulationsmodus.
## Wenn der Start fehlschlägt
**`EMFILE: too many open files, watch ...`** (Linux, betrifft `tsx watch` und
Vite gleichermaßen)
Nicht das Dateideskriptor-Limit, sondern die Zahl der erlaubten
inotify-*Instanzen* ist erschöpft typischerweise, weil VS Code und andere
Werkzeuge sie bereits aufgebraucht haben:
```bash
cat /proc/sys/fs/inotify/max_user_instances # meist 128
find /proc/*/fd -lname anon_inode:inotify | wc -l # tatsächlich belegt
```
Dauerhafte Abhilfe:
```bash
sudo sysctl -w fs.inotify.max_user_instances=1024
echo 'fs.inotify.max_user_instances=1024' | sudo tee /etc/sysctl.d/60-inotify.conf
```
Ohne Root-Rechte stattdessen:
```bash
npm run dev:polling # Server ohne Watcher, Vite mit Polling statt inotify
```
Der Server startet dann nicht automatisch neu nach Änderungen am Backend
manuell neu starten.
---
## Der Kern: die Zeitachse der Besteuerungsform
Der Wechsel von der Kleinunternehmerregelung zur Regelbesteuerung ist nicht als
Schalter umgesetzt, sondern als **Zeitachse**:
| Gültig ab | Besteuerungsform |
|---|---|
| 15.01.2024 | Kleinunternehmer (§19 UStG) |
| 01.01.2026 | Regelbesteuerung |
Jede Buchung wird nach dem Regime behandelt, das **zu ihrem Leistungsdatum**
galt. Daraus folgt:
- Altbuchungen bleiben unverändert, wenn ein Wechsel eingetragen wird.
- Ein **unterjähriger** Wechsel ist möglich nötig etwa beim Überschreiten der
100.000-EUR-Grenze, seit 2025 endet die Kleinunternehmerregelung dann sofort
mit dem überschreitenden Umsatz (§19 Abs. 1 Satz 3 UStG).
- Ein Jahr mit Wechsel weist Umsätze davor und danach in der EÜR getrennt aus.
Die Umsatzgrenzen werden laufend überwacht (25.000 EUR Vorjahr / 100.000 EUR
laufendes Jahr ab 2025; 22.000 / 50.000 bis 2024) und melden sich, bevor der
Wechsel fällig wird.
Die Regeln stehen an einer Stelle: [`server/src/domain/regime.ts`](server/src/domain/regime.ts).
## Was die Anwendung kann
**Erfassung**
- Buchungen mit Beleg-, Leistungs- und Zahlungsdatum; jedes Datum hat eine
eigene steuerliche Aufgabe (Regime, Soll-Versteuerung, Zuflussprinzip).
- Live-Vorschau: das Backend entscheidet über Steuersatz, Behandlung und
Vorsteuerabzug nicht das Formular.
- Reverse Charge (§13b), innergemeinschaftliche Erwerbe und Lieferungen,
steuerfreie und nicht steuerbare Umsätze.
- Privatanteile, Bewirtung (70 %), offene Posten.
- Anlagevermögen mit linearer AfA zeitanteilig, GWG-Sofortabschreibung und
Sammelposten; Kleinunternehmer schreiben vom Bruttobetrag ab.
- GoBD: keine Löschung, sondern Storno per Gegenbuchung; Festschreibung;
Änderungsprotokoll.
**Auswertungen**
- **EÜR** in der Gliederung der Anlage EÜR, mit Anlagenverzeichnis.
- **UStVA** mit den amtlichen Kennzahlen, Ist- oder Soll-Versteuerung,
monatlich oder vierteljährlich, mit Aufriss auf die Einzelbuchungen.
- **BWA** im Aufbau der BWA Form 01: Monat, kumuliert, Vorjahresvergleich,
Quoten wahlweise nach Zahlungs- oder Leistungszeitpunkt.
**Ausgabe**
- **ELSTER** über ERiC (siehe unten).
- **DATEV** Buchungsstapel im EXTF-Format (Version 700, Formatversion 13) plus
Kontenbeschriftungen das Paket für die Steuerkanzlei.
- **GoBD-Datenträgerüberlassung** (GDPdU, Format Z3) mit `index.xml` und CSV
das Paket für die Betriebsprüfung.
- EÜR und BWA zusätzlich als CSV.
## ELSTER-Übermittlung
**Ohne ERiC ist keine Übermittlung möglich auch nicht mit einem eigenen
XML-Versand.** ERiC ist die einzige zugelassene Schnittstelle zur
Finanzverwaltung. Die Bibliothek ist proprietär, darf nicht weitergegeben
werden und ist deshalb hier nicht enthalten.
So wird sie eingebunden:
1. Im [ELSTER-Entwicklerportal](https://www.elster.de/elsterweb/entwickler)
registrieren und ERiC für die eigene Plattform herunterladen.
2. `.env.beispiel` nach `.env` kopieren und ausfüllen:
`ERIC_LIB_PATH`, `ERIC_PLUGIN_PATH`, `ELSTER_ZERTIFIKAT`, `ELSTER_PIN`,
`ELSTER_HERSTELLER_ID`.
3. Erste Versuche mit `ELSTER_TESTMERKER=700000004` solche Übermittlungen
haben keine steuerliche Wirkung.
Ist ERiC nicht eingerichtet, läuft die Anwendung im **Simulationsmodus**: das
XML wird erzeugt, auf Wohlgeformtheit und Vollständigkeit geprüft und unter
`data/elster-ausgang/` abgelegt. Es wird nichts gesendet, und die Oberfläche
sagt das ausdrücklich.
Die Übermittlung ist verbindlich und nicht zurückzunehmen. Der Endpunkt
verlangt deshalb `"bestaetigt": true`, die Oberfläche eine Rückfrage.
Die Steuernummer wird automatisch vom Länderformat (`123/456/78901`) in das
13-stellige Bundesschema umgesetzt, das ELSTER erwartet für alle 16
Bundesländer, siehe [`server/src/elster/steuernummer.ts`](server/src/elster/steuernummer.ts).
## Was vor dem Produktiveinsatz zu prüfen ist
Die Rechenwege sind mit Tests abgesichert, aber einige Angaben ändern sich
jährlich und müssen gegen die amtlichen Vorgaben abgeglichen werden:
- **Zeilennummern der Anlage EÜR** sie ändern sich fast jedes Jahr. Der
fachliche Aufbau ist stabil, nur die Nummerierung nicht. Gepflegt werden sie
in `ZEILEN_NUMMERN` in
[`server/src/domain/euerZeilen.ts`](server/src/domain/euerZeilen.ts), getrennt
von der Berechnung. Stand: an der Fassung 2024/2025 orientiert.
- **ELSTER-Schemaversion** die Datenart-Version (`UStVA_<Jahr>`) und der
Aufbau des Nutzdatenblocks müssen zu der Version passen, die im installierten
ERiC steckt. Der erste Lauf sollte mit gesetztem Testmerker erfolgen.
- **DATEV-Formatversion** der Export folgt Version 700 / Formatversion 13.
Vor der ersten Übergabe eine Probedatei mit der Kanzlei einlesen.
- **Kontenrahmen** `server/src/kontenrahmen/skr04.ts` ist ein praxisnaher
Auszug aus dem SKR04, kein vollständiger Kontenrahmen. Konten lassen sich in
der Oberfläche ergänzen und umwidmen.
Die Anwendung ersetzt keine steuerliche Beratung. Für die Richtigkeit der
Erklärung bleibt die abgebende Person verantwortlich.
## Aufbau
```
server/src/
db.ts SQLite-Schema, Änderungsprotokoll
domain/
regime.ts Zeitachse der Besteuerungsform, USt-Ermittlung, §19-Grenzen
euer.ts Einnahmen-Überschuss-Rechnung
euerZeilen.ts Gliederung + Zeilennummern je Veranlagungsjahr
ustva.ts Voranmeldung, Kennzahlen, Ist/Soll
bwa.ts BWA Form 01
afa.ts lineare AfA, GWG, Sammelposten
geld.ts Cent-Arithmetik, kaufmännische Rundung
elster/
steuernummer.ts Länderformat -> Bundesschema (alle 16 Länder)
ustvaXml.ts ELSTER-Datenteil für die UStVA
eric.ts ERiC-Anbindung über koffi + Simulationsmodus
export/
datev.ts EXTF-Buchungsstapel und Kontenbeschriftungen
gdpdu.ts GoBD-Datenträgerüberlassung
zip.ts minimaler ZIP-Writer
routes/ HTTP-Schnittstelle
web/src/ Oberfläche (React, Vite)
```
Alle Beträge werden als ganzzahlige Cent geführt, Steuersätze als Basispunkte
(1900 = 19 %). Damit gibt es keine Fließkommafehler in der Steuerberechnung.