Steuerkram/migrations/001_initial_schema.sql
2026-03-25 00:19:33 +01:00

251 lines
12 KiB
SQL

-- Steuerkram - Initial Schema
-- GoBD-konform: Buchungen sind unveränderlich, vollständiges Audit-Log
SET NAMES utf8mb4;
SET time_zone = '+00:00';
-- ============================================================
-- Geschäftsjahre
-- ============================================================
CREATE TABLE IF NOT EXISTS fiscal_years (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
year SMALLINT UNSIGNED NOT NULL UNIQUE,
start_date DATE NOT NULL,
end_date DATE NOT NULL,
locked TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'GoBD: Jahr gesperrt nach Abschluss',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- ============================================================
-- Kontenrahmen (SKR03 und anpassbar)
-- ============================================================
CREATE TABLE IF NOT EXISTS accounts (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
kontonummer VARCHAR(10) NOT NULL UNIQUE,
bezeichnung VARCHAR(255) NOT NULL,
kontenart ENUM('Aktiv','Passiv','Aufwand','Ertrag','Neutral') NOT NULL,
beschreibung TEXT,
steuercode VARCHAR(20) NULL COMMENT 'z.B. USt19, VSt19, USt7, VSt7',
ustva_kz VARCHAR(10) NULL COMMENT 'ELSTER Kennzahl z.B. 81, 35',
is_system TINYINT(1) NOT NULL DEFAULT 0 COMMENT '1 = SKR03-Standard',
aktiv TINYINT(1) NOT NULL DEFAULT 1,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_kontonummer (kontonummer),
INDEX idx_kontenart (kontenart)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- ============================================================
-- Firmendaten / Einstellungen (Singleton, id=1)
-- ============================================================
CREATE TABLE IF NOT EXISTS company_settings (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
firma VARCHAR(255) NOT NULL DEFAULT '',
inhaber VARCHAR(255) NOT NULL DEFAULT '',
strasse VARCHAR(255) NOT NULL DEFAULT '',
plz VARCHAR(10) NOT NULL DEFAULT '',
ort VARCHAR(255) NOT NULL DEFAULT '',
telefon VARCHAR(50),
email VARCHAR(255),
website VARCHAR(255),
ust_id VARCHAR(20) COMMENT 'Umsatzsteuer-ID DE...',
steuernummer VARCHAR(30) COMMENT 'Steuernummer beim Finanzamt',
finanzamt VARCHAR(255),
iban VARCHAR(34),
bic VARCHAR(11),
bank_name VARCHAR(255),
logo_path VARCHAR(500) COMMENT 'Pfad zum Logo-PNG',
ustva_steuernummer VARCHAR(20) COMMENT 'ELSTER-Format: FFFBBBBBBUUUUP',
ustva_bundesland VARCHAR(2) COMMENT 'z.B. BY, NW, BE',
invoice_nr_prefix VARCHAR(10) NOT NULL DEFAULT 'AR',
invoice_nr_next INT UNSIGNED NOT NULL DEFAULT 1,
invoice_nr_year SMALLINT UNSIGNED NOT NULL DEFAULT 0,
beleg_nr_prefix VARCHAR(10) NOT NULL DEFAULT 'BLG',
beleg_nr_next INT UNSIGNED NOT NULL DEFAULT 1,
payment_days_default INT UNSIGNED NOT NULL DEFAULT 14,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT IGNORE INTO company_settings (id, firma, inhaber, strasse, plz, ort) VALUES (1, '', '', '', '', '');
-- ============================================================
-- Kunden
-- ============================================================
CREATE TABLE IF NOT EXISTS customers (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
kundennummer VARCHAR(20) NOT NULL UNIQUE,
firma VARCHAR(255),
anrede VARCHAR(20),
vorname VARCHAR(100),
nachname VARCHAR(100) NOT NULL,
strasse VARCHAR(255) NOT NULL,
plz VARCHAR(10) NOT NULL,
ort VARCHAR(255) NOT NULL,
land VARCHAR(2) NOT NULL DEFAULT 'DE',
email VARCHAR(255),
telefon VARCHAR(50),
ust_id VARCHAR(20) COMMENT 'Umsatzsteuer-ID des Kunden (B2B)',
steuernummer VARCHAR(30),
payment_days INT UNSIGNED NOT NULL DEFAULT 14,
notes TEXT,
archived TINYINT(1) NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_kundennummer (kundennummer),
INDEX idx_archived (archived)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- ============================================================
-- Belege (Dokumente / Quittungen)
-- ============================================================
CREATE TABLE IF NOT EXISTS belege (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
beleg_nr VARCHAR(30) NOT NULL UNIQUE,
filename VARCHAR(500) NOT NULL COMMENT 'Originaldateiname',
storage_path VARCHAR(500) NOT NULL COMMENT 'Pfad unter public/static/uploads/',
mime_type VARCHAR(100) NOT NULL,
file_hash VARCHAR(64) NOT NULL COMMENT 'SHA-256 für GoBD-Integrität',
beleg_datum DATE NULL COMMENT 'Datum auf dem Dokument',
eingangs_datum DATE NOT NULL COMMENT 'Eingangsdatum',
rechnungs_nr VARCHAR(100) COMMENT 'Rechnungsnummer des Lieferanten',
betrag_brutto DECIMAL(12,2) COMMENT 'Bruttobetrag lt. Dokument',
belegtyp ENUM('Eingangsrechnung','Ausgangsrechnung','Quittung','Kontoauszug','Sonstiges') NOT NULL DEFAULT 'Eingangsrechnung',
notes TEXT,
booked TINYINT(1) NOT NULL DEFAULT 0 COMMENT '1 = alle Buchungszeilen erfasst',
locked TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'GoBD: gesperrt mit Geschäftsjahr',
fiscal_year_id INT UNSIGNED NULL REFERENCES fiscal_years(id),
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_beleg_datum (beleg_datum),
INDEX idx_fiscal_year (fiscal_year_id),
INDEX idx_booked (booked)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- ============================================================
-- Buchungen (Journaleinträge, doppelte Buchführung)
-- GoBD: KEINE UPDATE/DELETE auf gesperrte Buchungen
-- ============================================================
CREATE TABLE IF NOT EXISTS bookings (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
buchungs_nr VARCHAR(30) NOT NULL UNIQUE,
beleg_id INT UNSIGNED NOT NULL,
buchungs_datum DATE NOT NULL,
soll_konto_id INT UNSIGNED NOT NULL,
haben_konto_id INT UNSIGNED NOT NULL,
betrag_netto DECIMAL(12,2) NOT NULL,
ust_satz DECIMAL(5,2) NOT NULL DEFAULT 0.00 COMMENT 'Steuersatz in % (0, 7, 19)',
betrag_ust DECIMAL(12,2) NOT NULL DEFAULT 0.00,
betrag_brutto DECIMAL(12,2) NOT NULL,
buchungstext VARCHAR(500) NOT NULL,
is_reversal TINYINT(1) NOT NULL DEFAULT 0 COMMENT '1 = Stornobuchung',
reversed_by INT UNSIGNED NULL COMMENT 'ID der Stornobuchung',
locked TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'GoBD: unveränderlich',
fiscal_year_id INT UNSIGNED NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_booking_beleg FOREIGN KEY (beleg_id) REFERENCES belege(id),
CONSTRAINT fk_booking_soll FOREIGN KEY (soll_konto_id) REFERENCES accounts(id),
CONSTRAINT fk_booking_haben FOREIGN KEY (haben_konto_id) REFERENCES accounts(id),
CONSTRAINT fk_booking_year FOREIGN KEY (fiscal_year_id) REFERENCES fiscal_years(id),
INDEX idx_buchungs_datum (buchungs_datum),
INDEX idx_fiscal_year (fiscal_year_id),
INDEX idx_soll_konto (soll_konto_id),
INDEX idx_haben_konto (haben_konto_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- GoBD-Trigger: Gesperrte Buchungen dürfen nicht geändert werden
DELIMITER $$
CREATE TRIGGER bookings_before_update
BEFORE UPDATE ON bookings
FOR EACH ROW
BEGIN
IF OLD.locked = 1 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'GoBD-Verletzung: Gesperrte Buchung kann nicht geändert werden.';
END IF;
END$$
CREATE TRIGGER bookings_before_delete
BEFORE DELETE ON bookings
FOR EACH ROW
BEGIN
IF OLD.locked = 1 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'GoBD-Verletzung: Gesperrte Buchung kann nicht gelöscht werden.';
END IF;
END$$
DELIMITER ;
-- ============================================================
-- Ausgangsrechnungen
-- ============================================================
CREATE TABLE IF NOT EXISTS invoices (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
rechnungs_nr VARCHAR(30) NULL UNIQUE COMMENT 'NULL solange Entwurf',
customer_id INT UNSIGNED NOT NULL,
betreff VARCHAR(500) NOT NULL,
leistungszeitraum_von DATE NULL,
leistungszeitraum_bis DATE NULL,
rechnungs_datum DATE NOT NULL,
faellig_datum DATE NOT NULL,
status ENUM('Entwurf','Gesendet','Bezahlt','Storniert') NOT NULL DEFAULT 'Entwurf',
betrag_netto DECIMAL(12,2) NOT NULL DEFAULT 0.00,
betrag_ust DECIMAL(12,2) NOT NULL DEFAULT 0.00,
betrag_brutto DECIMAL(12,2) NOT NULL DEFAULT 0.00,
beleg_id INT UNSIGNED NULL REFERENCES belege(id),
notes TEXT,
fiscal_year_id INT UNSIGNED NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT fk_invoice_customer FOREIGN KEY (customer_id) REFERENCES customers(id),
INDEX idx_status (status),
INDEX idx_rechnungs_datum (rechnungs_datum)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- ============================================================
-- Rechnungspositionen
-- ============================================================
CREATE TABLE IF NOT EXISTS invoice_lines (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
invoice_id INT UNSIGNED NOT NULL,
position SMALLINT UNSIGNED NOT NULL DEFAULT 1,
bezeichnung VARCHAR(500) NOT NULL,
menge DECIMAL(10,3) NOT NULL DEFAULT 1.000,
einheit VARCHAR(20) NOT NULL DEFAULT 'Stk.',
einzelpreis DECIMAL(12,2) NOT NULL,
ust_satz DECIMAL(5,2) NOT NULL DEFAULT 19.00,
betrag_netto DECIMAL(12,2) NOT NULL,
betrag_ust DECIMAL(12,2) NOT NULL,
betrag_brutto DECIMAL(12,2) NOT NULL,
CONSTRAINT fk_line_invoice FOREIGN KEY (invoice_id) REFERENCES invoices(id) ON DELETE CASCADE,
INDEX idx_invoice_id (invoice_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- ============================================================
-- UStVA Einreichungen
-- ============================================================
CREATE TABLE IF NOT EXISTS ustva_submissions (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
year SMALLINT UNSIGNED NOT NULL,
month TINYINT UNSIGNED NOT NULL COMMENT '1-12',
xml_content MEDIUMTEXT NOT NULL COMMENT 'Generiertes ELSTER-XML',
field_values JSON COMMENT 'Kennzahlen als JSON',
submitted_at DATETIME NULL COMMENT 'NULL = noch nicht eingereicht',
status ENUM('Erstellt','Eingereicht') NOT NULL DEFAULT 'Erstellt',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uq_year_month (year, month)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- ============================================================
-- Audit-Log (GoBD-Pflicht)
-- ============================================================
CREATE TABLE IF NOT EXISTS audit_log (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
timestamp DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
table_name VARCHAR(50) NOT NULL,
record_id INT UNSIGNED NOT NULL,
action ENUM('INSERT','UPDATE','DELETE','LOCK') NOT NULL,
old_values JSON,
new_values JSON,
ip_address VARCHAR(45),
user_agent VARCHAR(500),
INDEX idx_table_record (table_name, record_id),
INDEX idx_timestamp (timestamp)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;