284 lines
11 KiB
Markdown
284 lines
11 KiB
Markdown
# 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) | Dateiname des Logos in `data/logo/` (z.B. `logo.png`); Auslieferung nur über `/settings/logo/file` |
|
||
| `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) | Relativer Pfad unter `data/belege/` (außerhalb des Web-Roots, Auslieferung nur über `/belege/{id}/file`). Migration 089 hat das historische Präfix `static/uploads/` entfernt; `Beleg::absolutePath()` toleriert es weiterhin |
|
||
| `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.
|