Steuerkram/docs/datenbankstruktur.md
2026-04-12 19:55:51 +02:00

284 lines
11 KiB
Markdown
Raw 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.

# Datenbankstruktur
MariaDB 11, Engine InnoDB, Zeichensatz `utf8mb4_unicode_ci`. Das Schema wird durch nummerierte Migrations-Dateien in `migrations/` verwaltet und beim Container-Start automatisch eingespielt.
---
## Übersicht
| Tabelle | Zweck |
|---|---|
| `fiscal_years` | Geschäftsjahre |
| `accounts` | Kontenrahmen SKR03 |
| `company_settings` | Firmendaten und Einstellungen (Singleton) |
| `customers` | Kundenstammdaten |
| `belege` | Belegdokumente |
| `bookings` | Buchungszeilen (EÜR-Journal) |
| `invoices` | Ausgangsrechnungen |
| `invoice_lines` | Rechnungspositionen |
| `users` | Benutzerkonten |
| `ustva_submissions` | UStVA-Einreichungen |
| `audit_log` | GoBD-Audit-Log |
| `schema_migrations` | Protokoll angewendeter Migrationen |
---
## Tabellen im Detail
### `fiscal_years` – Geschäftsjahre
| Spalte | Typ | Beschreibung |
|---|---|---|
| `id` | INT UNSIGNED PK | |
| `year` | SMALLINT UNSIGNED UNIQUE | Jahreszahl (z.B. 2026) |
| `start_date` | DATE | Beginn des Geschäftsjahres |
| `end_date` | DATE | Ende des Geschäftsjahres |
| `locked` | TINYINT(1) | `1` = Jahr abgeschlossen, keine neuen Buchungen möglich |
| `created_at` | DATETIME | |
---
### `accounts` – Kontenrahmen SKR03
| Spalte | Typ | Beschreibung |
|---|---|---|
| `id` | INT UNSIGNED PK | |
| `kontonummer` | VARCHAR(10) UNIQUE | DATEV-Kontonummer |
| `bezeichnung` | VARCHAR(255) | Kontobezeichnung |
| `kontenart` | ENUM | `Betriebseinnahme`, `Betriebsausgabe`, `Anlagevermögen`, `Zahlungskonto`, `Verbindlichkeit`, `Privat` |
| `beschreibung` | TEXT | Erläuterungstext |
| `steuercode` | VARCHAR(20) | `USt19`, `USt7`, `VSt19`, `VSt7` oder leer |
| `ustva_kz` | VARCHAR(10) | ELSTER-Kennzahl (z.B. `81`, `35`) |
| `euer_zeile` | VARCHAR(5) | Zeilennummer Anlage EÜR (z.B. `14`, `51`) |
| `is_system` | TINYINT(1) | `1` = SKR03-Standardkonto |
| `aktiv` | TINYINT(1) | `0` = deaktiviert |
| `created_at` / `updated_at` | DATETIME | |
---
### `company_settings` – Firmendaten (Singleton, id=1)
| Spalte | Typ | Beschreibung |
|---|---|---|
| `id` | INT UNSIGNED PK | Immer `1` |
| `firma` | VARCHAR(255) | Firmenname |
| `inhaber` | VARCHAR(255) | Inhabername |
| `strasse`, `plz`, `ort` | VARCHAR | Adresse |
| `telefon`, `email`, `website` | VARCHAR | Kontaktdaten |
| `ust_id` | VARCHAR(20) | Umsatzsteuer-ID (DE...) |
| `steuernummer` | VARCHAR(30) | Steuernummer beim Finanzamt |
| `finanzamt` | VARCHAR(255) | Zuständiges Finanzamt |
| `iban`, `bic`, `bank_name` | VARCHAR | Bankverbindung |
| `logo_path` | VARCHAR(500) | Pfad zum Logo-PNG |
| `ustva_steuernummer` | VARCHAR(20) | ELSTER-Format: `FFFBBBBBBUUUUP` |
| `ustva_bundesland` | VARCHAR(2) | Bundesland-Kürzel (z.B. `BY`) |
| `invoice_nr_prefix` | VARCHAR(10) | Präfix Rechnungsnummer (Standard: `AR`) |
| `invoice_nr_next` | INT UNSIGNED | Nächste Rechnungsnummer |
| `invoice_nr_year` | SMALLINT UNSIGNED | Jahresstand des Rechnungszählers |
| `beleg_nr_prefix` | VARCHAR(10) | Präfix Belegnummer (Standard: `BLG`) |
| `beleg_nr_next` | INT UNSIGNED | Nächste Belegnummer |
| `anlage_nr_prefix` | VARCHAR(10) | Präfix Anlagen-Nummer (Standard: `ANL`) |
| `anlage_nr_next` | INT UNSIGNED | Nächste Anlagen-Nummer |
| `payment_days_default` | INT UNSIGNED | Standard-Zahlungsziel in Tagen |
| `ki_vorschlag_aktiv` | TINYINT(1) | KI-Kontierungsvorschlag aktiviert |
| `nextcloud_url` | VARCHAR(500) | WebDAV-URL der Nextcloud-Instanz |
| `nextcloud_user` | VARCHAR(255) | Nextcloud-Benutzername |
| `nextcloud_pass` | VARCHAR(500) | Nextcloud-Passwort / App-Passwort |
| `nextcloud_folder` | VARCHAR(500) | Import-Quellordner (Standard: `/Belege/Import`) |
| `nextcloud_done` | VARCHAR(500) | Zielordner nach erfolgreichem Import |
| `updated_at` | DATETIME | |
---
### `customers` – Kundenstammdaten
| Spalte | Typ | Beschreibung |
|---|---|---|
| `id` | INT UNSIGNED PK | |
| `kundennummer` | VARCHAR(20) UNIQUE | |
| `firma` | VARCHAR(255) | Firmenname (optional) |
| `anrede`, `vorname`, `nachname` | VARCHAR | Ansprechpartner |
| `strasse`, `plz`, `ort`, `land` | VARCHAR | Adresse |
| `email`, `telefon` | VARCHAR | Kontakt |
| `ust_id` | VARCHAR(20) | USt-ID des Kunden (B2B) |
| `steuernummer` | VARCHAR(30) | |
| `payment_days` | INT UNSIGNED | Zahlungsziel in Tagen |
| `notes` | TEXT | Interne Notizen |
| `archived` | TINYINT(1) | `1` = archiviert |
| `created_at` / `updated_at` | DATETIME | |
---
### `belege` – Belegdokumente
| Spalte | Typ | Beschreibung |
|---|---|---|
| `id` | INT UNSIGNED PK | |
| `beleg_nr` | VARCHAR(30) UNIQUE | Automatisch vergeben (z.B. `BLG-2026-0001`) |
| `filename` | VARCHAR(500) | Originaldateiname |
| `storage_path` | VARCHAR(500) | Pfad unter `public/static/uploads/` |
| `mime_type` | VARCHAR(100) | MIME-Typ der Datei |
| `file_hash` | VARCHAR(64) | SHA-256 für GoBD-Integritätsprüfung |
| `beleg_datum` | DATE | Datum auf dem Dokument |
| `rechnungs_nr` | VARCHAR(100) | Rechnungsnummer des Lieferanten |
| `betrag_brutto` | DECIMAL(12,2) | Bruttobetrag laut Dokument |
| `belegtyp` | ENUM | `Eingangsrechnung`, `Ausgangsrechnung`, `Quittung`, `Kontoauszug`, `Sonstiges` |
| `notes` | TEXT | Interne Notizen |
| `bewirtungsgrund` | TEXT | Anlass der Bewirtung (§ 4 Abs. 5 Nr. 2 EStG) |
| `teilnehmerliste` | TEXT | Teilnehmer der Bewirtung |
| `eigenanteil_30` | TINYINT(1) | `1` = 30%-Eigenanteil beim Buchen markiert |
| `ki_vorschlag` | JSON | KI-Kontierungsvorschlag |
| `ki_vorschlag_at` | TIMESTAMP | Zeitpunkt der KI-Analyse |
| `beleg_text` | MEDIUMTEXT | Extrahierter OCR-Text |
| `locked` | TINYINT(1) | GoBD: mit Geschäftsjahr gesperrt |
| `fiscal_year_id` | INT UNSIGNED FK | → `fiscal_years.id` |
| `created_at` / `updated_at` | DATETIME | |
---
### `bookings` – Buchungszeilen (EÜR-Journal)
GoBD-gesichert: MariaDB-Trigger verhindern Änderungen und Löschungen gesperrter Buchungen.
| Spalte | Typ | Beschreibung |
|---|---|---|
| `id` | INT UNSIGNED PK | |
| `buchungs_nr` | VARCHAR(30) UNIQUE | Automatisch vergeben |
| `beleg_id` | INT UNSIGNED FK | → `belege.id` |
| `asset_id` | INT UNSIGNED FK | → `assets.id` (nur bei Anlagevermögen-Buchungen) |
| `buchungs_datum` | DATE | |
| `buchungstyp` | ENUM | `Einnahme`, `Ausgabe` |
| `kategorie_konto_id` | INT UNSIGNED FK | → `accounts.id` (Was?) |
| `zahlungskonto_id` | INT UNSIGNED FK | → `accounts.id` (Wie bezahlt?) |
| `betrag_netto` | DECIMAL(12,2) | |
| `ust_satz` | DECIMAL(5,2) | Steuersatz in % (0, 7, 19) |
| `steuerart` | ENUM | `normal`, `ig_lieferung`, `export`, `ig_erwerb_19`, `ig_erwerb_7` |
| `betrag_ust` | DECIMAL(12,2) | |
| `betrag_brutto` | DECIMAL(12,2) | |
| `buchungstext` | VARCHAR(500) | |
| `is_reversal` | TINYINT(1) | `1` = Stornobuchung |
| `reversed_by` | INT UNSIGNED | ID der zugehörigen Stornobuchung |
| `locked` | TINYINT(1) | GoBD: unveränderlich |
| `fiscal_year_id` | INT UNSIGNED FK | → `fiscal_years.id` |
| `created_at` | DATETIME | |
> **Hinweis:** Die Spalte `asset_id` existiert nur nach Migration 030 (Feature-Branch `Betriebsvermögen`).
---
### `assets` – Anlagegüter (Wirtschaftsgüter)
> Diese Tabelle existiert nur nach Migration 028 (Feature-Branch `Betriebsvermögen`).
| Spalte | Typ | Beschreibung |
|---|---|---|
| `id` | INT UNSIGNED PK | |
| `lfd_nr` | INT UNSIGNED | Laufende Nummer |
| `anlage_nr` | VARCHAR(30) | Anlagen-Nummer (z.B. `ANL-2026-0001`) |
| `bezeichnung` | VARCHAR(255) | Bezeichnung des Wirtschaftsguts |
| `account_id` | INT UNSIGNED FK | → `accounts.id` (Anlageart) |
| `anschaffungsdatum` | DATE | |
| `anschaffungskosten` | DECIMAL(12,2) | Netto-Anschaffungskosten |
| `zugaenge` | DECIMAL(12,2) | Nachträgliche Anschaffungskosten |
| `abgaenge` | DECIMAL(12,2) | Wertminderungen |
| `abschreibung_jahr` | DECIMAL(12,2) | AfA laufendes Wirtschaftsjahr |
| `nutzungsdauer` | INT UNSIGNED | Nutzungsdauer in Jahren |
| `notizen` | TEXT | |
| `fiscal_year_id` | INT UNSIGNED FK | → `fiscal_years.id` |
| `created_at` / `updated_at` | DATETIME | |
---
### `invoices` – Ausgangsrechnungen
| Spalte | Typ | Beschreibung |
|---|---|---|
| `id` | INT UNSIGNED PK | |
| `rechnungs_nr` | VARCHAR(30) UNIQUE | `NULL` solange Entwurf |
| `customer_id` | INT UNSIGNED FK | → `customers.id` |
| `betreff` | VARCHAR(500) | |
| `leistungszeitraum_von` / `_bis` | DATE | |
| `rechnungs_datum` | DATE | |
| `faellig_datum` | DATE | |
| `status` | ENUM | `Entwurf`, `Gesendet`, `Bezahlt`, `Storniert` |
| `betrag_netto` / `_ust` / `_brutto` | DECIMAL(12,2) | Summenspalten |
| `beleg_id` | INT UNSIGNED FK | → `belege.id` |
| `notes` | TEXT | |
| `fiscal_year_id` | INT UNSIGNED FK | → `fiscal_years.id` |
| `created_at` / `updated_at` | DATETIME | |
---
### `invoice_lines` – Rechnungspositionen
| Spalte | Typ | Beschreibung |
|---|---|---|
| `id` | INT UNSIGNED PK | |
| `invoice_id` | INT UNSIGNED FK | → `invoices.id` (CASCADE DELETE) |
| `position` | SMALLINT UNSIGNED | Positionsnummer |
| `bezeichnung` | VARCHAR(500) | |
| `menge` | DECIMAL(10,3) | |
| `einheit` | VARCHAR(20) | z.B. `Stk.`, `h` |
| `einzelpreis` | DECIMAL(12,2) | Netto-Einzelpreis |
| `ust_satz` | DECIMAL(5,2) | |
| `betrag_netto` / `_ust` / `_brutto` | DECIMAL(12,2) | |
---
### `users` – Benutzerkonten
| Spalte | Typ | Beschreibung |
|---|---|---|
| `id` | INT UNSIGNED PK | |
| `username` | VARCHAR(64) UNIQUE | |
| `password_hash` | VARCHAR(255) | bcrypt |
| `active` | TINYINT(1) | `0` = deaktiviert |
| `created_at` | DATETIME | |
---
### `ustva_submissions` – UStVA-Einreichungen
| Spalte | Typ | Beschreibung |
|---|---|---|
| `id` | INT UNSIGNED PK | |
| `year` | SMALLINT UNSIGNED | Meldejahr |
| `month` | TINYINT UNSIGNED | Meldemonat (1–12) |
| `xml_content` | MEDIUMTEXT | Generiertes ELSTER-XML |
| `field_values` | JSON | Kennzahlen als JSON |
| `submitted_at` | DATETIME | `NULL` = noch nicht eingereicht |
| `status` | ENUM | `Erstellt`, `Eingereicht` |
| `created_at` | DATETIME | |
UNIQUE KEY auf `(year, month)`.
---
### `audit_log` – GoBD-Audit-Log
| Spalte | Typ | Beschreibung |
|---|---|---|
| `id` | BIGINT UNSIGNED PK | |
| `timestamp` | DATETIME | |
| `table_name` | VARCHAR(50) | Betroffene Tabelle |
| `record_id` | INT UNSIGNED | Betroffener Datensatz |
| `action` | ENUM | `INSERT`, `UPDATE`, `DELETE`, `LOCK` |
| `old_values` | JSON | Werte vor der Änderung |
| `new_values` | JSON | Werte nach der Änderung |
| `ip_address` | VARCHAR(45) | |
| `user_agent` | VARCHAR(500) | |
---
## GoBD-Mechanismen
- **Trigger `bookings_before_update` / `bookings_before_delete`**: Jeder Versuch, eine gesperrte Buchung (`locked = 1`) zu ändern oder zu löschen, wird mit einem SQL-Fehler abgewiesen.
- **`file_hash` in `belege`**: SHA-256-Prüfsumme jedes Belegdokuments; erlaubt nachträgliche Integritätsprüfung.
- **`audit_log`**: Protokolliert alle Datenbankänderungen mit alten und neuen Werten.
- **Stornobuchungen** statt Löschungen: Korrekturen erzeugen eine Gegenbuchung, die ursprüngliche Buchung bleibt erhalten.
---
## Migrationen
Schemaänderungen kommen ausschließlich als nummerierte SQL-Dateien in `migrations/` und werden vom Script `scripts/migrate.php` beim Container-Start automatisch eingespielt. Die Tabelle `schema_migrations` protokolliert, welche Dateien bereits angewendet wurden.