Der Plan ging davon aus, Umbuchungen stünden zweimal im Export und müssten zusammengeführt werden. Der Vollexport zeigt das Gegenteil: Eine Umbuchung steht einmal, in dem Konto, in dem sie erfasst wurde; die Gegenseite ist nur durch L[Zielkonto] benannt und muss beim Import erzeugt werden. Von 965 Umbuchungen hat keine ein zweites Vorkommen. Mit erzeugter Gegenseite stimmen acht gegen den FinanzManager geprüfte Kontosalden auf den Cent. Datenmodell: Migration 003 mit buchungen, buchung_splits und buchungsadressen. Eine Umbuchung ist ein Datensatz mit gegenkonto_id, kein gespiegeltes Paar - zwei Zeilen laufen früher oder später auseinander. Das Kontoblatt des Gegenkontos zeigt sie mit umgekehrtem Vorzeichen. Auch einzelne Splitzeilen dürfen ein Gegenkonto tragen. Konten führen zusätzlich in_summe, die Sigma-Spalte der Kontenübersicht, und saldo_online für Phase 5. Oberfläche: Kontoblatt mit laufendem Saldo, Filter auf Zeitraum, Kategorie, Klasse und Abgleich, Volltextsuche und Seitenblättern. Buchungsmaske für Einnahme, Ausgabe und Umbuchung in beide Richtungen, mit Splitzeilen und Restbetragsanzeige. Import: QifBuchungsImport arbeitet mit Stichtag. Alles davor wird je Konto zum Anfangssaldo aufsummiert, Gegenbuchungen eingerechnet; alles danach bis zum Importtag kommt einzeln in die Datenbank. Spätere Buchungen sind Vorausberechnungen des FinanzManagers und bleiben draußen. Wertpapiergeschäfte schlagen vorerst nur mit ihrem Geldfluss auf dem Verrechnungskonto zu Buche. Am Vollexport geprüft: 833 Buchungen ab 01.01.2026, 7946 in die Anfangssalden von 35 Konten verrechnet. Behoben: Die confirm()-Rückfragen vor dem Löschen waren wirkungslos. public/static/app.js brachte den Mechanismus mit, war aber nie im Layout eingebunden; die ersatzweise gesetzten onsubmit-Attribute verstoßen gegen die eigene Content-Security-Policy und wurden vom Browser verworfen - gelöscht wurde ohne Rückfrage. Ein E2E-Test besteht jetzt auf dem Dialog. Tests: 307 PHP-Prüfungen und 21 Playwright-Tests, alle grün. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
100 lines
4.9 KiB
SQL
100 lines
4.9 KiB
SQL
-- 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;
|