-- Phase 2: Buchungen, Splitzeilen und Buchungsadressen. -- -- Eine Umbuchung ist EINE Buchung mit gegenkonto_id, nicht zwei gespiegelte -- Datensätze. Das entspricht dem QIF-Export (dort steht jede Umbuchung genau -- einmal, die Gegenseite nur als L[Zielkonto]) und erspart die Pflege zweier -- Zeilen, die auseinanderlaufen können. Das Kontoblatt des Gegenkontos zeigt -- die Buchung mit umgekehrtem Vorzeichen. CREATE TABLE IF NOT EXISTS buchungen ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, benutzer_id INT UNSIGNED NOT NULL, konto_id INT UNSIGNED NOT NULL, datum DATE NOT NULL, valuta DATE NULL, betrag DECIMAL(15,2) NOT NULL DEFAULT 0.00, empfaenger VARCHAR(255) NOT NULL DEFAULT '', buchungstext TEXT NULL, nummer VARCHAR(32) NOT NULL DEFAULT '', abgeglichen TINYINT(1) NOT NULL DEFAULT 0, -- Entweder eine Kategorie, oder ein Gegenkonto (Umbuchung), oder Splitzeilen. kategorie_id INT UNSIGNED NULL, klasse_id INT UNSIGNED NULL, gegenkonto_id INT UNSIGNED NULL, gesplittet TINYINT(1) NOT NULL DEFAULT 0, quelle VARCHAR(16) NOT NULL DEFAULT 'manuell', -- Fingerabdruck für die Dublettenerkennung beim wiederholten Import. import_hash CHAR(40) NULL, erstellt_am DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, geaendert_am DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_buchung_konto (konto_id, datum), KEY idx_buchung_gegenkonto (gegenkonto_id, datum), KEY idx_buchung_datum (benutzer_id, datum), KEY idx_buchung_kategorie (kategorie_id), UNIQUE KEY uq_buchung_import (benutzer_id, import_hash), CONSTRAINT fk_buchung_benutzer FOREIGN KEY (benutzer_id) REFERENCES benutzer (id) ON DELETE CASCADE, CONSTRAINT fk_buchung_konto FOREIGN KEY (konto_id) REFERENCES konten (id) ON DELETE CASCADE, CONSTRAINT fk_buchung_gegenkonto FOREIGN KEY (gegenkonto_id) REFERENCES konten (id) ON DELETE SET NULL, CONSTRAINT fk_buchung_kategorie FOREIGN KEY (kategorie_id) REFERENCES kategorien (id) ON DELETE SET NULL, CONSTRAINT fk_buchung_klasse FOREIGN KEY (klasse_id) REFERENCES klassen (id) ON DELETE SET NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- Splitzeilen. Die Summe muss dem Buchungsbetrag entsprechen; geprüft im Model -- und im Test, nicht per Trigger. CREATE TABLE IF NOT EXISTS buchung_splits ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, buchung_id INT UNSIGNED NOT NULL, kategorie_id INT UNSIGNED NULL, klasse_id INT UNSIGNED NULL, gegenkonto_id INT UNSIGNED NULL, betrag DECIMAL(15,2) NOT NULL DEFAULT 0.00, notiz VARCHAR(255) NOT NULL DEFAULT '', sortierung INT NOT NULL DEFAULT 0, PRIMARY KEY (id), KEY idx_split_buchung (buchung_id), KEY idx_split_kategorie (kategorie_id), KEY idx_split_gegenkonto (gegenkonto_id), CONSTRAINT fk_split_buchung FOREIGN KEY (buchung_id) REFERENCES buchungen (id) ON DELETE CASCADE, CONSTRAINT fk_split_kategorie FOREIGN KEY (kategorie_id) REFERENCES kategorien (id) ON DELETE SET NULL, CONSTRAINT fk_split_klasse FOREIGN KEY (klasse_id) REFERENCES klassen (id) ON DELETE SET NULL, CONSTRAINT fk_split_gegenkonto FOREIGN KEY (gegenkonto_id) REFERENCES konten (id) ON DELETE SET NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- Die bis zu sechs A-Zeilen einer Buchung (Empfängeranschrift). CREATE TABLE IF NOT EXISTS buchungsadressen ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, buchung_id INT UNSIGNED NOT NULL, zeile TINYINT UNSIGNED NOT NULL DEFAULT 1, text VARCHAR(255) NOT NULL DEFAULT '', PRIMARY KEY (id), KEY idx_adresse_buchung (buchung_id), CONSTRAINT fk_adresse_buchung FOREIGN KEY (buchung_id) REFERENCES buchungen (id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- Die Σ-Spalte der Kontenübersicht: nicht jedes Konto zählt zur Summe der -- Kontostände (Rentenversicherungen und durchlaufende Auslagen etwa nicht). -- Der QIF-Export kennt das Kennzeichen nicht, es wird in der Oberfläche gepflegt. ALTER TABLE konten ADD COLUMN IF NOT EXISTS in_summe TINYINT(1) NOT NULL DEFAULT 1 AFTER aktiv; -- Online-Saldo aus dem Bankabruf (Phase 5); bis dahin bleibt er leer. ALTER TABLE konten ADD COLUMN IF NOT EXISTS saldo_online DECIMAL(15,2) NULL AFTER in_summe; ALTER TABLE konten ADD COLUMN IF NOT EXISTS saldo_online_am DATETIME NULL AFTER saldo_online; INSERT INTO einstellungen (schluessel, wert, bereich) VALUES ('import_stichtag', '2026-01-01', 'import'), ('import_zukunft_uebernehmen', '0', 'import') ON DUPLICATE KEY UPDATE schluessel = schluessel;