# 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) | Relativer Pfad unter `data/uploads/` (außerhalb des Web-Roots, Bestandsdaten mit altem Präfix `static/uploads/` werden von `Beleg::absolutePath()` weiter unterstützt) | | `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.