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.